Skip to content

Commit 678a917

Browse files
committed
chore: fix conflict
2 parents d16357f + 2fd341d commit 678a917

58 files changed

Lines changed: 3930 additions & 611 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.vscode/extensions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"recommendations": ["oxc.oxc-vscode", "Vue.volar"]
2+
"recommendations": ["oxc.oxc-vscode", "Vue.volar", "lokalise.i18n-ally"]
33
}

.vscode/settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"i18n-ally.localesPaths": ["./i18n/locales"],
3+
"i18n-ally.keystyle": "nested"
4+
}

CONTRIBUTING.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,89 @@ const props = defineProps<{
208208

209209
Ideally, extract utilities into separate files so they can be unit tested. 🙏
210210

211+
## Localization (i18n)
212+
213+
npmx.dev uses [@nuxtjs/i18n](https://i18n.nuxtjs.org/) for internationalization. We aim to make the UI accessible to users in their preferred language.
214+
215+
### Approach
216+
217+
- All user-facing strings should use translation keys via `$t()` in templates or `t()` in script
218+
- Translation files live in `i18n/locales/` (e.g., `en.json`)
219+
- We use the `no_prefix` strategy (no `/en/` or `/fr/` in URLs)
220+
- Locale preference is stored in cookies and respected on subsequent visits
221+
222+
### Adding translations
223+
224+
1. Add your translation key to `i18n/locales/en.json` first (English is the source of truth)
225+
2. Use the key in your component:
226+
227+
```vue
228+
<template>
229+
<p>{{ $t('my.translation.key') }}</p>
230+
</template>
231+
```
232+
233+
Or in script:
234+
235+
```typescript
236+
const { t } = useI18n()
237+
const message = t('my.translation.key')
238+
```
239+
240+
3. For dynamic values, use interpolation:
241+
242+
```json
243+
{ "greeting": "Hello, {name}!" }
244+
```
245+
246+
```vue
247+
<p>{{ $t('greeting', { name: userName }) }}</p>
248+
```
249+
250+
### Translation key conventions
251+
252+
- Use dot notation for hierarchy: `section.subsection.key`
253+
- Keep keys descriptive but concise
254+
- Group related keys together
255+
- Use `common.*` for shared strings (loading, retry, close, etc.)
256+
- Use component-specific prefixes: `package.card.*`, `settings.*`, `nav.*`
257+
258+
### Using i18n-ally (recommended)
259+
260+
We recommend the [i18n-ally](https://marketplace.visualstudio.com/items?itemName=lokalise.i18n-ally) VSCode extension for a better development experience:
261+
262+
- Inline translation previews in your code
263+
- Auto-completion for translation keys
264+
- Missing translation detection
265+
- Easy navigation to translation files
266+
267+
The extension is included in our workspace recommendations, so VSCode should prompt you to install it.
268+
269+
### Adding a new locale
270+
271+
1. Create a new JSON file in `i18n/locales/` (e.g., `fr.json`)
272+
2. Add the locale to `nuxt.config.ts`:
273+
274+
```typescript
275+
i18n: {
276+
locales: [
277+
{ code: 'en', language: 'en-US', name: 'English', file: 'en.json' },
278+
{ code: 'fr', language: 'fr-FR', name: 'Francais', file: 'fr.json' },
279+
],
280+
}
281+
```
282+
283+
3. Translate all keys from `en.json`
284+
285+
### Formatting with locale
286+
287+
When formatting numbers or dates that should respect the user's locale, pass the locale:
288+
289+
```typescript
290+
const { locale } = useI18n()
291+
const formatted = formatNumber(12345, locale.value) // "12,345" in en-US
292+
```
293+
211294
## Testing
212295

213296
### Unit tests

app/components/AppFooter.vue

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,35 +85,35 @@ onMounted(() => {
8585
>
8686
<div class="container py-2 sm:py-6 flex flex-col gap-1 sm:gap-3 text-fg-subtle text-sm">
8787
<div class="flex flex-row items-center justify-between gap-2 sm:gap-4">
88-
<p class="font-mono m-0 hidden sm:block">a better browser for the npm registry</p>
88+
<p class="font-mono m-0 hidden sm:block">{{ $t('tagline') }}</p>
8989
<!-- On mobile, show disclaimer here instead of tagline -->
90-
<p class="text-xs text-fg-muted m-0 sm:hidden">not affiliated with npm, Inc.</p>
90+
<p class="text-xs text-fg-muted m-0 sm:hidden">{{ $t('non_affiliation_disclaimer') }}</p>
9191
<div class="flex items-center gap-4 sm:gap-6">
9292
<a
9393
href="https://repo.npmx.dev"
9494
rel="noopener noreferrer"
9595
class="link-subtle font-mono text-xs min-h-11 min-w- flex items-center"
9696
>
97-
source
97+
{{ $t('footer.source') }}
9898
</a>
9999
<a
100100
href="https://social.npmx.dev"
101101
rel="noopener noreferrer"
102102
class="link-subtle font-mono text-xs min-h-11 min-w-11 flex items-center"
103103
>
104-
social
104+
{{ $t('footer.social') }}
105105
</a>
106106
<a
107107
href="https://chat.npmx.dev"
108108
rel="noopener noreferrer"
109109
class="link-subtle font-mono text-xs min-h-11 min-w-11 flex items-center"
110110
>
111-
chat
111+
{{ $t('footer.chat') }}
112112
</a>
113113
</div>
114114
</div>
115115
<p class="text-xs text-fg-muted text-center sm:text-left m-0 hidden sm:block">
116-
npm is a registered trademark of npm, Inc. This site is not affiliated with npm, Inc.
116+
{{ $t('trademark_disclaimer') }}
117117
</p>
118118
</div>
119119
</footer>

app/components/AppHeader.vue

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,36 @@ withDefaults(
99
showConnector: true,
1010
},
1111
)
12+
13+
const { isConnected, npmUser } = useConnector()
1214
</script>
1315

1416
<template>
1517
<header class="sticky top-0 z-50 bg-bg/80 backdrop-blur-md border-b border-border">
16-
<nav aria-label="Main navigation" class="container h-14 flex items-center justify-between">
17-
<NuxtLink
18-
v-if="showLogo"
19-
to="/"
20-
aria-label="npmx home"
21-
class="header-logo font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring rounded"
22-
>
23-
<span class="text-fg-subtle"><span style="letter-spacing: -0.2em">.</span>/</span>npmx
24-
</NuxtLink>
25-
<!-- Spacer when logo is hidden -->
26-
<span v-else class="w-1" />
18+
<nav aria-label="Main navigation" class="container h-14 flex items-center">
19+
<!-- Left: Logo -->
20+
<div class="flex-shrink-0">
21+
<NuxtLink
22+
v-if="showLogo"
23+
to="/"
24+
:aria-label="$t('header.home')"
25+
class="header-logo font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 focus-ring rounded"
26+
>
27+
<span class="text-fg-subtle"><span style="letter-spacing: -0.2em">.</span>/</span>npmx
28+
</NuxtLink>
29+
<!-- Spacer when logo is hidden -->
30+
<span v-else class="w-1" />
31+
</div>
2732

28-
<ul class="flex items-center gap-4 sm:gap-6 list-none m-0 p-0">
33+
<!-- Center: Main nav items -->
34+
<ul class="flex-1 flex items-center justify-center gap-4 sm:gap-6 list-none m-0 p-0">
2935
<li class="flex items-center">
3036
<NuxtLink
3137
to="/search"
3238
class="link-subtle font-mono text-sm inline-flex items-center gap-2"
3339
aria-keyshortcuts="/"
3440
>
35-
search
41+
{{ $t('nav.search') }}
3642
<kbd
3743
class="hidden sm:inline-flex items-center justify-center w-5 h-5 text-xs bg-bg-muted border border-border rounded"
3844
aria-hidden="true"
@@ -41,26 +47,38 @@ withDefaults(
4147
</kbd>
4248
</NuxtLink>
4349
</li>
44-
<li class="flex items-center">
45-
<ClientOnly>
46-
<SettingsMenu />
47-
</ClientOnly>
48-
</li>
49-
<li v-if="showConnector" class="flex items-center">
50-
<ConnectorStatus />
50+
51+
<!-- Packages dropdown (when connected) -->
52+
<li v-if="isConnected && npmUser" class="flex items-center">
53+
<HeaderPackagesDropdown :username="npmUser" />
5154
</li>
52-
<li v-else class="flex items-center">
53-
<a
54-
href="https://github.com/npmx-dev/npmx.dev"
55-
rel="noopener noreferrer"
56-
class="link-subtle font-mono text-sm inline-flex items-center gap-1.5"
57-
aria-label="GitHub"
58-
>
59-
<span class="i-carbon-logo-github w-4 h-4" aria-hidden="true" />
60-
<span class="hidden sm:inline" aria-hidden="true">github</span>
61-
</a>
55+
56+
<!-- Orgs dropdown (when connected) -->
57+
<li v-if="isConnected && npmUser" class="flex items-center">
58+
<HeaderOrgsDropdown :username="npmUser" />
6259
</li>
6360
</ul>
61+
62+
<!-- Right: User status + GitHub -->
63+
<div class="flex-shrink-0 flex items-center gap-6">
64+
<ClientOnly>
65+
<SettingsMenu />
66+
</ClientOnly>
67+
68+
<div v-if="showConnector">
69+
<ConnectorStatus />
70+
</div>
71+
72+
<a
73+
href="https://github.com/npmx-dev/npmx.dev"
74+
target="_blank"
75+
rel="noopener noreferrer"
76+
class="link-subtle"
77+
:aria-label="$t('header.github')"
78+
>
79+
<span class="i-carbon-logo-github w-5 h-5" aria-hidden="true" />
80+
</a>
81+
</div>
6482
</nav>
6583
</header>
6684
</template>

0 commit comments

Comments
 (0)