summaryrefslogtreecommitdiffhomepage
path: root/gui/scripts
diff options
context:
space:
mode:
authorOskar Nyberg <oskar@mullvad.net>2021-10-25 11:39:08 +0200
committerOskar Nyberg <oskar@mullvad.net>2021-10-25 13:59:06 +0200
commita48db7565661d8fdf83f4c7bdc9e61109ad97e80 (patch)
tree79d829526d50c02ecf04708a458be847974a92e9 /gui/scripts
parent1061372fd225052c402dc830f27a38ee43b57f45 (diff)
downloadmullvadvpn-a48db7565661d8fdf83f4c7bdc9e61109ad97e80.tar.xz
mullvadvpn-a48db7565661d8fdf83f4c7bdc9e61109ad97e80.zip
Add script which verifies that format specifiers in translations
Diffstat (limited to 'gui/scripts')
-rw-r--r--gui/scripts/verify-format-specifiers.ts73
1 files changed, 73 insertions, 0 deletions
diff --git a/gui/scripts/verify-format-specifiers.ts b/gui/scripts/verify-format-specifiers.ts
new file mode 100644
index 0000000000..2d161548d5
--- /dev/null
+++ b/gui/scripts/verify-format-specifiers.ts
@@ -0,0 +1,73 @@
+import fs from 'fs';
+import { GetTextTranslation, po } from 'gettext-parser';
+import path from 'path';
+
+const LOCALES_DIR = path.join('..', 'locales');
+
+function getLocales(): string[] {
+ const localesContent = fs.readdirSync(LOCALES_DIR);
+ const localeDirectories = localesContent.filter((item) =>
+ fs.statSync(path.join(LOCALES_DIR, item)).isDirectory(),
+ );
+ return localeDirectories;
+}
+
+function parseTranslationsForLocale(locale: string): GetTextTranslation[] {
+ const poFileContents = fs.readFileSync(path.join(LOCALES_DIR, locale, 'messages.po'));
+ const contexts = po.parse(poFileContents).translations;
+
+ const translations = Object.values(contexts)
+ .flatMap((context) => Object.values(context))
+ .filter((translation) => translation.msgid !== '');
+
+ return translations;
+}
+
+function getFormatSpecifiers(text: string): string[] {
+ // Matches both %(name)s and %s.
+ return text.match(/%(\(.*?\))?[a-z]/g) ?? [];
+}
+
+function formatSpecifiersEquals(source: string[], translation: string[]): boolean {
+ const sortedTranslation = translation.sort();
+ return (
+ source.length === translation.length &&
+ source.sort().every((value, index) => value === sortedTranslation[index])
+ );
+}
+
+function checkTranslationImpl(msgid: string, msgstr: string): boolean {
+ const sourceFormatSpecifiers = getFormatSpecifiers(msgid);
+ const translationFormatSpecifiers = getFormatSpecifiers(msgstr);
+ return formatSpecifiersEquals(sourceFormatSpecifiers, translationFormatSpecifiers);
+}
+
+function checkTranslation(translation: GetTextTranslation): boolean {
+ return translation.msgstr
+ .map((msgstr) => {
+ // Make sure that the translation matches either the singular or plural.
+ const equal =
+ checkTranslationImpl(translation.msgid, msgstr) ||
+ (translation.msgid_plural && checkTranslationImpl(translation.msgid_plural, msgstr));
+
+ if (!equal) {
+ console.error(`Error in "${translation.msgid}", "${msgstr}"`);
+ }
+
+ return equal;
+ })
+ .every((result) => result);
+}
+
+const isCorrect = getLocales()
+ .map(parseTranslationsForLocale)
+ // Map first to output all errors
+ .map((translations) => translations.every(checkTranslation))
+ .every((result) => result);
+
+if (isCorrect) {
+ console.log('Looks good!');
+} else {
+ console.error('See above errors');
+ process.exit(1);
+}