Skip to content

Commit 27692ae

Browse files
committed
chore(i18n): add linter enforcing alphabetically sorted locale files
Add check_i18n_sorted.py, which fails when any locale JSON file's keys are not sorted alphabetically (recursively, so nested objects are covered). Supports --fix to sort in place. Wire it into the i18n CI workflow alongside the existing parity check, and sort all existing locale files. Generated-By: PostHog Code Task-Id: 7cbf74de-6bf1-45c0-9c8e-c45f94119edc
1 parent b4bc84f commit 27692ae

220 files changed

Lines changed: 6392 additions & 6310 deletions

File tree

Some content is hidden

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

.claude/skills/frontend-i18n/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ User-visible strings are **never hard-coded** in components — they come from l
2323

2424
```bash
2525
python3 frontend/src/locales/check_i18n_locales.py
26+
python3 frontend/src/locales/check_i18n_sorted.py # add --fix to auto-sort
2627
```
2728

28-
It compares every non-English locale against `en_US` and **fails on any missing file, missing key, or extra key**. CI runs the same script (`.github/workflows/i18n.yml`) on any change under `frontend/src/locales/**`. It must pass with zero missing/extra keys.
29+
`check_i18n_locales.py` compares every non-English locale against `en_US` and **fails on any missing file, missing key, or extra key**. `check_i18n_sorted.py` **fails if any locale JSON file's keys aren't sorted alphabetically** (run with `--fix` to sort them in place). CI runs both scripts (`.github/workflows/i18n.yml`) on any change under `frontend/src/locales/**`; both must pass.
2930

3031
## Adding a new language
3132

.github/workflows/i18n.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,6 @@ jobs:
2222

2323
- name: Run localization check
2424
run: python frontend/src/locales/check_i18n_locales.py
25+
26+
- name: Check locale files are sorted
27+
run: python frontend/src/locales/check_i18n_sorted.py

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ npm run generate # regenerate types from backend OpenAPI (bac
9696
npm run build:tokens # regenerate v2 tokens.css (auto on predev/prebuild)
9797
npm run storybook # component library on :6006
9898
python3 src/locales/check_i18n_locales.py # i18n parity check
99+
python3 src/locales/check_i18n_sorted.py # locale keys sorted (--fix to sort)
99100
```
100101

101102
**Lint (both stacks):** `trunk fmt && trunk check`.
Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,30 @@
11
{
22
"add-collection": "Добави колекция",
3+
"cover": "Изображение на корица",
34
"create-collection": "Създай колекция",
5+
"create-failed": "Неуспешно създаване на колекция",
46
"create-smart-collection": "Създай умна колекция",
57
"current-filters": "Текущи филтри",
68
"danger-zone": "Опасна зона",
79
"delete-collection": "Изтрий колекция",
10+
"delete-collection-hint": "Премахва колекцията от RomM. Самите ROM файлове не се изтриват.",
811
"description": "Описание",
912
"download-collection": "Изтегли колекция",
1013
"edit-collection": "Редактирай колекция",
14+
"empty": "Тази колекция е празна.",
15+
"filters": "Филтри",
16+
"games-count": "{n} игра | {n} игри",
17+
"kind-header": "Вид",
18+
"kind-smart": "Умна",
19+
"kind-standard": "Стандартна",
20+
"kind-virtual": "Виртуална",
1121
"name": "Име",
22+
"name-exists": "Вече съществува колекция с това име.",
23+
"name-placeholder": "Име на колекцията",
24+
"no-collections-search": "Няма колекции, отговарящи на \"{query}\".",
25+
"no-collections-yet": "Все още няма колекции.",
26+
"no-collections-yet-hint": "Все още няма колекции. Създай една по-горе.",
27+
"not-found": "Колекцията не е намерена.",
1228
"owner": "Собственик",
1329
"private": "Частна",
1430
"private-desc": "Частна (видима само за мен)",
@@ -18,27 +34,11 @@
1834
"removing-collection-2": "от RomM. Потвърди?",
1935
"removing-smart-collection-1": "Премахване на умна колекция",
2036
"removing-smart-collection-2": "от RomM. Потвърди?",
37+
"save-as-smart": "Запази като умна колекция",
2138
"search-collection": "Търси колекция",
22-
"cover": "Изображение на корица",
23-
"delete-collection-hint": "Премахва колекцията от RomM. Самите ROM файлове не се изтриват.",
24-
"filters": "Филтри",
2539
"settings": "Настройки",
26-
"updated": "Колекцията е обновена",
27-
"name-exists": "Вече съществува колекция с това име.",
28-
"save-as-smart": "Запази като умна колекция",
2940
"smart-create-failed": "Неуспешно създаване на умна колекция",
3041
"smart-created": "Умната колекция е създадена.",
3142
"smart-no-filters": "Приложи някои филтри преди да създадеш умна колекция.",
32-
"games-count": "{n} игра | {n} игри",
33-
"kind-header": "Вид",
34-
"name-placeholder": "Име на колекцията",
35-
"no-collections-search": "Няма колекции, отговарящи на \"{query}\".",
36-
"no-collections-yet": "Все още няма колекции.",
37-
"no-collections-yet-hint": "Все още няма колекции. Създай една по-горе.",
38-
"create-failed": "Неуспешно създаване на колекция",
39-
"empty": "Тази колекция е празна.",
40-
"not-found": "Колекцията не е намерена.",
41-
"kind-standard": "Стандартна",
42-
"kind-smart": "Умна",
43-
"kind-virtual": "Виртуална"
43+
"updated": "Колекцията е обновена"
4444
}
Lines changed: 94 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,160 +1,160 @@
11
{
2-
"crt-mode": "CRT mode",
32
"about": "Относно",
3+
"about-community": "Общност",
4+
"about-documentation": "Документация",
5+
"about-romm": "За RomM",
6+
"about-source-code": "Изходен код",
7+
"about-version": "Версия на RomM",
48
"activity": "Активност",
59
"add": "Добави",
610
"administration": "Администрация",
11+
"all": "Всички",
12+
"all-files-skipped": "Всички файлове са пропуснати, нищо не е качено.",
13+
"all-platforms": "Всички платформи",
714
"and": "и",
815
"apply": "Приложи",
916
"ascii-only": "Само ASCII символи",
17+
"back": "Назад",
18+
"back-btn": "Назад",
19+
"beta": "Бета",
1020
"cancel": "Откажи",
21+
"changelog": "Списък с промени",
22+
"changelog-empty-hint": "Хранилището все още не е публикувало издания.",
23+
"changelog-empty-title": "Все още няма издания",
24+
"changelog-error-hint": "Връзката с GitHub е неуспешна. Проверете връзката или опитайте отново.",
25+
"changelog-error-title": "Изданията не можаха да се заредят",
26+
"changelog-loading": "Зареждане на изданията…",
27+
"changelog-view-all": "Вижте всички издания в GitHub",
28+
"clear": "Изчисти",
29+
"clear-file": "Изчисти",
30+
"clear-finished": "Изчисти завършените",
31+
"clipboard-copied": "{label} е копирано в клипборда",
32+
"clipboard-copy-failed": "Копирането в клипборда е неуспешно. Това изисква защитена (HTTPS) връзка.",
33+
"clipboard-unavailable": "Клипбордът не е достъпен в този контекст. Избери линка по-долу, за да го копираш ръчно.",
34+
"close": "Затвори",
35+
"cloud": "Облак",
36+
"collapse": "Свий",
1137
"collection": "Колекция",
1238
"collections": "Колекции",
1339
"confirm": "Потвърди",
1440
"confirm-deletion": "Потвърди изтриването",
41+
"copy-code": "Копирай кода",
1542
"core": "Основни",
1643
"create": "Създай",
44+
"crt-mode": "CRT mode",
1745
"delete": "Изтрий",
46+
"details": "Детайли",
47+
"discard": "Отхвърли",
48+
"dismiss": "Затвори",
49+
"download": "Свали",
1850
"dropzone-description": "Пусни ROM файловете тук или кликни за да избереш файлове",
19-
"dropzone-hint": "Плъзни и пусни или щракни за избор",
2051
"dropzone-drag-over": "Пусни за качване",
52+
"dropzone-hint": "Плъзни и пусни или щракни за избор",
2153
"dropzone-title": "Пусни файловете тук",
2254
"edit": "Редактирай",
2355
"exclude-on-delete": "Не добавяй повторно след изтриване",
56+
"expand": "Разшири",
2457
"filter": "Филтър",
2558
"firmware": "Фърмуер",
59+
"first-page": "Първа страница",
60+
"for": "за",
61+
"games": "Игри",
62+
"games-header": "Игри",
2663
"games-n": "{n} игра | {n} игри",
64+
"generate": "Генерирай",
65+
"got-it": "Разбрах",
66+
"hide-password": "Скрий паролата",
67+
"home": "Начало",
68+
"in-library": "В библиотеката",
2769
"invalid-email": "Невалиден имейл",
2870
"invalid-name": "Невалидно име",
71+
"kind": "Вид",
72+
"last-page": "Последна страница",
2973
"last-updated": "Последна промяна",
74+
"library": "Библиотека",
3075
"library-management": "Управление на библиотека",
76+
"library-tools": "Инструменти за библиотеката",
77+
"loading": "Зареждане",
78+
"loading-ellipsis": "Зареждане…",
79+
"local": "Локален",
3180
"logout": "Изход",
81+
"logs": "Logs",
3282
"name": "Име",
83+
"new-version-available": "Налична е нова версия",
84+
"next-page": "Следваща страница",
85+
"no-cover": "Без корица",
86+
"no-results": "Няма резултати",
87+
"not-linked": "Не е свързано",
88+
"offline": "Офлайн",
89+
"online": "Онлайн",
90+
"owned": "Притежавани",
91+
"page": "Страница",
3392
"password-length": "Паролата трябва да е между 6 и 255 символа",
3493
"patcher": "Пачър",
3594
"platform": "Платформа",
95+
"platform-created": "Платформата {name} е създадена",
3696
"platforms": "Платформи",
3797
"platforms-n": "{n} платформа | {n} платформи",
98+
"previous-page": "Предишна страница",
99+
"primary-navigation": "Основна навигация",
38100
"profile": "Профил",
39101
"random": "Случайно",
102+
"remove": "Премахни",
40103
"removing-from-filesystem": "Премахване от файловата система",
41104
"required": "Задължително",
42105
"save": "Запази",
43106
"saves": "Записи",
44107
"saves-n": "{n} запис | {n} записа",
45108
"screenshots-n": "{n} Екранна снимка | {n} Екранни снимки",
109+
"scroll-left": "Превърти наляво",
110+
"scroll-right": "Превърти надясно",
46111
"search": "Търси",
112+
"select": "Избери",
113+
"select-platform": "Избери платформа",
114+
"server-offline-retrying": "Server connection lost · retrying…",
115+
"server-reconnected": "Reconnected to the server",
47116
"server-stats": "Статистики на сървъра",
117+
"settings": "Настройки",
118+
"show-password": "Покажи паролата",
119+
"size": "Размер",
48120
"size-on-disk": "Размер на диска",
49121
"slug": "Слъг",
50122
"smart-collection": "Умна колекция",
51123
"smart-collections": "Умни колекции",
52124
"state": "Състояние",
53125
"states": "Състояния",
54126
"states-n": "{n} състояние | {n} състояния",
127+
"switch-classic-ui": "Превключи към класическия интерфейс",
55128
"sync": "Синхронизирай",
129+
"try-again": "Опитай отново",
56130
"type": "Тип",
131+
"type-to-confirm": "Напиши {label}, за да потвърдиш.",
132+
"unable-to-load-platforms": "Неуспешно зареждане на платформи: {error}",
133+
"unable-to-upload-roms": "Неуспешно качване на ROM-ове: {error}",
134+
"unknown-error": "неизвестна грешка",
57135
"update": "Обнови",
58136
"upload": "Качи",
59-
"upload-files-selected": "{count} избран файл | {count} избрани файла",
60-
"user-interface": "Потребителски интерфейс",
61-
"username-chars": "Потребителското име може да съдържа само букви, цифри, долни черти и тирета",
62-
"username-length": "Потребителското име трябва да е между 3 и 255 символа",
63-
"virtual-collection": "Автогенерирана колекция",
64-
"virtual-collections": "Автогенерирани колекции",
65-
"warning": "ВНИМАНИЕ:",
66-
"beta": "Бета",
67-
"clear": "Изчисти",
68-
"no-results": "Няма резултати",
69-
"got-it": "Разбрах",
70-
"in-library": "В библиотеката",
71-
"new-version-available": "Налична е нова версия",
72-
"dismiss": "Затвори",
73-
"whats-new": "Виж какво е новото",
74-
"hide-password": "Скрий паролата",
75-
"show-password": "Покажи паролата",
76-
"settings": "Настройки",
77-
"back": "Назад",
78-
"changelog": "Списък с промени",
79-
"clear-finished": "Изчисти завършените",
80-
"clipboard-unavailable": "Клипбордът не е достъпен в този контекст. Избери линка по-долу, за да го копираш ръчно.",
81-
"clipboard-copied": "{label} е копирано в клипборда",
82-
"clipboard-copy-failed": "Копирането в клипборда е неуспешно. Това изисква защитена (HTTPS) връзка.",
83-
"close": "Затвори",
84-
"cloud": "Облак",
85-
"collapse": "Свий",
86-
"copy-code": "Копирай кода",
87-
"expand": "Разшири",
88-
"first-page": "Първа страница",
89-
"games": "Игри",
90-
"games-header": "Игри",
91-
"home": "Начало",
92-
"kind": "Вид",
93-
"last-page": "Последна страница",
94-
"library": "Библиотека",
95-
"library-tools": "Инструменти за библиотеката",
96-
"loading-ellipsis": "Зареждане…",
97-
"local": "Локален",
98-
"next-page": "Следваща страница",
99-
"no-cover": "Без корица",
100-
"not-linked": "Не е свързано",
101-
"offline": "Офлайн",
102-
"online": "Онлайн",
103-
"owned": "Притежавани",
104-
"previous-page": "Предишна страница",
105-
"primary-navigation": "Основна навигация",
106-
"scroll-left": "Превърти наляво",
107-
"scroll-right": "Превърти надясно",
108-
"size": "Размер",
109-
"switch-classic-ui": "Превключи към класическия интерфейс",
110-
"try-again": "Опитай отново",
111-
"type-to-confirm": "Напиши {label}, за да потвърдиш.",
112137
"upload-collapse-panel": "Свий панела за качване",
113138
"upload-expand-panel": "Разшири панела за качване",
139+
"upload-files-selected": "{count} избран файл | {count} избрани файла",
140+
"upload-progress": "Прогрес на качването",
114141
"upload-roms": "Качи ROM-ове",
115142
"upload-stats": "{done} готови · {failed} неуспешни",
143+
"uploaded-and-scanning": "{ok} файла са качени · започва сканиране",
144+
"uploaded-n": "{n} качени",
145+
"uploaded-with-failed": "{ok} качени · {fail} пропуснати/неуспешни · започва сканиране",
116146
"uploading-files-n": "Качване на {n} файл | Качване на {n} файла",
147+
"user-interface": "Потребителски интерфейс",
148+
"username-chars": "Потребителското име може да съдържа само букви, цифри, долни черти и тирета",
149+
"username-length": "Потребителското име трябва да е между 3 и 255 символа",
117150
"v2-not-available-title": "Все още не е налично в новия интерфейс",
118151
"v2-not-migrated": "{view} все още не е мигриран към v2.",
119152
"v2-switch-back": "Превключи обратно към v1",
120-
"zoom-in": "Увеличи",
121-
"zoom-out": "Намали",
122-
"download": "Свали",
123-
"page": "Страница",
124-
"uploaded-n": "{n} качени",
125-
"upload-progress": "Прогрес на качването",
126-
"all": "Всички",
127-
"select": "Избери",
128-
"loading": "Зареждане",
129-
"back-btn": "Назад",
130-
"generate": "Генерирай",
131-
"all-platforms": "Всички платформи",
132-
"select-platform": "Избери платформа",
133-
"remove": "Премахни",
134-
"unable-to-load-platforms": "Неуспешно зареждане на платформи: {error}",
135-
"unable-to-upload-roms": "Неуспешно качване на ROM-ове: {error}",
136-
"unknown-error": "неизвестна грешка",
137-
"about-romm": "За RomM",
138-
"about-version": "Версия на RomM",
139-
"about-source-code": "Изходен код",
140-
"about-documentation": "Документация",
141-
"about-community": "Общност",
142-
"clear-file": "Изчисти",
143-
"platform-created": "Платформата {name} е създадена",
144-
"all-files-skipped": "Всички файлове са пропуснати, нищо не е качено.",
145-
"uploaded-and-scanning": "{ok} файла са качени · започва сканиране",
146-
"uploaded-with-failed": "{ok} качени · {fail} пропуснати/неуспешни · започва сканиране",
147-
"for": "за",
148-
"details": "Детайли",
149-
"discard": "Отхвърли",
150-
"changelog-loading": "Зареждане на изданията…",
151-
"changelog-error-title": "Изданията не можаха да се заредят",
152-
"changelog-error-hint": "Връзката с GitHub е неуспешна. Проверете връзката или опитайте отново.",
153-
"changelog-empty-title": "Все още няма издания",
154-
"changelog-empty-hint": "Хранилището все още не е публикувало издания.",
155-
"changelog-view-all": "Вижте всички издания в GitHub",
153+
"virtual-collection": "Автогенерирана колекция",
154+
"virtual-collections": "Автогенерирани колекции",
156155
"visibility": "Видимост",
157-
"server-offline-retrying": "Server connection lost · retrying…",
158-
"server-reconnected": "Reconnected to the server",
159-
"logs": "Logs"
156+
"warning": "ВНИМАНИЕ:",
157+
"whats-new": "Виж какво е новото",
158+
"zoom-in": "Увеличи",
159+
"zoom-out": "Намали"
160160
}

0 commit comments

Comments
 (0)