forked from MapComplete/MapComplete
		
	Add some statistics on translations in script
This commit is contained in:
		
							parent
							
								
									796ee40f3b
								
							
						
					
					
						commit
						e22ce4d5b1
					
				
					 3 changed files with 135 additions and 73 deletions
				
			
		| 
						 | 
					@ -19,11 +19,97 @@ import Svg from "../../Svg";
 | 
				
			||||||
class TranslatorsPanelContent extends Combine {
 | 
					class TranslatorsPanelContent extends Combine {
 | 
				
			||||||
    constructor(layout: LayoutConfig, isTranslator: UIEventSource<boolean>) {
 | 
					    constructor(layout: LayoutConfig, isTranslator: UIEventSource<boolean>) {
 | 
				
			||||||
        const t = Translations.t.translations
 | 
					        const t = Translations.t.translations
 | 
				
			||||||
        const completeness = new Map<string, number>()
 | 
					
 | 
				
			||||||
 | 
					        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<boolean> }, 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<string, number>, untranslated: Map<string, string[]>, total: number} {
 | 
				
			||||||
        let total = 0
 | 
					        let total = 0
 | 
				
			||||||
 | 
					        const completeness = new Map<string, number>()
 | 
				
			||||||
        const untranslated = new Map<string, string[]>()
 | 
					        const untranslated = new Map<string, string[]>()
 | 
				
			||||||
        Utils.WalkObject(layout, (o, path) => {
 | 
					        Utils.WalkObject(layout, (o, path) => {
 | 
				
			||||||
            const translation = <Translation><any>o;
 | 
					            const translation = <Translation><any>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()) {
 | 
					            for (const lang of translation.SupportedLanguages()) {
 | 
				
			||||||
                completeness.set(lang, 1 + (completeness.get(lang) ?? 0))
 | 
					                completeness.set(lang, 1 + (completeness.get(lang) ?? 0))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -49,76 +135,6 @@ class TranslatorsPanelContent extends Combine {
 | 
				
			||||||
            return o instanceof Translation;
 | 
					            return o instanceof Translation;
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {completeness, untranslated, total}
 | 
				
			||||||
        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<boolean> }, 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")
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										8
									
								
								Utils.ts
									
										
									
									
									
								
							
							
						
						
									
										8
									
								
								Utils.ts
									
										
									
									
									
								
							| 
						 | 
					@ -184,6 +184,14 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
 | 
				
			||||||
        return str.substr(0, l - 3) + "...";
 | 
					        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[] {
 | 
					    public static Dedup(arr: string[]): string[] {
 | 
				
			||||||
        if (arr === undefined) {
 | 
					        if (arr === undefined) {
 | 
				
			||||||
            return undefined;
 | 
					            return undefined;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,8 @@ import * as fs from "fs";
 | 
				
			||||||
import {readFileSync, writeFileSync} from "fs";
 | 
					import {readFileSync, writeFileSync} from "fs";
 | 
				
			||||||
import {Utils} from "../Utils";
 | 
					import {Utils} from "../Utils";
 | 
				
			||||||
import ScriptUtils from "./ScriptUtils";
 | 
					import ScriptUtils from "./ScriptUtils";
 | 
				
			||||||
 | 
					import {AllKnownLayouts} from "../Customizations/AllKnownLayouts";
 | 
				
			||||||
 | 
					import TranslatorsPanel from "../UI/BigComponents/TranslatorsPanel";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const knownLanguages = ["en", "nl", "de", "fr", "es", "gl", "ca"];
 | 
					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 l2 = generateTranslationsObjectFrom(ScriptUtils.getThemeFiles().filter(th => th.parsed.mustHaveLanguage === undefined), "themes")
 | 
				
			||||||
const l3 = generateTranslationsObjectFrom([{path: questionsPath, parsed: questionsParsed}], "shared-questions")
 | 
					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()
 | 
					usedLanguages.sort()
 | 
				
			||||||
fs.writeFileSync("./assets/generated/used_languages.json", JSON.stringify({languages: usedLanguages}))
 | 
					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/layers").validateStrict("layers")
 | 
				
			||||||
TranslationPart.fromDirectory("./langs/themes").validateStrict("themes")
 | 
					TranslationPart.fromDirectory("./langs/themes").validateStrict("themes")
 | 
				
			||||||
TranslationPart.fromDirectory("./langs/shared-questions").validateStrict("shared-questions")
 | 
					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<string, number[]>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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(""))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue