-
Notifications
You must be signed in to change notification settings - Fork 17
🤖 Sync Crowdin Translations #130
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
base: dev
Are you sure you want to change the base?
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note
|
| Cohort / File(s) | Change Summary |
|---|---|
Locale navigation & entities locales/*/common.json, locales/*/entities.json (e.g., ar, bn, es, fa, fr, ha, hi, ms, ps, ru, so, tr, ur, etc.) |
Add navigation.browse.empires entries and empire/empires/x-empires pluralization keys across many locales; many files also normalized EOF newlines. |
Empires filter (server + client) src/components/empires-filter/index.tsx, src/components/empires-filter/client.tsx |
New server wrapper fetching empires with counts and a client component with Fuse.js fuzzy search, multi-select, incremental loading, debounced URL param sync (router.replace), and clear/reset behavior. |
Pages wiring & route types src/app/[locale]/(rootEntityPages)/authors/page.tsx, .../texts/page.tsx, src/app/[locale]/(rootEntityPages)/authors/routeType.ts, .../texts/routeType.ts, src/app/[locale]/(rootEntityPages)/empires/page.tsx |
Plumb empires search param into authors/texts pages, render EmpiresFilter in filters, and add empires to routeType schemas. |
API / Types src/types/api/book.ts, src/types/api/author.ts, src/types/api/region.ts, src/types/author.ts, src/types/region.ts, src/types/empire.ts, src/lib/api/regions.ts |
Add regions, empires, advancedGenres to API/book types; author type now includes regions/empires; make overview optional and drop currentName/subLocations; add hijriStartYear/hijriEndYear to empire type; adjust findRegionBySlug typing. |
Prisma schema prisma/schema/usul.prisma |
Remove Location, LocationCityName models and LocationType enum; drop locations relation fields from Author and Region. |
Map & region UI src/components/regions-choropleth-map/client.tsx, src/components/regions-choropleth-map/regions-to-country-codes.tsx, src/components/region-search-result.tsx, src/lib/api/regions.ts |
Large interactive choropleth enhancements (fullscreen, zoom/pan, touch, tooltips), add region->country-code mapping constant, and remove sub-location subtitle/display. |
Author / Region pages & dialogs src/app/[locale]/(entityPages)/author/[authorSlug]/page.tsx, src/app/[locale]/(entityPages)/region/[regionSlug]/page.tsx, src/components/book-search-result/info-dialog.tsx |
Replace location-based logic with regions (use author.regions), remove city/location lists, show region names/slugs, and simplify region-related rendering. |
Team pages & locale data src/app/[locale]/(defaultLayout)/team/members.ts, src/app/[locale]/(defaultLayout)/team/page.tsx, locales/*/team.json |
Rework exported team collections (boardMembers, engineeringTeam, pastTeamMembers, researchAnnotationTeam), update team page sections, and add/update many locale team strings and member/board entries. |
UI components & misc src/app/[locale]/chat/status-label.tsx, src/components/chat/message-actions.tsx, src/app/[locale]/t/[bookId]/_components/reader-content/book-info.tsx, src/components/genres-tree-chart/client.tsx, src/app/api/og/region/[slug]/route.tsx, .vscode/settings.json, small locale chat/common updates |
StatusLabel expand/collapse; add Web Share API Share action in chat; book-info now renders advancedGenres/regions/empires via DottedList; genres-tree mobile-centering tweaks; OG route safer overview handling; VSCode settings updated. |
Sequence Diagram(s)
sequenceDiagram
participant Browser
participant EmpiresServer as Empires (server)
participant EmpiresClient as EmpiresFilterClient
participant API as findAllEmpiresWithBooksCount
participant Router
Browser->>EmpiresServer: initial page render (locale, filters)
EmpiresServer->>API: fetch empires + counts (pathLocale, filters)
API-->>EmpiresServer: empires + counts
EmpiresServer-->>Browser: render EmpiresFilterClient with `empires` prop
Browser->>EmpiresClient: user types search
EmpiresClient->>EmpiresClient: Fuse.js filter (debounced)
Browser->>EmpiresClient: user toggles selection
EmpiresClient->>Router: replace URL with empires=slug1,slug2 (debounced)
Router-->>Browser: URL updated, page state sync (no scroll)
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
- 🤖 Sync Crowdin Translations #75 — Overlaps on locale
team.jsonedits and team-member data changes; likely to touch same files. - Update copy #89 — Modifies
src/app/.../team/members.tsand related team page wiring; high potential conflict with this PR.
Poem
🐇 I hopped through keys and map-filled lands,
I stitched "empires" with careful hands,
Filters hum and maps expand,
Teams reshuffled at my soft command,
A joyous nibble — translations on demand!
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. | |
| Title check | ❓ Inconclusive | The title "🤖 Sync Crowdin Translations" is vague and generic, using a non-descriptive emoji prefix that doesn't convey meaningful information about the specific changes in the changeset. | Replace with a more specific title that describes the main changes, such as "Add empire localization entries and update team structure" or similar to reflect the primary purpose of the PR. |
✅ Passed checks (1 passed)
| Check name | Status | Explanation |
|---|---|---|
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
✏️ 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.
Comment @coderabbitai help to get the list of available commands and usage tips.
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: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
locales/es/entities.json (1)
1-42: Fix typo and remove incorrect punctuation in Spanish x-empires entry.Line 10 contains two errors:
- "empiros" should be "imperios"
- "¡Imperios" has an unnecessary exclamation mark not present in the English source or any other locale
Change
"x-empires": "{count, plural, =0 {No hay empiros} one {# Imperio} other {# ¡Imperios}}"to"x-empires": "{count, plural, =0 {No hay imperios} one {# Imperio} other {# Imperios}}".
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (26)
locales/ar/common.jsonlocales/ar/entities.jsonlocales/bn/common.jsonlocales/bn/entities.jsonlocales/es/common.jsonlocales/es/entities.jsonlocales/fa/common.jsonlocales/fa/entities.jsonlocales/fr/common.jsonlocales/fr/entities.jsonlocales/ha/common.jsonlocales/ha/entities.jsonlocales/hi/common.jsonlocales/hi/entities.jsonlocales/ms/common.jsonlocales/ms/entities.jsonlocales/ps/common.jsonlocales/ps/entities.jsonlocales/ru/common.jsonlocales/ru/entities.jsonlocales/so/common.jsonlocales/so/entities.jsonlocales/tr/common.jsonlocales/tr/entities.jsonlocales/ur/common.jsonlocales/ur/entities.json
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: seemorg/usul PR: 0
File: .cursor/rules/localization.mdc:0-0
Timestamp: 2025-08-06T11:58:32.752Z
Learning: Applies to locales/en/**/* : Whenever you're adding a new string to the codebase, make sure to add it to a translation file in `locales/en`.
🔇 Additional comments (23)
locales/bn/common.json (1)
79-86: LGTM! Empires navigation entry added correctly.The new empires entry follows the same structure as the existing genres entry and is properly integrated into the navigation.browse object with correct JSON syntax.
locales/fr/entities.json (1)
9-11: LGTM! Empire entity translations added correctly.The new empire-related keys follow the established pattern with proper ICU MessageFormat pluralization syntax. The French pluralization rules are correctly applied.
locales/ps/common.json (1)
79-86: LGTM! Empires navigation entry added correctly.The new empires entry is properly structured and follows the existing pattern for browse navigation items.
locales/fa/common.json (1)
79-86: LGTM! Empires navigation entry added correctly.The new empires entry is properly integrated into the navigation structure with correct JSON formatting.
locales/bn/entities.json (1)
9-11: LGTM! Empire entity translations added correctly.The new empire-related keys use proper ICU MessageFormat pluralization syntax consistent with other entity definitions in this locale.
locales/hi/entities.json (1)
9-11: LGTM! Empire entity translations added correctly.The new keys follow the established pattern with proper pluralization formatting for Hindi.
locales/so/entities.json (1)
9-11: LGTM! Empire entity translations added correctly.The new empire-related keys are properly formatted with correct ICU MessageFormat pluralization syntax for Somali.
locales/ru/entities.json (1)
9-11: LGTM! Empire entity translations added correctly.The new keys follow the established pattern with proper ICU MessageFormat pluralization syntax for Russian.
locales/fr/common.json (1)
83-86: LGTM! French translation is correct.The empires navigation entry follows the established pattern and uses proper French plural form "Empires" with correct ICU MessageFormat syntax.
locales/fa/entities.json (1)
9-11: LGTM! Farsi translations follow the correct pattern.The empire translations use proper ICU MessageFormat pluralization syntax consistent with other entity translations in this file.
locales/so/common.json (1)
83-86: LGTM! Somali translation is properly structured.The empires navigation entry follows the established pattern with correct ICU MessageFormat syntax.
locales/hi/common.json (1)
83-86: Verify Hindi plural form for navigation title.The title uses "साम्राज्य" which can serve as both singular and plural in Hindi. However, other browse categories show explicit plural forms in their descriptions (e.g., "पाठों" for texts, "लेखकों" for authors). Consider verifying with a Hindi speaker whether the plural form "साम्राज्यों" would be more appropriate for the navigation category title to maintain consistency.
locales/ps/entities.json (1)
9-11: LGTM! Pashto translations are properly structured.The empire entity translations follow the correct ICU MessageFormat pluralization pattern consistent with other entities in this file.
locales/ur/common.json (1)
83-86: LGTM! Urdu translation uses proper plural form.The empires navigation entry follows the established pattern with correct ICU MessageFormat syntax and proper Urdu plural form "سلطنتیں".
locales/ha/common.json (1)
82-86: LGTM! Empires localization added correctly.The new empires entry follows the existing pattern for browse categories and uses proper ICU message format syntax.
locales/ar/common.json (1)
82-86: LGTM! Arabic empires localization added correctly.The structure and ICU message format are consistent with existing browse entries.
locales/ms/entities.json (1)
9-11: LGTM! Malay empire entity keys added correctly.The ICU pluralization format is properly structured and consistent with existing entity patterns.
locales/ha/entities.json (1)
9-11: LGTM! Hausa empire entity keys added correctly.The pluralization rules properly distinguish between singular "Daula" and plural "Dauloli", and the ICU format is correct.
locales/ms/common.json (1)
82-86: LGTM! Malay empires navigation entry added correctly.The translation aligns with the corresponding entity keys and follows the established pattern.
locales/ur/entities.json (1)
9-11: LGTM! Urdu empire entity keys added correctly.The singular and plural forms are properly distinguished, and the ICU pluralization format is correct.
locales/es/common.json (2)
82-86: LGTM! Spanish empires localization added correctly.The translation follows the established pattern with consistent plural usage and proper ICU message format.
1-252: English locale files already contain the empires translations. Bothlocales/en/common.json(line 83) andlocales/en/entities.json(lines 9-10) include the required "empires" key and associated translations. No action needed.locales/tr/common.json (1)
82-86: Code is correct—Turkish uses singular nouns after numbers.The singular form "İmparatorluk" in the description is grammatically correct in Turkish. When you use a number or any word that indicates quantity before a noun, use the singular form of the noun. The description "{empires, number} İmparatorluk" follows this rule. The title "İmparatorluklar" (plural) stands separately without a number, so it correctly uses the plural form. No changes needed.
locales/ar/entities.json
Outdated
| "empire": "امبراطورية", | ||
| "empires": "الامبراطوريات", | ||
| "x-empires": "{count, plural, =0 {لا امبراطورية} one {# امبراطورية} other {# امبراطورية}}", |
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.
Complete the Arabic pluralization pattern for consistency.
The "x-empires" key uses only 3 plural forms (=0, one, other), while other Arabic entries in this file consistently use 6 forms (zero, =0, one, two, few, many, other). Arabic has distinct plural forms for different count ranges, and the simplified pattern may display incorrect text for counts like 2, 3-10, or 11-99.
🔎 Suggested fix to match the pattern used in other entries
- "x-empires": "{count, plural, =0 {لا امبراطورية} one {# امبراطورية} other {# امبراطورية}}",
+ "x-empires": "{count, plural, zero {}=0 {لا يوجد امبراطوريات} one {# امبراطورية} two {# امبراطوريات} few {# امبراطوريات} many {# امبراطوريات} other {# امبراطوريات}}",Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In locales/ar/entities.json around lines 9-11, the "x-empires" plural pattern is
incomplete (uses only =0, one, other); update it to match the file's Arabic
plural format by adding the full set of plural keys used elsewhere: zero, =0,
one, two, few, many, other, and supply the correct Arabic strings for each form
(e.g., zero and =0 for no empires, one for singular, two for dual, few for 3–10,
many for 11–99, and other as a fallback) so the localization handles all Arabic
numeric ranges consistently.
locales/es/entities.json
Outdated
| "x-regions": "{count, plural, =0 {No hay regiones} one {# Región} other {# Regiones}}", | ||
| "empire": "Imperio", | ||
| "empires": "Imperios", | ||
| "x-empires": "{count, plural, =0 {No hay empiros} one {# Imperio} other {# ¡Imperios}}", |
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.
Fix typo and remove inconsistent punctuation.
Line 11 contains two issues:
- Typo: "empiros" should be "imperios"
- Inconsistent punctuation: The exclamation mark in "¡Imperios" is not used in other plural patterns in this file (compare with lines 5, 8, 14, etc.)
🔎 Proposed fix
- "x-empires": "{count, plural, =0 {No hay empiros} one {# Imperio} other {# ¡Imperios}}",
+ "x-empires": "{count, plural, =0 {No hay imperios} one {# Imperio} other {# Imperios}}",📝 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.
| "x-empires": "{count, plural, =0 {No hay empiros} one {# Imperio} other {# ¡Imperios}}", | |
| "x-empires": "{count, plural, =0 {No hay imperios} one {# Imperio} other {# Imperios}}", |
🤖 Prompt for AI Agents
In locales/es/entities.json around line 11, fix the pluralization string by
correcting the typo "empiros" to "imperios" and remove the inconsistent leading
exclamation mark so the other plural variant matches the file's punctuation
style (e.g., change "{count, plural, =0 {No hay empiros} one {# Imperio} other
{# ¡Imperios}}" to use "imperios" and "Imperios" without the "¡").
| "empires": { | ||
| "title": "Империя", | ||
| "description": "{empires, number} Империя" | ||
| } |
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.
Use plural form "Империи" for consistency.
The navigation title uses the singular form "Империя" (Empire), but other browse categories (texts, authors, regions, genres) use plural forms. For consistency, consider using "Империи" (Empires) as the title.
🔎 Suggested correction
"empires": {
- "title": "Империя",
- "description": "{empires, number} Империя"
+ "title": "Империи",
+ "description": "{empires, number} Империи"
}📝 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.
| "empires": { | |
| "title": "Империя", | |
| "description": "{empires, number} Империя" | |
| } | |
| "empires": { | |
| "title": "Империи", | |
| "description": "{empires, number} Империи" | |
| } |
🤖 Prompt for AI Agents
In locales/ru/common.json around lines 83 to 86, the "empires" navigation title
uses the singular "Империя" while other browse categories use plural; change the
"title" value to the plural form "Империи" and keep the "description" unchanged
so labels are consistent across the UI.
locales/tr/entities.json
Outdated
| "empire": "İmparatorluk", | ||
| "empires": "İmparatorluklar", | ||
| "x-empires": "{count, plural, =0 {Hiç imparatorluk yok} one {# İmparatorluk} other {# İmparatorluklar}}", |
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.
Fix capitalization inconsistency in Turkish pluralization.
Line 11 uses lowercase "imparatorluk" in the zero-count case ("Hiç imparatorluk yok"), while the one and other cases use the capitalized form "İmparatorluk". Other entity translations in this file consistently capitalize the noun (e.g., "Bölge yok", "Konum yok", "Yazar yok"). The zero case should use "İmparatorluk" for consistency.
🔎 Suggested correction
- "x-empires": "{count, plural, =0 {Hiç imparatorluk yok} one {# İmparatorluk} other {# İmparatorluklar}}",
+ "x-empires": "{count, plural, =0 {Hiç İmparatorluk yok} one {# İmparatorluk} other {# İmparatorluklar}}",📝 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.
| "empire": "İmparatorluk", | |
| "empires": "İmparatorluklar", | |
| "x-empires": "{count, plural, =0 {Hiç imparatorluk yok} one {# İmparatorluk} other {# İmparatorluklar}}", | |
| "empire": "İmparatorluk", | |
| "empires": "İmparatorluklar", | |
| "x-empires": "{count, plural, =0 {Hiç İmparatorluk yok} one {# İmparatorluk} other {# İmparatorluklar}}", |
🤖 Prompt for AI Agents
In locales/tr/entities.json around lines 9 to 11, the zero-count plural form
uses a lowercase "imparatorluk" ("Hiç imparatorluk yok") which is inconsistent
with the capitalized noun used elsewhere; update the zero-case string to "Hiç
İmparatorluk yok" so it matches the capitalization of the one/other cases and
other entity translations, preserving the existing ICU plural syntax and
punctuation.
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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
.vscode/settings.json
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: seemorg/usul PR: 0
File: .cursor/rules/localization.mdc:0-0
Timestamp: 2025-08-06T11:58:32.752Z
Learning: Applies to locales/en/**/* : Whenever you're adding a new string to the codebase, make sure to add it to a translation file in `locales/en`.
🔇 Additional comments (1)
.vscode/settings.json (1)
31-33: Verify Prisma formatter setting belongs in this translation sync PR.While setting a default formatter for Prisma files is a good practice for team consistency, this change appears unrelated to Crowdin translation syncing. Confirm whether this setting was intentionally included or should be part of a separate configuration PR.
| "workbench.colorCustomizations": { | ||
| "activityBar.background": "#531D00", | ||
| "titleBar.activeBackground": "#742800", | ||
| "titleBar.activeForeground": "#FFFAF8" | ||
| }, |
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.
Personal UI preferences don't belong in workspace settings.
These color customizations appear unrelated to Crowdin translation syncing. Workspace settings affect all developers, but UI color schemes are personal preferences that should remain in individual user settings rather than being committed to the repository.
Consider removing these customizations from the workspace settings file.
🤖 Prompt for AI Agents
.vscode/settings.json around lines 26 to 30: remove the personal UI
colorCustomizations (activityBar.background, titleBar.activeBackground,
titleBar.activeForeground) from the workspace settings so repository-visible
settings don't enforce developer UI preferences; either delete the entire
"workbench.colorCustomizations" block from this file or move these entries into
a user-specific settings file and commit the workspace settings without these
keys.
- remove hover effect for message actions on mobile - share button - hide long generation message
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: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/components/genres-tree-chart/client.tsx (2)
137-157: Fix initial zoom transform math:scale()thentranslate()likely mis-centers on mobile.In d3-zoom,
transform.translate(x, y)is affected by the current scale (k). WithzoomIdentity.scale(initialScale).translate(...), your translate values get multiplied byinitialScale, which can produce incorrect centering and makescenterX = width / 1look like a compensation. Also, the comment says “Bigger zoom on mobile” but uses0.5.Proposed fix (more standard transform composition + avoid touching private
__zoom)- const initialScale = isMobile ? 0.5 : 1; // Bigger zoom on mobile - const centerX = isMobile ? width / 1 : width / 2; + const initialScale = isMobile ? 0.5 : 1; // Smaller zoom on mobile + const centerX = isMobile ? width / 2 : width / 2; const centerY = isMobile ? height / 2 : height / 2 - 100; - const initialTransform = d3.zoomIdentity - .scale(initialScale) - .translate( - centerX - initialScale * root.x, - centerY - initialScale * root.y, - ); + // translate to viewport center, apply scale, then translate root to origin + const initialTransform = d3.zoomIdentity + .translate(centerX, centerY) + .scale(initialScale) + .translate(-root.x, -root.y); svg.call(zoom as any); - // Set the initial transform state directly to avoid snap on first drag - (svg.node() as any).__zoom = initialTransform; - g.attr("transform", initialTransform.toString()); + // Initialize zoom state via API (keeps internal state consistent) + svg.call(zoom.transform as any, initialTransform);
611-622: Icon-only fullscreen button: add an accessible name; confirm hiding on mobile is intended.Since the button has only an icon, it should have an
aria-label(and optionallytitle). Also,hidden sm:flexremoves fullscreen on small screens—fine if intentional, but worth confirming.Proposed fix (a11y)
<Button size="icon" variant="outline" className="absolute right-4 bottom-4 z-10 hidden backdrop-blur-sm sm:flex" onClick={toggleFullscreen} + aria-label={isFullscreen ? tCommon("exitFullscreen") : tCommon("enterFullscreen")} + title={isFullscreen ? tCommon("exitFullscreen") : tCommon("enterFullscreen")} >src/app/[locale]/chat/status-label.tsx (1)
20-22: RemovedangerouslySetInnerHTML(XSS risk via query strings).
formatQueries()emits HTML (Line 21) and that string is injected withdangerouslySetInnerHTML(Line 77). If any query contains</>(or worse), this can become script/markup injection.Prefer rendering React nodes and letting React escape text.
Proposed fix (render as React nodes)
+import type { ReactNode } from "react"; -import { useState } from "react"; +import { Fragment, useState } from "react"; @@ -const formatQueries = (queries: string[]) => { - return queries.map((q) => `<i>${q}</i>`).join(", "); -}; +const formatQueries = (queries: string[]): ReactNode => { + return queries.map((q, idx) => ( + <Fragment key={idx}> + <em>{q}</em> + {idx < queries.length - 1 ? ", " : null} + </Fragment> + )); +}; @@ - let text = t("chat.status.thinking"); + let content: ReactNode = t("chat.status.thinking"); if (!isLoading) { @@ if (queriesStatus) { - text = `${t("chat.status.done_searched")} ${formatQueries(queriesStatus.queries)}`; + content = ( + <> + {t("chat.status.done_searched")} {formatQueries(queriesStatus.queries)} + </> + ); } else { - text = t("chat.status.done"); + content = t("chat.status.done"); } } else { @@ if (status?.value === "generating-queries") { - text = t("chat.status.generating_queries"); + content = t("chat.status.generating_queries"); } else if (status?.value === "generating-response") { - text = `${t("chat.status.generating_response")} ${formatQueries(queriesStatus?.queries ?? [])}`; + content = ( + <> + {t("chat.status.generating_response")}{" "} + {formatQueries(queriesStatus?.queries ?? [])} + </> + ); } else if (status?.value === "searching") { - text = `${t("chat.status.searching")} ${formatQueries(status.queries)}`; + content = ( + <> + {t("chat.status.searching")} {formatQueries(status.queries)} + </> + ); } } @@ <ShinyText @@ shimmerWidth={40} disabled={!isLoading} - dangerouslySetInnerHTML={{ __html: text }} - /> + > + {content} + </ShinyText>Also applies to: 34-65, 69-78
🤖 Fix all issues with AI agents
In @locales/en/common.json:
- Around line 83-86: The description value under the "empires" key is
inconsistent with the updated title "Empires & Eras"; update the
empires.description string to reflect the new terminology (for example change
"{empires, number} Empires" to something like "{empires, number} Empires & Eras"
or another appropriate localized phrasing) so empires.title and
empires.description match; edit the "empires" object (keys empires.title and
empires.description) to keep wording consistent across the locale entry.
In @src/app/[locale]/(entityPages)/author/[authorSlug]/page.tsx:
- Line 104: The variable assignment "const regions = author.regions;" can be
undefined and later code uses regions.length, causing a TypeError; change the
assignment to ensure a safe default (e.g., use nullish coalescing or similar) so
"regions" is always an array (for example set regions to author.regions ?? []),
and keep existing optional chaining (author.regions?.map) consistent with the
new default so all uses of regions (including regions.length) are safe.
In @src/app/[locale]/chat/status-label.tsx:
- Around line 1-8: This file uses React hooks useState and useTranslations in
the App Router, so add the Client Component directive by inserting the string
"use client" as the very first line of src/app/[locale]/chat/status-label.tsx
(before any imports); ensure it appears above the imports that include useState
and useTranslations so the component (the code referencing useState and the
useTranslations() call) is treated as a client component.
- Around line 68-92: The aria-label is hardcoded and the state toggle can suffer
stale reads: replace the literal aria-label with a localized string from your
i18n function (use t(...) to produce the Expand/Collapse text based on
isExpanded) and change the onClick handler on the Button from
setIsExpanded(!isExpanded) to a functional update setIsExpanded(prev => !prev)
so the toggle is localized and safe from stale state; apply these changes where
isExpanded and setIsExpanded are referenced and update the aria-label logic to
choose the localized key matching the ChevronUpIcon/ChevronDownIcon state.
In @src/app/[locale]/t/[bookId]/_components/reader-content/book-info.tsx:
- Around line 104-159: The three JSX blocks passed into the DottedList items
array (the wrapping <div> for advancedGenres, regions, and empires) need
explicit key props to satisfy React reconciliation; update the elements created
for advancedGenres, regions, and empires inside the items array to include
stable keys (e.g., key="genres", key="regions", key="empires" or derived stable
values) on the outer <div> nodes so each entry in the items array has a unique
key.
In @src/components/chat/message-actions.tsx:
- Around line 66-88: Replace hard-coded UI strings in handleShare with
translations via useTranslations (e.g., call useTranslations("common") and use
keys like reader.share, reader.shared, reader["no-text-to-share"],
reader["share-failed"], reader["share-footer"]) and concatenate
reader("share-footer") instead of the literal footer; ensure message.parts
processing remains the same. Also remove the silent catch: log the caught error
(console.error or a logger) and show a translated failure toast using
toast.error(reader("share-failed")) so users get feedback if navigator.share
throws, while keeping toast.success(reader("shared")) on success. Ensure imports
for useTranslations are added and translation keys are present in your locale
files.
In @src/components/empires-filter/client.tsx:
- Around line 78-102: The debounced timeout set in handleChange via
timeoutRef.current isn't cleared on unmount; add a useEffect cleanup that on
component unmount clears timeoutRef.current (using clearTimeout) to avoid memory
leaks and stale updates. Specifically, add a useEffect that returns a cleanup
function which checks if timeoutRef.current is set and calls
clearTimeout(timeoutRef.current), ensuring any pending startTransition/replace
triggered by the DEBOUNCE_DELAY is cancelled when the component unmounts; keep
references to timeoutRef and any used setters (setSelectedEmpires) unchanged.
- Around line 107-109: The mapping for selectedEmpiresItems can produce
undefined when a slug is missing from slugToEmpire; update the code that builds
selectedEmpiresItems (and any later use like matchedEmpires.items) to filter out
undefined results (e.g., map then filter(Boolean) or reduce) so only valid
empire objects from slugToEmpire are included; ensure the resulting array has
the same type as empires and downstream code (where empire.numberOfBooks is
accessed) only runs on these filtered, non-undefined empire objects.
In @src/components/empires-filter/index.tsx:
- Around line 23-27: The current filter uses numberOfBooks > 0 which incorrectly
drops empires when countType === "authors"; update the filtering logic for
filteredEmpires to check the relevant field based on countType (e.g., if
countType === "authors" use empire.numberOfAuthors > 0, otherwise use
empire.numberOfBooks > 0) and ensure any subsequent sort uses the same chosen
field (use empire.numberOfAuthors when counting authors, otherwise
empire.numberOfBooks) so both filtering and sorting are consistent with the
countType.
In @src/types/author.ts:
- Line 12: The author page reads author.regions which is now optional and can be
undefined; update the code in the author page component to create a safe
fallback like const regions = author.regions ?? []; then replace direct uses of
author.regions (the assignment at the earlier block and the subsequent
checks/calls that use .length and .map) to use the new regions variable so
.length and .map are called on an empty array when regions is absent.
🧹 Nitpick comments (15)
src/components/genres-tree-chart/client.tsx (1)
60-63: Mobile breakpoint logic is fine, but consider centralizing the640constant.Using
dimensions.width < 640matches Tailwind’ssmbreakpoint, but hardcoding it can drift from config if breakpoints change.src/components/regions-choropleth-map/client.tsx (6)
8-13: Remove unused imports.
MinusIconandPlusIconare imported but the zoom buttons are commented out (lines 633-649). Remove these unused imports or uncomment the zoom functionality.import { ArrowsPointingInIcon, ArrowsPointingOutIcon, - MinusIcon, - PlusIcon, } from "@heroicons/react/24/outline";
159-161: Misleading comment: wheel zoom is disabled, not allowed.The comment says "Allow wheel events for zooming" but
return falseblocks them. Update the comment to reflect the actual behavior..filter((event) => { - // Allow wheel events for zooming - if (event.type === "wheel") return false; + // Disable wheel events for zooming (use pan/pinch instead) + if (event.type === "wheel") return false;
350-354: Hard-coded strings should use translations.
"Books:"and"Authors:"are hard-coded in English. Per coding guidelines, use theuseTranslationshook fromnext-intlfor localization.+ const t = useTranslations(); // ... inside showTooltip: let tooltipContent = `<strong>${countryName}</strong><br/>`; - tooltipContent += `Books: ${booksCount.toLocaleString()}`; + tooltipContent += `${t("common.books")}: ${booksCount.toLocaleString()}`; if (authorsCount > 0) { - tooltipContent += `<br/>Authors: ${authorsCount.toLocaleString()}`; + tooltipContent += `<br/>${t("common.authors")}: ${authorsCount.toLocaleString()}`; }Based on coding guidelines, translations should use the
useTranslationshook.
278-280: External data dependency may affect reliability.The GeoJSON is fetched from a third-party GitHub repository. If that repository is modified, deleted, or rate-limited, the map will fail to render. Consider:
- Hosting the GeoJSON file locally in
/public- Using a CDN under your control
- Caching the response
596-649: Address or remove dead code for zoom controls.
handleZoomInandhandleZoomOutare defined but the buttons are commented out with a TODO. Either:
- Fix the zoom functionality and uncomment the buttons
- Remove the unused handlers and commented JSX to reduce code clutter
Leaving dead code increases maintenance burden.
Would you like me to open an issue to track fixing the zoom functionality?
650-661: Add accessible label to fullscreen button.The button only contains an icon with no text or
aria-label, making it inaccessible to screen readers.<Button size="icon" variant="outline" className="absolute right-4 bottom-4 z-10 hidden backdrop-blur-sm sm:flex" onClick={toggleFullscreen} + aria-label={isFullscreen ? "Exit fullscreen" : "Enter fullscreen"} >prisma/schema/usul.prisma (1)
18-20: Consider renamingRegion.authortoRegion.authorsfor naming consistency.The relation is correctly wired on both sides (same
@relation("AuthorToRegion")name), but the fieldauthoron the Region model is singular while it holds a list of Authors. This is inconsistent with the Author side which correctly usesregions(plural). Renaming toauthorswould improve clarity, though it's a breaking API change if clients depend on the current field name.src/components/chat/message-actions.tsx (1)
109-117: Remove unnecessary arrow function wrapper.The onClick handler wraps
handleSharein an arrow function unnecessarily. SincehandleSharetakes no arguments, you can pass it directly.♻️ Simplify the onClick handler
<Button size="icon" variant="ghost" className="hover:bg-secondary size-7" - onClick={() => handleShare()} + onClick={handleShare} tooltip={t("reader.share")} > <ShareIcon className="size-4" /> </Button>src/components/book-search-result/info-dialog.tsx (2)
27-32: Remove unusedorderobject.The
orderobject appears to be a remnant from the previous location-based implementation (where locations had types like "born", "resided", "visited", "died"). Since regions no longer have types and this object is not referenced anywhere in the component, it should be removed.♻️ Remove unused code
-const order: Record<string, number> = { - born: 1, - resided: 2, - visited: 3, - died: 4, -}; - export default function InfoDialog({
269-273: Redundant margin with the dash separator.The dash
<span>-</span>is followed by a link withml-2(margin-left), but the dash itself doesn't need to be in a separate element. Consider simplifying this structure.♻️ Simplify dash and link structure
- <span>-</span> - <Link - className="text-primary ml-2 hover:underline" + className="text-primary hover:underline before:content-['-'] before:mr-2" href={navigation.regions.bySlug(region.slug)} prefetch >Or keep it simple with inline dash:
<li key={region.id} - className="flex items-center gap-1 text-lg capitalize" + className="text-lg capitalize" > - <span>-</span> - - <Link - className="text-primary ml-2 hover:underline" + - <Link + className="text-primary hover:underline" href={navigation.regions.bySlug(region.slug)}src/types/api/author.ts (1)
11-22: Consider extracting a shared type for regions and empires.Both
regionsandempiresarrays have identical structure (id, slug, name, secondaryName). Consider extracting a shared type to reduce duplication and ensure consistency.♻️ Extract shared type
+export interface ApiEntityReference { + id: string; + slug: string; + name: string; + secondaryName: string; +} + export interface ApiAuthor { id: string; slug: string; year: number; numberOfBooks: number; primaryName: string; otherNames: string[]; secondaryName: string; secondaryOtherNames: string[]; bio: string; - regions: { - id: string; - slug: string; - name: string; - secondaryName: string; - }[]; - empires: { - id: string; - slug: string; - name: string; - secondaryName: string; - }[]; + regions: ApiEntityReference[]; + empires: ApiEntityReference[]; }src/components/empires-filter/index.tsx (1)
15-21: Remove commented-out code.Dead code clutters the codebase. If this alternative implementation is needed for reference, consider documenting the approach elsewhere or removing it entirely.
🧹 Suggested cleanup
const empires = await findAllEmpiresWithBooksCount(props.filters, pathLocale); - // const initialEmpires = await searchEmpires("", { - // page: 1, - // limit: 10, - // sortBy: "texts-desc", - // filters: props.filters, - // locale: pathLocale, - // }); // filter empires with count > 0src/app/[locale]/(defaultLayout)/team/page.tsx (2)
62-63: Type assertionas anybypasses translation key validation.The
as anycasts on translation keys (e.g., lines 62-63, 81-83, 100-102, 117-119) suppress TypeScript errors but lose compile-time safety. If translation keys are dynamic, consider using a typed pattern or ensuring the keys exist in the translation files.
75-75: Inconsistent heading hierarchy.Multiple
<h1>elements appear on the page (lines 39, 71, 75, 92, 111), which is semantically incorrect and can impact accessibility and SEO. Consider using<h2>for subsection headings.♻️ Suggested fix for one section (apply similar changes to others)
- <h1 className="text-1xl lg:text-2xl">{t("engineering-team-title")}</h1> + <h2 className="text-xl lg:text-2xl">{t("engineering-team-title")}</h2>Also applies to: 92-93, 111-111
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
public/static/team-images/ahmed-khan.pngis excluded by!**/*.pngpublic/static/team-images/rahma.pngis excluded by!**/*.png
📒 Files selected for processing (33)
locales/en/chat.jsonlocales/en/common.jsonlocales/en/entities.jsonlocales/en/team.jsonprisma/schema/usul.prismasrc/app/[locale]/(defaultLayout)/team/members.tssrc/app/[locale]/(defaultLayout)/team/page.tsxsrc/app/[locale]/(entityPages)/author/[authorSlug]/page.tsxsrc/app/[locale]/(entityPages)/region/[regionSlug]/page.tsxsrc/app/[locale]/(rootEntityPages)/authors/page.tsxsrc/app/[locale]/(rootEntityPages)/authors/routeType.tssrc/app/[locale]/(rootEntityPages)/empires/page.tsxsrc/app/[locale]/(rootEntityPages)/texts/page.tsxsrc/app/[locale]/(rootEntityPages)/texts/routeType.tssrc/app/[locale]/chat/status-label.tsxsrc/app/[locale]/t/[bookId]/_components/reader-content/book-info.tsxsrc/app/api/og/region/[slug]/route.tsxsrc/components/book-search-result/info-dialog.tsxsrc/components/chat/message-actions.tsxsrc/components/empires-filter/client.tsxsrc/components/empires-filter/index.tsxsrc/components/genres-tree-chart/client.tsxsrc/components/region-search-result.tsxsrc/components/regions-choropleth-map/client.tsxsrc/components/regions-choropleth-map/regions-to-country-codes.tsxsrc/lib/api/regions.tssrc/types/api/author.tssrc/types/api/book.tssrc/types/api/location.tssrc/types/api/region.tssrc/types/author.tssrc/types/empire.tssrc/types/region.ts
💤 Files with no reviewable changes (5)
- src/types/region.ts
- src/app/[locale]/(rootEntityPages)/empires/page.tsx
- src/app/[locale]/(entityPages)/region/[regionSlug]/page.tsx
- src/types/api/location.ts
- src/components/region-search-result.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/localization.mdc)
If you're using a translation key in the codebase, use the
useTranslationshook fromnext-intlas shown:import { useTranslations } from "next-intl"; const t = useTranslations(); t("common.delete");
Files:
src/components/book-search-result/info-dialog.tsxsrc/app/api/og/region/[slug]/route.tsxsrc/components/empires-filter/client.tsxsrc/components/regions-choropleth-map/regions-to-country-codes.tsxsrc/components/chat/message-actions.tsxsrc/app/[locale]/(rootEntityPages)/texts/page.tsxsrc/components/regions-choropleth-map/client.tsxsrc/app/[locale]/(rootEntityPages)/authors/page.tsxsrc/app/[locale]/(defaultLayout)/team/page.tsxsrc/components/empires-filter/index.tsxsrc/app/[locale]/chat/status-label.tsxsrc/components/genres-tree-chart/client.tsxsrc/app/[locale]/t/[bookId]/_components/reader-content/book-info.tsxsrc/app/[locale]/(entityPages)/author/[authorSlug]/page.tsx
locales/en/**/*
📄 CodeRabbit inference engine (.cursor/rules/localization.mdc)
Whenever you're adding a new string to the codebase, make sure to add it to a translation file in
locales/en.
Files:
locales/en/entities.jsonlocales/en/common.jsonlocales/en/chat.jsonlocales/en/team.json
🧠 Learnings (1)
📚 Learning: 2025-08-06T11:58:32.752Z
Learnt from: CR
Repo: seemorg/usul PR: 0
File: .cursor/rules/localization.mdc:0-0
Timestamp: 2025-08-06T11:58:32.752Z
Learning: Applies to **/*.tsx : If you're using a translation key in the codebase, use the `useTranslations` hook from `next-intl` as shown: `import { useTranslations } from "next-intl"; const t = useTranslations(); t("common.delete");`
Applied to files:
src/app/[locale]/(defaultLayout)/team/page.tsxsrc/app/[locale]/(entityPages)/author/[authorSlug]/page.tsx
🧬 Code graph analysis (9)
src/components/empires-filter/client.tsx (2)
src/lib/api/empires.ts (1)
findAllEmpiresWithBooksCount(19-37)src/navigation.tsx (2)
usePathname(11-11)useRouter(15-60)
src/app/[locale]/(rootEntityPages)/texts/page.tsx (1)
src/components/empires-filter/index.tsx (1)
EmpiresFilter(12-30)
src/app/[locale]/(rootEntityPages)/authors/page.tsx (1)
src/components/empires-filter/index.tsx (1)
EmpiresFilter(12-30)
src/app/[locale]/(defaultLayout)/team/page.tsx (4)
src/lib/constants.ts (1)
VOLUNTEER_URL(9-10)src/lib/seo.ts (1)
SITE_CONFIG(8-18)src/app/[locale]/(defaultLayout)/team/members.ts (4)
boardMembers(15-31)engineeringTeam(33-46)researchAnnotationTeam(48-73)pastTeamMembers(75-100)src/app/[locale]/(defaultLayout)/team/member-card.tsx (1)
MemberCard(3-50)
src/components/empires-filter/index.tsx (3)
src/components/empires-filter/client.tsx (1)
EmpiresFilterClient(40-192)src/lib/api/empires.ts (1)
findAllEmpiresWithBooksCount(19-37)src/lib/locale/server.ts (1)
getPathLocale(7-10)
src/app/[locale]/chat/status-label.tsx (2)
src/components/shiny-text.tsx (1)
ShinyText(9-41)src/lib/utils.ts (1)
cn(5-7)
src/app/[locale]/t/[bookId]/_components/reader-content/book-info.tsx (4)
src/components/ui/dotted-list/index.tsx (1)
DottedList(15-62)src/navigation.tsx (1)
Link(10-10)src/lib/urls.ts (1)
navigation(4-65)src/components/ui/badge/index.tsx (1)
Badge(45-45)
src/app/[locale]/(entityPages)/author/[authorSlug]/page.tsx (1)
src/components/ui/expandible-list/index.tsx (1)
ExpandibleList(34-115)
src/lib/api/regions.ts (2)
src/lib/api/utils.ts (1)
apiFetch(37-69)src/types/api/region.ts (1)
ApiRegion(1-9)
🪛 ast-grep (0.40.4)
src/app/[locale]/chat/status-label.tsx
[warning] 76-76: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 Biome (2.1.2)
src/app/[locale]/chat/status-label.tsx
[error] 77-77: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
src/app/[locale]/t/[bookId]/_components/reader-content/book-info.tsx
[error] 108-108: Missing key property for this element in iterable.
The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.
(lint/correctness/useJsxKeyInIterable)
[error] 125-125: Missing key property for this element in iterable.
The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.
(lint/correctness/useJsxKeyInIterable)
[error] 142-142: Missing key property for this element in iterable.
The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.
(lint/correctness/useJsxKeyInIterable)
🔇 Additional comments (23)
src/components/genres-tree-chart/client.tsx (1)
606-610: Responsive container sizing looks good.The
h-[300px] sm:h-[500px]change matches the new mobile-first behavior and should keep the chart usable on smaller screens.src/components/regions-choropleth-map/regions-to-country-codes.tsx (1)
42-42: Duplicate country code mapping for GBR.Both
united-kingdom(line 42) andwales(line 152) map to["GBR"]. Inclient.tsx, when multiple regions map to the same country code, the one with more books takes precedence (lines 119-121). This means one of these regions will never be displayed on the map.If Wales should be visually distinct, consider whether this is intentional or if Wales data should be consolidated with UK data upstream.
Also applies to: 152-152
src/components/regions-choropleth-map/client.tsx (1)
86-580: LGTM on the main map effect structure.The effect properly:
- Guards against missing data/dimensions
- Uses
isMountedRefto prevent state updates after unmount- Cleans up tooltip, SVG, and zoom ref on unmount
- Handles both mouse and touch interactions
The complexity is appropriate for the D3 integration requirements.
locales/en/chat.json (1)
64-64: LGTM!Trailing newline added - follows POSIX best practices for text files.
src/types/empire.ts (1)
11-13: LGTM!The optional
hijriStartYearandhijriEndYearfields appropriately support the "Empires & Eras" feature by enabling date range representation.src/app/api/og/region/[slug]/route.tsx (1)
43-46: LGTM!Good defensive coding - the null check on
region.overviewprevents potential runtime errors when the overview is undefined, and the nullish coalescing fallback to an empty string is appropriate.src/app/[locale]/(rootEntityPages)/texts/routeType.ts (1)
24-27: LGTM!The
empiresquery parameter follows the established pattern used byregions,authors, andgenres- consistent transformation and error handling.src/types/api/book.ts (1)
53-72: LGTM!The new
regions,empires, andadvancedGenresfields are well-structured and align with their usage in the book reader component. The type shapes appropriately reflect domain differences (e.g.,advancedGenresomitsnumberOfAuthors).src/app/[locale]/t/[bookId]/_components/reader-content/book-info.tsx (1)
43-45: LGTM!Good extraction of the new data fields from the book response. The translation keys (
entities.genres,entities.regions,entities.empires) follow theuseTranslationspattern correctly.locales/en/entities.json (1)
9-11: LGTM! Consistent terminology update.The update from "Empire" to "Empire & Era" is applied consistently across singular, plural, and count-based variations, which maintains proper localization structure.
src/app/[locale]/(rootEntityPages)/authors/page.tsx (1)
4-4: LGTM! Clean integration of EmpiresFilter.The EmpiresFilter is integrated consistently with the existing RegionsFilter pattern:
- Import added at the top
- Parameter extracted from searchParams
- Passed through to the search API filters
- Rendered in the filters section with appropriate props
The implementation follows established patterns in the codebase.
Also applies to: 38-38, 51-51, 86-87
src/app/[locale]/(rootEntityPages)/texts/page.tsx (1)
5-5: LGTM! Consistent EmpiresFilter integration.The EmpiresFilter implementation mirrors the pattern used in the authors page and is consistent with the existing RegionsFilter:
- Import added appropriately
- Parameter extracted from searchParams
- Passed through to searchBooks filters
- Rendered in the filters section with currentEmpires prop
The TODO comment about year range filtering is already present for RegionsFilter and can be addressed in a future enhancement.
Also applies to: 40-40, 54-54, 103-109
src/components/book-search-result/info-dialog.tsx (1)
67-67: LGTM! Clean refactoring from locations to regions.The direct usage of
author.regionsis cleaner and aligns with the updatedApiAuthortype. The fallback to an empty array handles cases where regions might be undefined.src/app/[locale]/(rootEntityPages)/authors/routeType.ts (1)
35-38: LGTM! Consistent addition of empires filter parameter.The
empiresparameter follows the same pattern as the existingregionsparameter, enabling filtering by empires in author listings. The implementation is clean and consistent.src/lib/api/regions.ts (2)
9-14: LGTM! Clean simplification of region API type.Removing the locations augmentation simplifies the API response and aligns with the broader refactoring from location-based to region-based data structures.
9-14: No issues found with the locations field removal.The verification confirms that no downstream code accesses the
locationsfield from region responses. The function is called in three locations (region/[regionSlug]/page.tsxandapi/og/region/[slug]/route.tsx), and in all cases, only theoverviewfield is accessed. The type simplification is safe.src/types/api/region.ts (1)
6-6: Migration verified:currentNameremoval is clean andoverviewoptional handling is proper.No references to
currentNameremain in the codebase. Theoverviewfield is properly handled in all consuming code:
src/app/api/og/region/[slug]/route.tsxuses explicit null coalescing (??)src/app/[locale]/(entityPages)/region/[regionSlug]/page.tsxconditionally renders with guard checkssrc/types/api/author.ts (1)
11-22: Migration fromlocationstoregionsandempiresis complete.The breaking change to the
ApiAuthortype has been successfully implemented. No remaining references toauthor.locationsexist in the codebase, and code accessing this field has been updated to use the newregionsfield (as seen in the author detail page). Bothregionsandempiresare properly defined with the expected structure.src/app/[locale]/(entityPages)/author/[authorSlug]/page.tsx (1)
117-126: LGTM on the regions data transformation.The refactor from locations to regions is well-implemented. The
EntityActionscorrectly receives region slugs, and theExpandibleListdisplays localized region names with proper singular/plural noun handling.Also applies to: 138-149
src/app/[locale]/(defaultLayout)/team/page.tsx (1)
38-69: LGTM on team page structure.The reorganization into distinct board, engineering, research/annotation, and past team sections is well-structured with consistent grid layouts and proper data binding.
Also applies to: 74-89, 91-108, 110-125
src/components/empires-filter/client.tsx (1)
40-77: LGTM on overall component structure.The component correctly implements debounced URL updates, fuzzy search with Fuse.js, and state synchronization. The UI properly handles loading states, clear filters, and incremental loading.
Also applies to: 134-191
locales/en/team.json (1)
8-10: LGTM on translation updates.The new translation keys (
engineering-team-title,research-annotation-team-title,past-team-members-title,board-members.*,director-of-operations) properly support the team page restructuring and align with the data inmembers.ts.Also applies to: 13-26, 77-78
src/app/[locale]/(defaultLayout)/team/members.ts (1)
15-100: LGTM on team data restructuring.The reorganization into
boardMembers,engineeringTeam,researchAnnotationTeam, andpastTeamMembersis well-structured. Board members appropriately omitroleKeysince they display differently, while other teams include role information. All keys align with the translation file.
| "empires": { | ||
| "title": "Empires", | ||
| "title": "Empires & Eras", | ||
| "description": "{empires, number} Empires" | ||
| } |
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.
Minor inconsistency between title and description.
The title was updated to "Empires & Eras" but the description still reads "{empires, number} Empires". Consider updating the description to match the new terminology for consistency.
Suggested fix
"empires": {
"title": "Empires & Eras",
- "description": "{empires, number} Empires"
+ "description": "{empires, number} Empires & Eras"
}📝 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.
| "empires": { | |
| "title": "Empires", | |
| "title": "Empires & Eras", | |
| "description": "{empires, number} Empires" | |
| } | |
| "empires": { | |
| "title": "Empires & Eras", | |
| "description": "{empires, number} Empires & Eras" | |
| } |
🤖 Prompt for AI Agents
In @locales/en/common.json around lines 83 - 86, The description value under the
"empires" key is inconsistent with the updated title "Empires & Eras"; update
the empires.description string to reflect the new terminology (for example
change "{empires, number} Empires" to something like "{empires, number} Empires
& Eras" or another appropriate localized phrasing) so empires.title and
empires.description match; edit the "empires" object (keys empires.title and
empires.description) to keep wording consistent across the locale entry.
| const bio = author.bio; | ||
| const otherNames = author.otherNames ?? []; | ||
|
|
||
| const regions = author.regions; |
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.
Potential runtime error if author.regions is undefined.
Line 124 uses optional chaining (author.regions?.map), suggesting regions can be undefined. However, line 138 accesses regions.length without a guard, which will throw a TypeError if regions is undefined.
🐛 Suggested fix
- const regions = author.regions;
+ const regions = author.regions ?? [];📝 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.
| const regions = author.regions; | |
| const regions = author.regions ?? []; |
🤖 Prompt for AI Agents
In @src/app/[locale]/(entityPages)/author/[authorSlug]/page.tsx at line 104, The
variable assignment "const regions = author.regions;" can be undefined and later
code uses regions.length, causing a TypeError; change the assignment to ensure a
safe default (e.g., use nullish coalescing or similar) so "regions" is always an
array (for example set regions to author.regions ?? []), and keep existing
optional chaining (author.regions?.map) consistent with the new default so all
uses of regions (including regions.length) are safe.
| import type { MessageAnnotation } from "@/types/chat"; | ||
| import { useState } from "react"; | ||
| import { ShinyText } from "@/components/shiny-text"; | ||
| import { Button } from "@/components/ui/button"; | ||
| import { cn } from "@/lib/utils"; | ||
| import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"; | ||
| import { useTranslations } from "next-intl"; | ||
|
|
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.
Add "use client" (hooks used in an App Router file).
This component uses useState (Line 32) and useTranslations() (Line 31), so it must be a Client Component in Next.js App Router.
Proposed fix
+'use client';
+
import type { MessageAnnotation } from "@/types/chat";
import { useState } from "react";
import { ShinyText } from "@/components/shiny-text";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
import { useTranslations } from "next-intl";Also applies to: 31-33
🤖 Prompt for AI Agents
In @src/app/[locale]/chat/status-label.tsx around lines 1 - 8, This file uses
React hooks useState and useTranslations in the App Router, so add the Client
Component directive by inserting the string "use client" as the very first line
of src/app/[locale]/chat/status-label.tsx (before any imports); ensure it
appears above the imports that include useState and useTranslations so the
component (the code referencing useState and the useTranslations() call) is
treated as a client component.
| <div className="flex items-start gap-2"> | ||
| <ShinyText | ||
| className={cn( | ||
| "-mb-2 w-fit flex-1 font-medium", | ||
| "sm:block", | ||
| !isExpanded && "line-clamp-1 sm:line-clamp-none", | ||
| )} | ||
| shimmerWidth={40} | ||
| disabled={!isLoading} | ||
| dangerouslySetInnerHTML={{ __html: text }} | ||
| /> | ||
| <Button | ||
| variant="ghost" | ||
| size="icon" | ||
| className="text-muted-foreground h-auto p-1 sm:hidden" | ||
| onClick={() => setIsExpanded(!isExpanded)} | ||
| aria-label={isExpanded ? "Collapse" : "Expand"} | ||
| > | ||
| {isExpanded ? ( | ||
| <ChevronUpIcon className="size-4" /> | ||
| ) : ( | ||
| <ChevronDownIcon className="size-4" /> | ||
| )} | ||
| </Button> | ||
| </div> |
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.
Localize toggle aria-label and use a functional state update.
aria-labelis hardcoded English (Line 84) in a localized UI; prefert(...).setIsExpanded(!isExpanded)is avoidable; use functional update to prevent stale reads.
Proposed fix
<Button
@@
- onClick={() => setIsExpanded(!isExpanded)}
- aria-label={isExpanded ? "Collapse" : "Expand"}
+ onClick={() => setIsExpanded((v) => !v)}
+ aria-label={
+ isExpanded ? t("common.collapse") : t("common.expand")
+ }
+ aria-expanded={isExpanded}
>🧰 Tools
🪛 ast-grep (0.40.4)
[warning] 76-76: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 Biome (2.1.2)
[error] 77-77: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🤖 Prompt for AI Agents
In @src/app/[locale]/chat/status-label.tsx around lines 68 - 92, The aria-label
is hardcoded and the state toggle can suffer stale reads: replace the literal
aria-label with a localized string from your i18n function (use t(...) to
produce the Expand/Collapse text based on isExpanded) and change the onClick
handler on the Button from setIsExpanded(!isExpanded) to a functional update
setIsExpanded(prev => !prev) so the toggle is localized and safe from stale
state; apply these changes where isExpanded and setIsExpanded are referenced and
update the aria-label logic to choose the localized key matching the
ChevronUpIcon/ChevronDownIcon state.
| <DottedList | ||
| className="mt-6 text-xs md:text-sm" | ||
| items={[ | ||
| advancedGenres.length > 0 ? ( | ||
| <div className="flex flex-wrap items-center gap-2"> | ||
| <span className="text-muted-foreground"> | ||
| {t("entities.genres")} | ||
| </span> | ||
| {advancedGenres.map((advancedGenre) => ( | ||
| <Link | ||
| key={advancedGenre.id} | ||
| href={navigation.genres.bySlug(advancedGenre.slug)} | ||
| > | ||
| <Badge variant="outline" className="hover:bg-accent"> | ||
| {advancedGenre.name} | ||
| </Badge> | ||
| </Link> | ||
| ))} | ||
| </div> | ||
| ) : null, | ||
| regions.length > 0 ? ( | ||
| <div className="flex flex-wrap items-center gap-2"> | ||
| <span className="text-muted-foreground"> | ||
| {t("entities.regions")} | ||
| </span> | ||
| {regions.map((region) => ( | ||
| <Link | ||
| key={region.id} | ||
| href={navigation.regions.bySlug(region.slug)} | ||
| > | ||
| <Badge variant="outline" className="hover:bg-accent"> | ||
| {region.name} | ||
| </Badge> | ||
| </Link> | ||
| ))} | ||
| </div> | ||
| ) : null, | ||
| empires.length > 0 ? ( | ||
| <div className="flex flex-wrap items-center gap-2"> | ||
| <span className="text-muted-foreground"> | ||
| {t("entities.empires")} | ||
| </span> | ||
| {empires.map((empire) => ( | ||
| <Link | ||
| key={empire.id} | ||
| href={navigation.empires.bySlug(empire.slug)} | ||
| > | ||
| <Badge variant="outline" className="hover:bg-accent"> | ||
| {empire.name} | ||
| </Badge> | ||
| </Link> | ||
| ))} | ||
| </div> | ||
| ) : null, | ||
| ]} | ||
| /> |
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.
Add keys to the items array elements to satisfy React's reconciliation.
The static analysis correctly flags that the <div> elements in the items array (lines 108, 125, 142) are missing key props. While DottedList uses array indices internally, React still expects keys on the JSX elements passed in the array.
Suggested fix
<DottedList
className="mt-6 text-xs md:text-sm"
items={[
advancedGenres.length > 0 ? (
- <div className="flex flex-wrap items-center gap-2">
+ <div key="genres" className="flex flex-wrap items-center gap-2">
<span className="text-muted-foreground">
{t("entities.genres")}
</span>
{advancedGenres.map((advancedGenre) => (
<Link
key={advancedGenre.id}
href={navigation.genres.bySlug(advancedGenre.slug)}
>
<Badge variant="outline" className="hover:bg-accent">
{advancedGenre.name}
</Badge>
</Link>
))}
</div>
) : null,
regions.length > 0 ? (
- <div className="flex flex-wrap items-center gap-2">
+ <div key="regions" className="flex flex-wrap items-center gap-2">
<span className="text-muted-foreground">
{t("entities.regions")}
</span>
{regions.map((region) => (
<Link
key={region.id}
href={navigation.regions.bySlug(region.slug)}
>
<Badge variant="outline" className="hover:bg-accent">
{region.name}
</Badge>
</Link>
))}
</div>
) : null,
empires.length > 0 ? (
- <div className="flex flex-wrap items-center gap-2">
+ <div key="empires" className="flex flex-wrap items-center gap-2">
<span className="text-muted-foreground">
{t("entities.empires")}
</span>
{empires.map((empire) => (
<Link
key={empire.id}
href={navigation.empires.bySlug(empire.slug)}
>
<Badge variant="outline" className="hover:bg-accent">
{empire.name}
</Badge>
</Link>
))}
</div>
) : null,
]}
/>📝 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.
| <DottedList | |
| className="mt-6 text-xs md:text-sm" | |
| items={[ | |
| advancedGenres.length > 0 ? ( | |
| <div className="flex flex-wrap items-center gap-2"> | |
| <span className="text-muted-foreground"> | |
| {t("entities.genres")} | |
| </span> | |
| {advancedGenres.map((advancedGenre) => ( | |
| <Link | |
| key={advancedGenre.id} | |
| href={navigation.genres.bySlug(advancedGenre.slug)} | |
| > | |
| <Badge variant="outline" className="hover:bg-accent"> | |
| {advancedGenre.name} | |
| </Badge> | |
| </Link> | |
| ))} | |
| </div> | |
| ) : null, | |
| regions.length > 0 ? ( | |
| <div className="flex flex-wrap items-center gap-2"> | |
| <span className="text-muted-foreground"> | |
| {t("entities.regions")} | |
| </span> | |
| {regions.map((region) => ( | |
| <Link | |
| key={region.id} | |
| href={navigation.regions.bySlug(region.slug)} | |
| > | |
| <Badge variant="outline" className="hover:bg-accent"> | |
| {region.name} | |
| </Badge> | |
| </Link> | |
| ))} | |
| </div> | |
| ) : null, | |
| empires.length > 0 ? ( | |
| <div className="flex flex-wrap items-center gap-2"> | |
| <span className="text-muted-foreground"> | |
| {t("entities.empires")} | |
| </span> | |
| {empires.map((empire) => ( | |
| <Link | |
| key={empire.id} | |
| href={navigation.empires.bySlug(empire.slug)} | |
| > | |
| <Badge variant="outline" className="hover:bg-accent"> | |
| {empire.name} | |
| </Badge> | |
| </Link> | |
| ))} | |
| </div> | |
| ) : null, | |
| ]} | |
| /> | |
| <DottedList | |
| className="mt-6 text-xs md:text-sm" | |
| items={[ | |
| advancedGenres.length > 0 ? ( | |
| <div key="genres" className="flex flex-wrap items-center gap-2"> | |
| <span className="text-muted-foreground"> | |
| {t("entities.genres")} | |
| </span> | |
| {advancedGenres.map((advancedGenre) => ( | |
| <Link | |
| key={advancedGenre.id} | |
| href={navigation.genres.bySlug(advancedGenre.slug)} | |
| > | |
| <Badge variant="outline" className="hover:bg-accent"> | |
| {advancedGenre.name} | |
| </Badge> | |
| </Link> | |
| ))} | |
| </div> | |
| ) : null, | |
| regions.length > 0 ? ( | |
| <div key="regions" className="flex flex-wrap items-center gap-2"> | |
| <span className="text-muted-foreground"> | |
| {t("entities.regions")} | |
| </span> | |
| {regions.map((region) => ( | |
| <Link | |
| key={region.id} | |
| href={navigation.regions.bySlug(region.slug)} | |
| > | |
| <Badge variant="outline" className="hover:bg-accent"> | |
| {region.name} | |
| </Badge> | |
| </Link> | |
| ))} | |
| </div> | |
| ) : null, | |
| empires.length > 0 ? ( | |
| <div key="empires" className="flex flex-wrap items-center gap-2"> | |
| <span className="text-muted-foreground"> | |
| {t("entities.empires")} | |
| </span> | |
| {empires.map((empire) => ( | |
| <Link | |
| key={empire.id} | |
| href={navigation.empires.bySlug(empire.slug)} | |
| > | |
| <Badge variant="outline" className="hover:bg-accent"> | |
| {empire.name} | |
| </Badge> | |
| </Link> | |
| ))} | |
| </div> | |
| ) : null, | |
| ]} | |
| /> |
🧰 Tools
🪛 Biome (2.1.2)
[error] 108-108: Missing key property for this element in iterable.
The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.
(lint/correctness/useJsxKeyInIterable)
[error] 125-125: Missing key property for this element in iterable.
The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.
(lint/correctness/useJsxKeyInIterable)
[error] 142-142: Missing key property for this element in iterable.
The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.
(lint/correctness/useJsxKeyInIterable)
🤖 Prompt for AI Agents
In @src/app/[locale]/t/[bookId]/_components/reader-content/book-info.tsx around
lines 104 - 159, The three JSX blocks passed into the DottedList items array
(the wrapping <div> for advancedGenres, regions, and empires) need explicit key
props to satisfy React reconciliation; update the elements created for
advancedGenres, regions, and empires inside the items array to include stable
keys (e.g., key="genres", key="regions", key="empires" or derived stable values)
on the outer <div> nodes so each entry in the items array has a unique key.
| const handleShare = async () => { | ||
| const textFromParts = message.parts | ||
| .filter((part) => part.type === "text") | ||
| .map((part) => part.text) | ||
| .join("\n") | ||
| .trim(); | ||
|
|
||
| if (!textFromParts) { | ||
| toast.error("There's no text to share!"); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| await navigator.share({ | ||
| title: "Usul", | ||
| text: textFromParts + "\n\n\nPowered by Usul.ai: https://usul.ai/", | ||
| // url: window.location.href, | ||
| }); | ||
| toast.success("Shared!"); | ||
| } catch (error) { | ||
| // toast.error("Failed to share!"); | ||
| } | ||
| }; |
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.
Use translations for user-facing strings and avoid silent error handling.
This function has several issues that need addressing:
- Hard-coded strings: Lines 74 and 84 use hard-coded English strings instead of translation keys, violating the i18n requirements specified in the coding guidelines.
- Silent error handling: The catch block (lines 85-87) silently swallows errors without any feedback to the user or logging.
- Hard-coded footer text: Line 81 includes a hard-coded promotional footer that should be externalized to a translation file.
🌐 Proposed fix to use translations and improve error handling
First, add the necessary translation keys to locales/en/common.json or the appropriate translation file:
{
"reader": {
"share": "Share",
"shared": "Shared successfully!",
"no-text-to-share": "There's no text to share!",
"share-failed": "Failed to share. Please try again.",
"share-footer": "Powered by Usul.ai: https://usul.ai/"
}
}Then update the function:
const handleShare = async () => {
const textFromParts = message.parts
.filter((part) => part.type === "text")
.map((part) => part.text)
.join("\n")
.trim();
if (!textFromParts) {
- toast.error("There's no text to share!");
+ toast.error(t("reader.no-text-to-share"));
return;
}
try {
await navigator.share({
title: "Usul",
- text: textFromParts + "\n\n\nPowered by Usul.ai: https://usul.ai/",
+ text: textFromParts + "\n\n\n" + t("reader.share-footer"),
// url: window.location.href,
});
- toast.success("Shared!");
+ toast.success(t("reader.shared"));
} catch (error) {
- // toast.error("Failed to share!");
+ // Only show error if user didn't cancel
+ if (error instanceof Error && error.name !== "AbortError") {
+ toast.error(t("reader.share-failed"));
+ }
}
};As per coding guidelines, all user-facing strings must use the useTranslations hook from next-intl.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @src/components/chat/message-actions.tsx around lines 66 - 88, Replace
hard-coded UI strings in handleShare with translations via useTranslations
(e.g., call useTranslations("common") and use keys like reader.share,
reader.shared, reader["no-text-to-share"], reader["share-failed"],
reader["share-footer"]) and concatenate reader("share-footer") instead of the
literal footer; ensure message.parts processing remains the same. Also remove
the silent catch: log the caught error (console.error or a logger) and show a
translated failure toast using toast.error(reader("share-failed")) so users get
feedback if navigator.share throws, while keeping
toast.success(reader("shared")) on success. Ensure imports for useTranslations
are added and translation keys are present in your locale files.
| const handleChange = (empireSlug: string) => { | ||
| let newSelectedEmpires = [...selectedEmpires]; | ||
|
|
||
| if (newSelectedEmpires.includes(empireSlug)) { | ||
| newSelectedEmpires = newSelectedEmpires.filter((g) => g !== empireSlug); | ||
| } else { | ||
| newSelectedEmpires.push(empireSlug); | ||
| } | ||
|
|
||
| setSelectedEmpires(newSelectedEmpires); | ||
|
|
||
| const params = getEmpireFilterUrlParams(newSelectedEmpires, searchParams); | ||
|
|
||
| if (timeoutRef.current) { | ||
| clearTimeout(timeoutRef.current); | ||
| } | ||
|
|
||
| const newTimeout = setTimeout(() => { | ||
| startTransition(() => { | ||
| replace(`${pathname}?${params.toString()}`, { scroll: false }); | ||
| }); | ||
| }, DEBOUNCE_DELAY); | ||
|
|
||
| timeoutRef.current = newTimeout; | ||
| }; |
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.
Missing timeout cleanup on component unmount.
The debounced timeout in handleChange is not cleared when the component unmounts, which can cause a memory leak and attempts to update state on an unmounted component.
🐛 Suggested fix - add cleanup effect
useEffect(() => {
setSelectedEmpires(currentEmpires);
}, [currentEmpires]);
+ useEffect(() => {
+ return () => {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ }
+ };
+ }, []);
+
const handleChange = (empireSlug: string) => {📝 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.
| const handleChange = (empireSlug: string) => { | |
| let newSelectedEmpires = [...selectedEmpires]; | |
| if (newSelectedEmpires.includes(empireSlug)) { | |
| newSelectedEmpires = newSelectedEmpires.filter((g) => g !== empireSlug); | |
| } else { | |
| newSelectedEmpires.push(empireSlug); | |
| } | |
| setSelectedEmpires(newSelectedEmpires); | |
| const params = getEmpireFilterUrlParams(newSelectedEmpires, searchParams); | |
| if (timeoutRef.current) { | |
| clearTimeout(timeoutRef.current); | |
| } | |
| const newTimeout = setTimeout(() => { | |
| startTransition(() => { | |
| replace(`${pathname}?${params.toString()}`, { scroll: false }); | |
| }); | |
| }, DEBOUNCE_DELAY); | |
| timeoutRef.current = newTimeout; | |
| }; | |
| useEffect(() => { | |
| setSelectedEmpires(currentEmpires); | |
| }, [currentEmpires]); | |
| useEffect(() => { | |
| return () => { | |
| if (timeoutRef.current) { | |
| clearTimeout(timeoutRef.current); | |
| } | |
| }; | |
| }, []); | |
| const handleChange = (empireSlug: string) => { | |
| let newSelectedEmpires = [...selectedEmpires]; | |
| if (newSelectedEmpires.includes(empireSlug)) { | |
| newSelectedEmpires = newSelectedEmpires.filter((g) => g !== empireSlug); | |
| } else { | |
| newSelectedEmpires.push(empireSlug); | |
| } | |
| setSelectedEmpires(newSelectedEmpires); | |
| const params = getEmpireFilterUrlParams(newSelectedEmpires, searchParams); | |
| if (timeoutRef.current) { | |
| clearTimeout(timeoutRef.current); | |
| } | |
| const newTimeout = setTimeout(() => { | |
| startTransition(() => { | |
| replace(`${pathname}?${params.toString()}`, { scroll: false }); | |
| }); | |
| }, DEBOUNCE_DELAY); | |
| timeoutRef.current = newTimeout; | |
| }; |
🤖 Prompt for AI Agents
In @src/components/empires-filter/client.tsx around lines 78 - 102, The
debounced timeout set in handleChange via timeoutRef.current isn't cleared on
unmount; add a useEffect cleanup that on component unmount clears
timeoutRef.current (using clearTimeout) to avoid memory leaks and stale updates.
Specifically, add a useEffect that returns a cleanup function which checks if
timeoutRef.current is set and calls clearTimeout(timeoutRef.current), ensuring
any pending startTransition/replace triggered by the DEBOUNCE_DELAY is cancelled
when the component unmounts; keep references to timeoutRef and any used setters
(setSelectedEmpires) unchanged.
| const selectedEmpiresItems = selectedEmpires.map( | ||
| (slug) => slugToEmpire[slug], | ||
| ) as typeof empires; |
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.
Potential undefined values when mapping selected empire slugs.
If a selectedEmpires slug doesn't exist in the slugToEmpire map (e.g., due to stale URL params), slugToEmpire[slug] returns undefined. This propagates to matchedEmpires.items and causes runtime errors when accessing properties like empire.numberOfBooks on line 158.
🐛 Suggested fix
const selectedEmpiresItems = selectedEmpires.map(
(slug) => slugToEmpire[slug],
- ) as typeof empires;
+ ).filter((empire): empire is (typeof empires)[number] => empire !== undefined);
const selectedEmpiresSet = new Set(selectedEmpires);📝 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.
| const selectedEmpiresItems = selectedEmpires.map( | |
| (slug) => slugToEmpire[slug], | |
| ) as typeof empires; | |
| const selectedEmpiresItems = selectedEmpires.map( | |
| (slug) => slugToEmpire[slug], | |
| ).filter((empire): empire is (typeof empires)[number] => empire !== undefined); |
🤖 Prompt for AI Agents
In @src/components/empires-filter/client.tsx around lines 107 - 109, The mapping
for selectedEmpiresItems can produce undefined when a slug is missing from
slugToEmpire; update the code that builds selectedEmpiresItems (and any later
use like matchedEmpires.items) to filter out undefined results (e.g., map then
filter(Boolean) or reduce) so only valid empire objects from slugToEmpire are
included; ensure the resulting array has the same type as empires and downstream
code (where empire.numberOfBooks is accessed) only runs on these filtered,
non-undefined empire objects.
| // filter empires with count > 0 | ||
| let filteredEmpires = empires.filter((empire) => empire.numberOfBooks > 0); | ||
|
|
||
| // sort empires by count descending | ||
| filteredEmpires.sort((a, b) => b.numberOfBooks - a.numberOfBooks); |
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.
Filtering logic may exclude empires when countType is "authors".
The filter checks numberOfBooks > 0, but when countType is "authors", empires with authors but zero books will be excluded. Consider filtering based on the actual countType:
🐛 Suggested fix
// filter empires with count > 0
- let filteredEmpires = empires.filter((empire) => empire.numberOfBooks > 0);
+ let filteredEmpires = empires.filter((empire) =>
+ props.countType === "authors"
+ ? empire.numberOfAuthors > 0
+ : empire.numberOfBooks > 0
+ );
// sort empires by count descending
- filteredEmpires.sort((a, b) => b.numberOfBooks - a.numberOfBooks);
+ filteredEmpires.sort((a, b) =>
+ props.countType === "authors"
+ ? b.numberOfAuthors - a.numberOfAuthors
+ : b.numberOfBooks - a.numberOfBooks
+ );📝 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.
| // filter empires with count > 0 | |
| let filteredEmpires = empires.filter((empire) => empire.numberOfBooks > 0); | |
| // sort empires by count descending | |
| filteredEmpires.sort((a, b) => b.numberOfBooks - a.numberOfBooks); | |
| // filter empires with count > 0 | |
| let filteredEmpires = empires.filter((empire) => | |
| props.countType === "authors" | |
| ? empire.numberOfAuthors > 0 | |
| : empire.numberOfBooks > 0 | |
| ); | |
| // sort empires by count descending | |
| filteredEmpires.sort((a, b) => | |
| props.countType === "authors" | |
| ? b.numberOfAuthors - a.numberOfAuthors | |
| : b.numberOfBooks - a.numberOfBooks | |
| ); |
🤖 Prompt for AI Agents
In @src/components/empires-filter/index.tsx around lines 23 - 27, The current
filter uses numberOfBooks > 0 which incorrectly drops empires when countType ===
"authors"; update the filtering logic for filteredEmpires to check the relevant
field based on countType (e.g., if countType === "authors" use
empire.numberOfAuthors > 0, otherwise use empire.numberOfBooks > 0) and ensure
any subsequent sort uses the same chosen field (use empire.numberOfAuthors when
counting authors, otherwise empire.numberOfBooks) so both filtering and sorting
are consistent with the countType.
| secondaryOtherNames?: string[]; | ||
|
|
||
| regions: string[]; // region slugs | ||
| regions?: string[]; // region slugs |
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.
🧩 Analysis chain
🏁 Script executed:
# First, let's look at the author.ts file to understand the context
head -30 src/types/author.tsRepository: seemorg/usul
Length of output: 348
🏁 Script executed:
# Search for all usages of author.regions in the codebase
echo "=== Direct property access to .regions ==="
rg -n --type=ts --type=tsx '\.regions' -B 2 -A 2
echo -e "\n=== References to regions in author context ==="
rg -n --type=ts --type=tsx -B 3 -A 3 'regions' | head -100Repository: seemorg/usul
Length of output: 220
🏁 Script executed:
# Check for optional chaining or undefined checks related to regions
echo "=== Optional chaining with regions ==="
rg -n --type=ts --type=tsx 'regions\?' -B 1 -A 1
echo -e "\n=== Conditional checks for regions ==="
rg -n --type=ts --type=tsx 'if.*regions|regions.*if' -B 1 -A 1Repository: seemorg/usul
Length of output: 207
🏁 Script executed:
# Search for all usages of regions in TypeScript files
echo "=== All regions references ==="
rg -n 'regions' --type ts -B 2 -A 2
echo -e "\n=== Files containing regions ==="
rg -l 'regions' --type tsRepository: seemorg/usul
Length of output: 47204
🏁 Script executed:
# Let's look at the repository structure to understand better
echo "=== Repository structure ==="
git ls-files | head -30Repository: seemorg/usul
Length of output: 705
🏁 Script executed:
# Search for imports of AuthorDocument
echo "=== Imports of AuthorDocument ==="
rg -n 'AuthorDocument|from.*author' --type ts -B 1 -A 1Repository: seemorg/usul
Length of output: 11836
🏁 Script executed:
# Look at the author page implementation in detail
sed -n '100,150p' src/app/[locale]/\(entityPages\)/author/\[authorSlug\]/page.tsxRepository: seemorg/usul
Length of output: 1555
🏁 Script executed:
# Check if there are other places using author.regions
rg -n 'author\.regions' --type tsRepository: seemorg/usul
Length of output: 282
Fix author page to safely handle optional regions property.
Making regions optional introduces a breaking change. The author page at src/app/[locale]/(entityPages)/author/[authorSlug]/page.tsx doesn't handle the undefined case:
- Line 104 assigns
author.regionsdirectly without null coalescing - Lines 138 and 142 call
.lengthand.map()on the variable, which will fail if undefined
Apply the same pattern used in src/components/book-search-result/info-dialog.tsx (line 67): use const regions = author.regions ?? []; to provide a safe fallback.
🤖 Prompt for AI Agents
In @src/types/author.ts at line 12, The author page reads author.regions which
is now optional and can be undefined; update the code in the author page
component to create a safe fallback like const regions = author.regions ?? [];
then replace direct uses of author.regions (the assignment at the earlier block
and the subsequent checks/calls that use .length and .map) to use the new
regions variable so .length and .map are called on an empty array when regions
is absent.
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: 5
🤖 Fix all issues with AI agents
In @locales/es/entities.json:
- Around line 9-11: The plural translation string for "x-empires" contains a
typo ("Sin empiros y eras"); update the Spanish plural case value to "Sin
imperios y eras" and, since this file is generated from Crowdin, make the same
correction in the Crowdin source project to prevent future overwrites (modify
the "x-empires" entry both in locales/es/entities.json and in the Crowdin source
translation).
In @locales/hi/team.json:
- Line 29: The translation for the JSON key "name" in team.json contains literal
escaped quotes (\"अनस हसनियन\"), remove the surrounding escaped quote characters
so the value is just "अनस हसनियन" (no extra quotes inside the string) to avoid
rendering literal quote marks in the UI; update the "name" property in
locales/hi/team.json accordingly.
In @locales/ms/entities.json:
- Around line 9-11: The plural key "x-empires" is missing a 'one' clause so
count=1 falls through to 'other'; add an explicit "one" plural case for
"x-empires" (mirroring the singular form used for "empire" if appropriate) e.g.
include one {# ...} between the existing =0 and other cases and confirm the
Malay singular wording for "Empayar & Era" before committing.
In @locales/ps/team.json:
- Line 8: Remove the literal "\n" characters and surrounding leading/trailing
whitespace from the "engineering-team-title" translation value so it no longer
includes unintended newlines when rendered; update the "engineering-team-title"
string to be a single-line Pashto phrase (and verify/correct the translation in
Crowdin or with a native speaker if the current text appears garbled).
- Line 6: The "founders-title" translation string contains a control character
(\u0006) and stray "803" corrupting the value; remove the non-printable
character and the extraneous digits and update the "founders-title" entry to the
correct Pashto text for "Founding Team" (edit in Crowdin or the
locales/ps/team.json source so the value begins directly with the Pashto text).
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (52)
locales/ar/chat.jsonlocales/ar/common.jsonlocales/ar/entities.jsonlocales/ar/team.jsonlocales/bn/chat.jsonlocales/bn/common.jsonlocales/bn/entities.jsonlocales/bn/team.jsonlocales/es/chat.jsonlocales/es/common.jsonlocales/es/entities.jsonlocales/es/team.jsonlocales/fa/chat.jsonlocales/fa/common.jsonlocales/fa/entities.jsonlocales/fa/team.jsonlocales/fr/chat.jsonlocales/fr/common.jsonlocales/fr/entities.jsonlocales/fr/team.jsonlocales/ha/chat.jsonlocales/ha/common.jsonlocales/ha/entities.jsonlocales/ha/team.jsonlocales/hi/chat.jsonlocales/hi/common.jsonlocales/hi/entities.jsonlocales/hi/team.jsonlocales/ms/chat.jsonlocales/ms/common.jsonlocales/ms/entities.jsonlocales/ms/team.jsonlocales/ps/chat.jsonlocales/ps/common.jsonlocales/ps/entities.jsonlocales/ps/team.jsonlocales/ru/chat.jsonlocales/ru/common.jsonlocales/ru/entities.jsonlocales/ru/team.jsonlocales/so/chat.jsonlocales/so/common.jsonlocales/so/entities.jsonlocales/so/team.jsonlocales/tr/chat.jsonlocales/tr/common.jsonlocales/tr/entities.jsonlocales/tr/team.jsonlocales/ur/chat.jsonlocales/ur/common.jsonlocales/ur/entities.jsonlocales/ur/team.json
✅ Files skipped from review due to trivial changes (12)
- locales/so/chat.json
- locales/bn/chat.json
- locales/es/chat.json
- locales/hi/chat.json
- locales/ps/chat.json
- locales/ar/chat.json
- locales/ru/chat.json
- locales/fa/chat.json
- locales/ms/chat.json
- locales/tr/chat.json
- locales/fr/chat.json
- locales/ur/chat.json
🚧 Files skipped from review as they are similar to previous changes (18)
- locales/fa/common.json
- locales/hi/common.json
- locales/so/common.json
- locales/fa/entities.json
- locales/ru/entities.json
- locales/ha/common.json
- locales/fr/common.json
- locales/ar/common.json
- locales/ha/entities.json
- locales/es/common.json
- locales/so/entities.json
- locales/ru/common.json
- locales/bn/common.json
- locales/tr/entities.json
- locales/fr/entities.json
- locales/ar/entities.json
- locales/ps/entities.json
- locales/hi/entities.json
🔇 Additional comments (19)
locales/ha/chat.json (1)
64-64: LGTM! Trailing newline added.The trailing newline at EOF follows standard formatting conventions and improves version control compatibility.
locales/ur/common.json (1)
82-86: LGTM! Translation addition looks correct.The Urdu translation for the empires navigation entry has been properly added with correct JSON structure and ICU MessageFormat syntax.
locales/ps/common.json (1)
82-86: LGTM! Translation addition looks correct.The Pashto translation for the empires navigation entry has been properly added with correct JSON structure and ICU MessageFormat syntax.
locales/ur/entities.json (1)
9-11: LGTM! Entity translations properly added.The Urdu translations for empire-related entities have been correctly added with valid ICU MessageFormat pluralization syntax.
locales/tr/common.json (1)
82-86: LGTM! Translation addition looks correct.The Turkish translation for the empires navigation entry has been properly added with correct JSON structure and ICU MessageFormat syntax.
locales/ha/team.json (1)
1-80: LGTM!The Hausa translation file has been properly updated with new team section keys, board members, and the new
director-of-operationsrole. JSON structure is valid and translations are complete.locales/fr/team.json (1)
1-80: LGTM!The French translation file is properly structured with all new team section keys, board members, and roles translated appropriately.
locales/fa/team.json (1)
1-80: LGTM!The Persian translation file is properly structured with all new keys translated. RTL text is correctly placed within the JSON string values.
locales/es/team.json (1)
1-80: LGTM!The Spanish translation file is properly structured with consistent keys and translations for all new team sections, board members, and roles.
locales/ru/team.json (1)
1-80: LGTM!The Russian translation file is properly structured with all new team section keys, board members, and roles translated appropriately.
locales/so/team.json (1)
1-80: LGTM!The Somali translation file is properly structured with all new keys translated consistently with other locale files.
locales/hi/team.json (1)
1-80: Overall structure is valid.Apart from the escaped quotes issue on line 29, the Hindi translation file is properly structured with all new team section keys, board members, and roles translated appropriately.
locales/ar/team.json (1)
1-80: LGTM!The Arabic translations are well-structured and consistent with the team data restructuring across locales. The new keys for board members, team sections, and the director-of-operations role are properly formatted.
locales/tr/team.json (1)
1-80: LGTM!The Turkish translations follow the same structure as other locales and all new keys are properly translated.
locales/ur/team.json (1)
1-80: LGTM!The Urdu translations are complete and consistent with the team restructuring pattern across all locales.
locales/bn/entities.json (1)
9-11: LGTM!The Bengali empire entity translations are complete with proper ICU plural formatting including all required cases (
=0,one,other).locales/ms/common.json (1)
82-86: LGTM!The Malay navigation entry for empires follows the established pattern and uses the correct placeholder variable.
locales/bn/team.json (1)
1-80: LGTM! Bengali translations are complete and well-structured.The file correctly adds:
- New section titles for engineering team, research/annotation team, and past team members
- Board members with localized names and descriptions
- Updated member entries with proper Bengali translations
- New
director-of-operationsroleThe JSON structure is valid and consistent with the expected format for locale files.
locales/ms/team.json (1)
1-80: LGTM! Malay translations are complete and well-structured.The file correctly adds:
- New section titles (
engineering-team-title,research-annotation-team-title,past-team-members-title)- Board members with appropriate Malay descriptions
- Updated member entries with proper Malay translations
- New
director-of-operationsrole translated as "Pengarah Operasi"The JSON structure is valid and consistent with other locale files in the PR.
| "empire": "Imperio y Era", | ||
| "empires": "Imperios y Eras", | ||
| "x-empires": "{count, plural, =0 {Sin empiros y eras} one {# Imperio y Era} other {# Imperios y Eras}}", |
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.
Fix typo in Spanish translation.
Line 11 contains a typo: "Sin empiros y eras" should be "Sin imperios y eras". The word "empiros" is incorrect Spanish; the correct plural form of "imperio" is "imperios".
Since this is an auto-generated Crowdin sync, the correction should ideally be made in the Crowdin source to prevent it from being overwritten in future syncs.
📝 Proposed fix
- "x-empires": "{count, plural, =0 {Sin empiros y eras} one {# Imperio y Era} other {# Imperios y Eras}}",
+ "x-empires": "{count, plural, =0 {Sin imperios y eras} one {# Imperio y Era} other {# Imperios y Eras}}",📝 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.
| "empire": "Imperio y Era", | |
| "empires": "Imperios y Eras", | |
| "x-empires": "{count, plural, =0 {Sin empiros y eras} one {# Imperio y Era} other {# Imperios y Eras}}", | |
| "empire": "Imperio y Era", | |
| "empires": "Imperios y Eras", | |
| "x-empires": "{count, plural, =0 {Sin imperios y eras} one {# Imperio y Era} other {# Imperios y Eras}}", |
🤖 Prompt for AI Agents
In @locales/es/entities.json around lines 9 - 11, The plural translation string
for "x-empires" contains a typo ("Sin empiros y eras"); update the Spanish
plural case value to "Sin imperios y eras" and, since this file is generated
from Crowdin, make the same correction in the Crowdin source project to prevent
future overwrites (modify the "x-empires" entry both in locales/es/entities.json
and in the Crowdin source translation).
| }, | ||
| "members": { | ||
| "anas-hassanien": { | ||
| "name": "\"अनस हसनियन\"", |
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.
Translation contains unintended escaped quotes.
The name value has escaped quotes (\") inside the string, which will render as literal quote marks in the UI: "अनस हसनियन". This appears to be a translation error from Crowdin.
Suggested fix
- "name": "\"अनस हसनियन\"",
+ "name": "अनस हसनियन",📝 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.
| "name": "\"अनस हसनियन\"", | |
| "name": "अनस हसनियन", |
🤖 Prompt for AI Agents
In @locales/hi/team.json at line 29, The translation for the JSON key "name" in
team.json contains literal escaped quotes (\"अनस हसनियन\"), remove the
surrounding escaped quote characters so the value is just "अनस हसनियन" (no extra
quotes inside the string) to avoid rendering literal quote marks in the UI;
update the "name" property in locales/hi/team.json accordingly.
| "empire": "Empayar & Era", | ||
| "empires": "Empayar & Era", | ||
| "x-empires": "{count, plural, =0 {Tiada empayar & era} other {# Empayar & Era}}", |
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.
Missing one plural case in x-empires.
The x-empires pluralization is missing the one case that other entity patterns in this file include. When count=1, this will fall through to the other case, potentially showing incorrect plural form.
🔧 Suggested fix
- "x-empires": "{count, plural, =0 {Tiada empayar & era} other {# Empayar & Era}}",
+ "x-empires": "{count, plural, =0 {Tiada empayar & era} one {# Empayar & Era} other {# Empayar & Era}}",Note: Verify with Malay translation guidelines whether singular and plural forms should differ for this term.
📝 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.
| "empire": "Empayar & Era", | |
| "empires": "Empayar & Era", | |
| "x-empires": "{count, plural, =0 {Tiada empayar & era} other {# Empayar & Era}}", | |
| "empire": "Empayar & Era", | |
| "empires": "Empayar & Era", | |
| "x-empires": "{count, plural, =0 {Tiada empayar & era} one {# Empayar & Era} other {# Empayar & Era}}", |
🤖 Prompt for AI Agents
In @locales/ms/entities.json around lines 9 - 11, The plural key "x-empires" is
missing a 'one' clause so count=1 falls through to 'other'; add an explicit
"one" plural case for "x-empires" (mirroring the singular form used for "empire"
if appropriate) e.g. include one {# ...} between the existing =0 and other cases
and confirm the Malay singular wording for "Empayar & Era" before committing.
| "subtitle": "د اصول ډلې د انجینرانو، طرحه کوونکو او څېړونکو څخه جوړه ده چې د اسلامي څېړنو بدلون لپاره د ډیجیټل وسیلو جوړولو ته لیوالتیا لري.", | ||
| "become-a-volunteer": "رضا کار شئ", | ||
| "contact-us": "موږ سره اړیکه ونیسئ", | ||
| "founders-title": "\u0006803وړونکی ټیم", |
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.
🧩 Analysis chain
🏁 Script executed:
# Find the file
find . -name "team.json" -path "*/ps/*"Repository: seemorg/usul
Length of output: 78
🏁 Script executed:
# Read the specific file to examine its content
cat locales/ps/team.jsonRepository: seemorg/usul
Length of output: 3631
🏁 Script executed:
# Use od to see the raw bytes and confirm the presence of \u0006
od -c locales/ps/team.json | head -50Repository: seemorg/usul
Length of output: 96
🏁 Script executed:
python3 << 'EOF'
import json
with open('locales/ps/team.json', 'r', encoding='utf-8') as f:
content = f.read()
# Check line 6 content
lines = content.split('\n')
for i, line in enumerate(lines[4:8], start=5):
print(f"Line {i}: {repr(line)}")
# Also parse and examine the founders-title value
with open('locales/ps/team.json', 'r', encoding='utf-8') as f:
data = json.load(f)
founders_title = data.get('founders-title', '')
print(f"\nfounders-title value: {repr(founders_title)}")
print(f"First character code point: U+{ord(founders_title[0]):04X}")
print(f"Characters breakdown: {[f'U+{ord(c):04X}' for c in founders_title[:5]]}")
EOFRepository: seemorg/usul
Length of output: 142
Remove control character from translation string.
The value contains \u0006 (ACK control character) followed by suspicious characters "803", indicating encoding corruption. This non-printable character will cause display issues in the UI. Please verify and correct this translation in Crowdin—the expected value should begin directly with the Pashto text for "Founding Team".
🤖 Prompt for AI Agents
In @locales/ps/team.json at line 6, The "founders-title" translation string
contains a control character (\u0006) and stray "803" corrupting the value;
remove the non-printable character and the extraneous digits and update the
"founders-title" entry to the correct Pashto text for "Founding Team" (edit in
Crowdin or the locales/ps/team.json source so the value begins directly with the
Pashto text).
| "contact-us": "موږ سره اړیکه ونیسئ", | ||
| "founders-title": "\u0006803وړونکی ټیم", | ||
| "founders-subtitle": "هغه بنسټیز ټیم چې Usul تر شا یې دی.", | ||
| "engineering-team-title": "\nتەکنالوژۍ ډلې ناروغه، کوو او د قرآني خبرې\n", |
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.
Unexpected newline characters in translation string.
The engineering-team-title value contains literal \n characters at the beginning and end of the string, which will cause unintended line breaks when rendered in the UI.
🔧 Suggested fix
- "engineering-team-title": "\nتەکنالوژۍ ډلې ناروغه، کوو او د قرآني خبرې\n",
+ "engineering-team-title": "انجنیري ټیم",Note: The actual Pashto translation should be verified in Crowdin—the current content also appears garbled and should be reviewed by a native speaker.
📝 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.
| "engineering-team-title": "\nتەکنالوژۍ ډلې ناروغه، کوو او د قرآني خبرې\n", | |
| "engineering-team-title": "انجنیري ټیم", |
🤖 Prompt for AI Agents
In @locales/ps/team.json at line 8, Remove the literal "\n" characters and
surrounding leading/trailing whitespace from the "engineering-team-title"
translation value so it no longer includes unintended newlines when rendered;
update the "engineering-team-title" string to be a single-line Pashto phrase
(and verify/correct the translation in Crowdin or with a native speaker if the
current text appears garbled).
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
🤖 Fix all issues with AI agents
In @locales/en/team.json:
- Around line 56-67: Update the public-facing bios for "mohamed-lamine",
"rahma-fateen", and "ahmed-khan" to fix grammar and punctuation: for
"mohamed-lamine" change description to a grammatically correct sentence (e.g.,
"Mohamed was the design lead at Thmanyah; he consults with startups to build
products from 0 to 1."), for "rahma-fateen" make the role and past tense
consistent (e.g., "Rahma led Arabic localization at Tarteel AI and was a
translator at Human Rights Watch."), and for "ahmed-khan" correct contractions
and article use (e.g., "Ahmed is a student at Zaytuna College. He is the creator
of The Creative Minority podcast."). Ensure punctuation and capitalization are
consistent across all descriptions.
🧹 Nitpick comments (1)
locales/en/team.json (1)
13-26: Board member bios: consider minor copy edits for consistency/readability.Proposed copy-only diff
"board-members": { "adnan-zulfiqar": { "name": "Adnan Zulfiqar", - "description": "Adnan specializes in Islamic and criminal law. He drafted criminal codes for Maldives and Somalia." + "description": "Adnan specializes in Islamic and criminal law. He drafted criminal codes for the Maldives and Somalia." }, "intisar-rabb": { "name": "Intisar Rabb", "description": "Intisar is a law professor at Harvard. She leads SHARIAsource and the Program in Islamic Law." }, "zeki-mokhtarzada": { "name": "Zeki Mokhtarzada", "description": "Zeki is an entrepreneur with deep technical expertise. He co-founded Freewebs and Truebill." } },
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
locales/en/team.json
🧰 Additional context used
📓 Path-based instructions (1)
locales/en/**/*
📄 CodeRabbit inference engine (.cursor/rules/localization.mdc)
Whenever you're adding a new string to the codebase, make sure to add it to a translation file in
locales/en.
Files:
locales/en/team.json
🔇 Additional comments (5)
locales/en/team.json (5)
30-30: Member bio update looks good.
32-35: New member entry looks consistent with existing structure.
47-47: No concerns on this change (JSON structure remains valid).
77-78:director-of-operationsis properly supported end-to-end. Role is defined insrc/app/[locale]/(defaultLayout)/team/members.ts:42with translations present across all 14 locale files, including the English translation inlocales/en/team.json:78.
8-10: No issues found. The three new section-title keys (engineering-team-title,research-annotation-team-title,past-team-members-title) are properly referenced in the UI component atsrc/app/[locale]/(defaultLayout)/team/page.tsxand consistently present across all 14 locale files. No missing-string fallbacks will occur.
| "mohamed-lamine": { | ||
| "name": "Mohamed Lamine", | ||
| "description": "Mohamed was the design lead at Thmanyah, he consults with startups to build 0 to 1 products." | ||
| }, | ||
| "zeki-mokhtarzada": { | ||
| "name": "Zeki Mokhtarzada", | ||
| "description": "Zeki is an entrepreneur with deep technical expertise. He co-founded Freewebs and Truebill." | ||
| "rahma-fateen": { | ||
| "name": "Rahma Fateen", | ||
| "description": "Rahma lead Arabic localization at Tarteel AI. She was a translator at Human Rights Watch." | ||
| }, | ||
| "ahmed-khan": { | ||
| "name": "Ahmed Khan", | ||
| "description": "Ahmed is a student at Zaytuna College. He's the creator of The Creative Minority podcast." | ||
| } |
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.
Fix grammar in new/updated member bios (public-facing copy).
Proposed copy-only diff
"mohamed-lamine": {
"name": "Mohamed Lamine",
- "description": "Mohamed was the design lead at Thmanyah, he consults with startups to build 0 to 1 products."
+ "description": "Mohamed was the design lead at Thmanyah. He consults with startups to build 0-to-1 products."
},
"rahma-fateen": {
"name": "Rahma Fateen",
- "description": "Rahma lead Arabic localization at Tarteel AI. She was a translator at Human Rights Watch."
+ "description": "Rahma led Arabic localization at Tarteel AI. She was a translator at Human Rights Watch."
},
"ahmed-khan": {
"name": "Ahmed Khan",
- "description": "Ahmed is a student at Zaytuna College. He's the creator of The Creative Minority podcast."
+ "description": "Ahmed is a student at Zaytuna College. He is the creator of The Creative Minority podcast."
}📝 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.
| "mohamed-lamine": { | |
| "name": "Mohamed Lamine", | |
| "description": "Mohamed was the design lead at Thmanyah, he consults with startups to build 0 to 1 products." | |
| }, | |
| "zeki-mokhtarzada": { | |
| "name": "Zeki Mokhtarzada", | |
| "description": "Zeki is an entrepreneur with deep technical expertise. He co-founded Freewebs and Truebill." | |
| "rahma-fateen": { | |
| "name": "Rahma Fateen", | |
| "description": "Rahma lead Arabic localization at Tarteel AI. She was a translator at Human Rights Watch." | |
| }, | |
| "ahmed-khan": { | |
| "name": "Ahmed Khan", | |
| "description": "Ahmed is a student at Zaytuna College. He's the creator of The Creative Minority podcast." | |
| } | |
| "mohamed-lamine": { | |
| "name": "Mohamed Lamine", | |
| "description": "Mohamed was the design lead at Thmanyah. He consults with startups to build 0-to-1 products." | |
| }, | |
| "rahma-fateen": { | |
| "name": "Rahma Fateen", | |
| "description": "Rahma led Arabic localization at Tarteel AI. She was a translator at Human Rights Watch." | |
| }, | |
| "ahmed-khan": { | |
| "name": "Ahmed Khan", | |
| "description": "Ahmed is a student at Zaytuna College. He is the creator of The Creative Minority podcast." | |
| } |
🤖 Prompt for AI Agents
In @locales/en/team.json around lines 56 - 67, Update the public-facing bios for
"mohamed-lamine", "rahma-fateen", and "ahmed-khan" to fix grammar and
punctuation: for "mohamed-lamine" change description to a grammatically correct
sentence (e.g., "Mohamed was the design lead at Thmanyah; he consults with
startups to build products from 0 to 1."), for "rahma-fateen" make the role and
past tense consistent (e.g., "Rahma led Arabic localization at Tarteel AI and
was a translator at Human Rights Watch."), and for "ahmed-khan" correct
contractions and article use (e.g., "Ahmed is a student at Zaytuna College. He
is the creator of The Creative Minority podcast."). Ensure punctuation and
capitalization are consistent across all descriptions.
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: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
locales/es/team.json (1)
13-67: Avoid translating/reordering proper names inmembers.*.name.A few entries appear translated or reordered (e.g., “Laminado de Mohamed”, “Khan Ahmed”, “Safadi de Ismail”), which can cause inconsistent identity display across locales and between “slug” and “name”. Recommend keeping the canonical proper name spellings/order consistent with the source locale.
Proposed diff (adjust if the source locale differs)
- "name": "Safadi de Ismail", + "name": "Ismail Safadi", @@ - "name": "Riad Ahmed", + "name": "Ahmed Riad", @@ - "name": "Laminado de Mohamed", + "name": "Mohamed Lamine", @@ - "name": "Khan Ahmed", + "name": "Ahmed Khan",
🤖 Fix all issues with AI agents
In @locales/ar/entities.json:
- Around line 9-11: The x-empires plural entry only has one and other; update
the "x-empires" ICU plural string to include the full Arabic CLDR categories
(zero, one, two, few, many, other) mirroring the other x-* keys in this file so
counts like 0,2,3-10,11+ are handled; add appropriate Arabic phrases for the
two, few, many (and ensure zero matches the existing "لا امبراطوريات وعصور")
while keeping the existing one/other forms.
In @locales/ar/team.json:
- Around line 13-26: In the locales/ar/team.json entries for "adnan-zulfiqar",
"intisar-rabb", and "zeki-mokhtarzada" fix Arabic copy and name-script
consistency: remove the erroneous leading conjunction in the "adnan-zulfiqar"
description (change "وعدنان" to "عدنان"), change the phrase "منظمة إنتيسار" in
"intisar-rabb" to a proper reference (e.g., "إنتيسار" or "الدكتورة إنتيسار" as
appropriate) and ensure all three "name" fields use Arabic script consistently
(replace the Latin-script names with their correct Arabic transliterations), and
adjust any awkward wording to natural Arabic phrasing while preserving the
original meaning.
In @locales/es/team.json:
- Around line 8-10: The translation for the key "past-team-members-title"
currently reads "Últimos miembros del equipo" (which implies "most recent");
update the value for "past-team-members-title" in locales/es/team.json to a term
that clearly means former members such as "Antiguos miembros del equipo" or
"Miembros anteriores del equipo" so the UI reflects “Past team members” rather
than “Latest”.
In @locales/hi/team.json:
- Around line 13-26: The Hindi bio for "intisar-rabb" has an inconsistent
transliteration: the name field is "इंतिसार रब्ब" but the description uses
"इंतेसर"; update the description text for the "intisar-rabb" object to use the
same spelling "इंतिसार" (so both name and description consistently use "इंतिसार
रब्ब" / "इंतिसार"), and scan the other board-member entries for similar
transliteration inconsistencies to make them uniform.
- Around line 28-35: The JSON value for members.anas-hassanien.name contains
stray double quotes that will render in the UI; remove the surrounding quotes so
the value becomes plain text (e.g., change "\"अनस हसनियन\"" to "अनस हसनियन") in
the members object (symbol: "anas-hassanien" -> "name") and ensure no other
member name strings have extra quote characters.
In @locales/ps/team.json:
- Around line 8-10: The Pashto locale has encoding/whitespace corruption: remove
the leading and trailing literal newlines from the "engineering-team-title"
value so it does not render blank lines, remove the control character U+0006
from the "founders-title" value, and fix the mojibake in the
"abdellatif-abdelfattah" description by correcting the source string in Crowdin
and re-syncing the locale file; update the corresponding keys
("engineering-team-title", "founders-title", "abdellatif-abdelfattah") in the
file after confirming the correct UTF-8 text from the source and re-run the
localization sync to ensure integrity.
In @locales/ru/team.json:
- Around line 60-63: The description for members.rahma-fateen.description
contains two sentences run together without a space after the period; update the
string value for "rahma-fateen" -> "description" so there is a single space
after the period (i.e., change "...Тартеэль И.Она..." to "...Тартеэль И.
Она...") to correct punctuation spacing.
In @locales/so/team.json:
- Around line 30-31: The description at members.anas-hassanien.description uses
the feminine pronoun "Waxay" but Anas is male; update the Somali sentence to use
the correct masculine pronoun (e.g., replace "Waxay" with "Wuxuu") and ensure
the rest of the sentence remains grammatically correct and consistent with
masculine agreement.
🧹 Nitpick comments (2)
locales/ur/team.json (1)
28-31: Consider aligning tense for the updated bio (present vs past) to match the intended style.
If bios are meant to be timeless/current, keep present tense everywhere; otherwise adjust this one for consistency.locales/tr/team.json (1)
28-35: (Optional) Bio tense consistency: Turkishanas-hassanienuses past (“yönetmiştir”) while others might use present; align if you want uniform voice.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
locales/ar/entities.jsonlocales/ar/team.jsonlocales/bn/team.jsonlocales/es/team.jsonlocales/fa/team.jsonlocales/fr/team.jsonlocales/ha/team.jsonlocales/hi/team.jsonlocales/ms/team.jsonlocales/ps/team.jsonlocales/ru/team.jsonlocales/so/team.jsonlocales/tr/team.jsonlocales/ur/team.json
🚧 Files skipped from review as they are similar to previous changes (2)
- locales/fa/team.json
- locales/ha/team.json
🔇 Additional comments (16)
locales/es/team.json (1)
77-78: Role addition is consistent with the other locales in this PR.locales/fr/team.json (2)
13-26: Structure/key additions look consistent (titles,board-members, new role).Also applies to: 30-35, 56-67, 77-78
8-10: No action needed. The three strings use standard French formatting with unescaped apostrophes, consistent with all other French translations in the codebase. The project uses next-intl (a simple JSON-based i18n library), not an ICU MessageFormat parser, so apostrophe escaping is not a concern. Existing French translations confirm this practice is already in use throughout the locale files.Likely an incorrect or invalid review comment.
locales/so/team.json (1)
8-26: New keys/sections and role addition look consistent with the PR’s schema.Also applies to: 32-35, 77-78
locales/bn/team.json (1)
8-26: Looks consistent: added section titles, board members, member updates, and new role.Also applies to: 30-35, 49-55, 77-78
locales/ru/team.json (1)
8-26: New keys/sections and role addition look consistent with the updated team schema.Also applies to: 30-35, 77-78
locales/hi/team.json (2)
8-10: Key parity looks consistent for new team section headers.
No issues with the added keys; ensure other locales use the exact same key names (including hyphens) so the UI doesn’t miss strings.
77-78: Role addition looks good; keep role keys consistent across locales.
Addingroles.director-of-operationsis fine; just ensure the UI expects this exact key (and not a different casing).locales/ur/team.json (2)
8-26: Structure for new headers + board-members looks correct and JSON-safe.
Keys match the other locale files shown (same hyphenated naming), which is the main risk for runtime i18n lookups.
77-78: Role addition is fine.locales/ar/team.json (1)
8-10: Added section headers + new role key look consistent with other locales.Also applies to: 77-78
locales/ms/team.json (3)
8-26: LGTM: new headers + board-members shape is consistent and valid JSON.
28-67: Member additions/updates look consistent with the new data model.
77-78: Role addition looks good.locales/tr/team.json (2)
8-26: New section titles + board-members structure look correct and consistent.
77-78: Role addition is fine.
| "empire": "الإمبراطورية و العصر", | ||
| "empires": "الإمبراطوريات و العصور", | ||
| "x-empires": "{count, plural, =0 {لا امبراطوريات وعصور} one {# امبراطورية وعصر} other {# امبراطورية وعصر}}", |
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.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Validate JSON and extract ICU plural categories
set -euo pipefail
# First, check if file exists and is valid JSON
if [ ! -f "locales/ar/entities.json" ]; then
echo "File not found: locales/ar/entities.json"
exit 1
fi
jq -e . locales/ar/entities.json >/dev/null 2>&1 && echo "✓ JSON is valid"
# Extract and analyze the x-empires key
python3 <<'PY'
import json, re
with open("locales/ar/entities.json", "r", encoding="utf-8") as f:
data = json.load(f)
x_empires = data.get("x-empires", "")
print("\n=== x-empires value ===")
print(x_empires)
# Extract plural categories
cats = re.findall(r'\b(zero|one|two|few|many|other|=0|=1|=\d+)\b', x_empires)
print("\n=== Categories found ===")
print(cats)
print("\n=== Unique categories ===")
print(sorted(set(cats)))
# Check pattern in other x-* keys
print("\n=== Other x-* keys in file ===")
for key in sorted(data.keys()):
if key.startswith("x-"):
val = data[key]
if isinstance(val, str) and "{count, plural" in val:
cats_in_key = re.findall(r'\b(zero|one|two|few|many|other|=0|=1|=\d+)\b', val)
print(f"{key}: {sorted(set(cats_in_key))}")
PYRepository: seemorg/usul
Length of output: 893
🌐 Web query:
Arabic ICU plural categories CLDR rules which plural forms needed
💡 Result:
Use all six CLDR/ICU plural categories for Arabic: zero, one, two, few, many, other.
Rule summary:
- zero → n is 0.
- one → n is 1.
- two → n is 2.
- few → n mod 100 in 3..10.
- many → n mod 100 in 11..99.
- other → everything else (including most decimals/fractions and the special ranges CLDR lists).
Sources: [CLDR language plural rules; LDML/CLDR spec]. (unicode.org)
Citations:
Add missing Arabic plural categories to x-empires to match other x-* keys in the file.
The x-empires key currently defines only one and other categories. All other x-* keys in locales/ar/entities.json use the complete CLDR Arabic plural rules: zero, one, two, few, many, and other. Add the missing categories to ensure proper pluralization for counts like 2, 3-10, and 11-99:
"x-empires": "{count, plural, =0 {لا امبراطوريات وعصور} one {# امبراطورية وعصر} two {# امبراطوريتان وعصران} few {# امبراطوريات وعصور} many {# امبراطوريات وعصور} other {# امبراطورية وعصر}}"
(Adjust the Arabic text for two, few, and many as needed for your use case.)
🤖 Prompt for AI Agents
In @locales/ar/entities.json around lines 9 - 11, The x-empires plural entry
only has one and other; update the "x-empires" ICU plural string to include the
full Arabic CLDR categories (zero, one, two, few, many, other) mirroring the
other x-* keys in this file so counts like 0,2,3-10,11+ are handled; add
appropriate Arabic phrases for the two, few, many (and ensure zero matches the
existing "لا امبراطوريات وعصور") while keeping the existing one/other forms.
| "board-members": { | ||
| "adnan-zulfiqar": { | ||
| "name": "Adnan Zulfiqar", | ||
| "description": "وعدنان متخصص في القانون الإسلامي والقانون الجنائي، وصاغ قانونا جنائيا لكل من ملديف والصومال." | ||
| }, | ||
| "intisar-rabb": { | ||
| "name": "Intisar Rabb", | ||
| "description": "منظمة إنتيسار هي أستاذة قانون في جامعة هارفارد. وهي تتولى قيادة مصدر شاريش وبرنامج الشريعة الإسلامية." | ||
| }, | ||
| "zeki-mokhtarzada": { | ||
| "name": "Zeki Mokhtarzada", | ||
| "description": "زكي هو رجل أعمال ذو خبرة فنية عميقة. شارك في تأسيس Freewebs و Truebill." | ||
| } | ||
| }, |
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.
Arabic board-member entries need a quick language QA (wording + name-script consistency).
There are a couple of user-visible phrasing issues (e.g., “منظمة إنتيسار”, leading “وعدنان”) and mixed Latin/Arabic name rendering; worth correcting before shipping.
🤖 Prompt for AI Agents
In @locales/ar/team.json around lines 13 - 26, In the locales/ar/team.json
entries for "adnan-zulfiqar", "intisar-rabb", and "zeki-mokhtarzada" fix Arabic
copy and name-script consistency: remove the erroneous leading conjunction in
the "adnan-zulfiqar" description (change "وعدنان" to "عدنان"), change the phrase
"منظمة إنتيسار" in "intisar-rabb" to a proper reference (e.g., "إنتيسار" or
"الدكتورة إنتيسار" as appropriate) and ensure all three "name" fields use Arabic
script consistently (replace the Latin-script names with their correct Arabic
transliterations), and adjust any awkward wording to natural Arabic phrasing
while preserving the original meaning.
| "engineering-team-title": "Equipo de Ingeniería", | ||
| "research-annotation-team-title": "Equipo de investigación y anotación", | ||
| "past-team-members-title": "Últimos miembros del equipo", |
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.
Consider adjusting “past-team-members-title” meaning (currently reads like “latest”).
“Últimos miembros del equipo” can read as “most recent” rather than “former/past”. If the UI section is “Past team members”, consider a translation closer to “Antiguos miembros del equipo” / “Miembros anteriores del equipo”.
🤖 Prompt for AI Agents
In @locales/es/team.json around lines 8 - 10, The translation for the key
"past-team-members-title" currently reads "Últimos miembros del equipo" (which
implies "most recent"); update the value for "past-team-members-title" in
locales/es/team.json to a term that clearly means former members such as
"Antiguos miembros del equipo" or "Miembros anteriores del equipo" so the UI
reflects “Past team members” rather than “Latest”.
| "board-members": { | ||
| "adnan-zulfiqar": { | ||
| "name": "अदनान ज़ुल्फ़िकार", | ||
| "description": "अदनान इस्लामिक और आपराधिक कानून में विशेषज्ञ हैं। उन्होंने मालदीव और सोमालिया के लिए आपराधिक संहिता का मसौदा तैयार किया।" | ||
| }, | ||
| "intisar-rabb": { | ||
| "name": "इंतिसार रब्ब", | ||
| "description": "इंतेसर हार्वर्ड में कानून की प्रोफेसर हैं। वे SHARIA स्रोत और इस्लामी कानून के कार्यक्रम का नेतृत्व करती हैं।" | ||
| }, | ||
| "zeki-mokhtarzada": { | ||
| "name": "ज़ेकी मोक़्तारज़ादा", | ||
| "description": "ज़ेकी एक उद्यमी हैं जिनके पास गहरी तकनीकी विशेषज्ञता है। उन्होंने Freewebs और Truebill की सह-स्थापना की।" | ||
| } | ||
| }, |
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.
Hindi board-member bios: please proofread spelling consistency (“इंतेसर” vs “इंतिसार”).
This reads like a transliteration typo that will be user-visible.
🤖 Prompt for AI Agents
In @locales/hi/team.json around lines 13 - 26, The Hindi bio for "intisar-rabb"
has an inconsistent transliteration: the name field is "इंतिसार रब्ब" but the
description uses "इंतेसर"; update the description text for the "intisar-rabb"
object to use the same spelling "इंतिसार" (so both name and description
consistently use "इंतिसार रब्ब" / "इंतिसार"), and scan the other board-member
entries for similar transliteration inconsistencies to make them uniform.
| "anas-hassanien": { | ||
| "name": "\"अनस हसनियन\"", | ||
| "description": "अनस Simli AI में पूर्व सॉफ्टवेयर इंजीनियर हैं।" | ||
| "description": "अनस सिमली एआई में पूर्व सॉफ्टवेयर इंजीनियर हैं। वह हमारे मुख्य उत्पादों के विकास का नेतृत्व करते थे।" | ||
| }, | ||
| "mohamed-lamine": { | ||
| "name": "मोहमद लामिन", | ||
| "description": "मोहमद थमानीया में डिज़ाइन लीड थे, वे स्टार्टअप्स के साथ 0 से 1 प्रोडक्ट्स बनाने के लिए परामर्श करते हैं।" | ||
| "abtsam-saleh": { | ||
| "name": "अबसाम सालेह", | ||
| "description": "अब्त्सम हार्वर्ड विश्वविद्यालय में पीएचडी के उम्मीदवार हैं, जो मानव-केंद्रित प्रौद्योगिकियों के सामाजिक आयामों पर शोध कर रहे हैं।" | ||
| }, |
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.
Remove unintended quotes around members.anas-hassanien.name (will render quotes in UI).
Proposed fix
- "name": "\"अनस हसनियन\"",
+ "name": "अनस हसनियन",📝 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.
| "anas-hassanien": { | |
| "name": "\"अनस हसनियन\"", | |
| "description": "अनस Simli AI में पूर्व सॉफ्टवेयर इंजीनियर हैं।" | |
| "description": "अनस सिमली एआई में पूर्व सॉफ्टवेयर इंजीनियर हैं। वह हमारे मुख्य उत्पादों के विकास का नेतृत्व करते थे।" | |
| }, | |
| "mohamed-lamine": { | |
| "name": "मोहमद लामिन", | |
| "description": "मोहमद थमानीया में डिज़ाइन लीड थे, वे स्टार्टअप्स के साथ 0 से 1 प्रोडक्ट्स बनाने के लिए परामर्श करते हैं।" | |
| "abtsam-saleh": { | |
| "name": "अबसाम सालेह", | |
| "description": "अब्त्सम हार्वर्ड विश्वविद्यालय में पीएचडी के उम्मीदवार हैं, जो मानव-केंद्रित प्रौद्योगिकियों के सामाजिक आयामों पर शोध कर रहे हैं।" | |
| }, | |
| "anas-hassanien": { | |
| "name": "अनस हसनियन", | |
| "description": "अनस सिमली एआई में पूर्व सॉफ्टवेयर इंजीनियर हैं। वह हमारे मुख्य उत्पादों के विकास का नेतृत्व करते थे।" | |
| }, | |
| "abtsam-saleh": { | |
| "name": "अबसाम सालेह", | |
| "description": "अब्त्सम हार्वर्ड विश्वविद्यालय में पीएचडी के उम्मीदवार हैं, जो मानव-केंद्रित प्रौद्योगिकियों के सामाजिक आयामों पर शोध कर रहे हैं।" | |
| }, |
🤖 Prompt for AI Agents
In @locales/hi/team.json around lines 28 - 35, The JSON value for
members.anas-hassanien.name contains stray double quotes that will render in the
UI; remove the surrounding quotes so the value becomes plain text (e.g., change
"\"अनस हसनियन\"" to "अनस हसनियन") in the members object (symbol:
"anas-hassanien" -> "name") and ensure no other member name strings have extra
quote characters.
| "engineering-team-title": "\nتەکنالوژۍ ډلې ناروغه، کوو او د قرآني خبرې\n", | ||
| "research-annotation-team-title": "څیړنې او د تشييص ټیم", | ||
| "past-team-members-title": "پخواني ټیم غړي", |
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.
🧩 Analysis chain
🏁 Script executed:
# First, read the file to inspect the actual content at the specified lines
cat -n locales/ps/team.json | head -60Repository: seemorg/usul
Length of output: 3390
🏁 Script executed:
# Now run the Python scanning script from the review comment
python - <<'PY'
from pathlib import Path
paths = [Path("locales/ps/team.json")]
bad = []
for p in paths:
try:
s = p.read_text(encoding="utf-8")
except Exception as e:
bad.append((str(p), f"read_error: {e}"))
continue
if "\ufffd" in s:
bad.append((str(p), "contains U+FFFD replacement character"))
# C0 controls except \t \n \r
controls = [ch for ch in s if ord(ch) < 32 and ch not in "\t\n\r"]
if controls:
bad.append((str(p), f"contains C0 controls: {sorted({ord(c) for c in controls})}"))
# Also do detailed line-by-line check for lines 8-10 and around line 50
with open("locales/ps/team.json", "r", encoding="utf-8") as f:
lines = f.readlines()
print("\n=== ISSUES FOUND ===")
print("\n".join(f"{p}: {msg}" for p, msg in bad) or "No U+FFFD or C0 control issues found.")
print("\n=== LINE 8-10 INSPECTION ===")
for i in range(7, min(10, len(lines))):
line = lines[i]
print(f"Line {i+1}: {repr(line)}")
print("\n=== LINE 50 INSPECTION ===")
if len(lines) > 49:
print(f"Line 50: {repr(lines[49])}")
PYRepository: seemorg/usul
Length of output: 142
Fix encoding and whitespace corruption in Pashto locale file before release.
Line 8 contains literal newlines at the start and end of the engineering-team-title value that will render extra blank lines in the UI. Line 50 has corrupted characters (mojibake) in the abdellatif-abdelfattah description. Additionally, line 6 includes a \u0006 control character in the founders-title that should be removed.
Proposed diff to remove leading/trailing newlines from engineering-team-title
- "engineering-team-title": "\nتەکنالوژۍ ډلې ناروغه، کوو او د قرآني خبرې\n",
+ "engineering-team-title": "تەکنالوژۍ ډلې ناروغه، کوو او د قرآني خبرې",The mojibake at line 50 and control character at line 6 should be corrected in the source (Crowdin) and the file re-synced to ensure data integrity.
🤖 Prompt for AI Agents
In @locales/ps/team.json around lines 8 - 10, The Pashto locale has
encoding/whitespace corruption: remove the leading and trailing literal newlines
from the "engineering-team-title" value so it does not render blank lines,
remove the control character U+0006 from the "founders-title" value, and fix the
mojibake in the "abdellatif-abdelfattah" description by correcting the source
string in Crowdin and re-syncing the locale file; update the corresponding keys
("engineering-team-title", "founders-title", "abdellatif-abdelfattah") in the
file after confirming the correct UTF-8 text from the source and re-run the
localization sync to ensure integrity.
| "rahma-fateen": { | ||
| "name": "Rahma Fateen", | ||
| "description": "Рахма руководила переводом на арабский язык в Тартеэль И.Она была переводчиком компании \"Хьюман райтс уотч\"." | ||
| }, |
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.
Fix punctuation spacing in members.rahma-fateen.description (missing space after period).
Proposed diff (minimal)
- "description": "Рахма руководила переводом на арабский язык в Тартеэль И.Она была переводчиком компании \"Хьюман райтс уотч\"."
+ "description": "Рахма руководила переводом на арабский язык в Тартеэль И. Она была переводчиком компании \"Хьюман райтс уотч\"."📝 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.
| "rahma-fateen": { | |
| "name": "Rahma Fateen", | |
| "description": "Рахма руководила переводом на арабский язык в Тартеэль И.Она была переводчиком компании \"Хьюман райтс уотч\"." | |
| }, | |
| "rahma-fateen": { | |
| "name": "Rahma Fateen", | |
| "description": "Рахма руководила переводом на арабский язык в Тартеэль И. Она была переводчиком компании \"Хьюман райтс уотч\"." | |
| }, |
🤖 Prompt for AI Agents
In @locales/ru/team.json around lines 60 - 63, The description for
members.rahma-fateen.description contains two sentences run together without a
space after the period; update the string value for "rahma-fateen" ->
"description" so there is a single space after the period (i.e., change
"...Тартеэль И.Она..." to "...Тартеэль И. Она...") to correct punctuation
spacing.
| "description": "Anas waa injineer software hore Simli AI. Waxay hoggaaminaysaa horumarinta alaabtayada aasaasiga ah." | ||
| }, |
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.
Double-check pronoun/gender agreement in members.anas-hassanien.description.
🤖 Prompt for AI Agents
In @locales/so/team.json around lines 30 - 31, The description at
members.anas-hassanien.description uses the feminine pronoun "Waxay" but Anas is
male; update the Somali sentence to use the correct masculine pronoun (e.g.,
replace "Waxay" with "Wuxuu") and ensure the rest of the sentence remains
grammatically correct and consistent with masculine agreement.
Auto-generated from by github action
Summary by CodeRabbit
New Features
UI Improvements
Enhancements
✏️ Tip: You can customize this high-level summary in your review settings.