diff --git a/apps/renderer/src/modules/settings/tabs/lists.tsx b/apps/renderer/src/modules/settings/tabs/lists.tsx index 10f663499..e2fa73eb5 100644 --- a/apps/renderer/src/modules/settings/tabs/lists.tsx +++ b/apps/renderer/src/modules/settings/tabs/lists.tsx @@ -82,7 +82,7 @@ export const SettingLists = () => { {t.settings("lists.view")} - {t.settings("lists.fee")} + {t.settings("lists.fee.label")} @@ -147,7 +147,7 @@ export const SettingLists = () => { variant="ghost" onClick={() => { present({ - title: t.settings("lists.edit"), + title: t.settings("lists.edit.label"), content: ({ dismiss }) => ( ), @@ -314,7 +314,7 @@ const ListCreationModalContent = ({ dismiss, id }: { dismiss: () => void; id?: s render={({ field }) => (
- {t("lists.fee")} + {t("lists.fee.label")} {t("lists.fee.description")}
@@ -434,7 +434,7 @@ export const ListFeedsModalContent = ({ id }: { id: string }) => { } }} > - {t("lists.feeds.add")} + {t("lists.feeds.add.label")} diff --git a/eslint.config.mjs b/eslint.config.mjs index 6010fe6ec..463795282 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,6 +1,7 @@ // @ts-check import { defineConfig } from "eslint-config-hyoban" +import checkI18nJson from "./plugins/eslint-check-i18n-json.js" import recursiveSort from "./plugins/eslint-recursive-sort.js" export default defineConfig( @@ -49,9 +50,11 @@ export default defineConfig( files: ["locales/**/*.json"], plugins: { "recursive-sort": recursiveSort, + "check-i18n-json": checkI18nJson, }, rules: { "recursive-sort/recursive-sort": "error", + "check-i18n-json/valid-i18n-keys": "error", }, }, ) diff --git a/locales/settings/en.json b/locales/settings/en.json index fe299461f..cd82ef5ad 100644 --- a/locales/settings/en.json +++ b/locales/settings/en.json @@ -156,19 +156,19 @@ "lists.created.error": "Failed to create list.", "lists.created.success": "List created successfully!", "lists.description": "Description", - "lists.edit": "Edit", "lists.edit.error": "Failed to edit list.", + "lists.edit.label": "Edit", "lists.edit.success": "List edited successfully!", - "lists.fee": "Fee", "lists.fee.description": "The fee others need to pay you to subscribe to this list.", - "lists.feeds": "Feeds", + "lists.fee.label": "Fee", "lists.feeds.actions": "Actions", - "lists.feeds.add": "Add", "lists.feeds.add.error": "Failed to add feed to list.", + "lists.feeds.add.label": "Add", "lists.feeds.add.success": "Feed added to list.", "lists.feeds.delete.error": "Failed to remove feed from list.", "lists.feeds.delete.success": "Feed removed from list.", "lists.feeds.id": "Feed ID", + "lists.feeds.label": "Feeds", "lists.feeds.manage": "Manage Feeds", "lists.feeds.owner": "Owner", "lists.feeds.search": "Search for feed", @@ -234,4 +234,4 @@ "wallet.withdraw.modalTitle": "Withdraw Power", "wallet.withdraw.submitButton": "Submit", "wallet.withdraw.success": "Withdrawal successful!" -} \ No newline at end of file +} diff --git a/locales/settings/ko.json b/locales/settings/ko.json index 4c5ccc5b0..ef7f4c706 100644 --- a/locales/settings/ko.json +++ b/locales/settings/ko.json @@ -156,19 +156,19 @@ "lists.created.error": "목록 생성에 실패했습니다.", "lists.created.success": "목록이 성공적으로 생성되었습니다!", "lists.description": "설명", - "lists.edit": "편집", "lists.edit.error": "목록 편집에 실패했습니다.", + "lists.edit.label": "편집", "lists.edit.success": "목록이 성공적으로 편집되었습니다!", - "lists.fee": "수수료", "lists.fee.description": "다른 사용자가 이 목록을 구독하려면 지불해야 하는 비용입니다.", - "lists.feeds": "피드", + "lists.fee.label": "수수료", "lists.feeds.actions": "액션", - "lists.feeds.add": "추가", "lists.feeds.add.error": "목록에 피드를 추가하지 못했습니다.", + "lists.feeds.add.label": "추가", "lists.feeds.add.success": "피드가 목록에 추가되었습니다.", "lists.feeds.delete.error": "목록에서 피드를 제거하지 못했습니다.", "lists.feeds.delete.success": "목록에서 피드가 제거되었습니다.", "lists.feeds.id": "피드 ID", + "lists.feeds.label": "피드", "lists.feeds.manage": "피드 관리", "lists.feeds.owner": "소유자", "lists.feeds.search": "피드 검색", @@ -234,4 +234,4 @@ "wallet.withdraw.modalTitle": "파워 인출", "wallet.withdraw.submitButton": "제출", "wallet.withdraw.success": "인출이 성공적으로 완료되었습니다!" -} \ No newline at end of file +} diff --git a/locales/settings/zh-CN.json b/locales/settings/zh-CN.json index f37a08a6e..51431edf9 100644 --- a/locales/settings/zh-CN.json +++ b/locales/settings/zh-CN.json @@ -156,19 +156,19 @@ "lists.created.error": "创建列表失败", "lists.created.success": "创建列表成功", "lists.description": "描述", - "lists.edit": "编辑", "lists.edit.error": "编辑列表失败", + "lists.edit.label": "编辑", "lists.edit.success": "编辑列表成功", - "lists.fee": "费用", "lists.fee.description": "其他人订阅这个列表时要支付的费用", - "lists.feeds": "订阅源", + "lists.fee.label": "费用", "lists.feeds.actions": "自动化", - "lists.feeds.add": "添加", "lists.feeds.add.error": "添加订阅源到列表时失败", + "lists.feeds.add.label": "添加", "lists.feeds.add.success": "订阅源已添加到列表", "lists.feeds.delete.error": "添加订阅源到列表时失败", "lists.feeds.delete.success": "订阅源已从列表删除", "lists.feeds.id": "订阅源 ID", + "lists.feeds.label": "订阅源", "lists.feeds.manage": "管理订阅源", "lists.feeds.owner": "所有者", "lists.feeds.search": "搜索订阅源", @@ -234,4 +234,4 @@ "wallet.withdraw.modalTitle": "提现 Power", "wallet.withdraw.submitButton": "提交", "wallet.withdraw.success": "提现成功!" -} \ No newline at end of file +} diff --git a/plugins/eslint-check-i18n-json.js b/plugins/eslint-check-i18n-json.js new file mode 100644 index 000000000..f2b5a2884 --- /dev/null +++ b/plugins/eslint-check-i18n-json.js @@ -0,0 +1,66 @@ +import path from "node:path" + +/** @type {import("eslint").ESLint.Plugin} */ +export default { + rules: { + "valid-i18n-keys": { + meta: { + type: "problem", + docs: { + description: "Ensure i18n JSON keys are flat and valid as object paths", + category: "Possible Errors", + recommended: true, + }, + fixable: null, + }, + create(context) { + return { + Program(node) { + const filename = context.getFilename() + if (path.basename(filename) !== "en.json") return + + const sourceCode = context.getSourceCode() + let json + try { + json = JSON.parse(sourceCode.text) + } catch { + context.report({ + node, + message: "Invalid JSON format", + }) + return + } + + const keys = Object.keys(json) + const keyPrefixes = new Set() + + for (const key of keys) { + if (key.includes(".")) { + const parts = key.split(".") + for (let i = 1; i < parts.length; i++) { + const prefix = parts.slice(0, i).join(".") + if (keys.includes(prefix)) { + context.report({ + node, + message: `Invalid key structure: '${key}' conflicts with '${prefix}'`, + }) + } + keyPrefixes.add(prefix) + } + } + } + + for (const key of keys) { + if (keyPrefixes.has(key)) { + context.report({ + node, + message: `Invalid key structure: '${key}' is a prefix of another key`, + }) + } + } + }, + } + }, + }, + }, +}