Skip to content

Commit d4da810

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/org-packages-fancy
2 parents 4c778dd + 6a5d286 commit d4da810

29 files changed

Lines changed: 289 additions & 132 deletions

.github/workflows/autofix.yml

Lines changed: 2 additions & 2 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'

.github/workflows/ci.yml

Lines changed: 10 additions & 10 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
@@ -64,9 +64,9 @@ jobs:
6464
image: mcr.microsoft.com/playwright:v1.57.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

CONTRIBUTING.md

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -220,20 +220,70 @@ const props = defineProps<{
220220
221221
Ideally, extract utilities into separate files so they can be unit tested. 🙏
222222

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-ie-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+
223238
## Localization (i18n)
224239

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

227242
### Approach
228243

229244
- All user-facing strings should use translation keys via `$t()` in templates and script
230-
- Translation files live in `i18n/locales/` (e.g., `en.json`)
231-
- We use the `no_prefix` strategy (no `/en/` or `/fr/` in URLs)
245+
- Translation files live in `i18n/locales/` (e.g., `en-US.json`)
246+
- We use the `no_prefix` strategy (no `/en-US/` or `/fr-FR/` in URLs)
232247
- Locale preference is stored in cookies and respected on subsequent visits
233248

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 register a new locale:
260+
261+
- for a single country, your JSON file should include the language and the country in the name (for example, `pl-PL.json`) and register the info at `locales` object
262+
- for multiple country variants, you need to add the default language JSON file (for example for Spanish, `es.json`) and one of the country variants (for example for Spanish for Spain, `es-ES.json`); register the language at `countryLocaleVariants` object adding the country variants with the JSON country file and register the language at `locales` object using the language JSON file (check how we register `es`, `es-ES` and `es-419` in [config/i18n.ts](./config/i18n.ts))
263+
264+
The country file should contain will contain only the translations that differ from the language JSON file, Vue I18n will merge the messages for us.
265+
266+
To add a new locale:
267+
268+
1. Add a new file at [locales](./i18n/locales) folder with the language code as the filename.
269+
2. Copy [en](./i18n/locales/en.json) and translate the strings
270+
3. Add the language to the `locales` array in [config/i18n.ts](./config/i18n.ts), below `en` and `ar`:
271+
- If your language has multiple country variants, add the generic one for language only (only if there are a lot of common entries, you can always add it as a new one)
272+
- Add all country variants in [country variants object](./config/i18n.ts)
273+
- Add all country variants files with empty `messages` object: `{}`
274+
- Translate the strings in the generic language file
275+
- Later, when anyone wants to add the corresponding translations for the country variant, just override any entry in the corresponding file: you can see an example with `es` variants.
276+
- If the generic language already exists:
277+
- If the translation doesn't differ from the generic language, then add the corresponding translations in the corresponding file
278+
- If the translation differs from the generic language, then add the corresponding translations in the corresponding file and remove it from the country variants entry
279+
4. If the language is `right-to-left`, add `dir` option with `rtl` value, for example, for [ar](./config/i18n.ts)
280+
5. If the language requires special pluralization rules, add `pluralRule` callback option, for example, for [ar](./config/i18n.ts)
281+
282+
Check [Pluralization rule callback](https://vue-i18n.intlify.dev/guide/essentials/pluralization.html#custom-pluralization) for more info.
283+
234284
### Adding translations
235285

236-
1. Add your translation key to `i18n/locales/en.json` first (English is the source of truth)
286+
1. Add your translation key to `i18n/locales/en.json` first (American English is the source of truth)
237287
2. Use the key in your component:
238288

239289
```vue
@@ -279,22 +329,6 @@ We recommend the [i18n-ally](https://marketplace.visualstudio.com/items?itemName
279329

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

282-
### Adding a new locale
283-
284-
1. Create a new JSON file in `i18n/locales/` (e.g., `fr.json`)
285-
2. Add the locale to `nuxt.config.ts`:
286-
287-
```typescript
288-
i18n: {
289-
locales: [
290-
{ code: 'en', language: 'en-US', name: 'English', file: 'en.json' },
291-
{ code: 'fr', language: 'fr-FR', name: 'Francais', file: 'fr.json' },
292-
],
293-
}
294-
```
295-
296-
3. Translate all keys from `en.json`
297-
298332
### Formatting with locale
299333

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

app/assets/main.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ html {
8080
}
8181
}
8282

83+
html[dir='rtl'] .rtl-flip {
84+
transform: scale(-1, 1);
85+
}
86+
8387
body {
8488
margin: 0;
8589
background-color: var(--bg);

app/components/AppFooter.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<footer class="border-t border-border mt-auto" :aria-label="$t('footer.site_footer')">
2+
<footer class="border-t border-border mt-auto">
33
<div class="container py-3 sm:py-8 flex flex-col gap-2 sm:gap-4 text-fg-subtle text-sm">
44
<div class="flex flex-col sm:flex-row items-center justify-between gap-2 sm:gap-4">
55
<p class="font-mono m-0 hidden sm:block">{{ $t('tagline') }}</p>

app/components/AppHeader.vue

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ const debouncedNavigate = debounce(async () => {
3030
name: 'search',
3131
query: query ? { q: query } : undefined,
3232
})
33-
searchQuery.value = ''
33+
// allow time for the navigation to occur before resetting searchQuery
34+
setTimeout(() => (searchQuery.value = ''), 1000)
3435
}, 100)
3536
3637
async function handleSearchInput() {
@@ -50,10 +51,7 @@ onKeyStroke(',', e => {
5051
</script>
5152

5253
<template>
53-
<header
54-
:aria-label="$t('header.site_header')"
55-
class="sticky top-0 z-50 bg-bg/80 backdrop-blur-md border-b border-border"
56-
>
54+
<header class="sticky top-0 z-50 bg-bg/80 backdrop-blur-md border-b border-border">
5755
<nav :aria-label="$t('nav.main_navigation')" class="container h-14 flex items-center">
5856
<!-- Left: Logo -->
5957
<div class="flex-shrink-0">
@@ -73,13 +71,7 @@ onKeyStroke(',', e => {
7371
<div class="flex-1 flex items-center justify-center gap-4 sm:gap-6">
7472
<!-- Search bar (shown on all pages except home and search) -->
7573
<search v-if="showSearchBar" class="hidden sm:block flex-1 max-w-md">
76-
<form
77-
role="search"
78-
method="GET"
79-
action="/search"
80-
class="relative"
81-
@submit.prevent="handleSearchInput"
82-
>
74+
<form method="GET" action="/search" class="relative" @submit.prevent="handleSearchInput">
8375
<label for="header-search" class="sr-only">
8476
{{ $t('search.label') }}
8577
</label>

app/components/PackageMaintainers.vue

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,25 @@ const {
1818
const showAddOwner = shallowRef(false)
1919
const newOwnerUsername = shallowRef('')
2020
const isAdding = shallowRef(false)
21+
const showAllMaintainers = shallowRef(false)
22+
23+
const DEFAULT_VISIBLE_MAINTAINERS = 5
2124
2225
// Show admin controls when connected (let npm CLI handle permission errors)
2326
const canManageOwners = computed(() => isConnected.value)
2427
28+
// Computed for visible maintainers with show more/fewer support
29+
const visibleMaintainers = computed(() => {
30+
if (canManageOwners.value || showAllMaintainers.value) {
31+
return maintainerAccess.value
32+
}
33+
return maintainerAccess.value.slice(0, DEFAULT_VISIBLE_MAINTAINERS)
34+
})
35+
36+
const hiddenMaintainersCount = computed(() =>
37+
Math.max(0, maintainerAccess.value.length - DEFAULT_VISIBLE_MAINTAINERS),
38+
)
39+
2540
// Extract org name from scoped package
2641
const orgName = computed(() => {
2742
if (!props.packageName.startsWith('@')) return null
@@ -173,14 +188,17 @@ watch(
173188
</h2>
174189
<ul class="space-y-2 list-none m-0 p-0" :aria-label="$t('package.maintainers.list_label')">
175190
<li
176-
v-for="maintainer in maintainerAccess.slice(0, canManageOwners ? undefined : 5)"
191+
v-for="maintainer in visibleMaintainers"
177192
:key="maintainer.name ?? maintainer.email"
178193
class="flex items-center justify-between gap-2"
179194
>
180195
<div class="flex items-center gap-2 min-w-0">
181196
<NuxtLink
182197
v-if="maintainer.name"
183-
:to="{ name: '~username', params: { username: maintainer.name } }"
198+
:to="{
199+
name: '~username',
200+
params: { username: maintainer.name },
201+
}"
184202
class="link-subtle font-mono text-sm shrink-0"
185203
>
186204
@{{ maintainer.name }}
@@ -192,7 +210,11 @@ watch(
192210
v-if="isConnected && maintainer.accessVia?.length && !isLoadingAccess"
193211
class="text-xs text-fg-subtle truncate"
194212
>
195-
{{ $t('package.maintainers.via', { teams: maintainer.accessVia.join(', ') }) }}
213+
{{
214+
$t('package.maintainers.via', {
215+
teams: maintainer.accessVia.join(', '),
216+
})
217+
}}
196218
</span>
197219
<span
198220
v-if="canManageOwners && maintainer.name === npmUser"
@@ -206,14 +228,34 @@ watch(
206228
v-if="canManageOwners && maintainer.name && maintainer.name !== npmUser"
207229
type="button"
208230
class="p-1 text-fg-subtle hover:text-red-400 transition-colors duration-200 shrink-0 rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
209-
:aria-label="$t('package.maintainers.remove_owner', { name: maintainer.name })"
231+
:aria-label="
232+
$t('package.maintainers.remove_owner', {
233+
name: maintainer.name,
234+
})
235+
"
210236
@click="handleRemoveOwner(maintainer.name)"
211237
>
212238
<span class="i-carbon-close block w-3.5 h-3.5" aria-hidden="true" />
213239
</button>
214240
</li>
215241
</ul>
216242

243+
<!-- Show more/less toggle (only when not managing and there are hidden maintainers) -->
244+
<button
245+
v-if="!canManageOwners && hiddenMaintainersCount > 0"
246+
type="button"
247+
class="mt-2 text-xs text-fg-muted hover:text-fg transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 rounded"
248+
@click="showAllMaintainers = !showAllMaintainers"
249+
>
250+
{{
251+
showAllMaintainers
252+
? $t('package.maintainers.show_less')
253+
: $t('package.maintainers.show_more', {
254+
count: hiddenMaintainersCount,
255+
})
256+
}}
257+
</button>
258+
217259
<!-- Add owner form (only when can manage) -->
218260
<div v-if="canManageOwners" class="mt-3">
219261
<div v-if="showAddOwner">

0 commit comments

Comments
 (0)