Skip to content

Commit 33f8c1b

Browse files
committed
Merge branch 'main' into add-rtl-css-script
# Conflicts: # package.json
2 parents 0256f32 + 88be800 commit 33f8c1b

File tree

15 files changed

+909
-173
lines changed

15 files changed

+909
-173
lines changed

.github/workflows/ci.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,25 @@ jobs:
210210

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

app/components/ColumnPicker.vue

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,24 @@ onKeyDown(
4141
const toggleableColumns = computed(() => props.columns.filter(col => col.id !== 'name'))
4242
4343
// Map column IDs to i18n keys
44-
const columnLabelKey: Record<string, string> = {
45-
name: 'filters.columns.name',
46-
version: 'filters.columns.version',
47-
description: 'filters.columns.description',
48-
downloads: 'filters.columns.downloads',
49-
updated: 'filters.columns.published',
50-
maintainers: 'filters.columns.maintainers',
51-
keywords: 'filters.columns.keywords',
52-
qualityScore: 'filters.columns.quality_score',
53-
popularityScore: 'filters.columns.popularity_score',
54-
maintenanceScore: 'filters.columns.maintenance_score',
55-
combinedScore: 'filters.columns.combined_score',
56-
security: 'filters.columns.security',
57-
}
44+
const columnLabelKey = computed(() => ({
45+
name: $t('filters.columns.name'),
46+
version: $t('filters.columns.version'),
47+
description: $t('filters.columns.description'),
48+
downloads: $t('filters.columns.downloads'),
49+
updated: $t('filters.columns.published'),
50+
maintainers: $t('filters.columns.maintainers'),
51+
keywords: $t('filters.columns.keywords'),
52+
qualityScore: $t('filters.columns.quality_score'),
53+
popularityScore: $t('filters.columns.popularity_score'),
54+
maintenanceScore: $t('filters.columns.maintenance_score'),
55+
combinedScore: $t('filters.columns.combined_score'),
56+
security: $t('filters.columns.security'),
57+
}))
5858
59-
function getColumnLabel(id: string): string {
60-
const key = columnLabelKey[id]
61-
return key ? $t(key) : id
59+
function getColumnLabel(id: ColumnId): string {
60+
const key = columnLabelKey.value[id]
61+
return key ?? id
6262
}
6363
6464
function handleReset() {

app/components/Org/MembersPanel.vue

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
import type { NewOperation } from '~/composables/useConnector'
33
import { buildScopeTeam } from '~/utils/npm/common'
44
5+
type MemberRole = 'developer' | 'admin' | 'owner'
6+
type MemberRoleFilter = MemberRole | 'all'
7+
58
const props = defineProps<{
69
orgName: string
710
}>()
@@ -21,7 +24,7 @@ const {
2124
} = useConnector()
2225
2326
// Members data: { username: role }
24-
const members = shallowRef<Record<string, 'developer' | 'admin' | 'owner'>>({})
27+
const members = shallowRef<Record<string, MemberRole>>({})
2528
const isLoading = shallowRef(false)
2629
const error = shallowRef<string | null>(null)
2730
@@ -31,15 +34,15 @@ const isLoadingTeams = shallowRef(false)
3134
3235
// Search/filter
3336
const searchQuery = shallowRef('')
34-
const filterRole = shallowRef<'all' | 'developer' | 'admin' | 'owner'>('all')
37+
const filterRole = shallowRef<MemberRoleFilter>('all')
3538
const filterTeam = shallowRef<string | null>(null)
3639
const sortBy = shallowRef<'name' | 'role'>('name')
3740
const sortOrder = shallowRef<'asc' | 'desc'>('asc')
3841
3942
// Add member form
4043
const showAddMember = shallowRef(false)
4144
const newUsername = shallowRef('')
42-
const newRole = shallowRef<'developer' | 'admin' | 'owner'>('developer')
45+
const newRole = shallowRef<MemberRole>('developer')
4346
const newTeam = shallowRef<string>('') // Empty string means "developers" (default)
4447
const isAddingMember = shallowRef(false)
4548
@@ -259,6 +262,17 @@ function getRoleBadgeClass(role: string): string {
259262
}
260263
}
261264
265+
const roleLabels = computed(() => ({
266+
owner: $t('org.members.role.owner'),
267+
admin: $t('org.members.role.admin'),
268+
developer: $t('org.members.role.developer'),
269+
all: $t('org.members.role.all'),
270+
}))
271+
272+
function getRoleLabel(role: MemberRoleFilter): string {
273+
return roleLabels.value[role]
274+
}
275+
262276
// Click on team badge to switch to teams tab and highlight
263277
function handleTeamClick(teamName: string) {
264278
emit('select-team', teamName)
@@ -341,7 +355,7 @@ watch(lastExecutionTime, () => {
341355
:aria-pressed="filterRole === role"
342356
@click="filterRole = role"
343357
>
344-
{{ $t(`org.members.role.${role}`) }}
358+
{{ getRoleLabel(role) }}
345359
<span v-if="role !== 'all'" class="text-fg-subtle">({{ roleCounts[role] }})</span>
346360
</button>
347361
</div>
@@ -439,7 +453,7 @@ watch(lastExecutionTime, () => {
439453
class="px-1.5 py-0.5 font-mono text-xs border rounded"
440454
:class="getRoleBadgeClass(member.role)"
441455
>
442-
{{ member.role }}
456+
{{ getRoleLabel(member.role) }}
443457
</span>
444458
</div>
445459
<div class="flex items-center gap-1">
@@ -459,9 +473,9 @@ watch(lastExecutionTime, () => {
459473
)
460474
"
461475
>
462-
<option value="developer">{{ $t('org.members.role.developer') }}</option>
463-
<option value="admin">{{ $t('org.members.role.admin') }}</option>
464-
<option value="owner">{{ $t('org.members.role.owner') }}</option>
476+
<option value="developer">{{ getRoleLabel('developer') }}</option>
477+
<option value="admin">{{ getRoleLabel('admin') }}</option>
478+
<option value="owner">{{ getRoleLabel('owner') }}</option>
465479
</select>
466480
<!-- Remove button -->
467481
<button

app/components/Package/DownloadAnalytics.vue

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,17 @@ function buildExportFilename(extension: string): string {
785785
return `${sanitise(label ?? '')}-${g}_${range}.${extension}`
786786
}
787787
788+
const granularityLabels = computed(() => ({
789+
daily: $t('package.downloads.granularity_daily'),
790+
weekly: $t('package.downloads.granularity_weekly'),
791+
monthly: $t('package.downloads.granularity_monthly'),
792+
yearly: $t('package.downloads.granularity_yearly'),
793+
}))
794+
795+
function getGranularityLabel(granularity: ChartTimeGranularity) {
796+
return granularityLabels.value[granularity]
797+
}
798+
788799
// VueUiXy chart component configuration
789800
const chartConfig = computed(() => {
790801
return {
@@ -835,7 +846,7 @@ const chartConfig = computed(() => {
835846
fontSize: isMobile.value ? 24 : 16,
836847
axis: {
837848
yLabel: $t('package.downloads.y_axis_label', {
838-
granularity: $t(`package.downloads.granularity_${selectedGranularity.value}`),
849+
granularity: getGranularityLabel(selectedGranularity.value),
839850
}),
840851
xLabel: isMultiPackageMode.value ? '' : xAxisLabel.value, // for multiple series, names are displayed in the chart's legend
841852
yLabelOffsetX: 12,

app/components/Package/Replacement.vue

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,6 @@ const props = defineProps<{
55
replacement: ModuleReplacement
66
}>()
77
8-
const message = computed<
9-
[string, { replacement?: string; nodeVersion?: string; community?: string }]
10-
>(() => {
11-
switch (props.replacement.type) {
12-
case 'native':
13-
return [
14-
'package.replacement.native',
15-
{
16-
replacement: props.replacement.replacement,
17-
nodeVersion: props.replacement.nodeVersion,
18-
},
19-
]
20-
case 'simple':
21-
return [
22-
'package.replacement.simple',
23-
{
24-
replacement: props.replacement.replacement,
25-
community: $t('package.replacement.community'),
26-
},
27-
]
28-
case 'documented':
29-
return [
30-
'package.replacement.documented',
31-
{
32-
community: $t('package.replacement.community'),
33-
},
34-
]
35-
case 'none':
36-
return ['package.replacement.none', {}]
37-
}
38-
})
39-
408
const mdnUrl = computed(() => {
419
if (props.replacement.type !== 'native' || !props.replacement.mdnPath) return null
4210
return `https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/${props.replacement.mdnPath}`
@@ -57,13 +25,43 @@ const docPath = computed(() => {
5725
{{ $t('package.replacement.title') }}
5826
</h2>
5927
<p class="text-sm m-0">
60-
<i18n-t :keypath="message[0]" scope="global">
28+
<i18n-t
29+
v-if="replacement.type === 'native'"
30+
keypath="package.replacement.native"
31+
scope="global"
32+
>
6133
<template #replacement>
62-
{{ message[1].replacement ?? '' }}
34+
{{ replacement.replacement }}
6335
</template>
6436
<template #nodeVersion>
65-
{{ message[1].nodeVersion ?? '' }}
37+
{{ replacement.nodeVersion }}
38+
</template>
39+
</i18n-t>
40+
<i18n-t
41+
v-else-if="replacement.type === 'simple'"
42+
keypath="package.replacement.simple"
43+
scope="global"
44+
>
45+
<template #community>
46+
<a
47+
href="https://e18e.dev/docs/replacements/"
48+
target="_blank"
49+
rel="noopener noreferrer"
50+
class="inline-flex items-center gap-1 ms-1 underline underline-offset-4 decoration-amber-600/60 dark:decoration-amber-400/50 hover:decoration-fg transition-colors"
51+
>
52+
{{ $t('package.replacement.community') }}
53+
<span class="i-carbon-launch w-3 h-3" aria-hidden="true" />
54+
</a>
55+
</template>
56+
<template #replacement>
57+
{{ replacement.replacement }}
6658
</template>
59+
</i18n-t>
60+
<i18n-t
61+
v-else-if="replacement.type === 'documented'"
62+
keypath="package.replacement.documented"
63+
scope="global"
64+
>
6765
<template #community>
6866
<a
6967
href="https://e18e.dev/docs/replacements/"
@@ -76,6 +74,9 @@ const docPath = computed(() => {
7674
</a>
7775
</template>
7876
</i18n-t>
77+
<template v-else>
78+
{{ $t('package.replacement.none') }}
79+
</template>
7980
<a
8081
v-if="mdnUrl"
8182
:href="mdnUrl"

app/components/Package/VulnerabilityTree.vue

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,22 @@ const hasVulnerabilities = computed(
2323
// Banner - amber for better light mode contrast
2424
const bannerColor = 'border-amber-600/40 bg-amber-500/10 text-amber-700 dark:text-amber-400'
2525
26+
const severityLabels = computed(() => ({
27+
critical: $t('package.vulnerabilities.severity.critical'),
28+
high: $t('package.vulnerabilities.severity.high'),
29+
moderate: $t('package.vulnerabilities.severity.moderate'),
30+
low: $t('package.vulnerabilities.severity.low'),
31+
}))
32+
33+
function getPackageSeverityLabel(severity: Exclude<OsvSeverityLevel, 'unknown'>) {
34+
return severityLabels.value[severity]
35+
}
36+
2637
const summaryText = computed(() => {
2738
if (!vulnTree.value) return ''
2839
const { totalCounts } = vulnTree.value
2940
return SEVERITY_LEVELS.filter(s => totalCounts[s] > 0)
30-
.map(s => `${totalCounts[s]} ${$t(`package.vulnerabilities.severity.${s}`)}`)
41+
.map(s => `${totalCounts[s]} ${getPackageSeverityLabel(s)}`)
3142
.join(', ')
3243
})
3344
@@ -130,7 +141,7 @@ function getDepthStyle(depth: string | undefined) {
130141
class="px-1.5 py-0.5 text-[10px] font-mono rounded border"
131142
:class="SEVERITY_COLORS[s]"
132143
>
133-
{{ pkg.counts[s] }} {{ $t(`package.vulnerabilities.severity.${s}`) }}
144+
{{ pkg.counts[s] }} {{ getPackageSeverityLabel(s) }}
134145
</span>
135146
</div>
136147
</div>

app/components/Terminal/Install.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ const copyCreateCommand = () => copyCreate(getFullCreateCommand())
104104
<span class="w-2.5 h-2.5 rounded-full bg-fg-subtle" />
105105
<span class="w-2.5 h-2.5 rounded-full bg-fg-subtle" />
106106
</div>
107-
<div class="px-3 pt-2 pb-3 sm:px-4 sm:pt-3 sm:pb-4 space-y-1 overflow-x-auto">
107+
<div class="px-3 pt-2 pb-3 sm:px-4 sm:pt-3 sm:pb-4 space-y-1 overflow-x-auto" dir="ltr">
108108
<!-- Install command - render all PM variants, CSS controls visibility -->
109109
<div
110110
v-for="pm in packageManagers"
@@ -162,7 +162,7 @@ const copyCreateCommand = () => copyCreate(getFullCreateCommand())
162162
<!-- Run command (only if package has executables) - render all PM variants -->
163163
<template v-if="executableInfo?.hasExecutable">
164164
<!-- Comment line -->
165-
<div class="flex items-center gap-2 pt-1">
165+
<div class="flex items-center gap-2 pt-1" dir="auto">
166166
<span class="text-fg-subtle font-mono text-sm select-none"
167167
># {{ $t('package.run.locally') }}</span
168168
>
@@ -196,7 +196,7 @@ const copyCreateCommand = () => copyCreate(getFullCreateCommand())
196196
<!-- Create command (for packages with associated create-* package) - render all PM variants -->
197197
<template v-if="createPackageInfo">
198198
<!-- Comment line -->
199-
<div class="flex items-center gap-2 pt-1 select-none">
199+
<div class="flex items-center gap-2 pt-1 select-none" dir="auto">
200200
<span class="text-fg-subtle font-mono text-sm"># {{ $t('package.create.title') }}</span>
201201
<TooltipApp
202202
:text="$t('package.create.view', { packageName: createPackageInfo.packageName })"

0 commit comments

Comments
 (0)