- {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`,
+ })
+ }
+ }
+ },
+ }
+ },
+ },
+ },
+}