docs: add i18n / error-localization conventions to CLAUDE.md#231
Conversation
Documents the i18n error-envelope rules so future changes follow the established playbook: - ResponseErrorL (fixed-400, D14) vs ResponseErrorLWithStatus (real status, new endpoints only) facades - pkg/errcode registration, 5xx<=>Internal invariant, anti-enumeration, Params/Details split - per-module api_i18n.go helpers, the make i18n-extract-check / i18n-lint gate, zh-CN translations, and the NoLegacyResponseError source guard - emailtmpl localized templates + i18n.OutboundLanguage Adds pkg/httperr and pkg/i18n to Key Packages and an i18n line to Coding Conventions.
Jerry-Xin
left a comment
There was a problem hiding this comment.
β APPROVE
Comprehensive i18n / error-localization documentation for CLAUDE.md.
ResponseErrorLvsResponseErrorLWithStatusdistinction (D14 compat vs real status) is clearly explained with the maintainer sign-off caveat- Error code registration recipe is complete: ID naming, HTTPStatus, DefaultMessage, SafeDetailKeys, Internal flag
- 5xx βΊ
Internal=trueinvariant and anti-enumeration guidance are important security conventions, well documented Params vs Details(D15) distinction is precise- CI enforcement chain (
i18n-extractβi18n-extract-checkβi18n-lint) gives clear action steps - Guard test and baseline exemption pattern documented
- Email template localization section ties into the broader i18n story
- Architecture checklist updated with the i18n cross-reference
No issues found. Clean documentation.
lml2468
left a comment
There was a problem hiding this comment.
Summary
Docs-only: adds i18n / error-localization conventions to CLAUDE.md. Comprehensive, accurate, well-structured. No blocking issues.
Covers:
- Two-facade distinction (ResponseErrorL vs ResponseErrorLWithStatus) with correct usage guidance
- Error code registration pattern with all key fields (ID, HTTPStatus, DefaultMessage, SafeDetailKeys, Internal)
- 5xx βΊ Internal=true invariant
- Anti-enumeration principle (single generic 401 for auth failures)
- Params vs Details (D15) distinction
- Per-module helper pattern (api_i18n.go)
- CI enforcement commands (i18n-extract, i18n-extract-check, i18n-lint)
- Guard test convention
- Email template localization
- Checklist addition reinforcing the rules
APPROVED.
yujiawei
left a comment
There was a problem hiding this comment.
Code Review β PR #231 (octo-server)
Scope: Documentation-only change to CLAUDE.md (+44/-4, single file) adding i18n / error-localization conventions. No production code is touched.
Verdict: APPROVED. I verified every concrete claim in the new documentation against the codebase at the PR head SHA. All structural claims (package paths, function signatures, struct fields, make targets, guard tests, email templates) are accurate. A few minor imprecisions are noted below as non-blocking nits.
What was verified β
- Package map β
pkg/errcode/oidc.go,pkg/httperr/(ResponseErrorL/ResponseErrorLWithStatusinrespond.go:22,41), andpkg/i18n/(codes registry, localizer, renderer, language negotiation,locales/) all exist as described. - Two-facade semantics β
ResponseErrorLpins the wire status to 400 with the real status inerror.http_status(respond.go:60-69, tested inrespond_test.go:15-56);ResponseErrorLWithStatusemits the code's realHTTPStatus(respond.go:61-62, testedrespond_test.go:93-120). The "currently justmodules/oidcbind" claim holds β grep confirms the only caller ismodules/oidc/api_i18n.go:40(respondBindError). - Code registration shape β
codes.CodehasID,HTTPStatus,DefaultMessage,SafeDetailKeys,Internal(registry.go:60-67); ID convention enforced by regex^err\.(shared|server)\....$(registry.go:43);err.shared.*codes (auth/rate/param/internal/not_found) exist incodes/shared.go. - Invariants β
5xx βΊ Internal=trueis enforced byshared_test.go:59-75and applied by the renderer (renderer.go:69-70,85-86hide message + details).SafeDetailKeyswhitelisting is implemented indetails.go:21-44. Params-vs-details (D15) split is enforced via distinct types (params.go,details.go). - Tooling β
make i18n-extract/i18n-extract-check/i18n-linttargets exist in theMakefileand back the described behavior (pkg/i18n/cmd/octo-i18n-extract,tools/lint-direct-error-response,tools/lint-unregistered-code). Per-moduleapi_i18n.gohelpers +mustLookupSharedCodeconfirmed;Test<Module>NoLegacyResponseErrorguards andtools/lint-direct-error-response/baseline.txtexemptions confirmed. - Emails β localized templates under
modules/base/common/emailtmpl/templates/{en-US,zh-CN}/withsubject/html/textviago:embed; send functions take alangarg resolved viai18n.OutboundLanguage(ctx)(service_email.go:28-30, used inmodules/user/api_emaillogin.go).
Non-blocking nits (P2)
Codestruct has an undocumented field. The actual struct (pkg/i18n/codes/registry.go:60-67) also hasDefaultMessages map[string]string(extreme-fallback translations), not shown in the doc's example. It's optional and usually omitted, so the example is fine as-is β optionally add a one-line note that the field exists.- Anti-enumeration wording is slightly broad. The doc says auth/verify failures "map to ONE generic code (e.g. a single 401)". In practice
codes/shared.gointentionally has several auth codes for different scenarios (auth.required,token_missing,token_invalid,token_expired,forbidden). The single-code rule correctly applies to credential-verification endpoints (e.g.ErrUserInvalidCredentials,ErrOIDCBindInvalidCredentials), where multiple failure reasons collapse to one code. Consider clarifying "for a single login/verify endpoint, never per-reason" to avoid a reader concluding only one auth code may exist globally.
Neither nit affects correctness, build, or security, and neither blocks merge. Good, accurate documentation that matches the implemented i18n system.
Part of #170 (backend i18n).
Summary
Docs-only. Captures the i18n / error-localization conventions in
CLAUDE.mdso future changes follow the playbook established by the migrated modules (#176/#188/#198/#203/#213/#214) and the recent OIDC (#223) and email (#224) work β instead of re-deriving it each time.What's documented
pkg/httperr):ResponseErrorL(fixed-400 / D14) vsResponseErrorLWithStatus(real status, new endpoints only), with a comparison table.pkg/errcode/<module>.go): registration shape, the 5xx βΊInternal=trueinvariant, anti-enumeration (one generic code), and the Params/Details split.modules/<module>/api_i18n.go) andmustLookupSharedCode.make i18n-extract/i18n-extract-check/i18n-lint, plus zh-CN translations inactive.zh-CN.toml.Test<Module>NoLegacyResponseErrorand thebaseline.txtexemption for protocol endpoints.emailtmpllocalized templates +i18n.OutboundLanguage(ctx).Also adds
pkg/httperr/pkg/i18nto the Key Packages table and an i18n line to Coding Conventions.Test plan