Skip to content

feat(i18n): auto-discover valid locales and harden language menu#425

Open
imAaryash wants to merge 1 commit intosiddharthvaddem:mainfrom
imAaryash:pr-locale-clean
Open

feat(i18n): auto-discover valid locales and harden language menu#425
imAaryash wants to merge 1 commit intosiddharthvaddem:mainfrom
imAaryash:pr-locale-clean

Conversation

@imAaryash
Copy link
Copy Markdown
Contributor

@imAaryash imAaryash commented Apr 12, 2026

Description

This PR improves language onboarding and language switching in the Launch/HUD flow, with a focus on reliability in Electron.

It includes:

  • first-launch system language suggestion (one-time prompt) when detected language is supported and different from default
  • dynamic locale discovery from locale folders (instead of hardcoded locale list)
  • locale validity filtering: a locale is shown only if all required namespace JSON files exist
  • clearer runtime logging for incomplete locale folders (missing namespace files)
  • improved HUD language menu interaction and UX:
    • pointer interaction reliability
    • scroll behavior for long locale lists
    • viewport-aware positioning to avoid clipping/cutoff
  • i18n check script now auto-discovers locale folders
  • launch prompt translation updates for en, es, and zh-CN

Motivation

The language selector and onboarding flow had interaction/clipping issues and required manual locale list maintenance.
This update improves usability and maintainability by:

  • making language selection stable during recording flow
  • preventing menu clipping/scroll issues in the HUD
  • ensuring new locales are only surfaced when structurally complete
  • reducing future maintenance overhead for locale additions

Type of Change

  • Bug Fix
  • Refactor / Code Cleanup
  • UX Improvement

Related Issue(s)

Fixes #356
Partially addresses #363

Summary by CodeRabbit

  • New Features
    • Added a language selection menu with support for system language suggestions.
    • System prompts users once with their device's preferred language if available.
  • Refactor
    • Language support now dynamically adapts based on available locale resources.
  • Style
    • Enhanced language menu interface with improved visual design and interactions.

- discover locales from folder structure and validate required namespaces

- keep incomplete locales out of selectors and system-language suggestion

- auto-discover locale folders in i18n-check

- fix launch language menu interaction, clipping, and scrolling
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 12, 2026

📝 Walkthrough

Walkthrough

This PR transitions the app from static locale hardcoding to dynamic runtime locale discovery. It adds system language detection that prompts users to switch to their system locale on first visit, replaces the language select with an interactive menu UI, and validates locale completeness based on namespace availability.

Changes

Cohort / File(s) Summary
I18n Infrastructure
scripts/i18n-check.mjs, src/i18n/config.ts, src/i18n/loader.ts, src/components/video-editor/VideoEditor.tsx
Dynamic locale enumeration replaces hardcoded SUPPORTED_LOCALES. Runtime validation filters locales by namespace completeness. Type broadened from union to string. New exports: getAvailableLocales(), getLocaleValidationErrors().
I18n Context & State
src/contexts/I18nContext.tsx
Added system locale detection from navigator.languages, one-time suggestion prompt logic with localStorage persistence, and handlers for accepting/dismissing suggestion. New state fields in I18nContextValue.
Language Menu UI
src/components/launch/LaunchWindow.tsx, src/components/launch/LaunchWindow.module.css
Replaced static language select with interactive language menu (icon-triggered, portal-rendered, positioned dynamically). Added system language suggestion prompt with localized title/description and action buttons. Menu includes keyboard/click-outside close behavior.
UI Component Enhancements
src/components/ui/dropdown-menu.tsx, src/components/ui/select.tsx
DropdownMenuContent now accepts optional portalled prop. SelectContent now accepts showScrollButtons and viewportClassName props for finer control over rendering and styling.
Locale Translations
src/i18n/locales/en/launch.json, src/i18n/locales/es/launch.json, src/i18n/locales/zh-CN/launch.json
Added systemLanguagePrompt object with localized title, description (with {{language}} interpolation), and action labels (switch, keepDefault).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🌍 no more hardcoded locales, let them flow—
detect the system whisper, steal the show 🎤
a menu springs to life, positioning with grace
zh-CN, español, français light up the place ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the main changes: auto-discovery of locales and language menu improvements, which aligns directly with the changeset.
Linked Issues check ✅ Passed PR directly addresses issue #356 by implementing system language auto-detection, dynamic locale discovery, and accessible language menu in the Launch flow.
Out of Scope Changes check ✅ Passed All changes are in-scope: i18n infrastructure, locale discovery, language menu UI/UX, and related component updates directly support the objectives.
Description check ✅ Passed PR description covers purpose, motivation, type of change, and linked issues comprehensively, though lacks explicit testing steps and screenshots.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

@imAaryash
Copy link
Copy Markdown
Contributor Author

heyy @siddharthvaddem created a new PR for this #362 the older one got polluted

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: 2a15e5fd8e

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/contexts/I18nContext.tsx (1)

101-111: ⚠️ Potential issue | 🟡 Minor

Guard setLocale before persisting.

Lowkey risky as-is: Locale is just string, so any caller can push an unsupported locale through here. The real danger is something like VideoEditor.tsx:1707 casting e.target.value as Locale without validation—that goes straight to localStorage, document.documentElement.lang, and the Electron API.

You already have isSupportedLocale() as a type guard in this file. A quick check before persisting would keep the provider honest and match the pattern you're using elsewhere.

nit: cleaner fix
 const setLocale = useCallback((newLocale: Locale) => {
+	if (!isSupportedLocale(newLocale)) return;
 	setLocaleState(newLocale);
 	try {
 		localStorage.setItem(LOCALE_STORAGE_KEY, newLocale);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contexts/I18nContext.tsx` around lines 101 - 111, Validate the incoming
locale with the existing isSupportedLocale() before applying side effects: in
setLocale, call isSupportedLocale(newLocale) and only then call
setLocaleState(newLocale), persist to localStorage (LOCALE_STORAGE_KEY), set
document.documentElement.lang, and invoke
window.electronAPI?.setLocale?.(newLocale); if the check fails, no-op (or
optionally log/return) to avoid persisting/propagating unsupported strings.
Ensure you reference the setLocale function and use the isSupportedLocale type
guard already defined in this module.
🧹 Nitpick comments (2)
src/i18n/config.ts (1)

12-12: Consider a small locale-normalization helper at the boundary.

Locale = string makes sense for dynamic discovery, but it also makes invalid locale values easier to pass around. a tiny normalizeLocale/isAvailableLocale gate in the context boundary would keep this safer and cleaner.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/i18n/config.ts` at line 12, Replace the open-ended Locale = string usage
by adding a small normalization/validation layer at the i18n boundary: implement
a normalizeLocale(locale: string): Locale function that canonicalizes values
(e.g., lower-case, underscore→dash, strip whitespace) and an
isAvailableLocale(locale: string): boolean that checks against the
discovered/allowed locales list; then update places that accept raw locale
strings to call normalizeLocale and gate with isAvailableLocale (e.g., where the
context is created or getLocaleFromRequest is used) so only normalized,
validated Locale values propagate through the app.
src/components/launch/LaunchWindow.tsx (1)

628-660: role="menu" with role="menuitemradio" is basically making promises you're not keeping here.

The semantics announce a full keyboard-accessible menu—arrow keys for navigation, Enter to select, Escape to close, focus movement into the menu and back out—but looking at the implementation, there's none of that. No arrow-key handlers, no focus trapping on open, no focus return to the trigger on close. Users who rely on keyboard nav are kinda out of luck here.

You've got two clean paths forward:

  1. Add proper roving focus – Move focus into the menu when it opens (to first item), handle ArrowUp/ArrowDown/Home/End, trap focus while menu is open, return to the trigger on close. This is more work but the menu pattern would then be honest.

  2. Switch to role="radiogroup" or role="listbox" – These patterns don't expect the full menu keyboard model, just arrow keys + Enter/Space, which is closer to what you're already doing with the click-to-select buttons.

Pick one or the other; the current state is lowkey risky accessibility-wise since assistive tech users will trust the roles and get disappointed by missing interactions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/launch/LaunchWindow.tsx` around lines 628 - 660, The current
menu uses role="menu" and role="menuitemradio" but lacks the required
keyboard/focus behavior; either implement full roving-focus/menu keyboard
handling or change the ARIA pattern to match current behavior. Option A
(preferred quick fix): replace role="menu" with role="radiogroup" (or
role="listbox") on the container (languageMenuPanelRef) and keep
role="menuitemradio" on each button/option, ensure ArrowUp/ArrowDown and
Enter/Space select the option by wiring keydown handlers on languageMenuPanelRef
that call setLocale(loc) and resolveSystemLocaleSuggestion(), and ensure focus
is set to the selected item when the panel opens (use isLanguageMenuOpen
effect). Option B (full accessibility): keep role="menu" and implement roving
focus and focus trapping when isLanguageMenuOpen is true (move focus into
first/selected item, handle ArrowUp/ArrowDown/Home/End/Escape to navigate/close,
and return focus to the trigger when closed). Use the existing identifiers
availableLocales, setLocale, resolveSystemLocaleSuggestion,
setIsLanguageMenuOpen, languageMenuPanelRef, and getLocaleName to locate and
update the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/i18n/loader.ts`:
- Around line 70-79: The exported getters expose internal arrays directly
allowing callers to mutate module state; update getAvailableLocales and
getLocaleValidationErrors to return defensive copies instead of the original
arrays (e.g., return [DEFAULT_LOCALE] when empty, otherwise return a shallow
copy of availableLocales) and return a shallow copy of localeValidationErrors so
callers cannot push/splice into the module's internal arrays.

---

Outside diff comments:
In `@src/contexts/I18nContext.tsx`:
- Around line 101-111: Validate the incoming locale with the existing
isSupportedLocale() before applying side effects: in setLocale, call
isSupportedLocale(newLocale) and only then call setLocaleState(newLocale),
persist to localStorage (LOCALE_STORAGE_KEY), set document.documentElement.lang,
and invoke window.electronAPI?.setLocale?.(newLocale); if the check fails, no-op
(or optionally log/return) to avoid persisting/propagating unsupported strings.
Ensure you reference the setLocale function and use the isSupportedLocale type
guard already defined in this module.

---

Nitpick comments:
In `@src/components/launch/LaunchWindow.tsx`:
- Around line 628-660: The current menu uses role="menu" and
role="menuitemradio" but lacks the required keyboard/focus behavior; either
implement full roving-focus/menu keyboard handling or change the ARIA pattern to
match current behavior. Option A (preferred quick fix): replace role="menu" with
role="radiogroup" (or role="listbox") on the container (languageMenuPanelRef)
and keep role="menuitemradio" on each button/option, ensure ArrowUp/ArrowDown
and Enter/Space select the option by wiring keydown handlers on
languageMenuPanelRef that call setLocale(loc) and
resolveSystemLocaleSuggestion(), and ensure focus is set to the selected item
when the panel opens (use isLanguageMenuOpen effect). Option B (full
accessibility): keep role="menu" and implement roving focus and focus trapping
when isLanguageMenuOpen is true (move focus into first/selected item, handle
ArrowUp/ArrowDown/Home/End/Escape to navigate/close, and return focus to the
trigger when closed). Use the existing identifiers availableLocales, setLocale,
resolveSystemLocaleSuggestion, setIsLanguageMenuOpen, languageMenuPanelRef, and
getLocaleName to locate and update the code.

In `@src/i18n/config.ts`:
- Line 12: Replace the open-ended Locale = string usage by adding a small
normalization/validation layer at the i18n boundary: implement a
normalizeLocale(locale: string): Locale function that canonicalizes values
(e.g., lower-case, underscore→dash, strip whitespace) and an
isAvailableLocale(locale: string): boolean that checks against the
discovered/allowed locales list; then update places that accept raw locale
strings to call normalizeLocale and gate with isAvailableLocale (e.g., where the
context is created or getLocaleFromRequest is used) so only normalized,
validated Locale values propagate through the app.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b0672418-96f0-4213-8920-e5c08470f9e6

📥 Commits

Reviewing files that changed from the base of the PR and between db10f92 and 2a15e5f.

📒 Files selected for processing (12)
  • scripts/i18n-check.mjs
  • src/components/launch/LaunchWindow.module.css
  • src/components/launch/LaunchWindow.tsx
  • src/components/ui/dropdown-menu.tsx
  • src/components/ui/select.tsx
  • src/components/video-editor/VideoEditor.tsx
  • src/contexts/I18nContext.tsx
  • src/i18n/config.ts
  • src/i18n/loader.ts
  • src/i18n/locales/en/launch.json
  • src/i18n/locales/es/launch.json
  • src/i18n/locales/zh-CN/launch.json

@imAaryash
Copy link
Copy Markdown
Contributor Author

Hey @siddharthvaddem, also a small suggestion you should create a GitHub organization for OpenScreen and transfer the repo to it. It'll give the project a more professional presence, help it reach more people, and let you assign maintainers. I'd love to contribute as a maintainer if you're open to it! 😊

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.

screen recorder how tu set it up chinese?

1 participant