Skip to content

feat: integrate localization#6

Closed
Guria wants to merge 1 commit into
mainfrom
paraglide
Closed

feat: integrate localization#6
Guria wants to merge 1 commit into
mainfrom
paraglide

Conversation

@Guria
Copy link
Copy Markdown
Owner

@Guria Guria commented Feb 22, 2026

Summary by CodeRabbit

  • New Features

    • Multi-language support with English and Spanish translations added
    • Language switcher integrated into the UI for easy language selection
    • Enhanced settings page with language, theme, and density preferences
    • New top-bar visibility controls for language switcher, GitHub link, and theme switcher
    • New Switch component for improved toggle interactions across the app
  • Documentation

    • Removed outdated Installable Park components documentation

Copilot AI review requested due to automatic review settings February 22, 2026 23:00
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 22, 2026

Walkthrough

This pull request introduces comprehensive internationalization support via ParaglideJS, adding English and Spanish message translations, updating 30+ UI components to use localized strings, creating a new Switch component with theming, and establishing locale/feature-flag state management while removing related documentation.

Changes

Cohort / File(s) Summary
Build & Configuration
vite.config.ts, .config/mise/conf.d/tasks-prepare.toml, .config/mise/conf.d/tasks-quality.toml, package.json, .gitignore, project.inlang/settings.json
Added ParaglideJS vite plugin and build tasks, new @inlang/paraglide-js dependency, Inlang project settings (baseLocale: "en", locales: ["en", "es"]), and .gitignore entry for generated paraglide output directory.
Internationalization Messages
messages/en.json, messages/es.json
Added complete English and Spanish message catalogs (77 lines each) containing UI text for navigation, settings, dashboard, connections, articles, error messages, and localization labels.
Locale & Top Bar State Management
src/shared/model/locale.ts, src/shared/model/topBar.ts, src/shared/model/index.ts
Introduced localeAtom for persisted locale state with runtime updates, and three visibility atoms (showLanguageSwitcherInTopBarAtom, showGithubLinkInTopBarAtom, showThemeSwitcherInTopBarAtom) for feature-flag-driven UI controls, all with localStorage persistence.
Switch Component & Theming
src/shared/components/ui/switch.tsx, src/shared/components/ui/index.ts, theme/recipes/switch.ts, theme/recipes/index.ts
Added new client-side Switch component with styled primitives (Root, Label, Thumb, Control), Indicator and ThumbIndicator variants, comprehensive switch recipe (141 lines) defining layout, sizing, states, and color behavior for checked/unchecked modes.
Navigation Items
src/pages/*/ui/*NavItem.tsx
Replaced hardcoded labels with localized messages (m.nav_*()) across Dashboard, Articles, Connections, Items, Calculator, Chat, Timeline, Timer, and Settings nav items.
Page Headers & Titles
src/pages/*/ui/*Page.tsx, src/app/MobileHeader.tsx
Updated page component titles and mobile headers to use localized messages (m.nav_*(), m.dashboard_title(), etc.) instead of hardcoded strings.
Error & Detail Pages
src/pages/articles/ui/ArticleNotFound.tsx, src/pages/articles/ui/ArticlesPageError.tsx, src/pages/connections/ui/ConnectionNotFound.tsx, src/pages/connections/ui/ConnectionsPageError.tsx
Replaced error titles, descriptions, and retry button text with localized message calls, supporting parameterized messages (articleId, connectionId).
Article & Connection Components
src/pages/articles/ui/components/ArticleList.tsx, src/pages/articles/ui/components/ArticleListLoading.tsx, src/pages/articles/ui/ArticleDetailMobileHeader.tsx, src/pages/connections/ui/ConnectionDetail.tsx, src/pages/connections/ui/ConnectionDetailMobileHeader.tsx, src/pages/connections/ui/ConnectionNoSelection.tsx
Updated UI text for article/connection lists, detail headers, and messaging (toaster notifications for testing, reconnecting) to use localized strings.
Settings Page
src/pages/settings/ui/SettingsPage.tsx
Introduced extensive localization (158 lines): replaced all hardcoded labels with m.settings_*() calls, added locale selection field with language switcher, created localized collections for notifications, appearance, theme, density options, and wired top-bar visibility atoms to new Switch controls.
App Shell & Layout
src/app/App.tsx, src/widgets/layout/ui/AppShell.tsx
Updated main app title and added locale atom invocation; enhanced AppShell with feature-flag-driven UI including conditional language switcher menu, GitHub link, and theme toggle button based on persisted visibility atoms (96 lines added).
Documentation
docs/installable-park-components.md
Removed documentation describing Park UI component installation workflow, containing usage examples, component table, and post-install guidance.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Park UI #3: Modifies the theme/recipes index export to add recipe exports; this PR similarly extends the recipes object by including switchRecipe.
  • Migrate project configuration and tasks to Mise #4: Updates the same Mise task configuration files (.config/mise/conf.d/) by adding prepare sub-tasks; this PR introduces paraglide prepare tasks to the same files.

Poem

🐰 Hop hop, the strings now dance with locale,
Messages bloom in Spanish, English set to sail,
A Switch flips soft with recipes so bright,
AppShell glows with features, flags set just right!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: integrate localization' accurately summarizes the main objective of the pull request, which involves adding comprehensive localization support throughout the codebase using ParaglideJS.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch paraglide

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.

❤️ Share

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

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR integrates localization into the application using @inlang/paraglide-js, adding support for English and Spanish languages. The implementation includes a language switcher UI, locale persistence, and comprehensive translation coverage across navigation items, settings, and user-facing messages.

Changes:

  • Adds @inlang/paraglide-js dependency and Vite plugin for i18n support
  • Implements Switch UI component with theme recipe for settings toggles
  • Creates locale management with Reatom atoms for persistent language selection
  • Localizes all user-facing strings across navigation, settings, errors, and page titles
  • Adds top bar visibility controls for language switcher, GitHub link, and theme switcher

Reviewed changes

Copilot reviewed 40 out of 42 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
vite.config.ts Adds paraglide Vite plugin for message compilation
theme/recipes/switch.ts Defines Switch component styling recipe with variants
theme/recipes/index.ts Exports switchRecipe for use in styled components
src/widgets/layout/ui/AppShell.tsx Adds language switcher dropdown, imports locale utilities, renames Menu icon
src/shared/model/topBar.ts Creates atoms for controlling top bar element visibility
src/shared/model/locale.ts Implements locale atom with localStorage persistence and paraglide sync
src/shared/model/index.ts Exports new locale and top bar visibility atoms
src/shared/components/ui/switch.tsx Implements Switch component using Ark UI with Panda CSS styling
src/shared/components/ui/index.ts Exports Switch component namespace
src/pages//ui/.tsx Replaces hardcoded strings with localized message calls throughout all page components
src/app/MobileHeader.tsx Localizes mobile header titles for all routes
src/app/App.tsx Initializes locale atom and localizes app name
project.inlang/settings.json Configures inlang with base locale (en) and Spanish support
package.json Adds @inlang/paraglide-js dependency
messages/en.json Defines English message catalog with 77 translation keys
messages/es.json Defines Spanish message catalog with 77 translation keys
docs/installable-park-components.md Removes Park UI component installation documentation
.gitignore Ignores generated paraglide output directory
.config/mise/conf.d/tasks-*.toml Adds paraglide compilation task to CI and prepare workflows

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/pages/connections/ui/ConnectionNoSelection.tsx
Comment on lines +361 to +367
onValueChange={wrap(
(details: Select.ValueChangeDetails<{ label: string; value: string }>) => {
const val = details.value[0]
if (val !== undefined) {
localeAtom.set(val as 'en' | 'es')
}
},
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

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

Type casting used for locale value without validation. While isLocale is checked in the AppShell.tsx language switcher, the Settings page directly casts the value to 'en' | 'es' without validation. Consider adding a runtime check using isLocale before setting the value, similar to the pattern in AppShell.tsx lines 51-55, to ensure type safety and prevent invalid locale values from being set.

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +8
export const localeAtom = atom(getLocale(), 'locale').extend(
withLocalStorage('locale'),
withChangeHook((value) => void setLocale(value, { reload: false })),
)
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

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

Potential initialization race condition. The localeAtom is initialized with getLocale() and extended with withLocalStorage('locale'). This creates a situation where the atom's initial value comes from paraglide's runtime, but withLocalStorage will also attempt to read from localStorage on initialization. If there's a mismatch between what paraglide returns and what's in localStorage, the behavior may be unpredictable. Consider ensuring these sources are synchronized, or documenting which source takes precedence during initialization.

Copilot uses AI. Check for mistakes.
Comment thread vite.config.ts
Comment on lines 52 to 56
plugins: [
//
react(),
paraglideVitePlugin({ project: './project.inlang', outdir: './src/paraglide' }),
enableDevTools && DevTools(),
].filter(Boolean),
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

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

A comment line was removed without replacement. The removed comment marker // at line 49 (in the original) may have served as a placeholder or visual separator in the plugins array. While this is a minor change, it's worth noting that the removal makes the code slightly less visually organized. This is a very minor stylistic change with no functional impact.

Copilot uses AI. Check for mistakes.
size="sm"
display={{ base: 'none', md: 'inline-flex' }}
onClick={cycleTheme}
aria-label="Toggle theme"
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

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

Hardcoded aria-label should be localized. The aria-label "Toggle theme" at line 242 is hardcoded in English, while other aria-labels in the same component (like the language switcher at line 211) use localized messages. For consistency and proper internationalization, this should use a message key like m.topbar_theme_switcher_label() to ensure screen reader users see the label in their preferred language.

Copilot uses AI. Check for mistakes.
Articles
{m.nav_articles()}
</styled.h3>
<IconButton size="xs" variant="plain" aria-label="New article">
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

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

Hardcoded aria-label should be localized. The aria-label "New article" is hardcoded in English. For consistency with the localization effort throughout the codebase, this should use a message key like m.article_new() or m.articles_add_button() to ensure screen reader users see the label in their preferred language.

Suggested change
<IconButton size="xs" variant="plain" aria-label="New article">
<IconButton size="xs" variant="plain" aria-label={m.article_new()}>

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +26
interface IndicatorProps extends ComponentProps<typeof StyledIndicator> {
fallback?: ReactNode | undefined
}
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

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

Inconsistent usage of ReactNode type import. Line 25 uses ReactNode (imported from 'react'), while line 40 uses React.ReactNode (namespace import). For consistency with the codebase convention, both should use ReactNode from the direct import at the top of the file.

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +41
interface ThumbIndicatorProps extends ComponentProps<typeof StyledThumbIndicator> {
fallback?: React.ReactNode | undefined
}
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

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

Inconsistent usage of ReactNode type import. Line 40 uses React.ReactNode (namespace import), while line 25 uses ReactNode (imported from 'react'). For consistency with the codebase convention and the import at line 4, this should be ReactNode instead of React.ReactNode.

Copilot uses AI. Check for mistakes.
Comment thread src/app/App.tsx
@@ -11,12 +13,13 @@ import { SidebarFooterNavigation } from './SidebarFooterNavigation'
import { SidebarNavigation } from './SidebarNavigation'

export const App = reatomComponent(() => {
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

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

The localeAtom() call appears to be present only to ensure the atom is initialized and subscribes to state changes. However, this creates a subscription without using the returned value. Consider adding a comment explaining why this atom needs to be called at the App root level (e.g., to synchronize with localStorage on mount), or consider if there's a more explicit pattern for initializing atoms that need side effects.

Suggested change
export const App = reatomComponent(() => {
export const App = reatomComponent(() => {
// Initialize and subscribe locale state at the app root (e.g. sync with persistent storage on mount)

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +96
const emailNotificationsCollection = createListCollection({
items: [
{ label: m.settings_notif_all(), value: 'all' },
{ label: m.settings_notif_important(), value: 'important' },
{ label: m.settings_notif_none(), value: 'none' },
],
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
})

const desktopNotificationsCollection = createListCollection({
items: [
{ label: m.settings_notif_enabled(), value: 'enabled' },
{ label: m.settings_notif_disabled(), value: 'disabled' },
],
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
})

const themeCollection = createListCollection({
items: [
{ label: m.settings_theme_light(), value: 'light' },
{ label: m.settings_theme_dark(), value: 'dark' },
{ label: m.settings_theme_system(), value: 'system' },
],
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
})

const densityCollection = createListCollection({
items: [
{ label: m.settings_density_compact(), value: 'compact' },
{ label: m.settings_density_comfortable(), value: 'comfortable' },
{ label: m.settings_density_spacious(), value: 'spacious' },
],
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
})

const languageCollection = createListCollection({
items: [
{ label: m.language_en(), value: 'en' },
{ label: m.language_es(), value: 'es' },
],
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
})
Copy link

Copilot AI Feb 22, 2026

Choose a reason for hiding this comment

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

List collections are being recreated on every render. The emailNotificationsCollection, desktopNotificationsCollection, themeCollection, densityCollection, and languageCollection are created inside the component body, which means they'll be recreated on every render. Since these collections depend on localized messages via m.*() functions, they need to be recreated when the locale changes, but this could be optimized using useMemo to only recreate when the locale actually changes, reducing unnecessary object allocations on other state changes.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pages/connections/ui/ConnectionNoSelection.tsx (1)

9-22: ⚠️ Potential issue | 🟡 Minor

Styling diverges from sibling "no selection" components.

The layout props now differ from the structurally identical ItemNoSelection and MessageThreadNoSelection components (see src/pages/items/ui/ItemNoSelection.tsx and src/pages/chat/ui/MessageThreadNoSelection.tsx):

Prop This component Siblings
height minH="400px" h="100%"
gap "4" "3"
padding (removed) p="8"
textAlign (removed) textAlign="center"
color (removed) color="gray.11"

Dropping textAlign="center" means the text will left-align, and dropping color="gray.11" removes the muted text color. If this is intentional (e.g., these styles are now handled elsewhere), please disregard — otherwise, consider aligning with the sibling components for visual consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/connections/ui/ConnectionNoSelection.tsx` around lines 9 - 22, The
ConnectionNoSelection component's container props diverge from ItemNoSelection
and MessageThreadNoSelection; update the styled.div in ConnectionNoSelection to
match the siblings by replacing minH="400px" with h="100%", changing gap="4" to
gap="3", re-adding p="8", textAlign="center", and color="gray.11" so the layout,
alignment and muted text color are consistent (locate the styled.div inside
ConnectionNoSelection and mirror the prop values used in ItemNoSelection and
MessageThreadNoSelection).
🧹 Nitpick comments (5)
src/pages/calculator/ui/CalculatorPage.tsx (1)

145-147: Consider a dedicated message key for the page heading.

The nav_calculator key is reused for the <h1> title. Nav labels and page headings are semantically distinct UI strings that can diverge in translations (e.g., abbreviated nav labels vs. full headings). A separate key like calculator_title would allow them to be translated independently.

✏️ Suggested change
-{m.nav_calculator()}
+{m.calculator_title()}

And add "calculator_title": "Calculator" to messages/en.json and messages/es.json.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/calculator/ui/CalculatorPage.tsx` around lines 145 - 147, The page
heading currently uses m.nav_calculator which is a nav label; change the heading
to a dedicated message key (e.g., m.calculator_title) in CalculatorPage (replace
the use of m.nav_calculator inside the styled.h1) and add the new key
"calculator_title": "Calculator" to your locale files (messages/en.json and
messages/es.json); also update any TypeScript message typings or message bundle
interfaces if you have a generated or typed messages object so
m.calculator_title is recognized.
.config/mise/conf.d/tasks-prepare.toml (1)

30-34: LGTM — minor: sources may miss additional project.inlang/ config files.

The task definition is correct. The sources glob only covers project.inlang/settings.json, but some Inlang project setups include additional plugin-specific files under project.inlang/. If those files change, mise won't invalidate the task and the catalog won't be recompiled.

💡 Broaden the sources glob (optional)
-sources = ["messages/**/*.json", "project.inlang/settings.json"]
+sources = ["messages/**/*.json", "project.inlang/**/*.json"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.config/mise/conf.d/tasks-prepare.toml around lines 30 - 34, The sources
glob for the task [tasks."prepare:paraglide"] is too narrow (only
"project.inlang/settings.json"); widen the sources array so changes to any files
under project.inlang invalidate the task — update the "sources" entry to include
a recursive glob like "project.inlang/**/*" (in addition to or replacing the
specific settings.json) so plugin-specific configs are picked up when modified.
theme/recipes/index.ts (1)

45-45: Nit: property key switchRecipe breaks the naming pattern of sibling entries.

All other keys in recipes use the bare component name (button, drawer, menu, etc.). Since switch is valid as an object property key in JS/TS (reserved words are allowed in property positions), you could keep this consistent:

♻️ Suggested change
-	switchRecipe,
+	switch: switchRecipe,

The className: 'switch' in the recipe definition already governs CSS output, so this is purely a config-level consistency matter.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@theme/recipes/index.ts` at line 45, The property key switchRecipe in the
recipes export breaks the naming pattern; rename the key to switch (matching
siblings like button, drawer, menu) and keep the recipe definition's className:
'switch' as-is; update any references/imports that currently use switchRecipe to
use the new switch key (locate the recipes object and exports where switchRecipe
is listed, and adjust usages across codebase such as imports or destructuring
that reference switchRecipe).
src/app/App.tsx (1)

16-16: Add a comment explaining the bare localeAtom() subscription.

The return value is intentionally discarded — the call solely subscribes this reatomComponent to locale changes, ensuring m.app_name() (and other m.*() calls in passed props) re-evaluates on every locale switch. ParaglideJS v2 requires the host framework's own reactivity to trigger re-renders after setLocale(), making this subscription load-bearing. Without it, the sidebar header would remain stale after a language change.

💡 Suggested clarifying comment
 export const App = reatomComponent(() => {
-	localeAtom()
+	// Subscribe to locale changes so m.*() calls in this render (e.g. m.app_name())
+	// re-evaluate whenever the user switches language.
+	localeAtom()
 	return (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/App.tsx` at line 16, Add a clarifying inline comment at the call site
of localeAtom() explaining that the return value is intentionally discarded
because the call only subscribes the reatomComponent to locale changes so that
calls like m.app_name() in props re-evaluate on locale switches; mention that
ParaglideJS v2 relies on the host framework to re-render after setLocale(), so
this subscription is load-bearing to keep the sidebar header in sync. Reference
the existing localeAtom() call and reatomComponent / m.app_name() in the
comment.
src/pages/settings/ui/SettingsPage.tsx (1)

50-96: List collections are recreated on every render.

All five createListCollection calls run inside the render body, producing new object references each time any atom changes. On a settings page this is unlikely to cause a noticeable perf issue, but if you want to avoid unnecessary Select re-renders you could memoize these (e.g., useMemo keyed on locale) or move the non-localized parts out of the render.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/settings/ui/SettingsPage.tsx` around lines 50 - 96, The five
createListCollection calls (emailNotificationsCollection,
desktopNotificationsCollection, themeCollection, densityCollection,
languageCollection) are being recreated on every render; wrap each call in a
memoization so the collection objects are stable across renders (e.g., useMemo)
and only recompute when their inputs change (for localized labels depend on
locale/m and for static lists use a single module-level constant), ensuring
createListCollection is invoked only when necessary to avoid needless Select
re-renders.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@messages/en.json`:
- Around line 53-55: Two description strings are missing a trailing period:
update the value for "settings_show_github_link_desc" and the value for
"settings_show_theme_switcher_desc" to end with a period so they match the other
`_desc` entries; locate those keys in messages/en.json and append a period to
each string while preserving the rest of the text.

In `@src/pages/settings/ui/SettingsPage.tsx`:
- Around line 134-138: The displayed role string is hardcoded ("Admin") inside
the FieldRow/ styled.span and should be localized like the rest; replace the
literal "Admin" with a call to the i18n message helper (e.g. use
m.settings_role_admin() or add a new message key and call it) so the span
renders the localized admin label; update the usage in the FieldRow where
styled.span currently contains "Admin" (and add the corresponding message key to
the messages file if it doesn't exist).
- Around line 360-367: Replace the type assertion with Paraglide's runtime
guard: import isLocale from '#paraglide/runtime.js' and inside the onValueChange
handler (the wrapped callback using Select.ValueChangeDetails) call
isLocale(val) and only then call localeAtom.set(val); remove the "as 'en' |
'es'" assertion and ensure the handler safely ignores non-locales.

In `@src/shared/components/ui/switch.tsx`:
- Around line 39-41: The interface ThumbIndicatorProps references
React.ReactNode but the file imports ReactNode as a named type, causing a TS
error; update the interface to use the already-imported ReactNode (i.e., change
React.ReactNode to ReactNode) or alternatively import the React namespace, and
ensure the interface extends ComponentProps<typeof StyledThumbIndicator> with
fallback?: ReactNode | undefined so it compiles; reference symbols:
ThumbIndicatorProps and StyledThumbIndicator.

In `@src/widgets/layout/ui/AppShell.tsx`:
- Line 193: The GitHub link in AppShell.tsx uses a hardcoded aria-label string
("View source on GitHub") while other labels use localization; add a new i18n
message key (e.g., topbar_github_link_label) to your messages bundle and replace
the hardcoded value in the JSX for the GitHub link (the element that currently
sets aria-label directly) with m.topbar_github_link_label() so it matches the
pattern used by m.topbar_language_switcher_label() and other localized labels.
- Around line 187-235: The topbar language Menu (controlled by
showLanguageSwitcher, Menu.Root, Menu.Trigger, and handleLocaleChange) is
correctly hidden on mobile via display={{ base: 'none', md: 'inline-flex' }},
but mobile users still need an accessible place to change locale; add the same
language switch UI (or a simplified variant that uses Menu.RadioItemGroup and
handleLocaleChange) into the mobile drawer or MobileHeader component so language
switching is discoverable on small screens—locate the drawer/sidebar or
MobileHeader rendering in the app shell and copy/adapt the language switcher
there, ensuring it uses the same locale state and handleLocaleChange handler.
- Around line 223-230: The locale options are hardcoded (Menu.RadioItem for
"en"/"es") so new project.inlang locales are missed; import the runtime-exported
readonly array locales and replace the two static Menu.RadioItem entries by
mapping over locales to render a Menu.RadioItem per locale (use each locale as
the value/key), render Menu.ItemText inside each item (use the existing m
translation helpers like m.language_<locale>() when available or fall back to
the raw locale string), and keep Menu.ItemIndicator unchanged so the menu
remains functional (refer to symbols locales, Menu.RadioItem, Menu.ItemText,
Menu.ItemIndicator, and m).

---

Outside diff comments:
In `@src/pages/connections/ui/ConnectionNoSelection.tsx`:
- Around line 9-22: The ConnectionNoSelection component's container props
diverge from ItemNoSelection and MessageThreadNoSelection; update the styled.div
in ConnectionNoSelection to match the siblings by replacing minH="400px" with
h="100%", changing gap="4" to gap="3", re-adding p="8", textAlign="center", and
color="gray.11" so the layout, alignment and muted text color are consistent
(locate the styled.div inside ConnectionNoSelection and mirror the prop values
used in ItemNoSelection and MessageThreadNoSelection).

---

Nitpick comments:
In @.config/mise/conf.d/tasks-prepare.toml:
- Around line 30-34: The sources glob for the task [tasks."prepare:paraglide"]
is too narrow (only "project.inlang/settings.json"); widen the sources array so
changes to any files under project.inlang invalidate the task — update the
"sources" entry to include a recursive glob like "project.inlang/**/*" (in
addition to or replacing the specific settings.json) so plugin-specific configs
are picked up when modified.

In `@src/app/App.tsx`:
- Line 16: Add a clarifying inline comment at the call site of localeAtom()
explaining that the return value is intentionally discarded because the call
only subscribes the reatomComponent to locale changes so that calls like
m.app_name() in props re-evaluate on locale switches; mention that ParaglideJS
v2 relies on the host framework to re-render after setLocale(), so this
subscription is load-bearing to keep the sidebar header in sync. Reference the
existing localeAtom() call and reatomComponent / m.app_name() in the comment.

In `@src/pages/calculator/ui/CalculatorPage.tsx`:
- Around line 145-147: The page heading currently uses m.nav_calculator which is
a nav label; change the heading to a dedicated message key (e.g.,
m.calculator_title) in CalculatorPage (replace the use of m.nav_calculator
inside the styled.h1) and add the new key "calculator_title": "Calculator" to
your locale files (messages/en.json and messages/es.json); also update any
TypeScript message typings or message bundle interfaces if you have a generated
or typed messages object so m.calculator_title is recognized.

In `@src/pages/settings/ui/SettingsPage.tsx`:
- Around line 50-96: The five createListCollection calls
(emailNotificationsCollection, desktopNotificationsCollection, themeCollection,
densityCollection, languageCollection) are being recreated on every render; wrap
each call in a memoization so the collection objects are stable across renders
(e.g., useMemo) and only recompute when their inputs change (for localized
labels depend on locale/m and for static lists use a single module-level
constant), ensuring createListCollection is invoked only when necessary to avoid
needless Select re-renders.

In `@theme/recipes/index.ts`:
- Line 45: The property key switchRecipe in the recipes export breaks the naming
pattern; rename the key to switch (matching siblings like button, drawer, menu)
and keep the recipe definition's className: 'switch' as-is; update any
references/imports that currently use switchRecipe to use the new switch key
(locate the recipes object and exports where switchRecipe is listed, and adjust
usages across codebase such as imports or destructuring that reference
switchRecipe).

Comment thread messages/en.json
Comment on lines +53 to +55
"settings_show_github_link_desc": "Show GitHub repository link in the top bar",
"settings_show_theme_switcher": "Show Theme Switcher",
"settings_show_theme_switcher_desc": "Show theme switcher in the top bar",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor: missing trailing period on two _desc strings, inconsistent with the rest.

All other _desc keys in this file end with a period. settings_show_github_link_desc (line 53) and settings_show_theme_switcher_desc (line 55) do not.

✏️ Proposed fix
-  "settings_show_github_link_desc": "Show GitHub repository link in the top bar",
+  "settings_show_github_link_desc": "Show GitHub repository link in the top bar.",
   "settings_show_theme_switcher": "Show Theme Switcher",
-  "settings_show_theme_switcher_desc": "Show theme switcher in the top bar",
+  "settings_show_theme_switcher_desc": "Show theme switcher in the top bar.",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"settings_show_github_link_desc": "Show GitHub repository link in the top bar",
"settings_show_theme_switcher": "Show Theme Switcher",
"settings_show_theme_switcher_desc": "Show theme switcher in the top bar",
"settings_show_github_link_desc": "Show GitHub repository link in the top bar.",
"settings_show_theme_switcher": "Show Theme Switcher",
"settings_show_theme_switcher_desc": "Show theme switcher in the top bar.",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@messages/en.json` around lines 53 - 55, Two description strings are missing a
trailing period: update the value for "settings_show_github_link_desc" and the
value for "settings_show_theme_switcher_desc" to end with a period so they match
the other `_desc` entries; locate those keys in messages/en.json and append a
period to each string while preserving the rest of the text.

Comment on lines +134 to 138
<FieldRow label={m.settings_role()}>
<styled.span fontSize="sm" color="gray.11">
Admin
</styled.span>
</FieldRow>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

"Admin" role label is still hardcoded in English.

All other user-facing strings in this file have been localized via m.*(), but line 136 still renders a raw "Admin" string. This looks like a localization miss.

Proposed fix
 <FieldRow label={m.settings_role()}>
   <styled.span fontSize="sm" color="gray.11">
-    Admin
+    {m.settings_role_admin()}
   </styled.span>
 </FieldRow>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/settings/ui/SettingsPage.tsx` around lines 134 - 138, The displayed
role string is hardcoded ("Admin") inside the FieldRow/ styled.span and should
be localized like the rest; replace the literal "Admin" with a call to the i18n
message helper (e.g. use m.settings_role_admin() or add a new message key and
call it) so the span renders the localized admin label; update the usage in the
FieldRow where styled.span currently contains "Admin" (and add the corresponding
message key to the messages file if it doesn't exist).

Comment on lines +360 to +367
value={[locale]}
onValueChange={wrap(
(details: Select.ValueChangeDetails<{ label: string; value: string }>) => {
const val = details.value[0]
if (val !== undefined) {
localeAtom.set(val as 'en' | 'es')
}
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how localeAtom is typed
ast-grep --pattern 'const localeAtom = $_'

Repository: Guria/modern-stack

Length of output: 340


🏁 Script executed:

#!/bin/bash
# Also search for localeAtom definition with rg to catch more variations
rg -A 5 'localeAtom\s*=' --type ts --type tsx | head -50

Repository: Guria/modern-stack

Length of output: 89


🏁 Script executed:

#!/bin/bash
# Look for languageCollection definition
rg -B 2 -A 10 'languageCollection' --type ts --type tsx | head -100

Repository: Guria/modern-stack

Length of output: 89


🏁 Script executed:

#!/bin/bash
# Search for Paraglide configuration or locale-related constants
rg -i 'paraglide|availableLanguages|supportedLocales' --type ts --type tsx --type js --type json | head -50

Repository: Guria/modern-stack

Length of output: 89


🏁 Script executed:

#!/bin/bash
# Read the complete locale.ts file to understand localeAtom type
cat -n src/shared/model/locale.ts

Repository: Guria/modern-stack

Length of output: 413


🏁 Script executed:

#!/bin/bash
# Search for languageCollection using ts file type only
rg -B 2 -A 10 'languageCollection' -t ts

Repository: Guria/modern-stack

Length of output: 2711


🏁 Script executed:

#!/bin/bash
# Search for locale type definitions
rg -B 2 -A 3 'type.*locale|locale.*type|Locale.*=' -t ts | head -100

Repository: Guria/modern-stack

Length of output: 488


🏁 Script executed:

#!/bin/bash
# Look at the SettingsPage.tsx file around lines 360-367
sed -n '350,380p' src/pages/settings/ui/SettingsPage.tsx

Repository: Guria/modern-stack

Length of output: 945


🏁 Script executed:

#!/bin/bash
# Find the isLocale function definition
rg -B 2 -A 8 'function isLocale|const isLocale|export.*isLocale' -t ts

Repository: Guria/modern-stack

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check the Paraglide runtime configuration
find . -path ./node_modules -prune -o -type f -name "*.json" -print | xargs grep -l "paraglide\|lang\|locale" 2>/dev/null | head -10

Repository: Guria/modern-stack

Length of output: 145


🏁 Script executed:

#!/bin/bash
# Look for Paraglide runtime directory or config
ls -la src/lib 2>/dev/null || ls -la . | grep -i para

Repository: Guria/modern-stack

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check tsconfig or similar for available locales
rg -i 'paraglide|available.*locale|supported.*language' -t ts -t json | head -30

Repository: Guria/modern-stack

Length of output: 2595


🏁 Script executed:

#!/bin/bash
# Check the Paraglide project settings
cat project.inlang/settings.json

Repository: Guria/modern-stack

Length of output: 432


🏁 Script executed:

#!/bin/bash
# Check if there's any locale type definition in the codebase
rg "type.*Locale|Locale\s*=|as const" -t ts -B 2 -A 2 | head -50

Repository: Guria/modern-stack

Length of output: 2278


🏁 Script executed:

#!/bin/bash
# Look at the complete AppShell.tsx section to see how isLocale is used
rg -B 5 -A 10 'isLocale' -t ts

Repository: Guria/modern-stack

Length of output: 2203


🏁 Script executed:

#!/bin/bash
# Check if localeAtom type is inferred from Paraglide
rg -B 5 -A 5 'getLocale\(\)' -t ts

Repository: Guria/modern-stack

Length of output: 573


Use Paraglide's isLocale() runtime guard instead of type assertion.

The hardcoded type assertion val as 'en' | 'es' bypasses type safety. While project.inlang/settings.json defines the canonical locales (["en", "es"]), this assertion ignores it. If a locale is added to the Paraglide config, TypeScript won't catch the mismatch.

The codebase already has a better pattern in src/widgets/layout/ui/AppShell.tsx:

import { isLocale } from '#paraglide/runtime.js'

if (isLocale(value)) {
  localeAtom.set(value)
}

Apply the same approach in SettingsPage.tsx for consistency and runtime safety.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/settings/ui/SettingsPage.tsx` around lines 360 - 367, Replace the
type assertion with Paraglide's runtime guard: import isLocale from
'#paraglide/runtime.js' and inside the onValueChange handler (the wrapped
callback using Select.ValueChangeDetails) call isLocale(val) and only then call
localeAtom.set(val); remove the "as 'en' | 'es'" assertion and ensure the
handler safely ignores non-locales.

Comment on lines +39 to +41
interface ThumbIndicatorProps extends ComponentProps<typeof StyledThumbIndicator> {
fallback?: React.ReactNode | undefined
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

React.ReactNode is not in scope — TypeScript compilation error.

Line 4 imports { type ComponentProps, type ReactNode } but line 40 references React.ReactNode. Since React is not imported as a namespace, this will fail to compile.

Proposed fix
-interface ThumbIndicatorProps extends ComponentProps<typeof StyledThumbIndicator> {
-	fallback?: React.ReactNode | undefined
-}
+interface ThumbIndicatorProps extends ComponentProps<typeof StyledThumbIndicator> {
+	fallback?: ReactNode | undefined
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
interface ThumbIndicatorProps extends ComponentProps<typeof StyledThumbIndicator> {
fallback?: React.ReactNode | undefined
}
interface ThumbIndicatorProps extends ComponentProps<typeof StyledThumbIndicator> {
fallback?: ReactNode | undefined
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/shared/components/ui/switch.tsx` around lines 39 - 41, The interface
ThumbIndicatorProps references React.ReactNode but the file imports ReactNode as
a named type, causing a TS error; update the interface to use the
already-imported ReactNode (i.e., change React.ReactNode to ReactNode) or
alternatively import the React namespace, and ensure the interface extends
ComponentProps<typeof StyledThumbIndicator> with fallback?: ReactNode |
undefined so it compiles; reference symbols: ThumbIndicatorProps and
StyledThumbIndicator.

Comment on lines +187 to +235
{showGithubLink && (
<IconButton
variant="plain"
size="sm"
display={{ base: 'none', md: 'inline-flex' }}
asChild
aria-label="View source on GitHub"
>
<Github className={css({ w: '4', h: '4' })} />
</a>
</IconButton>
<IconButton
variant="plain"
size="sm"
display={{ base: 'none', md: 'inline-flex' }}
onClick={cycleTheme}
aria-label="Toggle theme"
>
{preference === 'system' ? (
<Monitor className={css({ w: '4', h: '4' })} />
) : resolved === 'dark' ? (
<Moon className={css({ w: '4', h: '4' })} />
) : (
<Sun className={css({ w: '4', h: '4' })} />
)}
</IconButton>
<a
href="https://github.com/guria/modern-stack"
target="_blank"
rel="noopener noreferrer"
>
<Github className={css({ w: '4', h: '4' })} />
</a>
</IconButton>
)}
{showLanguageSwitcher && (
<Menu.Root positioning={{ placement: 'bottom-end' }}>
<Menu.Trigger asChild>
<IconButton
variant="plain"
size="sm"
display={{ base: 'none', md: 'inline-flex' }}
aria-label={m.topbar_language_switcher_label()}
>
<Languages className={css({ w: '4', h: '4' })} />
</IconButton>
</Menu.Trigger>
<Menu.Positioner>
<Menu.Content>
<Menu.RadioItemGroup
id="locale"
value={locale}
onValueChange={handleLocaleChange}
>
<Menu.RadioItem value="en">
<Menu.ItemText>{m.language_en()}</Menu.ItemText>
<Menu.ItemIndicator />
</Menu.RadioItem>
<Menu.RadioItem value="es">
<Menu.ItemText>{m.language_es()}</Menu.ItemText>
<Menu.ItemIndicator />
</Menu.RadioItem>
</Menu.RadioItemGroup>
</Menu.Content>
</Menu.Positioner>
</Menu.Root>
)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's find all component files and understand the structure
fd "MobileHeader|Drawer" --type f

Repository: Guria/modern-stack

Length of output: 291


🏁 Script executed:

# Also search more broadly for mobile components and language switching
rg -l "MobileHeader|Drawer" --type=tsx

Repository: Guria/modern-stack

Length of output: 89


🏁 Script executed:

# Search for language/locale references in the codebase
rg -n "locale|language|Language|Languages" --type=tsx -B2 -A2 | head -100

Repository: Guria/modern-stack

Length of output: 89


🏁 Script executed:

cat -n src/app/MobileHeader.tsx

Repository: Guria/modern-stack

Length of output: 2322


🏁 Script executed:

# Search for locale and language references without the problematic file type
rg "locale|language|Language|Languages" -B2 -A2 | head -150

Repository: Guria/modern-stack

Length of output: 9040


🏁 Script executed:

# Look at AppShell.tsx for drawer content
cat -n src/widgets/layout/ui/AppShell.tsx | head -200

Repository: Guria/modern-stack

Length of output: 7102


🏁 Script executed:

# Look for the sidebar content and how Settings is exposed
rg "nav_settings" -B3 -A3

Repository: Guria/modern-stack

Length of output: 1855


🏁 Script executed:

# Check what sidebar navigation items are available
cat -n src/widgets/layout/ui/AppShell.tsx | grep -A 50 "sidebarContent"

Repository: Guria/modern-stack

Length of output: 3611


Language switcher is correctly hidden on mobile, but language switching remains accessible.

The topbar language switcher uses display={{ base: 'none', md: 'inline-flex' }}, which is appropriate for mobile. However, mobile users can switch language via the Settings page (accessible through the sidebar drawer), so the concern that "mobile users are locked into their initial locale" is not accurate. That said, the Settings route is less discoverable than a dedicated topbar control—if improving mobile UX for language switching is desired, consider adding a language switcher directly in the drawer or MobileHeader.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/widgets/layout/ui/AppShell.tsx` around lines 187 - 235, The topbar
language Menu (controlled by showLanguageSwitcher, Menu.Root, Menu.Trigger, and
handleLocaleChange) is correctly hidden on mobile via display={{ base: 'none',
md: 'inline-flex' }}, but mobile users still need an accessible place to change
locale; add the same language switch UI (or a simplified variant that uses
Menu.RadioItemGroup and handleLocaleChange) into the mobile drawer or
MobileHeader component so language switching is discoverable on small
screens—locate the drawer/sidebar or MobileHeader rendering in the app shell and
copy/adapt the language switcher there, ensuring it uses the same locale state
and handleLocaleChange handler.

size="sm"
display={{ base: 'none', md: 'inline-flex' }}
asChild
aria-label="View source on GitHub"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

aria-label for GitHub link is hardcoded while other new labels are localized.

Line 211 correctly uses m.topbar_language_switcher_label(), but line 193's "View source on GitHub" is a bare string in the same new block. Consider adding a topbar_github_link_label message key for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/widgets/layout/ui/AppShell.tsx` at line 193, The GitHub link in
AppShell.tsx uses a hardcoded aria-label string ("View source on GitHub") while
other labels use localization; add a new i18n message key (e.g.,
topbar_github_link_label) to your messages bundle and replace the hardcoded
value in the JSX for the GitHub link (the element that currently sets aria-label
directly) with m.topbar_github_link_label() so it matches the pattern used by
m.topbar_language_switcher_label() and other localized labels.

Comment on lines +223 to +230
<Menu.RadioItem value="en">
<Menu.ItemText>{m.language_en()}</Menu.ItemText>
<Menu.ItemIndicator />
</Menu.RadioItem>
<Menu.RadioItem value="es">
<Menu.ItemText>{m.language_es()}</Menu.ItemText>
<Menu.ItemIndicator />
</Menu.RadioItem>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🌐 Web query:

ParaglideJS v2 runtime availableLocales API documentation

💡 Result:

In ParaglideJS v2, the runtime API that exposes the list of configured locales is documented as:

  • locales: readonly Locale[] — “The project's locales that have been specified in the settings.” (exported from the generated ./paraglide/runtime.js) [1]

Example:

import { locales } from "./paraglide/runtime.js";

console.log(locales); // e.g. ["en", "de"]

Notes on naming: the ParaglideJS v2 changelog shows there was a beta rename from runtime.localesruntime.availableLocales (to match v1) [2], but the current v2 runtime API reference page documents the export as locales [1].

Sources: [1] https://inlang.com/m/gerre34r/library-inlang-paraglideJs/runtime, [2] https://inlang.com/m/gerre34r/library-inlang-paraglideJs/changelog


🏁 Script executed:

# First, let's find and examine the AppShell.tsx file
find . -name "AppShell.tsx" -type f

Repository: Guria/modern-stack

Length of output: 98


🏁 Script executed:

# Check the project configuration to understand ParaglideJS setup
find . -name "project.inlang" -o -name "inlang.config.ts" -o -name "inlang.config.js" | head -20

Repository: Guria/modern-stack

Length of output: 78


🏁 Script executed:

# Look for any existing runtime imports in the codebase
rg "from.*paraglide/runtime" -A 2 -B 2

Repository: Guria/modern-stack

Length of output: 740


🏁 Script executed:

# Read the AppShell.tsx file to see the actual imports and locale handling
cat -n ./src/widgets/layout/ui/AppShell.tsx | head -40

Repository: Guria/modern-stack

Length of output: 1365


🏁 Script executed:

# Read the sections around the language switcher (lines 200-235)
sed -n '200,235p' ./src/widgets/layout/ui/AppShell.tsx | cat -n

Repository: Guria/modern-stack

Length of output: 1466


🏁 Script executed:

# Check the project.inlang configuration to see what locales are configured
cat ./project.inlang

Repository: Guria/modern-stack

Length of output: 99


🏁 Script executed:

# Look for any existing usage of locales export from paraglide runtime
rg "import.*locales.*from.*paraglide" -B 2 -A 2

Repository: Guria/modern-stack

Length of output: 44


🏁 Script executed:

# Check the inlang config file structure
find ./project.inlang -type f | head -10

Repository: Guria/modern-stack

Length of output: 92


🏁 Script executed:

# Read the main inlang config file
cat -n ./project.inlang/settings.json 2>/dev/null || cat -n ./project.inlang/inlang.config.ts 2>/dev/null || ls -la ./project.inlang/

Repository: Guria/modern-stack

Length of output: 516


🏁 Script executed:

# Look for locale configuration
rg "locales?" ./project.inlang/ 2>/dev/null | head -20

Repository: Guria/modern-stack

Length of output: 193


🏁 Script executed:

# Check the locale model to understand how locales are currently handled
cat -n ./src/shared/model/locale.ts

Repository: Guria/modern-stack

Length of output: 413


Hardcoded locale list will silently miss new languages.

The en/es options are hardcoded, so adding a third locale to project.inlang won't automatically surface it here. ParaglideJS v2's runtime exports locales (a readonly array of configured locales), which can drive the list dynamically.

♻️ Suggested refactor using `locales`

In the import at line 16, add locales:

-import { isLocale } from '#paraglide/runtime.js'
+import { locales, isLocale } from '#paraglide/runtime.js'

Replace the hardcoded items (lines 224–230):

-<Menu.RadioItem value="en">
-  <Menu.ItemText>{m.language_en()}</Menu.ItemText>
-  <Menu.ItemIndicator />
-</Menu.RadioItem>
-<Menu.RadioItem value="es">
-  <Menu.ItemText>{m.language_es()}</Menu.ItemText>
-  <Menu.ItemIndicator />
-</Menu.RadioItem>
+{locales.map((loc) => (
+  <Menu.RadioItem key={loc} value={loc}>
+    <Menu.ItemText>{m[`language_${loc}` as keyof typeof m]?.() ?? loc}</Menu.ItemText>
+    <Menu.ItemIndicator />
+  </Menu.RadioItem>
+))}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Menu.RadioItem value="en">
<Menu.ItemText>{m.language_en()}</Menu.ItemText>
<Menu.ItemIndicator />
</Menu.RadioItem>
<Menu.RadioItem value="es">
<Menu.ItemText>{m.language_es()}</Menu.ItemText>
<Menu.ItemIndicator />
</Menu.RadioItem>
{locales.map((loc) => (
<Menu.RadioItem key={loc} value={loc}>
<Menu.ItemText>{m[`language_${loc}` as keyof typeof m]?.() ?? loc}</Menu.ItemText>
<Menu.ItemIndicator />
</Menu.RadioItem>
))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/widgets/layout/ui/AppShell.tsx` around lines 223 - 230, The locale
options are hardcoded (Menu.RadioItem for "en"/"es") so new project.inlang
locales are missed; import the runtime-exported readonly array locales and
replace the two static Menu.RadioItem entries by mapping over locales to render
a Menu.RadioItem per locale (use each locale as the value/key), render
Menu.ItemText inside each item (use the existing m translation helpers like
m.language_<locale>() when available or fall back to the raw locale string), and
keep Menu.ItemIndicator unchanged so the menu remains functional (refer to
symbols locales, Menu.RadioItem, Menu.ItemText, Menu.ItemIndicator, and m).

@Guria Guria closed this Feb 23, 2026
@Guria Guria deleted the paraglide branch March 4, 2026 21:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants