spark-server/.cursor/skills/spark-im-v3/i18n.md
zhr ae9a71034d feat: implement Spark IM V3 modular monolith
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>
2026-09-17 19:52:11 +08:00

2.5 KiB

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:

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.