Introduce a hand-rolled i18n layer across all three UIs, each reading its
text from per-language JSON files (English base + Polish + Danish). Nothing
in the converted screens hardcodes English any more.
- Web App (Vue): src/i18n/{en,pl,da}.json + index.js exposing t()/tSplit(),
reactive to the signed-in profile locale. Every view, component, form and
the status labels in lib/format.js go through t().
- API Server panel (Vue): src/i18n/ with its own localStorage-persisted
language (the panel has no user profile) and a header language picker.
Chrome, cards, login and API section titles translated; endpoint reference
descriptions intentionally kept in English. Rebuilt embedded dist.
- Phone App (Flutter): assets/i18n/ + lib/i18n.dart loaded at startup,
driven by AppSettings.locale. Nav, login, lock, dashboard, the full
Settings panel (incl. language picker) and format.dart status labels
translated; remaining detail screens fall back to English.
Language = the language half of the existing BCP-47 locale; the region half
still drives date/number/currency formatting. Missing keys fall back to
English, and plurals use Intl.PluralRules / Intl.plural so Polish gets the
correct one/few/many forms. Settings flags languages without a translation.
Tests updated to assert the localized (Polish) status wording; all pass.
See TRANSLATIONS.md for the format and how to add a language.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
205 lines
6.9 KiB
JSON
205 lines
6.9 KiB
JSON
{
|
|
"common": {
|
|
"save": "Save",
|
|
"saving": "Saving…",
|
|
"saved": "Saved ✓",
|
|
"cancel": "Cancel",
|
|
"remove": "Remove",
|
|
"retry": "Retry",
|
|
"required": "Required",
|
|
"confirm": "Confirm",
|
|
"empty": "—"
|
|
},
|
|
|
|
"nav": {
|
|
"garage": "Garage",
|
|
"settings": "Settings",
|
|
"users": "Users"
|
|
},
|
|
|
|
"login": {
|
|
"tagline": "Your car, on track.",
|
|
"email": "Email",
|
|
"password": "Password",
|
|
"showPassword": "Show password",
|
|
"hidePassword": "Hide password",
|
|
"submit": "Sign in",
|
|
"submitting": "Signing in…",
|
|
"or": "or",
|
|
"signInWithFace": "Sign in with face recognition",
|
|
"signInWithFingerprint": "Sign in with fingerprint",
|
|
"signInWithBiometrics": "Sign in with biometrics",
|
|
"enrollTitle": "Enable biometric sign-in?",
|
|
"enrollBody": "Next time you can sign in with {method} instead of typing your password.",
|
|
"enrollNotNow": "Not now",
|
|
"enrollEnable": "Enable",
|
|
"methodFace": "face recognition",
|
|
"methodFingerprint": "your fingerprint",
|
|
"methodBiometrics": "biometrics",
|
|
"bioReasonSignInAs": "Sign in as {email}",
|
|
"bioReasonSignIn": "Sign in to DriverVault",
|
|
"savedInvalid": "Saved sign-in is no longer valid. Please sign in with your password.",
|
|
"serverSettings": "Server settings",
|
|
"apiServerUrl": "API server URL",
|
|
"leaveBlank": "Leave blank to use the default.",
|
|
"reset": "Reset",
|
|
"savedNote": "Saved ✓",
|
|
"resetNote": "Reset ✓"
|
|
},
|
|
|
|
"lock": {
|
|
"title": "DriverVault is locked",
|
|
"reason": "Unlock DriverVault",
|
|
"unlock": "Unlock",
|
|
"unlocking": "Unlocking…",
|
|
"usePassword": "Use password instead"
|
|
},
|
|
|
|
"dashboard": {
|
|
"eyebrow": "YOUR CARS",
|
|
"title": "Garage",
|
|
"addCar": "Add car",
|
|
"lightMode": "Light mode",
|
|
"darkMode": "Dark mode",
|
|
"logOut": "Log out",
|
|
"empty": "No cars yet.",
|
|
"shared": "Shared",
|
|
"sharedReadOnly": "Shared · read-only",
|
|
"serviceLife": "SERVICE LIFE",
|
|
"lastService": "Last service",
|
|
"currentOdometer": "Current odometer",
|
|
"nextDue": "Next due",
|
|
"nextDueKm": "Next due km",
|
|
"serviceRecords": {
|
|
"one": "{n} service record",
|
|
"other": "{n} service records"
|
|
}
|
|
},
|
|
|
|
"settings": {
|
|
"title": "Settings",
|
|
|
|
"account": {
|
|
"title": "Account",
|
|
"name": "Name",
|
|
"nameSaved": "Name saved.",
|
|
"email": "Email",
|
|
"verified": "Verified",
|
|
"notVerified": "Not verified",
|
|
"sending": "Sending…",
|
|
"resendVerification": "Resend verification email",
|
|
"verificationRequested": "Verification email requested.",
|
|
"changePassword": "Change password",
|
|
"currentPassword": "Current password",
|
|
"newPassword": "New password (min 8)",
|
|
"confirmNewPassword": "Confirm new password",
|
|
"tooShort": "New password must be at least 8 characters.",
|
|
"mismatch": "New password and confirmation don't match.",
|
|
"passwordUpdated": "Password updated.",
|
|
"updating": "Updating…",
|
|
"updatePassword": "Update password"
|
|
},
|
|
|
|
"appearance": {
|
|
"title": "Appearance",
|
|
"theme": "Theme",
|
|
"themeLight": "Light",
|
|
"themeDark": "Dark",
|
|
"themeSystem": "System",
|
|
"language": "Language",
|
|
"languageHint": "App text, and the names of months and days.",
|
|
"languageFallbackHint": "This language isn't translated yet — the app text stays in English.",
|
|
"region": "Region",
|
|
"regionHint": "Number and currency layout.",
|
|
"currency": "Currency",
|
|
"currencyHint": "Example: {example}. Amounts are display-only — nothing is converted.",
|
|
"dateFormat": "Date format",
|
|
"dateHint": "Example: {example}",
|
|
"fontSize": "Font size",
|
|
"fontSmall": "Small",
|
|
"fontMedium": "Medium",
|
|
"fontLarge": "Large"
|
|
},
|
|
|
|
"profile": {
|
|
"title": "Profile",
|
|
"working": "Working…",
|
|
"uploadPhoto": "Upload photo",
|
|
"remove": "Remove",
|
|
"bio": "Bio",
|
|
"bioHint": "A short note visible to other people in your household.",
|
|
"bioSaved": "Bio saved.",
|
|
"saveBio": "Save bio"
|
|
},
|
|
|
|
"security": {
|
|
"title": "Security",
|
|
"checkingDevice": "Checking device…",
|
|
"biometricSignIn": "Biometric sign-in",
|
|
"biometricAvailable": "Sign in with {method} instead of your password.",
|
|
"biometricUnavailable": "No biometrics are enrolled on this device.",
|
|
"methodFaceOrFingerprint": "face or fingerprint",
|
|
"methodFace": "face recognition",
|
|
"methodFingerprint": "fingerprint",
|
|
"methodBiometrics": "biometrics",
|
|
"turnedOff": "Biometric sign-in turned off",
|
|
"enabled": "Biometric sign-in enabled",
|
|
"couldNotEnable": "Could not enable: {error}",
|
|
"confirmPassword": "Confirm your password",
|
|
"password": "Password"
|
|
},
|
|
|
|
"privacy": {
|
|
"title": "Privacy & security",
|
|
"body": "Two-factor authentication isn't available yet. Sessions are held as server-issued tokens that expire on their own, so signing out ends this device's session only. To lock out every device, change your password above.",
|
|
"signOut": "Sign out"
|
|
},
|
|
|
|
"danger": {
|
|
"title": "Danger zone",
|
|
"body": "Deleting your account removes your login and profile. It does not delete your household's shared cars or service history. There's a 3-day cooldown before deletion is final, and you can cancel any time before then.",
|
|
"deleteAccount": "Delete my account",
|
|
"typeToConfirm": "Type {email} to confirm",
|
|
"typeEmailPrompt": "Type your email to confirm.",
|
|
"requesting": "Requesting…",
|
|
"requestDeletion": "Request deletion",
|
|
"requestedOn": "Account deletion requested on {date}. {tail}",
|
|
"cooldownPassed": "The cooldown has passed. You can now finalize the deletion.",
|
|
"canStillCancel": "You can still cancel — it becomes permanent after the 3-day cooldown.",
|
|
"cancelRequest": "Cancel deletion request",
|
|
"permanentlyDelete": "Permanently delete",
|
|
"finalizeTitle": "Delete account?",
|
|
"finalizeBody": "This permanently deletes your account. This cannot be undone.",
|
|
"delete": "Delete"
|
|
}
|
|
},
|
|
|
|
"status": {
|
|
"noData": "No data",
|
|
"serviceOverdueDays": "Service Overdue {days}d",
|
|
"dueInDays": "Due in {days}d",
|
|
"okDays": "OK · {days}d",
|
|
"noKm": "No km",
|
|
"serviceOverdueKm": "Service Overdue {km} km",
|
|
"inKm": "In {km} km",
|
|
"kmLeft": "{km} km left",
|
|
"expiredAgo": "Expired {days}d ago",
|
|
"expiresToday": "Expires today",
|
|
"renewInDays": "Renew in {days}d",
|
|
"validDays": "Valid · {days}d",
|
|
"noExpiry": "No expiry",
|
|
"done": "Done",
|
|
"noTrigger": "No trigger",
|
|
"overdue": "Overdue",
|
|
"overdueBy": "Overdue {parts}",
|
|
"dueIn": "Due in {parts}",
|
|
"upcoming": "Upcoming",
|
|
"today": "today",
|
|
"days": "{days}d",
|
|
"km": "{km} km",
|
|
"warrantyExpiredAgo": "Warranty expired {days}d ago",
|
|
"warrantyEndsIn": "Warranty ends in {days}d",
|
|
"underWarranty": "Under warranty · {days}d"
|
|
}
|
|
}
|