Skip to content

Commit fd74428

Browse files
committed
Ship the translated catalogs pulled from Transifex
The Transifex config addressed o:maproulette:p:maproulette4:r:main on the retired www.transifex.com host, so nothing could be pulled through it. It now points at o:osmlab:p:maproulette3:r:v4 on app.transifex.com with en as the source language, and carries the 50% minimum alongside the lang_map that spells pt_BR, pt_PT, ru_RU and zh_TW the way the loader expects. Pulling brings in fifteen locales, each carrying whatever MapRoulette 3 translated that MapRoulette 4 still uses -- between 57 and 200 of the source catalog's 1484 keys. loadCatalog merges them over English, so a partial catalog translates what it covers and leaves the rest in English. The missing-locale test used de as its stand-in for a locale with no catalog, which a shipped de.json turns into a false pass. It asks for a tag that genuinely has no file now, and a second case covers the other half of the real import() path: a catalog that does exist merges over the base rather than silently degrading to English.
1 parent a886353 commit fd74428

17 files changed

Lines changed: 2803 additions & 11 deletions

File tree

.tx/config

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
[main]
2-
host = https://www.transifex.com
3-
lang_map = pt_BR:pt-BR, pt_PT:pt-PT, ru_RU:ru-RU, zh_TW:zh-TW
2+
host = https://app.transifex.com
43

5-
[o:maproulette:p:maproulette4:r:main]
6-
file_filter = src/i18n/messages/<lang>.json
7-
source_file = src/i18n/messages/en-US.json
8-
source_lang = en-US
9-
type = KEYVALUEJSON
4+
[o:osmlab:p:maproulette3:r:v4]
5+
file_filter = src/i18n/messages/<lang>.json
6+
source_file = src/i18n/messages/en-US.json
7+
source_lang = en
8+
type = KEYVALUEJSON
109
minimum_perc = 50
10+
lang_map = pt_BR:pt-BR, pt_PT:pt-PT, ru_RU:ru-RU, zh_TW:zh-TW

src/i18n/messageFormatting.test.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ vi.mock('intl-messageformat', async () => {
1414
})
1515

1616
import { IntlMessageFormat } from 'intl-messageformat'
17+
import type { Locale } from './locales'
1718
import { clearFormatCache, formatMessage, loadCatalog } from './messageFormatting'
1819

1920
const baseCatalog = enUS as Record<string, string>
@@ -132,11 +133,25 @@ describe('loadCatalog', () => {
132133
})
133134

134135
it('falls back to the base catalog for a completely missing locale using the real loader', async () => {
135-
// No de.json exists in messages/, so the default loader should reject and
136-
// loadCatalog should fall back to the base catalog exactly like the
137-
// explicit-rejection case above, exercising the real import() path.
138-
const result = await loadCatalog('de')
136+
// Translated catalogs now ship for every locale that has Transifex
137+
// coverage, so this needs a tag that is genuinely absent from messages/ to
138+
// make the default loader reject and exercise the real import() path.
139+
const missingLocale = 'xx-XX' as unknown as Locale
140+
141+
const result = await loadCatalog(missingLocale)
139142

140143
expect(result).toBe(baseCatalog)
141144
})
145+
146+
it('merges a shipped translated catalog over the base catalog using the real loader', async () => {
147+
// Guards the real import() path against a locale catalog that exists but
148+
// is unreadable/malformed, which would silently degrade to English.
149+
const result = await loadCatalog('fr')
150+
151+
expect(result).not.toBe(baseCatalog)
152+
// Carried-over French translations are a subset of the source catalog, so
153+
// every key still resolves while at least one differs from its English.
154+
expect(Object.keys(result).length).toBe(Object.keys(baseCatalog).length)
155+
expect(Object.entries(result).some(([k, v]) => v !== baseCatalog[k])).toBe(true)
156+
})
142157
})

src/i18n/messages/de.json

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
{
2+
"appLayout.header.appName": "MapRoulette",
3+
"appLayout.header.globalSearch.findFeatureByName.exploreChallenges": "Kampagnen entdecken",
4+
"appLayout.header.userMenu.documentation": "Dokumentation",
5+
"appLayout.header.userMenu.signOut": "Abmelden",
6+
"browsedChallengePage.actionButtons.manageChallenge": "Kampagne verwalten",
7+
"browsedChallengePage.challengeModals.actions.completed": "Abgeschlossen",
8+
"browsedChallengePage.challengeModals.comments.owner": "Eigentümer",
9+
"browsedChallengePage.challengeModals.reportModal.checkboxError": "Bitte stelle sicher, dass das Kontrollkästchen aktiviert ist, bevor Du fortfährst.",
10+
"browsedChallengePage.challengeModals.reportModal.confirmLabel": "Ich habe versucht, den Ersteller der Challenge zu kontaktieren",
11+
"browsedChallengePage.challengeModals.reportModal.emailPlaceholder": "E-Mail Adresse eingeben",
12+
"browsedChallengePage.challengeModals.reportModal.textPlaceholder": "Text hier eingeben",
13+
"browsedChallengePage.footer.browseChallenge": "Kampagnen durchsuchen",
14+
"browsedChallengePage.footer.hideMap": "Karte ausblenden",
15+
"browsedChallengePage.footer.showMap": "Karte anzeigen",
16+
"browsedChallengePage.footer.startChallenge": "Kampagne starten",
17+
"browsedChallengePage.header.comments": "Kommentare",
18+
"browsedChallengePage.header.removeFromFavorites": "Aus Favoritenliste entfernen",
19+
"browsedChallengePage.header.taxonomy.cooperative": "Team",
20+
"browsedChallengePage.header.taxonomy.favorite": "Als Favorit",
21+
"browsedChallengePage.header.taxonomy.tagFix": "Tag Fix",
22+
"browsedChallengePage.progress.details": "Details",
23+
"common.actions": "Aktionen",
24+
"common.admin": "Administrator",
25+
"common.allChallenges": "Alle Kampagnen",
26+
"common.alreadyFixed": "Bereits behoben",
27+
"common.approved": "Bestätigt",
28+
"common.archived": "Archiviert",
29+
"common.available": "Verfügbar",
30+
"common.cancel": "Abbrechen",
31+
"common.cantComplete": "Zu schwierig",
32+
"common.challenge": "Kampagne",
33+
"common.challenges": "Kampagnen",
34+
"common.clear": "Löschen",
35+
"common.cloneChallenge": "Kampagne klonen",
36+
"common.completed": "Abgeschlossen",
37+
"common.cooperative": "Team",
38+
"common.copy": "Kopieren",
39+
"common.copyUrl": "URL kopieren",
40+
"common.created": "Erstellt",
41+
"common.current": "Aktuell",
42+
"common.delete": "Löschen",
43+
"common.deleted": "Gelöscht",
44+
"common.description": "Beschreibung",
45+
"common.difficulty": "Schwierigkeit",
46+
"common.disabled": "Deaktiviert",
47+
"common.discoverable": "Auffindbar",
48+
"common.easy": "Leicht",
49+
"common.edit": "Bearbeiten",
50+
"common.expert": "Experte",
51+
"common.exportCsv": "Exportiere CSV",
52+
"common.featured": "Empfohlen",
53+
"common.fixed": "Behoben",
54+
"common.geojson": "GeoJSON",
55+
"common.instructions": "Anleitung",
56+
"common.invited": "Eingeladen",
57+
"common.loadMore": "Mehr laden",
58+
"common.manage": "Verwalten",
59+
"common.member": "Mitglied",
60+
"common.modified": "Bearbeitet",
61+
"common.mrTags": "MR Tags",
62+
"common.name": "Name",
63+
"common.newest": "Neu",
64+
"common.noProjectsFound": "Keine Projekte gefunden",
65+
"common.normal": "Normal",
66+
"common.notAnIssue": "Kein Fehler",
67+
"common.notification": "Nachricht",
68+
"common.overpassQuery": "Overpass-Abfrage",
69+
"common.owned": "Im Besitz",
70+
"common.pinned": "Angeheftet",
71+
"common.popular": "Beliebt",
72+
"common.preview": "Vorschau",
73+
"common.priority": "Priorität",
74+
"common.project": "Projekt",
75+
"common.projects": "Projekte",
76+
"common.recentActivity": "Neueste Aktivität",
77+
"common.remove": "Entfernen",
78+
"common.reportChallenge": "Challenge melden",
79+
"common.save": "Speichern",
80+
"common.share": "Teilen",
81+
"common.shown": "Gezeigt",
82+
"common.signIn": "Anmelden",
83+
"common.skipped": "Übersprungen",
84+
"common.status": "Status",
85+
"common.tagFix": "Tag Fix",
86+
"common.task": "Aufgabe",
87+
"common.tasks": "Aufgaben",
88+
"common.teams": "Teams",
89+
"common.type": "Typ",
90+
"common.unnecessary": "Nicht benötigt",
91+
"common.view": "Ansehen",
92+
"common.write": "Schreiben",
93+
"configureColumnsDialog.done": "Erledigt",
94+
"configureColumnsDialog.reset": "Zurücksetzen",
95+
"configureColumnsDialog.title": "Spalten verwalten",
96+
"dashboard.roulette.card.startTask": "Aufgabe starten",
97+
"errorBoundary.actions.goBack": "Zurück",
98+
"exploreChallenges.challengeList.table.contributors": "Beitragende",
99+
"exploreChallenges.challengeList.table.disabled": "Deaktiviert",
100+
"exploreChallenges.challengeList.table.location": "Ort",
101+
"exploreChallenges.filterBar.difficulty.any": "Egal",
102+
"exploreChallenges.filterBar.sortBy.label": "Sortieren nach",
103+
"exploreChallenges.filterBar.sortBy.oldest": "Älteste",
104+
"exploreChallenges.filterBar.workOn.anything": "Alles",
105+
"exploreChallenges.filterBar.workOn.buildings": "Gebäude",
106+
"exploreChallenges.filterBar.workOn.label": "Thema",
107+
"exploreChallenges.filterBar.workOn.landUseAdministrativeBoundaries": "Landnutzung und Grenzen",
108+
"exploreChallenges.filterBar.workOn.pointsAreasOfInterest": "Interessante Orte",
109+
"exploreChallenges.filterBar.workOn.roadsPedestrianCycleways": "Straßen und Wege",
110+
"exploreChallenges.filterBar.workOn.transit": "Verkehr",
111+
"exploreChallenges.filterBar.workOn.water": "Wasser",
112+
"manageChallengeDetail.detail.statistics.completion": "Fertigstellung",
113+
"manageChallengeDetail.export.button": "Exportieren",
114+
"manageChallengeDetail.export.confirm": "Download",
115+
"manageChallengeDetail.statusIndicator.failedTitle": "Erstellen von Aufgaben fehlgeschlagen",
116+
"manageChallengeDetail.statusIndicator.tasksBuildingTitle": "Aufgaben werden erstellt...",
117+
"manageChallengeDetail.statusIndicator.tasksUpdatingTitle": "Aufgaben aktualisieren...",
118+
"manageChallengeDetail.tasksExplorer.columnBundle": "Gruppe",
119+
"manageChallengeNew.challengeForm.agreementCheckboxLabel": "Ich habe den OSM Automated Edits Verhaltenskodex gelesen und verstanden.",
120+
"manageChallengeNew.challengeForm.agreementSectionTitle": "Vereinbarung über den Verhaltenskodex von automatisierten Änderungen ",
121+
"manageChallengeNew.challengeForm.basemapTitle": "Kartenhintergrund",
122+
"manageChallengeNew.challengeForm.instructionsDescriptionAnd": "und",
123+
"manageChallengeNew.challengeForm.localGeoJSONOptionTitle": "GeoJSON-Datei hochladen",
124+
"manageChallengeNew.challengeForm.osmIdPropertyLabel": "OSM/Externe ID-Eigenschaft",
125+
"manageChallengeNew.challengeForm.overpassOptionTitle": "Ich möchte eine Overpass-Abfrage stellen",
126+
"manageChallengeNew.challengeForm.remoteGeoJSONOptionTitle": "Ich habe eine URL zu den GeoJSON-Daten",
127+
"manageProjectDetail.managers.searchPlaceholder": "OpenStreetMap-Benutzername",
128+
"manageProjectDetail.managers.teamsTitle": "Teams",
129+
"manageProjectNew.projectForm.displayNameLabel": "Anzeigename",
130+
"manageProjects.content.aboutTitle": "Über Projekte",
131+
"manageTaskEdit.form.nameDescription": "Aufgaben-Name",
132+
"manageTaskEdit.index.taskDetailsTitle": "Aufgabendetails",
133+
"manageTaskNew.title": "Neue Aufgabe",
134+
"manageTasksOpen.or": "oder",
135+
"managementPages.bulkActionsToolbar.tag": "Tag",
136+
"managementPages.rebuildTasksDialog.introLocal": "Beim Neuaufbau kann eine neue lokale Datei mit den neuesten GeoJSON-Daten hochgeladen und die Aufgaben der Kampagne aktualisiert werden:",
137+
"managementPages.rebuildTasksDialog.introOverpass": "Beim Neuaufbau werden die Overpass-Abfrage wiederholt und die Aufgaben der Kampagne mit den neuesten Daten aktualisiert:",
138+
"managementPages.rebuildTasksDialog.learnMore": "Mehr erfahren",
139+
"managementPages.rebuildTasksDialog.proceed": "Weiter",
140+
"managementPages.rebuildTasksDialog.removeIncomplete": "Zuerst nicht erledigte Aufgaben entfernen",
141+
"managementPages.rebuildTasksDialog.title": "Aufgaben der Kampagne wiederherstellen",
142+
"managementPages.rebuildTasksDialog.warning": "Warnung: Ein Neuaufbau kann zur Doppelung von Aufgaben führen, wenn Objekt-IDs nicht richtig eingerichtet sind oder wenn der Abgleich alter Daten mit neuen Daten nicht erfolgreich ist. Dieser Vorgang kann nicht rückgängig gemacht werden!",
143+
"map.layerControl.customLayers.panel.add": "Hinzufügen",
144+
"map.layerControl.customLayers.row.remove": "Entfernen",
145+
"map.styleSwitcher.nameLabel": "Name",
146+
"map.styleSwitcher.typeLabel": "Typ",
147+
"moveChallengeModal.title": "Kampagne verschieben",
148+
"notificationsPage.content.groupByTask": "Nach Aufgabe gruppieren",
149+
"notificationsPage.content.title": "Nachrichten",
150+
"notificationsPage.filters.clearFilters": "Filter löschen",
151+
"notificationsPage.filters.fromPlaceholder": "Von",
152+
"notificationsPage.item.challengeLabel": "Kampagne:",
153+
"profilePage.achievements.title": "Auszeichnungen",
154+
"profilePage.follow.block": "Sperren",
155+
"profilePage.follow.follow": "Folgen",
156+
"profilePage.follow.title": "folgen",
157+
"profilePage.follow.unblock": "Entsperren",
158+
"profilePage.follow.unfollow": "folgen",
159+
"profilePage.header.osmChaLink": "OSMCha",
160+
"profilePage.header.osmProfileLink": "OSM Profil",
161+
"profilePage.metrics.title": "Statistik",
162+
"profilePage.topChallenges.title": "Top-Kampagnen",
163+
"rapidEditor.reselectTask": "Wähle Aufgabe erneut aus",
164+
"superAdminChallengeReports.archivedBadge": "Archiviert",
165+
"superAdminChallenges.stats.totalTasks": "Aufgabenanzahl",
166+
"superAdminHome.cards.users.title": "Benutzer",
167+
"superAdminPlugins.card.configure": "Einrichten",
168+
"superAdminProjects.title": "Alle Projekte",
169+
"superAdminTeamImages.approve": "Bestätigen",
170+
"superAdminTeamImages.reject": "Verwerfen",
171+
"superAdminUsers.table.columns.email": "E-Mail",
172+
"superAdminUsers.table.columns.score": "Punktestand",
173+
"superAdminUsers.table.columns.user": "Benutzer",
174+
"superAdminUsers.table.next": "Nächste",
175+
"superAdminUsers.table.previous": "Vorherige",
176+
"taskEditPage.idEditor.collapsePanel": "Panel ausblenden",
177+
"taskEditPage.idEditor.expandPanel": "Panel einblenden",
178+
"taskEditPage.keyboardShortcuts.title": "Tastenkombinationen",
179+
"taskEditPage.pendingEdits.created": "Erstellt",
180+
"taskEditPage.pendingEdits.deleted": "Gelöscht",
181+
"taskEditPage.pendingEdits.modified": "Bearbeitet",
182+
"taskEditPage.taskActionModal.nearbyTask.label": "Aufgabe in der Nähe",
183+
"taskEditPage.taskActionModal.nextTask": "Nächste",
184+
"taskEditPage.taskActions.editorButton.editInId": "In iD bearbeiten",
185+
"taskEditPage.taskActions.editorButton.setDefaultEditor": "Standard-Editor einstellen:",
186+
"taskEditPage.taskActions.lockButton.locked": "Aufgabe gesperrt",
187+
"taskEditPage.taskActions.lockButton.unlock": "Freischalten",
188+
"taskEditPage.taskActions.main.notAnIssue": "Kein Fehler",
189+
"taskEditPage.taskMap.editInIdShort": "In iD bearbeiten",
190+
"taskInfoPanel.comments.systemUser": "System",
191+
"taskInfoPanel.data.properties": "Eigenschaften",
192+
"taskInfoPanel.drawer.startTask": "Aufgabe starten",
193+
"taskInfoPanel.editorChanges.reset": "Zurücksetzen",
194+
"taskPrioritizationPage.tierWarningBadges.noMatches": "Keine Treffer",
195+
"taskPropertyQueryBuilder.ruleGroup.addGroup": "Gruppe",
196+
"taskPropertyQueryBuilder.ruleLeaf.typeNumber": "Zahl",
197+
"taskPropertyQueryBuilder.ruleLeaf.valuePlaceholder": "Wert",
198+
"taskTags.taskTags.editTags": "Tags bearbeiten",
199+
"teamImages.status.approved": "Bestätigt",
200+
"teams.form.descriptionDescription": "Eine Kurzbeschreibung des Teams",
201+
"teams.form.nameDescription": "Der eindeutige Name des Teams"
202+
}

0 commit comments

Comments
 (0)