Skip to content

Commit 707dd00

Browse files
authored
feat(i18n): add eloqnt lint, and fix what it found (#9106)
* fix(i18n): remove 36 unused message keys from all locales Each key was verified unreferenced with a repository-wide literal search (excluding the locale catalogs). Eighteen are leftovers from the removed search-chat feature, and navBar.links.guides/.security no longer have entries in navigation.json. components.metabar.addedIn/.viewAs occur only as Storybook fixture data, and no blog post uses the advisory-board category. Keys are removed from ar.json as well, although that locale is disabled, so it does not drift further. * fix(i18n): restore dropped ICU arguments in two translations Both dropped an argument the English source defines and rendered silently, because use-intl drops an unused tag or value rather than throwing: - es layouts.download.codeBox.unsupportedVersionWarning lost its <link> tag, so the sentence ended on a colon and the EOL-support link never rendered. - zh-tw layouts.download.codeBox.platformInfo.default lost {platform}, so the sentence named no package manager. Note: these two strings are machine-suggested and should be replaced via 'eloqnt translate' or reviewed by a native speaker before release. * fix(i18n): remove translation keys with no counterpart in en.json Removes 812 stale entries across ten translation files, covering keys that were dropped from the source catalog but survived in translations. Most of it is one fact: the components.navigation.learn.* subtree (57 keys) was removed from en.json when the learn section moved to an external link, and lived on in ten locales. The list is derived from 'eloqnt lint --json' superfluous-key findings, not transcribed by hand. en.json itself is untouched. * feat(i18n): add eloqnt lint with orphan-message scoped to static keys Adds @eloqnt/cli and a config covering the 14 enabled locales from packages/i18n/src/config.json (ar is disabled and deliberately absent). The orphan-message rule resolves a key only when the call site names it as a literal string. This codebase builds some keys at runtime — labels come from navigation.json and constants.json, and several components interpolate a variable into the key — so the rule is turned off for exactly those keys via lint.overrides. Namespaces that also hold statically referenced keys list the dynamic ones individually instead of using a wildcard, so the rule keeps covering the rest. Result: eloqnt lint reports 0 errors and 562 pre-existing missing-translation warnings. * chore: add changeset for i18n message cleanup * refactor(i18n): derive eloqnt locales from i18n config, drop ui-components from srcPath The locale list now comes from packages/i18n/src/config.json (enabled locales only) instead of being duplicated. 'infer' is not used because it would pick up the disabled ar.json from disk. packages/ui-components does not use next-intl — components receive translated strings as props — and removing it from srcPath leaves the lint result unchanged (verified: 0 errors, 562 warnings). * chore: replace changeset with an empty one The i18n message cleanup does not need a release; the empty changeset keeps the Changesets CI check satisfied without bumping the package. * feat(i18n): run eloqnt lint in CI quality checks Adds a root lint:i18n script following the same pattern as the root prettier task: the tool spans apps/site and packages/i18n, so it is installed at the workspace root and wired into the Quality checks job via a turbo root task. Also shortens the config comment per review. * wip * Remove `minimumReleaseAgeExclude`
1 parent 834369d commit 707dd00

21 files changed

Lines changed: 302 additions & 1822 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@node-core/website-i18n': patch
3+
---
4+
5+
Remove unused and superfluous message keys from all locales, and restore
6+
dropped ICU arguments in the Spanish EOL warning and the Traditional Chinese
7+
package-manager notice, as reported by `eloqnt lint`.

.eloqnt/config.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { defineConfig } from '@eloqnt/cli';
2+
3+
import localeConfig from '../packages/i18n/src/config.json' with { type: 'json' };
4+
5+
export default defineConfig({
6+
srcPath: ['./apps/site'],
7+
messages: {
8+
path: './packages/i18n/src/locales',
9+
locales: localeConfig
10+
.filter(locale => locale.enabled)
11+
.map(locale => locale.code),
12+
sourceLocale: 'en',
13+
format: 'json',
14+
},
15+
lint: {
16+
overrides: [
17+
{
18+
// These keys are built at runtime, so static analysis cannot
19+
// see which ones are in use.
20+
keys: [
21+
'components.containers.footer.links.*',
22+
'components.containers.navBar.links.*',
23+
'components.navigation.about.links.*',
24+
'components.navigation.getInvolved.links.*',
25+
'layouts.download.codeBox.platformInfo.*',
26+
'layouts.blog.categories.*',
27+
'components.eolChip.severity.*',
28+
29+
// These namespaces also hold statically referenced keys, so the
30+
// dynamic ones are listed individually.
31+
'components.metabar.author',
32+
'components.metabar.authors',
33+
'components.releaseModal.title',
34+
'components.releaseModal.titleWithoutCodename',
35+
'components.banner.default',
36+
'components.banner.warning',
37+
'components.banner.error',
38+
],
39+
rules: { 'orphan-message': 'off' },
40+
},
41+
],
42+
},
43+
});

.github/workflows/lint-and-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ jobs:
7373
if: |
7474
(github.event_name == 'push' || github.event_name == 'merge_group') ||
7575
(github.event_name == 'pull_request' && github.event.pull_request.head.ref != 'chore/crowdin')
76-
run: node_modules/.bin/turbo lint lint:types prettier --cache-dir=.turbo/cache
76+
run: node_modules/.bin/turbo lint lint:types prettier //#lint:i18n --cache-dir=.turbo/cache
7777

7878
- name: Save Lint Cache
7979
# We only want to save caches on `push` events or `pull_request_target` events

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"format": "turbo //#prettier:fix prettier:fix lint:fix",
2222
"lint": "turbo lint",
2323
"lint:fix": "turbo lint:fix",
24+
"lint:i18n": "eloqnt lint",
2425
"lint:staged": "lint-staged",
2526
"lint:types": "turbo lint:types",
2627
"prepare": "husky",
@@ -42,6 +43,7 @@
4243
"devDependencies": {
4344
"@changesets/changelog-github": "^0.7.0",
4445
"@changesets/cli": "^2.31.1",
46+
"@eloqnt/cli": "^0.6.9",
4547
"@eslint/js": "~10.0.1",
4648
"@reporters/github": "^2.0.2",
4749
"@testing-library/react": "~16.3.2",

packages/i18n/src/locales/ar.json

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,7 @@
3636
"about": "حول",
3737
"download": "تنزيل",
3838
"docs": "التوثيق",
39-
"guides": "الأدلة",
4039
"learn": "تعلّم",
41-
"security": "الأمان",
4240
"certification": "الدورات",
4341
"blog": "المدونة",
4442
"contribute": "المساهمة"
@@ -70,9 +68,7 @@
7068
},
7169
"downloadReleasesTable": {
7270
"version": "Node.js",
73-
"nApiVersion": "N-API",
7471
"codename": "الاسم الرمزي",
75-
"releaseDate": "تاريخ الإصدار",
7672
"firstReleased": "أول إصدار",
7773
"lastUpdated": "آخر تحديث",
7874
"status": "الحالة",
@@ -86,10 +82,7 @@
8682
"releaseModal": {
8783
"title": "Node.js v{version} ({codename})",
8884
"titleWithoutCodename": "Node.js v{version}",
89-
"overview": "نظرة عامة",
9085
"minorVersions": "الإصدارات الفرعية",
91-
"releaseAnnouncement": "إعلان الإصدار",
92-
"unsupportedVersionWarning": "هذا الإصدار خارج مرحلة الصيانة. يرجى استخدام إصدار مدعوم. <link>تعرّف على دعم EOL.</link>",
9386
"ltsVersionFeaturesNotice": "هل تريد الميزات الجديدة في وقت أقرب؟ استخدم <link>أحدث إصدار من Node.js</link> بدلًا من ذلك وجرب أحدث التحسينات!"
9487
},
9588
"eolAlert": {
@@ -121,7 +114,6 @@
121114
"eolTable": {
122115
"version": "الإصدار",
123116
"codename": "الاسم الرمزي",
124-
"releaseDate": "تاريخ الإصدار",
125117
"lastUpdated": "آخر تحديث",
126118
"vulnerabilities": "الثغرات",
127119
"details": "التفاصيل",
@@ -134,7 +126,6 @@
134126
"npmVersion": "إصدار npm",
135127
"v8Version": "إصدار V8",
136128
"actions": {
137-
"release": "الإصدار",
138129
"changelog": "سجل التغييرات",
139130
"docs": "التوثيق"
140131
}
@@ -150,8 +141,7 @@
150141
"common": {
151142
"alertBox": {
152143
"info": "معلومة",
153-
"warning": "تحذير",
154-
"danger": "خطر"
144+
"warning": "تحذير"
155145
},
156146
"breadcrumbs": {
157147
"navigateToHome": "الانتقال إلى الصفحة الرئيسية"
@@ -179,12 +169,10 @@
179169
"metabar": {
180170
"lastUpdated": "آخر تحديث",
181171
"readingTime": "مدة القراءة",
182-
"addedIn": "أضيف في",
183172
"author": "المؤلف",
184173
"authors": "المؤلفون",
185174
"contribute": "المساهمة",
186175
"contributeText": "عدّل هذه الصفحة",
187-
"viewAs": "عرض كـ",
188176
"tableOfContents": "جدول المحتويات",
189177
"metadata": "بيانات المقال"
190178
},
@@ -196,25 +184,7 @@
196184
},
197185
"search": {
198186
"searchPlaceholder": "ابدأ الكتابة...",
199-
"chatPlaceholder": "اسألني عن أي شيء",
200187
"noResultsFoundFor": "لم يتم العثور على نتائج لـ",
201-
"suggestions": "اقتراحات",
202-
"seeAll": "عرض الكل",
203-
"addMore": "إضافة المزيد",
204-
"clearChat": "مسح المحادثة",
205-
"errorMessage": "حدث خطأ أثناء محاولة البحث. يرجى المحاولة مرة أخرى.",
206-
"disclaimer": "قد تخطئ ملخصات AI. يرجى التحقق من المعلومات.",
207-
"startYourSearch": "ابدأ البحث",
208-
"initErrorSearch": "تعذّر تهيئة خدمة البحث",
209-
"initErrorChat": "تعذّر تهيئة خدمة الدردشة",
210-
"chatButtonLabel": "الحصول على ملخص AI",
211-
"searchButtonLabel": "بحث",
212-
"poweredBy": "مدعوم من",
213-
"suggestionOne": "كيف أثبّت Node.js؟",
214-
"suggestionTwo": "كيف أنشئ خادم HTTP؟",
215-
"suggestionThree": "ترقية إصدار Node.js",
216-
"scrollToBottom": "التمرير إلى الأسفل",
217-
"closeChat": "إغلاق الدردشة",
218188
"keyboardShortcuts": {
219189
"select": "للاختيار",
220190
"navigate": "للتنقل",
@@ -229,14 +199,6 @@
229199
}
230200
},
231201
"layouts": {
232-
"blogPost": {
233-
"author": {
234-
"byLine": "{author, select, null {} other {بواسطة {author}، }}"
235-
}
236-
},
237-
"blogIndex": {
238-
"categoryName": "{category, select, all {المدونة} other {منشورات مدونة {category}}}"
239-
},
240202
"blog": {
241203
"title": "المدونة",
242204
"selectCategory": "التصنيفات",
@@ -245,7 +207,6 @@
245207
"announcements": "الإعلانات",
246208
"release": "الإصدارات",
247209
"vulnerability": "الثغرات",
248-
"advisory-board": "المجلس الاستشاري",
249210
"community": "المجتمع",
250211
"feature": "ميزة",
251212
"module": "الوحدات",
@@ -278,7 +239,6 @@
278239
"platform": "المنصة",
279240
"os": "نظام التشغيل",
280241
"version": "الإصدار",
281-
"installMethod": "طريقة التثبيت",
282242
"packageManager": "مدير الحزم",
283243
"unknown": "غير معروف",
284244
"platformGroups": {
@@ -288,7 +248,6 @@
288248
},
289249
"codeBox": {
290250
"unsupportedVersionWarning": "هذا الإصدار خارج مرحلة الصيانة. يرجى استخدام إصدار مدعوم حاليًا. <link>تعرّف على دعم EOL.</link>",
291-
"ltsVersionFeaturesNotice": "هل تريد الميزات الجديدة في وقت أقرب؟ استخدم <link>أحدث إصدار من Node.js</link> بدلًا من ذلك وجرب أحدث التحسينات!",
292251
"communityPlatformInfo": "طرق التثبيت التي تعتمد على برمجيات المجتمع تدعمها الفرق المسؤولة عن صيانة تلك البرمجيات.",
293252
"externalSupportInfo": "إذا واجهت أي مشاكل، يرجى زيارة <link>موقع {platform}</link>",
294253
"noScriptDetected": "تتطلب هذه الصفحة JavaScript. يمكنك تنزيل Node.js بدون JavaScript عبر زيارة <link>صفحة أرشيف التنزيلات</link> مباشرة.",
@@ -300,8 +259,7 @@
300259
"choco": "Chocolatey هو مدير حزم لـ Windows.",
301260
"docker": "Docker هو منصة للحاويات.",
302261
"n": "\"n\" هو مدير إصدارات Node.js متعدد المنصات.",
303-
"asdf": "\"asdf\" هو مدير إصدارات يعمل على عدة أنظمة تشغيل ويدعم عدة لغات برمجية.",
304-
"volta": "\"Volta\" هو مدير إصدارات Node.js متعدد المنصات."
262+
"asdf": "\"asdf\" هو مدير إصدارات يعمل على عدة أنظمة تشغيل ويدعم عدة لغات برمجية."
305263
}
306264
}
307265
},

packages/i18n/src/locales/en.json

Lines changed: 2 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@
3737
"download": "Download",
3838
"docs": "Docs",
3939
"betaDocs": "Beta Docs",
40-
"guides": "Guides",
4140
"learn": "Learn",
42-
"security": "Security",
4341
"certification": "Courses",
4442
"blog": "Blog",
4543
"contribute": "Contribute"
@@ -71,9 +69,7 @@
7169
},
7270
"downloadReleasesTable": {
7371
"version": "Node.js",
74-
"nApiVersion": "N-API",
7572
"codename": "Codename",
76-
"releaseDate": "Released at",
7773
"firstReleased": "First released",
7874
"lastUpdated": "Last updated",
7975
"status": "Status",
@@ -87,10 +83,7 @@
8783
"releaseModal": {
8884
"title": "Node.js v{version} ({codename})",
8985
"titleWithoutCodename": "Node.js v{version}",
90-
"overview": "Overview",
9186
"minorVersions": "Minor versions",
92-
"releaseAnnouncement": "Release Announcement",
93-
"unsupportedVersionWarning": "This version is out of maintenance. Please use a supported version. <link>Understand EOL support.</link>",
9487
"ltsVersionFeaturesNotice": "Want new features sooner? Get the <link>latest Node.js version</link> instead and try the latest improvements!"
9588
},
9689
"eolAlert": {
@@ -122,7 +115,6 @@
122115
"eolTable": {
123116
"version": "Version",
124117
"codename": "Codename",
125-
"releaseDate": "Released at",
126118
"lastUpdated": "Last updated",
127119
"vulnerabilities": "Vulnerabilities",
128120
"details": "Details",
@@ -135,7 +127,6 @@
135127
"npmVersion": "npm version",
136128
"v8Version": "V8 version",
137129
"actions": {
138-
"release": "Release",
139130
"changelog": "Changelog",
140131
"docs": "Docs"
141132
}
@@ -151,8 +142,7 @@
151142
"common": {
152143
"alertBox": {
153144
"info": "Info",
154-
"warning": "Warning",
155-
"danger": "Danger"
145+
"warning": "Warning"
156146
},
157147
"breadcrumbs": {
158148
"navigateToHome": "Navigate to Home"
@@ -180,12 +170,10 @@
180170
"metabar": {
181171
"lastUpdated": "Last Updated",
182172
"readingTime": "Reading Time",
183-
"addedIn": "Added In",
184173
"author": "Author",
185174
"authors": "Authors",
186175
"contribute": "Contribute",
187176
"contributeText": "Edit this page",
188-
"viewAs": "View as",
189177
"tableOfContents": "Table of Contents",
190178
"metadata": "Article metadata"
191179
},
@@ -197,25 +185,7 @@
197185
},
198186
"search": {
199187
"searchPlaceholder": "Start typing...",
200-
"chatPlaceholder": "Ask me anything",
201188
"noResultsFoundFor": "No results found for",
202-
"suggestions": "Suggestions",
203-
"seeAll": "See all",
204-
"addMore": "Add more",
205-
"clearChat": "Clear chat",
206-
"errorMessage": "An error occurred while trying to search. Please try again.",
207-
"disclaimer": "AI summaries can make mistakes. Please verify the information.",
208-
"startYourSearch": "Start your search",
209-
"initErrorSearch": "Unable to initialize search service",
210-
"initErrorChat": "Unable to initialize chat service",
211-
"chatButtonLabel": "Get an AI summary",
212-
"searchButtonLabel": "Search",
213-
"poweredBy": "Powered by",
214-
"suggestionOne": "How to install Node.js?",
215-
"suggestionTwo": "How to create an HTTP server?",
216-
"suggestionThree": "Upgrading Node.js version",
217-
"scrollToBottom": "Scroll to bottom",
218-
"closeChat": "Close chat",
219189
"keyboardShortcuts": {
220190
"select": "to select",
221191
"navigate": "to navigate",
@@ -230,14 +200,6 @@
230200
}
231201
},
232202
"layouts": {
233-
"blogPost": {
234-
"author": {
235-
"byLine": "{author, select, null {} other {By {author}, }}"
236-
}
237-
},
238-
"blogIndex": {
239-
"categoryName": "{category, select, all {Blog} other {{category} Blog Posts}}"
240-
},
241203
"blog": {
242204
"title": "Blog",
243205
"selectCategory": "Categories",
@@ -246,7 +208,6 @@
246208
"announcements": "Announcements",
247209
"release": "Releases",
248210
"vulnerability": "Vulnerabilities",
249-
"advisory-board": "Advisory Board",
250211
"community": "Community",
251212
"feature": "Feature",
252213
"module": "Module",
@@ -279,7 +240,6 @@
279240
"platform": "Platform",
280241
"os": "Operating System",
281242
"version": "Version",
282-
"installMethod": "Install Method",
283243
"packageManager": "Package Manager",
284244
"unknown": "Unknown",
285245
"platformGroups": {
@@ -289,7 +249,6 @@
289249
},
290250
"codeBox": {
291251
"unsupportedVersionWarning": "This version is out of maintenance. Please use a currently supported version. <link>Understand EOL support.</link>",
292-
"ltsVersionFeaturesNotice": "Want new features sooner? Get the <link>latest Node.js version</link> instead and try the latest improvements!",
293252
"communityPlatformInfo": "Installation methods that involve community software are supported by the teams maintaining that software.",
294253
"externalSupportInfo": "If you encounter any issues please visit <link>{platform}'s website</link>",
295254
"noScriptDetected": "This page requires JavaScript. You can download Node.js without JavaScript by visiting the <link>downloads archive page</link> directly.",
@@ -301,8 +260,7 @@
301260
"choco": "Chocolatey is a package manager for Windows.",
302261
"docker": "Docker is a containerization platform.",
303262
"n": "\"n\" is a cross-platform Node.js version manager.",
304-
"asdf": "\"asdf\" is a cross-platform version manager that supports multiple languages.",
305-
"volta": "\"Volta\" is a cross-platform Node.js version manager."
263+
"asdf": "\"asdf\" is a cross-platform version manager that supports multiple languages."
306264
}
307265
}
308266
},

0 commit comments

Comments
 (0)