mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-23 04:34:15 -04:00
(trans) Get list of expected translations from _base
This includes a series of if statements based on the config to attempt to exclude translations that are in _base but shouldn't be on the current language version.
This commit is contained in:
parent
0bef21dc25
commit
ee240c43a1
@ -1,165 +0,0 @@
|
||||
export default [
|
||||
'contact.language',
|
||||
'confirm.save',
|
||||
'confirm.dismiss',
|
||||
'calendar.events.mspec_lesbian_day',
|
||||
'calendar.events.mspec_lesbian_week',
|
||||
'calendar.events.mspec_gay_day',
|
||||
'calendar.events.mspec_gay_week',
|
||||
'calendar.events.masc_lesbian_day',
|
||||
'calendar.events.masc_lesbian_week',
|
||||
'user.login.domainPlaceholder',
|
||||
'user.login.deprecated',
|
||||
'user.login.depreciationNotice',
|
||||
'profile.flagsAsterisk',
|
||||
'profile.verifiedLinks.header',
|
||||
'profile.verifiedLinks.info',
|
||||
'sources.submit.spoiler',
|
||||
'translationMode.header',
|
||||
'translationMode.action',
|
||||
'translationMode.welcome',
|
||||
'translationMode.logIn',
|
||||
'translationMode.changes',
|
||||
'translationMode.commit',
|
||||
'translationMode.revert',
|
||||
'translationMode.pause',
|
||||
'contact.contribute.header',
|
||||
'contact.contribute.intro',
|
||||
'contact.contribute.entries.header',
|
||||
'contact.contribute.entries.description',
|
||||
'contact.contribute.translations.header',
|
||||
'contact.contribute.translations.description',
|
||||
'contact.contribute.version.header',
|
||||
'contact.contribute.version.description',
|
||||
'contact.contribute.technical.header',
|
||||
'contact.contribute.technical.description',
|
||||
'contact.contribute.technical.footer',
|
||||
'footer.stats.header',
|
||||
'footer.stats.overall',
|
||||
'footer.stats.current',
|
||||
'footer.stats.keys.users',
|
||||
'footer.stats.keys.cards',
|
||||
'footer.stats.keys.visitors',
|
||||
'footer.stats.keys.pageviews',
|
||||
'footer.stats.keys.realTimeVisitors',
|
||||
'footer.stats.keys.visitDuration',
|
||||
'footer.stats.keys.uptime',
|
||||
'footer.stats.keys.responseTime',
|
||||
'footer.using',
|
||||
'footer.version',
|
||||
'privacy.header',
|
||||
'user.avatar.failed',
|
||||
'user.qr.header',
|
||||
'user.qr.download',
|
||||
'footer.stats.month',
|
||||
'profile.wordsColumnHeader',
|
||||
'profile.opinions.header',
|
||||
'profile.opinions.description',
|
||||
'profile.opinions.colours._',
|
||||
'profile.opinions.colours.pink',
|
||||
'profile.opinions.colours.red',
|
||||
'profile.opinions.colours.orange',
|
||||
'profile.opinions.colours.green',
|
||||
'profile.opinions.colours.blue',
|
||||
'profile.opinions.colours.grey',
|
||||
'profile.opinions.styles._',
|
||||
'profile.opinions.styles.bold',
|
||||
'profile.opinions.styles.italics',
|
||||
'profile.opinions.styles.small',
|
||||
'profile.opinions.validation.missingIcon',
|
||||
'profile.opinions.validation.missingDescription',
|
||||
'profile.opinions.validation.duplicateIcon',
|
||||
'profile.opinions.validation.duplicateDescription',
|
||||
'profile.opinions.validation.invalidOpinion',
|
||||
'profile.opinions.custom',
|
||||
'mode.reducedColours',
|
||||
'crud.loadAll',
|
||||
'crud.validation.genericForm',
|
||||
'crud.validation.listMaxLength',
|
||||
'profile.circles.header',
|
||||
'profile.circles.info',
|
||||
'profile.circles.relationship',
|
||||
'profile.circles.mutual',
|
||||
'profile.circles.yourMentions.header',
|
||||
'profile.circles.yourMentions.description',
|
||||
'profile.circles.validation.userNotFound',
|
||||
'profile.circles.validation.required',
|
||||
'profile.timezone.header',
|
||||
'profile.timezone.placeholder',
|
||||
'profile.timezone.info',
|
||||
'profile.timezone.detect',
|
||||
'profile.timezone.publishArea',
|
||||
'profile.timezone.publishLocation',
|
||||
'profile.timezone.time',
|
||||
'profile.timezone.approximate',
|
||||
'profile.timezone.areas.Africa',
|
||||
'profile.timezone.areas.America',
|
||||
'profile.timezone.areas.Antarctica',
|
||||
'profile.timezone.areas.Arctic',
|
||||
'profile.timezone.areas.Asia',
|
||||
'profile.timezone.areas.Atlantic',
|
||||
'profile.timezone.areas.Australia',
|
||||
'profile.timezone.areas.Europe',
|
||||
'profile.timezone.areas.Indian',
|
||||
'profile.timezone.areas.Pacific',
|
||||
'profile.sensitive.header',
|
||||
'profile.sensitive.info',
|
||||
'profile.sensitive.display',
|
||||
'profile.sensitive.hide',
|
||||
'profile.sensitive.email.subject',
|
||||
'profile.sensitive.email.content',
|
||||
'crud.validation.invalidLink',
|
||||
'profile.flagsCustomForm.label',
|
||||
'profile.flagsCustomForm.description',
|
||||
'profile.flagsCustomForm.link',
|
||||
'profile.flagsCustomForm.alt',
|
||||
'profile.flagsCustomForm.altExample',
|
||||
'crud.alt',
|
||||
'profile.circles.removeSelf.action',
|
||||
'profile.circles.removeSelf.confirm',
|
||||
'calendar.events.aplatonic_visibility_day',
|
||||
'calendar.events.aromantic_visibility_day',
|
||||
'mode.accessibility',
|
||||
'mode.reducedItems',
|
||||
'user.socialLookup',
|
||||
'user.socialLookupWhy',
|
||||
'footer.source',
|
||||
'error.invalidImage',
|
||||
'profile.banner',
|
||||
'profile.example',
|
||||
'calendar.onlyFirstDays',
|
||||
'calendar.start',
|
||||
'calendar.events.nonmonogamy_visibility_day',
|
||||
'profile.backup.header',
|
||||
'profile.backup.export.action',
|
||||
'profile.backup.export.success',
|
||||
'profile.backup.import.action',
|
||||
'profile.backup.import.success',
|
||||
'profile.backup.error.signature',
|
||||
'profile.share.customise',
|
||||
'profile.share.local',
|
||||
'profile.share.atAlternative',
|
||||
'profile.share.pronouns',
|
||||
'profile.expendableList.more',
|
||||
'profile.expendableList.show',
|
||||
'profile.opinions.colours.yellow',
|
||||
'profile.opinions.colours.teal',
|
||||
'profile.opinions.colours.purple',
|
||||
'profile.opinions.colours.brown',
|
||||
'profile.markdown.enable',
|
||||
'profile.markdown.features',
|
||||
'profile.markdown.examples',
|
||||
'profile.calendar.header',
|
||||
'profile.calendar.info',
|
||||
'profile.calendar.customEvents.header',
|
||||
'profile.calendar.customEvents.disclaimer',
|
||||
'profile.calendar.customEvents.name',
|
||||
'profile.calendar.customEvents.month',
|
||||
'profile.calendar.customEvents.day',
|
||||
'profile.calendar.customEvents.comment',
|
||||
'profile.calendar.customEvents.validation.missingName',
|
||||
'profile.calendar.customEvents.validation.missingDate',
|
||||
'profile.calendar.customEvents.validation.invalidDate',
|
||||
'profile.calendar.publicEvents.header',
|
||||
'profile.pronunciation.ipa',
|
||||
];
|
@ -23,7 +23,7 @@ export const deepGet = (obj, path) => {
|
||||
let value = obj;
|
||||
for (const part of path.split('.')) {
|
||||
value = value[part];
|
||||
if (value === undefined) {
|
||||
if (value === undefined || value === null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -31,6 +31,18 @@ export const deepGet = (obj, path) => {
|
||||
return value;
|
||||
};
|
||||
|
||||
export function* deepListKeys(obj) {
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (value instanceof Object && !Array.isArray(value)) {
|
||||
for (const subkey of deepListKeys(value)) {
|
||||
yield `${key}.${subkey}`;
|
||||
}
|
||||
} else {
|
||||
yield key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const head = ({ title, description, banner, noindex = false, keywords }) => {
|
||||
const meta = { meta: [] };
|
||||
|
||||
|
92
src/missingTranslations.js
Normal file
92
src/missingTranslations.js
Normal file
@ -0,0 +1,92 @@
|
||||
import { deepGet, deepListKeys } from './helpers.js';
|
||||
|
||||
export function listMissingTranslations(translations, baseTranslations, config) {
|
||||
const expectedTranslations = [...deepListKeys(baseTranslations)];
|
||||
|
||||
return expectedTranslations.filter((k) => {
|
||||
function keyMatches(...pats) {
|
||||
for (const pat of pats) {
|
||||
if (pat.endsWith('.')) {
|
||||
if (k.startsWith(pat)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (k === pat) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function has(k) {
|
||||
return deepGet(translations, k) !== undefined;
|
||||
}
|
||||
|
||||
if (has(k)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// FAQ entries are fully customizable for a language version.
|
||||
if (keyMatches('faq.')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// optional keys
|
||||
if (keyMatches(
|
||||
'home.welcome',
|
||||
'contact.faq',
|
||||
'contact.technical',
|
||||
'contact.hate',
|
||||
'contact.team.extra',
|
||||
'contact.team.join.',
|
||||
'user.login.help',
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!has('home.welcome') && keyMatches('home.intro')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.pronouns.enabled && keyMatches(
|
||||
'pronouns.',
|
||||
'home.header',
|
||||
'home.headerLong',
|
||||
'home.pronouns',
|
||||
'home.generator.',
|
||||
'profile.pronouns',
|
||||
'profile.pronounsInfo',
|
||||
'profile.pronounsNotFound',
|
||||
'profile.share.pronouns',
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.pronouns.comprehensive && keyMatches('pronouns.comprehensive.')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.links.enabled && keyMatches('links.')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.sources.enabled && keyMatches('sources.')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.nouns.enabled && keyMatches('nouns.')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.terminology.enabled && keyMatches('terminology.')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.calendar?.enabled && keyMatches('calendar.')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
@ -2,8 +2,7 @@ import { decodeTime, ulid } from 'ulid';
|
||||
import mailer from './mailer.js';
|
||||
import Plausible from 'plausible-api';
|
||||
import fetch from 'node-fetch';
|
||||
import expectedTranslations from '../locale/expectedTranslations.js';
|
||||
import { deepGet } from './helpers.js';
|
||||
import { listMissingTranslations } from './missingTranslations.js';
|
||||
import fs from 'fs';
|
||||
import Suml from 'suml';
|
||||
|
||||
@ -124,13 +123,16 @@ export const calculateStats = async (db, allLocales, projectDir) => {
|
||||
},
|
||||
});
|
||||
|
||||
const baseTranslations = new Suml().parse(fs.readFileSync(`${projectDir}/locale/_base/translations.suml`).toString());
|
||||
|
||||
for (const locale in allLocales) {
|
||||
if (!allLocales.hasOwnProperty(locale)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const translations = new Suml().parse(fs.readFileSync(`${projectDir}/locale/${locale}/translations.suml`).toString());
|
||||
const missingTranslations = expectedTranslations.filter((key) => deepGet(translations, key) === undefined).length;
|
||||
const config = new Suml().parse(fs.readFileSync(`${projectDir}/locale/${locale}/config.suml`).toString());
|
||||
const missingTranslations = listMissingTranslations(translations, baseTranslations, config).length;
|
||||
|
||||
stats.push({
|
||||
locale,
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { deepGet } from './helpers.js';
|
||||
import config from '../data/config.suml';
|
||||
import translations from '../data/translations.suml';
|
||||
import baseTranslations from '../locale/_base/translations.suml';
|
||||
import expectedTranslations from '../locale/expectedTranslations.js';
|
||||
import { listMissingTranslations } from './missingTranslations.js';
|
||||
|
||||
class Translator {
|
||||
constructor(translations, baseTranslations, expectedTranslations) {
|
||||
constructor(translations, baseTranslations, config) {
|
||||
this.translations = translations;
|
||||
this.baseTranslations = baseTranslations;
|
||||
this.expectedTranslations = expectedTranslations;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
translate(key, params = {}, warn = false) {
|
||||
@ -54,8 +55,8 @@ class Translator {
|
||||
}
|
||||
|
||||
listMissingTranslations() {
|
||||
return this.expectedTranslations.filter((k) => !this.has(k));
|
||||
return listMissingTranslations(this.translations, this.baseTranslations, this.config);
|
||||
}
|
||||
}
|
||||
|
||||
export default new Translator(translations, baseTranslations, expectedTranslations);
|
||||
export default new Translator(translations, baseTranslations, config);
|
||||
|
3
test/fixtures/translations.js
vendored
3
test/fixtures/translations.js
vendored
@ -9,4 +9,7 @@ export const mockTranslations = (translations) => {
|
||||
jest.unstable_mockModule(`${__dirname}/../../locale/_base/translations.suml`, () => {
|
||||
return { default: {} };
|
||||
});
|
||||
jest.unstable_mockModule(`${__dirname}/../../data/config.suml`, () => {
|
||||
return { default: {} };
|
||||
});
|
||||
};
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { expect, test } from '@jest/globals';
|
||||
|
||||
import { loadSumlFromBase } from '../server/loader.js';
|
||||
import { deepGet } from '../src/helpers.js';
|
||||
import expectedTranslations from '../locale/expectedTranslations.js';
|
||||
import locales from '../locale/locales.js';
|
||||
|
||||
const baseTranslations = loadSumlFromBase('locale/_base/translations');
|
||||
@ -59,9 +57,4 @@ test.each(locales)('translations of $code match schema of base translations', ({
|
||||
expect(translations).toMatchBaseTranslationSchema();
|
||||
});
|
||||
|
||||
test('expected translations are defined in base translations', () => {
|
||||
const undefinedTranslations = expectedTranslations.filter((key) => deepGet(baseTranslations, key) === undefined);
|
||||
expect(undefinedTranslations).toEqual([]);
|
||||
});
|
||||
|
||||
expect.extend({ toMatchBaseTranslationSchema: toMatchBaseTranslationsSchema });
|
||||
|
Loading…
x
Reference in New Issue
Block a user