From 70f27085b95efc6856dc02b5963ab50d94bed43b Mon Sep 17 00:00:00 2001 From: Maurice Date: Fri, 30 Oct 2026 23:04:15 +0100 Subject: [PATCH] feat(i18n): bring the Hebrew locale up to date with dev and list it in the docs Translate the 55 keys dev added since the branch was cut (TREK services allowance, Valhalla routing service, PDF routing progress, overview distances, Unsplash key hint, tour day limit) and drop the two reservation toasts dev removed, so he matches en key for key again. Refresh the two admin strings and the Valhalla hint whose English changed. Check he in the placeholder spec and the RTL test, and count 28 languages with Hebrew in the README and the Languages, Environment Variables, Contributing and Help Center wiki pages. --- README.md | 2 +- client/tests/unit/i18n/index.test.ts | 3 +- shared/src/i18n/he/admin.ts | 48 ++++++++++++++++++++++- shared/src/i18n/he/dashboard.ts | 2 + shared/src/i18n/he/dayplan.ts | 2 + shared/src/i18n/he/map.ts | 2 + shared/src/i18n/he/reservations.ts | 3 +- shared/src/i18n/he/settings.ts | 9 ++++- shared/src/i18n/he/tours.ts | 2 + shared/src/i18n/i18n-placeholders.spec.ts | 2 + wiki/Contributing.md | 4 +- wiki/Environment-Variables.md | 3 +- wiki/Help-Center.md | 2 +- wiki/Languages.md | 7 ++-- 14 files changed, 77 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 2e167a54b..7f6bd8da6 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,7 @@ Most of what follows is an addon an admin switches on or off. Lists, Costs, Docu - **Admin panel**: users and the permission matrix, defaults for new users, categories and templates, addons, plugins, storage, notifications, MCP tokens, backups, audit log and releases - **Backups and storage**: scheduled backups with retention that restore their own secrets, and local or S3-compatible storage per content category with replicas - **Appearance**: light, dark or follow the OS, seven colour schemes plus a custom accent, transparency, density, reduced motion and text size -- **27 languages**: en, de, es, fr, it, nl, hu, ru, zh, zh-TW, pl, cs, ar (right to left), br, id, tr, ja, ko, uk, gr, sv, vi, ca, az, et, sk, th +- **28 languages**: en, de, es, fr, it, nl, hu, ru, zh, zh-TW, pl, cs, ar (right to left), he (right to left), br, id, tr, ja, ko, uk, gr, sv, vi, ca, az, et, sk, th - **Help center**: a ? in the top bar with 381 step-by-step guides with screenshots, searched together with the wiki that ships in the image diff --git a/client/tests/unit/i18n/index.test.ts b/client/tests/unit/i18n/index.test.ts index cd3c12907..b24d4f7b8 100644 --- a/client/tests/unit/i18n/index.test.ts +++ b/client/tests/unit/i18n/index.test.ts @@ -75,8 +75,9 @@ describe('getIntlLanguage', () => { // ── FE-COMP-I18N-007/008: isRtlLanguage ────────────────────────────────────── describe('isRtlLanguage', () => { - it('FE-COMP-I18N-007: returns true only for Arabic', () => { + it('FE-COMP-I18N-007: returns true for Arabic and Hebrew', () => { expect(isRtlLanguage('ar')).toBe(true) + expect(isRtlLanguage('he')).toBe(true) }) it('FE-COMP-I18N-008: returns false for all other supported languages', () => { diff --git a/shared/src/i18n/he/admin.ts b/shared/src/i18n/he/admin.ts index df21623cf..78d867fa5 100644 --- a/shared/src/i18n/he/admin.ts +++ b/shared/src/i18n/he/admin.ts @@ -150,11 +150,25 @@ const admin: TranslationStrings = { 'admin.googleCaveat.body': 'TREK הוא קוד פתוח ואנחנו לא ניטרליים כאן. דירוגים ותמונות של עסקים רגילים קיימים בהיקף כזה רק אצל Google, וזה בדיוק מונופול. השדה כאן כי אין חלופה, לא כי אנחנו ממליצים עליו. כל חיפוש יישלח אז ל-Google.', 'admin.trekApi.tagline': - 'אינדקס המקומות של TREK עצמו. חיפוש בלי מפתח Google, בלי מכסה ובלי שמישהו סופר את החיפושים שלכם.', + 'השירותים של TREK עצמו: חיפוש מקומות, חיפוש כתובות וניווט, בלי מפתח Google ובלי מפתח להזין. כל התקנה מקבלת מכסה יומית הוגנת, ורק מספר הבקשות נספר.', 'admin.trekApi.factPlaces': '73.6 מיליון מקומות בעולם', - 'admin.trekApi.factNoKey': 'בלי מפתח, בלי מכסה', + 'admin.trekApi.factNoKey': 'בלי מפתח להזין, מכסה יומית הוגנת', 'admin.trekApi.factOffline': 'חבילות מדינה עובדות אופליין', 'admin.trekApi.factPrivacy': 'חיפושים לעולם לא נרשמים', + 'admin.trekApi.factAddresses': 'כתובות ויישובים בכל העולם, ב-26 שפות', + 'admin.trekApi.factRouting': 'ניווט ברכב, באופניים וברגל, כולל גובה', + 'admin.trekApi.addrStreets': 'רחובות ומספרי בתים', + 'admin.trekApi.addrTowns': 'יישובים ואזורים', + 'admin.trekApi.addrReverse': 'הכתובת של נקודה על המפה', + 'admin.trekApi.addrLanguages': '26 שפות', + 'admin.trekApi.addressNote': 'כל כתובת ב-OpenStreetMap, תוך כדי הקלדה ומנקודה על המפה.', + 'admin.trekApi.routeCar': 'ברכב', + 'admin.trekApi.routeBike': 'באופניים', + 'admin.trekApi.routeFoot': 'ברגל', + 'admin.trekApi.routeAvoid': 'הימנעות מכבישי אגרה, כבישים מהירים ומעבורות', + 'admin.trekApi.routeElevation': 'פרופיל גובה', + 'admin.trekApi.routingNote': + 'מסלולים על פני כל כדור הארץ, שנבנים מחדש מדי חודש מ-OpenStreetMap, עם גבהים מאריחי פני שטח.', 'admin.trekApi.more': 'מה יש בו', 'admin.trekApi.fieldPhone': 'טלפון', 'admin.trekApi.fieldStableId': 'מזהה קבוע', @@ -167,6 +181,36 @@ const admin: TranslationStrings = { 'admin.trekApi.sourcesNote': 'כל שדה בתשובה מציין מאיזה מהמקורות האלה הוא הגיע.', 'admin.trekApi.included': 'כלול', 'admin.trekApi.notIncluded': 'לא כלול', + 'admin.trekApi.quota.title': 'המכסה של היום', + 'admin.trekApi.quota.used': 'בשימוש', + 'admin.trekApi.quota.remaining': 'נותר', + 'admin.trekApi.quota.resets': 'מתאפס ב', + 'admin.trekApi.quota.limit': 'מגבלה יומית', + 'admin.trekApi.quota.key': 'מפתח', + 'admin.trekApi.quota.state.connected': 'מחובר', + 'admin.trekApi.quota.state.registering': 'בהגדרה', + 'admin.trekApi.quota.state.legacy': 'מאגר משותף', + 'admin.trekApi.quota.state.off': 'כבוי', + 'admin.trekApi.quota.state.revoked': 'המפתח בוטל', + 'admin.trekApi.quota.tier.std': 'רגיל', + 'admin.trekApi.quota.tier.hosted': 'מתארח', + 'admin.trekApi.quota.tier.own': 'מופע עצמאי', + 'admin.trekApi.quota.tier.legacy': 'משותף', + 'admin.trekApi.quota.hint.registering': 'ההתקנה הזו מקבלת את המפתח שלה ברקע. החיפושים ממשיכים לעבוד בינתיים.', + 'admin.trekApi.quota.hint.legacy': 'עדיין אין מפתח. החיפושים משתמשים במאגר המשותף עד שההתקנה תקבל מפתח בחיפוש הבא.', + 'admin.trekApi.quota.hint.off': 'לא בשימוש במופע הזה: כבוי, או מופנה לעותק משלכם.', + 'admin.trekApi.quota.hint.revoked': 'המפתח של ההתקנה הזו בוטל. החיפושים משתמשים במאגר המשותף עד שתיצרו זהות חדשה.', + 'admin.trekApi.quota.unavailable': 'לא ניתן לקרוא את המכסה כרגע.', + 'admin.trekApi.quota.service.places': 'חיפוש מקומות', + 'admin.trekApi.quota.service.photon': 'חיפוש כתובות', + 'admin.trekApi.quota.service.valhalla': 'ניווט', + 'admin.trekApi.quota.notInUse': 'לא בשימוש במופע הזה', + 'admin.trekApi.quota.envKey': 'המפתח מגיע מ-TREK_SERVICES_KEY.', + 'admin.trekApi.quota.regenerate': 'זהות חדשה', + 'admin.trekApi.quota.regenerateTitle': 'ליצור זהות חדשה?', + 'admin.trekApi.quota.regenerateBody': + 'ההתקנה הזו מוותרת על המפתח שלה ומבקשת מפתח חדש ברקע. החיפושים ממשיכים לעבוד בינתיים. שימושי אחרי העתקת ההתקנה למכונה אחרת, או אחרי שהמפתח בוטל.', + 'admin.trekApi.quota.regenerated': 'זהות חדשה נמצאת בהגדרה.', 'admin.mapsKey': 'מפתח API של Google Maps', 'admin.mapsKeyHint': 'נדרש לחיפוש מקומות. השיגו ב-console.cloud.google.com', 'admin.mapsKeyHintLong': diff --git a/shared/src/i18n/he/dashboard.ts b/shared/src/i18n/he/dashboard.ts index acefcb417..bb85cba82 100644 --- a/shared/src/i18n/he/dashboard.ts +++ b/shared/src/i18n/he/dashboard.ts @@ -117,6 +117,8 @@ const dashboard: TranslationStrings = { 'dashboard.unsplashQueryRequired': 'הזינו מונח חיפוש', 'dashboard.unsplashNoResults': 'לא נמצאו תמונות', 'dashboard.coverSearchError': 'החיפוש ב-Unsplash נכשל', + 'dashboard.unsplashKeyNeeded': + 'Unsplash דחה את השרת הזה. מנהל מערכת יכול להוסיף מפתח API חינמי של Unsplash בהגדרות הניהול כדי שחיפוש התמונות יעבוד.', 'dashboard.useUnsplashPhoto': 'שימוש בתמונה של {photographer} מ-Unsplash', 'dashboard.titleRequired': 'נדרשת כותרת', 'dashboard.endDateError': 'תאריך הסיום חייב להיות אחרי תאריך ההתחלה', diff --git a/shared/src/i18n/he/dayplan.ts b/shared/src/i18n/he/dayplan.ts index 3999ba287..88f459a71 100644 --- a/shared/src/i18n/he/dayplan.ts +++ b/shared/src/i18n/he/dayplan.ts @@ -69,6 +69,8 @@ const dayplan: TranslationStrings = { 'dayplan.pdfTooltip': 'ייצוא התוכנית היומית כ-PDF', 'dayplan.pdfMine': 'התוכנית שלי כ-PDF', 'dayplan.pdfMineSub': 'רק הפעילויות וההזמנות שאתם משתתפים בהן', + 'dayplan.pdfRouting': 'מחשב מסלולים: {done} מתוך {total}', + 'dayplan.pdfSkipRouting': 'ייצוא בלי המסלולים שנותרו', 'dayplan.gpxTooltip': 'ייצוא כ-GPX למפות לא מקוונות ומכשירי GPS', 'dayplan.gpxAll': 'כל הטיול', 'dayplan.gpxPlaces': 'מקומות בלבד', diff --git a/shared/src/i18n/he/map.ts b/shared/src/i18n/he/map.ts index bbcd6664b..94f674e24 100644 --- a/shared/src/i18n/he/map.ts +++ b/shared/src/i18n/he/map.ts @@ -33,6 +33,8 @@ const map: TranslationStrings = { 'map.lock.unlock': 'לתת למפה לעקוב אחרי הבחירה', 'map.overview.hide': 'הסתרת כל הטיול', 'map.overview.total': 'מרחק כולל', + 'map.overview.distanceAtLeast': 'לפחות {distance}', + 'map.overview.notRouted': 'ללא מסלול', 'map.attribution': 'קרדיטים למפה', 'map.overview.unrouted': 'לא ניתן היה לחשב מסלול ל-{count} קטעים, ולכן המרחקים חלקיים.', 'map.overview.unrouted.one': 'לא ניתן היה לחשב מסלול לקטע אחד, ולכן המרחקים חלקיים.', diff --git a/shared/src/i18n/he/reservations.ts b/shared/src/i18n/he/reservations.ts index 1316b856a..949260eb5 100644 --- a/shared/src/i18n/he/reservations.ts +++ b/shared/src/i18n/he/reservations.ts @@ -79,8 +79,7 @@ const reservations: TranslationStrings = { 'reservations.confirm.deleteBodyWithExpense': '"{name}" יימחק לצמיתות, יחד עם העלויות המקושרות אליו.', 'reservations.toast.updated': 'ההזמנה עודכנה', 'reservations.toast.removed': 'ההזמנה נמחקה', - 'reservations.toast.fileUploaded': 'הקובץ הועלה', - 'reservations.toast.uploadError': 'ההעלאה נכשלה', + 'reservations.toast.filesNotUploaded': 'ההזמנה נשמרה, אבל חלק מהקבצים לא הועלו. שמרו שוב כדי לשלוח אותם.', 'reservations.newTitle': 'הזמנה חדשה', 'reservations.bookingType': 'סוג ההזמנה', 'reservations.titleLabel': 'כותרת', diff --git a/shared/src/i18n/he/settings.ts b/shared/src/i18n/he/settings.ts index 4ae69ddbf..27d1f143c 100644 --- a/shared/src/i18n/he/settings.ts +++ b/shared/src/i18n/he/settings.ts @@ -38,7 +38,14 @@ const settings: TranslationStrings = { 'מופע OSRM משלכם. ריק משתמש בשרתים הציבוריים, שמאפשרים בערך בקשה אחת לשנייה — מספיק ליום, צפוף לטיול דרכים. נכנס לתוקף אחרי הפעלה מחדש של השרת.', 'settings.valhallaBase': 'מופע Valhalla משלכם', 'settings.valhallaBaseHint': - 'TREK משתמש כברירת מחדל ב-Valhalla הציבורי של FOSSGIS כדי להימנע מכבישי אגרה, כבישים מהירים ומעבורות. הזינו כאן כתובת Valhalla משלכם כדי להשתמש בה במקום. אם מוגדר רק מופע ניווט מותאם, ה-Valhalla הציבורי לא ישמש. אחרי הזנת כתובת מותאמת, הפעילו מחדש את השרת וטענו מחדש את העמוד.', + 'Valhalla משלכם, שנשאל כששירות הניווט למעלה מוגדר אליו או לאוטומטי. אם מוגדר רק מנוע ניווט משלכם ולא נבחר שירות ניווט, אף Valhalla לא נשאל. אחרי הזנת כתובת, הפעילו מחדש את השרת וטענו מחדש את העמוד.', + 'settings.valhallaProvider': 'שירות ניווט', + 'settings.valhallaProviderHint': + 'עם שירותי TREK או Valhalla משלכם, Valhalla מחשב כל מסלול: ימים וטיולי דרכים ברגל, באופניים וברכב, מסלולים שנמנעים מכבישי אגרה, כבישים מהירים או מעבורות, וגם המסלולים, הגבהים והשבילים של סיורים. כשהוא לא יכול לענות, OSRM מחשב את אותו יום במקומו. שירותי TREK נשאלים דרך השרת הזה. עם ה-Valhalla הציבורי של FOSSGIS, OSRM מחשב את המסלולים ו-Valhalla עונה רק על מה ש-OSRM לא יכול. אוטומטי שואל את שירותי TREK, אלא אם הוגדר Valhalla משלכם או רק מנוע ניווט משלכם.', + 'settings.valhallaProviderOption.auto': 'אוטומטי', + 'settings.valhallaProviderOption.trek': 'שירותי TREK', + 'settings.valhallaProviderOption.fossgis': 'Valhalla ציבורי (FOSSGIS)', + 'settings.valhallaProviderOption.custom': 'מופע Valhalla משלכם', 'settings.mapHint': 'תבנית URL לאריחי מפה', 'settings.mapProvider': 'ספק מפות', 'settings.mapProviderHint': 'משפיע על המפות במתכנן הטיולים וביומן המסע. האטלס תמיד משתמש ב-Leaflet.', diff --git a/shared/src/i18n/he/tours.ts b/shared/src/i18n/he/tours.ts index a6e42f4c1..58fd8fa47 100644 --- a/shared/src/i18n/he/tours.ts +++ b/shared/src/i18n/he/tours.ts @@ -144,6 +144,8 @@ const tours: TranslationStrings = { 'tours.planner.plannedTotalInfoLabel': 'מידע על המשך הכולל המתוכנן', 'tours.planner.plannedTotalManual': 'הגדרה ידנית', 'tours.planner.timeValueInvalid': 'הזינו מספר שלם בין 0 ל-1,440 דקות.', + 'tours.planner.plannedTotalOverDay': + 'זמן ההליכה וההפסקות מסתכמים ביותר מ-24 שעות ביום. הפכו את הסיור לרב-יומי עם "סיום מקטע כאן" בעצירה, או הזינו סך מתוכנן קצר יותר.', 'tours.planner.walkingTimeUnknown': 'לא חושב', 'tours.planner.walkingShort': 'הליכה', 'tours.planner.breaksShort': 'הפסקות', diff --git a/shared/src/i18n/i18n-placeholders.spec.ts b/shared/src/i18n/i18n-placeholders.spec.ts index 627fedcba..b47973b27 100644 --- a/shared/src/i18n/i18n-placeholders.spec.ts +++ b/shared/src/i18n/i18n-placeholders.spec.ts @@ -9,6 +9,7 @@ import es from './es'; import et from './et'; import fr from './fr'; import gr from './gr'; +import he from './he'; import hu from './hu'; import id from './id'; import itIT from './it'; @@ -61,6 +62,7 @@ const LOCALES: Record = { et, fr, gr, + he, hu, id, it: itIT, diff --git a/wiki/Contributing.md b/wiki/Contributing.md index 35a5211a4..9177e67ec 100644 --- a/wiki/Contributing.md +++ b/wiki/Contributing.md @@ -67,10 +67,10 @@ See the [[Development Environment|Development-environment]] page for the full se | Database | SQLite with WAL mode | | Auth | JWT (HS256), bcrypt, TOTP MFA, passkeys (WebAuthn), OIDC | | Maps | Leaflet + react-leaflet (default, OpenFreeMap vector basemap via maplibre-gl-leaflet), MapLibre GL 5, Mapbox GL 3, Valhalla, OSRM, Photon, Nominatim | -| i18n | 27 languages, EN canonical (locale directories live in shared/src/i18n/) | +| i18n | 28 languages, EN canonical (locale directories live in shared/src/i18n/) | Documentation changes go to `wiki/`. The same pages ship inside the app: the in-app help reads them (served under `/api/help`) and links into them, and the MCP help tools search them, so a wiki edit reaches users with the next release as well as the GitHub wiki. Beyond the tests, CI enforces a set of quality gates: type checks, lint ratchets that may only shrink, and a SonarCloud gate on new code (80% coverage, at most 3% duplicated lines). The [[Development Environment|Development-environment]] page lists the commands, and the `CLAUDE.md` file of each package explains its rules. Desktop and phone views of a feature share one hook, so a feature that touches both keeps its logic in that hook. -Every translation key must exist in all 27 locales, the `Shared: i18n Parity` CI job fails on drift, so run `npm run i18n:parity:strict --workspace=shared` before pushing. Add a real translation to every locale; an English placeholder in a non-English file is not accepted. Two directory names differ from the language they hold: Brazilian Portuguese is `br`, Greek is `gr`. +Every translation key must exist in all 28 locales, the `Shared: i18n Parity` CI job fails on drift, so run `npm run i18n:parity:strict --workspace=shared` before pushing. Add a real translation to every locale; an English placeholder in a non-English file is not accepted. Two directory names differ from the language they hold: Brazilian Portuguese is `br`, Greek is `gr`. diff --git a/wiki/Environment-Variables.md b/wiki/Environment-Variables.md index d554582cf..bfdc6413d 100644 --- a/wiki/Environment-Variables.md +++ b/wiki/Environment-Variables.md @@ -97,7 +97,7 @@ Setting `ENCRYPTION_KEY` explicitly is recommended so you can back it up indepen ### `DEFAULT_LANGUAGE` — Supported Codes -You can set `DEFAULT_LANGUAGE` to any of the 27 languages TREK ships. The currently supported codes are: +You can set `DEFAULT_LANGUAGE` to any of the 28 languages TREK ships. The currently supported codes are: | Code | Language | |---------|--------------------| @@ -119,6 +119,7 @@ You can set `DEFAULT_LANGUAGE` to any of the 27 languages TREK ships. The curren | `tr` | Türkçe | | `ar` | العربية | | `az` | Azərbaycanca | +| `he` | עברית | | `id` | Bahasa Indonesia | | `ja` | 日本語 | | `ko` | 한국어 | diff --git a/wiki/Help-Center.md b/wiki/Help-Center.md index 6e50cf851..0a1034971 100644 --- a/wiki/Help-Center.md +++ b/wiki/Help-Center.md @@ -92,7 +92,7 @@ cost nothing to a visitor who never asks for help. ## Languages -The guides are written in English first and translated into all 27 languages TREK speaks (see +The guides are written in English first and translated into all 28 languages TREK speaks (see [Languages](Languages)). Every button and field a guide names is the app's own string in that language, so what you read matches what you see. diff --git a/wiki/Languages.md b/wiki/Languages.md index 1353951fd..5956c4a4e 100644 --- a/wiki/Languages.md +++ b/wiki/Languages.md @@ -1,6 +1,6 @@ # Languages -TREK ships with translations for 27 languages, the [Help Center](Help-Center) guides included. You can change your language at any time without logging out. +TREK ships with translations for 28 languages, the [Help Center](Help-Center) guides included. You can change your language at any time without logging out. ## Supported languages @@ -24,6 +24,7 @@ TREK ships with translations for 27 languages, the [Help Center](Help-Center) gu | `tr` | Türkçe | | `ar` | العربية | | `az` | Azərbaycanca | +| `he` | עברית | | `id` | Bahasa Indonesia | | `ja` | 日本語 | | `ko` | 한국어 | @@ -40,11 +41,11 @@ Place search, suggestions, place details and addresses follow the app language b ## RTL support -Arabic (`ar`) uses a right-to-left layout. All other languages use left-to-right. In Arabic, spacing, indents, alignment and the corners of cards and menus follow the reading direction; maps, timelines and other things placed by position stay as they are. +Arabic (`ar`) and Hebrew (`he`) use a right-to-left layout. All other languages use left-to-right. In Arabic and Hebrew, spacing, indents, alignment and the corners of cards and menus follow the reading direction; maps, timelines and other things placed by position stay as they are. ## Counts -Texts that count something use the forms each language has for numbers: one place and five places in English, and the separate forms Russian, Polish, Czech, Ukrainian and Arabic use for 2, 5 or 21 of something. Languages without plural forms, such as Japanese or Chinese, use one form for every number. +Texts that count something use the forms each language has for numbers: one place and five places in English, and the separate forms Russian, Polish, Czech, Ukrainian, Arabic and Hebrew use for 2, 5 or 21 of something. Languages without plural forms, such as Japanese or Chinese, use one form for every number. ## How language is detected