Skip to content

feat: Full localization support (8 languages)#495

Open
Lincb522 wants to merge 6 commits intosteipete:mainfrom
Lincb522:feat/localization
Open

feat: Full localization support (8 languages)#495
Lincb522 wants to merge 6 commits intosteipete:mainfrom
Lincb522:feat/localization

Conversation

@Lincb522
Copy link

@Lincb522 Lincb522 commented Mar 8, 2026

Summary

  • Add complete localization infrastructure using Apple String Catalog (.xcstrings)
  • Support 8 languages: English, 简体中文, 繁體中文, 日本語, 한국어, Français, Deutsch, Español
  • 437 translation keys covering all user-visible strings across ~45 source files
  • Per-app language switching in General preferences (System Default or manual selection)
  • package_app.sh updated to compile .xcstrings into .lproj bundles and inject CFBundleLocalizations into Info.plist

What's included

Infrastructure

  • Package.swift: Added defaultLocalization: "en" and Widget resources config
  • Sources/CodexBar/Resources/Localizable.xcstrings: Main app string catalog (437 keys)
  • Sources/CodexBarWidget/Resources/Localizable.xcstrings: Widget string catalog
  • Sources/CodexBar/AppLanguage.swift: Language enum + UserDefaults AppleLanguages switching
  • SettingsStore changes: Language preference persistence + apply on launch
  • PreferencesGeneralPane.swift: Language picker with restart prompt

String wrapping

  • All 19 Provider implementation files + 5 login flow files
  • 6 Preferences panes (General, Display, Advanced, About, Debug, Providers)
  • Provider detail/sidebar views
  • Menu bar: MenuDescriptor, StatusItemController+Menu, MenuCardView
  • Charts: CostHistoryChartMenuView, CreditsHistoryChartMenuView
  • Status: UsagePaceText, UsageStoreSupport, StatusItemController+Actions
  • Overview tab, cookie source UI

What's NOT localized (by design)

  • Provider brand names (Codex, Claude, Cursor, etc.)
  • Technical terms (API, CLI, MCP, OAuth, Keychain)
  • Brand name "CodexBar"

Notes

  • Based on latest main (commit 9d56dfd). All new providers (Kilo, OpenRouter, Ollama) are included.
  • Builds successfully with swift build.
  • Native speaker review for translations is welcome.

Test plan

  • swift build passes
  • Verify language switching works (General → Language → select non-English → restart)
  • Verify .lproj bundles are generated by package_app.sh
  • Spot-check translations in zh-Hans, ja, and other languages

Lincb522 added 2 commits March 8, 2026 22:10
- Add Localizable.xcstrings with 437 translation keys for en, zh-Hans,
  zh-Hant, ja, ko, fr, de, es
- Add AppLanguage enum with per-app language switching via UserDefaults
- Add language picker to General preferences with restart prompt
- Configure Package.swift with defaultLocalization and Widget resources
- Update package_app.sh to compile .xcstrings into .lproj bundles and
  inject CFBundleLocalizations into Info.plist
Apply localization to ~45 source files:
- All Provider implementation and login flow files (19 providers)
- Preferences panes (General, Display, Advanced, About, Debug, Providers)
- Provider detail/sidebar/error views
- Menu bar items and status item controller
- Menu card, cost/credits history charts, usage pace text
- Status indicators, overview tab, and cookie source UI

Translations provided by AI; native speaker review welcome.
Copy link

@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: 709d1847e7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +387 to +388
xcrun xcstringstool compile "$xcs" --output-directory "$PARENT_DIR" 2>/dev/null || true
rm -f "$xcs"

Choose a reason for hiding this comment

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

P1 Badge Fail build when xcstring compilation fails

In Scripts/package_app.sh, xcstringstool failures are explicitly ignored (|| true) and the source catalog is deleted immediately afterward. If xcrun/xcstringstool is unavailable or a catalog has a compile error, packaging still succeeds but all localized resources from that .xcstrings file are silently dropped from the shipped app, which makes localization regressions hard to detect and ship.

Useful? React with 👍 / 👎.

.font(.footnote)
Spacer()
Text(String(format: "%.0f%% used", min(100, max(0, self.section.percentUsed))))
Text(String(localized: "\(Int(min(100, max(0, self.section.percentUsed))))% used"))

Choose a reason for hiding this comment

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

P2 Badge Preserve rounded percentage when localizing usage text

This replaces rounded formatting (%.0f) with Int(...), which truncates instead of rounds. For non-integer percentages (for example, 99.9), the UI now displays 99% used rather than 100% used, so the extra-usage indicator can underreport spend near thresholds.

Useful? React with 👍 / 👎.

Lincb522 added 2 commits March 8, 2026 22:24
- Fail the build when xcstringstool compile fails instead of silently
  discarding localized resources (P1)
- Use .rounded() before Int conversion so 99.9% displays as 100% instead
  of being truncated to 99% (P2)
Send a system notification when any provider's usage reaches a
configurable percentage (50/80/90/95%). The threshold is selectable
in Preferences → General → Automation and defaults to off.

The alert fires once per threshold crossing and resets when usage
drops below the threshold again.

Includes localized strings for all 8 languages.
@Lincb522 Lincb522 force-pushed the feat/localization branch from 6b9acdb to 533d488 Compare March 9, 2026 03:32
- MenuDescriptor: menu actions, account/plan labels, quota text
- MenuCardView: percent labels, API key limit, status messages
- KeychainPromptCoordinator: all alert titles and messages
- Provider implementations: placeholders, action labels, status text
- Chart views: axis labels (Day, Credits, Cost, Service, Cap)
- Window titles: Cursor Login, Buy Credits
- Error view: Copy error, Show/Hide details
- 70+ newly localized strings with translations for all 8 languages

Made-with: Cursor
@ratulsarna
Copy link
Collaborator

Thanks for PR @Lincb522 ! I have some comments, please take a look:

  1. xcstringstool failures should definitely fail packaging. Otherwise we can silently ship missing localizations.

  2. Please make sure CFBundleLocalizations stays in sync with the actual generated .lproj folders. I’d rather derive it than maintain it separately.

  3. AppleLanguages switching feels a bit brittle here — especially for menus/widgets/helpers. Have we verified consistent behavior across relaunch for all bundles?

Instead of hardcoding the language list in Info.plist, scan the
compiled .lproj directories in the app bundle and inject
CFBundleLocalizations dynamically via PlistBuddy. This ensures the
declared languages always match the actually available translations.

Made-with: Cursor
@Lincb522
Copy link
Author

Lincb522 commented Mar 12, 2026

Hi @ratulsarna, thanks for reviewing! I've addressed your feedback:

1. xcstringstool failures should fail packaging
This was already fixed in an earlier commit — || true and 2>/dev/null have been removed, so packaging now fails with an error if xcstringstool compile doesn't succeed.

2. Derive CFBundleLocalizations from actual .lproj folders
Just pushed a fix for this — package_app.sh now scans for *.lproj directories after xcstringstool compile and injects the language codes into CFBundleLocalizations via PlistBuddy. The hardcoded language list has been removed.

3. AppleLanguages switching across bundles
Good question. The per-app switch uses UserDefaults.standard["AppleLanguages"], which is Apple's standard mechanism. Here's the behavior:

  • Main app — picks up the correct .lproj on launch; restart prompt ensures clean reload
  • Widget extension — runs in a separate process with its own Localizable.xcstrings compiled into .lproj; follows the system language (standard macOS widget behavior)
  • Helper binaries (CLI, watchdog) — no user-facing localized strings

I've tested language switching with restart and it works consistently for the main app. The widget follows the system locale, which matches how other macOS apps handle it.

Please let me know if anything else needs to be changed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants