Browse Source

fix: default untranslated keys to English if available

bertyhell/feature/translations-extraction-script
Bert Verhelst 3 years ago
parent
commit
188ac58a22
  1. 4
      extra/extract-translations.js
  2. 3
      extra/update-language-files/.gitignore
  3. 86
      extra/update-language-files/index.js
  4. 12
      extra/update-language-files/package.json
  5. 73
      package-lock.json
  6. 2
      package.json
  7. 18
      src/components/NotificationDialog.vue
  8. 126
      src/components/notifications/index.js
  9. 2
      src/languages/README.md

4
extra/extract-translations.js

@ -61,7 +61,7 @@ async function extractTranslations() {
for (let extractedTranslation of englishExtracted) {
for (let langDict of Object.values(languageList)) {
if (!Object.keys(langDict).includes(extractedTranslation)) {
langDict[extractedTranslation] = extractedTranslation;
langDict[extractedTranslation] = en[extractedTranslation] || extractedTranslation;
}
}
}
@ -78,6 +78,7 @@ async function extractTranslations() {
}
}
// Write the translation string json back to files
for (let langName of Object.keys(languageList)) {
const translationsString = JSON5.stringify(languageList[langName], {
quote: "\"",
@ -87,6 +88,7 @@ async function extractTranslations() {
await fs.writeFile(`./src/languages/${_.kebabCase(langName)}.js`, `export default ${translationsString};\n`);
}
// Output warnings if there are any
if (warnings.length) {
console.log("Extraction successful with warnings: \n\t" + warnings.join("\n\t"));
} else {

3
extra/update-language-files/.gitignore

@ -1,3 +0,0 @@
package-lock.json
test.js
languages/

86
extra/update-language-files/index.js

@ -1,86 +0,0 @@
// Need to use ES6 to read language files
import fs from "fs";
import path from "path";
import util from "util";
// https://stackoverflow.com/questions/13786160/copy-folder-recursively-in-node-js
/**
* Look ma, it's cp -R.
* @param {string} src The path to the thing to copy.
* @param {string} dest The path to the new copy.
*/
const copyRecursiveSync = function (src, dest) {
let exists = fs.existsSync(src);
let stats = exists && fs.statSync(src);
let isDirectory = exists && stats.isDirectory();
if (isDirectory) {
fs.mkdirSync(dest);
fs.readdirSync(src).forEach(function (childItemName) {
copyRecursiveSync(path.join(src, childItemName),
path.join(dest, childItemName));
});
} else {
fs.copyFileSync(src, dest);
}
};
console.log("Arguments:", process.argv);
const baseLangCode = process.argv[2] || "en";
console.log("Base Lang: " + baseLangCode);
if (fs.existsSync("./languages")) {
fs.rmdirSync("./languages", { recursive: true });
}
copyRecursiveSync("../../src/languages", "./languages");
const en = (await import("./languages/en.js")).default;
const baseLang = (await import(`./languages/${baseLangCode}.js`)).default;
const files = fs.readdirSync("./languages");
console.log("Files:", files);
for (const file of files) {
if (!file.endsWith(".js")) {
console.log("Skipping " + file);
continue;
}
console.log("Processing " + file);
const lang = await import("./languages/" + file);
let obj;
if (lang.default) {
obj = lang.default;
} else {
console.log("Empty file");
obj = {
languageName: "<Your Language name in your language (not in English)>"
};
}
// En first
for (const key in en) {
if (! obj[key]) {
obj[key] = en[key];
}
}
if (baseLang !== en) {
// Base second
for (const key in baseLang) {
if (! obj[key]) {
obj[key] = key;
}
}
}
const code = "export default " + util.inspect(obj, {
depth: null,
});
fs.writeFileSync(`../../src/languages/${file}`, code);
}
fs.rmdirSync("./languages", { recursive: true });
console.log("Done. Fixing formatting by ESLint...");

12
extra/update-language-files/package.json

@ -1,12 +0,0 @@
{
"name": "update-language-files",
"type": "module",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

73
package-lock.json

@ -1,12 +1,12 @@
{
"name": "uptime-kuma",
"version": "1.9.2",
"version": "1.10.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "uptime-kuma",
"version": "1.9.2",
"version": "1.10.0",
"license": "MIT",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "~1.2.36",
@ -76,8 +76,11 @@
"dns2": "~2.0.1",
"eslint": "~7.32.0",
"eslint-plugin-vue": "~7.18.0",
"find-in-files": "^0.5.0",
"jest": "~27.2.4",
"jest-puppeteer": "~6.0.0",
"json5": "^2.2.0",
"lodash": "^4.17.21",
"puppeteer": "~10.4.0",
"sass": "~1.42.1",
"stylelint": "~13.13.1",
@ -6238,6 +6241,15 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"node_modules/find": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/find/-/find-0.1.7.tgz",
"integrity": "sha1-yGyHrxqxjyIrvjjeyGy8dg0Wpvs=",
"dev": true,
"dependencies": {
"traverse-chain": "~0.1.0"
}
},
"node_modules/find-file-up": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-0.1.3.tgz",
@ -6251,6 +6263,16 @@
"node": ">=0.10.0"
}
},
"node_modules/find-in-files": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/find-in-files/-/find-in-files-0.5.0.tgz",
"integrity": "sha512-VraTc6HdtdSHmAp0yJpAy20yPttGKzyBWc7b7FPnnsX9TOgmKx0g9xajizpF/iuu4IvNK4TP0SpyBT9zAlwG+g==",
"dev": true,
"dependencies": {
"find": "^0.1.5",
"q": "^1.0.1"
}
},
"node_modules/find-pkg": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-0.1.2.tgz",
@ -11591,6 +11613,16 @@
}
}
},
"node_modules/q": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=",
"dev": true,
"engines": {
"node": ">=0.6.0",
"teleport": ">=0.2.0"
}
},
"node_modules/qrcode": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.4.4.tgz",
@ -13745,6 +13777,12 @@
"node": ">=8"
}
},
"node_modules/traverse-chain": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz",
"integrity": "sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE=",
"dev": true
},
"node_modules/tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
@ -19500,6 +19538,15 @@
}
}
},
"find": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/find/-/find-0.1.7.tgz",
"integrity": "sha1-yGyHrxqxjyIrvjjeyGy8dg0Wpvs=",
"dev": true,
"requires": {
"traverse-chain": "~0.1.0"
}
},
"find-file-up": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-0.1.3.tgz",
@ -19510,6 +19557,16 @@
"resolve-dir": "^0.1.0"
}
},
"find-in-files": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/find-in-files/-/find-in-files-0.5.0.tgz",
"integrity": "sha512-VraTc6HdtdSHmAp0yJpAy20yPttGKzyBWc7b7FPnnsX9TOgmKx0g9xajizpF/iuu4IvNK4TP0SpyBT9zAlwG+g==",
"dev": true,
"requires": {
"find": "^0.1.5",
"q": "^1.0.1"
}
},
"find-pkg": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-0.1.2.tgz",
@ -23526,6 +23583,12 @@
}
}
},
"q": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=",
"dev": true
},
"qrcode": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.4.4.tgz",
@ -25200,6 +25263,12 @@
"punycode": "^2.1.1"
}
},
"traverse-chain": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz",
"integrity": "sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE=",
"dev": true
},
"tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",

2
package.json

@ -48,8 +48,6 @@
"test-install-script-ubuntu1604": "npm run compile-install-script && docker build --progress plain -f test/test_install_script/ubuntu1604.dockerfile .",
"test-nodejs16": "docker build --progress plain -f test/ubuntu-nodejs16.dockerfile .",
"simple-dns-server": "node extra/simple-dns-server.js",
"update-language-files-with-base-lang": "cd extra/update-language-files && node index.js %npm_config_base_lang% && eslint ../../src/languages/**.js --fix",
"update-language-files": "cd extra/update-language-files && node index.js && eslint ../../src/languages/**.js --fix",
"extract-translations": "node extra/extract-translations.js"
},
"dependencies": {

18
src/components/NotificationDialog.vue

@ -13,7 +13,7 @@
<div class="mb-3">
<label for="notification-type" class="form-label">{{ $t("Notification Type") }}</label>
<select id="notification-type" v-model="notification.type" class="form-select">
<option v-for="type in notificationTypes" :key="type" :value="type">{{ $t(type) }}</option>
<option v-for="type in notificationTypes" :key="type" :value="type">{{ translatedType }}</option>
</select>
</div>
@ -69,10 +69,9 @@
<script lang="ts">
import { Modal } from "bootstrap";
import { ucfirst } from "../util.ts";
import Confirm from "./Confirm.vue";
import NotificationFormList from "./notifications";
import getNotificationFormList from "./notifications";
export default {
components: {
@ -81,14 +80,16 @@ export default {
props: {},
emits: ["added"],
data() {
const notificationTypeObjects = getNotificationFormList(this.$t);
return {
model: null,
processing: false,
id: null,
notificationTypes: Object.keys(NotificationFormList),
notificationTypeObjects: notificationTypeObjects,
notificationTypes: Object.keys(notificationTypeObjects),
notification: {
name: "",
/** @type { null | keyof NotificationFormList } */
/** @type { null | keyof getNotificationFormList() } */
type: null,
isDefault: false,
// Do not set default value here, please scroll to show()
@ -101,8 +102,11 @@ export default {
if (!this.notification.type) {
return null;
}
return NotificationFormList[this.notification.type];
return this.notificationTypeObjects[this.notification.type].component;
},
translatedType() {
return this.notificationTypeObjects[this.type].label;
}
},
watch: {
@ -192,7 +196,7 @@ export default {
});
},
/**
* @param {keyof NotificationFormList} notificationKey
* @param {keyof getNotificationFormList()} notificationKey
* @return {string}
*/
getUniqueDefaultName(notificationKey) {

126
src/components/notifications/index.js

@ -28,31 +28,103 @@ import Bark from "./Bark.vue";
*
* @type { Record<string, any> }
*/
const NotificationFormList = {
"telegram": Telegram,
"webhook": Webhook,
"smtp": STMP,
"discord": Discord,
"teams": Teams,
"signal": Signal,
"gotify": Gotify,
"slack": Slack,
"rocket.chat": RocketChat,
"pushover": Pushover,
"pushy": Pushy,
"octopush": Octopush,
"promosms": PromoSMS,
"clicksendsms": ClickSendSMS,
"lunasea": LunaSea,
"Feishu": Feishu,
"AliyunSMS": AliyunSMS,
"apprise": Apprise,
"pushbullet": Pushbullet,
"line": Line,
"mattermost": Mattermost,
"matrix": Matrix,
"DingDing": DingDing,
"Bark": Bark
};
const getNotificationFormList = ($t) => ({
"telegram": {
component: Telegram,
label: $t("telegram")
},
"webhook": {
component: Webhook,
label: $t("webhook"),
},
"smtp": {
component: STMP,
label: $t("smtp"),
},
"discord": {
component: Discord,
label: $t("discord"),
},
"teams": {
component: Teams,
label: $t("teams"),
},
"signal": {
component: Signal,
label: $t("signal"),
},
"gotify": {
component: Gotify,
label: $t("gotify"),
},
"slack": {
component: Slack,
label: $t("slack"),
},
"rocket.chat": {
component: RocketChat,
label: $t("rocket.chat"),
},
"pushover": {
component: Pushover,
label: $t("pushover"),
},
"pushy": {
component: Pushy,
label: $t("pushy"),
},
"octopush": {
component: Octopush,
label: $t("octopush"),
},
"promosms": {
component: PromoSMS,
label: $t("promosms"),
},
"clicksendsms": {
component: ClickSendSMS,
label: $t("clicksendsms"),
},
"lunasea": {
component: LunaSea,
label: $t("lunasea"),
},
"Feishu": {
component: Feishu,
label: $t("Feishu"),
},
"AliyunSMS": {
component: AliyunSMS,
label: $t("AliyunSMS"),
},
"apprise": {
component: Apprise,
label: $t("apprise"),
},
"pushbullet": {
component: Pushbullet,
label: $t("pushbullet"),
},
"line": {
component: Line,
label: $t("line"),
},
"mattermost": {
component: Mattermost,
label: $t("mattermost"),
},
"matrix": {
component: Matrix,
label: $t("matrix"),
},
"DingDing": {
component: DingDing,
label: $t("DingDing"),
},
"Bark": {
component: Bark,
label: $t("Bark"),
}
});
export default NotificationFormList;
export default getNotificationFormList;

2
src/languages/README.md

@ -2,7 +2,7 @@
1. Fork this repo.
2. Create a language file (e.g. `zh-TW.js`). The filename must be ISO language code: http://www.lingoes.net/en/translator/langcode.htm
3. Run `npm run update-language-files`. You can also use this command to check if there are new strings to translate for your language.
3. Run `npm run extract-translations`. This will add all translatable strings to your language file. Any untranslated keys will default to English.
4. Your language file should be filled in. You can translate now.
5. Translate `src/pages/Settings.vue` (search for a `Confirm` component with `rel="confirmDisableAuth"`).
6. Import your language file in `src/i18n.js` and add it to `languageList` constant.

Loading…
Cancel
Save