Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note
|
| Cohort / File(s) | Summary |
|---|---|
Maintenance Banner Infrastructure .env.example, src/env.ts, src/components/maintenance-banner.tsx, src/app/[locale]/layout.tsx, src/app/[locale]/page.tsx, src/components/navbar/index.tsx |
Added NEXT_PUBLIC_ENABLE_MAINTENANCE_BANNER environment variable, created MaintenanceBanner component with height-aware CSS custom properties, and integrated conditional banner rendering with layout offset adjustments to prevent content overlap. |
Locale Common Files - Maintenance & Descriptions locales/ar/common.json, locales/bn/common.json, locales/es/common.json, locales/fa/common.json, locales/fr/common.json, locales/ha/common.json, locales/hi/common.json, locales/ms/common.json, locales/ps/common.json, locales/ru/common.json, locales/so/common.json, locales/tr/common.json, locales/ur/common.json |
Added top-level maintenance object with title and message across all languages; updated navigation.contribute descriptions for add-text and report-mistake to reflect new wording patterns. |
Locale Common - Other Updates locales/ar/entities.json, locales/en/common.json |
Minor translation updates (empires/eras in Arabic) and English maintenance banner entry added. |
Locale Team Files - Extensive Restructuring locales/ar/team.json, locales/bn/team.json, locales/es/team.json, locales/fa/team.json, locales/fr/team.json, locales/ha/team.json, locales/hi/team.json, locales/ms/team.json, locales/ps/team.json, locales/ru/team.json, locales/so/team.json, locales/tr/team.json, locales/ur/team.json |
Major updates to team section: added support-usul key, renamed team titles (engineering/operations and research/digitization), expanded board member descriptions, added rahma-fateen and ahmed-khan as new members, replaced founding-ceo/founding-engineer roles with former-ceo/former-engineer, and added director-of-operations role across all languages. |
Locale Donate Files locales/ar/donate.json, locales/bn/donate.json, locales/es/donate.json, locales/fa/donate.json, locales/fr/donate.json, locales/ha/donate.json, locales/hi/donate.json, locales/ms/donate.json, locales/ps/donate.json, locales/ru/donate.json, locales/so/donate.json, locales/tr/donate.json, locales/ur/donate.json |
Updated hero.title across all language variants from "future of Islamic knowledge powered by AI" messaging to "why support Usul AI" messaging. |
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~25 minutes
The PR involves extensive but largely repetitive locale file updates across 13 language variants with consistent patterns (maintenance banner additions, team restructuring), along with new component implementation and environment integration. While individual file complexity is low, the sheer number of files and substantive team.json content changes require careful attention to ensure translations are accurate and team data restructuring is complete and consistent across all languages.
Possibly related PRs
- 🤖 Sync Crowdin Translations #75: Overlaps on localization file edits, particularly team.json member entries (e.g.,
members.ryad-ramo) indicating related team data changes. - Production deployment #106: Both PRs modify environment configuration files (
.env.exampleandsrc/env.ts), touching the same env variable exposure infrastructure. - Update copy #89: Shared modifications across localization JSON files (common.json, donate.json, team.json across multiple languages), suggesting concurrent or complementary locale management efforts.
Poem
🐰 Hops excitedly while shuffling locale files
A banner waves when systems rest,
Through thirteen tongues, we've done our best!
Teams reshape with titles new,
Donations ask "why not with you?"
While maintenance keeps watch up high,
We bounce along—sous le ciel! 🌙✨
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 5.56% 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 references the main change (syncing Crowdin translations) but is vague with an emoji and doesn't clearly convey the full scope including maintenance banner feature and team/donate content updates. | Consider using a more descriptive title like 'Sync Crowdin translations and add maintenance banner feature' to better capture both translation updates and the new maintenance functionality introduced in this 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.
✨ Finishing touches
- 📝 Generate docstrings
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
l10n_crowdin_translations
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.
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.
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.
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.
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.
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.
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.
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.
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
🧩 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.
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.
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.
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.
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.
| @@ -5,16 +5,33 @@ | |||
| "contact-us": "موږ سره اړیکه ونیسئ", | |||
| "founders-title": "\u0006803وړونکی ټیم", | |||
There was a problem hiding this comment.
🧩 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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
🧩 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.
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.
locales/ps/team.json
Outdated
| "engineering-team-title": "\nتەکنالوژۍ ډلې ناروغه، کوو او د قرآني خبرې\n", | ||
| "research-annotation-team-title": "څیړنې او د تشييص ټیم", | ||
| "past-team-members-title": "پخواني ټیم غړي", |
There was a problem hiding this comment.
🧩 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.
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.
3db2ac8 to
10cb417
Compare
There was a problem hiding this comment.
Actionable comments posted: 14
🤖 Fix all issues with AI agents
In `@locales/ar/donate.json`:
- Line 3: The "title" value in the Arabic donate locale has a brand typo
("Asul"); update the JSON value for the "title" key in locales/ar/donate.json to
use the correct product name "Usul" (preserve surrounding punctuation and
casing) so it matches other locales.
In `@locales/ar/team.json`:
- Line 11: The JSON key "past-team-members-title" in the Arabic locale is using
English text ("Usul AI Alumni"); change its value to an Arabic translation while
keeping the brand name if desired (for example "خريجو Usul AI" or similar) so it
matches the rest of the Arabic file; update the value for
"past-team-members-title" in locales/ar/team.json accordingly.
In `@locales/es/common.json`:
- Around line 251-254: Update the Spanish copy for the keys "collapse-all" and
"maintenance.message": replace "Colapsar todos" with wording consistent with
"Expandir todo" (e.g., "Contraer todo") for the "collapse-all" key, and replace
the awkward "La normalidad está actualmente en mantenimiento. Volveremos
pronto." at "maintenance.message" with a smoother sentence such as "Actualmente
el sitio se encuentra en mantenimiento. Volveremos pronto." Ensure you update
only the values for "collapse-all" and "maintenance.message" in
locales/es/common.json.
In `@locales/es/donate.json`:
- Line 3: The "title" string in locales/es/donate.json is missing a closing
question mark and uses the wrong verb ("soportar"); update the "title" value to
use "apoyar" and proper Spanish punctuation, e.g., change the value of the
"title" key to "¿Por qué apoyar a Usul AI?" so it reads as a correct question
and includes the preposition "a" before the name.
In `@locales/es/team.json`:
- Around line 21-25: Proofread and correct Spanish descriptions in
locales/es/team.json: fix spacing and punctuation in all "description" fields
(e.g., remove the space before commas in "SHARIAsource ,", change "webs. om" to
"webs.com" or the correct domain, fix garbled fragments like "humanceras",
"Usul. Yo" and ensure sentence-initial capitalization after periods), paying
special attention to the "description" value for the "zeki-mokhtarzada" key and
the other description blocks noted (around lines referenced in the review), and
apply consistent Spanish punctuation and capitalization across all affected
description strings.
In `@locales/fa/common.json`:
- Around line 253-255: Update the maintenance message in the fa locale so the
brand name matches the existing branding; in the JSON object containing "title"
and "message" (keys "title" and "message") replace the incorrect brand token
"لاوحد" with the established brand string used elsewhere (e.g., "Usul" or "اصول"
/ "Usul/اصول") so the message reads consistently with other translations.
In `@locales/fa/donate.json`:
- Line 3: The "title" string in the locales/fa/donate.json currently ends with a
period but is phrased as a question; update the value for the "title" key (the
"title" property in donate.json) to end with the Persian question mark "؟"
instead of the period so punctuation matches intent and other localized strings.
In `@locales/fa/team.json`:
- Around line 6-47: The file uses two brand formats ("Usul.AI" and "Usul AI");
update all occurrences to the chosen standard "Usul.AI" (e.g., in the
"support-usul" value, "board-subtitle", and member description values such as
under "members.anas-hassanien", "members.abtsam-saleh", "members.ryad-ramo",
"members.ahmed-aktan", and "members.ismail-safadi") so the brand is consistently
spelled "Usul.AI" throughout the JSON; prefer making the replacements in those
string values and keep the rest of the text unchanged.
In `@locales/ha/team.json`:
- Around line 9-11: The three localization keys "engineering-team-title",
"research-annotation-team-title", and "past-team-members-title" contain English
or mixed-language values; either provide Hausa translations for these strings
("Engineering & Operations", "Bincike & Digitization", "Usul AI Alumni") to
match the locale or confirm they are intentional brand terms with the Crowdin
translators and document that decision in the translation workflow; update the
values accordingly in the ha locale JSON (or add translator notes) so the
entries are consistent with the rest of the file.
In `@locales/ms/team.json`:
- Around line 11-55: The translations use inconsistent branding — replace all
occurrences of "Usul AI" with the standardized "Usul.AI" in the JSON keys shown
(e.g., "board-subtitle", any "board-members.*.description" and
"members.*.description" entries such as "anas-hassanien", "abtsam-saleh",
"ryad-ramo", "ahmed-aktan", "ismail-safadi", "abdellatif-abdelfattah",
"ahmed-riad") so every description and title matches the existing "Usul.AI"
style used elsewhere in the file.
In `@locales/ru/donate.json`:
- Line 3: Update the Russian hero title string for the donate page: in
locales/ru/donate.json change the "title" value from "Зачем поддерживать Usul
AI." to a question form that ends with a question mark, e.g. "Зачем поддерживать
Usul AI?", ensuring the key "title" remains unchanged.
In `@locales/ru/team.json`:
- Around line 13-55: Fix several RU copy typos and brand formatting: remove the
extra space before the comma in "board-subtitle"; in "zeki-mokhtarzada" correct
"entrepeneur"→"entrepreneur", "КТО"→"CTO" and tidy the parenthetical examples
punctuation; in "abtsam-saleh" restore the "Usul.AI" brand and fix the broken
sentence/grammar ("Usul. Я" → "Usul.AI" and rephrase to "она руководит
операциями, связями и инженерными исследованиями"); in "ismail-safadi" replace
the incorrect phrase "Ханафи фич" with a proper Russian formulation such as
"специализируется на ханафитской школе" and in "ahmed-riad" capitalize the
sentence-starting "он" to "Он". Ensure names (e.g., "Ahmet"/"Ahmed") are
consistent where present.
In `@locales/ur/donate.json`:
- Line 3: The Urdu localization key "title" in locales/ur/donate.json diverges
from the established "Why support Usul AI" hero-title pattern used in other
locales; confirm with the localization team whether this wording is intentional
and, if not, replace the "title" value to match the canonical hero title used in
English/Persian locales (aligning phrasing with the "Why support Usul AI"
pattern) or update other locales to match—refer to the "title" key in
locales/ur/donate.json when making the change.
In `@src/components/maintenance-banner.tsx`:
- Around line 12-21: The current useEffect in the maintenance banner sets
--maintenance-banner-height only once; update it to create a ResizeObserver that
watches bannerRef.current and updates
document.documentElement.style.setProperty("--maintenance-banner-height",
`${height}px`) whenever offsetHeight changes, and also set the initial value; on
cleanup disconnect the observer and remove the CSS variable
(document.documentElement.style.removeProperty("--maintenance-banner-height")).
Target the existing useEffect, bannerRef, and the maintenance banner component
when making this change.
🧹 Nitpick comments (3)
locales/hi/team.json (1)
6-6: Consider revising Hindi word order for natural phrasing.The current text "हमारा समर्थन करें Usul.AI" has unusual word order. A more natural Hindi phrasing would be "Usul.AI का समर्थन करें" (Support Usul.AI) or "Usul.AI को समर्थन करें".
locales/ar/team.json (1)
28-69: Consider standardizing name-script usage across members.The
namefields use inconsistent scripts: some are in Latin (e.g., "Anas Hassanien", "Rahma Fateen") while others are in Arabic (e.g., "ابتسام صالح", "أحمد خان"). For an Arabic locale file, consider using Arabic script consistently for all names, or establish a clear convention (e.g., Latin for official names, Arabic for display)..env.example (1)
16-17: Consider documenting the default explicitly.
Setting a clearfalsedefault avoids ambiguity for new setups.🔧 Suggested tweak
-NEXT_PUBLIC_ENABLE_MAINTENANCE_BANNER= +NEXT_PUBLIC_ENABLE_MAINTENANCE_BANNER=false
| { | ||
| "hero": { | ||
| "title": "مستقبل المعرفة الإسلامية، بدعم من الذكاء الاصطناعي", | ||
| "title": "لماذا تدعم Asul AI.", |
There was a problem hiding this comment.
Fix brand spelling (“Asul” → “Usul”).
The product name is “Usul” elsewhere; this looks like a typo.
✏️ Suggested fix
- "title": "لماذا تدعم Asul AI.",
+ "title": "لماذا تدعم Usul AI.",📝 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.
| "title": "لماذا تدعم Asul AI.", | |
| "title": "لماذا تدعم Usul AI.", |
🤖 Prompt for AI Agents
In `@locales/ar/donate.json` at line 3, The "title" value in the Arabic donate
locale has a brand typo ("Asul"); update the JSON value for the "title" key in
locales/ar/donate.json to use the correct product name "Usul" (preserve
surrounding punctuation and casing) so it matches other locales.
| "past-team-members-title": "أعضاء الفريق السابقين", | ||
| "engineering-team-title": "الهندسة والعمليات", | ||
| "research-annotation-team-title": "البحث والتحويل الرقمي", | ||
| "past-team-members-title": "Usul AI Alumni", |
There was a problem hiding this comment.
English text in Arabic locale file.
"past-team-members-title" is set to "Usul AI Alumni" (English) while all other titles in this file are translated to Arabic. This creates an inconsistent user experience for Arabic readers.
Consider translating to Arabic, e.g., "خريجو Usul AI" or similar, keeping only the brand name in Latin if desired.
🤖 Prompt for AI Agents
In `@locales/ar/team.json` at line 11, The JSON key "past-team-members-title" in
the Arabic locale is using English text ("Usul AI Alumni"); change its value to
an Arabic translation while keeping the brand name if desired (for example
"خريجو Usul AI" or similar) so it matches the rest of the Arabic file; update
the value for "past-team-members-title" in locales/ar/team.json accordingly.
| "collapse-all": "Colapsar todos", | ||
| "maintenance": { | ||
| "title": "En Mantenimiento", | ||
| "message": "La normalidad está actualmente en mantenimiento. Volveremos pronto." |
There was a problem hiding this comment.
Align “collapse-all” wording and smooth the maintenance message.
Line 251 is inconsistent with “Expandir todo,” and Line 254 reads awkwardly in Spanish.
✍️ Suggested copy tweaks
- "collapse-all": "Colapsar todos",
+ "collapse-all": "Colapsar todo",
...
- "message": "La normalidad está actualmente en mantenimiento. Volveremos pronto."
+ "message": "El sitio está actualmente en mantenimiento. Volveremos pronto."📝 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.
| "collapse-all": "Colapsar todos", | |
| "maintenance": { | |
| "title": "En Mantenimiento", | |
| "message": "La normalidad está actualmente en mantenimiento. Volveremos pronto." | |
| "collapse-all": "Colapsar todo", | |
| "maintenance": { | |
| "title": "En Mantenimiento", | |
| "message": "El sitio está actualmente en mantenimiento. Volveremos pronto." |
🤖 Prompt for AI Agents
In `@locales/es/common.json` around lines 251 - 254, Update the Spanish copy for
the keys "collapse-all" and "maintenance.message": replace "Colapsar todos" with
wording consistent with "Expandir todo" (e.g., "Contraer todo") for the
"collapse-all" key, and replace the awkward "La normalidad está actualmente en
mantenimiento. Volveremos pronto." at "maintenance.message" with a smoother
sentence such as "Actualmente el sitio se encuentra en mantenimiento. Volveremos
pronto." Ensure you update only the values for "collapse-all" and
"maintenance.message" in locales/es/common.json.
| { | ||
| "hero": { | ||
| "title": "El futuro del conocimiento islámico, impulsado por IA", | ||
| "title": "¿Por qué soportar Usul AI.", |
There was a problem hiding this comment.
Fix punctuation and verb choice in hero title.
The current title lacks a closing question mark and “soportar” reads as “to endure.” Consider “apoyar” with proper punctuation.
✍️ Suggested fix
- "title": "¿Por qué soportar Usul AI."
+ "title": "¿Por qué apoyar a Usul AI?"🤖 Prompt for AI Agents
In `@locales/es/donate.json` at line 3, The "title" string in
locales/es/donate.json is missing a closing question mark and uses the wrong
verb ("soportar"); update the "title" value to use "apoyar" and proper Spanish
punctuation, e.g., change the value of the "title" key to "¿Por qué apoyar a
Usul AI?" so it reads as a correct question and includes the preposition "a"
before the name.
| "description": "Es profesora de derecho e historia en Harvard, especializada en derecho penal e interpretación (usul al-fiqh). Dirige el Programa de Ley Islámica y el Laboratorio SHARIAsource , e IslamicLawBlog." | ||
| }, | ||
| "zeki-mokhtarzada": { | ||
| "name": "Zeki Mokhtarzada", | ||
| "description": "Zeki es un emprendedor con una profunda experiencia técnica, que cofundó Freewebs y Truebill." | ||
| "description": "Es un emprendedor en serie e innovador en tecnología y ha servido como CTO a varias empresas nuevas y ha cofundado múltiples empresas, incluyendo webs. om (adquirido por Vistaprint) y Truebill (ahora Rocket Money)." |
There was a problem hiding this comment.
Proofread Spanish bios for typos/garbled phrasing.
There are several user-facing text errors (e.g., spacing in “SHARIAsource ,”, “webs. om”, “humanceras”, “Usul. Yo”, and lowercase after a period). Please proofread and correct.
✍️ Suggested fixes for obvious typos
- "description": "Es profesora de derecho e historia en Harvard, especializada en derecho penal e interpretación (usul al-fiqh). Dirige el Programa de Ley Islámica y el Laboratorio SHARIAsource , e IslamicLawBlog."
+ "description": "Es profesora de derecho e historia en Harvard, especializada en derecho penal e interpretación (usul al-fiqh). Dirige el Programa de Ley Islámica y el Laboratorio SHARIAsource, e IslamicLawBlog."
- "description": "Es un emprendedor en serie e innovador en tecnología y ha servido como CTO a varias empresas nuevas y ha cofundado múltiples empresas, incluyendo webs. om (adquirido por Vistaprint) y Truebill (ahora Rocket Money)."
+ "description": "Es un emprendedor en serie e innovador en tecnología y ha servido como CTO a varias empresas nuevas y ha cofundado múltiples empresas, incluyendo webs.com (adquirido por Vistaprint) y Truebill (ahora Rocket Money)."
- "description": "Es una candidata a doctorado en la Universidad de Harvard investigando las dimensiones sociales de las tecnologías humanceras, y es administradora e investigadora en Usul. Yo, donde dirige operaciones y puentes ingeniería e investigación."
+ "description": "Es una candidata a doctorado en la Universidad de Harvard investigando las dimensiones sociales de las tecnologías humanas, y es administradora e investigadora en Usul.AI, donde dirige operaciones y tiende puentes entre ingeniería e investigación."
- "description": "Es un ingeniero de software que sirvió como ingeniero fundador en varias start-ups (incluyendo Remail AI, Betterbook y nuestra propia AI). también sirvió como ingeniero en Qur'an.com."
+ "description": "Es un ingeniero de software que sirvió como ingeniero fundador en varias start-ups (incluyendo Remail AI, Betterbook y nuestra propia Usul AI). También sirvió como ingeniero en Qur'an.com."Also applies to: 35-39, 55-55
🤖 Prompt for AI Agents
In `@locales/es/team.json` around lines 21 - 25, Proofread and correct Spanish
descriptions in locales/es/team.json: fix spacing and punctuation in all
"description" fields (e.g., remove the space before commas in "SHARIAsource ,",
change "webs. om" to "webs.com" or the correct domain, fix garbled fragments
like "humanceras", "Usul. Yo" and ensure sentence-initial capitalization after
periods), paying special attention to the "description" value for the
"zeki-mokhtarzada" key and the other description blocks noted (around lines
referenced in the review), and apply consistent Spanish punctuation and
capitalization across all affected description strings.
| "past-team-members-title": "Alumni Usul AI", | ||
| "board-title": "Bertemu lembaga", | ||
| "board-subtitle": "Usul ialah projek di bawah Seemore Foundation, yang lembaga pengarahnya terdiri daripada pakar terkemuka dalam bidang penyelidikan dan teknologi, masing-masing dengan pengalaman berdekad-dekad dalam bidang mereka. Bersama-sama, mereka memantau organisasi dan memastikan misinya terus maju.", | ||
| "board-subtitle": "Usul.AI ialah projek Yayasan Seemore, yang lembaganya terdiri daripada pakar terkemuka dalam penyelidikan Islam dan teknologi baharu. Setiap daripada mereka adalah pemimpin dalam bidang masing-masing, dengan pengalaman puluhan tahun dalam merintis cara kita mengakses dan berinteraksi dengan ilmu. Bersama-sama, mereka memastikan organisasi ini memajukan misinya.", | ||
| "board-members": { | ||
| "adnan-zulfiqar": { | ||
| "name": "Adnan Zulfiqar", | ||
| "description": "Adnan pakar dalam undang-undang Islam dan jenayah. Dia merangka kod jenayah untuk Maldives dan Somalia." | ||
| "description": "Beliau ialah Profesor undang-undang di Sekolah Undang-Undang Boston College, di mana beliau mengajar dan menulis mengenai undang-undang jenayah dan undang-undang peperangan. Beliau membantu merangka kod jenayah Islam untuk Maldives dan Somalia." | ||
| }, | ||
| "intisar-rabb": { | ||
| "name": "Intisar Rabb", | ||
| "description": "Intisar ialah profesor undang-undang di Harvard. Beliau mengetuai SHARIAsource dan Program dalam Undang-undang Islam." | ||
| "description": "Beliau ialah Profesor undang-undang dan sejarah di Harvard, pakar dalam undang-undang jenayah dan interpretasi (usul al-fiqh). Beliau mengetuai Program dalam Undang-Undang Islam dan Makmal SHARIAsource, serta IslamicLawBlog." | ||
| }, | ||
| "zeki-mokhtarzada": { | ||
| "name": "Zeki Mokhtarzada", | ||
| "description": "Zeki ialah seorang usahawan dengan kepakaran teknikal yang mendalam. Dia mengasaskan Freewebs dan Truebill." | ||
| "description": "Beliau adalah seorang usahawan bersiri dan perintis teknologi. Beliau pernah berkhidmat sebagai Ketua Pegawai Teknologi kepada pelbagai syarikat pemula dan telah mengasaskan beberapa perusahaan, termasuk webs.com (diambil alih oleh Vistaprint) dan Truebill (kini Rocket Money)." | ||
| } | ||
| }, | ||
| "members": { | ||
| "anas-hassanien": { | ||
| "name": "Anas Hassanien", | ||
| "description": "Anas ialah bekas jurutera perisian di Simli AI. Beliau memimpin pembangunan produk teras kami." | ||
| "description": "Beliau adalah jurutera perisian dengan pengalaman menyeluruh, yang sebelum ini berkhidmat di Simli AI. Beliau mengetuai pembangunan produk teras di Usul.AI." | ||
| }, | ||
| "abtsam-saleh": { | ||
| "name": "Abtsam Saleh", | ||
| "description": "Abtsam ialah calon PhD dalam Pengajian Agama dengan bidang kedua Sains Data." | ||
| "description": "Beliau adalah calon PhD di Universiti Harvard yang meneliti dimensi sosial teknologi berpusatkan manusia, dan berkhidmat sebagai pentadbir dan penyelidik di Usul.AI, di mana beliau mengarahkan operasi dan menjambatani kejuruteraan dan penyelidikan." | ||
| }, | ||
| "ryad-ramo": { | ||
| "name": "Ryad Ramo", | ||
| "description": "Ryad ialah penyelidik yang mengkhususkan diri dalam pengetahuan dan sejarah Islam. Beliau memiliki BA dalam Pengajian Islam dan merupakan pengasas kepada Gems of Al-Andalus." | ||
| "description": "Beliau adalah penyelidik pengetahuan dan sejarah Islam, memiliki ijazah BA dalam Pengajian Islam, dan mengasaskan penerbitan Gems of Al-Andalus. Beliau bekerja dalam digitalisasi dan anotasi buku di Usul AI." | ||
| }, | ||
| "ahmed-aktan": { | ||
| "name": "Ahmet Aktan", | ||
| "description": "Ahmed memiliki PhD dari Universiti Al-Azhar. Beliau mengetuai pasukan dan usaha pendigitalan." | ||
| "description": "Beliau adalah penyelidik yang memiliki PhD dalam Pengajian Islam dari Universiti Al-Azhar. Beliau mengetuai pasukan digitalisasi dan usaha untuk mengembangkan koleksi di Usul AI." | ||
| }, | ||
| "ismail-safadi": { | ||
| "name": "Ismail Safadi", | ||
| "description": "Ismail pakar dalam fiqh Hanafi. Beliau belajar di Universiti Jordan dan di Turki." | ||
| "description": "Beliau adalah penyelidik yang pakar dalam fiqh Hanafi. Beliau belajar di Universiti Jordan dan di Turki. Beliau bekerja dalam digitalisasi dan anotasi buku di Usul AI." | ||
| }, | ||
| "abdellatif-abdelfattah": { | ||
| "name": "Abdellatif Abdelfattah", | ||
| "description": "Abdellatif mengasas bersama Tarteel AI, menjadi peneraju kejuruteraan di Quran.com, dan bekerja di Twitter." | ||
| "description": "Beliau adalah jurutera yang berkhidmat sebagai jurutera pengasas di Tarteel AI dan Usul AI sendiri. Beliau sebelum ini mengetuai kejuruteraan di Quran.com dan bekerja di Twitter." | ||
| }, | ||
| "ahmed-riad": { | ||
| "name": "Ahmed Riad", | ||
| "description": "Ahmed mengasas bersama Remail AI, Betterbook, dan merupakan jurutera di Quran.com." | ||
| "description": "Beliau adalah jurutera perisian yang berkhidmat sebagai jurutera pengasas di beberapa syarikat pemula (termasuk Remail AI, Betterbook, dan Usul AI sendiri). Beliau juga berkhidmat sebagai jurutera di Qur'an.com." |
There was a problem hiding this comment.
Standardize “Usul.AI” branding across updated Malay bios.
Several updated strings use “Usul AI” while others use “Usul.AI”. This reads inconsistent to users. Consider normalizing to the same brand style used elsewhere in this file (e.g., “Usul.AI”).
🔧 Suggested normalization
- "past-team-members-title": "Alumni Usul AI",
+ "past-team-members-title": "Alumni Usul.AI",
@@
- "description": "Beliau adalah penyelidik pengetahuan dan sejarah Islam, memiliki ijazah BA dalam Pengajian Islam, dan mengasaskan penerbitan Gems of Al-Andalus. Beliau bekerja dalam digitalisasi dan anotasi buku di Usul AI."
+ "description": "Beliau adalah penyelidik pengetahuan dan sejarah Islam, memiliki ijazah BA dalam Pengajian Islam, dan mengasaskan penerbitan Gems of Al-Andalus. Beliau bekerja dalam digitalisasi dan anotasi buku di Usul.AI."
@@
- "description": "Beliau adalah penyelidik yang memiliki PhD dalam Pengajian Islam dari Universiti Al-Azhar. Beliau mengetuai pasukan digitalisasi dan usaha untuk mengembangkan koleksi di Usul AI."
+ "description": "Beliau adalah penyelidik yang memiliki PhD dalam Pengajian Islam dari Universiti Al-Azhar. Beliau mengetuai pasukan digitalisasi dan usaha untuk mengembangkan koleksi di Usul.AI."
@@
- "description": "Beliau adalah penyelidik yang pakar dalam fiqh Hanafi. Beliau belajar di Universiti Jordan dan di Turki. Beliau bekerja dalam digitalisasi dan anotasi buku di Usul AI."
+ "description": "Beliau adalah penyelidik yang pakar dalam fiqh Hanafi. Beliau belajar di Universiti Jordan dan di Turki. Beliau bekerja dalam digitalisasi dan anotasi buku di Usul.AI."
@@
- "description": "Beliau adalah jurutera yang berkhidmat sebagai jurutera pengasas di Tarteel AI dan Usul AI sendiri. Beliau sebelum ini mengetuai kejuruteraan di Quran.com dan bekerja di Twitter."
+ "description": "Beliau adalah jurutera yang berkhidmat sebagai jurutera pengasas di Tarteel AI dan Usul.AI sendiri. Beliau sebelum ini mengetuai kejuruteraan di Quran.com dan bekerja di Twitter."
@@
- "description": "Beliau adalah jurutera perisian yang berkhidmat sebagai jurutera pengasas di beberapa syarikat pemula (termasuk Remail AI, Betterbook, dan Usul AI sendiri). Beliau juga berkhidmat sebagai jurutera di Qur'an.com."
+ "description": "Beliau adalah jurutera perisian yang berkhidmat sebagai jurutera pengasas di beberapa syarikat pemula (termasuk Remail AI, Betterbook, dan Usul.AI sendiri). Beliau juga berkhidmat sebagai jurutera di Qur'an.com."📝 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.
| "past-team-members-title": "Alumni Usul AI", | |
| "board-title": "Bertemu lembaga", | |
| "board-subtitle": "Usul ialah projek di bawah Seemore Foundation, yang lembaga pengarahnya terdiri daripada pakar terkemuka dalam bidang penyelidikan dan teknologi, masing-masing dengan pengalaman berdekad-dekad dalam bidang mereka. Bersama-sama, mereka memantau organisasi dan memastikan misinya terus maju.", | |
| "board-subtitle": "Usul.AI ialah projek Yayasan Seemore, yang lembaganya terdiri daripada pakar terkemuka dalam penyelidikan Islam dan teknologi baharu. Setiap daripada mereka adalah pemimpin dalam bidang masing-masing, dengan pengalaman puluhan tahun dalam merintis cara kita mengakses dan berinteraksi dengan ilmu. Bersama-sama, mereka memastikan organisasi ini memajukan misinya.", | |
| "board-members": { | |
| "adnan-zulfiqar": { | |
| "name": "Adnan Zulfiqar", | |
| "description": "Adnan pakar dalam undang-undang Islam dan jenayah. Dia merangka kod jenayah untuk Maldives dan Somalia." | |
| "description": "Beliau ialah Profesor undang-undang di Sekolah Undang-Undang Boston College, di mana beliau mengajar dan menulis mengenai undang-undang jenayah dan undang-undang peperangan. Beliau membantu merangka kod jenayah Islam untuk Maldives dan Somalia." | |
| }, | |
| "intisar-rabb": { | |
| "name": "Intisar Rabb", | |
| "description": "Intisar ialah profesor undang-undang di Harvard. Beliau mengetuai SHARIAsource dan Program dalam Undang-undang Islam." | |
| "description": "Beliau ialah Profesor undang-undang dan sejarah di Harvard, pakar dalam undang-undang jenayah dan interpretasi (usul al-fiqh). Beliau mengetuai Program dalam Undang-Undang Islam dan Makmal SHARIAsource, serta IslamicLawBlog." | |
| }, | |
| "zeki-mokhtarzada": { | |
| "name": "Zeki Mokhtarzada", | |
| "description": "Zeki ialah seorang usahawan dengan kepakaran teknikal yang mendalam. Dia mengasaskan Freewebs dan Truebill." | |
| "description": "Beliau adalah seorang usahawan bersiri dan perintis teknologi. Beliau pernah berkhidmat sebagai Ketua Pegawai Teknologi kepada pelbagai syarikat pemula dan telah mengasaskan beberapa perusahaan, termasuk webs.com (diambil alih oleh Vistaprint) dan Truebill (kini Rocket Money)." | |
| } | |
| }, | |
| "members": { | |
| "anas-hassanien": { | |
| "name": "Anas Hassanien", | |
| "description": "Anas ialah bekas jurutera perisian di Simli AI. Beliau memimpin pembangunan produk teras kami." | |
| "description": "Beliau adalah jurutera perisian dengan pengalaman menyeluruh, yang sebelum ini berkhidmat di Simli AI. Beliau mengetuai pembangunan produk teras di Usul.AI." | |
| }, | |
| "abtsam-saleh": { | |
| "name": "Abtsam Saleh", | |
| "description": "Abtsam ialah calon PhD dalam Pengajian Agama dengan bidang kedua Sains Data." | |
| "description": "Beliau adalah calon PhD di Universiti Harvard yang meneliti dimensi sosial teknologi berpusatkan manusia, dan berkhidmat sebagai pentadbir dan penyelidik di Usul.AI, di mana beliau mengarahkan operasi dan menjambatani kejuruteraan dan penyelidikan." | |
| }, | |
| "ryad-ramo": { | |
| "name": "Ryad Ramo", | |
| "description": "Ryad ialah penyelidik yang mengkhususkan diri dalam pengetahuan dan sejarah Islam. Beliau memiliki BA dalam Pengajian Islam dan merupakan pengasas kepada Gems of Al-Andalus." | |
| "description": "Beliau adalah penyelidik pengetahuan dan sejarah Islam, memiliki ijazah BA dalam Pengajian Islam, dan mengasaskan penerbitan Gems of Al-Andalus. Beliau bekerja dalam digitalisasi dan anotasi buku di Usul AI." | |
| }, | |
| "ahmed-aktan": { | |
| "name": "Ahmet Aktan", | |
| "description": "Ahmed memiliki PhD dari Universiti Al-Azhar. Beliau mengetuai pasukan dan usaha pendigitalan." | |
| "description": "Beliau adalah penyelidik yang memiliki PhD dalam Pengajian Islam dari Universiti Al-Azhar. Beliau mengetuai pasukan digitalisasi dan usaha untuk mengembangkan koleksi di Usul AI." | |
| }, | |
| "ismail-safadi": { | |
| "name": "Ismail Safadi", | |
| "description": "Ismail pakar dalam fiqh Hanafi. Beliau belajar di Universiti Jordan dan di Turki." | |
| "description": "Beliau adalah penyelidik yang pakar dalam fiqh Hanafi. Beliau belajar di Universiti Jordan dan di Turki. Beliau bekerja dalam digitalisasi dan anotasi buku di Usul AI." | |
| }, | |
| "abdellatif-abdelfattah": { | |
| "name": "Abdellatif Abdelfattah", | |
| "description": "Abdellatif mengasas bersama Tarteel AI, menjadi peneraju kejuruteraan di Quran.com, dan bekerja di Twitter." | |
| "description": "Beliau adalah jurutera yang berkhidmat sebagai jurutera pengasas di Tarteel AI dan Usul AI sendiri. Beliau sebelum ini mengetuai kejuruteraan di Quran.com dan bekerja di Twitter." | |
| }, | |
| "ahmed-riad": { | |
| "name": "Ahmed Riad", | |
| "description": "Ahmed mengasas bersama Remail AI, Betterbook, dan merupakan jurutera di Quran.com." | |
| "description": "Beliau adalah jurutera perisian yang berkhidmat sebagai jurutera pengasas di beberapa syarikat pemula (termasuk Remail AI, Betterbook, dan Usul AI sendiri). Beliau juga berkhidmat sebagai jurutera di Qur'an.com." | |
| "past-team-members-title": "Alumni Usul.AI", | |
| "board-title": "Bertemu lembaga", | |
| "board-subtitle": "Usul.AI ialah projek Yayasan Seemore, yang lembaganya terdiri daripada pakar terkemuka dalam penyelidikan Islam dan teknologi baharu. Setiap daripada mereka adalah pemimpin dalam bidang masing-masing, dengan pengalaman puluhan tahun dalam merintis cara kita mengakses dan berinteraksi dengan ilmu. Bersama-sama, mereka memastikan organisasi ini memajukan misinya.", | |
| "board-members": { | |
| "adnan-zulfiqar": { | |
| "name": "Adnan Zulfiqar", | |
| "description": "Beliau ialah Profesor undang-undang di Sekolah Undang-Undang Boston College, di mana beliau mengajar dan menulis mengenai undang-undang jenayah dan undang-undang peperangan. Beliau membantu merangka kod jenayah Islam untuk Maldives dan Somalia." | |
| }, | |
| "intisar-rabb": { | |
| "name": "Intisar Rabb", | |
| "description": "Beliau ialah Profesor undang-undang dan sejarah di Harvard, pakar dalam undang-undang jenayah dan interpretasi (usul al-fiqh). Beliau mengetuai Program dalam Undang-Undang Islam dan Makmal SHARIAsource, serta IslamicLawBlog." | |
| }, | |
| "zeki-mokhtarzada": { | |
| "name": "Zeki Mokhtarzada", | |
| "description": "Beliau adalah seorang usahawan bersiri dan perintis teknologi. Beliau pernah berkhidmat sebagai Ketua Pegawai Teknologi kepada pelbagai syarikat pemula dan telah mengasaskan beberapa perusahaan, termasuk webs.com (diambil alih oleh Vistaprint) dan Truebill (kini Rocket Money)." | |
| } | |
| }, | |
| "members": { | |
| "anas-hassanien": { | |
| "name": "Anas Hassanien", | |
| "description": "Beliau adalah jurutera perisian dengan pengalaman menyeluruh, yang sebelum ini berkhidmat di Simli AI. Beliau mengetuai pembangunan produk teras di Usul.AI." | |
| }, | |
| "abtsam-saleh": { | |
| "name": "Abtsam Saleh", | |
| "description": "Beliau adalah calon PhD di Universiti Harvard yang meneliti dimensi sosial teknologi berpusatkan manusia, dan berkhidmat sebagai pentadbir dan penyelidik di Usul.AI, di mana beliau mengarahkan operasi dan menjambatani kejuruteraan dan penyelidikan." | |
| }, | |
| "ryad-ramo": { | |
| "name": "Ryad Ramo", | |
| "description": "Beliau adalah penyelidik pengetahuan dan sejarah Islam, memiliki ijazah BA dalam Pengajian Islam, dan mengasaskan penerbitan Gems of Al-Andalus. Beliau bekerja dalam digitalisasi dan anotasi buku di Usul.AI." | |
| }, | |
| "ahmed-aktan": { | |
| "name": "Ahmet Aktan", | |
| "description": "Beliau adalah penyelidik yang memiliki PhD dalam Pengajian Islam dari Universiti Al-Azhar. Beliau mengetuai pasukan digitalisasi dan usaha untuk mengembangkan koleksi di Usul.AI." | |
| }, | |
| "ismail-safadi": { | |
| "name": "Ismail Safadi", | |
| "description": "Beliau adalah penyelidik yang pakar dalam fiqh Hanafi. Beliau belajar di Universiti Jordan dan di Turki. Beliau bekerja dalam digitalisasi dan anotasi buku di Usul.AI." | |
| }, | |
| "abdellatif-abdelfattah": { | |
| "name": "Abdellatif Abdelfattah", | |
| "description": "Beliau adalah jurutera yang berkhidmat sebagai jurutera pengasas di Tarteel AI dan Usul.AI sendiri. Beliau sebelum ini mengetuai kejuruteraan di Quran.com dan bekerja di Twitter." | |
| }, | |
| "ahmed-riad": { | |
| "name": "Ahmed Riad", | |
| "description": "Beliau adalah jurutera perisian yang berkhidmat sebagai jurutera pengasas di beberapa syarikat pemula (termasuk Remail AI, Betterbook, dan Usul.AI sendiri). Beliau juga berkhidmat sebagai jurutera di Qur'an.com." | |
| } | |
| } |
🤖 Prompt for AI Agents
In `@locales/ms/team.json` around lines 11 - 55, The translations use inconsistent
branding — replace all occurrences of "Usul AI" with the standardized "Usul.AI"
in the JSON keys shown (e.g., "board-subtitle", any
"board-members.*.description" and "members.*.description" entries such as
"anas-hassanien", "abtsam-saleh", "ryad-ramo", "ahmed-aktan", "ismail-safadi",
"abdellatif-abdelfattah", "ahmed-riad") so every description and title matches
the existing "Usul.AI" style used elsewhere in the file.
| { | ||
| "hero": { | ||
| "title": "Будущее исламских знаний, поддерживаемое ИИ", | ||
| "title": "Зачем поддерживать Usul AI.", |
There was a problem hiding this comment.
Use a question mark for the hero title.
Line 3 reads as a question; a question mark reads more naturally.
✍️ Suggested tweak
- "title": "Зачем поддерживать Usul AI.",
+ "title": "Зачем поддерживать Usul AI?"📝 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.
| "title": "Зачем поддерживать Usul AI.", | |
| "title": "Зачем поддерживать Usul AI?" |
🤖 Prompt for AI Agents
In `@locales/ru/donate.json` at line 3, Update the Russian hero title string for
the donate page: in locales/ru/donate.json change the "title" value from "Зачем
поддерживать Usul AI." to a question form that ends with a question mark, e.g.
"Зачем поддерживать Usul AI?", ensuring the key "title" remains unchanged.
| "board-subtitle": "Usul.AI является проектом Фонда Seemore , в состав которого входят выдающиеся специалисты в исламских исследованиях и в новых технологиях. Каждый из них является лидерами в их соответствующих областях и имеет десятилетний опыт работы над тем, как мы получаем доступ к знаниям и участвуем в них. В совокупности они обеспечивают, чтобы организация продвигала свою миссию.", | ||
| "board-members": { | ||
| "adnan-zulfiqar": { | ||
| "name": "Adnan Zulfiqar", | ||
| "description": "Аднан специализируется на исламском и уголовном праве и разработал уголовные кодексы Мальдивских Островов и Сомали." | ||
| "description": "Является профессором права в Юридической школе Бостонского колледжа, где преподает и пишет по уголовному праву и законам войны. Он оказал помощь в разработке исламских уголовных кодексов Мальдивских Островов и Сомали." | ||
| }, | ||
| "intisar-rabb": { | ||
| "name": "Intisar Rabb", | ||
| "description": "Интисар является профессором права в Гарварде. Она руководит SHARIAsource и Программой в исламском праве." | ||
| "description": "Профессор права и истории в Гарварде, специализирующаяся в области уголовного права и толкования (узуль-аль-фих). Она руководит Программой в области исламского права и в лаборатории SHARIAsource и ИсламикаЛоблог." | ||
| }, | ||
| "zeki-mokhtarzada": { | ||
| "name": "Zeki Mokhtarzada", | ||
| "description": "Zeki является предпринимателем с глубокими техническими знаниями и соучредителем Freewebs и Truebill." | ||
| "description": "Он является серийным entrepeneur и инноватором в технологии. Он служил КТО различных стартапов и совместно основал множество предприятий, включая сети. (приобретено Vistaprint) и Truebill (теперь Rocket Money)." | ||
| } | ||
| }, | ||
| "members": { | ||
| "anas-hassanien": { | ||
| "name": "Anas Hassanien", | ||
| "description": "Анас является бывшим инженером по программному обеспечению Simli AI. Он руководит разработкой наших основных продуктов." | ||
| "description": "Он является инженером по программному обеспечению с богатым опытом работы, который ранее работал в Simli AI. Он руководит разработкой основных продуктов Usul.AI." | ||
| }, | ||
| "abtsam-saleh": { | ||
| "name": "Abtsam Saleh", | ||
| "description": "Abtsam является кандидатом на докторскую диссертацию по изучению религии с полем датированных наук." | ||
| "description": "Она является кандидатом на степень доктора наук в Гарвардском университете, исследуя социальные аспекты гуманных технологий, и выступает в качестве администратора и исследователя в Usul. Я, где она руководит операциями и мостами и инженерными исследованиями." | ||
| }, | ||
| "ryad-ramo": { | ||
| "name": "Ryad Ramo", | ||
| "description": "Ryad является исследователем, специализирующимся на изучении ислама и истории. Он имеет степень бакалавра в исламских исследованиях и является основателем Gems Al-Andalus." | ||
| "description": "Он является исследователем исламских знаний и истории, имеет степень бакалавра в исламских исследованиях и основал публикацию «Гемс Аль-Андалус». Он работает над оцифровкой и аннотацией книг на Usul AI." | ||
| }, | ||
| "ahmed-aktan": { | ||
| "name": "Ahmet Aktan", | ||
| "description": "Ахмед имеет докторскую диссертацию из Университета Аль-Азхара. Он возглавляет команду оцифровки и усилия." | ||
| "description": "Он является исследователем, который имеет степень доктора исламских исследований из Университета Аль-Азхара. Он возглавляет команду по оцифровке и старается расширить коллекции в Usul AI." | ||
| }, | ||
| "ismail-safadi": { | ||
| "name": "Исмаил Сафади", | ||
| "description": "Исмаил специализируется в Ханафи фикхе. Он учился в Иорданском университете и Турции." | ||
| "description": "Он является исследователем, который специализируется на Ханафи фич. Работает над оцифровкой и аннотацией книги на Usul AI." | ||
| }, | ||
| "abdellatif-abdelfattah": { | ||
| "name": "Abdellatif Abdelfattah", | ||
| "description": "Абдельлатиф соучредил Tarteel AI, ведущую инженерную компанию в Quran.com, и работал в Twitter." | ||
| "description": "Он является инженером, который работал соучредителем Тартеэлем ИИ и нашим собственным Usul AI. Ранее он возглавил инжиниринг на Quran.com и работал в Twitter." | ||
| }, | ||
| "ahmed-riad": { | ||
| "name": "Ахмед Риад", | ||
| "description": "Ахмед совместно основал Remail AI, Betterbook, и был инженером Quran.com." | ||
| "description": "Он является инженером по программному обеспечению, который работал основателем на нескольких запусках (включая Remail AI, Betterbook, и наш собственный Usul AI). он также служил инженером на Qur'an.com." |
There was a problem hiding this comment.
Fix obvious typos and brand formatting in updated RU bios.
Line 13 has an extra space before the comma, Line 25 contains spelling/role typos, Line 35 breaks the Usul.AI brand name, Line 47 uses an incorrect fiqh term, and Line 55 starts a sentence with a lowercase letter.
💬 Suggested copy fixes
- "board-subtitle": "Usul.AI является проектом Фонда Seemore , в состав которого входят выдающиеся специалисты в исламских исследованиях и в новых технологиях. Каждый из них является лидерами в их соответствующих областях и имеет десятилетний опыт работы над тем, как мы получаем доступ к знаниям и участвуем в них. В совокупности они обеспечивают, чтобы организация продвигала свою миссию.",
+ "board-subtitle": "Usul.AI является проектом Фонда Seemore, в состав которого входят выдающиеся специалисты в исламских исследованиях и в новых технологиях. Каждый из них является лидерами в их соответствующих областях и имеет десятилетний опыт работы над тем, как мы получаем доступ к знаниям и участвуем в них. В совокупности они обеспечивают, чтобы организация продвигала свою миссию.",
- "description": "Он является серийным entrepeneur и инноватором в технологии. Он служил КТО различных стартапов и совместно основал множество предприятий, включая сети. (приобретено Vistaprint) и Truebill (теперь Rocket Money)."
+ "description": "Он является серийным entrepreneur и инноватором в технологии. Он служил CTO различных стартапов и совместно основал множество предприятий, включая сети. (приобретено Vistaprint) и Truebill (теперь Rocket Money)."
- "description": "Она является кандидатом на степень доктора наук в Гарвардском университете, исследуя социальные аспекты гуманных технологий, и выступает в качестве администратора и исследователя в Usul. Я, где она руководит операциями и мостами и инженерными исследованиями."
+ "description": "Она является кандидатом на степень доктора наук в Гарвардском университете, исследуя социальные аспекты гуманных технологий, и выступает в качестве администратора и исследователя в Usul.AI, где она руководит операциями и мостами и инженерными исследованиями."
- "description": "Он является исследователем, который специализируется на Ханафи фич. Работает над оцифровкой и аннотацией книги на Usul AI."
+ "description": "Он является исследователем, который специализируется на Ханафи фикх. Работает над оцифровкой и аннотацией книги на Usul AI."
- "description": "Он является инженером по программному обеспечению, который работал основателем на нескольких запусках (включая Remail AI, Betterbook, и наш собственный Usul AI). он также служил инженером на Qur'an.com."
+ "description": "Он является инженером по программному обеспечению, который работал основателем на нескольких запусках (включая Remail AI, Betterbook, и наш собственный Usul AI). Он также служил инженером на Qur'an.com."🤖 Prompt for AI Agents
In `@locales/ru/team.json` around lines 13 - 55, Fix several RU copy typos and
brand formatting: remove the extra space before the comma in "board-subtitle";
in "zeki-mokhtarzada" correct "entrepeneur"→"entrepreneur", "КТО"→"CTO" and tidy
the parenthetical examples punctuation; in "abtsam-saleh" restore the "Usul.AI"
brand and fix the broken sentence/grammar ("Usul. Я" → "Usul.AI" and rephrase to
"она руководит операциями, связями и инженерными исследованиями"); in
"ismail-safadi" replace the incorrect phrase "Ханафи фич" with a proper Russian
formulation such as "специализируется на ханафитской школе" and in "ahmed-riad"
capitalize the sentence-starting "он" to "Он". Ensure names (e.g.,
"Ahmet"/"Ahmed") are consistent where present.
| { | ||
| "hero": { | ||
| "title": "اسلامی علم کا مستقبل، مصنوعی ذہانت کے ذریعہ تقویت یافتہ", | ||
| "title": "ہماری مدد کریں کہ ہم اسلامی علم کی سب سے بڑی لائبریری تعمیر کریں، سب سے وسیع رسائی کو یقینی بنائیں، اور اکیسویں صدی کے لیے سب سے جری تحقیق کو سہولت فراہم کریں۔", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's check the structure of the locales directory
ls -la locales/ | head -20Repository: seemorg/usul
Length of output: 894
🏁 Script executed:
# Check the current state of the Urdu donate.json file
cat locales/ur/donate.jsonRepository: seemorg/usul
Length of output: 5023
🏁 Script executed:
# Check other locale files to see the pattern for hero.title
find locales -name "donate.json" -type f | head -10Repository: seemorg/usul
Length of output: 285
🏁 Script executed:
# Check English locale to see the pattern
cat locales/en/donate.jsonRepository: seemorg/usul
Length of output: 4981
🏁 Script executed:
# Check a few other locales to confirm the pattern
cat locales/fa/donate.jsonRepository: seemorg/usul
Length of output: 4681
Confirm intended Urdu hero title messaging with localization team.
The Urdu hero.title deviates from the "Why support Usul AI" pattern used in English and Persian locales. Instead, it mirrors the subtitle's core message about building the library, ensuring access, and facilitating research. Please confirm this inconsistency is intentional.
🤖 Prompt for AI Agents
In `@locales/ur/donate.json` at line 3, The Urdu localization key "title" in
locales/ur/donate.json diverges from the established "Why support Usul AI"
hero-title pattern used in other locales; confirm with the localization team
whether this wording is intentional and, if not, replace the "title" value to
match the canonical hero title used in English/Persian locales (aligning
phrasing with the "Why support Usul AI" pattern) or update other locales to
match—refer to the "title" key in locales/ur/donate.json when making the change.
| useEffect(() => { | ||
| // Set CSS variable for banner height so navbar can adjust | ||
| if (bannerRef.current) { | ||
| const height = bannerRef.current.offsetHeight; | ||
| document.documentElement.style.setProperty( | ||
| "--maintenance-banner-height", | ||
| `${height}px`, | ||
| ); | ||
| } | ||
| }, []); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "maintenance-banner.tsx" -type fRepository: seemorg/usul
Length of output: 95
🏁 Script executed:
git ls-files | grep -E "(maintenance-banner|MaintenanceBanner)" | head -20Repository: seemorg/usul
Length of output: 93
🏁 Script executed:
cat -n src/components/maintenance-banner.tsxRepository: seemorg/usul
Length of output: 1620
🏁 Script executed:
# Check how this component is used
rg "MaintenanceBanner" --type ts --type tsx -A 3 -B 3Repository: seemorg/usul
Length of output: 83
🏁 Script executed:
# Look for locale change handlers and next-intl usage patterns
rg "useLocale|setLocale|locale" --type ts --type tsx src/ | head -20Repository: seemorg/usul
Length of output: 83
🏁 Script executed:
# Check if there's any responsive design or layout issues
rg "maintenance-banner-height" --type ts --type tsx --type cssRepository: seemorg/usul
Length of output: 83
🏁 Script executed:
# Check how this component is used
rg "MaintenanceBanner" -A 3 -B 3Repository: seemorg/usul
Length of output: 1431
🏁 Script executed:
# Look for locale change handlers and next-intl usage patterns
rg "useLocale|setLocale|locale" src/ | head -20Repository: seemorg/usul
Length of output: 1569
🏁 Script executed:
# Check if there's any responsive design or layout issues
rg "maintenance-banner-height"Repository: seemorg/usul
Length of output: 426
🏁 Script executed:
# Check the layout file to understand locale switching behavior
cat -n src/app/[locale]/layout.tsx | head -50Repository: seemorg/usul
Length of output: 1734
🏁 Script executed:
# Check if there's any i18n configuration or locale switching logic
rg "routing\.|locales" src/ | grep -E "(routing|locales)" | head -15Repository: seemorg/usul
Length of output: 1226
🏁 Script executed:
# Look at navbar implementation to see how it uses the CSS variable
cat -n src/components/navbar/index.tsx | head -80Repository: seemorg/usul
Length of output: 3286
🏁 Script executed:
# Check how locale changes work in this app - is it a full page reload/remount?
cat -n src/i18n/request.tsRepository: seemorg/usul
Length of output: 1386
🏁 Script executed:
# Check if the banner text is truly static or could change dynamically
rg "common.maintenance" locales/Repository: seemorg/usul
Length of output: 38
🏁 Script executed:
# Look at the complete navbar context around the CSS var usage
sed -n '70,85p' src/components/navbar/index.tsxRepository: seemorg/usul
Length of output: 756
🏁 Script executed:
# Check what happens when locale changes - is it a full page reload?
rg "useRouter|usePathname" src/components/navbar/ -A 2Repository: seemorg/usul
Length of output: 2142
🏁 Script executed:
# Look at the locales directory to see if maintenance messages vary significantly
find locales -name "common.json" | head -5Repository: seemorg/usul
Length of output: 170
🏁 Script executed:
# Check one locale file for the maintenance message
head -50 locales/en/common.json | rg "maintenance"Repository: seemorg/usul
Length of output: 38
🏁 Script executed:
# Check if maintenance key exists in common.json
rg "maintenance" locales/en/common.jsonRepository: seemorg/usul
Length of output: 151
🏁 Script executed:
# View the common.json file to see the structure
cat locales/en/common.json | head -100Repository: seemorg/usul
Length of output: 2786
🏁 Script executed:
# Check if locale switcher actually causes a full page reload or just client-side update
cat -n src/components/navbar/locale-switcher.tsxRepository: seemorg/usul
Length of output: 2077
🏁 Script executed:
# Verify banner doesn't dynamically change text during session (confirm text is static)
rg "common.maintenance" locales/ | wc -lRepository: seemorg/usul
Length of output: 57
🏁 Script executed:
# Check the actual maintenance key structure in the locale files
rg "maintenance" locales/en/common.json -A 2Repository: seemorg/usul
Length of output: 191
🏁 Script executed:
# Verify if the maintenance text is the same across all locales
for file in locales/*/common.json; do echo "=== $file ==="; rg "maintenance" "$file" -A 2 | head -5; doneRepository: seemorg/usul
Length of output: 2307
Handle responsive height changes with ResizeObserver.
The useEffect only captures the banner height once on mount. If the viewport resizes and text wraps differently, the CSS variable becomes stale. Add a ResizeObserver to track height changes and remove the CSS variable on unmount for cleanup.
💡 Suggested fix
useEffect(() => {
+ const el = bannerRef.current;
+ if (!el) return;
+
+ const update = () => {
+ const height = el.offsetHeight;
+ document.documentElement.style.setProperty(
+ "--maintenance-banner-height",
+ `${height}px`,
+ );
+ };
+
+ update();
+ const observer = new ResizeObserver(update);
+ observer.observe(el);
+
+ return () => {
+ observer.disconnect();
+ document.documentElement.style.removeProperty(
+ "--maintenance-banner-height",
+ );
+ };
}, []);📝 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.
| useEffect(() => { | |
| // Set CSS variable for banner height so navbar can adjust | |
| if (bannerRef.current) { | |
| const height = bannerRef.current.offsetHeight; | |
| document.documentElement.style.setProperty( | |
| "--maintenance-banner-height", | |
| `${height}px`, | |
| ); | |
| } | |
| }, []); | |
| useEffect(() => { | |
| const el = bannerRef.current; | |
| if (!el) return; | |
| const update = () => { | |
| const height = el.offsetHeight; | |
| document.documentElement.style.setProperty( | |
| "--maintenance-banner-height", | |
| `${height}px`, | |
| ); | |
| }; | |
| update(); | |
| const observer = new ResizeObserver(update); | |
| observer.observe(el); | |
| return () => { | |
| observer.disconnect(); | |
| document.documentElement.style.removeProperty( | |
| "--maintenance-banner-height", | |
| ); | |
| }; | |
| }, []); |
🤖 Prompt for AI Agents
In `@src/components/maintenance-banner.tsx` around lines 12 - 21, The current
useEffect in the maintenance banner sets --maintenance-banner-height only once;
update it to create a ResizeObserver that watches bannerRef.current and updates
document.documentElement.style.setProperty("--maintenance-banner-height",
`${height}px`) whenever offsetHeight changes, and also set the initial value; on
cleanup disconnect the observer and remove the CSS variable
(document.documentElement.style.removeProperty("--maintenance-banner-height")).
Target the existing useEffect, bannerRef, and the maintenance banner component
when making this change.
Auto-generated from by github action
Summary by CodeRabbit
New Features
Updates