Skip to content

feat(control-plane): add Chinese locale variants#1784

Merged
nicoloboschi merged 10 commits into
vectorize-io:mainfrom
MapleEve:maple/i18n-zh-locales-official-main
May 28, 2026
Merged

feat(control-plane): add Chinese locale variants#1784
nicoloboschi merged 10 commits into
vectorize-io:mainfrom
MapleEve:maple/i18n-zh-locales-official-main

Conversation

@MapleEve
Copy link
Copy Markdown
Contributor

@MapleEve MapleEve commented May 27, 2026

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 official next-intl message catalog structure instead of the superseded #1729 branch.

  • Adds zh-CN, zh-TW (正體中文(臺灣)), and canonical yue-Hant (粵語(繁體)) to the control-plane locale config and message loaders.
  • Removes the generic zh catalog so users choose an explicit Chinese locale variant.
  • Adds complete message catalogs for all three Chinese locales.
  • Re-applies translation memory from the earlier refined Chinese locale work where the English source text matches the official catalog.
  • Adds API error message keys to the official messages/*.json catalogs, translates them across all locales, and localizes API error payloads via i18n lookup.
  • Wires route handlers and respondWithSdk through the API error localizer while keeping errorKey internal, so API response bodies do not grow a new public field.
  • Includes generated skills/hindsight-docs sync produced by the repo generator (openapi.json version and one docs link).

Locale Notes

  • zh-CN is explicit Simplified Chinese and no longer hidden behind generic zh.
  • zh-TW uses Taiwan Traditional Chinese UI terms such as 設定, 設定檔, 檢視, 重新整理, 資料, 資訊, and 存取金鑰.
  • yue-Hant uses Traditional-script Cantonese as the canonical locale code, while keeping UI copy more formal/general than region-specific colloquial Hong Kong wording.
  • de/es/fr/ja/ko/pt API error strings are translated instead of left as English fallback values.

Validation

  • npm test --workspace=hindsight-control-plane passes: 53 tests.
  • npm test --workspace=hindsight-control-plane -- src/messages/messages.test.ts passes: 20 tests.
  • npm run i18n:check --workspace=hindsight-control-plane passes.
  • Locale parity check: 10 JSON locales, each with 1319 leaf keys; missing/extra keys are 0.
  • Referenced API error key check: 74 referenced api.errors.* keys, missing=0 in every locale; de/es/fr/ja/ko/pt have 0 API error values identical to the English fallback.
  • Raw static API error response scan: no remaining NextResponse.json({ error: ... }) static payloads in API routes or middleware.
  • git diff --check passes.
  • npx tsc --noEmit --pretty false still 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'.

Copilot AI review requested due to automatic review settings May 27, 2026 14:42
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 with api.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 requestJson is not valid JSON, JSON.parse throws and the outer catch will return a 500 api.errors.files.upload, which is misleading and incorrectly classifies a client error as a server error. Wrap JSON.parse in 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 requiring errorKey to 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 so translateApiError() falls back to en (keeping a consistent language rather than partially translated catalogs).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@MapleEve
Copy link
Copy Markdown
Contributor Author

Thanks again for the earlier i18n discussion and for the official #1775 implementation on main.

I ported the Chinese locale work onto the current official main / next-intl message catalog structure in this PR, rather than continuing from the superseded #1729 branch. This adds complete zh-CN, zh-TW (正體中文(臺灣)), and canonical yue-Hant (粵語(繁體)) catalogs, and also localizes API error messages through the official message catalogs while keeping errorKey internal.

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.

@MapleEve MapleEve requested a review from Copilot May 27, 2026 16:56
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • localizeApiErrorPayload imports all locale message JSONs via api-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 restructuring api-errors to 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:1
  • localizeApiErrorPayload imports all locale message JSONs via api-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 restructuring api-errors to 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 updates src/messages/zh.json (including adding api.errors.*). If zh.json is no longer loaded, those new translations won’t be used; and if zh-CN.json / zh-TW.json don’t contain the new api.errors.* keys, Chinese users will fall back to English or the raw key. Move/duplicate the api.errors.* additions into the actual zh-CN.json / zh-TW.json resources (or revert the loader change to keep using zh.json).
    hindsight-control-plane/src/lib/sdk-response.ts:1
  • Mapping failureLabel strings to errorKey is brittle because the label is also user-facing and easy to tweak; any copy change will silently break localization. Prefer making errorKey an explicit argument at call sites (or use a typed enum/const object for both failureLabel and errorKey) and avoid relying on string matching for correctness.
    hindsight-control-plane/src/app/api/files/retain/route.ts:1
  • When errorKey exists, localizeApiErrorPayload replaces the error field with a localized string, which discards the original error.message you’re currently sending to clients. If you want to preserve diagnostic detail, include it in a separate non-localized field (e.g., detail or upstream.detail) before calling localizeApiErrorPayload, so the client still receives actionable context while error remains localized.
    hindsight-control-plane/src/app/api/reflect/route.ts:1
  • The errorKey used 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.

@MapleEve
Copy link
Copy Markdown
Contributor Author

@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.

Copy link
Copy Markdown
Contributor

@XIYBHK XIYBHK left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@MapleEve
Copy link
Copy Markdown
Contributor Author

@XIYBHK I checked the render sites and addressed the actionable zh-CN catalog feedback in 23bb9c10:

  • bank.sidebar.recall / bank.sidebar.reflect now stay as Recall / Reflect across zh-CN, zh-TW, and yue-Hant, matching the product-term precedent from fix(control-plane): polish zh translation for naturalness #1791.
  • common.next now uses 下一步 across the Chinese variants so the generic key reads as a flow/action label rather than pagination.
  • bankStats.last is used for the “last consolidated” / most-recent timestamp label, not pagination, so it now uses 最近 across the Chinese variants.

I left bankProfile.empathyLow / bankProfile.empathyHigh as 抽离 / 共情 because your note described that one as a tradeoff and the current wording matches the profile-scale meaning.

Validation passed:

  • npm run i18n:check --workspace=hindsight-control-plane
  • npm test --workspace=hindsight-control-plane
  • git diff --check
  • locale parity check: all 10 catalogs have 1319 leaf keys, missing=0, extra=0

@MapleEve MapleEve requested review from XIYBHK and Copilot May 28, 2026 03:48
@MapleEve
Copy link
Copy Markdown
Contributor Author

@XIYBHK Thanks for calling out Recall / Reflect. I aligned the sidebar labels with the upstream #1791 precedent and Hindsight's product-level primitives, so they now stay as Recall / Reflect in the navigation.

Just to clarify the terminology choice: 召回 / 反思 are not incorrect Chinese translations in themselves. 召回 is an established term in information retrieval / memory retrieval contexts, and 反思 is also reasonable in cognitive or agent-loop contexts. For the sidebar/navigation labels, though, keeping the English product terms is more consistent with the current Hindsight terminology.

@nicoloboschi nicoloboschi merged commit 617939d into vectorize-io:main May 28, 2026
2 checks passed
@MapleEve MapleEve deleted the maple/i18n-zh-locales-official-main branch May 28, 2026 10:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants