diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 87400b48b3..c2fe9b8640 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,3 +1,3 @@
{
- "recommendations": ["oxc.oxc-vscode", "Vue.volar"]
+ "recommendations": ["oxc.oxc-vscode", "Vue.volar", "lokalise.i18n-ally"]
}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000..f33841cbac
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "i18n-ally.localesPaths": ["./i18n/locales"],
+ "i18n-ally.keystyle": "nested"
+}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c13a1354eb..189f5c37a1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -208,6 +208,89 @@ const props = defineProps<{
Ideally, extract utilities into separate files so they can be unit tested. 🙏
+## Localization (i18n)
+
+npmx.dev uses [@nuxtjs/i18n](https://i18n.nuxtjs.org/) for internationalization. We aim to make the UI accessible to users in their preferred language.
+
+### Approach
+
+- All user-facing strings should use translation keys via `$t()` in templates or `t()` in script
+- Translation files live in `i18n/locales/` (e.g., `en.json`)
+- We use the `no_prefix` strategy (no `/en/` or `/fr/` in URLs)
+- Locale preference is stored in cookies and respected on subsequent visits
+
+### Adding translations
+
+1. Add your translation key to `i18n/locales/en.json` first (English is the source of truth)
+2. Use the key in your component:
+
+ ```vue
+
+ {{ $t('my.translation.key') }}
{{ $t('greeting', { name: userName }) }}
+ ``` + +### Translation key conventions + +- Use dot notation for hierarchy: `section.subsection.key` +- Keep keys descriptive but concise +- Group related keys together +- Use `common.*` for shared strings (loading, retry, close, etc.) +- Use component-specific prefixes: `package.card.*`, `settings.*`, `nav.*` + +### Using i18n-ally (recommended) + +We recommend the [i18n-ally](https://marketplace.visualstudio.com/items?itemName=lokalise.i18n-ally) VSCode extension for a better development experience: + +- Inline translation previews in your code +- Auto-completion for translation keys +- Missing translation detection +- Easy navigation to translation files + +The extension is included in our workspace recommendations, so VSCode should prompt you to install it. + +### Adding a new locale + +1. Create a new JSON file in `i18n/locales/` (e.g., `fr.json`) +2. Add the locale to `nuxt.config.ts`: + + ```typescript + i18n: { + locales: [ + { code: 'en', language: 'en-US', name: 'English', file: 'en.json' }, + { code: 'fr', language: 'fr-FR', name: 'Francais', file: 'fr.json' }, + ], + } + ``` + +3. Translate all keys from `en.json` + +### Formatting with locale + +When formatting numbers or dates that should respect the user's locale, pass the locale: + +```typescript +const { locale } = useI18n() +const formatted = formatNumber(12345, locale.value) // "12,345" in en-US +``` + ## Testing ### Unit tests diff --git a/app/components/AppFooter.vue b/app/components/AppFooter.vue index cba4d974d1..04a604d3b2 100644 --- a/app/components/AppFooter.vue +++ b/app/components/AppFooter.vue @@ -85,35 +85,35 @@ onMounted(() => { >not affiliated with npm, Inc.
+{{ $t('non_affiliation_disclaimer') }}
- You can now publish new versions to this package using
- npm publish.
+ {{ $t('claim.modal.success_hint') }}
Invalid package name:
+{{ $t('claim.modal.invalid_name') }}
Warnings:
+{{ $t('common.warnings') }}
This name is available!
+{{ $t('claim.modal.available') }}
This name is already taken.
+{{ $t('claim.modal.taken') }}
Consider using a scoped package instead
+{{ $t('claim.modal.scope_warning_title') }}
- Unscoped package names are a shared resource. Only claim a name if you intend to
- publish and maintain a package. For personal or organizational projects, use a
- scoped name like
- @{{ npmUser || 'username' }}/{{ packageName }}.
+ {{
+ $t('claim.modal.scope_warning_text', {
+ username: npmUser || 'username',
+ name: packageName,
+ })
+ }}
Connect to the local connector to claim this package name.
+{{ $t('claim.modal.connect_required') }}
- This will publish a minimal placeholder package. + {{ $t('claim.modal.publish_hint') }}
@@ -368,7 +370,7 @@ const connectorModalOpen = shallowRef(false){{
JSON.stringify(previewPackageJson, null, 2)
@@ -381,7 +383,9 @@ const connectorModalOpen = shallowRef(false)
class="w-full px-4 py-2 font-mono text-sm text-bg bg-fg rounded-md transition-colors duration-200 hover:bg-fg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
@click="handleClaim"
>
- {{ isPublishing ? 'Publishing…' : 'Claim Package Name' }}
+ {{
+ isPublishing ? $t('claim.modal.publishing') : $t('claim.modal.claim_button')
+ }}
No files in this directory
+{{ $t('code.no_files') }}
| Name | -Size | +{{ $t('code.table.name') }} | +{{ $t('code.table.size') }} |
|---|