Skip to content

feat(desktop): add minimal i18n foundation for desktop UI strings#7380

Closed
bavadim wants to merge 3 commits intoblock:mainfrom
redsquad-tech:feat/desktop-i18n-support
Closed

feat(desktop): add minimal i18n foundation for desktop UI strings#7380
bavadim wants to merge 3 commits intoblock:mainfrom
redsquad-tech:feat/desktop-i18n-support

Conversation

@bavadim
Copy link
Copy Markdown
Contributor

@bavadim bavadim commented Feb 20, 2026

Summary

This PR adds a minimal, low-maintenance i18n foundation for Goose Desktop without introducing new maintained locales in upstream.

What is included:

  • A lightweight translation runtime in ui/desktop/src/i18n/index.ts
  • English dictionary ui/desktop/src/i18n/en.json as the canonical default
  • UI string wiring to t('key', 'fallback') across key desktop surfaces
  • Locale-aware date/time formatting via GOOSE_LOCALE (currentLocaleTag())
  • i18n unit tests in ui/desktop/src/i18n/i18n.test.ts
  • Documentation update in CUSTOM_DISTROS.md for localization in custom distros

What is intentionally NOT included:

  • No upstream commitment to maintain multiple languages now
  • No language selector UI
  • No additional locale catalogs in this PR
  • No CI guardrails/check scripts

Why this should be accepted now:

  • Keeps upstream default behavior (English-first, safe fallbacks)
  • Reduces hardcoded UI text and creates a stable path for downstream/fork localization
  • Minimal operational burden on core maintainers
  • Aligns with current project velocity: infrastructure first, language packs later as optional follow-ups

Type of Change

  • Feature
  • Bug fix
  • Refactor / Code quality
  • Performance improvement
  • Documentation
  • Tests
  • Security fix
  • Build / Release
  • Other (specify below)

AI Assistance

  • This PR was created or reviewed with AI assistance

Testing

  • cd ui/desktop && npm run test:run -- src/i18n/i18n.test.ts
  • cd ui/desktop && npm run typecheck
  • Manual smoke review of updated UI surfaces using translation function fallbacks

Related Issues

Relates to #2376
Discussion: #2376
Discussion: #4230

Screenshots/Demos (for UX changes)

Before:
UI strings were hardcoded in many desktop components.

After:
UI strings are routed through i18n keys with English fallback, and locale tag is respected for date/time formatting.

Copy link
Copy Markdown

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

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: 48b63f36f2

ℹ️ 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".

export function currentLocaleTag(): string {
const raw = getConfiguredLocaleRaw();
if (raw) {
return normalizeLocaleTag(raw);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Validate locale tag before returning it

When GOOSE_LOCALE is set to a common POSIX-style value like en_US.UTF-8, currentLocaleTag() returns en-US.UTF-8, which is not a valid BCP-47 locale tag; formatMessageTimestamp() then passes this value to toLocaleTimeString/toLocaleDateString, which throws RangeError and can break session/chat rendering wherever timestamps are shown. This only appears when users or distros provide a locale with encoding suffixes, but that is a realistic configuration path given the new env-based locale support.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, fixed in fix(desktop): validate locale tag before timestamp formatting.

Changes:

  • Strip POSIX locale suffixes (like .UTF-8 and @modifier) before using locale tags
  • Validate/canonicalize locale tags via Intl.getCanonicalLocales
  • Fallback to en-US when locale is invalid
  • Added tests for en_US.UTF-8 normalization and invalid tag fallback

This prevents RangeError in toLocaleTimeString/toLocaleDateString paths.

@bavadim bavadim force-pushed the feat/desktop-i18n-support branch from d6e8edf to aead6aa Compare February 20, 2026 13:57
- add lightweight i18n runtime and English catalog\n- wire key desktop UI strings via translation keys with fallbacks\n- validate/canonicalize locale tag for timestamp formatting\n- document desktop localization path for custom distros

Signed-off-by: Vadim Polulyakh <[email protected]>
@@ -81,7 +93,10 @@ export function GroupedExtensionLoadingToast({
<div className="font-medium text-base">{getSummaryText()}</div>
{errorCount > 0 && (
<div className="text-sm opacity-90">
{errorCount} extension{errorCount !== 1 ? 's' : ''} failed to load
{t('extensions.failed_count', '{count} extension{suffix} failed to load', {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It's not clear to me how this framework will support pluralisation in most languages, where a simple suffix is not enough. I might have missed something though. To give an example, in French this string would have one of these forms:

Singular: 1 extension n'a pas pu être chargée
Plural: 10 extensions n'ont pas pu être chargées

How would a hypothetical i18n/fr.json implement this translation?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Due to this issue I decided to take a stab at a different approach using ICU MessageFormat for the translation strings (which is flexible enough to support the wide variety of different plural/gender/etc complexities that different languages have, and also has the advantage of being a standard with a lot of tooling support)

That PR is in #8105 -- I'll close this one. Would welcome any review/discussion/help over there

@@ -37,7 +38,7 @@ export const SearchBar: React.FC<SearchBarProps> = ({
searchResults,
inputRef: externalInputRef,
initialSearchTerm = '',
placeholder = 'Search conversation...',
placeholder = t('common.search_conversation', 'Search conversation...'),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This gets called at module load time; are we sure locale config is always already available then? It might be safer to move the t() call into the component body

{getSearchShortcutText()} to search.
<p className="text-sm text-text-muted mb-2">
{t(
'extensions.description',
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This key doesn't exist in i18n/en.json -- could we add a lint that all keys used in t() calls in code actually exist in the base translation file? If we have that, maybe we don't need to duplicate the default text in both the t() call and the translation file.

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