Skip to content

feat(i18n): long-tail extraction — finish app-wide i18n (batch 6)#1157

Open
JamieRuderman wants to merge 7 commits into
i18n/extract-columnsfrom
i18n/extract-longtail
Open

feat(i18n): long-tail extraction — finish app-wide i18n (batch 6)#1157
JamieRuderman wants to merge 7 commits into
i18n/extract-columnsfrom
i18n/extract-longtail

Conversation

@JamieRuderman

Copy link
Copy Markdown
Member

Summary

The final long-tail batch — internationalizes the remaining ~200 components and pages, completing the app-wide string extraction. ~1560 new keys, translated to ja/de/es. Also finishes Attributes.tsx (the connection-type value strings deferred from batch 5).

Stacked on i18n/extract-columns (#1156). With this merged (up the stack into feature/i18n), the app is fully internationalized.

What's covered

All previously-untouched surfaces: device detail / connection / network pages, organization + billing + plans + reseller + customer, admin pages, MFA/security, access keys, scripting/products/files, onboarding, notifications, and the shared components across all of them. Plus Attributes.tsx connection-type values ("Peer to Peer", "Inactive", "Local Proxy"…), Yes/No, "This system", "since refresh".

How it was done

To cover this volume, conversion was fanned out across 8 parallel agents (25 files each), each keying strings under the file's component/page name (so keys never collide across files) with inline English defaults, then translation was fanned out across 8 more agents + a dedicated plural pass. Every string uses t() / i18n.t(); interpolation via {{placeholders}}; manual plural ternaries converted to i18next count plurals; sentences with embedded <Link> split rather than concatenated; brand/technical terms (remote.it, AWS, Docker, SAML, TOTP…) left verbatim.

Notable structural fixes agents made where a hook couldn't be used: module-level Attribute arrays converted to getX(t) factories or i18n.t(), default-prop strings moved into component bodies, and t-shadowing lambda params renamed.

Verification

  • tsc --noEmit: 0 errors across the whole project (all 200 files compile together).
  • Production build passes.
  • i18n:check passes — 4 locales × 3 namespaces, no missing/dead keys, no empty English, plural-aware (the check caught and I fixed an over-generation of Japanese _one forms — ja correctly carries only _other; de/es carry _one/_other, es also _many).
  • Per-locale key counts differ exactly by CLDR plural categories (en 1738 / ja 1696 / de 1738 / es 1780).
  • Runtime spot-check: translations resolve per language and plurals inflect correctly (e.g. ja "1名のユーザー" vs "3名のユーザー"; de singular "1 Benutzer").
  • Placeholder parity verified programmatically across all locales (zero mismatches).

Caveats (important)

  • Machine-seeded translations. This is a very large automated pass — solid for shipping (English fallback everywhere) and for QA, but ja/de/es here need a professional linguistic review before being considered production-final. Key naming is per-file/automated and not hand-curated for cross-app consistency.
  • The survey targeted files with the common string-prop / JSX-text patterns; a few strings in unusual patterns may remain and will surface during QA (they render in English via fallback).

🤖 Generated with Claude Code

@JamieRuderman

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e5d05c81bf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 52 to +55
setDefaults() {
this.type = 'COMMAND'
this.icon = 'code-simple'
this.name = 'Command'
this.name = t('appType.launch.command', 'Command')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resolve launch labels after language changes

When the user changes the app language while viewing the same service or connection, these translated names remain in the previous language. LaunchMethod.name is stored when the Application is constructed, while selectApplication memoizes that object using only defaults, service, and connection as inputs, so the language update does not reconstruct it; launch-method menus and derived titles stay stale until one of those inputs changes. Resolve the label at display time or include the resolved language in the selector inputs.

Useful? React with 👍 / 👎.

Comment on lines +14 to +18
name: i18n.t('routeSetting.adaptiveName', 'Adaptive'),
description: i18n.t(
'routeSetting.adaptiveDescription',
'Will pick the best route available, prioritizing a direct connection to this service.'
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Build translated route options reactively

For any non-English startup or in-app language change, the route names and descriptions remain English. ROUTES is evaluated at module import while i18n boots with lng: 'en', and useTranslation() later only rerenders RouteSetting; it does not recompute this module-level array. Construct these translated fields inside the component or through a language-aware factory.

Useful? React with 👍 / 👎.

Comment on lines +76 to +77
export const networkName = (name?: string): string =>
(name && SYSTEM_NETWORK_KEYS[name] ? i18n.t(SYSTEM_NETWORK_KEYS[name], { defaultValue: name }) : name) || ''

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Distinguish system networks by ID before translating

When a user creates or renames a network to Active, Recent, Local, Personal, or Cloud Proxy, NetworkListTitle now displays the localized system label instead of the user-provided name. The creation and rename forms allow these names, and this helper classifies solely by name, so ordinary cloud networks collide with the built-ins. Determine whether the network is synthetic from its ID/type and translate only those instances.

Useful? React with 👍 / 👎.

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.

1 participant