Skip to content

Commit ceb83f6

Browse files
authored
Merge branch 'main' into chore/fix-auth-error-message
2 parents 5215979 + 4ca425f commit ceb83f6

File tree

146 files changed

+7078
-1163
lines changed

Some content is hidden

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

146 files changed

+7078
-1163
lines changed

.github/workflows/autofix.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ jobs:
3232
- name: 📦 Install dependencies
3333
run: pnpm install
3434

35+
- name: 🎨 Check for non-RTL CSS classes
36+
run: pnpm rtl:check
37+
3538
- name: 🌐 Compare translations
3639
run: pnpm i18n:check
3740

.github/workflows/ci.yml

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,14 @@ jobs:
3333

3434
- uses: pnpm/action-setup@1e1c8eafbd745f64b1ef30a7d7ed7965034c486c # 1e1c8eafbd745f64b1ef30a7d7ed7965034c486c
3535
name: 🟧 Install pnpm
36-
# pnpm cache skipped deliberately as the project is not actually installed here
36+
with:
37+
cache: true
38+
39+
- name: 📦 Install dependencies (root only, no scripts)
40+
run: pnpm install --filter . --ignore-scripts
3741

3842
- name: 🔠 Lint project
39-
run: node scripts/lint.ts
43+
run: pnpm lint
4044

4145
types:
4246
name: 💪 Type check
@@ -146,7 +150,9 @@ jobs:
146150
run: pnpm install
147151

148152
- name: 🏗️ Build project
149-
run: pnpm build:playwright
153+
run: pnpm build:test
154+
env:
155+
VALIDATE_HTML: true
150156

151157
- name: 🖥️ Test project (browser)
152158
run: pnpm test:browser:prebuilt
@@ -174,7 +180,7 @@ jobs:
174180
run: pnpm install
175181

176182
- name: 🏗️ Build project
177-
run: NODE_ENV=test pnpm build
183+
run: pnpm build:test
178184

179185
- name: ♿ Accessibility audit (Lighthouse - ${{ matrix.mode }} mode)
180186
run: ./scripts/lighthouse-a11y.sh
@@ -206,3 +212,25 @@ jobs:
206212

207213
- name: 🧹 Check for unused production code
208214
run: pnpm knip --production
215+
216+
i18n:
217+
name: 🌐 i18n validation
218+
runs-on: ubuntu-24.04-arm
219+
220+
steps:
221+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
222+
223+
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
224+
with:
225+
node-version: lts/*
226+
227+
- uses: pnpm/action-setup@1e1c8eafbd745f64b1ef30a7d7ed7965034c486c # 1e1c8eafbd745f64b1ef30a7d7ed7965034c486c
228+
name: 🟧 Install pnpm
229+
with:
230+
cache: true
231+
232+
- name: 📦 Install dependencies (root only, no scripts)
233+
run: pnpm install --filter . --ignore-scripts
234+
235+
- name: 🌐 Check for missing or dynamic i18n keys
236+
run: pnpm i18n:report

.github/workflows/welcome.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: welcome
2+
3+
on:
4+
pull_request_target:
5+
types:
6+
- closed
7+
8+
permissions: {}
9+
10+
jobs:
11+
welcome:
12+
permissions:
13+
pull-requests: write # to comment on PRs
14+
if: github.repository == 'npmx-dev/npmx.dev' && github.event.pull_request.merged == true
15+
runs-on: ubuntu-slim
16+
name: 🎉 Welcome new contributor
17+
steps:
18+
- name: 🎉 Welcome new contributor
19+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
20+
with:
21+
script: |
22+
const pr = context.payload.pull_request;
23+
const author = pr.user.login;
24+
25+
// Check if this is the author's first merged PR
26+
const { data: prs } = await github.rest.search.issuesAndPullRequests({
27+
q: `repo:${context.repo.owner}/${context.repo.repo} type:pr is:merged author:${author}`,
28+
});
29+
30+
// If the only merged PR is this one, it's their first contribution
31+
if (prs.total_count !== 1) {
32+
console.log(`@${author} already has ${prs.total_count} merged PRs — skipping welcome comment.`);
33+
return;
34+
}
35+
36+
const emojis = ['🎉', '🥳', '🎊', '🚀', '⭐', '💫', '✨', '💪', '👏', '🙌', '🤩', '💥'];
37+
const emoji = emojis[Math.floor(Math.random() * emojis.length)];
38+
39+
const body = [
40+
`Thanks for your first contribution, @${author}! ${emoji}`,
41+
'',
42+
`We'd love to welcome you to the npmx community. Come and say hi on [Discord](https://chat.npmx.dev)! And once you've joined, visit [npmx.wamellow.com](https://npmx.wamellow.com/) to claim the **contributor** role.`,
43+
].join('\n');
44+
45+
await github.rest.issues.createComment({
46+
owner: context.repo.owner,
47+
repo: context.repo.repo,
48+
issue_number: pr.number,
49+
body,
50+
});
51+
52+
console.log(`Welcomed new contributor @${author} on PR #${pr.number}`);

.gitignore

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

4040
# generated files
4141
shared/types/lexicons
42+
43+
# output
44+
.vercel

.oxlintrc.json

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"$schema": "https://unpkg.com/oxlint/configuration_schema.json",
33
"plugins": ["unicorn", "typescript", "oxc", "vue", "vitest"],
4+
"jsPlugins": ["@e18e/eslint-plugin"],
45
"categories": {
56
"correctness": "error",
67
"suspicious": "warn",
@@ -11,8 +12,27 @@
1112
"no-await-in-loop": "off",
1213
"unicorn/no-array-sort": "off",
1314
"no-restricted-globals": "error",
14-
"typescript/consistent-type-imports": "error"
15+
"typescript/consistent-type-imports": "error",
16+
"e18e/prefer-array-from-map": "error",
17+
"e18e/prefer-timer-args": "error",
18+
"e18e/prefer-date-now": "error",
19+
"e18e/prefer-regex-test": "error",
20+
"e18e/prefer-array-some": "error"
1521
},
22+
"overrides": [
23+
{
24+
"files": [
25+
"server/**/*",
26+
"cli/**/*",
27+
"scripts/**/*",
28+
"modules/**/*",
29+
"app/components/OgImage/*"
30+
],
31+
"rules": {
32+
"no-console": "off"
33+
}
34+
}
35+
],
1636
"ignorePatterns": [
1737
".output/**",
1838
".data/**",

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
22
"i18n-ally.localesPaths": ["./i18n/locales"],
3-
"i18n-ally.keystyle": "nested"
3+
"i18n-ally.keystyle": "nested",
4+
"typescript.tsdk": "node_modules/typescript/lib"
45
}

CONTRIBUTING.md

Lines changed: 115 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ This focus helps guide our project decisions as a community and what we choose t
3939
- [Import order](#import-order)
4040
- [Naming conventions](#naming-conventions)
4141
- [Vue components](#vue-components)
42+
- [Internal linking](#internal-linking)
4243
- [RTL Support](#rtl-support)
4344
- [Localization (i18n)](#localization-i18n)
4445
- [Approach](#approach)
@@ -278,6 +279,79 @@ const props = defineProps<{
278279
279280
Ideally, extract utilities into separate files so they can be unit tested. 🙏
280281

282+
### Internal linking
283+
284+
Always use **object syntax with named routes** for internal navigation. This makes links resilient to URL structure changes and provides type safety via `unplugin-vue-router`.
285+
286+
```vue
287+
<!-- Good: named route -->
288+
<NuxtLink :to="{ name: 'settings' }">Settings</NuxtLink>
289+
290+
<!-- Bad: string path -->
291+
<NuxtLink to="/settings">Settings</NuxtLink>
292+
```
293+
294+
The same applies to programmatic navigation:
295+
296+
```typescript
297+
// Good
298+
navigateTo({ name: 'compare' })
299+
router.push({ name: 'search' })
300+
301+
// Bad
302+
navigateTo('/compare')
303+
router.push('/search')
304+
```
305+
306+
For routes with parameters, pass them explicitly:
307+
308+
```vue
309+
<NuxtLink :to="{ name: '~username', params: { username } }">Profile</NuxtLink>
310+
<NuxtLink :to="{ name: 'org', params: { org: orgName } }">Organization</NuxtLink>
311+
```
312+
313+
Query parameters work as expected:
314+
315+
```vue
316+
<NuxtLink :to="{ name: 'compare', query: { packages: pkg.name } }">Compare</NuxtLink>
317+
```
318+
319+
#### Package routes
320+
321+
For package links, use the auto-imported `packageRoute()` utility from `app/utils/router.ts`. It handles scoped/unscoped packages and optional versions:
322+
323+
```vue
324+
<!-- Links to /package/vue -->
325+
<NuxtLink :to="packageRoute('vue')">vue</NuxtLink>
326+
327+
<!-- Links to /package/@nuxt/kit -->
328+
<NuxtLink :to="packageRoute('@nuxt/kit')">@nuxt/kit</NuxtLink>
329+
330+
<!-- Links to /package/vue/v/3.5.0 -->
331+
<NuxtLink :to="packageRoute('vue', '3.5.0')">vue@3.5.0</NuxtLink>
332+
```
333+
334+
> [!IMPORTANT]
335+
> Never construct package URLs as strings. The route structure uses separate `org` and `name` params, and `packageRoute()` handles the splitting correctly.
336+
337+
#### Available route names
338+
339+
| Route name | URL pattern | Parameters |
340+
| ----------------- | --------------------------------- | ------------------------- |
341+
| `index` | `/` | &mdash; |
342+
| `about` | `/about` | &mdash; |
343+
| `compare` | `/compare` | &mdash; |
344+
| `privacy` | `/privacy` | &mdash; |
345+
| `search` | `/search` | &mdash; |
346+
| `settings` | `/settings` | &mdash; |
347+
| `package` | `/package/:org?/:name` | `org?`, `name` |
348+
| `package-version` | `/package/:org?/:name/v/:version` | `org?`, `name`, `version` |
349+
| `code` | `/package-code/:path+` | `path` (array) |
350+
| `docs` | `/package-docs/:path+` | `path` (array) |
351+
| `org` | `/org/:org` | `org` |
352+
| `~username` | `/~:username` | `username` |
353+
| `~username-orgs` | `/~:username/orgs` | `username` |
354+
281355
## RTL Support
282356

283357
We support `right-to-left` languages, we need to make sure that the UI is working correctly in both directions.
@@ -334,18 +408,18 @@ To add a new locale:
334408
cp i18n/locales/uk-UA.json lunaria/files/uk-UA.json
335409
```
336410

337-
> [!IMPORTANT]
411+
> **Important:**
338412
> This file must be committed. Lunaria uses git history to track translation progress, so the build will fail if this file is missing.
339413
340414
5. If the language is `right-to-left`, add `dir: 'rtl'` (see `ar-EG` in config for example)
341415
6. If the language requires special pluralization rules, add a `pluralRule` callback (see `ar-EG` or `ru-RU` in config for examples)
342416

343-
Check [Pluralization rule callback](https://vue-i18n.intlify.dev/guide/essentials/pluralization.html#custom-pluralization) for more info.
417+
Check [Pluralization rule callback](https://vue-i18n.intlify.dev/guide/essentials/pluralization#custom-pluralization) and [Plural Rules](https://cldr.unicode.org/index/cldr-spec/plural-rules#TOC-Determining-Plural-Categories) for more info.
344418

345419
### Update translation
346420

347421
We track the current progress of translations with [Lunaria](https://lunaria.dev/) on this site: https://i18n.npmx.dev/
348-
If you see any outdated translations in your language, feel free to update the keys to match then English version.
422+
If you see any outdated translations in your language, feel free to update the keys to match the English version.
349423

350424
In order to make sure you have everything up-to-date, you can run:
351425

@@ -408,13 +482,51 @@ See how `es`, `es-ES`, and `es-419` are configured in [config/i18n.ts](./config/
408482
<p>{{ $t('greeting', { name: userName }) }}</p>
409483
```
410484

485+
4. Don't concatenate string messages in the Vue templates, some languages can have different word order. Use placeholders instead.
486+
487+
**Bad:**
488+
489+
```vue
490+
<p>{{ $t('hello') }} {{ userName }}</p>
491+
```
492+
493+
**Good:**
494+
495+
```vue
496+
<p>{{ $t('greeting', { name: userName }) }}</p>
497+
```
498+
499+
**Complex content:**
500+
501+
If you need to include HTML or components inside the translation, use [`i18n-t`](https://vue-i18n.intlify.dev/guide/advanced/component.html) component. This is especially useful when the order of elements might change between languages.
502+
503+
```json
504+
{
505+
"agreement": "I accept the {terms} and {privacy}.",
506+
"terms_link": "Terms of Service",
507+
"privacy_policy": "Privacy Policy"
508+
}
509+
```
510+
511+
```vue
512+
<i18n-t keypath="agreement" tag="p">
513+
<template #terms>
514+
<NuxtLink to="/terms">{{ $t('terms_link') }}</NuxtLink>
515+
</template>
516+
<template #privacy>
517+
<strong>{{ $t('privacy_policy') }}</strong>
518+
</template>
519+
</i18n-t>
520+
```
521+
411522
### Translation key conventions
412523

413524
- Use dot notation for hierarchy: `section.subsection.key`
414525
- Keep keys descriptive but concise
415526
- Group related keys together
416527
- Use `common.*` for shared strings (loading, retry, close, etc.)
417528
- Use component-specific prefixes: `package.card.*`, `settings.*`, `nav.*`
529+
- Do not use dashes (`-`) in translation keys; always use underscore (`_`): e.g., `privacy_policy` instead of `privacy-policy`
418530

419531
### Using i18n-ally (recommended)
420532

0 commit comments

Comments
 (0)