diff --git a/Models/ThemeConfig/TagRenderingConfig.ts b/Models/ThemeConfig/TagRenderingConfig.ts index 0f2aa3fdb9..aa9f05eb89 100644 --- a/Models/ThemeConfig/TagRenderingConfig.ts +++ b/Models/ThemeConfig/TagRenderingConfig.ts @@ -1,4 +1,4 @@ -import {Translation} from "../../UI/i18n/Translation"; +import {Translation, TypedTranslation} from "../../UI/i18n/Translation"; import {TagsFilter} from "../../Logic/Tags/TagsFilter"; import Translations from "../../UI/i18n/Translations"; import {TagUtils} from "../../Logic/Tags/TagUtils"; @@ -22,8 +22,8 @@ export default class TagRenderingConfig { public readonly id: string; public readonly group: string; - public readonly render?: Translation; - public readonly question?: Translation; + public readonly render?: TypedTranslation; + public readonly question?: TypedTranslation; public readonly condition?: TagsFilter; public readonly configuration_warnings: string[] = [] @@ -43,7 +43,7 @@ export default class TagRenderingConfig { public readonly mappings?: { readonly if: TagsFilter, readonly ifnot?: TagsFilter, - readonly then: Translation, + readonly then: TypedTranslation, readonly icon: string, readonly iconClass: string readonly hideInAnswer: boolean | TagsFilter @@ -110,12 +110,13 @@ export default class TagRenderingConfig { } const type = json.freeform.type ?? "string" - let placeholder = Translations.T(json.freeform.placeholder) + let placeholder: Translation = Translations.T(json.freeform.placeholder) if (placeholder === undefined) { const typeDescription = Translations.t.validation[type]?.description - placeholder = Translations.T(json.freeform.key+" ("+type+")") if(typeDescription !== undefined){ - placeholder = placeholder.Subs({[type]: typeDescription}) + placeholder = Translations.T(json.freeform.key+" ("+type+")").Subs({[type]: typeDescription}) + }else{ + placeholder = Translations.T(json.freeform.key+" ("+type+")") } } @@ -383,7 +384,7 @@ export default class TagRenderingConfig { let freeformKeyDefined = this.freeform?.key !== undefined; let usedFreeformValues = new Set() // We run over all the mappings first, to check if the mapping matches - const applicableMappings: { then: Translation, img?: string }[] = Utils.NoNull((this.mappings ?? [])?.map(mapping => { + const applicableMappings: { then: TypedTranslation, img?: string }[] = Utils.NoNull((this.mappings ?? [])?.map(mapping => { if (mapping.if === undefined) { return mapping; } @@ -404,7 +405,7 @@ export default class TagRenderingConfig { const leftovers = freeformValues.filter(v => !usedFreeformValues.has(v)) for (const leftover of leftovers) { applicableMappings.push({then: - this.render.replace("{"+this.freeform.key+"}", leftover) + new TypedTranslation(this.render.replace("{"+this.freeform.key+"}", leftover).translations) }) } } @@ -412,7 +413,7 @@ export default class TagRenderingConfig { return applicableMappings } - public GetRenderValue(tags: any, defltValue: any = undefined): Translation { + public GetRenderValue(tags: any, defltValue: any = undefined): TypedTranslation { return this.GetRenderValueWithImage(tags, defltValue).then } @@ -421,7 +422,7 @@ export default class TagRenderingConfig { * Not compatible with multiAnswer - use GetRenderValueS instead in that case * @constructor */ - public GetRenderValueWithImage(tags: any, defltValue: any = undefined): { then: Translation, icon?: string } { + public GetRenderValueWithImage(tags: any, defltValue: any = undefined): { then: TypedTranslation, icon?: string } { if (this.mappings !== undefined && !this.multiAnswer) { for (const mapping of this.mappings) { if (mapping.if === undefined) { diff --git a/UI/BigComponents/CopyrightPanel.ts b/UI/BigComponents/CopyrightPanel.ts index c1cadcf8b4..1b196f02b5 100644 --- a/UI/BigComponents/CopyrightPanel.ts +++ b/UI/BigComponents/CopyrightPanel.ts @@ -22,7 +22,7 @@ import {OsmConnection} from "../../Logic/Osm/OsmConnection"; import Constants from "../../Models/Constants"; import ContributorCount from "../../Logic/ContributorCount"; import Img from "../Base/Img"; -import {Translation} from "../i18n/Translation"; +import {TypedTranslation} from "../i18n/Translation"; import TranslatorsPanel from "./TranslatorsPanel"; export class OpenIdEditor extends VariableUiElement { @@ -198,7 +198,7 @@ export default class CopyrightPanel extends Combine { this.SetStyle("max-width:100%; width: 40rem; margin-left: 0.75rem; margin-right: 0.5rem") } - private static CodeContributors(contributors, translation: Translation): BaseUIElement { + private static CodeContributors(contributors, translation: TypedTranslation<{contributors, hiddenCount}>): BaseUIElement { const total = contributors.contributors.length; let filtered = [...contributors.contributors] diff --git a/UI/BigComponents/TranslatorsPanel.ts b/UI/BigComponents/TranslatorsPanel.ts index a036a1d546..311f7527f3 100644 --- a/UI/BigComponents/TranslatorsPanel.ts +++ b/UI/BigComponents/TranslatorsPanel.ts @@ -14,7 +14,7 @@ import Title from "../Base/Title"; import {UIEventSource} from "../../Logic/UIEventSource"; import {SubtleButton} from "../Base/SubtleButton"; import Svg from "../../Svg"; - +import * as native_languages from "../../assets/language_native.json" class TranslatorsPanelContent extends Combine { constructor(layout: LayoutConfig, isTranslator: UIEventSource) { @@ -48,7 +48,8 @@ class TranslatorsPanelContent extends Combine { // "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) + translated: new Translation(completenessTr), + language: seed.OnEveryLanguage((_, lng) => native_languages[lng]) }) super([ diff --git a/UI/Reviews/ReviewElement.ts b/UI/Reviews/ReviewElement.ts index 526160b110..79fe40132c 100644 --- a/UI/Reviews/ReviewElement.ts +++ b/UI/Reviews/ReviewElement.ts @@ -25,7 +25,7 @@ export default class ReviewElement extends VariableUiElement { SingleReview.GenStars(avg), new Link( revs.length === 1 ? Translations.t.reviews.title_singular.Clone() : - Translations.t.reviews.title.Clone() + Translations.t.reviews.title .Subs({count: "" + revs.length}), `https://mangrove.reviews/search?sub=${encodeURIComponent(subject)}`, true diff --git a/UI/Wikipedia/WikidataPreviewBox.ts b/UI/Wikipedia/WikidataPreviewBox.ts index a5f125e5cc..1e6db1ca33 100644 --- a/UI/Wikipedia/WikidataPreviewBox.ts +++ b/UI/Wikipedia/WikidataPreviewBox.ts @@ -1,7 +1,7 @@ import {VariableUiElement} from "../Base/VariableUIElement"; import {UIEventSource} from "../../Logic/UIEventSource"; import Wikidata, {WikidataResponse} from "../../Logic/Web/Wikidata"; -import {Translation} from "../i18n/Translation"; +import {Translation, TypedTranslation} from "../i18n/Translation"; import {FixedUiElement} from "../Base/FixedUiElement"; import Loading from "../Base/Loading"; import Translations from "../i18n/Translations"; @@ -22,7 +22,7 @@ export default class WikidataPreviewBox extends VariableUiElement { private static extraProperties: { requires?: { p: number, q?: number }[], property: string, - display: Translation | Map BaseUIElement) /*If translation: Subs({value: * }) */> + display: TypedTranslation<{value}> | Map BaseUIElement) /*If translation: Subs({value: * }) */> }[] = [ { requires: WikidataPreviewBox.isHuman, diff --git a/UI/i18n/Translation.ts b/UI/i18n/Translation.ts index e9c8157081..677878acc3 100644 --- a/UI/i18n/Translation.ts +++ b/UI/i18n/Translation.ts @@ -1,9 +1,6 @@ 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 { @@ -164,25 +161,7 @@ export class Translation extends BaseUIElement { public AllValues(): string[] { return this.SupportedLanguages().map(lng => this.translations[lng]); } - - /** - * 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, context?: string): Translation { const newTranslations = {}; for (const lang in this.translations) { @@ -278,5 +257,28 @@ export class Translation extends BaseUIElement { return this.txt } +} +export class TypedTranslation extends Translation { + constructor(translations: object, context?: string) { + super(translations, context); + } + + /** + * Substitutes text in a translation. + * If a translation is passed, it'll be fused + * + * // Should replace simple keys + * new TypedTranslation({"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 TypedTranslation({"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" + */ + Subs(text: T, context?: string): Translation { + return this.OnEveryLanguage((template, lang) => Utils.SubstituteKeys(template, text, lang), context) + } } \ No newline at end of file diff --git a/UI/i18n/Translations.ts b/UI/i18n/Translations.ts index 482cc7eeb4..8186226fbd 100644 --- a/UI/i18n/Translations.ts +++ b/UI/i18n/Translations.ts @@ -1,5 +1,5 @@ import {FixedUiElement} from "../Base/FixedUiElement"; -import {Translation} from "./Translation"; +import {Translation, TypedTranslation} from "./Translation"; import BaseUIElement from "../BaseUIElement"; import * as known_languages from "../../assets/generated/used_languages.json" import CompiledTranslations from "../../assets/generated/CompiledTranslations"; @@ -22,7 +22,7 @@ export default class Translations { return s; } - static T(t: string | any, context = undefined): Translation { + static T(t: string | any, context = undefined): TypedTranslation { if (t === undefined || t === null) { return undefined; } @@ -30,17 +30,17 @@ export default class Translations { t = "" + t } if (typeof t === "string") { - return new Translation({"*": t}, context); + return new TypedTranslation({"*": t}, context); } if (t.render !== undefined) { const msg = "Creating a translation, but this object contains a 'render'-field. Use the translation directly" console.error(msg, t); throw msg } - if (t instanceof Translation) { + if (t instanceof TypedTranslation) { return t; } - return new Translation(t, context); + return new TypedTranslation(t, context); } /** diff --git a/langs/gl.json b/langs/gl.json index ce0e6ed5e3..42dc16620a 100644 --- a/langs/gl.json +++ b/langs/gl.json @@ -42,11 +42,6 @@ "getStartedLogin": "Entra no OpenStreetMap para comezar", "getStartedNewAccount": " ou crea unha nova conta", "goToInbox": "Abrir mensaxes", - "index": { - "intro": "O MapComplete é un visor e editor do OpenStreetMap, que te amosa información sobre un tema específico.", - "pickTheme": "Escolle un tema para comezar.", - "title": "Benvido ao MapComplete" - }, "layerSelection": { "title": "Seleccionar capas", "zoomInToSeeThisLayer": "Achégate para ver esta capa" diff --git a/langs/nl.json b/langs/nl.json index 16317da4b3..b3ce6c699b 100644 --- a/langs/nl.json +++ b/langs/nl.json @@ -97,12 +97,6 @@ "backToMapcomplete": "Terug naar het themaoverzicht", "backgroundMap": "Achtergrondkaart", "cancel": "Annuleren", - "centerMessage": { - "loadingData": "Data wordt geladen…", - "ready": "Klaar!", - "retrying": "Data inladen mislukt. Opnieuw proberen over {count} seconden…", - "zoomIn": "Zoom in om de data te zien en te bewerken" - }, "confirm": "Bevestigen", "customThemeIntro": "

Onofficiële thema's

De onderstaande thema's heb je eerder bezocht en zijn gemaakt door andere OpenStreetMappers.", "download": { @@ -132,12 +126,6 @@ "histogram": { "error_loading": "Kan het histogram niet laden" }, - "index": { - "#": "Deze teksten worden getoond boven de themaknoppen als er geen thema is geladen", - "intro": "MapComplete is een OpenStreetMap applicatie waar informatie over een specifiek thema bekeken en aangepast kan worden.", - "pickTheme": "Kies hieronder een thema om te beginnen.", - "title": "Welkom bij MapComplete" - }, "layerSelection": { "title": "Selecteer lagen", "zoomInToSeeThisLayer": "Vergroot de kaart om deze laag te zien" @@ -199,22 +187,6 @@ "readYourMessages": "Gelieve eerst je berichten op OpenStreetMap te lezen alvorens nieuwe punten toe te voegen.", "removeLocationHistory": "Verwijder de geschiedenis aan locaties", "returnToTheMap": "Ga terug naar de kaart", - "reviews": { - "affiliated_reviewer_warning": "(Review door betrokkene)", - "attribution": "De beoordelingen worden voorzien door Mangrove Reviews en zijn beschikbaar onder deCC-BY 4.0-licentie.", - "i_am_affiliated": "Ik ben persoonlijk betrokken
Vink aan indien je de oprichter, maker, werknemer, ... of dergelijke bent", - "name_required": "De naam van dit object moet gekend zijn om een review te kunnen maken", - "no_rating": "Geen score bekend", - "no_reviews_yet": "Er zijn nog geen beoordelingen. Wees de eerste om een beoordeling te schrijven en help open data en het bedrijf!", - "plz_login": "Meld je aan om een beoordeling te geven", - "posting_as": "Ingelogd als", - "saved": "Bedankt om je beoordeling te delen!", - "saving_review": "Opslaan…", - "title": "{count} beoordelingen", - "title_singular": "Eén beoordeling", - "tos": "Als je je review publiceert, ga je akkoord met de de gebruiksvoorwaarden en privacy policy van Mangrove.reviews", - "write_a_comment": "Schrijf een beoordeling…" - }, "save": "Opslaan", "search": { "error": "Niet gelukt...", diff --git a/langs/pl.json b/langs/pl.json index 2f7a400ad5..507e993164 100644 --- a/langs/pl.json +++ b/langs/pl.json @@ -35,121 +35,9 @@ "cancel": "Anuluj", "customThemeIntro": "

Motywy własne

Są to wcześniej odwiedzone motywy stworzone przez użytkowników.", "fewChangesBefore": "Proszę odpowiedzieć na kilka pytań dotyczących istniejących punktów przed dodaniem nowego punktu.", - "general": { - "about": "Łatwo edytuj i dodaj OpenStreetMap dla określonego motywu", - "aboutMapcomplete": "

O MapComplete

Dzięki MapComplete możesz wzbogacić OpenStreetMap o informacje na pojedynczy temat. Odpowiedz na kilka pytań, a w ciągu kilku minut Twój wkład będzie dostępny na całym świecie! Opiekun tematu definiuje elementy, pytania i języki dla tematu.

Dowiedz się więcej

MapComplete zawsze oferuje następny krok, by dowiedzieć się więcej o OpenStreetMap.

  • Po osadzeniu na stronie internetowej, element iframe łączy się z pełnoekranowym MapComplete
  • Wersja pełnoekranowa oferuje informacje o OpenStreetMap
  • Przeglądanie działa bez logowania, ale edycja wymaga loginu OSM.
  • Jeżeli nie jesteś zalogowany, zostaniesz poproszony o zalogowanie się
  • Po udzieleniu odpowiedzi na jedno pytanie, możesz dodać nowe punkty do mapy
  • Po chwili wyświetlane są rzeczywiste tagi OSM, które później linkują do wiki


Zauważyłeś problem? Czy masz prośbę o dodanie jakiejś funkcji? Chcesz pomóc w tłumaczeniu? Udaj się do kodu źródłowego lub issue trackera.

Chcesz zobaczyć swoje postępy? Śledź liczbę edycji na OsmCha.

", - "add": { - "addNew": "Dodaj nową {category} tutaj", - "confirmButton": "Dodaj tutaj {category}.
Twój dodatek jest widoczny dla wszystkich
", - "confirmIntro": "

Czy dodać tutaj {title}?

Punkt, który tutaj utworzysz, będzie widoczny dla wszystkich. Proszę, dodawaj rzeczy do mapy tylko wtedy, gdy naprawdę istnieją. Wiele aplikacji korzysta z tych danych.", - "intro": "Kliknąłeś gdzieś, gdzie nie są jeszcze znane żadne dane.
", - "layerNotEnabled": "Warstwa {layer} nie jest włączona. Włącz tę warstwę, aby dodać punkt", - "openLayerControl": "Otwórz okno sterowania warstwą", - "pleaseLogin": "Zaloguj się, aby dodać nowy punkt", - "stillLoading": "Dane wciąż się ładują. Poczekaj chwilę, zanim dodasz nowy punkt.", - "title": "Czy dodać nowy punkt?", - "zoomInFurther": "Powiększ jeszcze bardziej, aby dodać punkt." - }, - "backgroundMap": "Tło mapy", - "cancel": "Anuluj", - "customThemeIntro": "

Motywy własne

Są to wcześniej odwiedzone motywy stworzone przez użytkowników.", - "fewChangesBefore": "Proszę odpowiedzieć na kilka pytań dotyczących istniejących punktów przed dodaniem nowego punktu.", - "getStartedLogin": "Zaloguj się za pomocą OpenStreetMap, aby rozpocząć", - "getStartedNewAccount": " lub utwórz nowe konto", - "goToInbox": "Otwórz skrzynkę odbiorczą", - "layerSelection": { - "title": "Wybierz warstwy", - "zoomInToSeeThisLayer": "Powiększ, aby zobaczyć tę warstwę" - }, - "loginToStart": "Zaloguj się, aby odpowiedzieć na to pytanie", - "loginWithOpenStreetMap": "Zaloguj z OpenStreetMap", - "nameInlineQuestion": "Nazwa tej {category} to $$$", - "noNameCategory": "{category} bez nazwy", - "noTagsSelected": "Nie wybrano tagów", - "number": "numer", - "oneSkippedQuestion": "Jedno pytanie zostało pominięte", - "opening_hours": { - "closed_permanently": "Zamknięte na nieokreślony czas", - "closed_until": "Zamknięte do {date}", - "error_loading": "Błąd: nie można zwizualizować tych godzin otwarcia.", - "not_all_rules_parsed": "Godziny otwarcia tego sklepu są skomplikowane. Następujące reguły są ignorowane w elemencie wejściowym:", - "openTill": "do", - "open_24_7": "Otwarte przez całą dobę", - "open_during_ph": "W czasie świąt państwowych udogodnienie to jest", - "opensAt": "z", - "ph_closed": "zamknięte", - "ph_not_known": " ", - "ph_open": "otwarte" - }, - "osmLinkTooltip": "Zobacz ten obiekt na OpenStreetMap, aby uzyskać historię i więcej opcji edycji", - "pickLanguage": "Wybierz język: ", - "questions": { - "emailIs": "Adres e-mail {category} to {email}", - "emailOf": "Jaki jest adres e-mail {category}?", - "phoneNumberIs": "Numer telefonu {category} to {phone}", - "phoneNumberOf": "Jaki jest numer telefonu do {category}?", - "websiteIs": "Strona internetowa: {website}", - "websiteOf": "Jaka jest strona internetowa {category}?" - }, - "readYourMessages": "Przeczytaj wszystkie wiadomości OpenStreetMap przed dodaniem nowego punktu.", - "returnToTheMap": "Wróć do mapy", - "save": "Zapisz", - "search": { - "error": "Coś poszło nie tak…", - "nothing": "Nic nie znaleziono…", - "search": "Wyszukaj lokalizację", - "searching": "Szukanie…" - }, - "sharescreen": { - "addToHomeScreen": "

Dodaj do ekranu głównego

Możesz łatwo dodać tę stronę do ekranu głównego smartfona, aby poczuć się jak w domu. Kliknij przycisk \"Dodaj do ekranu głównego\" na pasku adresu URL, aby to zrobić.", - "copiedToClipboard": "Link został skopiowany do schowka", - "editThemeDescription": "Dodaj lub zmień pytania do tego motywu mapy", - "editThisTheme": "Edytuj ten motyw", - "embedIntro": "

Umieść na swojej stronie internetowej

Proszę, umieść tę mapę na swojej stronie internetowej.
Zachęcamy cię do tego - nie musisz nawet pytać o zgodę.
Jest ona darmowa i zawsze będzie. Im więcej osób jej używa, tym bardziej staje się wartościowa.", - "fsAddNew": "Włącz przycisk \"Dodaj nowe POI\"", - "fsGeolocation": "Włącz przycisk „Zlokalizuj mnie” (tylko na urządzeniach mobilnych)", - "fsIncludeCurrentBackgroundMap": "Dołącz bieżący wybór tła {name}", - "fsIncludeCurrentLayers": "Uwzględnij wybór bieżącej warstwy", - "fsIncludeCurrentLocation": "Uwzględnij bieżącą lokalizację", - "fsLayerControlToggle": "Zacznij od rozwiniętej kontroli warstw", - "fsLayers": "Włącz kontrolę warstw", - "fsSearch": "Włącz pasek wyszukiwania", - "fsUserbadge": "Włącz przycisk logowania", - "fsWelcomeMessage": "Pokaż wyskakujące okienko wiadomości powitalnej i powiązane zakładki", - "intro": "

Udostępnij tę mapę

Udostępnij tę mapę, kopiując poniższy link i wysyłając ją do przyjaciół i rodziny:", - "thanksForSharing": "Dzięki za udostępnienie!" - }, - "skip": "Pomiń to pytanie", - "skippedQuestions": "Niektóre pytania są pominięte", - "weekdays": { - "abbreviations": { - "friday": "Pt", - "monday": "Pn", - "saturday": "Sob", - "sunday": "Niedz", - "thursday": "Czw", - "tuesday": "Wt", - "wednesday": "Śr" - }, - "friday": "Piątek", - "monday": "Poniedziałek", - "saturday": "Sobota", - "sunday": "Niedziela", - "thursday": "Czwartek", - "tuesday": "Wtorek", - "wednesday": "Środa" - }, - "welcomeBack": "Jesteś zalogowany, witaj z powrotem!" - }, "getStartedLogin": "Zaloguj się za pomocą OpenStreetMap, aby rozpocząć", "getStartedNewAccount": " lub utwórz nowe konto", "goToInbox": "Otwórz skrzynkę odbiorczą", - "index": { - "#": "Te teksty są wyświetlane nad przyciskami motywu, gdy nie jest załadowany żaden motyw", - "intro": "MapComplete to przeglądarka i edytor OpenStreetMap, który pokazuje informacje podzielone według tematu.", - "pickTheme": "Wybierz temat poniżej, aby rozpocząć.", - "title": "Witaj w MapComplete" - }, "layerSelection": { "title": "Wybierz warstwy", "zoomInToSeeThisLayer": "Powiększ, aby zobaczyć tę warstwę" diff --git a/langs/zh_Hant.json b/langs/zh_Hant.json index c7be5a7539..e0916e9bf4 100644 --- a/langs/zh_Hant.json +++ b/langs/zh_Hant.json @@ -90,130 +90,6 @@ "title": "下載可視的資料" }, "fewChangesBefore": "請先回答有關既有節點的問題再來新增新節點。", - "general": { - "about": "相當容易編輯,而且能為開放街圖新增特定主題", - "aboutMapcomplete": "

關於 MapComplete

使用 MapComplete 你可以藉由單一主題豐富開放街圖的圖資。回答幾個問題,然後幾分鐘之內你的貢獻立刻就傳遍全球!主題維護者定議主題的元素、問題與語言。

發現更多

MapComplete 總是提供學習更多開放街圖下一步的知識

  • 當你內嵌網站,網頁內嵌會連結到全螢幕的 MapComplete
  • 全螢幕的版本提供關於開放街圖的資訊
  • 不登入檢視成果,但是要編輯則需登入 OSM。
  • 如果你沒有登入,你會被要求先登入
  • 當你回答單一問題時,你可以在地圖新增新的節點
  • 過了一陣子,實際的 OSM-標籤會顯示,之後會連結到 wiki


你有注意到問題嗎?你想請求功能嗎?想要幫忙翻譯嗎?來到原始碼或是問題追蹤器。

想要看到你的進度嗎?到OsmCha追蹤編輯數。

", - "add": { - "addNew": "在這裡新增新的 {category}", - "confirmButton": "在此新增 {category}。
大家都可以看到您新增的內容
", - "confirmIntro": "

在這裡新增 {title} ?

你在這裡新增的節點所有人都看得到。請只有在確定有物件存在的情形下才新增上去,許多應用程式都使用這份資料。", - "intro": "您點擊處目前未有已知的資料。
", - "layerNotEnabled": "圖層 {layer} 目前無法使用,請先啟用這圖層再加新的節點", - "openLayerControl": "開啟圖層控制框", - "pleaseLogin": "請先登入來新增節點", - "stillLoading": "目前仍在載入資料,請稍後再來新增節點。", - "title": "新增新的節點?", - "zoomInFurther": "放大來新增新的節點。" - }, - "attribution": { - "attributionContent": "

所有資料由開放街圖提供,在開放資料庫授權條款之下自由再利用。

", - "attributionTitle": "署名通知", - "codeContributionsBy": "MapComplete 是由 {contributors} 和其他 {hiddenCount} 位貢獻者構建而成", - "iconAttribution": { - "title": "使用的圖示" - }, - "mapContributionsBy": "目前檢視的資料由 {contributors} 貢獻編輯", - "mapContributionsByAndHidden": "目前顯到的資料是由 {contributors} 和其他 {hiddenCount} 位貢獻者編輯貢獻", - "themeBy": "由 {author} 維護主題" - }, - "backgroundMap": "背景地圖", - "cancel": "取消", - "customThemeIntro": "

客製化主題

觀看這些先前使用者創造的主題。", - "fewChangesBefore": "請先回答有關既有節點的問題再來新增新節點。", - "getStartedLogin": "登入開放街圖帳號來開始", - "getStartedNewAccount": " 或是 註冊新帳號", - "goToInbox": "開啟訊息框", - "layerSelection": { - "title": "選擇圖層", - "zoomInToSeeThisLayer": "放大來看這個圖層" - }, - "loginToStart": "登入之後來回答這問題", - "loginWithOpenStreetMap": "用開放街圖帳號登入", - "morescreen": { - "createYourOwnTheme": "從零開始建立你的 MapComplete 主題", - "intro": "

看更多主題地圖?

您喜歡蒐集地理資料嗎?
還有更多主題。", - "requestATheme": "如果你有客製化要求,請到問題追踪器那邊提出要求", - "streetcomplete": "行動裝置另有類似的應用程式 StreetComplete。" - }, - "nameInlineQuestion": "這個 {category} 的名稱是 $$$", - "noNameCategory": "{category} 沒有名稱", - "noTagsSelected": "沒有選取標籤", - "number": "號碼", - "oneSkippedQuestion": "跳過一個問題", - "openStreetMapIntro": "

開放的地圖

如果有一份地圖,任何人都能自由使用與編輯,單一的地圖能夠儲存所有地理相關資訊?這樣不就很酷嗎?接著,所有的網站使用不同的、範圍小的,不相容的地圖 (通常也都過時了),也就不再需要了。

開放街圖就是這樣的地圖,人人都能免費這些圖資 (只要署名與公開變動這資料)。只要遵循這些,任何人都能自由新增新資料與修正錯誤,這些網站也都使用開放街圖,資料也都來自開放街圖,你的答案與修正也會加到開放街圖上面。

許多人與應用程式已經採用開放街圖了:Organic MapsOsmAnd,還有 Facebook、Instagram,蘋果地圖、Bing 地圖(部分)採用開放街圖。如果你在開放街圖上變動資料,也會同時影響這些應用 - 在他們下次更新資料之後!

", - "opening_hours": { - "closed_permanently": "不清楚關閉多久了", - "closed_until": "{date} 起關閉", - "error_loading": "錯誤:無法視覺化開放時間。", - "not_all_rules_parsed": "這間店的開放時間相當複雜,在輸入元素時忽略接下來的規則:", - "openTill": "結束時間", - "open_24_7": "24小時營業", - "open_during_ph": "國定假日的時候,這個場所是", - "opensAt": "開始時間", - "ph_closed": "無營業", - "ph_not_known": " ", - "ph_open": "有營業" - }, - "osmLinkTooltip": "在開放街圖歷史和更多編輯選項下面來檢視這物件", - "pickLanguage": "選擇語言: ", - "questions": { - "emailIs": "{category} 的電子郵件地址是{email}", - "emailOf": "{category} 的電子郵件地址是?", - "phoneNumberIs": "此 {category} 的電話號碼為 {phone}", - "phoneNumberOf": "{category} 的電話號碼是?", - "websiteIs": "網站:{website}", - "websiteOf": "{category} 的網站網址是?" - }, - "readYourMessages": "請先閱讀開放街圖訊息之前再來新增新節點。", - "returnToTheMap": "回到地圖", - "save": "儲存", - "search": { - "error": "有狀況發生了…", - "nothing": "沒有找到…", - "search": "搜尋地點", - "searching": "搜尋中…" - }, - "sharescreen": { - "addToHomeScreen": "

新增到您的主畫面

您可以輕易將這網站新增到您智慧型手機的主畫面,在網址列點選「新增到主畫面按鈕」來做這件事情。", - "copiedToClipboard": "複製連結到簡貼簿", - "editThemeDescription": "新增或改變這個地圖的問題", - "editThisTheme": "編輯這個主題", - "embedIntro": "

嵌入到你的網站

請考慮將這份地圖嵌入您的網站。
地圖毋須額外授權,非常歡迎您多加利用。
一切都是免費的,而且之後也是免費的,越有更多人使用,則越顯得它的價值。", - "fsAddNew": "啟用'新增新的興趣點'按鈕", - "fsGeolocation": "啟用'地理定位自身'按鈕 (只有行動版本)", - "fsIncludeCurrentBackgroundMap": "包含目前背景選擇{name}", - "fsIncludeCurrentLayers": "包含目前選擇圖層", - "fsIncludeCurrentLocation": "包含目前位置", - "fsLayerControlToggle": "開始時擴展圖層控制", - "fsLayers": "啟用圖層控制", - "fsSearch": "啟用搜尋列", - "fsUserbadge": "啟用登入按鈕", - "fsWelcomeMessage": "顯示歡迎訊息以及相關頁籤", - "intro": "

分享這地圖

複製下面的連結來向朋友與家人分享這份地圖:", - "thanksForSharing": "感謝分享!" - }, - "skip": "跳過這問題", - "skippedQuestions": "有些問題已經跳過了", - "weekdays": { - "abbreviations": { - "friday": "星期五", - "monday": "星期一", - "saturday": "星期六", - "sunday": "星期日", - "thursday": "星期四", - "tuesday": "星期二", - "wednesday": "星期三" - }, - "friday": "星期五", - "monday": "星期一", - "saturday": "星期六", - "sunday": "星期日", - "thursday": "星期四", - "tuesday": "星期二", - "wednesday": "星期三" - }, - "welcomeBack": "你已經登入了,歡迎回來!" - }, "getStartedLogin": "登入開放街圖帳號來開始", "getStartedNewAccount": " 或是 註冊新帳號", "goToInbox": "開啟訊息框", diff --git a/scripts/generateTranslations.ts b/scripts/generateTranslations.ts index 71dae3c505..7e3d4682d0 100644 --- a/scripts/generateTranslations.ts +++ b/scripts/generateTranslations.ts @@ -2,8 +2,6 @@ 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"]; @@ -275,7 +273,20 @@ function transformTranslation(obj: any, path: string[] = [], languageWhitelist : } value = nv; } - values += `${Utils.Times((_) => " ", path.length + 1)}get ${key}() { return new Translation(${JSON.stringify(value)}, "core:${path.join(".")}.${key}") }, + + + if(value["en"] === undefined){ + throw `At ${path.join(".")}: Missing 'en' translation for ${JSON.stringify(value)}` + } + const subParts : string[] = value["en"].match(/{[^}]*}/g) + let expr = `return new Translation(${JSON.stringify(value)}, "core:${path.join(".")}.${key}")` + if(subParts !== null){ + // convert '{to_substitute}' into 'to_substitute' + const types = Utils.Dedup( subParts.map(tp => tp.substring(1, tp.length - 1))) + expr = `return new TypedTranslation<{ ${types.join(", ")} }>(${JSON.stringify(value)}, "core:${path.join(".")}.${key}")` + } + + values += `${Utils.Times((_) => " ", path.length + 1)}get ${key}() { ${expr} }, ` } else { values += (Utils.Times((_) => " ", path.length + 1)) + key + ": " + transformTranslation(value, [...path, key], languageWhitelist) + ",\n" @@ -318,7 +329,7 @@ function genTranslations() { const translations = JSON.parse(fs.readFileSync("./assets/generated/translations.json", "utf-8")) const transformed = transformTranslation(translations); - let module = `import {Translation} from "../../UI/i18n/Translation"\n\nexport default class CompiledTranslations {\n\n`; + let module = `import {Translation, TypedTranslation} from "../../UI/i18n/Translation"\n\nexport default class CompiledTranslations {\n\n`; module += " public static t = " + transformed; module += "\n }"