chore: create a testcase to detect basic missing translations (#6591)
This commit is contained in:
commit
85c278ece4
@ -14,25 +14,25 @@
|
||||
<option value="numeric">{{ $t("Telephone number") }}</option>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
<p><b>{{ $t("Alphanumeric (recommended)") }}:</b><br /> {{ $t("Alphanumeric string (max 11 alphanumeric characters). Recipients can not reply to the message.") }}</p>
|
||||
<p><b>{{ $t("Telephone number") }}:</b><br /> {{ $t("Numeric value (max 15 digits) with telephone number on international format without leading 00 (example UK number 07920 110 000 should be set as 447920110000). Recipients can reply to the message.") }}</p>
|
||||
<p><b>{{ $t("Alphanumeric (recommended)") }}:</b><br /> {{ $t("cellsyntOriginatortypeAlphanumeric") }}</p>
|
||||
<p><b>{{ $t("Telephone number") }}:</b><br /> {{ $t("cellsyntOriginatortypeNumeric") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="cellsynt-originator" class="form-label">{{ $t("Originator") }} <small>({{ $parent.notification.cellsyntOriginatortype === 'alpha' ? $t("max 11 alphanumeric characters") : $t("max 15 digits") }})</small></label>
|
||||
<input v-if="$parent.notification.cellsyntOriginatortype === 'alpha'" id="cellsynt-originator" v-model="$parent.notification.cellsyntOriginator" type="text" class="form-control" pattern="[a-zA-Z0-9\s]+" maxlength="11" required>
|
||||
<input v-else id="cellsynt-originator" v-model="$parent.notification.cellsyntOriginator" type="number" class="form-control" pattern="[0-9]+" maxlength="15" required>
|
||||
<div class="form-text"><p>{{ $t("Visible on recipient's mobile phone as originator of the message. Allowed values and function depends on parameter originatortype.") }}</p></div>
|
||||
<div class="form-text"><p>{{ $t("cellsyntOriginator") }}</p></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="cellsynt-destination" class="form-label">{{ $t("Destination") }}</label>
|
||||
<input id="cellsynt-destination" v-model="$parent.notification.cellsyntDestination" type="text" class="form-control" required>
|
||||
<div class="form-text"><p>{{ $t("Recipient's telephone number using international format with leading 00 followed by country code, e.g. 00447920110000 for the UK number 07920 110 000 (max 17 digits in total). Max 25000 comma separated recipients per HTTP request.") }}</p></div>
|
||||
<div class="form-text"><p>{{ $t("cellsyntDestination") }}</p></div>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input id="cellsynt-allow-long" v-model="$parent.notification.cellsyntAllowLongSMS" type="checkbox" class="form-check-input">
|
||||
<label for="cellsynt-allow-long" class="form-label">{{ $t("Allow Long SMS") }}</label>
|
||||
<div class="form-text">{{ $t("Split long messages into up to 6 parts. 153 x 6 = 918 characters.") }}</div>
|
||||
<div class="form-text">{{ $t("cellsyntSplitLongMessages") }}</div>
|
||||
</div>
|
||||
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
|
||||
<a href="https://www.cellsynt.com/en/" target="_blank">https://www.cellsynt.com/en/</a>
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="form-text">
|
||||
{{ $t("checkPrice", [$t("clicksendsms")]) }}
|
||||
{{ $t("checkPrice", ["clicksendsms"]) }}
|
||||
<a href="https://www.clicksend.com/us/pricing" target="_blank">https://clicksend.com/us/pricing</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<div class="mb-3">
|
||||
<label for="octopush-version" class="form-label">{{ $t("Octopush API Version") }}</label>
|
||||
<select id="octopush-version" v-model="$parent.notification.octopushVersion" class="form-select">
|
||||
<option value="2">{{ $t("octopush") }} ({{ $t("endpoint") }}: api.octopush.com)</option>
|
||||
<option value="2">{{ "octopush" }} ({{ $t("endpoint") }}: api.octopush.com)</option>
|
||||
<option value="1">{{ $t("Legacy Octopush-DM") }} ({{ $t("endpoint") }}: www.octopush-dm.com)</option>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
<a href="https://docs.rocket.chat/guides/administration/administration/integrations" target="_blank">https://docs.rocket.chat/guides/administration/administration/integrations</a>
|
||||
</i18n-t>
|
||||
<p style="margin-top: 8px;">
|
||||
{{ $t("aboutChannelName", [$t("rocket.chat")]) }}
|
||||
{{ $t("aboutChannelName", ["rocket.chat"]) }}
|
||||
</p>
|
||||
<p style="margin-top: 8px;">
|
||||
{{ $t("aboutKumaURL") }}
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
<a href="https://api.slack.com/messaging/webhooks" target="_blank">https://api.slack.com/messaging/webhooks</a>
|
||||
</i18n-t>
|
||||
<p style="margin-top: 8px;">
|
||||
{{ $t("aboutChannelName", [$t("slack")]) }}
|
||||
{{ $t("aboutChannelName", ["slack"]) }}
|
||||
</p>
|
||||
<p style="margin-top: 8px;">
|
||||
{{ $t("aboutKumaURL") }}
|
||||
|
||||
@ -100,7 +100,7 @@
|
||||
</i18n-t>
|
||||
<i18n-t tag="p" keypath="disableauth.message2">
|
||||
<template #intendThirdPartyAuth>
|
||||
<strong>{{ $t('intend to implement third-party authentication') }}</strong>
|
||||
<strong>{{ $t('where you intend to implement third-party authentication') }}</strong>
|
||||
</template>
|
||||
</i18n-t>
|
||||
<p>{{ $t("Please use this option carefully!") }}</p>
|
||||
|
||||
@ -1174,6 +1174,7 @@
|
||||
"resendFromName": "From Name",
|
||||
"resendFromEmail": "From Email",
|
||||
"resendLeaveBlankForDefaultName": "leave blank for default name",
|
||||
"resendLeaveBlankForDefaultSubject": "Leave blank for default subject",
|
||||
"resendToEmail": "To Email",
|
||||
"resendSubject": "Subject",
|
||||
"pingCountLabel": "Max Packets",
|
||||
@ -1245,7 +1246,43 @@
|
||||
"minimumIntervalWarning": "Intervals below 20 seconds may result in poor performance.",
|
||||
"lowIntervalWarning": "Are you sure want to set the interval value below 20 seconds? Performance may be degraded, particularly if there are a large number of monitors.",
|
||||
"imageResetConfirmation": "Image reset to default",
|
||||
"mtls-auth-server-cert-label": "Cert",
|
||||
"mtls-auth-server-cert-placeholder": "Cert body",
|
||||
"mtls-auth-server-key-label": "Key",
|
||||
"mtls-auth-server-key-placeholder": "Key body",
|
||||
"mtls-auth-server-ca-label": "CA",
|
||||
"mtls-auth-server-ca-placeholder": "Server CA",
|
||||
"avgPing": "Avg Ping",
|
||||
"Uptime Kuma": "Uptime Kuma",
|
||||
"maxPing": "Max Ping",
|
||||
"minPing": "Min Ping"
|
||||
"minPing": "Min Ping",
|
||||
"Clear current filters": "Clear current filters",
|
||||
"Sort options": "Sort options",
|
||||
"Sort by status": "Sort by status",
|
||||
"Sort by name": "Sort by name",
|
||||
"Sort by uptime": "Sort by uptime",
|
||||
"Sort by certificate expiry": "Sort by certificate expiry",
|
||||
"Splunk Rest URL": "Splunk Rest URL",
|
||||
"Severity": "Severity",
|
||||
"SMSManager": "SMSManager",
|
||||
"Message Format": "Message Format",
|
||||
"smscTranslit": "smscTranslit",
|
||||
"promosms": "promosms",
|
||||
"Region": "Region",
|
||||
"PushDeer Server URL": "PushDeer Server URL",
|
||||
"To Number": "To Number",
|
||||
"GrafanaOncallURL": "Grafana Oncall URL",
|
||||
"Never": "Never",
|
||||
"Json Query": "Json Query",
|
||||
"System Service": "System Service",
|
||||
"SSL/TLS": "SSL/TLS",
|
||||
"playground": "playground",
|
||||
"Check Type": "Check Type",
|
||||
"Service Name": "Service Name",
|
||||
"GRPC Options": "GRPC Options",
|
||||
"Metadata": "Metadata",
|
||||
"End": "End",
|
||||
"Show this Maintenance Message on which Status Pages": "Show this Maintenance Message on which Status Pages",
|
||||
"Endpoint": "Endpoint",
|
||||
"Details": "Details"
|
||||
}
|
||||
|
||||
@ -1166,16 +1166,16 @@
|
||||
<template v-if="monitor.authMethod && monitor.authMethod !== null ">
|
||||
<template v-if="monitor.authMethod === 'mtls' ">
|
||||
<div class="my-3">
|
||||
<label for="tls-cert" class="form-label">{{ $t("Cert") }}</label>
|
||||
<textarea id="tls-cert" v-model="monitor.tlsCert" class="form-control" :placeholder="$t('Cert body')" required></textarea>
|
||||
<label for="tls-cert" class="form-label">{{ $t("mtls-auth-server-cert-label") }}</label>
|
||||
<textarea id="tls-cert" v-model="monitor.tlsCert" class="form-control" :placeholder="$t('mtls-auth-server-cert-placeholder')" required></textarea>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<label for="tls-key" class="form-label">{{ $t("Key") }}</label>
|
||||
<textarea id="tls-key" v-model="monitor.tlsKey" class="form-control" :placeholder="$t('Key body')" required></textarea>
|
||||
<label for="tls-key" class="form-label">{{ $t("mtls-auth-server-key-label") }}</label>
|
||||
<textarea id="tls-key" v-model="monitor.tlsKey" class="form-control" :placeholder="$t('mtls-auth-server-key-placeholder')" required></textarea>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<label for="tls-ca" class="form-label">{{ $t("CA") }}</label>
|
||||
<textarea id="tls-ca" v-model="monitor.tlsCa" class="form-control" :placeholder="$t('Server CA')"></textarea>
|
||||
<label for="tls-ca" class="form-label">{{ $t("mtls-auth-server-ca-label") }}</label>
|
||||
<textarea id="tls-ca" v-model="monitor.tlsCa" class="form-control" :placeholder="$t('mtls-auth-server-ca-placeholder')"></textarea>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="monitor.authMethod === 'oauth2-cc' ">
|
||||
@ -1447,17 +1447,6 @@ export default {
|
||||
return this.monitor.type === "ping" ? 60 : undefined;
|
||||
},
|
||||
|
||||
timeoutLabel() {
|
||||
return this.monitor.type === "ping" ? this.$t("pingTimeoutLabel") : this.$t("Request Timeout");
|
||||
},
|
||||
|
||||
timeoutDescription() {
|
||||
if (this.monitor.type === "ping") {
|
||||
return this.$t("pingTimeoutDescription");
|
||||
}
|
||||
return "";
|
||||
},
|
||||
|
||||
defaultFriendlyName() {
|
||||
if (this.monitor.hostname) {
|
||||
return this.monitor.hostname;
|
||||
|
||||
75
test/backend-test/check-translations.test.js
Normal file
75
test/backend-test/check-translations.test.js
Normal file
@ -0,0 +1,75 @@
|
||||
const { describe, it } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
/**
|
||||
* Recursively walks a directory and yields file paths.
|
||||
* @param {string} dir The directory to walk.
|
||||
* @yields {string} The path to a file.
|
||||
* @returns {Generator<string>} A generator that yields file paths.
|
||||
*/
|
||||
function* walk(dir) {
|
||||
const files = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const file of files) {
|
||||
if (file.isDirectory()) {
|
||||
yield* walk(path.join(dir, file.name));
|
||||
} else {
|
||||
yield path.join(dir, file.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe("Check Translations", () => {
|
||||
it("should not have missing translation keys", () => {
|
||||
const enTranslations = JSON.parse(fs.readFileSync("src/lang/en.json", "utf-8"));
|
||||
|
||||
// this is a resonably crude check, you can get around this trivially
|
||||
/// this check is just to save on maintainer energy to explain this on every review ^^
|
||||
const translationRegex = /\$t\(['"](?<key1>.*?)['"]\s*[,)]|i18n-t[^>]*\s+keypath="(?<key2>[^"]+)"/gd;
|
||||
|
||||
const missingKeys = [];
|
||||
|
||||
for (const filePath of walk("src")) {
|
||||
if (filePath.endsWith(".vue") || filePath.endsWith(".js")) {
|
||||
const lines = fs.readFileSync(filePath, "utf-8").split("\n");
|
||||
lines.forEach((line, lineNum) => {
|
||||
let match;
|
||||
while ((match = translationRegex.exec(line)) !== null) {
|
||||
const key = match.groups.key1 || match.groups.key2;
|
||||
if (key && !enTranslations[key]) {
|
||||
const [ start, end ] = match.groups.key1 ? match.indices.groups.key1 : match.indices.groups.key2;
|
||||
missingKeys.push({
|
||||
filePath,
|
||||
lineNum: lineNum + 1,
|
||||
key,
|
||||
line: line,
|
||||
start,
|
||||
end,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (missingKeys.length > 0) {
|
||||
let report = "Missing translation keys found:\n";
|
||||
missingKeys.forEach(({ filePath, lineNum, key, line, start, end }) => {
|
||||
report += `\nerror: Missing translation key: '${key}'`;
|
||||
report += `\n --> ${filePath}:${lineNum}:${start}`;
|
||||
report += "\n |";
|
||||
report += `\n${String(lineNum).padEnd(5)}| ${line}`;
|
||||
const arrow = " ".repeat(start) + "^".repeat(end - start);
|
||||
report += `\n | ${arrow} unrecognized translation key`;
|
||||
report += "\n |";
|
||||
report += `\n = note: please register the translation key '${key}' in en.json so that our awesome team of translators can translate them`;
|
||||
report += "\n = tip: if you want to contribute translations, please visit https://weblate.kuma.pet\n";
|
||||
});
|
||||
report += "\n===============================";
|
||||
const fileCount = new Set(missingKeys.map(item => item.filePath)).size;
|
||||
report += `\nFound a total of ${missingKeys.length} missing keys in ${fileCount} files.`;
|
||||
assert.fail(report);
|
||||
}
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user