feat: expand i18n locale support and fallback handling

This commit is contained in:
2026-03-11 16:40:14 +01:00
parent 2791f95fb4
commit 391f5dfbc2
13 changed files with 6332 additions and 2524 deletions

View File

@@ -9,6 +9,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.ResourceBundle.Control;
@@ -16,6 +17,7 @@ import java.util.ResourceBundle.Control;
public class TranslationProvider implements I18NProvider {
public static final String BUNDLE_PREFIX = "messages";
private static final Locale DEFAULT_LOCALE = Locale.GERMAN;
// Custom Control to map language codes to file names
private static final Control BUNDLE_CONTROL = new Control() {
@@ -35,7 +37,7 @@ public class TranslationProvider implements I18NProvider {
case "es" -> Locale.of("es"); // Spanish -> messages_es.properties
case "fr" -> Locale.of("fr"); // French -> messages_fr.properties
case "en" -> Locale.of("en"); // English -> messages_en.properties
case "de" -> Locale.of("de"); // German -> messages.properties (default)
case "de" -> Locale.of("de"); // German -> messages_de.properties
default -> locale;
};
@@ -57,19 +59,38 @@ public class TranslationProvider implements I18NProvider {
}
try {
ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_PREFIX, locale, BUNDLE_CONTROL);
String value = bundle.getString(key);
String value = findTranslation(key, locale);
if (value == null) {
return key;
}
if (params.length > 0) {
value = MessageFormat.format(value, params);
}
return value;
} catch (Exception e) {
} catch (MissingResourceException e) {
return key;
}
}
private String findTranslation(String key, Locale locale) {
Locale effectiveLocale = locale != null ? locale : DEFAULT_LOCALE;
ResourceBundle localizedBundle = ResourceBundle.getBundle(BUNDLE_PREFIX, effectiveLocale, BUNDLE_CONTROL);
if (localizedBundle.containsKey(key)) {
return localizedBundle.getString(key);
}
if (!DEFAULT_LOCALE.getLanguage().equals(effectiveLocale.getLanguage())) {
ResourceBundle germanBundle = ResourceBundle.getBundle(BUNDLE_PREFIX, DEFAULT_LOCALE, BUNDLE_CONTROL);
if (germanBundle.containsKey(key)) {
return germanBundle.getString(key);
}
}
return null;
}
public String getTranslation(String key, Language language) {
Locale locale = switch (language) {
case DE -> Locale.GERMAN;
@@ -85,4 +106,4 @@ public class TranslationProvider implements I18NProvider {
};
return getTranslation(key, locale);
}
}
}

View File

@@ -1,6 +1,7 @@
package de.assecutor.votianlt.service;
import com.vaadin.flow.component.UI;
import de.assecutor.votianlt.config.TranslationProvider;
import de.assecutor.votianlt.model.Language;
import de.assecutor.votianlt.model.User;
import de.assecutor.votianlt.repository.UserRepository;
@@ -8,16 +9,17 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Locale;
import java.util.ResourceBundle;
@Service
public class LanguageService {
private final UserRepository userRepository;
private final TranslationProvider translationProvider;
@Autowired
public LanguageService(UserRepository userRepository) {
public LanguageService(UserRepository userRepository, TranslationProvider translationProvider) {
this.userRepository = userRepository;
this.translationProvider = translationProvider;
}
public void updateUserLanguage(User user, Language language) {
@@ -30,31 +32,7 @@ public class LanguageService {
}
public String getTranslation(String key, Language language) {
try {
Locale locale;
switch (language) {
case DE:
locale = Locale.GERMAN;
break;
case EN:
locale = Locale.ENGLISH;
break;
case FR:
locale = Locale.FRENCH;
break;
case ES:
locale = Locale.of("es", "ES");
break;
default:
locale = Locale.GERMAN;
}
ResourceBundle bundle = ResourceBundle.getBundle("messages", locale);
return bundle.getString(key);
} catch (Exception e) {
// Fallback to key itself if translation not found
return key;
}
return translationProvider.getTranslation(key, language != null ? language : Language.DE);
}
public String getTranslation(String key, User user) {
@@ -78,6 +56,12 @@ public class LanguageService {
case EN -> Locale.ENGLISH;
case FR -> Locale.FRENCH;
case ES -> Locale.of("es", "ES");
case TR -> Locale.of("tr", "TR");
case PL -> Locale.of("pl", "PL");
case RU -> Locale.of("ru", "RU");
case EE -> Locale.of("et", "EE");
case LV -> Locale.of("lv", "LV");
case LT -> Locale.of("lt", "LT");
default -> Locale.GERMAN;
};
}

View File

@@ -246,7 +246,7 @@ page.title.customer.create=Neuen Kunden anlegen
page.title.login=Bei VotianLT anmelden
page.title.jobs=Aufträge
page.title.appuser.edit=App-Nutzer bearbeiten
page.title.statistics=KI-Statistiken
page.title.statistics=Statistiken
page.title.password.forget=Passwort zurücksetzen
page.title.invoices=Rechnungen
page.title.appusers=App-Nutzer
@@ -268,10 +268,7 @@ page.title.create.invoice=Rechnung erstellen
page.title.add.customer=Neuen Kunden anlegen
page.title.edit.appuser=App-Nutzer bearbeiten
page.title.forget.password=Passwort zurücksetzen
page.title.job.history=Job Historie
page.title.admin.pricetable=Preis-Tabelle
page.title.invoice.generator=Rechnungsgenerator
page.title.job.summary=Zusammenfassung
page.title.add.job=Neuen Auftrag anlegen
# Dashboard
@@ -638,7 +635,6 @@ jobs.dialog.complete.text=Möchten Sie den Auftrag {0} manuell abschließen?
jobs.dialog.complete.confirm=Abschließen
jobs.dialog.delete.title=Auftrag löschen
jobs.dialog.delete.text=Möchten Sie den Auftrag {0} wirklich löschen?
jobs.notification.completed=Auftrag {0} wurde abgeschlossen
jobs.notification.complete.error=Fehler beim Abschließen: {0}
jobs.notification.deleted=Auftrag {0} wurde gelöscht
jobs.notification.delete.error=Fehler beim Löschen: {0}
@@ -721,7 +717,7 @@ appuser.column.appcode=App-Code
appuser.column.email=E-Mail
# Statistics
statistics.title=KI-Statistiken
statistics.title=Statistiken
statistics.subtitle=Stellen Sie Fragen zu Ihren Aufträgen und Kunden
statistics.prompt.placeholder=Frage eingeben...
statistics.quick.jobcount=Anzahl Aufträge

File diff suppressed because it is too large Load Diff

View File

@@ -4,50 +4,50 @@ dialog.confirm=Confirm
# Navigation and Main Layout
nav.jobs=Jobs
nav.job.create=Create New Job
nav.job.create=Create Job
nav.customers=Customers
nav.appusers=App Users
nav.statistics=Statistics
nav.invoices=Invoices
nav.messages=Messages
nav.profile=My Profile
nav.myinvoices=My Invoices
nav.myinvoices=Invoices
nav.imprint=Imprint
nav.management=Management
nav.users=Users
nav.showprofile=Show Profile
nav.settings=Settings
nav.logout=Logout
nav.logout=Log Out
# Profile View
profile.title=Edit Profile
profile.language=Language
profile.company=Company
profile.companyadd=Company Addition
profile.companyadd=Company Suffix
profile.firstname=First Name
profile.lastname=Last Name
profile.phone=Phone Number
profile.fax=Fax
profile.mobile=Mobile
profile.fax=Phone (Fax)
profile.mobile=Phone (Mobile)
profile.email=Email Address (Login)*
profile.street=Street
profile.housenr=House No.
profile.addressadd=Address Addition
profile.zip=Zip Code
profile.addressadd=Address Suffix
profile.zip=Postal Code
profile.city=City
profile.diffinvoice=Different Invoice Address
profile.basicdata=Basic Data
profile.diffinvoice=Different Billing Address
profile.basicdata=Master Data
profile.map=Map
profile.invoicecreation=Invoice Creation
profile.settings=Settings
profile.settings.digitalprocess=Digital Processing
profile.settings.digitalprocess.info=Jobs are processed digitally via the app
profile.settings.locateappuser=Locate App User
profile.settings.locateappuser.info=Location of app users is regularly transmitted
profile.settings.locateappuser=Locate App Users
profile.settings.locateappuser.info=App user location is transmitted regularly
profile.account=Account
profile.security=Security
profile.security.twofactor=Two-Factor Authentication
profile.security.twofactor.info=An additional code is sent by email when logging in
profile.security.twofactor.info=An additional code is sent via email when logging in
profile.services=Service Catalog
profile.saved=Profile saved
profile.save.error=Error saving: {0}
@@ -55,14 +55,14 @@ profile.validation.required.fill=Please fill in all required fields correctly
# Profile Settings
settings.digitalprocessing=Digital Processing via App
settings.digitalprocessinginfo=Enables digital order processing through the mobile app
settings.locationtracking=Track App Users
settings.locationtrackinginfo=Allows tracking of app users during order execution
settings.twofactor=Two-Factor Authentication
settings.twofactorinfo=When enabled, a code will be sent via email for each login
settings.digitalprocessinginfo=Enables digital job processing via the mobile app
settings.locationtracking=Locate App Users
settings.locationtrackinginfo=Allows locating app users during job execution
settings.twofactor=2-Factor Authentication
settings.twofactorinfo=When enabled, a code is sent via email at each login
# Profile Billing
profile.billing.enabled=Billing via votianLT
profile.billing.enabled=Invoicing via votianLT
profile.billing.prefix=Invoice Prefix
# Profile Validation
@@ -72,21 +72,21 @@ profile.validation.lastname=Last name is a required field
profile.validation.phone=Phone number is a required field
profile.validation.street=Street is a required field
profile.validation.housenr=House number is a required field
profile.validation.zip=Zip code is a required field
profile.validation.zip=Postal code is a required field
profile.validation.city=City is a required field
profile.validation.email.required=Email address is a required field
profile.validation.email.invalid=Please enter a valid email address
profile.validation.company.required=Company is required
profile.validation.street.required=Street is required
profile.validation.housenr.required=House number is required
profile.validation.zip.required=Zip code is required
profile.validation.zip.required=Postal code is required
profile.validation.city.required=City is required
profile.validation.firstname.required=First name is required
profile.validation.lastname.required=Last name is required
profile.validation.phone.required=Phone number is required
# Profile Invoice
profile.invoice.masterdata=My Data
profile.invoice.masterdata=My Master Data
profile.invoice.name=Name
profile.invoice.city=City
profile.invoice.email=Email
@@ -94,7 +94,7 @@ profile.invoice.phone=Phone
profile.invoice.placeholder.company=Your Company
profile.invoice.placeholder.name=Your Name
profile.invoice.placeholder.street=Your Street
profile.invoice.placeholder.city=ZIP City
profile.invoice.placeholder.city=Postal Code City
profile.invoice.placeholder.email=your@email.com
profile.invoice.placeholder.phone=Your Phone Number
profile.invoice.services.list=List Services
@@ -119,7 +119,7 @@ profile.invoice.element.line=Line
profile.invoice.element.image=Image
profile.invoice.element.invoicenumber=Invoice Number
profile.invoice.properties=Properties
profile.invoice.properties.info=Click on an element in the canvas to edit its properties
profile.invoice.properties.info=Click on an element in the canvas to edit its properties.
profile.invoice.type=Type
profile.invoice.variable=Variable
profile.invoice.xposition=X Position
@@ -133,8 +133,8 @@ profile.invoice.image.uploaded=Image uploaded successfully
profile.invoice.image.upload.error=Error uploading: {0}
profile.invoice.file.rejected=File rejected: {0}
profile.invoice.text.from.masterdata=Text comes from your master data
profile.invoice.canvas.cleared=Canvas cleared
profile.invoice.canvas.read.error=Error: Could not read canvas data
profile.invoice.canvas.cleared=Canvas has been cleared
profile.invoice.canvas.read.error=Error: Canvas data could not be read
profile.invoice.template.saved=Template saved successfully
profile.invoice.pdf.error=Error generating PDF: {0}
profile.invoice.pdf.preview=Preview
@@ -142,7 +142,7 @@ profile.invoice.pdf.preview.error=Error generating preview: {0}
# Profile Services
profile.services.label=Services
profile.services.description=Manage your services that you offer to your customers
profile.services.description=Manage your services that you offer to your customers here.
profile.services.add=Add New Service
profile.services.load.error=Error loading services: {0}
profile.services.saved=Service saved successfully
@@ -152,14 +152,14 @@ profile.services.delete.error=Error deleting service: {0}
profile.services.dialog.create=Create New Service
profile.services.dialog.edit=Edit Service
profile.services.basis=Calculation Basis
profile.services.basis.distance=Distance (km)
profile.services.basis.distance=Distance Driven
profile.services.basis.time=Time
profile.services.basis.flatrate=Flat Rate
profile.services.vatrate=VAT Rate (%)
profile.services.vatrate.percent=VAT Rate (%)
profile.services.price.flatrate=Flat Rate Price ()
profile.services.price.distance=Price per Kilometer ()
profile.services.price.time=Price per 15 Minutes ()
profile.services.price.flatrate=Flat Rate Price (\u20ac)
profile.services.price.distance=Price per Kilometer (\u20ac)
profile.services.price.time=Price per 15 Minutes (\u20ac)
profile.services.mandatory=Mandatory
profile.services.calculated=Calculated
profile.services.validation.name=Name is required
@@ -211,15 +211,15 @@ notification.error=Error saving
notification.languagechanged=Language changed
# Login
login.title=Login
login.title=Log In
login.username=Username
login.password=Password
login.login=Login
login.forgotpassword=Forgot password?
login.rememberme=Remember me
login.login=Log In
login.forgotpassword=Forgot Password?
login.rememberme=Remember Me
login.register=Register
login.2fa.helper=6-digit code
login.2fa.sent=Code sent via email
login.2fa.sent=Code was sent via email
login.2fa.no.credentials=No credentials available
login.2fa.invalid.code=Invalid code
login.2fa.wrong.code=Wrong code
@@ -233,7 +233,7 @@ error.validation=Validation error
page.title.dashboard=VotianLT - Dashboard
page.title.appuser.create=Create New App User
page.title.messages=Messages
page.title.register=Register with VotianLT
page.title.register=Register at VotianLT
page.title.customers=Customers
page.title.customer.edit=Edit Customer
page.title.verwaltung=Management
@@ -243,22 +243,22 @@ page.title.profile.edit=Edit Profile
page.title.admin.dashboard=Admin Dashboard
page.title.invoice.create=Create Invoice
page.title.customer.create=Create New Customer
page.title.login=Login to VotianLT
page.title.login=Log In to VotianLT
page.title.jobs=Jobs
page.title.appuser.edit=Edit App User
page.title.statistics=AI Statistics
page.title.statistics=Statistics
page.title.password.forget=Reset Password
page.title.invoices=Invoices
page.title.appusers=App Users
page.title.job.history=Job History
page.title.message.history=Message History
page.title.myinvoices=My Invoices
page.title.myinvoices=Invoices
page.title.job.create=Create New Job
page.title.job.summary=Summary
page.title.pricetable=Price Table
page.title.invoice.generator=Invoice Generator
page.title.welcome=VotianLT - Welcome
page.title.password.reset=Reset Password - Enter Email
page.title.password.reset=Reset Password \u2013 Enter Email
page.title.add.appuser=Create New App User
page.title.user.messages=Messages
page.title.edit.customer=Edit Customer
@@ -268,16 +268,13 @@ page.title.create.invoice=Create Invoice
page.title.add.customer=Create New Customer
page.title.edit.appuser=Edit App User
page.title.forget.password=Reset Password
page.title.job.history=Job History
page.title.admin.pricetable=Price Table
page.title.invoice.generator=Invoice Generator
page.title.job.summary=Summary
page.title.add.job=Create New Job
# Dashboard
dashboard.welcome=Welcome, {0}!
dashboard.footer.copyright=© 2024 VotianLT. All rights reserved.
dashboard.description=Here you can manage your jobs, organize customers and use all important features of VotianLT.
dashboard.footer.copyright=\u00a9 2024 VotianLT. All rights reserved.
dashboard.description=Here you can manage your jobs, organize customers, and use all important features of VotianLT.
dashboard.system.title=System Overview
dashboard.system.intro=Manage your business processes efficiently with the following features
dashboard.feature.setup.title=Setup
@@ -287,7 +284,7 @@ dashboard.feature.customers.desc=Manage your customer relationships and contacts
dashboard.feature.jobs.title=Jobs
dashboard.feature.jobs.desc=Create and manage jobs efficiently
dashboard.app.title=Mobile App
dashboard.app.description=Use the VotianLT app on the go and stay connected
dashboard.app.description=Use the VotianLT App on the go and stay connected at all times
# Add App User
addappuser.title=Create New App User
@@ -299,7 +296,7 @@ addappuser.button.submit=Create App User
addappuser.validation.designation=Designation is required
addappuser.validation.phone=Phone number is required
addappuser.validation.password.required=Password is required
addappuser.validation.password.min=Password must be at least 6 characters
addappuser.validation.password.min=Password must have at least 6 characters
addappuser.validation.password.confirm=Password confirmation is required
addappuser.validation.password.mismatch=Passwords do not match
addappuser.validation.email.required=Email is required
@@ -323,13 +320,13 @@ editappuser.notification.check=Please check your input
editappuser.notification.password.confirm=Please confirm the new password
editappuser.notification.password.enter=Please enter a new password
editappuser.notification.deleted=App user deleted successfully
editappuser.dialog.delete.text=Do you really want to delete this app user?
editappuser.dialog.delete.text=Are you sure you want to delete this app user?
editappuser.dialog.delete.confirm=Delete
# Customers
customers.title=Customers
customers.button.add=Add New Customer
customers.hint.click=Click on a customer to view details
customers.hint.click=Click on a customer to see details
customers.column.company=Company
customers.column.name=Name
customers.column.email=Email
@@ -344,7 +341,7 @@ editcustomer.notification.invalid.id=Invalid customer ID
editcustomer.notification.saved=Customer saved successfully
editcustomer.notification.check=Please check your input
editcustomer.notification.deleted=Customer deleted successfully
editcustomer.dialog.delete.text=Do you really want to delete this customer?
editcustomer.dialog.delete.text=Are you sure you want to delete this customer?
editcustomer.dialog.delete.confirm=Delete
# Add Customer
@@ -362,13 +359,13 @@ addcompany.button.submit=Create Company
# Verwaltung
verwaltung.title=Management
verwaltung.description=Manage your companies, customers and system settings here
verwaltung.description=Manage your companies, customers, and system settings here
# User Messages
usermessages.title.with=Messages with {0}
usermessages.general.title=General Conversations
usermessages.general.conversation=General Conversation
usermessages.job.title=Job-related Messages
usermessages.job.title=Job-Related Messages
usermessages.job.conversation=Job {0}
usermessages.no.job.messages=No job-related messages
usermessages.preview.empty=No preview available
@@ -394,7 +391,7 @@ admindashboard.stat.inprogress=In Progress
admindashboard.stat.completed=Completed
admindashboard.stat.cargo=Cargo Items
admindashboard.stat.status.info=Status
admindashboard.stat.status.unavailable=Not Available
admindashboard.stat.status.unavailable=Unavailable
admindashboard.stat.totaltasks=Total Tasks
admindashboard.stat.completedtasks=Completed
admindashboard.stat.pendingtasks=Pending
@@ -415,7 +412,7 @@ admindashboard.stat.memory=Memory
# Messages
messages.title=Messages
messages.column.status=Status
messages.column.client=Client
messages.column.client=Customer
messages.column.email=Email
messages.column.total=Total
messages.column.unread=Unread
@@ -424,51 +421,51 @@ messages.column.preview=Preview
messages.notification.error=Error loading messages
messages.preview.image=Image
messages.preview.empty=No preview
messages.sender.unknown=Unknown sender
messages.sender.unknown=Unknown Sender
# Add Job
addjob.title=Create New Job
addjob.customer.label=Customer
addjob.customer.placeholder=Select customer
addjob.customer.placeholder=Select Customer
addjob.customer.unnamed=Unnamed Customer
addjob.button.clearfields=Clear Fields
addjob.button.submit=Create Job
addjob.address.salutation=Salutation
addjob.address.salutation.placeholder=Select salutation
addjob.address.salutation.placeholder=Select Salutation
addjob.salutation.mr=Mr
addjob.salutation.ms=Ms
addjob.salutation.other=Other
addjob.address.company.placeholder=Enter company
addjob.address.street.placeholder=Enter street
addjob.address.housenumber=House Number
addjob.address.addition.placeholder=Address addition
addjob.address.addition.placeholder=Address suffix
addjob.address.city=City
addjob.address.city.placeholder.pickup=City (Pickup)
addjob.address.city.placeholder.delivery=City (Delivery)
addjob.address.delivery.street.placeholder=Street (Delivery)
addjob.address.delivery.addition.placeholder=Address addition (Delivery)
addjob.address.delivery.addition.placeholder=Address suffix (Delivery)
addjob.address.save=Save Address
addjob.section.pickup=Pickup
addjob.section.delivery=Delivery
addjob.stations.apply=Apply Stations
addjob.station.delivery=Delivery Station {0}
addjob.station.add=Add delivery station
addjob.station.add=Add Delivery Station
addjob.station.remove.confirm=Really remove delivery station {0}?
addjob.station.max.reached=Maximum of 25 delivery stations reached
addjob.station.max.reached=Maximum number of 25 delivery stations reached
addjob.station.unused=Not used
addjob.appointment.delivery.info=Delivery dates are set directly in the delivery stations.
addjob.tab.addresses=Customer & Addresses
addjob.tab.addresses=Client & Addresses
addjob.tab.appointments=Appointments & Processing
addjob.tab.cargo=Cargo
addjob.tab.tasks=Tasks
addjob.tab.price=Price & Submit
addjob.tab.price=Price & Completion
addjob.appointment.date=Date
addjob.appointment.time=Time
addjob.appointment.pickup=Pickup Appointment
addjob.appointment.delivery=Delivery Appointment
addjob.appointment.pickup=Pickup Date
addjob.appointment.delivery=Delivery Date
addjob.settings.digitalprocess=Digital Processing via App
addjob.appuser.label=App User
addjob.appuser.placeholder=Select app user
addjob.appuser.placeholder=Select App User
addjob.cargo.description=Description
addjob.cargo.description.placeholder=Enter description
addjob.cargo.quantity=Quantity
@@ -478,14 +475,14 @@ addjob.cargo.width=Width
addjob.cargo.height=Height
addjob.cargo.europalette=Euro Pallet
addjob.cargo.disposablepalette=Disposable Pallet
addjob.cargo.dusseldorfpalette=Düsseldorf Pallet
addjob.cargo.dusseldorfpalette=Dusseldorf Pallet
addjob.cargo.gridboxpalette=Grid Box Pallet
addjob.cargo.gridcart=Grid Cart
addjob.cargo.parcel=Parcel
addjob.cargo.add=Add Cargo
addjob.tasks.title=Tasks
addjob.tasks.template.placeholder=Select template
addjob.tasks.template.save.tooltip=Save as template
addjob.tasks.template.placeholder=Select Template
addjob.tasks.template.save.tooltip=Save as Template
addjob.tasks.template.save.title=Save Template
addjob.tasks.template.name=Template Name
addjob.tasks.template.name.placeholder=Enter name
@@ -495,14 +492,14 @@ addjob.tasks.template.save.error=Error saving: {0}
addjob.tasks.template.dialog.error=Error opening dialog: {0}
addjob.tasks.template.no.tasks=No tasks to save
addjob.tasks.template.load.title=Load Template
addjob.tasks.template.load.text=Do you want to load template "{0}"? This will replace all current tasks.
addjob.tasks.template.load.text=Do you want to load the template "{0}"? This action will replace all current tasks.
addjob.tasks.template.load.confirm=Load
addjob.tasks.template.loaded=Template "{0}" loaded
addjob.tasks.template.load.error=Error loading: {0}
addjob.tasks.template.load.templates.error=Error loading templates: {0}
addjob.tasks.add=Add Task
addjob.tasks.tasktype=Task Type
addjob.tasks.tasktype.placeholder=Select type
addjob.tasks.tasktype.placeholder=Select Type
addjob.tasks.description=Description
addjob.tasks.description.placeholder=Enter description
addjob.tasks.buttontext=Button Text
@@ -524,14 +521,14 @@ addjob.tasks.optional=Task is optional
addjob.services.title=Services
addjob.services.add=Add Service
addjob.services.calculation=Calculation
addjob.services.basis.distance=Distance (km)
addjob.services.basis.distance=Distance Driven
addjob.services.basis.time=Time
addjob.services.basis.flatrate=Flat Rate
addjob.services.vat=VAT
addjob.services.route.missing=Route missing
addjob.services.dialog.title=Select Service
addjob.services.dialog.placeholder=Select service
addjob.services.dialog.station.placeholder=Select delivery station
addjob.services.dialog.placeholder=Select Service
addjob.services.dialog.station.placeholder=Select Delivery Station
addjob.services.dialog.add=Add
addjob.services.deliverystation=Delivery Station
addjob.summary.title=Summary
@@ -543,34 +540,34 @@ addjob.route.distance=Distance
addjob.route.distance.km=Distance (km)
addjob.route.distance.placeholder=e.g. 150.5
addjob.route.duration=Duration
addjob.route.duration.min=Duration (Min.)
addjob.route.duration.min=Duration (min.)
addjob.route.duration.placeholder=e.g. 120
addjob.route.manual.title=Manual Route Entry
addjob.route.manual.hint=Enter distance and duration manually if no route was calculated
addjob.notification.success=Job {0} created successfully
addjob.notification.cleared=All fields cleared
addjob.notification.cleared=All fields have been cleared
addjob.notification.draft.restored=Draft restored
addjob.validation.required.fields=Please fill in all required fields
addjob.validation.appuser.required=Please select an app user
addjob.validation.cargo.required=Please enter at least one cargo item
addjob.validation.cargo.required=Please specify at least one cargo item
addjob.validation.pickupdate.future=Pickup date must be today or in the future
addjob.validation.deliverydate.future=Delivery date must be today or in the future
addjob.validation.dialog.title=Address Validation
addjob.validation.dialog.loading=Validating addresses...
addjob.validation.dialog.back=Back
addjob.validation.dialog.continue=Continue
addjob.validation.dialog.continue.anyway=Continue anyway
addjob.validation.dialog.continue.anyway=Continue Anyway
addjob.validation.pickup.address=Pickup Address
addjob.validation.delivery.address=Delivery Address
addjob.validation.route=Route
addjob.validation.address.not.found.title=Address not found
addjob.validation.address.not.found.message=The entered address could not be clearly found on Google. Do you still want to save?
addjob.validation.address.save.anyway=Save anyway
addjob.validation.address.correct=Correct address
addjob.validation.address.not.found.title=Address Not Found
addjob.validation.address.not.found.message=The entered address could not be clearly found on Google. Do you want to save anyway?
addjob.validation.address.save.anyway=Save Anyway
addjob.validation.address.correct=Correct Address
# Job Summary
jobsummary.title=Summary
jobsummary.error.noid=No job ID provided
jobsummary.error.noid=No job ID specified
jobsummary.error.invalidid=Invalid job ID format: {0}
jobsummary.error.notfound=Job with ID {0} not found
jobsummary.button.sendmessage=Send Message
@@ -580,9 +577,9 @@ jobsummary.dialog.complete.title=Complete Job
jobsummary.dialog.complete.text=Do you want to manually complete job {0}?
jobsummary.dialog.complete.cancel=Cancel
jobsummary.dialog.complete.confirm=Complete
jobsummary.notification.completed=Job {0} completed
jobsummary.notification.complete.error=Error completing job: {0}
jobsummary.notification.noappuser=No app user assigned to this job
jobsummary.notification.completed=Job {0} has been completed
jobsummary.notification.complete.error=Error completing: {0}
jobsummary.notification.noappuser=No app user is assigned to this job
jobsummary.section.pickup=Pickup
jobsummary.section.delivery=Delivery
jobsummary.station.phone=Phone
@@ -590,17 +587,17 @@ jobsummary.section.tasks=Tasks to Confirm
jobsummary.section.cargo=Cargo to Transport
jobsummary.section.info=Additional Information
jobsummary.tasks.none=No tasks
jobsummary.cargo.none=No cargo information
jobsummary.cargo.none=No cargo details
jobsummary.info.netto=Net
jobsummary.info.ust=VAT
jobsummary.info.gesamt=Total
jobsummary.info.bemerkung=Remark
jobsummary.info.digital=Digital Processing via App: enabled
jobsummary.info.digital=Digital processing via app: enabled
jobsummary.info.appuser=App User
jobsummary.task.status.abgeschlossen=Completed
jobsummary.task.status.offen=Open
jobsummary.task.typ=Type
jobsummary.task.completedAt=Completed at
jobsummary.task.completedAt=Completed on
jobsummary.task.completedBy=Completed by
jobsummary.task.todo.items=To-Do Items
jobsummary.task.photo.info=Photos
@@ -621,7 +618,7 @@ jobs.filter.apply=Apply Filter
jobs.status.all=All
jobs.status.open=Open
jobs.status.done=Done
jobs.notification.completed=Job {0} completed
jobs.notification.completed=Job {0} has been completed
jobs.column.status=Status
jobs.column.customer=Customer
jobs.column.jobnumber=Job Number
@@ -637,11 +634,10 @@ jobs.dialog.complete.title=Complete Job
jobs.dialog.complete.text=Do you want to manually complete job {0}?
jobs.dialog.complete.confirm=Complete
jobs.dialog.delete.title=Delete Job
jobs.dialog.delete.text=Do you really want to delete job {0}?
jobs.notification.completed=Job {0} completed
jobs.notification.complete.error=Error completing job: {0}
jobs.notification.deleted=Job {0} deleted
jobs.notification.delete.error=Error deleting job: {0}
jobs.dialog.delete.text=Are you sure you want to delete job {0}?
jobs.notification.complete.error=Error completing: {0}
jobs.notification.deleted=Job {0} has been deleted
jobs.notification.delete.error=Error deleting: {0}
# Create Invoice
createinvoice.title=Create Invoice \u2013 Job {0}
@@ -657,12 +653,12 @@ createinvoice.field.customer=Customer
createinvoice.field.status=Status
createinvoice.field.price=Price
createinvoice.route.distance=Distance
createinvoice.route.duration=Duration
createinvoice.route.duration=Travel Time
createinvoice.column.service=Service
createinvoice.column.basis=Calculation Basis
createinvoice.summary.net=Net Total
createinvoice.summary.vat=VAT ({0}%)
createinvoice.summary.total=Total Amount
createinvoice.summary.total=Grand Total
createinvoice.notification.noservices=Please select at least one service
createinvoice.notification.nouser=User not found
createinvoice.notification.notemplate=No invoice template found
@@ -672,7 +668,7 @@ createinvoice.preview.title=Invoice Preview
createinvoice.preview.number=PREVIEW
createinvoice.button.save=Save
createinvoice.confirm.save.title=Save Invoice
createinvoice.confirm.save.message=This invoice will be permanently saved and can no longer be modified. Continue?
createinvoice.confirm.save.message=This invoice will be permanently saved and cannot be modified afterwards. Continue?
createinvoice.confirm.save.confirm=Yes, save
# Invoices
@@ -686,8 +682,8 @@ invoices.empty=No invoices have been created yet.
invoices.notification.pdf.missing=No PDF is stored for this invoice.
# My Invoices
myinvoices.title=My Invoices
myinvoices.hint.noopen=You have no open invoices. All invoices are settled.
myinvoices.title=Invoices
myinvoices.hint.noopen=You have no open invoices. All invoices have been paid.
myinvoices.bank.institute=Bank
myinvoices.bank.beneficiary=Beneficiary
myinvoices.bank.iban=IBAN
@@ -695,10 +691,10 @@ myinvoices.recipient.name=Customer
myinvoices.recipient.department=
myinvoices.item.description=Item: {0}
myinvoices.card.open=Open Invoices
myinvoices.card.bank=Bank Account
myinvoices.bank.reference=Reference
myinvoices.card.bank=Bank Details
myinvoices.bank.reference=Payment Reference
myinvoices.section.title=All Invoices
myinvoices.filter.pagesize=Entries per page
myinvoices.filter.pagesize=Entries per Page
myinvoices.filter.search=Search
myinvoices.filter.search.placeholder=Search invoice number...
myinvoices.column.status=Status
@@ -721,7 +717,7 @@ appuser.column.appcode=App Code
appuser.column.email=Email
# Statistics
statistics.title=AI Statistics
statistics.title=Statistics
statistics.subtitle=Ask questions about your jobs and customers
statistics.prompt.placeholder=Enter question...
statistics.quick.jobcount=Number of Jobs
@@ -729,9 +725,9 @@ statistics.quick.jobcount.prompt=How many jobs do I currently have?
statistics.quick.revenue=Revenue
statistics.quick.revenue.prompt=What is my revenue this month?
statistics.quick.trend=Trends
statistics.quick.trend.prompt=Show me trends in the last 3 months
statistics.quick.trend.prompt=Show me trends from the last 3 months as a bar chart
statistics.ai.label=AI Response
statistics.data.fetched=Data fetched
statistics.data.fetched=Data has been retrieved
statistics.loading=Calculating...
# Job Status
@@ -754,12 +750,12 @@ passwordreset.button.submit=Save Password
passwordreset.button.cancel=Cancel
passwordreset.button.send=Send Email
passwordreset.notification.enterpassword=Please enter a new password
passwordreset.notification.mismatch=Passwords do not match
passwordreset.notification.success=Password changed successfully
passwordreset.notification.mismatch=The passwords do not match
passwordreset.notification.success=Password has been changed successfully
passwordreset.notification.invalidtoken=Token invalid or expired
passwordreset.notification.entermail=Please enter email
passwordreset.notification.sent=If the email exists, a link has been sent
passwordreset.notification.wait=Please wait {0} seconds before sending the code again
passwordreset.notification.wait=Please wait {0} seconds before resending the code
# Email
email.2fa.subject=Your VotianLT Verification Code
@@ -779,31 +775,31 @@ register.phone=Phone Number
register.company=Company
register.street=Street
register.housenr=House No.
register.zip=Zip Code
register.zip=Postal Code
register.city=City
register.code.label=Verification Code (6 digits)
register.code.placeholder=e.g. 123456
register.button.submit=Register
register.button.verify=Verify Code and Register
register.button.resend=Resend Code
register.button.back=Back to Start Page
register.button.back=Back to Home
register.notification.email.required=Please enter an email address
register.notification.email.invalid=Please enter a valid email address
register.notification.email.duplicate=A user with this email address already exists
register.notification.password.required=Please enter a password
register.notification.password.min=Password must be at least 6 characters long
register.notification.password.mismatch=Passwords do not match
register.notification.password.min=The password must be at least 6 characters long
register.notification.password.mismatch=The passwords do not match
register.notification.firstname.required=Please enter your first name
register.notification.lastname.required=Please enter your last name
register.notification.phone.required=Please enter your phone number
register.notification.company.required=Please enter the company name
register.notification.street.required=Please enter the street
register.notification.housenr.required=Please enter the house number
register.notification.zip.required=Please enter the zip code
register.notification.zip.required=Please enter the postal code
register.notification.city.required=Please enter the city
register.notification.code.sent=A verification code has been sent to {0}
register.notification.code.emailerror=Error sending email: {0}
register.notification.code.expired=The code has expired. Please request a new code.
register.notification.code.expired=The code has expired. Please send a new code.
register.notification.code.invalid=The entered code is invalid
register.notification.code.startfirst=Please start the registration first
register.notification.code.required=Please enter the 6-digit code
@@ -812,29 +808,29 @@ register.notification.failed=Registration failed: {0}
# Start Page
start.title=VotianLT - Your Digital Transport Partner
start.button.login=Login
start.button.login=Log In
start.button.register=Register
start.button.createorder=Create Order
start.button.createorder=Create Job
start.button.notifications=Notifications
start.button.nonotifications=No new notifications
start.hero.description=For solo self-employed and small business owners in the transport industry - fully digital and all-in-one. Focus on your business, we take care of the paperwork.
start.system.title=The System
start.system.intro=For solo self-employed and small business owners in the transport industry, it is crucial to focus primarily on their core business: winning customers and delivering goods from A to B.
start.feature.setup.title=Setup Assistant
start.feature.setup.desc=Use the setup assistant to complete your user profile.
start.feature.customers.title=Customer and Job Management
start.feature.customers.desc=With customer and job management, you always have all contact details and job details in view.
start.system.intro=For solo self-employed and small business owners in the transport industry, it is crucial that they can primarily focus on their actual business: winning customers and delivering goods from A to B.
start.feature.setup.title=Setup Wizard
start.feature.setup.desc=With the setup wizard, you can complete your user profile.
start.feature.customers.title=Customer & Job Management
start.feature.customers.desc=With customer and job management, you always have all contact details and job details at a glance.
start.feature.jobs.title=Job Creation
start.feature.jobs.desc=Create jobs in the system with just a few clicks and determine which employee should process which transport job.
start.feature.jobs.desc=Create jobs in the system with just a few clicks and assign which employee should handle which transport job.
start.app.title=The App
start.app.description=Every job can optionally be processed via the votianLT app - completely without "paperwork". All relevant job information goes directly to the driver's smartphone.
start.app.description=Every job can optionally be processed via the votianLT app \u2013 completely paperless. All relevant job information is delivered directly to the driver's smartphone.
start.imprint.title=Imprint
start.imprint.company=Assecutor Data Service GmbH
start.imprint.address=Ottensener Str. 8, 22525 Hamburg
start.imprint.phone=Phone: +49 40 18 123 771 0
start.imprint.email=Email: ahoi@assecutor.de
start.cta.text=Register today and use the free trial month to test the system thoroughly.
start.slogan=Run your business smart with votianLT!
start.cta.text=Register today and use the free trial month to put the system through its paces.
start.slogan=Run your business smart ... with votianLT!
start.version=Version
# Login View
@@ -847,14 +843,14 @@ login.version=Version
messagedetails.button.send=Send
messagedetails.placeholder=Enter message...
messagedetails.noimage=(no image content)
messagedetails.imageerror=(image could not be loaded)
messagedetails.imageerror=(Image could not be loaded)
# Invoice Generator
invoicegenerator.properties.title=Properties
invoicegenerator.properties.type=Type
invoicegenerator.fontsize.label=Font Size
invoicegenerator.color.label=Text Color
invoicegenerator.color.dialog.title=Choose Text Color
invoicegenerator.color.label=Font Color
invoicegenerator.color.dialog.title=Choose Font Color
invoicegenerator.color.dialog.hex=Hex Color Value
invoicegenerator.button.cancel=Cancel
invoicegenerator.button.apply=Apply
@@ -864,10 +860,11 @@ invoicegenerator.upload.drop=Drag image here or click
invoicegenerator.upload.success=Image uploaded successfully
invoicegenerator.upload.error=Error uploading: {0}
invoicegenerator.file.rejected=File rejected: {0}
invoicegenerator.properties.select.info=Click on an element in the canvas to edit its properties
invoicegenerator.properties.select.info=Click on an element in the canvas to edit its properties.
invoicegenerator.template.vline=Vertical Line
# CSV Export
csv.header.customer=Customer
csv.header.customer=Client
csv.header.jobnumber=Job Number
csv.header.jobdate=Job Date
csv.header.destination=Destination
@@ -876,7 +873,7 @@ csv.filename=jobs.csv
# DatePicker I18n
datepicker.month.januar=January
datepicker.month.februar=February
datepicker.month.märz=March
datepicker.month.m\u00e4rz=March
datepicker.month.april=April
datepicker.month.mai=May
datepicker.month.juni=June
@@ -893,13 +890,13 @@ datepicker.weekday.mittwoch=Wednesday
datepicker.weekday.donnerstag=Thursday
datepicker.weekday.freitag=Friday
datepicker.weekday.samstag=Saturday
datepicker.weekdayshort.so=Su
datepicker.weekdayshort.mo=Mo
datepicker.weekdayshort.di=Tu
datepicker.weekdayshort.mi=We
datepicker.weekdayshort.do=Th
datepicker.weekdayshort.fr=Fr
datepicker.weekdayshort.sa=Sa
datepicker.weekdayshort.so=Sun
datepicker.weekdayshort.mo=Mon
datepicker.weekdayshort.di=Tue
datepicker.weekdayshort.mi=Wed
datepicker.weekdayshort.do=Thu
datepicker.weekdayshort.fr=Fri
datepicker.weekdayshort.sa=Sat
# Job History
jobhistory.status.pickupscheduled=Pickup Scheduled
@@ -908,11 +905,11 @@ jobhistory.status.intransit=In Transit
jobhistory.status.delivered=Delivered
jobhistory.image.alt=Enlarged Photo
jobhistory.title=Job History
jobhistory.header=Job history for {0}
jobhistory.header=Job History for {0}
jobhistory.info.customer=Customer: {0}
jobhistory.info.createdat=Created at: {0}
jobhistory.info.createdat=Created on: {0}
jobhistory.info.status=Status: {0}
jobhistory.count={0} history entries
jobhistory.count={0} entries in history
jobhistory.changedby=Changed by: {0}
# Version
@@ -927,7 +924,7 @@ management.companies=Companies
# User Menu
usermenu.profile=Show Profile
usermenu.settings=Settings
usermenu.logout=Logout
usermenu.logout=Log Out
# CTA Button
cta.freetest=Try for free now
@@ -937,14 +934,14 @@ misc.toggle.hide=Hide
misc.toggle.show=Show
misc.nodata=No data available
misc.loading=Loading data...
misc.error=Error occurred
misc.error=An error occurred
misc.retry=Retry
# Admin Price Table
adminpricetable.title=Price Table
adminpricetable.field.monthly=Monthly Base Package
adminpricetable.field.applicense=App Usage License
adminpricetable.field.revenue=Revenue Participation
adminpricetable.field.revenue=Revenue Share
adminpricetable.notification.saved=Price table has been saved
adminpricetable.notification.save.error=Error saving: {0}
adminpricetable.notification.load.error=Error loading: {0}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff