feat(control-plane): add Chinese locale variants#1784
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds localized (i18n) API error payloads across the control-plane API routes and introduces additional Chinese locales, while bumping the referenced OpenAPI version.
Changes:
- Introduce
localizeApiErrorPayload()/ locale resolution for API error responses and wire it into many API routes + middleware. - Add new locales (
zh-CN,zh-TW,yue-Hant) and their message catalogs; extend existing catalogs withapi.errors.*keys. - Update OpenAPI reference version and documentation link for the consolidation endpoint.
Reviewed changes
Copilot reviewed 65 out of 65 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| skills/hindsight-docs/references/openapi.json | Bumps referenced API version to 0.7.0. |
| skills/hindsight-docs/references/developer/configuration.md | Adds link to consolidation endpoint docs in config table. |
| hindsight-control-plane/src/middleware.ts | Localizes 401 API response in middleware. |
| hindsight-control-plane/src/lib/i18n/api-errors.ts | Adds locale detection + error-key translation + payload localization utilities. |
| hindsight-control-plane/src/lib/sdk-response.ts | Extends SDK response helper to attach/translate error keys. |
| hindsight-control-plane/src/i18n/config.ts | Adds new locales and display names. |
| hindsight-control-plane/src/i18n/request.ts | Adds loaders for new locale message bundles. |
| hindsight-control-plane/src/messages/*.json | Adds api.errors.* translations (and new zh-CN/zh-TW catalogs). |
| hindsight-control-plane/src/app/api/**/route.ts | Updates many API routes to return localized error payloads. |
Comments suppressed due to low confidence (3)
hindsight-control-plane/src/app/api/files/retain/route.ts:1
- If
requestJsonis not valid JSON,JSON.parsethrows and the outer catch will return a 500api.errors.files.upload, which is misleading and incorrectly classifies a client error as a server error. WrapJSON.parsein its own try/catch and return a 400 with an appropriate validation/auth errorKey (e.g.,api.errors.auth.invalidRequestBody), keeping 500 for actual upload failures.
hindsight-control-plane/src/lib/sdk-response.ts:1 - Mapping
failureLabel(English strings) to translation keys is brittle: any wording change, punctuation change, or reuse of the helper with a slightly different label silently disables localization. Prefer requiringerrorKeyto be passed explicitly (or using a typed enum/const for failure labels) so localization doesn’t depend on exact string matching.
hindsight-control-plane/src/messages/pt.json:1 - The new
api.errors.*strings in several non-English locale files (e.g., pt/ko/ja/fr/es/de) are still in English, which will produce mixed-language UI/API messages for those locales. Either translate these values for each locale or intentionally omit them sotranslateApiError()falls back toen(keeping a consistent language rather than partially translated catalogs).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Thanks again for the earlier i18n discussion and for the official #1775 implementation on I ported the Chinese locale work onto the current official This includes the key catalog I have been using locally for a while, adjusted against the official main-branch implementation so it complements the current direction instead of bypassing the earlier contributor work. I also addressed the remaining Copilot thread about the observation fetch error copy and translation key. Looping in @benfrank241 and @XIYBHK from the earlier discussion: if you have time, I would really appreciate your review, especially on the Chinese locale coverage and language quality. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 64 out of 64 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (6)
hindsight-control-plane/src/middleware.ts:1
localizeApiErrorPayloadimports all locale message JSONs viaapi-errors.ts. Using it inside Next.js middleware (Edge runtime) can significantly increase the edge bundle size and may hit edge size/latency limits. Consider returning{ errorKey }(and letting the client localize), or restructuringapi-errorsto lazy-load only the resolved locale (dynamic import) and/or to include only the small subset of auth error strings needed by middleware.
hindsight-control-plane/src/middleware.ts:1localizeApiErrorPayloadimports all locale message JSONs viaapi-errors.ts. Using it inside Next.js middleware (Edge runtime) can significantly increase the edge bundle size and may hit edge size/latency limits. Consider returning{ errorKey }(and letting the client localize), or restructuringapi-errorsto lazy-load only the resolved locale (dynamic import) and/or to include only the small subset of auth error strings needed by middleware.
hindsight-control-plane/src/i18n/request.ts:1- The app now loads
zh-CN.json/zh-TW.json, but this PR updatessrc/messages/zh.json(including addingapi.errors.*). Ifzh.jsonis no longer loaded, those new translations won’t be used; and ifzh-CN.json/zh-TW.jsondon’t contain the newapi.errors.*keys, Chinese users will fall back to English or the raw key. Move/duplicate theapi.errors.*additions into the actualzh-CN.json/zh-TW.jsonresources (or revert the loader change to keep usingzh.json).
hindsight-control-plane/src/lib/sdk-response.ts:1 - Mapping
failureLabelstrings toerrorKeyis brittle because the label is also user-facing and easy to tweak; any copy change will silently break localization. Prefer makingerrorKeyan explicit argument at call sites (or use a typed enum/const object for bothfailureLabelanderrorKey) and avoid relying on string matching for correctness.
hindsight-control-plane/src/app/api/files/retain/route.ts:1 - When
errorKeyexists,localizeApiErrorPayloadreplaces theerrorfield with a localized string, which discards the originalerror.messageyou’re currently sending to clients. If you want to preserve diagnostic detail, include it in a separate non-localized field (e.g.,detailorupstream.detail) before callinglocalizeApiErrorPayload, so the client still receives actionable context whileerrorremains localized.
hindsight-control-plane/src/app/api/reflect/route.ts:1 - The
errorKeyused here (api.errors.auth.invalidRequestBody) implies an auth-related error and (per en.json) translates to “Invalid request body”, while the fallback text is “Invalid JSON body”. To keep API error messaging consistent and accurately categorized, add/use a dedicated key for invalid JSON (e.g.,api.errors.validation.invalidJsonBody) or align the fallback string to the existing key’s meaning.
|
@nicoloboschi quick scope check: this PR currently includes both the Chinese UI/message catalog work and API error localization through the same message catalogs. Would you prefer keeping this PR focused only on the UI/message catalog locale work, with the API error localization split into a follow-up PR, or is the combined scope acceptable while you are evaluating it? I have both directions available. The current branch keeps them together, but I can narrow or split the PR depending on what would be easiest for review and merge. |
XIYBHK
left a comment
There was a problem hiding this comment.
Thanks for porting this onto the official next-intl structure — the coverage looks solid and the three-variant approach (zh-CN / zh-TW / yue-Hant) is well thought out.
A few notes from going through the zh-CN catalog:
1. common.next → 下一步 vs 下一页
下一页 reads as pagination ("next page"). In wizard/flow contexts this key reads more naturally as 下一步. Worth checking the actual render sites to confirm which sense applies — if it's used for both, 下一步 is the safer default.
2. bank.general → 概览 ✅
Agree with 概览 over 常规 — better reflects the Overview tab intent.
3. bank.sidebar.recall / bank.sidebar.reflect — suggest keeping as Recall / Reflect
These are product-level proper nouns with specific definitions in Hindsight's own docs. The merged #1791 kept them untranslated for this reason — translating them to 召回 / 反思 introduces semantic drift (e.g. 召回 has a strong "product recall" or "retrieval" connotation in Chinese that doesn't match the Hindsight concept). Keeping the English terms is consistent with how Webhook, MCP, and other technical terms are handled throughout the catalog.
4. bankProfile.empathyLow/High → 客观/感性 vs 抽离/共情
抽离/共情 is more psychologically precise, but 客观/感性 is immediately intuitive to non-specialist users. Either works — just noting the tradeoff. If the upstream intent is closer to a clinical empathy scale, 抽离/共情 is better; if it's a general-audience UI slider, 客观/感性 lands better.
5. bankStats.last → 最近 vs 末页
末页 is technically correct for "last page" in pagination, but feels unusually formal for a UI label. Worth double-checking the render site — if it's a pagination control, 末页 is fine; if it's a "most recent" indicator, 最近 is correct.
Overall the translation quality is good — main suggestion is to align on Recall/Reflect with the upstream precedent from #1791.
|
@XIYBHK I checked the render sites and addressed the actionable zh-CN catalog feedback in
I left Validation passed:
|
|
@XIYBHK Thanks for calling out Just to clarify the terminology choice: |
Summary
This follow-up is based on the official i18n implementation merged in #1775 (
feat(control-plane): add i18n support with 8 locales). It ports the Chinese locale work onto the officialnext-intlmessage catalog structure instead of the superseded #1729 branch.zh-CN,zh-TW(正體中文(臺灣)), and canonicalyue-Hant(粵語(繁體)) to the control-plane locale config and message loaders.zhcatalog so users choose an explicit Chinese locale variant.messages/*.jsoncatalogs, translates them across all locales, and localizes API error payloads via i18n lookup.respondWithSdkthrough the API error localizer while keepingerrorKeyinternal, so API response bodies do not grow a new public field.skills/hindsight-docssync produced by the repo generator (openapi.jsonversion and one docs link).Locale Notes
zh-CNis explicit Simplified Chinese and no longer hidden behind genericzh.zh-TWuses Taiwan Traditional Chinese UI terms such as設定,設定檔,檢視,重新整理,資料,資訊, and存取金鑰.yue-Hantuses Traditional-script Cantonese as the canonical locale code, while keeping UI copy more formal/general than region-specific colloquial Hong Kong wording.Validation
npm test --workspace=hindsight-control-planepasses: 53 tests.npm test --workspace=hindsight-control-plane -- src/messages/messages.test.tspasses: 20 tests.npm run i18n:check --workspace=hindsight-control-planepasses.api.errors.*keys, missing=0 in every locale; de/es/fr/ja/ko/pt have 0 API error values identical to the English fallback.NextResponse.json({ error: ... })static payloads in API routes or middleware.git diff --checkpasses.npx tsc --noEmit --pretty falsestill reports pre-existing non-i18n issues:src/app/api/banks/[bankId]/observations/route.ts(41,53): Parameter 'item' implicitly has an 'any' type.src/app/api/health/route.ts(2,49): Cannot find module '@vectorize-io/hindsight-client'.src/lib/hindsight-client.ts(12,8): Cannot find module '@vectorize-io/hindsight-client'.