forked from MapComplete/MapComplete
Add translation buttons
This commit is contained in:
parent
592bc4ae0b
commit
2c7fb556dc
31 changed files with 442 additions and 150 deletions
|
@ -16,6 +16,7 @@ export default class Link extends BaseUIElement {
|
|||
if (this._embeddedShow === undefined) {
|
||||
throw "Error: got a link where embeddedShow is undefined"
|
||||
}
|
||||
this.onClick(() => {})
|
||||
|
||||
}
|
||||
|
||||
|
|
33
UI/Base/LinkToWeblate.ts
Normal file
33
UI/Base/LinkToWeblate.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import {VariableUiElement} from "./VariableUIElement";
|
||||
import Locale from "../i18n/Locale";
|
||||
import Link from "./Link";
|
||||
import Svg from "../../Svg";
|
||||
|
||||
export default class LinkToWeblate extends VariableUiElement {
|
||||
constructor(context: string, availableTranslations: object) {
|
||||
super( Locale.language.map(ln => {
|
||||
if (Locale.showLinkToWeblate.data === false) {
|
||||
return undefined;
|
||||
}
|
||||
if(availableTranslations["*"] !== undefined){
|
||||
return undefined
|
||||
}
|
||||
const icon = Svg.translate_svg()
|
||||
.SetClass("rounded-full border border-gray-400 inline-block w-4 h-4 m-1 weblate-link self-center")
|
||||
if(availableTranslations[ln] === undefined){
|
||||
icon.SetClass("bg-red-400")
|
||||
}
|
||||
return new Link(icon,
|
||||
LinkToWeblate.hrefToWeblate(ln, context), true)
|
||||
} ,[Locale.showLinkToWeblate]));
|
||||
this.SetClass("enable-links hidden-on-mobile")
|
||||
}
|
||||
|
||||
public static hrefToWeblate(language: string, contextKey: string): string{
|
||||
const [category, ...rest] = contextKey.split(":")
|
||||
const key = rest.join(":")
|
||||
|
||||
const baseUrl = "https://hosted.weblate.org/translate/mapcomplete/"
|
||||
return baseUrl + category + "/" + language + "/?offset=1&q=context%3A%3D%22" + key + "%22"
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
]
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
)
|
||||
|
|
124
UI/BigComponents/TranslatorsPanel.ts
Normal file
124
UI/BigComponents/TranslatorsPanel.ts
Normal 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")
|
||||
|
||||
}
|
||||
}
|
|
@ -842,7 +842,7 @@ export default class SpecialVisualizations {
|
|||
|
||||
return new LoginToggle(
|
||||
new Combine([
|
||||
new Title("Add a comment"),
|
||||
new Title(t.addAComment),
|
||||
textField,
|
||||
new Combine([
|
||||
stateButtons.SetClass("sm:mr-2"),
|
||||
|
|
|
@ -9,6 +9,7 @@ import Combine from "./Base/Combine";
|
|||
import BaseUIElement from "./BaseUIElement";
|
||||
import {DefaultGuiState} from "./DefaultGuiState";
|
||||
import FeaturePipelineState from "../Logic/State/FeaturePipelineState";
|
||||
import LinkToWeblate from "./Base/LinkToWeblate";
|
||||
|
||||
export class SubstitutedTranslation extends VariableUiElement {
|
||||
|
||||
|
@ -34,6 +35,8 @@ export class SubstitutedTranslation extends VariableUiElement {
|
|||
)
|
||||
})
|
||||
|
||||
const linkToWeblate = new LinkToWeblate(translation.context, translation.translations)
|
||||
|
||||
super(
|
||||
Locale.language.map(language => {
|
||||
let txt = translation?.textFor(language);
|
||||
|
@ -44,7 +47,7 @@ export class SubstitutedTranslation extends VariableUiElement {
|
|||
txt = txt.replace(new RegExp(`{${key}}`, "g"), `{${key}()}`)
|
||||
})
|
||||
|
||||
return new Combine(SubstitutedTranslation.ExtractSpecialComponents(txt, extraMappings).map(
|
||||
const allElements = SubstitutedTranslation.ExtractSpecialComponents(txt, extraMappings).map(
|
||||
proto => {
|
||||
if (proto.fixed !== undefined) {
|
||||
return new VariableUiElement(tagsSource.map(tags => Utils.SubstituteKeys(proto.fixed, tags)));
|
||||
|
@ -56,8 +59,12 @@ export class SubstitutedTranslation extends VariableUiElement {
|
|||
console.error("SPECIALRENDERING FAILED for", tagsSource.data?.id, e)
|
||||
return new FixedUiElement(`Could not generate special rendering for ${viz.func.funcName}(${viz.args.join(", ")}) ${e}`).SetStyle("alert")
|
||||
}
|
||||
}
|
||||
))
|
||||
});
|
||||
allElements.push(linkToWeblate)
|
||||
|
||||
return new Combine(
|
||||
allElements
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
|
|
|
@ -39,16 +39,12 @@ export default class WikidataPreviewBox extends VariableUiElement {
|
|||
{
|
||||
property: "P569",
|
||||
requires: WikidataPreviewBox.isHuman,
|
||||
display: new Translation({
|
||||
"*": "Born: {value}"
|
||||
})
|
||||
display: Translations.t.general.wikipedia.previewbox.born
|
||||
},
|
||||
{
|
||||
property: "P570",
|
||||
requires: WikidataPreviewBox.isHuman,
|
||||
display: new Translation({
|
||||
"*": "Died: {value}"
|
||||
})
|
||||
display:Translations.t.general.wikipedia.previewbox.died
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@ import {QueryParameters} from "../../Logic/Web/QueryParameters";
|
|||
export default class Locale {
|
||||
|
||||
public static language: UIEventSource<string> = Locale.setup();
|
||||
|
||||
public static showLinkToWeblate: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
|
||||
private static setup() {
|
||||
const source = LocalStorageSource.Get('language', "en");
|
||||
if (!Utils.runningFromConsole) {
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
import Locale from "./Locale";
|
||||
import {Utils} from "../../Utils";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import Link from "../Base/Link";
|
||||
import Svg from "../../Svg";
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import LinkToWeblate from "../Base/LinkToWeblate";
|
||||
|
||||
export class Translation extends BaseUIElement {
|
||||
|
||||
public static forcedLanguage = undefined;
|
||||
|
||||
public readonly translations: object
|
||||
context?: string;
|
||||
|
||||
constructor(translations: object, context?: string) {
|
||||
super()
|
||||
this.context = context;
|
||||
if (translations === undefined) {
|
||||
console.error("Translation without content at "+context)
|
||||
throw `Translation without content (${context})`
|
||||
|
@ -101,13 +107,35 @@ export class Translation extends BaseUIElement {
|
|||
InnerConstructElement(): HTMLElement {
|
||||
const el = document.createElement("span")
|
||||
const self = this
|
||||
|
||||
|
||||
Locale.language.addCallbackAndRun(_ => {
|
||||
if (self.isDestroyed) {
|
||||
return true
|
||||
}
|
||||
el.innerHTML = this.txt
|
||||
})
|
||||
return el;
|
||||
|
||||
if (self.translations["*"] !== undefined || self.context === undefined || self.context?.indexOf(":") < 0) {
|
||||
return el;
|
||||
}
|
||||
|
||||
const linkToWeblate = new LinkToWeblate(self.context, self.translations)
|
||||
|
||||
const wrapper = document.createElement("span")
|
||||
wrapper.appendChild(el)
|
||||
wrapper.classList.add("flex")
|
||||
Locale.showLinkToWeblate.addCallbackAndRun(doShow => {
|
||||
|
||||
if (!doShow) {
|
||||
return;
|
||||
}
|
||||
wrapper.appendChild(linkToWeblate.ConstructElement())
|
||||
return true;
|
||||
})
|
||||
|
||||
|
||||
return wrapper ;
|
||||
}
|
||||
|
||||
public SupportedLanguages(): string[] {
|
||||
|
@ -131,11 +159,25 @@ export class Translation extends BaseUIElement {
|
|||
return this.SupportedLanguages().map(lng => this.translations[lng]);
|
||||
}
|
||||
|
||||
public Subs(text: any): Translation {
|
||||
return this.OnEveryLanguage((template, lang) => Utils.SubstituteKeys(template, text, lang))
|
||||
/**
|
||||
* Substitutes text in a translation.
|
||||
* If a translation is passed, it'll be fused
|
||||
*
|
||||
* // Should replace simple keys
|
||||
* new Translation({"en": "Some text {key}"}).Subs({key: "xyz"}).textFor("en") // => "Some text xyz"
|
||||
*
|
||||
* // Should fuse translations
|
||||
* const subpart = new Translation({"en": "subpart","nl":"onderdeel"})
|
||||
* const tr = new Translation({"en": "Full sentence with {part}", nl: "Volledige zin met {part}"})
|
||||
* const subbed = tr.Subs({part: subpart})
|
||||
* subbed.textFor("en") // => "Full sentence with subpart"
|
||||
* subbed.textFor("nl") // => "Volledige zin met onderdeel"
|
||||
*/
|
||||
public Subs(text: any, context?: string): Translation {
|
||||
return this.OnEveryLanguage((template, lang) => Utils.SubstituteKeys(template, text, lang), context)
|
||||
}
|
||||
|
||||
public OnEveryLanguage(f: (s: string, language: string) => string): Translation {
|
||||
public OnEveryLanguage(f: (s: string, language: string) => string, context?: string): Translation {
|
||||
const newTranslations = {};
|
||||
for (const lang in this.translations) {
|
||||
if (!this.translations.hasOwnProperty(lang)) {
|
||||
|
@ -143,37 +185,10 @@ export class Translation extends BaseUIElement {
|
|||
}
|
||||
newTranslations[lang] = f(this.translations[lang], lang);
|
||||
}
|
||||
return new Translation(newTranslations);
|
||||
return new Translation(newTranslations, context ?? this.context);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Given a translation such as `{en: "How much of bicycle_types are rented here}` (which is this translation)
|
||||
* and a translation object `{ en: "electrical bikes" }`, plus the translation specification `bicycle_types`, will return
|
||||
* a new translation:
|
||||
* `{en: "How much electrical bikes are rented here?"}`
|
||||
*
|
||||
* @param translationObject
|
||||
* @param stringToReplace
|
||||
* @constructor
|
||||
*/
|
||||
public Fuse(translationObject: Translation, stringToReplace: string): Translation{
|
||||
const translations = this.translations
|
||||
const newTranslations = {}
|
||||
for (const lang in translations) {
|
||||
const target = translationObject.textFor(lang)
|
||||
if(target === undefined){
|
||||
continue
|
||||
}
|
||||
if(typeof target !== "string"){
|
||||
throw "Invalid object in Translation.fuse: translationObject['"+lang+"'] is not a string, it is: "+JSON.stringify(target)
|
||||
}
|
||||
newTranslations[lang] = this.translations[lang].replaceAll(stringToReplace, target)
|
||||
}
|
||||
return new Translation(newTranslations)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replaces the given string with the given text in the language.
|
||||
* Other substitutions are left in place
|
||||
|
@ -190,7 +205,7 @@ export class Translation extends BaseUIElement {
|
|||
}
|
||||
|
||||
public Clone() {
|
||||
return new Translation(this.translations)
|
||||
return new Translation(this.translations, this.context)
|
||||
}
|
||||
|
||||
FirstSentence() {
|
||||
|
@ -256,4 +271,6 @@ export class Translation extends BaseUIElement {
|
|||
AsMarkdown(): string {
|
||||
return this.txt
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue