@@ -23,6 +23,24 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
'common.close': 'Close',
|
||||
'common.copied': 'Copied',
|
||||
'common.logged_out': 'Logged out',
|
||||
'errors.invalid_credentials': 'Invalid email or password',
|
||||
'errors.too_many_requests': 'Too many attempts. Wait a minute and try again.',
|
||||
'errors.validation_error': 'Some fields are not filled in correctly',
|
||||
'errors.version_mismatch': 'The tab changed since it was loaded. Check it and try again.',
|
||||
'errors.command_already_pending': 'A close request for this tab is already waiting for the terminal',
|
||||
'errors.tab_not_owned': 'This tab is no longer open on the terminal',
|
||||
'errors.device_revoked': 'The terminal for this tab has been unpaired',
|
||||
'errors.name_exists': 'This customer already exists',
|
||||
'errors.email_exists': 'A user with this email already exists',
|
||||
'errors.resend_not_configured': 'Sending email is not set up on the server',
|
||||
'errors.reminders_in_progress': 'Reminders are already being sent',
|
||||
'errors.no_email': 'This customer has no email address',
|
||||
'errors.forbidden': "You don't have permission to do this",
|
||||
'errors.venue_forbidden': "You don't have permission to do this",
|
||||
'errors.internal_error': 'Something went wrong on the server',
|
||||
'errors.network_error': "Can't reach the server. Check the connection.",
|
||||
'errors.session_expired': 'Your session has expired. Log in again.',
|
||||
'errors.unknown': 'An error occurred',
|
||||
'status_page.home': 'Back to KoolTap',
|
||||
'not_found.title': 'Page not found',
|
||||
'not_found.desc': "This page doesn't exist or has been moved.",
|
||||
@@ -56,7 +74,13 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
'tabs.email': 'Email',
|
||||
'tabs.amount': 'Amount',
|
||||
'tabs.items': 'Items',
|
||||
'tabs.version': 'Version',
|
||||
'tabs.search_placeholder': 'Search customers…',
|
||||
'tabs.sort': 'Sort by',
|
||||
'tabs.sort_name': 'Name',
|
||||
'tabs.sort_amount': 'Highest amount',
|
||||
'tabs.outstanding': '{amount} outstanding',
|
||||
'tabs.closing': 'Closing…',
|
||||
'tabs.no_matches': 'No tabs match "{query}"',
|
||||
'tabs.reported': 'Last updated',
|
||||
'tabs.actions': 'Actions',
|
||||
'tabs.close': 'Close',
|
||||
@@ -140,7 +164,8 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
'commands.load_failed': 'Failed to load commands',
|
||||
'commands.empty': 'No commands found',
|
||||
'commands.empty_desc': 'Commands appear here when you send close requests.',
|
||||
'commands.tab': 'Tab',
|
||||
'commands.customer': 'Customer',
|
||||
'commands.amount': 'Amount',
|
||||
'commands.requested': 'Requested By',
|
||||
'commands.payment': 'Payment',
|
||||
'commands.created': 'Created',
|
||||
@@ -157,7 +182,6 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
'settings.device': 'Terminal',
|
||||
'settings.paired': 'Paired as "{name}" — last seen {time}',
|
||||
'settings.not_paired': 'No terminal paired yet',
|
||||
'settings.connected': 'Connected',
|
||||
'settings.since': 'Since {date}',
|
||||
'settings.pair_desc': 'Pair a bar terminal to start receiving tabs.',
|
||||
'settings.pair': 'Pair Terminal',
|
||||
@@ -178,7 +202,8 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
'overview.title': 'Overview',
|
||||
'overview.open_tabs': 'Open Tabs',
|
||||
'overview.device': 'Terminal',
|
||||
'overview.pending': 'Pending Commands',
|
||||
'overview.pending': 'Waiting for terminal',
|
||||
'overview.outstanding': 'Outstanding',
|
||||
'overview.recent': 'Recent Commands',
|
||||
'overview.no_commands': 'No commands yet',
|
||||
'overview.not_connected': 'Not connected',
|
||||
@@ -199,6 +224,7 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
'reminders.no_candidates_desc': 'Make sure planned customers have an email address.',
|
||||
'reminders.send': 'Send Reminders',
|
||||
'reminders.sending': 'Sending...',
|
||||
'reminders.admin_only': 'Only admins can send reminders.',
|
||||
'reminders.confirm': 'This emails a payment reminder to {count} customer(s) with an unpaid tab.',
|
||||
'reminders.sent_title': 'Reminders Sent',
|
||||
'reminders.partial': '{sent} of {total} sent',
|
||||
@@ -233,6 +259,24 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
'common.close': 'Sluiten',
|
||||
'common.copied': 'Gekopieerd',
|
||||
'common.logged_out': 'Uitgelogd',
|
||||
'errors.invalid_credentials': 'Ongeldig e-mailadres of wachtwoord',
|
||||
'errors.too_many_requests': 'Te veel pogingen. Wacht een minuutje en probeer opnieuw.',
|
||||
'errors.validation_error': 'Sommige velden zijn niet correct ingevuld',
|
||||
'errors.version_mismatch': 'De poef is gewijzigd sinds ze geladen werd. Controleer en probeer opnieuw.',
|
||||
'errors.command_already_pending': 'Er wacht al een sluitverzoek voor deze poef op de kassa',
|
||||
'errors.tab_not_owned': 'Deze poef staat niet meer open op de kassa',
|
||||
'errors.device_revoked': 'De kassa van deze poef is ontkoppeld',
|
||||
'errors.name_exists': 'Deze klant bestaat al',
|
||||
'errors.email_exists': 'Er bestaat al een gebruiker met dit e-mailadres',
|
||||
'errors.resend_not_configured': 'E-mail versturen is niet ingesteld op de server',
|
||||
'errors.reminders_in_progress': 'Herinneringen worden al verstuurd',
|
||||
'errors.no_email': 'Deze klant heeft geen e-mailadres',
|
||||
'errors.forbidden': 'Je hebt geen toestemming om dit te doen',
|
||||
'errors.venue_forbidden': 'Je hebt geen toestemming om dit te doen',
|
||||
'errors.internal_error': 'Er ging iets mis op de server',
|
||||
'errors.network_error': 'Kan de server niet bereiken. Controleer de verbinding.',
|
||||
'errors.session_expired': 'Je sessie is verlopen. Log opnieuw in.',
|
||||
'errors.unknown': 'Er is een fout opgetreden',
|
||||
'status_page.home': 'Terug naar KoolTap',
|
||||
'not_found.title': 'Pagina niet gevonden',
|
||||
'not_found.desc': 'Deze pagina bestaat niet of is verplaatst.',
|
||||
@@ -266,7 +310,13 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
'tabs.email': 'E-mail',
|
||||
'tabs.amount': 'Bedrag',
|
||||
'tabs.items': 'Items',
|
||||
'tabs.version': 'Versie',
|
||||
'tabs.search_placeholder': 'Zoek klant…',
|
||||
'tabs.sort': 'Sorteer op',
|
||||
'tabs.sort_name': 'Naam',
|
||||
'tabs.sort_amount': 'Hoogste bedrag',
|
||||
'tabs.outstanding': '{amount} openstaand',
|
||||
'tabs.closing': 'Wordt gesloten…',
|
||||
'tabs.no_matches': 'Geen poefs gevonden voor "{query}"',
|
||||
'tabs.reported': 'Laatst bijgewerkt',
|
||||
'tabs.actions': 'Acties',
|
||||
'tabs.close': 'Sluiten',
|
||||
@@ -350,7 +400,8 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
'commands.load_failed': 'Opdrachten konden niet geladen worden',
|
||||
'commands.empty': 'Geen opdrachten gevonden',
|
||||
'commands.empty_desc': 'Opdrachten verschijnen hier zodra je een sluitverzoek verstuurt.',
|
||||
'commands.tab': 'Poef',
|
||||
'commands.customer': 'Klant',
|
||||
'commands.amount': 'Bedrag',
|
||||
'commands.requested': 'Aangevraagd door',
|
||||
'commands.payment': 'Betaling',
|
||||
'commands.created': 'Aangemaakt',
|
||||
@@ -367,7 +418,6 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
'settings.device': 'Terminal',
|
||||
'settings.paired': 'Gekoppeld als "{name}" — laatst gezien {time}',
|
||||
'settings.not_paired': 'Nog geen terminal gekoppeld',
|
||||
'settings.connected': 'Verbonden',
|
||||
'settings.since': 'Sinds {date}',
|
||||
'settings.pair_desc': 'Koppel een barterminal om poefs te ontvangen.',
|
||||
'settings.pair': 'Terminal koppelen',
|
||||
@@ -388,7 +438,8 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
'overview.title': 'Overzicht',
|
||||
'overview.open_tabs': 'Open poefs',
|
||||
'overview.device': 'Terminal',
|
||||
'overview.pending': 'Openstaande opdrachten',
|
||||
'overview.pending': 'Wachtend op kassa',
|
||||
'overview.outstanding': 'Openstaand',
|
||||
'overview.recent': 'Recente opdrachten',
|
||||
'overview.no_commands': 'Nog geen opdrachten',
|
||||
'overview.not_connected': 'Niet verbonden',
|
||||
@@ -409,6 +460,7 @@ const translations: Record<Lang, Record<string, string>> = {
|
||||
'reminders.no_candidates_desc': 'Zorg dat geplande klanten een e-mailadres hebben.',
|
||||
'reminders.send': 'Herinneringen versturen',
|
||||
'reminders.sending': 'Bezig met versturen...',
|
||||
'reminders.admin_only': 'Alleen beheerders kunnen herinneringen versturen.',
|
||||
'reminders.confirm': 'Er wordt een betalingsherinnering gemaild naar {count} klant(en) met een openstaande poef.',
|
||||
'reminders.sent_title': 'Herinneringen verstuurd',
|
||||
'reminders.partial': '{sent} van {total} verstuurd',
|
||||
@@ -436,6 +488,8 @@ interface LangContextType {
|
||||
t: (key: string, params?: Params) => string;
|
||||
/** Uses `<key>_plural` unless `count` is 1; `{count}` is filled in. */
|
||||
tn: (key: string, count: number, params?: Params) => string;
|
||||
/** An API error in the current language: by its code when translated, else its message. */
|
||||
te: (error: unknown, fallbackKey?: string) => string;
|
||||
/** "just now", "5m ago"…, falling back to a date after a week. */
|
||||
relativeTime: (date: string | Date) => string;
|
||||
dateTime: (date: string | Date) => string;
|
||||
@@ -459,6 +513,12 @@ function helpers(lang: Lang): Omit<LangContextType, 'setLang'> {
|
||||
lang,
|
||||
t,
|
||||
tn: (key, count, params) => t(count === 1 ? key : `${key}_plural`, { count, ...params }),
|
||||
te: (error, fallbackKey = 'common.unknown_error') => {
|
||||
const { code, message } = (error ?? {}) as { code?: string; message?: string };
|
||||
const key = `errors.${code}`;
|
||||
if (code && (translations[lang][key] ?? translations.en[key])) return t(key);
|
||||
return message || t(fallbackKey);
|
||||
},
|
||||
relativeTime: (value) => {
|
||||
const date = new Date(value);
|
||||
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
||||
|
||||
Reference in New Issue
Block a user