Skip to content

Commit e106049

Browse files
committed
Merge origin/main into feature branch
2 parents 3d91ad1 + 5cab540 commit e106049

157 files changed

Lines changed: 16933 additions & 2712 deletions

File tree

Some content is hidden

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

.github/workflows/autofix.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ jobs:
1717
runs-on: ubuntu-latest
1818

1919
steps:
20-
- uses: actions/checkout@v6
20+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
2121
- run: corepack enable
22-
- uses: actions/setup-node@v6
22+
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
2323
with:
2424
node-version: lts/*
2525
cache: 'pnpm'
@@ -42,7 +42,8 @@ jobs:
4242
- name: 🏃 Update component test snapshots
4343
run: pnpm test:nuxt -u
4444

45-
- name: 🖥️ Update browser test snapshots
46-
run: pnpm test:browser --update-snapshots
45+
# TODO: re-enable when we have snapshots in browser tests
46+
# - name: 🖥️ Update browser test snapshots
47+
# run: pnpm test:browser --update-snapshots
4748

4849
- uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27

.github/workflows/ci.yml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ jobs:
1919
runs-on: ubuntu-latest
2020

2121
steps:
22-
- uses: actions/checkout@v6
22+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
2323
- run: corepack enable
24-
- uses: actions/setup-node@v6
24+
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
2525
with:
2626
node-version: lts/*
2727
cache: pnpm
@@ -36,9 +36,9 @@ jobs:
3636
runs-on: ubuntu-latest
3737

3838
steps:
39-
- uses: actions/checkout@v6
39+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
4040
- run: corepack enable
41-
- uses: actions/setup-node@v6
41+
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
4242
with:
4343
node-version: lts/*
4444
cache: pnpm
@@ -61,12 +61,12 @@ jobs:
6161
browser:
6262
runs-on: ubuntu-latest
6363
container:
64-
image: mcr.microsoft.com/playwright:v1.57.0-noble
64+
image: mcr.microsoft.com/playwright:v1.58.0-noble
6565

6666
steps:
67-
- uses: actions/checkout@v6
67+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
6868
- run: corepack enable
69-
- uses: actions/setup-node@v6
69+
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
7070
with:
7171
node-version: lts/*
7272
cache: pnpm
@@ -81,9 +81,9 @@ jobs:
8181
runs-on: ubuntu-latest
8282

8383
steps:
84-
- uses: actions/checkout@v6
84+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
8585
- run: corepack enable
86-
- uses: actions/setup-node@v6
86+
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
8787
with:
8888
node-version: lts/*
8989
cache: pnpm
@@ -103,9 +103,9 @@ jobs:
103103
runs-on: ubuntu-latest
104104

105105
steps:
106-
- uses: actions/checkout@v6
106+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
107107
- run: corepack enable
108-
- uses: actions/setup-node@v6
108+
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
109109
with:
110110
node-version: lts/*
111111
cache: pnpm

.github/workflows/lunaria.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ jobs:
2222

2323
steps:
2424
- name: Checkout
25-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
25+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
2626
with:
2727
# Necessary for Lunaria to work properly
2828
# Makes the action clone the entire git history
2929
fetch-depth: 0
3030

3131
- run: corepack enable
32-
- uses: actions/setup-node@v6
32+
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
3333
with:
3434
node-version: lts/*
3535
cache: pnpm

.github/workflows/provenance.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
check-provenance:
1717
runs-on: ubuntu-latest
1818
steps:
19-
- uses: actions/checkout@v6
19+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
2020
with:
2121
fetch-depth: 0
2222
- name: Check provenance downgrades

.github/workflows/semantic-pull-requests.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ jobs:
2323
uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
2424
with:
2525
scopes: |
26+
a11y
27+
deps
2628
docs
2729
i18n
28-
deps
30+
ui
2931
subjectPattern: ^(?![A-Z]).+$
3032
subjectPatternError: |
3133
The subject "{subject}" found in the pull request title "{title}"

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,6 @@ test-results/
3636

3737
# Lighthouse
3838
.lighthouseci
39+
40+
# generated files
41+
shared/types/lexicons

CONTRIBUTING.md

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,18 @@ import { hasProtocol } from 'ufo'
189189
| Constants | SCREAMING_SNAKE_CASE | `NPM_REGISTRY`, `ALLOWED_TAGS` |
190190
| Types/Interfaces | PascalCase | `NpmSearchResponse` |
191191

192+
> [!TIP]
193+
> Exports in `app/composables/`, `app/utils/`, and `server/utils/` are auto-imported by Nuxt. To prevent [knip](https://knip.dev/) from flagging them as unused, add a `@public` JSDoc annotation:
194+
>
195+
> ```typescript
196+
> /**
197+
> * @public
198+
> */
199+
> export function myAutoImportedFunction() {
200+
> // ...
201+
> }
202+
> ```
203+
192204
### Vue components
193205
194206
- Use Composition API with `<script setup lang="ts">`
@@ -208,20 +220,85 @@ const props = defineProps<{
208220
209221
Ideally, extract utilities into separate files so they can be unit tested. 🙏
210222

223+
## RTL Support
224+
225+
We support `right-to-left` languages, we need to make sure that the UI is working correctly in both directions.
226+
227+
Simple approach used by most websites of relying on direction set in HTML element does not work because direction for various items, such as timeline, does not always match direction set in HTML.
228+
229+
We've added some `UnoCSS` utilities styles to help you with that:
230+
231+
- Do not use `left/right` padding and margin: for example `pl-1`. Use `padding-inline-start/end` instead. So `pl-1` should be `ps-1`, `pr-1` should be `pe-1`. The same rules apply to margin.
232+
- Do not use `rtl-` classes, such as `rtl-left-0`.
233+
- For icons that should be rotated for RTL, add `class="rtl-flip"`. This can only be used for icons outside of elements with `dir="auto"`.
234+
- For absolute positioned elements, don't use `left/right`: for example `left-0`. Use `inset-inline-start/end` instead. `UnoCSS` shortcuts are `inset-is` for `inset-inline-start` and `inset-ie` for `inset-inline-end`. Example: `left-0` should be replaced with `inset-is-0`.
235+
- If you need to change the border radius for an entire left or right side, use `border-inline-start/end`. `UnoCSS` shortcuts are `rounded-is` for left side, `rounded-ie` for right side. Example: `rounded-l-5` should be replaced with `rounded-is-5`.
236+
- If you need to change the border radius for one corner, use `border-start-end-radius` and similar rules. `UnoCSS` shortcuts are `rounded` + top/bottom as either `-bs` (top) or `-be` (bottom) + left/right as either `-is` (left) or `-ie` (right). Example: `rounded-tl-0` should be replaced with `rounded-bs-is-0`.
237+
211238
## Localization (i18n)
212239

213240
npmx.dev uses [@nuxtjs/i18n](https://i18n.nuxtjs.org/) for internationalization. We aim to make the UI accessible to users in their preferred language.
214241

215242
### Approach
216243

217244
- All user-facing strings should use translation keys via `$t()` in templates and 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)
245+
- Translation files live in [`i18n/locales/`](i18n/locales) (e.g., `en-US.json`)
246+
- We use the `no_prefix` strategy (no `/en-US/` or `/fr-FR/` in URLs)
220247
- Locale preference is stored in cookies and respected on subsequent visits
221248

249+
### Adding a new locale
250+
251+
We are using localization using country variants (ISO-6391) via [multiple translation files](https://i18n.nuxtjs.org/docs/guide/lazy-load-translations#multiple-files-lazy-loading) to avoid repeating every key per country.
252+
253+
The [config/i18n.ts](./config/i18n.ts) configuration file will be used to register the new locale:
254+
255+
- `countryLocaleVariants` object will be used to register the country variants
256+
- `locales` object will be used to link the supported locales (country and single one)
257+
- `buildLocales` function will build the target locales
258+
259+
To add a new locale:
260+
261+
1. Create a new JSON file in [`i18n/locales/`](./i18n/locales) with the locale code as the filename (e.g., `uk-UA.json`, `de-DE.json`)
262+
2. Copy [`en.json`](./i18n/locales/en.json) and translate the strings
263+
3. Add the locale to the `locales` array in [config/i18n.ts](./config/i18n.ts):
264+
265+
```typescript
266+
{
267+
code: 'uk-UA', // Must match the filename (without .json)
268+
file: 'uk-UA.json',
269+
name: 'Українська', // Native name of the language
270+
},
271+
```
272+
273+
4. Copy your translation file to `lunaria/files/` for translation tracking:
274+
275+
```bash
276+
cp i18n/locales/uk-UA.json lunaria/files/uk-UA.json
277+
```
278+
279+
> [!IMPORTANT]
280+
> This file must be committed. Lunaria uses git history to track translation progress, so the build will fail if this file is missing.
281+
282+
5. If the language is `right-to-left`, add `dir: 'rtl'` (see `ar-EG` in config for example)
283+
6. If the language requires special pluralization rules, add a `pluralRule` callback (see `ar-EG` or `ru-RU` in config for examples)
284+
285+
Check [Pluralization rule callback](https://vue-i18n.intlify.dev/guide/essentials/pluralization.html#custom-pluralization) for more info.
286+
287+
#### Country variants (advanced)
288+
289+
Most languages only need a single locale file. Country variants are only needed when you want to support regional differences (e.g., `es-ES` for Spain vs `es-419` for Latin America).
290+
291+
If you need country variants:
292+
293+
1. Create a base language file (e.g., `es.json`) with all translations
294+
2. Create country variant files (e.g., `es-ES.json`, `es-419.json`) with only the differing translations
295+
3. Register the base language in `locales` and add variants to `countryLocaleVariants`
296+
297+
See how `es`, `es-ES`, and `es-419` are configured in [config/i18n.ts](./config/i18n.ts) for a complete example.
298+
222299
### Adding translations
223300

224-
1. Add your translation key to `i18n/locales/en.json` first (English is the source of truth)
301+
1. Add your translation key to `i18n/locales/en.json` first (American English is the source of truth)
225302
2. Use the key in your component:
226303

227304
```vue
@@ -267,22 +344,6 @@ We recommend the [i18n-ally](https://marketplace.visualstudio.com/items?itemName
267344

268345
The extension is included in our workspace recommendations, so VSCode should prompt you to install it.
269346

270-
### Adding a new locale
271-
272-
1. Create a new JSON file in `i18n/locales/` (e.g., `fr.json`)
273-
2. Add the locale to `nuxt.config.ts`:
274-
275-
```typescript
276-
i18n: {
277-
locales: [
278-
{ code: 'en', language: 'en-US', name: 'English', file: 'en.json' },
279-
{ code: 'fr', language: 'fr-FR', name: 'Francais', file: 'fr.json' },
280-
],
281-
}
282-
```
283-
284-
3. Translate all keys from `en.json`
285-
286347
### Formatting with locale
287348

288349
When formatting numbers or dates that should respect the user's locale, pass the locale:

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
<p align="center">
66
<a href="https://npmx.dev/">
7-
<img width="1090" alt="Screenshot of npmx.dev showing the nuxt package" src="https://github.com/user-attachments/assets/229497a2-8491-461c-aa1d-fba981215340">
7+
<img width="1090" alt="Screenshot of npmx.dev showing the nuxt package" src="https://github.com/user-attachments/assets/1a2a3205-0227-46dc-b1f9-48f9a65691d3">
88
</a>
99
</p>
1010

app/app.vue

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,38 @@
11
<script setup lang="ts">
2+
import type { Directions } from '@nuxtjs/i18n'
23
import { useEventListener } from '@vueuse/core'
34
45
const route = useRoute()
56
const router = useRouter()
7+
const { locale, locales } = useI18n()
68
7-
// Initialize accent color before hydration to prevent flash
8-
initAccentOnPrehydrate()
9+
// Initialize user preferences (accent color, package manager) before hydration to prevent flash/CLS
10+
initPreferencesOnPrehydrate()
911
1012
const isHomepage = computed(() => route.name === 'index')
1113
14+
const localeMap = locales.value.reduce(
15+
(acc, l) => {
16+
acc[l.code] = l.dir ?? 'ltr'
17+
return acc
18+
},
19+
{} as Record<string, Directions>,
20+
)
21+
1222
useHead({
23+
htmlAttrs: {
24+
lang: () => locale.value,
25+
dir: () => localeMap[locale.value] ?? 'ltr',
26+
},
1327
titleTemplate: titleChunk => {
1428
return titleChunk ? titleChunk : 'npmx - Better npm Package Browser'
1529
},
1630
})
1731
32+
if (import.meta.server) {
33+
setJsonLd(createWebSiteSchema())
34+
}
35+
1836
// Global keyboard shortcut: "/" focuses search or navigates to search page
1937
function handleGlobalKeydown(e: KeyboardEvent) {
2038
const target = e.target as HTMLElement

0 commit comments

Comments
 (0)