Просмотр исходного кода

Fix locale quality: diacritics, inspection UI, shared components (#242)

* Restore stripped diacritics in the locale files

French, Spanish, Brazilian Portuguese and Polish had accents missing from
large parts of their translations: Parametres, Configuracion, Configuracoes,
Modeles, Inspecoes, Domyslne, servico, usun. Settings carried the bulk of it,
so it showed on every settings screen in four languages.

1451 words restored across nine files. Every replacement was taken from a
spelling the same locale already used elsewhere, so nothing is invented, and
the script that did it is kept in scripts/ so the check can be re-run.

Only words whose unaccented form is not itself a word in that language were
touched. French supprime/supprimé, Spanish esta/está and Polish faktura/
fakturą are distinct words, and no script can tell which one a given string
meant, so those were left alone rather than guessed at.

Verified that the key structure of every changed file is byte-identical to
before; only values moved.

* Wire the inspection UI for translation, with Norwegian complete

Every string in the inspection feature moves into an inspections namespace and
the 26 components now read from it. Grades and overall results are seeded from
each locale's existing share.json, so the page, the shared report and the PDF
can never disagree about what a defect grade is called.

Locales not yet translated carry the English values, which is what the UI
showed before, so no language regresses while the rest are filled in.

* Translate the inspection UI into Norwegian, German and Dutch

Full coverage for nb, de and nl. The remaining eight locales carry the grades
and overall results, seeded from their own share.json so the terminology is
already right, and fall back to English for the rest — which is what the whole
screen showed before, so nothing regresses.

* Translate the inspection UI into the remaining eight languages

French, Spanish, Italian, Polish, Brazilian Portuguese, Russian, Turkish and
Lithuanian, completing all twelve.

Verified mechanically across every locale: key structure identical to English,
and every {placeholder} surviving translation, so no string can render a raw
token or drop a count.

* Localise the inspections list, and label every audit action

The list page, the New Inspection dialog and the share dialog were still
English. I had converted the detail page and the template screens and stopped
there, so the first screen anyone opens was untouched. 35 more strings, all
twelve languages.

Separately, ten audit actions had no label in any locale, so the dashboard
printed a raw key and filled the console with MISSING_MESSAGE. Two of them
were mine: inspection.reopen and inspectionTemplate.import.

Both call sites that render an audit action wrapped the lookup in try/catch,
but next-intl reports a missing message and returns a placeholder rather than
throwing, so the fallback never ran. They ask with has() now. They also used
replace() where an action like scheduled_message.send_now has more than one
separator, so replaceAll().

A test now walks the source for emitted audit actions and asserts every one
resolves in all twelve locales, which is the class of bug rather than the ten
keys.

* Localise the shared table footer and the other shared components

The pagination footer was English in every table in the app: 'Showing x-x of
x', 'per page' and 'Page x of y'. It is one shared component, so fixing it
fixes every list at once.

Its four page buttons were icon-only with no accessible name at all, so a
screen reader announced four unlabelled buttons. They now say first, previous,
next and last page.

Sweeping the rest of src/components for the same class of problem turned up
five more that had never been wired for translation: the close button on the
glass modal, the upgrade prompt, the demo banner, the install prompt and the
barcode scanner. Nineteen strings, all twelve languages, zero hardcoded
user-visible strings left in that directory.
Bernt Christian Egeland 1 месяц назад
Родитель
Сommit
e469aaba1b
69 измененных файлов с 7000 добавлено и 1473 удалено
  1. 11 1
      messages/de/audit.json
  2. 24 0
      messages/de/common.json
  3. 396 0
      messages/de/inspections.json
  4. 11 1
      messages/en/audit.json
  5. 24 0
      messages/en/common.json
  6. 396 0
      messages/en/inspections.json
  7. 11 1
      messages/es/audit.json
  8. 32 32
      messages/es/auth.json
  9. 29 5
      messages/es/common.json
  10. 396 0
      messages/es/inspections.json
  11. 203 203
      messages/es/settings.json
  12. 52 52
      messages/es/vehicles.json
  13. 11 1
      messages/fr/audit.json
  14. 24 0
      messages/fr/common.json
  15. 396 0
      messages/fr/inspections.json
  16. 250 250
      messages/fr/settings.json
  17. 11 1
      messages/it/audit.json
  18. 24 0
      messages/it/common.json
  19. 396 0
      messages/it/inspections.json
  20. 11 1
      messages/lt/audit.json
  21. 24 0
      messages/lt/common.json
  22. 396 0
      messages/lt/inspections.json
  23. 11 1
      messages/nb/audit.json
  24. 24 0
      messages/nb/common.json
  25. 396 0
      messages/nb/inspections.json
  26. 11 1
      messages/nl/audit.json
  27. 24 0
      messages/nl/common.json
  28. 396 0
      messages/nl/inspections.json
  29. 11 1
      messages/pl/audit.json
  30. 24 0
      messages/pl/common.json
  31. 396 0
      messages/pl/inspections.json
  32. 227 227
      messages/pl/settings.json
  33. 11 1
      messages/pt-BR/audit.json
  34. 24 0
      messages/pt-BR/common.json
  35. 1 1
      messages/pt-BR/customers.json
  36. 396 0
      messages/pt-BR/inspections.json
  37. 1 1
      messages/pt-BR/portal.json
  38. 275 275
      messages/pt-BR/settings.json
  39. 11 1
      messages/ru/audit.json
  40. 24 0
      messages/ru/common.json
  41. 396 0
      messages/ru/inspections.json
  42. 11 1
      messages/tr/audit.json
  43. 24 0
      messages/tr/common.json
  44. 396 0
      messages/tr/inspections.json
  45. 241 0
      scripts/restore-diacritics.py
  46. 68 0
      src/__tests__/i18n/audit-actions.test.ts
  47. 5 5
      src/app/(authenticated)/audit-log/audit-log-client.tsx
  48. 9 7
      src/app/(authenticated)/dashboard-client.tsx
  49. 15 14
      src/app/(authenticated)/inspections/inspections-client.tsx
  50. 6 4
      src/components/barcode-scanner-dialog.tsx
  51. 9 7
      src/components/data-table-pagination.tsx
  52. 3 1
      src/components/demo-banner.tsx
  53. 3 1
      src/components/glass-modal.tsx
  54. 8 5
      src/components/pwa-install-prompt.tsx
  55. 4 2
      src/components/upgrade-prompt.tsx
  56. 14 19
      src/features/inspections/Components/DefectSuggestions.tsx
  57. 42 42
      src/features/inspections/Components/InspectionCertificateCard.tsx
  58. 30 33
      src/features/inspections/Components/InspectionItemRow.tsx
  59. 94 91
      src/features/inspections/Components/InspectionPageClient.tsx
  60. 8 6
      src/features/inspections/Components/InspectionShareDialog.tsx
  61. 6 4
      src/features/inspections/Components/MediaLightbox.tsx
  62. 11 9
      src/features/inspections/Components/NewInspectionDialog.tsx
  63. 68 70
      src/features/inspections/Components/TemplateForm.tsx
  64. 38 43
      src/features/inspections/Components/TemplateListClient.tsx
  65. 24 29
      src/features/inspections/Components/TemplatePackageDialogs.tsx
  66. 18 17
      src/features/inspections/Components/TemplatePresetPicker.tsx
  67. 6 6
      src/features/inspections/Lib/testIntervals.ts
  68. 50 0
      src/features/inspections/Lib/useConditionLabels.ts
  69. 2 0
      src/i18n/request.ts

+ 11 - 1
messages/de/audit.json

@@ -98,6 +98,16 @@
     "webhook_create": "Webhook erstellt",
     "webhook_update": "Webhook aktualisiert",
     "webhook_delete": "Webhook gelöscht",
-    "webhook_rotateSecret": "Webhook-Schlüssel rotiert"
+    "webhook_rotateSecret": "Webhook-Schlüssel rotiert",
+    "inspection_reopen": "Inspektion wieder geöffnet",
+    "inspectionTemplate_import": "Inspektionsvorlage importiert",
+    "scheduled_message_create": "Geplante Nachricht erstellt",
+    "scheduled_message_update": "Geplante Nachricht aktualisiert",
+    "scheduled_message_delete": "Geplante Nachricht gelöscht",
+    "scheduled_message_cancel": "Geplante Nachricht abgebrochen",
+    "scheduled_message_send_now": "Geplante Nachricht gesendet",
+    "settings_applyTaxRate": "Steuersatz angewendet",
+    "settings_convertToExclusive": "Preise auf netto umgestellt",
+    "settings_convertToInclusive": "Preise auf brutto umgestellt"
   }
 }

+ 24 - 0
messages/de/common.json

@@ -52,5 +52,29 @@
     "updated": "Torqvoice wurde auf {version} aktualisiert",
     "whatsNew": "Neuigkeiten ansehen",
     "dismiss": "Schließen"
+  },
+  "pagination": {
+    "showing": "{start}–{end} von {total}",
+    "perPage": "pro Seite",
+    "pageOf": "Seite {page} von {pages}",
+    "first": "Erste Seite",
+    "previous": "Vorherige Seite",
+    "next": "Nächste Seite",
+    "last": "Letzte Seite"
+  },
+  "shared": {
+    "close": "Schließen",
+    "viewPlans": "Tarife ansehen",
+    "manageLicense": "Lizenz verwalten",
+    "installOwn": "Eigene Instanz installieren",
+    "install": "Installieren",
+    "installApp": "App installieren",
+    "installTorqvoice": "Torqvoice installieren",
+    "dismiss": "Schließen",
+    "useCamera": "Kamera verwenden",
+    "enterManually": "Manuell eingeben",
+    "enterBarcode": "Barcode eingeben…",
+    "scanHint": "Richten Sie die Kamera auf einen Barcode oder geben Sie ihn manuell ein",
+    "iosInstallHint": "Auf Teilen > Zum Home-Bildschirm tippen"
   }
 }

+ 396 - 0
messages/de/inspections.json

@@ -0,0 +1,396 @@
+{
+  "grades": {
+    "eu": {
+      "pass": "Kein Mangel",
+      "attention": "Geringer Mangel",
+      "fail": "Erheblicher Mangel",
+      "dangerous": "Gefährlicher Mangel",
+      "not_inspected": "Nicht geprüft"
+    },
+    "basic": {
+      "pass": "Bestanden",
+      "attention": "Achtung",
+      "fail": "Durchgefallen",
+      "dangerous": "Gefährlich",
+      "not_inspected": "Nicht geprüft"
+    },
+    "short": {
+      "pass": "OK",
+      "attention": "Gering",
+      "fail": "Erheblich",
+      "dangerous": "Gefährlich",
+      "not_inspected": "–"
+    },
+    "hint": {
+      "pass": "Erfüllt die Anforderungen. Nichts zu melden.",
+      "attention": "Ohne wesentliche Auswirkung auf die Sicherheit. Fahrzeug besteht; bei Gelegenheit beheben.",
+      "fail": "Kann die Sicherheit beeinträchtigen oder die Umwelt belasten. Fahrzeug besteht nicht; beheben und nachprüfen lassen.",
+      "dangerous": "Unmittelbare Gefahr für die Verkehrssicherheit. Das Fahrzeug darf nicht gefahren werden.",
+      "not_inspected": "Dieser Prüfpunkt wurde nicht durchgeführt."
+    }
+  },
+  "results": {
+    "pass": "Bestanden",
+    "pass_minor": "Bestanden mit geringen Mängeln",
+    "fail": "Nicht bestanden — erhebliche Mängel",
+    "fail_dangerous": "Nicht bestanden — gefährliche Mängel",
+    "incomplete": "Nicht abgeschlossen",
+    "detail": {
+      "pass": "Es wurden keine Mängel festgestellt.",
+      "pass_minor": "Das Fahrzeug hat bestanden. Beheben Sie die geringen Mängel ohne unnötige Verzögerung.",
+      "fail": "Das Fahrzeug hat nicht bestanden. Die Mängel müssen behoben und das Fahrzeug erneut geprüft werden.",
+      "fail_dangerous": "Das Fahrzeug darf bis zur Behebung der gefährlichen Mängel nicht im öffentlichen Straßenverkehr genutzt werden.",
+      "incomplete": "Diese Prüfung läuft noch."
+    }
+  },
+  "page": {
+    "back": "Inspektionen",
+    "statusInProgress": "Läuft",
+    "statusCompleted": "Abgeschlossen",
+    "complete": "Abschließen",
+    "reopen": "Wieder öffnen",
+    "share": "Teilen",
+    "save": "Speichern",
+    "saving": "Speichert…",
+    "savedAt": "Gespeichert {time}",
+    "savesAsYouGo": "Speichert laufend",
+    "saveFailed": "Einige Änderungen wurden nicht gespeichert",
+    "allSaved": "Alle Änderungen sind gespeichert",
+    "moreActions": "Weitere Aktionen",
+    "downloadCertificate": "Prüfbericht herunterladen",
+    "manageTemplates": "Vorlagen verwalten",
+    "delete": "Löschen",
+    "createWorkOrder": "Auftrag anlegen",
+    "viewWorkOrder": "Auftrag anzeigen",
+    "createQuote": "Angebot anlegen",
+    "viewQuote": "Angebot anzeigen",
+    "needsDefect": "Verfügbar, sobald ein Mangel erfasst ist",
+    "workOrderRaised": "Auftrag {number} am {date} aus dieser Inspektion angelegt",
+    "open": "Öffnen",
+    "quoteCreated": "Angebot von {name} am {date} angelegt",
+    "quoteRequested": "Der Kunde hat ein Angebot zu {count} Prüfpunkt(en) angefragt",
+    "stillToGrade": "{count} von {total} Prüfpunkten sind noch zu bewerten.",
+    "progress": "Fortschritt",
+    "checksGraded": "Prüfpunkte bewertet",
+    "progressLabel": "{graded} von {total} Prüfpunkten bewertet: {pass} kein Mangel, {attention} gering, {fail} erheblich, {dangerous} gefährlich.",
+    "sections": "Gruppen",
+    "graded": "{graded}/{total} bewertet",
+    "vehicle": "Fahrzeug",
+    "vessel": "Wasserfahrzeug",
+    "customer": "Kunde",
+    "plate": "Kennzeichen",
+    "registration": "Registrierung",
+    "template": "Vorlage",
+    "scaleEu": "Bewertet nach der EU-Mängelskala aus Richtlinie 2014/45/EU.",
+    "scaleBasic": "Bewertet bestanden / Achtung / durchgefallen.",
+    "country": "Land: {code}.",
+    "manageInspectionTemplates": "Inspektionsvorlagen verwalten",
+    "readMore": "Mehr erfahren",
+    "odometer": "Kilometerstand",
+    "engineHours": "Betriebsstunden",
+    "completeTitle": "Diese Inspektion abschließen?",
+    "completeBody": "Die Prüfpunkte werden mit dem Abschluss gesperrt, und das Ergebnis lautet dann „{result}“.",
+    "completeBlocked": "{count} von der Vorlage als Pflicht markierte Prüfpunkte sind offen.",
+    "completeUngraded": "{count} Prüfpunkt(e) wurden nicht bewertet.",
+    "completeDangerous": "{count} gefährliche(r) Mangel erfasst. Das Fahrzeug darf nicht am öffentlichen Verkehr teilnehmen.",
+    "reopenTitle": "Diese Inspektion wieder öffnen?",
+    "reopenBody": "Die Prüfpunkte werden wieder bearbeitbar und das Ergebnis geht zurück auf laufend. Prüfer, Bescheinigungsnummer und nächster Prüftermin bleiben erhalten.",
+    "reopenShared": "Der Kundenlink bleibt aktiv und zeigt, wie sich das Ergebnis während Ihrer Bearbeitung ändert. Ein bereits heruntergeladener Prüfbericht behält das alte Ergebnis.",
+    "deleteTitle": "Diese Inspektion löschen?",
+    "deleteBody": "Damit werden die Inspektion, ihre Bewertungen, Notizen und Fotos dauerhaft gelöscht. Das lässt sich nicht rückgängig machen.",
+    "cancel": "Abbrechen",
+    "completed": "Inspektion abgeschlossen",
+    "reopened": "Inspektion wieder geöffnet",
+    "deleted": "Inspektion gelöscht",
+    "completeFailed": "Inspektion konnte nicht abgeschlossen werden",
+    "reopenFailed": "Inspektion konnte nicht wieder geöffnet werden",
+    "deleteFailed": "Inspektion konnte nicht gelöscht werden",
+    "quoteFailed": "Angebot konnte nicht angelegt werden",
+    "workOrderFailed": "Auftrag konnte nicht angelegt werden"
+  },
+  "item": {
+    "required": "(Pflicht)",
+    "saving": "Speichert",
+    "addNote": "Notiz hinzufügen",
+    "hideNote": "Notiz ausblenden",
+    "addPhoto": "Foto oder Video zu {name} hinzufügen",
+    "notesFor": "Notizen zu {name}",
+    "describeDefect": "Mangel beschreiben — erscheint im Bericht",
+    "optionalNote": "Notiz hinzufügen (optional)",
+    "notesRequired": "Beschreiben Sie den Mangel, bevor diese Bewertung gespeichert wird.",
+    "photoRequired": "Hier ist ein Foto des Mangels erforderlich. Die Inspektion kann erst abgeschlossen werden, wenn eines angehängt ist.",
+    "reading": "Messwert",
+    "readingUnit": "Messwert ({unit})",
+    "limit": "Grenzwert: {range}",
+    "recordedValue": "Erfasster Wert",
+    "answer": "Antwort",
+    "selectAnswer": "Antwort wählen",
+    "viewPhoto": "Foto {index} von {name} in voller Größe ansehen",
+    "photoAlt": "{name}, Foto {index}",
+    "removePhoto": "Foto {index} von {name} entfernen",
+    "clearTitle": "Diese Bewertung entfernen?",
+    "clearBody": "Die zu „{name}“ erfasste Notiz wird entfernt und der Prüfpunkt geht zurück auf nicht geprüft.",
+    "clear": "Entfernen",
+    "cancel": "Abbrechen",
+    "saveFailed": "Dieser Prüfpunkt konnte nicht gespeichert werden",
+    "uploadFailed": "Upload fehlgeschlagen",
+    "uploadFailedFor": "{name} konnte nicht hochgeladen werden"
+  },
+  "suggestions": {
+    "title": "Häufige Mängel",
+    "add": "„{text}“ hinzufügen und als {grade} bewerten",
+    "more": "Weitere ({count})",
+    "search": "Mängeltext suchen",
+    "empty": "Kein passender Text.",
+    "source": {
+      "workshop": "Ihre Werkstatt",
+      "history": "Schon verwendet",
+      "regulation": "Richtlinie 2014/45/EU",
+      "general": "Allgemein"
+    }
+  },
+  "media": {
+    "viewer": "Bild {index} von {total}: {caption}",
+    "close": "Bildansicht schließen",
+    "previous": "Vorheriges Bild",
+    "next": "Nächstes Bild"
+  },
+  "certificate": {
+    "title": "Angaben zum Prüfbericht",
+    "description": "Nach Richtlinie 2014/45/EU, Anhang IV, auf einem Prüfbericht erforderlich.",
+    "inspector": "Prüfer",
+    "notRecorded": "Nicht erfasst",
+    "addTechnician": "Mechaniker hinzufügen…",
+    "recordedAs": "Erfasst als {name}",
+    "vehicleCategory": "Fahrzeugklasse",
+    "mileageAtTest": "{label} zum Zeitpunkt der Prüfung",
+    "reportReference": "Berichtsreferenz",
+    "reportReferenceHelp": "Ihre eigene Referenz für diesen Bericht. Wenn Sie als anerkannte Prüfstelle einen amtlichen Prüfbericht ausstellen, tragen Sie hier dessen Nummer ein, damit beide übereinstimmen. Andernfalls lassen Sie das Feld leer.",
+    "optional": "Optional",
+    "placeOfTest": "Prüfort",
+    "placeholderAddress": "Prüfstelle oder Werkstattadresse",
+    "fromSettings": "Aus Ihren Werkstatteinstellungen.",
+    "change": "Ändern",
+    "differsFromWorkshop": "Weicht von Ihrer Werkstattadresse ab.",
+    "useWorkshopAddress": "Werkstattadresse verwenden",
+    "noWorkshopAddress": "Es ist keine Werkstattadresse hinterlegt.",
+    "addOne": "Eine hinterlegen",
+    "nextTestDue": "Nächste Prüfung fällig",
+    "notSet": "Nicht gesetzt",
+    "intervalGroup": "Nächsten Prüftermin ab dem Datum dieser Prüfung setzen",
+    "intervalHelp": "Gerechnet ab dem Datum dieser Prüfung. Zwei Jahre ist der EU-Ausgangswert für Pkw und leichte Nutzfahrzeuge; prüfen Sie das in Ihrem Land geltende Intervall.",
+    "months6": "6 Monate",
+    "years1": "1 Jahr",
+    "years2": "2 Jahre",
+    "years3": "3 Jahre",
+    "years4": "4 Jahre",
+    "unsaved": "Nicht gespeicherte Änderungen",
+    "saveDetails": "Angaben speichern",
+    "saved": "Angaben gespeichert",
+    "saveFailed": "Die Angaben konnten nicht gespeichert werden",
+    "addTechnicianTitle": "Mechaniker hinzufügen",
+    "addTechnicianBody": "Die Person kommt in die Werkstattliste und kann sowohl Arbeiten als auch Inspektionen zugewiesen bekommen.",
+    "technicianName": "Name",
+    "technicianPlaceholder": "z. B. Anna Berg",
+    "addAndSelect": "Hinzufügen und auswählen",
+    "technicianAdded": "{name} hinzugefügt und ausgewählt",
+    "technicianFailed": "Der Mechaniker konnte nicht hinzugefügt werden",
+    "cancel": "Abbrechen"
+  },
+  "templates": {
+    "title": "Inspektionsvorlagen",
+    "description": "Die Prüflisten, die Ihre Mechaniker durchgehen. Legen Sie eine je Land, je Fahrzeugtyp oder je angebotener Leistung an.",
+    "import": "Importieren",
+    "browse": "Prüflisten durchsuchen",
+    "new": "Neue Vorlage",
+    "restoreBanner": "{count} fertige Prüflisten wurden aus Ihrer Liste entfernt. Sie können sie zurückholen.",
+    "restore": "Zurückholen",
+    "restored": "{count} Prüfliste(n) zurückgeholt",
+    "allPresent": "Alle Prüflisten sind bereits vorhanden",
+    "restoreFailed": "Die Prüflisten konnten nicht hinzugefügt werden",
+    "emptyTitle": "Noch keine Vorlagen",
+    "emptyBody": "Die fertigen Prüflisten, einschließlich der periodischen EU-Fahrzeugprüfung, wurden alle entfernt. Holen Sie sie zurück oder bauen Sie Ihre eigene von Grund auf.",
+    "restoreChecklists": "Prüflisten zurückholen",
+    "buildMyOwn": "Eigene bauen",
+    "default": "Standard",
+    "actionsFor": "Aktionen für {name}",
+    "edit": "Bearbeiten",
+    "duplicate": "Duplizieren",
+    "export": "Exportieren…",
+    "delete": "Löschen",
+    "sections": "{count} Gruppe(n)",
+    "checks": "{count} Prüfpunkt(e)",
+    "measured": "{count} gemessen",
+    "euScale": "EU-Mängelskala",
+    "basicScale": "Bestanden / Achtung / durchgefallen",
+    "deleteTitle": "„{name}“ löschen?",
+    "deleteBody": "Damit wird die Vorlage dauerhaft gelöscht. Bereits durchgeführte Inspektionen behalten ihre eigene Kopie der Prüfliste und sind nicht betroffen.",
+    "deleted": "Vorlage gelöscht",
+    "deleteFailed": "Vorlage konnte nicht gelöscht werden",
+    "duplicated": "Vorlage dupliziert",
+    "duplicateFailed": "Vorlage konnte nicht dupliziert werden",
+    "cancel": "Abbrechen",
+    "srSections": "Gruppen",
+    "srChecks": "Prüfpunkte",
+    "srMeasurements": "Messwerte"
+  },
+  "presets": {
+    "title": "Mit einer Vorlage beginnen",
+    "description": "Jede Vorlage wird als normale Vorlage in Ihre Werkstatt kopiert. Benennen Sie Gruppen um, verschieben Sie Prüfpunkte und ändern Sie jeden Grenzwert passend zu Ihrem Land und Ihrer Ausrüstung.",
+    "search": "Nach Name, Land oder Standard suchen",
+    "searchLabel": "Vorlagen suchen",
+    "inYourList": "In Ihrer Liste",
+    "counts": "{sections} Gruppen · {checks} Prüfpunkte",
+    "noMatch": "Keine Vorlage passt zu „{query}“.",
+    "warning": "Die gesetzlichen Vorlagen enthalten die üblichen EU-Grenzwerte. Prüfen Sie sie gegen Ihre nationalen Vorschriften, bevor Sie einen Prüfbericht ausstellen.",
+    "cancel": "Abbrechen",
+    "add": "Vorlage hinzufügen",
+    "added": "Vorlage hinzugefügt. Passen Sie sie an Ihre Werkstatt an.",
+    "addFailed": "Vorlage konnte nicht hinzugefügt werden",
+    "group": {
+      "regulatory": "Gesetzlich",
+      "workshop": "Werkstatt",
+      "specialist": "Spezialisiert"
+    },
+    "groupDescription": {
+      "regulatory": "Prüflisten für die periodische Fahrzeugprüfung auf Basis der Richtlinie 2014/45/EU.",
+      "workshop": "Allgemeine Service- und Verkaufsprüflisten ohne Bezug zu einer Vorschrift.",
+      "specialist": "Prüflisten für Antriebe und Wasserfahrzeuge, die ein eigenes Verfahren brauchen."
+    },
+    "euScale": "EU-Mängelskala",
+    "basicScale": "Bestanden / Achtung / durchgefallen"
+  },
+  "builder": {
+    "newTitle": "Neue Vorlage",
+    "editTitle": "Vorlage bearbeiten",
+    "description": "Bauen Sie die Prüfliste, die Ihre Werkstatt tatsächlich verwendet. Ziehen Sie zum Umsortieren und richten Sie jeden Prüfpunkt so ein, dass er eine Bewertung, einen Messwert, eine geschriebene Notiz oder eine feste Antwort erfasst.",
+    "name": "Name",
+    "namePlaceholder": "z. B. Periodische Fahrzeugprüfung",
+    "templateDescription": "Beschreibung",
+    "descriptionPlaceholder": "Wofür diese Prüfliste da ist und wann sie verwendet wird.",
+    "country": "Land",
+    "notCountrySpecific": "Nicht länderspezifisch",
+    "countryHelp": "Wird im Bericht vermerkt, damit eine grenzüberschreitend tätige Werkstatt ihre Prüflisten auseinanderhalten kann.",
+    "defectScale": "Mängelskala",
+    "scaleEu": "Gering / erheblich / gefährlich (EU)",
+    "scaleBasic": "Bestanden / Achtung / durchgefallen",
+    "scaleEuHelp": "Die Kategorien aus Artikel 7 Absatz 2 der Richtlinie 2014/45/EU. Ein Fahrzeug mit ausschließlich geringen Mängeln besteht weiterhin.",
+    "scaleBasicHelp": "Eine einfache dreistufige Skala für Serviceprüflisten, die keine gesetzliche Untersuchung sind.",
+    "defaultTemplate": "Standardvorlage",
+    "defaultHelp": "Vorausgewählt, wenn ein Mechaniker eine neue Inspektion beginnt.",
+    "summary": "{sections} Gruppe(n) · {checks} Prüfpunkt(e)",
+    "renumber": "Referenzen neu nummerieren",
+    "renumberHelp": "Nummeriert jede Gruppe und jeden Prüfpunkt der Reihe nach: 1, 1.1, 1.2, 2. Neue Zeilen werden beim Hinzufügen nummeriert, das hier brauchen Sie also nur nach dem Verschieben.",
+    "renumberWarning": "Diese Prüfliste verwendet Verweise auf Vorschriften wie 1.1.13. Eine Neunummerierung ersetzt sie.",
+    "addSection": "Gruppe hinzufügen",
+    "addCheck": "Prüfpunkt hinzufügen",
+    "ref": "Ref.",
+    "sectionName": "Gruppenname, z. B. Bremsanlage",
+    "sectionNote": "Optionaler Hinweis über den Prüfpunkten dieser Gruppe",
+    "checkName": "Name des Prüfpunkts, z. B. Bremsbeläge",
+    "reorderSection": "Gruppe {name} umsortieren",
+    "reorderCheck": "Prüfpunkt {name} umsortieren",
+    "removeSection": "Gruppe {name} entfernen",
+    "removeCheck": "Prüfpunkt {name} entfernen",
+    "duplicateCheck": "Prüfpunkt {name} duplizieren",
+    "configure": "{name} einrichten",
+    "hideSettings": "Einstellungen für {name} ausblenden",
+    "howRecorded": "Wie wird es erfasst?",
+    "guidance": "Hinweis für den Mechaniker",
+    "guidancePlaceholder": "Wird im Inspektionsformular unter dem Namen des Prüfpunkts angezeigt.",
+    "measurement": "Messwert",
+    "unit": "Einheit",
+    "minimum": "Minimum",
+    "maximum": "Maximum",
+    "outOfRange": "Mangel außerhalb des Bereichs",
+    "rangeHelp": "Lassen Sie eine Grenze leer für eine einseitige Vorgabe, etwa ein Minimum von 1,6 mm ohne Maximum. Messwerte außerhalb des Bereichs werden automatisch bewertet.",
+    "answers": "Antworten",
+    "answersHelp": "Trennen Sie die Antworten mit Kommas.",
+    "mustComplete": "Muss ausgefüllt werden",
+    "photoRequired": "Foto erforderlich, wenn ein Mangel erfasst wird",
+    "commonWording": "Häufige Mängeltexte",
+    "commonWordingHelp": "Eine Formulierung pro Zeile. Diese werden hier zuerst angeboten, wenn ein Mechaniker einen Mangel erfasst, noch vor den eingebauten Texten, damit sich Ihre Werkstatt auf eine Beschreibung einigen kann.",
+    "inputType": {
+      "condition": "Mängelbewertung",
+      "measurement": "Messwert",
+      "text": "Freitext",
+      "choice": "Auswahl"
+    },
+    "inputHint": {
+      "condition": "Der Mechaniker bewertet den Prüfpunkt nach der Mängelskala.",
+      "measurement": "Erfasst eine Zahl mit Einheit, automatisch gegen einen Bereich bewertet.",
+      "text": "Erfasst eine schriftliche Beobachtung, etwa eine Reifenmarke oder einen Fehlercode.",
+      "choice": "Der Mechaniker wählt eine Antwort aus einer festen Liste."
+    },
+    "cancel": "Abbrechen",
+    "create": "Vorlage anlegen",
+    "saveChanges": "Änderungen speichern",
+    "created": "Vorlage angelegt",
+    "updated": "Vorlage aktualisiert",
+    "saveFailed": "Vorlage konnte nicht gespeichert werden",
+    "severityAttention": "Geringer Mangel",
+    "severityFail": "Erheblicher Mangel",
+    "severityDangerous": "Gefährlicher Mangel",
+    "answersPlaceholder": "Vollständig, Teilweise, Keine"
+  },
+  "packages": {
+    "exportTitle": "„{name}“ exportieren",
+    "exportDescription": "Lädt die gesamte Prüfliste als Datei herunter: Gruppen, Prüfpunkte, Einheiten und Grenzwerte. Jeder, der Torqvoice betreibt, kann sie importieren.",
+    "includeWording": "Eigene Mängeltexte einbeziehen",
+    "includeWordingHelp": "Die Formulierungen, die Ihre Werkstatt zu den Prüfpunkten ergänzt hat. Nützlich für den Empfänger, aber es ist Freitext, der an der Werkbank getippt wurde, und kann einen Kunden, eine Kollegin oder eine örtliche Absprache benennen. Aus, sofern Sie sie nicht gelesen haben.",
+    "attributeTo": "Zuschreiben an (optional)",
+    "attributePlaceholder": "z. B. Egeland Auto",
+    "attributeHelp": "Wird in die Datei geschrieben, damit der Installierende sieht, woher sie stammt. Aus Ihrem Konto wird nichts übernommen.",
+    "download": "Herunterladen",
+    "exported": "Vorlage exportiert",
+    "exportFailed": "Diese Vorlage konnte nicht exportiert werden",
+    "importTitle": "Prüfliste importieren",
+    "importDescription": "Fügt eine Prüfliste aus einer Torqvoice-Paketdatei hinzu. Sie kommt als normale, bearbeitbare Vorlage an und ersetzt niemals eine vorhandene.",
+    "chooseFile": "Paketdatei wählen",
+    "chooseAnother": "Andere Datei wählen",
+    "unreadable": "Die Datei konnte nicht gelesen werden. Ein Torqvoice-Paket ist eine aus einer Vorlage exportierte .json-Datei.",
+    "from": "Von {author}",
+    "limitsWarning": "Die Grenzwerte einer geteilten Prüfliste sind die, die ihr Autor gesetzt hat. Prüfen Sie sie gegen Ihre nationalen Vorschriften, bevor Sie etwas daraus ausstellen.",
+    "import": "Importieren",
+    "imported": "{count} Vorlage(n) importiert",
+    "importFailed": "Dieses Paket konnte nicht importiert werden",
+    "cancel": "Abbrechen"
+  },
+  "list": {
+    "tabAll": "Alle",
+    "tabInProgress": "Läuft",
+    "tabCompleted": "Abgeschlossen",
+    "search": "Inspektionen suchen…",
+    "new": "Neue Inspektion",
+    "empty": "Keine Inspektionen gefunden.",
+    "vehicle": "Fahrzeug",
+    "template": "Vorlage",
+    "progress": "Fortschritt",
+    "status": "Status",
+    "date": "Datum",
+    "statusInProgress": "Läuft",
+    "statusCompleted": "Abgeschlossen"
+  },
+  "new": {
+    "title": "Neue Inspektion",
+    "vehicle": "Fahrzeug",
+    "searchVehicles": "Fahrzeuge suchen…",
+    "loadingVehicles": "Fahrzeuge werden geladen…",
+    "noVehicles": "Keine Fahrzeuge gefunden.",
+    "template": "Vorlage",
+    "selectTemplate": "Vorlage wählen",
+    "manageTemplates": "Vorlagen verwalten",
+    "cancel": "Abbrechen",
+    "create": "Inspektion anlegen",
+    "created": "Inspektion angelegt",
+    "createFailed": "Inspektion konnte nicht angelegt werden"
+  },
+  "shareDialog": {
+    "title": "Inspektion teilen",
+    "description": "Teilen Sie eine schreibgeschützte Ansicht dieser Inspektion mit Ihrem Kunden.",
+    "copyLink": "Link kopieren",
+    "copied": "Link in die Zwischenablage kopiert",
+    "generated": "Freigabelink erstellt",
+    "revoked": "Freigabelink zurückgezogen"
+  }
+}

+ 11 - 1
messages/en/audit.json

@@ -98,6 +98,16 @@
     "webhook_create": "Created Webhook",
     "webhook_update": "Updated Webhook",
     "webhook_delete": "Deleted Webhook",
-    "webhook_rotateSecret": "Rotated Webhook Secret"
+    "webhook_rotateSecret": "Rotated Webhook Secret",
+    "inspection_reopen": "Reopened Inspection",
+    "inspectionTemplate_import": "Imported Inspection Template",
+    "scheduled_message_create": "Created Scheduled Message",
+    "scheduled_message_update": "Updated Scheduled Message",
+    "scheduled_message_delete": "Deleted Scheduled Message",
+    "scheduled_message_cancel": "Cancelled Scheduled Message",
+    "scheduled_message_send_now": "Sent Scheduled Message",
+    "settings_applyTaxRate": "Applied Tax Rate",
+    "settings_convertToExclusive": "Converted Prices to Tax Exclusive",
+    "settings_convertToInclusive": "Converted Prices to Tax Inclusive"
   }
 }

+ 24 - 0
messages/en/common.json

@@ -52,5 +52,29 @@
     "updated": "Torqvoice has been updated to {version}",
     "whatsNew": "See what is new",
     "dismiss": "Dismiss"
+  },
+  "pagination": {
+    "showing": "Showing {start}-{end} of {total}",
+    "perPage": "per page",
+    "pageOf": "Page {page} of {pages}",
+    "first": "First page",
+    "previous": "Previous page",
+    "next": "Next page",
+    "last": "Last page"
+  },
+  "shared": {
+    "close": "Close",
+    "viewPlans": "View Plans",
+    "manageLicense": "Manage License",
+    "installOwn": "Install your own",
+    "install": "Install",
+    "installApp": "Install App",
+    "installTorqvoice": "Install Torqvoice",
+    "dismiss": "Dismiss",
+    "useCamera": "Use camera",
+    "enterManually": "Enter manually",
+    "enterBarcode": "Enter barcode...",
+    "scanHint": "Point your camera at a barcode or enter it manually",
+    "iosInstallHint": "Tap Share > Add to Home Screen"
   }
 }

+ 396 - 0
messages/en/inspections.json

@@ -0,0 +1,396 @@
+{
+  "grades": {
+    "eu": {
+      "pass": "No defect",
+      "attention": "Minor defect",
+      "fail": "Major defect",
+      "dangerous": "Dangerous defect",
+      "not_inspected": "Not inspected"
+    },
+    "basic": {
+      "pass": "Pass",
+      "attention": "Attention",
+      "fail": "Fail",
+      "dangerous": "Dangerous",
+      "not_inspected": "Not inspected"
+    },
+    "short": {
+      "pass": "OK",
+      "attention": "Minor",
+      "fail": "Major",
+      "dangerous": "Danger",
+      "not_inspected": "N/A"
+    },
+    "hint": {
+      "pass": "Meets requirements. Nothing to report.",
+      "attention": "No significant effect on safety. Vehicle passes; repair when convenient.",
+      "fail": "May prejudice safety or affect the environment. Vehicle fails; repair and retest.",
+      "dangerous": "Direct and immediate risk to road safety. The vehicle must not be driven.",
+      "not_inspected": "This check has not been carried out."
+    }
+  },
+  "results": {
+    "pass": "Pass",
+    "pass_minor": "Pass with minor defects",
+    "fail": "Fail — major defects",
+    "fail_dangerous": "Fail — dangerous defects",
+    "incomplete": "Not completed",
+    "detail": {
+      "pass": "No deficiencies were found.",
+      "pass_minor": "The vehicle passes. Repair the minor deficiencies without undue delay.",
+      "fail": "The vehicle fails. The deficiencies must be repaired and the vehicle retested.",
+      "fail_dangerous": "The vehicle must not be used on the public road until the dangerous deficiencies are repaired.",
+      "incomplete": "The inspection is still in progress."
+    }
+  },
+  "page": {
+    "back": "Inspections",
+    "statusInProgress": "In progress",
+    "statusCompleted": "Completed",
+    "complete": "Complete",
+    "reopen": "Reopen",
+    "share": "Share",
+    "save": "Save",
+    "saving": "Saving…",
+    "savedAt": "Saved {time}",
+    "savesAsYouGo": "Saves as you go",
+    "saveFailed": "Some changes did not save",
+    "allSaved": "All changes are saved",
+    "moreActions": "More actions",
+    "downloadCertificate": "Download certificate",
+    "manageTemplates": "Manage templates",
+    "delete": "Delete",
+    "createWorkOrder": "Create work order",
+    "viewWorkOrder": "View work order",
+    "createQuote": "Create quote",
+    "viewQuote": "View quote",
+    "needsDefect": "Available once a defect has been recorded",
+    "workOrderRaised": "Work order {number} raised from this inspection on {date}",
+    "open": "Open",
+    "quoteCreated": "Quote created by {name} on {date}",
+    "quoteRequested": "The customer asked for a quote on {count} item(s)",
+    "stillToGrade": "{count} of {total} checks still to grade.",
+    "progress": "Progress",
+    "checksGraded": "checks graded",
+    "progressLabel": "{graded} of {total} checks graded: {pass} no defect, {attention} minor, {fail} major, {dangerous} dangerous.",
+    "sections": "Sections",
+    "graded": "{graded}/{total} graded",
+    "vehicle": "Vehicle",
+    "vessel": "Vessel",
+    "customer": "Customer",
+    "plate": "Plate",
+    "registration": "Registration",
+    "template": "Template",
+    "scaleEu": "Graded on the EU defect scale from Directive 2014/45/EU.",
+    "scaleBasic": "Graded pass / attention / fail.",
+    "country": "Country: {code}.",
+    "manageInspectionTemplates": "Manage inspection templates",
+    "readMore": "Read more",
+    "odometer": "Odometer",
+    "engineHours": "Engine hours",
+    "completeTitle": "Complete this inspection?",
+    "completeBody": "The checks are locked once the inspection is completed, and the result becomes \"{result}\".",
+    "completeBlocked": "{count} check(s) the template marks as mandatory are outstanding.",
+    "completeUngraded": "{count} check(s) have not been graded.",
+    "completeDangerous": "{count} dangerous defect(s) recorded. The vehicle must not be used on the public road.",
+    "reopenTitle": "Reopen this inspection?",
+    "reopenBody": "The checks become editable again and the result goes back to in progress. The inspector, certificate number and next test date are kept.",
+    "reopenShared": "The customer's link stays live and will show the result changing as you edit. Any certificate already downloaded keeps the old result.",
+    "deleteTitle": "Delete this inspection?",
+    "deleteBody": "This permanently deletes the inspection, its grades, notes and photos. It cannot be undone.",
+    "cancel": "Cancel",
+    "completed": "Inspection completed",
+    "reopened": "Inspection reopened",
+    "deleted": "Inspection deleted",
+    "completeFailed": "Failed to complete inspection",
+    "reopenFailed": "Failed to reopen inspection",
+    "deleteFailed": "Failed to delete inspection",
+    "quoteFailed": "Failed to create quote",
+    "workOrderFailed": "Failed to create work order"
+  },
+  "item": {
+    "required": "(required)",
+    "saving": "Saving",
+    "addNote": "Add note",
+    "hideNote": "Hide note",
+    "addPhoto": "Add a photo or video to {name}",
+    "notesFor": "Notes for {name}",
+    "describeDefect": "Describe the defect — this appears on the report",
+    "optionalNote": "Add a note (optional)",
+    "notesRequired": "Describe the defect before this grade is saved.",
+    "photoRequired": "A photo of the defect is required here. The inspection cannot be completed until one is attached.",
+    "reading": "Reading",
+    "readingUnit": "Reading ({unit})",
+    "limit": "Limit: {range}",
+    "recordedValue": "Recorded value",
+    "answer": "Answer",
+    "selectAnswer": "Select an answer",
+    "viewPhoto": "View photo {index} of {name} full size",
+    "photoAlt": "{name}, photo {index}",
+    "removePhoto": "Remove photo {index} from {name}",
+    "clearTitle": "Clear this grade?",
+    "clearBody": "The note recorded against \"{name}\" will be removed and the check goes back to not inspected.",
+    "clear": "Clear",
+    "cancel": "Cancel",
+    "saveFailed": "Could not save this check",
+    "uploadFailed": "Upload failed",
+    "uploadFailedFor": "Could not upload {name}"
+  },
+  "suggestions": {
+    "title": "Common defects",
+    "add": "Add \"{text}\" and grade as {grade}",
+    "more": "More ({count})",
+    "search": "Search defect wording",
+    "empty": "No matching phrase.",
+    "source": {
+      "workshop": "Your workshop",
+      "history": "Used before",
+      "regulation": "Directive 2014/45/EU",
+      "general": "General"
+    }
+  },
+  "media": {
+    "viewer": "Image {index} of {total}: {caption}",
+    "close": "Close image viewer",
+    "previous": "Previous image",
+    "next": "Next image"
+  },
+  "certificate": {
+    "title": "Certificate details",
+    "description": "Required on a roadworthiness certificate under Directive 2014/45/EU, Annex IV.",
+    "inspector": "Inspector",
+    "notRecorded": "Not recorded",
+    "addTechnician": "Add technician…",
+    "recordedAs": "Recorded as {name}",
+    "vehicleCategory": "Vehicle category",
+    "mileageAtTest": "{label} at time of test",
+    "reportReference": "Report reference",
+    "reportReferenceHelp": "Your own reference for this report. If you are an approved test centre issuing a national control slip, put its number here so the two match. Otherwise leave it empty.",
+    "optional": "Optional",
+    "placeOfTest": "Place of test",
+    "placeholderAddress": "Test centre or workshop address",
+    "fromSettings": "From your workshop settings.",
+    "change": "Change",
+    "differsFromWorkshop": "Differs from your workshop address.",
+    "useWorkshopAddress": "Use workshop address",
+    "noWorkshopAddress": "No workshop address is set.",
+    "addOne": "Add one",
+    "nextTestDue": "Next test due",
+    "notSet": "Not set",
+    "intervalGroup": "Set the next test date from the date of this test",
+    "intervalHelp": "Counted from the date of this test. Two years is the Union baseline for cars and light goods vehicles; check the interval your country applies.",
+    "months6": "6 months",
+    "years1": "1 year",
+    "years2": "2 years",
+    "years3": "3 years",
+    "years4": "4 years",
+    "unsaved": "Unsaved changes",
+    "saveDetails": "Save details",
+    "saved": "Certificate details saved",
+    "saveFailed": "Could not save the certificate details",
+    "addTechnicianTitle": "Add technician",
+    "addTechnicianBody": "They join the workshop roster and can be assigned work as well as inspections.",
+    "technicianName": "Name",
+    "technicianPlaceholder": "e.g. Anna Berg",
+    "addAndSelect": "Add and select",
+    "technicianAdded": "{name} added and selected",
+    "technicianFailed": "Could not add the technician",
+    "cancel": "Cancel"
+  },
+  "templates": {
+    "title": "Inspection templates",
+    "description": "The checklists your technicians work through. Build one per country, per vehicle type or per service you sell.",
+    "import": "Import",
+    "browse": "Browse checklists",
+    "new": "New template",
+    "restoreBanner": "{count} ready-made checklist(s) have been removed from your list. You can put them back.",
+    "restore": "Restore",
+    "restored": "Restored {count} checklist(s)",
+    "allPresent": "Every checklist is already here",
+    "restoreFailed": "Failed to add the checklists",
+    "emptyTitle": "No templates yet",
+    "emptyBody": "The ready-made checklists — including the EU periodic technical inspection — have all been removed. Put them back, or build your own from scratch.",
+    "restoreChecklists": "Restore checklists",
+    "buildMyOwn": "Build my own",
+    "default": "Default",
+    "actionsFor": "Actions for {name}",
+    "edit": "Edit",
+    "duplicate": "Duplicate",
+    "export": "Export…",
+    "delete": "Delete",
+    "sections": "{count} section(s)",
+    "checks": "{count} check(s)",
+    "measured": "{count} measured",
+    "euScale": "EU defect scale",
+    "basicScale": "Pass / attention / fail",
+    "deleteTitle": "Delete \"{name}\"?",
+    "deleteBody": "This permanently deletes the template. Inspections already carried out with it keep their own copy of the checklist and are not affected.",
+    "deleted": "Template deleted",
+    "deleteFailed": "Failed to delete template",
+    "duplicated": "Template duplicated",
+    "duplicateFailed": "Failed to duplicate template",
+    "cancel": "Cancel",
+    "srSections": "Sections",
+    "srChecks": "Checks",
+    "srMeasurements": "Measurements"
+  },
+  "presets": {
+    "title": "Start from a template",
+    "description": "Every preset is copied into your workshop as a normal template. Rename sections, move checks and change any limit to match your country and your equipment.",
+    "search": "Search by name, country or standard",
+    "searchLabel": "Search templates",
+    "inYourList": "In your list",
+    "counts": "{sections} sections · {checks} checks",
+    "noMatch": "No template matches \"{query}\".",
+    "warning": "Regulatory presets carry the common EU limits. Check them against your national rules before you issue a certificate.",
+    "cancel": "Cancel",
+    "add": "Add template",
+    "added": "Template added. Adjust it to match your workshop.",
+    "addFailed": "Failed to add template",
+    "group": {
+      "regulatory": "Regulatory",
+      "workshop": "Workshop",
+      "specialist": "Specialist"
+    },
+    "groupDescription": {
+      "regulatory": "Periodic technical inspection checklists built on Directive 2014/45/EU.",
+      "workshop": "General service and sales checklists that are not tied to a regulation.",
+      "specialist": "Checklists for drivetrains and vessels that need their own procedure."
+    },
+    "euScale": "EU defect scale",
+    "basicScale": "Pass / attention / fail"
+  },
+  "builder": {
+    "newTitle": "New template",
+    "editTitle": "Edit template",
+    "description": "Build the checklist your workshop actually uses. Drag to reorder, and configure each check to record a grade, a measurement, a written note or a fixed answer.",
+    "name": "Name",
+    "namePlaceholder": "e.g. Periodic technical inspection",
+    "templateDescription": "Description",
+    "descriptionPlaceholder": "What this checklist is for and when to use it.",
+    "country": "Country",
+    "notCountrySpecific": "Not country-specific",
+    "countryHelp": "Recorded on the report so a shop working across borders can tell its checklists apart.",
+    "defectScale": "Defect scale",
+    "scaleEu": "Minor / major / dangerous (EU)",
+    "scaleBasic": "Pass / attention / fail",
+    "scaleEuHelp": "The categories in Article 7(2) of Directive 2014/45/EU. A vehicle with only minor defects still passes.",
+    "scaleBasicHelp": "A plain three-step scale for service checklists that are not a statutory test.",
+    "defaultTemplate": "Default template",
+    "defaultHelp": "Pre-selected when a technician starts a new inspection.",
+    "summary": "{sections} section(s) · {checks} check(s)",
+    "renumber": "Renumber references",
+    "renumberHelp": "Numbers every section and check in order — 1, 1.1, 1.2, 2. New rows are numbered as you add them, so you only need this after moving things about.",
+    "renumberWarning": "This checklist uses regulation references such as 1.1.13. Renumbering replaces them.",
+    "addSection": "Add section",
+    "addCheck": "Add check",
+    "ref": "Ref.",
+    "sectionName": "Section name, e.g. Braking equipment",
+    "sectionNote": "Optional note shown above the checks in this section",
+    "checkName": "Check name, e.g. Brake linings and pads",
+    "reorderSection": "Reorder section {name}",
+    "reorderCheck": "Reorder check {name}",
+    "removeSection": "Remove section {name}",
+    "removeCheck": "Remove check {name}",
+    "duplicateCheck": "Duplicate check {name}",
+    "configure": "Configure {name}",
+    "hideSettings": "Hide settings for {name}",
+    "howRecorded": "How is it recorded?",
+    "guidance": "Guidance for the technician",
+    "guidancePlaceholder": "Shown under the check name on the inspection form.",
+    "measurement": "Measurement",
+    "unit": "Unit",
+    "minimum": "Minimum",
+    "maximum": "Maximum",
+    "outOfRange": "Defect if out of range",
+    "rangeHelp": "Leave a bound empty for a one-sided limit, e.g. a minimum of 1.6 mm with no maximum. Readings outside the range are graded automatically.",
+    "answers": "Answers",
+    "answersHelp": "Separate the answers with commas.",
+    "mustComplete": "Must be completed",
+    "photoRequired": "Photo required when a defect is recorded",
+    "commonWording": "Common defect wording",
+    "commonWordingHelp": "One phrase per line. These are offered first when a technician records a defect here, ahead of the built-in wording, so your shop can settle on how it describes a fault.",
+    "inputType": {
+      "condition": "Defect grading",
+      "measurement": "Measurement",
+      "text": "Free text",
+      "choice": "Choice"
+    },
+    "inputHint": {
+      "condition": "Technician grades the check against the defect scale.",
+      "measurement": "Records a number in a unit, graded automatically against a range.",
+      "text": "Records a written observation, e.g. a tyre brand or a fault code.",
+      "choice": "Technician picks one of a fixed list of answers."
+    },
+    "cancel": "Cancel",
+    "create": "Create template",
+    "saveChanges": "Save changes",
+    "created": "Template created",
+    "updated": "Template updated",
+    "saveFailed": "Failed to save template",
+    "severityAttention": "Minor defect",
+    "severityFail": "Major defect",
+    "severityDangerous": "Dangerous defect",
+    "answersPlaceholder": "Full, Partial, None"
+  },
+  "packages": {
+    "exportTitle": "Export \"{name}\"",
+    "exportDescription": "Downloads the whole checklist as a file — sections, checks, units and limits. Anyone running Torqvoice can import it.",
+    "includeWording": "Include your own defect wording",
+    "includeWordingHelp": "The phrases your workshop added to checks. Useful to whoever receives this, but they are free text typed at a bench and can name a customer, a colleague or a local arrangement. Off unless you have read them.",
+    "attributeTo": "Attribute it to (optional)",
+    "attributePlaceholder": "e.g. Egeland Auto",
+    "attributeHelp": "Written into the file so whoever installs it can see where it came from. Nothing is taken from your account.",
+    "download": "Download",
+    "exported": "Template exported",
+    "exportFailed": "Could not export this template",
+    "importTitle": "Import a checklist",
+    "importDescription": "Adds a checklist from a Torqvoice package file. It arrives as a normal template you can edit, and never replaces one you already have.",
+    "chooseFile": "Choose a package file",
+    "chooseAnother": "Choose a different file",
+    "unreadable": "That file could not be read. A Torqvoice package is a .json file exported from a template.",
+    "from": "From {author}",
+    "limitsWarning": "Limits in a shared checklist are whatever its author set. Check them against your national rules before issuing anything from it.",
+    "import": "Import",
+    "imported": "Imported {count} template(s)",
+    "importFailed": "Could not import this package",
+    "cancel": "Cancel"
+  },
+  "list": {
+    "tabAll": "All",
+    "tabInProgress": "In Progress",
+    "tabCompleted": "Completed",
+    "search": "Search inspections...",
+    "new": "New Inspection",
+    "empty": "No inspections found.",
+    "vehicle": "Vehicle",
+    "template": "Template",
+    "progress": "Progress",
+    "status": "Status",
+    "date": "Date",
+    "statusInProgress": "In Progress",
+    "statusCompleted": "Completed"
+  },
+  "new": {
+    "title": "New Inspection",
+    "vehicle": "Vehicle",
+    "searchVehicles": "Search vehicles...",
+    "loadingVehicles": "Loading vehicles...",
+    "noVehicles": "No vehicles found.",
+    "template": "Template",
+    "selectTemplate": "Select template",
+    "manageTemplates": "Manage templates",
+    "cancel": "Cancel",
+    "create": "Create Inspection",
+    "created": "Inspection created",
+    "createFailed": "Failed to create inspection"
+  },
+  "shareDialog": {
+    "title": "Share Inspection",
+    "description": "Share a read-only view of this inspection with your customer.",
+    "copyLink": "Copy link",
+    "copied": "Link copied to clipboard",
+    "generated": "Share link generated",
+    "revoked": "Share link revoked"
+  }
+}

+ 11 - 1
messages/es/audit.json

@@ -98,6 +98,16 @@
     "webhook_create": "Webhook creado",
     "webhook_update": "Webhook actualizado",
     "webhook_delete": "Webhook eliminado",
-    "webhook_rotateSecret": "Secreto de webhook rotado"
+    "webhook_rotateSecret": "Secreto de webhook rotado",
+    "inspection_reopen": "Inspección reabierta",
+    "inspectionTemplate_import": "Plantilla de inspección importada",
+    "scheduled_message_create": "Mensaje programado creado",
+    "scheduled_message_update": "Mensaje programado actualizado",
+    "scheduled_message_delete": "Mensaje programado eliminado",
+    "scheduled_message_cancel": "Mensaje programado cancelado",
+    "scheduled_message_send_now": "Mensaje programado enviado",
+    "settings_applyTaxRate": "Tipo impositivo aplicado",
+    "settings_convertToExclusive": "Precios convertidos a sin impuestos",
+    "settings_convertToInclusive": "Precios convertidos a con impuestos"
   }
 }

+ 32 - 32
messages/es/auth.json

@@ -1,9 +1,9 @@
 {
   "signIn": {
     "title": "Bienvenido de nuevo",
-    "description": "Inicie sesion en su taller",
-    "passwordPlaceholder": "Ingrese su contrasena",
-    "forgotPassword": "Olvido su contrasena?",
+    "description": "Inicie sesión en su taller",
+    "passwordPlaceholder": "Ingrese su contraseña",
+    "forgotPassword": "Olvido su contraseña?",
     "noAccount": "No tiene una cuenta?",
     "createOne": "Crear una",
     "termsAgreement": "Al utilizar este servicio, usted acepta nuestros",
@@ -12,7 +12,7 @@
     "passkeyDescription": "Usa tu huella digital, rostro o llave de seguridad",
     "errors": {
       "tooManyAttempts": "Demasiados intentos, vuelva a intentarlo mas tarde.",
-      "invalidCredentials": "Correo electronico o contrasena no validos",
+      "invalidCredentials": "Correo electrónico o contraseña no validos",
       "passkeyFailed": "Error al iniciar sesión con passkey. Inténtelo de nuevo o use correo y contraseña."
     },
     "resetPasswordCta": "Restablécela aquí"
@@ -20,10 +20,10 @@
   "signUp": {
     "title": "Crear una cuenta",
     "descriptionInvite": "Cree su cuenta para unirse al equipo",
-    "descriptionDefault": "Configure su taller para gestionar los vehiculos de sus clientes",
+    "descriptionDefault": "Configure su taller para gestionar los vehículos de sus clientes",
     "fullName": "Nombre completo",
     "fullNamePlaceholder": "Juan Perez",
-    "passwordPlaceholder": "Cree una contrasena segura",
+    "passwordPlaceholder": "Cree una contraseña segura",
     "agreeToTerms": "Acepto los",
     "createAccountJoin": "Crear cuenta y unirse al equipo",
     "createAccount": "Crear cuenta",
@@ -31,7 +31,7 @@
     "errors": {
       "couldNotCreate": "No se pudo crear la cuenta",
       "registrationDisabled": "El registro esta deshabilitado actualmente. Por favor, contacte a su administrador.",
-      "invitationFailed": "No se pudo aceptar la invitacion",
+      "invitationFailed": "No se pudo aceptar la invitación",
       "termsRequired": "Acepta los términos del servicio para continuar",
       "emailExists": "Ya existe una cuenta con este correo electrónico."
     },
@@ -39,28 +39,28 @@
     "signInInstead": "Inicia sesión en su lugar"
   },
   "forgotPassword": {
-    "title": "Restablecer su contrasena",
-    "description": "Ingrese su correo electronico y le enviaremos un enlace de restablecimiento",
+    "title": "Restablecer su contraseña",
+    "description": "Ingrese su correo electrónico y le enviaremos un enlace de restablecimiento",
     "sendResetLink": "Enviar enlace de restablecimiento",
-    "backToSignIn": "Volver a iniciar sesion",
-    "successMessage": "Si existe una cuenta con ese correo electronico, se ha enviado un enlace para restablecer la contrasena. Revise su bandeja de entrada."
+    "backToSignIn": "Volver a iniciar sesión",
+    "successMessage": "Si existe una cuenta con ese correo electrónico, se ha enviado un enlace para restablecer la contraseña. Revise su bandeja de entrada."
   },
   "resetPassword": {
-    "title": "Establecer nueva contrasena",
-    "description": "Ingrese su nueva contrasena a continuacion",
-    "newPassword": "Nueva contrasena",
-    "newPasswordPlaceholder": "Ingrese la nueva contrasena",
-    "confirmPassword": "Confirmar contrasena",
-    "confirmPasswordPlaceholder": "Confirme la nueva contrasena",
-    "resetButton": "Restablecer contrasena",
-    "backToSignIn": "Volver a iniciar sesion",
+    "title": "Establecer nueva contraseña",
+    "description": "Ingrese su nueva contraseña a continuación",
+    "newPassword": "Nueva contraseña",
+    "newPasswordPlaceholder": "Ingrese la nueva contraseña",
+    "confirmPassword": "Confirmar contraseña",
+    "confirmPasswordPlaceholder": "Confirme la nueva contraseña",
+    "resetButton": "Restablecer contraseña",
+    "backToSignIn": "Volver a iniciar sesión",
     "requestNewLink": "Solicitar nuevo enlace",
-    "invalidToken": "Token de restablecimiento no valido o faltante. Por favor, solicite un nuevo enlace de restablecimiento de contrasena.",
-    "successMessage": "Su contrasena se ha restablecido correctamente. Ahora puede iniciar sesion con su nueva contrasena.",
+    "invalidToken": "Token de restablecimiento no válido o faltante. Por favor, solicite un nuevo enlace de restablecimiento de contraseña.",
+    "successMessage": "Su contraseña se ha restablecido correctamente. Ahora puede iniciar sesión con su nueva contraseña.",
     "errors": {
-      "passwordsMismatch": "Las contrasenas no coinciden.",
-      "passwordTooShort": "La contrasena debe tener al menos 8 caracteres.",
-      "resetFailed": "No se pudo restablecer la contrasena. Es posible que el enlace haya expirado."
+      "passwordsMismatch": "Las contraseñas no coinciden.",
+      "passwordTooShort": "La contraseña debe tener al menos 8 caracteres.",
+      "resetFailed": "No se pudo restablecer la contraseña. Es posible que el enlace haya expirado."
     }
   },
   "verifyEmail": {
@@ -74,18 +74,18 @@
   },
   "verify2fa": {
     "title": "Autenticacion de dos factores",
-    "descriptionTotp": "Ingrese el codigo de 6 digitos de su aplicacion de autenticacion",
+    "descriptionTotp": "Ingrese el código de 6 dígitos de su aplicación de autenticacion",
     "descriptionBackup": "Ingrese uno de sus codigos de respaldo",
-    "authCode": "Codigo de autenticacion",
-    "backupCode": "Codigo de respaldo",
-    "backupCodePlaceholder": "Ingrese el codigo de respaldo",
+    "authCode": "Código de autenticacion",
+    "backupCode": "Código de respaldo",
+    "backupCodePlaceholder": "Ingrese el código de respaldo",
     "totpPlaceholder": "000000",
-    "trustDevice": "Confiar en este dispositivo durante 30 dias",
+    "trustDevice": "Confiar en este dispositivo durante 30 días",
     "verify": "Verificar",
-    "useAuthenticator": "Usar la aplicacion de autenticacion",
-    "useBackupCode": "Usar un codigo de respaldo",
+    "useAuthenticator": "Usar la aplicación de autenticacion",
+    "useBackupCode": "Usar un código de respaldo",
     "errors": {
-      "invalidCode": "Codigo no valido"
+      "invalidCode": "Código no válido"
     }
   }
 }

+ 29 - 5
messages/es/common.json

@@ -1,7 +1,7 @@
 {
   "brandName": "Torqvoice",
   "buttons": {
-    "signIn": "Iniciar sesion",
+    "signIn": "Iniciar sesión",
     "save": "Guardar",
     "cancel": "Cancelar",
     "delete": "Eliminar",
@@ -21,8 +21,8 @@
     "delete": "Eliminar"
   },
   "form": {
-    "email": "Correo electronico",
-    "password": "Contrasena",
+    "email": "Correo electrónico",
+    "password": "Contraseña",
     "emailPlaceholder": "you@example.com",
     "name": "Nombre",
     "phone": "Teléfono",
@@ -31,11 +31,11 @@
     "notes": "Notas"
   },
   "errors": {
-    "unexpected": "Ha ocurrido un error inesperado. Por favor, intentelo de nuevo.",
+    "unexpected": "Ha ocurrido un error inesperado. Por favor, inténtelo de nuevo.",
     "error": "Error"
   },
   "terms": {
-    "termsOfService": "Terminos de servicio"
+    "termsOfService": "Términos de servicio"
   },
   "errorPage": {
     "title": "Algo salió mal",
@@ -52,5 +52,29 @@
     "updated": "Torqvoice se ha actualizado a {version}",
     "whatsNew": "Ver novedades",
     "dismiss": "Cerrar"
+  },
+  "pagination": {
+    "showing": "Mostrando {start}–{end} de {total}",
+    "perPage": "por página",
+    "pageOf": "Página {page} de {pages}",
+    "first": "Primera página",
+    "previous": "Página anterior",
+    "next": "Página siguiente",
+    "last": "Última página"
+  },
+  "shared": {
+    "close": "Cerrar",
+    "viewPlans": "Ver planes",
+    "manageLicense": "Gestionar licencia",
+    "installOwn": "Instale el suyo",
+    "install": "Instalar",
+    "installApp": "Instalar aplicación",
+    "installTorqvoice": "Instalar Torqvoice",
+    "dismiss": "Descartar",
+    "useCamera": "Usar la cámara",
+    "enterManually": "Introducir manualmente",
+    "enterBarcode": "Introducir código de barras…",
+    "scanHint": "Apunte la cámara a un código de barras o introdúzcalo manualmente",
+    "iosInstallHint": "Toque Compartir > Añadir a inicio"
   }
 }

+ 396 - 0
messages/es/inspections.json

@@ -0,0 +1,396 @@
+{
+  "grades": {
+    "eu": {
+      "pass": "Sin deficiencia",
+      "attention": "Deficiencia leve",
+      "fail": "Deficiencia grave",
+      "dangerous": "Deficiencia peligrosa",
+      "not_inspected": "No inspeccionado"
+    },
+    "basic": {
+      "pass": "Apto",
+      "attention": "Atención",
+      "fail": "No apto",
+      "dangerous": "Peligroso",
+      "not_inspected": "No inspeccionado"
+    },
+    "short": {
+      "pass": "OK",
+      "attention": "Leve",
+      "fail": "Grave",
+      "dangerous": "Peligrosa",
+      "not_inspected": "–"
+    },
+    "hint": {
+      "pass": "Cumple los requisitos. Nada que señalar.",
+      "attention": "Sin efecto significativo sobre la seguridad. El vehículo es apto; subsanar cuando convenga.",
+      "fail": "Puede perjudicar la seguridad o afectar al medio ambiente. El vehículo no es apto; subsanar y volver a inspeccionar.",
+      "dangerous": "Riesgo directo e inmediato para la seguridad vial. El vehículo no debe circular.",
+      "not_inspected": "Este punto no se ha realizado."
+    }
+  },
+  "results": {
+    "pass": "Favorable",
+    "pass_minor": "Favorable con deficiencias leves",
+    "fail": "Desfavorable — deficiencias graves",
+    "fail_dangerous": "Negativo — deficiencias peligrosas",
+    "incomplete": "Sin finalizar",
+    "detail": {
+      "pass": "No se detectaron deficiencias.",
+      "pass_minor": "El vehículo es apto. Repare las deficiencias leves sin demora indebida.",
+      "fail": "El vehículo no es apto. Las deficiencias deben repararse y el vehículo volver a inspeccionarse.",
+      "fail_dangerous": "El vehículo no debe circular por la vía pública hasta que se reparen las deficiencias peligrosas.",
+      "incomplete": "Esta inspección sigue en curso."
+    }
+  },
+  "page": {
+    "back": "Inspecciones",
+    "statusInProgress": "En curso",
+    "statusCompleted": "Cerrada",
+    "complete": "Cerrar",
+    "reopen": "Reabrir",
+    "share": "Compartir",
+    "save": "Guardar",
+    "saving": "Guardando…",
+    "savedAt": "Guardado {time}",
+    "savesAsYouGo": "Guarda mientras trabaja",
+    "saveFailed": "Algunos cambios no se guardaron",
+    "allSaved": "Todos los cambios están guardados",
+    "moreActions": "Más acciones",
+    "downloadCertificate": "Descargar informe",
+    "manageTemplates": "Gestionar plantillas",
+    "delete": "Eliminar",
+    "createWorkOrder": "Crear orden de trabajo",
+    "viewWorkOrder": "Ver orden de trabajo",
+    "createQuote": "Crear presupuesto",
+    "viewQuote": "Ver presupuesto",
+    "needsDefect": "Disponible cuando se registre una deficiencia",
+    "workOrderRaised": "Orden de trabajo {number} creada desde esta inspección el {date}",
+    "open": "Abrir",
+    "quoteCreated": "Presupuesto creado por {name} el {date}",
+    "quoteRequested": "El cliente pidió presupuesto de {count} punto(s)",
+    "stillToGrade": "Quedan {count} de {total} puntos por calificar.",
+    "progress": "Avance",
+    "checksGraded": "puntos calificados",
+    "progressLabel": "{graded} de {total} puntos calificados: {pass} sin deficiencia, {attention} leves, {fail} graves, {dangerous} peligrosas.",
+    "sections": "Grupos",
+    "graded": "{graded}/{total} calificados",
+    "vehicle": "Vehículo",
+    "vessel": "Embarcación",
+    "customer": "Cliente",
+    "plate": "Matrícula",
+    "registration": "Matrícula",
+    "template": "Plantilla",
+    "scaleEu": "Calificado según la escala de deficiencias de la UE de la Directiva 2014/45/UE.",
+    "scaleBasic": "Calificado apto / atención / no apto.",
+    "country": "País: {code}.",
+    "manageInspectionTemplates": "Gestionar plantillas de inspección",
+    "readMore": "Más información",
+    "odometer": "Cuentakilómetros",
+    "engineHours": "Horas de motor",
+    "completeTitle": "¿Cerrar esta inspección?",
+    "completeBody": "Los puntos quedan bloqueados al cerrar, y el resultado pasa a ser «{result}».",
+    "completeBlocked": "Quedan {count} punto(s) que la plantilla marca como obligatorios.",
+    "completeUngraded": "{count} punto(s) no se han calificado.",
+    "completeDangerous": "{count} deficiencia(s) peligrosa(s) registrada(s). El vehículo no debe circular por la vía pública.",
+    "reopenTitle": "¿Reabrir esta inspección?",
+    "reopenBody": "Los puntos vuelven a ser editables y el resultado regresa a en curso. Se conservan el inspector, el número de certificado y la fecha de la próxima inspección.",
+    "reopenShared": "El enlace del cliente sigue activo y mostrará cómo cambia el resultado mientras trabaja. Un informe ya descargado conserva el resultado anterior.",
+    "deleteTitle": "¿Eliminar esta inspección?",
+    "deleteBody": "Esto elimina de forma permanente la inspección, sus calificaciones, notas y fotos. No se puede deshacer.",
+    "cancel": "Cancelar",
+    "completed": "Inspección cerrada",
+    "reopened": "Inspección reabierta",
+    "deleted": "Inspección eliminada",
+    "completeFailed": "No se pudo cerrar la inspección",
+    "reopenFailed": "No se pudo reabrir la inspección",
+    "deleteFailed": "No se pudo eliminar la inspección",
+    "quoteFailed": "No se pudo crear el presupuesto",
+    "workOrderFailed": "No se pudo crear la orden de trabajo"
+  },
+  "item": {
+    "required": "(obligatorio)",
+    "saving": "Guardando",
+    "addNote": "Añadir nota",
+    "hideNote": "Ocultar nota",
+    "addPhoto": "Añadir foto o vídeo a {name}",
+    "notesFor": "Notas de {name}",
+    "describeDefect": "Describa la deficiencia — aparece en el informe",
+    "optionalNote": "Añadir una nota (opcional)",
+    "notesRequired": "Describa la deficiencia antes de que se guarde esta calificación.",
+    "photoRequired": "Aquí se exige una foto de la deficiencia. La inspección no puede cerrarse hasta que se adjunte una.",
+    "reading": "Lectura",
+    "readingUnit": "Lectura ({unit})",
+    "limit": "Límite: {range}",
+    "recordedValue": "Valor registrado",
+    "answer": "Respuesta",
+    "selectAnswer": "Elija una respuesta",
+    "viewPhoto": "Ver la foto {index} de {name} a tamaño completo",
+    "photoAlt": "{name}, foto {index}",
+    "removePhoto": "Quitar la foto {index} de {name}",
+    "clearTitle": "¿Borrar esta calificación?",
+    "clearBody": "Se eliminará la nota registrada en «{name}» y el punto volverá a no inspeccionado.",
+    "clear": "Borrar",
+    "cancel": "Cancelar",
+    "saveFailed": "No se pudo guardar este punto",
+    "uploadFailed": "Error al subir",
+    "uploadFailedFor": "No se pudo subir {name}"
+  },
+  "suggestions": {
+    "title": "Deficiencias habituales",
+    "add": "Añadir «{text}» y calificar como {grade}",
+    "more": "Más ({count})",
+    "search": "Buscar redacción",
+    "empty": "Ninguna redacción coincide.",
+    "source": {
+      "workshop": "Su taller",
+      "history": "Usada antes",
+      "regulation": "Directiva 2014/45/UE",
+      "general": "General"
+    }
+  },
+  "media": {
+    "viewer": "Imagen {index} de {total}: {caption}",
+    "close": "Cerrar el visor",
+    "previous": "Imagen anterior",
+    "next": "Imagen siguiente"
+  },
+  "certificate": {
+    "title": "Datos del informe",
+    "description": "Exigidos en un informe de inspección técnica conforme a la Directiva 2014/45/UE, anexo IV.",
+    "inspector": "Inspector",
+    "notRecorded": "No registrado",
+    "addTechnician": "Añadir técnico…",
+    "recordedAs": "Registrado como {name}",
+    "vehicleCategory": "Categoría del vehículo",
+    "mileageAtTest": "{label} en el momento de la inspección",
+    "reportReference": "Referencia del informe",
+    "reportReferenceHelp": "Su propia referencia para este informe. Si es un centro autorizado que emite un informe oficial, escriba aquí su número para que ambos coincidan. En caso contrario, déjelo vacío.",
+    "optional": "Opcional",
+    "placeOfTest": "Lugar de la inspección",
+    "placeholderAddress": "Estación de inspección o dirección del taller",
+    "fromSettings": "De la configuración de su taller.",
+    "change": "Cambiar",
+    "differsFromWorkshop": "Difiere de la dirección de su taller.",
+    "useWorkshopAddress": "Usar la dirección del taller",
+    "noWorkshopAddress": "No hay ninguna dirección de taller configurada.",
+    "addOne": "Añadir una",
+    "nextTestDue": "Próxima inspección",
+    "notSet": "Sin definir",
+    "intervalGroup": "Fijar la fecha de la próxima inspección a partir de esta",
+    "intervalHelp": "Contado desde la fecha de esta inspección. Dos años es la referencia de la Unión para turismos y vehículos comerciales ligeros; compruebe el intervalo que aplica su país.",
+    "months6": "6 meses",
+    "years1": "1 año",
+    "years2": "2 años",
+    "years3": "3 años",
+    "years4": "4 años",
+    "unsaved": "Cambios sin guardar",
+    "saveDetails": "Guardar datos",
+    "saved": "Datos guardados",
+    "saveFailed": "No se pudieron guardar los datos",
+    "addTechnicianTitle": "Añadir técnico",
+    "addTechnicianBody": "Se incorpora a la lista del taller y se le pueden asignar tanto trabajos como inspecciones.",
+    "technicianName": "Nombre",
+    "technicianPlaceholder": "p. ej. Anna Berg",
+    "addAndSelect": "Añadir y seleccionar",
+    "technicianAdded": "{name} añadido y seleccionado",
+    "technicianFailed": "No se pudo añadir al técnico",
+    "cancel": "Cancelar"
+  },
+  "templates": {
+    "title": "Plantillas de inspección",
+    "description": "Las listas de comprobación que recorren sus técnicos. Cree una por país, por tipo de vehículo o por servicio que venda.",
+    "import": "Importar",
+    "browse": "Explorar listas",
+    "new": "Nueva plantilla",
+    "restoreBanner": "{count} listas ya preparadas se han quitado de su lista. Puede volver a añadirlas.",
+    "restore": "Restaurar",
+    "restored": "{count} lista(s) restaurada(s)",
+    "allPresent": "Todas las listas ya están aquí",
+    "restoreFailed": "No se pudieron añadir las listas",
+    "emptyTitle": "Aún no hay plantillas",
+    "emptyBody": "Las listas ya preparadas, incluida la inspección técnica periódica de la UE, se han quitado todas. Vuelva a añadirlas o construya la suya desde cero.",
+    "restoreChecklists": "Restaurar listas",
+    "buildMyOwn": "Construir la mía",
+    "default": "Predeterminada",
+    "actionsFor": "Acciones de {name}",
+    "edit": "Editar",
+    "duplicate": "Duplicar",
+    "export": "Exportar…",
+    "delete": "Eliminar",
+    "sections": "{count} grupo(s)",
+    "checks": "{count} punto(s)",
+    "measured": "{count} medidos",
+    "euScale": "Escala de deficiencias UE",
+    "basicScale": "Apto / atención / no apto",
+    "deleteTitle": "¿Eliminar «{name}»?",
+    "deleteBody": "Esto elimina la plantilla de forma permanente. Las inspecciones ya realizadas con ella conservan su propia copia de la lista y no se ven afectadas.",
+    "deleted": "Plantilla eliminada",
+    "deleteFailed": "No se pudo eliminar la plantilla",
+    "duplicated": "Plantilla duplicada",
+    "duplicateFailed": "No se pudo duplicar la plantilla",
+    "cancel": "Cancelar",
+    "srSections": "Grupos",
+    "srChecks": "Puntos",
+    "srMeasurements": "Mediciones"
+  },
+  "presets": {
+    "title": "Partir de una plantilla",
+    "description": "Cada plantilla se copia a su taller como una plantilla normal. Renombre grupos, mueva puntos y cambie cualquier límite para que encaje con su país y su equipo.",
+    "search": "Buscar por nombre, país o norma",
+    "searchLabel": "Buscar plantillas",
+    "inYourList": "En su lista",
+    "counts": "{sections} grupos · {checks} puntos",
+    "noMatch": "Ninguna plantilla coincide con «{query}».",
+    "warning": "Las plantillas reglamentarias llevan los límites habituales de la UE. Compruébelos con su normativa nacional antes de emitir un informe.",
+    "cancel": "Cancelar",
+    "add": "Añadir plantilla",
+    "added": "Plantilla añadida. Adáptela a su taller.",
+    "addFailed": "No se pudo añadir la plantilla",
+    "group": {
+      "regulatory": "Reglamentaria",
+      "workshop": "Taller",
+      "specialist": "Especializada"
+    },
+    "groupDescription": {
+      "regulatory": "Listas de inspección técnica periódica basadas en la Directiva 2014/45/UE.",
+      "workshop": "Listas generales de servicio y venta no vinculadas a una normativa.",
+      "specialist": "Listas para motorizaciones y embarcaciones que requieren su propio procedimiento."
+    },
+    "euScale": "Escala de deficiencias UE",
+    "basicScale": "Apto / atención / no apto"
+  },
+  "builder": {
+    "newTitle": "Nueva plantilla",
+    "editTitle": "Editar plantilla",
+    "description": "Construya la lista que su taller usa de verdad. Arrastre para reordenar y configure cada punto para que registre una calificación, una medición, una nota escrita o una respuesta fija.",
+    "name": "Nombre",
+    "namePlaceholder": "p. ej. Inspección técnica periódica",
+    "templateDescription": "Descripción",
+    "descriptionPlaceholder": "Para qué sirve esta lista y cuándo se usa.",
+    "country": "País",
+    "notCountrySpecific": "No específica de un país",
+    "countryHelp": "Se registra en el informe para que un taller que trabaja a ambos lados de una frontera distinga sus listas.",
+    "defectScale": "Escala de deficiencias",
+    "scaleEu": "Leve / grave / peligrosa (UE)",
+    "scaleBasic": "Apto / atención / no apto",
+    "scaleEuHelp": "Las categorías del artículo 7, apartado 2, de la Directiva 2014/45/UE. Un vehículo con solo deficiencias leves sigue siendo apto.",
+    "scaleBasicHelp": "Una escala sencilla de tres pasos para listas de servicio que no son una inspección reglamentaria.",
+    "defaultTemplate": "Plantilla predeterminada",
+    "defaultHelp": "Preseleccionada cuando un técnico inicia una nueva inspección.",
+    "summary": "{sections} grupo(s) · {checks} punto(s)",
+    "renumber": "Renumerar referencias",
+    "renumberHelp": "Numera cada grupo y cada punto en orden: 1, 1.1, 1.2, 2. Las filas nuevas se numeran al añadirlas, así que esto solo hace falta tras mover cosas.",
+    "renumberWarning": "Esta lista usa referencias normativas como 1.1.13. Renumerar las sustituye.",
+    "addSection": "Añadir grupo",
+    "addCheck": "Añadir punto",
+    "ref": "Ref.",
+    "sectionName": "Nombre del grupo, p. ej. Frenos",
+    "sectionNote": "Nota opcional mostrada sobre los puntos de este grupo",
+    "checkName": "Nombre del punto, p. ej. Pastillas de freno",
+    "reorderSection": "Reordenar el grupo {name}",
+    "reorderCheck": "Reordenar el punto {name}",
+    "removeSection": "Quitar el grupo {name}",
+    "removeCheck": "Quitar el punto {name}",
+    "duplicateCheck": "Duplicar el punto {name}",
+    "configure": "Configurar {name}",
+    "hideSettings": "Ocultar los ajustes de {name}",
+    "howRecorded": "¿Cómo se registra?",
+    "guidance": "Indicación para el técnico",
+    "guidancePlaceholder": "Se muestra bajo el nombre del punto en el formulario de inspección.",
+    "measurement": "Medición",
+    "unit": "Unidad",
+    "minimum": "Mínimo",
+    "maximum": "Máximo",
+    "outOfRange": "Deficiencia fuera de intervalo",
+    "rangeHelp": "Deje un límite vacío para un requisito de un solo lado, por ejemplo un mínimo de 1,6 mm sin máximo. Las lecturas fuera del intervalo se califican automáticamente.",
+    "answers": "Respuestas",
+    "answersHelp": "Separe las respuestas con comas.",
+    "mustComplete": "Debe cumplimentarse",
+    "photoRequired": "Foto obligatoria cuando se registra una deficiencia",
+    "commonWording": "Redacciones habituales",
+    "commonWordingHelp": "Una redacción por línea. Se ofrecen las primeras cuando un técnico registra aquí una deficiencia, por delante de los textos incorporados, para que su taller acuerde cómo describir un fallo.",
+    "inputType": {
+      "condition": "Calificación de deficiencia",
+      "measurement": "Medición",
+      "text": "Texto libre",
+      "choice": "Elección"
+    },
+    "inputHint": {
+      "condition": "El técnico califica el punto según la escala de deficiencias.",
+      "measurement": "Registra un número con unidad, calificado automáticamente frente a un intervalo.",
+      "text": "Registra una observación escrita, por ejemplo una marca de neumático o un código de avería.",
+      "choice": "El técnico elige una respuesta de una lista fija."
+    },
+    "cancel": "Cancelar",
+    "create": "Crear plantilla",
+    "saveChanges": "Guardar cambios",
+    "created": "Plantilla creada",
+    "updated": "Plantilla actualizada",
+    "saveFailed": "No se pudo guardar la plantilla",
+    "severityAttention": "Deficiencia leve",
+    "severityFail": "Deficiencia grave",
+    "severityDangerous": "Deficiencia peligrosa",
+    "answersPlaceholder": "Completo, Parcial, Ninguno"
+  },
+  "packages": {
+    "exportTitle": "Exportar «{name}»",
+    "exportDescription": "Descarga toda la lista como archivo: grupos, puntos, unidades y límites. Cualquiera que use Torqvoice puede importarla.",
+    "includeWording": "Incluir sus propias redacciones",
+    "includeWordingHelp": "Las redacciones que su taller añadió a los puntos. Útiles para quien las reciba, pero son texto libre escrito en el banco de trabajo y pueden nombrar a un cliente, a un compañero o un acuerdo local. Desactivado salvo que las haya leído.",
+    "attributeTo": "Atribuir a (opcional)",
+    "attributePlaceholder": "p. ej. Egeland Auto",
+    "attributeHelp": "Se escribe en el archivo para que quien lo instale vea de dónde viene. No se toma nada de su cuenta.",
+    "download": "Descargar",
+    "exported": "Plantilla exportada",
+    "exportFailed": "No se pudo exportar esta plantilla",
+    "importTitle": "Importar una lista",
+    "importDescription": "Añade una lista desde un archivo de paquete de Torqvoice. Llega como una plantilla normal que puede editar y nunca sustituye a una que ya tenga.",
+    "chooseFile": "Elegir un archivo de paquete",
+    "chooseAnother": "Elegir otro archivo",
+    "unreadable": "No se pudo leer ese archivo. Un paquete de Torqvoice es un archivo .json exportado desde una plantilla.",
+    "from": "De {author}",
+    "limitsWarning": "Los límites de una lista compartida son los que fijó su autor. Compruébelos con su normativa nacional antes de emitir nada a partir de ella.",
+    "import": "Importar",
+    "imported": "{count} plantilla(s) importada(s)",
+    "importFailed": "No se pudo importar este paquete",
+    "cancel": "Cancelar"
+  },
+  "list": {
+    "tabAll": "Todas",
+    "tabInProgress": "En curso",
+    "tabCompleted": "Cerradas",
+    "search": "Buscar inspecciones…",
+    "new": "Nueva inspección",
+    "empty": "No se encontraron inspecciones.",
+    "vehicle": "Vehículo",
+    "template": "Plantilla",
+    "progress": "Avance",
+    "status": "Estado",
+    "date": "Fecha",
+    "statusInProgress": "En curso",
+    "statusCompleted": "Cerrada"
+  },
+  "new": {
+    "title": "Nueva inspección",
+    "vehicle": "Vehículo",
+    "searchVehicles": "Buscar vehículos…",
+    "loadingVehicles": "Cargando vehículos…",
+    "noVehicles": "No se encontraron vehículos.",
+    "template": "Plantilla",
+    "selectTemplate": "Elegir plantilla",
+    "manageTemplates": "Gestionar plantillas",
+    "cancel": "Cancelar",
+    "create": "Crear inspección",
+    "created": "Inspección creada",
+    "createFailed": "No se pudo crear la inspección"
+  },
+  "shareDialog": {
+    "title": "Compartir inspección",
+    "description": "Comparta con su cliente una vista de solo lectura de esta inspección.",
+    "copyLink": "Copiar enlace",
+    "copied": "Enlace copiado al portapapeles",
+    "generated": "Enlace de compartición creado",
+    "revoked": "Enlace de compartición revocado"
+  }
+}

+ 203 - 203
messages/es/settings.json

@@ -1,10 +1,10 @@
 {
-  "title": "Configuracion",
+  "title": "Configuración",
   "subtitle": "Administre su taller, facturas y preferencias",
   "nav": {
     "categories": {
       "general": "General",
-      "billing": "Facturacion y documentos",
+      "billing": "Facturación y documentos",
       "communications": "Comunicaciones",
       "workshop": "Taller",
       "integrations": "Integraciones",
@@ -16,12 +16,12 @@
         "description": "Datos comerciales y marca"
       },
       "subscription": {
-        "title": "Suscripcion",
-        "description": "Plan y facturacion"
+        "title": "Suscripción",
+        "description": "Plan y facturación"
       },
       "account": {
         "title": "Cuenta",
-        "description": "Nombre, correo y contrasena"
+        "description": "Nombre, correo y contraseña"
       },
       "team": {
         "title": "Equipo",
@@ -29,7 +29,7 @@
       },
       "invoice": {
         "title": "Facturas y presupuestos",
-        "description": "Configuracion de facturas, diseno y campos personalizados"
+        "description": "Configuración de facturas, diseño y campos personalizados"
       },
       "templates": {
         "title": "Plantillas",
@@ -57,11 +57,11 @@
       },
       "email": {
         "title": "Correo",
-        "description": "Proveedor de correo y envio"
+        "description": "Proveedor de correo y envío"
       },
       "sms": {
         "title": "SMS",
-        "description": "Proveedor de SMS y mensajeria"
+        "description": "Proveedor de SMS y mensajería"
       },
       "telegram": {
         "title": "Telegram",
@@ -73,7 +73,7 @@
       },
       "workshop": {
         "title": "Taller",
-        "description": "Valores predeterminados de tecnico y mano de obra"
+        "description": "Valores predeterminados de técnico y mano de obra"
       },
       "laborPresets": {
         "title": "Paquetes de mano de obra",
@@ -97,7 +97,7 @@
       },
       "appearance": {
         "title": "Apariencia",
-        "description": "Tema y visualizacion"
+        "description": "Tema y visualización"
       },
       "data": {
         "title": "Datos",
@@ -109,7 +109,7 @@
       },
       "about": {
         "title": "Acerca de",
-        "description": "Version e informacion"
+        "description": "Version e información"
       },
       "alerts": {
         "title": "Alertas",
@@ -123,7 +123,7 @@
     "pro": "PRO"
   },
   "readOnly": {
-    "banner": "Tiene acceso de solo lectura a la configuracion. Contacte a un administrador para realizar cambios."
+    "banner": "Tiene acceso de solo lectura a la configuración. Contacte a un administrador para realizar cambios."
   },
   "featureLocked": {
     "requiresPro": "Requiere una licencia Pro",
@@ -141,23 +141,23 @@
     "failedUploadLogo": "Error al subir el logo",
     "detailsTitle": "Datos de la empresa",
     "shopName": "Nombre de la empresa / taller",
-    "shopNamePlaceholder": "Taller Mecanico Lopez",
-    "orgNumber": "Numero de organizacion",
+    "shopNamePlaceholder": "Taller Mecánico Lopez",
+    "orgNumber": "Número de organización",
     "orgNumberPlaceholder": "123 456 789",
-    "phone": "Telefono",
+    "phone": "Teléfono",
     "phonePlaceholder": "(555) 123-4567",
-    "email": "Correo electronico",
+    "email": "Correo electrónico",
     "emailPlaceholder": "servicio@ejemplo.com",
-    "address": "Direccion",
+    "address": "Dirección",
     "addressPlaceholder": "Calle Principal 123\nPiso 1\n12345 Ciudad",
     "saveCompany": "Guardar datos de empresa",
     "addNewCompany": "Agregar nueva empresa",
     "companyName": "Nombre de la empresa",
-    "companyNamePlaceholder": "Ej. Taller Mecanico Lopez",
+    "companyNamePlaceholder": "Ej. Taller Mecánico Lopez",
     "cancel": "Cancelar",
     "createCompany": "Crear empresa",
-    "settingsSaved": "Configuracion de empresa guardada",
-    "failedSave": "Error al guardar la configuracion",
+    "settingsSaved": "Configuración de empresa guardada",
+    "failedSave": "Error al guardar la configuración",
     "companyCreated": "Empresa creada exitosamente",
     "serviceType": "Tipo de Servicio",
     "serviceTypeDescription": "Seleccione el tipo de servicio que ofrece su taller. Esto cambia las etiquetas de los campos en toda la aplicación.",
@@ -168,7 +168,7 @@
   },
   "account": {
     "profileTitle": "Perfil",
-    "profileDescription": "Actualice su nombre para mostrar y direccion de correo electronico.",
+    "profileDescription": "Actualice su nombre para mostrar y dirección de correo electrónico.",
     "emailVerified": "Verificado",
     "emailNotVerified": "No verificado",
     "sendVerificationEmail": "Enviar correo de verificación",
@@ -178,50 +178,50 @@
     "emailChangeSent": "Se ha enviado un enlace de confirmación a su nueva dirección de correo electrónico. Su correo se actualizará cuando haga clic en el enlace.",
     "name": "Nombre",
     "namePlaceholder": "Su nombre",
-    "emailLabel": "Correo electronico",
-    "emailPlaceholder": "Su correo electronico",
+    "emailLabel": "Correo electrónico",
+    "emailPlaceholder": "Su correo electrónico",
     "saveProfile": "Guardar perfil",
     "profileUpdated": "Perfil actualizado",
     "failedUpdateProfile": "Error al actualizar el perfil",
-    "failedUpdateEmail": "Error al actualizar el correo electronico",
-    "changePasswordTitle": "Cambiar contrasena",
-    "changePasswordDescription": "Ingrese su contrasena actual y elija una nueva.",
-    "currentPassword": "Contrasena actual",
-    "newPassword": "Nueva contrasena",
-    "confirmNewPassword": "Confirmar nueva contrasena",
-    "changePassword": "Cambiar contrasena",
-    "passwordChanged": "Contrasena cambiada",
-    "passwordsNoMatch": "Las contrasenas no coinciden",
-    "passwordMinLength": "La contrasena debe tener al menos 8 caracteres",
-    "failedChangePassword": "Error al cambiar la contrasena",
+    "failedUpdateEmail": "Error al actualizar el correo electrónico",
+    "changePasswordTitle": "Cambiar contraseña",
+    "changePasswordDescription": "Ingrese su contraseña actual y elija una nueva.",
+    "currentPassword": "Contraseña actual",
+    "newPassword": "Nueva contraseña",
+    "confirmNewPassword": "Confirmar nueva contraseña",
+    "changePassword": "Cambiar contraseña",
+    "passwordChanged": "Contraseña cambiada",
+    "passwordsNoMatch": "Las contraseñas no coinciden",
+    "passwordMinLength": "La contraseña debe tener al menos 8 caracteres",
+    "failedChangePassword": "Error al cambiar la contraseña",
     "twoFactorTitle": "Autenticacion de dos factores",
     "twoFactorEnabled": "La autenticacion de dos factores esta habilitada",
-    "twoFactorDisablePrompt": "Ingrese su contrasena para deshabilitar la autenticacion de dos factores.",
-    "twoFactorEnablePrompt": "Agregue una capa adicional de seguridad a su cuenta habilitando la autenticacion de dos factores con una aplicacion de autenticacion.",
-    "password": "Contrasena",
-    "enterPassword": "Ingrese su contrasena",
+    "twoFactorDisablePrompt": "Ingrese su contraseña para deshabilitar la autenticacion de dos factores.",
+    "twoFactorEnablePrompt": "Agregue una capa adicional de seguridad a su cuenta habilitando la autenticacion de dos factores con una aplicación de autenticacion.",
+    "password": "Contraseña",
+    "enterPassword": "Ingrese su contraseña",
     "disable2FA": "Deshabilitar 2FA",
     "enable2FA": "Habilitar 2FA",
     "twoFactorDisabled": "Autenticacion de dos factores deshabilitada",
     "failedEnable2FA": "Error al habilitar 2FA",
     "failedDisable2FA": "Error al deshabilitar 2FA",
-    "enterPasswordError": "Por favor ingrese su contrasena",
+    "enterPasswordError": "Por favor ingrese su contraseña",
     "setupDialogTitle": "Habilitar autenticacion de dos factores",
-    "setupDialogDescription": "Ingrese su contrasena para comenzar a configurar 2FA con una aplicacion de autenticacion.",
+    "setupDialogDescription": "Ingrese su contraseña para comenzar a configurar 2FA con una aplicación de autenticacion.",
     "continue": "Continuar",
-    "scanQRTitle": "Escanear codigo QR",
-    "scanQRDescription": "Escanee este codigo QR con su aplicacion de autenticacion (Google Authenticator, Authy, etc.)",
-    "verifyCodeTitle": "Verificar codigo",
-    "verifyCodeDescription": "Ingrese el codigo de 6 digitos de su aplicacion de autenticacion para verificar que esta configurada correctamente.",
-    "authCode": "Codigo de autenticacion",
+    "scanQRTitle": "Escanear código QR",
+    "scanQRDescription": "Escanee este código QR con su aplicación de autenticacion (Google Authenticator, Authy, etc.)",
+    "verifyCodeTitle": "Verificar código",
+    "verifyCodeDescription": "Ingrese el código de 6 dígitos de su aplicación de autenticacion para verificar que esta configurada correctamente.",
+    "authCode": "Código de autenticacion",
     "authCodePlaceholder": "000000",
-    "back": "Atras",
+    "back": "Atrás",
     "verify": "Verificar",
-    "enterCodeError": "Por favor ingrese el codigo de su aplicacion de autenticacion",
-    "invalidCode": "Codigo invalido. Intente nuevamente.",
-    "verificationFailed": "Verificacion fallida",
+    "enterCodeError": "Por favor ingrese el código de su aplicación de autenticacion",
+    "invalidCode": "Código invalido. Intente nuevamente.",
+    "verificationFailed": "Verificación fallida",
     "backupCodesTitle": "Guardar codigos de respaldo",
-    "backupCodesDescription": "Guarde estos codigos en un lugar seguro. Puede usarlos para iniciar sesion si pierde acceso a su aplicacion de autenticacion. Cada codigo solo puede usarse una vez.",
+    "backupCodesDescription": "Guarde estos codigos en un lugar seguro. Puede usarlos para iniciar sesión si pierde acceso a su aplicación de autenticacion. Cada código solo puede usarse una vez.",
     "copyCodes": "Copiar codigos",
     "copied": "Copiado",
     "backupCodesCopied": "Codigos de respaldo copiados al portapapeles",
@@ -229,28 +229,28 @@
     "twoFactorEnabledSuccess": "Autenticacion de dos factores habilitada",
     "dangerZone": "Zona de peligro",
     "deleteContentTitle": "Eliminar contenido",
-    "deleteContentDescription": "Elimine selectivamente datos de la organizacion como vehiculos, clientes, cotizaciones o inventario. Su cuenta y configuracion permaneceran intactas.",
+    "deleteContentDescription": "Elimine selectivamente datos de la organización como vehículos, clientes, cotizaciones o inventario. Su cuenta y configuración permaneceran intactas.",
     "deleteContentButton": "Eliminar contenido",
     "deleteAccountTitle": "Eliminar cuenta",
-    "deleteAccountDescription": "Elimine permanentemente su cuenta y todos los datos asociados incluyendo vehiculos, registros de servicio, ordenes de trabajo, facturas, clientes, archivos y pagos. Esta accion no se puede deshacer.",
+    "deleteAccountDescription": "Elimine permanentemente su cuenta y todos los datos asociados incluyendo vehículos, registros de servicio, órdenes de trabajo, facturas, clientes, archivos y pagos. Esta acción no se puede deshacer.",
     "deleteAccountButton": "Eliminar cuenta",
     "deleteAccountDialogTitle": "Eliminar cuenta",
-    "deleteAccountDialogDescription": "Esto eliminara permanentemente su cuenta y todos los datos asociados. Esta accion es irreversible. Todos sus vehiculos, registros de servicio, ordenes de trabajo, cotizaciones, clientes, archivos e historial de pagos seran eliminados permanentemente.",
+    "deleteAccountDialogDescription": "Esto eliminará permanentemente su cuenta y todos los datos asociados. Esta acción es irreversible. Todos sus vehículos, registros de servicio, órdenes de trabajo, cotizaciones, clientes, archivos e historial de pagos seran eliminados permanentemente.",
     "deleteAccountConfirmPhrase": "delete me",
     "deleteAccountConfirmPrompt": "Escriba <bold>delete me</bold> para confirmar",
     "permanentlyDelete": "Eliminar permanentemente",
     "deleting": "Eliminando...",
     "failedDeleteAccount": "Error al eliminar la cuenta",
     "deleteContentDialogTitle": "Eliminar contenido",
-    "deleteContentDialogDescription": "Seleccione el contenido que desea eliminar permanentemente. Esta accion no se puede deshacer.",
-    "contentVehicles": "Vehiculos",
-    "contentVehiclesDescription": "Todos los vehiculos y sus registros de servicio, notas, recordatorios, registros de combustible y facturas recurrentes",
+    "deleteContentDialogDescription": "Seleccione el contenido que desea eliminar permanentemente. Esta acción no se puede deshacer.",
+    "contentVehicles": "Vehículos",
+    "contentVehiclesDescription": "Todos los vehículos y sus registros de servicio, notas, recordatorios, registros de combustible y facturas recurrentes",
     "contentCustomers": "Clientes",
-    "contentCustomersDescription": "Todos los registros de clientes. Los vehiculos vinculados a clientes eliminados seran desvinculados.",
+    "contentCustomersDescription": "Todos los registros de clientes. Los vehículos vinculados a clientes eliminados seran desvinculados.",
     "contentQuotes": "Cotizaciones",
     "contentQuotesDescription": "Todas las cotizaciones y sus partidas",
     "contentInventory": "Inventario",
-    "contentInventoryDescription": "Todas las piezas de inventario y sus imagenes",
+    "contentInventoryDescription": "Todas las piezas de inventario y sus imágenes",
     "contentInspections": "Inspecciones",
     "contentInspectionsDescription": "Todas las inspecciones de vehículos y sus resultados",
     "contentTechnicians": "Técnicos",
@@ -306,43 +306,43 @@
   "team": {
     "title": "Equipo",
     "noOrgDescription": "Cree un equipo para compartir datos con otros usuarios.",
-    "orgDescription": "Administre los miembros de su organizacion y sus roles.",
-    "createOrg": "Crear organizacion",
-    "createOrgDescription": "Cree una organizacion para invitar miembros del equipo. Usted sera el propietario.",
-    "orgName": "Nombre de la organizacion",
+    "orgDescription": "Administre los miembros de su organización y sus roles.",
+    "createOrg": "Crear organización",
+    "createOrgDescription": "Cree una organización para invitar miembros del equipo. Usted sera el propietario.",
+    "orgName": "Nombre de la organización",
     "orgNamePlaceholder": "Mi taller",
     "create": "Crear",
-    "orgCreated": "Organizacion creada",
-    "failedCreateOrg": "Error al crear la organizacion",
+    "orgCreated": "Organización creada",
+    "failedCreateOrg": "Error al crear la organización",
     "memberCount": "{count} miembro",
     "memberCountPlural": "{count} miembros",
     "admin": "Administrador",
     "member": "Miembro",
-    "inviteByEmail": "Invitar por correo electronico",
+    "inviteByEmail": "Invitar por correo electrónico",
     "inviteEmailPlaceholder": "usuario@ejemplo.com",
     "invite": "Invitar",
     "invited": "Miembro invitado exitosamente.",
     "userNotFoundTitle": "Usuario no encontrado",
-    "userNotFoundDescription": "No se encontro cuenta para \"{email}\". Enviar un correo de invitacion para registrarse y unirse a su equipo?",
-    "sendInvitation": "Enviar invitacion",
-    "invitationSentTitle": "Invitacion enviada",
-    "invitationSentDescription": "Se ha enviado un correo de invitacion a {email}.",
+    "userNotFoundDescription": "No se encontró cuenta para \"{email}\". Enviar un correo de invitación para registrarse y unirse a su equipo?",
+    "sendInvitation": "Enviar invitación",
+    "invitationSentTitle": "Invitación enviada",
+    "invitationSentDescription": "Se ha enviado un correo de invitación a {email}.",
     "failedInvite": "Error al invitar al miembro",
-    "failedSendInvitation": "Error al enviar la invitacion",
+    "failedSendInvitation": "Error al enviar la invitación",
     "pendingInvitations": "Invitaciones pendientes",
     "invitedAs": "Invitado como {role}",
     "expires": "Expira {date}",
     "pending": "Pendiente",
-    "copyInviteLink": "Copiar enlace de invitacion",
-    "inviteLinkCopied": "Enlace de invitacion copiado al portapapeles",
-    "cancelInvitation": "Cancelar invitacion",
-    "cancelInvitationDescription": "Cancelar la invitacion a {email}? Ya no podran usar este enlace para unirse.",
-    "invitationCancelled": "Invitacion cancelada",
-    "failedCancelInvitation": "Error al cancelar la invitacion",
+    "copyInviteLink": "Copiar enlace de invitación",
+    "inviteLinkCopied": "Enlace de invitación copiado al portapapeles",
+    "cancelInvitation": "Cancelar invitación",
+    "cancelInvitationDescription": "Cancelar la invitación a {email}? Ya no podran usar este enlace para unirse.",
+    "invitationCancelled": "Invitación cancelada",
+    "failedCancelInvitation": "Error al cancelar la invitación",
     "customRoles": "Roles personalizados",
     "newRole": "Nuevo rol",
     "roleName": "Nombre del rol",
-    "roleNamePlaceholder": "Ej. Tecnico",
+    "roleNamePlaceholder": "Ej. Técnico",
     "fullAdminAccess": "Acceso completo de administrador (omite todas las verificaciones de permisos)",
     "permissions": "Permisos",
     "createRole": "Crear rol",
@@ -351,7 +351,7 @@
     "roleUpdated": "Rol actualizado",
     "failedSaveRole": "Error al guardar el rol",
     "deleteRole": "Eliminar rol",
-    "deleteRoleDescription": "Eliminar el rol \"{name}\"? Los miembros con este rol perderan sus permisos personalizados.",
+    "deleteRoleDescription": "Eliminar el rol \"{name}\"? Los miembros con este rol perderán sus permisos personalizados.",
     "roleDeleted": "Rol eliminado",
     "failedDeleteRole": "Error al eliminar el rol",
     "fullAccess": "Acceso completo",
@@ -364,7 +364,7 @@
     "presetNone": "Ninguno",
     "builtInRoles": "Roles integrados",
     "ownerLabel": "Propietario",
-    "ownerDescription": "Acceso completo. Puede gestionar miembros, cambiar roles y eliminar la organizacion.",
+    "ownerDescription": "Acceso completo. Puede gestionar miembros, cambiar roles y eliminar la organización.",
     "adminLabel": "Administrador",
     "adminDescription": "Puede invitar/eliminar miembros y gestionar datos compartidos.",
     "memberLabel": "Miembro",
@@ -383,26 +383,26 @@
     "editRole": "Edit role"
   },
   "invoice": {
-    "title": "Diseno de factura",
+    "title": "Diseño de factura",
     "description": "Configure lo que aparece en las facturas PDF generadas.",
     "tabs": {
       "general": "General",
       "layout": "Diseño",
       "customFields": "Campos personalizados"
     },
-    "invoiceNumberFormat": "Formato de numero de factura",
+    "invoiceNumberFormat": "Formato de número de factura",
     "invoiceNumberFormatHint": "Use <code>{year}</code> para el ano actual. Vista previa: <bold>{preview}</bold>",
-    "quoteNumberFormat": "Formato de numero de presupuesto",
+    "quoteNumberFormat": "Formato de número de presupuesto",
     "quoteNumberFormatHint": "Use <code>{year}</code> para el ano actual. Vista previa: <bold>{preview}</bold>",
     "quoteValidDays": "Validez del presupuesto (días)",
     "quoteValidDaysHint": "Los nuevos presupuestos reciben una fecha de validez con esta cantidad de días. 0 lo desactiva.",
     "sectionInvoices": "Facturas",
     "sectionQuotes": "Presupuestos",
-    "nextInvoiceNumber": "Siguiente numero de factura",
+    "nextInvoiceNumber": "Siguiente número de factura",
     "nextInvoiceNumberPlaceholder": "Ej. 94",
-    "nextInvoiceNumberHint": "La siguiente factura usara este numero (ej. {example})",
-    "dueDays": "Dias de vencimiento",
-    "dueDaysHint": "Numero de dias hasta el vencimiento de la factura",
+    "nextInvoiceNumberHint": "La siguiente factura usara este número (ej. {example})",
+    "dueDays": "Días de vencimiento",
+    "dueDaysHint": "Número de días hasta el vencimiento de la factura",
     "visibilityTitle": "Visibilidad en la factura",
     "companyLogo": "Logo de la empresa",
     "companyLogoHint": "Mostrar logo de la empresa en el encabezado de la factura",
@@ -410,9 +410,9 @@
     "companyNameHint": "Mostrar texto del nombre de la empresa en el encabezado",
     "bankAccount": "Cuenta bancaria",
     "bankAccountHint": "Mostrar cuenta bancaria / IBAN en la factura",
-    "organizationNumber": "Numero de organizacion",
-    "organizationNumberHint": "Mostrar numero de organizacion en el encabezado",
-    "customFooter": "Pie de pagina personalizado",
+    "organizationNumber": "Número de organización",
+    "organizationNumberHint": "Mostrar número de organización en el encabezado",
+    "customFooter": "Pie de página personalizado",
     "footerPlaceholder": "Gracias por su preferencia!",
     "footerHint": "Este texto aparece al final de cada factura",
     "partsMarkupTitle": "Margen de Repuestos",
@@ -421,8 +421,8 @@
     "defaultMarkupPercentHint": "Los repuestos nuevos agregados manualmente se prellenan con este margen. Déjelo en 0 para ingresar precios manualmente.",
     "markupAppliesToInventory": "Aplicar margen a repuestos de inventario",
     "markupAppliesToInventoryHint": "Cuando está habilitado, los repuestos seleccionados del inventario también usan costo + margen predeterminado (anula el precio de venta del inventario).",
-    "saveInvoice": "Guardar configuracion de factura",
-    "saved": "Configuracion de factura guardada",
+    "saveInvoice": "Guardar configuración de factura",
+    "saved": "Configuración de factura guardada",
     "layoutDescription": "Personaliza el orden y la visibilidad de las secciones en tus facturas y presupuestos.",
     "layoutSaved": "Diseño guardado correctamente",
     "preview": "Vista previa",
@@ -492,18 +492,18 @@
   "templates": {
     "title": "Plantillas",
     "invoiceDescription": "Personalice la apariencia de sus facturas y cotizaciones PDF.",
-    "inspectionsDescription": "Administre listas de verificacion de inspecciones y plantillas multipunto.",
+    "inspectionsDescription": "Administre listas de verificación de inspecciones y plantillas multipunto.",
     "smsDescription": "Personalice los mensajes SMS enviados a los clientes.",
     "tabs": {
       "invoice": "Factura",
-      "quotation": "Cotizacion",
+      "quotation": "Cotización",
       "inspections": "Inspecciones",
       "sms": "SMS",
       "layout": "Disposicion"
     },
     "presets": "Presets de plantilla",
     "primaryColor": "Color primario",
-    "fontAndLayout": "Fuente y diseno",
+    "fontAndLayout": "Fuente y diseño",
     "fontFamily": "Familia tipografica",
     "helveticaDefault": "Helvetica (Predeterminado)",
     "timesRoman": "Times Roman",
@@ -516,7 +516,7 @@
     "preview": "Vista previa de {name}",
     "billTo": "Facturar a",
     "preparedFor": "Preparado para",
-    "vehicle": "Vehiculo",
+    "vehicle": "Vehículo",
     "part": "Pieza",
     "qty": "Cant.",
     "price": "Precio",
@@ -533,19 +533,19 @@
     "previewAdditionalInfo": "Información adicional",
     "previewThankYou": "Gracias por su confianza",
     "saveInvoiceTemplate": "Guardar plantilla de factura",
-    "saveQuotationTemplate": "Guardar plantilla de cotizacion",
-    "invoiceTemplateSaved": "Configuracion de plantilla de factura guardada",
-    "quotationTemplateSaved": "Configuracion de plantilla de cotizacion guardada",
-    "failedSave": "Error al guardar la configuracion",
+    "saveQuotationTemplate": "Guardar plantilla de cotización",
+    "invoiceTemplateSaved": "Configuración de plantilla de factura guardada",
+    "quotationTemplateSaved": "Configuración de plantilla de cotización guardada",
+    "failedSave": "Error al guardar la configuración",
     "smsTemplates": "Plantillas de mensajes SMS",
     "smsTemplatesDescription": "Personalice los mensajes de texto enviados a los clientes. Use variables como <code>'{companyName}'</code> para insertar valores dinamicos.",
     "reset": "Restablecer",
     "invoiceReady": "Factura lista",
     "invoiceReadyDescription": "Se envia al compartir una factura con un cliente.",
-    "quoteReady": "Cotizacion lista",
-    "quoteReadyDescription": "Se envia al compartir una cotizacion con un cliente.",
-    "inspectionReady": "Inspeccion lista",
-    "inspectionReadyDescription": "Se envia al compartir un informe de inspeccion con un cliente.",
+    "quoteReady": "Cotización lista",
+    "quoteReadyDescription": "Se envia al compartir una cotización con un cliente.",
+    "inspectionReady": "Inspección lista",
+    "inspectionReadyDescription": "Se envia al compartir un informe de inspección con un cliente.",
     "statusInProgress": "Estado: En progreso",
     "statusInProgressDescription": "Se envia cuando el estado de una orden de trabajo cambia a En progreso.",
     "statusWaitingParts": "Estado: Esperando piezas",
@@ -557,7 +557,7 @@
     "paymentReceived": "Pago recibido",
     "paymentReceivedDescription": "Se envia cuando se registra un pago para una factura.",
     "saveSmsTemplates": "Guardar plantillas SMS",
-    "smsTemplateSaved": "Configuracion de plantillas SMS guardada",
+    "smsTemplateSaved": "Configuración de plantillas SMS guardada",
     "smsDefaults": {
       "invoiceReady": "Su factura está lista. Véala aquí: {share_link}",
       "quoteReady": "Su presupuesto está listo para revisión. Véalo aquí: {share_link}",
@@ -590,12 +590,12 @@
     "detailsDescription": "Cuenta bancaria y condiciones de pago en las facturas.",
     "bankAccount": "Cuenta bancaria (IBAN)",
     "bankAccountPlaceholder": "NO93 8601 1117 947",
-    "bankAccountHint": "Su numero de cuenta bancaria o IBAN para recibir pagos",
+    "bankAccountHint": "Su número de cuenta bancaria o IBAN para recibir pagos",
     "paymentTerms": "Condiciones de pago",
-    "paymentTermsPlaceholder": "Neto 14 dias",
-    "paymentTermsHint": "Texto de condiciones de pago en las facturas (ej. \"Neto 14 dias\", \"Pago al recibir\")",
-    "onlinePayments": "Pagos en linea",
-    "onlinePaymentsDescription": "Permita a los clientes pagar facturas en linea a traves de enlaces de factura compartidos.",
+    "paymentTermsPlaceholder": "Neto 14 días",
+    "paymentTermsHint": "Texto de condiciones de pago en las facturas (ej. \"Neto 14 días\", \"Pago al recibir\")",
+    "onlinePayments": "Pagos en línea",
+    "onlinePaymentsDescription": "Permita a los clientes pagar facturas en línea a través de enlaces de factura compartidos.",
     "stripe": "Stripe",
     "stripeDescription": "Aceptar pagos con tarjeta de credito/debito",
     "secretKey": "Clave secreta",
@@ -607,26 +607,26 @@
     "vippsDescription": "Aceptar pagos moviles Vipps (Noruega)",
     "clientId": "ID de cliente",
     "clientSecret": "Secreto del cliente",
-    "subscriptionKey": "Clave de suscripcion",
-    "merchantSerialNumber": "Numero de serie del comerciante",
+    "subscriptionKey": "Clave de suscripción",
+    "merchantSerialNumber": "Número de serie del comerciante",
     "testMode": "Modo de prueba",
     "callbackUrl": "URL de callback",
-    "termsOfSale": "Condiciones de venta (Salgsvilkar)",
-    "termsOfSaleDescription": "Requerido por Vipps. Los clientes pueden verlas desde la pagina de factura.",
+    "termsOfSale": "Condiciones de venta (Salgsvilkår)",
+    "termsOfSaleDescription": "Requerido por Vipps. Los clientes pueden verlas desde la página de factura.",
     "externalTermsUrl": "URL de condiciones externas (opcional)",
-    "externalTermsUrlHint": "Enlace a una pagina externa con sus condiciones. Si se establece, la factura enlazara aqui.",
+    "externalTermsUrlHint": "Enlace a una página externa con sus condiciones. Si se establece, la factura enlazara aquí.",
     "publicTermsUrl": "URL publica de condiciones",
     "publicTermsUrlHint": "Comparta esta URL con Vipps o enlacela desde su sitio web.",
     "paypal": "PayPal",
     "paypalDescription": "Aceptar pagos PayPal",
     "sandboxMode": "Modo sandbox",
     "paypalWebhookHint": "Agregue esta URL en su Panel de Desarrollador de PayPal en Webhooks. Suscribase a <code>PAYMENT.CAPTURE.COMPLETED</code>.",
-    "savePayment": "Guardar configuracion de pago",
-    "saved": "Configuracion de pago guardada"
+    "savePayment": "Guardar configuración de pago",
+    "saved": "Configuración de pago guardada"
   },
   "tax": {
     "title": "Impuesto",
-    "description": "Configure la tasa de impuesto aplicada a facturas, presupuestos y ordenes de trabajo.",
+    "description": "Configure la tasa de impuesto aplicada a facturas, presupuestos y órdenes de trabajo.",
     "modeLabel": "Modo de impuesto",
     "modeHint": "Elija si los precios de las lineas se introducen con impuesto incluido (inclusivo) o antes de impuestos (exclusivo). Los registros existentes mantienen el modo con el que fueron creados.",
     "modeExclusive": "Exclusivo",
@@ -665,18 +665,18 @@
     "enableTax": "Habilitar impuestos",
     "enableTaxHint": "Mostrar calculo de impuestos en facturas y registros de servicio",
     "defaultTaxRate": "Tasa de impuesto predeterminada (%)",
-    "defaultTaxRateHint": "Se completa automaticamente al crear nuevos registros de servicio",
+    "defaultTaxRateHint": "Se completa automáticamente al crear nuevos registros de servicio",
     "applyTaxToExistingLabel": "Aplicar impuesto a registros existentes",
-    "applyTaxToExistingHint": "Aplica la tasa de impuesto predeterminada a ordenes de trabajo y presupuestos existentes que no tienen tasa configurada. Elegibles: {serviceRecords} ordenes de trabajo, {quotes} presupuestos.",
+    "applyTaxToExistingHint": "Aplica la tasa de impuesto predeterminada a órdenes de trabajo y presupuestos existentes que no tienen tasa configurada. Elegibles: {serviceRecords} órdenes de trabajo, {quotes} presupuestos.",
     "applyTaxToExistingButton": "Aplicar impuesto a registros existentes",
     "applyTaxConfirmTitle": "¿Aplicar impuesto a registros existentes?",
-    "applyTaxConfirmDescription": "Esto establecera la tasa de impuesto en {rate}% en {serviceRecords} ordenes de trabajo y {quotes} presupuestos que actualmente no tienen tasa, y recalculara sus totales. Los registros con una tasa personalizada no se modificaran. Esta accion no se puede deshacer.",
+    "applyTaxConfirmDescription": "Esto establecerá la tasa de impuesto en {rate}% en {serviceRecords} órdenes de trabajo y {quotes} presupuestos que actualmente no tienen tasa, y recalculara sus totales. Los registros con una tasa personalizada no se modificaran. Esta acción no se puede deshacer.",
     "applyTaxConfirmLabel": "Aplicar impuesto",
-    "applyTaxSuccess": "Se actualizaron {serviceRecords} ordenes de trabajo y {quotes} presupuestos.",
+    "applyTaxSuccess": "Se actualizaron {serviceRecords} órdenes de trabajo y {quotes} presupuestos.",
     "applyTaxFailed": "No se pudo aplicar el impuesto a los registros existentes",
-    "applyTaxNoRecords": "Ningun registro necesita la tasa de impuesto predeterminada.",
-    "saveSettings": "Guardar configuracion",
-    "saved": "Configuracion guardada",
+    "applyTaxNoRecords": "Ningún registro necesita la tasa de impuesto predeterminada.",
+    "saveSettings": "Guardar configuración",
+    "saved": "Configuración guardada",
     "currencies": {
       "USD": "Dolar estadounidense",
       "EUR": "Euro",
@@ -736,9 +736,9 @@
     },
     "formatTitle": "Formato de moneda",
     "formatSymbolName": "Simbolo",
-    "formatCodeName": "Codigo",
+    "formatCodeName": "Código",
     "formatSymbolLabel": "Simbolo: {value}",
-    "formatCodeLabel": "Codigo: {value}"
+    "formatCodeLabel": "Código: {value}"
   },
   "localization": {
     "languageTitle": "Idioma",
@@ -767,7 +767,7 @@
     "label": "Etiqueta",
     "labelPlaceholder": "ej. Vencimiento de garantía",
     "fieldName": "Nombre del campo (ID)",
-    "fieldNamePlaceholder": "vencimiento_garantia",
+    "fieldNamePlaceholder": "vencimiento_garantía",
     "fieldNameHint": "Solo letras minúsculas, números y guiones bajos.",
     "fieldType": "Tipo de campo",
     "entityType": "Tipo de entidad",
@@ -798,7 +798,7 @@
   "workshop": {
     "title": "Valores predeterminados del taller",
     "description": "Valores predeterminados al crear nuevos registros de servicio.",
-    "defaultTechnician": "Nombre del tecnico predeterminado",
+    "defaultTechnician": "Nombre del técnico predeterminado",
     "technicianPlaceholder": "Buscar técnico...",
     "failedCreateTech": "Error al crear técnico",
     "creating": "Creando...",
@@ -819,8 +819,8 @@
     "liters": "litros (L)",
     "miles": "millas (mi)",
     "gallons": "galones (gal)",
-    "saveWorkshop": "Guardar configuracion del taller",
-    "saved": "Configuracion del taller guardada",
+    "saveWorkshop": "Guardar configuración del taller",
+    "saved": "Configuración del taller guardada",
     "assignDialogTitle": "¿Asignar a órdenes de trabajo existentes?",
     "assignDialogDescription": "¿Desea asignar {name} a todas las órdenes de trabajo que no tienen un técnico asignado?",
     "assignDialogConfirm": "Sí, asignar",
@@ -835,23 +835,23 @@
       "0": "Domingo",
       "1": "Lunes",
       "2": "Martes",
-      "3": "Miercoles",
+      "3": "Miércoles",
       "4": "Jueves",
       "5": "Viernes",
-      "6": "Sabado"
+      "6": "Sábado"
     }
   },
   "maintenance": {
     "title": "Mantenimiento predictivo",
-    "description": "Prediga el kilometraje actual de cada vehiculo a partir de su historial de servicio y muestre los vehiculos que necesitan o se acercan a su proximo servicio.",
+    "description": "Prediga el kilometraje actual de cada vehículo a partir de su historial de servicio y muestre los vehículos que necesitan o se acercan a su próximo servicio.",
     "enablePredicted": "Habilitar mantenimiento predictivo",
-    "enablePredictedHint": "Mostrar predicciones en detalles del vehiculo y panel",
+    "enablePredictedHint": "Mostrar predicciones en detalles del vehículo y panel",
     "serviceInterval": "Intervalo de servicio ({unit})",
     "serviceIntervalHint": "Distancia entre servicios recomendados",
     "approachingThreshold": "Umbral de proximidad ({unit})",
-    "approachingThresholdHint": "Mostrar advertencia cuando este dentro de esta distancia del proximo servicio",
-    "saveMaintenance": "Guardar configuracion de mantenimiento",
-    "saved": "Configuracion de mantenimiento guardada"
+    "approachingThresholdHint": "Mostrar advertencia cuando este dentro de esta distancia del próximo servicio",
+    "saveMaintenance": "Guardar configuración de mantenimiento",
+    "saved": "Configuración de mantenimiento guardada"
   },
   "appearance": {
     "title": "Apariencia",
@@ -859,36 +859,36 @@
     "darkMode": "Modo oscuro",
     "darkModeHint": "Cambiar entre temas claro y oscuro",
     "dateTimeTitle": "Fecha y hora",
-    "dateTimeDescription": "Configure como se muestran las fechas y horas en la aplicacion.",
+    "dateTimeDescription": "Configure como se muestran las fechas y horas en la aplicación.",
     "dateFormat": "Formato de fecha",
     "timeFormat": "Formato de hora",
     "time12h": "12 horas (2:30 PM)",
     "time24h": "24 horas (14:30)",
     "timezone": "Zona horaria",
-    "timezoneAuto": "Detectar automaticamente (navegador)",
+    "timezoneAuto": "Detectar automáticamente (navegador)",
     "searchTimezone": "Buscar zona horaria...",
     "noTimezoneFound": "No se encontró zona horaria.",
     "timezoneHint": "Con deteccion automatica, cada usuario ve las fechas en la zona horaria de su navegador.",
     "previewLabel": "Vista previa",
     "dateLabel": "Fecha",
     "dateTimeLabel": "Fecha y hora",
-    "saveSettings": "Guardar configuracion",
-    "saved": "Configuracion guardada"
+    "saveSettings": "Guardar configuración",
+    "saved": "Configuración guardada"
   },
   "data": {
-    "title": "Gestion de datos",
+    "title": "Gestión de datos",
     "description": "Exporte sus datos como respaldo o importe un archivo previamente exportado.",
     "exportTitle": "Exportar datos",
-    "exportDescription": "Descargue sus datos como archivo zip. Seleccione las categorias a incluir en el respaldo.",
+    "exportDescription": "Descargue sus datos como archivo zip. Seleccione las categorías a incluir en el respaldo.",
     "includeInBackup": "Incluir en el respaldo:",
     "selectAll": "Seleccionar todo",
     "deselectAll": "Deseleccionar todo",
-    "optSettings": "Configuracion",
-    "optSettingsDesc": "Info del taller, configuracion de facturas, proveedores de pago",
+    "optSettings": "Configuración",
+    "optSettingsDesc": "Info del taller, configuración de facturas, proveedores de pago",
     "optCustomers": "Clientes",
-    "optCustomersDesc": "Registros de clientes e informacion de contacto",
-    "optVehicles": "Vehiculos y registros de servicio",
-    "optVehiclesDesc": "Vehiculos, notas, registros de combustible, recordatorios, registros de servicio",
+    "optCustomersDesc": "Registros de clientes e información de contacto",
+    "optVehicles": "Vehículos y registros de servicio",
+    "optVehiclesDesc": "Vehículos, notas, registros de combustible, recordatorios, registros de servicio",
     "optQuotes": "Cotizaciones",
     "optQuotesDesc": "Cotizaciones con piezas y mano de obra",
     "optInventory": "Inventario",
@@ -908,7 +908,7 @@
     "optCustomFields": "Campos personalizados",
     "optCustomFieldsDesc": "Definiciones y valores de campos personalizados",
     "optFiles": "Archivos subidos",
-    "optFilesDesc": "Imagenes, adjuntos de servicio, logo",
+    "optFilesDesc": "Imágenes, adjuntos de servicio, logo",
     "filesWarning": "Incluye archivos subidos — el respaldo puede ser mas grande",
     "downloadBackup": "Descargar respaldo",
     "exportComplete": "Exportacion completada",
@@ -916,12 +916,12 @@
     "exportFailed": "Exportacion fallida",
     "exportFailedMessage": "No se pudieron exportar sus datos. Intente nuevamente.",
     "importTitle": "Importar datos",
-    "importWarning": "La importacion <bold>reemplazara todos los datos existentes</bold> con el contenido del archivo de respaldo. Esta accion no se puede deshacer.",
+    "importWarning": "La importación <bold>reemplazara todos los datos existentes</bold> con el contenido del archivo de respaldo. Esta acción no se puede deshacer.",
     "importFileHint": "Soporta archivos de respaldo .zip (v2) y .json (v1).",
     "uploadRestore": "Subir y restaurar",
-    "importComplete": "Importacion completada",
+    "importComplete": "Importación completada",
     "importCompleteMessage": "Todos los datos han sido restaurados del archivo de respaldo.",
-    "importFailed": "Importacion fallida",
+    "importFailed": "Importación fallida",
     "importFromOther": "Importar de otros servicios",
     "importFromOtherDescription": "Migre sus datos desde otra plataforma. Seleccione un servicio para comenzar.",
     "lubelogTitle": "Importar de LubeLog",
@@ -932,11 +932,11 @@
     "lubelogStep4": "Suba el archivo zip descargado abajo",
     "lubelogAddNote": "Esto <bold>agregara datos junto a</bold> sus registros existentes — nada sera eliminado.",
     "lubelogImported": "Que se importara:",
-    "lubelogVehicles": "Vehiculos",
+    "lubelogVehicles": "Vehículos",
     "lubelogServiceRecords": "Registros de servicio con adjuntos",
     "lubelogNotes": "Notas",
     "lubelogFileHint": "Archivos hasta 200MB soportados.",
-    "lubelogImportComplete": "Importacion de LubeLog completada",
+    "lubelogImportComplete": "Importación de LubeLog completada",
     "cancel": "Cancelar",
     "import": "Importar",
     "invoiceNinjaTitle": "Importar de Invoice Ninja",
@@ -953,7 +953,7 @@
     "invoiceNinjaDocuments": "Documentos y adjuntos",
     "invoiceNinjaPayments": "Pagos",
     "invoiceNinjaFileHint": "Suba un archivo de exportacion zip de Invoice Ninja. Archivos hasta 200MB soportados.",
-    "invoiceNinjaImportComplete": "Importacion de Invoice Ninja completada",
+    "invoiceNinjaImportComplete": "Importación de Invoice Ninja completada",
     "backupStatus": {
       "title": "Protección de datos",
       "description": "Nos tomamos sus datos en serio. Todos los datos de su organización, incluidos clientes, vehículos, órdenes de trabajo, facturas y archivos subidos, se respaldan automáticamente cada hora. Las copias de seguridad se protegen con cifrado AES-256 antes de salir de nuestra infraestructura y se almacenan en una ubicación separada y segura, de modo que ningún tercero puede leerlas.",
@@ -964,22 +964,22 @@
   },
   "license": {
     "title": "Licencia marca blanca",
-    "description": "Active una licencia de marca blanca para eliminar la marca Torqvoice de las facturas y la aplicacion.",
+    "description": "Active una licencia de marca blanca para eliminar la marca Torqvoice de las facturas y la aplicación.",
     "purchaseWhiteLabel": "Comprar marca blanca",
     "status": "Estado:",
     "active": "Activa",
     "inactive": "Inactiva",
-    "lastChecked": "Ultima verificacion: {date}",
+    "lastChecked": "Última verificación: {date}",
     "enterLicenseKey": "Ingrese clave de licencia",
     "validate": "Validar",
     "validated": "Licencia validada exitosamente",
     "invalid": "Clave de licencia invalida",
     "failedValidate": "Error al validar la licencia",
-    "keyHint": "Cada clave de licencia esta vinculada a esta organizacion y no puede compartirse entre varias organizaciones."
+    "keyHint": "Cada clave de licencia esta vinculada a esta organización y no puede compartirse entre varias organizaciones."
   },
   "about": {
     "title": "Acerca de Torqvoice",
-    "description": "Torqvoice es su solucion completa de gestion de servicios de vehiculos. Rastree historial de servicio, reparaciones y mas.",
+    "description": "Torqvoice es su solucion completa de gestión de servicios de vehículos. Rastree historial de servicio, reparaciones y mas.",
     "version": "Version",
     "website": "Sitio web",
     "documentation": "Documentacion",
@@ -987,21 +987,21 @@
   },
   "portal": {
     "title": "Portal del cliente",
-    "description": "Permita a sus clientes iniciar sesion y ver sus vehiculos, facturas, cotizaciones e inspecciones. Los clientes tambien pueden enviar solicitudes de servicio.",
+    "description": "Permita a sus clientes iniciar sesión y ver sus vehículos, facturas, cotizaciones e inspecciones. Los clientes también pueden enviar solicitudes de servicio.",
     "enablePortal": "Habilitar portal del cliente",
-    "enablePortalHint": "Los clientes con direccion de correo electronico pueden iniciar sesion con enlace magico",
+    "enablePortalHint": "Los clientes con dirección de correo electrónico pueden iniciar sesión con enlace magico",
     "enabled": "Portal del cliente habilitado",
     "disabled": "Portal del cliente deshabilitado",
-    "failedUpdate": "Error al actualizar la configuracion",
+    "failedUpdate": "Error al actualizar la configuración",
     "customSlug": "Slug personalizado del portal",
     "save": "Guardar",
     "slugSaved": "Slug del portal guardado",
     "slugRemoved": "Slug del portal eliminado",
     "failedUpdateSlug": "Error al actualizar el slug",
-    "slugHint": "Letras minusculas, numeros, guiones y guiones bajos. 3-48 caracteres. Dejar vacio para usar la URL predeterminada.",
+    "slugHint": "Letras minúsculas, números, guiones y guiones bajos. 3-48 caracteres. Dejar vacío para usar la URL predeterminada.",
     "portalUrl": "URL del portal",
     "urlCopied": "URL del portal copiada",
-    "shareHint": "Comparta este enlace con sus clientes para que puedan iniciar sesion en su portal.",
+    "shareHint": "Comparta este enlace con sus clientes para que puedan iniciar sesión en su portal.",
     "serviceRequestAlertsHint": "Las solicitudes de servicio del portal siempre aparecen en tu campana de notificaciones. También puedes recibirlas por correo.",
     "serviceRequestAlertsLink": "Ajustes de avisos →",
     "descriptionLabel": "Sobre tu taller",
@@ -1039,28 +1039,28 @@
   },
   "email": {
     "title": "Proveedor de correo",
-    "description": "Configure el proveedor de correo electronico de su organizacion para enviar facturas y presupuestos. Si no se configura, se utilizara el proveedor de correo predeterminado de la plataforma.",
+    "description": "Configure el proveedor de correo electrónico de su organización para enviar facturas y presupuestos. Si no se configura, se utilizara el proveedor de correo predeterminado de la plataforma.",
     "useCustomLabel": "Usar proveedor de correo personalizado",
     "useCustomHint": "Active para configurar su propio proveedor de correo en lugar de usar el predeterminado de la plataforma",
-    "platformDefaultInfo": "Su organizacion utiliza el proveedor de correo predeterminado de la plataforma. Las facturas y presupuestos se enviaran con la configuracion de correo de la plataforma.",
+    "platformDefaultInfo": "Su organización utiliza el proveedor de correo predeterminado de la plataforma. Las facturas y presupuestos se enviaran con la configuración de correo de la plataforma.",
     "smtpHost": "Host SMTP",
     "smtpHostPlaceholder": "smtp.example.com",
     "smtpPort": "Puerto SMTP",
     "smtpPortPlaceholder": "587",
     "username": "Usuario",
     "usernamePlaceholder": "usuario@example.com",
-    "password": "Contrasena",
+    "password": "Contraseña",
     "fromEmail": "Correo del remitente",
     "fromEmailPlaceholder": "noreply@example.com",
     "fromEmailDomainPlaceholder": "noreply@sudominio.com",
     "fromName": "Nombre del remitente",
     "fromNamePlaceholder": "Su Taller",
-    "tlsConnection": "Conexion TLS (Puerto 465)",
-    "tlsConnectionHint": "Active para TLS implicito. Desactive para STARTTLS (puerto 587/25).",
+    "tlsConnection": "Conexión TLS (Puerto 465)",
+    "tlsConnectionHint": "Active para TLS implícito. Desactive para STARTTLS (puerto 587/25).",
     "verifyTlsCerts": "Verificar certificados TLS",
-    "verifyTlsCertsHint": "Desactive para permitir certificados autofirmados (no recomendado para produccion)",
-    "requireTlsUpgrade": "Requerir actualizacion TLS",
-    "requireTlsUpgradeHint": "Forzar actualizacion TLS en conexiones STARTTLS",
+    "verifyTlsCertsHint": "Desactive para permitir certificados autofirmados (no recomendado para producción)",
+    "requireTlsUpgrade": "Requerir actualización TLS",
+    "requireTlsUpgradeHint": "Forzar actualización TLS en conexiones STARTTLS",
     "apiKey": "Clave API",
     "apiKeyHintResend": "Obtenga su clave API en",
     "serverToken": "Token de servidor",
@@ -1068,31 +1068,31 @@
     "apiKeyHintMailgun": "Obtenga su clave API en",
     "domain": "Dominio",
     "domainPlaceholder": "mg.sudominio.com",
-    "region": "Region",
+    "region": "Región",
     "apiKeyHintSendGrid": "Obtenga su clave API en",
     "accessKeyId": "ID de clave de acceso",
     "secretAccessKey": "Clave de acceso secreta",
-    "awsRegion": "Region AWS",
+    "awsRegion": "Región AWS",
     "awsRegionPlaceholder": "us-east-1",
-    "awsRegionHint": "La region AWS donde esta configurado SES (ej. us-east-1, eu-west-1)",
+    "awsRegionHint": "La región AWS donde esta configurado SES (ej. us-east-1, eu-west-1)",
     "sendTestEmail": "Enviar correo de prueba",
-    "testEmailHint": "Guarde la configuracion primero, luego envie un correo de prueba a su cuenta",
-    "saveSettings": "Guardar configuracion",
-    "savedDefault": "Configuracion de correo guardada — usando predeterminado de la plataforma",
-    "saved": "Configuracion de correo guardada",
-    "failedSave": "Error al guardar la configuracion",
+    "testEmailHint": "Guarde la configuración primero, luego envie un correo de prueba a su cuenta",
+    "saveSettings": "Guardar configuración",
+    "savedDefault": "Configuración de correo guardada — usando predeterminado de la plataforma",
+    "saved": "Configuración de correo guardada",
+    "failedSave": "Error al guardar la configuración",
     "testSentTo": "Correo de prueba enviado a {email}",
     "testFailed": "Prueba de correo fallida"
   },
   "sms": {
     "title": "Proveedor SMS",
-    "description": "Configure su proveedor de SMS para mensajeria bidireccional con clientes. Cada organizacion usa su propio proveedor de SMS y numero de telefono.",
-    "enableLabel": "Activar mensajeria SMS",
+    "description": "Configure su proveedor de SMS para mensajería bidireccional con clientes. Cada organización usa su propio proveedor de SMS y número de teléfono.",
+    "enableLabel": "Activar mensajería SMS",
     "enableHint": "Enviar y recibir mensajes SMS con clientes",
-    "disabledInfo": "La mensajeria SMS esta desactivada. Activela para enviar y recibir mensajes de texto con sus clientes.",
-    "phoneNumber": "Numero de telefono",
+    "disabledInfo": "La mensajería SMS esta desactivada. Activela para enviar y recibir mensajes de texto con sus clientes.",
+    "phoneNumber": "Número de teléfono",
     "phoneNumberPlaceholder": "+34612345678",
-    "phoneNumberHint": "Su numero de telefono habilitado para SMS en formato E.164 (ej. +34612345678)",
+    "phoneNumberHint": "Su número de teléfono habilitado para SMS en formato E.164 (ej. +34612345678)",
     "accountSid": "SID de cuenta",
     "authToken": "Token de autenticacion",
     "apiKey": "Clave API",
@@ -1100,15 +1100,15 @@
     "webhookUrl": "URL de webhook",
     "webhookUrlHint": "Configure esta URL en su panel de {provider} para recibir mensajes SMS entrantes.",
     "webhookCopied": "URL de webhook copiada",
-    "testPhoneNumber": "Numero de telefono de prueba",
+    "testPhoneNumber": "Número de teléfono de prueba",
     "testPhonePlaceholder": "+34698765432",
     "sendTestSms": "Enviar SMS de prueba",
-    "testSmsHint": "Guarde la configuracion primero, luego ingrese un numero de destinatario para enviar un SMS de prueba",
-    "enterTestPhone": "Ingrese un numero de telefono para enviar el SMS de prueba",
-    "saveSettings": "Guardar configuracion",
-    "savedDisabled": "Configuracion SMS guardada — SMS desactivado",
-    "saved": "Configuracion SMS guardada",
-    "failedSave": "Error al guardar la configuracion",
+    "testSmsHint": "Guarde la configuración primero, luego ingrese un número de destinatario para enviar un SMS de prueba",
+    "enterTestPhone": "Ingrese un número de teléfono para enviar el SMS de prueba",
+    "saveSettings": "Guardar configuración",
+    "savedDisabled": "Configuración SMS guardada — SMS desactivado",
+    "saved": "Configuración SMS guardada",
+    "failedSave": "Error al guardar la configuración",
     "testSentTo": "SMS de prueba enviado a {phone}",
     "testFailed": "Prueba de SMS fallida",
     "copyWebhookUrl": "Copy webhook URL"
@@ -1182,23 +1182,23 @@
   "ai": {
     "title": "Asistente de IA",
     "readMore": "Read More",
-    "description": "Configure la IA para generar automaticamente notas de servicio y resumir el historial del vehiculo.",
+    "description": "Configure la IA para generar automáticamente notas de servicio y resumir el historial del vehículo.",
     "enableLabel": "Habilitar IA",
-    "enableHint": "Usar funciones impulsadas por IA en toda la aplicacion",
-    "disabledInfo": "Habilite la IA para usar funciones como descripciones de servicio generadas automaticamente y resumenes del historial del vehiculo.",
+    "enableHint": "Usar funciones impulsadas por IA en toda la aplicación",
+    "disabledInfo": "Habilite la IA para usar funciones como descripciones de servicio generadas automáticamente y resumenes del historial del vehículo.",
     "provider": "Proveedor de IA",
     "apiKey": "Clave API",
     "apiKeyHint": "Su clave API de {provider}. Almacenada de forma segura y nunca compartida.",
     "model": "Modelo",
-    "testConnection": "Probar conexion",
-    "testSuccess": "Conexion de IA exitosa!",
-    "testFailed": "Conexion fallida. Verifique su clave API y modelo.",
-    "saved": "Configuracion de IA guardada",
-    "failedSave": "Error al guardar la configuracion de IA",
-    "saveSettings": "Guardar configuracion",
+    "testConnection": "Probar conexión",
+    "testSuccess": "Conexión de IA exitosa!",
+    "testFailed": "Conexión fallida. Verifique su clave API y modelo.",
+    "saved": "Configuración de IA guardada",
+    "failedSave": "Error al guardar la configuración de IA",
+    "saveSettings": "Guardar configuración",
     "featuresTitle": "Funciones impulsadas por IA incluidas:",
-    "featureServiceNotes": "Generar automaticamente descripciones profesionales de servicio a partir de piezas y mano de obra",
-    "featureHistorySummary": "Resumen del historial de servicio del vehiculo con un clic",
+    "featureServiceNotes": "Generar automáticamente descripciones profesionales de servicio a partir de piezas y mano de obra",
+    "featureHistorySummary": "Resumen del historial de servicio del vehículo con un clic",
     "cost": {
       "high": "Alto costo",
       "medium": "Medio",

+ 52 - 52
messages/es/vehicles.json

@@ -3,13 +3,13 @@
     "active": "Activos",
     "archived": "Archivados",
     "searchPlaceholder": "Buscar por placa, marca, modelo, VIN...",
-    "addVehicle": "Agregar vehiculo",
-    "emptySearch": "Ningun vehiculo coincide con su busqueda.",
-    "emptyArchived": "No hay vehiculos archivados.",
-    "empty": "Aun no hay vehiculos.",
+    "addVehicle": "Agregar vehículo",
+    "emptySearch": "Ningún vehículo coincide con su búsqueda.",
+    "emptyArchived": "No hay vehículos archivados.",
+    "empty": "Aun no hay vehículos.",
     "table": {
       "plate": "Placa",
-      "vehicle": "Vehiculo",
+      "vehicle": "Vehículo",
       "customer": "Cliente",
       "mileage": "Kilometraje",
       "mileageMarine": "Horas de Motor",
@@ -19,28 +19,28 @@
     "archive": "Archivar",
     "unarchive": "Desarchivar",
     "delete": "Eliminar",
-    "deleteTitle": "Eliminar vehiculo",
-    "deleteDescription": "Eliminar {name}? Esto eliminara todos los registros de servicio y notas asociados.",
-    "unarchiveTitle": "Desarchivar vehiculo",
-    "unarchiveDescription": "Restaurar {name} a la lista de vehiculos activos?",
-    "vehicleUnarchived": "Vehiculo desarchivado",
-    "unarchiveError": "Error al desarchivar vehiculo",
-    "deleteError": "Error al eliminar vehiculo",
-    "error": "Error al cargar vehiculos",
+    "deleteTitle": "Eliminar vehículo",
+    "deleteDescription": "Eliminar {name}? Esto eliminará todos los registros de servicio y notas asociados.",
+    "unarchiveTitle": "Desarchivar vehículo",
+    "unarchiveDescription": "Restaurar {name} a la lista de vehículos activos?",
+    "vehicleUnarchived": "Vehículo desarchivado",
+    "unarchiveError": "Error al desarchivar vehículo",
+    "deleteError": "Error al eliminar vehículo",
+    "error": "Error al cargar vehículos",
     "viewTable": "Table view",
     "viewGrid": "Grid view",
     "viewLargeGrid": "Large grid view",
     "openMenu": "Open menu"
   },
   "detail": {
-    "vehicleArchived": "Este vehiculo esta archivado",
-    "backToVehicles": "Volver a vehiculos",
+    "vehicleArchived": "Este vehículo esta archivado",
+    "backToVehicles": "Volver a vehículos",
     "newWorkOrder": "Nueva orden de trabajo",
-    "editVehicle": "Editar vehiculo",
+    "editVehicle": "Editar vehículo",
     "unarchive": "Desarchivar",
     "archive": "Archivar",
     "delete": "Eliminar",
-    "notFound": "Vehiculo no encontrado",
+    "notFound": "Vehículo no encontrado",
     "predictedKm": "Km predichos",
     "predictedMileage": "Kilometraje predicho",
     "predictedHours": "Horas Previstas",
@@ -48,7 +48,7 @@
     "dismissed": "Descartado",
     "undo": "Deshacer",
     "serviceOverdue": "Servicio vencido",
-    "serviceSoon": "Servicio proximo",
+    "serviceSoon": "Servicio próximo",
     "services": "servicios",
     "purchased": "Comprado el {date}",
     "tabs": {
@@ -63,12 +63,12 @@
     "noteDeleteError": "Error al eliminar",
     "reminderDeleted": "Recordatorio eliminado",
     "reminderDeleteError": "Error al eliminar",
-    "vehicleUnarchived": "Vehiculo desarchivado",
+    "vehicleUnarchived": "Vehículo desarchivado",
     "unarchiveError": "Error al desarchivar",
-    "vehicleDeleted": "Vehiculo eliminado",
-    "vehicleDeleteError": "Error al eliminar vehiculo",
-    "deleteTitle": "Eliminar vehiculo?",
-    "deleteDescription": "Esto eliminara permanentemente {name} y todos los registros de servicio, notas y recordatorios asociados. Esta accion no se puede deshacer.",
+    "vehicleDeleted": "Vehículo eliminado",
+    "vehicleDeleteError": "Error al eliminar vehículo",
+    "deleteTitle": "Eliminar vehículo?",
+    "deleteDescription": "Esto eliminará permanentemente {name} y todos los registros de servicio, notas y recordatorios asociados. Esta acción no se puede deshacer.",
     "edit": "Editar",
     "aiSummary": "Resumen IA",
     "aiSummaryTitle": "Resumen del historial de servicio",
@@ -102,7 +102,7 @@
     "columnTotal": "Total"
   },
   "inspections": {
-    "newInspection": "Nueva inspeccion",
+    "newInspection": "Nueva inspección",
     "empty": "Aun no hay inspecciones",
     "template": "Plantilla",
     "progress": "Progreso",
@@ -125,9 +125,9 @@
     "dueAt": "Vence a los {mileage} {unit}",
     "editTitle": "Editar recordatorio",
     "addTitle": "Agregar recordatorio",
-    "titleLabel": "Titulo *",
+    "titleLabel": "Título *",
     "titlePlaceholder": "ej. Cambio de aceite",
-    "descriptionLabel": "Descripcion",
+    "descriptionLabel": "Descripción",
     "descriptionPlaceholder": "Detalles opcionales...",
     "dueDateLabel": "Fecha de vencimiento",
     "dueMileageLabel": "Kilometraje de vencimiento",
@@ -143,22 +143,22 @@
     "searchPlaceholder": "Buscar registros...",
     "allTypes": "Todos los tipos",
     "maintenance": "Mantenimiento",
-    "repair": "Reparacion",
+    "repair": "Reparación",
     "upgrade": "Mejora",
-    "inspection": "Inspeccion",
+    "inspection": "Inspección",
     "newWorkOrder": "Nueva orden de trabajo",
     "exportServiceHistory": "Exportar Historial",
     "table": {
       "date": "Fecha",
-      "title": "Titulo",
+      "title": "Título",
       "type": "Tipo",
       "status": "Estado",
       "mileage": "Kilometraje",
       "mileageMarine": "Horas de Motor",
-      "technician": "Tecnico",
+      "technician": "Técnico",
       "files": "Archivos",
       "total": "Total",
-      "warranty": "Garantia"
+      "warranty": "Garantía"
     },
     "warranty": {
       "status": {
@@ -168,11 +168,11 @@
         "none": "N/A"
       }
     },
-    "emptyFiltered": "Ningun registro de servicio coincide con sus filtros.",
+    "emptyFiltered": "Ningún registro de servicio coincide con sus filtros.",
     "empty": "Aun no hay registros de servicio.",
     "showing": "Mostrando {start}-{end} de {total}",
-    "perPage": "por pagina",
-    "page": "Pagina {page} de {totalPages}",
+    "perPage": "por página",
+    "page": "Página {page} de {totalPages}",
     "firstPage": "First page",
     "previousPage": "Previous page",
     "nextPage": "Next page",
@@ -181,7 +181,7 @@
   "notes": {
     "addNote": "Agregar nota",
     "table": {
-      "title": "Titulo",
+      "title": "Título",
       "content": "Contenido",
       "date": "Fecha"
     },
@@ -190,17 +190,17 @@
     "unpin": "Desfijar",
     "delete": "Eliminar",
     "showing": "Mostrando {start}-{end} de {total}",
-    "perPage": "por pagina",
-    "page": "Pagina {page} de {totalPages}",
+    "perPage": "por página",
+    "page": "Página {page} de {totalPages}",
     "editTitle": "Editar nota",
     "addTitle": "Agregar nota",
-    "titleLabel": "Titulo *",
-    "titlePlaceholder": "Titulo de la nota",
+    "titleLabel": "Título *",
+    "titlePlaceholder": "Título de la nota",
     "contentLabel": "Contenido *",
     "contentPlaceholder": "Escriba su nota...",
     "noteUpdated": "Nota actualizada",
     "noteCreated": "Nota creada",
-    "titleRequired": "El titulo y el contenido son obligatorios",
+    "titleRequired": "El título y el contenido son obligatorios",
     "saveError": "Error al {action} nota",
     "openMenu": "Open menu",
     "firstPage": "First page",
@@ -276,19 +276,19 @@
     }
   },
   "form": {
-    "editTitle": "Editar vehiculo",
-    "addTitle": "Agregar nuevo vehiculo",
+    "editTitle": "Editar vehículo",
+    "addTitle": "Agregar nuevo vehículo",
     "photo": "Foto",
     "clickToUpload": "Haga clic para subir una foto",
     "imageFormats": "JPEG, PNG, WebP hasta 5MB",
     "imageTooLarge": "Imagen demasiado grande",
-    "maxFileSize": "El tamano maximo del archivo es 5MB",
+    "maxFileSize": "El tamaño máximo del archivo es 5MB",
     "uploadingImage": "Subiendo imagen...",
     "uploadFailed": "Error al subir",
     "imageUploaded": "Imagen subida",
     "customer": "Cliente",
     "searchCustomers": "Buscar clientes...",
-    "noCustomerFound": "No se encontro ningun cliente.",
+    "noCustomerFound": "No se encontró ningún cliente.",
     "noCustomer": "Sin cliente",
     "addCustomer": "Agregar cliente",
     "make": "Marca *",
@@ -304,11 +304,11 @@
     "electric": "Electrico",
     "hybrid": "Hibrido",
     "other": "Otro",
-    "transmission": "Transmision",
+    "transmission": "Transmisión",
     "automatic": "Automatica",
     "manual": "Manual",
     "cvt": "CVT",
-    "engineSize": "Tamano del motor",
+    "engineSize": "Tamaño del motor",
     "engineCode": "Código del motor",
     "makeMarine": "Fabricante *",
     "mileageMarine": "Horas de Motor",
@@ -319,17 +319,17 @@
     "twoStroke": "Dos Tiempos",
     "vinMarine": "HIN",
     "licensePlateMarine": "Número de Registro",
-    "vehicleUpdated": "Vehiculo actualizado",
-    "vehicleAdded": "Vehiculo agregado",
-    "saveError": "Error al guardar vehiculo"
+    "vehicleUpdated": "Vehículo actualizado",
+    "vehicleAdded": "Vehículo agregado",
+    "saveError": "Error al guardar vehículo"
   },
   "archiveDialog": {
-    "title": "Archivar vehiculo",
-    "description": "Archivar <span>{name}</span>? Se ocultara de la lista principal pero todo el historial de servicio se conservara.",
+    "title": "Archivar vehículo",
+    "description": "Archivar <span>{name}</span>? Se ocultara de la lista principal pero todo el historial de servicio se conservará.",
     "reasonLabel": "Motivo (opcional)",
     "reasonPlaceholder": "ej. Vendido, siniestrado, dado de baja...",
     "archived": "{name} archivado",
-    "archiveError": "Error al archivar vehiculo"
+    "archiveError": "Error al archivar vehículo"
   },
   "serviceRecords": {
     "firstPage": "First page",

+ 11 - 1
messages/fr/audit.json

@@ -98,6 +98,16 @@
     "webhook_create": "Webhook créé",
     "webhook_update": "Webhook mis à jour",
     "webhook_delete": "Webhook supprimé",
-    "webhook_rotateSecret": "Secret du webhook renouvelé"
+    "webhook_rotateSecret": "Secret du webhook renouvelé",
+    "inspection_reopen": "Inspection rouverte",
+    "inspectionTemplate_import": "Modèle d'inspection importé",
+    "scheduled_message_create": "Message planifié créé",
+    "scheduled_message_update": "Message planifié mis à jour",
+    "scheduled_message_delete": "Message planifié supprimé",
+    "scheduled_message_cancel": "Message planifié annulé",
+    "scheduled_message_send_now": "Message planifié envoyé",
+    "settings_applyTaxRate": "Taux de taxe appliqué",
+    "settings_convertToExclusive": "Prix convertis hors taxes",
+    "settings_convertToInclusive": "Prix convertis toutes taxes comprises"
   }
 }

+ 24 - 0
messages/fr/common.json

@@ -52,5 +52,29 @@
     "updated": "Torqvoice a été mis à jour vers {version}",
     "whatsNew": "Voir les nouveautés",
     "dismiss": "Fermer"
+  },
+  "pagination": {
+    "showing": "{start}–{end} sur {total}",
+    "perPage": "par page",
+    "pageOf": "Page {page} sur {pages}",
+    "first": "Première page",
+    "previous": "Page précédente",
+    "next": "Page suivante",
+    "last": "Dernière page"
+  },
+  "shared": {
+    "close": "Fermer",
+    "viewPlans": "Voir les formules",
+    "manageLicense": "Gérer la licence",
+    "installOwn": "Installer la vôtre",
+    "install": "Installer",
+    "installApp": "Installer l'application",
+    "installTorqvoice": "Installer Torqvoice",
+    "dismiss": "Fermer",
+    "useCamera": "Utiliser la caméra",
+    "enterManually": "Saisir manuellement",
+    "enterBarcode": "Saisir un code-barres…",
+    "scanHint": "Pointez la caméra vers un code-barres ou saisissez-le manuellement",
+    "iosInstallHint": "Touchez Partager > Sur l'écran d'accueil"
   }
 }

+ 396 - 0
messages/fr/inspections.json

@@ -0,0 +1,396 @@
+{
+  "grades": {
+    "eu": {
+      "pass": "Aucune défaillance",
+      "attention": "Défaillance mineure",
+      "fail": "Défaillance majeure",
+      "dangerous": "Défaillance critique",
+      "not_inspected": "Non contrôlé"
+    },
+    "basic": {
+      "pass": "Conforme",
+      "attention": "Attention",
+      "fail": "Non conforme",
+      "dangerous": "Dangereux",
+      "not_inspected": "Non contrôlé"
+    },
+    "short": {
+      "pass": "OK",
+      "attention": "Mineure",
+      "fail": "Majeure",
+      "dangerous": "Critique",
+      "not_inspected": "–"
+    },
+    "hint": {
+      "pass": "Conforme aux exigences. Rien à signaler.",
+      "attention": "Sans effet notable sur la sécurité. Le véhicule est accepté ; à réparer à l'occasion.",
+      "fail": "Peut compromettre la sécurité ou affecter l'environnement. Le véhicule est refusé ; à réparer et recontrôler.",
+      "dangerous": "Danger direct et immédiat pour la sécurité routière. Le véhicule ne doit pas circuler.",
+      "not_inspected": "Ce point n'a pas été contrôlé."
+    }
+  },
+  "results": {
+    "pass": "Favorable",
+    "pass_minor": "Favorable avec défaillances mineures",
+    "fail": "Défavorable — défaillances majeures",
+    "fail_dangerous": "Défavorable — défaillances critiques",
+    "incomplete": "Non terminé",
+    "detail": {
+      "pass": "Aucune défaillance n'a été constatée.",
+      "pass_minor": "Le véhicule est accepté. Réparez les défaillances mineures sans délai excessif.",
+      "fail": "Le véhicule est refusé. Les défaillances doivent être réparées et le véhicule doit repasser au contrôle.",
+      "fail_dangerous": "Le véhicule ne doit pas circuler sur la voie publique tant que les défaillances critiques ne sont pas réparées.",
+      "incomplete": "Ce contrôle est toujours en cours."
+    }
+  },
+  "page": {
+    "back": "Inspections",
+    "statusInProgress": "En cours",
+    "statusCompleted": "Terminée",
+    "complete": "Clôturer",
+    "reopen": "Rouvrir",
+    "share": "Partager",
+    "save": "Enregistrer",
+    "saving": "Enregistrement…",
+    "savedAt": "Enregistré {time}",
+    "savesAsYouGo": "Enregistre au fil de l'eau",
+    "saveFailed": "Certaines modifications n'ont pas été enregistrées",
+    "allSaved": "Toutes les modifications sont enregistrées",
+    "moreActions": "Autres actions",
+    "downloadCertificate": "Télécharger le procès-verbal",
+    "manageTemplates": "Gérer les modèles",
+    "delete": "Supprimer",
+    "createWorkOrder": "Créer un ordre de réparation",
+    "viewWorkOrder": "Voir l'ordre de réparation",
+    "createQuote": "Créer un devis",
+    "viewQuote": "Voir le devis",
+    "needsDefect": "Disponible dès qu'une défaillance est constatée",
+    "workOrderRaised": "Ordre de réparation {number} créé depuis cette inspection le {date}",
+    "open": "Ouvrir",
+    "quoteCreated": "Devis créé par {name} le {date}",
+    "quoteRequested": "Le client a demandé un devis sur {count} point(s)",
+    "stillToGrade": "{count} points sur {total} restent à évaluer.",
+    "progress": "Avancement",
+    "checksGraded": "points évalués",
+    "progressLabel": "{graded} points sur {total} évalués : {pass} aucune défaillance, {attention} mineures, {fail} majeures, {dangerous} critiques.",
+    "sections": "Groupes",
+    "graded": "{graded}/{total} évalués",
+    "vehicle": "Véhicule",
+    "vessel": "Bateau",
+    "customer": "Client",
+    "plate": "Immatriculation",
+    "registration": "Immatriculation",
+    "template": "Modèle",
+    "scaleEu": "Évalué selon l'échelle de défaillances de l'UE issue de la directive 2014/45/UE.",
+    "scaleBasic": "Évalué conforme / attention / non conforme.",
+    "country": "Pays : {code}.",
+    "manageInspectionTemplates": "Gérer les modèles d'inspection",
+    "readMore": "En savoir plus",
+    "odometer": "Compteur",
+    "engineHours": "Heures moteur",
+    "completeTitle": "Clôturer cette inspection ?",
+    "completeBody": "Les points sont verrouillés à la clôture, et le résultat devient « {result} ».",
+    "completeBlocked": "{count} point(s) que le modèle rend obligatoires restent en attente.",
+    "completeUngraded": "{count} point(s) n'ont pas été évalués.",
+    "completeDangerous": "{count} défaillance(s) critique(s) constatée(s). Le véhicule ne doit pas circuler sur la voie publique.",
+    "reopenTitle": "Rouvrir cette inspection ?",
+    "reopenBody": "Les points redeviennent modifiables et le résultat repasse en cours. Le contrôleur, le numéro de certificat et la date du prochain contrôle sont conservés.",
+    "reopenShared": "Le lien client reste actif et montrera le résultat évoluer pendant votre travail. Un procès-verbal déjà téléchargé conserve l'ancien résultat.",
+    "deleteTitle": "Supprimer cette inspection ?",
+    "deleteBody": "Cela supprime définitivement l'inspection, ses évaluations, ses notes et ses photos. L'opération est irréversible.",
+    "cancel": "Annuler",
+    "completed": "Inspection clôturée",
+    "reopened": "Inspection rouverte",
+    "deleted": "Inspection supprimée",
+    "completeFailed": "Impossible de clôturer l'inspection",
+    "reopenFailed": "Impossible de rouvrir l'inspection",
+    "deleteFailed": "Impossible de supprimer l'inspection",
+    "quoteFailed": "Impossible de créer le devis",
+    "workOrderFailed": "Impossible de créer l'ordre de réparation"
+  },
+  "item": {
+    "required": "(obligatoire)",
+    "saving": "Enregistrement",
+    "addNote": "Ajouter une note",
+    "hideNote": "Masquer la note",
+    "addPhoto": "Ajouter une photo ou une vidéo à {name}",
+    "notesFor": "Notes pour {name}",
+    "describeDefect": "Décrivez la défaillance — elle figure sur le rapport",
+    "optionalNote": "Ajouter une note (facultatif)",
+    "notesRequired": "Décrivez la défaillance avant que cette évaluation soit enregistrée.",
+    "photoRequired": "Une photo de la défaillance est requise ici. L'inspection ne peut pas être clôturée tant qu'aucune n'est jointe.",
+    "reading": "Mesure",
+    "readingUnit": "Mesure ({unit})",
+    "limit": "Limite : {range}",
+    "recordedValue": "Valeur relevée",
+    "answer": "Réponse",
+    "selectAnswer": "Choisir une réponse",
+    "viewPhoto": "Voir la photo {index} de {name} en taille réelle",
+    "photoAlt": "{name}, photo {index}",
+    "removePhoto": "Retirer la photo {index} de {name}",
+    "clearTitle": "Effacer cette évaluation ?",
+    "clearBody": "La note enregistrée sur « {name} » sera supprimée et le point repassera à non contrôlé.",
+    "clear": "Effacer",
+    "cancel": "Annuler",
+    "saveFailed": "Impossible d'enregistrer ce point",
+    "uploadFailed": "Échec du téléversement",
+    "uploadFailedFor": "Impossible de téléverser {name}"
+  },
+  "suggestions": {
+    "title": "Défaillances courantes",
+    "add": "Ajouter « {text} » et évaluer en {grade}",
+    "more": "Plus ({count})",
+    "search": "Rechercher une formulation",
+    "empty": "Aucune formulation correspondante.",
+    "source": {
+      "workshop": "Votre atelier",
+      "history": "Déjà utilisée",
+      "regulation": "Directive 2014/45/UE",
+      "general": "Général"
+    }
+  },
+  "media": {
+    "viewer": "Image {index} sur {total} : {caption}",
+    "close": "Fermer la visionneuse",
+    "previous": "Image précédente",
+    "next": "Image suivante"
+  },
+  "certificate": {
+    "title": "Mentions du procès-verbal",
+    "description": "Requises sur un procès-verbal de contrôle technique au titre de la directive 2014/45/UE, annexe IV.",
+    "inspector": "Contrôleur",
+    "notRecorded": "Non renseigné",
+    "addTechnician": "Ajouter un technicien…",
+    "recordedAs": "Enregistré comme {name}",
+    "vehicleCategory": "Catégorie du véhicule",
+    "mileageAtTest": "{label} au moment du contrôle",
+    "reportReference": "Référence du rapport",
+    "reportReferenceHelp": "Votre propre référence pour ce rapport. Si vous êtes un centre agréé délivrant un procès-verbal officiel, indiquez ici son numéro pour que les deux concordent. Sinon, laissez vide.",
+    "optional": "Facultatif",
+    "placeOfTest": "Lieu du contrôle",
+    "placeholderAddress": "Centre de contrôle ou adresse de l'atelier",
+    "fromSettings": "D'après les paramètres de votre atelier.",
+    "change": "Modifier",
+    "differsFromWorkshop": "Diffère de l'adresse de votre atelier.",
+    "useWorkshopAddress": "Utiliser l'adresse de l'atelier",
+    "noWorkshopAddress": "Aucune adresse d'atelier n'est renseignée.",
+    "addOne": "En ajouter une",
+    "nextTestDue": "Prochain contrôle",
+    "notSet": "Non défini",
+    "intervalGroup": "Définir la date du prochain contrôle à partir de celle-ci",
+    "intervalHelp": "Compté à partir de la date de ce contrôle. Deux ans est la référence de l'Union pour les voitures et les véhicules utilitaires légers ; vérifiez la périodicité appliquée dans votre pays.",
+    "months6": "6 mois",
+    "years1": "1 an",
+    "years2": "2 ans",
+    "years3": "3 ans",
+    "years4": "4 ans",
+    "unsaved": "Modifications non enregistrées",
+    "saveDetails": "Enregistrer les mentions",
+    "saved": "Mentions enregistrées",
+    "saveFailed": "Impossible d'enregistrer les mentions",
+    "addTechnicianTitle": "Ajouter un technicien",
+    "addTechnicianBody": "La personne rejoint la liste de l'atelier et peut se voir attribuer des travaux comme des inspections.",
+    "technicianName": "Nom",
+    "technicianPlaceholder": "p. ex. Anna Berg",
+    "addAndSelect": "Ajouter et sélectionner",
+    "technicianAdded": "{name} ajouté et sélectionné",
+    "technicianFailed": "Impossible d'ajouter le technicien",
+    "cancel": "Annuler"
+  },
+  "templates": {
+    "title": "Modèles d'inspection",
+    "description": "Les listes de points que vos techniciens parcourent. Créez-en une par pays, par type de véhicule ou par prestation que vous vendez.",
+    "import": "Importer",
+    "browse": "Parcourir les listes",
+    "new": "Nouveau modèle",
+    "restoreBanner": "{count} listes toutes prêtes ont été retirées de votre liste. Vous pouvez les remettre.",
+    "restore": "Restaurer",
+    "restored": "{count} liste(s) restaurée(s)",
+    "allPresent": "Toutes les listes sont déjà présentes",
+    "restoreFailed": "Impossible d'ajouter les listes",
+    "emptyTitle": "Aucun modèle pour l'instant",
+    "emptyBody": "Les listes toutes prêtes, dont le contrôle technique périodique de l'UE, ont toutes été retirées. Remettez-les, ou construisez la vôtre de zéro.",
+    "restoreChecklists": "Restaurer les listes",
+    "buildMyOwn": "Construire la mienne",
+    "default": "Par défaut",
+    "actionsFor": "Actions pour {name}",
+    "edit": "Modifier",
+    "duplicate": "Dupliquer",
+    "export": "Exporter…",
+    "delete": "Supprimer",
+    "sections": "{count} groupe(s)",
+    "checks": "{count} point(s)",
+    "measured": "{count} mesurés",
+    "euScale": "Échelle de défaillances UE",
+    "basicScale": "Conforme / attention / non conforme",
+    "deleteTitle": "Supprimer « {name} » ?",
+    "deleteBody": "Cela supprime définitivement le modèle. Les inspections déjà réalisées avec lui conservent leur propre copie de la liste et ne sont pas affectées.",
+    "deleted": "Modèle supprimé",
+    "deleteFailed": "Impossible de supprimer le modèle",
+    "duplicated": "Modèle dupliqué",
+    "duplicateFailed": "Impossible de dupliquer le modèle",
+    "cancel": "Annuler",
+    "srSections": "Groupes",
+    "srChecks": "Points",
+    "srMeasurements": "Mesures"
+  },
+  "presets": {
+    "title": "Partir d'un modèle",
+    "description": "Chaque modèle est copié dans votre atelier comme un modèle ordinaire. Renommez les groupes, déplacez les points et modifiez toute limite pour correspondre à votre pays et à votre équipement.",
+    "search": "Rechercher par nom, pays ou norme",
+    "searchLabel": "Rechercher des modèles",
+    "inYourList": "Dans votre liste",
+    "counts": "{sections} groupes · {checks} points",
+    "noMatch": "Aucun modèle ne correspond à « {query} ».",
+    "warning": "Les modèles réglementaires portent les limites usuelles de l'UE. Vérifiez-les au regard de votre réglementation nationale avant de délivrer un procès-verbal.",
+    "cancel": "Annuler",
+    "add": "Ajouter le modèle",
+    "added": "Modèle ajouté. Adaptez-le à votre atelier.",
+    "addFailed": "Impossible d'ajouter le modèle",
+    "group": {
+      "regulatory": "Réglementaire",
+      "workshop": "Atelier",
+      "specialist": "Spécialisé"
+    },
+    "groupDescription": {
+      "regulatory": "Listes de contrôle technique périodique fondées sur la directive 2014/45/UE.",
+      "workshop": "Listes générales d'entretien et de vente, sans lien avec une réglementation.",
+      "specialist": "Listes pour des motorisations et des bateaux qui exigent leur propre procédure."
+    },
+    "euScale": "Échelle de défaillances UE",
+    "basicScale": "Conforme / attention / non conforme"
+  },
+  "builder": {
+    "newTitle": "Nouveau modèle",
+    "editTitle": "Modifier le modèle",
+    "description": "Construisez la liste que votre atelier utilise réellement. Faites glisser pour réordonner, et configurez chaque point pour enregistrer une évaluation, une mesure, une note écrite ou une réponse fixe.",
+    "name": "Nom",
+    "namePlaceholder": "p. ex. Contrôle technique périodique",
+    "templateDescription": "Description",
+    "descriptionPlaceholder": "À quoi sert cette liste et quand l'utiliser.",
+    "country": "Pays",
+    "notCountrySpecific": "Non spécifique à un pays",
+    "countryHelp": "Consigné sur le rapport pour qu'un atelier travaillant de part et d'autre d'une frontière distingue ses listes.",
+    "defectScale": "Échelle de défaillances",
+    "scaleEu": "Mineure / majeure / critique (UE)",
+    "scaleBasic": "Conforme / attention / non conforme",
+    "scaleEuHelp": "Les catégories de l'article 7, paragraphe 2, de la directive 2014/45/UE. Un véhicule ne présentant que des défaillances mineures reste accepté.",
+    "scaleBasicHelp": "Une échelle simple à trois niveaux pour les listes d'entretien qui ne sont pas un contrôle réglementaire.",
+    "defaultTemplate": "Modèle par défaut",
+    "defaultHelp": "Présélectionné quand un technicien démarre une nouvelle inspection.",
+    "summary": "{sections} groupe(s) · {checks} point(s)",
+    "renumber": "Renuméroter les références",
+    "renumberHelp": "Numérote chaque groupe et chaque point dans l'ordre : 1, 1.1, 1.2, 2. Les nouvelles lignes sont numérotées à l'ajout, vous n'en avez donc besoin qu'après avoir déplacé des éléments.",
+    "renumberWarning": "Cette liste utilise des références réglementaires comme 1.1.13. La renumérotation les remplace.",
+    "addSection": "Ajouter un groupe",
+    "addCheck": "Ajouter un point",
+    "ref": "Réf.",
+    "sectionName": "Nom du groupe, p. ex. Freinage",
+    "sectionNote": "Note facultative affichée au-dessus des points de ce groupe",
+    "checkName": "Nom du point, p. ex. Garnitures de frein",
+    "reorderSection": "Réordonner le groupe {name}",
+    "reorderCheck": "Réordonner le point {name}",
+    "removeSection": "Supprimer le groupe {name}",
+    "removeCheck": "Supprimer le point {name}",
+    "duplicateCheck": "Dupliquer le point {name}",
+    "configure": "Configurer {name}",
+    "hideSettings": "Masquer les réglages de {name}",
+    "howRecorded": "Comment est-ce enregistré ?",
+    "guidance": "Consigne pour le technicien",
+    "guidancePlaceholder": "Affichée sous le nom du point sur le formulaire d'inspection.",
+    "measurement": "Mesure",
+    "unit": "Unité",
+    "minimum": "Minimum",
+    "maximum": "Maximum",
+    "outOfRange": "Défaillance hors plage",
+    "rangeHelp": "Laissez une borne vide pour une limite unilatérale, par exemple un minimum de 1,6 mm sans maximum. Les mesures hors plage sont évaluées automatiquement.",
+    "answers": "Réponses",
+    "answersHelp": "Séparez les réponses par des virgules.",
+    "mustComplete": "Doit être renseigné",
+    "photoRequired": "Photo requise lorsqu'une défaillance est constatée",
+    "commonWording": "Formulations courantes",
+    "commonWordingHelp": "Une formulation par ligne. Elles sont proposées en premier lorsqu'un technicien constate une défaillance ici, avant les textes intégrés, pour que votre atelier s'accorde sur la description d'un défaut.",
+    "inputType": {
+      "condition": "Évaluation de défaillance",
+      "measurement": "Mesure",
+      "text": "Texte libre",
+      "choice": "Choix"
+    },
+    "inputHint": {
+      "condition": "Le technicien évalue le point selon l'échelle de défaillances.",
+      "measurement": "Enregistre un nombre assorti d'une unité, évalué automatiquement par rapport à une plage.",
+      "text": "Enregistre une observation écrite, par exemple une marque de pneumatique ou un code défaut.",
+      "choice": "Le technicien choisit une réponse parmi une liste fixe."
+    },
+    "cancel": "Annuler",
+    "create": "Créer le modèle",
+    "saveChanges": "Enregistrer les modifications",
+    "created": "Modèle créé",
+    "updated": "Modèle mis à jour",
+    "saveFailed": "Impossible d'enregistrer le modèle",
+    "severityAttention": "Défaillance mineure",
+    "severityFail": "Défaillance majeure",
+    "severityDangerous": "Défaillance critique",
+    "answersPlaceholder": "Complet, Partiel, Aucun"
+  },
+  "packages": {
+    "exportTitle": "Exporter « {name} »",
+    "exportDescription": "Télécharge toute la liste sous forme de fichier : groupes, points, unités et limites. Toute personne utilisant Torqvoice peut l'importer.",
+    "includeWording": "Inclure vos propres formulations",
+    "includeWordingHelp": "Les formulations que votre atelier a ajoutées aux points. Utiles au destinataire, mais ce sont des textes libres saisis à l'établi, qui peuvent nommer un client, un collègue ou un arrangement local. Désactivé sauf si vous les avez relus.",
+    "attributeTo": "Attribuer à (facultatif)",
+    "attributePlaceholder": "p. ex. Egeland Auto",
+    "attributeHelp": "Inscrit dans le fichier pour que la personne qui l'installe sache d'où il vient. Rien n'est repris de votre compte.",
+    "download": "Télécharger",
+    "exported": "Modèle exporté",
+    "exportFailed": "Impossible d'exporter ce modèle",
+    "importTitle": "Importer une liste",
+    "importDescription": "Ajoute une liste depuis un fichier de paquet Torqvoice. Elle arrive comme un modèle ordinaire que vous pouvez modifier, et ne remplace jamais un modèle existant.",
+    "chooseFile": "Choisir un fichier de paquet",
+    "chooseAnother": "Choisir un autre fichier",
+    "unreadable": "Ce fichier n'a pas pu être lu. Un paquet Torqvoice est un fichier .json exporté depuis un modèle.",
+    "from": "De {author}",
+    "limitsWarning": "Les limites d'une liste partagée sont celles définies par son auteur. Vérifiez-les au regard de votre réglementation nationale avant d'en délivrer quoi que ce soit.",
+    "import": "Importer",
+    "imported": "{count} modèle(s) importé(s)",
+    "importFailed": "Impossible d'importer ce paquet",
+    "cancel": "Annuler"
+  },
+  "list": {
+    "tabAll": "Toutes",
+    "tabInProgress": "En cours",
+    "tabCompleted": "Terminées",
+    "search": "Rechercher des inspections…",
+    "new": "Nouvelle inspection",
+    "empty": "Aucune inspection trouvée.",
+    "vehicle": "Véhicule",
+    "template": "Modèle",
+    "progress": "Avancement",
+    "status": "Statut",
+    "date": "Date",
+    "statusInProgress": "En cours",
+    "statusCompleted": "Terminée"
+  },
+  "new": {
+    "title": "Nouvelle inspection",
+    "vehicle": "Véhicule",
+    "searchVehicles": "Rechercher des véhicules…",
+    "loadingVehicles": "Chargement des véhicules…",
+    "noVehicles": "Aucun véhicule trouvé.",
+    "template": "Modèle",
+    "selectTemplate": "Choisir un modèle",
+    "manageTemplates": "Gérer les modèles",
+    "cancel": "Annuler",
+    "create": "Créer l'inspection",
+    "created": "Inspection créée",
+    "createFailed": "Impossible de créer l'inspection"
+  },
+  "shareDialog": {
+    "title": "Partager l'inspection",
+    "description": "Partagez une vue en lecture seule de cette inspection avec votre client.",
+    "copyLink": "Copier le lien",
+    "copied": "Lien copié dans le presse-papiers",
+    "generated": "Lien de partage créé",
+    "revoked": "Lien de partage révoqué"
+  }
+}

+ 250 - 250
messages/fr/settings.json

@@ -1,5 +1,5 @@
 {
-  "title": "Parametres",
+  "title": "Paramètres",
   "subtitle": "Gerez votre atelier, factures et preferences",
   "nav": {
     "categories": {
@@ -8,12 +8,12 @@
       "communications": "Communications",
       "workshop": "Atelier",
       "integrations": "Intégrations",
-      "system": "Systeme"
+      "system": "Système"
     },
     "items": {
       "company": {
         "title": "Entreprise",
-        "description": "Details commerciaux et image de marque"
+        "description": "Détails commerciaux et image de marque"
       },
       "subscription": {
         "title": "Abonnement",
@@ -24,15 +24,15 @@
         "description": "Nom, e-mail et mot de passe"
       },
       "team": {
-        "title": "Equipe",
+        "title": "Équipe",
         "description": "Membres et roles"
       },
       "invoice": {
         "title": "Factures et devis",
-        "description": "Parametres de facturation, mise en page et champs personnalises"
+        "description": "Paramètres de facturation, mise en page et champs personnalises"
       },
       "templates": {
-        "title": "Modeles",
+        "title": "Modèles",
         "description": "Style visuel, couleurs et polices"
       },
       "payment": {
@@ -41,11 +41,11 @@
       },
       "tax": {
         "title": "Taxe",
-        "description": "Taux de taxe et valeurs par defaut"
+        "description": "Taux de taxe et valeurs par défaut"
       },
       "currency": {
         "title": "Devise",
-        "description": "Devise et taxes par defaut"
+        "description": "Devise et taxes par défaut"
       },
       "localization": {
         "title": "Localisation",
@@ -53,7 +53,7 @@
       },
       "customFields": {
         "title": "Champs personnalises",
-        "description": "Definir des champs de donnees personnalises"
+        "description": "Définir des champs de données personnalises"
       },
       "email": {
         "title": "E-mail",
@@ -73,7 +73,7 @@
       },
       "workshop": {
         "title": "Atelier",
-        "description": "Valeurs par defaut technicien et main-d'oeuvre"
+        "description": "Valeurs par défaut technicien et main-d'oeuvre"
       },
       "laborPresets": {
         "title": "Forfaits main-d'oeuvre",
@@ -85,7 +85,7 @@
       },
       "ai": {
         "title": "AI",
-        "description": "Fournisseur IA et fonctionnalites"
+        "description": "Fournisseur IA et fonctionnalités"
       },
       "reportSchedule": {
         "title": "Rapports planifiés",
@@ -100,7 +100,7 @@
         "description": "Theme et affichage"
       },
       "data": {
-        "title": "Donnees",
+        "title": "Données",
         "description": "Exporter et importer une sauvegarde"
       },
       "license": {
@@ -123,7 +123,7 @@
     "pro": "PRO"
   },
   "readOnly": {
-    "banner": "Vous avez un acces en lecture seule aux parametres. Contactez un administrateur pour effectuer des modifications."
+    "banner": "Vous avez un accès en lecture seule aux paramètres. Contactez un administrateur pour effectuer des modifications."
   },
   "featureLocked": {
     "requiresPro": "Necessite une licence Pro",
@@ -133,32 +133,32 @@
   "company": {
     "logoTitle": "Logo de l'entreprise",
     "logoAlt": "Logo de l'entreprise",
-    "upload": "Telecharger",
+    "upload": "Télécharger",
     "remove": "Supprimer",
     "logoHint": "JPG, PNG, WebP, SVG — max 2 Mo",
-    "uploadingLogo": "Telechargement du logo...",
+    "uploadingLogo": "Téléchargement du logo...",
     "logoUpdated": "Logo mis a jour",
-    "failedUploadLogo": "Echec du telechargement du logo",
-    "detailsTitle": "Details de l'entreprise",
+    "failedUploadLogo": "Échec du téléchargement du logo",
+    "detailsTitle": "Détails de l'entreprise",
     "shopName": "Nom de l'entreprise / atelier",
     "shopNamePlaceholder": "Garage Auto Martin",
-    "orgNumber": "Numero d'organisation",
+    "orgNumber": "Numéro d'organisation",
     "orgNumberPlaceholder": "123 456 789",
-    "phone": "Telephone",
+    "phone": "Téléphone",
     "phonePlaceholder": "(555) 123-4567",
     "email": "E-mail",
     "emailPlaceholder": "service@exemple.com",
     "address": "Adresse",
     "addressPlaceholder": "123 Rue Principale\nEtage 1\n75001 Paris",
-    "saveCompany": "Enregistrer les details",
+    "saveCompany": "Enregistrer les détails",
     "addNewCompany": "Ajouter une entreprise",
     "companyName": "Nom de l'entreprise",
     "companyNamePlaceholder": "Ex. Garage Auto Martin",
     "cancel": "Annuler",
-    "createCompany": "Creer l'entreprise",
-    "settingsSaved": "Parametres de l'entreprise enregistres",
-    "failedSave": "Echec de l'enregistrement des parametres",
-    "companyCreated": "Entreprise creee avec succes",
+    "createCompany": "Créer l'entreprise",
+    "settingsSaved": "Paramètres de l'entreprise enregistrés",
+    "failedSave": "Échec de l'enregistrement des paramètres",
+    "companyCreated": "Entreprise créée avec succès",
     "serviceType": "Type de Service",
     "serviceTypeDescription": "Sélectionnez le type de service proposé par votre atelier. Cela modifie les libellés des champs dans toute l'application.",
     "automotive": "Automobile",
@@ -182,8 +182,8 @@
     "emailPlaceholder": "Votre e-mail",
     "saveProfile": "Enregistrer le profil",
     "profileUpdated": "Profil mis a jour",
-    "failedUpdateProfile": "Echec de la mise a jour du profil",
-    "failedUpdateEmail": "Echec de la mise a jour de l'e-mail",
+    "failedUpdateProfile": "Échec de la mise a jour du profil",
+    "failedUpdateEmail": "Échec de la mise a jour de l'e-mail",
     "changePasswordTitle": "Changer le mot de passe",
     "changePasswordDescription": "Entrez votre mot de passe actuel et choisissez-en un nouveau.",
     "currentPassword": "Mot de passe actuel",
@@ -192,36 +192,36 @@
     "changePassword": "Changer le mot de passe",
     "passwordChanged": "Mot de passe change",
     "passwordsNoMatch": "Les mots de passe ne correspondent pas",
-    "passwordMinLength": "Le mot de passe doit comporter au moins 8 caracteres",
-    "failedChangePassword": "Echec du changement de mot de passe",
+    "passwordMinLength": "Le mot de passe doit comporter au moins 8 caractères",
+    "failedChangePassword": "Échec du changement de mot de passe",
     "twoFactorTitle": "Authentification a deux facteurs",
     "twoFactorEnabled": "L'authentification a deux facteurs est activee",
-    "twoFactorDisablePrompt": "Entrez votre mot de passe pour desactiver l'authentification a deux facteurs.",
-    "twoFactorEnablePrompt": "Ajoutez une couche de securite supplementaire a votre compte en activant l'authentification a deux facteurs avec une application d'authentification.",
+    "twoFactorDisablePrompt": "Entrez votre mot de passe pour désactiver l'authentification a deux facteurs.",
+    "twoFactorEnablePrompt": "Ajoutez une couche de sécurité supplémentaire a votre compte en activant l'authentification a deux facteurs avec une application d'authentification.",
     "password": "Mot de passe",
     "enterPassword": "Entrez votre mot de passe",
-    "disable2FA": "Desactiver 2FA",
+    "disable2FA": "Désactiver 2FA",
     "enable2FA": "Activer 2FA",
-    "twoFactorDisabled": "Authentification a deux facteurs desactivee",
-    "failedEnable2FA": "Echec de l'activation de 2FA",
-    "failedDisable2FA": "Echec de la desactivation de 2FA",
+    "twoFactorDisabled": "Authentification a deux facteurs désactivée",
+    "failedEnable2FA": "Échec de l'activation de 2FA",
+    "failedDisable2FA": "Échec de la desactivation de 2FA",
     "enterPasswordError": "Veuillez entrer votre mot de passe",
     "setupDialogTitle": "Activer l'authentification a deux facteurs",
     "setupDialogDescription": "Entrez votre mot de passe pour commencer la configuration de 2FA avec une application d'authentification.",
     "continue": "Continuer",
     "scanQRTitle": "Scanner le code QR",
     "scanQRDescription": "Scannez ce code QR avec votre application d'authentification (Google Authenticator, Authy, etc.)",
-    "verifyCodeTitle": "Verifier le code",
-    "verifyCodeDescription": "Entrez le code a 6 chiffres de votre application d'authentification pour verifier qu'elle est correctement configuree.",
+    "verifyCodeTitle": "Vérifier le code",
+    "verifyCodeDescription": "Entrez le code a 6 chiffres de votre application d'authentification pour vérifier qu'elle est correctement configurée.",
     "authCode": "Code d'authentification",
     "authCodePlaceholder": "000000",
     "back": "Retour",
-    "verify": "Verifier",
+    "verify": "Vérifier",
     "enterCodeError": "Veuillez entrer le code de votre application d'authentification",
-    "invalidCode": "Code invalide. Veuillez reessayer.",
-    "verificationFailed": "Verification echouee",
+    "invalidCode": "Code invalide. Veuillez réessayer.",
+    "verificationFailed": "Vérification échouée",
     "backupCodesTitle": "Sauvegarder les codes de secours",
-    "backupCodesDescription": "Conservez ces codes dans un endroit sur. Vous pouvez les utiliser pour vous connecter si vous perdez l'acces a votre application d'authentification. Chaque code ne peut etre utilise qu'une seule fois.",
+    "backupCodesDescription": "Conservez ces codes dans un endroit sur. Vous pouvez les utiliser pour vous connecter si vous perdez l'accès a votre application d'authentification. Chaque code ne peut être utilise qu'une seule fois.",
     "copyCodes": "Copier les codes",
     "copied": "Copie",
     "backupCodesCopied": "Codes de secours copies dans le presse-papiers",
@@ -229,28 +229,28 @@
     "twoFactorEnabledSuccess": "Authentification a deux facteurs activee",
     "dangerZone": "Zone dangereuse",
     "deleteContentTitle": "Supprimer le contenu",
-    "deleteContentDescription": "Supprimez selectivement les donnees de l'organisation telles que les vehicules, clients, devis ou inventaire. Votre compte et vos parametres resteront intacts.",
+    "deleteContentDescription": "Supprimez selectivement les données de l'organisation telles que les véhicules, clients, devis ou inventaire. Votre compte et vos paramètres resteront intacts.",
     "deleteContentButton": "Supprimer le contenu",
     "deleteAccountTitle": "Supprimer le compte",
-    "deleteAccountDescription": "Supprimez definitivement votre compte et toutes les donnees associees, y compris les vehicules, les dossiers de service, les ordres de travail, les factures, les clients, les fichiers et les paiements. Cette action ne peut pas etre annulee.",
+    "deleteAccountDescription": "Supprimez définitivement votre compte et toutes les données associées, y compris les véhicules, les dossiers de service, les ordres de travail, les factures, les clients, les fichiers et les paiements. Cette action ne peut pas être annulée.",
     "deleteAccountButton": "Supprimer le compte",
     "deleteAccountDialogTitle": "Supprimer le compte",
-    "deleteAccountDialogDescription": "Cela supprimera definitivement votre compte et toutes les donnees associees. Cette action est irreversible. Tous vos vehicules, dossiers de service, ordres de travail, devis, clients, fichiers et historique de paiements seront definitivement supprimes.",
+    "deleteAccountDialogDescription": "Cela supprimera définitivement votre compte et toutes les données associées. Cette action est irréversible. Tous vos véhicules, dossiers de service, ordres de travail, devis, clients, fichiers et historique de paiements seront définitivement supprimes.",
     "deleteAccountConfirmPhrase": "delete me",
     "deleteAccountConfirmPrompt": "Tapez <bold>delete me</bold> pour confirmer",
-    "permanentlyDelete": "Supprimer definitivement",
+    "permanentlyDelete": "Supprimer définitivement",
     "deleting": "Suppression...",
-    "failedDeleteAccount": "Echec de la suppression du compte",
+    "failedDeleteAccount": "Échec de la suppression du compte",
     "deleteContentDialogTitle": "Supprimer le contenu",
-    "deleteContentDialogDescription": "Selectionnez le contenu que vous souhaitez supprimer definitivement. Cette action ne peut pas etre annulee.",
-    "contentVehicles": "Vehicules",
-    "contentVehiclesDescription": "Tous les vehicules et leurs dossiers de service, notes, rappels, journaux de carburant et factures recurrentes",
+    "deleteContentDialogDescription": "Sélectionnez le contenu que vous souhaitez supprimer définitivement. Cette action ne peut pas être annulée.",
+    "contentVehicles": "Véhicules",
+    "contentVehiclesDescription": "Tous les véhicules et leurs dossiers de service, notes, rappels, journaux de carburant et factures recurrentes",
     "contentCustomers": "Clients",
-    "contentCustomersDescription": "Tous les dossiers clients. Les vehicules lies aux clients supprimes seront dissocies.",
+    "contentCustomersDescription": "Tous les dossiers clients. Les véhicules lies aux clients supprimes seront dissocies.",
     "contentQuotes": "Devis",
     "contentQuotesDescription": "Tous les devis et leurs lignes",
     "contentInventory": "Inventaire",
-    "contentInventoryDescription": "Toutes les pieces d'inventaire et leurs images",
+    "contentInventoryDescription": "Toutes les pièces d'inventaire et leurs images",
     "contentInspections": "Inspections",
     "contentInspectionsDescription": "Toutes les inspections de véhicules et leurs résultats",
     "contentTechnicians": "Techniciens",
@@ -266,8 +266,8 @@
     "deleteContentConfirmPhrase": "delete my data",
     "deleteContentConfirmPrompt": "Tapez <bold>delete my data</bold> pour confirmer",
     "deleteSelected": "Supprimer {count} selectionnes",
-    "contentDeleted": "Le contenu selectionne a ete definitivement supprime",
-    "failedDeleteContent": "Echec de la suppression du contenu",
+    "contentDeleted": "Le contenu selectionne a ete définitivement supprime",
+    "failedDeleteContent": "Échec de la suppression du contenu",
     "passkey": {
       "title": "Passkeys",
       "description": "Connectez-vous avec la biométrie ou une clé de sécurité au lieu d'un mot de passe.",
@@ -304,16 +304,16 @@
     "deleteAccountMovedLink": "Vous pouvez le faire dans les paramètres des données."
   },
   "team": {
-    "title": "Equipe",
-    "noOrgDescription": "Creez une equipe pour partager des donnees avec d'autres utilisateurs.",
+    "title": "Équipe",
+    "noOrgDescription": "Créez une équipe pour partager des données avec d'autres utilisateurs.",
     "orgDescription": "Gerez les membres de votre organisation et leurs roles.",
-    "createOrg": "Creer une organisation",
-    "createOrgDescription": "Creez une organisation pour inviter des membres. Vous serez le proprietaire.",
+    "createOrg": "Créer une organisation",
+    "createOrgDescription": "Créez une organisation pour inviter des membres. Vous serez le propriétaire.",
     "orgName": "Nom de l'organisation",
     "orgNamePlaceholder": "Mon atelier",
-    "create": "Creer",
-    "orgCreated": "Organisation creee",
-    "failedCreateOrg": "Echec de la creation de l'organisation",
+    "create": "Créer",
+    "orgCreated": "Organisation créée",
+    "failedCreateOrg": "Échec de la création de l'organisation",
     "memberCount": "{count} membre",
     "memberCountPlural": "{count} membres",
     "admin": "Administrateur",
@@ -321,14 +321,14 @@
     "inviteByEmail": "Inviter par e-mail",
     "inviteEmailPlaceholder": "utilisateur@exemple.com",
     "invite": "Inviter",
-    "invited": "Membre invite avec succes.",
+    "invited": "Membre invite avec succès.",
     "userNotFoundTitle": "Utilisateur non trouve",
-    "userNotFoundDescription": "Aucun compte trouve pour \"{email}\". Envoyer un e-mail d'invitation pour s'inscrire et rejoindre votre equipe ?",
+    "userNotFoundDescription": "Aucun compte trouve pour \"{email}\". Envoyer un e-mail d'invitation pour s'inscrire et rejoindre votre équipe ?",
     "sendInvitation": "Envoyer l'invitation",
-    "invitationSentTitle": "Invitation envoyee",
+    "invitationSentTitle": "Invitation envoyée",
     "invitationSentDescription": "Un e-mail d'invitation a ete envoye a {email}.",
-    "failedInvite": "Echec de l'invitation du membre",
-    "failedSendInvitation": "Echec de l'envoi de l'invitation",
+    "failedInvite": "Échec de l'invitation du membre",
+    "failedSendInvitation": "Échec de l'envoi de l'invitation",
     "pendingInvitations": "Invitations en attente",
     "invitedAs": "Invite en tant que {role}",
     "expires": "Expire le {date}",
@@ -337,81 +337,81 @@
     "inviteLinkCopied": "Lien d'invitation copie dans le presse-papiers",
     "cancelInvitation": "Annuler l'invitation",
     "cancelInvitationDescription": "Annuler l'invitation a {email} ? Ils ne pourront plus utiliser ce lien.",
-    "invitationCancelled": "Invitation annulee",
-    "failedCancelInvitation": "Echec de l'annulation de l'invitation",
+    "invitationCancelled": "Invitation annulée",
+    "failedCancelInvitation": "Échec de l'annulation de l'invitation",
     "customRoles": "Roles personnalises",
     "newRole": "Nouveau role",
     "roleName": "Nom du role",
     "roleNamePlaceholder": "Ex. Technicien",
-    "fullAdminAccess": "Acces administrateur complet (contourne toutes les verifications de permissions)",
+    "fullAdminAccess": "Accès administrateur complet (contourne toutes les verifications de permissions)",
     "permissions": "Permissions",
-    "createRole": "Creer le role",
+    "createRole": "Créer le role",
     "updateRole": "Mettre a jour le role",
     "roleCreated": "Role cree",
     "roleUpdated": "Role mis a jour",
-    "failedSaveRole": "Echec de l'enregistrement du role",
+    "failedSaveRole": "Échec de l'enregistrement du role",
     "deleteRole": "Supprimer le role",
     "deleteRoleDescription": "Supprimer le role \"{name}\" ? Les membres avec ce role perdront leurs permissions personnalisees.",
     "roleDeleted": "Role supprime",
-    "failedDeleteRole": "Echec de la suppression du role",
-    "fullAccess": "Acces complet",
+    "failedDeleteRole": "Échec de la suppression du role",
+    "fullAccess": "Accès complet",
     "permissionCount": "{count} permission",
     "permissionCountPlural": "{count} permissions",
-    "noCustomRoles": "Pas encore de roles personnalises. Creez-en un pour definir des permissions granulaires pour les membres de l'equipe.",
+    "noCustomRoles": "Pas encore de roles personnalises. Créez-en un pour définir des permissions granulaires pour les membres de l'équipe.",
     "presetAll": "Tous",
     "presetViewOnly": "Lecture seule",
     "presetWriteAll": "Lecture et écriture",
     "presetNone": "Aucun",
     "builtInRoles": "Roles integres",
-    "ownerLabel": "Proprietaire",
-    "ownerDescription": "Acces complet. Peut gerer les membres, changer les roles et supprimer l'organisation.",
+    "ownerLabel": "Propriétaire",
+    "ownerDescription": "Accès complet. Peut gérer les membres, changer les roles et supprimer l'organisation.",
     "adminLabel": "Administrateur",
-    "adminDescription": "Peut inviter/supprimer des membres et gerer les donnees partagees.",
+    "adminDescription": "Peut inviter/supprimer des membres et gérer les données partagées.",
     "memberLabel": "Membre",
-    "memberDescription": "Permissions determinees par le role personnalise assigne. Sans role personnalise, acces en lecture seule.",
+    "memberDescription": "Permissions determinees par le role personnalise assigne. Sans role personnalise, accès en lecture seule.",
     "roleAssigned": "Role mis a jour",
-    "failedAssignRole": "Echec de la mise a jour du role",
+    "failedAssignRole": "Échec de la mise a jour du role",
     "removeMemberTitle": "Retirer le membre",
-    "removeMemberDescription": "Retirer {name} de l'equipe ? Il perdra l'acces aux donnees partagees.",
+    "removeMemberDescription": "Retirer {name} de l'équipe ? Il perdra l'accès aux données partagées.",
     "removeButton": "Retirer",
     "memberRemoved": "Membre retire",
-    "failedRemoveMember": "Echec du retrait du membre",
+    "failedRemoveMember": "Échec du retrait du membre",
     "memberRoleUpdated": "Role du membre mis a jour",
-    "failedUpdateRole": "Echec de la mise a jour du role",
+    "failedUpdateRole": "Échec de la mise a jour du role",
     "removeMember": "Remove member",
     "cancelInvite": "Cancel invitation",
     "editRole": "Edit role"
   },
   "invoice": {
     "title": "Mise en page de facture",
-    "description": "Configurez ce qui apparait sur les factures PDF generees.",
+    "description": "Configurez ce qui apparait sur les factures PDF générées.",
     "tabs": {
       "general": "Général",
       "layout": "Mise en page",
       "customFields": "Champs personnalisés"
     },
-    "invoiceNumberFormat": "Format du numero de facture",
-    "invoiceNumberFormatHint": "Utilisez <code>{year}</code> pour l'annee en cours. Apercu : <bold>{preview}</bold>",
-    "quoteNumberFormat": "Format du numero de devis",
-    "quoteNumberFormatHint": "Utilisez <code>{year}</code> pour l'annee en cours. Apercu : <bold>{preview}</bold>",
+    "invoiceNumberFormat": "Format du numéro de facture",
+    "invoiceNumberFormatHint": "Utilisez <code>{year}</code> pour l'année en cours. Aperçu : <bold>{preview}</bold>",
+    "quoteNumberFormat": "Format du numéro de devis",
+    "quoteNumberFormatHint": "Utilisez <code>{year}</code> pour l'année en cours. Aperçu : <bold>{preview}</bold>",
     "quoteValidDays": "Validité du devis (jours)",
     "quoteValidDaysHint": "Les nouveaux devis reçoivent une date de validité de ce nombre de jours. 0 désactive cette option.",
     "sectionInvoices": "Factures",
     "sectionQuotes": "Devis",
-    "nextInvoiceNumber": "Prochain numero de facture",
+    "nextInvoiceNumber": "Prochain numéro de facture",
     "nextInvoiceNumberPlaceholder": "Ex. 94",
-    "nextInvoiceNumberHint": "La prochaine facture utilisera ce numero (ex. {example})",
-    "dueDays": "Jours d'echeance",
-    "dueDaysHint": "Nombre de jours avant l'echeance de la facture",
-    "visibilityTitle": "Visibilite sur la facture",
+    "nextInvoiceNumberHint": "La prochaine facture utilisera ce numéro (ex. {example})",
+    "dueDays": "Jours d'échéance",
+    "dueDaysHint": "Nombre de jours avant l'échéance de la facture",
+    "visibilityTitle": "Visibilité sur la facture",
     "companyLogo": "Logo de l'entreprise",
-    "companyLogoHint": "Afficher le logo dans l'en-tete de la facture",
+    "companyLogoHint": "Afficher le logo dans l'en-tête de la facture",
     "companyName": "Nom de l'entreprise",
-    "companyNameHint": "Afficher le nom de l'entreprise dans l'en-tete",
+    "companyNameHint": "Afficher le nom de l'entreprise dans l'en-tête",
     "bankAccount": "Compte bancaire",
     "bankAccountHint": "Afficher le compte bancaire / IBAN sur la facture",
-    "organizationNumber": "Numero d'organisation",
-    "organizationNumberHint": "Afficher le numero d'organisation dans l'en-tete",
+    "organizationNumber": "Numéro d'organisation",
+    "organizationNumberHint": "Afficher le numéro d'organisation dans l'en-tête",
     "customFooter": "Pied de page personnalise",
     "footerPlaceholder": "Merci pour votre confiance !",
     "footerHint": "Ce texte apparait en bas de chaque facture",
@@ -421,8 +421,8 @@
     "defaultMarkupPercentHint": "Les nouvelles pièces ajoutées manuellement sont préremplies avec cette marge. Laisser à 0 pour saisir les prix manuellement.",
     "markupAppliesToInventory": "Appliquer la marge aux pièces du stock",
     "markupAppliesToInventoryHint": "Lorsque activé, les pièces sélectionnées dans le stock utilisent aussi coût + marge par défaut (remplace le prix de vente du stock).",
-    "saveInvoice": "Enregistrer les parametres de facture",
-    "saved": "Parametres de facture enregistres",
+    "saveInvoice": "Enregistrer les paramètres de facture",
+    "saved": "Paramètres de facture enregistrés",
     "layoutDescription": "Personnalisez l'ordre et la visibilité des sections dans vos factures et devis.",
     "layoutSaved": "Mise en page enregistrée",
     "preview": "Aperçu",
@@ -490,10 +490,10 @@
     }
   },
   "templates": {
-    "title": "Modeles",
+    "title": "Modèles",
     "invoiceDescription": "Personnalisez l'apparence de vos factures et devis PDF.",
-    "inspectionsDescription": "Gerez les listes de controle d'inspection et les modeles multi-points.",
-    "smsDescription": "Personnalisez les messages SMS envoyes aux clients.",
+    "inspectionsDescription": "Gerez les listes de contrôle d'inspection et les modèles multi-points.",
+    "smsDescription": "Personnalisez les messages SMS envoyés aux clients.",
     "tabs": {
       "invoice": "Facture",
       "quotation": "Devis",
@@ -501,23 +501,23 @@
       "sms": "SMS",
       "layout": "Mise en page"
     },
-    "presets": "Presets de modele",
+    "presets": "Presets de modèle",
     "primaryColor": "Couleur primaire",
     "fontAndLayout": "Police et mise en page",
     "fontFamily": "Famille de police",
-    "helveticaDefault": "Helvetica (Par defaut)",
+    "helveticaDefault": "Helvetica (Par défaut)",
     "timesRoman": "Times Roman",
     "courier": "Courier",
-    "headerStyle": "Style d'en-tete",
+    "headerStyle": "Style d'en-tête",
     "logoSize": "Taille du logo",
     "standard": "Standard",
     "compact": "Compact",
     "modern": "Moderne",
-    "preview": "Apercu {name}",
+    "preview": "Aperçu {name}",
     "billTo": "Facturer a",
     "preparedFor": "Prepare pour",
-    "vehicle": "Vehicule",
-    "part": "Piece",
+    "vehicle": "Véhicule",
+    "part": "Pièce",
     "qty": "Qte",
     "price": "Prix",
     "total": "Total",
@@ -532,32 +532,32 @@
     "previewDue": "Échéance: {date}",
     "previewAdditionalInfo": "Informations supplémentaires",
     "previewThankYou": "Merci pour votre confiance",
-    "saveInvoiceTemplate": "Enregistrer le modele de facture",
-    "saveQuotationTemplate": "Enregistrer le modele de devis",
-    "invoiceTemplateSaved": "Parametres du modele de facture enregistres",
-    "quotationTemplateSaved": "Parametres du modele de devis enregistres",
-    "failedSave": "Echec de l'enregistrement des parametres",
-    "smsTemplates": "Modeles de messages SMS",
-    "smsTemplatesDescription": "Personnalisez les messages texte envoyes aux clients. Utilisez des variables comme <code>'{companyName}'</code> pour inserer des valeurs dynamiques.",
-    "reset": "Reinitialiser",
-    "invoiceReady": "Facture prete",
+    "saveInvoiceTemplate": "Enregistrer le modèle de facture",
+    "saveQuotationTemplate": "Enregistrer le modèle de devis",
+    "invoiceTemplateSaved": "Paramètres du modèle de facture enregistrés",
+    "quotationTemplateSaved": "Paramètres du modèle de devis enregistrés",
+    "failedSave": "Échec de l'enregistrement des paramètres",
+    "smsTemplates": "Modèles de messages SMS",
+    "smsTemplatesDescription": "Personnalisez les messages texte envoyés aux clients. Utilisez des variables comme <code>'{companyName}'</code> pour inserer des valeurs dynamiques.",
+    "reset": "Réinitialiser",
+    "invoiceReady": "Facture prête",
     "invoiceReadyDescription": "Envoye lors du partage d'une facture avec un client.",
-    "quoteReady": "Devis pret",
+    "quoteReady": "Devis prêt",
     "quoteReadyDescription": "Envoye lors du partage d'un devis avec un client.",
-    "inspectionReady": "Inspection prete",
+    "inspectionReady": "Inspection prête",
     "inspectionReadyDescription": "Envoye lors du partage d'un rapport d'inspection avec un client.",
     "statusInProgress": "Statut : En cours",
     "statusInProgressDescription": "Envoye lorsque le statut d'un ordre de travail passe a En cours.",
-    "statusWaitingParts": "Statut : En attente de pieces",
-    "statusWaitingPartsDescription": "Envoye lorsque le statut passe a En attente de pieces.",
-    "statusReady": "Statut : Pret a recuperer",
-    "statusReadyDescription": "Envoye lorsque le statut passe a Pret.",
+    "statusWaitingParts": "Statut : En attente de pièces",
+    "statusWaitingPartsDescription": "Envoye lorsque le statut passe a En attente de pièces.",
+    "statusReady": "Statut : Prêt a récupérer",
+    "statusReadyDescription": "Envoye lorsque le statut passe a Prêt.",
     "statusCompleted": "Statut : Termine",
     "statusCompletedDescription": "Envoye lorsque le statut passe a Termine.",
-    "paymentReceived": "Paiement recu",
+    "paymentReceived": "Paiement reçu",
     "paymentReceivedDescription": "Envoye lorsqu'un paiement est enregistre pour une facture.",
-    "saveSmsTemplates": "Enregistrer les modeles SMS",
-    "smsTemplateSaved": "Parametres des modeles SMS enregistres",
+    "saveSmsTemplates": "Enregistrer les modèles SMS",
+    "smsTemplateSaved": "Paramètres des modèles SMS enregistrés",
     "smsDefaults": {
       "invoiceReady": "Votre facture est prête. Consultez-la ici : {share_link}",
       "quoteReady": "Votre devis est prêt pour examen. Consultez-le ici : {share_link}",
@@ -579,26 +579,26 @@
       "indigo": "Indigo"
     },
     "layoutTitle": "Mise en page de la facture",
-    "layoutDescription": "Personnalisez l'ordre et la visibilite des sections dans vos factures et devis.",
-    "layoutSaved": "Mise en page enregistree",
+    "layoutDescription": "Personnalisez l'ordre et la visibilité des sections dans vos factures et devis.",
+    "layoutSaved": "Mise en page enregistrée",
     "layoutDocInvoice": "Facture",
     "layoutDocQuote": "Devis"
   },
   "payment": {
     "readMore": "En savoir plus",
-    "detailsTitle": "Details de paiement",
+    "detailsTitle": "Détails de paiement",
     "detailsDescription": "Compte bancaire et conditions de paiement sur les factures.",
     "bankAccount": "Compte bancaire (IBAN)",
     "bankAccountPlaceholder": "NO93 8601 1117 947",
-    "bankAccountHint": "Votre numero de compte bancaire ou IBAN pour recevoir les paiements",
+    "bankAccountHint": "Votre numéro de compte bancaire ou IBAN pour recevoir les paiements",
     "paymentTerms": "Conditions de paiement",
     "paymentTermsPlaceholder": "Net 14 jours",
-    "paymentTermsHint": "Texte des conditions de paiement sur les factures (ex. \"Net 14 jours\", \"A reception\")",
+    "paymentTermsHint": "Texte des conditions de paiement sur les factures (ex. \"Net 14 jours\", \"A réception\")",
     "onlinePayments": "Paiements en ligne",
     "onlinePaymentsDescription": "Permettez aux clients de payer les factures en ligne via des liens de facture partages.",
     "stripe": "Stripe",
     "stripeDescription": "Accepter les paiements par carte de credit/debit",
-    "secretKey": "Cle secrete",
+    "secretKey": "Cle secrète",
     "publishableKey": "Cle publique",
     "webhookSecret": "Secret du webhook",
     "webhookUrl": "URL du webhook",
@@ -608,10 +608,10 @@
     "clientId": "ID client",
     "clientSecret": "Secret client",
     "subscriptionKey": "Cle d'abonnement",
-    "merchantSerialNumber": "Numero de serie du commercant",
+    "merchantSerialNumber": "Numéro de serie du commercant",
     "testMode": "Mode test",
     "callbackUrl": "URL de callback",
-    "termsOfSale": "Conditions de vente (Salgsvilkar)",
+    "termsOfSale": "Conditions de vente (Salgsvilkår)",
     "termsOfSaleDescription": "Requis par Vipps. Les clients peuvent les consulter depuis la page de facture.",
     "externalTermsUrl": "URL des conditions externes (optionnel)",
     "externalTermsUrlHint": "Lien vers une page externe avec vos conditions. Si defini, la facture redirigera ici.",
@@ -621,18 +621,18 @@
     "paypalDescription": "Accepter les paiements PayPal",
     "sandboxMode": "Mode sandbox",
     "paypalWebhookHint": "Ajoutez cette URL dans votre tableau de bord PayPal sous Webhooks. Abonnez-vous a <code>PAYMENT.CAPTURE.COMPLETED</code>.",
-    "savePayment": "Enregistrer les parametres de paiement",
-    "saved": "Parametres de paiement enregistres"
+    "savePayment": "Enregistrer les paramètres de paiement",
+    "saved": "Paramètres de paiement enregistrés"
   },
   "tax": {
     "title": "Taxe",
     "description": "Configurez le taux de taxe applique aux factures, devis et bons de travail.",
     "modeLabel": "Mode de taxe",
-    "modeHint": "Choisissez si les prix des lignes sont saisis avec la taxe deja incluse (inclusif) ou hors taxe (exclusif). Les enregistrements existants conservent le mode avec lequel ils ont ete crees.",
+    "modeHint": "Choisissez si les prix des lignes sont saisis avec la taxe déjà incluse (inclusif) ou hors taxe (exclusif). Les enregistrements existants conservent le mode avec lequel ils ont ete crees.",
     "modeExclusive": "Exclusif",
     "modeInclusive": "Inclusif",
     "modeExclusiveExample": "Exemple : 100 + {rate}% de taxe = 100 + taxe en plus.",
-    "modeInclusiveExample": "Exemple : 100 inclut deja {rate}% de taxe — la part de taxe est calculee en sens inverse.",
+    "modeInclusiveExample": "Exemple : 100 inclut déjà {rate}% de taxe — la part de taxe est calculee en sens inverse.",
     "labelLabel": "Libellé de taxe",
     "labelHint": "Nom personnalisé affiché sur les factures et devis (p. ex. TVA, GST). Laisser vide pour utiliser le libellé localisé par défaut.",
     "labelPlaceholder": "TVA",
@@ -657,26 +657,26 @@
   },
   "currency": {
     "title": "Devise et taxes",
-    "description": "Devise par defaut et taux d'imposition pour les factures et les dossiers de service.",
+    "description": "Devise par défaut et taux d'imposition pour les factures et les dossiers de service.",
     "currencyLabel": "Devise",
     "searchCurrency": "Rechercher une devise...",
     "noCurrencyFound": "Aucune devise trouvée.",
-    "previewLabel": "Apercu : {value}",
+    "previewLabel": "Aperçu : {value}",
     "enableTax": "Activer les taxes",
     "enableTaxHint": "Afficher le calcul des taxes sur les factures et les dossiers de service",
-    "defaultTaxRate": "Taux d'imposition par defaut (%)",
-    "defaultTaxRateHint": "Rempli automatiquement lors de la creation de nouveaux dossiers de service",
+    "defaultTaxRate": "Taux d'imposition par défaut (%)",
+    "defaultTaxRateHint": "Rempli automatiquement lors de la création de nouveaux dossiers de service",
     "applyTaxToExistingLabel": "Appliquer la taxe aux enregistrements existants",
-    "applyTaxToExistingHint": "Applique le taux de taxe par defaut aux bons de travail et devis existants sans taux de taxe. Eligibles : {serviceRecords} bons de travail, {quotes} devis.",
+    "applyTaxToExistingHint": "Applique le taux de taxe par défaut aux bons de travail et devis existants sans taux de taxe. Éligibles : {serviceRecords} bons de travail, {quotes} devis.",
     "applyTaxToExistingButton": "Appliquer la taxe aux enregistrements existants",
     "applyTaxConfirmTitle": "Appliquer la taxe aux enregistrements existants ?",
-    "applyTaxConfirmDescription": "Cela definira le taux de taxe a {rate}% sur {serviceRecords} bons de travail et {quotes} devis qui n'ont actuellement aucun taux de taxe, et recalculera leurs totaux. Les enregistrements avec un taux personnalise ne seront pas modifies. Cette action est irreversible.",
+    "applyTaxConfirmDescription": "Cela definira le taux de taxe a {rate}% sur {serviceRecords} bons de travail et {quotes} devis qui n'ont actuellement aucun taux de taxe, et recalculera leurs totaux. Les enregistrements avec un taux personnalise ne seront pas modifies. Cette action est irréversible.",
     "applyTaxConfirmLabel": "Appliquer la taxe",
     "applyTaxSuccess": "{serviceRecords} bons de travail et {quotes} devis mis a jour.",
     "applyTaxFailed": "Impossible d'appliquer la taxe aux enregistrements existants",
-    "applyTaxNoRecords": "Aucun enregistrement ne necessite le taux de taxe par defaut.",
-    "saveSettings": "Enregistrer les parametres",
-    "saved": "Parametres enregistres",
+    "applyTaxNoRecords": "Aucun enregistrement ne necessite le taux de taxe par défaut.",
+    "saveSettings": "Enregistrer les paramètres",
+    "saved": "Paramètres enregistrés",
     "currencies": {
       "USD": "Dollar americain",
       "EUR": "Euro",
@@ -796,9 +796,9 @@
     "delete": "Delete"
   },
   "workshop": {
-    "title": "Valeurs par defaut de l'atelier",
-    "description": "Valeurs par defaut lors de la creation de nouveaux dossiers de service.",
-    "defaultTechnician": "Nom du technicien par defaut",
+    "title": "Valeurs par défaut de l'atelier",
+    "description": "Valeurs par défaut lors de la création de nouveaux dossiers de service.",
+    "defaultTechnician": "Nom du technicien par défaut",
     "technicianPlaceholder": "Rechercher technicien...",
     "failedCreateTech": "Échec de la création du technicien",
     "creating": "Création...",
@@ -806,11 +806,11 @@
     "newTechPlaceholder": "Nom du technicien",
     "addTech": "Ajouter",
     "addNewTech": "+ Ajouter nouveau",
-    "defaultLaborRate": "Taux horaire par defaut",
+    "defaultLaborRate": "Taux horaire par défaut",
     "laborRatePlaceholder": "75,00",
     "unitsTitle": "Unites",
     "unitsDescription": "Choisissez entre metrique (km, litres) et imperial (miles, gallons).",
-    "unitSystem": "Systeme d'unites",
+    "unitSystem": "Système d'unites",
     "metric": "Metrique (km, litres)",
     "imperial": "Imperial (miles, gallons)",
     "distanceLabel": "Distance",
@@ -819,8 +819,8 @@
     "liters": "litres (L)",
     "miles": "miles (mi)",
     "gallons": "gallons (gal)",
-    "saveWorkshop": "Enregistrer les parametres de l'atelier",
-    "saved": "Parametres de l'atelier enregistres",
+    "saveWorkshop": "Enregistrer les paramètres de l'atelier",
+    "saved": "Paramètres de l'atelier enregistrés",
     "assignDialogTitle": "Assigner aux ordres de travail existants ?",
     "assignDialogDescription": "Voulez-vous assigner {name} à tous les ordres de travail qui n'ont pas de technicien ?",
     "assignDialogConfirm": "Oui, assigner",
@@ -828,8 +828,8 @@
     "assignedUnassigned": "{count, plural, =0 {Aucun ordre non assigné trouvé} one {1 ordre assigné} other {# ordres assignés}}",
     "assignFailed": "Échec de l'assignation des ordres",
     "noTechnician": "Aucun",
-    "weekStartDay": "Debut de semaine",
-    "workDayStart": "Debut de journee",
+    "weekStartDay": "Début de semaine",
+    "workDayStart": "Début de journee",
     "workDayEnd": "Fin de journee",
     "weekDays": {
       "0": "Dimanche",
@@ -843,15 +843,15 @@
   },
   "maintenance": {
     "title": "Maintenance predictive",
-    "description": "Predisez le kilometrage actuel de chaque vehicule a partir de son historique de service et affichez les vehicules necessitant ou approchant leur prochain service.",
+    "description": "Predisez le kilométrage actuel de chaque véhicule a partir de son historique de service et affichez les véhicules necessitant ou approchant leur prochain service.",
     "enablePredicted": "Activer la maintenance predictive",
-    "enablePredictedHint": "Afficher les predictions sur les details du vehicule et le tableau de bord",
+    "enablePredictedHint": "Afficher les predictions sur les détails du véhicule et le tableau de bord",
     "serviceInterval": "Intervalle de service ({unit})",
     "serviceIntervalHint": "Distance entre les services recommandes",
     "approachingThreshold": "Seuil d'approche ({unit})",
     "approachingThresholdHint": "Afficher un avertissement a cette distance du prochain service",
-    "saveMaintenance": "Enregistrer les parametres de maintenance",
-    "saved": "Parametres de maintenance enregistres"
+    "saveMaintenance": "Enregistrer les paramètres de maintenance",
+    "saved": "Paramètres de maintenance enregistrés"
   },
   "appearance": {
     "title": "Apparence",
@@ -869,30 +869,30 @@
     "searchTimezone": "Rechercher un fuseau horaire...",
     "noTimezoneFound": "Aucun fuseau horaire trouvé.",
     "timezoneHint": "En mode auto-detection, chaque utilisateur voit les dates dans le fuseau horaire de son navigateur.",
-    "previewLabel": "Apercu",
+    "previewLabel": "Aperçu",
     "dateLabel": "Date",
     "dateTimeLabel": "Date et heure",
-    "saveSettings": "Enregistrer les parametres",
-    "saved": "Parametres enregistres"
+    "saveSettings": "Enregistrer les paramètres",
+    "saved": "Paramètres enregistrés"
   },
   "data": {
-    "title": "Gestion des donnees",
-    "description": "Exportez vos donnees comme sauvegarde ou importez un fichier precedemment exporte.",
-    "exportTitle": "Exporter les donnees",
-    "exportDescription": "Telechargez vos donnees sous forme de fichier zip. Selectionnez les categories a inclure.",
+    "title": "Gestion des données",
+    "description": "Exportez vos données comme sauvegarde ou importez un fichier precedemment exporte.",
+    "exportTitle": "Exporter les données",
+    "exportDescription": "Telechargez vos données sous forme de fichier zip. Sélectionnez les catégories a inclure.",
     "includeInBackup": "Inclure dans la sauvegarde :",
-    "selectAll": "Tout selectionner",
-    "deselectAll": "Tout deselectionner",
-    "optSettings": "Parametres",
+    "selectAll": "Tout sélectionner",
+    "deselectAll": "Tout désélectionner",
+    "optSettings": "Paramètres",
     "optSettingsDesc": "Info atelier, config facturation, fournisseurs de paiement",
     "optCustomers": "Clients",
     "optCustomersDesc": "Dossiers clients et informations de contact",
-    "optVehicles": "Vehicules et dossiers de service",
-    "optVehiclesDesc": "Vehicules, notes, journaux de carburant, rappels, dossiers de service",
+    "optVehicles": "Véhicules et dossiers de service",
+    "optVehiclesDesc": "Véhicules, notes, journaux de carburant, rappels, dossiers de service",
     "optQuotes": "Devis",
-    "optQuotesDesc": "Devis avec pieces et main-d'oeuvre",
+    "optQuotesDesc": "Devis avec pièces et main-d'oeuvre",
     "optInventory": "Inventaire",
-    "optInventoryDesc": "Pieces d'inventaire et niveaux de stock",
+    "optInventoryDesc": "Pièces d'inventaire et niveaux de stock",
     "optTechnicians": "Techniciens",
     "optTechniciansDesc": "Profils des techniciens et affectations au tableau de travail",
     "optInspections": "Inspections",
@@ -906,54 +906,54 @@
     "optNotifications": "Notifications",
     "optNotificationsDesc": "Notifications système",
     "optCustomFields": "Champs personnalises",
-    "optCustomFieldsDesc": "Definitions et valeurs des champs personnalises",
+    "optCustomFieldsDesc": "Définitions et valeurs des champs personnalises",
     "optFiles": "Fichiers telecharges",
-    "optFilesDesc": "Images, pieces jointes de service, logo",
-    "filesWarning": "Inclut les fichiers telecharges — la sauvegarde peut etre plus volumineuse",
-    "downloadBackup": "Telecharger la sauvegarde",
-    "exportComplete": "Exportation terminee",
+    "optFilesDesc": "Images, pièces jointes de service, logo",
+    "filesWarning": "Inclut les fichiers telecharges — la sauvegarde peut être plus volumineuse",
+    "downloadBackup": "Télécharger la sauvegarde",
+    "exportComplete": "Exportation terminée",
     "exportCompleteMessage": "Votre fichier de sauvegarde a ete telecharge.",
-    "exportFailed": "Exportation echouee",
-    "exportFailedMessage": "Impossible d'exporter vos donnees. Veuillez reessayer.",
-    "importTitle": "Importer des donnees",
-    "importWarning": "L'importation va <bold>remplacer toutes les donnees existantes</bold> par le contenu du fichier de sauvegarde. Cette action ne peut pas etre annulee.",
+    "exportFailed": "Exportation échouée",
+    "exportFailedMessage": "Impossible d'exporter vos données. Veuillez réessayer.",
+    "importTitle": "Importer des données",
+    "importWarning": "L'importation va <bold>remplacer toutes les données existantes</bold> par le contenu du fichier de sauvegarde. Cette action ne peut pas être annulée.",
     "importFileHint": "Supporte les fichiers de sauvegarde .zip (v2) et .json (v1).",
-    "uploadRestore": "Telecharger et restaurer",
-    "importComplete": "Importation terminee",
-    "importCompleteMessage": "Toutes les donnees ont ete restaurees a partir du fichier de sauvegarde.",
-    "importFailed": "Importation echouee",
+    "uploadRestore": "Télécharger et restaurer",
+    "importComplete": "Importation terminée",
+    "importCompleteMessage": "Toutes les données ont ete restaurees a partir du fichier de sauvegarde.",
+    "importFailed": "Importation échouée",
     "importFromOther": "Importer d'autres services",
-    "importFromOtherDescription": "Migrez vos donnees depuis une autre plateforme. Selectionnez un service pour commencer.",
+    "importFromOtherDescription": "Migrez vos données depuis une autre plateforme. Sélectionnez un service pour commencer.",
     "lubelogTitle": "Importer de LubeLog",
-    "lubelogExportSteps": "Comment exporter vos donnees LubeLog :",
+    "lubelogExportSteps": "Comment exporter vos données LubeLog :",
     "lubelogStep1": "Ouvrez votre instance LubeLog",
     "lubelogStep2": "Allez dans <bold>Settings</bold>",
-    "lubelogStep3": "Cliquez sur <bold>Make Backup</bold> pour telecharger un fichier zip",
+    "lubelogStep3": "Cliquez sur <bold>Make Backup</bold> pour télécharger un fichier zip",
     "lubelogStep4": "Telechargez le fichier zip ci-dessous",
-    "lubelogAddNote": "Cela va <bold>ajouter des donnees</bold> a cote de vos enregistrements existants — rien ne sera supprime.",
+    "lubelogAddNote": "Cela va <bold>ajouter des données</bold> a cote de vos enregistrements existants — rien ne sera supprime.",
     "lubelogImported": "Ce qui sera importe :",
-    "lubelogVehicles": "Vehicules",
-    "lubelogServiceRecords": "Dossiers de service avec pieces jointes",
+    "lubelogVehicles": "Véhicules",
+    "lubelogServiceRecords": "Dossiers de service avec pièces jointes",
     "lubelogNotes": "Notes",
     "lubelogFileHint": "Fichiers jusqu'a 200 Mo supportes.",
-    "lubelogImportComplete": "Importation LubeLog terminee",
+    "lubelogImportComplete": "Importation LubeLog terminée",
     "cancel": "Annuler",
     "import": "Importer",
     "invoiceNinjaTitle": "Importer de Invoice Ninja",
-    "invoiceNinjaExportSteps": "Comment exporter vos donnees Invoice Ninja :",
+    "invoiceNinjaExportSteps": "Comment exporter vos données Invoice Ninja :",
     "invoiceNinjaStep1": "Ouvrez votre instance Invoice Ninja",
     "invoiceNinjaStep2": "Allez dans <bold>Settings > Import | Export</bold>",
-    "invoiceNinjaStep3": "Cliquez sur <bold>Export</bold> pour telecharger un fichier zip",
+    "invoiceNinjaStep3": "Cliquez sur <bold>Export</bold> pour télécharger un fichier zip",
     "invoiceNinjaStep4": "Telechargez le fichier zip ci-dessous",
-    "invoiceNinjaAddNote": "Cela va <bold>ajouter des donnees</bold> a cote de vos enregistrements existants — rien ne sera supprime.",
+    "invoiceNinjaAddNote": "Cela va <bold>ajouter des données</bold> a cote de vos enregistrements existants — rien ne sera supprime.",
     "invoiceNinjaImported": "Ce qui sera importe :",
     "invoiceNinjaCustomers": "Clients",
     "invoiceNinjaProducts": "Produits (inventaire)",
-    "invoiceNinjaInvoices": "Factures comme dossiers de service (pieces et main-d'oeuvre)",
-    "invoiceNinjaDocuments": "Documents et pieces jointes",
+    "invoiceNinjaInvoices": "Factures comme dossiers de service (pièces et main-d'oeuvre)",
+    "invoiceNinjaDocuments": "Documents et pièces jointes",
     "invoiceNinjaPayments": "Paiements",
     "invoiceNinjaFileHint": "Telechargez un fichier d'export zip Invoice Ninja. Fichiers jusqu'a 200 Mo supportes.",
-    "invoiceNinjaImportComplete": "Importation Invoice Ninja terminee",
+    "invoiceNinjaImportComplete": "Importation Invoice Ninja terminée",
     "backupStatus": {
       "title": "Protection des données",
       "description": "Nous prenons vos données au sérieux. Toutes les données de votre organisation, y compris les clients, les véhicules, les ordres de travail, les factures et les fichiers téléversés, sont sauvegardées automatiquement toutes les heures. Les sauvegardes sont protégées par un chiffrement AES-256 avant de quitter notre infrastructure et stockées dans un emplacement séparé et sécurisé, de sorte qu'aucun tiers ne peut les lire.",
@@ -969,17 +969,17 @@
     "status": "Statut :",
     "active": "Active",
     "inactive": "Inactive",
-    "lastChecked": "Derniere verification : {date}",
+    "lastChecked": "Dernière vérification : {date}",
     "enterLicenseKey": "Entrez la cle de licence",
     "validate": "Valider",
-    "validated": "Licence validee avec succes",
+    "validated": "Licence validee avec succès",
     "invalid": "Cle de licence invalide",
-    "failedValidate": "Echec de la validation de la licence",
-    "keyHint": "Chaque cle de licence est liee a cette organisation et ne peut pas etre partagee entre plusieurs organisations."
+    "failedValidate": "Échec de la validation de la licence",
+    "keyHint": "Chaque cle de licence est liée a cette organisation et ne peut pas être partagee entre plusieurs organisations."
   },
   "about": {
     "title": "A propos de Torqvoice",
-    "description": "Torqvoice est votre solution complete de gestion de services vehicules. Suivez l'historique de service, les reparations et plus encore.",
+    "description": "Torqvoice est votre solution complete de gestion de services véhicules. Suivez l'historique de service, les réparations et plus encore.",
     "version": "Version",
     "website": "Site web",
     "documentation": "Documentation",
@@ -987,18 +987,18 @@
   },
   "portal": {
     "title": "Portail client",
-    "description": "Permettez a vos clients de se connecter et de consulter leurs vehicules, factures, devis et inspections. Les clients peuvent egalement soumettre des demandes de service.",
+    "description": "Permettez a vos clients de se connecter et de consulter leurs véhicules, factures, devis et inspections. Les clients peuvent également soumettre des demandes de service.",
     "enablePortal": "Activer le portail client",
     "enablePortalHint": "Les clients avec une adresse e-mail peuvent se connecter via un lien magique",
     "enabled": "Portail client active",
     "disabled": "Portail client desactive",
-    "failedUpdate": "Echec de la mise a jour du parametre",
+    "failedUpdate": "Échec de la mise a jour du paramètre",
     "customSlug": "Slug personnalise du portail",
     "save": "Enregistrer",
     "slugSaved": "Slug du portail enregistre",
     "slugRemoved": "Slug du portail supprime",
-    "failedUpdateSlug": "Echec de la mise a jour du slug",
-    "slugHint": "Lettres minuscules, chiffres, tirets et underscores. 3-48 caracteres. Laissez vide pour utiliser l'URL par defaut.",
+    "failedUpdateSlug": "Échec de la mise a jour du slug",
+    "slugHint": "Lettres minuscules, chiffres, tirets et underscores. 3-48 caractères. Laissez vide pour utiliser l'URL par défaut.",
     "portalUrl": "URL du portail",
     "urlCopied": "URL du portail copiee",
     "shareHint": "Partagez ce lien avec vos clients pour qu'ils puissent se connecter a leur portail.",
@@ -1039,11 +1039,11 @@
   },
   "email": {
     "title": "Fournisseur de messagerie",
-    "description": "Configurez le fournisseur de messagerie de votre organisation pour l'envoi de factures et devis. Sans configuration, le fournisseur de messagerie par defaut de la plateforme sera utilise.",
+    "description": "Configurez le fournisseur de messagerie de votre organisation pour l'envoi de factures et devis. Sans configuration, le fournisseur de messagerie par défaut de la plateforme sera utilise.",
     "useCustomLabel": "Utiliser un fournisseur de messagerie personnalise",
-    "useCustomHint": "Activer pour configurer votre propre fournisseur de messagerie au lieu du fournisseur par defaut de la plateforme",
-    "platformDefaultInfo": "Votre organisation utilise le fournisseur de messagerie par defaut de la plateforme. Les factures et devis seront envoyes avec les parametres de messagerie configures par la plateforme.",
-    "smtpHost": "Hote SMTP",
+    "useCustomHint": "Activer pour configurer votre propre fournisseur de messagerie au lieu du fournisseur par défaut de la plateforme",
+    "platformDefaultInfo": "Votre organisation utilise le fournisseur de messagerie par défaut de la plateforme. Les factures et devis seront envoyés avec les paramètres de messagerie configures par la plateforme.",
+    "smtpHost": "Hôte SMTP",
     "smtpHostPlaceholder": "smtp.example.com",
     "smtpPort": "Port SMTP",
     "smtpPortPlaceholder": "587",
@@ -1053,12 +1053,12 @@
     "fromEmail": "E-mail d'expedition",
     "fromEmailPlaceholder": "noreply@example.com",
     "fromEmailDomainPlaceholder": "noreply@votredomaine.com",
-    "fromName": "Nom d'expediteur",
+    "fromName": "Nom d'expéditeur",
     "fromNamePlaceholder": "Votre Atelier",
     "tlsConnection": "Connexion TLS (Port 465)",
-    "tlsConnectionHint": "Activer pour TLS implicite. Desactiver pour STARTTLS (port 587/25).",
-    "verifyTlsCerts": "Verifier les certificats TLS",
-    "verifyTlsCertsHint": "Desactiver pour autoriser les certificats auto-signes (non recommande en production)",
+    "tlsConnectionHint": "Activer pour TLS implicite. Désactiver pour STARTTLS (port 587/25).",
+    "verifyTlsCerts": "Vérifier les certificats TLS",
+    "verifyTlsCertsHint": "Désactiver pour autoriser les certificats auto-signes (non recommande en production)",
     "requireTlsUpgrade": "Exiger la mise a niveau TLS",
     "requireTlsUpgradeHint": "Forcer la mise a niveau TLS sur les connexions STARTTLS",
     "apiKey": "Cle API",
@@ -1068,31 +1068,31 @@
     "apiKeyHintMailgun": "Obtenez votre cle API sur",
     "domain": "Domaine",
     "domainPlaceholder": "mg.votredomaine.com",
-    "region": "Region",
+    "region": "Région",
     "apiKeyHintSendGrid": "Obtenez votre cle API sur",
-    "accessKeyId": "ID de cle d'acces",
-    "secretAccessKey": "Cle d'acces secrete",
-    "awsRegion": "Region AWS",
+    "accessKeyId": "ID de cle d'accès",
+    "secretAccessKey": "Cle d'accès secrète",
+    "awsRegion": "Région AWS",
     "awsRegionPlaceholder": "us-east-1",
-    "awsRegionHint": "La region AWS ou SES est configure (ex. us-east-1, eu-west-1)",
+    "awsRegionHint": "La région AWS ou SES est configure (ex. us-east-1, eu-west-1)",
     "sendTestEmail": "Envoyer un e-mail de test",
-    "testEmailHint": "Enregistrez d'abord les parametres, puis envoyez un e-mail de test a votre compte",
-    "saveSettings": "Enregistrer les parametres",
-    "savedDefault": "Parametres de messagerie enregistres — fournisseur par defaut de la plateforme utilise",
-    "saved": "Parametres de messagerie enregistres",
-    "failedSave": "Echec de l'enregistrement des parametres",
+    "testEmailHint": "Enregistrez d'abord les paramètres, puis envoyez un e-mail de test a votre compte",
+    "saveSettings": "Enregistrer les paramètres",
+    "savedDefault": "Paramètres de messagerie enregistrés — fournisseur par défaut de la plateforme utilise",
+    "saved": "Paramètres de messagerie enregistrés",
+    "failedSave": "Échec de l'enregistrement des paramètres",
     "testSentTo": "E-mail de test envoye a {email}",
-    "testFailed": "Echec du test de messagerie"
+    "testFailed": "Échec du test de messagerie"
   },
   "sms": {
     "title": "Fournisseur SMS",
-    "description": "Configurez votre fournisseur SMS pour la messagerie bidirectionnelle avec les clients. Chaque organisation utilise son propre fournisseur SMS et numero de telephone.",
+    "description": "Configurez votre fournisseur SMS pour la messagerie bidirectionnelle avec les clients. Chaque organisation utilise son propre fournisseur SMS et numéro de téléphone.",
     "enableLabel": "Activer la messagerie SMS",
     "enableHint": "Envoyer et recevoir des SMS avec les clients",
-    "disabledInfo": "La messagerie SMS est desactivee. Activez-la pour envoyer et recevoir des messages texte avec vos clients.",
-    "phoneNumber": "Numero de telephone",
+    "disabledInfo": "La messagerie SMS est désactivée. Activez-la pour envoyer et recevoir des messages texte avec vos clients.",
+    "phoneNumber": "Numéro de téléphone",
     "phoneNumberPlaceholder": "+33612345678",
-    "phoneNumberHint": "Votre numero de telephone compatible SMS au format E.164 (ex. +33612345678)",
+    "phoneNumberHint": "Votre numéro de téléphone compatible SMS au format E.164 (ex. +33612345678)",
     "accountSid": "SID du compte",
     "authToken": "Jeton d'authentification",
     "apiKey": "Cle API",
@@ -1100,17 +1100,17 @@
     "webhookUrl": "URL du webhook",
     "webhookUrlHint": "Configurez cette URL dans votre tableau de bord {provider} pour recevoir les SMS entrants.",
     "webhookCopied": "URL du webhook copiee",
-    "testPhoneNumber": "Numero de telephone de test",
+    "testPhoneNumber": "Numéro de téléphone de test",
     "testPhonePlaceholder": "+33698765432",
     "sendTestSms": "Envoyer un SMS de test",
-    "testSmsHint": "Enregistrez d'abord les parametres, puis entrez un numero de destinataire pour envoyer un SMS de test",
-    "enterTestPhone": "Entrez un numero de telephone pour envoyer le SMS de test",
-    "saveSettings": "Enregistrer les parametres",
-    "savedDisabled": "Parametres SMS enregistres — SMS desactive",
-    "saved": "Parametres SMS enregistres",
-    "failedSave": "Echec de l'enregistrement des parametres",
+    "testSmsHint": "Enregistrez d'abord les paramètres, puis entrez un numéro de destinataire pour envoyer un SMS de test",
+    "enterTestPhone": "Entrez un numéro de téléphone pour envoyer le SMS de test",
+    "saveSettings": "Enregistrer les paramètres",
+    "savedDisabled": "Paramètres SMS enregistrés — SMS desactive",
+    "saved": "Paramètres SMS enregistrés",
+    "failedSave": "Échec de l'enregistrement des paramètres",
     "testSentTo": "SMS de test envoye a {phone}",
-    "testFailed": "Echec du test SMS",
+    "testFailed": "Échec du test SMS",
     "copyWebhookUrl": "Copy webhook URL"
   },
   "subscription": {
@@ -1182,23 +1182,23 @@
   "ai": {
     "title": "Assistant IA",
     "readMore": "Read More",
-    "description": "Configurez l'IA pour generer automatiquement des notes de service et resumer l'historique du vehicule.",
+    "description": "Configurez l'IA pour générer automatiquement des notes de service et resumer l'historique du véhicule.",
     "enableLabel": "Activer l'IA",
-    "enableHint": "Utiliser les fonctionnalites alimentees par l'IA dans toute l'application",
-    "disabledInfo": "Activez l'IA pour utiliser des fonctionnalites telles que les descriptions de service generees automatiquement et les resumes d'historique de vehicule.",
+    "enableHint": "Utiliser les fonctionnalités alimentees par l'IA dans toute l'application",
+    "disabledInfo": "Activez l'IA pour utiliser des fonctionnalités telles que les descriptions de service générées automatiquement et les resumes d'historique de véhicule.",
     "provider": "Fournisseur IA",
     "apiKey": "Cle API",
-    "apiKeyHint": "Votre cle API {provider}. Stockee en toute securite et jamais partagee.",
-    "model": "Modele",
+    "apiKeyHint": "Votre cle API {provider}. Stockee en toute sécurité et jamais partagee.",
+    "model": "Modèle",
     "testConnection": "Tester la connexion",
-    "testSuccess": "Connexion IA reussie !",
-    "testFailed": "Echec de la connexion. Verifiez votre cle API et le modele.",
-    "saved": "Parametres IA enregistres",
-    "failedSave": "Echec de l'enregistrement des parametres IA",
-    "saveSettings": "Enregistrer les parametres",
-    "featuresTitle": "Fonctionnalites IA incluses :",
-    "featureServiceNotes": "Generation automatique de descriptions de service professionnelles a partir des pieces et de la main-d'oeuvre",
-    "featureHistorySummary": "Resume de l'historique de service du vehicule en un clic",
+    "testSuccess": "Connexion IA réussie !",
+    "testFailed": "Échec de la connexion. Vérifiez votre cle API et le modèle.",
+    "saved": "Paramètres IA enregistrés",
+    "failedSave": "Échec de l'enregistrement des paramètres IA",
+    "saveSettings": "Enregistrer les paramètres",
+    "featuresTitle": "Fonctionnalités IA incluses :",
+    "featureServiceNotes": "Génération automatique de descriptions de service professionnelles a partir des pièces et de la main-d'oeuvre",
+    "featureHistorySummary": "Resume de l'historique de service du véhicule en un clic",
     "cost": {
       "high": "Coût élevé",
       "medium": "Moyen",

+ 11 - 1
messages/it/audit.json

@@ -98,6 +98,16 @@
     "webhook_create": "Webhook creato",
     "webhook_update": "Webhook aggiornato",
     "webhook_delete": "Webhook eliminato",
-    "webhook_rotateSecret": "Segreto webhook ruotato"
+    "webhook_rotateSecret": "Segreto webhook ruotato",
+    "inspection_reopen": "Ispezione riaperta",
+    "inspectionTemplate_import": "Modello di ispezione importato",
+    "scheduled_message_create": "Messaggio pianificato creato",
+    "scheduled_message_update": "Messaggio pianificato aggiornato",
+    "scheduled_message_delete": "Messaggio pianificato eliminato",
+    "scheduled_message_cancel": "Messaggio pianificato annullato",
+    "scheduled_message_send_now": "Messaggio pianificato inviato",
+    "settings_applyTaxRate": "Aliquota applicata",
+    "settings_convertToExclusive": "Prezzi convertiti in imponibile",
+    "settings_convertToInclusive": "Prezzi convertiti in ivato"
   }
 }

+ 24 - 0
messages/it/common.json

@@ -52,5 +52,29 @@
     "updated": "Torqvoice è stato aggiornato alla versione {version}",
     "whatsNew": "Scopri le novità",
     "dismiss": "Chiudi"
+  },
+  "pagination": {
+    "showing": "{start}–{end} di {total}",
+    "perPage": "per pagina",
+    "pageOf": "Pagina {page} di {pages}",
+    "first": "Prima pagina",
+    "previous": "Pagina precedente",
+    "next": "Pagina successiva",
+    "last": "Ultima pagina"
+  },
+  "shared": {
+    "close": "Chiudi",
+    "viewPlans": "Vedi i piani",
+    "manageLicense": "Gestisci licenza",
+    "installOwn": "Installa la tua",
+    "install": "Installa",
+    "installApp": "Installa l'app",
+    "installTorqvoice": "Installa Torqvoice",
+    "dismiss": "Chiudi",
+    "useCamera": "Usa la fotocamera",
+    "enterManually": "Inserisci manualmente",
+    "enterBarcode": "Inserisci codice a barre…",
+    "scanHint": "Punta la fotocamera su un codice a barre oppure inseriscilo manualmente",
+    "iosInstallHint": "Tocca Condividi > Aggiungi a Home"
   }
 }

+ 396 - 0
messages/it/inspections.json

@@ -0,0 +1,396 @@
+{
+  "grades": {
+    "eu": {
+      "pass": "Nessuna carenza",
+      "attention": "Carenza lieve",
+      "fail": "Carenza grave",
+      "dangerous": "Carenza pericolosa",
+      "not_inspected": "Non controllato"
+    },
+    "basic": {
+      "pass": "Superato",
+      "attention": "Attenzione",
+      "fail": "Non superato",
+      "dangerous": "Pericoloso",
+      "not_inspected": "Non controllato"
+    },
+    "short": {
+      "pass": "OK",
+      "attention": "Lieve",
+      "fail": "Grave",
+      "dangerous": "Pericolosa",
+      "not_inspected": "–"
+    },
+    "hint": {
+      "pass": "Conforme ai requisiti. Nulla da segnalare.",
+      "attention": "Nessun effetto rilevante sulla sicurezza. Il veicolo è idoneo; riparare quando conviene.",
+      "fail": "Può pregiudicare la sicurezza o incidere sull'ambiente. Il veicolo non è idoneo; riparare e ricontrollare.",
+      "dangerous": "Pericolo diretto e immediato per la sicurezza stradale. Il veicolo non deve circolare.",
+      "not_inspected": "Questo punto non è stato eseguito."
+    }
+  },
+  "results": {
+    "pass": "Esito favorevole",
+    "pass_minor": "Favorevole con carenze lievi",
+    "fail": "Non favorevole — carenze gravi",
+    "fail_dangerous": "Non favorevole — carenze pericolose",
+    "incomplete": "Non completato",
+    "detail": {
+      "pass": "Non sono state riscontrate carenze.",
+      "pass_minor": "Il veicolo è idoneo. Riparare le carenze lievi senza indebito ritardo.",
+      "fail": "Il veicolo non è idoneo. Le carenze devono essere riparate e il veicolo deve essere ricontrollato.",
+      "fail_dangerous": "Il veicolo non deve circolare su strada pubblica finché le carenze pericolose non sono riparate.",
+      "incomplete": "Questo controllo è ancora in corso."
+    }
+  },
+  "page": {
+    "back": "Ispezioni",
+    "statusInProgress": "In corso",
+    "statusCompleted": "Chiusa",
+    "complete": "Chiudi",
+    "reopen": "Riapri",
+    "share": "Condividi",
+    "save": "Salva",
+    "saving": "Salvataggio…",
+    "savedAt": "Salvato {time}",
+    "savesAsYouGo": "Salva mentre lavori",
+    "saveFailed": "Alcune modifiche non sono state salvate",
+    "allSaved": "Tutte le modifiche sono salvate",
+    "moreActions": "Altre azioni",
+    "downloadCertificate": "Scarica il verbale",
+    "manageTemplates": "Gestisci modelli",
+    "delete": "Elimina",
+    "createWorkOrder": "Crea commessa",
+    "viewWorkOrder": "Vedi commessa",
+    "createQuote": "Crea preventivo",
+    "viewQuote": "Vedi preventivo",
+    "needsDefect": "Disponibile quando è registrata una carenza",
+    "workOrderRaised": "Commessa {number} creata da questa ispezione il {date}",
+    "open": "Apri",
+    "quoteCreated": "Preventivo creato da {name} il {date}",
+    "quoteRequested": "Il cliente ha chiesto un preventivo su {count} punto/i",
+    "stillToGrade": "Restano {count} punti su {total} da valutare.",
+    "progress": "Avanzamento",
+    "checksGraded": "punti valutati",
+    "progressLabel": "{graded} punti su {total} valutati: {pass} nessuna carenza, {attention} lievi, {fail} gravi, {dangerous} pericolose.",
+    "sections": "Gruppi",
+    "graded": "{graded}/{total} valutati",
+    "vehicle": "Veicolo",
+    "vessel": "Imbarcazione",
+    "customer": "Cliente",
+    "plate": "Targa",
+    "registration": "Immatricolazione",
+    "template": "Modello",
+    "scaleEu": "Valutato secondo la scala delle carenze UE della direttiva 2014/45/UE.",
+    "scaleBasic": "Valutato superato / attenzione / non superato.",
+    "country": "Paese: {code}.",
+    "manageInspectionTemplates": "Gestisci i modelli di ispezione",
+    "readMore": "Scopri di più",
+    "odometer": "Contachilometri",
+    "engineHours": "Ore motore",
+    "completeTitle": "Chiudere questa ispezione?",
+    "completeBody": "I punti vengono bloccati alla chiusura e l'esito diventa «{result}».",
+    "completeBlocked": "Restano {count} punto/i che il modello indica come obbligatori.",
+    "completeUngraded": "{count} punto/i non sono stati valutati.",
+    "completeDangerous": "{count} carenza/e pericolosa/e registrata/e. Il veicolo non deve circolare su strada pubblica.",
+    "reopenTitle": "Riaprire questa ispezione?",
+    "reopenBody": "I punti tornano modificabili e l'esito torna a in corso. Ispettore, numero del verbale e data della prossima revisione vengono mantenuti.",
+    "reopenShared": "Il link del cliente resta attivo e mostrerà l'esito cambiare mentre lavori. Un verbale già scaricato mantiene l'esito precedente.",
+    "deleteTitle": "Eliminare questa ispezione?",
+    "deleteBody": "Questo elimina definitivamente l'ispezione, le valutazioni, le note e le foto. L'operazione non è reversibile.",
+    "cancel": "Annulla",
+    "completed": "Ispezione chiusa",
+    "reopened": "Ispezione riaperta",
+    "deleted": "Ispezione eliminata",
+    "completeFailed": "Impossibile chiudere l'ispezione",
+    "reopenFailed": "Impossibile riaprire l'ispezione",
+    "deleteFailed": "Impossibile eliminare l'ispezione",
+    "quoteFailed": "Impossibile creare il preventivo",
+    "workOrderFailed": "Impossibile creare la commessa"
+  },
+  "item": {
+    "required": "(obbligatorio)",
+    "saving": "Salvataggio",
+    "addNote": "Aggiungi nota",
+    "hideNote": "Nascondi nota",
+    "addPhoto": "Aggiungi foto o video a {name}",
+    "notesFor": "Note per {name}",
+    "describeDefect": "Descrivi la carenza — comparirà sul rapporto",
+    "optionalNote": "Aggiungi una nota (facoltativo)",
+    "notesRequired": "Descrivi la carenza prima che questa valutazione venga salvata.",
+    "photoRequired": "Qui è richiesta una foto della carenza. L'ispezione non può essere chiusa finché non ne viene allegata una.",
+    "reading": "Misura",
+    "readingUnit": "Misura ({unit})",
+    "limit": "Limite: {range}",
+    "recordedValue": "Valore registrato",
+    "answer": "Risposta",
+    "selectAnswer": "Scegli una risposta",
+    "viewPhoto": "Vedi la foto {index} di {name} a dimensione piena",
+    "photoAlt": "{name}, foto {index}",
+    "removePhoto": "Rimuovi la foto {index} da {name}",
+    "clearTitle": "Cancellare questa valutazione?",
+    "clearBody": "La nota registrata su «{name}» verrà rimossa e il punto tornerà a non controllato.",
+    "clear": "Cancella",
+    "cancel": "Annulla",
+    "saveFailed": "Impossibile salvare questo punto",
+    "uploadFailed": "Caricamento non riuscito",
+    "uploadFailedFor": "Impossibile caricare {name}"
+  },
+  "suggestions": {
+    "title": "Carenze frequenti",
+    "add": "Aggiungi «{text}» e valuta come {grade}",
+    "more": "Altre ({count})",
+    "search": "Cerca una formulazione",
+    "empty": "Nessuna formulazione corrispondente.",
+    "source": {
+      "workshop": "La tua officina",
+      "history": "Già usata",
+      "regulation": "Direttiva 2014/45/UE",
+      "general": "Generale"
+    }
+  },
+  "media": {
+    "viewer": "Immagine {index} di {total}: {caption}",
+    "close": "Chiudi il visualizzatore",
+    "previous": "Immagine precedente",
+    "next": "Immagine successiva"
+  },
+  "certificate": {
+    "title": "Dati del verbale",
+    "description": "Richiesti su un verbale di revisione ai sensi della direttiva 2014/45/UE, allegato IV.",
+    "inspector": "Ispettore",
+    "notRecorded": "Non registrato",
+    "addTechnician": "Aggiungi tecnico…",
+    "recordedAs": "Registrato come {name}",
+    "vehicleCategory": "Categoria del veicolo",
+    "mileageAtTest": "{label} al momento del controllo",
+    "reportReference": "Riferimento del rapporto",
+    "reportReferenceHelp": "Il tuo riferimento per questo rapporto. Se sei un centro autorizzato che emette un verbale ufficiale, inserisci qui il suo numero così i due coincidono. Altrimenti lascia vuoto.",
+    "optional": "Facoltativo",
+    "placeOfTest": "Luogo del controllo",
+    "placeholderAddress": "Centro di controllo o indirizzo dell'officina",
+    "fromSettings": "Dalle impostazioni della tua officina.",
+    "change": "Modifica",
+    "differsFromWorkshop": "Diverso dall'indirizzo della tua officina.",
+    "useWorkshopAddress": "Usa l'indirizzo dell'officina",
+    "noWorkshopAddress": "Non è impostato alcun indirizzo dell'officina.",
+    "addOne": "Aggiungine uno",
+    "nextTestDue": "Prossima revisione",
+    "notSet": "Non impostata",
+    "intervalGroup": "Imposta la data della prossima revisione a partire da questo controllo",
+    "intervalHelp": "Calcolato dalla data di questo controllo. Due anni è il riferimento dell'Unione per autovetture e veicoli commerciali leggeri; verifica la periodicità applicata nel tuo paese.",
+    "months6": "6 mesi",
+    "years1": "1 anno",
+    "years2": "2 anni",
+    "years3": "3 anni",
+    "years4": "4 anni",
+    "unsaved": "Modifiche non salvate",
+    "saveDetails": "Salva i dati",
+    "saved": "Dati salvati",
+    "saveFailed": "Impossibile salvare i dati",
+    "addTechnicianTitle": "Aggiungi tecnico",
+    "addTechnicianBody": "Entra nell'elenco dell'officina e può ricevere sia lavori sia ispezioni.",
+    "technicianName": "Nome",
+    "technicianPlaceholder": "es. Anna Berg",
+    "addAndSelect": "Aggiungi e seleziona",
+    "technicianAdded": "{name} aggiunto e selezionato",
+    "technicianFailed": "Impossibile aggiungere il tecnico",
+    "cancel": "Annulla"
+  },
+  "templates": {
+    "title": "Modelli di ispezione",
+    "description": "Le liste di controllo che i tuoi tecnici percorrono. Creane una per paese, per tipo di veicolo o per servizio che vendi.",
+    "import": "Importa",
+    "browse": "Sfoglia le liste",
+    "new": "Nuovo modello",
+    "restoreBanner": "{count} liste già pronte sono state rimosse dal tuo elenco. Puoi rimetterle.",
+    "restore": "Ripristina",
+    "restored": "{count} lista/e ripristinata/e",
+    "allPresent": "Tutte le liste sono già presenti",
+    "restoreFailed": "Impossibile aggiungere le liste",
+    "emptyTitle": "Ancora nessun modello",
+    "emptyBody": "Le liste già pronte, inclusa la revisione periodica UE, sono state tutte rimosse. Rimettile o costruisci la tua da zero.",
+    "restoreChecklists": "Ripristina le liste",
+    "buildMyOwn": "Costruisci la mia",
+    "default": "Predefinito",
+    "actionsFor": "Azioni per {name}",
+    "edit": "Modifica",
+    "duplicate": "Duplica",
+    "export": "Esporta…",
+    "delete": "Elimina",
+    "sections": "{count} gruppo/i",
+    "checks": "{count} punto/i",
+    "measured": "{count} misurati",
+    "euScale": "Scala delle carenze UE",
+    "basicScale": "Superato / attenzione / non superato",
+    "deleteTitle": "Eliminare «{name}»?",
+    "deleteBody": "Questo elimina definitivamente il modello. Le ispezioni già eseguite con esso mantengono la propria copia della lista e non ne risentono.",
+    "deleted": "Modello eliminato",
+    "deleteFailed": "Impossibile eliminare il modello",
+    "duplicated": "Modello duplicato",
+    "duplicateFailed": "Impossibile duplicare il modello",
+    "cancel": "Annulla",
+    "srSections": "Gruppi",
+    "srChecks": "Punti",
+    "srMeasurements": "Misure"
+  },
+  "presets": {
+    "title": "Partire da un modello",
+    "description": "Ogni modello viene copiato nella tua officina come modello ordinario. Rinomina i gruppi, sposta i punti e cambia qualsiasi limite per adattarlo al tuo paese e alla tua attrezzatura.",
+    "search": "Cerca per nome, paese o norma",
+    "searchLabel": "Cerca modelli",
+    "inYourList": "Nel tuo elenco",
+    "counts": "{sections} gruppi · {checks} punti",
+    "noMatch": "Nessun modello corrisponde a «{query}».",
+    "warning": "I modelli normativi riportano i limiti usuali dell'UE. Verificali rispetto alla normativa nazionale prima di emettere un verbale.",
+    "cancel": "Annulla",
+    "add": "Aggiungi modello",
+    "added": "Modello aggiunto. Adattalo alla tua officina.",
+    "addFailed": "Impossibile aggiungere il modello",
+    "group": {
+      "regulatory": "Normativo",
+      "workshop": "Officina",
+      "specialist": "Specialistico"
+    },
+    "groupDescription": {
+      "regulatory": "Liste di revisione periodica basate sulla direttiva 2014/45/UE.",
+      "workshop": "Liste generali di assistenza e vendita non legate a una normativa.",
+      "specialist": "Liste per motorizzazioni e imbarcazioni che richiedono una procedura propria."
+    },
+    "euScale": "Scala delle carenze UE",
+    "basicScale": "Superato / attenzione / non superato"
+  },
+  "builder": {
+    "newTitle": "Nuovo modello",
+    "editTitle": "Modifica modello",
+    "description": "Costruisci la lista che la tua officina usa davvero. Trascina per riordinare e configura ogni punto per registrare una valutazione, una misura, una nota scritta o una risposta fissa.",
+    "name": "Nome",
+    "namePlaceholder": "es. Revisione periodica",
+    "templateDescription": "Descrizione",
+    "descriptionPlaceholder": "A cosa serve questa lista e quando si usa.",
+    "country": "Paese",
+    "notCountrySpecific": "Non specifico per paese",
+    "countryHelp": "Registrato sul rapporto così un'officina che lavora oltre confine distingue le proprie liste.",
+    "defectScale": "Scala delle carenze",
+    "scaleEu": "Lieve / grave / pericolosa (UE)",
+    "scaleBasic": "Superato / attenzione / non superato",
+    "scaleEuHelp": "Le categorie dell'articolo 7, paragrafo 2, della direttiva 2014/45/UE. Un veicolo con sole carenze lievi resta idoneo.",
+    "scaleBasicHelp": "Una semplice scala a tre livelli per liste di assistenza che non sono un controllo obbligatorio.",
+    "defaultTemplate": "Modello predefinito",
+    "defaultHelp": "Preselezionato quando un tecnico avvia una nuova ispezione.",
+    "summary": "{sections} gruppo/i · {checks} punto/i",
+    "renumber": "Rinumera i riferimenti",
+    "renumberHelp": "Numera ogni gruppo e ogni punto in ordine: 1, 1.1, 1.2, 2. Le nuove righe vengono numerate all'aggiunta, quindi serve solo dopo aver spostato qualcosa.",
+    "renumberWarning": "Questa lista usa riferimenti normativi come 1.1.13. La rinumerazione li sostituisce.",
+    "addSection": "Aggiungi gruppo",
+    "addCheck": "Aggiungi punto",
+    "ref": "Rif.",
+    "sectionName": "Nome del gruppo, es. Impianto frenante",
+    "sectionNote": "Nota facoltativa mostrata sopra i punti di questo gruppo",
+    "checkName": "Nome del punto, es. Pastiglie freno",
+    "reorderSection": "Riordina il gruppo {name}",
+    "reorderCheck": "Riordina il punto {name}",
+    "removeSection": "Rimuovi il gruppo {name}",
+    "removeCheck": "Rimuovi il punto {name}",
+    "duplicateCheck": "Duplica il punto {name}",
+    "configure": "Configura {name}",
+    "hideSettings": "Nascondi le impostazioni di {name}",
+    "howRecorded": "Come viene registrato?",
+    "guidance": "Indicazione per il tecnico",
+    "guidancePlaceholder": "Mostrata sotto il nome del punto nel modulo di ispezione.",
+    "measurement": "Misura",
+    "unit": "Unità",
+    "minimum": "Minimo",
+    "maximum": "Massimo",
+    "outOfRange": "Carenza fuori intervallo",
+    "rangeHelp": "Lascia un estremo vuoto per un limite unilaterale, per esempio un minimo di 1,6 mm senza massimo. Le misure fuori intervallo sono valutate automaticamente.",
+    "answers": "Risposte",
+    "answersHelp": "Separa le risposte con le virgole.",
+    "mustComplete": "Deve essere compilato",
+    "photoRequired": "Foto richiesta quando viene registrata una carenza",
+    "commonWording": "Formulazioni frequenti",
+    "commonWordingHelp": "Una formulazione per riga. Sono proposte per prime quando un tecnico registra qui una carenza, prima dei testi predefiniti, così la tua officina può accordarsi su come descrivere un guasto.",
+    "inputType": {
+      "condition": "Valutazione della carenza",
+      "measurement": "Misura",
+      "text": "Testo libero",
+      "choice": "Scelta"
+    },
+    "inputHint": {
+      "condition": "Il tecnico valuta il punto secondo la scala delle carenze.",
+      "measurement": "Registra un numero con unità, valutato automaticamente rispetto a un intervallo.",
+      "text": "Registra un'osservazione scritta, per esempio una marca di pneumatico o un codice guasto.",
+      "choice": "Il tecnico sceglie una risposta da un elenco fisso."
+    },
+    "cancel": "Annulla",
+    "create": "Crea modello",
+    "saveChanges": "Salva le modifiche",
+    "created": "Modello creato",
+    "updated": "Modello aggiornato",
+    "saveFailed": "Impossibile salvare il modello",
+    "severityAttention": "Carenza lieve",
+    "severityFail": "Carenza grave",
+    "severityDangerous": "Carenza pericolosa",
+    "answersPlaceholder": "Completo, Parziale, Nessuno"
+  },
+  "packages": {
+    "exportTitle": "Esporta «{name}»",
+    "exportDescription": "Scarica l'intera lista come file: gruppi, punti, unità e limiti. Chiunque usi Torqvoice può importarla.",
+    "includeWording": "Includi le tue formulazioni",
+    "includeWordingHelp": "Le formulazioni che la tua officina ha aggiunto ai punti. Utili a chi le riceve, ma sono testo libero scritto al banco e possono nominare un cliente, un collega o un accordo locale. Disattivato a meno che tu non le abbia rilette.",
+    "attributeTo": "Attribuisci a (facoltativo)",
+    "attributePlaceholder": "es. Egeland Auto",
+    "attributeHelp": "Scritto nel file così chi lo installa vede da dove proviene. Nulla viene preso dal tuo account.",
+    "download": "Scarica",
+    "exported": "Modello esportato",
+    "exportFailed": "Impossibile esportare questo modello",
+    "importTitle": "Importa una lista",
+    "importDescription": "Aggiunge una lista da un file pacchetto Torqvoice. Arriva come modello ordinario modificabile e non sostituisce mai uno che hai già.",
+    "chooseFile": "Scegli un file pacchetto",
+    "chooseAnother": "Scegli un altro file",
+    "unreadable": "Impossibile leggere quel file. Un pacchetto Torqvoice è un file .json esportato da un modello.",
+    "from": "Da {author}",
+    "limitsWarning": "I limiti di una lista condivisa sono quelli impostati dal suo autore. Verificali rispetto alla normativa nazionale prima di emettere qualsiasi cosa.",
+    "import": "Importa",
+    "imported": "{count} modello/i importato/i",
+    "importFailed": "Impossibile importare questo pacchetto",
+    "cancel": "Annulla"
+  },
+  "list": {
+    "tabAll": "Tutte",
+    "tabInProgress": "In corso",
+    "tabCompleted": "Chiuse",
+    "search": "Cerca ispezioni…",
+    "new": "Nuova ispezione",
+    "empty": "Nessuna ispezione trovata.",
+    "vehicle": "Veicolo",
+    "template": "Modello",
+    "progress": "Avanzamento",
+    "status": "Stato",
+    "date": "Data",
+    "statusInProgress": "In corso",
+    "statusCompleted": "Chiusa"
+  },
+  "new": {
+    "title": "Nuova ispezione",
+    "vehicle": "Veicolo",
+    "searchVehicles": "Cerca veicoli…",
+    "loadingVehicles": "Caricamento veicoli…",
+    "noVehicles": "Nessun veicolo trovato.",
+    "template": "Modello",
+    "selectTemplate": "Scegli un modello",
+    "manageTemplates": "Gestisci modelli",
+    "cancel": "Annulla",
+    "create": "Crea ispezione",
+    "created": "Ispezione creata",
+    "createFailed": "Impossibile creare l'ispezione"
+  },
+  "shareDialog": {
+    "title": "Condividi ispezione",
+    "description": "Condividi con il cliente una vista in sola lettura di questa ispezione.",
+    "copyLink": "Copia link",
+    "copied": "Link copiato negli appunti",
+    "generated": "Link di condivisione creato",
+    "revoked": "Link di condivisione revocato"
+  }
+}

+ 11 - 1
messages/lt/audit.json

@@ -98,6 +98,16 @@
     "webhook_create": "Sukurtas webhook’as",
     "webhook_update": "Atnaujintas webhook’as",
     "webhook_delete": "Ištrintas webhook’as",
-    "webhook_rotateSecret": "Atnaujintas webhook’o slaptasis raktas"
+    "webhook_rotateSecret": "Atnaujintas webhook’o slaptasis raktas",
+    "inspection_reopen": "Apžiūra atverta iš naujo",
+    "inspectionTemplate_import": "Importuotas apžiūros šablonas",
+    "scheduled_message_create": "Sukurta suplanuota žinutė",
+    "scheduled_message_update": "Atnaujinta suplanuota žinutė",
+    "scheduled_message_delete": "Ištrinta suplanuota žinutė",
+    "scheduled_message_cancel": "Atšaukta suplanuota žinutė",
+    "scheduled_message_send_now": "Išsiųsta suplanuota žinutė",
+    "settings_applyTaxRate": "Pritaikytas mokesčio tarifas",
+    "settings_convertToExclusive": "Kainos konvertuotos be mokesčio",
+    "settings_convertToInclusive": "Kainos konvertuotos su mokesčiu"
   }
 }

+ 24 - 0
messages/lt/common.json

@@ -52,5 +52,29 @@
     "updated": "Torqvoice atnaujinta į {version}",
     "whatsNew": "Žiūrėti naujienas",
     "dismiss": "Uždaryti"
+  },
+  "pagination": {
+    "showing": "Rodoma {start}–{end} iš {total}",
+    "perPage": "puslapyje",
+    "pageOf": "Puslapis {page} iš {pages}",
+    "first": "Pirmas puslapis",
+    "previous": "Ankstesnis puslapis",
+    "next": "Kitas puslapis",
+    "last": "Paskutinis puslapis"
+  },
+  "shared": {
+    "close": "Uždaryti",
+    "viewPlans": "Peržiūrėti planus",
+    "manageLicense": "Tvarkyti licenciją",
+    "installOwn": "Įdiekite savo",
+    "install": "Įdiegti",
+    "installApp": "Įdiegti programą",
+    "installTorqvoice": "Įdiegti Torqvoice",
+    "dismiss": "Atmesti",
+    "useCamera": "Naudoti kamerą",
+    "enterManually": "Įvesti ranka",
+    "enterBarcode": "Įveskite brūkšninį kodą…",
+    "scanHint": "Nukreipkite kamerą į brūkšninį kodą arba įveskite jį ranka",
+    "iosInstallHint": "Palieskite Bendrinti > Įtraukti į pradžios ekraną"
   }
 }

+ 396 - 0
messages/lt/inspections.json

@@ -0,0 +1,396 @@
+{
+  "grades": {
+    "eu": {
+      "pass": "Trūkumų nėra",
+      "attention": "Nedidelis trūkumas",
+      "fail": "Didelis trūkumas",
+      "dangerous": "Pavojingas trūkumas",
+      "not_inspected": "Netikrinta"
+    },
+    "basic": {
+      "pass": "Tinkama",
+      "attention": "Dėmesio",
+      "fail": "Netinkama",
+      "dangerous": "Pavojinga",
+      "not_inspected": "Netikrinta"
+    },
+    "short": {
+      "pass": "OK",
+      "attention": "Nedidelis",
+      "fail": "Didelis",
+      "dangerous": "Pavojingas",
+      "not_inspected": "–"
+    },
+    "hint": {
+      "pass": "Atitinka reikalavimus. Pastabų nėra.",
+      "attention": "Reikšmingos įtakos saugai neturi. Transporto priemonė tinkama; pašalinkite progai pasitaikius.",
+      "fail": "Gali pakenkti saugai arba aplinkai. Netinkama; pašalinkite ir atlikite pakartotinę apžiūrą.",
+      "dangerous": "Tiesioginis ir neatidėliotinas pavojus eismo saugumui. Transporto priemone važiuoti negalima.",
+      "not_inspected": "Šis punktas nebuvo atliktas."
+    }
+  },
+  "results": {
+    "pass": "Teigiamas",
+    "pass_minor": "Teigiamas su nedideliais trūkumais",
+    "fail": "Neigiamas — dideli trūkumai",
+    "fail_dangerous": "Neigiamas — pavojingi trūkumai",
+    "incomplete": "Nebaigta",
+    "detail": {
+      "pass": "Trūkumų nenustatyta.",
+      "pass_minor": "Transporto priemonė tinkama naudoti. Nedelsdami pašalinkite nedidelius trūkumus.",
+      "fail": "Transporto priemonė netinkama. Trūkumus reikia pašalinti ir atlikti pakartotinę apžiūrą.",
+      "fail_dangerous": "Transporto priemone draudžiama važiuoti viešaisiais keliais, kol nebus pašalinti pavojingi trūkumai.",
+      "incomplete": "Ši apžiūra dar vyksta."
+    }
+  },
+  "page": {
+    "back": "Apžiūros",
+    "statusInProgress": "Vyksta",
+    "statusCompleted": "Baigta",
+    "complete": "Užbaigti",
+    "reopen": "Atverti iš naujo",
+    "share": "Bendrinti",
+    "save": "Įrašyti",
+    "saving": "Įrašoma…",
+    "savedAt": "Įrašyta {time}",
+    "savesAsYouGo": "Įrašo jums dirbant",
+    "saveFailed": "Kai kurie pakeitimai neįrašyti",
+    "allSaved": "Visi pakeitimai įrašyti",
+    "moreActions": "Daugiau veiksmų",
+    "downloadCertificate": "Atsisiųsti pažymą",
+    "manageTemplates": "Tvarkyti šablonus",
+    "delete": "Šalinti",
+    "createWorkOrder": "Sukurti darbo užsakymą",
+    "viewWorkOrder": "Žiūrėti darbo užsakymą",
+    "createQuote": "Sukurti pasiūlymą",
+    "viewQuote": "Žiūrėti pasiūlymą",
+    "needsDefect": "Galima, kai užfiksuojamas trūkumas",
+    "workOrderRaised": "Darbo užsakymas {number} sukurtas iš šios apžiūros {date}",
+    "open": "Atverti",
+    "quoteCreated": "Pasiūlymą sukūrė {name} {date}",
+    "quoteRequested": "Klientas paprašė pasiūlymo dėl {count} punkto (-ų)",
+    "stillToGrade": "Liko įvertinti {count} iš {total} punktų.",
+    "progress": "Eiga",
+    "checksGraded": "punktų įvertinta",
+    "progressLabel": "Įvertinta {graded} iš {total} punktų: {pass} be trūkumų, {attention} nedideli, {fail} dideli, {dangerous} pavojingi.",
+    "sections": "Grupės",
+    "graded": "{graded}/{total} įvertinta",
+    "vehicle": "Transporto priemonė",
+    "vessel": "Laivas",
+    "customer": "Klientas",
+    "plate": "Valstybinis numeris",
+    "registration": "Registracija",
+    "template": "Šablonas",
+    "scaleEu": "Vertinama pagal ES trūkumų skalę iš Direktyvos 2014/45/ES.",
+    "scaleBasic": "Vertinama tinkama / dėmesio / netinkama.",
+    "country": "Šalis: {code}.",
+    "manageInspectionTemplates": "Tvarkyti apžiūrų šablonus",
+    "readMore": "Skaityti daugiau",
+    "odometer": "Rida",
+    "engineHours": "Variklio valandos",
+    "completeTitle": "Užbaigti šią apžiūrą?",
+    "completeBody": "Užbaigus punktai užrakinami, o rezultatas tampa „{result}“.",
+    "completeBlocked": "Liko {count} punktas (-ai), kuriuos šablonas žymi kaip privalomus.",
+    "completeUngraded": "{count} punktas (-ai) neįvertinti.",
+    "completeDangerous": "Užfiksuota pavojingų trūkumų: {count}. Transporto priemone važiuoti viešaisiais keliais draudžiama.",
+    "reopenTitle": "Atverti šią apžiūrą iš naujo?",
+    "reopenBody": "Punktus vėl bus galima redaguoti, o rezultatas grįš į vyksta. Kontrolierius, pažymos numeris ir kitos apžiūros data išsaugomi.",
+    "reopenShared": "Kliento nuoroda lieka veikianti ir rodys, kaip rezultatas keičiasi jums redaguojant. Jau atsisiųsta pažyma išlaiko ankstesnį rezultatą.",
+    "deleteTitle": "Šalinti šią apžiūrą?",
+    "deleteBody": "Tai visam laikui pašalins apžiūrą, jos įvertinimus, pastabas ir nuotraukas. Veiksmo atšaukti negalima.",
+    "cancel": "Atšaukti",
+    "completed": "Apžiūra užbaigta",
+    "reopened": "Apžiūra atverta iš naujo",
+    "deleted": "Apžiūra pašalinta",
+    "completeFailed": "Nepavyko užbaigti apžiūros",
+    "reopenFailed": "Nepavyko atverti apžiūros iš naujo",
+    "deleteFailed": "Nepavyko pašalinti apžiūros",
+    "quoteFailed": "Nepavyko sukurti pasiūlymo",
+    "workOrderFailed": "Nepavyko sukurti darbo užsakymo"
+  },
+  "item": {
+    "required": "(privaloma)",
+    "saving": "Įrašoma",
+    "addNote": "Pridėti pastabą",
+    "hideNote": "Slėpti pastabą",
+    "addPhoto": "Pridėti nuotrauką ar vaizdo įrašą prie „{name}“",
+    "notesFor": "Pastabos apie „{name}“",
+    "describeDefect": "Aprašykite trūkumą — jis atsiras ataskaitoje",
+    "optionalNote": "Pridėti pastabą (nebūtina)",
+    "notesRequired": "Aprašykite trūkumą, kad šis įvertinimas būtų įrašytas.",
+    "photoRequired": "Čia būtina trūkumo nuotrauka. Apžiūros užbaigti negalima, kol ji nepridėta.",
+    "reading": "Rodmuo",
+    "readingUnit": "Rodmuo ({unit})",
+    "limit": "Ribinė vertė: {range}",
+    "recordedValue": "Įrašyta reikšmė",
+    "answer": "Atsakymas",
+    "selectAnswer": "Pasirinkite atsakymą",
+    "viewPhoto": "Žiūrėti „{name}“ {index} nuotrauką visu dydžiu",
+    "photoAlt": "{name}, nuotrauka {index}",
+    "removePhoto": "Pašalinti {index} nuotrauką iš „{name}“",
+    "clearTitle": "Išvalyti šį įvertinimą?",
+    "clearBody": "Prie „{name}“ įrašyta pastaba bus pašalinta, o punktas grįš į netikrinta.",
+    "clear": "Išvalyti",
+    "cancel": "Atšaukti",
+    "saveFailed": "Nepavyko įrašyti šio punkto",
+    "uploadFailed": "Nepavyko įkelti",
+    "uploadFailedFor": "Nepavyko įkelti {name}"
+  },
+  "suggestions": {
+    "title": "Dažni trūkumai",
+    "add": "Pridėti „{text}“ ir įvertinti kaip {grade}",
+    "more": "Daugiau ({count})",
+    "search": "Ieškoti formuluotės",
+    "empty": "Tinkamų formuluočių nėra.",
+    "source": {
+      "workshop": "Jūsų dirbtuvės",
+      "history": "Naudota anksčiau",
+      "regulation": "Direktyva 2014/45/ES",
+      "general": "Bendra"
+    }
+  },
+  "media": {
+    "viewer": "Vaizdas {index} iš {total}: {caption}",
+    "close": "Uždaryti peržiūrą",
+    "previous": "Ankstesnis vaizdas",
+    "next": "Kitas vaizdas"
+  },
+  "certificate": {
+    "title": "Pažymos duomenys",
+    "description": "Privalomi techninės apžiūros pažymoje pagal Direktyvos 2014/45/ES IV priedą.",
+    "inspector": "Kontrolierius",
+    "notRecorded": "Neįrašyta",
+    "addTechnician": "Pridėti mechaniką…",
+    "recordedAs": "Įrašyta kaip {name}",
+    "vehicleCategory": "Transporto priemonės kategorija",
+    "mileageAtTest": "{label} apžiūros metu",
+    "reportReference": "Ataskaitos nuoroda",
+    "reportReferenceHelp": "Jūsų pačių nuoroda šiai ataskaitai. Jei esate įgaliota techninės apžiūros stotis, išduodanti oficialią pažymą, įrašykite čia jos numerį, kad abu sutaptų. Kitu atveju palikite tuščią.",
+    "optional": "Nebūtina",
+    "placeOfTest": "Apžiūros vieta",
+    "placeholderAddress": "Apžiūros stotis arba dirbtuvių adresas",
+    "fromSettings": "Iš jūsų dirbtuvių nustatymų.",
+    "change": "Keisti",
+    "differsFromWorkshop": "Skiriasi nuo jūsų dirbtuvių adreso.",
+    "useWorkshopAddress": "Naudoti dirbtuvių adresą",
+    "noWorkshopAddress": "Dirbtuvių adresas nenurodytas.",
+    "addOne": "Pridėti adresą",
+    "nextTestDue": "Kita apžiūra",
+    "notSet": "Nenustatyta",
+    "intervalGroup": "Nustatyti kitos apžiūros datą nuo šios apžiūros",
+    "intervalHelp": "Skaičiuojama nuo šios apžiūros datos. Dveji metai – Sąjungos atskaitos taškas lengviesiems ir lengviesiems krovininiams automobiliams; pasitikrinkite savo šalyje taikomą periodiškumą.",
+    "months6": "6 mėnesiai",
+    "years1": "1 metai",
+    "years2": "2 metai",
+    "years3": "3 metai",
+    "years4": "4 metai",
+    "unsaved": "Neįrašyti pakeitimai",
+    "saveDetails": "Įrašyti duomenis",
+    "saved": "Duomenys įrašyti",
+    "saveFailed": "Nepavyko įrašyti duomenų",
+    "addTechnicianTitle": "Pridėti mechaniką",
+    "addTechnicianBody": "Jis įtraukiamas į dirbtuvių sąrašą, jam galima skirti ir darbus, ir apžiūras.",
+    "technicianName": "Vardas",
+    "technicianPlaceholder": "pvz., Anna Berg",
+    "addAndSelect": "Pridėti ir pasirinkti",
+    "technicianAdded": "{name} pridėtas ir pasirinktas",
+    "technicianFailed": "Nepavyko pridėti mechaniko",
+    "cancel": "Atšaukti"
+  },
+  "templates": {
+    "title": "Apžiūrų šablonai",
+    "description": "Tikrinimo sąrašai, kuriuos peržiūri jūsų mechanikai. Sukurkite po vieną kiekvienai šaliai, transporto priemonės tipui ar paslaugai, kurią parduodate.",
+    "import": "Importuoti",
+    "browse": "Naršyti sąrašus",
+    "new": "Naujas šablonas",
+    "restoreBanner": "Iš jūsų sąrašo pašalinta paruoštų sąrašų: {count}. Galite juos grąžinti.",
+    "restore": "Atkurti",
+    "restored": "Atkurta sąrašų: {count}",
+    "allPresent": "Visi sąrašai jau čia",
+    "restoreFailed": "Nepavyko pridėti sąrašų",
+    "emptyTitle": "Šablonų dar nėra",
+    "emptyBody": "Visi paruošti sąrašai, įskaitant ES periodinę techninę apžiūrą, buvo pašalinti. Grąžinkite juos arba susikurkite savo nuo nulio.",
+    "restoreChecklists": "Atkurti sąrašus",
+    "buildMyOwn": "Kurti savo",
+    "default": "Numatytasis",
+    "actionsFor": "Veiksmai su „{name}“",
+    "edit": "Redaguoti",
+    "duplicate": "Dubliuoti",
+    "export": "Eksportuoti…",
+    "delete": "Šalinti",
+    "sections": "grupių: {count}",
+    "checks": "punktų: {count}",
+    "measured": "su matavimu: {count}",
+    "euScale": "ES trūkumų skalė",
+    "basicScale": "Tinkama / dėmesio / netinkama",
+    "deleteTitle": "Šalinti „{name}“?",
+    "deleteBody": "Tai visam laikui pašalins šabloną. Jau atliktos apžiūros išlaiko savo sąrašo kopiją ir nenukenčia.",
+    "deleted": "Šablonas pašalintas",
+    "deleteFailed": "Nepavyko pašalinti šablono",
+    "duplicated": "Šablonas dubliuotas",
+    "duplicateFailed": "Nepavyko dubliuoti šablono",
+    "cancel": "Atšaukti",
+    "srSections": "Grupės",
+    "srChecks": "Punktai",
+    "srMeasurements": "Matavimai"
+  },
+  "presets": {
+    "title": "Pradėti nuo šablono",
+    "description": "Kiekvienas šablonas nukopijuojamas į jūsų dirbtuves kaip įprastas šablonas. Pervadinkite grupes, perkelkite punktus ir keiskite bet kurią ribinę vertę pagal savo šalį ir savo įrangą.",
+    "search": "Ieškoti pagal pavadinimą, šalį ar normą",
+    "searchLabel": "Ieškoti šablonų",
+    "inYourList": "Jūsų sąraše",
+    "counts": "grupių: {sections} · punktų: {checks}",
+    "noMatch": "Nė vienas šablonas neatitinka „{query}“.",
+    "warning": "Teisės aktais grįsti šablonai turi įprastas ES ribines vertes. Prieš išduodami pažymą patikrinkite jas pagal savo šalies taisykles.",
+    "cancel": "Atšaukti",
+    "add": "Pridėti šabloną",
+    "added": "Šablonas pridėtas. Pritaikykite jį savo dirbtuvėms.",
+    "addFailed": "Nepavyko pridėti šablono",
+    "group": {
+      "regulatory": "Teisės aktai",
+      "workshop": "Dirbtuvės",
+      "specialist": "Specializuoti"
+    },
+    "groupDescription": {
+      "regulatory": "Periodinės techninės apžiūros sąrašai, parengti pagal Direktyvą 2014/45/ES.",
+      "workshop": "Bendri serviso ir pardavimo sąrašai, nesusieti su teisės aktais.",
+      "specialist": "Sąrašai pavaroms ir laivams, kuriems reikia atskiros procedūros."
+    },
+    "euScale": "ES trūkumų skalė",
+    "basicScale": "Tinkama / dėmesio / netinkama"
+  },
+  "builder": {
+    "newTitle": "Naujas šablonas",
+    "editTitle": "Redaguoti šabloną",
+    "description": "Sukurkite sąrašą, kurį jūsų dirbtuvės iš tikrųjų naudoja. Vilkite, kad pertvarkytumėte, ir nustatykite kiekvieną punktą taip, kad jis fiksuotų įvertinimą, matavimą, įrašytą pastabą ar nustatytą atsakymą.",
+    "name": "Pavadinimas",
+    "namePlaceholder": "pvz., Periodinė techninė apžiūra",
+    "templateDescription": "Aprašymas",
+    "descriptionPlaceholder": "Kam šis sąrašas skirtas ir kada jį naudoti.",
+    "country": "Šalis",
+    "notCountrySpecific": "Nesusieta su šalimi",
+    "countryHelp": "Įrašoma ataskaitoje, kad dirbtuvės, dirbančios keliose šalyse, atskirtų savo sąrašus.",
+    "defectScale": "Trūkumų skalė",
+    "scaleEu": "Nedidelis / didelis / pavojingas (ES)",
+    "scaleBasic": "Tinkama / dėmesio / netinkama",
+    "scaleEuHelp": "Direktyvos 2014/45/ES 7 straipsnio 2 dalies kategorijos. Transporto priemonė, turinti tik nedidelių trūkumų, vis tiek vertinama teigiamai.",
+    "scaleBasicHelp": "Paprasta trijų pakopų skalė serviso sąrašams, kurie nėra privaloma apžiūra.",
+    "defaultTemplate": "Numatytasis šablonas",
+    "defaultHelp": "Pasirenkamas iš anksto, kai mechanikas pradeda naują apžiūrą.",
+    "summary": "grupių: {sections} · punktų: {checks}",
+    "renumber": "Pernumeruoti nuorodas",
+    "renumberHelp": "Numeruoja kiekvieną grupę ir punktą iš eilės: 1, 1.1, 1.2, 2. Naujos eilutės numeruojamos jas pridedant, tad to prireiks tik po perkėlimų.",
+    "renumberWarning": "Šiame sąraše naudojamos teisės aktų nuorodos, pavyzdžiui 1.1.13. Pernumeravimas jas pakeis.",
+    "addSection": "Pridėti grupę",
+    "addCheck": "Pridėti punktą",
+    "ref": "Nuor.",
+    "sectionName": "Grupės pavadinimas, pvz., Stabdžių sistema",
+    "sectionNote": "Nebūtina pastaba, rodoma virš šios grupės punktų",
+    "checkName": "Punkto pavadinimas, pvz., Stabdžių kaladėlės",
+    "reorderSection": "Pertvarkyti grupę „{name}“",
+    "reorderCheck": "Pertvarkyti punktą „{name}“",
+    "removeSection": "Pašalinti grupę „{name}“",
+    "removeCheck": "Pašalinti punktą „{name}“",
+    "duplicateCheck": "Dubliuoti punktą „{name}“",
+    "configure": "Konfigūruoti „{name}“",
+    "hideSettings": "Slėpti „{name}“ nustatymus",
+    "howRecorded": "Kaip tai fiksuojama?",
+    "guidance": "Nurodymas mechanikui",
+    "guidancePlaceholder": "Rodomas po punkto pavadinimu apžiūros formoje.",
+    "measurement": "Matavimas",
+    "unit": "Vienetas",
+    "minimum": "Mažiausia",
+    "maximum": "Didžiausia",
+    "outOfRange": "Trūkumas už intervalo ribų",
+    "rangeHelp": "Palikite vieną ribą tuščią, jei reikalavimas vienpusis, pavyzdžiui mažiausiai 1,6 mm be didžiausios ribos. Rodmenys už intervalo ribų vertinami automatiškai.",
+    "answers": "Atsakymai",
+    "answersHelp": "Atsakymus atskirkite kableliais.",
+    "mustComplete": "Privaloma užpildyti",
+    "photoRequired": "Fiksuojant trūkumą būtina nuotrauka",
+    "commonWording": "Dažnos formuluotės",
+    "commonWordingHelp": "Po vieną formuluotę eilutėje. Jos siūlomos pirmiausia, kai mechanikas čia fiksuoja trūkumą, pirmiau už įtaisytus tekstus, kad jūsų dirbtuvės sutartų dėl gedimo aprašymo.",
+    "inputType": {
+      "condition": "Trūkumo įvertinimas",
+      "measurement": "Matavimas",
+      "text": "Laisvas tekstas",
+      "choice": "Pasirinkimas"
+    },
+    "inputHint": {
+      "condition": "Mechanikas vertina punktą pagal trūkumų skalę.",
+      "measurement": "Fiksuoja skaičių su matavimo vienetu, automatiškai vertinamą pagal intervalą.",
+      "text": "Fiksuoja įrašytą pastebėjimą, pavyzdžiui padangos markę ar gedimo kodą.",
+      "choice": "Mechanikas pasirenka vieną atsakymą iš nustatyto sąrašo."
+    },
+    "cancel": "Atšaukti",
+    "create": "Sukurti šabloną",
+    "saveChanges": "Įrašyti pakeitimus",
+    "created": "Šablonas sukurtas",
+    "updated": "Šablonas atnaujintas",
+    "saveFailed": "Nepavyko įrašyti šablono",
+    "severityAttention": "Nedidelis trūkumas",
+    "severityFail": "Didelis trūkumas",
+    "severityDangerous": "Pavojingas trūkumas",
+    "answersPlaceholder": "Pilna, Dalinė, Nėra"
+  },
+  "packages": {
+    "exportTitle": "Eksportuoti „{name}“",
+    "exportDescription": "Atsisiunčia visą sąrašą kaip failą: grupes, punktus, vienetus ir ribines vertes. Importuoti gali kiekvienas, naudojantis Torqvoice.",
+    "includeWording": "Įtraukti savo formuluotes",
+    "includeWordingHelp": "Formuluotės, kurias jūsų dirbtuvės pridėjo prie punktų. Naudingos gavėjui, tačiau tai laisvas tekstas, surinktas prie stalo, ir jame gali būti paminėtas klientas, kolega ar vietinis susitarimas. Išjungta, nebent jas perskaitėte.",
+    "attributeTo": "Priskirti (nebūtina)",
+    "attributePlaceholder": "pvz., Egeland Auto",
+    "attributeHelp": "Įrašoma į failą, kad diegiantysis matytų, iš kur jis. Iš jūsų paskyros nieko neimama.",
+    "download": "Atsisiųsti",
+    "exported": "Šablonas eksportuotas",
+    "exportFailed": "Nepavyko eksportuoti šio šablono",
+    "importTitle": "Importuoti sąrašą",
+    "importDescription": "Prideda sąrašą iš Torqvoice paketo failo. Jis atkeliauja kaip įprastas redaguojamas šablonas ir niekada nepakeičia jau turimo.",
+    "chooseFile": "Pasirinkite paketo failą",
+    "chooseAnother": "Pasirinkite kitą failą",
+    "unreadable": "Šio failo nepavyko perskaityti. Torqvoice paketas – tai iš šablono eksportuotas .json failas.",
+    "from": "Iš {author}",
+    "limitsWarning": "Bendrinamo sąrašo ribinės vertės yra tos, kurias nustatė jo autorius. Prieš ką nors pagal jį išduodami, patikrinkite jas pagal savo šalies taisykles.",
+    "import": "Importuoti",
+    "imported": "Importuota šablonų: {count}",
+    "importFailed": "Nepavyko importuoti šio paketo",
+    "cancel": "Atšaukti"
+  },
+  "list": {
+    "tabAll": "Visos",
+    "tabInProgress": "Vyksta",
+    "tabCompleted": "Baigtos",
+    "search": "Ieškoti apžiūrų…",
+    "new": "Nauja apžiūra",
+    "empty": "Apžiūrų nerasta.",
+    "vehicle": "Transporto priemonė",
+    "template": "Šablonas",
+    "progress": "Eiga",
+    "status": "Būsena",
+    "date": "Data",
+    "statusInProgress": "Vyksta",
+    "statusCompleted": "Baigta"
+  },
+  "new": {
+    "title": "Nauja apžiūra",
+    "vehicle": "Transporto priemonė",
+    "searchVehicles": "Ieškoti transporto priemonių…",
+    "loadingVehicles": "Įkeliamos transporto priemonės…",
+    "noVehicles": "Transporto priemonių nerasta.",
+    "template": "Šablonas",
+    "selectTemplate": "Pasirinkite šabloną",
+    "manageTemplates": "Tvarkyti šablonus",
+    "cancel": "Atšaukti",
+    "create": "Sukurti apžiūrą",
+    "created": "Apžiūra sukurta",
+    "createFailed": "Nepavyko sukurti apžiūros"
+  },
+  "shareDialog": {
+    "title": "Bendrinti apžiūrą",
+    "description": "Bendrinkite su klientu tik skaitymui skirtą šios apžiūros rodinį.",
+    "copyLink": "Kopijuoti nuorodą",
+    "copied": "Nuoroda nukopijuota į iškarpinę",
+    "generated": "Bendrinimo nuoroda sukurta",
+    "revoked": "Bendrinimo nuoroda atšaukta"
+  }
+}

+ 11 - 1
messages/nb/audit.json

@@ -98,6 +98,16 @@
     "webhook_create": "Opprettet webhook",
     "webhook_update": "Oppdatert webhook",
     "webhook_delete": "Slettet webhook",
-    "webhook_rotateSecret": "Roterte webhook-hemmelighet"
+    "webhook_rotateSecret": "Roterte webhook-hemmelighet",
+    "inspection_reopen": "Gjenåpnet inspeksjon",
+    "inspectionTemplate_import": "Importerte inspeksjonsmal",
+    "scheduled_message_create": "Opprettet planlagt melding",
+    "scheduled_message_update": "Oppdaterte planlagt melding",
+    "scheduled_message_delete": "Slettet planlagt melding",
+    "scheduled_message_cancel": "Avbrøt planlagt melding",
+    "scheduled_message_send_now": "Sendte planlagt melding",
+    "settings_applyTaxRate": "Brukte avgiftssats",
+    "settings_convertToExclusive": "Konverterte priser til eks. avgift",
+    "settings_convertToInclusive": "Konverterte priser til inkl. avgift"
   }
 }

+ 24 - 0
messages/nb/common.json

@@ -52,5 +52,29 @@
     "updated": "Torqvoice er oppdatert til {version}",
     "whatsNew": "Se hva som er nytt",
     "dismiss": "Lukk"
+  },
+  "pagination": {
+    "showing": "Viser {start}–{end} av {total}",
+    "perPage": "per side",
+    "pageOf": "Side {page} av {pages}",
+    "first": "Første side",
+    "previous": "Forrige side",
+    "next": "Neste side",
+    "last": "Siste side"
+  },
+  "shared": {
+    "close": "Lukk",
+    "viewPlans": "Se abonnementer",
+    "manageLicense": "Administrer lisens",
+    "installOwn": "Installer din egen",
+    "install": "Installer",
+    "installApp": "Installer app",
+    "installTorqvoice": "Installer Torqvoice",
+    "dismiss": "Lukk",
+    "useCamera": "Bruk kamera",
+    "enterManually": "Skriv inn manuelt",
+    "enterBarcode": "Skriv inn strekkode…",
+    "scanHint": "Rett kameraet mot en strekkode, eller skriv den inn manuelt",
+    "iosInstallHint": "Trykk Del > Legg til på Hjem-skjerm"
   }
 }

+ 396 - 0
messages/nb/inspections.json

@@ -0,0 +1,396 @@
+{
+  "grades": {
+    "eu": {
+      "pass": "Ingen mangel",
+      "attention": "Mindre mangel",
+      "fail": "Vesentlig mangel",
+      "dangerous": "Farlig mangel",
+      "not_inspected": "Ikke kontrollert"
+    },
+    "basic": {
+      "pass": "Godkjent",
+      "attention": "Merknad",
+      "fail": "Ikke godkjent",
+      "dangerous": "Farlig",
+      "not_inspected": "Ikke kontrollert"
+    },
+    "short": {
+      "pass": "OK",
+      "attention": "Mindre",
+      "fail": "Vesentlig",
+      "dangerous": "Farlig",
+      "not_inspected": "–"
+    },
+    "hint": {
+      "pass": "Oppfyller kravene. Ingenting å melde.",
+      "attention": "Ingen vesentlig betydning for sikkerheten. Kjøretøyet godkjennes; utbedres ved anledning.",
+      "fail": "Kan svekke sikkerheten eller påvirke miljøet. Kjøretøyet godkjennes ikke; utbedres og etterkontrolleres.",
+      "dangerous": "Direkte og umiddelbar fare for trafikksikkerheten. Kjøretøyet skal ikke kjøres.",
+      "not_inspected": "Dette punktet er ikke kontrollert."
+    }
+  },
+  "results": {
+    "pass": "Godkjent",
+    "pass_minor": "Godkjent med mindre mangler",
+    "fail": "Ikke godkjent — vesentlige mangler",
+    "fail_dangerous": "Ikke godkjent — farlige mangler",
+    "incomplete": "Ikke fullført",
+    "detail": {
+      "pass": "Det ble ikke funnet mangler.",
+      "pass_minor": "Kjøretøyet er godkjent. Utbedre de mindre manglene uten unødig opphold.",
+      "fail": "Kjøretøyet er ikke godkjent. Manglene må utbedres og kjøretøyet kontrolleres på nytt.",
+      "fail_dangerous": "Kjøretøyet må ikke brukes på offentlig vei før de farlige manglene er utbedret.",
+      "incomplete": "Denne kontrollen pågår fortsatt."
+    }
+  },
+  "page": {
+    "back": "Inspeksjoner",
+    "statusInProgress": "Pågår",
+    "statusCompleted": "Fullført",
+    "complete": "Fullfør",
+    "reopen": "Gjenåpne",
+    "share": "Del",
+    "save": "Lagre",
+    "saving": "Lagrer…",
+    "savedAt": "Lagret {time}",
+    "savesAsYouGo": "Lagrer underveis",
+    "saveFailed": "Noen endringer ble ikke lagret",
+    "allSaved": "Alle endringer er lagret",
+    "moreActions": "Flere handlinger",
+    "downloadCertificate": "Last ned kontrollseddel",
+    "manageTemplates": "Administrer maler",
+    "delete": "Slett",
+    "createWorkOrder": "Opprett arbeidsordre",
+    "viewWorkOrder": "Vis arbeidsordre",
+    "createQuote": "Opprett tilbud",
+    "viewQuote": "Vis tilbud",
+    "needsDefect": "Tilgjengelig når en mangel er registrert",
+    "workOrderRaised": "Arbeidsordre {number} opprettet fra denne inspeksjonen {date}",
+    "open": "Åpne",
+    "quoteCreated": "Tilbud opprettet av {name} {date}",
+    "quoteRequested": "Kunden ba om tilbud på {count} punkt(er)",
+    "stillToGrade": "{count} av {total} punkter gjenstår å vurdere.",
+    "progress": "Fremdrift",
+    "checksGraded": "punkter vurdert",
+    "progressLabel": "{graded} av {total} punkter vurdert: {pass} ingen mangel, {attention} mindre, {fail} vesentlige, {dangerous} farlige.",
+    "sections": "Grupper",
+    "graded": "{graded}/{total} vurdert",
+    "vehicle": "Kjøretøy",
+    "vessel": "Fartøy",
+    "customer": "Kunde",
+    "plate": "Kjennemerke",
+    "registration": "Registrering",
+    "template": "Mal",
+    "scaleEu": "Vurdert etter EUs mangelskala fra direktiv 2014/45/EU.",
+    "scaleBasic": "Vurdert godkjent / merknad / ikke godkjent.",
+    "country": "Land: {code}.",
+    "manageInspectionTemplates": "Administrer inspeksjonsmaler",
+    "readMore": "Les mer",
+    "odometer": "Kilometerstand",
+    "engineHours": "Motortimer",
+    "completeTitle": "Fullføre denne inspeksjonen?",
+    "completeBody": "Punktene låses når inspeksjonen fullføres, og resultatet blir «{result}».",
+    "completeBlocked": "{count} punkt(er) som malen krever, gjenstår.",
+    "completeUngraded": "{count} punkt(er) er ikke vurdert.",
+    "completeDangerous": "{count} farlig(e) mangel(er) registrert. Kjøretøyet skal ikke brukes på offentlig vei.",
+    "reopenTitle": "Gjenåpne denne inspeksjonen?",
+    "reopenBody": "Punktene kan redigeres igjen og resultatet går tilbake til pågår. Kontrollør, sertifikatnummer og neste kontroll beholdes.",
+    "reopenShared": "Kundelenken er fortsatt aktiv og vil vise at resultatet endrer seg mens du redigerer. En kontrollseddel som allerede er lastet ned, beholder det gamle resultatet.",
+    "deleteTitle": "Slette denne inspeksjonen?",
+    "deleteBody": "Dette sletter inspeksjonen, vurderingene, merknadene og bildene permanent. Handlingen kan ikke angres.",
+    "cancel": "Avbryt",
+    "completed": "Inspeksjonen er fullført",
+    "reopened": "Inspeksjonen er gjenåpnet",
+    "deleted": "Inspeksjonen er slettet",
+    "completeFailed": "Kunne ikke fullføre inspeksjonen",
+    "reopenFailed": "Kunne ikke gjenåpne inspeksjonen",
+    "deleteFailed": "Kunne ikke slette inspeksjonen",
+    "quoteFailed": "Kunne ikke opprette tilbud",
+    "workOrderFailed": "Kunne ikke opprette arbeidsordre"
+  },
+  "item": {
+    "required": "(påkrevd)",
+    "saving": "Lagrer",
+    "addNote": "Legg til merknad",
+    "hideNote": "Skjul merknad",
+    "addPhoto": "Legg til bilde eller video for {name}",
+    "notesFor": "Merknader for {name}",
+    "describeDefect": "Beskriv mangelen — dette vises på rapporten",
+    "optionalNote": "Legg til en merknad (valgfritt)",
+    "notesRequired": "Beskriv mangelen før denne vurderingen lagres.",
+    "photoRequired": "Det kreves bilde av mangelen her. Inspeksjonen kan ikke fullføres før et bilde er lagt ved.",
+    "reading": "Måling",
+    "readingUnit": "Måling ({unit})",
+    "limit": "Grense: {range}",
+    "recordedValue": "Registrert verdi",
+    "answer": "Svar",
+    "selectAnswer": "Velg et svar",
+    "viewPhoto": "Vis bilde {index} av {name} i full størrelse",
+    "photoAlt": "{name}, bilde {index}",
+    "removePhoto": "Fjern bilde {index} fra {name}",
+    "clearTitle": "Fjerne denne vurderingen?",
+    "clearBody": "Merknaden som er registrert på «{name}» fjernes, og punktet settes tilbake til ikke kontrollert.",
+    "clear": "Fjern",
+    "cancel": "Avbryt",
+    "saveFailed": "Kunne ikke lagre dette punktet",
+    "uploadFailed": "Opplasting mislyktes",
+    "uploadFailedFor": "Kunne ikke laste opp {name}"
+  },
+  "suggestions": {
+    "title": "Vanlige mangler",
+    "add": "Legg til «{text}» og sett vurdering {grade}",
+    "more": "Flere ({count})",
+    "search": "Søk i mangelbeskrivelser",
+    "empty": "Ingen treff.",
+    "source": {
+      "workshop": "Ditt verksted",
+      "history": "Brukt før",
+      "regulation": "Direktiv 2014/45/EU",
+      "general": "Generelt"
+    }
+  },
+  "media": {
+    "viewer": "Bilde {index} av {total}: {caption}",
+    "close": "Lukk bildevisning",
+    "previous": "Forrige bilde",
+    "next": "Neste bilde"
+  },
+  "certificate": {
+    "title": "Kontrollseddel",
+    "description": "Påkrevd på en kontrollseddel etter direktiv 2014/45/EU, vedlegg IV.",
+    "inspector": "Kontrollør",
+    "notRecorded": "Ikke registrert",
+    "addTechnician": "Legg til mekaniker…",
+    "recordedAs": "Registrert som {name}",
+    "vehicleCategory": "Kjøretøygruppe",
+    "mileageAtTest": "{label} ved kontrolltidspunktet",
+    "reportReference": "Rapportreferanse",
+    "reportReferenceHelp": "Din egen referanse for denne rapporten. Er du en godkjent kontrollorganisasjon som utsteder kontrollseddel, skriv inn nummeret her slik at de stemmer overens. Ellers kan feltet stå tomt.",
+    "optional": "Valgfritt",
+    "placeOfTest": "Kontrollsted",
+    "placeholderAddress": "Kontrollsted eller verkstedsadresse",
+    "fromSettings": "Fra verkstedinnstillingene dine.",
+    "change": "Endre",
+    "differsFromWorkshop": "Avviker fra verkstedsadressen din.",
+    "useWorkshopAddress": "Bruk verkstedsadressen",
+    "noWorkshopAddress": "Ingen verkstedsadresse er satt.",
+    "addOne": "Legg til en",
+    "nextTestDue": "Neste kontroll",
+    "notSet": "Ikke satt",
+    "intervalGroup": "Sett dato for neste kontroll ut fra denne kontrollen",
+    "intervalHelp": "Regnes fra datoen for denne kontrollen. To år er EUs utgangspunkt for personbiler og lette varebiler; sjekk intervallet som gjelder i ditt land.",
+    "months6": "6 måneder",
+    "years1": "1 år",
+    "years2": "2 år",
+    "years3": "3 år",
+    "years4": "4 år",
+    "unsaved": "Ulagrede endringer",
+    "saveDetails": "Lagre opplysninger",
+    "saved": "Opplysningene er lagret",
+    "saveFailed": "Kunne ikke lagre opplysningene",
+    "addTechnicianTitle": "Legg til mekaniker",
+    "addTechnicianBody": "Vedkommende legges til i verkstedets liste og kan tildeles både arbeid og inspeksjoner.",
+    "technicianName": "Navn",
+    "technicianPlaceholder": "f.eks. Anna Berg",
+    "addAndSelect": "Legg til og velg",
+    "technicianAdded": "{name} er lagt til og valgt",
+    "technicianFailed": "Kunne ikke legge til mekanikeren",
+    "cancel": "Avbryt"
+  },
+  "templates": {
+    "title": "Inspeksjonsmaler",
+    "description": "Sjekklistene mekanikerne dine går gjennom. Lag én per land, per kjøretøytype eller per tjeneste du selger.",
+    "import": "Importer",
+    "browse": "Bla i sjekklister",
+    "new": "Ny mal",
+    "restoreBanner": "{count} ferdige sjekklister er fjernet fra listen din. Du kan legge dem tilbake.",
+    "restore": "Gjenopprett",
+    "restored": "Gjenopprettet {count} sjekkliste(r)",
+    "allPresent": "Alle sjekklister er allerede her",
+    "restoreFailed": "Kunne ikke legge til sjekklistene",
+    "emptyTitle": "Ingen maler ennå",
+    "emptyBody": "De ferdige sjekklistene, inkludert EUs periodiske kjøretøykontroll, er alle fjernet. Legg dem tilbake, eller bygg din egen fra bunnen.",
+    "restoreChecklists": "Gjenopprett sjekklister",
+    "buildMyOwn": "Bygg min egen",
+    "default": "Standard",
+    "actionsFor": "Handlinger for {name}",
+    "edit": "Rediger",
+    "duplicate": "Dupliser",
+    "export": "Eksporter…",
+    "delete": "Slett",
+    "sections": "{count} gruppe(r)",
+    "checks": "{count} punkt(er)",
+    "measured": "{count} målt",
+    "euScale": "EUs mangelskala",
+    "basicScale": "Godkjent / merknad / ikke godkjent",
+    "deleteTitle": "Slette «{name}»?",
+    "deleteBody": "Dette sletter malen permanent. Inspeksjoner som allerede er utført med den, beholder sin egen kopi av sjekklisten og påvirkes ikke.",
+    "deleted": "Malen er slettet",
+    "deleteFailed": "Kunne ikke slette malen",
+    "duplicated": "Malen er duplisert",
+    "duplicateFailed": "Kunne ikke duplisere malen",
+    "cancel": "Avbryt",
+    "srSections": "Grupper",
+    "srChecks": "Punkter",
+    "srMeasurements": "Målinger"
+  },
+  "presets": {
+    "title": "Start fra en mal",
+    "description": "Hver ferdigmal kopieres inn i verkstedet ditt som en vanlig mal. Endre navn på grupper, flytt punkter og juster alle grenser slik at de passer landet ditt og utstyret ditt.",
+    "search": "Søk på navn, land eller standard",
+    "searchLabel": "Søk i maler",
+    "inYourList": "I listen din",
+    "counts": "{sections} grupper · {checks} punkter",
+    "noMatch": "Ingen mal treffer «{query}».",
+    "warning": "De lovpålagte malene har de vanlige EU-grensene. Kontroller dem mot nasjonale regler før du utsteder en kontrollseddel.",
+    "cancel": "Avbryt",
+    "add": "Legg til mal",
+    "added": "Malen er lagt til. Tilpass den til verkstedet ditt.",
+    "addFailed": "Kunne ikke legge til malen",
+    "group": {
+      "regulatory": "Lovpålagt",
+      "workshop": "Verksted",
+      "specialist": "Spesialist"
+    },
+    "groupDescription": {
+      "regulatory": "Sjekklister for periodisk kjøretøykontroll bygget på direktiv 2014/45/EU.",
+      "workshop": "Generelle service- og salgssjekklister som ikke er knyttet til et regelverk.",
+      "specialist": "Sjekklister for drivlinjer og fartøy som krever egen prosedyre."
+    },
+    "euScale": "EUs mangelskala",
+    "basicScale": "Godkjent / merknad / ikke godkjent"
+  },
+  "builder": {
+    "newTitle": "Ny mal",
+    "editTitle": "Rediger mal",
+    "description": "Bygg sjekklisten verkstedet ditt faktisk bruker. Dra for å endre rekkefølge, og still inn hvert punkt til å registrere en vurdering, en måling, en skreven merknad eller et fast svar.",
+    "name": "Navn",
+    "namePlaceholder": "f.eks. Periodisk kjøretøykontroll",
+    "templateDescription": "Beskrivelse",
+    "descriptionPlaceholder": "Hva sjekklisten brukes til og når.",
+    "country": "Land",
+    "notCountrySpecific": "Ikke landsspesifikk",
+    "countryHelp": "Registreres på rapporten slik at et verksted som jobber over landegrenser kan skille sjekklistene fra hverandre.",
+    "defectScale": "Mangelskala",
+    "scaleEu": "Mindre / vesentlig / farlig (EU)",
+    "scaleBasic": "Godkjent / merknad / ikke godkjent",
+    "scaleEuHelp": "Kategoriene i artikkel 7 nr. 2 i direktiv 2014/45/EU. Et kjøretøy med bare mindre mangler blir fortsatt godkjent.",
+    "scaleBasicHelp": "En enkel treetrinnsskala for servicesjekklister som ikke er en lovpålagt kontroll.",
+    "defaultTemplate": "Standardmal",
+    "defaultHelp": "Forhåndsvalgt når en mekaniker starter en ny inspeksjon.",
+    "summary": "{sections} gruppe(r) · {checks} punkt(er)",
+    "renumber": "Nummerer referanser på nytt",
+    "renumberHelp": "Nummererer hver gruppe og hvert punkt i rekkefølge: 1, 1.1, 1.2, 2. Nye rader nummereres når du legger dem til, så dette trengs bare etter at du har flyttet på ting.",
+    "renumberWarning": "Denne sjekklisten bruker regelverkshenvisninger som 1.1.13. Ny nummerering erstatter dem.",
+    "addSection": "Legg til gruppe",
+    "addCheck": "Legg til punkt",
+    "ref": "Ref.",
+    "sectionName": "Gruppenavn, f.eks. Bremser",
+    "sectionNote": "Valgfri merknad som vises over punktene i denne gruppen",
+    "checkName": "Punktnavn, f.eks. Bremsebelegg",
+    "reorderSection": "Endre rekkefølge på gruppen {name}",
+    "reorderCheck": "Endre rekkefølge på punktet {name}",
+    "removeSection": "Fjern gruppen {name}",
+    "removeCheck": "Fjern punktet {name}",
+    "duplicateCheck": "Dupliser punktet {name}",
+    "configure": "Still inn {name}",
+    "hideSettings": "Skjul innstillinger for {name}",
+    "howRecorded": "Hvordan registreres det?",
+    "guidance": "Veiledning til mekanikeren",
+    "guidancePlaceholder": "Vises under punktnavnet på inspeksjonsskjemaet.",
+    "measurement": "Måling",
+    "unit": "Benevning",
+    "minimum": "Minimum",
+    "maximum": "Maksimum",
+    "outOfRange": "Mangel utenfor intervallet",
+    "rangeHelp": "La en grense stå tom for et ensidig krav, for eksempel minimum 1,6 mm uten maksimum. Målinger utenfor intervallet vurderes automatisk.",
+    "answers": "Svar",
+    "answersHelp": "Skill svarene med komma.",
+    "mustComplete": "Må fylles ut",
+    "photoRequired": "Bilde kreves når det registreres en mangel",
+    "commonWording": "Vanlige mangelbeskrivelser",
+    "commonWordingHelp": "Én formulering per linje. Disse tilbys først når en mekaniker registrerer en mangel her, foran de innebygde, slik at verkstedet ditt kan bli enige om hvordan en feil beskrives.",
+    "inputType": {
+      "condition": "Mangelvurdering",
+      "measurement": "Måling",
+      "text": "Fritekst",
+      "choice": "Valg"
+    },
+    "inputHint": {
+      "condition": "Mekanikeren vurderer punktet etter mangelskalaen.",
+      "measurement": "Registrerer et tall med benevning, vurdert automatisk mot et intervall.",
+      "text": "Registrerer en skreven observasjon, for eksempel et dekkmerke eller en feilkode.",
+      "choice": "Mekanikeren velger ett av flere faste svar."
+    },
+    "cancel": "Avbryt",
+    "create": "Opprett mal",
+    "saveChanges": "Lagre endringer",
+    "created": "Malen er opprettet",
+    "updated": "Malen er oppdatert",
+    "saveFailed": "Kunne ikke lagre malen",
+    "severityAttention": "Mindre mangel",
+    "severityFail": "Vesentlig mangel",
+    "severityDangerous": "Farlig mangel",
+    "answersPlaceholder": "Full, Delvis, Ingen"
+  },
+  "packages": {
+    "exportTitle": "Eksporter «{name}»",
+    "exportDescription": "Laster ned hele sjekklisten som en fil: grupper, punkter, benevninger og grenser. Alle som kjører Torqvoice kan importere den.",
+    "includeWording": "Ta med verkstedets egne mangelbeskrivelser",
+    "includeWordingHelp": "Formuleringene verkstedet ditt har lagt til på punktene. Nyttige for den som mottar filen, men de er fritekst skrevet ved benken og kan navngi en kunde, en kollega eller en lokal ordning. Av med mindre du har lest gjennom dem.",
+    "attributeTo": "Tilskriv (valgfritt)",
+    "attributePlaceholder": "f.eks. Egeland Auto",
+    "attributeHelp": "Skrives inn i filen slik at den som installerer den ser hvor den kommer fra. Ingenting hentes fra kontoen din.",
+    "download": "Last ned",
+    "exported": "Malen er eksportert",
+    "exportFailed": "Kunne ikke eksportere denne malen",
+    "importTitle": "Importer en sjekkliste",
+    "importDescription": "Legger til en sjekkliste fra en Torqvoice-pakkefil. Den kommer inn som en vanlig mal du kan redigere, og erstatter aldri en du allerede har.",
+    "chooseFile": "Velg en pakkefil",
+    "chooseAnother": "Velg en annen fil",
+    "unreadable": "Filen kunne ikke leses. En Torqvoice-pakke er en .json-fil eksportert fra en mal.",
+    "from": "Fra {author}",
+    "limitsWarning": "Grensene i en delt sjekkliste er de forfatteren satte. Kontroller dem mot nasjonale regler før du utsteder noe fra den.",
+    "import": "Importer",
+    "imported": "Importerte {count} mal(er)",
+    "importFailed": "Kunne ikke importere denne pakken",
+    "cancel": "Avbryt"
+  },
+  "list": {
+    "tabAll": "Alle",
+    "tabInProgress": "Pågår",
+    "tabCompleted": "Fullført",
+    "search": "Søk i inspeksjoner…",
+    "new": "Ny inspeksjon",
+    "empty": "Ingen inspeksjoner funnet.",
+    "vehicle": "Kjøretøy",
+    "template": "Mal",
+    "progress": "Fremdrift",
+    "status": "Status",
+    "date": "Dato",
+    "statusInProgress": "Pågår",
+    "statusCompleted": "Fullført"
+  },
+  "new": {
+    "title": "Ny inspeksjon",
+    "vehicle": "Kjøretøy",
+    "searchVehicles": "Søk i kjøretøy…",
+    "loadingVehicles": "Laster kjøretøy…",
+    "noVehicles": "Ingen kjøretøy funnet.",
+    "template": "Mal",
+    "selectTemplate": "Velg mal",
+    "manageTemplates": "Administrer maler",
+    "cancel": "Avbryt",
+    "create": "Opprett inspeksjon",
+    "created": "Inspeksjonen er opprettet",
+    "createFailed": "Kunne ikke opprette inspeksjonen"
+  },
+  "shareDialog": {
+    "title": "Del inspeksjon",
+    "description": "Del en skrivebeskyttet visning av denne inspeksjonen med kunden.",
+    "copyLink": "Kopier lenke",
+    "copied": "Lenken er kopiert",
+    "generated": "Delingslenke opprettet",
+    "revoked": "Delingslenke trukket tilbake"
+  }
+}

+ 11 - 1
messages/nl/audit.json

@@ -98,6 +98,16 @@
     "webhook_create": "Webhook aangemaakt",
     "webhook_update": "Webhook bijgewerkt",
     "webhook_delete": "Webhook verwijderd",
-    "webhook_rotateSecret": "Webhook-geheim geroteerd"
+    "webhook_rotateSecret": "Webhook-geheim geroteerd",
+    "inspection_reopen": "Inspectie heropend",
+    "inspectionTemplate_import": "Inspectiesjabloon geïmporteerd",
+    "scheduled_message_create": "Gepland bericht aangemaakt",
+    "scheduled_message_update": "Gepland bericht bijgewerkt",
+    "scheduled_message_delete": "Gepland bericht verwijderd",
+    "scheduled_message_cancel": "Gepland bericht geannuleerd",
+    "scheduled_message_send_now": "Gepland bericht verzonden",
+    "settings_applyTaxRate": "Btw-tarief toegepast",
+    "settings_convertToExclusive": "Prijzen omgezet naar exclusief btw",
+    "settings_convertToInclusive": "Prijzen omgezet naar inclusief btw"
   }
 }

+ 24 - 0
messages/nl/common.json

@@ -52,5 +52,29 @@
     "updated": "Torqvoice is bijgewerkt naar {version}",
     "whatsNew": "Bekijk wat er nieuw is",
     "dismiss": "Sluiten"
+  },
+  "pagination": {
+    "showing": "{start}–{end} van {total} weergegeven",
+    "perPage": "per pagina",
+    "pageOf": "Pagina {page} van {pages}",
+    "first": "Eerste pagina",
+    "previous": "Vorige pagina",
+    "next": "Volgende pagina",
+    "last": "Laatste pagina"
+  },
+  "shared": {
+    "close": "Sluiten",
+    "viewPlans": "Abonnementen bekijken",
+    "manageLicense": "Licentie beheren",
+    "installOwn": "Installeer je eigen",
+    "install": "Installeren",
+    "installApp": "App installeren",
+    "installTorqvoice": "Torqvoice installeren",
+    "dismiss": "Sluiten",
+    "useCamera": "Camera gebruiken",
+    "enterManually": "Handmatig invoeren",
+    "enterBarcode": "Barcode invoeren…",
+    "scanHint": "Richt uw camera op een barcode of voer hem handmatig in",
+    "iosInstallHint": "Tik op Deel > Zet op beginscherm"
   }
 }

+ 396 - 0
messages/nl/inspections.json

@@ -0,0 +1,396 @@
+{
+  "grades": {
+    "eu": {
+      "pass": "Geen gebrek",
+      "attention": "Klein gebrek",
+      "fail": "Groot gebrek",
+      "dangerous": "Gevaarlijk gebrek",
+      "not_inspected": "Niet gekeurd"
+    },
+    "basic": {
+      "pass": "Goedgekeurd",
+      "attention": "Aandacht",
+      "fail": "Afgekeurd",
+      "dangerous": "Gevaarlijk",
+      "not_inspected": "Niet gekeurd"
+    },
+    "short": {
+      "pass": "OK",
+      "attention": "Klein",
+      "fail": "Groot",
+      "dangerous": "Gevaarlijk",
+      "not_inspected": "–"
+    },
+    "hint": {
+      "pass": "Voldoet aan de eisen. Niets te melden.",
+      "attention": "Geen wezenlijk effect op de veiligheid. Voertuig is goedgekeurd; herstel wanneer het uitkomt.",
+      "fail": "Kan de veiligheid schaden of het milieu belasten. Voertuig is afgekeurd; herstellen en opnieuw laten keuren.",
+      "dangerous": "Direct en onmiddellijk gevaar voor de verkeersveiligheid. Het voertuig mag niet rijden.",
+      "not_inspected": "Dit punt is niet uitgevoerd."
+    }
+  },
+  "results": {
+    "pass": "Goedgekeurd",
+    "pass_minor": "Goedgekeurd met kleine gebreken",
+    "fail": "Afgekeurd — grote gebreken",
+    "fail_dangerous": "Afgekeurd — gevaarlijke gebreken",
+    "incomplete": "Niet afgerond",
+    "detail": {
+      "pass": "Er zijn geen gebreken vastgesteld.",
+      "pass_minor": "Het voertuig is goedgekeurd. Herstel de kleine gebreken zonder onnodig uitstel.",
+      "fail": "Het voertuig is afgekeurd. De gebreken moeten worden hersteld en het voertuig opnieuw gekeurd.",
+      "fail_dangerous": "Het voertuig mag niet op de openbare weg worden gebruikt totdat de gevaarlijke gebreken zijn hersteld.",
+      "incomplete": "Deze keuring loopt nog."
+    }
+  },
+  "page": {
+    "back": "Inspecties",
+    "statusInProgress": "Loopt",
+    "statusCompleted": "Afgerond",
+    "complete": "Afronden",
+    "reopen": "Heropenen",
+    "share": "Delen",
+    "save": "Opslaan",
+    "saving": "Opslaan…",
+    "savedAt": "Opgeslagen {time}",
+    "savesAsYouGo": "Slaat op tijdens het werk",
+    "saveFailed": "Sommige wijzigingen zijn niet opgeslagen",
+    "allSaved": "Alle wijzigingen zijn opgeslagen",
+    "moreActions": "Meer acties",
+    "downloadCertificate": "Keuringsrapport downloaden",
+    "manageTemplates": "Sjablonen beheren",
+    "delete": "Verwijderen",
+    "createWorkOrder": "Werkorder aanmaken",
+    "viewWorkOrder": "Werkorder bekijken",
+    "createQuote": "Offerte aanmaken",
+    "viewQuote": "Offerte bekijken",
+    "needsDefect": "Beschikbaar zodra een gebrek is vastgelegd",
+    "workOrderRaised": "Werkorder {number} op {date} vanuit deze inspectie aangemaakt",
+    "open": "Openen",
+    "quoteCreated": "Offerte aangemaakt door {name} op {date}",
+    "quoteRequested": "De klant vroeg een offerte voor {count} punt(en)",
+    "stillToGrade": "{count} van {total} punten moeten nog beoordeeld worden.",
+    "progress": "Voortgang",
+    "checksGraded": "punten beoordeeld",
+    "progressLabel": "{graded} van {total} punten beoordeeld: {pass} geen gebrek, {attention} klein, {fail} groot, {dangerous} gevaarlijk.",
+    "sections": "Groepen",
+    "graded": "{graded}/{total} beoordeeld",
+    "vehicle": "Voertuig",
+    "vessel": "Vaartuig",
+    "customer": "Klant",
+    "plate": "Kenteken",
+    "registration": "Registratie",
+    "template": "Sjabloon",
+    "scaleEu": "Beoordeeld op de EU-gebrekenschaal uit richtlijn 2014/45/EU.",
+    "scaleBasic": "Beoordeeld goedgekeurd / aandacht / afgekeurd.",
+    "country": "Land: {code}.",
+    "manageInspectionTemplates": "Inspectiesjablonen beheren",
+    "readMore": "Meer lezen",
+    "odometer": "Kilometerstand",
+    "engineHours": "Draaiuren",
+    "completeTitle": "Deze inspectie afronden?",
+    "completeBody": "De punten worden bij afronding vergrendeld en de uitkomst wordt “{result}”.",
+    "completeBlocked": "{count} punt(en) die het sjabloon als verplicht markeert, staan nog open.",
+    "completeUngraded": "{count} punt(en) zijn niet beoordeeld.",
+    "completeDangerous": "{count} gevaarlijk(e) gebrek(en) vastgelegd. Het voertuig mag niet de openbare weg op.",
+    "reopenTitle": "Deze inspectie heropenen?",
+    "reopenBody": "De punten worden weer bewerkbaar en de uitkomst gaat terug naar loopt. Keurmeester, certificaatnummer en volgende keuring blijven behouden.",
+    "reopenShared": "De klantlink blijft actief en laat zien hoe de uitkomst verandert terwijl u werkt. Een al gedownload rapport houdt de oude uitkomst.",
+    "deleteTitle": "Deze inspectie verwijderen?",
+    "deleteBody": "Hiermee worden de inspectie, de beoordelingen, notities en foto's definitief verwijderd. Dit kan niet ongedaan worden gemaakt.",
+    "cancel": "Annuleren",
+    "completed": "Inspectie afgerond",
+    "reopened": "Inspectie heropend",
+    "deleted": "Inspectie verwijderd",
+    "completeFailed": "Inspectie kon niet worden afgerond",
+    "reopenFailed": "Inspectie kon niet worden heropend",
+    "deleteFailed": "Inspectie kon niet worden verwijderd",
+    "quoteFailed": "Offerte kon niet worden aangemaakt",
+    "workOrderFailed": "Werkorder kon niet worden aangemaakt"
+  },
+  "item": {
+    "required": "(verplicht)",
+    "saving": "Opslaan",
+    "addNote": "Notitie toevoegen",
+    "hideNote": "Notitie verbergen",
+    "addPhoto": "Foto of video toevoegen aan {name}",
+    "notesFor": "Notities voor {name}",
+    "describeDefect": "Beschrijf het gebrek — dit komt op het rapport",
+    "optionalNote": "Notitie toevoegen (optioneel)",
+    "notesRequired": "Beschrijf het gebrek voordat deze beoordeling wordt opgeslagen.",
+    "photoRequired": "Hier is een foto van het gebrek vereist. De inspectie kan pas worden afgerond als er een is toegevoegd.",
+    "reading": "Meetwaarde",
+    "readingUnit": "Meetwaarde ({unit})",
+    "limit": "Grenswaarde: {range}",
+    "recordedValue": "Vastgelegde waarde",
+    "answer": "Antwoord",
+    "selectAnswer": "Kies een antwoord",
+    "viewPhoto": "Foto {index} van {name} op volledige grootte bekijken",
+    "photoAlt": "{name}, foto {index}",
+    "removePhoto": "Foto {index} van {name} verwijderen",
+    "clearTitle": "Deze beoordeling wissen?",
+    "clearBody": "De notitie bij “{name}” wordt verwijderd en het punt gaat terug naar niet gekeurd.",
+    "clear": "Wissen",
+    "cancel": "Annuleren",
+    "saveFailed": "Dit punt kon niet worden opgeslagen",
+    "uploadFailed": "Uploaden mislukt",
+    "uploadFailedFor": "{name} kon niet worden geüpload"
+  },
+  "suggestions": {
+    "title": "Veelvoorkomende gebreken",
+    "add": "“{text}” toevoegen en beoordelen als {grade}",
+    "more": "Meer ({count})",
+    "search": "Zoek in gebreksomschrijvingen",
+    "empty": "Geen passende omschrijving.",
+    "source": {
+      "workshop": "Uw werkplaats",
+      "history": "Eerder gebruikt",
+      "regulation": "Richtlijn 2014/45/EU",
+      "general": "Algemeen"
+    }
+  },
+  "media": {
+    "viewer": "Afbeelding {index} van {total}: {caption}",
+    "close": "Afbeeldingsweergave sluiten",
+    "previous": "Vorige afbeelding",
+    "next": "Volgende afbeelding"
+  },
+  "certificate": {
+    "title": "Gegevens keuringsrapport",
+    "description": "Vereist op een keuringsrapport volgens richtlijn 2014/45/EU, bijlage IV.",
+    "inspector": "Keurmeester",
+    "notRecorded": "Niet vastgelegd",
+    "addTechnician": "Monteur toevoegen…",
+    "recordedAs": "Vastgelegd als {name}",
+    "vehicleCategory": "Voertuigcategorie",
+    "mileageAtTest": "{label} op het moment van keuring",
+    "reportReference": "Rapportreferentie",
+    "reportReferenceHelp": "Uw eigen referentie voor dit rapport. Bent u een erkende keuringsinstantie die een officieel keuringsrapport afgeeft, vul dan hier het nummer in zodat beide overeenkomen. Laat het anders leeg.",
+    "optional": "Optioneel",
+    "placeOfTest": "Plaats van keuring",
+    "placeholderAddress": "Keuringsstation of werkplaatsadres",
+    "fromSettings": "Uit uw werkplaatsinstellingen.",
+    "change": "Wijzigen",
+    "differsFromWorkshop": "Wijkt af van uw werkplaatsadres.",
+    "useWorkshopAddress": "Werkplaatsadres gebruiken",
+    "noWorkshopAddress": "Er is geen werkplaatsadres ingesteld.",
+    "addOne": "Er een toevoegen",
+    "nextTestDue": "Volgende keuring",
+    "notSet": "Niet ingesteld",
+    "intervalGroup": "Stel de volgende keuringsdatum in vanaf de datum van deze keuring",
+    "intervalHelp": "Gerekend vanaf de datum van deze keuring. Twee jaar is het EU-uitgangspunt voor personenauto's en lichte bedrijfsauto's; controleer welk interval in uw land geldt.",
+    "months6": "6 maanden",
+    "years1": "1 jaar",
+    "years2": "2 jaar",
+    "years3": "3 jaar",
+    "years4": "4 jaar",
+    "unsaved": "Niet-opgeslagen wijzigingen",
+    "saveDetails": "Gegevens opslaan",
+    "saved": "Gegevens opgeslagen",
+    "saveFailed": "De gegevens konden niet worden opgeslagen",
+    "addTechnicianTitle": "Monteur toevoegen",
+    "addTechnicianBody": "Deze persoon komt op de werkplaatslijst en kan zowel werk als inspecties toegewezen krijgen.",
+    "technicianName": "Naam",
+    "technicianPlaceholder": "bijv. Anna Berg",
+    "addAndSelect": "Toevoegen en selecteren",
+    "technicianAdded": "{name} toegevoegd en geselecteerd",
+    "technicianFailed": "De monteur kon niet worden toegevoegd",
+    "cancel": "Annuleren"
+  },
+  "templates": {
+    "title": "Inspectiesjablonen",
+    "description": "De checklists die uw monteurs doorlopen. Maak er één per land, per voertuigtype of per dienst die u verkoopt.",
+    "import": "Importeren",
+    "browse": "Checklists doorzoeken",
+    "new": "Nieuw sjabloon",
+    "restoreBanner": "{count} kant-en-klare checklists zijn uit uw lijst verwijderd. U kunt ze terugzetten.",
+    "restore": "Terugzetten",
+    "restored": "{count} checklist(s) teruggezet",
+    "allPresent": "Alle checklists staan er al",
+    "restoreFailed": "De checklists konden niet worden toegevoegd",
+    "emptyTitle": "Nog geen sjablonen",
+    "emptyBody": "De kant-en-klare checklists, waaronder de periodieke EU-keuring, zijn allemaal verwijderd. Zet ze terug of bouw uw eigen vanaf nul.",
+    "restoreChecklists": "Checklists terugzetten",
+    "buildMyOwn": "Zelf bouwen",
+    "default": "Standaard",
+    "actionsFor": "Acties voor {name}",
+    "edit": "Bewerken",
+    "duplicate": "Dupliceren",
+    "export": "Exporteren…",
+    "delete": "Verwijderen",
+    "sections": "{count} groep(en)",
+    "checks": "{count} punt(en)",
+    "measured": "{count} gemeten",
+    "euScale": "EU-gebrekenschaal",
+    "basicScale": "Goedgekeurd / aandacht / afgekeurd",
+    "deleteTitle": "“{name}” verwijderen?",
+    "deleteBody": "Hiermee wordt het sjabloon definitief verwijderd. Al uitgevoerde inspecties houden hun eigen kopie van de checklist en worden niet geraakt.",
+    "deleted": "Sjabloon verwijderd",
+    "deleteFailed": "Sjabloon kon niet worden verwijderd",
+    "duplicated": "Sjabloon gedupliceerd",
+    "duplicateFailed": "Sjabloon kon niet worden gedupliceerd",
+    "cancel": "Annuleren",
+    "srSections": "Groepen",
+    "srChecks": "Punten",
+    "srMeasurements": "Metingen"
+  },
+  "presets": {
+    "title": "Beginnen met een sjabloon",
+    "description": "Elk sjabloon wordt als gewoon sjabloon naar uw werkplaats gekopieerd. Hernoem groepen, verplaats punten en pas elke grenswaarde aan uw land en uw apparatuur aan.",
+    "search": "Zoek op naam, land of standaard",
+    "searchLabel": "Sjablonen zoeken",
+    "inYourList": "In uw lijst",
+    "counts": "{sections} groepen · {checks} punten",
+    "noMatch": "Geen sjabloon komt overeen met “{query}”.",
+    "warning": "De wettelijke sjablonen bevatten de gangbare EU-grenswaarden. Controleer ze aan de hand van uw nationale regels voordat u een keuringsrapport afgeeft.",
+    "cancel": "Annuleren",
+    "add": "Sjabloon toevoegen",
+    "added": "Sjabloon toegevoegd. Pas het aan uw werkplaats aan.",
+    "addFailed": "Sjabloon kon niet worden toegevoegd",
+    "group": {
+      "regulatory": "Wettelijk",
+      "workshop": "Werkplaats",
+      "specialist": "Specialistisch"
+    },
+    "groupDescription": {
+      "regulatory": "Checklists voor de periodieke keuring, gebouwd op richtlijn 2014/45/EU.",
+      "workshop": "Algemene service- en verkoopchecklists die niet aan een regeling zijn gebonden.",
+      "specialist": "Checklists voor aandrijvingen en vaartuigen die een eigen procedure vragen."
+    },
+    "euScale": "EU-gebrekenschaal",
+    "basicScale": "Goedgekeurd / aandacht / afgekeurd"
+  },
+  "builder": {
+    "newTitle": "Nieuw sjabloon",
+    "editTitle": "Sjabloon bewerken",
+    "description": "Bouw de checklist die uw werkplaats echt gebruikt. Sleep om te herordenen en stel elk punt in om een beoordeling, een meting, een geschreven notitie of een vast antwoord vast te leggen.",
+    "name": "Naam",
+    "namePlaceholder": "bijv. Periodieke keuring",
+    "templateDescription": "Omschrijving",
+    "descriptionPlaceholder": "Waar deze checklist voor is en wanneer u hem gebruikt.",
+    "country": "Land",
+    "notCountrySpecific": "Niet landspecifiek",
+    "countryHelp": "Wordt op het rapport vastgelegd zodat een werkplaats die over de grens werkt haar checklists uit elkaar kan houden.",
+    "defectScale": "Gebrekenschaal",
+    "scaleEu": "Klein / groot / gevaarlijk (EU)",
+    "scaleBasic": "Goedgekeurd / aandacht / afgekeurd",
+    "scaleEuHelp": "De categorieën uit artikel 7, lid 2, van richtlijn 2014/45/EU. Een voertuig met uitsluitend kleine gebreken is nog steeds goedgekeurd.",
+    "scaleBasicHelp": "Een eenvoudige schaal met drie stappen voor servicechecklists die geen wettelijke keuring zijn.",
+    "defaultTemplate": "Standaardsjabloon",
+    "defaultHelp": "Vooraf geselecteerd wanneer een monteur een nieuwe inspectie start.",
+    "summary": "{sections} groep(en) · {checks} punt(en)",
+    "renumber": "Verwijzingen hernummeren",
+    "renumberHelp": "Nummert elke groep en elk punt op volgorde: 1, 1.1, 1.2, 2. Nieuwe regels krijgen bij toevoegen al een nummer, dus dit hebt u alleen nodig na het verplaatsen.",
+    "renumberWarning": "Deze checklist gebruikt verwijzingen naar regelgeving zoals 1.1.13. Hernummeren vervangt ze.",
+    "addSection": "Groep toevoegen",
+    "addCheck": "Punt toevoegen",
+    "ref": "Ref.",
+    "sectionName": "Groepsnaam, bijv. Reminrichting",
+    "sectionNote": "Optionele notitie boven de punten in deze groep",
+    "checkName": "Naam van het punt, bijv. Remvoeringen",
+    "reorderSection": "Groep {name} herordenen",
+    "reorderCheck": "Punt {name} herordenen",
+    "removeSection": "Groep {name} verwijderen",
+    "removeCheck": "Punt {name} verwijderen",
+    "duplicateCheck": "Punt {name} dupliceren",
+    "configure": "{name} instellen",
+    "hideSettings": "Instellingen voor {name} verbergen",
+    "howRecorded": "Hoe wordt het vastgelegd?",
+    "guidance": "Toelichting voor de monteur",
+    "guidancePlaceholder": "Wordt op het inspectieformulier onder de naam van het punt getoond.",
+    "measurement": "Meting",
+    "unit": "Eenheid",
+    "minimum": "Minimum",
+    "maximum": "Maximum",
+    "outOfRange": "Gebrek buiten het bereik",
+    "rangeHelp": "Laat een grens leeg voor een eenzijdige eis, bijvoorbeeld een minimum van 1,6 mm zonder maximum. Waarden buiten het bereik worden automatisch beoordeeld.",
+    "answers": "Antwoorden",
+    "answersHelp": "Scheid de antwoorden met komma's.",
+    "mustComplete": "Moet worden ingevuld",
+    "photoRequired": "Foto vereist wanneer een gebrek wordt vastgelegd",
+    "commonWording": "Veelgebruikte gebreksomschrijvingen",
+    "commonWordingHelp": "Eén omschrijving per regel. Deze worden hier als eerste aangeboden wanneer een monteur een gebrek vastlegt, vóór de ingebouwde teksten, zodat uw werkplaats het eens kan worden over de omschrijving.",
+    "inputType": {
+      "condition": "Gebrekbeoordeling",
+      "measurement": "Meting",
+      "text": "Vrije tekst",
+      "choice": "Keuze"
+    },
+    "inputHint": {
+      "condition": "De monteur beoordeelt het punt volgens de gebrekenschaal.",
+      "measurement": "Legt een getal met eenheid vast, automatisch beoordeeld tegen een bereik.",
+      "text": "Legt een geschreven waarneming vast, bijvoorbeeld een bandenmerk of foutcode.",
+      "choice": "De monteur kiest één antwoord uit een vaste lijst."
+    },
+    "cancel": "Annuleren",
+    "create": "Sjabloon aanmaken",
+    "saveChanges": "Wijzigingen opslaan",
+    "created": "Sjabloon aangemaakt",
+    "updated": "Sjabloon bijgewerkt",
+    "saveFailed": "Sjabloon kon niet worden opgeslagen",
+    "severityAttention": "Klein gebrek",
+    "severityFail": "Groot gebrek",
+    "severityDangerous": "Gevaarlijk gebrek",
+    "answersPlaceholder": "Volledig, Gedeeltelijk, Geen"
+  },
+  "packages": {
+    "exportTitle": "“{name}” exporteren",
+    "exportDescription": "Downloadt de hele checklist als bestand: groepen, punten, eenheden en grenswaarden. Iedereen die Torqvoice draait kan hem importeren.",
+    "includeWording": "Eigen gebreksomschrijvingen meesturen",
+    "includeWordingHelp": "De omschrijvingen die uw werkplaats aan punten heeft toegevoegd. Nuttig voor de ontvanger, maar het is vrije tekst die aan de werkbank is getypt en kan een klant, een collega of een lokale afspraak noemen. Uit, tenzij u ze hebt gelezen.",
+    "attributeTo": "Toeschrijven aan (optioneel)",
+    "attributePlaceholder": "bijv. Egeland Auto",
+    "attributeHelp": "Wordt in het bestand geschreven zodat wie het installeert ziet waar het vandaan komt. Er wordt niets uit uw account overgenomen.",
+    "download": "Downloaden",
+    "exported": "Sjabloon geëxporteerd",
+    "exportFailed": "Dit sjabloon kon niet worden geëxporteerd",
+    "importTitle": "Een checklist importeren",
+    "importDescription": "Voegt een checklist toe uit een Torqvoice-pakketbestand. Hij komt binnen als gewoon bewerkbaar sjabloon en vervangt nooit een bestaand sjabloon.",
+    "chooseFile": "Kies een pakketbestand",
+    "chooseAnother": "Kies een ander bestand",
+    "unreadable": "Dat bestand kon niet worden gelezen. Een Torqvoice-pakket is een .json-bestand dat uit een sjabloon is geëxporteerd.",
+    "from": "Van {author}",
+    "limitsWarning": "De grenswaarden in een gedeelde checklist zijn die van de auteur. Controleer ze aan de hand van uw nationale regels voordat u er iets uit afgeeft.",
+    "import": "Importeren",
+    "imported": "{count} sjabloon/sjablonen geïmporteerd",
+    "importFailed": "Dit pakket kon niet worden geïmporteerd",
+    "cancel": "Annuleren"
+  },
+  "list": {
+    "tabAll": "Alle",
+    "tabInProgress": "Loopt",
+    "tabCompleted": "Afgerond",
+    "search": "Inspecties zoeken…",
+    "new": "Nieuwe inspectie",
+    "empty": "Geen inspecties gevonden.",
+    "vehicle": "Voertuig",
+    "template": "Sjabloon",
+    "progress": "Voortgang",
+    "status": "Status",
+    "date": "Datum",
+    "statusInProgress": "Loopt",
+    "statusCompleted": "Afgerond"
+  },
+  "new": {
+    "title": "Nieuwe inspectie",
+    "vehicle": "Voertuig",
+    "searchVehicles": "Voertuigen zoeken…",
+    "loadingVehicles": "Voertuigen laden…",
+    "noVehicles": "Geen voertuigen gevonden.",
+    "template": "Sjabloon",
+    "selectTemplate": "Kies een sjabloon",
+    "manageTemplates": "Sjablonen beheren",
+    "cancel": "Annuleren",
+    "create": "Inspectie aanmaken",
+    "created": "Inspectie aangemaakt",
+    "createFailed": "Inspectie kon niet worden aangemaakt"
+  },
+  "shareDialog": {
+    "title": "Inspectie delen",
+    "description": "Deel een alleen-lezen weergave van deze inspectie met uw klant.",
+    "copyLink": "Link kopiëren",
+    "copied": "Link gekopieerd naar klembord",
+    "generated": "Deellink aangemaakt",
+    "revoked": "Deellink ingetrokken"
+  }
+}

+ 11 - 1
messages/pl/audit.json

@@ -98,6 +98,16 @@
     "webhook_create": "Utworzono webhook",
     "webhook_update": "Zaktualizowano webhook",
     "webhook_delete": "Usunięto webhook",
-    "webhook_rotateSecret": "Zrotowano sekret webhooka"
+    "webhook_rotateSecret": "Zrotowano sekret webhooka",
+    "inspection_reopen": "Otwarto inspekcję ponownie",
+    "inspectionTemplate_import": "Zaimportowano szablon inspekcji",
+    "scheduled_message_create": "Utworzono zaplanowaną wiadomość",
+    "scheduled_message_update": "Zaktualizowano zaplanowaną wiadomość",
+    "scheduled_message_delete": "Usunięto zaplanowaną wiadomość",
+    "scheduled_message_cancel": "Anulowano zaplanowaną wiadomość",
+    "scheduled_message_send_now": "Wysłano zaplanowaną wiadomość",
+    "settings_applyTaxRate": "Zastosowano stawkę podatku",
+    "settings_convertToExclusive": "Przeliczono ceny na netto",
+    "settings_convertToInclusive": "Przeliczono ceny na brutto"
   }
 }

+ 24 - 0
messages/pl/common.json

@@ -52,5 +52,29 @@
     "updated": "Torqvoice zaktualizowano do wersji {version}",
     "whatsNew": "Zobacz nowości",
     "dismiss": "Zamknij"
+  },
+  "pagination": {
+    "showing": "Wyświetlane {start}–{end} z {total}",
+    "perPage": "na stronę",
+    "pageOf": "Strona {page} z {pages}",
+    "first": "Pierwsza strona",
+    "previous": "Poprzednia strona",
+    "next": "Następna strona",
+    "last": "Ostatnia strona"
+  },
+  "shared": {
+    "close": "Zamknij",
+    "viewPlans": "Zobacz plany",
+    "manageLicense": "Zarządzaj licencją",
+    "installOwn": "Zainstaluj własną",
+    "install": "Zainstaluj",
+    "installApp": "Zainstaluj aplikację",
+    "installTorqvoice": "Zainstaluj Torqvoice",
+    "dismiss": "Zamknij",
+    "useCamera": "Użyj aparatu",
+    "enterManually": "Wprowadź ręcznie",
+    "enterBarcode": "Wprowadź kod kreskowy…",
+    "scanHint": "Skieruj aparat na kod kreskowy albo wprowadź go ręcznie",
+    "iosInstallHint": "Dotknij Udostępnij > Do ekranu początkowego"
   }
 }

+ 396 - 0
messages/pl/inspections.json

@@ -0,0 +1,396 @@
+{
+  "grades": {
+    "eu": {
+      "pass": "Brak usterki",
+      "attention": "Usterka drobna",
+      "fail": "Usterka poważna",
+      "dangerous": "Usterka stwarzająca zagrożenie",
+      "not_inspected": "Nie sprawdzono"
+    },
+    "basic": {
+      "pass": "Pozytywny",
+      "attention": "Uwaga",
+      "fail": "Negatywny",
+      "dangerous": "Niebezpieczne",
+      "not_inspected": "Nie sprawdzono"
+    },
+    "short": {
+      "pass": "OK",
+      "attention": "Drobna",
+      "fail": "Poważna",
+      "dangerous": "Zagrożenie",
+      "not_inspected": "–"
+    },
+    "hint": {
+      "pass": "Spełnia wymagania. Nie ma czego zgłaszać.",
+      "attention": "Bez istotnego wpływu na bezpieczeństwo. Pojazd uzyskuje wynik pozytywny; usunąć przy okazji.",
+      "fail": "Może pogorszyć bezpieczeństwo lub oddziaływać na środowisko. Wynik negatywny; usunąć i poddać badaniu ponownemu.",
+      "dangerous": "Bezpośrednie i natychmiastowe zagrożenie dla bezpieczeństwa ruchu. Pojazdem nie wolno jechać.",
+      "not_inspected": "Ta pozycja nie została wykonana."
+    }
+  },
+  "results": {
+    "pass": "Wynik pozytywny",
+    "pass_minor": "Pozytywny z usterkami drobnymi",
+    "fail": "Negatywny — usterki poważne",
+    "fail_dangerous": "Negatywny — usterki stwarzające zagrożenie",
+    "incomplete": "Nieukończone",
+    "detail": {
+      "pass": "Nie stwierdzono usterek.",
+      "pass_minor": "Pojazd uzyskał wynik pozytywny. Usuń usterki drobne bez zbędnej zwłoki.",
+      "fail": "Pojazd uzyskał wynik negatywny. Usterki należy usunąć, a pojazd poddać ponownemu badaniu.",
+      "fail_dangerous": "Pojazdem nie wolno poruszać się po drogach publicznych do czasu usunięcia usterek stwarzających zagrożenie.",
+      "incomplete": "To badanie jest wciąż w toku."
+    }
+  },
+  "page": {
+    "back": "Inspekcje",
+    "statusInProgress": "W toku",
+    "statusCompleted": "Zakończona",
+    "complete": "Zakończ",
+    "reopen": "Otwórz ponownie",
+    "share": "Udostępnij",
+    "save": "Zapisz",
+    "saving": "Zapisywanie…",
+    "savedAt": "Zapisano {time}",
+    "savesAsYouGo": "Zapisuje na bieżąco",
+    "saveFailed": "Niektóre zmiany nie zostały zapisane",
+    "allSaved": "Wszystkie zmiany są zapisane",
+    "moreActions": "Więcej działań",
+    "downloadCertificate": "Pobierz zaświadczenie",
+    "manageTemplates": "Zarządzaj szablonami",
+    "delete": "Usuń",
+    "createWorkOrder": "Utwórz zlecenie",
+    "viewWorkOrder": "Zobacz zlecenie",
+    "createQuote": "Utwórz wycenę",
+    "viewQuote": "Zobacz wycenę",
+    "needsDefect": "Dostępne, gdy zostanie zapisana usterka",
+    "workOrderRaised": "Zlecenie {number} utworzone z tej inspekcji dnia {date}",
+    "open": "Otwórz",
+    "quoteCreated": "Wycena utworzona przez {name} dnia {date}",
+    "quoteRequested": "Klient poprosił o wycenę {count} pozycji",
+    "stillToGrade": "Pozostało {count} z {total} pozycji do oceny.",
+    "progress": "Postęp",
+    "checksGraded": "pozycji ocenionych",
+    "progressLabel": "Oceniono {graded} z {total} pozycji: {pass} bez usterki, {attention} drobne, {fail} poważne, {dangerous} stwarzające zagrożenie.",
+    "sections": "Grupy",
+    "graded": "{graded}/{total} ocenionych",
+    "vehicle": "Pojazd",
+    "vessel": "Jednostka pływająca",
+    "customer": "Klient",
+    "plate": "Numer rejestracyjny",
+    "registration": "Rejestracja",
+    "template": "Szablon",
+    "scaleEu": "Oceniane według unijnej skali usterek z dyrektywy 2014/45/UE.",
+    "scaleBasic": "Oceniane pozytywny / uwaga / negatywny.",
+    "country": "Kraj: {code}.",
+    "manageInspectionTemplates": "Zarządzaj szablonami inspekcji",
+    "readMore": "Dowiedz się więcej",
+    "odometer": "Przebieg",
+    "engineHours": "Motogodziny",
+    "completeTitle": "Zakończyć tę inspekcję?",
+    "completeBody": "Pozycje zostają zablokowane po zakończeniu, a wynik staje się „{result}”.",
+    "completeBlocked": "Pozostaje {count} pozycji, które szablon oznacza jako obowiązkowe.",
+    "completeUngraded": "{count} pozycji nie zostało ocenionych.",
+    "completeDangerous": "Zapisano {count} usterek stwarzających zagrożenie. Pojazdem nie wolno poruszać się po drogach publicznych.",
+    "reopenTitle": "Otworzyć tę inspekcję ponownie?",
+    "reopenBody": "Pozycje znów będzie można edytować, a wynik wróci do w toku. Diagnosta, numer zaświadczenia i data następnego badania zostają zachowane.",
+    "reopenShared": "Link dla klienta pozostaje aktywny i pokaże zmieniający się wynik w trakcie edycji. Pobrane wcześniej zaświadczenie zachowa poprzedni wynik.",
+    "deleteTitle": "Usunąć tę inspekcję?",
+    "deleteBody": "To trwale usuwa inspekcję, jej oceny, uwagi i zdjęcia. Nie można tego cofnąć.",
+    "cancel": "Anuluj",
+    "completed": "Inspekcja zakończona",
+    "reopened": "Inspekcja otwarta ponownie",
+    "deleted": "Inspekcja usunięta",
+    "completeFailed": "Nie udało się zakończyć inspekcji",
+    "reopenFailed": "Nie udało się otworzyć inspekcji ponownie",
+    "deleteFailed": "Nie udało się usunąć inspekcji",
+    "quoteFailed": "Nie udało się utworzyć wyceny",
+    "workOrderFailed": "Nie udało się utworzyć zlecenia"
+  },
+  "item": {
+    "required": "(wymagane)",
+    "saving": "Zapisywanie",
+    "addNote": "Dodaj uwagę",
+    "hideNote": "Ukryj uwagę",
+    "addPhoto": "Dodaj zdjęcie lub film do {name}",
+    "notesFor": "Uwagi do {name}",
+    "describeDefect": "Opisz usterkę — pojawi się w raporcie",
+    "optionalNote": "Dodaj uwagę (opcjonalnie)",
+    "notesRequired": "Opisz usterkę, zanim ta ocena zostanie zapisana.",
+    "photoRequired": "Tutaj wymagane jest zdjęcie usterki. Inspekcji nie można zakończyć, dopóki nie zostanie dołączone.",
+    "reading": "Odczyt",
+    "readingUnit": "Odczyt ({unit})",
+    "limit": "Wartość graniczna: {range}",
+    "recordedValue": "Zapisana wartość",
+    "answer": "Odpowiedź",
+    "selectAnswer": "Wybierz odpowiedź",
+    "viewPhoto": "Zobacz zdjęcie {index} pozycji {name} w pełnym rozmiarze",
+    "photoAlt": "{name}, zdjęcie {index}",
+    "removePhoto": "Usuń zdjęcie {index} z pozycji {name}",
+    "clearTitle": "Wyczyścić tę ocenę?",
+    "clearBody": "Uwaga zapisana przy „{name}” zostanie usunięta, a pozycja wróci do nie sprawdzono.",
+    "clear": "Wyczyść",
+    "cancel": "Anuluj",
+    "saveFailed": "Nie udało się zapisać tej pozycji",
+    "uploadFailed": "Przesyłanie nie powiodło się",
+    "uploadFailedFor": "Nie udało się przesłać {name}"
+  },
+  "suggestions": {
+    "title": "Typowe usterki",
+    "add": "Dodaj „{text}” i oceń jako {grade}",
+    "more": "Więcej ({count})",
+    "search": "Szukaj sformułowania",
+    "empty": "Brak pasującego sformułowania.",
+    "source": {
+      "workshop": "Twój warsztat",
+      "history": "Użyte wcześniej",
+      "regulation": "Dyrektywa 2014/45/UE",
+      "general": "Ogólne"
+    }
+  },
+  "media": {
+    "viewer": "Obraz {index} z {total}: {caption}",
+    "close": "Zamknij podgląd",
+    "previous": "Poprzedni obraz",
+    "next": "Następny obraz"
+  },
+  "certificate": {
+    "title": "Dane zaświadczenia",
+    "description": "Wymagane w zaświadczeniu o badaniu technicznym zgodnie z dyrektywą 2014/45/UE, załącznik IV.",
+    "inspector": "Diagnosta",
+    "notRecorded": "Nie zapisano",
+    "addTechnician": "Dodaj diagnostę…",
+    "recordedAs": "Zapisano jako {name}",
+    "vehicleCategory": "Kategoria pojazdu",
+    "mileageAtTest": "{label} w chwili badania",
+    "reportReference": "Oznaczenie raportu",
+    "reportReferenceHelp": "Twoje własne oznaczenie tego raportu. Jeśli jesteś uprawnioną stacją wystawiającą urzędowe zaświadczenie, wpisz tutaj jego numer, aby oba się zgadzały. W przeciwnym razie zostaw puste.",
+    "optional": "Opcjonalne",
+    "placeOfTest": "Miejsce badania",
+    "placeholderAddress": "Stacja kontroli lub adres warsztatu",
+    "fromSettings": "Z ustawień Twojego warsztatu.",
+    "change": "Zmień",
+    "differsFromWorkshop": "Różni się od adresu Twojego warsztatu.",
+    "useWorkshopAddress": "Użyj adresu warsztatu",
+    "noWorkshopAddress": "Nie ustawiono adresu warsztatu.",
+    "addOne": "Dodaj adres",
+    "nextTestDue": "Następne badanie",
+    "notSet": "Nie ustawiono",
+    "intervalGroup": "Ustaw datę następnego badania od daty tego badania",
+    "intervalHelp": "Liczone od daty tego badania. Dwa lata to punkt wyjścia Unii dla samochodów osobowych i lekkich dostawczych; sprawdź okres obowiązujący w Twoim kraju.",
+    "months6": "6 miesięcy",
+    "years1": "1 rok",
+    "years2": "2 lata",
+    "years3": "3 lata",
+    "years4": "4 lata",
+    "unsaved": "Niezapisane zmiany",
+    "saveDetails": "Zapisz dane",
+    "saved": "Dane zapisane",
+    "saveFailed": "Nie udało się zapisać danych",
+    "addTechnicianTitle": "Dodaj diagnostę",
+    "addTechnicianBody": "Osoba trafia na listę warsztatu i można jej przydzielać zarówno prace, jak i inspekcje.",
+    "technicianName": "Imię i nazwisko",
+    "technicianPlaceholder": "np. Anna Berg",
+    "addAndSelect": "Dodaj i wybierz",
+    "technicianAdded": "Dodano i wybrano: {name}",
+    "technicianFailed": "Nie udało się dodać diagnosty",
+    "cancel": "Anuluj"
+  },
+  "templates": {
+    "title": "Szablony inspekcji",
+    "description": "Listy kontrolne, przez które przechodzą Twoi diagności. Utwórz po jednej na kraj, na typ pojazdu albo na usługę, którą sprzedajesz.",
+    "import": "Importuj",
+    "browse": "Przeglądaj listy",
+    "new": "Nowy szablon",
+    "restoreBanner": "{count} gotowych list usunięto z Twojej listy. Możesz je przywrócić.",
+    "restore": "Przywróć",
+    "restored": "Przywrócono {count} list(y)",
+    "allPresent": "Wszystkie listy już tu są",
+    "restoreFailed": "Nie udało się dodać list",
+    "emptyTitle": "Brak szablonów",
+    "emptyBody": "Gotowe listy, w tym okresowe badanie techniczne UE, zostały usunięte. Przywróć je albo zbuduj własną od zera.",
+    "restoreChecklists": "Przywróć listy",
+    "buildMyOwn": "Zbuduj własną",
+    "default": "Domyślny",
+    "actionsFor": "Działania dla {name}",
+    "edit": "Edytuj",
+    "duplicate": "Duplikuj",
+    "export": "Eksportuj…",
+    "delete": "Usuń",
+    "sections": "{count} grup(y)",
+    "checks": "{count} pozycji",
+    "measured": "{count} mierzonych",
+    "euScale": "Unijna skala usterek",
+    "basicScale": "Pozytywny / uwaga / negatywny",
+    "deleteTitle": "Usunąć „{name}”?",
+    "deleteBody": "To trwale usuwa szablon. Inspekcje już wykonane na jego podstawie zachowują własną kopię listy i nie zostaną naruszone.",
+    "deleted": "Szablon usunięty",
+    "deleteFailed": "Nie udało się usunąć szablonu",
+    "duplicated": "Szablon zduplikowany",
+    "duplicateFailed": "Nie udało się zduplikować szablonu",
+    "cancel": "Anuluj",
+    "srSections": "Grupy",
+    "srChecks": "Pozycje",
+    "srMeasurements": "Pomiary"
+  },
+  "presets": {
+    "title": "Zacznij od szablonu",
+    "description": "Każdy szablon jest kopiowany do Twojego warsztatu jako zwykły szablon. Zmieniaj nazwy grup, przenoś pozycje i dostosuj każdą wartość graniczną do swojego kraju i swojego wyposażenia.",
+    "search": "Szukaj po nazwie, kraju lub normie",
+    "searchLabel": "Szukaj szablonów",
+    "inYourList": "Na Twojej liście",
+    "counts": "{sections} grup · {checks} pozycji",
+    "noMatch": "Żaden szablon nie pasuje do „{query}”.",
+    "warning": "Szablony ustawowe zawierają typowe wartości graniczne UE. Sprawdź je z przepisami krajowymi, zanim wystawisz zaświadczenie.",
+    "cancel": "Anuluj",
+    "add": "Dodaj szablon",
+    "added": "Szablon dodany. Dostosuj go do swojego warsztatu.",
+    "addFailed": "Nie udało się dodać szablonu",
+    "group": {
+      "regulatory": "Ustawowe",
+      "workshop": "Warsztatowe",
+      "specialist": "Specjalistyczne"
+    },
+    "groupDescription": {
+      "regulatory": "Listy okresowego badania technicznego oparte na dyrektywie 2014/45/UE.",
+      "workshop": "Ogólne listy serwisowe i sprzedażowe niezwiązane z przepisami.",
+      "specialist": "Listy dla napędów i jednostek pływających wymagających własnej procedury."
+    },
+    "euScale": "Unijna skala usterek",
+    "basicScale": "Pozytywny / uwaga / negatywny"
+  },
+  "builder": {
+    "newTitle": "Nowy szablon",
+    "editTitle": "Edytuj szablon",
+    "description": "Zbuduj listę, z której Twój warsztat naprawdę korzysta. Przeciągaj, aby zmieniać kolejność, i ustaw każdą pozycję tak, by zapisywała ocenę, pomiar, wpisaną uwagę albo stałą odpowiedź.",
+    "name": "Nazwa",
+    "namePlaceholder": "np. Okresowe badanie techniczne",
+    "templateDescription": "Opis",
+    "descriptionPlaceholder": "Do czego służy ta lista i kiedy jej używać.",
+    "country": "Kraj",
+    "notCountrySpecific": "Niezwiązana z krajem",
+    "countryHelp": "Zapisywane w raporcie, aby warsztat pracujący po obu stronach granicy odróżniał swoje listy.",
+    "defectScale": "Skala usterek",
+    "scaleEu": "Drobna / poważna / stwarzająca zagrożenie (UE)",
+    "scaleBasic": "Pozytywny / uwaga / negatywny",
+    "scaleEuHelp": "Kategorie z art. 7 ust. 2 dyrektywy 2014/45/UE. Pojazd wyłącznie z drobnymi usterkami nadal uzyskuje wynik pozytywny.",
+    "scaleBasicHelp": "Prosta trzystopniowa skala dla list serwisowych, które nie są badaniem ustawowym.",
+    "defaultTemplate": "Szablon domyślny",
+    "defaultHelp": "Wstępnie wybrany, gdy diagnosta zaczyna nową inspekcję.",
+    "summary": "{sections} grup(y) · {checks} pozycji",
+    "renumber": "Przenumeruj odniesienia",
+    "renumberHelp": "Numeruje każdą grupę i pozycję po kolei: 1, 1.1, 1.2, 2. Nowe wiersze są numerowane przy dodawaniu, więc przyda się to tylko po przenoszeniu.",
+    "renumberWarning": "Ta lista używa odniesień do przepisów, takich jak 1.1.13. Przenumerowanie je zastąpi.",
+    "addSection": "Dodaj grupę",
+    "addCheck": "Dodaj pozycję",
+    "ref": "Odn.",
+    "sectionName": "Nazwa grupy, np. Układ hamulcowy",
+    "sectionNote": "Opcjonalna uwaga wyświetlana nad pozycjami tej grupy",
+    "checkName": "Nazwa pozycji, np. Okładziny hamulcowe",
+    "reorderSection": "Zmień kolejność grupy {name}",
+    "reorderCheck": "Zmień kolejność pozycji {name}",
+    "removeSection": "Usuń grupę {name}",
+    "removeCheck": "Usuń pozycję {name}",
+    "duplicateCheck": "Duplikuj pozycję {name}",
+    "configure": "Skonfiguruj {name}",
+    "hideSettings": "Ukryj ustawienia dla {name}",
+    "howRecorded": "Jak jest to zapisywane?",
+    "guidance": "Wskazówka dla diagnosty",
+    "guidancePlaceholder": "Wyświetlana pod nazwą pozycji w formularzu inspekcji.",
+    "measurement": "Pomiar",
+    "unit": "Jednostka",
+    "minimum": "Minimum",
+    "maximum": "Maksimum",
+    "outOfRange": "Usterka poza zakresem",
+    "rangeHelp": "Zostaw jedną granicę pustą, aby uzyskać wymóg jednostronny, na przykład minimum 1,6 mm bez maksimum. Odczyty poza zakresem są oceniane automatycznie.",
+    "answers": "Odpowiedzi",
+    "answersHelp": "Oddziel odpowiedzi przecinkami.",
+    "mustComplete": "Musi zostać wypełnione",
+    "photoRequired": "Zdjęcie wymagane, gdy zapisywana jest usterka",
+    "commonWording": "Typowe sformułowania",
+    "commonWordingHelp": "Jedno sformułowanie w wierszu. Są proponowane jako pierwsze, gdy diagnosta zapisuje tu usterkę, przed tekstami wbudowanymi, aby Twój warsztat ustalił jeden sposób opisu.",
+    "inputType": {
+      "condition": "Ocena usterki",
+      "measurement": "Pomiar",
+      "text": "Tekst dowolny",
+      "choice": "Wybór"
+    },
+    "inputHint": {
+      "condition": "Diagnosta ocenia pozycję według skali usterek.",
+      "measurement": "Zapisuje liczbę z jednostką, ocenianą automatycznie względem zakresu.",
+      "text": "Zapisuje obserwację, na przykład markę opony albo kod usterki.",
+      "choice": "Diagnosta wybiera jedną odpowiedź ze stałej listy."
+    },
+    "cancel": "Anuluj",
+    "create": "Utwórz szablon",
+    "saveChanges": "Zapisz zmiany",
+    "created": "Szablon utworzony",
+    "updated": "Szablon zaktualizowany",
+    "saveFailed": "Nie udało się zapisać szablonu",
+    "severityAttention": "Usterka drobna",
+    "severityFail": "Usterka poważna",
+    "severityDangerous": "Usterka stwarzająca zagrożenie",
+    "answersPlaceholder": "Pełna, Częściowa, Brak"
+  },
+  "packages": {
+    "exportTitle": "Eksportuj „{name}”",
+    "exportDescription": "Pobiera całą listę jako plik: grupy, pozycje, jednostki i wartości graniczne. Każdy, kto korzysta z Torqvoice, może ją zaimportować.",
+    "includeWording": "Dołącz własne sformułowania",
+    "includeWordingHelp": "Sformułowania, które Twój warsztat dodał do pozycji. Przydatne dla odbiorcy, ale to tekst wpisany przy stanowisku i może wymieniać klienta, współpracownika albo lokalne ustalenie. Wyłączone, chyba że je przeczytałeś.",
+    "attributeTo": "Przypisz do (opcjonalnie)",
+    "attributePlaceholder": "np. Egeland Auto",
+    "attributeHelp": "Zapisywane w pliku, aby osoba instalująca widziała, skąd pochodzi. Z Twojego konta nic nie jest pobierane.",
+    "download": "Pobierz",
+    "exported": "Szablon wyeksportowany",
+    "exportFailed": "Nie udało się wyeksportować tego szablonu",
+    "importTitle": "Importuj listę",
+    "importDescription": "Dodaje listę z pliku pakietu Torqvoice. Trafia jako zwykły szablon, który możesz edytować, i nigdy nie zastępuje istniejącego.",
+    "chooseFile": "Wybierz plik pakietu",
+    "chooseAnother": "Wybierz inny plik",
+    "unreadable": "Nie udało się odczytać tego pliku. Pakiet Torqvoice to plik .json wyeksportowany z szablonu.",
+    "from": "Od {author}",
+    "limitsWarning": "Wartości graniczne w udostępnionej liście są takie, jakie ustawił jej autor. Sprawdź je z przepisami krajowymi, zanim cokolwiek na jej podstawie wystawisz.",
+    "import": "Importuj",
+    "imported": "Zaimportowano {count} szablon(y)",
+    "importFailed": "Nie udało się zaimportować tego pakietu",
+    "cancel": "Anuluj"
+  },
+  "list": {
+    "tabAll": "Wszystkie",
+    "tabInProgress": "W toku",
+    "tabCompleted": "Zakończone",
+    "search": "Szukaj inspekcji…",
+    "new": "Nowa inspekcja",
+    "empty": "Nie znaleziono inspekcji.",
+    "vehicle": "Pojazd",
+    "template": "Szablon",
+    "progress": "Postęp",
+    "status": "Status",
+    "date": "Data",
+    "statusInProgress": "W toku",
+    "statusCompleted": "Zakończona"
+  },
+  "new": {
+    "title": "Nowa inspekcja",
+    "vehicle": "Pojazd",
+    "searchVehicles": "Szukaj pojazdów…",
+    "loadingVehicles": "Wczytywanie pojazdów…",
+    "noVehicles": "Nie znaleziono pojazdów.",
+    "template": "Szablon",
+    "selectTemplate": "Wybierz szablon",
+    "manageTemplates": "Zarządzaj szablonami",
+    "cancel": "Anuluj",
+    "create": "Utwórz inspekcję",
+    "created": "Inspekcja utworzona",
+    "createFailed": "Nie udało się utworzyć inspekcji"
+  },
+  "shareDialog": {
+    "title": "Udostępnij inspekcję",
+    "description": "Udostępnij klientowi widok tej inspekcji tylko do odczytu.",
+    "copyLink": "Kopiuj link",
+    "copied": "Link skopiowany do schowka",
+    "generated": "Link do udostępniania utworzony",
+    "revoked": "Link do udostępniania wycofany"
+  }
+}

+ 227 - 227
messages/pl/settings.json

@@ -1,9 +1,9 @@
 {
   "title": "Ustawienia",
-  "subtitle": "Zarzadzaj swoim warsztatem, fakturami i preferencjami",
+  "subtitle": "Zarządzaj swoim warsztatem, fakturami i preferencjami",
   "nav": {
     "categories": {
-      "general": "Ogolne",
+      "general": "Ogólne",
       "billing": "Fakturowanie i dokumenty",
       "communications": "Komunikacja",
       "workshop": "Warsztat",
@@ -41,11 +41,11 @@
       },
       "tax": {
         "title": "Podatek",
-        "description": "Stawka podatku i wartosci domyslne"
+        "description": "Stawka podatku i wartości domyślne"
       },
       "currency": {
         "title": "Waluta",
-        "description": "Domyslna waluta i podatki"
+        "description": "Domyślna waluta i podatki"
       },
       "localization": {
         "title": "Lokalizacja",
@@ -61,7 +61,7 @@
       },
       "sms": {
         "title": "SMS",
-        "description": "Dostawca SMS i wiadomosci"
+        "description": "Dostawca SMS i wiadomości"
       },
       "telegram": {
         "title": "Telegram",
@@ -73,7 +73,7 @@
       },
       "workshop": {
         "title": "Warsztat",
-        "description": "Domyslne ustawienia technika i robocizny"
+        "description": "Domyślne ustawienia technika i robocizny"
       },
       "laborPresets": {
         "title": "Pakiety robocizny",
@@ -96,8 +96,8 @@
         "description": "Powiadomienia o zdarzeniach w czasie rzeczywistym"
       },
       "appearance": {
-        "title": "Wyglad",
-        "description": "Motyw i wyswietlanie"
+        "title": "Wygląd",
+        "description": "Motyw i wyświetlanie"
       },
       "data": {
         "title": "Dane",
@@ -123,7 +123,7 @@
     "pro": "PRO"
   },
   "readOnly": {
-    "banner": "Masz dostep tylko do odczytu ustawien. Skontaktuj sie z administratorem, aby wprowadzic zmiany."
+    "banner": "Masz dostęp tylko do odczytu ustawień. Skontaktuj sie z administratorem, aby wprowadzić zmiany."
   },
   "featureLocked": {
     "requiresPro": "Wymaga licencji Pro",
@@ -133,8 +133,8 @@
   "company": {
     "logoTitle": "Logo firmy",
     "logoAlt": "Logo firmy",
-    "upload": "Przeslij",
-    "remove": "Usun",
+    "upload": "Prześlij",
+    "remove": "Usuń",
     "logoHint": "JPG, PNG, WebP, SVG — maks. 2MB",
     "uploadingLogo": "Przesylanie logo...",
     "logoUpdated": "Logo zaktualizowane",
@@ -151,14 +151,14 @@
     "address": "Adres",
     "addressPlaceholder": "ul. Glowna 123\n00-001 Warszawa",
     "saveCompany": "Zapisz dane firmy",
-    "addNewCompany": "Dodaj nowa firme",
+    "addNewCompany": "Dodaj nowa firmę",
     "companyName": "Nazwa firmy",
     "companyNamePlaceholder": "Np. Warsztat Samochodowy Kowalski",
     "cancel": "Anuluj",
-    "createCompany": "Utworz firme",
+    "createCompany": "Utwórz firmę",
     "settingsSaved": "Ustawienia firmy zapisane",
-    "failedSave": "Nie udalo sie zapisac ustawien",
-    "companyCreated": "Firma utworzona pomyslnie",
+    "failedSave": "Nie udalo sie zapisać ustawień",
+    "companyCreated": "Firma utworzona pomyślnie",
     "serviceType": "Typ Usługi",
     "serviceTypeDescription": "Wybierz typ usługi oferowanej przez Twój warsztat. Zmienia to etykiety pól w całej aplikacji.",
     "automotive": "Motoryzacja",
@@ -168,7 +168,7 @@
   },
   "account": {
     "profileTitle": "Profil",
-    "profileDescription": "Zaktualizuj swoja nazwe wyswietlana i adres e-mail.",
+    "profileDescription": "Zaktualizuj swoją nazwę wyświetlana i adres e-mail.",
     "emailVerified": "Zweryfikowany",
     "emailNotVerified": "Niezweryfikowany",
     "sendVerificationEmail": "Wyślij e-mail weryfikacyjny",
@@ -179,78 +179,78 @@
     "name": "Nazwa",
     "namePlaceholder": "Twoja nazwa",
     "emailLabel": "E-mail",
-    "emailPlaceholder": "Twoj e-mail",
+    "emailPlaceholder": "Twój e-mail",
     "saveProfile": "Zapisz profil",
     "profileUpdated": "Profil zaktualizowany",
-    "failedUpdateProfile": "Nie udalo sie zaktualizowac profilu",
-    "failedUpdateEmail": "Nie udalo sie zaktualizowac e-maila",
-    "changePasswordTitle": "Zmien haslo",
-    "changePasswordDescription": "Wprowadz aktualne haslo i wybierz nowe.",
+    "failedUpdateProfile": "Nie udalo sie zaktualizować profilu",
+    "failedUpdateEmail": "Nie udalo sie zaktualizować e-maila",
+    "changePasswordTitle": "Zmień haslo",
+    "changePasswordDescription": "Wprowadź aktualne haslo i wybierz nowe.",
     "currentPassword": "Aktualne haslo",
     "newPassword": "Nowe haslo",
-    "confirmNewPassword": "Potwierdz nowe haslo",
-    "changePassword": "Zmien haslo",
+    "confirmNewPassword": "Potwierdź nowe haslo",
+    "changePassword": "Zmień haslo",
     "passwordChanged": "Haslo zmienione",
     "passwordsNoMatch": "Hasla nie sa zgodne",
-    "passwordMinLength": "Haslo musi miec co najmniej 8 znakow",
-    "failedChangePassword": "Nie udalo sie zmienic hasla",
+    "passwordMinLength": "Haslo musi mieć co najmniej 8 znaków",
+    "failedChangePassword": "Nie udalo sie zmienić hasla",
     "twoFactorTitle": "Uwierzytelnianie dwuskladnikowe",
     "twoFactorEnabled": "Uwierzytelnianie dwuskladnikowe jest wlaczone",
-    "twoFactorDisablePrompt": "Wprowadz haslo, aby wylaczyc uwierzytelnianie dwuskladnikowe.",
-    "twoFactorEnablePrompt": "Dodaj dodatkowa warstwe bezpieczenstwa do swojego konta, wlaczajac uwierzytelnianie dwuskladnikowe z aplikacja uwierzytelniajaca.",
+    "twoFactorDisablePrompt": "Wprowadź haslo, aby wylaczyc uwierzytelnianie dwuskladnikowe.",
+    "twoFactorEnablePrompt": "Dodaj dodatkowa warstwe bezpieczeństwa do swojego konta, wlaczajac uwierzytelnianie dwuskladnikowe z aplikacja uwierzytelniajaca.",
     "password": "Haslo",
-    "enterPassword": "Wprowadz haslo",
+    "enterPassword": "Wprowadź haslo",
     "disable2FA": "Wylacz 2FA",
     "enable2FA": "Wlacz 2FA",
     "twoFactorDisabled": "Uwierzytelnianie dwuskladnikowe wylaczone",
     "failedEnable2FA": "Nie udalo sie wlaczyc 2FA",
     "failedDisable2FA": "Nie udalo sie wylaczyc 2FA",
-    "enterPasswordError": "Prosze wprowadzic haslo",
+    "enterPasswordError": "Proszę wprowadzić haslo",
     "setupDialogTitle": "Wlacz uwierzytelnianie dwuskladnikowe",
-    "setupDialogDescription": "Wprowadz haslo, aby rozpoczac konfiguracje 2FA z aplikacja uwierzytelniajaca.",
+    "setupDialogDescription": "Wprowadź haslo, aby rozpocząć konfiguracje 2FA z aplikacja uwierzytelniajaca.",
     "continue": "Kontynuuj",
     "scanQRTitle": "Zeskanuj kod QR",
     "scanQRDescription": "Zeskanuj ten kod QR aplikacja uwierzytelniajaca (Google Authenticator, Authy itp.)",
     "verifyCodeTitle": "Zweryfikuj kod",
-    "verifyCodeDescription": "Wprowadz 6-cyfrowy kod z aplikacji uwierzytelniającej, aby potwierdzic poprawna konfiguracje.",
+    "verifyCodeDescription": "Wprowadź 6-cyfrowy kod z aplikacji uwierzytelniającej, aby potwierdzić poprawna konfiguracje.",
     "authCode": "Kod uwierzytelniania",
     "authCodePlaceholder": "000000",
     "back": "Wstecz",
     "verify": "Zweryfikuj",
-    "enterCodeError": "Prosze wprowadzic kod z aplikacji uwierzytelniającej",
-    "invalidCode": "Nieprawidlowy kod. Sprobuj ponownie.",
+    "enterCodeError": "Proszę wprowadzić kod z aplikacji uwierzytelniającej",
+    "invalidCode": "Nieprawidlowy kod. Spróbuj ponownie.",
     "verificationFailed": "Weryfikacja nie powiodla sie",
     "backupCodesTitle": "Zapisz kody zapasowe",
-    "backupCodesDescription": "Zapisz te kody w bezpiecznym miejscu. Mozesz ich uzyc do zalogowania sie, jesli stracisz dostep do aplikacji uwierzytelniającej. Kazdy kod moze byc uzyty tylko raz.",
+    "backupCodesDescription": "Zapisz te kody w bezpiecznym miejscu. Możesz ich użyć do zalogowania sie, jeśli stracisz dostęp do aplikacji uwierzytelniającej. Każdy kod może byc uzyty tylko raz.",
     "copyCodes": "Kopiuj kody",
     "copied": "Skopiowano",
     "backupCodesCopied": "Kody zapasowe skopiowane do schowka",
     "savedBackupCodes": "Zapisalem moje kody zapasowe",
     "twoFactorEnabledSuccess": "Uwierzytelnianie dwuskladnikowe wlaczone",
     "dangerZone": "Strefa niebezpieczenstwa",
-    "deleteContentTitle": "Usun zawartosc",
-    "deleteContentDescription": "Selektywnie usun dane organizacji, takie jak pojazdy, klienci, wyceny lub inwentarz. Twoje konto i ustawienia pozostana nienaruszone.",
-    "deleteContentButton": "Usun zawartosc",
-    "deleteAccountTitle": "Usun konto",
-    "deleteAccountDescription": "Trwale usun swoje konto i wszystkie powiazane dane, w tym pojazdy, rekordy serwisowe, zlecenia, faktury, klientow, pliki i platnosci. Tej akcji nie mozna cofnac.",
-    "deleteAccountButton": "Usun konto",
-    "deleteAccountDialogTitle": "Usun konto",
-    "deleteAccountDialogDescription": "To trwale usunie Twoje konto i wszystkie powiazane dane. Ta akcja jest nieodwracalna. Wszystkie pojazdy, rekordy serwisowe, zlecenia, wyceny, klienci, pliki i historia platnosci zostana trwale usuniete.",
+    "deleteContentTitle": "Usuń zawartość",
+    "deleteContentDescription": "Selektywnie usuń dane organizacji, takie jak pojazdy, klienci, wyceny lub inwentarz. Twoje konto i ustawienia pozostana nienaruszone.",
+    "deleteContentButton": "Usuń zawartość",
+    "deleteAccountTitle": "Usuń konto",
+    "deleteAccountDescription": "Trwale usuń swoje konto i wszystkie powiązane dane, w tym pojazdy, rekordy serwisowe, zlecenia, faktury, klientów, pliki i platnosci. Tej akcji nie można cofnąć.",
+    "deleteAccountButton": "Usuń konto",
+    "deleteAccountDialogTitle": "Usuń konto",
+    "deleteAccountDialogDescription": "To trwale usunie Twoje konto i wszystkie powiązane dane. Ta akcja jest nieodwracalna. Wszystkie pojazdy, rekordy serwisowe, zlecenia, wyceny, klienci, pliki i historia platnosci zostaną trwale usunięte.",
     "deleteAccountConfirmPhrase": "delete me",
-    "deleteAccountConfirmPrompt": "Wpisz <bold>delete me</bold> aby potwierdzic",
-    "permanentlyDelete": "Usun trwale",
+    "deleteAccountConfirmPrompt": "Wpisz <bold>delete me</bold> aby potwierdzić",
+    "permanentlyDelete": "Usuń trwale",
     "deleting": "Usuwanie...",
-    "failedDeleteAccount": "Nie udalo sie usunac konta",
-    "deleteContentDialogTitle": "Usun zawartosc",
-    "deleteContentDialogDescription": "Wybierz zawartosc, ktora chcesz trwale usunac. Tej akcji nie mozna cofnac.",
+    "failedDeleteAccount": "Nie udalo sie usunąć konta",
+    "deleteContentDialogTitle": "Usuń zawartość",
+    "deleteContentDialogDescription": "Wybierz zawartość, ktora chcesz trwale usunąć. Tej akcji nie można cofnąć.",
     "contentVehicles": "Pojazdy",
     "contentVehiclesDescription": "Wszystkie pojazdy i ich rekordy serwisowe, notatki, przypomnienia, dzienniki paliwa i faktury cykliczne",
     "contentCustomers": "Klienci",
-    "contentCustomersDescription": "Wszystkie rekordy klientow. Pojazdy powiazane z usunietymi klientami zostana odlaczone.",
+    "contentCustomersDescription": "Wszystkie rekordy klientów. Pojazdy powiązane z usunietymi klientami zostaną odlaczone.",
     "contentQuotes": "Wyceny",
     "contentQuotesDescription": "Wszystkie wyceny i ich pozycje",
     "contentInventory": "Inwentarz",
-    "contentInventoryDescription": "Wszystkie czesci inwentarza i ich zdjecia",
+    "contentInventoryDescription": "Wszystkie części inwentarza i ich zdjęcia",
     "contentInspections": "Inspekcje",
     "contentInspectionsDescription": "Wszystkie inspekcje pojazdów i ich wyniki",
     "contentTechnicians": "Technicy",
@@ -264,10 +264,10 @@
     "contentCustomFields": "Pola niestandardowe",
     "contentCustomFieldsDescription": "Wszystkie definicje pól niestandardowych i ich wartości",
     "deleteContentConfirmPhrase": "delete my data",
-    "deleteContentConfirmPrompt": "Wpisz <bold>delete my data</bold> aby potwierdzic",
-    "deleteSelected": "Usun {count} wybranych",
-    "contentDeleted": "Wybrana zawartosc zostala trwale usunieta",
-    "failedDeleteContent": "Nie udalo sie usunac zawartosci",
+    "deleteContentConfirmPrompt": "Wpisz <bold>delete my data</bold> aby potwierdzić",
+    "deleteSelected": "Usuń {count} wybranych",
+    "contentDeleted": "Wybrana zawartość zostala trwale usunięta",
+    "failedDeleteContent": "Nie udalo sie usunąć zawartosci",
     "passkey": {
       "title": "Passkeys",
       "description": "Loguj się za pomocą biometrii lub klucza bezpieczeństwa zamiast hasła.",
@@ -305,116 +305,116 @@
   },
   "team": {
     "title": "Zespol",
-    "noOrgDescription": "Utworz zespol, aby dzielic dane z innymi uzytkownikami.",
-    "orgDescription": "Zarzadzaj czlonkami organizacji i ich rolami.",
-    "createOrg": "Utworz organizacje",
-    "createOrgDescription": "Utworz organizacje, aby zaprosic czlonkow zespolu. Bedziesz wlascicielem.",
+    "noOrgDescription": "Utwórz zespol, aby dzielic dane z innymi użytkownikami.",
+    "orgDescription": "Zarządzaj czlonkami organizacji i ich rolami.",
+    "createOrg": "Utwórz organizacje",
+    "createOrgDescription": "Utwórz organizacje, aby zaprosic czlonkow zespolu. Będziesz wlascicielem.",
     "orgName": "Nazwa organizacji",
     "orgNamePlaceholder": "Moj warsztat",
-    "create": "Utworz",
+    "create": "Utwórz",
     "orgCreated": "Organizacja utworzona",
-    "failedCreateOrg": "Nie udalo sie utworzyc organizacji",
+    "failedCreateOrg": "Nie udalo sie utworzyć organizacji",
     "memberCount": "{count} czlonek",
     "memberCountPlural": "{count} czlonkow",
     "admin": "Administrator",
     "member": "Czlonek",
     "inviteByEmail": "Zapros przez e-mail",
-    "inviteEmailPlaceholder": "uzytkownik@przyklad.pl",
+    "inviteEmailPlaceholder": "użytkownik@przyklad.pl",
     "invite": "Zapros",
-    "invited": "Czlonek zaproszony pomyslnie.",
-    "userNotFoundTitle": "Uzytkownik nie znaleziony",
+    "invited": "Czlonek zaproszony pomyślnie.",
+    "userNotFoundTitle": "Użytkownik nie znaleziony",
     "userNotFoundDescription": "Nie znaleziono konta dla \"{email}\". Wyslac e-mail z zaproszeniem do rejestracji i dolaczenia do zespolu?",
-    "sendInvitation": "Wyslij zaproszenie",
+    "sendInvitation": "Wyślij zaproszenie",
     "invitationSentTitle": "Zaproszenie wyslane",
     "invitationSentDescription": "E-mail z zaproszeniem zostal wyslany do {email}.",
     "failedInvite": "Nie udalo sie zaprosic czlonka",
     "failedSendInvitation": "Nie udalo sie wyslac zaproszenia",
-    "pendingInvitations": "Oczekujace zaproszenia",
+    "pendingInvitations": "Oczekujące zaproszenia",
     "invitedAs": "Zaproszony jako {role}",
     "expires": "Wygasa {date}",
-    "pending": "Oczekujace",
+    "pending": "Oczekujące",
     "copyInviteLink": "Kopiuj link zaproszenia",
     "inviteLinkCopied": "Link zaproszenia skopiowany do schowka",
     "cancelInvitation": "Anuluj zaproszenie",
-    "cancelInvitationDescription": "Anulowac zaproszenie dla {email}? Nie beda mogli uzyc tego linku.",
+    "cancelInvitationDescription": "Anulować zaproszenie dla {email}? Nie będą mogli użyć tego linku.",
     "invitationCancelled": "Zaproszenie anulowane",
-    "failedCancelInvitation": "Nie udalo sie anulowac zaproszenia",
+    "failedCancelInvitation": "Nie udalo sie anulować zaproszenia",
     "customRoles": "Role niestandardowe",
     "newRole": "Nowa rola",
     "roleName": "Nazwa roli",
     "roleNamePlaceholder": "Np. Technik",
-    "fullAdminAccess": "Pelny dostep administratora (pomija wszystkie sprawdzenia uprawnien)",
+    "fullAdminAccess": "Pelny dostęp administratora (pomija wszystkie sprawdzenia uprawnien)",
     "permissions": "Uprawnienia",
-    "createRole": "Utworz role",
+    "createRole": "Utwórz role",
     "updateRole": "Zaktualizuj role",
     "roleCreated": "Rola utworzona",
     "roleUpdated": "Rola zaktualizowana",
-    "failedSaveRole": "Nie udalo sie zapisac roli",
-    "deleteRole": "Usun role",
-    "deleteRoleDescription": "Usunac role \"{name}\"? Czlonkowie z ta rola straca swoje niestandardowe uprawnienia.",
-    "roleDeleted": "Rola usunieta",
-    "failedDeleteRole": "Nie udalo sie usunac roli",
-    "fullAccess": "Pelny dostep",
+    "failedSaveRole": "Nie udalo sie zapisać roli",
+    "deleteRole": "Usuń role",
+    "deleteRoleDescription": "Usunąć role \"{name}\"? Czlonkowie z ta rola stracą swoje niestandardowe uprawnienia.",
+    "roleDeleted": "Rola usunięta",
+    "failedDeleteRole": "Nie udalo sie usunąć roli",
+    "fullAccess": "Pelny dostęp",
     "permissionCount": "{count} uprawnienie",
     "permissionCountPlural": "{count} uprawnien",
-    "noCustomRoles": "Brak ról niestandardowych. Utworz jedną, aby zdefiniować szczególowe uprawnienia dla czlonkow zespolu.",
+    "noCustomRoles": "Brak ról niestandardowych. Utwórz jedną, aby zdefiniować szczególowe uprawnienia dla czlonkow zespolu.",
     "presetAll": "Wszystkie",
     "presetViewOnly": "Tylko podgląd",
     "presetWriteAll": "Odczyt i zapis",
     "presetNone": "Brak",
     "builtInRoles": "Role wbudowane",
     "ownerLabel": "Wlasciciel",
-    "ownerDescription": "Pelny dostep. Moze zarzadzac czlonkami, zmieniac role i usuwac organizacje.",
+    "ownerDescription": "Pelny dostęp. Może zarządzać czlonkami, zmieniac role i usuwac organizacje.",
     "adminLabel": "Administrator",
-    "adminDescription": "Moze zapraszac/usuwac czlonkow i zarzadzac wspoldzielonymi danymi.",
+    "adminDescription": "Może zapraszac/usuwac czlonkow i zarządzać wspoldzielonymi danymi.",
     "memberLabel": "Czlonek",
-    "memberDescription": "Uprawnienia okreslone przez przypisana role niestandardowa. Brak roli niestandardowej oznacza dostep tylko do odczytu.",
+    "memberDescription": "Uprawnienia okreslone przez przypisana role niestandardowa. Brak roli niestandardowej oznacza dostęp tylko do odczytu.",
     "roleAssigned": "Rola zaktualizowana",
-    "failedAssignRole": "Nie udalo sie zaktualizowac roli",
-    "removeMemberTitle": "Usun czlonka",
-    "removeMemberDescription": "Usunac {name} z zespolu? Straci dostep do wspoldzielonych danych.",
-    "removeButton": "Usun",
-    "memberRemoved": "Czlonek usuniety",
-    "failedRemoveMember": "Nie udalo sie usunac czlonka",
+    "failedAssignRole": "Nie udalo sie zaktualizować roli",
+    "removeMemberTitle": "Usuń czlonka",
+    "removeMemberDescription": "Usunąć {name} z zespolu? Straci dostęp do wspoldzielonych danych.",
+    "removeButton": "Usuń",
+    "memberRemoved": "Czlonek usunięty",
+    "failedRemoveMember": "Nie udalo sie usunąć czlonka",
     "memberRoleUpdated": "Rola czlonka zaktualizowana",
-    "failedUpdateRole": "Nie udalo sie zaktualizowac roli",
+    "failedUpdateRole": "Nie udalo sie zaktualizować roli",
     "removeMember": "Remove member",
     "cancelInvite": "Cancel invitation",
     "editRole": "Edit role"
   },
   "invoice": {
     "title": "Uklad faktury",
-    "description": "Skonfiguruj, co pojawia sie na generowanych fakturach PDF.",
+    "description": "Skonfiguruj, co pojawią sie na generowanych fakturach PDF.",
     "tabs": {
       "general": "Ogólne",
       "layout": "Układ",
       "customFields": "Pola niestandardowe"
     },
     "invoiceNumberFormat": "Format numeru faktury",
-    "invoiceNumberFormatHint": "Uzyj <code>{year}</code> dla biezacego roku. Podglad: <bold>{preview}</bold>",
+    "invoiceNumberFormatHint": "Użyj <code>{year}</code> dla biezacego roku. Podgląd: <bold>{preview}</bold>",
     "quoteNumberFormat": "Format numeru wyceny",
-    "quoteNumberFormatHint": "Uzyj <code>{year}</code> dla biezacego roku. Podglad: <bold>{preview}</bold>",
+    "quoteNumberFormatHint": "Użyj <code>{year}</code> dla biezacego roku. Podgląd: <bold>{preview}</bold>",
     "quoteValidDays": "Ważność wyceny (dni)",
     "quoteValidDaysHint": "Nowe wyceny otrzymują datę ważności o tyle dni do przodu. 0 wyłącza tę opcję.",
     "sectionInvoices": "Faktury",
     "sectionQuotes": "Wyceny",
-    "nextInvoiceNumber": "Nastepny numer faktury",
+    "nextInvoiceNumber": "Następny numer faktury",
     "nextInvoiceNumberPlaceholder": "Np. 94",
-    "nextInvoiceNumberHint": "Nastepna faktura uzyje tego numeru (np. {example})",
+    "nextInvoiceNumberHint": "Następna faktura uzyje tego numeru (np. {example})",
     "dueDays": "Dni do terminu",
     "dueDaysHint": "Liczba dni do terminu platnosci faktury",
-    "visibilityTitle": "Widocznosc na fakturze",
+    "visibilityTitle": "Widoczność na fakturze",
     "companyLogo": "Logo firmy",
-    "companyLogoHint": "Wyswietl logo firmy w naglowku faktury",
+    "companyLogoHint": "Wyświetl logo firmy w naglowku faktury",
     "companyName": "Nazwa firmy",
-    "companyNameHint": "Wyswietl nazwe firmy w naglowku faktury",
+    "companyNameHint": "Wyświetl nazwę firmy w naglowku faktury",
     "bankAccount": "Konto bankowe",
-    "bankAccountHint": "Pokaz konto bankowe / IBAN na fakturze",
+    "bankAccountHint": "Pokaż konto bankowe / IBAN na fakturze",
     "organizationNumber": "Numer organizacji",
-    "organizationNumberHint": "Pokaz numer organizacji w naglowku faktury",
+    "organizationNumberHint": "Pokaż numer organizacji w naglowku faktury",
     "customFooter": "Niestandardowa stopka faktury",
-    "footerPlaceholder": "Dziekujemy za zamowienie!",
-    "footerHint": "Ten tekst pojawia sie na dole kazdej faktury",
+    "footerPlaceholder": "Dziękujemy za zamowienie!",
+    "footerHint": "Ten tekst pojawią sie na dole każdej faktury",
     "partsMarkupTitle": "Marża na Części",
     "partsMarkupDescription": "Marża doliczana do ceny zakupu przy dodawaniu części. Tylko wewnętrznie — klient nigdy nie widzi kosztu ani marży, tylko cenę końcową.",
     "defaultMarkupPercent": "Domyślna Marża %",
@@ -491,9 +491,9 @@
   },
   "templates": {
     "title": "Szablony",
-    "invoiceDescription": "Dostosuj wyglad faktur i wycen PDF.",
-    "inspectionsDescription": "Zarzadzaj listami kontrolnymi inspekcji i szablonami wielopunktowymi.",
-    "smsDescription": "Dostosuj wiadomosci SMS wysylane do klientow.",
+    "invoiceDescription": "Dostosuj wygląd faktur i wycen PDF.",
+    "inspectionsDescription": "Zarządzaj listami kontrolnymi inspekcji i szablonami wielopunktowymi.",
+    "smsDescription": "Dostosuj wiadomości SMS wysylane do klientów.",
     "tabs": {
       "invoice": "Faktura",
       "quotation": "Wycena",
@@ -505,7 +505,7 @@
     "primaryColor": "Kolor glowny",
     "fontAndLayout": "Czcionka i uklad",
     "fontFamily": "Rodzina czcionek",
-    "helveticaDefault": "Helvetica (Domyslna)",
+    "helveticaDefault": "Helvetica (Domyślna)",
     "timesRoman": "Times Roman",
     "courier": "Courier",
     "headerStyle": "Styl naglowka",
@@ -513,18 +513,18 @@
     "standard": "Standardowy",
     "compact": "Kompaktowy",
     "modern": "Nowoczesny",
-    "preview": "Podglad {name}",
+    "preview": "Podgląd {name}",
     "billTo": "Faktura dla",
     "preparedFor": "Przygotowano dla",
     "vehicle": "Pojazd",
-    "part": "Czesc",
-    "qty": "Ilosc",
+    "part": "Część",
+    "qty": "Ilość",
     "price": "Cena",
     "total": "Razem",
     "labor": "Robocizna",
     "hours": "Godziny",
     "rate": "Stawka",
-    "subtotal": "Suma czesciowa",
+    "subtotal": "Suma częściowa",
     "tax": "Podatek ({rate}%)",
     "previewService": "Serwis",
     "previewNotes": "Uwagi",
@@ -536,9 +536,9 @@
     "saveQuotationTemplate": "Zapisz szablon wyceny",
     "invoiceTemplateSaved": "Ustawienia szablonu faktury zapisane",
     "quotationTemplateSaved": "Ustawienia szablonu wyceny zapisane",
-    "failedSave": "Nie udalo sie zapisac ustawien",
-    "smsTemplates": "Szablony wiadomosci SMS",
-    "smsTemplatesDescription": "Dostosuj wiadomosci tekstowe wysylane do klientow. Uzyj zmiennych jak <code>'{companyName}'</code> aby wstawic dynamiczne wartosci.",
+    "failedSave": "Nie udalo sie zapisać ustawień",
+    "smsTemplates": "Szablony wiadomości SMS",
+    "smsTemplatesDescription": "Dostosuj wiadomości tekstowe wysylane do klientów. Użyj zmiennych jak <code>'{companyName}'</code> aby wstawic dynamiczne wartości.",
     "reset": "Resetuj",
     "invoiceReady": "Faktura gotowa",
     "invoiceReadyDescription": "Wysylana przy udostepnianiu faktury klientowi.",
@@ -548,16 +548,16 @@
     "inspectionReadyDescription": "Wysylana przy udostepnianiu raportu inspekcji klientowi.",
     "statusInProgress": "Status: W toku",
     "statusInProgressDescription": "Wysylana gdy status zlecenia zmieni sie na W toku.",
-    "statusWaitingParts": "Status: Oczekiwanie na czesci",
-    "statusWaitingPartsDescription": "Wysylana gdy status zmieni sie na Oczekiwanie na czesci.",
+    "statusWaitingParts": "Status: Oczekiwanie na części",
+    "statusWaitingPartsDescription": "Wysylana gdy status zmieni sie na Oczekiwanie na części.",
     "statusReady": "Status: Gotowy do odbioru",
     "statusReadyDescription": "Wysylana gdy status zmieni sie na Gotowy.",
-    "statusCompleted": "Status: Zakonczony",
-    "statusCompletedDescription": "Wysylana gdy status zmieni sie na Zakonczony.",
+    "statusCompleted": "Status: Zakończony",
+    "statusCompletedDescription": "Wysylana gdy status zmieni sie na Zakończony.",
     "paymentReceived": "Platnosc otrzymana",
     "paymentReceivedDescription": "Wysylana gdy platnosc zostanie zarejestrowana dla faktury.",
     "saveSmsTemplates": "Zapisz szablony SMS",
-    "smsTemplateSaved": "Ustawienia szablonow SMS zapisane",
+    "smsTemplateSaved": "Ustawienia szablonów SMS zapisane",
     "smsDefaults": {
       "invoiceReady": "Twoja faktura jest gotowa. Zobacz ją tutaj: {share_link}",
       "quoteReady": "Twoja wycena jest gotowa do przeglądu. Zobacz ją tutaj: {share_link}",
@@ -579,15 +579,15 @@
       "indigo": "Indygo"
     },
     "layoutTitle": "Uklad faktury",
-    "layoutDescription": "Dostosuj kolejnosc i widocznosc sekcji w fakturach i wycenach.",
-    "layoutSaved": "Uklad zapisany pomyslnie",
+    "layoutDescription": "Dostosuj kolejność i widoczność sekcji w fakturach i wycenach.",
+    "layoutSaved": "Uklad zapisany pomyślnie",
     "layoutDocInvoice": "Faktura",
     "layoutDocQuote": "Wycena"
   },
   "payment": {
     "readMore": "Czytaj więcej",
     "detailsTitle": "Szczegoly platnosci",
-    "detailsDescription": "Konto bankowe i warunki platnosci wyswietlane na fakturach.",
+    "detailsDescription": "Konto bankowe i warunki platnosci wyświetlane na fakturach.",
     "bankAccount": "Konto bankowe (IBAN)",
     "bankAccountPlaceholder": "NO93 8601 1117 947",
     "bankAccountHint": "Numer konta bankowego lub IBAN do odbierania platnosci",
@@ -595,7 +595,7 @@
     "paymentTermsPlaceholder": "14 dni netto",
     "paymentTermsHint": "Tekst warunkow platnosci na fakturach (np. \"14 dni netto\", \"Platne przy odbiorze\")",
     "onlinePayments": "Platnosci online",
-    "onlinePaymentsDescription": "Pozwol klientom placic faktury online przez udostepnione linki do faktur.",
+    "onlinePaymentsDescription": "Pozwol klientom placic faktury online przez udostępnione linki do faktur.",
     "stripe": "Stripe",
     "stripeDescription": "Akceptuj platnosci karta kredytowa/debetowa",
     "secretKey": "Klucz tajny",
@@ -611,12 +611,12 @@
     "merchantSerialNumber": "Numer seryjny sprzedawcy",
     "testMode": "Tryb testowy",
     "callbackUrl": "URL callback",
-    "termsOfSale": "Warunki sprzedazy (Salgsvilkar)",
-    "termsOfSaleDescription": "Wymagane przez Vipps. Klienci moga je wyswietlic ze strony faktury.",
+    "termsOfSale": "Warunki sprzedaży (Salgsvilkår)",
+    "termsOfSaleDescription": "Wymagane przez Vipps. Klienci mogą je wyswietlic ze strony faktury.",
     "externalTermsUrl": "Zewnetrzny URL warunkow (opcjonalnie)",
-    "externalTermsUrlHint": "Link do zewnetrznej strony z warunkami. Jesli ustawiony, faktura bedzie tu kierowac.",
+    "externalTermsUrlHint": "Link do zewnetrznej strony z warunkami. Jeśli ustawiony, faktura będzie tu kierowac.",
     "publicTermsUrl": "Publiczny URL warunkow",
-    "publicTermsUrlHint": "Udostepnij ten URL Vipps lub podlacz go ze swojej strony.",
+    "publicTermsUrlHint": "Udostępnij ten URL Vipps lub podlacz go ze swojej strony.",
     "paypal": "PayPal",
     "paypalDescription": "Akceptuj platnosci PayPal",
     "sandboxMode": "Tryb sandbox",
@@ -626,13 +626,13 @@
   },
   "tax": {
     "title": "Podatek",
-    "description": "Skonfiguruj stawke podatku stosowana do faktur, ofert i zlecen serwisowych.",
+    "description": "Skonfiguruj stawkę podatku stosowana do faktur, ofert i zleceń serwisowych.",
     "modeLabel": "Tryb podatku",
-    "modeHint": "Wybierz, czy ceny pozycji sa wprowadzane z juz uwzglednionym podatkiem (brutto) czy bez podatku (netto). Istniejace rekordy zachowuja tryb, z ktorym zostaly utworzone.",
+    "modeHint": "Wybierz, czy ceny pozycji sa wprowadzane z juz uwzglednionym podatkiem (brutto) czy bez podatku (netto). Istniejące rekordy zachowuja tryb, z którym zostaly utworzone.",
     "modeExclusive": "Netto",
     "modeInclusive": "Brutto",
     "modeExclusiveExample": "Przyklad: 100 + {rate}% podatku = 100 + podatek doliczony.",
-    "modeInclusiveExample": "Przyklad: 100 juz zawiera {rate}% podatku — czesc podatkowa jest obliczana wstecz.",
+    "modeInclusiveExample": "Przyklad: 100 juz zawiera {rate}% podatku — część podatkowa jest obliczana wstecz.",
     "labelLabel": "Etykieta podatku",
     "labelHint": "Własna nazwa wyświetlana na fakturach i ofertach (np. VAT, MwSt.). Pozostaw puste, aby użyć domyślnej zlokalizowanej nazwy.",
     "labelPlaceholder": "VAT",
@@ -657,24 +657,24 @@
   },
   "currency": {
     "title": "Waluta i podatki",
-    "description": "Domyslna waluta i stawka podatkowa dla faktur i rekordow serwisowych.",
+    "description": "Domyślna waluta i stawka podatkowa dla faktur i rekordów serwisowych.",
     "currencyLabel": "Waluta",
     "searchCurrency": "Szukaj waluty...",
     "noCurrencyFound": "Nie znaleziono waluty.",
-    "previewLabel": "Podglad: {value}",
+    "previewLabel": "Podgląd: {value}",
     "enableTax": "Wlacz podatek",
-    "enableTaxHint": "Pokaz obliczanie podatku na fakturach i rekordach serwisowych",
-    "defaultTaxRate": "Domyslna stawka podatkowa (%)",
-    "defaultTaxRateHint": "Automatycznie wypelniana przy tworzeniu nowych rekordow serwisowych",
-    "applyTaxToExistingLabel": "Zastosuj podatek do istniejacych rekordow",
-    "applyTaxToExistingHint": "Stosuje domyslna stawke podatku do istniejacych zlecen serwisowych i ofert bez ustawionej stawki. Kwalifikujace sie: {serviceRecords} zlecen, {quotes} ofert.",
-    "applyTaxToExistingButton": "Zastosuj podatek do istniejacych rekordow",
-    "applyTaxConfirmTitle": "Zastosowac podatek do istniejacych rekordow?",
-    "applyTaxConfirmDescription": "Ustawi to stawke podatku na {rate}% dla {serviceRecords} zlecen serwisowych i {quotes} ofert, ktore obecnie nie maja ustawionej stawki, i przeliczy ich sumy. Rekordy z wlasna stawka podatku nie zostana zmienione. Tej operacji nie mozna cofnac.",
+    "enableTaxHint": "Pokaż obliczanie podatku na fakturach i rekordach serwisowych",
+    "defaultTaxRate": "Domyślna stawka podatkowa (%)",
+    "defaultTaxRateHint": "Automatycznie wypelniana przy tworzeniu nowych rekordów serwisowych",
+    "applyTaxToExistingLabel": "Zastosuj podatek do istniejących rekordów",
+    "applyTaxToExistingHint": "Stosuje domyślna stawkę podatku do istniejących zleceń serwisowych i ofert bez ustawionej stawki. Kwalifikujące sie: {serviceRecords} zleceń, {quotes} ofert.",
+    "applyTaxToExistingButton": "Zastosuj podatek do istniejących rekordów",
+    "applyTaxConfirmTitle": "Zastosować podatek do istniejących rekordów?",
+    "applyTaxConfirmDescription": "Ustawi to stawkę podatku na {rate}% dla {serviceRecords} zleceń serwisowych i {quotes} ofert, które obecnie nie maja ustawionej stawki, i przeliczy ich sumy. Rekordy z wlasna stawka podatku nie zostaną zmienione. Tej operacji nie można cofnąć.",
     "applyTaxConfirmLabel": "Zastosuj podatek",
-    "applyTaxSuccess": "Zaktualizowano {serviceRecords} zlecen serwisowych i {quotes} ofert.",
-    "applyTaxFailed": "Nie udalo sie zastosowac podatku do istniejacych rekordow",
-    "applyTaxNoRecords": "Zadne rekordy nie wymagaja domyslnej stawki podatku.",
+    "applyTaxSuccess": "Zaktualizowano {serviceRecords} zleceń serwisowych i {quotes} ofert.",
+    "applyTaxFailed": "Nie udalo sie zastosować podatku do istniejących rekordów",
+    "applyTaxNoRecords": "Żadne rekordy nie wymagają domyślnej stawki podatku.",
     "saveSettings": "Zapisz ustawienia",
     "saved": "Ustawienia zapisane",
     "currencies": {
@@ -767,7 +767,7 @@
     "label": "Etykieta",
     "labelPlaceholder": "np. Wygaśnięcie gwarancji",
     "fieldName": "Nazwa pola (ID)",
-    "fieldNamePlaceholder": "wygasniecie_gwarancji",
+    "fieldNamePlaceholder": "wygaśnięcie_gwarancji",
     "fieldNameHint": "Tylko małe litery, cyfry i podkreślenia.",
     "fieldType": "Typ pola",
     "entityType": "Typ obiektu",
@@ -796,9 +796,9 @@
     "delete": "Delete"
   },
   "workshop": {
-    "title": "Domyslne warsztatu",
-    "description": "Domyslne wartosci przy tworzeniu nowych rekordow serwisowych.",
-    "defaultTechnician": "Domyslna nazwa technika",
+    "title": "Domyślne warsztatu",
+    "description": "Domyślne wartości przy tworzeniu nowych rekordów serwisowych.",
+    "defaultTechnician": "Domyślna nazwa technika",
     "technicianPlaceholder": "Szukaj technika...",
     "failedCreateTech": "Nie udało się utworzyć technika",
     "creating": "Tworzenie...",
@@ -806,10 +806,10 @@
     "newTechPlaceholder": "Nazwa technika",
     "addTech": "Dodaj",
     "addNewTech": "+ Dodaj nowego",
-    "defaultLaborRate": "Domyslna stawka za godzine",
+    "defaultLaborRate": "Domyślna stawka za godzinę",
     "laborRatePlaceholder": "75,00",
     "unitsTitle": "Jednostki",
-    "unitsDescription": "Wybierz miedzy metrycznym (km, litry) a imperialnym (mile, galony).",
+    "unitsDescription": "Wybierz między metrycznym (km, litry) a imperialnym (mile, galony).",
     "unitSystem": "System jednostek",
     "metric": "Metryczny (km, litry)",
     "imperial": "Imperialny (mile, galony)",
@@ -835,48 +835,48 @@
       "0": "Niedziela",
       "1": "Poniedzialek",
       "2": "Wtorek",
-      "3": "Sroda",
+      "3": "Środa",
       "4": "Czwartek",
-      "5": "Piatek",
+      "5": "Piątek",
       "6": "Sobota"
     }
   },
   "maintenance": {
     "title": "Przewidywana konserwacja",
-    "description": "Przewiduj aktualny przebieg kazdego pojazdu na podstawie historii serwisowej i wyswietlaj pojazdy wymagajace lub zblizajace sie do nastepnego serwisu.",
+    "description": "Przewiduj aktualny przebieg każdego pojazdu na podstawie historii serwisowej i wyswietlaj pojazdy wymagajace lub zblizajace sie do nastepnego serwisu.",
     "enablePredicted": "Wlacz przewidywana konserwacje",
-    "enablePredictedHint": "Pokaz prognozy na szczegolach pojazdu i pulpicie",
+    "enablePredictedHint": "Pokaż prognozy na szczegolach pojazdu i pulpicie",
     "serviceInterval": "Interwal serwisowy ({unit})",
-    "serviceIntervalHint": "Odleglosc miedzy zalecanymi serwisami",
-    "approachingThreshold": "Prog zblizania ({unit})",
-    "approachingThresholdHint": "Pokaz ostrzezenie w tej odleglosci od nastepnego serwisu",
+    "serviceIntervalHint": "Odleglosc między zalecanymi serwisami",
+    "approachingThreshold": "Próg zblizania ({unit})",
+    "approachingThresholdHint": "Pokaż ostrzezenie w tej odleglosci od nastepnego serwisu",
     "saveMaintenance": "Zapisz ustawienia konserwacji",
     "saved": "Ustawienia konserwacji zapisane"
   },
   "appearance": {
-    "title": "Wyglad",
-    "description": "Dostosuj wyglad i styl Torqvoice.",
+    "title": "Wygląd",
+    "description": "Dostosuj wygląd i styl Torqvoice.",
     "darkMode": "Tryb ciemny",
-    "darkModeHint": "Przelacz miedzy jasnym a ciemnym motywem",
+    "darkModeHint": "Przelacz między jasnym a ciemnym motywem",
     "dateTimeTitle": "Data i czas",
-    "dateTimeDescription": "Skonfiguruj sposob wyswietlania dat i godzin w aplikacji.",
+    "dateTimeDescription": "Skonfiguruj sposób wyświetlania dat i godzin w aplikacji.",
     "dateFormat": "Format daty",
     "timeFormat": "Format czasu",
     "time12h": "12-godzinny (2:30 PM)",
     "time24h": "24-godzinny (14:30)",
     "timezone": "Strefa czasowa",
-    "timezoneAuto": "Automatyczne wykrywanie (przegladarka)",
+    "timezoneAuto": "Automatyczne wykrywanie (przeglądarka)",
     "searchTimezone": "Szukaj strefy czasowej...",
     "noTimezoneFound": "Nie znaleziono strefy czasowej.",
-    "timezoneHint": "Przy automatycznym wykrywaniu kazdy uzytkownik widzi daty w strefie czasowej przegladarki.",
-    "previewLabel": "Podglad",
+    "timezoneHint": "Przy automatycznym wykrywaniu każdy użytkownik widzi daty w strefie czasowej przeglądarki.",
+    "previewLabel": "Podgląd",
     "dateLabel": "Data",
     "dateTimeLabel": "Data i czas",
     "saveSettings": "Zapisz ustawienia",
     "saved": "Ustawienia zapisane"
   },
   "data": {
-    "title": "Zarzadzanie danymi",
+    "title": "Zarządzanie danymi",
     "description": "Eksportuj dane jako kopie zapasowa lub importuj wczesniej wyeksportowany plik.",
     "exportTitle": "Eksportuj dane",
     "exportDescription": "Pobierz dane jako plik zip. Wybierz kategorie do uwzglednienia w kopii zapasowej.",
@@ -886,13 +886,13 @@
     "optSettings": "Ustawienia",
     "optSettingsDesc": "Info warsztatu, konfiguracja faktur, dostawcy platnosci",
     "optCustomers": "Klienci",
-    "optCustomersDesc": "Rekordy klientow i dane kontaktowe",
+    "optCustomersDesc": "Rekordy klientów i dane kontaktowe",
     "optVehicles": "Pojazdy i rekordy serwisowe",
     "optVehiclesDesc": "Pojazdy, notatki, dzienniki paliwa, przypomnienia, rekordy serwisowe",
     "optQuotes": "Wyceny",
     "optQuotesDesc": "Wyceny z czesciami i robocizna",
     "optInventory": "Inwentarz",
-    "optInventoryDesc": "Czesci inwentarza i poziomy zapasow",
+    "optInventoryDesc": "Części inwentarza i poziomy zapasow",
     "optTechnicians": "Technicy",
     "optTechniciansDesc": "Profile techników i przypisania do tablicy pracy",
     "optInspections": "Inspekcje",
@@ -906,54 +906,54 @@
     "optNotifications": "Powiadomienia",
     "optNotificationsDesc": "Powiadomienia systemowe",
     "optCustomFields": "Pola niestandardowe",
-    "optCustomFieldsDesc": "Definicje i wartosci pol niestandardowych",
+    "optCustomFieldsDesc": "Definicje i wartości pol niestandardowych",
     "optFiles": "Przeslane pliki",
-    "optFilesDesc": "Zdjecia, zalaczniki serwisowe, logo",
-    "filesWarning": "Zawiera przeslane pliki — kopia zapasowa moze byc wieksza",
+    "optFilesDesc": "Zdjęcia, zalaczniki serwisowe, logo",
+    "filesWarning": "Zawiera przeslane pliki — kopia zapasowa może byc wieksza",
     "downloadBackup": "Pobierz kopie zapasowa",
-    "exportComplete": "Eksport zakonczony",
+    "exportComplete": "Eksport zakończony",
     "exportCompleteMessage": "Plik kopii zapasowej zostal pobrany.",
     "exportFailed": "Eksport nie powiodl sie",
-    "exportFailedMessage": "Nie udalo sie wyeksportowac danych. Sprobuj ponownie.",
+    "exportFailedMessage": "Nie udalo sie wyeksportowac danych. Spróbuj ponownie.",
     "importTitle": "Importuj dane",
-    "importWarning": "Import <bold>zastapi wszystkie istniejace dane</bold> zawartoscia pliku kopii zapasowej. Tej akcji nie mozna cofnac.",
+    "importWarning": "Import <bold>zastapi wszystkie istniejące dane</bold> zawartoscia pliku kopii zapasowej. Tej akcji nie można cofnąć.",
     "importFileHint": "Obsluguje pliki kopii zapasowej .zip (v2) i .json (v1).",
-    "uploadRestore": "Przeslij i przywroc",
-    "importComplete": "Import zakonczony",
+    "uploadRestore": "Prześlij i przywróć",
+    "importComplete": "Import zakończony",
     "importCompleteMessage": "Wszystkie dane zostaly przywrocone z pliku kopii zapasowej.",
     "importFailed": "Import nie powiodl sie",
     "importFromOther": "Importuj z innych uslug",
-    "importFromOtherDescription": "Migruj dane z innej platformy. Wybierz usluge, aby rozpoczac.",
+    "importFromOtherDescription": "Migruj dane z innej platformy. Wybierz usluge, aby rozpocząć.",
     "lubelogTitle": "Importuj z LubeLog",
     "lubelogExportSteps": "Jak wyeksportowac dane z LubeLog:",
-    "lubelogStep1": "Otworz instancje LubeLog",
-    "lubelogStep2": "Przejdz do <bold>Settings</bold>",
-    "lubelogStep3": "Kliknij <bold>Make Backup</bold> aby pobrac plik zip",
-    "lubelogStep4": "Przeslij pobrany plik zip ponizej",
-    "lubelogAddNote": "To <bold>doda dane obok</bold> istniejacych rekordow — nic nie zostanie usuniete.",
+    "lubelogStep1": "Otwórz instancje LubeLog",
+    "lubelogStep2": "Przejdź do <bold>Settings</bold>",
+    "lubelogStep3": "Kliknij <bold>Make Backup</bold> aby pobrać plik zip",
+    "lubelogStep4": "Prześlij pobrany plik zip poniżej",
+    "lubelogAddNote": "To <bold>doda dane obok</bold> istniejących rekordów — nic nie zostanie usunięte.",
     "lubelogImported": "Co zostanie zaimportowane:",
     "lubelogVehicles": "Pojazdy",
     "lubelogServiceRecords": "Rekordy serwisowe z zalacznikami",
     "lubelogNotes": "Notatki",
     "lubelogFileHint": "Obslugiwane pliki do 200MB.",
-    "lubelogImportComplete": "Import z LubeLog zakonczony",
+    "lubelogImportComplete": "Import z LubeLog zakończony",
     "cancel": "Anuluj",
     "import": "Importuj",
     "invoiceNinjaTitle": "Importuj z Invoice Ninja",
     "invoiceNinjaExportSteps": "Jak wyeksportowac dane z Invoice Ninja:",
-    "invoiceNinjaStep1": "Otworz instancje Invoice Ninja",
-    "invoiceNinjaStep2": "Przejdz do <bold>Settings > Import | Export</bold>",
-    "invoiceNinjaStep3": "Kliknij <bold>Export</bold> aby pobrac plik zip",
-    "invoiceNinjaStep4": "Przeslij pobrany plik zip ponizej",
-    "invoiceNinjaAddNote": "To <bold>doda dane obok</bold> istniejacych rekordow — nic nie zostanie usuniete.",
+    "invoiceNinjaStep1": "Otwórz instancje Invoice Ninja",
+    "invoiceNinjaStep2": "Przejdź do <bold>Settings > Import | Export</bold>",
+    "invoiceNinjaStep3": "Kliknij <bold>Export</bold> aby pobrać plik zip",
+    "invoiceNinjaStep4": "Prześlij pobrany plik zip poniżej",
+    "invoiceNinjaAddNote": "To <bold>doda dane obok</bold> istniejących rekordów — nic nie zostanie usunięte.",
     "invoiceNinjaImported": "Co zostanie zaimportowane:",
     "invoiceNinjaCustomers": "Klienci",
     "invoiceNinjaProducts": "Produkty (inwentarz)",
-    "invoiceNinjaInvoices": "Faktury jako rekordy serwisowe (czesci i robocizna)",
+    "invoiceNinjaInvoices": "Faktury jako rekordy serwisowe (części i robocizna)",
     "invoiceNinjaDocuments": "Dokumenty i zalaczniki",
     "invoiceNinjaPayments": "Platnosci",
-    "invoiceNinjaFileHint": "Przeslij plik eksportu zip Invoice Ninja. Obslugiwane pliki do 200MB.",
-    "invoiceNinjaImportComplete": "Import z Invoice Ninja zakonczony",
+    "invoiceNinjaFileHint": "Prześlij plik eksportu zip Invoice Ninja. Obslugiwane pliki do 200MB.",
+    "invoiceNinjaImportComplete": "Import z Invoice Ninja zakończony",
     "backupStatus": {
       "title": "Ochrona danych",
       "description": "Poważnie traktujemy Twoje dane. Wszystkie dane Twojej organizacji, w tym klienci, pojazdy, zlecenia, faktury i przesłane pliki, są automatycznie kopiowane co godzinę. Kopie zapasowe są chronione szyfrowaniem AES-256, zanim opuszczą naszą infrastrukturę, i przechowywane w oddzielnej, bezpiecznej lokalizacji, więc nikt z zewnątrz nie może ich odczytać.",
@@ -964,22 +964,22 @@
   },
   "license": {
     "title": "Licencja white-label",
-    "description": "Aktywuj licencje white-label, aby usunac marke Torqvoice z faktur i aplikacji.",
+    "description": "Aktywuj licencje white-label, aby usunąć marke Torqvoice z faktur i aplikacji.",
     "purchaseWhiteLabel": "Kup white-label",
     "status": "Status:",
     "active": "Aktywna",
     "inactive": "Nieaktywna",
     "lastChecked": "Ostatnio sprawdzono: {date}",
-    "enterLicenseKey": "Wprowadz klucz licencji",
+    "enterLicenseKey": "Wprowadź klucz licencji",
     "validate": "Zweryfikuj",
-    "validated": "Licencja zweryfikowana pomyslnie",
+    "validated": "Licencja zweryfikowana pomyślnie",
     "invalid": "Nieprawidlowy klucz licencji",
-    "failedValidate": "Nie udalo sie zweryfikowac licencji",
-    "keyHint": "Kazdy klucz licencji jest powiazany z ta organizacja i nie moze byc wspoldzielony miedzy wieloma organizacjami."
+    "failedValidate": "Nie udalo sie zweryfikować licencji",
+    "keyHint": "Każdy klucz licencji jest powiazany z ta organizacja i nie może byc wspoldzielony między wieloma organizacjami."
   },
   "about": {
     "title": "O Torqvoice",
-    "description": "Torqvoice to kompletne rozwiazanie do zarzadzania serwisem pojazdow. Sledz historie serwisowa, naprawy i wiecej.",
+    "description": "Torqvoice to kompletne rozwiazanie do zarzadzania serwisem pojazdów. Sledz historię serwisowa, naprawy i więcej.",
     "version": "Wersja",
     "website": "Strona",
     "documentation": "Dokumentacja",
@@ -987,21 +987,21 @@
   },
   "portal": {
     "title": "Portal klienta",
-    "description": "Pozwol swoim klientom logowac sie i przegladac swoje pojazdy, faktury, wyceny i inspekcje. Klienci moga rowniez wysylac zapytania serwisowe.",
+    "description": "Pozwol swoim klientom logować sie i przeglądać swoje pojazdy, faktury, wyceny i inspekcje. Klienci mogą również wysylac zapytania serwisowe.",
     "enablePortal": "Wlacz portal klienta",
-    "enablePortalHint": "Klienci z adresem e-mail moga logowac sie za pomoca magicznego linku",
+    "enablePortalHint": "Klienci z adresem e-mail mogą logować sie za pomocą magicznego linku",
     "enabled": "Portal klienta wlaczony",
     "disabled": "Portal klienta wylaczony",
-    "failedUpdate": "Nie udalo sie zaktualizowac ustawienia",
+    "failedUpdate": "Nie udalo sie zaktualizować ustawienia",
     "customSlug": "Niestandardowy slug portalu",
     "save": "Zapisz",
     "slugSaved": "Slug portalu zapisany",
-    "slugRemoved": "Slug portalu usuniety",
-    "failedUpdateSlug": "Nie udalo sie zaktualizowac sluga",
-    "slugHint": "Male litery, cyfry, myslniki i podkreslenia. 3-48 znakow. Pozostaw puste, aby uzyc domyslnego URL.",
+    "slugRemoved": "Slug portalu usunięty",
+    "failedUpdateSlug": "Nie udalo sie zaktualizować sluga",
+    "slugHint": "Male litery, cyfry, myslniki i podkreślenia. 3-48 znaków. Pozostaw puste, aby użyć domyslnego URL.",
     "portalUrl": "URL portalu",
     "urlCopied": "URL portalu skopiowany",
-    "shareHint": "Udostepnij ten link swoim klientom, aby mogli zalogowac sie do swojego portalu.",
+    "shareHint": "Udostępnij ten link swoim klientom, aby mogli zalogować sie do swojego portalu.",
     "serviceRequestAlertsHint": "Zgłoszenia serwisowe z portalu zawsze pojawiają się w dzwonku powiadomień. Możesz je otrzymywać także e-mailem.",
     "serviceRequestAlertsLink": "Ustawienia powiadomień →",
     "descriptionLabel": "O Twoim warsztacie",
@@ -1039,28 +1039,28 @@
   },
   "email": {
     "title": "Dostawca poczty e-mail",
-    "description": "Skonfiguruj dostawce poczty e-mail swojej organizacji do wysylania faktur i ofert. Jesli nie skonfigurowano, zostanie uzyty domyslny dostawca poczty e-mail platformy.",
-    "useCustomLabel": "Uzyj wlasnego dostawcy poczty e-mail",
-    "useCustomHint": "Wlacz, aby skonfigurowac wlasnego dostawce poczty e-mail zamiast domyslnego platformy",
-    "platformDefaultInfo": "Twoja organizacja korzysta z domyslnego dostawcy poczty e-mail platformy. Faktury i oferty beda wysylane przy uzyciu skonfigurowanych ustawien poczty e-mail platformy.",
+    "description": "Skonfiguruj dostawcę poczty e-mail swojej organizacji do wysylania faktur i ofert. Jeśli nie skonfigurowano, zostanie uzyty domyślny dostawca poczty e-mail platformy.",
+    "useCustomLabel": "Użyj wlasnego dostawcy poczty e-mail",
+    "useCustomHint": "Wlacz, aby skonfigurować wlasnego dostawcę poczty e-mail zamiast domyslnego platformy",
+    "platformDefaultInfo": "Twoja organizacja korzysta z domyslnego dostawcy poczty e-mail platformy. Faktury i oferty będą wysylane przy uzyciu skonfigurowanych ustawień poczty e-mail platformy.",
     "smtpHost": "Host SMTP",
     "smtpHostPlaceholder": "smtp.example.com",
     "smtpPort": "Port SMTP",
     "smtpPortPlaceholder": "587",
-    "username": "Nazwa uzytkownika",
-    "usernamePlaceholder": "uzytkownik@example.com",
+    "username": "Nazwa użytkownika",
+    "usernamePlaceholder": "użytkownik@example.com",
     "password": "Haslo",
     "fromEmail": "E-mail nadawcy",
     "fromEmailPlaceholder": "noreply@example.com",
     "fromEmailDomainPlaceholder": "noreply@twojadomena.com",
     "fromName": "Nazwa nadawcy",
-    "fromNamePlaceholder": "Twoj Warsztat",
+    "fromNamePlaceholder": "Twój Warsztat",
     "tlsConnection": "Polaczenie TLS (Port 465)",
     "tlsConnectionHint": "Wlacz dla niejawnego TLS. Wylacz dla STARTTLS (port 587/25).",
     "verifyTlsCerts": "Weryfikuj certyfikaty TLS",
-    "verifyTlsCertsHint": "Wylacz, aby zezwolic na samopodpisane certyfikaty (niezalecane w produkcji)",
+    "verifyTlsCertsHint": "Wylacz, aby zezwolić na samopodpisane certyfikaty (niezalecane w produkcji)",
     "requireTlsUpgrade": "Wymagaj aktualizacji TLS",
-    "requireTlsUpgradeHint": "Wymus aktualizacje TLS na polaczeniach STARTTLS",
+    "requireTlsUpgradeHint": "Wymuś aktualizacje TLS na polaczeniach STARTTLS",
     "apiKey": "Klucz API",
     "apiKeyHintResend": "Uzyskaj klucz API na",
     "serverToken": "Token serwera",
@@ -1070,45 +1070,45 @@
     "domainPlaceholder": "mg.twojadomena.com",
     "region": "Region",
     "apiKeyHintSendGrid": "Uzyskaj klucz API na",
-    "accessKeyId": "ID klucza dostepu",
-    "secretAccessKey": "Tajny klucz dostepu",
+    "accessKeyId": "ID klucza dostępu",
+    "secretAccessKey": "Tajny klucz dostępu",
     "awsRegion": "Region AWS",
     "awsRegionPlaceholder": "us-east-1",
-    "awsRegionHint": "Region AWS, w ktorym skonfigurowano SES (np. us-east-1, eu-west-1)",
-    "sendTestEmail": "Wyslij testowy e-mail",
-    "testEmailHint": "Najpierw zapisz ustawienia, a nastepnie wyslij testowy e-mail na swoje konto",
+    "awsRegionHint": "Region AWS, w którym skonfigurowano SES (np. us-east-1, eu-west-1)",
+    "sendTestEmail": "Wyślij testowy e-mail",
+    "testEmailHint": "Najpierw zapisz ustawienia, a następnie wyślij testowy e-mail na swoje konto",
     "saveSettings": "Zapisz ustawienia",
-    "savedDefault": "Ustawienia poczty e-mail zapisane — uzyto domyslnych platformy",
+    "savedDefault": "Ustawienia poczty e-mail zapisane — użyto domyslnych platformy",
     "saved": "Ustawienia poczty e-mail zapisane",
-    "failedSave": "Nie udalo sie zapisac ustawien",
+    "failedSave": "Nie udalo sie zapisać ustawień",
     "testSentTo": "Testowy e-mail wyslany do {email}",
     "testFailed": "Test e-mail nie powiodl sie"
   },
   "sms": {
     "title": "Dostawca SMS",
-    "description": "Skonfiguruj dostawce SMS do dwukierunkowej komunikacji z klientami. Kazda organizacja uzywa wlasnego dostawcy SMS i numeru telefonu.",
-    "enableLabel": "Wlacz wiadomosci SMS",
-    "enableHint": "Wysylaj i odbieraj wiadomosci SMS z klientami",
-    "disabledInfo": "Wiadomosci SMS sa wylaczone. Wlacz, aby wysylac i odbierac wiadomosci tekstowe ze swoimi klientami.",
+    "description": "Skonfiguruj dostawcę SMS do dwukierunkowej komunikacji z klientami. Każda organizacja uzywa wlasnego dostawcy SMS i numeru telefonu.",
+    "enableLabel": "Wlacz wiadomości SMS",
+    "enableHint": "Wysylaj i odbieraj wiadomości SMS z klientami",
+    "disabledInfo": "Wiadomości SMS sa wylaczone. Wlacz, aby wysylac i odbierać wiadomości tekstowe ze swoimi klientami.",
     "phoneNumber": "Numer telefonu",
     "phoneNumberPlaceholder": "+48123456789",
-    "phoneNumberHint": "Twoj numer telefonu z obsluga SMS w formacie E.164 (np. +48123456789)",
+    "phoneNumberHint": "Twój numer telefonu z obsluga SMS w formacie E.164 (np. +48123456789)",
     "accountSid": "SID konta",
     "authToken": "Token autoryzacji",
     "apiKey": "Klucz API",
     "apiSecret": "Sekret API",
     "webhookUrl": "URL webhooka",
-    "webhookUrlHint": "Skonfiguruj ten URL w panelu {provider}, aby odbierac przychodzace wiadomosci SMS.",
+    "webhookUrlHint": "Skonfiguruj ten URL w panelu {provider}, aby odbierać przychodzace wiadomości SMS.",
     "webhookCopied": "URL webhooka skopiowany",
     "testPhoneNumber": "Testowy numer telefonu",
     "testPhonePlaceholder": "+48987654321",
-    "sendTestSms": "Wyslij testowy SMS",
-    "testSmsHint": "Najpierw zapisz ustawienia, a nastepnie wprowadz numer odbiorcy, aby wyslac testowy SMS",
-    "enterTestPhone": "Wprowadz numer telefonu, aby wyslac testowy SMS",
+    "sendTestSms": "Wyślij testowy SMS",
+    "testSmsHint": "Najpierw zapisz ustawienia, a następnie wprowadź numer odbiorcy, aby wyslac testowy SMS",
+    "enterTestPhone": "Wprowadź numer telefonu, aby wyslac testowy SMS",
     "saveSettings": "Zapisz ustawienia",
     "savedDisabled": "Ustawienia SMS zapisane — SMS wylaczony",
     "saved": "Ustawienia SMS zapisane",
-    "failedSave": "Nie udalo sie zapisac ustawien",
+    "failedSave": "Nie udalo sie zapisać ustawień",
     "testSentTo": "Testowy SMS wyslany do {phone}",
     "testFailed": "Test SMS nie powiodl sie",
     "copyWebhookUrl": "Copy webhook URL"
@@ -1188,16 +1188,16 @@
     "disabledInfo": "Wlacz AI, aby korzystac z funkcji takich jak automatycznie generowane opisy serwisowe i podsumowania historii pojazdu.",
     "provider": "Dostawca AI",
     "apiKey": "Klucz API",
-    "apiKeyHint": "Twoj klucz API {provider}. Przechowywany bezpiecznie i nigdy nieudostepniany.",
+    "apiKeyHint": "Twój klucz API {provider}. Przechowywany bezpiecznie i nigdy nieudostepniany.",
     "model": "Model",
     "testConnection": "Testuj polaczenie",
     "testSuccess": "Polaczenie AI udane!",
-    "testFailed": "Polaczenie nieudane. Sprawdz klucz API i model.",
+    "testFailed": "Polaczenie nieudane. Sprawdź klucz API i model.",
     "saved": "Ustawienia AI zapisane",
-    "failedSave": "Nie udalo sie zapisac ustawien AI",
+    "failedSave": "Nie udalo sie zapisać ustawień AI",
     "saveSettings": "Zapisz ustawienia",
     "featuresTitle": "Zawarte funkcje AI:",
-    "featureServiceNotes": "Automatyczne generowanie profesjonalnych opisow serwisowych z czesci i robocizny",
+    "featureServiceNotes": "Automatyczne generowanie profesjonalnych opisow serwisowych z części i robocizny",
     "featureHistorySummary": "Podsumowanie historii serwisowej pojazdu jednym kliknieciem",
     "cost": {
       "high": "Wysoki koszt",

+ 11 - 1
messages/pt-BR/audit.json

@@ -98,6 +98,16 @@
     "webhook_create": "Webhook criado",
     "webhook_update": "Webhook atualizado",
     "webhook_delete": "Webhook excluído",
-    "webhook_rotateSecret": "Segredo do webhook rotacionado"
+    "webhook_rotateSecret": "Segredo do webhook rotacionado",
+    "inspection_reopen": "Inspeção reaberta",
+    "inspectionTemplate_import": "Modelo de inspeção importado",
+    "scheduled_message_create": "Mensagem agendada criada",
+    "scheduled_message_update": "Mensagem agendada atualizada",
+    "scheduled_message_delete": "Mensagem agendada excluída",
+    "scheduled_message_cancel": "Mensagem agendada cancelada",
+    "scheduled_message_send_now": "Mensagem agendada enviada",
+    "settings_applyTaxRate": "Alíquota aplicada",
+    "settings_convertToExclusive": "Preços convertidos para sem imposto",
+    "settings_convertToInclusive": "Preços convertidos para com imposto"
   }
 }

+ 24 - 0
messages/pt-BR/common.json

@@ -52,5 +52,29 @@
     "updated": "O Torqvoice foi atualizado para {version}",
     "whatsNew": "Ver novidades",
     "dismiss": "Fechar"
+  },
+  "pagination": {
+    "showing": "Mostrando {start}–{end} de {total}",
+    "perPage": "por página",
+    "pageOf": "Página {page} de {pages}",
+    "first": "Primeira página",
+    "previous": "Página anterior",
+    "next": "Próxima página",
+    "last": "Última página"
+  },
+  "shared": {
+    "close": "Fechar",
+    "viewPlans": "Ver planos",
+    "manageLicense": "Gerenciar licença",
+    "installOwn": "Instale a sua",
+    "install": "Instalar",
+    "installApp": "Instalar aplicativo",
+    "installTorqvoice": "Instalar Torqvoice",
+    "dismiss": "Dispensar",
+    "useCamera": "Usar a câmera",
+    "enterManually": "Digitar manualmente",
+    "enterBarcode": "Digitar código de barras…",
+    "scanHint": "Aponte a câmera para um código de barras ou digite-o manualmente",
+    "iosInstallHint": "Toque em Compartilhar > Adicionar à Tela de Início"
   }
 }

+ 1 - 1
messages/pt-BR/customers.json

@@ -54,7 +54,7 @@
     "editTitle": "Editar Cliente",
     "nameRequired": "Nome *",
     "namePlaceholder": "João Silva",
-    "emailPlaceholder": "joao@exemplo.com.br",
+    "emailPlaceholder": "joão@exemplo.com.br",
     "phonePlaceholder": "(11) 91234-5678",
     "companyPlaceholder": "Nome da empresa",
     "addressPlaceholder": "Rua Principal 123, São Paulo, SP",

+ 396 - 0
messages/pt-BR/inspections.json

@@ -0,0 +1,396 @@
+{
+  "grades": {
+    "eu": {
+      "pass": "Sem deficiência",
+      "attention": "Deficiência leve",
+      "fail": "Deficiência grave",
+      "dangerous": "Deficiência perigosa",
+      "not_inspected": "Não inspecionado"
+    },
+    "basic": {
+      "pass": "Aprovado",
+      "attention": "Atenção",
+      "fail": "Reprovado",
+      "dangerous": "Perigoso",
+      "not_inspected": "Não inspecionado"
+    },
+    "short": {
+      "pass": "OK",
+      "attention": "Leve",
+      "fail": "Grave",
+      "dangerous": "Perigosa",
+      "not_inspected": "–"
+    },
+    "hint": {
+      "pass": "Atende aos requisitos. Nada a relatar.",
+      "attention": "Sem efeito relevante sobre a segurança. O veículo é aprovado; reparar quando der.",
+      "fail": "Pode prejudicar a segurança ou afetar o meio ambiente. O veículo é reprovado; reparar e reinspecionar.",
+      "dangerous": "Risco direto e imediato à segurança viária. O veículo não deve ser conduzido.",
+      "not_inspected": "Este item não foi executado."
+    }
+  },
+  "results": {
+    "pass": "Aprovado",
+    "pass_minor": "Aprovado com deficiências leves",
+    "fail": "Reprovado — deficiências graves",
+    "fail_dangerous": "Reprovado — deficiências perigosas",
+    "incomplete": "Não concluído",
+    "detail": {
+      "pass": "Nenhuma deficiência foi encontrada.",
+      "pass_minor": "O veículo foi aprovado. Repare as deficiências leves sem demora indevida.",
+      "fail": "O veículo foi reprovado. As deficiências devem ser reparadas e o veículo reinspecionado.",
+      "fail_dangerous": "O veículo não deve circular em via pública até que as deficiências perigosas sejam reparadas.",
+      "incomplete": "Esta inspeção ainda está em andamento."
+    }
+  },
+  "page": {
+    "back": "Inspeções",
+    "statusInProgress": "Em andamento",
+    "statusCompleted": "Concluída",
+    "complete": "Concluir",
+    "reopen": "Reabrir",
+    "share": "Compartilhar",
+    "save": "Salvar",
+    "saving": "Salvando…",
+    "savedAt": "Salvo {time}",
+    "savesAsYouGo": "Salva enquanto você trabalha",
+    "saveFailed": "Algumas alterações não foram salvas",
+    "allSaved": "Todas as alterações estão salvas",
+    "moreActions": "Mais ações",
+    "downloadCertificate": "Baixar laudo",
+    "manageTemplates": "Gerenciar modelos",
+    "delete": "Excluir",
+    "createWorkOrder": "Criar ordem de serviço",
+    "viewWorkOrder": "Ver ordem de serviço",
+    "createQuote": "Criar orçamento",
+    "viewQuote": "Ver orçamento",
+    "needsDefect": "Disponível assim que uma deficiência for registrada",
+    "workOrderRaised": "Ordem de serviço {number} criada a partir desta inspeção em {date}",
+    "open": "Abrir",
+    "quoteCreated": "Orçamento criado por {name} em {date}",
+    "quoteRequested": "O cliente pediu orçamento de {count} item(ns)",
+    "stillToGrade": "Faltam {count} de {total} itens para classificar.",
+    "progress": "Progresso",
+    "checksGraded": "itens classificados",
+    "progressLabel": "{graded} de {total} itens classificados: {pass} sem deficiência, {attention} leves, {fail} graves, {dangerous} perigosas.",
+    "sections": "Grupos",
+    "graded": "{graded}/{total} classificados",
+    "vehicle": "Veículo",
+    "vessel": "Embarcação",
+    "customer": "Cliente",
+    "plate": "Placa",
+    "registration": "Registro",
+    "template": "Modelo",
+    "scaleEu": "Classificado pela escala de deficiências da UE da Diretiva 2014/45/UE.",
+    "scaleBasic": "Classificado aprovado / atenção / reprovado.",
+    "country": "País: {code}.",
+    "manageInspectionTemplates": "Gerenciar modelos de inspeção",
+    "readMore": "Saiba mais",
+    "odometer": "Odômetro",
+    "engineHours": "Horas de motor",
+    "completeTitle": "Concluir esta inspeção?",
+    "completeBody": "Os itens são travados na conclusão, e o resultado passa a ser “{result}”.",
+    "completeBlocked": "Restam {count} item(ns) que o modelo marca como obrigatórios.",
+    "completeUngraded": "{count} item(ns) não foram classificados.",
+    "completeDangerous": "{count} deficiência(s) perigosa(s) registrada(s). O veículo não deve circular em via pública.",
+    "reopenTitle": "Reabrir esta inspeção?",
+    "reopenBody": "Os itens voltam a ser editáveis e o resultado retorna para em andamento. Inspetor, número do laudo e data da próxima inspeção são mantidos.",
+    "reopenShared": "O link do cliente continua ativo e mostrará o resultado mudando enquanto você edita. Um laudo já baixado mantém o resultado anterior.",
+    "deleteTitle": "Excluir esta inspeção?",
+    "deleteBody": "Isso exclui permanentemente a inspeção, suas classificações, observações e fotos. Não é possível desfazer.",
+    "cancel": "Cancelar",
+    "completed": "Inspeção concluída",
+    "reopened": "Inspeção reaberta",
+    "deleted": "Inspeção excluída",
+    "completeFailed": "Não foi possível concluir a inspeção",
+    "reopenFailed": "Não foi possível reabrir a inspeção",
+    "deleteFailed": "Não foi possível excluir a inspeção",
+    "quoteFailed": "Não foi possível criar o orçamento",
+    "workOrderFailed": "Não foi possível criar a ordem de serviço"
+  },
+  "item": {
+    "required": "(obrigatório)",
+    "saving": "Salvando",
+    "addNote": "Adicionar observação",
+    "hideNote": "Ocultar observação",
+    "addPhoto": "Adicionar foto ou vídeo a {name}",
+    "notesFor": "Observações de {name}",
+    "describeDefect": "Descreva a deficiência — ela aparece no laudo",
+    "optionalNote": "Adicionar uma observação (opcional)",
+    "notesRequired": "Descreva a deficiência antes que esta classificação seja salva.",
+    "photoRequired": "Aqui é exigida uma foto da deficiência. A inspeção não pode ser concluída até que uma seja anexada.",
+    "reading": "Leitura",
+    "readingUnit": "Leitura ({unit})",
+    "limit": "Limite: {range}",
+    "recordedValue": "Valor registrado",
+    "answer": "Resposta",
+    "selectAnswer": "Escolha uma resposta",
+    "viewPhoto": "Ver a foto {index} de {name} em tamanho real",
+    "photoAlt": "{name}, foto {index}",
+    "removePhoto": "Remover a foto {index} de {name}",
+    "clearTitle": "Limpar esta classificação?",
+    "clearBody": "A observação registrada em “{name}” será removida e o item voltará para não inspecionado.",
+    "clear": "Limpar",
+    "cancel": "Cancelar",
+    "saveFailed": "Não foi possível salvar este item",
+    "uploadFailed": "Falha no envio",
+    "uploadFailedFor": "Não foi possível enviar {name}"
+  },
+  "suggestions": {
+    "title": "Deficiências comuns",
+    "add": "Adicionar “{text}” e classificar como {grade}",
+    "more": "Mais ({count})",
+    "search": "Buscar redação",
+    "empty": "Nenhuma redação corresponde.",
+    "source": {
+      "workshop": "Sua oficina",
+      "history": "Já usada",
+      "regulation": "Diretiva 2014/45/UE",
+      "general": "Geral"
+    }
+  },
+  "media": {
+    "viewer": "Imagem {index} de {total}: {caption}",
+    "close": "Fechar o visualizador",
+    "previous": "Imagem anterior",
+    "next": "Próxima imagem"
+  },
+  "certificate": {
+    "title": "Dados do laudo",
+    "description": "Exigidos em um laudo de inspeção técnica conforme a Diretiva 2014/45/UE, anexo IV.",
+    "inspector": "Inspetor",
+    "notRecorded": "Não registrado",
+    "addTechnician": "Adicionar técnico…",
+    "recordedAs": "Registrado como {name}",
+    "vehicleCategory": "Categoria do veículo",
+    "mileageAtTest": "{label} no momento da inspeção",
+    "reportReference": "Referência do laudo",
+    "reportReferenceHelp": "Sua própria referência para este laudo. Se você for um centro credenciado que emite um laudo oficial, informe aqui o número dele para que os dois coincidam. Caso contrário, deixe vazio.",
+    "optional": "Opcional",
+    "placeOfTest": "Local da inspeção",
+    "placeholderAddress": "Centro de inspeção ou endereço da oficina",
+    "fromSettings": "Das configurações da sua oficina.",
+    "change": "Alterar",
+    "differsFromWorkshop": "Difere do endereço da sua oficina.",
+    "useWorkshopAddress": "Usar o endereço da oficina",
+    "noWorkshopAddress": "Nenhum endereço de oficina está definido.",
+    "addOne": "Adicionar um",
+    "nextTestDue": "Próxima inspeção",
+    "notSet": "Não definida",
+    "intervalGroup": "Definir a data da próxima inspeção a partir desta",
+    "intervalHelp": "Contado a partir da data desta inspeção. Dois anos é a referência da União para automóveis e veículos comerciais leves; verifique o intervalo aplicado no seu país.",
+    "months6": "6 meses",
+    "years1": "1 ano",
+    "years2": "2 anos",
+    "years3": "3 anos",
+    "years4": "4 anos",
+    "unsaved": "Alterações não salvas",
+    "saveDetails": "Salvar dados",
+    "saved": "Dados salvos",
+    "saveFailed": "Não foi possível salvar os dados",
+    "addTechnicianTitle": "Adicionar técnico",
+    "addTechnicianBody": "A pessoa entra na lista da oficina e pode receber tanto trabalhos quanto inspeções.",
+    "technicianName": "Nome",
+    "technicianPlaceholder": "ex.: Anna Berg",
+    "addAndSelect": "Adicionar e selecionar",
+    "technicianAdded": "{name} adicionado e selecionado",
+    "technicianFailed": "Não foi possível adicionar o técnico",
+    "cancel": "Cancelar"
+  },
+  "templates": {
+    "title": "Modelos de inspeção",
+    "description": "As listas de verificação que seus técnicos percorrem. Crie uma por país, por tipo de veículo ou por serviço que você vende.",
+    "import": "Importar",
+    "browse": "Explorar listas",
+    "new": "Novo modelo",
+    "restoreBanner": "{count} listas prontas foram removidas da sua lista. Você pode colocá-las de volta.",
+    "restore": "Restaurar",
+    "restored": "{count} lista(s) restaurada(s)",
+    "allPresent": "Todas as listas já estão aqui",
+    "restoreFailed": "Não foi possível adicionar as listas",
+    "emptyTitle": "Ainda não há modelos",
+    "emptyBody": "As listas prontas, incluindo a inspeção técnica periódica da UE, foram todas removidas. Coloque-as de volta ou construa a sua do zero.",
+    "restoreChecklists": "Restaurar listas",
+    "buildMyOwn": "Construir a minha",
+    "default": "Padrão",
+    "actionsFor": "Ações de {name}",
+    "edit": "Editar",
+    "duplicate": "Duplicar",
+    "export": "Exportar…",
+    "delete": "Excluir",
+    "sections": "{count} grupo(s)",
+    "checks": "{count} item(ns)",
+    "measured": "{count} medidos",
+    "euScale": "Escala de deficiências da UE",
+    "basicScale": "Aprovado / atenção / reprovado",
+    "deleteTitle": "Excluir “{name}”?",
+    "deleteBody": "Isso exclui o modelo permanentemente. Inspeções já realizadas com ele mantêm a própria cópia da lista e não são afetadas.",
+    "deleted": "Modelo excluído",
+    "deleteFailed": "Não foi possível excluir o modelo",
+    "duplicated": "Modelo duplicado",
+    "duplicateFailed": "Não foi possível duplicar o modelo",
+    "cancel": "Cancelar",
+    "srSections": "Grupos",
+    "srChecks": "Itens",
+    "srMeasurements": "Medições"
+  },
+  "presets": {
+    "title": "Começar por um modelo",
+    "description": "Cada modelo é copiado para a sua oficina como um modelo comum. Renomeie grupos, mova itens e altere qualquer limite para combinar com o seu país e o seu equipamento.",
+    "search": "Buscar por nome, país ou norma",
+    "searchLabel": "Buscar modelos",
+    "inYourList": "Na sua lista",
+    "counts": "{sections} grupos · {checks} itens",
+    "noMatch": "Nenhum modelo corresponde a “{query}”.",
+    "warning": "Os modelos regulamentares trazem os limites usuais da UE. Confira-os com as suas regras nacionais antes de emitir um laudo.",
+    "cancel": "Cancelar",
+    "add": "Adicionar modelo",
+    "added": "Modelo adicionado. Ajuste-o à sua oficina.",
+    "addFailed": "Não foi possível adicionar o modelo",
+    "group": {
+      "regulatory": "Regulamentar",
+      "workshop": "Oficina",
+      "specialist": "Especializado"
+    },
+    "groupDescription": {
+      "regulatory": "Listas de inspeção técnica periódica baseadas na Diretiva 2014/45/UE.",
+      "workshop": "Listas gerais de serviço e venda sem vínculo com uma regulamentação.",
+      "specialist": "Listas para motorizações e embarcações que exigem procedimento próprio."
+    },
+    "euScale": "Escala de deficiências da UE",
+    "basicScale": "Aprovado / atenção / reprovado"
+  },
+  "builder": {
+    "newTitle": "Novo modelo",
+    "editTitle": "Editar modelo",
+    "description": "Construa a lista que a sua oficina realmente usa. Arraste para reordenar e configure cada item para registrar uma classificação, uma medição, uma observação escrita ou uma resposta fixa.",
+    "name": "Nome",
+    "namePlaceholder": "ex.: Inspeção técnica periódica",
+    "templateDescription": "Descrição",
+    "descriptionPlaceholder": "Para que serve esta lista e quando usá-la.",
+    "country": "País",
+    "notCountrySpecific": "Não específico de um país",
+    "countryHelp": "Registrado no laudo para que uma oficina que trabalha em mais de um país distinga suas listas.",
+    "defectScale": "Escala de deficiências",
+    "scaleEu": "Leve / grave / perigosa (UE)",
+    "scaleBasic": "Aprovado / atenção / reprovado",
+    "scaleEuHelp": "As categorias do artigo 7.º, n.º 2, da Diretiva 2014/45/UE. Um veículo apenas com deficiências leves continua aprovado.",
+    "scaleBasicHelp": "Uma escala simples de três passos para listas de serviço que não são uma inspeção obrigatória.",
+    "defaultTemplate": "Modelo padrão",
+    "defaultHelp": "Pré-selecionado quando um técnico inicia uma nova inspeção.",
+    "summary": "{sections} grupo(s) · {checks} item(ns)",
+    "renumber": "Renumerar referências",
+    "renumberHelp": "Numera cada grupo e cada item em ordem: 1, 1.1, 1.2, 2. Linhas novas são numeradas ao serem adicionadas, então isso só é necessário depois de mover coisas.",
+    "renumberWarning": "Esta lista usa referências normativas como 1.1.13. Renumerar substitui essas referências.",
+    "addSection": "Adicionar grupo",
+    "addCheck": "Adicionar item",
+    "ref": "Ref.",
+    "sectionName": "Nome do grupo, ex.: Freios",
+    "sectionNote": "Observação opcional exibida acima dos itens deste grupo",
+    "checkName": "Nome do item, ex.: Pastilhas de freio",
+    "reorderSection": "Reordenar o grupo {name}",
+    "reorderCheck": "Reordenar o item {name}",
+    "removeSection": "Remover o grupo {name}",
+    "removeCheck": "Remover o item {name}",
+    "duplicateCheck": "Duplicar o item {name}",
+    "configure": "Configurar {name}",
+    "hideSettings": "Ocultar as configurações de {name}",
+    "howRecorded": "Como é registrado?",
+    "guidance": "Orientação para o técnico",
+    "guidancePlaceholder": "Exibida sob o nome do item no formulário de inspeção.",
+    "measurement": "Medição",
+    "unit": "Unidade",
+    "minimum": "Mínimo",
+    "maximum": "Máximo",
+    "outOfRange": "Deficiência fora da faixa",
+    "rangeHelp": "Deixe um limite vazio para uma exigência de um só lado, por exemplo um mínimo de 1,6 mm sem máximo. Leituras fora da faixa são classificadas automaticamente.",
+    "answers": "Respostas",
+    "answersHelp": "Separe as respostas com vírgulas.",
+    "mustComplete": "Deve ser preenchido",
+    "photoRequired": "Foto obrigatória quando uma deficiência é registrada",
+    "commonWording": "Redações comuns",
+    "commonWordingHelp": "Uma redação por linha. Elas são oferecidas primeiro quando um técnico registra uma deficiência aqui, antes dos textos internos, para que a sua oficina combine como descrever uma falha.",
+    "inputType": {
+      "condition": "Classificação de deficiência",
+      "measurement": "Medição",
+      "text": "Texto livre",
+      "choice": "Escolha"
+    },
+    "inputHint": {
+      "condition": "O técnico classifica o item pela escala de deficiências.",
+      "measurement": "Registra um número com unidade, classificado automaticamente contra uma faixa.",
+      "text": "Registra uma observação escrita, por exemplo uma marca de pneu ou um código de falha.",
+      "choice": "O técnico escolhe uma resposta de uma lista fixa."
+    },
+    "cancel": "Cancelar",
+    "create": "Criar modelo",
+    "saveChanges": "Salvar alterações",
+    "created": "Modelo criado",
+    "updated": "Modelo atualizado",
+    "saveFailed": "Não foi possível salvar o modelo",
+    "severityAttention": "Deficiência leve",
+    "severityFail": "Deficiência grave",
+    "severityDangerous": "Deficiência perigosa",
+    "answersPlaceholder": "Completo, Parcial, Nenhum"
+  },
+  "packages": {
+    "exportTitle": "Exportar “{name}”",
+    "exportDescription": "Baixa a lista inteira como arquivo: grupos, itens, unidades e limites. Qualquer pessoa que use o Torqvoice pode importá-la.",
+    "includeWording": "Incluir suas próprias redações",
+    "includeWordingHelp": "As redações que a sua oficina adicionou aos itens. Úteis para quem receber, mas são texto livre digitado na bancada e podem citar um cliente, um colega ou um acordo local. Desligado, a menos que você as tenha lido.",
+    "attributeTo": "Atribuir a (opcional)",
+    "attributePlaceholder": "ex.: Egeland Auto",
+    "attributeHelp": "Gravado no arquivo para que quem instalar veja de onde ele veio. Nada é retirado da sua conta.",
+    "download": "Baixar",
+    "exported": "Modelo exportado",
+    "exportFailed": "Não foi possível exportar este modelo",
+    "importTitle": "Importar uma lista",
+    "importDescription": "Adiciona uma lista a partir de um arquivo de pacote do Torqvoice. Ela chega como um modelo comum que você pode editar e nunca substitui um que você já tenha.",
+    "chooseFile": "Escolher um arquivo de pacote",
+    "chooseAnother": "Escolher outro arquivo",
+    "unreadable": "Não foi possível ler esse arquivo. Um pacote do Torqvoice é um arquivo .json exportado de um modelo.",
+    "from": "De {author}",
+    "limitsWarning": "Os limites de uma lista compartilhada são os que o autor definiu. Confira-os com as suas regras nacionais antes de emitir qualquer coisa a partir dela.",
+    "import": "Importar",
+    "imported": "{count} modelo(s) importado(s)",
+    "importFailed": "Não foi possível importar este pacote",
+    "cancel": "Cancelar"
+  },
+  "list": {
+    "tabAll": "Todas",
+    "tabInProgress": "Em andamento",
+    "tabCompleted": "Concluídas",
+    "search": "Buscar inspeções…",
+    "new": "Nova inspeção",
+    "empty": "Nenhuma inspeção encontrada.",
+    "vehicle": "Veículo",
+    "template": "Modelo",
+    "progress": "Progresso",
+    "status": "Status",
+    "date": "Data",
+    "statusInProgress": "Em andamento",
+    "statusCompleted": "Concluída"
+  },
+  "new": {
+    "title": "Nova inspeção",
+    "vehicle": "Veículo",
+    "searchVehicles": "Buscar veículos…",
+    "loadingVehicles": "Carregando veículos…",
+    "noVehicles": "Nenhum veículo encontrado.",
+    "template": "Modelo",
+    "selectTemplate": "Escolher modelo",
+    "manageTemplates": "Gerenciar modelos",
+    "cancel": "Cancelar",
+    "create": "Criar inspeção",
+    "created": "Inspeção criada",
+    "createFailed": "Não foi possível criar a inspeção"
+  },
+  "shareDialog": {
+    "title": "Compartilhar inspeção",
+    "description": "Compartilhe com seu cliente uma visualização somente leitura desta inspeção.",
+    "copyLink": "Copiar link",
+    "copied": "Link copiado para a área de transferência",
+    "generated": "Link de compartilhamento criado",
+    "revoked": "Link de compartilhamento revogado"
+  }
+}

+ 1 - 1
messages/pt-BR/portal.json

@@ -26,7 +26,7 @@
     "signInTitle": "Acesso do cliente",
     "signInSubtitle": "Entre para ver suas faturas, orçamentos e histórico de serviço.",
     "emailLabel": "Endereço de e-mail",
-    "emailPlaceholder": "voce@exemplo.com",
+    "emailPlaceholder": "você@exemplo.com",
     "sendLink": "Enviar link de acesso",
     "checkEmail": "Verifique seu e-mail",
     "checkEmailDescription": "Se uma conta existir para esse e-mail, enviamos um link de acesso. O link expira em 15 minutos.",

+ 275 - 275
messages/pt-BR/settings.json

@@ -1,5 +1,5 @@
 {
-  "title": "Configuracoes",
+  "title": "Configurações",
   "subtitle": "Gerencie sua oficina, faturas e preferencias",
   "nav": {
     "categories": {
@@ -28,8 +28,8 @@
         "description": "Membros e funcoes"
       },
       "invoice": {
-        "title": "Faturas e orcamentos",
-        "description": "Configuracoes de fatura, layout e campos personalizados"
+        "title": "Faturas e orçamentos",
+        "description": "Configurações de fatura, layout e campos personalizados"
       },
       "templates": {
         "title": "Modelos",
@@ -37,7 +37,7 @@
       },
       "payment": {
         "title": "Pagamento",
-        "description": "Condicoes de pagamento e contas"
+        "description": "Condições de pagamento e contas"
       },
       "tax": {
         "title": "Imposto",
@@ -73,15 +73,15 @@
       },
       "workshop": {
         "title": "Oficina",
-        "description": "Padroes de tecnico e mao de obra"
+        "description": "Padroes de técnico e mao de obra"
       },
       "laborPresets": {
         "title": "Pacotes de mao de obra",
         "description": "Pacotes de trabalho predefinidos"
       },
       "maintenance": {
-        "title": "Manutencao",
-        "description": "Intervalos de servico previstos"
+        "title": "Manutenção",
+        "description": "Intervalos de serviço previstos"
       },
       "ai": {
         "title": "AI",
@@ -96,20 +96,20 @@
         "description": "Notificações de eventos em tempo real"
       },
       "appearance": {
-        "title": "Aparencia",
-        "description": "Tema e exibicao"
+        "title": "Aparência",
+        "description": "Tema e exibição"
       },
       "data": {
         "title": "Dados",
         "description": "Exportar e importar backup"
       },
       "license": {
-        "title": "Licenca",
-        "description": "Licenca marca branca"
+        "title": "Licença",
+        "description": "Licença marca branca"
       },
       "about": {
         "title": "Sobre",
-        "description": "Versao e informacoes"
+        "description": "Versao e informações"
       },
       "alerts": {
         "title": "Alertas",
@@ -123,12 +123,12 @@
     "pro": "PRO"
   },
   "readOnly": {
-    "banner": "Voce tem acesso somente leitura as configuracoes. Contate um administrador para fazer alteracoes."
+    "banner": "Você tem acesso somente leitura as configurações. Contate um administrador para fazer alterações."
   },
   "featureLocked": {
-    "requiresPro": "Requer uma licenca Pro",
+    "requiresPro": "Requer uma licença Pro",
     "upgradePlan": "Atualizar plano",
-    "activateLicense": "Ativar licenca"
+    "activateLicense": "Ativar licença"
   },
   "company": {
     "logoTitle": "Logo da empresa",
@@ -142,13 +142,13 @@
     "detailsTitle": "Detalhes da empresa",
     "shopName": "Nome da empresa / oficina",
     "shopNamePlaceholder": "Auto Mecanica Silva",
-    "orgNumber": "Numero da organizacao",
+    "orgNumber": "Número da organização",
     "orgNumberPlaceholder": "123 456 789",
     "phone": "Telefone",
     "phonePlaceholder": "(11) 91234-5678",
     "email": "E-mail",
-    "emailPlaceholder": "servico@exemplo.com.br",
-    "address": "Endereco",
+    "emailPlaceholder": "serviço@exemplo.com.br",
+    "address": "Endereço",
     "addressPlaceholder": "Rua Principal 123\nSala 100\n01234-567 Sao Paulo",
     "saveCompany": "Salvar detalhes da empresa",
     "addNewCompany": "Adicionar nova empresa",
@@ -156,8 +156,8 @@
     "companyNamePlaceholder": "Ex. Auto Mecanica Silva",
     "cancel": "Cancelar",
     "createCompany": "Criar empresa",
-    "settingsSaved": "Configuracoes da empresa salvas",
-    "failedSave": "Falha ao salvar configuracoes",
+    "settingsSaved": "Configurações da empresa salvas",
+    "failedSave": "Falha ao salvar configurações",
     "companyCreated": "Empresa criada com sucesso",
     "serviceType": "Tipo de Serviço",
     "serviceTypeDescription": "Selecione o tipo de serviço que sua oficina oferece. Isso altera os rótulos dos campos em todo o aplicativo.",
@@ -168,7 +168,7 @@
   },
   "account": {
     "profileTitle": "Perfil",
-    "profileDescription": "Atualize seu nome de exibicao e endereco de e-mail.",
+    "profileDescription": "Atualize seu nome de exibição e endereço de e-mail.",
     "emailVerified": "Verificado",
     "emailNotVerified": "Não verificado",
     "sendVerificationEmail": "Enviar e-mail de verificação",
@@ -194,63 +194,63 @@
     "passwordsNoMatch": "As senhas nao correspondem",
     "passwordMinLength": "A senha deve ter pelo menos 8 caracteres",
     "failedChangePassword": "Falha ao alterar a senha",
-    "twoFactorTitle": "Autenticacao de dois fatores",
-    "twoFactorEnabled": "A autenticacao de dois fatores esta ativada",
-    "twoFactorDisablePrompt": "Digite sua senha para desativar a autenticacao de dois fatores.",
-    "twoFactorEnablePrompt": "Adicione uma camada extra de seguranca a sua conta ativando a autenticacao de dois fatores com um aplicativo autenticador.",
+    "twoFactorTitle": "Autenticação de dois fatores",
+    "twoFactorEnabled": "A autenticação de dois fatores esta ativada",
+    "twoFactorDisablePrompt": "Digite sua senha para desativar a autenticação de dois fatores.",
+    "twoFactorEnablePrompt": "Adicione uma camada extra de segurança a sua conta ativando a autenticação de dois fatores com um aplicativo autenticador.",
     "password": "Senha",
     "enterPassword": "Digite sua senha",
     "disable2FA": "Desativar 2FA",
     "enable2FA": "Ativar 2FA",
-    "twoFactorDisabled": "Autenticacao de dois fatores desativada",
+    "twoFactorDisabled": "Autenticação de dois fatores desativada",
     "failedEnable2FA": "Falha ao ativar 2FA",
     "failedDisable2FA": "Falha ao desativar 2FA",
     "enterPasswordError": "Por favor, digite sua senha",
-    "setupDialogTitle": "Ativar autenticacao de dois fatores",
-    "setupDialogDescription": "Digite sua senha para comecar a configurar 2FA com um aplicativo autenticador.",
+    "setupDialogTitle": "Ativar autenticação de dois fatores",
+    "setupDialogDescription": "Digite sua senha para começar a configurar 2FA com um aplicativo autenticador.",
     "continue": "Continuar",
-    "scanQRTitle": "Escanear codigo QR",
-    "scanQRDescription": "Escaneie este codigo QR com seu aplicativo autenticador (Google Authenticator, Authy, etc.)",
-    "verifyCodeTitle": "Verificar codigo",
-    "verifyCodeDescription": "Digite o codigo de 6 digitos do seu aplicativo autenticador para verificar se esta configurado corretamente.",
-    "authCode": "Codigo de autenticacao",
+    "scanQRTitle": "Escanear código QR",
+    "scanQRDescription": "Escaneie este código QR com seu aplicativo autenticador (Google Authenticator, Authy, etc.)",
+    "verifyCodeTitle": "Verificar código",
+    "verifyCodeDescription": "Digite o código de 6 dígitos do seu aplicativo autenticador para verificar se esta configurado corretamente.",
+    "authCode": "Código de autenticação",
     "authCodePlaceholder": "000000",
     "back": "Voltar",
     "verify": "Verificar",
-    "enterCodeError": "Por favor, digite o codigo do seu aplicativo autenticador",
-    "invalidCode": "Codigo invalido. Tente novamente.",
-    "verificationFailed": "Verificacao falhou",
-    "backupCodesTitle": "Salvar codigos de backup",
-    "backupCodesDescription": "Salve estes codigos em um local seguro. Voce pode usa-los para entrar se perder acesso ao seu aplicativo autenticador. Cada codigo so pode ser usado uma vez.",
-    "copyCodes": "Copiar codigos",
+    "enterCodeError": "Por favor, digite o código do seu aplicativo autenticador",
+    "invalidCode": "Código inválido. Tente novamente.",
+    "verificationFailed": "Verificação falhou",
+    "backupCodesTitle": "Salvar códigos de backup",
+    "backupCodesDescription": "Salve estes códigos em um local seguro. Você pode usa-los para entrar se perder acesso ao seu aplicativo autenticador. Cada código so pode ser usado uma vez.",
+    "copyCodes": "Copiar códigos",
     "copied": "Copiado",
-    "backupCodesCopied": "Codigos de backup copiados para a area de transferencia",
-    "savedBackupCodes": "Salvei meus codigos de backup",
-    "twoFactorEnabledSuccess": "Autenticacao de dois fatores ativada",
+    "backupCodesCopied": "Códigos de backup copiados para a área de transferência",
+    "savedBackupCodes": "Salvei meus códigos de backup",
+    "twoFactorEnabledSuccess": "Autenticação de dois fatores ativada",
     "dangerZone": "Zona de perigo",
-    "deleteContentTitle": "Excluir conteudo",
-    "deleteContentDescription": "Exclua seletivamente dados da organizacao como veiculos, clientes, orcamentos ou inventario. Sua conta e configuracoes permanecerao intactas.",
-    "deleteContentButton": "Excluir conteudo",
+    "deleteContentTitle": "Excluir conteúdo",
+    "deleteContentDescription": "Exclua seletivamente dados da organização como veículos, clientes, orçamentos ou inventário. Sua conta e configurações permanecerao intactas.",
+    "deleteContentButton": "Excluir conteúdo",
     "deleteAccountTitle": "Excluir conta",
-    "deleteAccountDescription": "Exclua permanentemente sua conta e todos os dados associados, incluindo veiculos, registros de servico, ordens de servico, faturas, clientes, arquivos e pagamentos. Esta acao nao pode ser desfeita.",
+    "deleteAccountDescription": "Exclua permanentemente sua conta e todos os dados associados, incluindo veículos, registros de serviço, ordens de serviço, faturas, clientes, arquivos e pagamentos. Esta ação nao pode ser desfeita.",
     "deleteAccountButton": "Excluir conta",
     "deleteAccountDialogTitle": "Excluir conta",
-    "deleteAccountDialogDescription": "Isso excluira permanentemente sua conta e todos os dados associados. Esta acao e irreversivel. Todos os seus veiculos, registros de servico, ordens de servico, orcamentos, clientes, arquivos e historico de pagamentos serao permanentemente removidos.",
+    "deleteAccountDialogDescription": "Isso excluirá permanentemente sua conta e todos os dados associados. Esta ação e irreversivel. Todos os seus veículos, registros de serviço, ordens de serviço, orçamentos, clientes, arquivos e histórico de pagamentos serão permanentemente removidos.",
     "deleteAccountConfirmPhrase": "delete me",
     "deleteAccountConfirmPrompt": "Digite <bold>delete me</bold> para confirmar",
     "permanentlyDelete": "Excluir permanentemente",
     "deleting": "Excluindo...",
     "failedDeleteAccount": "Falha ao excluir a conta",
-    "deleteContentDialogTitle": "Excluir conteudo",
-    "deleteContentDialogDescription": "Selecione o conteudo que deseja excluir permanentemente. Esta acao nao pode ser desfeita.",
-    "contentVehicles": "Veiculos",
-    "contentVehiclesDescription": "Todos os veiculos e seus registros de servico, notas, lembretes, registros de combustivel e faturas recorrentes",
+    "deleteContentDialogTitle": "Excluir conteúdo",
+    "deleteContentDialogDescription": "Selecione o conteúdo que deseja excluir permanentemente. Esta ação nao pode ser desfeita.",
+    "contentVehicles": "Veículos",
+    "contentVehiclesDescription": "Todos os veículos e seus registros de serviço, notas, lembretes, registros de combustível e faturas recorrentes",
     "contentCustomers": "Clientes",
-    "contentCustomersDescription": "Todos os registros de clientes. Veiculos vinculados a clientes excluidos serao desvinculados.",
-    "contentQuotes": "Orcamentos",
-    "contentQuotesDescription": "Todos os orcamentos e seus itens",
-    "contentInventory": "Inventario",
-    "contentInventoryDescription": "Todas as pecas do inventario e suas imagens",
+    "contentCustomersDescription": "Todos os registros de clientes. Veículos vinculados a clientes excluidos serão desvinculados.",
+    "contentQuotes": "Orçamentos",
+    "contentQuotesDescription": "Todos os orçamentos e seus itens",
+    "contentInventory": "Inventário",
+    "contentInventoryDescription": "Todas as peças do inventário e suas imagens",
     "contentInspections": "Inspeções",
     "contentInspectionsDescription": "Todas as inspeções de veículos e seus resultados",
     "contentTechnicians": "Técnicos",
@@ -266,8 +266,8 @@
     "deleteContentConfirmPhrase": "delete my data",
     "deleteContentConfirmPrompt": "Digite <bold>delete my data</bold> para confirmar",
     "deleteSelected": "Excluir {count} selecionados",
-    "contentDeleted": "O conteudo selecionado foi excluido permanentemente",
-    "failedDeleteContent": "Falha ao excluir o conteudo",
+    "contentDeleted": "O conteúdo selecionado foi excluído permanentemente",
+    "failedDeleteContent": "Falha ao excluir o conteúdo",
     "passkey": {
       "title": "Passkeys",
       "description": "Entre com biometria ou uma chave de segurança em vez de uma senha.",
@@ -305,24 +305,24 @@
   },
   "team": {
     "title": "Equipe",
-    "noOrgDescription": "Crie uma equipe para compartilhar dados com outros usuarios.",
-    "orgDescription": "Gerencie os membros da sua organizacao e suas funcoes.",
-    "createOrg": "Criar organizacao",
-    "createOrgDescription": "Crie uma organizacao para convidar membros da equipe. Voce sera o proprietario.",
-    "orgName": "Nome da organizacao",
+    "noOrgDescription": "Crie uma equipe para compartilhar dados com outros usuários.",
+    "orgDescription": "Gerencie os membros da sua organização e suas funcoes.",
+    "createOrg": "Criar organização",
+    "createOrgDescription": "Crie uma organização para convidar membros da equipe. Você será o proprietário.",
+    "orgName": "Nome da organização",
     "orgNamePlaceholder": "Minha oficina",
     "create": "Criar",
-    "orgCreated": "Organizacao criada",
-    "failedCreateOrg": "Falha ao criar a organizacao",
+    "orgCreated": "Organização criada",
+    "failedCreateOrg": "Falha ao criar a organização",
     "memberCount": "{count} membro",
     "memberCountPlural": "{count} membros",
     "admin": "Administrador",
     "member": "Membro",
     "inviteByEmail": "Convidar por e-mail",
-    "inviteEmailPlaceholder": "usuario@exemplo.com.br",
+    "inviteEmailPlaceholder": "usuário@exemplo.com.br",
     "invite": "Convidar",
     "invited": "Membro convidado com sucesso.",
-    "userNotFoundTitle": "Usuario nao encontrado",
+    "userNotFoundTitle": "Usuário nao encontrado",
     "userNotFoundDescription": "Nenhuma conta encontrada para \"{email}\". Enviar um e-mail de convite para se registrar e entrar na equipe?",
     "sendInvitation": "Enviar convite",
     "invitationSentTitle": "Convite enviado",
@@ -334,50 +334,50 @@
     "expires": "Expira em {date}",
     "pending": "Pendente",
     "copyInviteLink": "Copiar link de convite",
-    "inviteLinkCopied": "Link de convite copiado para a area de transferencia",
+    "inviteLinkCopied": "Link de convite copiado para a área de transferência",
     "cancelInvitation": "Cancelar convite",
     "cancelInvitationDescription": "Cancelar o convite para {email}? Eles nao poderao mais usar este link.",
     "invitationCancelled": "Convite cancelado",
     "failedCancelInvitation": "Falha ao cancelar o convite",
     "customRoles": "Funcoes personalizadas",
-    "newRole": "Nova funcao",
-    "roleName": "Nome da funcao",
-    "roleNamePlaceholder": "Ex. Tecnico",
-    "fullAdminAccess": "Acesso total de administrador (ignora todas as verificacoes de permissao)",
+    "newRole": "Nova função",
+    "roleName": "Nome da função",
+    "roleNamePlaceholder": "Ex. Técnico",
+    "fullAdminAccess": "Acesso total de administrador (ignora todas as verificacoes de permissão)",
     "permissions": "Permissoes",
-    "createRole": "Criar funcao",
-    "updateRole": "Atualizar funcao",
-    "roleCreated": "Funcao criada",
-    "roleUpdated": "Funcao atualizada",
-    "failedSaveRole": "Falha ao salvar a funcao",
-    "deleteRole": "Excluir funcao",
-    "deleteRoleDescription": "Excluir a funcao \"{name}\"? Membros com esta funcao perderao suas permissoes personalizadas.",
-    "roleDeleted": "Funcao excluida",
-    "failedDeleteRole": "Falha ao excluir a funcao",
+    "createRole": "Criar função",
+    "updateRole": "Atualizar função",
+    "roleCreated": "Função criada",
+    "roleUpdated": "Função atualizada",
+    "failedSaveRole": "Falha ao salvar a função",
+    "deleteRole": "Excluir função",
+    "deleteRoleDescription": "Excluir a função \"{name}\"? Membros com esta função perderão suas permissoes personalizadas.",
+    "roleDeleted": "Função excluída",
+    "failedDeleteRole": "Falha ao excluir a função",
     "fullAccess": "Acesso total",
-    "permissionCount": "{count} permissao",
+    "permissionCount": "{count} permissão",
     "permissionCountPlural": "{count} permissoes",
-    "noCustomRoles": "Nenhuma funcao personalizada ainda. Crie uma para definir permissoes granulares para membros da equipe.",
+    "noCustomRoles": "Nenhuma função personalizada ainda. Crie uma para definir permissoes granulares para membros da equipe.",
     "presetAll": "Todos",
     "presetViewOnly": "Somente leitura",
     "presetWriteAll": "Leitura e escrita",
     "presetNone": "Nenhum",
     "builtInRoles": "Funcoes integradas",
-    "ownerLabel": "Proprietario",
-    "ownerDescription": "Acesso total. Pode gerenciar membros, alterar funcoes e excluir a organizacao.",
+    "ownerLabel": "Proprietário",
+    "ownerDescription": "Acesso total. Pode gerenciar membros, alterar funcoes e excluir a organização.",
     "adminLabel": "Administrador",
     "adminDescription": "Pode convidar/remover membros e gerenciar dados compartilhados.",
     "memberLabel": "Membro",
-    "memberDescription": "Permissoes determinadas pela funcao personalizada atribuida. Sem funcao personalizada significa acesso somente leitura.",
-    "roleAssigned": "Funcao atualizada",
-    "failedAssignRole": "Falha ao atualizar a funcao",
+    "memberDescription": "Permissoes determinadas pela função personalizada atribuída. Sem função personalizada significa acesso somente leitura.",
+    "roleAssigned": "Função atualizada",
+    "failedAssignRole": "Falha ao atualizar a função",
     "removeMemberTitle": "Remover membro",
     "removeMemberDescription": "Remover {name} da equipe? Ele perdera acesso aos dados compartilhados.",
     "removeButton": "Remover",
     "memberRemoved": "Membro removido",
     "failedRemoveMember": "Falha ao remover o membro",
-    "memberRoleUpdated": "Funcao do membro atualizada",
-    "failedUpdateRole": "Falha ao atualizar a funcao",
+    "memberRoleUpdated": "Função do membro atualizada",
+    "failedUpdateRole": "Falha ao atualizar a função",
     "removeMember": "Remove member",
     "cancelInvite": "Cancel invitation",
     "editRole": "Edit role"
@@ -390,30 +390,30 @@
       "layout": "Layout",
       "customFields": "Campos personalizados"
     },
-    "invoiceNumberFormat": "Formato do numero da fatura",
-    "invoiceNumberFormatHint": "Use <code>{year}</code> para o ano atual. Visualizacao: <bold>{preview}</bold>",
-    "quoteNumberFormat": "Formato do numero do orçamento",
-    "quoteNumberFormatHint": "Use <code>{year}</code> para o ano atual. Visualizacao: <bold>{preview}</bold>",
+    "invoiceNumberFormat": "Formato do número da fatura",
+    "invoiceNumberFormatHint": "Use <code>{year}</code> para o ano atual. Visualização: <bold>{preview}</bold>",
+    "quoteNumberFormat": "Formato do número do orçamento",
+    "quoteNumberFormatHint": "Use <code>{year}</code> para o ano atual. Visualização: <bold>{preview}</bold>",
     "quoteValidDays": "Validade do orçamento (dias)",
     "quoteValidDaysHint": "Novos orçamentos recebem uma data de validade com esse número de dias. 0 desativa.",
     "sectionInvoices": "Faturas",
     "sectionQuotes": "Orçamentos",
-    "nextInvoiceNumber": "Proximo numero da fatura",
+    "nextInvoiceNumber": "Próximo número da fatura",
     "nextInvoiceNumberPlaceholder": "Ex. 94",
-    "nextInvoiceNumberHint": "A proxima fatura usara este numero (ex. {example})",
+    "nextInvoiceNumberHint": "A próxima fatura usara este número (ex. {example})",
     "dueDays": "Dias de vencimento",
-    "dueDaysHint": "Numero de dias ate o vencimento da fatura",
+    "dueDaysHint": "Número de dias ate o vencimento da fatura",
     "visibilityTitle": "Visibilidade na fatura",
     "companyLogo": "Logo da empresa",
-    "companyLogoHint": "Exibir logo da empresa no cabecalho da fatura",
+    "companyLogoHint": "Exibir logo da empresa no cabeçalho da fatura",
     "companyName": "Nome da empresa",
-    "companyNameHint": "Exibir nome da empresa no cabecalho da fatura",
-    "bankAccount": "Conta bancaria",
-    "bankAccountHint": "Mostrar conta bancaria / IBAN na fatura",
-    "organizationNumber": "Numero da organizacao",
-    "organizationNumberHint": "Mostrar numero da organizacao no cabecalho",
-    "customFooter": "Rodape personalizado da fatura",
-    "footerPlaceholder": "Obrigado pela preferencia!",
+    "companyNameHint": "Exibir nome da empresa no cabeçalho da fatura",
+    "bankAccount": "Conta bancária",
+    "bankAccountHint": "Mostrar conta bancária / IBAN na fatura",
+    "organizationNumber": "Número da organização",
+    "organizationNumberHint": "Mostrar número da organização no cabeçalho",
+    "customFooter": "Rodapé personalizado da fatura",
+    "footerPlaceholder": "Obrigado pela preferência!",
     "footerHint": "Este texto aparece no final de cada fatura",
     "partsMarkupTitle": "Margem de Peças",
     "partsMarkupDescription": "Margem aplicada ao custo do fornecedor ao adicionar peças. Apenas interno — o cliente nunca vê o custo ou a margem, somente o preço final.",
@@ -421,8 +421,8 @@
     "defaultMarkupPercentHint": "Novas peças adicionadas manualmente são pré-preenchidas com esta margem. Deixe em 0 para inserir preços manualmente.",
     "markupAppliesToInventory": "Aplicar margem a peças do estoque",
     "markupAppliesToInventoryHint": "Quando ativado, peças selecionadas do estoque também usam custo + margem padrão (substitui o preço de venda do estoque).",
-    "saveInvoice": "Salvar configuracoes da fatura",
-    "saved": "Configuracoes da fatura salvas",
+    "saveInvoice": "Salvar configurações da fatura",
+    "saved": "Configurações da fatura salvas",
     "layoutDescription": "Personalize a ordem e visibilidade das seções em suas faturas e orçamentos.",
     "layoutSaved": "Layout salvo com sucesso",
     "preview": "Pré-visualização",
@@ -491,13 +491,13 @@
   },
   "templates": {
     "title": "Modelos",
-    "invoiceDescription": "Personalize a aparencia das suas faturas e orcamentos PDF.",
-    "inspectionsDescription": "Gerencie listas de verificacao de inspecao e modelos multiponto.",
+    "invoiceDescription": "Personalize a aparência das suas faturas e orçamentos PDF.",
+    "inspectionsDescription": "Gerencie listas de verificação de inspeção e modelos multiponto.",
     "smsDescription": "Personalize as mensagens SMS enviadas aos clientes.",
     "tabs": {
       "invoice": "Fatura",
-      "quotation": "Orcamento",
-      "inspections": "Inspecoes",
+      "quotation": "Orçamento",
+      "inspections": "Inspeções",
       "sms": "SMS",
       "layout": "Layout"
     },
@@ -505,21 +505,21 @@
     "primaryColor": "Cor primaria",
     "fontAndLayout": "Fonte e layout",
     "fontFamily": "Familia de fontes",
-    "helveticaDefault": "Helvetica (Padrao)",
+    "helveticaDefault": "Helvetica (Padrão)",
     "timesRoman": "Times Roman",
     "courier": "Courier",
-    "headerStyle": "Estilo do cabecalho",
+    "headerStyle": "Estilo do cabeçalho",
     "logoSize": "Tamanho do logotipo",
-    "standard": "Padrao",
+    "standard": "Padrão",
     "compact": "Compacto",
     "modern": "Moderno",
-    "preview": "Visualizacao de {name}",
+    "preview": "Visualização de {name}",
     "billTo": "Faturar para",
     "preparedFor": "Preparado para",
-    "vehicle": "Veiculo",
-    "part": "Peca",
+    "vehicle": "Veículo",
+    "part": "Peça",
     "qty": "Qtd.",
-    "price": "Preco",
+    "price": "Preço",
     "total": "Total",
     "labor": "Mao de obra",
     "hours": "Horas",
@@ -533,31 +533,31 @@
     "previewAdditionalInfo": "Informações adicionais",
     "previewThankYou": "Obrigado pela preferência",
     "saveInvoiceTemplate": "Salvar modelo de fatura",
-    "saveQuotationTemplate": "Salvar modelo de orcamento",
-    "invoiceTemplateSaved": "Configuracoes do modelo de fatura salvas",
-    "quotationTemplateSaved": "Configuracoes do modelo de orcamento salvas",
-    "failedSave": "Falha ao salvar configuracoes",
+    "saveQuotationTemplate": "Salvar modelo de orçamento",
+    "invoiceTemplateSaved": "Configurações do modelo de fatura salvas",
+    "quotationTemplateSaved": "Configurações do modelo de orçamento salvas",
+    "failedSave": "Falha ao salvar configurações",
     "smsTemplates": "Modelos de mensagens SMS",
     "smsTemplatesDescription": "Personalize as mensagens de texto enviadas aos clientes. Use variaveis como <code>'{companyName}'</code> para inserir valores dinamicos.",
     "reset": "Redefinir",
     "invoiceReady": "Fatura pronta",
     "invoiceReadyDescription": "Enviada ao compartilhar uma fatura com um cliente.",
-    "quoteReady": "Orcamento pronto",
-    "quoteReadyDescription": "Enviada ao compartilhar um orcamento com um cliente.",
-    "inspectionReady": "Inspecao pronta",
-    "inspectionReadyDescription": "Enviada ao compartilhar um relatorio de inspecao com um cliente.",
+    "quoteReady": "Orçamento pronto",
+    "quoteReadyDescription": "Enviada ao compartilhar um orçamento com um cliente.",
+    "inspectionReady": "Inspeção pronta",
+    "inspectionReadyDescription": "Enviada ao compartilhar um relatório de inspeção com um cliente.",
     "statusInProgress": "Status: Em andamento",
-    "statusInProgressDescription": "Enviada quando o status de uma ordem de servico muda para Em andamento.",
-    "statusWaitingParts": "Status: Aguardando pecas",
-    "statusWaitingPartsDescription": "Enviada quando o status muda para Aguardando pecas.",
+    "statusInProgressDescription": "Enviada quando o status de uma ordem de serviço muda para Em andamento.",
+    "statusWaitingParts": "Status: Aguardando peças",
+    "statusWaitingPartsDescription": "Enviada quando o status muda para Aguardando peças.",
     "statusReady": "Status: Pronto para retirada",
     "statusReadyDescription": "Enviada quando o status muda para Pronto.",
-    "statusCompleted": "Status: Concluido",
-    "statusCompletedDescription": "Enviada quando o status muda para Concluido.",
+    "statusCompleted": "Status: Concluído",
+    "statusCompletedDescription": "Enviada quando o status muda para Concluído.",
     "paymentReceived": "Pagamento recebido",
     "paymentReceivedDescription": "Enviada quando um pagamento e registrado para uma fatura.",
     "saveSmsTemplates": "Salvar modelos SMS",
-    "smsTemplateSaved": "Configuracoes dos modelos SMS salvas",
+    "smsTemplateSaved": "Configurações dos modelos SMS salvas",
     "smsDefaults": {
       "invoiceReady": "Sua fatura está pronta. Veja aqui: {share_link}",
       "quoteReady": "Seu orçamento está pronto para revisão. Veja aqui: {share_link}",
@@ -574,30 +574,30 @@
       "emerald": "Esmeralda",
       "red": "Vermelho",
       "purple": "Roxo",
-      "slate": "Ardosia",
+      "slate": "Ardósia",
       "rose": "Rosa",
       "indigo": "Indigo"
     },
     "layoutTitle": "Layout da fatura",
-    "layoutDescription": "Personalize a ordem e visibilidade das secoes nas suas faturas e orcamentos.",
+    "layoutDescription": "Personalize a ordem e visibilidade das seções nas suas faturas e orçamentos.",
     "layoutSaved": "Layout salvo com sucesso",
     "layoutDocInvoice": "Fatura",
-    "layoutDocQuote": "Orcamento"
+    "layoutDocQuote": "Orçamento"
   },
   "payment": {
     "readMore": "Saiba mais",
     "detailsTitle": "Detalhes de pagamento",
-    "detailsDescription": "Conta bancaria e condicoes de pagamento nas faturas.",
-    "bankAccount": "Conta bancaria (IBAN)",
+    "detailsDescription": "Conta bancária e condições de pagamento nas faturas.",
+    "bankAccount": "Conta bancária (IBAN)",
     "bankAccountPlaceholder": "NO93 8601 1117 947",
-    "bankAccountHint": "Seu numero de conta bancaria ou IBAN para receber pagamentos",
-    "paymentTerms": "Condicoes de pagamento",
-    "paymentTermsPlaceholder": "Liquido 14 dias",
-    "paymentTermsHint": "Texto das condicoes de pagamento nas faturas (ex. \"Liquido 14 dias\", \"No recebimento\")",
+    "bankAccountHint": "Seu número de conta bancária ou IBAN para receber pagamentos",
+    "paymentTerms": "Condições de pagamento",
+    "paymentTermsPlaceholder": "Líquido 14 dias",
+    "paymentTermsHint": "Texto das condições de pagamento nas faturas (ex. \"Líquido 14 dias\", \"No recebimento\")",
     "onlinePayments": "Pagamentos online",
     "onlinePaymentsDescription": "Permita que os clientes paguem faturas online atraves de links de fatura compartilhados.",
     "stripe": "Stripe",
-    "stripeDescription": "Aceitar pagamentos com cartao de credito/debito",
+    "stripeDescription": "Aceitar pagamentos com cartão de credito/debito",
     "secretKey": "Chave secreta",
     "publishableKey": "Chave publica",
     "webhookSecret": "Segredo do webhook",
@@ -608,27 +608,27 @@
     "clientId": "ID do cliente",
     "clientSecret": "Segredo do cliente",
     "subscriptionKey": "Chave de assinatura",
-    "merchantSerialNumber": "Numero de serie do comerciante",
+    "merchantSerialNumber": "Número de serie do comerciante",
     "testMode": "Modo de teste",
     "callbackUrl": "URL de callback",
-    "termsOfSale": "Condicoes de venda (Salgsvilkar)",
-    "termsOfSaleDescription": "Exigido pelo Vipps. Os clientes podem visualizar na pagina da fatura.",
-    "externalTermsUrl": "URL de condicoes externas (opcional)",
-    "externalTermsUrlHint": "Link para uma pagina externa com suas condicoes. Se definido, a fatura ira direcionar aqui.",
-    "publicTermsUrl": "URL publica das condicoes",
+    "termsOfSale": "Condições de venda (Salgsvilkår)",
+    "termsOfSaleDescription": "Exigido pelo Vipps. Os clientes podem visualizar na página da fatura.",
+    "externalTermsUrl": "URL de condições externas (opcional)",
+    "externalTermsUrlHint": "Link para uma página externa com suas condições. Se definido, a fatura ira direcionar aqui.",
+    "publicTermsUrl": "URL publica das condições",
     "publicTermsUrlHint": "Compartilhe esta URL com o Vipps ou vincule-a ao seu site.",
     "paypal": "PayPal",
     "paypalDescription": "Aceitar pagamentos PayPal",
     "sandboxMode": "Modo sandbox",
     "paypalWebhookHint": "Adicione esta URL no Painel de Desenvolvedor do PayPal em Webhooks. Inscreva-se em <code>PAYMENT.CAPTURE.COMPLETED</code>.",
-    "savePayment": "Salvar configuracoes de pagamento",
-    "saved": "Configuracoes de pagamento salvas"
+    "savePayment": "Salvar configurações de pagamento",
+    "saved": "Configurações de pagamento salvas"
   },
   "tax": {
     "title": "Imposto",
-    "description": "Configure a taxa de imposto aplicada a faturas, orcamentos e ordens de servico.",
+    "description": "Configure a taxa de imposto aplicada a faturas, orçamentos e ordens de serviço.",
     "modeLabel": "Modo de imposto",
-    "modeHint": "Escolha se os precos das linhas sao inseridos com imposto ja incluido (inclusivo) ou antes do imposto (exclusivo). Os registros existentes mantem o modo com o qual foram criados.",
+    "modeHint": "Escolha se os preços das linhas sao inseridos com imposto ja incluído (inclusivo) ou antes do imposto (exclusivo). Os registros existentes mantem o modo com o qual foram criados.",
     "modeExclusive": "Exclusivo",
     "modeInclusive": "Inclusivo",
     "modeExclusiveExample": "Exemplo: 100 + {rate}% de imposto = 100 + imposto por cima.",
@@ -657,26 +657,26 @@
   },
   "currency": {
     "title": "Moeda e impostos",
-    "description": "Moeda padrao e taxa de imposto para faturas e registros de servico.",
+    "description": "Moeda padrão e taxa de imposto para faturas e registros de serviço.",
     "currencyLabel": "Moeda",
     "searchCurrency": "Pesquisar moeda...",
     "noCurrencyFound": "Nenhuma moeda encontrada.",
-    "previewLabel": "Visualizacao: {value}",
+    "previewLabel": "Visualização: {value}",
     "enableTax": "Ativar impostos",
-    "enableTaxHint": "Mostrar calculo de impostos em faturas e registros de servico",
-    "defaultTaxRate": "Taxa de imposto padrao (%)",
-    "defaultTaxRateHint": "Preenchido automaticamente ao criar novos registros de servico",
+    "enableTaxHint": "Mostrar calculo de impostos em faturas e registros de serviço",
+    "defaultTaxRate": "Taxa de imposto padrão (%)",
+    "defaultTaxRateHint": "Preenchido automaticamente ao criar novos registros de serviço",
     "applyTaxToExistingLabel": "Aplicar imposto aos registros existentes",
-    "applyTaxToExistingHint": "Aplica a taxa de imposto padrao as ordens de servico e orcamentos existentes sem taxa definida. Elegiveis: {serviceRecords} ordens de servico, {quotes} orcamentos.",
+    "applyTaxToExistingHint": "Aplica a taxa de imposto padrão as ordens de serviço e orçamentos existentes sem taxa definida. Elegíveis: {serviceRecords} ordens de serviço, {quotes} orçamentos.",
     "applyTaxToExistingButton": "Aplicar imposto aos registros existentes",
     "applyTaxConfirmTitle": "Aplicar imposto aos registros existentes?",
-    "applyTaxConfirmDescription": "Isso definira a taxa de imposto em {rate}% em {serviceRecords} ordens de servico e {quotes} orcamentos que atualmente nao tem taxa, e recalculara seus totais. Registros com uma taxa personalizada nao serao alterados. Esta acao nao pode ser desfeita.",
+    "applyTaxConfirmDescription": "Isso definirá a taxa de imposto em {rate}% em {serviceRecords} ordens de serviço e {quotes} orçamentos que atualmente nao tem taxa, e recalculara seus totais. Registros com uma taxa personalizada nao serão alterados. Esta ação nao pode ser desfeita.",
     "applyTaxConfirmLabel": "Aplicar imposto",
-    "applyTaxSuccess": "Atualizadas {serviceRecords} ordens de servico e {quotes} orcamentos.",
+    "applyTaxSuccess": "Atualizadas {serviceRecords} ordens de serviço e {quotes} orçamentos.",
     "applyTaxFailed": "Falha ao aplicar imposto aos registros existentes",
-    "applyTaxNoRecords": "Nenhum registro precisa da taxa de imposto padrao.",
-    "saveSettings": "Salvar configuracoes",
-    "saved": "Configuracoes salvas",
+    "applyTaxNoRecords": "Nenhum registro precisa da taxa de imposto padrão.",
+    "saveSettings": "Salvar configurações",
+    "saved": "Configurações salvas",
     "currencies": {
       "USD": "Dolar americano",
       "EUR": "Euro",
@@ -736,9 +736,9 @@
     },
     "formatTitle": "Formato da moeda",
     "formatSymbolName": "Simbolo",
-    "formatCodeName": "Codigo",
+    "formatCodeName": "Código",
     "formatSymbolLabel": "Simbolo: {value}",
-    "formatCodeLabel": "Codigo: {value}"
+    "formatCodeLabel": "Código: {value}"
   },
   "localization": {
     "languageTitle": "Idioma",
@@ -767,7 +767,7 @@
     "label": "Rótulo",
     "labelPlaceholder": "ex. Expiração da garantia",
     "fieldName": "Nome do campo (ID)",
-    "fieldNamePlaceholder": "expiracao_garantia",
+    "fieldNamePlaceholder": "expiração_garantia",
     "fieldNameHint": "Apenas letras minúsculas, números e underscores.",
     "fieldType": "Tipo de campo",
     "entityType": "Tipo de entidade",
@@ -797,8 +797,8 @@
   },
   "workshop": {
     "title": "Padroes da oficina",
-    "description": "Valores padrao ao criar novos registros de servico.",
-    "defaultTechnician": "Nome do tecnico padrao",
+    "description": "Valores padrão ao criar novos registros de serviço.",
+    "defaultTechnician": "Nome do técnico padrão",
     "technicianPlaceholder": "Buscar técnico...",
     "failedCreateTech": "Falha ao criar técnico",
     "creating": "Criando...",
@@ -806,7 +806,7 @@
     "newTechPlaceholder": "Nome do técnico",
     "addTech": "Adicionar",
     "addNewTech": "+ Adicionar novo",
-    "defaultLaborRate": "Taxa de mao de obra padrao (por hora)",
+    "defaultLaborRate": "Taxa de mao de obra padrão (por hora)",
     "laborRatePlaceholder": "75,00",
     "unitsTitle": "Unidades",
     "unitsDescription": "Escolha entre metrico (km, litros) e imperial (milhas, galoes).",
@@ -819,8 +819,8 @@
     "liters": "litros (L)",
     "miles": "milhas (mi)",
     "gallons": "galoes (gal)",
-    "saveWorkshop": "Salvar configuracoes da oficina",
-    "saved": "Configuracoes da oficina salvas",
+    "saveWorkshop": "Salvar configurações da oficina",
+    "saved": "Configurações da oficina salvas",
     "assignDialogTitle": "Atribuir às ordens de serviço existentes?",
     "assignDialogDescription": "Deseja atribuir {name} a todas as ordens de serviço que não têm um técnico atribuído?",
     "assignDialogConfirm": "Sim, atribuir",
@@ -828,52 +828,52 @@
     "assignedUnassigned": "{count, plural, =0 {Nenhuma ordem sem técnico encontrada} one {1 ordem atribuída} other {# ordens atribuídas}}",
     "assignFailed": "Falha ao atribuir ordens de serviço",
     "noTechnician": "Nenhum",
-    "weekStartDay": "Inicio da semana",
-    "workDayStart": "Inicio do expediente",
+    "weekStartDay": "Início da semana",
+    "workDayStart": "Início do expediente",
     "workDayEnd": "Fim do expediente",
     "weekDays": {
       "0": "Domingo",
       "1": "Segunda-feira",
-      "2": "Terca-feira",
+      "2": "Terça-feira",
       "3": "Quarta-feira",
       "4": "Quinta-feira",
       "5": "Sexta-feira",
-      "6": "Sabado"
+      "6": "Sábado"
     }
   },
   "maintenance": {
-    "title": "Manutencao preditiva",
-    "description": "Preveja a quilometragem atual de cada veiculo a partir do historico de servico e mostre veiculos que precisam ou estao proximos do proximo servico.",
-    "enablePredicted": "Ativar manutencao preditiva",
-    "enablePredictedHint": "Mostrar previsoes nos detalhes do veiculo e painel",
-    "serviceInterval": "Intervalo de servico ({unit})",
-    "serviceIntervalHint": "Distancia entre servicos recomendados",
+    "title": "Manutenção preditiva",
+    "description": "Preveja a quilometragem atual de cada veículo a partir do histórico de serviço e mostre veículos que precisam ou estão próximos do próximo serviço.",
+    "enablePredicted": "Ativar manutenção preditiva",
+    "enablePredictedHint": "Mostrar previsoes nos detalhes do veículo e painel",
+    "serviceInterval": "Intervalo de serviço ({unit})",
+    "serviceIntervalHint": "Distancia entre serviços recomendados",
     "approachingThreshold": "Limite de proximidade ({unit})",
-    "approachingThresholdHint": "Mostrar aviso quando estiver dentro desta distancia do proximo servico",
-    "saveMaintenance": "Salvar configuracoes de manutencao",
-    "saved": "Configuracoes de manutencao salvas"
+    "approachingThresholdHint": "Mostrar aviso quando estiver dentro desta distancia do próximo serviço",
+    "saveMaintenance": "Salvar configurações de manutenção",
+    "saved": "Configurações de manutenção salvas"
   },
   "appearance": {
-    "title": "Aparencia",
-    "description": "Personalize a aparencia do Torqvoice.",
+    "title": "Aparência",
+    "description": "Personalize a aparência do Torqvoice.",
     "darkMode": "Modo escuro",
     "darkModeHint": "Alternar entre temas claro e escuro",
     "dateTimeTitle": "Data e hora",
-    "dateTimeDescription": "Configure como datas e horarios sao exibidos no aplicativo.",
+    "dateTimeDescription": "Configure como datas e horários sao exibidos no aplicativo.",
     "dateFormat": "Formato de data",
     "timeFormat": "Formato de hora",
     "time12h": "12 horas (2:30 PM)",
     "time24h": "24 horas (14:30)",
-    "timezone": "Fuso horario",
+    "timezone": "Fuso horário",
     "timezoneAuto": "Detectar automaticamente (navegador)",
     "searchTimezone": "Pesquisar fuso horário...",
     "noTimezoneFound": "Nenhum fuso horário encontrado.",
-    "timezoneHint": "Com deteccao automatica, cada usuario ve as datas no fuso horario do navegador.",
-    "previewLabel": "Visualizacao",
+    "timezoneHint": "Com deteccao automatica, cada usuário ve as datas no fuso horário do navegador.",
+    "previewLabel": "Visualização",
     "dateLabel": "Data",
     "dateTimeLabel": "Data e hora",
-    "saveSettings": "Salvar configuracoes",
-    "saved": "Configuracoes salvas"
+    "saveSettings": "Salvar configurações",
+    "saved": "Configurações salvas"
   },
   "data": {
     "title": "Gerenciamento de dados",
@@ -883,16 +883,16 @@
     "includeInBackup": "Incluir no backup:",
     "selectAll": "Selecionar tudo",
     "deselectAll": "Desmarcar tudo",
-    "optSettings": "Configuracoes",
+    "optSettings": "Configurações",
     "optSettingsDesc": "Info da oficina, config de faturas, provedores de pagamento",
     "optCustomers": "Clientes",
-    "optCustomersDesc": "Registros de clientes e informacoes de contato",
-    "optVehicles": "Veiculos e registros de servico",
-    "optVehiclesDesc": "Veiculos, notas, registros de combustivel, lembretes, registros de servico",
-    "optQuotes": "Orcamentos",
-    "optQuotesDesc": "Orcamentos com pecas e mao de obra",
-    "optInventory": "Inventario",
-    "optInventoryDesc": "Pecas do inventario e niveis de estoque",
+    "optCustomersDesc": "Registros de clientes e informações de contato",
+    "optVehicles": "Veículos e registros de serviço",
+    "optVehiclesDesc": "Veículos, notas, registros de combustível, lembretes, registros de serviço",
+    "optQuotes": "Orçamentos",
+    "optQuotesDesc": "Orçamentos com peças e mao de obra",
+    "optInventory": "Inventário",
+    "optInventoryDesc": "Peças do inventário e niveis de estoque",
     "optTechnicians": "Técnicos",
     "optTechniciansDesc": "Perfis de técnicos e atribuições do quadro de trabalho",
     "optInspections": "Inspeções",
@@ -906,37 +906,37 @@
     "optNotifications": "Notificações",
     "optNotificationsDesc": "Notificações do sistema",
     "optCustomFields": "Campos personalizados",
-    "optCustomFieldsDesc": "Definicoes e valores de campos personalizados",
+    "optCustomFieldsDesc": "Definições e valores de campos personalizados",
     "optFiles": "Arquivos enviados",
-    "optFilesDesc": "Imagens, anexos de servico, logo",
+    "optFilesDesc": "Imagens, anexos de serviço, logo",
     "filesWarning": "Inclui arquivos enviados — o backup pode ser maior",
     "downloadBackup": "Baixar backup",
-    "exportComplete": "Exportacao concluida",
+    "exportComplete": "Exportacao concluída",
     "exportCompleteMessage": "Seu arquivo de backup foi baixado.",
     "exportFailed": "Exportacao falhou",
-    "exportFailedMessage": "Nao foi possivel exportar seus dados. Tente novamente.",
+    "exportFailedMessage": "Nao foi possível exportar seus dados. Tente novamente.",
     "importTitle": "Importar dados",
-    "importWarning": "A importacao ira <bold>substituir todos os dados existentes</bold> pelo conteudo do arquivo de backup. Esta acao nao pode ser desfeita.",
+    "importWarning": "A importação ira <bold>substituir todos os dados existentes</bold> pelo conteúdo do arquivo de backup. Esta ação nao pode ser desfeita.",
     "importFileHint": "Suporta arquivos de backup .zip (v2) e .json (v1).",
     "uploadRestore": "Enviar e restaurar",
-    "importComplete": "Importacao concluida",
+    "importComplete": "Importação concluída",
     "importCompleteMessage": "Todos os dados foram restaurados do arquivo de backup.",
-    "importFailed": "Importacao falhou",
-    "importFromOther": "Importar de outros servicos",
-    "importFromOtherDescription": "Migre seus dados de outra plataforma. Selecione um servico para comecar.",
+    "importFailed": "Importação falhou",
+    "importFromOther": "Importar de outros serviços",
+    "importFromOtherDescription": "Migre seus dados de outra plataforma. Selecione um serviço para começar.",
     "lubelogTitle": "Importar do LubeLog",
     "lubelogExportSteps": "Como exportar seus dados do LubeLog:",
     "lubelogStep1": "Abra sua instancia do LubeLog",
     "lubelogStep2": "Va para <bold>Settings</bold>",
     "lubelogStep3": "Clique em <bold>Make Backup</bold> para baixar um arquivo zip",
     "lubelogStep4": "Envie o arquivo zip baixado abaixo",
-    "lubelogAddNote": "Isto ira <bold>adicionar dados junto</bold> aos seus registros existentes — nada sera excluido.",
-    "lubelogImported": "O que sera importado:",
-    "lubelogVehicles": "Veiculos",
-    "lubelogServiceRecords": "Registros de servico com anexos",
+    "lubelogAddNote": "Isto ira <bold>adicionar dados junto</bold> aos seus registros existentes — nada será excluído.",
+    "lubelogImported": "O que será importado:",
+    "lubelogVehicles": "Veículos",
+    "lubelogServiceRecords": "Registros de serviço com anexos",
     "lubelogNotes": "Notas",
     "lubelogFileHint": "Arquivos ate 200MB suportados.",
-    "lubelogImportComplete": "Importacao do LubeLog concluida",
+    "lubelogImportComplete": "Importação do LubeLog concluída",
     "cancel": "Cancelar",
     "import": "Importar",
     "invoiceNinjaTitle": "Importar do Invoice Ninja",
@@ -945,15 +945,15 @@
     "invoiceNinjaStep2": "Va para <bold>Settings > Import | Export</bold>",
     "invoiceNinjaStep3": "Clique em <bold>Export</bold> para baixar um arquivo zip",
     "invoiceNinjaStep4": "Envie o arquivo zip baixado abaixo",
-    "invoiceNinjaAddNote": "Isto ira <bold>adicionar dados junto</bold> aos seus registros existentes — nada sera excluido.",
-    "invoiceNinjaImported": "O que sera importado:",
+    "invoiceNinjaAddNote": "Isto ira <bold>adicionar dados junto</bold> aos seus registros existentes — nada será excluído.",
+    "invoiceNinjaImported": "O que será importado:",
     "invoiceNinjaCustomers": "Clientes",
-    "invoiceNinjaProducts": "Produtos (inventario)",
-    "invoiceNinjaInvoices": "Faturas como registros de servico (pecas e mao de obra)",
+    "invoiceNinjaProducts": "Produtos (inventário)",
+    "invoiceNinjaInvoices": "Faturas como registros de serviço (peças e mao de obra)",
     "invoiceNinjaDocuments": "Documentos e anexos",
     "invoiceNinjaPayments": "Pagamentos",
     "invoiceNinjaFileHint": "Envie um arquivo de exportacao zip do Invoice Ninja. Arquivos ate 200MB suportados.",
-    "invoiceNinjaImportComplete": "Importacao do Invoice Ninja concluida",
+    "invoiceNinjaImportComplete": "Importação do Invoice Ninja concluída",
     "backupStatus": {
       "title": "Proteção de dados",
       "description": "Levamos seus dados a sério. Todos os dados da sua organização, incluindo clientes, veículos, ordens de serviço, faturas e arquivos enviados, passam por backup automático a cada hora. Os backups são protegidos com criptografia AES-256 antes de sair da nossa infraestrutura e armazenados em um local separado e seguro, de modo que nenhuma parte externa pode lê-los.",
@@ -963,42 +963,42 @@
     }
   },
   "license": {
-    "title": "Licenca marca branca",
-    "description": "Ative uma licenca marca branca para remover a marca Torqvoice das faturas e do aplicativo.",
+    "title": "Licença marca branca",
+    "description": "Ative uma licença marca branca para remover a marca Torqvoice das faturas e do aplicativo.",
     "purchaseWhiteLabel": "Comprar marca branca",
     "status": "Status:",
     "active": "Ativa",
     "inactive": "Inativa",
-    "lastChecked": "Ultima verificacao: {date}",
-    "enterLicenseKey": "Insira a chave de licenca",
+    "lastChecked": "Última verificação: {date}",
+    "enterLicenseKey": "Insira a chave de licença",
     "validate": "Validar",
-    "validated": "Licenca validada com sucesso",
-    "invalid": "Chave de licenca invalida",
-    "failedValidate": "Falha ao validar a licenca",
-    "keyHint": "Cada chave de licenca esta vinculada a esta organizacao e nao pode ser compartilhada entre varias organizacoes."
+    "validated": "Licença validada com sucesso",
+    "invalid": "Chave de licença invalida",
+    "failedValidate": "Falha ao validar a licença",
+    "keyHint": "Cada chave de licença esta vinculada a esta organização e nao pode ser compartilhada entre varias organizações."
   },
   "about": {
     "title": "Sobre o Torqvoice",
-    "description": "Torqvoice e sua solucao completa de gerenciamento de servicos veiculares. Rastreie historico de servicos, reparos e muito mais.",
+    "description": "Torqvoice e sua solucao completa de gerenciamento de serviços veiculares. Rastreie histórico de serviços, reparos e muito mais.",
     "version": "Versao",
     "website": "Site",
     "documentation": "Documentacao",
-    "changelog": "Registro de alteracoes"
+    "changelog": "Registro de alterações"
   },
   "portal": {
     "title": "Portal do cliente",
-    "description": "Permita que seus clientes facam login e visualizem seus veiculos, faturas, orcamentos e inspecoes. Os clientes tambem podem enviar solicitacoes de servico.",
+    "description": "Permita que seus clientes facam login e visualizem seus veículos, faturas, orçamentos e inspeções. Os clientes também podem enviar solicitações de serviço.",
     "enablePortal": "Ativar portal do cliente",
-    "enablePortalHint": "Clientes com endereco de e-mail podem fazer login via link magico",
+    "enablePortalHint": "Clientes com endereço de e-mail podem fazer login via link magico",
     "enabled": "Portal do cliente ativado",
     "disabled": "Portal do cliente desativado",
-    "failedUpdate": "Falha ao atualizar configuracao",
+    "failedUpdate": "Falha ao atualizar configuração",
     "customSlug": "Slug personalizado do portal",
     "save": "Salvar",
     "slugSaved": "Slug do portal salvo",
     "slugRemoved": "Slug do portal removido",
     "failedUpdateSlug": "Falha ao atualizar slug",
-    "slugHint": "Letras minusculas, numeros, hifens e underscores. 3-48 caracteres. Deixe em branco para usar a URL padrao.",
+    "slugHint": "Letras minúsculas, números, hifens e underscores. 3-48 caracteres. Deixe em branco para usar a URL padrão.",
     "portalUrl": "URL do portal",
     "urlCopied": "URL do portal copiada",
     "shareHint": "Compartilhe este link com seus clientes para que possam fazer login no portal.",
@@ -1039,76 +1039,76 @@
   },
   "email": {
     "title": "Provedor de e-mail",
-    "description": "Configure o provedor de e-mail da sua organizacao para envio de faturas e orcamentos. Se nao configurado, o provedor de e-mail padrao da plataforma sera usado.",
+    "description": "Configure o provedor de e-mail da sua organização para envio de faturas e orçamentos. Se nao configurado, o provedor de e-mail padrão da plataforma será usado.",
     "useCustomLabel": "Usar provedor de e-mail personalizado",
-    "useCustomHint": "Ative para configurar seu proprio provedor de e-mail em vez de usar o padrao da plataforma",
-    "platformDefaultInfo": "Sua organizacao esta usando o provedor de e-mail padrao da plataforma. Faturas e orcamentos serao enviados usando as configuracoes de e-mail da plataforma.",
+    "useCustomHint": "Ative para configurar seu próprio provedor de e-mail em vez de usar o padrão da plataforma",
+    "platformDefaultInfo": "Sua organização esta usando o provedor de e-mail padrão da plataforma. Faturas e orçamentos serão enviados usando as configurações de e-mail da plataforma.",
     "smtpHost": "Host SMTP",
     "smtpHostPlaceholder": "smtp.example.com",
     "smtpPort": "Porta SMTP",
     "smtpPortPlaceholder": "587",
-    "username": "Usuario",
-    "usernamePlaceholder": "usuario@example.com",
+    "username": "Usuário",
+    "usernamePlaceholder": "usuário@example.com",
     "password": "Senha",
     "fromEmail": "E-mail do remetente",
     "fromEmailPlaceholder": "noreply@example.com",
     "fromEmailDomainPlaceholder": "noreply@seudominio.com",
     "fromName": "Nome do remetente",
     "fromNamePlaceholder": "Sua Oficina",
-    "tlsConnection": "Conexao TLS (Porta 465)",
-    "tlsConnectionHint": "Ative para TLS implicito. Desative para STARTTLS (porta 587/25).",
+    "tlsConnection": "Conexão TLS (Porta 465)",
+    "tlsConnectionHint": "Ative para TLS implícito. Desative para STARTTLS (porta 587/25).",
     "verifyTlsCerts": "Verificar certificados TLS",
-    "verifyTlsCertsHint": "Desative para permitir certificados autoassinados (nao recomendado para producao)",
-    "requireTlsUpgrade": "Exigir atualizacao TLS",
-    "requireTlsUpgradeHint": "Forcar atualizacao TLS em conexoes STARTTLS",
+    "verifyTlsCertsHint": "Desative para permitir certificados autoassinados (nao recomendado para produção)",
+    "requireTlsUpgrade": "Exigir atualização TLS",
+    "requireTlsUpgradeHint": "Forçar atualização TLS em conexões STARTTLS",
     "apiKey": "Chave API",
     "apiKeyHintResend": "Obtenha sua chave API em",
     "serverToken": "Token do servidor",
     "serverTokenHintPostmark": "Obtenha seu Token do servidor em",
     "apiKeyHintMailgun": "Obtenha sua chave API em",
-    "domain": "Dominio",
+    "domain": "Domínio",
     "domainPlaceholder": "mg.seudominio.com",
-    "region": "Regiao",
+    "region": "Região",
     "apiKeyHintSendGrid": "Obtenha sua chave API em",
     "accessKeyId": "ID da chave de acesso",
     "secretAccessKey": "Chave de acesso secreta",
-    "awsRegion": "Regiao AWS",
+    "awsRegion": "Região AWS",
     "awsRegionPlaceholder": "us-east-1",
-    "awsRegionHint": "A regiao AWS onde o SES esta configurado (ex. us-east-1, eu-west-1)",
+    "awsRegionHint": "A região AWS onde o SES esta configurado (ex. us-east-1, eu-west-1)",
     "sendTestEmail": "Enviar e-mail de teste",
-    "testEmailHint": "Salve as configuracoes primeiro, depois envie um e-mail de teste para sua conta",
-    "saveSettings": "Salvar configuracoes",
-    "savedDefault": "Configuracoes de e-mail salvas — usando padrao da plataforma",
-    "saved": "Configuracoes de e-mail salvas",
-    "failedSave": "Falha ao salvar configuracoes",
+    "testEmailHint": "Salve as configurações primeiro, depois envie um e-mail de teste para sua conta",
+    "saveSettings": "Salvar configurações",
+    "savedDefault": "Configurações de e-mail salvas — usando padrão da plataforma",
+    "saved": "Configurações de e-mail salvas",
+    "failedSave": "Falha ao salvar configurações",
     "testSentTo": "E-mail de teste enviado para {email}",
     "testFailed": "Teste de e-mail falhou"
   },
   "sms": {
     "title": "Provedor SMS",
-    "description": "Configure seu provedor de SMS para mensagens bidirecionais com clientes. Cada organizacao usa seu proprio provedor de SMS e numero de telefone.",
+    "description": "Configure seu provedor de SMS para mensagens bidirecionais com clientes. Cada organização usa seu próprio provedor de SMS e número de telefone.",
     "enableLabel": "Ativar mensagens SMS",
     "enableHint": "Enviar e receber mensagens SMS com clientes",
-    "disabledInfo": "Mensagens SMS estao desativadas. Ative para enviar e receber mensagens de texto com seus clientes.",
-    "phoneNumber": "Numero de telefone",
+    "disabledInfo": "Mensagens SMS estão desativadas. Ative para enviar e receber mensagens de texto com seus clientes.",
+    "phoneNumber": "Número de telefone",
     "phoneNumberPlaceholder": "+5511912345678",
-    "phoneNumberHint": "Seu numero de telefone habilitado para SMS no formato E.164 (ex. +5511912345678)",
+    "phoneNumberHint": "Seu número de telefone habilitado para SMS no formato E.164 (ex. +5511912345678)",
     "accountSid": "SID da conta",
-    "authToken": "Token de autenticacao",
+    "authToken": "Token de autenticação",
     "apiKey": "Chave API",
     "apiSecret": "Segredo API",
     "webhookUrl": "URL do webhook",
     "webhookUrlHint": "Configure esta URL no painel do {provider} para receber mensagens SMS recebidas.",
     "webhookCopied": "URL do webhook copiada",
-    "testPhoneNumber": "Numero de telefone de teste",
+    "testPhoneNumber": "Número de telefone de teste",
     "testPhonePlaceholder": "+5511998765432",
     "sendTestSms": "Enviar SMS de teste",
-    "testSmsHint": "Salve as configuracoes primeiro, depois insira um numero de destinatario para enviar um SMS de teste",
-    "enterTestPhone": "Insira um numero de telefone para enviar o SMS de teste",
-    "saveSettings": "Salvar configuracoes",
-    "savedDisabled": "Configuracoes SMS salvas — SMS desativado",
-    "saved": "Configuracoes SMS salvas",
-    "failedSave": "Falha ao salvar configuracoes",
+    "testSmsHint": "Salve as configurações primeiro, depois insira um número de destinatário para enviar um SMS de teste",
+    "enterTestPhone": "Insira um número de telefone para enviar o SMS de teste",
+    "saveSettings": "Salvar configurações",
+    "savedDisabled": "Configurações SMS salvas — SMS desativado",
+    "saved": "Configurações SMS salvas",
+    "failedSave": "Falha ao salvar configurações",
     "testSentTo": "SMS de teste enviado para {phone}",
     "testFailed": "Teste de SMS falhou",
     "copyWebhookUrl": "Copy webhook URL"
@@ -1182,23 +1182,23 @@
   "ai": {
     "title": "Assistente de IA",
     "readMore": "Read More",
-    "description": "Configure a IA para gerar automaticamente notas de servico e resumir o historico do veiculo.",
+    "description": "Configure a IA para gerar automaticamente notas de serviço e resumir o histórico do veículo.",
     "enableLabel": "Ativar IA",
     "enableHint": "Usar recursos de IA em toda a aplicacao",
-    "disabledInfo": "Ative a IA para usar recursos como descricoes de servico geradas automaticamente e resumos do historico do veiculo.",
+    "disabledInfo": "Ative a IA para usar recursos como descricoes de serviço geradas automaticamente e resumos do histórico do veículo.",
     "provider": "Provedor de IA",
     "apiKey": "Chave API",
-    "apiKeyHint": "Sua chave API {provider}. Armazenada com seguranca e nunca compartilhada.",
+    "apiKeyHint": "Sua chave API {provider}. Armazenada com segurança e nunca compartilhada.",
     "model": "Modelo",
-    "testConnection": "Testar conexao",
-    "testSuccess": "Conexao de IA bem-sucedida!",
-    "testFailed": "Conexao falhou. Verifique sua chave API e modelo.",
-    "saved": "Configuracoes de IA salvas",
-    "failedSave": "Falha ao salvar configuracoes de IA",
-    "saveSettings": "Salvar configuracoes",
+    "testConnection": "Testar conexão",
+    "testSuccess": "Conexão de IA bem-sucedida!",
+    "testFailed": "Conexão falhou. Verifique sua chave API e modelo.",
+    "saved": "Configurações de IA salvas",
+    "failedSave": "Falha ao salvar configurações de IA",
+    "saveSettings": "Salvar configurações",
     "featuresTitle": "Recursos de IA incluidos:",
-    "featureServiceNotes": "Gerar automaticamente descricoes profissionais de servico a partir de pecas e mao de obra",
-    "featureHistorySummary": "Resumo do historico de servico do veiculo com um clique",
+    "featureServiceNotes": "Gerar automaticamente descricoes profissionais de serviço a partir de peças e mao de obra",
+    "featureHistorySummary": "Resumo do histórico de serviço do veículo com um clique",
     "cost": {
       "high": "Alto custo",
       "medium": "Médio",

+ 11 - 1
messages/ru/audit.json

@@ -98,6 +98,16 @@
     "webhook_create": "Создан вебхук",
     "webhook_update": "Обновлён вебхук",
     "webhook_delete": "Удалён вебхук",
-    "webhook_rotateSecret": "Обновлён секрет вебхука"
+    "webhook_rotateSecret": "Обновлён секрет вебхука",
+    "inspection_reopen": "Осмотр открыт заново",
+    "inspectionTemplate_import": "Импортирован шаблон осмотра",
+    "scheduled_message_create": "Создано запланированное сообщение",
+    "scheduled_message_update": "Обновлено запланированное сообщение",
+    "scheduled_message_delete": "Удалено запланированное сообщение",
+    "scheduled_message_cancel": "Отменено запланированное сообщение",
+    "scheduled_message_send_now": "Отправлено запланированное сообщение",
+    "settings_applyTaxRate": "Применена налоговая ставка",
+    "settings_convertToExclusive": "Цены переведены в без налога",
+    "settings_convertToInclusive": "Цены переведены во с налогом"
   }
 }

+ 24 - 0
messages/ru/common.json

@@ -52,5 +52,29 @@
     "updated": "Torqvoice обновлён до версии {version}",
     "whatsNew": "Что нового",
     "dismiss": "Закрыть"
+  },
+  "pagination": {
+    "showing": "Показано {start}–{end} из {total}",
+    "perPage": "на странице",
+    "pageOf": "Страница {page} из {pages}",
+    "first": "Первая страница",
+    "previous": "Предыдущая страница",
+    "next": "Следующая страница",
+    "last": "Последняя страница"
+  },
+  "shared": {
+    "close": "Закрыть",
+    "viewPlans": "Посмотреть тарифы",
+    "manageLicense": "Управление лицензией",
+    "installOwn": "Установить свой",
+    "install": "Установить",
+    "installApp": "Установить приложение",
+    "installTorqvoice": "Установить Torqvoice",
+    "dismiss": "Закрыть",
+    "useCamera": "Использовать камеру",
+    "enterManually": "Ввести вручную",
+    "enterBarcode": "Введите штрихкод…",
+    "scanHint": "Наведите камеру на штрихкод или введите его вручную",
+    "iosInstallHint": "Нажмите «Поделиться» > «На экран «Домой»»"
   }
 }

+ 396 - 0
messages/ru/inspections.json

@@ -0,0 +1,396 @@
+{
+  "grades": {
+    "eu": {
+      "pass": "Неисправностей нет",
+      "attention": "Незначительная неисправность",
+      "fail": "Значительная неисправность",
+      "dangerous": "Опасная неисправность",
+      "not_inspected": "Не проверялось"
+    },
+    "basic": {
+      "pass": "Годен",
+      "attention": "Внимание",
+      "fail": "Не годен",
+      "dangerous": "Опасно",
+      "not_inspected": "Не проверялось"
+    },
+    "short": {
+      "pass": "ОК",
+      "attention": "Незнач.",
+      "fail": "Значит.",
+      "dangerous": "Опасная",
+      "not_inspected": "–"
+    },
+    "hint": {
+      "pass": "Соответствует требованиям. Замечаний нет.",
+      "attention": "Существенно не влияет на безопасность. Транспортное средство допущено; устранить при случае.",
+      "fail": "Может снизить безопасность или повлиять на окружающую среду. Не допущено; устранить и пройти повторную проверку.",
+      "dangerous": "Прямая и непосредственная угроза безопасности движения. Эксплуатация запрещена.",
+      "not_inspected": "Этот пункт не выполнялся."
+    }
+  },
+  "results": {
+    "pass": "Пройдено",
+    "pass_minor": "Пройдено с незначительными неисправностями",
+    "fail": "Не пройдено — значительные неисправности",
+    "fail_dangerous": "Не пройдено — опасные неисправности",
+    "incomplete": "Не завершено",
+    "detail": {
+      "pass": "Неисправностей не выявлено.",
+      "pass_minor": "Транспортное средство допущено к эксплуатации. Устраните незначительные неисправности без промедления.",
+      "fail": "Транспортное средство не допущено. Неисправности необходимо устранить, после чего пройти повторную проверку.",
+      "fail_dangerous": "Транспортное средство запрещается использовать на дорогах общего пользования до устранения опасных неисправностей.",
+      "incomplete": "Проверка ещё не завершена."
+    }
+  },
+  "page": {
+    "back": "Осмотры",
+    "statusInProgress": "В работе",
+    "statusCompleted": "Завершён",
+    "complete": "Завершить",
+    "reopen": "Открыть заново",
+    "share": "Поделиться",
+    "save": "Сохранить",
+    "saving": "Сохранение…",
+    "savedAt": "Сохранено {time}",
+    "savesAsYouGo": "Сохраняет по ходу работы",
+    "saveFailed": "Некоторые изменения не сохранены",
+    "allSaved": "Все изменения сохранены",
+    "moreActions": "Ещё действия",
+    "downloadCertificate": "Скачать заключение",
+    "manageTemplates": "Управление шаблонами",
+    "delete": "Удалить",
+    "createWorkOrder": "Создать заказ-наряд",
+    "viewWorkOrder": "Открыть заказ-наряд",
+    "createQuote": "Создать смету",
+    "viewQuote": "Открыть смету",
+    "needsDefect": "Доступно после того, как зафиксирована неисправность",
+    "workOrderRaised": "Заказ-наряд {number} создан по этому осмотру {date}",
+    "open": "Открыть",
+    "quoteCreated": "Смета создана: {name}, {date}",
+    "quoteRequested": "Клиент запросил смету по {count} пункт(ам)",
+    "stillToGrade": "Осталось оценить {count} из {total} пунктов.",
+    "progress": "Ход выполнения",
+    "checksGraded": "пунктов оценено",
+    "progressLabel": "Оценено {graded} из {total} пунктов: {pass} без неисправностей, {attention} незначительных, {fail} значительных, {dangerous} опасных.",
+    "sections": "Группы",
+    "graded": "{graded}/{total} оценено",
+    "vehicle": "Транспортное средство",
+    "vessel": "Судно",
+    "customer": "Клиент",
+    "plate": "Регистрационный знак",
+    "registration": "Регистрация",
+    "template": "Шаблон",
+    "scaleEu": "Оценивается по шкале неисправностей ЕС из Директивы 2014/45/ЕС.",
+    "scaleBasic": "Оценивается годен / внимание / не годен.",
+    "country": "Страна: {code}.",
+    "manageInspectionTemplates": "Управление шаблонами осмотра",
+    "readMore": "Подробнее",
+    "odometer": "Пробег",
+    "engineHours": "Моточасы",
+    "completeTitle": "Завершить этот осмотр?",
+    "completeBody": "После завершения пункты блокируются, а итог становится «{result}».",
+    "completeBlocked": "Осталось {count} пункт(ов), которые шаблон отмечает как обязательные.",
+    "completeUngraded": "{count} пункт(ов) не оценены.",
+    "completeDangerous": "Зафиксировано опасных неисправностей: {count}. Эксплуатация на дорогах общего пользования запрещена.",
+    "reopenTitle": "Открыть этот осмотр заново?",
+    "reopenBody": "Пункты снова станут доступны для правки, а итог вернётся в состояние в работе. Контролёр, номер свидетельства и дата следующей проверки сохраняются.",
+    "reopenShared": "Ссылка клиента останется активной и покажет, как итог меняется по ходу правки. Уже скачанное заключение сохранит прежний итог.",
+    "deleteTitle": "Удалить этот осмотр?",
+    "deleteBody": "Осмотр, его оценки, примечания и фотографии будут удалены безвозвратно. Отменить это нельзя.",
+    "cancel": "Отмена",
+    "completed": "Осмотр завершён",
+    "reopened": "Осмотр открыт заново",
+    "deleted": "Осмотр удалён",
+    "completeFailed": "Не удалось завершить осмотр",
+    "reopenFailed": "Не удалось открыть осмотр заново",
+    "deleteFailed": "Не удалось удалить осмотр",
+    "quoteFailed": "Не удалось создать смету",
+    "workOrderFailed": "Не удалось создать заказ-наряд"
+  },
+  "item": {
+    "required": "(обязательно)",
+    "saving": "Сохранение",
+    "addNote": "Добавить примечание",
+    "hideNote": "Скрыть примечание",
+    "addPhoto": "Добавить фото или видео к «{name}»",
+    "notesFor": "Примечания к «{name}»",
+    "describeDefect": "Опишите неисправность — она попадёт в отчёт",
+    "optionalNote": "Добавить примечание (необязательно)",
+    "notesRequired": "Опишите неисправность, прежде чем эта оценка будет сохранена.",
+    "photoRequired": "Здесь требуется фотография неисправности. Осмотр нельзя завершить, пока она не приложена.",
+    "reading": "Показание",
+    "readingUnit": "Показание ({unit})",
+    "limit": "Предел: {range}",
+    "recordedValue": "Записанное значение",
+    "answer": "Ответ",
+    "selectAnswer": "Выберите ответ",
+    "viewPhoto": "Открыть фото {index} пункта «{name}» в полном размере",
+    "photoAlt": "{name}, фото {index}",
+    "removePhoto": "Удалить фото {index} из пункта «{name}»",
+    "clearTitle": "Очистить эту оценку?",
+    "clearBody": "Примечание, записанное к «{name}», будет удалено, а пункт вернётся к состоянию «не проверялось».",
+    "clear": "Очистить",
+    "cancel": "Отмена",
+    "saveFailed": "Не удалось сохранить этот пункт",
+    "uploadFailed": "Не удалось загрузить",
+    "uploadFailedFor": "Не удалось загрузить {name}"
+  },
+  "suggestions": {
+    "title": "Типичные неисправности",
+    "add": "Добавить «{text}» и выставить оценку {grade}",
+    "more": "Ещё ({count})",
+    "search": "Поиск формулировки",
+    "empty": "Подходящих формулировок нет.",
+    "source": {
+      "workshop": "Ваша мастерская",
+      "history": "Использовалось ранее",
+      "regulation": "Директива 2014/45/ЕС",
+      "general": "Общие"
+    }
+  },
+  "media": {
+    "viewer": "Изображение {index} из {total}: {caption}",
+    "close": "Закрыть просмотр",
+    "previous": "Предыдущее изображение",
+    "next": "Следующее изображение"
+  },
+  "certificate": {
+    "title": "Сведения заключения",
+    "description": "Требуются в заключении о техническом осмотре согласно Директиве 2014/45/ЕС, приложение IV.",
+    "inspector": "Контролёр",
+    "notRecorded": "Не указано",
+    "addTechnician": "Добавить механика…",
+    "recordedAs": "Указан как {name}",
+    "vehicleCategory": "Категория ТС",
+    "mileageAtTest": "{label} на момент проверки",
+    "reportReference": "Ссылка на отчёт",
+    "reportReferenceHelp": "Ваше собственное обозначение этого отчёта. Если вы аккредитованный пункт, выдающий официальное заключение, укажите здесь его номер, чтобы они совпадали. Иначе оставьте поле пустым.",
+    "optional": "Необязательно",
+    "placeOfTest": "Место проверки",
+    "placeholderAddress": "Пункт проверки или адрес мастерской",
+    "fromSettings": "Из настроек вашей мастерской.",
+    "change": "Изменить",
+    "differsFromWorkshop": "Отличается от адреса вашей мастерской.",
+    "useWorkshopAddress": "Использовать адрес мастерской",
+    "noWorkshopAddress": "Адрес мастерской не задан.",
+    "addOne": "Добавить адрес",
+    "nextTestDue": "Следующая проверка",
+    "notSet": "Не задано",
+    "intervalGroup": "Задать дату следующей проверки от даты этой",
+    "intervalHelp": "Отсчитывается от даты этой проверки. Два года — ориентир Союза для легковых и лёгких грузовых автомобилей; уточните периодичность, применяемую в вашей стране.",
+    "months6": "6 месяцев",
+    "years1": "1 год",
+    "years2": "2 года",
+    "years3": "3 года",
+    "years4": "4 года",
+    "unsaved": "Несохранённые изменения",
+    "saveDetails": "Сохранить сведения",
+    "saved": "Сведения сохранены",
+    "saveFailed": "Не удалось сохранить сведения",
+    "addTechnicianTitle": "Добавить механика",
+    "addTechnicianBody": "Он попадёт в список мастерской, и ему можно будет назначать как работы, так и осмотры.",
+    "technicianName": "Имя",
+    "technicianPlaceholder": "например, Анна Берг",
+    "addAndSelect": "Добавить и выбрать",
+    "technicianAdded": "{name} добавлен и выбран",
+    "technicianFailed": "Не удалось добавить механика",
+    "cancel": "Отмена"
+  },
+  "templates": {
+    "title": "Шаблоны осмотра",
+    "description": "Контрольные списки, по которым работают ваши механики. Создайте по одному на страну, на тип транспортного средства или на услугу.",
+    "import": "Импорт",
+    "browse": "Просмотр списков",
+    "new": "Новый шаблон",
+    "restoreBanner": "Из вашего списка удалено готовых списков: {count}. Их можно вернуть.",
+    "restore": "Восстановить",
+    "restored": "Восстановлено списков: {count}",
+    "allPresent": "Все списки уже здесь",
+    "restoreFailed": "Не удалось добавить списки",
+    "emptyTitle": "Шаблонов пока нет",
+    "emptyBody": "Все готовые списки, включая периодический технический осмотр ЕС, были удалены. Верните их или соберите свой с нуля.",
+    "restoreChecklists": "Восстановить списки",
+    "buildMyOwn": "Собрать свой",
+    "default": "По умолчанию",
+    "actionsFor": "Действия для «{name}»",
+    "edit": "Изменить",
+    "duplicate": "Дублировать",
+    "export": "Экспорт…",
+    "delete": "Удалить",
+    "sections": "групп: {count}",
+    "checks": "пунктов: {count}",
+    "measured": "с измерением: {count}",
+    "euScale": "Шкала неисправностей ЕС",
+    "basicScale": "Годен / внимание / не годен",
+    "deleteTitle": "Удалить «{name}»?",
+    "deleteBody": "Шаблон будет удалён безвозвратно. Уже проведённые по нему осмотры сохраняют собственную копию списка и не затрагиваются.",
+    "deleted": "Шаблон удалён",
+    "deleteFailed": "Не удалось удалить шаблон",
+    "duplicated": "Шаблон продублирован",
+    "duplicateFailed": "Не удалось продублировать шаблон",
+    "cancel": "Отмена",
+    "srSections": "Группы",
+    "srChecks": "Пункты",
+    "srMeasurements": "Измерения"
+  },
+  "presets": {
+    "title": "Начать с шаблона",
+    "description": "Каждый шаблон копируется в вашу мастерскую как обычный. Переименовывайте группы, перемещайте пункты и меняйте любые предельные значения под свою страну и своё оборудование.",
+    "search": "Поиск по названию, стране или норме",
+    "searchLabel": "Поиск шаблонов",
+    "inYourList": "В вашем списке",
+    "counts": "групп: {sections} · пунктов: {checks}",
+    "noMatch": "Ни один шаблон не подходит под «{query}».",
+    "warning": "Нормативные шаблоны содержат обычные предельные значения ЕС. Сверьте их с национальными правилами, прежде чем выдавать заключение.",
+    "cancel": "Отмена",
+    "add": "Добавить шаблон",
+    "added": "Шаблон добавлен. Настройте его под свою мастерскую.",
+    "addFailed": "Не удалось добавить шаблон",
+    "group": {
+      "regulatory": "Нормативные",
+      "workshop": "Мастерская",
+      "specialist": "Специальные"
+    },
+    "groupDescription": {
+      "regulatory": "Списки периодического технического осмотра на основе Директивы 2014/45/ЕС.",
+      "workshop": "Общие сервисные и торговые списки, не связанные с нормативами.",
+      "specialist": "Списки для силовых установок и судов, требующих отдельной процедуры."
+    },
+    "euScale": "Шкала неисправностей ЕС",
+    "basicScale": "Годен / внимание / не годен"
+  },
+  "builder": {
+    "newTitle": "Новый шаблон",
+    "editTitle": "Изменить шаблон",
+    "description": "Соберите список, которым ваша мастерская пользуется на деле. Перетаскивайте для изменения порядка и настройте каждый пункт так, чтобы он фиксировал оценку, измерение, записанное примечание или заданный ответ.",
+    "name": "Название",
+    "namePlaceholder": "например, Периодический технический осмотр",
+    "templateDescription": "Описание",
+    "descriptionPlaceholder": "Для чего этот список и когда его применять.",
+    "country": "Страна",
+    "notCountrySpecific": "Без привязки к стране",
+    "countryHelp": "Записывается в отчёт, чтобы мастерская, работающая в нескольких странах, различала свои списки.",
+    "defectScale": "Шкала неисправностей",
+    "scaleEu": "Незначительная / значительная / опасная (ЕС)",
+    "scaleBasic": "Годен / внимание / не годен",
+    "scaleEuHelp": "Категории статьи 7(2) Директивы 2014/45/ЕС. Транспортное средство только с незначительными неисправностями всё равно считается пройденным.",
+    "scaleBasicHelp": "Простая трёхступенчатая шкала для сервисных списков, не являющихся обязательной проверкой.",
+    "defaultTemplate": "Шаблон по умолчанию",
+    "defaultHelp": "Выбирается заранее, когда механик начинает новый осмотр.",
+    "summary": "групп: {sections} · пунктов: {checks}",
+    "renumber": "Перенумеровать ссылки",
+    "renumberHelp": "Нумерует каждую группу и каждый пункт по порядку: 1, 1.1, 1.2, 2. Новые строки нумеруются при добавлении, так что это нужно только после перемещения.",
+    "renumberWarning": "В этом списке используются ссылки на нормы, например 1.1.13. Перенумерация их заменит.",
+    "addSection": "Добавить группу",
+    "addCheck": "Добавить пункт",
+    "ref": "Ссылка",
+    "sectionName": "Название группы, например Тормозная система",
+    "sectionNote": "Необязательное примечание над пунктами этой группы",
+    "checkName": "Название пункта, например Тормозные колодки",
+    "reorderSection": "Изменить порядок группы «{name}»",
+    "reorderCheck": "Изменить порядок пункта «{name}»",
+    "removeSection": "Удалить группу «{name}»",
+    "removeCheck": "Удалить пункт «{name}»",
+    "duplicateCheck": "Дублировать пункт «{name}»",
+    "configure": "Настроить «{name}»",
+    "hideSettings": "Скрыть настройки «{name}»",
+    "howRecorded": "Как это фиксируется?",
+    "guidance": "Пояснение для механика",
+    "guidancePlaceholder": "Показывается под названием пункта в форме осмотра.",
+    "measurement": "Измерение",
+    "unit": "Единица",
+    "minimum": "Минимум",
+    "maximum": "Максимум",
+    "outOfRange": "Неисправность вне диапазона",
+    "rangeHelp": "Оставьте одну границу пустой для одностороннего требования, например минимум 1,6 мм без максимума. Показания вне диапазона оцениваются автоматически.",
+    "answers": "Ответы",
+    "answersHelp": "Разделяйте ответы запятыми.",
+    "mustComplete": "Обязателен к заполнению",
+    "photoRequired": "Требуется фото при фиксации неисправности",
+    "commonWording": "Типовые формулировки",
+    "commonWordingHelp": "По одной формулировке в строке. Они предлагаются первыми, когда механик фиксирует здесь неисправность, раньше встроенных, чтобы ваша мастерская описывала неисправность одинаково.",
+    "inputType": {
+      "condition": "Оценка неисправности",
+      "measurement": "Измерение",
+      "text": "Произвольный текст",
+      "choice": "Выбор"
+    },
+    "inputHint": {
+      "condition": "Механик оценивает пункт по шкале неисправностей.",
+      "measurement": "Фиксирует число с единицей измерения, автоматически оцениваемое по диапазону.",
+      "text": "Фиксирует записанное наблюдение, например марку шины или код ошибки.",
+      "choice": "Механик выбирает один ответ из заданного списка."
+    },
+    "cancel": "Отмена",
+    "create": "Создать шаблон",
+    "saveChanges": "Сохранить изменения",
+    "created": "Шаблон создан",
+    "updated": "Шаблон обновлён",
+    "saveFailed": "Не удалось сохранить шаблон",
+    "severityAttention": "Незначительная неисправность",
+    "severityFail": "Значительная неисправность",
+    "severityDangerous": "Опасная неисправность",
+    "answersPlaceholder": "Полная, Частичная, Нет"
+  },
+  "packages": {
+    "exportTitle": "Экспорт «{name}»",
+    "exportDescription": "Скачивает весь список файлом: группы, пункты, единицы измерения и предельные значения. Импортировать его сможет любой, у кого есть Torqvoice.",
+    "includeWording": "Включить собственные формулировки",
+    "includeWordingHelp": "Формулировки, добавленные вашей мастерской к пунктам. Полезны получателю, но это произвольный текст, набранный у верстака, и он может называть клиента, коллегу или местную договорённость. Выключено, если вы их не прочитали.",
+    "attributeTo": "Указать автора (необязательно)",
+    "attributePlaceholder": "например, Egeland Auto",
+    "attributeHelp": "Записывается в файл, чтобы устанавливающий видел, откуда он. Из вашей учётной записи ничего не берётся.",
+    "download": "Скачать",
+    "exported": "Шаблон экспортирован",
+    "exportFailed": "Не удалось экспортировать этот шаблон",
+    "importTitle": "Импорт списка",
+    "importDescription": "Добавляет список из файла пакета Torqvoice. Он приходит как обычный шаблон, который можно править, и никогда не заменяет уже имеющийся.",
+    "chooseFile": "Выберите файл пакета",
+    "chooseAnother": "Выберите другой файл",
+    "unreadable": "Этот файл не удалось прочитать. Пакет Torqvoice — это файл .json, выгруженный из шаблона.",
+    "from": "От {author}",
+    "limitsWarning": "Предельные значения в общем списке — те, что задал его автор. Сверьте их с национальными правилами, прежде чем что-либо выдавать на его основе.",
+    "import": "Импорт",
+    "imported": "Импортировано шаблонов: {count}",
+    "importFailed": "Не удалось импортировать этот пакет",
+    "cancel": "Отмена"
+  },
+  "list": {
+    "tabAll": "Все",
+    "tabInProgress": "В работе",
+    "tabCompleted": "Завершённые",
+    "search": "Поиск осмотров…",
+    "new": "Новый осмотр",
+    "empty": "Осмотры не найдены.",
+    "vehicle": "Транспортное средство",
+    "template": "Шаблон",
+    "progress": "Ход выполнения",
+    "status": "Статус",
+    "date": "Дата",
+    "statusInProgress": "В работе",
+    "statusCompleted": "Завершён"
+  },
+  "new": {
+    "title": "Новый осмотр",
+    "vehicle": "Транспортное средство",
+    "searchVehicles": "Поиск транспортных средств…",
+    "loadingVehicles": "Загрузка транспортных средств…",
+    "noVehicles": "Транспортные средства не найдены.",
+    "template": "Шаблон",
+    "selectTemplate": "Выберите шаблон",
+    "manageTemplates": "Управление шаблонами",
+    "cancel": "Отмена",
+    "create": "Создать осмотр",
+    "created": "Осмотр создан",
+    "createFailed": "Не удалось создать осмотр"
+  },
+  "shareDialog": {
+    "title": "Поделиться осмотром",
+    "description": "Поделитесь с клиентом версией этого осмотра только для чтения.",
+    "copyLink": "Копировать ссылку",
+    "copied": "Ссылка скопирована в буфер обмена",
+    "generated": "Ссылка для доступа создана",
+    "revoked": "Ссылка для доступа отозвана"
+  }
+}

+ 11 - 1
messages/tr/audit.json

@@ -98,6 +98,16 @@
     "webhook_create": "Webhook oluşturuldu",
     "webhook_update": "Webhook güncellendi",
     "webhook_delete": "Webhook silindi",
-    "webhook_rotateSecret": "Webhook gizli anahtarı yenilendi"
+    "webhook_rotateSecret": "Webhook gizli anahtarı yenilendi",
+    "inspection_reopen": "Muayene yeniden açıldı",
+    "inspectionTemplate_import": "Muayene şablonu içe aktarıldı",
+    "scheduled_message_create": "Zamanlanmış mesaj oluşturuldu",
+    "scheduled_message_update": "Zamanlanmış mesaj güncellendi",
+    "scheduled_message_delete": "Zamanlanmış mesaj silindi",
+    "scheduled_message_cancel": "Zamanlanmış mesaj iptal edildi",
+    "scheduled_message_send_now": "Zamanlanmış mesaj gönderildi",
+    "settings_applyTaxRate": "Vergi oranı uygulandı",
+    "settings_convertToExclusive": "Fiyatlar vergi hariç yapıldı",
+    "settings_convertToInclusive": "Fiyatlar vergi dahil yapıldı"
   }
 }

+ 24 - 0
messages/tr/common.json

@@ -52,5 +52,29 @@
     "updated": "Torqvoice {version} sürümüne güncellendi",
     "whatsNew": "Yenilikleri gör",
     "dismiss": "Kapat"
+  },
+  "pagination": {
+    "showing": "{total} kayıttan {start}–{end} gösteriliyor",
+    "perPage": "sayfa başına",
+    "pageOf": "Sayfa {page}/{pages}",
+    "first": "İlk sayfa",
+    "previous": "Önceki sayfa",
+    "next": "Sonraki sayfa",
+    "last": "Son sayfa"
+  },
+  "shared": {
+    "close": "Kapat",
+    "viewPlans": "Planlara bak",
+    "manageLicense": "Lisansı yönet",
+    "installOwn": "Kendinizinkini kurun",
+    "install": "Kur",
+    "installApp": "Uygulamayı kur",
+    "installTorqvoice": "Torqvoice'i kur",
+    "dismiss": "Kapat",
+    "useCamera": "Kamerayı kullan",
+    "enterManually": "Elle gir",
+    "enterBarcode": "Barkod girin…",
+    "scanHint": "Kamerayı bir barkoda doğrultun ya da elle girin",
+    "iosInstallHint": "Paylaş > Ana Ekrana Ekle'ye dokunun"
   }
 }

+ 396 - 0
messages/tr/inspections.json

@@ -0,0 +1,396 @@
+{
+  "grades": {
+    "eu": {
+      "pass": "Kusur yok",
+      "attention": "Hafif kusur",
+      "fail": "Ağır kusur",
+      "dangerous": "Tehlikeli kusur",
+      "not_inspected": "Muayene edilmedi"
+    },
+    "basic": {
+      "pass": "Geçti",
+      "attention": "Dikkat",
+      "fail": "Kaldı",
+      "dangerous": "Tehlikeli",
+      "not_inspected": "Muayene edilmedi"
+    },
+    "short": {
+      "pass": "OK",
+      "attention": "Hafif",
+      "fail": "Ağır",
+      "dangerous": "Tehlikeli",
+      "not_inspected": "–"
+    },
+    "hint": {
+      "pass": "Gereklilikleri karşılıyor. Bildirilecek bir şey yok.",
+      "attention": "Güvenlik üzerinde kayda değer etkisi yok. Araç geçer; uygun bir zamanda giderin.",
+      "fail": "Güvenliği zayıflatabilir veya çevreyi etkileyebilir. Araç kalır; giderin ve yeniden muayene ettirin.",
+      "dangerous": "Trafik güvenliği için doğrudan ve acil tehlike. Araç kullanılmamalıdır.",
+      "not_inspected": "Bu madde uygulanmadı."
+    }
+  },
+  "results": {
+    "pass": "Geçti",
+    "pass_minor": "Hafif kusurlarla geçti",
+    "fail": "Kaldı — ağır kusurlar",
+    "fail_dangerous": "Kaldı — tehlikeli kusurlar",
+    "incomplete": "Tamamlanmadı",
+    "detail": {
+      "pass": "Herhangi bir kusur bulunmadı.",
+      "pass_minor": "Araç geçti. Hafif kusurları gecikmeden giderin.",
+      "fail": "Araç kaldı. Kusurlar giderilmeli ve araç yeniden muayene edilmelidir.",
+      "fail_dangerous": "Tehlikeli kusurlar giderilene kadar araç karayolunda kullanılmamalıdır.",
+      "incomplete": "Bu muayene hâlâ devam ediyor."
+    }
+  },
+  "page": {
+    "back": "Muayeneler",
+    "statusInProgress": "Sürüyor",
+    "statusCompleted": "Tamamlandı",
+    "complete": "Tamamla",
+    "reopen": "Yeniden aç",
+    "share": "Paylaş",
+    "save": "Kaydet",
+    "saving": "Kaydediliyor…",
+    "savedAt": "Kaydedildi {time}",
+    "savesAsYouGo": "Siz çalışırken kaydeder",
+    "saveFailed": "Bazı değişiklikler kaydedilmedi",
+    "allSaved": "Tüm değişiklikler kaydedildi",
+    "moreActions": "Diğer işlemler",
+    "downloadCertificate": "Raporu indir",
+    "manageTemplates": "Şablonları yönet",
+    "delete": "Sil",
+    "createWorkOrder": "İş emri oluştur",
+    "viewWorkOrder": "İş emrini gör",
+    "createQuote": "Teklif oluştur",
+    "viewQuote": "Teklifi gör",
+    "needsDefect": "Bir kusur kaydedildiğinde kullanılabilir",
+    "workOrderRaised": "{date} tarihinde bu muayeneden {number} numaralı iş emri oluşturuldu",
+    "open": "Aç",
+    "quoteCreated": "Teklif {name} tarafından {date} tarihinde oluşturuldu",
+    "quoteRequested": "Müşteri {count} madde için teklif istedi",
+    "stillToGrade": "{total} maddeden {count} tanesi hâlâ değerlendirilmedi.",
+    "progress": "İlerleme",
+    "checksGraded": "madde değerlendirildi",
+    "progressLabel": "{total} maddeden {graded} tanesi değerlendirildi: {pass} kusursuz, {attention} hafif, {fail} ağır, {dangerous} tehlikeli.",
+    "sections": "Gruplar",
+    "graded": "{graded}/{total} değerlendirildi",
+    "vehicle": "Araç",
+    "vessel": "Tekne",
+    "customer": "Müşteri",
+    "plate": "Plaka",
+    "registration": "Tescil",
+    "template": "Şablon",
+    "scaleEu": "2014/45/AB sayılı direktifin AB kusur ölçeğine göre değerlendirilir.",
+    "scaleBasic": "Geçti / dikkat / kaldı olarak değerlendirilir.",
+    "country": "Ülke: {code}.",
+    "manageInspectionTemplates": "Muayene şablonlarını yönet",
+    "readMore": "Daha fazla bilgi",
+    "odometer": "Kilometre",
+    "engineHours": "Motor saati",
+    "completeTitle": "Bu muayene tamamlansın mı?",
+    "completeBody": "Tamamlandığında maddeler kilitlenir ve sonuç “{result}” olur.",
+    "completeBlocked": "Şablonun zorunlu işaretlediği {count} madde açık.",
+    "completeUngraded": "{count} madde değerlendirilmedi.",
+    "completeDangerous": "{count} tehlikeli kusur kaydedildi. Araç karayolunda kullanılmamalıdır.",
+    "reopenTitle": "Bu muayene yeniden açılsın mı?",
+    "reopenBody": "Maddeler yeniden düzenlenebilir olur ve sonuç sürüyor durumuna döner. Muayene görevlisi, belge numarası ve sonraki muayene tarihi korunur.",
+    "reopenShared": "Müşteri bağlantısı etkin kalır ve siz düzenlerken sonucun değiştiğini gösterir. Daha önce indirilmiş bir rapor eski sonucu korur.",
+    "deleteTitle": "Bu muayene silinsin mi?",
+    "deleteBody": "Bu işlem muayeneyi, değerlendirmelerini, notlarını ve fotoğraflarını kalıcı olarak siler. Geri alınamaz.",
+    "cancel": "İptal",
+    "completed": "Muayene tamamlandı",
+    "reopened": "Muayene yeniden açıldı",
+    "deleted": "Muayene silindi",
+    "completeFailed": "Muayene tamamlanamadı",
+    "reopenFailed": "Muayene yeniden açılamadı",
+    "deleteFailed": "Muayene silinemedi",
+    "quoteFailed": "Teklif oluşturulamadı",
+    "workOrderFailed": "İş emri oluşturulamadı"
+  },
+  "item": {
+    "required": "(zorunlu)",
+    "saving": "Kaydediliyor",
+    "addNote": "Not ekle",
+    "hideNote": "Notu gizle",
+    "addPhoto": "{name} için fotoğraf veya video ekle",
+    "notesFor": "{name} notları",
+    "describeDefect": "Kusuru açıklayın — raporda görünür",
+    "optionalNote": "Not ekleyin (isteğe bağlı)",
+    "notesRequired": "Bu değerlendirme kaydedilmeden önce kusuru açıklayın.",
+    "photoRequired": "Burada kusurun fotoğrafı gereklidir. Bir fotoğraf eklenene kadar muayene tamamlanamaz.",
+    "reading": "Ölçüm",
+    "readingUnit": "Ölçüm ({unit})",
+    "limit": "Sınır: {range}",
+    "recordedValue": "Kaydedilen değer",
+    "answer": "Yanıt",
+    "selectAnswer": "Bir yanıt seçin",
+    "viewPhoto": "{name} maddesinin {index}. fotoğrafını tam boyutta gör",
+    "photoAlt": "{name}, fotoğraf {index}",
+    "removePhoto": "{name} maddesinden {index}. fotoğrafı kaldır",
+    "clearTitle": "Bu değerlendirme temizlensin mi?",
+    "clearBody": "“{name}” için kaydedilen not kaldırılır ve madde muayene edilmedi durumuna döner.",
+    "clear": "Temizle",
+    "cancel": "İptal",
+    "saveFailed": "Bu madde kaydedilemedi",
+    "uploadFailed": "Yükleme başarısız",
+    "uploadFailedFor": "{name} yüklenemedi"
+  },
+  "suggestions": {
+    "title": "Sık görülen kusurlar",
+    "add": "“{text}” ekle ve {grade} olarak değerlendir",
+    "more": "Daha fazla ({count})",
+    "search": "İfade ara",
+    "empty": "Eşleşen ifade yok.",
+    "source": {
+      "workshop": "Atölyeniz",
+      "history": "Daha önce kullanılan",
+      "regulation": "2014/45/AB direktifi",
+      "general": "Genel"
+    }
+  },
+  "media": {
+    "viewer": "Görsel {index}/{total}: {caption}",
+    "close": "Görsel görüntüleyiciyi kapat",
+    "previous": "Önceki görsel",
+    "next": "Sonraki görsel"
+  },
+  "certificate": {
+    "title": "Rapor bilgileri",
+    "description": "2014/45/AB sayılı direktifin Ek IV'ü uyarınca muayene raporunda zorunludur.",
+    "inspector": "Muayene görevlisi",
+    "notRecorded": "Kaydedilmedi",
+    "addTechnician": "Teknisyen ekle…",
+    "recordedAs": "{name} olarak kaydedildi",
+    "vehicleCategory": "Araç sınıfı",
+    "mileageAtTest": "Muayene anındaki {label}",
+    "reportReference": "Rapor referansı",
+    "reportReferenceHelp": "Bu rapor için kendi referansınız. Resmî bir rapor düzenleyen yetkili bir muayene istasyonuysanız, ikisi örtüşsün diye numarasını buraya yazın. Aksi hâlde boş bırakın.",
+    "optional": "İsteğe bağlı",
+    "placeOfTest": "Muayene yeri",
+    "placeholderAddress": "Muayene istasyonu veya atölye adresi",
+    "fromSettings": "Atölye ayarlarınızdan.",
+    "change": "Değiştir",
+    "differsFromWorkshop": "Atölye adresinizden farklı.",
+    "useWorkshopAddress": "Atölye adresini kullan",
+    "noWorkshopAddress": "Atölye adresi tanımlı değil.",
+    "addOne": "Bir adres ekle",
+    "nextTestDue": "Sonraki muayene",
+    "notSet": "Belirlenmedi",
+    "intervalGroup": "Sonraki muayene tarihini bu muayenenin tarihinden itibaren belirle",
+    "intervalHelp": "Bu muayenenin tarihinden itibaren sayılır. İki yıl, otomobiller ve hafif ticari araçlar için Birliğin çıkış noktasıdır; ülkenizde uygulanan aralığı kontrol edin.",
+    "months6": "6 ay",
+    "years1": "1 yıl",
+    "years2": "2 yıl",
+    "years3": "3 yıl",
+    "years4": "4 yıl",
+    "unsaved": "Kaydedilmemiş değişiklikler",
+    "saveDetails": "Bilgileri kaydet",
+    "saved": "Bilgiler kaydedildi",
+    "saveFailed": "Bilgiler kaydedilemedi",
+    "addTechnicianTitle": "Teknisyen ekle",
+    "addTechnicianBody": "Atölye listesine katılır ve hem işler hem muayeneler kendisine atanabilir.",
+    "technicianName": "Ad",
+    "technicianPlaceholder": "örn. Anna Berg",
+    "addAndSelect": "Ekle ve seç",
+    "technicianAdded": "{name} eklendi ve seçildi",
+    "technicianFailed": "Teknisyen eklenemedi",
+    "cancel": "İptal"
+  },
+  "templates": {
+    "title": "Muayene şablonları",
+    "description": "Teknisyenlerinizin izlediği kontrol listeleri. Ülke başına, araç türü başına ya da sattığınız hizmet başına bir tane oluşturun.",
+    "import": "İçe aktar",
+    "browse": "Listelere göz at",
+    "new": "Yeni şablon",
+    "restoreBanner": "{count} hazır liste listenizden kaldırıldı. Geri koyabilirsiniz.",
+    "restore": "Geri yükle",
+    "restored": "{count} liste geri yüklendi",
+    "allPresent": "Tüm listeler zaten burada",
+    "restoreFailed": "Listeler eklenemedi",
+    "emptyTitle": "Henüz şablon yok",
+    "emptyBody": "AB periyodik teknik muayenesi dahil hazır listelerin hepsi kaldırıldı. Geri koyun ya da kendinizinkini sıfırdan kurun.",
+    "restoreChecklists": "Listeleri geri yükle",
+    "buildMyOwn": "Kendiminkini kur",
+    "default": "Varsayılan",
+    "actionsFor": "{name} için işlemler",
+    "edit": "Düzenle",
+    "duplicate": "Çoğalt",
+    "export": "Dışa aktar…",
+    "delete": "Sil",
+    "sections": "{count} grup",
+    "checks": "{count} madde",
+    "measured": "{count} ölçümlü",
+    "euScale": "AB kusur ölçeği",
+    "basicScale": "Geçti / dikkat / kaldı",
+    "deleteTitle": "“{name}” silinsin mi?",
+    "deleteBody": "Bu işlem şablonu kalıcı olarak siler. Onunla yapılmış muayeneler listenin kendi kopyasını korur ve etkilenmez.",
+    "deleted": "Şablon silindi",
+    "deleteFailed": "Şablon silinemedi",
+    "duplicated": "Şablon çoğaltıldı",
+    "duplicateFailed": "Şablon çoğaltılamadı",
+    "cancel": "İptal",
+    "srSections": "Gruplar",
+    "srChecks": "Maddeler",
+    "srMeasurements": "Ölçümler"
+  },
+  "presets": {
+    "title": "Bir şablonla başla",
+    "description": "Her şablon atölyenize normal bir şablon olarak kopyalanır. Grupları yeniden adlandırın, maddeleri taşıyın ve her sınırı ülkenize ve ekipmanınıza göre değiştirin.",
+    "search": "Ada, ülkeye veya standarda göre ara",
+    "searchLabel": "Şablon ara",
+    "inYourList": "Listenizde",
+    "counts": "{sections} grup · {checks} madde",
+    "noMatch": "“{query}” ile eşleşen şablon yok.",
+    "warning": "Mevzuata dayalı şablonlar yaygın AB sınır değerlerini taşır. Rapor düzenlemeden önce ulusal kurallarınıza göre denetleyin.",
+    "cancel": "İptal",
+    "add": "Şablon ekle",
+    "added": "Şablon eklendi. Atölyenize göre uyarlayın.",
+    "addFailed": "Şablon eklenemedi",
+    "group": {
+      "regulatory": "Mevzuat",
+      "workshop": "Atölye",
+      "specialist": "Uzmanlık"
+    },
+    "groupDescription": {
+      "regulatory": "2014/45/AB direktifine dayanan periyodik teknik muayene listeleri.",
+      "workshop": "Bir mevzuata bağlı olmayan genel bakım ve satış listeleri.",
+      "specialist": "Kendi prosedürünü gerektiren motorlar ve deniz araçları için listeler."
+    },
+    "euScale": "AB kusur ölçeği",
+    "basicScale": "Geçti / dikkat / kaldı"
+  },
+  "builder": {
+    "newTitle": "Yeni şablon",
+    "editTitle": "Şablonu düzenle",
+    "description": "Atölyenizin gerçekten kullandığı listeyi kurun. Sırayı değiştirmek için sürükleyin ve her maddeyi bir değerlendirme, bir ölçüm, yazılı bir not ya da sabit bir yanıt kaydedecek şekilde ayarlayın.",
+    "name": "Ad",
+    "namePlaceholder": "örn. Periyodik teknik muayene",
+    "templateDescription": "Açıklama",
+    "descriptionPlaceholder": "Bu listenin ne için olduğu ve ne zaman kullanılacağı.",
+    "country": "Ülke",
+    "notCountrySpecific": "Ülkeye özgü değil",
+    "countryHelp": "Sınır ötesinde çalışan bir atölyenin listelerini ayırt edebilmesi için rapora kaydedilir.",
+    "defectScale": "Kusur ölçeği",
+    "scaleEu": "Hafif / ağır / tehlikeli (AB)",
+    "scaleBasic": "Geçti / dikkat / kaldı",
+    "scaleEuHelp": "2014/45/AB direktifinin 7(2) maddesindeki kategoriler. Yalnızca hafif kusurları olan bir araç yine de geçer.",
+    "scaleBasicHelp": "Yasal bir muayene olmayan bakım listeleri için basit üç adımlı ölçek.",
+    "defaultTemplate": "Varsayılan şablon",
+    "defaultHelp": "Bir teknisyen yeni muayene başlattığında önceden seçilir.",
+    "summary": "{sections} grup · {checks} madde",
+    "renumber": "Atıfları yeniden numaralandır",
+    "renumberHelp": "Her grubu ve maddeyi sırayla numaralandırır: 1, 1.1, 1.2, 2. Yeni satırlar eklenirken numaralanır, bu yüzden buna yalnızca taşıma sonrası gerek duyarsınız.",
+    "renumberWarning": "Bu liste 1.1.13 gibi mevzuat atıfları kullanıyor. Yeniden numaralandırma bunların yerini alır.",
+    "addSection": "Grup ekle",
+    "addCheck": "Madde ekle",
+    "ref": "Atıf",
+    "sectionName": "Grup adı, örn. Fren sistemi",
+    "sectionNote": "Bu gruptaki maddelerin üstünde görünen isteğe bağlı not",
+    "checkName": "Madde adı, örn. Fren balataları",
+    "reorderSection": "{name} grubunu yeniden sırala",
+    "reorderCheck": "{name} maddesini yeniden sırala",
+    "removeSection": "{name} grubunu kaldır",
+    "removeCheck": "{name} maddesini kaldır",
+    "duplicateCheck": "{name} maddesini çoğalt",
+    "configure": "{name} ayarla",
+    "hideSettings": "{name} ayarlarını gizle",
+    "howRecorded": "Nasıl kaydedilir?",
+    "guidance": "Teknisyen için açıklama",
+    "guidancePlaceholder": "Muayene formunda madde adının altında gösterilir.",
+    "measurement": "Ölçüm",
+    "unit": "Birim",
+    "minimum": "En az",
+    "maximum": "En çok",
+    "outOfRange": "Aralık dışındaysa kusur",
+    "rangeHelp": "Tek yönlü bir sınır için bir ucu boş bırakın, örneğin en çok değeri olmayan en az 1,6 mm. Aralık dışındaki ölçümler otomatik değerlendirilir.",
+    "answers": "Yanıtlar",
+    "answersHelp": "Yanıtları virgülle ayırın.",
+    "mustComplete": "Doldurulması zorunlu",
+    "photoRequired": "Kusur kaydedildiğinde fotoğraf zorunlu",
+    "commonWording": "Sık kullanılan ifadeler",
+    "commonWordingHelp": "Satır başına bir ifade. Bir teknisyen burada kusur kaydettiğinde yerleşik metinlerden önce sunulurlar; böylece atölyeniz bir arızayı nasıl tanımlayacağında anlaşır.",
+    "inputType": {
+      "condition": "Kusur değerlendirmesi",
+      "measurement": "Ölçüm",
+      "text": "Serbest metin",
+      "choice": "Seçim"
+    },
+    "inputHint": {
+      "condition": "Teknisyen maddeyi kusur ölçeğine göre değerlendirir.",
+      "measurement": "Birimli bir sayı kaydeder, bir aralığa göre otomatik değerlendirilir.",
+      "text": "Yazılı bir gözlem kaydeder, örneğin bir lastik markası veya arıza kodu.",
+      "choice": "Teknisyen sabit bir listeden bir yanıt seçer."
+    },
+    "cancel": "İptal",
+    "create": "Şablon oluştur",
+    "saveChanges": "Değişiklikleri kaydet",
+    "created": "Şablon oluşturuldu",
+    "updated": "Şablon güncellendi",
+    "saveFailed": "Şablon kaydedilemedi",
+    "severityAttention": "Hafif kusur",
+    "severityFail": "Ağır kusur",
+    "severityDangerous": "Tehlikeli kusur",
+    "answersPlaceholder": "Tam, Kısmi, Yok"
+  },
+  "packages": {
+    "exportTitle": "“{name}” dışa aktar",
+    "exportDescription": "Listenin tamamını dosya olarak indirir: gruplar, maddeler, birimler ve sınırlar. Torqvoice kullanan herkes içe aktarabilir.",
+    "includeWording": "Kendi ifadelerinizi dahil edin",
+    "includeWordingHelp": "Atölyenizin maddelere eklediği ifadeler. Alan kişi için yararlıdır, ama tezgâhta yazılmış serbest metindir ve bir müşteriyi, bir meslektaşı ya da yerel bir düzenlemeyi adlandırabilir. Okumadıysanız kapalı bırakın.",
+    "attributeTo": "Şuna atfet (isteğe bağlı)",
+    "attributePlaceholder": "örn. Egeland Auto",
+    "attributeHelp": "Kuran kişinin nereden geldiğini görmesi için dosyaya yazılır. Hesabınızdan hiçbir şey alınmaz.",
+    "download": "İndir",
+    "exported": "Şablon dışa aktarıldı",
+    "exportFailed": "Bu şablon dışa aktarılamadı",
+    "importTitle": "Liste içe aktar",
+    "importDescription": "Bir Torqvoice paket dosyasından liste ekler. Düzenleyebileceğiniz normal bir şablon olarak gelir ve mevcut bir şablonun yerini asla almaz.",
+    "chooseFile": "Bir paket dosyası seçin",
+    "chooseAnother": "Başka bir dosya seçin",
+    "unreadable": "Bu dosya okunamadı. Torqvoice paketi, bir şablondan dışa aktarılmış bir .json dosyasıdır.",
+    "from": "{author} tarafından",
+    "limitsWarning": "Paylaşılan bir listedeki sınırlar, yazarının belirlediği sınırlardır. Ondan bir şey düzenlemeden önce ulusal kurallarınıza göre denetleyin.",
+    "import": "İçe aktar",
+    "imported": "{count} şablon içe aktarıldı",
+    "importFailed": "Bu paket içe aktarılamadı",
+    "cancel": "İptal"
+  },
+  "list": {
+    "tabAll": "Tümü",
+    "tabInProgress": "Sürüyor",
+    "tabCompleted": "Tamamlandı",
+    "search": "Muayene ara…",
+    "new": "Yeni muayene",
+    "empty": "Muayene bulunamadı.",
+    "vehicle": "Araç",
+    "template": "Şablon",
+    "progress": "İlerleme",
+    "status": "Durum",
+    "date": "Tarih",
+    "statusInProgress": "Sürüyor",
+    "statusCompleted": "Tamamlandı"
+  },
+  "new": {
+    "title": "Yeni muayene",
+    "vehicle": "Araç",
+    "searchVehicles": "Araç ara…",
+    "loadingVehicles": "Araçlar yükleniyor…",
+    "noVehicles": "Araç bulunamadı.",
+    "template": "Şablon",
+    "selectTemplate": "Şablon seçin",
+    "manageTemplates": "Şablonları yönet",
+    "cancel": "İptal",
+    "create": "Muayene oluştur",
+    "created": "Muayene oluşturuldu",
+    "createFailed": "Muayene oluşturulamadı"
+  },
+  "shareDialog": {
+    "title": "Muayeneyi paylaş",
+    "description": "Bu muayenenin salt okunur görünümünü müşterinizle paylaşın.",
+    "copyLink": "Bağlantıyı kopyala",
+    "copied": "Bağlantı panoya kopyalandı",
+    "generated": "Paylaşım bağlantısı oluşturuldu",
+    "revoked": "Paylaşım bağlantısı geri alındı"
+  }
+}

+ 241 - 0
scripts/restore-diacritics.py

@@ -0,0 +1,241 @@
+#!/usr/bin/env python3
+"""
+Restores stripped diacritics in the locale files.
+
+Only words whose unaccented form is not itself a valid word in that language
+are touched. Pairs like French supprime/supprimé, Spanish esta/está or Polish
+faktura/fakturą are real distinct words and are left alone: a script cannot
+tell which one a given string meant, and guessing would corrupt correct text.
+
+Every replacement below was taken from spellings the same locale already uses
+elsewhere, so nothing is invented.
+"""
+import json
+import pathlib
+import re
+import sys
+
+ROOT = pathlib.Path("/home/sinamics/torqvoice/messages")
+
+FIXES = {
+    "fr": {
+        "parametres": "paramètres", "parametre": "paramètre", "echec": "échec",
+        "donnees": "données", "defaut": "défaut", "numero": "numéro",
+        "numeros": "numéros", "enregistres": "enregistrés", "vehicules": "véhicules",
+        "vehicule": "véhicule", "pieces": "pièces", "piece": "pièce",
+        "acces": "accès", "modele": "modèle", "modeles": "modèles",
+        "etre": "être", "definitivement": "définitivement", "equipe": "équipe",
+        "telephone": "téléphone", "details": "détails", "telecharger": "télécharger",
+        "telechargement": "téléchargement", "apercu": "aperçu", "creer": "créer",
+        "creez": "créez", "creation": "création", "annulee": "annulée",
+        "desactiver": "désactiver", "verifier": "vérifier", "verifiez": "vérifiez",
+        "verification": "vérification", "terminee": "terminée",
+        "fonctionnalites": "fonctionnalités", "tete": "tête",
+        "selectionnez": "sélectionnez", "selectionner": "sélectionner",
+        "deselectionner": "désélectionner", "succes": "succès", "region": "région",
+        "echouee": "échouée", "envoyes": "envoyés", "envoyee": "envoyée",
+        "pret": "prêt", "prete": "prête", "echeance": "échéance", "annee": "année",
+        "irreversible": "irréversible", "partagees": "partagées",
+        "associees": "associées", "definir": "définir", "definitions": "définitions",
+        "generees": "générées", "generer": "générer", "generation": "génération",
+        "deja": "déjà", "debut": "début", "creee": "créée",
+        "caracteres": "caractères", "gerer": "gérer", "proprietaire": "propriétaire",
+        "systeme": "système", "secrete": "secrète", "securite": "sécurité",
+        "reessayer": "réessayer", "desactivee": "désactivée",
+        "visibilite": "visibilité", "kilometrage": "kilométrage",
+        "controle": "contrôle", "enregistree": "enregistrée", "derniere": "dernière",
+        "reparations": "réparations", "liee": "liée", "configuree": "configurée",
+        "reception": "réception", "recu": "reçu", "categories": "catégories",
+        "recuperer": "récupérer", "egalement": "également", "hote": "hôte",
+        "expediteur": "expéditeur", "reinitialiser": "réinitialiser",
+        "eligibles": "éligibles", "reussie": "réussie",
+        "supplementaire": "supplémentaire", "salgsvilkar": "salgsvilkår",
+    },
+    "es": {
+        "configuracion": "configuración", "contrasena": "contraseña",
+        "contrasenas": "contraseñas", "vehiculo": "vehículo",
+        "vehiculos": "vehículos", "numero": "número", "numeros": "números",
+        "codigo": "código", "organizacion": "organización",
+        "electronico": "electrónico", "invitacion": "invitación",
+        "aplicacion": "aplicación", "sesion": "sesión", "ordenes": "órdenes",
+        "pagina": "página", "titulo": "título", "accion": "acción",
+        "telefono": "teléfono", "dias": "días",
+        "automaticamente": "automáticamente", "cotizacion": "cotización",
+        "importacion": "importación", "tecnico": "técnico",
+        "inspeccion": "inspección", "conexion": "conexión",
+        "mensajeria": "mensajería", "ningun": "ningún", "eliminara": "eliminará",
+        "proximo": "próximo", "verificacion": "verificación",
+        "direccion": "dirección", "region": "región", "diseno": "diseño",
+        "informacion": "información", "garantia": "garantía",
+        "imagenes": "imágenes", "gestion": "gestión", "valido": "válido",
+        "linea": "línea", "actualizacion": "actualización",
+        "encontro": "encontró", "suscripcion": "suscripción",
+        "digitos": "dígitos", "facturacion": "facturación", "mecanico": "mecánico",
+        "tamano": "tamaño", "descripcion": "descripción", "terminos": "términos",
+        "miercoles": "miércoles", "sabado": "sábado", "busqueda": "búsqueda",
+        "reparacion": "reparación", "aqui": "aquí", "traves": "través",
+        "conservara": "conservará", "atras": "atrás",
+        "continuacion": "continuación", "categorias": "categorías",
+        "establecera": "establecerá", "ultima": "última", "tambien": "también",
+        "implicito": "implícito", "produccion": "producción",
+        "intentelo": "inténtelo", "envio": "envío", "perderan": "perderán",
+        "vacio": "vacío", "visualizacion": "visualización",
+        "minusculas": "minúsculas", "maximo": "máximo",
+        "transmision": "transmisión", "salgsvilkar": "salgsvilkår",
+    },
+    "pt-BR": {
+        "configuracoes": "configurações", "configuracao": "configuração",
+        "servico": "serviço", "servicos": "serviços", "funcao": "função",
+        "orcamentos": "orçamentos", "orcamento": "orçamento", "numero": "número",
+        "numeros": "números", "organizacao": "organização",
+        "organizacoes": "organizações", "padrao": "padrão", "veiculos": "veículos",
+        "veiculo": "veículo", "licenca": "licença", "codigo": "código",
+        "codigos": "códigos", "autenticacao": "autenticação",
+        "condicoes": "condições", "pecas": "peças", "peca": "peça",
+        "conteudo": "conteúdo", "historico": "histórico", "sera": "será",
+        "inventario": "inventário", "bancaria": "bancária",
+        "manutencao": "manutenção", "acao": "ação", "usuario": "usuário",
+        "usuarios": "usuários", "visualizacao": "visualização",
+        "importacao": "importação", "conexao": "conexão", "conexoes": "conexões",
+        "voce": "você", "serao": "serão", "concluida": "concluída",
+        "concluido": "concluído", "aparencia": "aparência",
+        "cabecalho": "cabeçalho", "tecnico": "técnico", "inspecao": "inspeção",
+        "inspecoes": "inspeções", "excluido": "excluído", "excluida": "excluída",
+        "excluira": "excluirá", "proximo": "próximo", "proxima": "próxima",
+        "proximos": "próximos", "verificacao": "verificação",
+        "endereco": "endereço", "regiao": "região", "informacoes": "informações",
+        "liquido": "líquido", "alteracoes": "alterações", "comecar": "começar",
+        "atualizacao": "atualização", "transferencia": "transferência",
+        "inicio": "início", "pagina": "página", "proprietario": "proprietário",
+        "proprio": "próprio", "estao": "estão", "horario": "horário",
+        "horarios": "horários", "permissao": "permissão",
+        "combustivel": "combustível", "seguranca": "segurança",
+        "exibicao": "exibição", "area": "área", "preco": "preço",
+        "precos": "preços", "incluido": "incluído", "relatorio": "relatório",
+        "terca": "terça", "sabado": "sábado", "cartao": "cartão",
+        "possivel": "possível", "definira": "definirá", "tambem": "também",
+        "implicito": "implícito", "producao": "produção", "forcar": "forçar",
+        "dominio": "domínio", "digitos": "dígitos", "invalido": "inválido",
+        "solicitacoes": "solicitações", "joao": "joão", "secoes": "seções",
+        "definicoes": "definições", "perderao": "perderão", "rodape": "rodapé",
+        "preferencia": "preferência", "elegiveis": "elegíveis",
+        "expiracao": "expiração", "minusculas": "minúsculas",
+        "atribuida": "atribuída", "ardosia": "ardósia", "ultima": "última",
+        "destinatario": "destinatário", "salgsvilkar": "salgsvilkår",
+    },
+    "pl": {
+        "usun": "usuń", "usunac": "usunąć", "wiadomosci": "wiadomości",
+        "rekordow": "rekordów", "wprowadz": "wprowadź",
+        "wprowadzic": "wprowadzić", "utworz": "utwórz", "utworzyc": "utworzyć",
+        "ustawien": "ustawień", "dostep": "dostęp", "dostepu": "dostępu",
+        "domyslna": "domyślna", "domyslne": "domyślne", "domyslny": "domyślny",
+        "domyslnej": "domyślnej", "istniejacych": "istniejących",
+        "istniejace": "istniejące", "czesci": "części", "czesc": "część",
+        "czesciowa": "częściowa", "zapisac": "zapisać",
+        "zaktualizowac": "zaktualizować", "zakonczony": "zakończony",
+        "zlecen": "zleceń", "klientow": "klientów", "pokaz": "pokaż",
+        "przeslij": "prześlij", "moze": "może", "mozesz": "możesz",
+        "mozna": "można", "moga": "mogą", "zawartosc": "zawartość",
+        "podglad": "podgląd", "miedzy": "między", "cofnac": "cofnąć",
+        "wyslij": "wyślij", "uzyj": "użyj", "uzyc": "użyć", "uzyto": "użyto",
+        "pomyslnie": "pomyślnie", "uzytkownik": "użytkownik",
+        "uzytkownika": "użytkownika", "uzytkownikami": "użytkownikami",
+        "twoj": "twój", "wyglad": "wygląd", "wartosci": "wartości",
+        "usuniete": "usunięte", "usunieta": "usunięta", "usuniety": "usunięty",
+        "kazdy": "każdy", "kazda": "każda", "kazdej": "każdej",
+        "kazdego": "każdego", "dostawce": "dostawcę", "zostana": "zostaną",
+        "zarzadzaj": "zarządzaj", "zarzadzac": "zarządzać",
+        "zarzadzanie": "zarządzanie", "powiazane": "powiązane", "jesli": "jeśli",
+        "potwierdzic": "potwierdzić", "potwierdz": "potwierdź",
+        "stawke": "stawkę", "zdjecia": "zdjęcia", "udostepnij": "udostępnij",
+        "udostepnione": "udostępnione", "prosze": "proszę",
+        "wyswietl": "wyświetl", "wyswietlane": "wyświetlane",
+        "wyswietlanie": "wyświetlanie", "wyswietlana": "wyświetlana",
+        "wyswietlania": "wyświetlania", "odbierac": "odbierać",
+        "oczekujace": "oczekujące", "nazwe": "nazwę", "ponizej": "poniżej",
+        "przejdz": "przejdź", "rozpoczac": "rozpocząć", "znakow": "znaków",
+        "otworz": "otwórz", "zastosowac": "zastosować", "pobrac": "pobrać",
+        "ktorym": "którym", "ktore": "które", "nastepnie": "następnie",
+        "nastepny": "następny", "nastepna": "następna", "sprobuj": "spróbuj",
+        "firme": "firmę", "zmien": "zmień", "zmienic": "zmienić",
+        "logowac": "logować", "zalogowac": "zalogować",
+        "widocznosc": "widoczność", "beda": "będą", "bedzie": "będzie",
+        "bedziesz": "będziesz", "pojawia": "pojawią", "anulowac": "anulować",
+        "ilosc": "ilość", "sprzedazy": "sprzedaży", "sroda": "środa",
+        "piatek": "piątek", "szablonow": "szablonów", "zadne": "żadne",
+        "przegladac": "przeglądać", "przegladarka": "przeglądarka",
+        "przegladarki": "przeglądarki", "wiecej": "więcej",
+        "dziekujemy": "dziękujemy", "pojazdow": "pojazdów", "swoja": "swoją",
+        "prog": "próg", "miec": "mieć", "ogolne": "ogólne",
+        "zweryfikowac": "zweryfikować", "rowniez": "również",
+        "skonfigurowac": "skonfigurować", "zezwolic": "zezwolić",
+        "wymus": "wymuś", "historie": "historię", "sprawdz": "sprawdź",
+        "przywroc": "przywróć", "pomoca": "pomocą",
+        "bezpieczenstwa": "bezpieczeństwa", "straca": "stracą",
+        "kolejnosc": "kolejność", "kwalifikujace": "kwalifikujące",
+        "wygasniecie": "wygaśnięcie", "podkreslenia": "podkreślenia",
+        "godzine": "godzinę", "sposob": "sposób", "wymagaja": "wymagają",
+        "salgsvilkar": "salgsvilkår",
+    },
+}
+
+
+def match_case(src: str, repl: str) -> str:
+    """Keep the original capitalisation: Numero -> Numéro, NUMERO -> NUMÉRO."""
+    if src.isupper():
+        return repl.upper()
+    if src[:1].isupper():
+        return repl[:1].upper() + repl[1:]
+    return repl
+
+
+def fix_text(text: str, table: dict[str, str], counts: dict[str, int]) -> str:
+    def sub(m: re.Match) -> str:
+        word = m.group(0)
+        repl = table.get(word.lower())
+        if repl is None:
+            return word
+        counts[word.lower()] = counts.get(word.lower(), 0) + 1
+        return match_case(word, repl)
+
+    pattern = re.compile(
+        r"(?<![^\W\d_])(" + "|".join(sorted(map(re.escape, table), key=len, reverse=True)) + r")(?![^\W\d_])",
+        re.IGNORECASE,
+    )
+    return pattern.sub(sub, text)
+
+
+def walk(node, table, counts):
+    if isinstance(node, str):
+        return fix_text(node, table, counts)
+    if isinstance(node, dict):
+        return {k: walk(v, table, counts) for k, v in node.items()}
+    if isinstance(node, list):
+        return [walk(v, table, counts) for v in node]
+    return node
+
+
+def main(write: bool) -> None:
+    grand = 0
+    for locale, table in FIXES.items():
+        counts: dict[str, int] = {}
+        touched = 0
+        for path in sorted((ROOT / locale).glob("*.json")):
+            original = path.read_text(encoding="utf-8")
+            data = json.loads(original)
+            fixed = walk(data, table, counts)
+            out = json.dumps(fixed, ensure_ascii=False, indent=2) + "\n"
+            if json.dumps(data, ensure_ascii=False, indent=2) + "\n" != out:
+                touched += 1
+                if write:
+                    path.write_text(out, encoding="utf-8")
+        total = sum(counts.values())
+        grand += total
+        top = sorted(counts.items(), key=lambda kv: -kv[1])[:6]
+        print(f"{locale:6} {total:5} words restored across {touched:2} files   "
+              + ", ".join(f"{k}->{table[k]} x{v}" for k, v in top))
+    print(f"\nTOTAL: {grand} words restored")
+
+
+if __name__ == "__main__":
+    main(write="--write" in sys.argv)

+ 68 - 0
src/__tests__/i18n/audit-actions.test.ts

@@ -0,0 +1,68 @@
+import { describe, it, expect } from "vitest";
+import fs from "fs";
+import path from "path";
+
+/**
+ * Every audit action the code writes must have a label in every locale.
+ *
+ * A missing one is not a crash: next-intl returns a placeholder, so the raw
+ * key reaches the dashboard and the console fills with MISSING_MESSAGE. That
+ * is exactly how `scheduled_message.create` shipped, so this closes the gap
+ * rather than the individual keys.
+ */
+const ROOT = process.cwd();
+const LOCALES = fs
+  .readdirSync(path.join(ROOT, "messages"))
+  .filter((d) => fs.statSync(path.join(ROOT, "messages", d)).isDirectory());
+
+/** Audit actions are namespaced with a dot or an underscore; bare words in the
+ *  `action:` position belong to permissions, API payloads and test fixtures. */
+function emittedActions(): string[] {
+  const found = new Set<string>();
+  const walk = (dir: string) => {
+    for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
+      const full = path.join(dir, entry.name);
+      if (entry.isDirectory()) {
+        if (entry.name !== "node_modules" && entry.name !== "__tests__") walk(full);
+      } else if (/\.tsx?$/.test(entry.name)) {
+        const src = fs.readFileSync(full, "utf-8");
+        for (const m of src.matchAll(/action:\s*"([a-zA-Z][\w]*\.[\w.]+)"/g)) {
+          found.add(m[1]);
+        }
+      }
+    }
+  };
+  walk(path.join(ROOT, "src"));
+  return [...found].sort();
+}
+
+const ACTIONS = emittedActions();
+
+describe("audit action labels", () => {
+  it("finds the audit actions the code emits", () => {
+    expect(ACTIONS.length).toBeGreaterThan(50);
+    expect(ACTIONS).toContain("inspection.create");
+  });
+
+  it.each(LOCALES)("%s has a label for every emitted action", (locale) => {
+    const actions = JSON.parse(
+      fs.readFileSync(path.join(ROOT, "messages", locale, "audit.json"), "utf-8")
+    ).actions as Record<string, string>;
+
+    const missing = ACTIONS.filter((a) => !(a.replaceAll(".", "_") in actions));
+    expect(missing, `missing in ${locale}: ${missing.join(", ")}`).toEqual([]);
+  });
+
+  it("keeps every locale on the same set of action keys as English", () => {
+    const keysFor = (locale: string) =>
+      Object.keys(
+        JSON.parse(
+          fs.readFileSync(path.join(ROOT, "messages", locale, "audit.json"), "utf-8")
+        ).actions
+      ).sort();
+    const en = keysFor("en");
+    for (const locale of LOCALES) {
+      expect(keysFor(locale), `${locale} drifted from en`).toEqual(en);
+    }
+  });
+});

+ 5 - 5
src/app/(authenticated)/audit-log/audit-log-client.tsx

@@ -93,11 +93,11 @@ export function AuditLogClient({
   const t = useTranslations("audit");
 
   const getActionLabel = (action: string) => {
-    try {
-      return t(`actions.${action.replace(".", "_")}`);
-    } catch {
-      return action;
-    }
+    // next-intl returns a placeholder for a missing message rather than
+    // throwing, so this asks first instead of catching something that never
+    // happens. replaceAll because an action can carry more than one separator.
+    const key = `actions.${action.replaceAll(".", "_")}`;
+    return t.has(key) ? t(key) : action;
   };
 
   const navigate = useCallback(

+ 9 - 7
src/app/(authenticated)/dashboard-client.tsx

@@ -1499,13 +1499,15 @@ export function DashboardClient({
                   const meta = (log as unknown as { metadata?: Record<string, unknown> }).metadata || {};
                   const vehicleDisplay = typeof meta["vehicleDisplay"] === "string" ? (meta["vehicleDisplay"] as string) : undefined;
                   const quoteNumber = typeof meta["quoteNumber"] === "string" ? (meta["quoteNumber"] as string) : undefined;
-                  const actionKey = log.action.replace(".", "_");
-                  let friendlyAction: string;
-                  try {
-                    friendlyAction = tAudit(`actions.${actionKey}`);
-                  } catch {
-                    friendlyAction = log.action;
-                  }
+                  // replaceAll, not replace: an action like
+                  // "scheduled_message.send_now" has more than one separator.
+                  const actionKey = `actions.${log.action.replaceAll(".", "_")}`;
+                  // has(), not try/catch: next-intl reports a missing message
+                  // and returns a placeholder rather than throwing, so a catch
+                  // never runs and the raw key reaches the screen.
+                  const friendlyAction = tAudit.has(actionKey)
+                    ? tAudit(actionKey)
+                    : log.action;
                   const entityLabel = vehicleDisplay ?? quoteNumber ?? log.entityId?.substring(0, 8);
                   return (
                     <div key={log.id} className="px-5 py-3 text-sm flex items-center justify-between">

+ 15 - 14
src/app/(authenticated)/inspections/inspections-client.tsx

@@ -52,10 +52,10 @@ interface TemplateOption {
 }
 
 const statusTabs = [
-  { key: "all", label: "All" },
-  { key: "in_progress", label: "In Progress" },
-  { key: "completed", label: "Completed" },
-];
+  { key: "all", labelKey: "tabAll" },
+  { key: "in_progress", labelKey: "tabInProgress" },
+  { key: "completed", labelKey: "tabCompleted" },
+] as const;
 
 const statusColors: Record<string, string> = {
   in_progress: "bg-blue-500/10 text-blue-500 border-blue-500/20",
@@ -105,6 +105,7 @@ export function InspectionsClient({
   const [isPending, startTransition] = useTransition();
   const [showNewDialog, setShowNewDialog] = useState(false);
   const tcm = useTranslations("common.contextMenu");
+  const t = useTranslations("inspections.list");
 
   const navigate = useCallback(
     (params: Record<string, string | number | undefined>) => {
@@ -160,7 +161,7 @@ export function InspectionsClient({
               size="sm"
               onClick={() => navigate({ status: tab.key === "all" ? undefined : tab.key })}
             >
-              {tab.label}
+              {t(tab.labelKey)}
               {count !== undefined && (
                 <Badge variant="secondary" className="ml-1.5 h-5 min-w-5 px-1 text-xs">
                   {count}
@@ -176,7 +177,7 @@ export function InspectionsClient({
           <form onSubmit={handleSearch} className="relative flex-1 sm:max-w-sm">
             <Search className="absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
             <Input
-              placeholder="Search inspections..."
+              placeholder={t("search")}
               value={searchInput}
               onChange={(e) => setSearchInput(e.target.value)}
               className="pl-9"
@@ -186,7 +187,7 @@ export function InspectionsClient({
         </div>
         <Button size="sm" onClick={() => setShowNewDialog(true)}>
           <Plus className="mr-1 h-3.5 w-3.5" />
-          New Inspection
+          {t("new")}
         </Button>
       </div>
 
@@ -197,23 +198,23 @@ export function InspectionsClient({
             <TableRow>
               <TableHead>
                 <button type="button" className="flex items-center hover:text-foreground" onClick={() => handleSort("vehicle")}>
-                  Vehicle<SortIcon column="vehicle" />
+                  {t("vehicle")}<SortIcon column="vehicle" />
                 </button>
               </TableHead>
               <TableHead className="hidden w-[24%] md:table-cell">
                 <button type="button" className="flex items-center hover:text-foreground" onClick={() => handleSort("template")}>
-                  Template<SortIcon column="template" />
+                  {t("template")}<SortIcon column="template" />
                 </button>
               </TableHead>
-              <TableHead className="w-32">Progress</TableHead>
+              <TableHead className="w-32">{t("progress")}</TableHead>
               <TableHead className="w-28">
                 <button type="button" className="flex items-center hover:text-foreground" onClick={() => handleSort("status")}>
-                  Status<SortIcon column="status" />
+                  {t("status")}<SortIcon column="status" />
                 </button>
               </TableHead>
               <TableHead className="w-24">
                 <button type="button" className="flex items-center hover:text-foreground" onClick={() => handleSort("createdAt")}>
-                  Date<SortIcon column="createdAt" />
+                  {t("date")}<SortIcon column="createdAt" />
                 </button>
               </TableHead>
             </TableRow>
@@ -222,7 +223,7 @@ export function InspectionsClient({
             {data.records.length === 0 ? (
               <TableRow>
                 <TableCell colSpan={5} className="h-32 text-center text-muted-foreground">
-                  No inspections found.
+                  {t("empty")}
                 </TableCell>
               </TableRow>
             ) : (
@@ -253,7 +254,7 @@ export function InspectionsClient({
                   </TableCell>
                   <TableCell>
                     <Badge variant="outline" className={`text-xs ${statusColors[insp.status] || ""}`}>
-                      {insp.status === "in_progress" ? "In Progress" : "Completed"}
+                      {insp.status === "in_progress" ? t("statusInProgress") : t("statusCompleted")}
                     </Badge>
                   </TableCell>
                   <TableCell className="font-mono text-xs">

+ 6 - 4
src/components/barcode-scanner-dialog.tsx

@@ -11,6 +11,7 @@ import {
 import { Button } from '@/components/ui/button'
 import { Input } from '@/components/ui/input'
 import { Camera, Keyboard } from 'lucide-react'
+import { useTranslations } from "next-intl";
 
 interface BarcodeScannerDialogProps {
   open: boolean
@@ -25,6 +26,7 @@ export function BarcodeScannerDialog({
   onScan,
   title = 'Scan Barcode',
 }: BarcodeScannerDialogProps) {
+  const t = useTranslations("common.shared");
   const scannerRef = useRef<HTMLDivElement>(null)
   const html5QrRef = useRef<any>(null)
   const [error, setError] = useState<string | null>(null)
@@ -178,7 +180,7 @@ export function BarcodeScannerDialog({
         <DialogHeader>
           <DialogTitle>{title}</DialogTitle>
           <DialogDescription className="sr-only">
-            Point your camera at a barcode or enter it manually
+            {t("scanHint")}
           </DialogDescription>
         </DialogHeader>
 
@@ -196,7 +198,7 @@ export function BarcodeScannerDialog({
               onClick={() => setShowManual(true)}
             >
               <Keyboard className="mr-2 h-4 w-4" />
-              Enter manually
+              {t("enterManually")}
             </Button>
           </div>
         ) : (
@@ -206,7 +208,7 @@ export function BarcodeScannerDialog({
             )}
             <form onSubmit={handleManualSubmit} className="flex gap-2">
               <Input
-                placeholder="Enter barcode..."
+                placeholder={t("enterBarcode")}
                 value={manualValue}
                 onChange={(e) => setManualValue(e.target.value)}
                 autoFocus
@@ -224,7 +226,7 @@ export function BarcodeScannerDialog({
                 onClick={() => setShowManual(false)}
               >
                 <Camera className="mr-2 h-4 w-4" />
-                Use camera
+                {t("useCamera")}
               </Button>
             )}
           </div>

+ 9 - 7
src/components/data-table-pagination.tsx

@@ -1,5 +1,6 @@
 "use client";
 
+import { useTranslations } from "next-intl";
 import { Button } from "@/components/ui/button";
 import {
   Select,
@@ -30,6 +31,7 @@ export function DataTablePagination({
   totalPages,
   onNavigate,
 }: DataTablePaginationProps) {
+  const t = useTranslations("common.pagination");
   if (total === 0) return null;
 
   const startItem = (page - 1) * pageSize + 1;
@@ -38,9 +40,7 @@ export function DataTablePagination({
   return (
     <div className="flex flex-col items-center justify-between gap-3 sm:flex-row">
       <div className="flex items-center gap-2 text-sm text-muted-foreground">
-        <span>
-          Showing {startItem}-{endItem} of {total}
-        </span>
+        <span>{t("showing", { start: startItem, end: endItem, total })}</span>
         <Select
           value={String(pageSize)}
           onValueChange={(v) => onNavigate({ pageSize: v, page: 1 })}
@@ -54,7 +54,7 @@ export function DataTablePagination({
             <SelectItem value="50">50</SelectItem>
           </SelectContent>
         </Select>
-        <span>per page</span>
+        <span>{t("perPage")}</span>
       </div>
       <div className="flex items-center gap-1">
         <Button
@@ -62,6 +62,7 @@ export function DataTablePagination({
           size="icon"
           className="h-8 w-8"
           disabled={page <= 1}
+          aria-label={t("first")}
           onClick={() => onNavigate({ page: 1 })}
         >
           <ChevronsLeft className="h-4 w-4" />
@@ -71,18 +72,18 @@ export function DataTablePagination({
           size="icon"
           className="h-8 w-8"
           disabled={page <= 1}
+          aria-label={t("previous")}
           onClick={() => onNavigate({ page: page - 1 })}
         >
           <ChevronLeft className="h-4 w-4" />
         </Button>
-        <span className="px-3 text-sm">
-          Page {page} of {totalPages}
-        </span>
+        <span className="px-3 text-sm">{t("pageOf", { page, pages: totalPages })}</span>
         <Button
           variant="outline"
           size="icon"
           className="h-8 w-8"
           disabled={page >= totalPages}
+          aria-label={t("next")}
           onClick={() => onNavigate({ page: page + 1 })}
         >
           <ChevronRight className="h-4 w-4" />
@@ -92,6 +93,7 @@ export function DataTablePagination({
           size="icon"
           className="h-8 w-8"
           disabled={page >= totalPages}
+          aria-label={t("last")}
           onClick={() => onNavigate({ page: totalPages })}
         >
           <ChevronsRight className="h-4 w-4" />

+ 3 - 1
src/components/demo-banner.tsx

@@ -1,6 +1,8 @@
 import { isDemoMode } from "@/lib/demo";
+import { useTranslations } from "next-intl";
 
 export function DemoBanner() {
+  const t = useTranslations("common.shared");
   if (!isDemoMode) return null;
 
   return (
@@ -10,7 +12,7 @@ export function DemoBanner() {
         href="https://torqvoice.com/docs/installation"
         className="underline underline-offset-2 hover:text-amber-900"
       >
-        Install your own →
+        {t("installOwn")} →
       </a>
     </div>
   );

+ 3 - 1
src/components/glass-modal.tsx

@@ -11,6 +11,7 @@ import {
 import { Button } from "@/components/ui/button";
 import { AlertTriangle, CheckCircle2, Info, XCircle } from "lucide-react";
 import { create } from "zustand";
+import { useTranslations } from "next-intl";
 
 type ModalType = "error" | "success" | "info" | "warning";
 
@@ -40,6 +41,7 @@ const icons: Record<ModalType, React.ReactNode> = {
 };
 
 export function GlassModal() {
+  const t = useTranslations("common.shared");
   const { isOpen, type, title, message, close } = useGlassModal();
 
   return (
@@ -56,7 +58,7 @@ export function GlassModal() {
         </DialogHeader>
         <DialogFooter>
           <Button onClick={close} variant="outline" className="w-full sm:w-auto">
-            Close
+            {t("close")}
           </Button>
         </DialogFooter>
       </DialogContent>

+ 8 - 5
src/components/pwa-install-prompt.tsx

@@ -11,6 +11,7 @@ import {
   SidebarMenuItem,
 } from '@/components/ui/sidebar'
 
+import { useTranslations } from 'next-intl'
 interface BeforeInstallPromptEvent extends Event {
   prompt(): Promise<void>
   userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>
@@ -93,6 +94,7 @@ function useInstallPrompt() {
 }
 
 export function InstallBanner() {
+  const t = useTranslations('common.shared')
   const { canShow, isIOS, install, hide } = useInstallPrompt()
 
   if (!canShow) return null
@@ -109,7 +111,7 @@ export function InstallBanner() {
             className="shrink-0 rounded-lg"
           />
           <div className="min-w-0 flex-1">
-            <p className="text-sm font-medium">Install TorqVoice</p>
+            <p className="text-sm font-medium">{t("installTorqvoice")}</p>
             <p className="truncate text-xs text-muted-foreground">
               {isIOS
                 ? 'Tap Share then "Add to Home Screen"'
@@ -118,13 +120,13 @@ export function InstallBanner() {
           </div>
           {!isIOS && (
             <Button size="sm" onClick={install}>
-              Install
+              {t("install")}
             </Button>
           )}
           <button
             onClick={hide}
             className="shrink-0 rounded-full p-1 text-muted-foreground hover:text-foreground"
-            aria-label="Dismiss"
+            aria-label={t("dismiss")}
           >
             <X className="size-4" />
           </button>
@@ -135,6 +137,7 @@ export function InstallBanner() {
 }
 
 export function SidebarInstallButton() {
+  const t = useTranslations('common.shared')
   const { canShow, isIOS, install, hide } = useInstallPrompt()
 
   if (!canShow) return null
@@ -145,10 +148,10 @@ export function SidebarInstallButton() {
         <SidebarMenuItem>
           <SidebarMenuButton
             onClick={isIOS ? hide : install}
-            tooltip={isIOS ? 'Tap Share > Add to Home Screen' : 'Install App'}
+            tooltip={isIOS ? t("iosInstallHint") : t("installApp")}
           >
             <Download className="size-4" />
-            <span className="font-medium">Install App</span>
+            <span className="font-medium">{t("installApp")}</span>
           </SidebarMenuButton>
         </SidebarMenuItem>
       </SidebarMenu>

+ 4 - 2
src/components/upgrade-prompt.tsx

@@ -9,6 +9,7 @@ import {
 } from "@/components/ui/card";
 import { Button } from "@/components/ui/button";
 import { Zap } from "lucide-react";
+import { useTranslations } from "next-intl";
 
 export function UpgradePrompt({
   feature,
@@ -19,6 +20,7 @@ export function UpgradePrompt({
   title?: string;
   description?: string;
 }) {
+  const t = useTranslations("common.shared");
   const cloud = isCloudMode();
 
   return (
@@ -37,11 +39,11 @@ export function UpgradePrompt({
         <CardContent>
           {cloud ? (
             <Button asChild>
-              <Link href="/settings/subscription">View Plans</Link>
+              <Link href="/settings/subscription">{t("viewPlans")}</Link>
             </Button>
           ) : (
             <Button asChild>
-              <Link href="/settings/license">Manage License</Link>
+              <Link href="/settings/license">{t("manageLicense")}</Link>
             </Button>
           )}
         </CardContent>

+ 14 - 19
src/features/inspections/Components/DefectSuggestions.tsx

@@ -12,24 +12,19 @@ import {
   CommandList,
 } from "@/components/ui/command";
 import { Plus } from "lucide-react";
+import { useTranslations } from "next-intl";
 import {
   rankSuggestions,
   type DefectSeverity,
   type DefectSuggestion,
   type SuggestionCheck,
 } from "../Lib/defectCatalogue";
-import { CONDITION_TOKENS, conditionLabel, type Condition, type SeverityScale } from "../Lib/conditions";
+import { CONDITION_TOKENS, type Condition, type SeverityScale } from "../Lib/conditions";
+import { useConditionLabels } from "../Lib/useConditionLabels";
 
 /** How many phrases sit inline before the rest move into the search popover. */
 const INLINE_LIMIT = 5;
 
-const SOURCE_LABEL: Record<DefectSuggestion["source"], string> = {
-  workshop: "Your workshop",
-  history: "Used before",
-  regulation: "Directive 2014/45/EU",
-  general: "General",
-};
-
 function SeverityDot({ severity }: { severity: DefectSeverity }) {
   return (
     <span
@@ -62,6 +57,9 @@ export function DefectSuggestions({
   disabled?: boolean;
   onPick: (suggestion: DefectSuggestion) => void;
 }) {
+  const t = useTranslations("inspections.suggestions");
+  const sourceLabel = (source: DefectSuggestion["source"]) => t(`source.${source}`);
+  const { label: gradeLabel } = useConditionLabels(scale);
   const [open, setOpen] = useState(false);
 
   const suggestions = useMemo(
@@ -91,7 +89,7 @@ export function DefectSuggestions({
   return (
     <div className="mt-2">
       <p className="text-muted-foreground text-xs" id={`${check.name}-suggestions-label`}>
-        Common defects
+        {t("title")}
       </p>
       <div
         role="group"
@@ -104,11 +102,8 @@ export function DefectSuggestions({
             type="button"
             disabled={disabled}
             onClick={() => pick(suggestion)}
-            aria-label={`Add "${suggestion.text}" and grade as ${conditionLabel(
-              suggestion.severity,
-              scale
-            )}`}
-            title={SOURCE_LABEL[suggestion.source]}
+            aria-label={t("add", { text: suggestion.text, grade: gradeLabel(suggestion.severity) })}
+            title={sourceLabel(suggestion.source)}
             className="bg-background hover:bg-muted focus-visible:ring-ring inline-flex max-w-full items-center gap-1.5 rounded-full border px-2.5 py-1.5 text-xs transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:opacity-50"
           >
             <SeverityDot severity={suggestion.severity} />
@@ -127,19 +122,19 @@ export function DefectSuggestions({
                 className="h-[30px] rounded-full px-2.5 text-xs"
               >
                 <Plus className="mr-1 h-3 w-3" aria-hidden="true" />
-                More ({suggestions.length - inline.length})
+                {t("more", { count: suggestions.length - inline.length })}
               </Button>
             </PopoverTrigger>
             <PopoverContent className="w-[min(28rem,90vw)] p-0" align="start">
               <Command>
-                <CommandInput placeholder="Search defect wording" />
+                <CommandInput placeholder={t("search")} />
                 <CommandList>
-                  <CommandEmpty>No matching phrase.</CommandEmpty>
+                  <CommandEmpty>{t("empty")}</CommandEmpty>
                   {(["workshop", "history", "regulation", "general"] as const).map((source) => {
                     const group = suggestions.filter((s) => s.source === source);
                     if (group.length === 0) return null;
                     return (
-                      <CommandGroup key={source} heading={SOURCE_LABEL[source]}>
+                      <CommandGroup key={source} heading={sourceLabel(source)}>
                         {group.map((suggestion) => (
                           <CommandItem
                             key={`${source}-${suggestion.text}`}
@@ -150,7 +145,7 @@ export function DefectSuggestions({
                             <SeverityDot severity={suggestion.severity} />
                             <span className="flex-1">{suggestion.text}</span>
                             <span className="text-muted-foreground shrink-0 text-xs">
-                              {conditionLabel(suggestion.severity, scale)}
+                              {gradeLabel(suggestion.severity)}
                             </span>
                           </CommandItem>
                         ))}

+ 42 - 42
src/features/inspections/Components/InspectionCertificateCard.tsx

@@ -24,6 +24,7 @@ import {
   SelectValue,
 } from "@/components/ui/select";
 import { FileCheck2, Loader2, RotateCcw, UserPlus } from "lucide-react";
+import { useTranslations } from "next-intl";
 import { toast } from "sonner";
 import { updateInspectionDetails } from "../Actions/inspectionActions";
 import { createTechnician } from "@/features/workboard/Actions/technicianActions";
@@ -55,6 +56,7 @@ function AddTechnicianDialog({
   onOpenChange: (open: boolean) => void;
   onCreated: (technician: TechnicianOption) => void;
 }) {
+  const t = useTranslations("inspections.certificate");
   const [name, setName] = useState("");
   const [isPending, startTransition] = useTransition();
   const fieldId = useId();
@@ -67,11 +69,11 @@ function AddTechnicianDialog({
       const result = await createTechnician({ name: trimmed, color: "#3b82f6" });
       if (result.success && result.data) {
         onCreated({ id: result.data.id, name: result.data.name, color: result.data.color });
-        toast.success(`${result.data.name} added and selected`);
+        toast.success(t("technicianAdded", { name: result.data.name }));
         setName("");
         onOpenChange(false);
       } else {
-        toast.error(result.error || "Could not add the technician");
+        toast.error(result.error || t("technicianFailed"));
       }
     });
   };
@@ -80,31 +82,31 @@ function AddTechnicianDialog({
     <Dialog open={open} onOpenChange={onOpenChange}>
       <DialogContent className="sm:max-w-sm">
         <DialogHeader>
-          <DialogTitle>Add technician</DialogTitle>
+          <DialogTitle>{t("addTechnicianTitle")}</DialogTitle>
           <DialogDescription>
-            They join the workshop roster and can be assigned work as well as inspections.
+            {t("addTechnicianBody")}
           </DialogDescription>
         </DialogHeader>
         <form onSubmit={submit} className="space-y-4">
           <div className="space-y-1.5">
-            <Label htmlFor={`${fieldId}-name`}>Name</Label>
+            <Label htmlFor={`${fieldId}-name`}>{t("technicianName")}</Label>
             {/* eslint-disable-next-line jsx-a11y/no-autofocus */}
             <Input
               id={`${fieldId}-name`}
               value={name}
               onChange={(e) => setName(e.target.value)}
-              placeholder="e.g. Anna Berg"
+              placeholder={t("technicianPlaceholder")}
               autoFocus
               required
             />
           </div>
           <DialogFooter>
             <Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
-              Cancel
+              {t("cancel")}
             </Button>
             <Button type="submit" disabled={isPending || !name.trim()}>
               {isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />}
-              Add and select
+              {t("addAndSelect")}
             </Button>
           </DialogFooter>
         </form>
@@ -147,6 +149,7 @@ export function InspectionCertificateCard({
   mileageLabel: string;
   isCompleted: boolean;
 }) {
+  const t = useTranslations("inspections.certificate");
   const router = useRouter();
   const fieldId = useId();
   const [isPending, startTransition] = useTransition();
@@ -217,10 +220,10 @@ export function InspectionCertificateCard({
         nextTestDue: nextTestDue ? new Date(`${nextTestDue}T00:00:00`) : null,
       });
       if (result.success) {
-        toast.success("Certificate details saved");
+        toast.success(t("saved"));
         router.refresh();
       } else {
-        toast.error(result.error || "Could not save the certificate details");
+        toast.error(result.error || t("saveFailed"));
       }
     });
   };
@@ -231,27 +234,27 @@ export function InspectionCertificateCard({
         <FileCheck2 className="text-muted-foreground mt-0.5 h-4 w-4 shrink-0" aria-hidden="true" />
         <div>
           <h2 id={`${fieldId}-heading`} className="text-sm font-semibold">
-            Certificate details
+            {t("title")}
           </h2>
           <p className="text-muted-foreground text-xs">
-            Required on a roadworthiness certificate under Directive 2014/45/EU, Annex IV.
+            {t("description")}
           </p>
         </div>
       </header>
 
       <div className="grid gap-4 p-4 sm:grid-cols-2">
         <div className="space-y-1.5">
-          <Label htmlFor={`${fieldId}-inspector`}>Inspector</Label>
+          <Label htmlFor={`${fieldId}-inspector`}>{t("inspector")}</Label>
           <Select
             value={technicianId}
             onValueChange={handleTechnicianChange}
             disabled={isCompleted}
           >
             <SelectTrigger id={`${fieldId}-inspector`}>
-              <SelectValue placeholder="Not recorded" />
+              <SelectValue placeholder={t("notRecorded")} />
             </SelectTrigger>
             <SelectContent>
-              <SelectItem value={NO_TECHNICIAN}>Not recorded</SelectItem>
+              <SelectItem value={NO_TECHNICIAN}>{t("notRecorded")}</SelectItem>
               {roster.map((tech) => (
                 <SelectItem key={tech.id} value={tech.id}>
                   <span className="flex items-center gap-2">
@@ -270,7 +273,7 @@ export function InspectionCertificateCard({
                   <SelectItem value={ADD_TECHNICIAN}>
                     <span className="flex items-center gap-2">
                       <UserPlus className="h-3.5 w-3.5" aria-hidden="true" />
-                      Add technician…
+                      {t("addTechnician")}
                     </span>
                   </SelectItem>
                 </>
@@ -281,23 +284,23 @@ export function InspectionCertificateCard({
               name on it; say so rather than showing an empty field. */}
           {technicianId === NO_TECHNICIAN && inspection.inspectorName && (
             <p className="text-muted-foreground text-xs">
-              Recorded as {inspection.inspectorName}
+              {t("recordedAs", { name: inspection.inspectorName })}
             </p>
           )}
         </div>
 
         <div className="space-y-1.5">
-          <Label htmlFor={`${fieldId}-category`}>Vehicle category</Label>
+          <Label htmlFor={`${fieldId}-category`}>{t("vehicleCategory")}</Label>
           <Select
             value={vehicleCategory}
             onValueChange={setVehicleCategory}
             disabled={isCompleted}
           >
             <SelectTrigger id={`${fieldId}-category`}>
-              <SelectValue placeholder="Not recorded" />
+              <SelectValue placeholder={t("notRecorded")} />
             </SelectTrigger>
             <SelectContent>
-              <SelectItem value={NO_TECHNICIAN}>Not recorded</SelectItem>
+              <SelectItem value={NO_TECHNICIAN}>{t("notRecorded")}</SelectItem>
               {VEHICLE_CATEGORIES.map((c) => (
                 <SelectItem key={c.value} value={c.value}>
                   {c.label}
@@ -308,7 +311,7 @@ export function InspectionCertificateCard({
         </div>
 
         <div className="space-y-1.5">
-          <Label htmlFor={`${fieldId}-mileage`}>{mileageLabel} at time of test</Label>
+          <Label htmlFor={`${fieldId}-mileage`}>{t("mileageAtTest", { label: mileageLabel })}</Label>
           <Input
             id={`${fieldId}-mileage`}
             inputMode="numeric"
@@ -319,58 +322,56 @@ export function InspectionCertificateCard({
         </div>
 
         <div className="space-y-1.5">
-          <Label htmlFor={`${fieldId}-certificate`}>Report reference</Label>
+          <Label htmlFor={`${fieldId}-certificate`}>{t("reportReference")}</Label>
           <Input
             id={`${fieldId}-certificate`}
             value={certificateNumber}
             onChange={(e) => setCertificateNumber(e.target.value)}
-            placeholder="Optional"
+            placeholder={t("optional")}
             disabled={isCompleted}
           />
           {!isCompleted && (
             <p className="text-muted-foreground text-xs">
-              Your own reference for this report. If you are an approved test centre issuing a
-              national control slip, put its number here so the two match. Otherwise leave it
-              empty.
+              {t("reportReferenceHelp")}
             </p>
           )}
         </div>
 
         <div className="space-y-1.5 sm:col-span-2">
-          <Label htmlFor={`${fieldId}-location`}>Place of test</Label>
+          <Label htmlFor={`${fieldId}-location`}>{t("placeOfTest")}</Label>
           <Input
             id={`${fieldId}-location`}
             value={testLocation}
             onChange={(e) => setTestLocation(e.target.value)}
-            placeholder={workshopAddress || "Test centre or workshop address"}
+            placeholder={workshopAddress || t("placeholderAddress")}
             disabled={isCompleted}
           />
           {!isCompleted && (
             <p className="text-muted-foreground flex flex-wrap items-center gap-x-2 text-xs">
               {locationOverridden ? (
                 <>
-                  <span>Differs from your workshop address.</span>
+                  <span>{t("differsFromWorkshop")}</span>
                   <button
                     type="button"
                     onClick={() => setTestLocation(workshopAddress)}
                     className="text-primary inline-flex items-center gap-1 hover:underline"
                   >
                     <RotateCcw className="h-3 w-3" aria-hidden="true" />
-                    Use workshop address
+                    {t("useWorkshopAddress")}
                   </button>
                 </>
               ) : workshopAddress ? (
                 <>
-                  <span>From your workshop settings.</span>
+                  <span>{t("fromSettings")}</span>
                   <Link href="/settings/workshop" className="text-primary hover:underline">
-                    Change
+                    {t("change")}
                   </Link>
                 </>
               ) : (
                 <>
-                  <span>No workshop address is set.</span>
+                  <span>{t("noWorkshopAddress")}</span>
                   <Link href="/settings/workshop" className="text-primary hover:underline">
-                    Add one
+                    {t("addOne")}
                   </Link>
                 </>
               )}
@@ -379,20 +380,20 @@ export function InspectionCertificateCard({
         </div>
 
         <div className="space-y-1.5 sm:col-span-2">
-          <Label htmlFor={`${fieldId}-next`}>Next test due</Label>
+          <Label htmlFor={`${fieldId}-next`}>{t("nextTestDue")}</Label>
           <div className="flex flex-wrap items-center gap-2">
             <div className="min-w-[12rem] flex-1">
               <DateInput
                 id={`${fieldId}-next`}
                 value={nextTestDue}
                 onChange={setNextTestDue}
-                placeholder="Not set"
+                placeholder={t("notSet")}
               />
             </div>
             {!isCompleted && (
               <div
                 role="group"
-                aria-label="Set the next test date from the date of this test"
+                aria-label={t("intervalGroup")}
                 className="flex flex-wrap gap-1"
               >
                 {TEST_INTERVALS.map((interval) => {
@@ -409,7 +410,7 @@ export function InspectionCertificateCard({
                           : "bg-background hover:bg-muted"
                       }`}
                     >
-                      {interval.label}
+                      {t(interval.key)}
                     </button>
                   );
                 })}
@@ -418,8 +419,7 @@ export function InspectionCertificateCard({
           </div>
           {!isCompleted && (
             <p className="text-muted-foreground text-xs">
-              Counted from the date of this test. Two years is the Union baseline for cars and
-              light goods vehicles; check the interval your country applies.
+              {t("intervalHelp")}
             </p>
           )}
         </div>
@@ -429,12 +429,12 @@ export function InspectionCertificateCard({
         <div className="flex items-center justify-end gap-3 border-t px-4 py-3">
           {isDirty && (
             <p className="text-muted-foreground text-xs" role="status">
-              Unsaved changes
+              {t("unsaved")}
             </p>
           )}
           <Button type="button" size="sm" onClick={handleSave} disabled={isPending || !isDirty}>
             {isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />}
-            Save details
+            {t("saveDetails")}
           </Button>
         </div>
       )}

+ 30 - 33
src/features/inspections/Components/InspectionItemRow.tsx

@@ -31,6 +31,7 @@ import {
   X,
   XCircle,
 } from "lucide-react";
+import { useTranslations } from "next-intl";
 import { toast } from "sonner";
 import { updateInspectionItem } from "../Actions/inspectionActions";
 import { DefectSuggestions } from "./DefectSuggestions";
@@ -39,13 +40,13 @@ import {
   CONDITION_TOKENS,
   SCALE_STEPS,
   conditionGrade,
-  gradedConditionLabel,
   formatRange,
   gradeMeasurement,
   isDefect,
   type Condition,
   type SeverityScale,
 } from "../Lib/conditions";
+import { useConditionLabels } from "../Lib/useConditionLabels";
 
 export interface InspectionItemData {
   id: string;
@@ -113,6 +114,7 @@ function GradeControl({
   disabled?: boolean;
   onChange: (next: Condition, options?: { focusNotes?: boolean }) => void;
 }) {
+  const { graded, hint, short } = useConditionLabels(scale, country);
   const steps: Condition[] = [...SCALE_STEPS[scale], "not_inspected"];
   const refs = useRef<(HTMLButtonElement | null)[]>([]);
 
@@ -150,7 +152,7 @@ function GradeControl({
         const Icon = CONDITION_ICONS[step];
         const checked = value === step;
         const grade = conditionGrade(step, scale, country);
-        const text = step === "not_inspected" ? "N/A" : token.short;
+        const text = short(step);
         return (
           <button
             key={step}
@@ -160,7 +162,7 @@ function GradeControl({
             type="button"
             role="radio"
             aria-checked={checked}
-            aria-label={`${gradedConditionLabel(step, scale, country)}. ${token.hint}`}
+            aria-label={`${graded(step)}. ${hint(step)}`}
             tabIndex={index === activeIndex ? 0 : -1}
             disabled={disabled}
             onClick={() => onChange(step)}
@@ -204,6 +206,7 @@ export function InspectionItemRow({
   /** Reports the autosave lifecycle so the page can show whether work is safe. */
   onSaveState?: (itemId: string, state: "saving" | "saved" | "error") => void;
 }) {
+  const t = useTranslations("inspections.item");
   const fieldId = useId();
   const nameId = `${fieldId}-name`;
 
@@ -260,7 +263,7 @@ export function InspectionItemRow({
         onSaveState?.(item.id, "saved");
       } else {
         onSaveState?.(item.id, "error");
-        toast.error(result.error || "Could not save this check");
+        toast.error(result.error || t("saveFailed"));
       }
     });
   };
@@ -348,7 +351,7 @@ export function InspectionItemRow({
         });
         const data = await res.json();
         if (data.url) uploaded.push(data.url);
-        else toast.error(data.error || `Could not upload ${file.name}`);
+        else toast.error(data.error || t("uploadFailedFor", { name: file.name }));
       }
       if (uploaded.length > 0) {
         const next = [...imageUrls, ...uploaded];
@@ -356,7 +359,7 @@ export function InspectionItemRow({
         save({ imageUrls: next });
       }
     } catch {
-      toast.error("Upload failed");
+      toast.error(t("uploadFailed"));
     } finally {
       setIsUploading(false);
       if (fileInputRef.current) fileInputRef.current.value = "";
@@ -412,12 +415,12 @@ export function InspectionItemRow({
             )}
             <span>{item.name}</span>
             {item.required && (
-              <span className="text-muted-foreground text-xs font-normal">(required)</span>
+              <span className="text-muted-foreground text-xs font-normal">{t("required")}</span>
             )}
             {isSaving && (
               <>
                 <Loader2 className="text-muted-foreground h-3 w-3 animate-spin" aria-hidden="true" />
-                <span className="sr-only">Saving</span>
+                <span className="sr-only">{t("saving")}</span>
               </>
             )}
           </p>
@@ -446,7 +449,7 @@ export function InspectionItemRow({
               aria-expanded={showNotes}
               onClick={() => setShowNotes((v) => !v)}
             >
-              {showNotes ? "Hide note" : "Add note"}
+              {showNotes ? t("hideNote") : t("addNote")}
             </Button>
             <Button
               type="button"
@@ -455,7 +458,7 @@ export function InspectionItemRow({
               className="h-9 w-9"
               disabled={isCompleted || isUploading}
               onClick={() => fileInputRef.current?.click()}
-              aria-label={`Add a photo or video to ${item.name}`}
+              aria-label={t("addPhoto", { name: item.name })}
             >
               {isUploading ? (
                 <Loader2 className="h-4 w-4 animate-spin" aria-hidden="true" />
@@ -476,12 +479,12 @@ export function InspectionItemRow({
         </div>
       </div>
 
-      {/* Recorded value */}
+      {/* {t("recordedValue")} */}
       {inputType === "measurement" && (
         <div className="mt-3 flex flex-wrap items-end gap-2">
           <div className="space-y-1">
             <label htmlFor={`${fieldId}-value`} className="text-muted-foreground text-xs">
-              Reading{item.unit ? ` (${item.unit})` : ""}
+              {item.unit ? t("readingUnit", { unit: item.unit }) : t("reading")}
             </label>
             <Input
               id={`${fieldId}-value`}
@@ -496,7 +499,7 @@ export function InspectionItemRow({
           </div>
           {range && (
             <p className="text-muted-foreground pb-3 text-xs">
-              Limit: <span className="font-medium">{range}</span>
+              <span className="font-medium">{t("limit", { range })}</span>
             </p>
           )}
         </div>
@@ -505,7 +508,7 @@ export function InspectionItemRow({
       {inputType === "text" && (
         <div className="mt-3 space-y-1">
           <label htmlFor={`${fieldId}-text`} className="text-muted-foreground text-xs">
-            Recorded value
+            {t("recordedValue")}
           </label>
           <Input
             id={`${fieldId}-text`}
@@ -521,7 +524,7 @@ export function InspectionItemRow({
       {inputType === "choice" && (item.choices?.length ?? 0) > 0 && (
         <div className="mt-3 space-y-1">
           <label htmlFor={`${fieldId}-choice`} className="text-muted-foreground text-xs">
-            Answer
+            {t("answer")}
           </label>
           <Select
             value={textValue || undefined}
@@ -532,7 +535,7 @@ export function InspectionItemRow({
             }}
           >
             <SelectTrigger id={`${fieldId}-choice`} className="h-11 w-full sm:w-64">
-              <SelectValue placeholder="Select an answer" />
+              <SelectValue placeholder={t("selectAnswer")} />
             </SelectTrigger>
             <SelectContent>
               {item.choices?.map((choice) => (
@@ -548,7 +551,7 @@ export function InspectionItemRow({
       {showNotes && (
         <div className="mt-3 space-y-1">
           <label htmlFor={`${fieldId}-notes`} className="sr-only">
-            Notes for {item.name}
+            {t("notesFor", { name: item.name })}
           </label>
           <Textarea
             id={`${fieldId}-notes`}
@@ -559,18 +562,14 @@ export function InspectionItemRow({
             disabled={isCompleted}
             aria-invalid={notesRequired}
             aria-describedby={describedBy || undefined}
-            placeholder={
-              notesRequired
-                ? "Describe the defect — this appears on the report"
-                : "Add a note (optional)"
-            }
+            placeholder={notesRequired ? t("describeDefect") : t("optionalNote")}
             className={`min-h-[68px] text-sm ${
               notesRequired ? "border-destructive focus-visible:ring-destructive" : ""
             }`}
           />
           {notesRequired && (
             <p id={`${fieldId}-notes-error`} className="text-destructive text-xs">
-              Describe the defect before this grade is saved.
+              {t("notesRequired")}
             </p>
           )}
           {!isCompleted && (
@@ -592,8 +591,7 @@ export function InspectionItemRow({
 
       {needsPhoto && (
         <p id={`${fieldId}-photo-error`} className="text-destructive mt-2 text-xs">
-          A photo of the defect is required here. The inspection cannot be completed until one
-          is attached.
+          {t("photoRequired")}
         </p>
       )}
 
@@ -609,12 +607,12 @@ export function InspectionItemRow({
                   type="button"
                   onClick={() => onOpenImage(url)}
                   className="focus-visible:ring-ring block overflow-hidden rounded-lg border focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none"
-                  aria-label={`View photo ${index + 1} of ${item.name} full size`}
+                  aria-label={t("viewPhoto", { index: index + 1, name: item.name })}
                 >
                   {/* eslint-disable-next-line @next/next/no-img-element */}
                   <img
                     src={url}
-                    alt={`${item.name}, photo ${index + 1}`}
+                    alt={t("photoAlt", { name: item.name, index: index + 1 })}
                     className="h-20 w-20 object-cover"
                   />
                 </button>
@@ -626,7 +624,7 @@ export function InspectionItemRow({
                   size="icon"
                   className="absolute -top-2 -right-2 h-6 w-6 rounded-full"
                   onClick={() => handleRemoveFile(index)}
-                  aria-label={`Remove photo ${index + 1} from ${item.name}`}
+                  aria-label={t("removePhoto", { index: index + 1, name: item.name })}
                 >
                   <X className="h-3 w-3" aria-hidden="true" />
                 </Button>
@@ -639,14 +637,13 @@ export function InspectionItemRow({
       <AlertDialog open={showClearDialog} onOpenChange={setShowClearDialog}>
         <AlertDialogContent>
           <AlertDialogHeader>
-            <AlertDialogTitle>Clear this grade?</AlertDialogTitle>
+            <AlertDialogTitle>{t("clearTitle")}</AlertDialogTitle>
             <AlertDialogDescription>
-              The note recorded against &ldquo;{item.name}&rdquo; will be removed and the check
-              goes back to not inspected.
+              {t("clearBody", { name: item.name })}
             </AlertDialogDescription>
           </AlertDialogHeader>
           <AlertDialogFooter>
-            <AlertDialogCancel>Cancel</AlertDialogCancel>
+            <AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
             <AlertDialogAction
               className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
               onClick={() => {
@@ -658,7 +655,7 @@ export function InspectionItemRow({
                 save({ condition: "not_inspected", notes: "" });
               }}
             >
-              Clear
+              {t("clear")}
             </AlertDialogAction>
           </AlertDialogFooter>
         </AlertDialogContent>

+ 94 - 91
src/features/inspections/Components/InspectionPageClient.tsx

@@ -38,6 +38,7 @@ import {
   Share2,
   Trash2,
 } from "lucide-react";
+import { useTranslations } from "next-intl";
 import { toast } from "sonner";
 import {
   completeInspection,
@@ -57,15 +58,14 @@ import { useServiceType } from "@/components/service-type-context";
 import {
   CONDITION_TOKENS,
   TEST_RESULT_TOKENS,
-  conditionLabel,
   countConditions,
-  gradedConditionLabel,
   deriveTestResult,
   isDefect,
   worstCondition,
   type Condition,
   type SeverityScale,
 } from "../Lib/conditions";
+import { useConditionLabels } from "../Lib/useConditionLabels";
 import { describeBlocker, findCompletionBlockers } from "../Lib/completion";
 
 export interface InspectionData {
@@ -129,8 +129,10 @@ const isVideo = (url: string) => /\.(mp4|webm|mov)$/i.test(url);
 /** Segmented bar showing how the checks are distributed across the grades. */
 function ProgressRail({
   counts,
+  label,
 }: {
   counts: ReturnType<typeof countConditions>;
+  label: string;
 }) {
   const segments = (["pass", "attention", "fail", "dangerous"] as const)
     .map((key) => ({ key, value: counts[key] }))
@@ -140,7 +142,7 @@ function ProgressRail({
     <div
       className="bg-muted flex h-2.5 w-full overflow-hidden rounded-full"
       role="img"
-      aria-label={`${counts.inspected} of ${counts.total} checks graded: ${counts.pass} no defect, ${counts.attention} minor, ${counts.fail} major, ${counts.dangerous} dangerous.`}
+      aria-label={label}
     >
       {segments.map((segment) => (
         <div
@@ -156,11 +158,11 @@ function ProgressRail({
 function CountChip({
   condition,
   value,
-  scale,
+  label,
 }: {
   condition: Condition;
   value: number;
-  scale: SeverityScale;
+  label: string;
 }) {
   const token = CONDITION_TOKENS[condition];
   return (
@@ -168,7 +170,7 @@ function CountChip({
       <span className={`h-2.5 w-2.5 shrink-0 rounded-full ${token.bar}`} aria-hidden="true" />
       <span className="text-xs">
         <span className="font-semibold">{value}</span>{" "}
-        <span className="text-muted-foreground">{conditionLabel(condition, scale)}</span>
+        <span className="text-muted-foreground">{label}</span>
       </span>
     </div>
   );
@@ -190,6 +192,7 @@ export function InspectionPageClient({
   technicians?: TechnicianOption[];
   workshopAddress?: string;
 }) {
+  const t = useTranslations("inspections.page");
   const router = useRouter();
   const { formatDate } = useFormatDate();
   const serviceType = useServiceType();
@@ -223,7 +226,8 @@ export function InspectionPageClient({
   const isCompleted = inspection.status === "completed";
   const scale: SeverityScale = inspection.template.severityScale === "basic" ? "basic" : "eu";
   const country = inspection.template.country ?? null;
-  const mileageLabel = serviceType === "marine" ? "Engine hours" : "Odometer";
+  const { label: gradeLabel, graded, result: resultLabel, resultDetail } = useConditionLabels(scale, country);
+  const mileageLabel = serviceType === "marine" ? t("engineHours") : t("odometer");
 
   const sections = useMemo(() => {
     const sorted = [...inspection.items].sort((a, b) => a.sortOrder - b.sortOrder);
@@ -308,7 +312,7 @@ export function InspectionPageClient({
   const handleSaveNow = () => {
     (document.activeElement as HTMLElement | null)?.blur();
     if (savingIds.size === 0 && !saveFailed) {
-      toast.success("All changes are saved");
+      toast.success(t("allSaved"));
     }
   };
 
@@ -341,7 +345,7 @@ export function InspectionPageClient({
     if (created.success && created.data) {
       router.push(`/quotes/${created.data.id}`);
     } else {
-      toast.error(created.error || "Failed to create quote");
+      toast.error(created.error || t("quoteFailed"));
       setIsCreatingQuote(false);
     }
   };
@@ -357,7 +361,7 @@ export function InspectionPageClient({
     if (created.success && created.data) {
       router.push(`/vehicles/${created.data.vehicleId}/service/${created.data.id}`);
     } else {
-      toast.error(created.error || "Failed to create work order");
+      toast.error(created.error || t("workOrderFailed"));
       setIsCreatingWorkOrder(false);
     }
   };
@@ -366,12 +370,12 @@ export function InspectionPageClient({
     startTransition(async () => {
       const done = await completeInspection(inspection.id);
       if (done.success) {
-        toast.success("Inspection completed");
+        toast.success(t("completed"));
         setShowCompleteDialog(false);
         setShowShareDialog(true);
         router.refresh();
       } else {
-        toast.error(done.error || "Failed to complete inspection");
+        toast.error(done.error || t("completeFailed"));
       }
     });
   };
@@ -380,11 +384,11 @@ export function InspectionPageClient({
     startTransition(async () => {
       const reopened = await reopenInspection(inspection.id);
       if (reopened.success) {
-        toast.success("Inspection reopened");
+        toast.success(t("reopened"));
         setShowReopenDialog(false);
         router.refresh();
       } else {
-        toast.error(reopened.error || "Failed to reopen inspection");
+        toast.error(reopened.error || t("reopenFailed"));
       }
     });
   };
@@ -393,10 +397,10 @@ export function InspectionPageClient({
     startTransition(async () => {
       const removed = await deleteInspection(inspection.id);
       if (removed.success) {
-        toast.success("Inspection deleted");
+        toast.success(t("deleted"));
         router.push("/inspections");
       } else {
-        toast.error(removed.error || "Failed to delete inspection");
+        toast.error(removed.error || t("deleteFailed"));
       }
     });
   };
@@ -412,7 +416,7 @@ export function InspectionPageClient({
               className="text-muted-foreground hover:text-foreground focus-visible:ring-ring inline-flex h-9 items-center gap-1.5 rounded-md text-sm transition-colors focus-visible:ring-2 focus-visible:outline-none"
             >
               <ArrowLeft className="h-4 w-4" aria-hidden="true" />
-              <span className="hidden sm:inline">Inspections</span>
+              <span className="hidden sm:inline">{t("back")}</span>
             </Link>
             <div className="bg-border h-5 w-px" aria-hidden="true" />
             <div className="min-w-0">
@@ -434,21 +438,21 @@ export function InspectionPageClient({
                   : "border-blue-500/30 bg-blue-500/10 text-blue-700 dark:text-blue-300"
               }
             >
-              {isCompleted ? "Completed" : "In progress"}
+              {isCompleted ? t("statusCompleted") : t("statusInProgress")}
             </Badge>
             {!isCompleted && (
               <div className="mr-1 flex items-center gap-2">
                 <p className="text-muted-foreground hidden text-xs sm:block" role="status">
                   {savingIds.size > 0
-                    ? "Saving…"
+                    ? t("saving")
                     : saveFailed
-                      ? "Some changes did not save"
+                      ? t("saveFailed")
                       : lastSavedAt
                         ? `Saved ${lastSavedAt.toLocaleTimeString([], {
                             hour: "2-digit",
                             minute: "2-digit",
                           })}`
-                        : "Saves as you go"}
+                        : t("savesAsYouGo")}
                 </p>
                 <Button
                   variant="outline"
@@ -461,19 +465,19 @@ export function InspectionPageClient({
                   ) : (
                     <Save className="mr-1 h-4 w-4" aria-hidden="true" />
                   )}
-                  Save
+                  {t("save")}
                 </Button>
               </div>
             )}
             {isCompleted ? (
               <Button variant="outline" size="sm" onClick={() => setShowReopenDialog(true)}>
                 <RotateCcw className="mr-1 h-4 w-4" aria-hidden="true" />
-                Reopen
+                {t("reopen")}
               </Button>
             ) : (
               <Button size="sm" onClick={() => setShowCompleteDialog(true)}>
                 <CheckCircle2 className="mr-1 h-4 w-4" aria-hidden="true" />
-                Complete
+                {t("complete")}
               </Button>
             )}
             {/* Rendered whether or not there is anything to raise yet, and
@@ -488,7 +492,7 @@ export function InspectionPageClient({
                 }
               >
                 <Wrench className="mr-1 h-4 w-4" aria-hidden="true" />
-                View work order
+                {t("viewWorkOrder")}
               </Button>
             ) : (
               <Button
@@ -497,7 +501,7 @@ export function InspectionPageClient({
                 disabled={isCreatingWorkOrder || defectItems.length === 0}
                 title={
                   defectItems.length === 0
-                    ? "Available once a defect has been recorded"
+                    ? t("needsDefect")
                     : undefined
                 }
                 onClick={handleCreateWorkOrder}
@@ -507,7 +511,7 @@ export function InspectionPageClient({
                 ) : (
                   <Wrench className="mr-1 h-4 w-4" aria-hidden="true" />
                 )}
-                Create work order
+                {t("createWorkOrder")}
               </Button>
             )}
             {inspection.quotes.length > 0 ? (
@@ -517,7 +521,7 @@ export function InspectionPageClient({
                 onClick={() => router.push(`/quotes/${inspection.quotes[0].id}`)}
               >
                 <FileText className="mr-1 h-4 w-4" aria-hidden="true" />
-                View quote
+                {t("viewQuote")}
               </Button>
             ) : (
               <Button
@@ -526,7 +530,7 @@ export function InspectionPageClient({
                 disabled={isCreatingQuote || defectItems.length === 0}
                 title={
                   defectItems.length === 0
-                    ? "Available once a defect has been recorded"
+                    ? t("needsDefect")
                     : undefined
                 }
                 onClick={handleCreateQuoteFromInspection}
@@ -536,16 +540,16 @@ export function InspectionPageClient({
                 ) : (
                   <FileText className="mr-1 h-4 w-4" aria-hidden="true" />
                 )}
-                Create quote
+                {t("createQuote")}
               </Button>
             )}
             <Button variant="outline" size="sm" onClick={() => setShowShareDialog(true)}>
               <Share2 className="mr-1 h-4 w-4" aria-hidden="true" />
-              Share
+              {t("share")}
             </Button>
             <DropdownMenu>
               <DropdownMenuTrigger asChild>
-                <Button variant="ghost" size="icon" className="h-9 w-9" aria-label="More actions">
+                <Button variant="ghost" size="icon" className="h-9 w-9" aria-label={t("moreActions")}>
                   <MoreVertical className="h-4 w-4" aria-hidden="true" />
                 </Button>
               </DropdownMenuTrigger>
@@ -556,12 +560,12 @@ export function InspectionPageClient({
                   }
                 >
                   <Download className="mr-2 h-4 w-4" aria-hidden="true" />
-                  Download certificate
+                  {t("downloadCertificate")}
                 </DropdownMenuItem>
                 <DropdownMenuItem asChild>
                   <Link href="/settings/templates?tab=inspections">
                     <Settings2 className="mr-2 h-4 w-4" aria-hidden="true" />
-                    Manage templates
+                    {t("manageTemplates")}
                   </Link>
                 </DropdownMenuItem>
                 <DropdownMenuSeparator />
@@ -570,7 +574,7 @@ export function InspectionPageClient({
                   onClick={() => setShowDeleteDialog(true)}
                 >
                   <Trash2 className="mr-2 h-4 w-4" aria-hidden="true" />
-                  Delete
+                  {t("delete")}
                 </DropdownMenuItem>
               </DropdownMenuContent>
             </DropdownMenu>
@@ -587,12 +591,12 @@ export function InspectionPageClient({
             className={`rounded-lg border p-4 ${resultToken.soft}`}
           >
             <h2 id="inspection-result" className="text-base font-semibold">
-              {resultToken.label}
+              {resultLabel(result)}
             </h2>
-            <p className="mt-1 text-sm">{resultToken.detail}</p>
+            <p className="mt-1 text-sm">{resultDetail(result)}</p>
             {result === "incomplete" && notInspected > 0 && (
               <p className="mt-1 text-sm">
-                {notInspected} of {counts.total} checks still to grade.
+                {t("stillToGrade", { count: notInspected, total: counts.total })}
               </p>
             )}
           </section>
@@ -604,8 +608,7 @@ export function InspectionPageClient({
                 aria-hidden="true"
               />
               <p className="min-w-0 flex-1 text-sm text-blue-900 dark:text-blue-100">
-                Work order {workOrder.invoiceNumber ?? ""} raised from this inspection on{" "}
-                {formatDate(new Date(workOrder.createdAt))}
+                {t("workOrderRaised", { number: workOrder.invoiceNumber ?? "", date: formatDate(new Date(workOrder.createdAt)) })}
               </p>
               <Button
                 size="sm"
@@ -615,7 +618,7 @@ export function InspectionPageClient({
                   router.push(`/vehicles/${inspection.vehicle.id}/service/${workOrder.id}`)
                 }
               >
-                Open
+                {t("open")}
               </Button>
             </div>
           )}
@@ -628,8 +631,7 @@ export function InspectionPageClient({
                 aria-hidden="true"
               />
               <p className="min-w-0 flex-1 text-sm text-emerald-900 dark:text-emerald-100">
-                Quote created by {inspection.quotes[0].user.name} on{" "}
-                {formatDate(new Date(inspection.quotes[0].createdAt))}
+                {t("quoteCreated", { name: inspection.quotes[0].user.name, date: formatDate(new Date(inspection.quotes[0].createdAt)) })}
               </p>
               <Button
                 size="sm"
@@ -637,7 +639,7 @@ export function InspectionPageClient({
                 className="shrink-0"
                 onClick={() => router.push(`/quotes/${inspection.quotes[0].id}`)}
               >
-                View quote
+                {t("viewQuote")}
               </Button>
             </div>
           ) : pendingQuoteRequest ? (
@@ -648,9 +650,7 @@ export function InspectionPageClient({
               />
               <div className="min-w-0 flex-1">
                 <p className="text-sm font-medium text-amber-900 dark:text-amber-100">
-                  The customer asked for a quote on{" "}
-                  {pendingQuoteRequest.selectedItemIds.length} item
-                  {pendingQuoteRequest.selectedItemIds.length === 1 ? "" : "s"}
+                  {t("quoteRequested", { count: pendingQuoteRequest.selectedItemIds.length })}
                 </p>
                 {pendingQuoteRequest.message && (
                   <p className="mt-0.5 text-sm text-amber-900/80 dark:text-amber-200/80">
@@ -669,7 +669,7 @@ export function InspectionPageClient({
                   ) : (
                     <FileText className="mr-1 h-4 w-4" aria-hidden="true" />
                   )}
-                  Create quote
+                  {t("createQuote")}
                 </Button>
               </div>
             </div>
@@ -713,11 +713,11 @@ export function InspectionPageClient({
                   <div className="flex items-center gap-3">
                     {isDefect(worst) && (
                       <Badge variant="outline" className={CONDITION_TOKENS[worst].soft}>
-                        {gradedConditionLabel(worst, scale, country)}
+                        {graded(worst)}
                       </Badge>
                     )}
                     <span className="text-muted-foreground text-xs">
-                      {sectionCounts.inspected}/{sectionCounts.total} graded
+                      {t("graded", { graded: sectionCounts.inspected, total: sectionCounts.total })}
                     </span>
                   </div>
                 </header>
@@ -748,29 +748,39 @@ export function InspectionPageClient({
         <aside className="space-y-4 lg:sticky lg:top-36">
           <section aria-labelledby="inspection-progress" className="bg-card rounded-lg border p-4">
             <h2 id="inspection-progress" className="text-sm font-semibold">
-              Progress
+              {t("progress")}
             </h2>
             <p className="mt-1 text-2xl font-semibold tabular-nums">
               {counts.inspected}
               <span className="text-muted-foreground text-base font-normal">/{counts.total}</span>
             </p>
-            <p className="text-muted-foreground text-xs">checks graded</p>
+            <p className="text-muted-foreground text-xs">{t("checksGraded")}</p>
             <div className="mt-3">
-              <ProgressRail counts={counts} />
+              <ProgressRail
+                counts={counts}
+                label={t("progressLabel", {
+                  graded: counts.inspected,
+                  total: counts.total,
+                  pass: counts.pass,
+                  attention: counts.attention,
+                  fail: counts.fail,
+                  dangerous: counts.dangerous,
+                })}
+              />
             </div>
             <div className="mt-3 grid gap-1.5">
-              <CountChip condition="pass" value={counts.pass} scale={scale} />
-              <CountChip condition="attention" value={counts.attention} scale={scale} />
-              <CountChip condition="fail" value={counts.fail} scale={scale} />
+              <CountChip condition="pass" value={counts.pass} label={gradeLabel("pass")} />
+              <CountChip condition="attention" value={counts.attention} label={gradeLabel("attention")} />
+              <CountChip condition="fail" value={counts.fail} label={gradeLabel("fail")} />
               {scale === "eu" && (
-                <CountChip condition="dangerous" value={counts.dangerous} scale={scale} />
+                <CountChip condition="dangerous" value={counts.dangerous} label={gradeLabel("dangerous")} />
               )}
             </div>
           </section>
 
           <nav aria-labelledby="inspection-sections" className="bg-card rounded-lg border p-4">
             <h2 id="inspection-sections" className="text-sm font-semibold">
-              Sections
+              {t("sections")}
             </h2>
             <ul className="mt-2 space-y-0.5">
               {sections.map((section, index) => {
@@ -803,12 +813,12 @@ export function InspectionPageClient({
 
           <section aria-labelledby="inspection-vehicle" className="bg-card rounded-lg border p-4">
             <h2 id="inspection-vehicle" className="text-sm font-semibold">
-              Vehicle
+              {t("vehicle")}
             </h2>
             <dl className="mt-2 space-y-2 text-sm">
               <div>
                 <dt className="text-muted-foreground text-xs">
-                  {serviceType === "marine" ? "Vessel" : "Vehicle"}
+                  {serviceType === "marine" ? t("vessel") : t("vehicle")}
                 </dt>
                 <dd>
                   {inspection.vehicle.year} {inspection.vehicle.make} {inspection.vehicle.model}
@@ -825,14 +835,14 @@ export function InspectionPageClient({
               {inspection.vehicle.licensePlate && (
                 <div>
                   <dt className="text-muted-foreground text-xs">
-                    {serviceType === "marine" ? "Registration" : "Plate"}
+                    {serviceType === "marine" ? t("registration") : t("plate")}
                   </dt>
                   <dd className="font-mono">{inspection.vehicle.licensePlate}</dd>
                 </div>
               )}
               {inspection.vehicle.customer && (
                 <div>
-                  <dt className="text-muted-foreground text-xs">Customer</dt>
+                  <dt className="text-muted-foreground text-xs">{t("customer")}</dt>
                   <dd>
                     <Link
                       href={`/customers/${inspection.vehicle.customer.id}`}
@@ -849,7 +859,7 @@ export function InspectionPageClient({
           <section aria-labelledby="inspection-template" className="bg-card rounded-lg border p-4">
             <div className="flex items-baseline justify-between gap-2">
               <h2 id="inspection-template" className="text-sm font-semibold">
-                Template
+                {t("template")}
               </h2>
               <a
                 href="https://torqvoice.com/docs/features/inspections"
@@ -857,22 +867,22 @@ export function InspectionPageClient({
                 rel="noopener noreferrer"
                 className="text-muted-foreground hover:text-foreground focus-visible:ring-ring shrink-0 rounded text-[11px] transition-colors focus-visible:ring-2 focus-visible:outline-none"
               >
-                Read more →
+                {t("readMore")} →
               </a>
             </div>
             <p className="mt-1 text-sm">{inspection.template.name}</p>
             <p className="text-muted-foreground mt-1 text-xs">
               {scale === "eu"
-                ? "Graded on the EU defect scale from Directive 2014/45/EU."
-                : "Graded pass / attention / fail."}
-              {inspection.template.country ? ` Country: ${inspection.template.country}.` : ""}
+                ? t("scaleEu")
+                : t("scaleBasic")}
+              {country ? ` ${t("country", { code: country })}` : ""}
             </p>
             <Link
               href="/settings/templates?tab=inspections"
               className="text-primary focus-visible:ring-ring mt-3 inline-flex items-center gap-1.5 rounded-md text-sm hover:underline focus-visible:ring-2 focus-visible:outline-none"
             >
               <Settings2 className="h-3.5 w-3.5" aria-hidden="true" />
-              Manage inspection templates
+              {t("manageInspectionTemplates")}
             </Link>
           </section>
         </aside>
@@ -882,14 +892,13 @@ export function InspectionPageClient({
       <AlertDialog open={showCompleteDialog} onOpenChange={setShowCompleteDialog}>
         <AlertDialogContent>
           <AlertDialogHeader>
-            <AlertDialogTitle>Complete this inspection?</AlertDialogTitle>
+            <AlertDialogTitle>{t("completeTitle")}</AlertDialogTitle>
             <AlertDialogDescription asChild>
               <div className="space-y-2">
                 {blockers.length > 0 ? (
                   <>
                     <p className="text-destructive font-medium">
-                      {blockers.length} check{blockers.length === 1 ? "" : "s"} the template marks
-                      as mandatory {blockers.length === 1 ? "is" : "are"} outstanding.
+                      {t("completeBlocked", { count: blockers.length })}
                     </p>
                     <ul className="list-disc space-y-0.5 pl-5">
                       {blockers.slice(0, 8).map((blocker) => (
@@ -902,32 +911,29 @@ export function InspectionPageClient({
                   </>
                 ) : null}
                 <p>
-                  The checks are locked once the inspection is completed, and the result becomes
-                  &ldquo;{TEST_RESULT_TOKENS[deriveTestResult(gradedItems)].label}&rdquo;.
+                  {t("completeBody", { result: resultLabel(deriveTestResult(gradedItems)) })}
                 </p>
                 {notInspected > 0 && (
                   <p className="font-medium text-amber-700 dark:text-amber-400">
-                    {notInspected} check{notInspected === 1 ? " has" : "s have"} not been graded.
+                    {t("completeUngraded", { count: notInspected })}
                   </p>
                 )}
                 {counts.dangerous > 0 && (
                   <p className="text-destructive font-medium">
-                    {counts.dangerous} dangerous defect
-                    {counts.dangerous === 1 ? "" : "s"} recorded. The vehicle must not be used on
-                    the public road.
+                    {t("completeDangerous", { count: counts.dangerous })}
                   </p>
                 )}
               </div>
             </AlertDialogDescription>
           </AlertDialogHeader>
           <AlertDialogFooter>
-            <AlertDialogCancel>Cancel</AlertDialogCancel>
+            <AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
             <AlertDialogAction
               onClick={handleComplete}
               disabled={isPending || blockers.length > 0}
             >
               {isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />}
-              Complete
+              {t("complete")}
             </AlertDialogAction>
           </AlertDialogFooter>
         </AlertDialogContent>
@@ -937,27 +943,25 @@ export function InspectionPageClient({
       <AlertDialog open={showReopenDialog} onOpenChange={setShowReopenDialog}>
         <AlertDialogContent>
           <AlertDialogHeader>
-            <AlertDialogTitle>Reopen this inspection?</AlertDialogTitle>
+            <AlertDialogTitle>{t("reopenTitle")}</AlertDialogTitle>
             <AlertDialogDescription asChild>
               <div className="space-y-2">
                 <p>
-                  The checks become editable again and the result goes back to in progress. The
-                  inspector, certificate number and next test date are kept.
+                  {t("reopenBody")}
                 </p>
                 {inspection.publicToken && (
                   <p>
-                    The customer&apos;s link stays live and will show the result changing as you
-                    edit. Any certificate already downloaded keeps the old result.
+                    {t("reopenShared")}
                   </p>
                 )}
               </div>
             </AlertDialogDescription>
           </AlertDialogHeader>
           <AlertDialogFooter>
-            <AlertDialogCancel>Cancel</AlertDialogCancel>
+            <AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
             <AlertDialogAction onClick={handleReopen} disabled={isPending}>
               {isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />}
-              Reopen
+              {t("reopen")}
             </AlertDialogAction>
           </AlertDialogFooter>
         </AlertDialogContent>
@@ -967,21 +971,20 @@ export function InspectionPageClient({
       <AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
         <AlertDialogContent>
           <AlertDialogHeader>
-            <AlertDialogTitle>Delete this inspection?</AlertDialogTitle>
+            <AlertDialogTitle>{t("deleteTitle")}</AlertDialogTitle>
             <AlertDialogDescription>
-              This permanently deletes the inspection, its grades, notes and photos. It cannot be
-              undone.
+              {t("deleteBody")}
             </AlertDialogDescription>
           </AlertDialogHeader>
           <AlertDialogFooter>
-            <AlertDialogCancel>Cancel</AlertDialogCancel>
+            <AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
             <AlertDialogAction
               onClick={handleDelete}
               disabled={isPending}
               className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
             >
               {isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />}
-              Delete
+              {t("delete")}
             </AlertDialogAction>
           </AlertDialogFooter>
         </AlertDialogContent>

+ 8 - 6
src/features/inspections/Components/InspectionShareDialog.tsx

@@ -10,6 +10,7 @@ import { Input } from "@/components/ui/input";
 import { Checkbox } from "@/components/ui/checkbox";
 import { Label } from "@/components/ui/label";
 import { Check, Copy, Link2, Loader2, Mail, MessageSquare, Unlink } from "lucide-react";
+import { useTranslations } from "next-intl";
 import { toast } from "sonner";
 import {
   generateInspectionPublicLink,
@@ -46,6 +47,7 @@ export function InspectionShareDialog({
   smsEnabled = false,
   emailEnabled = false,
 }: InspectionShareDialogProps) {
+  const t = useTranslations("inspections.shareDialog");
   const router = useRouter();
   const [isPending, startTransition] = useTransition();
   const [copied, setCopied] = useState(false);
@@ -66,7 +68,7 @@ export function InspectionShareDialog({
       const result = await generateInspectionPublicLink(inspectionId);
       if (result.success && result.data) {
         setToken(result.data.token);
-        toast.success("Share link generated");
+        toast.success(t("generated"));
         router.refresh();
       } else {
         toast.error(result.error || "Failed to generate link");
@@ -79,7 +81,7 @@ export function InspectionShareDialog({
       const result = await revokeInspectionPublicLink(inspectionId);
       if (result.success) {
         setToken(null);
-        toast.success("Share link revoked");
+        toast.success(t("revoked"));
         router.refresh();
       } else {
         toast.error(result.error || "Failed to revoke link");
@@ -91,7 +93,7 @@ export function InspectionShareDialog({
     if (!shareUrl) return;
     await navigator.clipboard.writeText(shareUrl);
     setCopied(true);
-    toast.success("Link copied to clipboard");
+    toast.success(t("copied"));
     setTimeout(() => setCopied(false), 2000);
   };
 
@@ -146,9 +148,9 @@ export function InspectionShareDialog({
     <Dialog open={open} onOpenChange={onOpenChange}>
       <DialogContent className="sm:max-w-md">
         <DialogHeader>
-          <DialogTitle>Share Inspection</DialogTitle>
+          <DialogTitle>{t("title")}</DialogTitle>
           <DialogDescription>
-            Share a read-only view of this inspection with your customer.
+            {t("description")}
           </DialogDescription>
         </DialogHeader>
 
@@ -157,7 +159,7 @@ export function InspectionShareDialog({
             <>
               <div className="flex items-center gap-2">
                 <Input value={shareUrl} readOnly className="font-mono text-xs" />
-                <Button size="icon" variant="outline" onClick={handleCopy} aria-label="Copy link">
+                <Button size="icon" variant="outline" onClick={handleCopy} aria-label={t("copyLink")}>
                   {copied ? (
                     <Check className="h-4 w-4 text-emerald-500" />
                   ) : (

+ 6 - 4
src/features/inspections/Components/MediaLightbox.tsx

@@ -2,6 +2,7 @@
 
 import { useCallback, useEffect, useRef } from "react";
 import { ChevronLeft, ChevronRight, X } from "lucide-react";
+import { useTranslations } from "next-intl";
 
 export interface LightboxImage {
   url: string;
@@ -30,6 +31,7 @@ export function MediaLightbox({
   onClose: () => void;
   onNavigate: (index: number) => void;
 }) {
+  const t = useTranslations("inspections.media");
   const containerRef = useRef<HTMLDivElement>(null);
   const previouslyFocused = useRef<HTMLElement | null>(null);
   const touchStartX = useRef<number | null>(null);
@@ -121,7 +123,7 @@ export function MediaLightbox({
       ref={containerRef}
       role="dialog"
       aria-modal="true"
-      aria-label={`Image ${index + 1} of ${images.length}: ${image.caption}`}
+      aria-label={t("viewer", { index: index + 1, total: images.length, caption: image.caption })}
       className="fixed inset-0 z-50 flex items-center justify-center bg-black/90 p-4"
       onTouchStart={handleTouchStart}
       onTouchEnd={handleTouchEnd}
@@ -129,7 +131,7 @@ export function MediaLightbox({
       <button
         type="button"
         onClick={onClose}
-        aria-label="Close image viewer"
+        aria-label={t("close")}
         className="absolute top-3 right-3 z-10 flex h-11 w-11 items-center justify-center rounded-full bg-white/10 text-white transition-colors hover:bg-white/20 focus-visible:ring-2 focus-visible:ring-white focus-visible:outline-none sm:top-4 sm:right-4"
       >
         <X className="h-5 w-5" aria-hidden="true" />
@@ -145,7 +147,7 @@ export function MediaLightbox({
         <button
           type="button"
           onClick={goPrev}
-          aria-label="Previous image"
+          aria-label={t("previous")}
           className="absolute left-2 z-10 flex h-12 w-12 items-center justify-center rounded-full bg-white/10 text-white transition-colors hover:bg-white/20 focus-visible:ring-2 focus-visible:ring-white focus-visible:outline-none sm:left-4"
         >
           <ChevronLeft className="h-6 w-6" aria-hidden="true" />
@@ -156,7 +158,7 @@ export function MediaLightbox({
         <button
           type="button"
           onClick={goNext}
-          aria-label="Next image"
+          aria-label={t("next")}
           className="absolute right-2 z-10 flex h-12 w-12 items-center justify-center rounded-full bg-white/10 text-white transition-colors hover:bg-white/20 focus-visible:ring-2 focus-visible:ring-white focus-visible:outline-none sm:right-4"
         >
           <ChevronRight className="h-6 w-6" aria-hidden="true" />

+ 11 - 9
src/features/inspections/Components/NewInspectionDialog.tsx

@@ -2,6 +2,7 @@
 
 import { useState, useEffect, useTransition } from 'react'
 import { useRouter } from 'next/navigation'
+import { useTranslations } from 'next-intl'
 import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
 import { Button } from '@/components/ui/button'
 import { Input } from '@/components/ui/input'
@@ -55,6 +56,7 @@ export function NewInspectionDialog({
   templates: TemplateOption[]
   preselectedVehicleId?: string
 }) {
+  const t = useTranslations("inspections.new");
   const router = useRouter()
   const serviceType = useServiceType()
   const [isPending, startTransition] = useTransition()
@@ -118,15 +120,15 @@ export function NewInspectionDialog({
     <Dialog open={open} onOpenChange={onOpenChange}>
       <DialogContent className="sm:max-w-md" aria-describedby={undefined}>
         <DialogHeader>
-          <DialogTitle>New Inspection</DialogTitle>
+          <DialogTitle>{t("title")}</DialogTitle>
         </DialogHeader>
         <form onSubmit={handleSubmit} className="space-y-4">
           <div className="space-y-2">
-            <Label>Vehicle</Label>
+            <Label>{t("vehicle")}</Label>
             {loadingVehicles ? (
               <div className="flex items-center gap-2 text-sm text-muted-foreground">
                 <Loader2 className="h-4 w-4 animate-spin" />
-                Loading vehicles...
+                {t("loadingVehicles")}
               </div>
             ) : (
               <Popover open={vehiclePopoverOpen} onOpenChange={setVehiclePopoverOpen}>
@@ -150,9 +152,9 @@ export function NewInspectionDialog({
                 </PopoverTrigger>
                 <PopoverContent className="w-[--radix-popover-trigger-width] p-0" align="start">
                   <Command>
-                    <CommandInput placeholder="Search vehicles..." />
+                    <CommandInput placeholder={t("searchVehicles")} />
                     <CommandList>
-                      <CommandEmpty>No vehicles found.</CommandEmpty>
+                      <CommandEmpty>{t("noVehicles")}</CommandEmpty>
                       <CommandGroup>
                         {vehicles.map((v) => {
                           const label = `${v.year} ${v.make} ${v.model}${v.licensePlate ? ` (${v.licensePlate})` : ''}`
@@ -185,19 +187,19 @@ export function NewInspectionDialog({
 
           <div className="space-y-2">
             <div className="flex items-center justify-between">
-              <Label>Template</Label>
+              <Label>{t("template")}</Label>
               <Link
                 href="/settings/templates?tab=inspections"
                 className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
                 onClick={() => onOpenChange(false)}
               >
                 <Settings className="h-3 w-3" />
-                Manage templates
+                {t("manageTemplates")}
               </Link>
             </div>
             <Select value={templateId} onValueChange={setTemplateId} required>
               <SelectTrigger>
-                <SelectValue placeholder="Select template" />
+                <SelectValue placeholder={t("selectTemplate")} />
               </SelectTrigger>
               <SelectContent>
                 {templates.map((t) => (
@@ -223,7 +225,7 @@ export function NewInspectionDialog({
 
           <div className="flex justify-end gap-2">
             <Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
-              Cancel
+              {t("cancel")}
             </Button>
             <Button type="submit" disabled={isPending || !vehicleId || !templateId}>
               {isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}

+ 68 - 70
src/features/inspections/Components/TemplateForm.tsx

@@ -44,6 +44,7 @@ import {
   Settings2,
   Trash2,
 } from "lucide-react";
+import { useTranslations } from "next-intl";
 import { toast } from "sonner";
 import { createTemplate, updateTemplate } from "../Actions/templateActions";
 import { COMMON_UNITS, INPUT_TYPES, type InputType, type SeverityScale } from "../Lib/conditions";
@@ -269,6 +270,7 @@ function CheckRow({
   onDuplicate: () => void;
   onDelete: () => void;
 }) {
+  const t = useTranslations("inspections.builder");
   const [expanded, setExpanded] = useState(false);
   const fieldId = useId();
   const { setNodeRef, style, isDragging, handleProps } = useSortableRow(item.key);
@@ -276,7 +278,7 @@ function CheckRow({
 
   const isMeasurement = item.inputType === "measurement";
   const isChoice = item.inputType === "choice";
-  const typeLabel = INPUT_TYPES.find((t) => t.value === item.inputType)?.label ?? "Defect grading";
+  const typeLabel = t(`inputType.${item.inputType}`);
 
   return (
     <li
@@ -286,20 +288,20 @@ function CheckRow({
     >
       <div className="flex items-center gap-1.5 p-1.5">
         <DragHandle
-          label={`Reorder check ${item.name || index + 1}`}
+          label={t("reorderCheck", { name: item.name || String(index + 1) })}
           {...handleProps}
         />
         <Input
           value={item.code}
           onChange={(e) => onChange({ code: e.target.value })}
-          placeholder="Ref."
+          placeholder={t("ref")}
           aria-label={`Regulation reference for check ${index + 1}`}
           className="h-9 w-20 shrink-0 font-mono text-xs"
         />
         <Input
           value={item.name}
           onChange={(e) => onChange({ name: e.target.value })}
-          placeholder="Check name, e.g. Brake linings and pads"
+          placeholder={t("checkName")}
           aria-label={`Name of check ${index + 1} in section ${sectionIndex + 1}`}
           className="h-9"
           required
@@ -321,7 +323,9 @@ function CheckRow({
             aria-hidden="true"
           />
           <span className="sr-only">
-            {expanded ? "Hide settings for" : "Configure"} {item.name || `check ${index + 1}`}
+            {expanded
+              ? t("hideSettings", { name: item.name || String(index + 1) })
+              : t("configure", { name: item.name || String(index + 1) })}
           </span>
         </Button>
         <Button
@@ -330,7 +334,7 @@ function CheckRow({
           size="icon"
           className="h-9 w-9 shrink-0"
           onClick={onDuplicate}
-          aria-label={`Duplicate check ${item.name || index + 1}`}
+          aria-label={t("duplicateCheck", { name: item.name || String(index + 1) })}
         >
           <Copy className="h-3.5 w-3.5" aria-hidden="true" />
         </Button>
@@ -341,7 +345,7 @@ function CheckRow({
           className="text-muted-foreground hover:text-destructive h-9 w-9 shrink-0"
           onClick={onDelete}
           disabled={!canDelete}
-          aria-label={`Remove check ${item.name || index + 1}`}
+          aria-label={t("removeCheck", { name: item.name || String(index + 1) })}
         >
           <Trash2 className="h-3.5 w-3.5" aria-hidden="true" />
         </Button>
@@ -351,7 +355,7 @@ function CheckRow({
         <div id={panelId} className="bg-muted/30 space-y-4 border-t px-3 py-4">
           <div className="grid gap-4 sm:grid-cols-2">
             <div className="space-y-1.5">
-              <Label htmlFor={`${fieldId}-type`}>How is it recorded?</Label>
+              <Label htmlFor={`${fieldId}-type`}>{t("howRecorded")}</Label>
               <Select
                 value={item.inputType}
                 onValueChange={(v) => onChange({ inputType: v as InputType })}
@@ -360,25 +364,25 @@ function CheckRow({
                   <SelectValue />
                 </SelectTrigger>
                 <SelectContent>
-                  {INPUT_TYPES.map((t) => (
-                    <SelectItem key={t.value} value={t.value}>
-                      {t.label}
+                  {INPUT_TYPES.map((type) => (
+                    <SelectItem key={type.value} value={type.value}>
+                      {t(`inputType.${type.value}`)}
                     </SelectItem>
                   ))}
                 </SelectContent>
               </Select>
               <p className="text-muted-foreground text-xs">
-                {INPUT_TYPES.find((t) => t.value === item.inputType)?.hint}
+                {t(`inputHint.${item.inputType}`)}
               </p>
             </div>
 
             <div className="space-y-1.5">
-              <Label htmlFor={`${fieldId}-desc`}>Guidance for the technician</Label>
+              <Label htmlFor={`${fieldId}-desc`}>{t("guidance")}</Label>
               <Textarea
                 id={`${fieldId}-desc`}
                 value={item.description}
                 onChange={(e) => onChange({ description: e.target.value })}
-                placeholder="Shown under the check name on the inspection form."
+                placeholder={t("guidancePlaceholder")}
                 className="min-h-[64px] text-sm"
               />
             </div>
@@ -386,10 +390,10 @@ function CheckRow({
 
           {isMeasurement && (
             <fieldset className="space-y-3">
-              <legend className="text-sm font-medium">Measurement</legend>
+              <legend className="text-sm font-medium">{t("measurement")}</legend>
               <div className="grid gap-3 sm:grid-cols-4">
                 <div className="space-y-1.5">
-                  <Label htmlFor={`${fieldId}-unit`}>Unit</Label>
+                  <Label htmlFor={`${fieldId}-unit`}>{t("unit")}</Label>
                   <Input
                     id={`${fieldId}-unit`}
                     value={item.unit}
@@ -404,7 +408,7 @@ function CheckRow({
                   </datalist>
                 </div>
                 <div className="space-y-1.5">
-                  <Label htmlFor={`${fieldId}-min`}>Minimum</Label>
+                  <Label htmlFor={`${fieldId}-min`}>{t("minimum")}</Label>
                   <Input
                     id={`${fieldId}-min`}
                     inputMode="decimal"
@@ -416,7 +420,7 @@ function CheckRow({
                   />
                 </div>
                 <div className="space-y-1.5">
-                  <Label htmlFor={`${fieldId}-max`}>Maximum</Label>
+                  <Label htmlFor={`${fieldId}-max`}>{t("maximum")}</Label>
                   <Input
                     id={`${fieldId}-max`}
                     inputMode="decimal"
@@ -428,7 +432,7 @@ function CheckRow({
                   />
                 </div>
                 <div className="space-y-1.5">
-                  <Label htmlFor={`${fieldId}-sev`}>Defect if out of range</Label>
+                  <Label htmlFor={`${fieldId}-sev`}>{t("outOfRange")}</Label>
                   <Select
                     value={item.defaultSeverity || "fail"}
                     onValueChange={(v) =>
@@ -439,35 +443,34 @@ function CheckRow({
                       <SelectValue />
                     </SelectTrigger>
                     <SelectContent>
-                      <SelectItem value="attention">Minor defect</SelectItem>
-                      <SelectItem value="fail">Major defect</SelectItem>
-                      <SelectItem value="dangerous">Dangerous defect</SelectItem>
+                      <SelectItem value="attention">{t("severityAttention")}</SelectItem>
+                      <SelectItem value="fail">{t("severityFail")}</SelectItem>
+                      <SelectItem value="dangerous">{t("severityDangerous")}</SelectItem>
                     </SelectContent>
                   </Select>
                 </div>
               </div>
               <p className="text-muted-foreground text-xs">
-                Leave a bound empty for a one-sided limit, e.g. a minimum of 1.6 mm with no maximum.
-                Readings outside the range are graded automatically.
+                {t("rangeHelp")}
               </p>
             </fieldset>
           )}
 
           {isChoice && (
             <div className="space-y-1.5">
-              <Label htmlFor={`${fieldId}-choices`}>Answers</Label>
+              <Label htmlFor={`${fieldId}-choices`}>{t("answers")}</Label>
               <Input
                 id={`${fieldId}-choices`}
                 value={item.choices}
                 onChange={(e) => onChange({ choices: e.target.value })}
-                placeholder="Full, Partial, None"
+                placeholder={t("answersPlaceholder")}
               />
-              <p className="text-muted-foreground text-xs">Separate the answers with commas.</p>
+              <p className="text-muted-foreground text-xs">{t("answersHelp")}</p>
             </div>
           )}
 
           <div className="space-y-1.5">
-            <Label htmlFor={`${fieldId}-suggestions`}>Common defect wording</Label>
+            <Label htmlFor={`${fieldId}-suggestions`}>{t("commonWording")}</Label>
             <Textarea
               id={`${fieldId}-suggestions`}
               value={item.defectSuggestions}
@@ -476,9 +479,7 @@ function CheckRow({
               className="min-h-[76px] text-sm"
             />
             <p className="text-muted-foreground text-xs">
-              One phrase per line. These are offered first when a technician records a defect
-              here, ahead of the built-in wording, so your shop can settle on how it describes a
-              fault.
+              {t("commonWordingHelp")}
             </p>
           </div>
 
@@ -490,7 +491,7 @@ function CheckRow({
                 onCheckedChange={(v) => onChange({ required: v })}
               />
               <Label htmlFor={`${fieldId}-required`} className="font-normal">
-                Must be completed
+                {t("mustComplete")}
               </Label>
             </div>
             <div className="flex items-center gap-2">
@@ -500,7 +501,7 @@ function CheckRow({
                 onCheckedChange={(v) => onChange({ photoRequired: v })}
               />
               <Label htmlFor={`${fieldId}-photo`} className="font-normal">
-                Photo required when a defect is recorded
+                {t("photoRequired")}
               </Label>
             </div>
           </div>
@@ -537,6 +538,7 @@ function SectionCard({
   onItemDelete: (itemIndex: number) => void;
   onItemsReorder: (from: number, to: number) => void;
 }) {
+  const t = useTranslations("inspections.builder");
   const fieldId = useId();
   const { setNodeRef, style, isDragging, handleProps } = useSortableRow(section.key);
 
@@ -561,12 +563,12 @@ function SectionCard({
       className={`bg-card rounded-lg border ${isDragging ? "ring-primary/40 shadow-lg ring-2" : ""}`}
     >
       <header className="flex items-start gap-1.5 border-b p-3">
-        <DragHandle label={`Reorder section ${section.name || index + 1}`} {...handleProps} />
+        <DragHandle label={t("reorderSection", { name: section.name || String(index + 1) })} {...handleProps} />
         <div className="grid flex-1 gap-2 sm:grid-cols-[5rem_1fr]">
           <Input
             value={section.code}
             onChange={(e) => onChange({ code: e.target.value })}
-            placeholder="Ref."
+            placeholder={t("ref")}
             aria-label={`Regulation reference for section ${index + 1}`}
             className="font-mono text-xs"
           />
@@ -574,7 +576,7 @@ function SectionCard({
             id={`${fieldId}-name`}
             value={section.name}
             onChange={(e) => onChange({ name: e.target.value })}
-            placeholder="Section name, e.g. Braking equipment"
+            placeholder={t("sectionName")}
             aria-label={`Name of section ${index + 1}`}
             className="font-medium"
             required
@@ -582,7 +584,7 @@ function SectionCard({
           <Input
             value={section.description}
             onChange={(e) => onChange({ description: e.target.value })}
-            placeholder="Optional note shown above the checks in this section"
+            placeholder={t("sectionNote")}
             aria-label={`Description of section ${index + 1}`}
             className="text-sm sm:col-span-2"
           />
@@ -594,7 +596,7 @@ function SectionCard({
           className="text-muted-foreground hover:text-destructive h-9 w-9 shrink-0"
           onClick={onDelete}
           disabled={!canDelete}
-          aria-label={`Remove section ${section.name || index + 1}`}
+          aria-label={t("removeSection", { name: section.name || String(index + 1) })}
         >
           <Trash2 className="h-4 w-4" aria-hidden="true" />
         </Button>
@@ -630,7 +632,7 @@ function SectionCard({
 
         <Button type="button" variant="ghost" size="sm" className="mt-2" onClick={onAddItem}>
           <Plus className="mr-1 h-3.5 w-3.5" aria-hidden="true" />
-          Add check
+          {t("addCheck")}
         </Button>
       </div>
     </section>
@@ -650,6 +652,7 @@ export function TemplateForm({
   onOpenChange: (open: boolean) => void;
   template?: TemplateFormData;
 }) {
+  const t = useTranslations("inspections.builder");
   const router = useRouter();
   const [isPending, startTransition] = useTransition();
   const isEdit = !!template;
@@ -745,11 +748,11 @@ export function TemplateForm({
     startTransition(async () => {
       const result = isEdit ? await updateTemplate(payload) : await createTemplate(payload);
       if (result.success) {
-        toast.success(isEdit ? "Template updated" : "Template created");
+        toast.success(isEdit ? t("updated") : t("created"));
         onOpenChange(false);
         router.refresh();
       } else {
-        toast.error(result.error || "Failed to save template");
+        toast.error(result.error || t("saveFailed"));
       }
     });
   };
@@ -762,10 +765,9 @@ export function TemplateForm({
         aria-describedby="template-builder-description"
       >
         <SheetHeader className="border-b px-6 py-4">
-          <SheetTitle>{isEdit ? "Edit template" : "New template"}</SheetTitle>
+          <SheetTitle>{isEdit ? t("editTitle") : t("newTitle")}</SheetTitle>
           <SheetDescription id="template-builder-description">
-            Build the checklist your workshop actually uses. Drag to reorder, and configure each
-            check to record a grade, a measurement, a written note or a fixed answer.
+            {t("description")}
           </SheetDescription>
         </SheetHeader>
 
@@ -774,35 +776,35 @@ export function TemplateForm({
             {/* Settings */}
             <div className="space-y-5 overflow-y-auto border-b p-6 lg:border-r lg:border-b-0">
               <div className="space-y-2">
-                <Label htmlFor="template-name">Name</Label>
+                <Label htmlFor="template-name">{t("name")}</Label>
                 <Input
                   id="template-name"
                   value={name}
                   onChange={(e) => setName(e.target.value)}
-                  placeholder="e.g. Periodic technical inspection"
+                  placeholder={t("namePlaceholder")}
                   required
                 />
               </div>
 
               <div className="space-y-2">
-                <Label htmlFor="template-desc">Description</Label>
+                <Label htmlFor="template-desc">{t("templateDescription")}</Label>
                 <Textarea
                   id="template-desc"
                   value={description}
                   onChange={(e) => setDescription(e.target.value)}
-                  placeholder="What this checklist is for and when to use it."
+                  placeholder={t("descriptionPlaceholder")}
                   className="min-h-[72px]"
                 />
               </div>
 
               <div className="space-y-2">
-                <Label htmlFor="template-country">Country</Label>
+                <Label htmlFor="template-country">{t("country")}</Label>
                 <Select value={country} onValueChange={setCountry}>
                   <SelectTrigger id="template-country">
                     <SelectValue />
                   </SelectTrigger>
                   <SelectContent>
-                    <SelectItem value="none">Not country-specific</SelectItem>
+                    <SelectItem value="none">{t("notCountrySpecific")}</SelectItem>
                     {TEMPLATE_COUNTRIES.map((c) => (
                       <SelectItem key={c.code} value={c.code}>
                         {c.name}
@@ -811,29 +813,28 @@ export function TemplateForm({
                   </SelectContent>
                 </Select>
                 <p className="text-muted-foreground text-xs">
-                  Recorded on the report so a shop working across borders can tell its checklists
-                  apart.
+                  {t("countryHelp")}
                 </p>
               </div>
 
               <fieldset className="space-y-2">
-                <legend className="text-sm font-medium">Defect scale</legend>
+                <legend className="text-sm font-medium">{t("defectScale")}</legend>
                 <Select
                   value={severityScale}
                   onValueChange={(v) => setSeverityScale(v as SeverityScale)}
                 >
-                  <SelectTrigger aria-label="Defect scale">
+                  <SelectTrigger aria-label={t("defectScale")}>
                     <SelectValue />
                   </SelectTrigger>
                   <SelectContent>
-                    <SelectItem value="eu">Minor / major / dangerous (EU)</SelectItem>
-                    <SelectItem value="basic">Pass / attention / fail</SelectItem>
+                    <SelectItem value="eu">{t("scaleEu")}</SelectItem>
+                    <SelectItem value="basic">{t("scaleBasic")}</SelectItem>
                   </SelectContent>
                 </Select>
                 <p className="text-muted-foreground text-xs">
                   {severityScale === "eu"
-                    ? "The categories in Article 7(2) of Directive 2014/45/EU. A vehicle with only minor defects still passes."
-                    : "A plain three-step scale for service checklists that are not a statutory test."}
+                    ? t("scaleEuHelp")
+                    : t("scaleBasicHelp")}
                 </p>
               </fieldset>
 
@@ -845,9 +846,9 @@ export function TemplateForm({
                   className="mt-0.5"
                 />
                 <div>
-                  <Label htmlFor="template-default">Default template</Label>
+                  <Label htmlFor="template-default">{t("defaultTemplate")}</Label>
                   <p className="text-muted-foreground text-xs">
-                    Pre-selected when a technician starts a new inspection.
+                    {t("defaultHelp")}
                   </p>
                 </div>
               </div>
@@ -855,8 +856,7 @@ export function TemplateForm({
               <div className="space-y-3 border-t pt-4">
                 <div className="text-muted-foreground flex items-center gap-2 text-xs">
                   <Settings2 className="h-3.5 w-3.5" aria-hidden="true" />
-                  {sections.length} section{sections.length === 1 ? "" : "s"} &middot;{" "}
-                  {totalChecks} check{totalChecks === 1 ? "" : "s"}
+                  {t("summary", { sections: sections.length, checks: totalChecks })}
                 </div>
                 <Button
                   type="button"
@@ -866,15 +866,13 @@ export function TemplateForm({
                   onClick={() => setSections((prev) => renumber(prev))}
                 >
                   <ListOrdered className="mr-1 h-3.5 w-3.5" aria-hidden="true" />
-                  Renumber references
+                  {t("renumber")}
                 </Button>
                 <p className="text-muted-foreground text-xs">
-                  Numbers every section and check in order — 1, 1.1, 1.2, 2. New rows are
-                  numbered as you add them, so you only need this after moving things about.
+                  {t("renumberHelp")}
                   {hasRegulationCodes && (
                     <strong className="text-foreground block pt-1">
-                      This checklist uses regulation references such as 1.1.13. Renumbering
-                      replaces them.
+                      {t("renumberWarning")}
                     </strong>
                   )}
                 </p>
@@ -950,18 +948,18 @@ export function TemplateForm({
                 }
               >
                 <Plus className="mr-1 h-4 w-4" aria-hidden="true" />
-                Add section
+                {t("addSection")}
               </Button>
             </div>
           </div>
 
           <div className="flex items-center justify-end gap-2 border-t px-6 py-4">
             <Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
-              Cancel
+              {t("cancel")}
             </Button>
             <Button type="submit" disabled={isPending}>
               {isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />}
-              {isEdit ? "Save changes" : "Create template"}
+              {isEdit ? t("saveChanges") : t("create")}
             </Button>
           </div>
         </form>

+ 38 - 43
src/features/inspections/Components/TemplateListClient.tsx

@@ -38,6 +38,7 @@ import {
   Trash2,
   Upload,
 } from "lucide-react";
+import { useTranslations } from "next-intl";
 import { toast } from "sonner";
 import {
   deleteTemplate,
@@ -104,6 +105,7 @@ function TemplateCard({
   onDelete: () => void;
   isDuplicating: boolean;
 }) {
+  const t = useTranslations("inspections.templates");
   const checkCount = template.sections.reduce((sum, s) => sum + s.items.length, 0);
   const measurementCount = template.sections.reduce(
     (sum, s) => sum + s.items.filter((i) => i.inputType === "measurement").length,
@@ -120,7 +122,7 @@ function TemplateCard({
             <span className="truncate">{template.name}</span>
             {template.isDefault && (
               <Badge variant="secondary" className="shrink-0 text-[11px]">
-                Default
+                {t("default")}
               </Badge>
             )}
           </h3>
@@ -136,7 +138,7 @@ function TemplateCard({
               variant="ghost"
               size="icon"
               className="h-9 w-9 shrink-0"
-              aria-label={`Actions for ${template.name}`}
+              aria-label={t("actionsFor", { name: template.name })}
             >
               {isDuplicating ? (
                 <Loader2 className="h-4 w-4 animate-spin" aria-hidden="true" />
@@ -148,20 +150,20 @@ function TemplateCard({
           <DropdownMenuContent align="end" className="min-w-44">
             <DropdownMenuItem onClick={onEdit}>
               <Pencil className="mr-2 h-4 w-4" aria-hidden="true" />
-              Edit
+              {t("edit")}
             </DropdownMenuItem>
             <DropdownMenuItem onClick={onDuplicate}>
               <Copy className="mr-2 h-4 w-4" aria-hidden="true" />
-              Duplicate
+              {t("duplicate")}
             </DropdownMenuItem>
             <DropdownMenuItem onClick={onExport}>
               <Download className="mr-2 h-4 w-4" aria-hidden="true" />
-              Export…
+              {t("export")}
             </DropdownMenuItem>
             <DropdownMenuSeparator />
             <DropdownMenuItem className="text-destructive" onClick={onDelete}>
               <Trash2 className="mr-2 h-4 w-4" aria-hidden="true" />
-              Delete
+              {t("delete")}
             </DropdownMenuItem>
           </DropdownMenuContent>
         </DropdownMenu>
@@ -170,23 +172,19 @@ function TemplateCard({
       <dl className="text-muted-foreground mt-4 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs">
         <div className="flex items-center gap-1.5">
           <LayoutTemplate className="h-3.5 w-3.5" aria-hidden="true" />
-          <dt className="sr-only">Sections</dt>
-          <dd>
-            {template.sections.length} section{template.sections.length === 1 ? "" : "s"}
-          </dd>
+          <dt className="sr-only">{t("srSections")}</dt>
+          <dd>{t("sections", { count: template.sections.length })}</dd>
         </div>
         <div className="flex items-center gap-1.5">
           <ListChecks className="h-3.5 w-3.5" aria-hidden="true" />
-          <dt className="sr-only">Checks</dt>
-          <dd>
-            {checkCount} check{checkCount === 1 ? "" : "s"}
-          </dd>
+          <dt className="sr-only">{t("srChecks")}</dt>
+          <dd>{t("checks", { count: checkCount })}</dd>
         </div>
         {measurementCount > 0 && (
           <div className="flex items-center gap-1.5">
             <Ruler className="h-3.5 w-3.5" aria-hidden="true" />
-            <dt className="sr-only">Measurements</dt>
-            <dd>{measurementCount} measured</dd>
+            <dt className="sr-only">{t("srMeasurements")}</dt>
+            <dd>{t("measured", { count: measurementCount })}</dd>
           </div>
         )}
       </dl>
@@ -200,7 +198,7 @@ function TemplateCard({
         )}
         <Badge variant="outline" className="gap-1 text-[11px]">
           <ShieldCheck className="h-3 w-3" aria-hidden="true" />
-          {isEu ? "EU defect scale" : "Pass / attention / fail"}
+          {isEu ? t("euScale") : t("basicScale")}
         </Badge>
       </div>
     </article>
@@ -208,6 +206,7 @@ function TemplateCard({
 }
 
 export function TemplateListClient({ templates }: { templates: Template[] }) {
+  const t = useTranslations("inspections.templates");
   const router = useRouter();
   const [showForm, setShowForm] = useState(false);
   const [showPresets, setShowPresets] = useState(false);
@@ -231,12 +230,12 @@ export function TemplateListClient({ templates }: { templates: Template[] }) {
       if (result.success && result.data) {
         toast.success(
           result.data.added === 0
-            ? "Every checklist is already here"
-            : `Restored ${result.data.added} checklist${result.data.added === 1 ? "" : "s"}`
+            ? t("allPresent")
+            : t("restored", { count: result.data.added })
         );
         router.refresh();
       } else {
-        toast.error(result.error || "Failed to add the checklists");
+        toast.error(result.error || t("restoreFailed"));
       }
     });
   };
@@ -246,11 +245,11 @@ export function TemplateListClient({ templates }: { templates: Template[] }) {
     startDeleteTransition(async () => {
       const result = await deleteTemplate(id);
       if (result.success) {
-        toast.success("Template deleted");
+        toast.success(t("deleted"));
         setDeleteTarget(null);
         router.refresh();
       } else {
-        toast.error(result.error || "Failed to delete template");
+        toast.error(result.error || t("deleteFailed"));
       }
     });
   };
@@ -260,10 +259,10 @@ export function TemplateListClient({ templates }: { templates: Template[] }) {
     const result = await duplicateTemplate(id);
     setDuplicatingId(null);
     if (result.success) {
-      toast.success("Template duplicated");
+      toast.success(t("duplicated"));
       router.refresh();
     } else {
-      toast.error(result.error || "Failed to duplicate template");
+      toast.error(result.error || t("duplicateFailed"));
     }
   };
 
@@ -281,24 +280,23 @@ export function TemplateListClient({ templates }: { templates: Template[] }) {
     <div className="space-y-5">
       <div className="flex flex-wrap items-start justify-between gap-3">
         <div>
-          <h2 className="text-lg font-semibold">Inspection templates</h2>
+          <h2 className="text-lg font-semibold">{t("title")}</h2>
           <p className="text-muted-foreground text-sm">
-            The checklists your technicians work through. Build one per country, per vehicle type
-            or per service you sell.
+            {t("description")}
           </p>
         </div>
         <div className="flex shrink-0 gap-2">
           <Button variant="outline" size="sm" onClick={() => setShowImport(true)}>
             <Upload className="mr-1 h-3.5 w-3.5" aria-hidden="true" />
-            Import
+            {t("import")}
           </Button>
           <Button variant="outline" size="sm" onClick={() => setShowPresets(true)}>
             <ShieldCheck className="mr-1 h-3.5 w-3.5" aria-hidden="true" />
-            Browse checklists
+            {t("browse")}
           </Button>
           <Button size="sm" onClick={handleCreate}>
             <Plus className="mr-1 h-3.5 w-3.5" aria-hidden="true" />
-            New template
+            {t("new")}
           </Button>
         </div>
       </div>
@@ -307,8 +305,7 @@ export function TemplateListClient({ templates }: { templates: Template[] }) {
         <div className="bg-muted/40 flex flex-wrap items-center gap-3 rounded-lg border border-dashed p-3">
           <LibraryBig className="text-muted-foreground h-4 w-4 shrink-0" aria-hidden="true" />
           <p className="min-w-0 flex-1 text-sm">
-            {missing.length} ready-made checklist{missing.length === 1 ? " has" : "s have"} been
-            removed from your list. You can put {missing.length === 1 ? "it" : "them"} back.
+            {t("restoreBanner", { count: missing.length })}
           </p>
           <Button
             variant="outline"
@@ -320,7 +317,7 @@ export function TemplateListClient({ templates }: { templates: Template[] }) {
             {isInstalling && (
               <Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" aria-hidden="true" />
             )}
-            Restore
+            {t("restore")}
           </Button>
         </div>
       )}
@@ -328,10 +325,9 @@ export function TemplateListClient({ templates }: { templates: Template[] }) {
       {templates.length === 0 ? (
         <div className="flex flex-col items-center justify-center rounded-lg border border-dashed px-6 py-16 text-center">
           <ClipboardCheck className="text-muted-foreground/40 h-10 w-10" aria-hidden="true" />
-          <h3 className="mt-4 font-medium">No templates yet</h3>
+          <h3 className="mt-4 font-medium">{t("emptyTitle")}</h3>
           <p className="text-muted-foreground mt-1 max-w-sm text-sm">
-            The ready-made checklists — including the EU periodic technical inspection — have all
-            been removed. Put them back, or build your own from scratch.
+            {t("emptyBody")}
           </p>
           <div className="mt-5 flex gap-2">
             <Button onClick={handleRestore} disabled={isInstalling}>
@@ -340,11 +336,11 @@ export function TemplateListClient({ templates }: { templates: Template[] }) {
               ) : (
                 <LibraryBig className="mr-1 h-4 w-4" aria-hidden="true" />
               )}
-              Restore checklists
+              {t("restoreChecklists")}
             </Button>
             <Button variant="outline" onClick={handleCreate}>
               <Plus className="mr-1 h-4 w-4" aria-hidden="true" />
-              Build my own
+              {t("buildMyOwn")}
             </Button>
           </div>
         </div>
@@ -397,21 +393,20 @@ export function TemplateListClient({ templates }: { templates: Template[] }) {
       >
         <AlertDialogContent>
           <AlertDialogHeader>
-            <AlertDialogTitle>Delete &ldquo;{deleteTarget?.name}&rdquo;?</AlertDialogTitle>
+            <AlertDialogTitle>{t("deleteTitle", { name: deleteTarget?.name ?? "" })}</AlertDialogTitle>
             <AlertDialogDescription>
-              This permanently deletes the template. Inspections already carried out with it keep
-              their own copy of the checklist and are not affected.
+              {t("deleteBody")}
             </AlertDialogDescription>
           </AlertDialogHeader>
           <AlertDialogFooter>
-            <AlertDialogCancel>Cancel</AlertDialogCancel>
+            <AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
             <AlertDialogAction
               onClick={() => deleteTarget && handleDelete(deleteTarget.id)}
               disabled={isDeleting}
               className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
             >
               {isDeleting && <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />}
-              Delete
+              {t("delete")}
             </AlertDialogAction>
           </AlertDialogFooter>
         </AlertDialogContent>

+ 24 - 29
src/features/inspections/Components/TemplatePackageDialogs.tsx

@@ -15,6 +15,7 @@ import { Input } from "@/components/ui/input";
 import { Label } from "@/components/ui/label";
 import { Switch } from "@/components/ui/switch";
 import { Download, FileJson, Loader2, TriangleAlert, Upload } from "lucide-react";
+import { useTranslations } from "next-intl";
 import { toast } from "sonner";
 import { exportTemplatePackage, importTemplatePackage } from "../Actions/packageActions";
 import { packageFileName, parsePackage, PackageFormatError } from "@/lib/packages/format";
@@ -46,6 +47,7 @@ export function TemplateExportDialog({
   template: { id: string; name: string } | null;
   onOpenChange: (open: boolean) => void;
 }) {
+  const t = useTranslations("inspections.packages");
   const [includeWording, setIncludeWording] = useState(false);
   const [author, setAuthor] = useState("");
   const [isPending, startTransition] = useTransition();
@@ -65,7 +67,7 @@ export function TemplateExportDialog({
         author,
       });
       if (!result.success || !result.data) {
-        toast.error(result.error || "Could not export this template");
+        toast.error(result.error || t("exportFailed"));
         return;
       }
 
@@ -79,7 +81,7 @@ export function TemplateExportDialog({
       link.click();
       URL.revokeObjectURL(url);
 
-      toast.success("Template exported");
+      toast.success(t("exported"));
       onOpenChange(false);
     });
   };
@@ -88,10 +90,9 @@ export function TemplateExportDialog({
     <Dialog open={!!template} onOpenChange={onOpenChange}>
       <DialogContent className="sm:max-w-lg">
         <DialogHeader>
-          <DialogTitle>Export &ldquo;{template?.name}&rdquo;</DialogTitle>
+          <DialogTitle>{t("exportTitle", { name: template?.name ?? "" })}</DialogTitle>
           <DialogDescription>
-            Downloads the whole checklist as a file — sections, checks, units and limits. Anyone
-            running Torqvoice can import it.
+            {t("exportDescription")}
           </DialogDescription>
         </DialogHeader>
 
@@ -104,34 +105,31 @@ export function TemplateExportDialog({
               className="mt-0.5"
             />
             <div>
-              <Label htmlFor="include-wording">Include your own defect wording</Label>
+              <Label htmlFor="include-wording">{t("includeWording")}</Label>
               <p className="text-muted-foreground mt-0.5 text-xs">
-                The phrases your workshop added to checks. Useful to whoever receives this, but
-                they are free text typed at a bench and can name a customer, a colleague or a
-                local arrangement. Off unless you have read them.
+                {t("includeWordingHelp")}
               </p>
             </div>
           </div>
 
           <div className="space-y-1.5">
-            <Label htmlFor="package-author">Attribute it to (optional)</Label>
+            <Label htmlFor="package-author">{t("attributeTo")}</Label>
             <Input
               id="package-author"
               value={author}
               onChange={(e) => setAuthor(e.target.value)}
-              placeholder="e.g. Egeland Auto"
+              placeholder={t("attributePlaceholder")}
               maxLength={120}
             />
             <p className="text-muted-foreground text-xs">
-              Written into the file so whoever installs it can see where it came from. Nothing is
-              taken from your account.
+              {t("attributeHelp")}
             </p>
           </div>
         </div>
 
         <DialogFooter>
           <Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
-            Cancel
+            {t("cancel")}
           </Button>
           <Button type="button" onClick={handleExport} disabled={isPending}>
             {isPending ? (
@@ -139,7 +137,7 @@ export function TemplateExportDialog({
             ) : (
               <Download className="mr-2 h-4 w-4" aria-hidden="true" />
             )}
-            Download
+            {t("download")}
           </Button>
         </DialogFooter>
       </DialogContent>
@@ -165,6 +163,7 @@ export function TemplateImportDialog({
   open: boolean;
   onOpenChange: (open: boolean) => void;
 }) {
+  const t = useTranslations("inspections.packages");
   const router = useRouter();
   const fileInputRef = useRef<HTMLInputElement>(null);
   const [preview, setPreview] = useState<Preview | null>(null);
@@ -198,7 +197,7 @@ export function TemplateImportDialog({
       setError(
         err instanceof PackageFormatError
           ? err.message
-          : "That file could not be read. A Torqvoice package is a .json file exported from a template."
+          : t("unreadable")
       );
     } finally {
       if (fileInputRef.current) fileInputRef.current.value = "";
@@ -211,14 +210,12 @@ export function TemplateImportDialog({
       const result = await importTemplatePackage(preview.raw);
       if (result.success && result.data) {
         toast.success(
-          `Imported ${result.data.templates.length} template${
-            result.data.templates.length === 1 ? "" : "s"
-          }`
+          t("imported", { count: result.data.templates.length })
         );
         onOpenChange(false);
         router.refresh();
       } else {
-        toast.error(result.error || "Could not import this package");
+        toast.error(result.error || t("importFailed"));
       }
     });
   };
@@ -227,10 +224,9 @@ export function TemplateImportDialog({
     <Dialog open={open} onOpenChange={onOpenChange}>
       <DialogContent className="sm:max-w-lg">
         <DialogHeader>
-          <DialogTitle>Import a checklist</DialogTitle>
+          <DialogTitle>{t("importTitle")}</DialogTitle>
           <DialogDescription>
-            Adds a checklist from a Torqvoice package file. It arrives as a normal template you
-            can edit, and never replaces one you already have.
+            {t("importDescription")}
           </DialogDescription>
         </DialogHeader>
 
@@ -250,7 +246,7 @@ export function TemplateImportDialog({
             onClick={() => fileInputRef.current?.click()}
           >
             <Upload className="h-5 w-5" aria-hidden="true" />
-            <span>{preview ? "Choose a different file" : "Choose a package file"}</span>
+            <span>{preview ? t("chooseAnother") : t("chooseFile")}</span>
             <span className="text-muted-foreground text-xs font-normal">.json</span>
           </Button>
 
@@ -268,7 +264,7 @@ export function TemplateImportDialog({
                 <div className="min-w-0">
                   <p className="font-medium">{preview.name}</p>
                   {preview.author && (
-                    <p className="text-muted-foreground text-xs">From {preview.author}</p>
+                    <p className="text-muted-foreground text-xs">{t("from", { author: preview.author })}</p>
                   )}
                 </div>
               </div>
@@ -276,8 +272,7 @@ export function TemplateImportDialog({
                 <DetailList key={content.type} details={content.details} />
               ))}
               <p className="text-muted-foreground border-t pt-3 text-xs">
-                Limits in a shared checklist are whatever its author set. Check them against your
-                national rules before issuing anything from it.
+                {t("limitsWarning")}
               </p>
             </div>
           )}
@@ -285,11 +280,11 @@ export function TemplateImportDialog({
 
         <DialogFooter>
           <Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
-            Cancel
+            {t("cancel")}
           </Button>
           <Button type="button" onClick={handleImport} disabled={!preview || isPending}>
             {isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />}
-            Import
+            {t("import")}
           </Button>
         </DialogFooter>
       </DialogContent>

+ 18 - 17
src/features/inspections/Components/TemplatePresetPicker.tsx

@@ -13,6 +13,7 @@ import { Button } from "@/components/ui/button";
 import { Input } from "@/components/ui/input";
 import { Badge } from "@/components/ui/badge";
 import { Check, Globe2, Loader2, Search, ShieldCheck, Wrench, Zap } from "lucide-react";
+import { useTranslations } from "next-intl";
 import { toast } from "sonner";
 import { createTemplateFromPreset } from "../Actions/templateActions";
 import {
@@ -39,6 +40,7 @@ function PresetCard({
   alreadyAdded: boolean;
   onSelect: () => void;
 }) {
+  const t = useTranslations("inspections.presets");
   const itemCount = countPresetItems(preset);
   return (
     <button
@@ -57,7 +59,7 @@ function PresetCard({
       <div className="flex items-start justify-between gap-2">
         <span className="text-sm font-semibold">{preset.name}</span>
         {alreadyAdded ? (
-          <span className="text-muted-foreground shrink-0 text-[11px]">In your list</span>
+          <span className="text-muted-foreground shrink-0 text-[11px]">{t("inYourList")}</span>
         ) : (
           selected && <Check className="text-primary h-4 w-4 shrink-0" aria-hidden="true" />
         )}
@@ -76,10 +78,10 @@ function PresetCard({
           {preset.standardLabel}
         </Badge>
         <Badge variant="outline" className="text-[11px]">
-          {preset.severityScale === "eu" ? "EU defect scale" : "Pass / attention / fail"}
+          {preset.severityScale === "eu" ? t("euScale") : t("basicScale")}
         </Badge>
         <span className="text-muted-foreground ml-auto text-[11px]">
-          {preset.sections.length} sections &middot; {itemCount} checks
+          {t("counts", { sections: preset.sections.length, checks: itemCount })}
         </span>
       </div>
     </button>
@@ -96,6 +98,7 @@ export function TemplatePresetPicker({
   /** Names already in the workshop's list, so the same checklist is not offered twice. */
   installedNames?: string[];
 }) {
+  const t = useTranslations("inspections.presets");
   const router = useRouter();
   const [query, setQuery] = useState("");
   const [selectedId, setSelectedId] = useState<string | null>(null);
@@ -122,13 +125,13 @@ export function TemplatePresetPicker({
     startTransition(async () => {
       const result = await createTemplateFromPreset(selectedId);
       if (result.success) {
-        toast.success("Template added. Adjust it to match your workshop.");
+        toast.success(t("added"));
         onOpenChange(false);
         setSelectedId(null);
         setQuery("");
         router.refresh();
       } else {
-        toast.error(result.error || "Failed to add template");
+        toast.error(result.error || t("addFailed"));
       }
     });
   };
@@ -137,10 +140,9 @@ export function TemplatePresetPicker({
     <Dialog open={open} onOpenChange={onOpenChange}>
       <DialogContent className="flex max-h-[85vh] flex-col gap-0 p-0 sm:max-w-3xl">
         <DialogHeader className="border-b px-6 py-4">
-          <DialogTitle>Start from a template</DialogTitle>
+          <DialogTitle>{t("title")}</DialogTitle>
           <DialogDescription>
-            Every preset is copied into your workshop as a normal template. Rename sections, move
-            checks and change any limit to match your country and your equipment.
+            {t("description")}
           </DialogDescription>
         </DialogHeader>
 
@@ -153,9 +155,9 @@ export function TemplatePresetPicker({
             <Input
               value={query}
               onChange={(e) => setQuery(e.target.value)}
-              placeholder="Search by name, country or standard"
+              placeholder={t("search")}
               className="pl-9"
-              aria-label="Search templates"
+              aria-label={t("searchLabel")}
             />
           </div>
         </div>
@@ -170,10 +172,10 @@ export function TemplatePresetPicker({
                 <div className="flex items-center gap-2">
                   <Icon className="text-muted-foreground h-4 w-4" aria-hidden="true" />
                   <h3 id={`preset-group-${group.key}`} className="text-sm font-semibold">
-                    {group.label}
+                    {t(`group.${group.key}`)}
                   </h3>
                 </div>
-                <p className="text-muted-foreground mt-0.5 text-xs">{group.description}</p>
+                <p className="text-muted-foreground mt-0.5 text-xs">{t(`groupDescription.${group.key}`)}</p>
                 <div className="mt-3 grid gap-3 sm:grid-cols-2">
                   {presets.map((preset) => (
                     <PresetCard
@@ -191,23 +193,22 @@ export function TemplatePresetPicker({
 
           {filtered.length === 0 && (
             <p className="text-muted-foreground py-10 text-center text-sm">
-              No template matches &ldquo;{query}&rdquo;.
+              {t("noMatch", { query })}
             </p>
           )}
         </div>
 
         <div className="flex items-center justify-between gap-3 border-t px-6 py-4">
           <p className="text-muted-foreground text-xs">
-            Regulatory presets carry the common EU limits. Check them against your national rules
-            before you issue a certificate.
+            {t("warning")}
           </p>
           <div className="flex shrink-0 gap-2">
             <Button type="button" variant="outline" onClick={() => onOpenChange(false)}>
-              Cancel
+              {t("cancel")}
             </Button>
             <Button type="button" onClick={handleUse} disabled={!selectedId || isPending}>
               {isPending && <Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />}
-              Add template
+              {t("add")}
             </Button>
           </div>
         </div>

+ 6 - 6
src/features/inspections/Lib/testIntervals.ts

@@ -10,12 +10,12 @@ import { addMonths, addYears } from 'date-fns'
  */
 export const DEFAULT_INTERVAL_MONTHS = 24
 
-export const TEST_INTERVALS: { months: number; label: string }[] = [
-  { months: 6, label: '6 months' },
-  { months: 12, label: '1 year' },
-  { months: 24, label: '2 years' },
-  { months: 36, label: '3 years' },
-  { months: 48, label: '4 years' },
+export const TEST_INTERVALS: { months: number; label: string; key: string }[] = [
+  { months: 6, label: '6 months', key: 'months6' },
+  { months: 12, label: '1 year', key: 'years1' },
+  { months: 24, label: '2 years', key: 'years2' },
+  { months: 36, label: '3 years', key: 'years3' },
+  { months: 48, label: '4 years', key: 'years4' },
 ]
 
 /** Local-time ISO date (yyyy-mm-dd), which is what the date input speaks. */

+ 50 - 0
src/features/inspections/Lib/useConditionLabels.ts

@@ -0,0 +1,50 @@
+"use client";
+
+import { useTranslations } from "next-intl";
+import { useCallback } from "react";
+import {
+  conditionGrade,
+  type Condition,
+  type SeverityScale,
+  type TestResult,
+} from "./conditions";
+
+/**
+ * Translated names for the defect grades and the overall result.
+ *
+ * conditions.ts stays free of i18n on purpose: it also runs on the server for
+ * the PDF, where the locale arrives as a bag of labels rather than a React
+ * context. This hook is the client-side view of the same vocabulary, so the
+ * page, the shared report and the certificate all say the same thing in
+ * whichever language the reader has.
+ */
+export function useConditionLabels(scale: SeverityScale, country?: string | null) {
+  const t = useTranslations("inspections");
+
+  const label = useCallback(
+    (condition: Condition) => t(`grades.${scale === "basic" ? "basic" : "eu"}.${condition}`),
+    [t, scale]
+  );
+
+  const short = useCallback(
+    (condition: Condition) => t(`grades.short.${condition}`),
+    [t]
+  );
+
+  const hint = useCallback((condition: Condition) => t(`grades.hint.${condition}`), [t]);
+
+  /** "2 — Vesentlig mangel" where the country numbers its grades, else the name. */
+  const graded = useCallback(
+    (condition: Condition) => {
+      const grade = conditionGrade(condition, scale, country);
+      const text = label(condition);
+      return grade === null ? text : `${grade} — ${text}`;
+    },
+    [label, scale, country]
+  );
+
+  const result = useCallback((value: TestResult) => t(`results.${value}`), [t]);
+  const resultDetail = useCallback((value: TestResult) => t(`results.detail.${value}`), [t]);
+
+  return { label, short, hint, graded, result, resultDetail };
+}

+ 2 - 0
src/i18n/request.ts

@@ -60,6 +60,7 @@ export default getRequestConfig(async () => {
   const telegramMessages = (await import(`../../messages/${locale}/telegramMessages.json`)).default
   const statusReport = (await import(`../../messages/${locale}/statusReport.json`)).default
   const support = (await import(`../../messages/${locale}/support.json`)).default
+  const inspections = (await import(`../../messages/${locale}/inspections.json`)).default
 
   return {
     locale,
@@ -94,6 +95,7 @@ export default getRequestConfig(async () => {
       telegramMessages,
       statusReport,
       support,
+      inspections,
     },
   }
 })