Skip to content

Conversation

@goofmint
Copy link
Owner

@goofmint goofmint commented Nov 21, 2025

Summary by CodeRabbit

  • New Features
    • Multilingual interface (English/Japanese) with a persistent language switcher in the top navigation and a GitHub link.
    • Automatic detection and redirect to the appropriate language path via cookie or browser preference.
    • Manual language selection persisted across sessions and reflected in URLs.
    • Localized welcome screen, translated UI labels, rules panel, and a localized code-language selector.

✏️ Tip: You can customize this high-level summary in your review settings.

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.
@coderabbitai
Copy link

coderabbitai bot commented Nov 21, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds language-aware routing and detection: a root loader redirects to /en or /ja (cookie or Accept-Language), a dynamic :lang route with validation and localized metadata, a Header component with language switcher and GitHub link, and a Welcome component refactored to accept a lang prop and use i18n keys.

Changes

Cohort / File(s) Summary
Root Loader
app/routes/_index.tsx
New loader that reads lang cookie or Accept-Language and issues a redirect to /en or /ja.
Dynamic Language Route
app/routes/$lang.tsx
New route exporting meta, loader, and default component; validates :lang param (en/ja), redirects on invalid values, and supplies loaderData.lang to the page.
Route Registry
app/routes.ts
Import route added and exported route array expanded to include index('_index.tsx') and route(':lang', 'routes/$lang.tsx').
Header Component
app/components/Header.tsx
New React Header({ currentLang }: HeaderProps) component rendering title, language switcher (calls saveLanguage, sets cookie, navigates to /en//ja), and GitHub link (inline SVG).
Localized Welcome
app/welcome/welcome.tsx
Welcome signature changed to accept lang: SupportedLanguage; UI replaced with i18n-driven content (title, catchphrase, rules, code language selector, start button) via t(lang).
Locale Files
app/locales/en.json, app/locales/ja.json
Added catchphrase and rules.* keys and removed label.uiLanguage; localization entries updated to support the new Welcome UI.

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)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay attention to cookie handling and expiry when saveLanguage is called in Header.
  • Verify Accept-Language parsing and fallbacks in _index.tsx.
  • Confirm $lang.tsx meta/loader redirect behavior for invalid params and that loaderData.lang is used consistently.
  • Ensure Welcome prop change is propagated to all usages.

Possibly related PRs

Poem

🐰 I hopped from root to /en and /ja,
I left a cookie trail to show the way,
Links and headers swing in bilingual cheer,
Welcome speaks softly in tongues both near,
A little rabbit claps: "Good code, hooray!"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: adding i18n support with a language switcher component, routing for language-specific paths, and localized content across the application.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae1188b and 66a7129.

📒 Files selected for processing (1)
  • app/components/Header.tsx (1 hunks)

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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: 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 resources array 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.lang to SupportedLanguage without validation. While the loader validates the parameter before rendering, the meta function 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

📥 Commits

Reviewing files that changed from the base of the PR and between 450e03b and 1e829ca.

📒 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 lang parameter 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 LanguageHome component correctly passes the loader data to both the Header and Welcome components, 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.
@goofmint goofmint merged commit 44b6fa9 into main Nov 21, 2025
Copy link

@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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e829ca and ae1188b.

📒 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.lang and passes it to both Header and Welcome components. 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.json and ja.json contain identical key structures (21 keys each). The new additions (catchphrase and rules.* keys) are properly present in both files with no mismatches detected.

Comment on lines +10 to +22
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!',
},
];
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

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.

3 participants