Skip to content

Comments

Localisation for core languages#419

Draft
01painadam wants to merge 32 commits intodevelopfrom
i18n-ui
Draft

Localisation for core languages#419
01painadam wants to merge 32 commits intodevelopfrom
i18n-ui

Conversation

@01painadam
Copy link
Collaborator

Adds full UI internationalisation to the app using next-intl, supporting 5 languages: English, French, Spanish, Portuguese, and Indonesian. All ~270 fixed UI strings across ~45 components are now translatable. English is bundled statically (zero latency for most users); other locales are lazy-loaded on demand and cached in memory.

⚠️ This PR does not translate: dataset content, user-generated content, AI assistant responses, or the landing page (/).

⚠️ Translations are AI driven and need validation

chrome-capture-2026-02-22

What changed

96 files changed · 4,028 additions · 509 deletions · 22 commits

Infrastructure

  • Installed next-intl v4.8.3
  • Added app/i18n/config.ts — supported locales, default locale, lazy-loading config
  • Added app/i18n/getMessages.ts — async message loader that merges all namespace files per locale
  • Added app/i18n/welcomeMessages.ts — standalone welcome messages (used by chatStore before i18n provider mounts)
  • Added app/components/providers/index.tsxI18nProvider wrapping NextIntlClientProvider with reactive locale switching and timeZone="UTC"
  • Added app/config/languages.ts and app/components/LanguageSelector.tsx

Locale Files (35 JSON files)

  • 7 English namespace files in public/locales/en/: common, chat, dialogs, onboarding, dashboard, landing, errors
  • 28 translated files in public/locales/{fr,es,pt,id}/ — real translations for 6 namespaces (landing excluded, remains English)
  • 4 welcome prompt files: public/welcome-prompts-{fr,es,pt,id}.json — locale-specific starter prompt suggestions

Migrated Components (~45 files)

Area Components
App Shell PageHeader, sidebar, ChatInput, ChatPanelHeader, ContextButton (+hook), ContextTag, ContextMenu, HighlightedFeaturesLayer, LanguageSelector
Chat ChatMessages, ChatPanel, MessageBubble, Reasoning, CopySelectionTooltip, WelcomeModal, WidgetMessage, ChatDisclaimer, VisualizationDisclaimer, SamplePrompts
Map MapAreaControls, BasemapSelector, UploadAreaDialog
Dialogs ThreadActionsMenu, ThreadDeleteDialog, ThreadRenameDialog, ThreadShareDialog, DatasetInfoModal
Insight InsightProvenanceDrawer
Onboarding app/onboarding/form.tsx
Dashboard app/dashboard/page.tsx
Error Pages not-found.tsx, maintenance/page.tsx, unauthorized/page.tsx
Landing GlobalHeader (+NavItems component), Hero, FeaturesTabs, HowItWorks, Footer, SupportWorkTabs, TrustedPlatforms, FutureOfMonitoring, TeamSection, NewEraQuote, LatestUpdates, CTA

Store Changes

  • authStore.ts — added preferredLanguageCode field, extracted from /auth/me response
  • chatStore.tsbuildInitialState respects language, setWelcomeLanguage action, metadata field
  • promptStore.tsloadPromptsForLanguage() fetches locale-specific prompt files
  • app/types/chat.ts — added metadata?.preferred_language_code
  • app/api/auth/me/route.ts — extracts and returns preferredLanguageCode

Key Patterns

  • ChatContextOptionsuseChatContextOptions() hook — allows t() calls for context chip labels
  • Module-level const arrays (FEATURE_TABS, HOW_STEPS, BASEMAP_OPTIONS) converted to in-component arrays with t() calls
  • renderNavItemsNavItems component — GlobalHeader's standalone function couldn't use hooks
  • Zustand store errors — use error type codes, translated at component render time (not in the store)
  • Brand names kept in English — "Global Nature Watch", "Global Forest Watch", "Land & Carbon Lab", "PREVIEW" are not translated

Language Selector Behavior

  • In-app header — globe icon + uppercase language code (e.g. 🌐 EN). Dropdown shows 5 supported languages + an "Other Languages…" info item
  • Session-only — header language changes update in-memory Zustand state only. They do not persist to the profile API
  • Profile setting — language set during onboarding or in /dashboard is persisted via PATCH and is the authoritative source on page reload
  • Resolution order: Authenticated → profile API value on load → header selector for session override. Anonymous → navigator.language if supported → defaults to "en"

Verification

A comprehensive verification document is included at docs/i18n-translations-verification.md with:

  • Tables for all ~270 translation keys across 6 namespaces
  • All 5 language translations side-by-side
  • Source component file(s) for each key
  • Step-by-step instructions for how to access each string in the running app

Not in Scope

Item Reason
Landing page translations (/) Excluded per request — landing.json remains English-only
Dataset content & metadata Dynamic content from API
AI assistant responses Controlled by LLM, not UI strings
User-generated content Thread names, feedback text, etc.
Landing page body paragraphs (sections 3, 5, 7–10) Lower priority marketing copy, headings are translated
Toast messages originating in Zustand stores Kept in English in chat history; some sidebar/store toasts still English

Testing

  • Manual verification: switch language via header selector → all chat UI, sidebar, dialogs, onboarding, dashboard, and error pages update
  • JSON validation: all 35 locale files parse correctly, key parity verified between English and all 4 non-English locales

Development

  1. Add the English string to the relevant JSON file in public/locales/en/
    (e.g., common.json, chat.json, dialogs.json, etc.)
  2. Add translations to the same key in all 4 other locale files: es/, fr/,
    id/, pt/
  3. Use it in the component via useTranslations:
  const t = useTranslations("common");
  // ...
  {t("languageSelector.aiDisclaimer")}

The locale files are organized by namespace — common.json for shared UI,
chat.json for chat-specific, dialogs.json for modals, landing.json for the
landing page, etc.

kamicut and others added 28 commits January 19, 2026 12:15
Merging in changes from develop
Merging develop and main
- Add languages.ts config with 5 supported languages
- Add LanguageSelector component
- Add preferredLanguageCode to authStore
- Add setWelcomeLanguage to chatStore with buildInitialState pattern
- Add loadPromptsForLanguage to promptStore
- Add preferredLanguageCode to /api/auth/me response
- Add metadata.preferred_language_code to ChatPrompt type
- Add welcome messages for all 5 languages
- Add translated welcome-prompts JSON files
- Wrap app in NextIntlClientProvider
- Load English synchronously, other locales on demand with caching
- AuthBootstrapper now reads preferredLanguageCode from /api/auth/me
- Browser locale detection fallback for anonymous users
- Language changes propagate to chatStore welcome message and promptStore
- html lang attribute kept in sync with active locale
- Replace static ChatContextOptions with useChatContextOptions() hook
- Export ChatContextIcons for icon-only consumers
- Update ContextTag, ContextMenu, HighlightedFeaturesLayer imports
Migrated: WelcomeModal, Reasoning, CopySelectionTooltip, ChatPanel,
MessageBubble, WidgetMessage, InsightProvenanceDrawer, BasemapSelector,
MapAreaControls, UploadAreaDialog, ChatDisclaimer, DatasetInfoModal,
ThreadActionsMenu, ThreadDeleteDialog, ThreadRenameDialog, ThreadShareDialog,
VisualizationDisclaimer
…or fr/es/pt/id)

These files are English placeholders. Real translations will be added
incrementally without code changes.
…ages' option + toast

- Add 'Display Language' label above the 5 language options
- Add separator + 'Other Languages…' item with info icon at bottom
- Clicking 'Other' shows informational toast explaining the assistant
  understands many more languages, without changing the current selection
- All strings use useTranslations for i18n support
Previously the header LanguageSelector only updated in-memory state,
so the choice was lost on page reload. Now for authenticated users it
also PATCHes /api/proxy/auth/profile (fire-and-forget) so the
preference survives across sessions and stays in sync with the
dashboard/onboarding profile setting.
Use explicit gray.800 color for language names, gray.500 for codes
and labels, and bump font size to sm. The semantic color tokens
(fg.muted etc) were resolving to near-invisible values on the white
dropdown background.
Replace English placeholder content with actual translations across
6 namespaces (common, chat, dialogs, onboarding, dashboard, errors)
for all 4 non-English locales. Landing page strings left as English
per request. Brand names (Global Nature Watch, Global Forest Watch,
Land & Carbon Lab, PREVIEW) kept in English across all locales.

All 24 JSON files validated for correct syntax and key parity with
the English source files.
Comprehensive markdown document with tables for all ~200 translation
keys across 6 namespaces (common, chat, dialogs, onboarding, dashboard,
errors). Each row includes: key, all 5 language translations, source
component file(s), and how to access that string in the running app.
… doc

Migrate ChatMessages.tsx preview disclaimer from hardcoded English to
next-intl translation keys (previewDisclaimer.*). Add translations for
all 5 languages. Brand heading kept in English.

Update verification doc with:
- Preview disclaimer section (8 new keys × 5 languages)
- Starter prompts section explaining the separate welcome-prompts JSON
  files and how they differ from the next-intl translation system
- Replace GlobeIcon with TranslateIcon (A↔文) in LanguageSelector
- Add display=inline-block to LanguageSelector wrapper to prevent
  block-level stretching
- Add px=4 py=1.5 to prompts progress bar for button-like padding
- Reduce header Flex gap from 6 to 3 for tighter, visually even spacing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@01painadam 01painadam requested a review from LanesGood February 23, 2026 08:31
@vercel
Copy link

vercel bot commented Feb 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
project-zeno-next Ready Ready Preview, Comment Feb 23, 2026 11:52am

Request Review

…ents

- Reject empty prompts arrays (previously passed Array.isArray check)
- Filter out non-string and whitespace-only elements from prompt data
- Fall back to English when all elements are invalid after filtering
- Add Vitest infrastructure and 9 tests covering validation + regressions
- Add AbortController to cancel in-flight fetches when language changes
- Abort happens before the English early-return path, not just before
  new fetches, so switching to English mid-fetch cancels correctly
- Silently ignore aborted requests via signal.aborted check (robust
  across environments that may not throw DOMException)
- Add 3 race condition tests: last-language-wins, abort-does-not-
  fallback, and signal-passed-to-fetch
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