diff --git a/.vscode/settings.json b/.vscode/settings.json index a95344d..cd20aa6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -65,5 +65,8 @@ "unsyncable" ], "i18next.defaultTranslatedLocale": "en", - "i18next.i18nPaths": "public/locales" + "i18next.i18nPaths": "public/locales", + "files.associations": { + "public/locales/**.json": "json5" + } } diff --git a/README.md b/README.md index 99b3118..3c138c9 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,9 @@ ### Contributing translations Translation files are currently created manually in the -[i18next JSON format](https://www.i18next.com/misc/json-format). You can find -existing translations in [`public/locales`](public/locales). The +[i18next JSON format](https://www.i18next.com/misc/json-format), with support +for [JSON5 syntax](https://spec.json5.org/). You can find existing translations +in [`public/locales`](public/locales). The [English (GB) translation](public/locales/en.json) is used as the fallback. Language files are named with diff --git a/package.json b/package.json index c69b961..7593c70 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "i18next": "^19.9.1", "i18next-browser-languagedetector": "^6.0.1", "i18next-http-backend": "^1.1.1", + "json5": "^2.2.0", "lodash-es": "^4.17.21", "lru-cache": "^6.0.0", "rc-menu": "^8.10.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 214223b..c40c5dc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,12 +16,14 @@ i18next: 19.9.1 i18next-browser-languagedetector: 6.0.1 i18next-http-backend: 1.1.1 + json5: 2.2.0 lodash-es: 4.17.21 lru-cache: 6.0.0 rc-menu: 8.10.6_react-dom@17.0.1+react@17.0.1 react: 17.0.1 react-chartjs-2: 2.11.1_6c446a34f83b2a92e3214f8b711c141a react-dom: 17.0.1_react@17.0.1 + react-file-drop: 3.1.2_react-dom@17.0.1+react@17.0.1 react-hotkeys: 2.0.0_react@17.0.1 react-i18next: 11.8.8_i18next@19.9.1+react@17.0.1 react-linkify: 1.0.0-alpha @@ -68,7 +70,6 @@ eslint-plugin-react-hooks: 4.2.0_eslint@7.21.0 eslint-plugin-tsdoc: 0.2.11 git-revision-webpack-plugin: 3.0.6 - react-file-drop: 3.1.2_react-dom@17.0.1+react@17.0.1 react-refresh: 0.9.0 react-scripts: 4.0.3_react@17.0.1+typescript@4.1.5 redux-devtools-extension: 2.13.8_redux@4.0.5 @@ -7877,7 +7878,6 @@ /json5/2.2.0: dependencies: minimist: 1.2.5 - dev: true engines: node: '>=6' hasBin: true @@ -8427,7 +8427,6 @@ resolution: integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== /minimist/1.2.5: - dev: true resolution: integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== /minipass-collect/1.0.2: @@ -10910,7 +10909,7 @@ prop-types: 15.7.2 react: 17.0.1 react-dom: 17.0.1_react@17.0.1 - dev: true + dev: false peerDependencies: react: ^16.13.1 react-dom: ^16.13.1 @@ -13877,6 +13876,7 @@ i18next: ^19.9.1 i18next-browser-languagedetector: ^6.0.1 i18next-http-backend: ^1.1.1 + json5: ^2.2.0 lodash-es: ^4.17.21 lru-cache: ^6.0.0 rc-menu: ^8.10.6 diff --git a/src/pages/settings/translations/analyseLangs.ts b/src/pages/settings/translations/analyseLangs.ts index 372ef3f..8167082 100644 --- a/src/pages/settings/translations/analyseLangs.ts +++ b/src/pages/settings/translations/analyseLangs.ts @@ -1,6 +1,8 @@ // Copyright (c) 2020-2021 Drew Lemmy // This file is part of KristWeb 2 under GPL-3.0. // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import JSON5 from "json5"; + import { Language, getLanguages } from "@utils/i18n"; export interface LangKeys { [key: string]: string } @@ -19,7 +21,10 @@ const res = await fetch(`/locales/${code}.json`); if (!res.ok) throw new Error(res.statusText); - const translation = await res.json(); + // Translations now use JSON5 to allow for comments, newlines, and basic + // syntax errors like trailing commas + const data = await res.text(); + const translation = JSON5.parse(data); const isObject = (val: any) => typeof val === "object" && !Array.isArray(val); const addDelimiter = (a: string, b: string) => a ? `${a}.${b}` : b; diff --git a/src/pages/settings/translations/importJSON.ts b/src/pages/settings/translations/importJSON.ts index fad2a97..62f8cd4 100644 --- a/src/pages/settings/translations/importJSON.ts +++ b/src/pages/settings/translations/importJSON.ts @@ -3,6 +3,7 @@ import { notification } from "antd"; import i18n from "@utils/i18n"; +import JSON5 from "json5"; import Debug from "debug"; const debug = Debug("kristweb:settings-import-json"); @@ -10,7 +11,7 @@ function importLanguage(contents: string) { try { // Parse the imported language - const resources = JSON.parse(contents); + const resources = JSON5.parse(contents); // Update the language i18n.addResourceBundle("und", "translation", resources, true, true); diff --git a/src/utils/i18n.ts b/src/utils/i18n.ts index ab5cee9..240d231 100644 --- a/src/utils/i18n.ts +++ b/src/utils/i18n.ts @@ -9,6 +9,7 @@ import Backend from "i18next-http-backend"; import LanguageDetector from "i18next-browser-languagedetector"; import { initReactI18next, TFunction } from "react-i18next"; +import JSON5 from "json5"; import languagesJson from "../__data__/languages.json"; import packageJson from "../../package.json"; @@ -75,7 +76,11 @@ backend: { queryStringParams: { v: packageJson.version }, - loadPath: "/locales/{{lng}}.json" + loadPath: "/locales/{{lng}}.json", + + // Translations now use JSON5 to allow for comments, newlines, and basic + // syntax errors like trailing commas + parse: JSON5.parse } }) .then(() => {