mirror of
https://github.com/photoprism/photoprism.git
synced 2026-07-17 16:49:04 +00:00
UX: Name message fields messageId/messageParams in i18n payloads #5682
Rename the i18n notification/error envelope fields from id/params to messageId/messageParams across the WS notify Data and the i18n.Response body, so they are unambiguous next to session, log, and correlation ids. Updates the frontend consumers (notify.vue, api.js), tests, swagger, and the event/locales READMEs.
This commit is contained in:
parent
2fd9e93247
commit
fd8448d6de
9 changed files with 39 additions and 38 deletions
|
|
@ -15,9 +15,9 @@ It's source code can be obtained on [GitHub](https://github.com/vslavik/poedit).
|
|||
|
||||
Only asynchronous notifications and certain API responses need translation to provide a
|
||||
consistent user experience.
|
||||
Asynchronous notifications are additionally rendered by the web frontend in each user's current
|
||||
UI language (using the message identifier); the `default.po` translation here serves as a fallback
|
||||
and for non-browser consumers such as the CLI.
|
||||
Asynchronous notifications and user-facing API error responses are additionally rendered by the web
|
||||
frontend in each user's current UI language (using the message identifier); the `default.po` translation
|
||||
here serves as a fallback and for non-browser consumers such as the CLI.
|
||||
Technical log messages should be in English to avoid ambiguities and (even slightly) wrong translations.
|
||||
|
||||
`default.po` files in subdirectories contain localized messages for each
|
||||
|
|
|
|||
|
|
@ -111,9 +111,9 @@ $api.interceptors.response.use(
|
|||
code = data.code;
|
||||
}
|
||||
|
||||
if (data.id) {
|
||||
if (data.messageId) {
|
||||
// Render the backend message in the current UI locale from its source id and params.
|
||||
errorMessage = Tp(data.id, data.params);
|
||||
errorMessage = Tp(data.messageId, data.messageParams);
|
||||
} else if (data.message) {
|
||||
errorMessage = data.message;
|
||||
} else if (data.error) {
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ export default {
|
|||
|
||||
// Render in the current UI language when the backend sent a message id (the English source
|
||||
// string); otherwise fall back to the pre-rendered message string as-is.
|
||||
let m = data.id ? Tp(data.id, data.params) : data.message;
|
||||
let m = data.messageId ? Tp(data.messageId, data.messageParams) : data.message;
|
||||
|
||||
// Skip empty messages.
|
||||
if (!m || !m.length) {
|
||||
|
|
|
|||
|
|
@ -3435,13 +3435,13 @@
|
|||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"params": {
|
||||
"messageId": {
|
||||
"type": "string"
|
||||
},
|
||||
"messageParams": {
|
||||
"items": {},
|
||||
"type": "array"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,12 @@ event.Warn("low disk space")
|
|||
```
|
||||
|
||||
The `*Msg` helpers (`SuccessMsg`/`ErrorMsg`/`InfoMsg`/`WarnMsg`) publish a structured payload
|
||||
`Data{"message", "id", "params"}`: `message` is the server-rendered string in the instance locale,
|
||||
`id` is the untranslated source string (`i18n.Source(id)`), and `params` are the substitution values.
|
||||
The Web UI renders the notification from `id` + `params` in each user's current UI language; `message`
|
||||
is a fallback. The plain `Success`/`Error`/`Info`/`Warn` string forms publish only `message` and are
|
||||
**not** localized — reserve them for already-translated or non-user-facing text.
|
||||
`Data{"message", "messageId", "messageParams"}`: `message` is the server-rendered string in the
|
||||
instance locale, `messageId` is the untranslated source string (`i18n.Source(id)`), and
|
||||
`messageParams` are the substitution values. The Web UI renders the notification from `messageId` +
|
||||
`messageParams` in each user's current UI language; `message` is a fallback. The plain
|
||||
`Success`/`Error`/`Info`/`Warn` string forms publish only `message` and are **not** localized —
|
||||
reserve them for already-translated or non-user-facing text.
|
||||
|
||||
Subscribe to topics:
|
||||
```go
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ func Warn(msg string) {
|
|||
func publishMsg(level logrus.Level, topic string, id i18n.Message, params ...any) {
|
||||
msg := i18n.Msg(id, params...)
|
||||
Log.Log(level, strings.ToLower(msg))
|
||||
Publish(topic, Data{"message": msg, "id": i18n.Source(id), "params": params})
|
||||
Publish(topic, Data{"message": msg, "messageId": i18n.Source(id), "messageParams": params})
|
||||
}
|
||||
|
||||
// ErrorMsg publishes a localized error notification.
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ func TestSuccessMsg(t *testing.T) {
|
|||
msg := <-s.Receiver
|
||||
assert.Equal(t, "notify.success", msg.Name)
|
||||
assert.Equal(t, "Album Holiday deleted", msg.Fields["message"])
|
||||
assert.Equal(t, "Album %s deleted", msg.Fields["id"])
|
||||
assert.Equal(t, []any{"Holiday"}, msg.Fields["params"])
|
||||
assert.Equal(t, "Album %s deleted", msg.Fields["messageId"])
|
||||
assert.Equal(t, []any{"Holiday"}, msg.Fields["messageParams"])
|
||||
Unsubscribe(s)
|
||||
})
|
||||
t.Run("WithoutParams", func(t *testing.T) {
|
||||
|
|
@ -25,7 +25,7 @@ func TestSuccessMsg(t *testing.T) {
|
|||
msg := <-s.Receiver
|
||||
assert.Equal(t, "notify.success", msg.Name)
|
||||
assert.Equal(t, "Album created", msg.Fields["message"])
|
||||
assert.Equal(t, "Album created", msg.Fields["id"])
|
||||
assert.Equal(t, "Album created", msg.Fields["messageId"])
|
||||
Unsubscribe(s)
|
||||
})
|
||||
}
|
||||
|
|
@ -36,8 +36,8 @@ func TestErrorMsg(t *testing.T) {
|
|||
msg := <-s.Receiver
|
||||
assert.Equal(t, "notify.error", msg.Name)
|
||||
assert.Equal(t, "A cat already exists", msg.Fields["message"])
|
||||
assert.Equal(t, "%s already exists", msg.Fields["id"])
|
||||
assert.Equal(t, []any{"A cat"}, msg.Fields["params"])
|
||||
assert.Equal(t, "%s already exists", msg.Fields["messageId"])
|
||||
assert.Equal(t, []any{"A cat"}, msg.Fields["messageParams"])
|
||||
Unsubscribe(s)
|
||||
}
|
||||
|
||||
|
|
@ -47,8 +47,8 @@ func TestInfoMsg(t *testing.T) {
|
|||
msg := <-s.Receiver
|
||||
assert.Equal(t, "notify.info", msg.Name)
|
||||
assert.Equal(t, "Indexing files in /photos", msg.Fields["message"])
|
||||
assert.Equal(t, "Indexing files in %s", msg.Fields["id"])
|
||||
assert.Equal(t, []any{"/photos"}, msg.Fields["params"])
|
||||
assert.Equal(t, "Indexing files in %s", msg.Fields["messageId"])
|
||||
assert.Equal(t, []any{"/photos"}, msg.Fields["messageParams"])
|
||||
Unsubscribe(s)
|
||||
}
|
||||
|
||||
|
|
@ -58,6 +58,6 @@ func TestWarnMsg(t *testing.T) {
|
|||
msg := <-s.Receiver
|
||||
assert.Equal(t, "notify.warning", msg.Name)
|
||||
assert.Equal(t, "Busy, please try again later", msg.Fields["message"])
|
||||
assert.Equal(t, "Busy, please try again later", msg.Fields["id"])
|
||||
assert.Equal(t, "Busy, please try again later", msg.Fields["messageId"])
|
||||
Unsubscribe(s)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,15 +5,15 @@ import "strings"
|
|||
// Response represents an i18n-aware response payload.
|
||||
//
|
||||
// Err/Msg carry the server-rendered string in the instance locale (a fallback for non-browser
|
||||
// consumers); ID and Params carry the untranslated source id and its parameters so the Web UI can
|
||||
// render the message in each user's current UI locale.
|
||||
// consumers); MessageID and MessageParams carry the untranslated source string and its parameters
|
||||
// so the Web UI can render the message in each user's current UI locale.
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Err string `json:"error,omitempty"`
|
||||
Msg string `json:"message,omitempty"`
|
||||
Details string `json:"details,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Params []any `json:"params,omitempty"`
|
||||
Code int `json:"code"`
|
||||
Err string `json:"error,omitempty"`
|
||||
Msg string `json:"message,omitempty"`
|
||||
MessageID string `json:"messageId,omitempty"`
|
||||
MessageParams []any `json:"messageParams,omitempty"`
|
||||
Details string `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
func (r Response) String() string {
|
||||
|
|
@ -42,7 +42,7 @@ func (r Response) Success() bool {
|
|||
// It carries the untranslated source string (ID) and Params so the frontend can render the message
|
||||
// in the current UI locale, in addition to the server-rendered Err/Msg fallback.
|
||||
func NewResponse(code int, id Message, params ...any) Response {
|
||||
r := Response{Code: code, ID: Source(id), Params: params}
|
||||
r := Response{Code: code, MessageID: Source(id), MessageParams: params}
|
||||
if code < 400 {
|
||||
r.Msg = Msg(id, params...)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -14,27 +14,27 @@ func TestNewResponse(t *testing.T) {
|
|||
assert.Equal(t, http.StatusConflict, resp.Code)
|
||||
assert.Equal(t, "A cat already exists", resp.Err)
|
||||
assert.Equal(t, "", resp.Msg)
|
||||
assert.Equal(t, "%s already exists", resp.ID)
|
||||
assert.Equal(t, []any{"A cat"}, resp.Params)
|
||||
assert.Equal(t, "%s already exists", resp.MessageID)
|
||||
assert.Equal(t, []any{"A cat"}, resp.MessageParams)
|
||||
})
|
||||
t.Run("UnexpectedError", func(t *testing.T) {
|
||||
resp := NewResponse(http.StatusInternalServerError, ErrUnexpected, "A cat")
|
||||
assert.Equal(t, http.StatusInternalServerError, resp.Code)
|
||||
assert.Equal(t, "Something went wrong, try again", resp.Err)
|
||||
assert.Equal(t, "", resp.Msg)
|
||||
assert.Equal(t, "Something went wrong, try again", resp.ID)
|
||||
assert.Equal(t, "Something went wrong, try again", resp.MessageID)
|
||||
})
|
||||
t.Run("ChangesSaved", func(t *testing.T) {
|
||||
resp := NewResponse(http.StatusOK, MsgChangesSaved)
|
||||
assert.Equal(t, http.StatusOK, resp.Code)
|
||||
assert.Equal(t, "", resp.Err)
|
||||
assert.Equal(t, "Changes successfully saved", resp.Msg)
|
||||
assert.Equal(t, "Changes successfully saved", resp.ID)
|
||||
assert.Equal(t, "Changes successfully saved", resp.MessageID)
|
||||
|
||||
if s, err := json.Marshal(resp); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
assert.Equal(t, `{"code":200,"message":"Changes successfully saved","id":"Changes successfully saved"}`, string(s))
|
||||
assert.Equal(t, `{"code":200,"message":"Changes successfully saved","messageId":"Changes successfully saved"}`, string(s))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue