Skip to content

refactor(i18n): migrate packages/i18n from MobX to react-i18next#8898

Open
sriramveeraghanta wants to merge 1 commit intopreviewfrom
refactor-translations
Open

refactor(i18n): migrate packages/i18n from MobX to react-i18next#8898
sriramveeraghanta wants to merge 1 commit intopreviewfrom
refactor-translations

Conversation

@sriramveeraghanta
Copy link
Copy Markdown
Member

@sriramveeraghanta sriramveeraghanta commented Apr 15, 2026

Summary

Migrates packages/i18n from the MobX-based TranslationStore to react-i18next, splits the monolithic translations.ts files into per-feature JSON namespace files, and adds build tooling for cross-locale sync validation. Public API is unchanged — consumers of useTranslation() and TranslationProvider require no updates.

Changes

Core runtime: MobX → i18next

  • Replaced TranslationStore with an i18next instance using i18next-icu (preserves ICU MessageFormat) and i18next-resources-to-backend (lazy namespace loading)
  • New packages/i18n/src/core/ — i18next instance + imperative setLanguage()
  • New packages/i18n/src/provider/TranslationProvider (thin wrapper around I18nextProvider)
  • useTranslation() and TranslationProvider keep identical signatures
  • All namespaces are pre-loaded for the current language during init to prevent re-render cascades
  • Language is read from localStorage synchronously before i18nInstance.init() so non-English users load their language directly

Translation files: TS → JSON namespaces

  • Converted 19 languages' translation files into 570 JSON namespace files (19 × 30 namespaces)
  • Split the monolithic translations.ts into feature-based files: workspace.json, project.json, work-item.json, cycle.json, inbox.json, page.json, wiki.json, etc.
  • Removed old translations.ts, accessibility.ts, editor.ts, empty-state.ts, tour.ts, core.ts (77 files deleted)

Build tooling

  • scripts/generate-types.ts — Generates keys.generated.ts (flat union of ~4,098 keys) from English JSON files; runs before every build. Also detects cross-namespace key collisions and path conflicts.
  • scripts/sync-check.ts — Cross-locale missing/stale key detection with --ci mode that exits non-zero on issues

App-level changes

  • Removed useTranslation-based language sync effect from apps/web/core/lib/wrappers/store-wrapper.tsx
  • Language now synced imperatively from the MobX profile store via setLanguage():
    • profile.store.fetchUserProfile() → calls setLanguage(profile.language) after load
    • profile.store.updateUserProfile() → calls setLanguage() optimistically
    • root.store.resetOnSignOut() → resets to FALLBACK_LANGUAGE

Community scope

  • Excluded enterprise namespaces: customer, epic, initiative, pql, power-k, teamspace, release
  • Pruned enterprise keys from shared namespaces: empty-state, navigation, project-settings, workspace-settings, work-item, importer, page, work-item-type (initiatives / teamspaces / customers / releases / release_picker references removed)

Test plan

  • packages/i18n build succeeds (pnpm run build — generates types + bundles)
  • packages/i18n type check passes (pnpm run check:types)
  • apps/web type check passes (pnpm run check:types)
  • apps/space type check passes (pnpm run check:types)
  • Manually verify language switching in web app
  • Manually verify language persists across reloads
  • Run sync-check CI to confirm all 19 locales are aligned with English
  • Verify translations render correctly in production build

Related

  • Enterprise PR: makeplane/plane-ee#6449

… per-feature namespaces

Replaces the internals of packages/i18n with react-i18next while preserving the
identical public API. Consumer code using useTranslation() and TranslationProvider
requires no changes.

Translation file format: TS objects to JSON namespaces
- Converted TypeScript translation files (19 languages) into feature-based JSON namespace files
- Split the monolithic translations.ts into per-feature namespace files: workspace.json,
  project.json, work-item.json, cycle.json, inbox.json, etc.
- 30 community namespaces across 19 languages = 570 JSON files

Core runtime: MobX to i18next
- Replaced MobX TranslationStore with an i18next instance using i18next-icu
  (preserves ICU MessageFormat) and i18next-resources-to-backend (namespace lazy loading)
- useTranslation() and TranslationProvider keep identical signatures
- All namespaces pre-loaded during init for the current language to prevent
  re-render cascades
- Reads saved language from localStorage before init for faster first paint

Build tooling
- scripts/generate-types.ts: Reads English JSON files and outputs keys.generated.ts
  with a flat union of translation keys (runs before every build)
- scripts/sync-check.ts: Cross-locale missing/stale key detection, cross-namespace
  collision detection, path conflict detection (supports --ci mode)

App-level changes
- Removed useTranslation-based language sync effect from store-wrapper
- Language is now synced imperatively from profile.store (fetchUserProfile,
  updateUserProfile) and root.store (resetOnSignOut) via setLanguage()

Community scope
- Enterprise-only namespaces (customer, epic, initiative, pql, power-k, teamspace,
  release) excluded
- Enterprise-only keys pruned from shared namespaces (empty-state, navigation,
  project-settings, workspace-settings, work-item, importer, page, work-item-type)
Copilot AI review requested due to automatic review settings April 15, 2026 14:46
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 15, 2026

Important

Review skipped

Too many files!

This PR contains 299 files, which is 149 over the limit of 150.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b10ec370-30f3-4f35-830c-65880fa8f1ac

📥 Commits

Reviewing files that changed from the base of the PR and between 13db2f8 and fbfad27.

⛔ Files ignored due to path filters (1)
  • .claude/scheduled_tasks.lock is excluded by !**/*.lock
📒 Files selected for processing (299)
  • .gitignore
  • apps/web/core/lib/wrappers/store-wrapper.tsx
  • apps/web/core/store/root.store.ts
  • apps/web/core/store/user/profile.store.ts
  • packages/i18n/package.json
  • packages/i18n/scripts/generate-types.ts
  • packages/i18n/scripts/sync-check.ts
  • packages/i18n/scripts/tsconfig.json
  • packages/i18n/src/constants/index.ts
  • packages/i18n/src/constants/language.ts
  • packages/i18n/src/constants/namespaces.ts
  • packages/i18n/src/context/index.tsx
  • packages/i18n/src/core/index.ts
  • packages/i18n/src/core/instance.ts
  • packages/i18n/src/core/set-language.ts
  • packages/i18n/src/hooks/use-translation.ts
  • packages/i18n/src/index.ts
  • packages/i18n/src/locales/cs/accessibility.json
  • packages/i18n/src/locales/cs/accessibility.ts
  • packages/i18n/src/locales/cs/auth.json
  • packages/i18n/src/locales/cs/automation.json
  • packages/i18n/src/locales/cs/common.json
  • packages/i18n/src/locales/cs/cycle.json
  • packages/i18n/src/locales/cs/dashboard-widget.json
  • packages/i18n/src/locales/cs/editor.json
  • packages/i18n/src/locales/cs/editor.ts
  • packages/i18n/src/locales/cs/empty-state.json
  • packages/i18n/src/locales/cs/empty-state.ts
  • packages/i18n/src/locales/cs/home.json
  • packages/i18n/src/locales/cs/importer.json
  • packages/i18n/src/locales/cs/inbox.json
  • packages/i18n/src/locales/cs/intake-form.json
  • packages/i18n/src/locales/cs/integration.json
  • packages/i18n/src/locales/cs/module.json
  • packages/i18n/src/locales/cs/navigation.json
  • packages/i18n/src/locales/cs/notification.json
  • packages/i18n/src/locales/cs/page.json
  • packages/i18n/src/locales/cs/project-settings.json
  • packages/i18n/src/locales/cs/project.json
  • packages/i18n/src/locales/cs/settings.json
  • packages/i18n/src/locales/cs/stickies.json
  • packages/i18n/src/locales/cs/template.json
  • packages/i18n/src/locales/cs/tour.json
  • packages/i18n/src/locales/cs/translations.ts
  • packages/i18n/src/locales/cs/update.json
  • packages/i18n/src/locales/cs/wiki.json
  • packages/i18n/src/locales/cs/work-item-type.json
  • packages/i18n/src/locales/cs/work-item.json
  • packages/i18n/src/locales/cs/workflow.json
  • packages/i18n/src/locales/cs/workspace-settings.json
  • packages/i18n/src/locales/cs/workspace.json
  • packages/i18n/src/locales/de/accessibility.json
  • packages/i18n/src/locales/de/accessibility.ts
  • packages/i18n/src/locales/de/auth.json
  • packages/i18n/src/locales/de/automation.json
  • packages/i18n/src/locales/de/common.json
  • packages/i18n/src/locales/de/cycle.json
  • packages/i18n/src/locales/de/dashboard-widget.json
  • packages/i18n/src/locales/de/editor.json
  • packages/i18n/src/locales/de/empty-state.json
  • packages/i18n/src/locales/de/empty-state.ts
  • packages/i18n/src/locales/de/home.json
  • packages/i18n/src/locales/de/importer.json
  • packages/i18n/src/locales/de/inbox.json
  • packages/i18n/src/locales/de/intake-form.json
  • packages/i18n/src/locales/de/integration.json
  • packages/i18n/src/locales/de/module.json
  • packages/i18n/src/locales/de/navigation.json
  • packages/i18n/src/locales/de/notification.json
  • packages/i18n/src/locales/de/page.json
  • packages/i18n/src/locales/de/project-settings.json
  • packages/i18n/src/locales/de/project.json
  • packages/i18n/src/locales/de/settings.json
  • packages/i18n/src/locales/de/stickies.json
  • packages/i18n/src/locales/de/template.json
  • packages/i18n/src/locales/de/tour.json
  • packages/i18n/src/locales/de/translations.ts
  • packages/i18n/src/locales/de/update.json
  • packages/i18n/src/locales/de/wiki.json
  • packages/i18n/src/locales/de/work-item-type.json
  • packages/i18n/src/locales/de/work-item.json
  • packages/i18n/src/locales/de/workflow.json
  • packages/i18n/src/locales/de/workspace-settings.json
  • packages/i18n/src/locales/de/workspace.json
  • packages/i18n/src/locales/en/accessibility.json
  • packages/i18n/src/locales/en/accessibility.ts
  • packages/i18n/src/locales/en/auth.json
  • packages/i18n/src/locales/en/automation.json
  • packages/i18n/src/locales/en/common.json
  • packages/i18n/src/locales/en/core.ts
  • packages/i18n/src/locales/en/cycle.json
  • packages/i18n/src/locales/en/dashboard-widget.json
  • packages/i18n/src/locales/en/editor.json
  • packages/i18n/src/locales/en/editor.ts
  • packages/i18n/src/locales/en/empty-state.json
  • packages/i18n/src/locales/en/empty-state.ts
  • packages/i18n/src/locales/en/home.json
  • packages/i18n/src/locales/en/importer.json
  • packages/i18n/src/locales/en/inbox.json
  • packages/i18n/src/locales/en/intake-form.json
  • packages/i18n/src/locales/en/integration.json
  • packages/i18n/src/locales/en/module.json
  • packages/i18n/src/locales/en/navigation.json
  • packages/i18n/src/locales/en/notification.json
  • packages/i18n/src/locales/en/page.json
  • packages/i18n/src/locales/en/project-settings.json
  • packages/i18n/src/locales/en/project.json
  • packages/i18n/src/locales/en/settings.json
  • packages/i18n/src/locales/en/stickies.json
  • packages/i18n/src/locales/en/template.json
  • packages/i18n/src/locales/en/tour.json
  • packages/i18n/src/locales/en/translations.ts
  • packages/i18n/src/locales/en/update.json
  • packages/i18n/src/locales/en/wiki.json
  • packages/i18n/src/locales/en/work-item-type.json
  • packages/i18n/src/locales/en/work-item.json
  • packages/i18n/src/locales/en/workflow.json
  • packages/i18n/src/locales/en/workspace-settings.json
  • packages/i18n/src/locales/en/workspace.json
  • packages/i18n/src/locales/es/accessibility.json
  • packages/i18n/src/locales/es/accessibility.ts
  • packages/i18n/src/locales/es/auth.json
  • packages/i18n/src/locales/es/automation.json
  • packages/i18n/src/locales/es/common.json
  • packages/i18n/src/locales/es/cycle.json
  • packages/i18n/src/locales/es/dashboard-widget.json
  • packages/i18n/src/locales/es/editor.json
  • packages/i18n/src/locales/es/editor.ts
  • packages/i18n/src/locales/es/empty-state.json
  • packages/i18n/src/locales/es/empty-state.ts
  • packages/i18n/src/locales/es/home.json
  • packages/i18n/src/locales/es/importer.json
  • packages/i18n/src/locales/es/inbox.json
  • packages/i18n/src/locales/es/intake-form.json
  • packages/i18n/src/locales/es/integration.json
  • packages/i18n/src/locales/es/module.json
  • packages/i18n/src/locales/es/navigation.json
  • packages/i18n/src/locales/es/notification.json
  • packages/i18n/src/locales/es/page.json
  • packages/i18n/src/locales/es/project-settings.json
  • packages/i18n/src/locales/es/project.json
  • packages/i18n/src/locales/es/settings.json
  • packages/i18n/src/locales/es/stickies.json
  • packages/i18n/src/locales/es/template.json
  • packages/i18n/src/locales/es/tour.json
  • packages/i18n/src/locales/es/translations.ts
  • packages/i18n/src/locales/es/update.json
  • packages/i18n/src/locales/es/wiki.json
  • packages/i18n/src/locales/es/work-item-type.json
  • packages/i18n/src/locales/es/work-item.json
  • packages/i18n/src/locales/es/workflow.json
  • packages/i18n/src/locales/es/workspace-settings.json
  • packages/i18n/src/locales/es/workspace.json
  • packages/i18n/src/locales/fr/accessibility.json
  • packages/i18n/src/locales/fr/accessibility.ts
  • packages/i18n/src/locales/fr/auth.json
  • packages/i18n/src/locales/fr/automation.json
  • packages/i18n/src/locales/fr/common.json
  • packages/i18n/src/locales/fr/cycle.json
  • packages/i18n/src/locales/fr/dashboard-widget.json
  • packages/i18n/src/locales/fr/editor.json
  • packages/i18n/src/locales/fr/editor.ts
  • packages/i18n/src/locales/fr/empty-state.json
  • packages/i18n/src/locales/fr/empty-state.ts
  • packages/i18n/src/locales/fr/home.json
  • packages/i18n/src/locales/fr/importer.json
  • packages/i18n/src/locales/fr/inbox.json
  • packages/i18n/src/locales/fr/intake-form.json
  • packages/i18n/src/locales/fr/integration.json
  • packages/i18n/src/locales/fr/module.json
  • packages/i18n/src/locales/fr/navigation.json
  • packages/i18n/src/locales/fr/notification.json
  • packages/i18n/src/locales/fr/page.json
  • packages/i18n/src/locales/fr/project-settings.json
  • packages/i18n/src/locales/fr/project.json
  • packages/i18n/src/locales/fr/settings.json
  • packages/i18n/src/locales/fr/stickies.json
  • packages/i18n/src/locales/fr/template.json
  • packages/i18n/src/locales/fr/tour.json
  • packages/i18n/src/locales/fr/translations.ts
  • packages/i18n/src/locales/fr/update.json
  • packages/i18n/src/locales/fr/wiki.json
  • packages/i18n/src/locales/fr/work-item-type.json
  • packages/i18n/src/locales/fr/work-item.json
  • packages/i18n/src/locales/fr/workflow.json
  • packages/i18n/src/locales/fr/workspace-settings.json
  • packages/i18n/src/locales/fr/workspace.json
  • packages/i18n/src/locales/id/accessibility.json
  • packages/i18n/src/locales/id/accessibility.ts
  • packages/i18n/src/locales/id/auth.json
  • packages/i18n/src/locales/id/automation.json
  • packages/i18n/src/locales/id/common.json
  • packages/i18n/src/locales/id/cycle.json
  • packages/i18n/src/locales/id/dashboard-widget.json
  • packages/i18n/src/locales/id/editor.json
  • packages/i18n/src/locales/id/editor.ts
  • packages/i18n/src/locales/id/empty-state.json
  • packages/i18n/src/locales/id/empty-state.ts
  • packages/i18n/src/locales/id/home.json
  • packages/i18n/src/locales/id/importer.json
  • packages/i18n/src/locales/id/inbox.json
  • packages/i18n/src/locales/id/intake-form.json
  • packages/i18n/src/locales/id/integration.json
  • packages/i18n/src/locales/id/module.json
  • packages/i18n/src/locales/id/navigation.json
  • packages/i18n/src/locales/id/notification.json
  • packages/i18n/src/locales/id/page.json
  • packages/i18n/src/locales/id/project-settings.json
  • packages/i18n/src/locales/id/project.json
  • packages/i18n/src/locales/id/settings.json
  • packages/i18n/src/locales/id/stickies.json
  • packages/i18n/src/locales/id/template.json
  • packages/i18n/src/locales/id/tour.json
  • packages/i18n/src/locales/id/translations.ts
  • packages/i18n/src/locales/id/update.json
  • packages/i18n/src/locales/id/wiki.json
  • packages/i18n/src/locales/id/work-item-type.json
  • packages/i18n/src/locales/id/work-item.json
  • packages/i18n/src/locales/id/workflow.json
  • packages/i18n/src/locales/id/workspace-settings.json
  • packages/i18n/src/locales/id/workspace.json
  • packages/i18n/src/locales/index.ts
  • packages/i18n/src/locales/it/accessibility.json
  • packages/i18n/src/locales/it/accessibility.ts
  • packages/i18n/src/locales/it/auth.json
  • packages/i18n/src/locales/it/automation.json
  • packages/i18n/src/locales/it/common.json
  • packages/i18n/src/locales/it/cycle.json
  • packages/i18n/src/locales/it/dashboard-widget.json
  • packages/i18n/src/locales/it/editor.json
  • packages/i18n/src/locales/it/editor.ts
  • packages/i18n/src/locales/it/empty-state.json
  • packages/i18n/src/locales/it/empty-state.ts
  • packages/i18n/src/locales/it/home.json
  • packages/i18n/src/locales/it/importer.json
  • packages/i18n/src/locales/it/inbox.json
  • packages/i18n/src/locales/it/intake-form.json
  • packages/i18n/src/locales/it/integration.json
  • packages/i18n/src/locales/it/module.json
  • packages/i18n/src/locales/it/navigation.json
  • packages/i18n/src/locales/it/notification.json
  • packages/i18n/src/locales/it/page.json
  • packages/i18n/src/locales/it/project-settings.json
  • packages/i18n/src/locales/it/project.json
  • packages/i18n/src/locales/it/settings.json
  • packages/i18n/src/locales/it/stickies.json
  • packages/i18n/src/locales/it/template.json
  • packages/i18n/src/locales/it/tour.json
  • packages/i18n/src/locales/it/translations.ts
  • packages/i18n/src/locales/it/update.json
  • packages/i18n/src/locales/it/wiki.json
  • packages/i18n/src/locales/it/work-item-type.json
  • packages/i18n/src/locales/it/work-item.json
  • packages/i18n/src/locales/it/workflow.json
  • packages/i18n/src/locales/it/workspace-settings.json
  • packages/i18n/src/locales/it/workspace.json
  • packages/i18n/src/locales/ja/accessibility.json
  • packages/i18n/src/locales/ja/accessibility.ts
  • packages/i18n/src/locales/ja/auth.json
  • packages/i18n/src/locales/ja/automation.json
  • packages/i18n/src/locales/ja/common.json
  • packages/i18n/src/locales/ja/cycle.json
  • packages/i18n/src/locales/ja/dashboard-widget.json
  • packages/i18n/src/locales/ja/editor.json
  • packages/i18n/src/locales/ja/editor.ts
  • packages/i18n/src/locales/ja/empty-state.json
  • packages/i18n/src/locales/ja/empty-state.ts
  • packages/i18n/src/locales/ja/home.json
  • packages/i18n/src/locales/ja/importer.json
  • packages/i18n/src/locales/ja/inbox.json
  • packages/i18n/src/locales/ja/intake-form.json
  • packages/i18n/src/locales/ja/integration.json
  • packages/i18n/src/locales/ja/module.json
  • packages/i18n/src/locales/ja/navigation.json
  • packages/i18n/src/locales/ja/notification.json
  • packages/i18n/src/locales/ja/page.json
  • packages/i18n/src/locales/ja/project-settings.json
  • packages/i18n/src/locales/ja/project.json
  • packages/i18n/src/locales/ja/settings.json
  • packages/i18n/src/locales/ja/stickies.json
  • packages/i18n/src/locales/ja/template.json
  • packages/i18n/src/locales/ja/tour.json
  • packages/i18n/src/locales/ja/translations.ts
  • packages/i18n/src/locales/ja/update.json
  • packages/i18n/src/locales/ja/wiki.json
  • packages/i18n/src/locales/ja/work-item-type.json
  • packages/i18n/src/locales/ja/work-item.json
  • packages/i18n/src/locales/ja/workflow.json
  • packages/i18n/src/locales/ja/workspace-settings.json
  • packages/i18n/src/locales/ja/workspace.json
  • packages/i18n/src/locales/ko/accessibility.json
  • packages/i18n/src/locales/ko/accessibility.ts
  • packages/i18n/src/locales/ko/auth.json
  • packages/i18n/src/locales/ko/automation.json
  • packages/i18n/src/locales/ko/common.json
  • packages/i18n/src/locales/ko/cycle.json
  • packages/i18n/src/locales/ko/dashboard-widget.json
  • packages/i18n/src/locales/ko/editor.json
  • packages/i18n/src/locales/ko/editor.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor-translations

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

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.

Migrates packages/i18n from a MobX-backed translation store to a react-i18next/i18next runtime with ICU formatting, while moving translations from TS modules to per-namespace JSON files and adding locale sync tooling.

Changes:

  • Replaced MobX TranslationStore usage with an i18next instance + TranslationProvider wrapper and updated useTranslation().
  • Split translations into per-feature namespace JSON files across locales and removed legacy TS locale modules.
  • Added scripts to generate translation key types and verify locale sync/collisions in CI.

Reviewed changes

Copilot reviewed 82 out of 673 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
packages/i18n/src/locales/de/workflow.json Adds German workflow namespace JSON translations
packages/i18n/src/locales/de/wiki.json Adds German wiki namespace JSON translations
packages/i18n/src/locales/de/update.json Adds German update namespace JSON translations
packages/i18n/src/locales/de/tour.json Adds German tour namespace JSON translations
packages/i18n/src/locales/de/template.json Adds German template namespace JSON translations
packages/i18n/src/locales/de/stickies.json Adds German stickies namespace JSON translations
packages/i18n/src/locales/de/settings.json Adds German settings namespace JSON translations
packages/i18n/src/locales/de/page.json Adds German page namespace JSON translations
packages/i18n/src/locales/de/notification.json Adds German notification namespace JSON translations
packages/i18n/src/locales/de/navigation.json Adds German navigation namespace JSON translations
packages/i18n/src/locales/de/module.json Adds German module namespace JSON translations
packages/i18n/src/locales/de/intake-form.json Adds German intake-form namespace JSON translations
packages/i18n/src/locales/de/inbox.json Adds German inbox namespace JSON translations
packages/i18n/src/locales/de/home.json Adds German home namespace JSON translations
packages/i18n/src/locales/de/empty-state.ts Removes legacy German TS translation module
packages/i18n/src/locales/de/empty-state.json Adds German empty-state namespace JSON translations
packages/i18n/src/locales/de/editor.json Adds German editor namespace JSON translations
packages/i18n/src/locales/de/dashboard-widget.json Adds German dashboard-widget namespace JSON translations
packages/i18n/src/locales/de/cycle.json Adds German cycle namespace JSON translations
packages/i18n/src/locales/de/automation.json Adds German automation namespace JSON translations
packages/i18n/src/locales/de/auth.json Adds German auth namespace JSON translations
packages/i18n/src/locales/de/accessibility.ts Removes legacy German accessibility TS module
packages/i18n/src/locales/de/accessibility.json Adds German accessibility namespace JSON translations
packages/i18n/src/locales/cs/workflow.json Adds Czech workflow namespace JSON translations
packages/i18n/src/locales/cs/wiki.json Adds Czech wiki namespace JSON translations
packages/i18n/src/locales/cs/update.json Adds Czech update namespace JSON translations
packages/i18n/src/locales/cs/tour.json Adds Czech tour namespace JSON translations
packages/i18n/src/locales/cs/template.json Adds Czech template namespace JSON translations
packages/i18n/src/locales/cs/stickies.json Adds Czech stickies namespace JSON translations
packages/i18n/src/locales/cs/settings.json Adds Czech settings namespace JSON translations
packages/i18n/src/locales/cs/project.json Adds Czech project namespace JSON translations
packages/i18n/src/locales/cs/page.json Adds Czech page namespace JSON translations
packages/i18n/src/locales/cs/notification.json Adds Czech notification namespace JSON translations
packages/i18n/src/locales/cs/navigation.json Adds Czech navigation namespace JSON translations
packages/i18n/src/locales/cs/module.json Adds Czech module namespace JSON translations
packages/i18n/src/locales/cs/intake-form.json Adds Czech intake-form namespace JSON translations
packages/i18n/src/locales/cs/inbox.json Adds Czech inbox namespace JSON translations
packages/i18n/src/locales/cs/home.json Adds Czech home namespace JSON translations
packages/i18n/src/locales/cs/empty-state.ts Removes legacy Czech TS translation module
packages/i18n/src/locales/cs/empty-state.json Adds Czech empty-state namespace JSON translations
packages/i18n/src/locales/cs/editor.ts Removes legacy Czech editor TS translation module
packages/i18n/src/locales/cs/editor.json Adds Czech editor namespace JSON translations
packages/i18n/src/locales/cs/dashboard-widget.json Adds Czech dashboard-widget namespace JSON translations
packages/i18n/src/locales/cs/cycle.json Adds Czech cycle namespace JSON translations
packages/i18n/src/locales/cs/automation.json Adds Czech automation namespace JSON translations
packages/i18n/src/locales/cs/accessibility.ts Removes legacy Czech accessibility TS module
packages/i18n/src/locales/cs/accessibility.json Adds Czech accessibility namespace JSON translations
packages/i18n/src/index.ts Refactors i18n package exports to new provider/hook/core APIs
packages/i18n/src/hooks/use-translation.ts Reimplements useTranslation() using react-i18next
packages/i18n/src/core/set-language.ts Adds imperative setLanguage() utility
packages/i18n/src/core/instance.ts Adds and initializes i18next instance with lazy JSON namespace loading
packages/i18n/src/core/index.ts Exposes core i18n instance/init and setLanguage()
packages/i18n/src/context/index.tsx Removes MobX TranslationContext/Provider implementation
packages/i18n/src/constants/namespaces.ts Adds namespace list/constants for i18next configuration
packages/i18n/src/constants/language.ts Removes legacy translation file enum; keeps language constants
packages/i18n/src/constants/index.ts Re-exports namespaces alongside language constants
packages/i18n/scripts/tsconfig.json Adds scripts TS config (Node16 ESM)
packages/i18n/scripts/sync-check.ts Adds locale sync/collision/path-conflict validation script
packages/i18n/scripts/generate-types.ts Adds translation key type generation from English JSON
packages/i18n/package.json Updates deps/scripts for i18next migration and new tooling
apps/web/core/store/user/profile.store.ts Syncs app language via setLanguage() after profile fetch/update
apps/web/core/store/root.store.ts Resets language via setLanguage(FALLBACK_LANGUAGE) on sign-out
apps/web/core/lib/wrappers/store-wrapper.tsx Removes language-sync effect that used useTranslation()

Comment on lines +18 to +21
i18nInstance
.use(ICU)
.use(initReactI18next)
.use(resourcesToBackend((language: string, namespace: string) => import(`../locales/${language}/${namespace}.json`)));
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

The backend import path uses the full i18next language code (e.g. de-DE) as the locale directory name. In this PR, locale directories shown are src/locales/de/... and src/locales/cs/..., which will cause runtime load failures (at least an initial failed import for de-DE) and can break translations if i18next doesn’t successfully fall back to de. Consider normalizing language to a folder key (e.g. language.split("-")[0]) inside the backend resolver, or ensure the on-disk locale folders match the SUPPORTED_LANGUAGES values (and configure i18next load: "languageOnly" if that’s the intended behavior).

Suggested change
i18nInstance
.use(ICU)
.use(initReactI18next)
.use(resourcesToBackend((language: string, namespace: string) => import(`../locales/${language}/${namespace}.json`)));
const getLocaleFolderKey = (language: string): string => language.split("-")[0];
i18nInstance
.use(ICU)
.use(initReactI18next)
.use(
resourcesToBackend((language: string, namespace: string) =>
import(`../locales/${getLocaleFolderKey(language)}/${namespace}.json`),
),
);

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +39
const { t, i18n } = useI18nextTranslation();

const changeLanguage = useCallback(
(lng: TLanguage) => {
void (async () => {
try {
await i18n.changeLanguage(lng);
if (typeof window === "undefined") return;
localStorage.setItem(LANGUAGE_STORAGE_KEY, lng);
document.documentElement.lang = lng;
} catch (err) {
console.error("Failed to change language:", err);
}
})();
},
[i18n]
);
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

changeLanguage() duplicates the side effects in core/set-language.ts (localStorage + <html lang>), which makes it easy for the two paths to drift (e.g., preloading namespaces after language change, waiting for initPromise, error handling). To keep behavior consistent, consider delegating changeLanguage() to the exported setLanguage() utility (or at minimum share a single internal helper).

Copilot uses AI. Check for mistakes.
* @returns {ILanguageOption[]} languages - available languages
* @throws {Error} if the TranslationProvider is not used
*/
export function useTranslation(): TTranslationStore {
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

Previously this hook threw an explicit error when used outside TranslationProvider. The new implementation will no longer throw (and may silently use a different/default i18n instance if the provider isn’t mounted), which is a behavioral change for API consumers and can make misconfiguration harder to detect. If “must be used within TranslationProvider” is still intended, consider restoring an explicit invariant (or ensure the hook always binds to the package’s i18nInstance so it behaves deterministically).

Copilot uses AI. Check for mistakes.
// No namespace arg — fallbackNS in the i18next config ensures all namespaces
// are searched for any key. Passing NAMESPACES here would trigger concurrent
// async loads per component, causing a re-render cascade.
const { t, i18n } = useI18nextTranslation();
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

Previously this hook threw an explicit error when used outside TranslationProvider. The new implementation will no longer throw (and may silently use a different/default i18n instance if the provider isn’t mounted), which is a behavioral change for API consumers and can make misconfiguration harder to detect. If “must be used within TranslationProvider” is still intended, consider restoring an explicit invariant (or ensure the hook always binds to the package’s i18nInstance so it behaves deterministically).

Copilot uses AI. Check for mistakes.
Comment on lines +8 to +11
export { TranslationProvider } from "./provider";

// Hooks
export { useTranslation } from "./hooks/use-translation";
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

PR description claims “Public API is unchanged”, but src/index.ts no longer re-exports ./context, ./store, and ./locales (it previously exported export * from "./..."). If any downstream code imports those symbols from @plane/i18n, this becomes a breaking change even if useTranslation() / TranslationProvider signatures are stable. Consider either restoring the previous re-exports or clarifying in the PR description what is considered public API.

Copilot uses AI. Check for mistakes.
Comment on lines +20 to +23
export { setLanguage } from "./core/set-language";

// Constants
export { FALLBACK_LANGUAGE, SUPPORTED_LANGUAGES, LANGUAGE_STORAGE_KEY } from "./constants/language";
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

PR description claims “Public API is unchanged”, but src/index.ts no longer re-exports ./context, ./store, and ./locales (it previously exported export * from "./..."). If any downstream code imports those symbols from @plane/i18n, this becomes a breaking change even if useTranslation() / TranslationProvider signatures are stable. Consider either restoring the previous re-exports or clarifying in the PR description what is considered public API.

Copilot uses AI. Check for mistakes.
Comment on lines +8 to +12
"chart_models": {
"basic": "Základní",
"stacked": "Skládaný",
"grouped": "Skupinový"
},
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

This Czech dashboard-widget JSON changes the shape of chart_models.* compared to other locales (e.g., it’s a string here but appears to be an object with short_label/long_label in German). If the UI expects nested keys like ...chart_models.basic.short_label, those lookups will fail and fall back to English/missing keys. The locale files should keep the same key structure across languages; update this locale to match the expected schema.

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +75
"wrong_name": "Der Notizname darf nicht länger als 100 Zeichen sein.",
"already_exists": "Es existiert bereits eine Notiz ohne Beschreibung"
},
"created": {
"title": "Notiz erstellt",
"message": "Die Notiz wurde erfolgreich erstellt"
},
"not_created": {
"title": "Notiz nicht erstellt",
"message": "Die Notiz konnte nicht erstellt werden"
},
"updated": {
"title": "Notiz aktualisiert",
"message": "Die Notiz wurde erfolgreich aktualisiert"
},
"not_updated": {
"title": "Notiz nicht aktualisiert",
"message": "Die Notiz konnte nicht aktualisiert werden"
},
"removed": {
"title": "Notiz entfernt",
"message": "Die Notiz wurde erfolgreich entfernt"
},
"not_removed": {
"title": "Notiz nicht entfernt",
"message": "Die Notiz konnte nicht entfernt werden"
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

In the page_navigation_pane.toasts section, multiple messages reference “Notiz” (note) and appear to be copied from stickies/notes, not page navigation. This will show incorrect user-facing error/success messaging in the page UI. Adjust these strings (and possibly the keys) to match the page/navigation context.

Suggested change
"wrong_name": "Der Notizname darf nicht länger als 100 Zeichen sein.",
"already_exists": "Es existiert bereits eine Notiz ohne Beschreibung"
},
"created": {
"title": "Notiz erstellt",
"message": "Die Notiz wurde erfolgreich erstellt"
},
"not_created": {
"title": "Notiz nicht erstellt",
"message": "Die Notiz konnte nicht erstellt werden"
},
"updated": {
"title": "Notiz aktualisiert",
"message": "Die Notiz wurde erfolgreich aktualisiert"
},
"not_updated": {
"title": "Notiz nicht aktualisiert",
"message": "Die Notiz konnte nicht aktualisiert werden"
},
"removed": {
"title": "Notiz entfernt",
"message": "Die Notiz wurde erfolgreich entfernt"
},
"not_removed": {
"title": "Notiz nicht entfernt",
"message": "Die Notiz konnte nicht entfernt werden"
"wrong_name": "Der Name des Navigationspunkts darf nicht länger als 100 Zeichen sein.",
"already_exists": "Es existiert bereits ein Navigationspunkt ohne Beschreibung"
},
"created": {
"title": "Navigationspunkt erstellt",
"message": "Der Navigationspunkt wurde erfolgreich erstellt"
},
"not_created": {
"title": "Navigationspunkt nicht erstellt",
"message": "Der Navigationspunkt konnte nicht erstellt werden"
},
"updated": {
"title": "Navigationspunkt aktualisiert",
"message": "Der Navigationspunkt wurde erfolgreich aktualisiert"
},
"not_updated": {
"title": "Navigationspunkt nicht aktualisiert",
"message": "Der Navigationspunkt konnte nicht aktualisiert werden"
},
"removed": {
"title": "Navigationspunkt entfernt",
"message": "Der Navigationspunkt wurde erfolgreich entfernt"
},
"not_removed": {
"title": "Navigationspunkt nicht entfernt",
"message": "Der Navigationspunkt konnte nicht entfernt werden"

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +20
"build": "pnpm run generate:types && tsdown",
"generate:types": "npx tsx@4.19.2 scripts/generate-types.ts",
"sync:check": "npx tsx@4.19.2 scripts/sync-check.ts",
"check:sync": "npx tsx@4.19.2 scripts/sync-check.ts --ci",
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

Running tsx via npx in package scripts can add network dependency and variability/latency in CI and local builds (especially in constrained environments). Consider adding tsx as a devDependency (pinned to the same version) and invoking it directly (e.g., tsx scripts/...) for more reliable and faster execution.

Copilot uses AI. Check for mistakes.
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.

2 participants