Skip to content

Conversation

@goofmint
Copy link
Owner

@goofmint goofmint commented Nov 22, 2025

  • Add ranking route with loader for SSR
  • Query top 50 scores from last 7 days from D1
  • Add code language filter tabs (all, javascript, python, php, ruby, java, dart)
  • Add ranking translations for ja and en locales
  • Display ranking with player name, score, accuracy, and date
  • Add HTML title with i18n support
  • Link to individual result pages from ranking

Summary by CodeRabbit

  • New Features

    • Added a language-aware ranking page showing top scores from the past seven days with optional code-language filtering.
    • Ranking page reachable from the welcome screen and from score result pages (links now respect selected language).
    • Full English and Japanese translations for ranking UI and messages.
  • Bug Fixes

    • Minor JSON syntax corrections in localization files.

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

- Add ranking route with loader for SSR
- Query top 50 scores from last 7 days from D1
- Add code language filter tabs (all, javascript, python, php, ruby, java, dart)
- Add ranking translations for ja and en locales
- Display ranking with player name, score, accuracy, and date
- Add HTML title with i18n support
- Link to individual result pages from ranking
@coderabbitai
Copy link

coderabbitai bot commented Nov 22, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds a language-aware ranking page: new route and route file, loader that queries top-50 scores from the last 7 days (optional code_language filter), localization keys for ranking in English and Japanese, and updates navigation links to use language-prefixed routes.

Changes

Cohort / File(s) Summary
Localization
app/locales/en.json, app/locales/ja.json
Add ranking.* keys (title, period, period.week, rank, player, score, accuracy, date, codeLanguage, noData, anonymous); adjust JSON punctuation to include new entries.
Routing config
app/routes.ts
Add new route mapping route(':lang/ranking', 'routes/$lang.ranking.tsx') to RouteConfig.
Ranking page
app/routes/$lang.ranking.tsx
New route file: meta computes localized title; loader validates :lang, reads optional code filter, queries DB for top-50 scores in last 7 days; exports types and default React component rendering localized ranking table, language tabs, date formatting, top-3 badges, and no-data fallback.
Navigation updates
app/routes/result.$id.tsx, app/welcome/welcome.tsx
Replace hard-coded /ranking with language-prefixed /${lang}/ranking; add ranking link/button in Welcome using i18n label.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Browser
    participant Route as "/:lang/ranking"
    participant Loader
    participant DB as Database
    participant UI as Ranking UI

    User->>Browser: Open /{lang}/ranking
    Browser->>Route: request route
    Route->>Loader: invoke loader(params, request, context)
    Loader->>Loader: validate lang (ja|en)\nparse code filter
    Loader->>DB: query top 50 scores (last 7 days, optional code_language)
    DB-->>Loader: scores[]
    Loader-->>Route: return { scores, codeLanguage, lang }
    Route->>UI: render with useLoaderData()
    UI-->>User: display localized table or no-data message

    rect rgba(120,180,160,0.08)
    Note over UI: highlights: top-3 badges, locale-aware date formatting, language tabs
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay attention to: SQL query correctness (date range, limit, ordering), proper escaping/parameterization, loader lang validation and edge cases, and consistent use of new i18n keys in component and JSON files.

Possibly related PRs

Poem

🐰 I hopped the rails to find the ranks,
Scores sparkling like carrot-crisped banks.
In Japanese and English I sing,
Top-three badges make my heart spring—
Hop on, view the board, and clap your hands! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% 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 'Implement ranking feature with SSR' accurately and concisely summarizes the main change—adding a new ranking feature with server-side rendering support.

📜 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 76c32b5 and 3909650.

📒 Files selected for processing (1)
  • app/routes/$lang.ranking.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: 2

🧹 Nitpick comments (2)
app/routes/ranking.tsx (2)

28-65: Consider adding database indexes for query performance.

The ranking queries filter by created_at, code_language, and order by score DESC. For optimal performance with the top-50 queries, ensure the scores table has appropriate indexes:

  1. For the "all languages" query: Index on (created_at, score DESC)
  2. For specific language queries: Index on (code_language, created_at, score DESC)

Example index creation (run these as migrations):

-- For all languages query
CREATE INDEX IF NOT EXISTS idx_scores_created_score 
  ON scores(created_at, score DESC);

-- For language-specific queries
CREATE INDEX IF NOT EXISTS idx_scores_lang_created_score 
  ON scores(code_language, created_at, score DESC);

197-202: Consider using Link components for non-data-loading navigation.

Lines 197-202 (player links) and 226-231 (back to home button) use <a> tags, which trigger full page reloads. Since these navigations don't require server-side data fetching for the ranking page, using React Router's <Link> component would provide smoother client-side navigation.

The language filter tabs (lines 128-141) appropriately use <a> tags since they need to reload ranking data from the server.

Example refactor:

+import { Link } from 'react-router';

 // Player name link (line 197-202)
-                        <a
+                        <Link
+                          to={`/result/${score.id}`}
-                          href={`/result/${score.id}`}
                           className="text-sky-600 dark:text-sky-400 hover:underline"
                         >
                           {score.player_name || t('ranking.anonymous', lang)}
-                        </a>
+                        </Link>

 // Back button (line 226-231)
-                        <a
-                          href={`/${lang}`}
+                        <Link
+                          to={`/${lang}`}
                           className="px-6 py-3 rounded-md bg-sky-500 text-white hover:bg-sky-600 active:bg-sky-700 transition font-medium"
                         >
                           {t('nav.home', lang)}
-                        </a>
+                        </Link>

Also applies to: 226-231

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad87b0a and 4ac2818.

📒 Files selected for processing (4)
  • app/locales/en.json (1 hunks)
  • app/locales/ja.json (1 hunks)
  • app/routes.ts (1 hunks)
  • app/routes/ranking.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/routes/ranking.tsx (2)
app/locales/index.ts (3)
  • SupportedLanguage (8-8)
  • detectLanguage (32-56)
  • t (21-24)
app/components/Header.tsx (1)
  • Header (9-68)
🔇 Additional comments (5)
app/locales/en.json (1)

23-34: LGTM! Translation keys properly added.

The ranking translation keys are well-structured and cover all UI elements in the ranking page. The trailing comma on line 23 correctly maintains JSON syntax.

app/locales/ja.json (1)

23-34: LGTM! Japanese translations properly added.

The Japanese translations for ranking keys are well-formatted and align with the English version. JSON syntax is correct.

app/routes/ranking.tsx (3)

18-26: LGTM! Meta function properly handles i18n.

The meta function correctly generates localized page titles using the i18n system and handles language detection appropriately.


87-99: LGTM! Good defensive date formatting.

The formatDate function properly handles locale-specific formatting with appropriate error handling. The try-catch ensures the app won't crash on malformed date strings.


177-218: LGTM! Ranking table rendering is well-implemented.

The table rendering logic properly handles rank display with special styling for the top 3 positions, includes appropriate hover states, and correctly formats accuracy percentages.

Comment on lines +67 to +78
function getCodeLanguageDisplay(code: string): string {
const map: Record<string, string> = {
all: 'All',
javascript: 'JavaScript',
python: 'Python',
php: 'PHP',
ruby: 'Ruby',
java: 'Java',
dart: 'Dart',
};
return map[code] || code;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Replace hardcoded English strings with i18n for consistency.

The getCodeLanguageDisplay function returns hardcoded English strings, which bypasses the app's i18n system and will display English language names even when the UI is in Japanese. The app already has language.* keys in the locale files for this purpose.

Apply this diff to use the i18n system:

-function getCodeLanguageDisplay(code: string): string {
-  const map: Record<string, string> = {
-    all: 'All',
-    javascript: 'JavaScript',
-    python: 'Python',
-    php: 'PHP',
-    ruby: 'Ruby',
-    java: 'Java',
-    dart: 'Dart',
-  };
-  return map[code] || code;
-}
+function getCodeLanguageDisplay(code: string, lang: SupportedLanguage): string {
+  return t(`language.${code}`, lang);
+}

Then update the call site at line 205:

-                        {getCodeLanguageDisplay(score.code_language)}
+                        {getCodeLanguageDisplay(score.code_language, lang)}

Committable suggestion skipped: line range outside the PR's diff.

- Move ranking route from /ranking to /:lang/ranking
- Update ranking page to use params.lang instead of query params
- Add ranking link to home page (Welcome component)
- Update result page ranking link to include locale
- Remove unused useLocation and query param handling
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

♻️ Duplicate comments (1)
app/routes/$lang.ranking.tsx (1)

72-83: Use i18n for code language labels instead of hardcoded English

getCodeLanguageDisplay currently returns hardcoded English strings:

function getCodeLanguageDisplay(code: string): string {
  const map: Record<string, string> = {
    all: 'All',
    javascript: 'JavaScript',
    python: 'Python',
    php: 'PHP',
    ruby: 'Ruby',
    java: 'Java',
    dart: 'Dart',
  };
  return map[code] || code;
}

And the ranking table uses it at the call site:

<td className="py-3 px-4 text-sm">
  {getCodeLanguageDisplay(score.code_language)}
</td>

Everywhere else (e.g., the filter tabs) already uses t('language.*', lang), so this bypasses your i18n layer and forces English labels even in Japanese UI.

You can unify this with your existing locale keys:

function getCodeLanguageDisplay(code: string, lang: SupportedLanguage): string {
  return t(`language.${code}`, lang);
}

And update the table cell:

<td className="py-3 px-4 text-sm">
  {getCodeLanguageDisplay(score.code_language, lang)}
</td>

This keeps language names consistent with the rest of the app.

Also applies to: 175-204

🧹 Nitpick comments (1)
app/routes/$lang.ranking.tsx (1)

19-25: Normalize lang in meta to avoid calling t with unsupported locales

meta currently does:

const lang = params.lang as SupportedLanguage;
const title = t('ranking.title', lang);

If someone manually navigates to an unsupported locale like /fr/ranking, lang will be 'fr' at runtime, and t will receive a value outside SupportedLanguage, which can lead to runtime errors in the locale lookup.

To be defensive and keep behavior aligned with the loader, normalize or validate here as well, for example:

function normalizeLang(raw: string | undefined): SupportedLanguage {
  return raw === 'ja' ? 'ja' : 'en';
}

export function meta({ params }: Route.MetaArgs) {
  const lang = normalizeLang(params.lang);
  const title = t('ranking.title', lang);
  const period = t('ranking.period.week', lang);
  return [{ title: `${title} (${period}) | Bug Sniper` }];
}

You can then reuse the same helper in the loader to keep language handling centralized.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ac2818 and 76c32b5.

📒 Files selected for processing (4)
  • app/routes.ts (1 hunks)
  • app/routes/$lang.ranking.tsx (1 hunks)
  • app/routes/result.$id.tsx (1 hunks)
  • app/welcome/welcome.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
app/welcome/welcome.tsx (1)
app/locales/index.ts (1)
  • t (21-24)
app/routes/$lang.ranking.tsx (3)
app/routes/result.$id.tsx (1)
  • loader (60-83)
app/locales/index.ts (2)
  • SupportedLanguage (8-8)
  • t (21-24)
app/components/Header.tsx (1)
  • Header (9-68)
🔇 Additional comments (3)
app/routes/result.$id.tsx (1)

240-251: Ranking link now correctly targets language-prefixed route

Using href={/${lang}/ranking} aligns with the new :lang/ranking route and keeps navigation locale-aware from the result page. Looks good.

app/welcome/welcome.tsx (1)

41-46: Welcome ranking CTA is wired consistently with routing and i18n

The new ranking Link (to={/${lang}/ranking} with t('nav.ranking', lang)) matches the :lang/ranking route and existing translation usage. No issues from a routing or UX perspective.

app/routes.ts (1)

3-10: New :lang/ranking route integrates cleanly with existing route patterns

Registering route(':lang/ranking', 'routes/$lang.ranking.tsx') keeps the language-prefixed structure consistent with :lang and :lang/:codeLanguage/play. The ordering among these specific paths is safe and should match as expected.

@goofmint goofmint merged commit 6f8c901 into main Nov 22, 2025
1 check was pending
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