feat(web): add i18n support with 4 locales (en, ko, ja, zh)#107
feat(web): add i18n support with 4 locales (en, ko, ja, zh)#107
Conversation
Configure 4 locales (en, ko, ja, zh) with lazy-loaded JSON files, browser language detection with cookie persistence, and ISR route rules for locale-prefixed paths.
Extract all UI strings from index.vue, PluginCard.vue, PluginSearch.vue, and InstallModal.vue to use i18n translation function calls referencing locale JSON files.
Build LanguageSwitcher using USelectMenu with locale options, integrate into hero section, and extend font stack with Japanese (Hiragino, Yu Gothic) and Chinese (PingFang SC, Microsoft YaHei) system fonts.
The @nuxtjs/i18n module resolves langDir relative to its own i18n/ base directory. Also fix zh.json unicode quote issue that caused build parser failure.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the web application's accessibility for a global audience by integrating comprehensive internationalization capabilities. It allows users to experience the interface in their native language, improving usability and reach without impacting performance due to optimized routing and lazy-loaded translations. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces internationalization (i18n) support to the web application using @nuxtjs/i18n. It adds new locale files for English, Korean, Japanese, and Chinese, updates the Nuxt configuration for i18n and ISR rules, and modifies several Vue components to use translated strings. Documentation for the i18n feature has also been added. Feedback includes a critical issue with the langDir configuration in nuxt.config.ts which needs to be corrected to i18n/locales, and a suggestion to leverage @nuxtjs/i18n's built-in pluralization feature for the search results count in PluginSearch.vue for cleaner and more maintainable code.
| ], | ||
| defaultLocale: 'en', | ||
| strategy: 'prefix_except_default', | ||
| langDir: 'locales', |
There was a problem hiding this comment.
The langDir seems to be configured incorrectly. The translation files are located in apps/web/i18n/locales/, but langDir is set to 'locales'. Since langDir is relative to the Nuxt project root (apps/web/), it should be 'i18n/locales'. Without this change, Nuxt will look for translations in apps/web/locales/ and fail to find them.
| langDir: 'locales', | |
| langDir: 'i18n/locales', |
| size="sm" | ||
| > | ||
| {{ filteredCount }} {{ filteredCount === 1 ? 'result' : 'results' }} | ||
| {{ filteredCount }} {{ filteredCount === 1 ? $t('search.result') : $t('search.results') }} |
There was a problem hiding this comment.
For better internationalization support and to fully leverage the @nuxtjs/i18n library, I recommend using its built-in pluralization feature. This will make the code cleaner and more maintainable, especially if you add languages with more complex pluralization rules in the future.
You can update your locale files like this:
en.json
"search": {
...
"results": "1 result | {count} results",
...
}(The result key can be removed.)
ko.json
"search": {
...
"results": "{count}개 결과",
...
}(The result key can be removed.)
Then, you can simplify the template code.
{{ $t('search.results', filteredCount) }}
- Use {count} interpolation for result count instead of manual
singular/plural ternary (CJK-safe)
- Use i18n-t component interpolation for footer text to support
proper word order across locales
- Wrap error.message in localized description template
There was a problem hiding this comment.
2 issues found across 17 files
Confidence score: 4/5
- This PR is safe to merge with minimal risk: both findings are low severity (4/10 and 3/10) and mainly affect wording/clarity rather than core runtime behavior.
- The most user-facing issue is in
apps/web/app/components/PluginSearch.vue, where concatenated translated fragments may produce awkward or incorrect grammar in some locales; switching to a single i18n key with{query}improves localization quality. .please/docs/tracks/active/web-i18n-20260326/plan.mdhas conflicting English-route expectations (/vs/or/en), which could cause implementation or QA confusion if not clarified.- Pay close attention to
apps/web/app/components/PluginSearch.vueand.please/docs/tracks/active/web-i18n-20260326/plan.md- ensure i18n sentence composition and route expectations are aligned.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/web/app/components/PluginSearch.vue">
<violation number="1" location="apps/web/app/components/PluginSearch.vue:45">
P2: Avoid concatenating translated fragments with dynamic text for user-facing sentences; use one i18n message with a `{query}` placeholder so each locale can control grammar and spacing.</violation>
</file>
<file name=".please/docs/tracks/active/web-i18n-20260326/plan.md">
<violation number="1" location=".please/docs/tracks/active/web-i18n-20260326/plan.md:90">
P3: The plan has conflicting English-route expectations (`/` only vs `/` or `/en`). Align this line with the routing strategy to avoid ambiguous implementation and QA validation.</violation>
</file>
Architecture diagram
sequenceDiagram
participant Client as Browser (User)
participant Nuxt as Nuxt Server / ISR
participant i18n as @nuxtjs/i18n Module
participant Locales as Locale JSON Files
participant API as Marketplace API
Note over Client, API: Initial Visit (Browser Language Detection)
Client->>Nuxt: GET / (Accept-Language: ko)
Nuxt->>i18n: NEW: detectBrowserLanguage()
i18n->>i18n: Check 'i18n_redirected' cookie
alt Cookie missing & Locale is non-default
i18n-->>Nuxt: Redirect to /ko
Nuxt-->>Client: NEW: 302 Redirect to /ko + Set Cookie
end
Note over Client, API: Rendering localized page (ISR)
Client->>Nuxt: GET /ko
Nuxt->>i18n: Resolve locale context ('ko')
i18n->>Locales: NEW: Lazy-load ko.json
Locales-->>i18n: Locale keys ($t)
Nuxt->>API: fetch("/api/marketplaces")
API-->>Nuxt: Plugin data (Untranslated)
Nuxt->>Nuxt: CHANGED: Render components with $t()
Nuxt-->>Client: Localized HTML (Cached via ISR)
Note over Client, API: Manual Language Switching
Client->>Client: Click LanguageSwitcher.vue
Client->>i18n: NEW: useSwitchLocalePath('ja')
i18n-->>Client: Returns "/ja"
Client->>Nuxt: navigateTo("/ja")
Nuxt->>i18n: Update locale to 'ja'
i18n->>Locales: NEW: Lazy-load ja.json
Nuxt-->>Client: Updated UI in Japanese
Note over Client, API: Failure Path (Missing Translation)
Client->>Nuxt: GET /zh
Nuxt->>i18n: Request missing key
i18n-->>Nuxt: CHANGED: Fallback to 'en' key/string
Nuxt-->>Client: Rendered HTML with fallback text
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| <div v-if="searchQuery" class="mt-2 text-xs text-muted flex items-center gap-1"> | ||
| <UIcon name="i-heroicons-funnel" /> | ||
| <span>Filtering by: <span class="font-semibold">{{ searchQuery }}</span></span> | ||
| <span>{{ $t('search.filteringBy') }} <span class="font-semibold">{{ searchQuery }}</span></span> |
There was a problem hiding this comment.
P2: Avoid concatenating translated fragments with dynamic text for user-facing sentences; use one i18n message with a {query} placeholder so each locale can control grammar and spacing.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/components/PluginSearch.vue, line 45:
<comment>Avoid concatenating translated fragments with dynamic text for user-facing sentences; use one i18n message with a `{query}` placeholder so each locale can control grammar and spacing.</comment>
<file context>
@@ -42,7 +42,7 @@ const searchQuery = defineModel<string>({ default: '' })
<div v-if="searchQuery" class="mt-2 text-xs text-muted flex items-center gap-1">
<UIcon name="i-heroicons-funnel" />
- <span>Filtering by: <span class="font-semibold">{{ searchQuery }}</span></span>
+ <span>{{ $t('search.filteringBy') }} <span class="font-semibold">{{ searchQuery }}</span></span>
</div>
</div>
</file context>
| - After visiting `/ko`, all UI text appears in Korean | ||
| - After visiting `/ja`, all UI text appears in Japanese | ||
| - After visiting `/zh`, all UI text appears in Chinese | ||
| - After visiting `/` or `/en`, UI remains in English |
There was a problem hiding this comment.
P3: The plan has conflicting English-route expectations (/ only vs / or /en). Align this line with the routing strategy to avoid ambiguous implementation and QA validation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .please/docs/tracks/active/web-i18n-20260326/plan.md, line 90:
<comment>The plan has conflicting English-route expectations (`/` only vs `/` or `/en`). Align this line with the routing strategy to avoid ambiguous implementation and QA validation.</comment>
<file context>
@@ -0,0 +1,114 @@
+- After visiting `/ko`, all UI text appears in Korean
+- After visiting `/ja`, all UI text appears in Japanese
+- After visiting `/zh`, all UI text appears in Chinese
+- After visiting `/` or `/en`, UI remains in English
+- Running `bun run build` in `apps/web/` completes without errors
+
</file context>
- Add useLocaleHead() for automatic hreflang link generation (NFR-3) - Add i18n.test.ts verifying all locale files share identical keys (AC-7)
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/web/tests/i18n.test.ts">
<violation number="1" location="apps/web/tests/i18n.test.ts:27">
P2: Avoid computing `referenceKeys` with a non-null assertion before verifying `en.json` exists; a missing/invalid `en.json` will crash the suite before the file-existence test runs.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| const locales = Object.fromEntries( | ||
| files.map(f => [f.replace('.json', ''), loadLocale(f)]), | ||
| ) | ||
| const referenceKeys = getKeys(locales.en!).sort() |
There was a problem hiding this comment.
P2: Avoid computing referenceKeys with a non-null assertion before verifying en.json exists; a missing/invalid en.json will crash the suite before the file-existence test runs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/tests/i18n.test.ts, line 27:
<comment>Avoid computing `referenceKeys` with a non-null assertion before verifying `en.json` exists; a missing/invalid `en.json` will crash the suite before the file-existence test runs.</comment>
<file context>
@@ -0,0 +1,47 @@
+ const locales = Object.fromEntries(
+ files.map(f => [f.replace('.json', ''), loadLocale(f)]),
+ )
+ const referenceKeys = getKeys(locales.en!).sort()
+
+ it('should have all expected locale files', () => {
</file context>
Summary
@nuxtjs/i18nmodule withprefix_except_defaultrouting strategyTest plan
/ko— UI renders in Korean/ja— UI renders in Japanese/zh— UI renders in Chinese/— UI renders in English (default)bun run buildcompletes without errorsTrack
web-i18n-20260326— spec | plan