Add HTTP /v1 MVC, TCP send path, groups with server-side member-avatar collage, object-storage upload, and friends/favorites around the architecture-v3 layout. Co-authored-by: Cursor <cursoragent@cursor.com>
58 lines
2.5 KiB
Markdown
58 lines
2.5 KiB
Markdown
# i18n (zh-CN / en / zh-TW)
|
|
|
|
User-visible **error `msg`** and **notification copy** are never hardcoded in handlers. They come from `internal/pkg/i18n` using the request language.
|
|
|
|
## Language on every request
|
|
|
|
Canonical tags: `zh-CN` (default), `en`, `zh-TW`. Parse with `i18n.Parse`; store on `reqctx.RequestContext.Language`. Read with `reqctx.Lang(ctx)` / `reqctx.Language(ctx)`.
|
|
|
|
| Transport | Client sends | Server stores |
|
|
|-----------|--------------|----------------|
|
|
| HTTP | `X-Language` if set, else `Accept-Language` | Trace middleware, every request |
|
|
| TCP | AUTH JSON field `language` | `session.Conn.SetLanguage` for the connection; copied into each command's `RequestContext` |
|
|
|
|
Missing or unknown tags → `zh-CN`. `zh` / `zh-Hans` → `zh-CN`. `zh-TW` / `zh-HK` / `zh-Hant` → `zh-TW`. `en*` → `en`.
|
|
|
|
Do not put language in JWT. Rate limit still uses `reqctx.RealIP`, not language.
|
|
|
|
## Errors
|
|
|
|
Sentinel `AppError` keeps an English `Msg` for logs. Envelope `msg` is localized at the transport boundary:
|
|
|
|
- HTTP: `reply.FromContext(ctx, err)`
|
|
- TCP: `protocol.EncodeErrLang(i18n.Parse(conn.Language()), err)`
|
|
|
|
`WithMsg` is only for **already-localized** text (validator). Do not `WithMsg("unauthorized")` in a handler.
|
|
|
|
Add a new business code: register `err.{code}` in `internal/pkg/i18n/catalog_err.go` for all three languages.
|
|
|
|
## Validation
|
|
|
|
`label` and `errmsg` are **catalog keys**, not Chinese sentences:
|
|
|
|
```go
|
|
Email string `json:"email" validate:"required,email" label:"field.email" errmsg:"required:validate.login.email_required"`
|
|
```
|
|
|
|
`validate.StructLang(reqctx.Lang(ctx), in)`. Default templates are `validate.required` etc. with `{label}` / `{param}`.
|
|
|
|
## Notification types
|
|
|
|
Push and in-app system notices use type keys, then `i18n.Notify(lang, typ, args)` for title/body. Recipient language is the **target device** session language (TCP) or the user's stored lang — never the sender's.
|
|
|
|
| Type | Key |
|
|
|------|-----|
|
|
| Same-device kick | `notify.kick.replaced` |
|
|
| Friend request | `notify.friend.request` |
|
|
| Group invite | `notify.group.invite` |
|
|
| Removed from group | `notify.group.removed` |
|
|
| Message recall | `notify.message.recall` |
|
|
|
|
`push.Notification.Type` holds the key. Fill `Title`/`Body` with `i18n.Notify` before `Notifier.Notify`. Add new types in `catalog_notify.go` (`.title` / `.body`, `{name}` placeholders).
|
|
|
|
## Do not
|
|
|
|
- Hardcode `msg` or kick/push strings in `handler/*` or EventLoop.
|
|
- Localize with the sender language when notifying another user.
|
|
- Introduce a fourth locale without adding it to `i18n.Lang` and every catalog entry.
|