diff --git a/UI/BigComponents/TranslatorsPanel.ts b/UI/BigComponents/TranslatorsPanel.ts index 87f6118fa..6d2c67f8a 100644 --- a/UI/BigComponents/TranslatorsPanel.ts +++ b/UI/BigComponents/TranslatorsPanel.ts @@ -19,11 +19,97 @@ import Svg from "../../Svg"; class TranslatorsPanelContent extends Combine { constructor(layout: LayoutConfig, isTranslator: UIEventSource) { const t = Translations.t.translations - const completeness = new Map() + + const {completeness, untranslated, total} = TranslatorsPanel.MissingTranslationsFor(layout) + + const seed = t.completeness + for (const ln of Array.from(completeness.keys())) { + if(ln === "*"){ + continue + } + if (seed.translations[ln] === undefined) { + seed.translations[ln] = seed.translations["en"] + } + } + + const completenessTr = {} + const completenessPercentage = {} + seed.SupportedLanguages().forEach(ln => { + completenessTr[ln] = ""+(completeness.get(ln) ?? 0) + completenessPercentage[ln] = ""+Math.round(100 * (completeness.get(ln) ?? 0) / total) + }) + + const missingTranslationsFor = (ln: string) => Utils.NoNull(untranslated.get(ln) ?? []) + .map(ctx => ctx.replace(/note_import_[a-zA-Z0-9_]*/, "note_import")) + .map(context => new Link(context, LinkToWeblate.hrefToWeblate(ln, context), true)) + + + // "translationCompleteness": "Translations for {theme} in {language} are at {percentage}: {translated} out of {total}", + const translated = seed.Subs({total, theme: layout.title, + percentage: new Translation(completenessPercentage), + translated: new Translation(completenessTr) + }) + + super([ + new Title( + Translations.t.translations.activateButton, + ), + new Toggle(t.isTranslator.SetClass("thanks block"), undefined, isTranslator), + t.help, + translated, + /*Disable button:*/ + new SubtleButton(undefined, t.deactivate) + .onClick(() => { + Locale.showLinkToWeblate.setData(false) + }), + + new VariableUiElement(Locale.language.map(ln => { + + const missing = missingTranslationsFor(ln) + if (missing.length === 0) { + return undefined + } + return new Toggleable( + new Title(Translations.t.translations.missing.Subs({count: missing.length})), + new Combine(missing).SetClass("flex flex-col") + ) + })) + ]) + + } + +} + +export default class TranslatorsPanel extends Toggle { + + + constructor(state: { layoutToUse: LayoutConfig, isTranslator: UIEventSource }, iconStyle?: string) { + const t = Translations.t.translations + super( + new Lazy(() => new TranslatorsPanelContent(state.layoutToUse, state.isTranslator) + ).SetClass("flex flex-col"), + new SubtleButton(Svg.translate_ui().SetStyle(iconStyle), t.activateButton).onClick(() => Locale.showLinkToWeblate.setData(true)), + Locale.showLinkToWeblate + ) + this.SetClass("hidden-on-mobile") + + } + + + public static MissingTranslationsFor(layout: LayoutConfig) : {completeness: Map, untranslated: Map, total: number} { let total = 0 + const completeness = new Map() const untranslated = new Map() Utils.WalkObject(layout, (o, path) => { const translation = o; + if(translation.translations["*"] !== undefined){ + return + } + if(translation.context === undefined || translation.context.indexOf(":") < 0){ + // no source given - lets ignore + return + } + for (const lang of translation.SupportedLanguages()) { completeness.set(lang, 1 + (completeness.get(lang) ?? 0)) } @@ -49,76 +135,6 @@ class TranslatorsPanelContent extends Combine { return o instanceof Translation; }) - - const seed = t.completeness - for (const ln of Array.from(completeness.keys())) { - if(ln === "*"){ - continue - } - if (seed.translations[ln] === undefined) { - seed.translations[ln] = seed.translations["en"] - } - } - - const completenessTr = {} - const completenessPercentage = {} - seed.SupportedLanguages().forEach(ln => { - completenessTr[ln] = ""+(completeness.get(ln) ?? 0) - completenessPercentage[ln] = ""+Math.round(100 * (completeness.get(ln) ?? 0) / total) - }) - - // "translationCompleteness": "Translations for {theme} in {language} are at {percentage}: {translated} out of {total}", - const translated = seed.Subs({total, theme: layout.title, - percentage: new Translation(completenessPercentage), - translated: new Translation(completenessTr) - }) - - const missingTranslationsFor = (ln: string) => Utils.NoNull(untranslated.get(ln) ?? []) - .filter(ctx => ctx.indexOf(':') > 0) - .map(ctx => ctx.replace(/note_import_[a-zA-Z0-9_]*/, "note_import")) - .map(context => new Link(context, LinkToWeblate.hrefToWeblate(ln, context), true)) - - const disable = new SubtleButton(undefined, t.deactivate) - .onClick(() => { - Locale.showLinkToWeblate.setData(false) - }) - - super([ - new Title( - Translations.t.translations.activateButton, - ), - new Toggle(t.isTranslator.SetClass("thanks block"), undefined, isTranslator), - t.help, - translated, - disable, - new VariableUiElement(Locale.language.map(ln => { - - const missing = missingTranslationsFor(ln) - if (missing.length === 0) { - return undefined - } - return new Toggleable( - new Title(Translations.t.translations.missing.Subs({count: missing.length})), - new Combine(missing).SetClass("flex flex-col") - ) - })) - ]) - - } -} - -export default class TranslatorsPanel extends Toggle { - - - constructor(state: { layoutToUse: LayoutConfig, isTranslator: UIEventSource }, iconStyle?: string) { - const t = Translations.t.translations - super( - new Lazy(() => new TranslatorsPanelContent(state.layoutToUse, state.isTranslator) - ).SetClass("flex flex-col"), - new SubtleButton(Svg.translate_ui().SetStyle(iconStyle), t.activateButton).onClick(() => Locale.showLinkToWeblate.setData(true)), - Locale.showLinkToWeblate - ) - this.SetClass("hidden-on-mobile") - + return {completeness, untranslated, total} } } diff --git a/Utils.ts b/Utils.ts index 1aa02822b..6d011ba6e 100644 --- a/Utils.ts +++ b/Utils.ts @@ -183,6 +183,14 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be } return str.substr(0, l - 3) + "..."; } + + public static FixedLength(str: string, l: number) { + str = Utils.EllipsesAfter(str, l) + while(str.length < l){ + str = " "+str + } + return str; + } public static Dedup(arr: string[]): string[] { if (arr === undefined) { diff --git a/scripts/generateTranslations.ts b/scripts/generateTranslations.ts index 8ee1fe36b..cc10944eb 100644 --- a/scripts/generateTranslations.ts +++ b/scripts/generateTranslations.ts @@ -2,6 +2,8 @@ import * as fs from "fs"; import {readFileSync, writeFileSync} from "fs"; import {Utils} from "../Utils"; import ScriptUtils from "./ScriptUtils"; +import {AllKnownLayouts} from "../Customizations/AllKnownLayouts"; +import TranslatorsPanel from "../UI/BigComponents/TranslatorsPanel"; const knownLanguages = ["en", "nl", "de", "fr", "es", "gl", "ca"]; @@ -534,7 +536,7 @@ const l1 = generateTranslationsObjectFrom(ScriptUtils.getLayerFiles(), "layers") const l2 = generateTranslationsObjectFrom(ScriptUtils.getThemeFiles().filter(th => th.parsed.mustHaveLanguage === undefined), "themes") const l3 = generateTranslationsObjectFrom([{path: questionsPath, parsed: questionsParsed}], "shared-questions") -const usedLanguages = Utils.Dedup(l1.concat(l2).concat(l3)).filter(v => v !== "*") +const usedLanguages: string[] = Utils.Dedup(l1.concat(l2).concat(l3)).filter(v => v !== "*") usedLanguages.sort() fs.writeFileSync("./assets/generated/used_languages.json", JSON.stringify({languages: usedLanguages})) @@ -555,3 +557,39 @@ TranslationPart.fromDirectory("./langs").validateStrict("./langs") TranslationPart.fromDirectory("./langs/layers").validateStrict("layers") TranslationPart.fromDirectory("./langs/themes").validateStrict("themes") TranslationPart.fromDirectory("./langs/shared-questions").validateStrict("shared-questions") + + +// Some statistics +console.log(Utils.FixedLength("",12)+" "+usedLanguages.map(l => Utils.FixedLength(l, 6)).join("")) +const all = new Map() + +usedLanguages.forEach(ln => all.set(ln, [])) + +for (const layoutId of Array.from(AllKnownLayouts.allKnownLayouts.keys())) { + const layout = AllKnownLayouts.allKnownLayouts.get(layoutId) + + const {completeness, total} = TranslatorsPanel.MissingTranslationsFor(layout) + process.stdout.write(Utils.FixedLength(layout.id, 12)+" ") + for (const language of usedLanguages) { + const compl = completeness.get(language) + all.get(language).push((compl ?? 0) / total) + if(compl === undefined){ + process.stdout.write(" ") + continue + } + const percentage = Math.round(100 * compl / total) + process.stdout.write(Utils.FixedLength(percentage+"%", 6)) + } + process.stdout.write("\n") +} + +process.stdout.write(Utils.FixedLength("average", 12)+" ") +for (const language of usedLanguages) { + const ratios = all.get(language) + let sum = 0 + ratios.forEach(x => sum += x) + const percentage = Math.round(100 * (sum / ratios.length)) + process.stdout.write(Utils.FixedLength(percentage+"% ", 6)) +} +process.stdout.write("\n") +console.log(Utils.FixedLength("",12)+" "+usedLanguages.map(l => Utils.FixedLength(l, 6)).join(""))