Skip to content

feat(i18n): Add full localization support with 40 languages#27

Merged
eyelock merged 7 commits into
mainfrom
chore/localization
Jan 14, 2026
Merged

feat(i18n): Add full localization support with 40 languages#27
eyelock merged 7 commits into
mainfrom
chore/localization

Conversation

@eyelock
Copy link
Copy Markdown
Owner

@eyelock eyelock commented Jan 14, 2026

Summary

  • Add complete localization infrastructure for TermQ with support for 40 languages
  • Implement language settings UI with searchable picker
  • Create validation scripts for localization string consistency

Changes

Core Infrastructure

  • SupportedLanguage.swift - Language enum with native names, regions, and flags
  • Strings.swift - Strongly-typed LocalizedStringKey accessors for all 180+ strings
  • SettingsView.swift - Language picker with search and preview

Languages Supported (40 total)

Arabic, Catalan, Chinese (Simplified, Traditional, Hong Kong), Croatian, Czech, Danish, Dutch, English (US, UK, AU), Finnish, French (France, Canada), German, Greek, Hebrew, Hindi, Hungarian, Indonesian, Italian, Japanese, Korean, Malay, Norwegian, Polish, Portuguese (Brazil, Portugal), Romanian, Russian, Slovak, Slovenian, Spanish (Spain, Latin America), Swedish, Thai, Turkish, Ukrainian, Vietnamese

Developer Tooling

  • scripts/localization/validate-strings.sh - Validates all language files have matching keys
  • scripts/localization/extract-to-json.sh - Extracts strings for translation services
  • scripts/localization/generate-translations.sh - Regenerates .strings files from JSON
  • .claude/commands/localization.md - AI assistant localization guidelines
  • CONTRIBUTING.md - Contributor documentation including localization workflow

Test plan

  • Build completes without warnings
  • Language picker displays in Settings > Language
  • Switching languages updates UI strings
  • Run ./scripts/localization/validate-strings.sh - all 40 files should match

🤖 Generated with Claude Code

David Collie and others added 7 commits January 14, 2026 17:11
- Add Language section to Settings > General
- Support all 40+ macOS languages with native names
- Searchable dropdown with English name, native name, and code
- System Default option to follow macOS preference
- Language override via AppleLanguages UserDefaults
- Fix: add termqmcp to .gitignore

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Move Language section to bottom of Settings > General
- Add "Current" label showing selected language above search
- Keeps restart requirement (industry standard)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Expand Strings.swift with ~200 localization keys
- Update en.lproj/Localizable.strings with all translations
- Add translation template files for 39 additional languages
- Create localization management scripts:
  - generate-translations.sh: Create translation templates
  - validate-strings.sh: Validate all languages have matching keys
  - extract-to-json.sh: Export strings for LLM translation
- Update .claude/commands/localization.md with full language list
- Update CONTRIBUTING.md with localization workflow
- Add localization validation to code hygiene checklist

Supported languages: English (US/UK/AU), Spanish (ES/LATAM),
French (FR/CA), German, Italian, Portuguese (BR/PT), Dutch,
Swedish, Danish, Finnish, Norwegian, Polish, Russian, Ukrainian,
Czech, Slovak, Hungarian, Romanian, Croatian, Slovenian, Greek,
Turkish, Hebrew, Arabic, Thai, Vietnamese, Indonesian, Malay,
Chinese (Simplified/Traditional/HK), Japanese, Korean, Hindi,
and Catalan.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Reviewed and validated all localization files ensuring consistency
and accuracy across Western European, Nordic, Eastern European,
Mediterranean, Middle Eastern, Asian, and regional variants.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extracted language picker into separate view to fix SwiftLint
type_body_length violation (was 532 lines, max is 500).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add PRE-PUSH / PRE-PR REQUIREMENTS section
- Require local build, format, lint, and test before any push
- Document that local/CI discrepancies are bugs to investigate
- Reduces wasted CI resources and environmental impact

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Rename CLI from 'termq' to 'termqcli' to fix macOS case-insensitive filesystem collision
- Update CLIInstaller paths from termq to termqcli
- Localize command palette actions and menu items
- Fix 'Open' badge translation (was 'Open Terminal')
- Rename L() helper to localized() to fix SwiftLint identifier_name violation
- Add DEVELOPER_DIR instructions to CLAUDE.md for local toolchain issues
- Update all 40 language files with new keys and CLI description

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@eyelock eyelock force-pushed the chore/localization branch from 45688b4 to 902c1b2 Compare January 14, 2026 20:53
@eyelock eyelock merged commit 6efd88a into main Jan 14, 2026
5 checks passed
@eyelock eyelock deleted the chore/localization branch January 14, 2026 20:58
eyelock pushed a commit that referenced this pull request Jan 16, 2026
The original implementation created a Bundle from .lproj folder paths,
but Bundle.localizedString() doesn't work on such pseudo-bundles since
they lack proper bundle metadata (Info.plist, etc).

This fix adds a custom .strings file parser that:
- Handles both /* */ and // style comments
- Properly parses "key" = "value"; format
- Handles escaped characters (\", \n, \\)
- Caches parsed strings for the user's preferred language

The bug was latent since the original i18n PR (#27) and only manifested
when a user explicitly set a non-system-default language preference.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
eyelock pushed a commit that referenced this pull request Jan 16, 2026
The original implementation created a Bundle from .lproj folder paths,
but Bundle.localizedString() doesn't work on such pseudo-bundles since
they lack proper bundle metadata (Info.plist, etc).

This fix adds a custom .strings file parser that:
- Handles both /* */ and // style comments
- Properly parses "key" = "value"; format
- Handles escaped characters (\", \n, \\)
- Caches parsed strings for the user's preferred language

The bug was latent since the original i18n PR (#27) and only manifested
when a user explicitly set a non-system-default language preference.

Also adds comprehensive localization tests to prevent regression:
- Parser edge case tests (comments, escaping, empty lines)
- English and Danish translation verification
- Multi-language file parsing validation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
eyelock added a commit that referenced this pull request Jan 16, 2026
The original implementation created a Bundle from .lproj folder paths,
but Bundle.localizedString() doesn't work on such pseudo-bundles since
they lack proper bundle metadata (Info.plist, etc).

This fix adds a custom .strings file parser that:
- Handles both /* */ and // style comments
- Properly parses "key" = "value"; format
- Handles escaped characters (\", \n, \\)
- Caches parsed strings for the user's preferred language

The bug was latent since the original i18n PR (#27) and only manifested
when a user explicitly set a non-system-default language preference.

Also adds comprehensive localization tests to prevent regression:
- Parser edge case tests (comments, escaping, empty lines)
- English and Danish translation verification
- Multi-language file parsing validation

Co-authored-by: David Collie <support@eyelock.net>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
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