Add translation buttons

This commit is contained in:
Pieter Vander Vennet 2022-04-01 12:51:55 +02:00
parent 592bc4ae0b
commit 2c7fb556dc
31 changed files with 442 additions and 150 deletions

View file

@ -23,6 +23,7 @@ import Constants from "../../Models/Constants";
import ContributorCount from "../../Logic/ContributorCount";
import Img from "../Base/Img";
import {Translation} from "../i18n/Translation";
import TranslatorsPanel from "./TranslatorsPanel";
export class OpenIdEditor extends VariableUiElement {
constructor(state: { locationControl: UIEventSource<Loc> }, iconStyle?: string, objectId?: string) {
@ -110,7 +111,8 @@ export default class CopyrightPanel extends Combine {
featurePipeline: FeaturePipeline,
currentBounds: UIEventSource<BBox>,
locationControl: UIEventSource<Loc>,
osmConnection: OsmConnection
osmConnection: OsmConnection,
isTranslator: UIEventSource<boolean>
}) {
const t = Translations.t.general.attribution
@ -131,25 +133,21 @@ export default class CopyrightPanel extends Combine {
}),
new OpenIdEditor(state, iconStyle),
new OpenMapillary(state, iconStyle),
new OpenJosm(state, iconStyle)
new OpenJosm(state, iconStyle),
new TranslatorsPanel(state, iconStyle)
]
const iconAttributions = layoutToUse.usedImages.map(CopyrightPanel.IconAttribution)
let maintainer: BaseUIElement = undefined
if (layoutToUse.maintainer !== undefined && layoutToUse.maintainer !== "" && layoutToUse.maintainer.toLowerCase() !== "mapcomplete") {
maintainer = Translations.t.general.attribution.themeBy.Subs({author: layoutToUse.maintainer})
maintainer = t.themeBy.Subs({author: layoutToUse.maintainer})
}
const contributions = new ContributorCount(state).Contributors
super([
Translations.t.general.attribution.attributionContent,
new FixedUiElement("MapComplete " + Constants.vNumber).SetClass("font-bold"),
maintainer,
new Combine(actionButtons).SetClass("block w-full"),
new FixedUiElement(layoutToUse.credits),
new VariableUiElement(contributions.map(contributions => {
const dataContributors = new VariableUiElement(contributions.map(contributions => {
if (contributions === undefined) {
return ""
}
@ -170,20 +168,29 @@ export default class CopyrightPanel extends Combine {
const contribs = links.join(", ")
if (hiddenCount <= 0) {
return Translations.t.general.attribution.mapContributionsBy.Subs({
return t.mapContributionsBy.Subs({
contributors: contribs
})
} else {
return Translations.t.general.attribution.mapContributionsByAndHidden.Subs({
return t.mapContributionsByAndHidden.Subs({
contributors: contribs,
hiddenCount: hiddenCount
});
}
})),
CopyrightPanel.CodeContributors(contributors, Translations.t.general.attribution.codeContributionsBy),
CopyrightPanel.CodeContributors(translators, Translations.t.general.attribution.translatedBy),
}))
super([
new Title(t.attributionTitle),
t.attributionContent,
maintainer,
new FixedUiElement(layoutToUse.credits),
dataContributors,
CopyrightPanel.CodeContributors(contributors, t.codeContributionsBy),
CopyrightPanel.CodeContributors(translators, t.translatedBy),
new FixedUiElement("MapComplete " + Constants.vNumber).SetClass("font-bold"),
new Combine(actionButtons).SetClass("block w-full"),
new Title(t.iconAttribution.title, 3),
...iconAttributions
].map(e => e?.SetClass("mt-4")));
@ -213,9 +220,9 @@ export default class CopyrightPanel extends Combine {
private static IconAttribution(iconPath: string): BaseUIElement {
if (iconPath.startsWith("http")) {
try{
try {
iconPath = "." + new URL(iconPath).pathname;
}catch(e){
} catch (e) {
console.warn(e)
}
}
@ -234,16 +241,16 @@ export default class CopyrightPanel extends Combine {
new Img(iconPath).SetClass("w-12 min-h-12 mr-2 mb-2"),
new Combine([
new FixedUiElement(license.authors.join("; ")).SetClass("font-bold"),
license.license,
new Combine([ ...sources.map(lnk => {
let sourceLinkContent = lnk;
try {
sourceLinkContent = new URL(lnk).hostname
} catch {
console.error("Not a valid URL:", lnk)
}
return new Link(sourceLinkContent, lnk, true).SetClass("mr-2 mb-2");
})]).SetClass("flex flex-wrap")
license.license,
new Combine([...sources.map(lnk => {
let sourceLinkContent = lnk;
try {
sourceLinkContent = new URL(lnk).hostname
} catch {
console.error("Not a valid URL:", lnk)
}
return new Link(sourceLinkContent, lnk, true).SetClass("mr-2 mb-2");
})]).SetClass("flex flex-wrap")
]).SetClass("flex flex-col").SetStyle("width: calc(100% - 50px - 0.5em); min-width: 12rem;")
]).SetClass("flex flex-wrap border-b border-gray-300 m-2 border-box")
}

View file

@ -101,9 +101,7 @@ export default class FilterView extends VariableUiElement {
iconStyle
);
const name: Translation = Translations.WT(
filteredLayer.layerDef.name
);
const name: Translation = filteredLayer.layerDef.name.Clone()
const styledNameChecked = name.Clone().SetStyle("font-size:large").SetClass("ml-3");

View file

@ -83,9 +83,7 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
new Combine(
[
Translations.t.general.openStreetMapIntro.SetClass("link-underline"),
Translations.t.general.attribution.attributionTitle,
new CopyrightPanel(state)
]
)
}

View file

@ -9,7 +9,7 @@ import BaseUIElement from "../BaseUIElement";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
import {UIEventSource} from "../../Logic/UIEventSource";
import Loc from "../../Models/Loc";
import UserDetails, {OsmConnection} from "../../Logic/Osm/OsmConnection";
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
import UserRelatedState from "../../Logic/State/UserRelatedState";
import Toggle from "../Input/Toggle";
import {Utils} from "../../Utils";
@ -53,7 +53,8 @@ export default class MoreScreen extends Combine {
icon: string,
title: any,
shortDescription: any,
definition?: any
definition?: any,
mustHaveLanguage?: boolean
}, isCustom: boolean = false
):
BaseUIElement {
@ -109,7 +110,7 @@ export default class MoreScreen extends Combine {
return new SubtleButton(layout.icon,
new Combine([
`<dt class='text-lg leading-6 font-medium text-gray-900 group-hover:text-blue-800'>`,
new Translation(layout.title),
new Translation(layout.title, !isCustom && !layout.mustHaveLanguage ? "themes:"+layout.id+".title" : undefined),
`</dt>`,
`<dd class='mt-1 text-base text-gray-500 group-hover:text-blue-900 overflow-ellipsis'>`,
new Translation(layout.shortDescription)?.SetClass("subtle") ?? "",
@ -142,9 +143,10 @@ export default class MoreScreen extends Combine {
icon: string,
title: any,
shortDescription: any,
definition?: any
definition?: any,
isOfficial: boolean
} = JSON.parse(str)
value.isOfficial = false
return MoreScreen.createLinkButton(state, value, true)
} catch (e) {
console.debug("Could not parse unofficial theme information for " + id, "The json is: ", str, e)

View file

@ -117,7 +117,7 @@ export default class SimpleAddUI extends Toggle {
selectedPreset.setData(undefined)
}
const message = Translations.t.general.add.addNew.Subs({category: preset.name});
const message = Translations.t.general.add.addNew.Subs({category: preset.name}, preset.name["context"]);
return new ConfirmLocationOfPoint(state, filterViewIsOpened, preset,
message,
state.LastClickLocation.data,
@ -184,12 +184,13 @@ export default class SimpleAddUI extends Toggle {
private static CreatePresetSelectButton(preset: PresetInfo) {
const title = Translations.t.general.add.addNew.Subs({
category: preset.name
}, preset.name["context"])
return new SubtleButton(
preset.icon(),
new Combine([
Translations.t.general.add.addNew.Subs({
category: preset.name
}).SetClass("font-bold"),
title.SetClass("font-bold"),
Translations.WT(preset.description)?.FirstSentence()
]).SetClass("flex flex-col")
)

View file

@ -0,0 +1,124 @@
import Toggle from "../Input/Toggle";
import Lazy from "../Base/Lazy";
import {Utils} from "../../Utils";
import Translations from "../i18n/Translations";
import Combine from "../Base/Combine";
import Locale from "../i18n/Locale";
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
import {Translation} from "../i18n/Translation";
import {VariableUiElement} from "../Base/VariableUIElement";
import Link from "../Base/Link";
import LinkToWeblate from "../Base/LinkToWeblate";
import Toggleable from "../Base/Toggleable";
import Title from "../Base/Title";
import {UIEventSource} from "../../Logic/UIEventSource";
import {SubtleButton} from "../Base/SubtleButton";
import Svg from "../../Svg";
class TranslatorsPanelContent extends Combine {
constructor(layout: LayoutConfig, isTranslator: UIEventSource<boolean>) {
const t = Translations.t.translations
const completeness = new Map<string, number>()
let total = 0
const untranslated = new Map<string, string[]>()
Utils.WalkObject(layout, (o, path) => {
const translation = <Translation><any>o;
for (const lang of translation.SupportedLanguages()) {
completeness.set(lang, 1 + (completeness.get(lang) ?? 0))
}
layout.title.SupportedLanguages().forEach(ln => {
const trans = translation.translations
if (trans["*"] !== undefined) {
return;
}
if (trans[ln] === undefined) {
if (!untranslated.has(ln)) {
untranslated.set(ln, [])
}
untranslated.get(ln).push(translation.context)
}
})
if(translation.translations["*"] === undefined){
total++
}
}, o => {
if (o === undefined || o === null) {
return false;
}
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<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")
}
}