Better translators panel

This commit is contained in:
pietervdvn 2022-04-15 00:10:04 +02:00
parent 063ccd2025
commit 7d7abc303b
3 changed files with 58 additions and 30 deletions

View file

@ -4,6 +4,7 @@ import Link from "./Link";
import Svg from "../../Svg"; import Svg from "../../Svg";
export default class LinkToWeblate extends VariableUiElement { export default class LinkToWeblate extends VariableUiElement {
private static URI: any;
constructor(context: string, availableTranslations: object) { constructor(context: string, availableTranslations: object) {
super( Locale.language.map(ln => { super( Locale.language.map(ln => {
if (Locale.showLinkToWeblate.data === false) { if (Locale.showLinkToWeblate.data === false) {
@ -36,4 +37,10 @@ export default class LinkToWeblate extends VariableUiElement {
const baseUrl = "https://hosted.weblate.org/translate/mapcomplete/" const baseUrl = "https://hosted.weblate.org/translate/mapcomplete/"
return baseUrl + category + "/" + language + "/?offset=1&q=context%3A%3D%22" + key + "%22" return baseUrl + category + "/" + language + "/?offset=1&q=context%3A%3D%22" + key + "%22"
} }
public static hrefToWeblateZen(language: string, category: string, searchKey: string): string{
const baseUrl = "https://hosted.weblate.org/zen/mapcomplete/"
// ?offset=1&q=+state%3A%3Ctranslated+context%3Acampersite&sort_by=-priority%2Cposition&checksum=
return baseUrl + category + "/" + language + "?offset=1&q=+state%3A%3Ctranslated+context%3A"+encodeURIComponent(searchKey)+"&sort_by=-priority%2Cposition&checksum="
}
} }

View file

@ -15,6 +15,8 @@ import {UIEventSource} from "../../Logic/UIEventSource";
import {SubtleButton} from "../Base/SubtleButton"; import {SubtleButton} from "../Base/SubtleButton";
import Svg from "../../Svg"; import Svg from "../../Svg";
import * as native_languages from "../../assets/language_native.json" import * as native_languages from "../../assets/language_native.json"
import * as used_languages from "../../assets/generated/used_languages.json"
import BaseUIElement from "../BaseUIElement";
class TranslatorsPanelContent extends Combine { class TranslatorsPanelContent extends Combine {
constructor(layout: LayoutConfig, isTranslator: UIEventSource<boolean>) { constructor(layout: LayoutConfig, isTranslator: UIEventSource<boolean>) {
@ -24,7 +26,7 @@ class TranslatorsPanelContent extends Combine {
const seed = t.completeness const seed = t.completeness
for (const ln of Array.from(completeness.keys())) { for (const ln of Array.from(completeness.keys())) {
if(ln === "*"){ if (ln === "*") {
continue continue
} }
if (seed.translations[ln] === undefined) { if (seed.translations[ln] === undefined) {
@ -35,26 +37,42 @@ class TranslatorsPanelContent extends Combine {
const completenessTr = {} const completenessTr = {}
const completenessPercentage = {} const completenessPercentage = {}
seed.SupportedLanguages().forEach(ln => { seed.SupportedLanguages().forEach(ln => {
completenessTr[ln] = ""+(completeness.get(ln) ?? 0) completenessTr[ln] = "" + (completeness.get(ln) ?? 0)
completenessPercentage[ln] = ""+Math.round(100 * (completeness.get(ln) ?? 0) / total) completenessPercentage[ln] = "" + Math.round(100 * (completeness.get(ln) ?? 0) / total)
}) })
const missingTranslationsFor = (ln: string) => Utils.NoNull(untranslated.get(ln) ?? []) function missingTranslationsFor(language: string): BaseUIElement[] {
.filter(ctx => ctx.indexOf(":") >= 0) // e.g. "themes:<themename>.layers.0.tagRenderings..., or "layers:<layername>.description
.map(ctx => ctx.replace(/note_import_[a-zA-Z0-9_]*/, "note_import")) const missingKeys = Utils.NoNull(untranslated.get(language) ?? [])
.map(context => new Link(context, LinkToWeblate.hrefToWeblate(ln, context), true)) .filter(ctx => ctx.indexOf(":") >= 0)
.map(ctx => ctx.replace(/note_import_[a-zA-Z0-9_]*/, "note_import"))
const hasMissingTheme = missingKeys.some(k => k.startsWith("themes:"))
const missingLayers = Utils.Dedup( missingKeys.filter(k => k.startsWith("layers:"))
.map(k => k.slice("layers:".length).split(".")[0]))
console.log("Getting untranslated string for",language,"raw:",missingKeys,"hasMissingTheme:",hasMissingTheme,"missingLayers:",missingLayers)
return [
hasMissingTheme ? new Link("themes:" + layout.id + ".* (zen mode)", LinkToWeblate.hrefToWeblateZen(language, "themes", layout.id), true) : undefined,
...missingLayers.map(id => new Link("layer:" + id + ".* (zen mode)", LinkToWeblate.hrefToWeblateZen(language, "layers", id), true)),
...missingKeys.map(context => new Link(context, LinkToWeblate.hrefToWeblate(language, context), true))
]
}
//
//
// "translationCompleteness": "Translations for {theme} in {language} are at {percentage}: {translated} out of {total}", // "translationCompleteness": "Translations for {theme} in {language} are at {percentage}: {translated} out of {total}",
const translated = seed.Subs({total, theme: layout.title, const translated = seed.Subs({
total, theme: layout.title,
percentage: new Translation(completenessPercentage), percentage: new Translation(completenessPercentage),
translated: new Translation(completenessTr), translated: new Translation(completenessTr),
language: seed.OnEveryLanguage((_, lng) => native_languages[lng]) language: seed.OnEveryLanguage((_, lng) => native_languages[lng] ?? lng)
}) })
super([ super([
new Title( new Title(
Translations.t.translations.activateButton, Translations.t.translations.activateButton,
), ),
new Toggle(t.isTranslator.SetClass("thanks block"), undefined, isTranslator), new Toggle(t.isTranslator.SetClass("thanks block"), undefined, isTranslator),
t.help, t.help,
@ -66,13 +84,16 @@ class TranslatorsPanelContent extends Combine {
}), }),
new VariableUiElement(Locale.language.map(ln => { new VariableUiElement(Locale.language.map(ln => {
const missing = missingTranslationsFor(ln) const missing = missingTranslationsFor(ln)
if (missing.length === 0) { if (missing.length === 0) {
return undefined return undefined
} }
let title = Translations.t.translations.allMissing;
if(untranslated.get(ln) !== undefined){
title = Translations.t.translations.missing.Subs({count: untranslated.get(ln).length})
}
return new Toggleable( return new Toggleable(
new Title(Translations.t.translations.missing.Subs({count: missing.length})), new Title(title),
new Combine(missing).SetClass("flex flex-col") new Combine(missing).SetClass("flex flex-col")
) )
})) }))
@ -88,7 +109,7 @@ export default class TranslatorsPanel extends Toggle {
constructor(state: { layoutToUse: LayoutConfig, isTranslator: UIEventSource<boolean> }, iconStyle?: string) { constructor(state: { layoutToUse: LayoutConfig, isTranslator: UIEventSource<boolean> }, iconStyle?: string) {
const t = Translations.t.translations const t = Translations.t.translations
super( super(
new Lazy(() => new TranslatorsPanelContent(state.layoutToUse, state.isTranslator) new Lazy(() => new TranslatorsPanelContent(state.layoutToUse, state.isTranslator)
).SetClass("flex flex-col"), ).SetClass("flex flex-col"),
new SubtleButton(Svg.translate_ui().SetStyle(iconStyle), t.activateButton).onClick(() => Locale.showLinkToWeblate.setData(true)), new SubtleButton(Svg.translate_ui().SetStyle(iconStyle), t.activateButton).onClick(() => Locale.showLinkToWeblate.setData(true)),
Locale.showLinkToWeblate Locale.showLinkToWeblate
@ -98,24 +119,23 @@ export default class TranslatorsPanel extends Toggle {
} }
public static MissingTranslationsFor(layout: LayoutConfig) : {completeness: Map<string, number>, untranslated: Map<string, string[]>, total: number} { 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 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){ if (translation.translations["*"] !== undefined) {
return return
} }
if(translation.context === undefined || translation.context.indexOf(":") < 0){ if (translation.context === undefined || translation.context.indexOf(":") < 0) {
// no source given - lets ignore // no source given - lets ignore
return return
} }
for (const lang of translation.SupportedLanguages()) { total ++
completeness.set(lang, 1 + (completeness.get(lang) ?? 0)) used_languages.languages.forEach(ln => {
}
layout.title.SupportedLanguages().forEach(ln => {
const trans = translation.translations const trans = translation.translations
if (trans["*"] !== undefined) { if (trans["*"] !== undefined) {
return; return;
@ -125,11 +145,11 @@ export default class TranslatorsPanel extends Toggle {
untranslated.set(ln, []) untranslated.set(ln, [])
} }
untranslated.get(ln).push(translation.context) untranslated.get(ln).push(translation.context)
}else{
completeness.set(ln, 1 + (completeness.get(ln) ?? 0))
} }
}) })
if(translation.translations["*"] === undefined){
total++
}
}, o => { }, o => {
if (o === undefined || o === null) { if (o === undefined || o === null) {
return false; return false;

View file

@ -532,6 +532,7 @@
}, },
"translations": { "translations": {
"activateButton": "Help to translate MapComplete", "activateButton": "Help to translate MapComplete",
"allMissing": "No translations yet",
"completeness": "Translations for {theme} in {language} are at {percentage}%: {translated} strings out of {total} are translated", "completeness": "Translations for {theme} in {language} are at {percentage}%: {translated} strings out of {total} are translated",
"deactivate": "Disable translation buttons", "deactivate": "Disable translation buttons",
"help": "Click the 'translate'-icon next to a string to enter or update a piece of text. You need a Weblate-account for this. Create one with your OSM-username to automatically unlock translation mode.", "help": "Click the 'translate'-icon next to a string to enter or update a piece of text. You need a Weblate-account for this. Create one with your OSM-username to automatically unlock translation mode.",