-
Notifications
You must be signed in to change notification settings - Fork 0
Add multilingual support with language switcher #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add multilingual support with language switcher #3
Conversation
This commit implements the multilingual foundation for Bug Sniper, allowing users to switch between Japanese and English languages. Changes: - Added language-specific routing (/ja, /en) - Created Header component with language switcher using flag emojis (🇯🇵/🇬🇧) - Implemented browser language detection with fallback to Accept-Language header - Added cookie-based language persistence - Updated routing structure to support dynamic language paths - Modified Welcome component to display UI in selected language - Root path (/) now redirects to appropriate language route based on browser settings The implementation follows BASIC.md specification: - Default language is determined by browser settings - Japanese redirects to /ja, English to /en - Language can be switched via header flag icons
Update routes configuration to support dynamic language paths. This file was inadvertently not included in the previous commit.
|
Caution Review failedThe pull request is closed. WalkthroughAdds language-aware routing and detection: a root loader redirects to Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant B as Browser
participant Root as _index.tsx (loader)
participant Routes as routes.ts
participant LangRoute as $lang.tsx (loader/meta)
participant UI as LanguageHome (Header + Welcome)
B->>Root: GET /
Root->>Root: read Cookie 'lang' or Accept-Language
alt lang resolved (cookie or header)
Root->>B: 302 Redirect to /en or /ja
else fallback
Root->>B: 302 Redirect to /en
end
B->>Routes: GET /en or /ja
Routes->>LangRoute: invoke loader/meta for :lang
LangRoute->>LangRoute: validate lang (en|ja) or redirect to /
LangRoute->>UI: return loaderData.lang
UI->>B: 200 Render Header(currentLang) and Welcome(lang)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/welcome/welcome.tsx (1)
47-87: Remove unused code.The
resourcesarray is defined but never used in the component. This dead code should be removed to maintain codebase cleanliness.Apply this diff to remove the unused code:
- -const resources = [ - { - href: 'https://reactrouter.com/docs', - text: 'React Router Docs', - icon: ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="24" - height="20" - viewBox="0 0 20 20" - fill="none" - className="stroke-gray-600 group-hover:stroke-current dark:stroke-gray-300" - > - <path - d="M9.99981 10.0751V9.99992M17.4688 17.4688C15.889 19.0485 11.2645 16.9853 7.13958 12.8604C3.01467 8.73546 0.951405 4.11091 2.53116 2.53116C4.11091 0.951405 8.73546 3.01467 12.8604 7.13958C16.9853 11.2645 19.0485 15.889 17.4688 17.4688ZM2.53132 17.4688C0.951566 15.8891 3.01483 11.2645 7.13974 7.13963C11.2647 3.01471 15.8892 0.951453 17.469 2.53121C19.0487 4.11096 16.9854 8.73551 12.8605 12.8604C8.73562 16.9853 4.11107 19.0486 2.53132 17.4688Z" - strokeWidth="1.5" - strokeLinecap="round" - /> - </svg> - ), - }, - { - href: 'https://rmx.as/discord', - text: 'Join Discord', - icon: ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="24" - height="20" - viewBox="0 0 24 20" - fill="none" - className="stroke-gray-600 group-hover:stroke-current dark:stroke-gray-300" - > - <path - d="M15.0686 1.25995L14.5477 1.17423L14.2913 1.63578C14.1754 1.84439 14.0545 2.08275 13.9422 2.31963C12.6461 2.16488 11.3406 2.16505 10.0445 2.32014C9.92822 2.08178 9.80478 1.84975 9.67412 1.62413L9.41449 1.17584L8.90333 1.25995C7.33547 1.51794 5.80717 1.99419 4.37748 2.66939L4.19 2.75793L4.07461 2.93019C1.23864 7.16437 0.46302 11.3053 0.838165 15.3924L0.868838 15.7266L1.13844 15.9264C2.81818 17.1714 4.68053 18.1233 6.68582 18.719L7.18892 18.8684L7.50166 18.4469C7.96179 17.8268 8.36504 17.1824 8.709 16.4944L8.71099 16.4904C10.8645 17.0471 13.128 17.0485 15.2821 16.4947C15.6261 17.1826 16.0293 17.8269 16.4892 18.4469L16.805 18.8725L17.3116 18.717C19.3056 18.105 21.1876 17.1751 22.8559 15.9238L23.1224 15.724L23.1528 15.3923C23.5873 10.6524 22.3579 6.53306 19.8947 2.90714L19.7759 2.73227L19.5833 2.64518C18.1437 1.99439 16.6386 1.51826 15.0686 1.25995ZM16.6074 10.7755L16.6074 10.7756C16.5934 11.6409 16.0212 12.1444 15.4783 12.1444C14.9297 12.1444 14.3493 11.6173 14.3493 10.7877C14.3493 9.94885 14.9378 9.41192 15.4783 9.41192C16.0471 9.41192 16.6209 9.93851 16.6074 10.7755ZM8.49373 12.1444C7.94513 12.1444 7.36471 11.6173 7.36471 10.7877C7.36471 9.94885 7.95323 9.41192 8.49373 9.41192C9.06038 9.41192 9.63892 9.93712 9.6417 10.7815C9.62517 11.6239 9.05462 12.1444 8.49373 12.1444Z" - strokeWidth="1.5" - /> - </svg> - ), - }, -];
🧹 Nitpick comments (2)
app/routes/_index.tsx (1)
7-20: Consider using a cookie parsing utility.Manual cookie parsing is functional but could be simplified and made more robust with a utility library or helper function. This reduces the risk of parsing edge cases incorrectly.
Consider using a cookie parsing utility:
import { parse as parseCookie } from 'cookie'; // or similar library export function loader({ request }: Route.LoaderArgs) { const cookieHeader = request.headers.get('Cookie'); const cookies = cookieHeader ? parseCookie(cookieHeader) : {}; const langFromCookie = cookies.lang; // ... rest of logic }app/routes/$lang.tsx (1)
10-22: Consider validating lang parameter in meta function.Line 11 performs an unsafe type assertion on
params.langtoSupportedLanguagewithout validation. While the loader validates the parameter before rendering, themetafunction runs independently and could receive invalid values.Consider adding validation or using a safe fallback:
export function meta({ params }: Route.MetaArgs) { - const lang = params.lang as SupportedLanguage; + const lang = (params.lang === 'ja' || params.lang === 'en') ? params.lang : 'en'; return [ { title: 'Bug Sniper' }, { name: 'description', content: lang === 'ja' ? 'コードレビューゲーム - バグを見つけてスコアを競おう!' : 'Code review game - Find bugs and compete for high scores!', }, ]; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
app/components/Header.tsx(1 hunks)app/routes.ts(1 hunks)app/routes/$lang.tsx(1 hunks)app/routes/_index.tsx(1 hunks)app/welcome/welcome.tsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
app/components/Header.tsx (1)
app/locales/index.ts (2)
SupportedLanguage(8-8)saveLanguage(62-66)
app/routes/_index.tsx (1)
app/routes/$lang.tsx (1)
loader(24-36)
app/routes/$lang.tsx (4)
app/locales/index.ts (1)
SupportedLanguage(8-8)app/routes/_index.tsx (1)
loader(7-37)app/components/Header.tsx (1)
Header(8-69)app/welcome/welcome.tsx (1)
Welcome(3-45)
app/welcome/welcome.tsx (1)
app/locales/index.ts (2)
SupportedLanguage(8-8)t(21-24)
🔇 Additional comments (7)
app/welcome/welcome.tsx (2)
1-3: LGTM! Clean i18n integration.The imports and updated function signature correctly integrate language support. The typed
langparameter ensures type safety throughout the component.
5-41: LGTM! Comprehensive i18n implementation.The component correctly uses the
t()function for all user-facing strings, and the disabled UI language selector appropriately reflects the current language from the route.app/routes/_index.tsx (1)
22-36: LGTM! Clean language detection logic.The implementation correctly prioritizes the cookie over the Accept-Language header and validates the language value before redirecting. The fallback chain ensures users always land on a valid language route.
app/components/Header.tsx (1)
17-68: LGTM! Well-structured header with good accessibility.The header implementation includes proper accessibility attributes (
aria-label,title) and clean styling. Note that flag emojis (🇯🇵, 🇬🇧) may render inconsistently across different platforms and browsers, but this is an acceptable design trade-off for simplicity.app/routes.ts (1)
1-6: LGTM! Clean route configuration.The route configuration correctly defines the index route for language detection and a dynamic language parameter route. The implementation follows React Router conventions and is type-safe.
app/routes/$lang.tsx (2)
24-36: LGTM! Proper validation and type narrowing.The loader correctly validates the language parameter and redirects invalid values. The type assertion on Line 33 is safe because of the validation guard on Lines 28-30.
38-45: LGTM! Clean component composition.The
LanguageHomecomponent correctly passes the loader data to both theHeaderandWelcomecomponents, creating a well-structured language-aware page.
Updated the home page to provide a better user experience: Changes: - Added catchphrase below title: "60秒間バグを見つけ続けろ!" (Find bugs for 60 seconds!) - Added game rules section with clear instructions - Removed UI language selector (language switching now done via header) - Removed Cloudflare debug message - Simplified Welcome component props by removing unused message parameter - Updated locale files (ja.json, en.json) with new text keys The home page now provides clear information about game rules before users start playing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
app/locales/en.json(2 hunks)app/locales/ja.json(2 hunks)app/routes/$lang.tsx(1 hunks)app/welcome/welcome.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- app/welcome/welcome.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
app/routes/$lang.tsx (4)
app/locales/index.ts (1)
SupportedLanguage(8-8)app/routes/_index.tsx (1)
loader(7-37)app/components/Header.tsx (1)
Header(8-69)app/welcome/welcome.tsx (1)
Welcome(3-56)
🔇 Additional comments (4)
app/locales/ja.json (1)
3-3: LGTM! Japanese translations look appropriate.The Japanese localization keys mirror the English structure and provide natural translations for the game interface. The translations are idiomatic and convey the intended meaning effectively.
Also applies to: 18-22
app/routes/$lang.tsx (2)
24-35: LGTM! Loader validation is solid.The loader correctly validates the language parameter and redirects to the root route for invalid languages. The type assertion on line 33 is safe because it occurs after validation. Using
throw redirect('/')is the correct pattern for React Router v7.
37-44: LGTM! Component implementation is clean.The component correctly uses the validated
loaderData.langand passes it to bothHeaderandWelcomecomponents. The implementation is straightforward and follows React best practices.app/locales/en.json (1)
3-3: Localization keys are consistent across both locale files.Both
en.jsonandja.jsoncontain identical key structures (21 keys each). The new additions (catchphraseandrules.*keys) are properly present in both files with no mismatches detected.
| export function meta({ params }: Route.MetaArgs) { | ||
| const lang = params.lang as SupportedLanguage; | ||
| return [ | ||
| { title: 'Bug Sniper' }, | ||
| { | ||
| name: 'description', | ||
| content: | ||
| lang === 'ja' | ||
| ? 'コードレビューゲーム - バグを見つけてスコアを競おう!' | ||
| : 'Code review game - Find bugs and compete for high scores!', | ||
| }, | ||
| ]; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unsafe type assertion in meta function.
Line 11 performs an unsafe type assertion of params.lang to SupportedLanguage without validation. Since the meta function runs before or alongside the loader, the validation in the loader (lines 28-29) does not protect the meta function. If a user accesses an invalid language route (e.g., /fr), the meta function will cast 'fr' to SupportedLanguage, potentially causing type confusion.
Apply this diff to add validation to the meta function:
export function meta({ params }: Route.MetaArgs) {
- const lang = params.lang as SupportedLanguage;
+ const lang = params.lang;
+
+ // Use default language for invalid params
+ const validLang: SupportedLanguage = (lang === 'ja' || lang === 'en') ? lang : 'en';
+
return [
{ title: 'Bug Sniper' },
{
name: 'description',
content:
- lang === 'ja'
+ validLang === 'ja'
? 'コードレビューゲーム - バグを見つけてスコアを競おう!'
: 'Code review game - Find bugs and compete for high scores!',
},
];
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function meta({ params }: Route.MetaArgs) { | |
| const lang = params.lang as SupportedLanguage; | |
| return [ | |
| { title: 'Bug Sniper' }, | |
| { | |
| name: 'description', | |
| content: | |
| lang === 'ja' | |
| ? 'コードレビューゲーム - バグを見つけてスコアを競おう!' | |
| : 'Code review game - Find bugs and compete for high scores!', | |
| }, | |
| ]; | |
| } | |
| export function meta({ params }: Route.MetaArgs) { | |
| const lang = params.lang; | |
| // Use default language for invalid params | |
| const validLang: SupportedLanguage = (lang === 'ja' || lang === 'en') ? lang : 'en'; | |
| return [ | |
| { title: 'Bug Sniper' }, | |
| { | |
| name: 'description', | |
| content: | |
| validLang === 'ja' | |
| ? 'コードレビューゲーム - バグを見つけてスコアを競おう!' | |
| : 'Code review game - Find bugs and compete for high scores!', | |
| }, | |
| ]; | |
| } |
🤖 Prompt for AI Agents
In app/routes/$lang.tsx around lines 10 to 22, avoid the unsafe cast of
params.lang to SupportedLanguage; instead treat params.lang as an
unknown/string, validate it (e.g., check if it equals 'ja' otherwise default to
'en' or check it against your canonical list of supported languages), then use
the validated value to pick the title/description. Replace the direct type
assertion with a small type guard or simple conditional that ensures only known
languages are used in meta and falls back to a safe default so invalid routes
(like /fr) can't cause type confusion.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.