diff --git a/apps/client/src/services/i18n.spec.ts b/apps/client/src/services/i18n.spec.ts new file mode 100644 index 000000000..12d93e100 --- /dev/null +++ b/apps/client/src/services/i18n.spec.ts @@ -0,0 +1,21 @@ +import { LOCALES } from "@triliumnext/commons"; +import { readFileSync } from "fs"; +import { join, dirname } from "path"; +import { fileURLToPath } from "url"; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); + +describe("i18n", () => { + it("translations are valid JSON", () => { + for (const locale of LOCALES) { + if (locale.contentOnly) { + continue; + } + + const translationPath = join(scriptDir, "..", "translations", locale.id, "translation.json"); + const translationFile = readFileSync(translationPath, { encoding: "utf-8" }); + expect(() => JSON.parse(translationFile), `JSON error while parsing locale '${locale.id}' at "${translationPath}"`) + .not.toThrow(); + } + }); +}); diff --git a/apps/server/src/services/i18n.spec.ts b/apps/server/src/services/i18n.spec.ts index 0156b2e0c..050cc2cd1 100644 --- a/apps/server/src/services/i18n.spec.ts +++ b/apps/server/src/services/i18n.spec.ts @@ -1,30 +1,18 @@ -import { describe, expect, it } from "vitest"; -import * as i18n from "./i18n.js"; -import path from "path"; -import fs from "fs"; - -function checkTranslations(translationDir: string, translationFileName: string) { - const locales = i18n.getLocales(); - - for (const locale of locales) { - if (locale.contentOnly) { - continue; - } - - const translationPath = path.join(translationDir, locale.id, translationFileName); - const translationFile = fs.readFileSync(translationPath, { encoding: "utf-8" }); - expect(() => { - JSON.parse(translationFile); - }, `JSON error while parsing locale '${locale.id}' at "${translationPath}"`).not.toThrow(); - } -} +import { LOCALES } from "@triliumnext/commons"; +import { readFileSync } from "fs"; +import { join } from "path"; describe("i18n", () => { - it("frontend translations are valid JSON", () => { - checkTranslations("src/public/translations", "translation.json"); - }); + it("translations are valid JSON", () => { + for (const locale of LOCALES) { + if (locale.contentOnly) { + continue; + } - it("backend translations are valid JSON", () => { - checkTranslations("translations", "server.json"); + const translationPath = join(__dirname, "..", "assets", "translations", locale.id, "server.json"); + const translationFile = readFileSync(translationPath, { encoding: "utf-8" }); + expect(() => JSON.parse(translationFile), `JSON error while parsing locale '${locale.id}' at "${translationPath}"`) + .not.toThrow(); + } }); }); diff --git a/apps/server/src/services/i18n.ts b/apps/server/src/services/i18n.ts index db12fb626..a14385ab6 100644 --- a/apps/server/src/services/i18n.ts +++ b/apps/server/src/services/i18n.ts @@ -4,75 +4,7 @@ import sql_init from "./sql_init.js"; import { join } from "path"; import { getResourceDir } from "./utils.js"; import hidden_subtree from "./hidden_subtree.js"; -import type { Locale } from "@triliumnext/commons"; - -const LOCALES: Locale[] = [ - { - id: "en", - name: "English", - electronLocale: "en" - }, - { - id: "de", - name: "Deutsch", - electronLocale: "de" - }, - { - id: "es", - name: "Español", - electronLocale: "es" - }, - { - id: "fr", - name: "Français", - electronLocale: "fr" - }, - { - id: "cn", - name: "简体中文", - electronLocale: "zh_CN" - }, - { - id: "tw", - name: "繁體中文", - electronLocale: "zh_TW" - }, - { - id: "ro", - name: "Română", - electronLocale: "ro" - }, - - /* - * Right to left languages - * - * Currently they are only for setting the language of text notes. - */ - { // Arabic - id: "ar", - name: "اَلْعَرَبِيَّةُ", - rtl: true, - contentOnly: true - }, - { // Hebrew - id: "he", - name: "עברית", - rtl: true, - contentOnly: true - }, - { // Kurdish - id: "ku", - name: "کوردی", - rtl: true, - contentOnly: true - }, - { // Persian - id: "fa", - name: "فارسی", - rtl: true, - contentOnly: true - } -].sort((a, b) => a.name.localeCompare(b.name)); +import { LOCALES, type Locale } from "@triliumnext/commons"; export async function initializeTranslations() { const resourceDir = getResourceDir(); diff --git a/packages/commons/src/lib/i18n.ts b/packages/commons/src/lib/i18n.ts index 43fa82eb8..5967a1275 100644 --- a/packages/commons/src/lib/i18n.ts +++ b/packages/commons/src/lib/i18n.ts @@ -7,4 +7,72 @@ export interface Locale { contentOnly?: boolean; /** The value to pass to `--lang` for the Electron instance in order to set it as a locale. Not setting it will hide it from the list of supported locales. */ electronLocale?: string; -} \ No newline at end of file +} + +export const LOCALES: Locale[] = [ + { + id: "en", + name: "English", + electronLocale: "en" + }, + { + id: "de", + name: "Deutsch", + electronLocale: "de" + }, + { + id: "es", + name: "Español", + electronLocale: "es" + }, + { + id: "fr", + name: "Français", + electronLocale: "fr" + }, + { + id: "cn", + name: "简体中文", + electronLocale: "zh_CN" + }, + { + id: "tw", + name: "繁體中文", + electronLocale: "zh_TW" + }, + { + id: "ro", + name: "Română", + electronLocale: "ro" + }, + + /* + * Right to left languages + * + * Currently they are only for setting the language of text notes. + */ + { // Arabic + id: "ar", + name: "اَلْعَرَبِيَّةُ", + rtl: true, + contentOnly: true + }, + { // Hebrew + id: "he", + name: "עברית", + rtl: true, + contentOnly: true + }, + { // Kurdish + id: "ku", + name: "کوردی", + rtl: true, + contentOnly: true + }, + { // Persian + id: "fa", + name: "فارسی", + rtl: true, + contentOnly: true + } +].sort((a, b) => a.name.localeCompare(b.name));