Skip to content

Commit 48c905a

Browse files
committed
Merge branch 'main' into feat/like-comparaison
2 parents 2e5d511 + 2273d3b commit 48c905a

File tree

124 files changed

+6493
-987
lines changed

Some content is hidden

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

124 files changed

+6493
-987
lines changed

.github/workflows/ci.yml

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

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ jobs:
2626
a11y
2727
deps
2828
docs
29+
cli
2930
i18n
3031
ui
3132
subjectPattern: ^(?![A-Z]).+$

.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/**",

CONTRIBUTING.md

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,12 +340,12 @@ To add a new locale:
340340
5. If the language is `right-to-left`, add `dir: 'rtl'` (see `ar-EG` in config for example)
341341
6. If the language requires special pluralization rules, add a `pluralRule` callback (see `ar-EG` or `ru-RU` in config for examples)
342342

343-
Check [Pluralization rule callback](https://vue-i18n.intlify.dev/guide/essentials/pluralization.html#custom-pluralization) for more info.
343+
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.
344344

345345
### Update translation
346346

347347
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.
348+
If you see any outdated translations in your language, feel free to update the keys to match the English version.
349349

350350
In order to make sure you have everything up-to-date, you can run:
351351

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

411+
4. Don't concatenate string messages in the Vue templates, some languages can have different word order. Use placeholders instead.
412+
413+
**Bad:**
414+
415+
```vue
416+
<p>{{ $t('hello') }} {{ userName }}</p>
417+
```
418+
419+
**Good:**
420+
421+
```vue
422+
<p>{{ $t('greeting', { name: userName }) }}</p>
423+
```
424+
425+
**Complex content:**
426+
427+
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.
428+
429+
```json
430+
{
431+
"agreement": "I accept the {terms} and {privacy}.",
432+
"terms_link": "Terms of Service",
433+
"privacy_policy": "Privacy Policy"
434+
}
435+
```
436+
437+
```vue
438+
<i18n-t keypath="agreement" tag="p">
439+
<template #terms>
440+
<NuxtLink to="/terms">{{ $t('terms_link') }}</NuxtLink>
441+
</template>
442+
<template #privacy>
443+
<strong>{{ $t('privacy_policy') }}</strong>
444+
</template>
445+
</i18n-t>
446+
```
447+
411448
### Translation key conventions
412449

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

419457
### Using i18n-ally (recommended)
420458

app/app.vue

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup lang="ts">
22
import type { Directions } from '@nuxtjs/i18n'
3-
import { useEventListener } from '@vueuse/core'
3+
import { useEventListener, onKeyDown, onKeyUp } from '@vueuse/core'
44
import { isEditableElement } from '~/utils/input'
55
66
const route = useRoute()
@@ -47,16 +47,12 @@ if (import.meta.server) {
4747
setJsonLd(createWebSiteSchema())
4848
}
4949
50-
// Global keyboard shortcut:
51-
// "/" focuses search or navigates to search page
52-
// "?" highlights all keyboard shortcut elements
53-
function handleGlobalKeydown(e: KeyboardEvent) {
54-
if (isEditableElement(e.target)) return
55-
56-
if (isKeyWithoutModifiers(e, '/')) {
50+
onKeyDown(
51+
'/',
52+
e => {
53+
if (isEditableElement(e.target)) return
5754
e.preventDefault()
5855
59-
// Try to find and focus search input on current page
6056
const searchInput = document.querySelector<HTMLInputElement>(
6157
'input[type="search"], input[name="q"]',
6258
)
@@ -67,18 +63,29 @@ function handleGlobalKeydown(e: KeyboardEvent) {
6763
}
6864
6965
router.push('/search')
70-
}
66+
},
67+
{ dedupe: true },
68+
)
7169
72-
// For "?" we check the key property directly since it's usually combined with shift
73-
if (e.key === '?') {
70+
onKeyDown(
71+
'?',
72+
e => {
73+
if (isEditableElement(e.target)) return
7474
e.preventDefault()
7575
showKbdHints.value = true
76-
}
77-
}
76+
},
77+
{ dedupe: true },
78+
)
7879
79-
function handleGlobalKeyup() {
80-
showKbdHints.value = false
81-
}
80+
onKeyUp(
81+
'?',
82+
e => {
83+
if (isEditableElement(e.target)) return
84+
e.preventDefault()
85+
showKbdHints.value = false
86+
},
87+
{ dedupe: true },
88+
)
8289
8390
// Light dismiss fallback for browsers that don't support closedby="any" (Safari + old Chrome/Firefox)
8491
// https://codepen.io/paramagicdev/pen/gbYompq
@@ -99,9 +106,6 @@ function handleModalLightDismiss(e: MouseEvent) {
99106
}
100107
101108
if (import.meta.client) {
102-
useEventListener(document, 'keydown', handleGlobalKeydown)
103-
useEventListener(document, 'keyup', handleGlobalKeyup)
104-
105109
// Feature check for native light dismiss support via closedby="any"
106110
// https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog#closedby
107111
const supportsClosedBy =

app/assets/main.css

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ html {
155155
-moz-osx-font-smoothing: grayscale;
156156
text-rendering: optimizeLegibility;
157157
scroll-padding-top: 5rem; /* Offset for fixed header - otherwise anchor headers are cutted */
158+
scrollbar-gutter: stable;
158159
}
159160

160161
/*
@@ -229,6 +230,10 @@ select:focus-visible {
229230
}
230231

231232
/* Scrollbar styling */
233+
* {
234+
scrollbar-color: var(--border) var(--bg);
235+
}
236+
232237
::-webkit-scrollbar {
233238
width: 8px;
234239
height: 8px;
@@ -244,14 +249,13 @@ select:focus-visible {
244249
}
245250

246251
::-webkit-scrollbar-thumb:hover {
247-
background: #404040;
252+
background: var(--border-hover);
248253
}
249254

250255
/* Scrollbar styling for Firefox */
251256
@supports not selector(::-webkit-scrollbar) {
252257
* {
253258
scrollbar-width: thin;
254-
scrollbar-color: var(--border) var(--bg);
255259
}
256260
}
257261

app/components/AppFooter.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ const isHome = computed(() => route.name === 'index')
1818
<NuxtLink to="/about" class="link-subtle font-mono text-xs flex items-center">
1919
{{ $t('footer.about') }}
2020
</NuxtLink>
21+
<NuxtLink
22+
to="/privacy"
23+
class="link-subtle font-mono text-xs min-h-11 flex items-center gap-1 lowercase"
24+
>
25+
{{ $t('privacy_policy.title') }}
26+
</NuxtLink>
2127
<a
2228
href="https://docs.npmx.dev"
2329
target="_blank"

app/components/AppHeader.vue

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ onKeyStroke(
109109
to="/"
110110
:aria-label="$t('header.home')"
111111
dir="ltr"
112-
class="inline-flex items-center gap-2 header-logo font-mono text-lg font-medium text-fg hover:text-fg transition-colors duration-200 rounded"
112+
class="inline-flex items-center gap-1 header-logo font-mono text-lg font-medium text-fg hover:text-fg/90 transition-colors duration-200 rounded"
113113
>
114114
<AppLogo class="w-8 h-8 rounded-lg" />
115115
<span>npmx</span>
@@ -185,19 +185,14 @@ onKeyStroke(
185185
<HeaderAccountMenu />
186186
</div>
187187

188-
<!-- Mobile: Menu button (always visible, toggles menu) -->
188+
<!-- Mobile: Menu button (always visible, click to open menu) -->
189189
<button
190190
type="button"
191191
class="sm:hidden flex items-center p-2 -m-2 text-fg-subtle hover:text-fg transition-colors duration-200 focus-visible:outline-accent/70 rounded"
192-
:aria-label="showMobileMenu ? $t('common.close') : $t('nav.open_menu')"
193-
:aria-expanded="showMobileMenu"
194-
@click="showMobileMenu = !showMobileMenu"
192+
:aria-label="$t('nav.open_menu')"
193+
@click="showMobileMenu = true"
195194
>
196-
<span
197-
class="w-6 h-6 inline-block"
198-
:class="showMobileMenu ? 'i-carbon:close' : 'i-carbon:menu'"
199-
aria-hidden="true"
200-
/>
195+
<span class="w-6 h-6 inline-block i-carbon:menu" aria-hidden="true" />
201196
</button>
202197
</div>
203198
</nav>

app/components/AppLogo.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ defineProps<{
1515
>
1616
<title>{{ $t('alt_logo') }}</title>
1717
<rect fill="var(--bg)" width="512" height="512" rx="64" />
18-
<rect fill="var(--fg)" x="110" y="310" width="60" height="60" />
18+
<rect fill="currentColor" x="110" y="310" width="60" height="60" />
1919
<text
2020
fill="var(--accent)"
2121
x="320"

app/components/CollapsibleSection.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,10 @@ useHead({
125125

126126
<div
127127
:id="contentId"
128-
class="grid ms-6 transition-[grid-template-rows] duration-200 ease-in-out collapsible-content overflow-hidden"
128+
class="grid ms-6 grid-rows-[1fr] transition-[grid-template-rows] duration-200 ease-in-out collapsible-content overflow-hidden"
129129
:inert="!isOpen"
130130
>
131-
<div class="min-h-0 min-w-0 p-1">
131+
<div class="min-h-0 min-w-0">
132132
<slot />
133133
</div>
134134
</div>

0 commit comments

Comments
 (0)