fix(i18n): a quantity under another name than count is a plural group too

The count-string gate only knew {count} and {n}, the params t() picks a
plural form by. A quantity named anything else kept one form in every
language and passed: Russian read "Все {days} дней" for 2 days and
"{objects} объектов" for 3 objects, Greek "{minutes} λεπτά" for 1 minute.

Twelve such en strings become plural groups with their forms in all 27
locales, the quantity renamed to {count}: the trip length limit, the
chat image limit, the API key limit, the storage move prompt and its
reclaimable line, the Dawarich wish rule and suggestion duration, the
road trip lateness warnings, and the road trip echo and window texts.
Their callers pass the count as a number; the storage panels used to
pass it as a string, which no form is picked by. The reclaimable line
loses its dash for a comma in every locale while it is rewritten.

i18n-parity now treats the quantity names in QUANTITY_PARAMS ({days},
{minutes}, {objects}, {max} and the like) like {count}. The four strings
where the number is followed by nothing but a unit symbol (the Dawarich
durations, the upload size limit) go on NOT_PLURAL with that reason.
This commit is contained in:
Maurice
2026-10-08 13:00:39 +02:00
parent c0516dd9bf
commit 9a7c17e7cc
183 changed files with 864 additions and 357 deletions
@@ -982,11 +982,24 @@ describe('AdminStoragePanel', () => {
);
expect(screen.getByText(/Moving Trip documents… 2\/5/)).toBeInTheDocument();
expect(screen.getByText(/Move finished: 4 copied, 0 skipped/)).toBeInTheDocument();
expect(screen.getByText(/4 objects \(4\.0 KB\) remain on uploads-local — reclaim manually/)).toBeInTheDocument();
expect(screen.getByText(/4 objects \(4\.0 KB\) remain on uploads-local, reclaim manually/)).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Cancel move' }));
await waitFor(() => expect(cancelledCategory).toBe('files'));
});
it('FE-ADMIN-STOR-050: the reclaimable line takes the plural form for the object count', async () => {
const state = baseState();
(state as StorageAdminState).migrations = [
{
category: 'journey', from: 'uploads-local', to: 'off-box',
status: 'done', done: 1, total: 1, copied: 1, skipped: 0, failed: 0, startedAt: 1, finishedAt: 2,
reclaimable: { objects: 1, bytes: 1024 },
},
];
await renderPanel(state);
expect(screen.getByText(/1 object \(1\.0 KB\) remains on uploads-local, reclaim manually/)).toBeInTheDocument();
});
it('FE-ADMIN-STOR-038: a done migration with sweep failures shows the doneFailures line; a clean one shows nothing extra', async () => {
const state = baseState();
(state as StorageAdminState).migrations = [
@@ -716,7 +716,7 @@ export default function AdminStoragePanel(): React.ReactElement {
{m.reclaimable && (
<p className={META} style={fs(11.5)}>
{t('storage.migrate.reclaimable', {
objects: String(m.reclaimable.objects),
count: m.reclaimable.objects,
size: formatBytes(m.reclaimable.bytes),
from: m.from,
})}
@@ -837,7 +837,7 @@ export default function AdminStoragePanel(): React.ReactElement {
})
: t('storage.migrate.promptLine', {
category: t(`storage.category.${c.category}`),
objects: String(c.objects),
count: c.objects,
size: formatBytes(c.bytes ?? 0),
from: c.from,
to: c.to,
@@ -124,7 +124,7 @@ export function useCollabChat(tripId: any, currentUser: any) {
const addImageFiles = useCallback((incoming: File[] | FileList) => {
images.add(incoming, ({ rejected, overflow }) => {
if (rejected) toast.error(t('collab.chat.imageRejected'))
if (overflow) toast.error(t('collab.chat.imageLimit', { max: MAX_CHAT_IMAGES }))
if (overflow) toast.error(t('collab.chat.imageLimit', { count: MAX_CHAT_IMAGES }))
})
}, [images, toast, t])
const removeImage = images.remove
@@ -147,7 +147,7 @@ export default function DawarichAcceptDialog({
</div>
<div className="mt-2 flex flex-wrap items-center gap-1">
<DawarichBadge icon={Hourglass} size="sm">
{t('dawarich.accept.duration', { minutes: suggestion.durationMinutes })}
{t('dawarich.accept.duration', { count: suggestion.durationMinutes })}
</DawarichBadge>
{/* The source's own verdict, shown here rather than on every row of
the list: since Dawarich 1.12.0 "unconfirmed" is the normal state
@@ -180,7 +180,7 @@ export default function DawarichAcceptDialog({
{' — '}
{t('dawarich.bucket.rule', {
meters: DAWARICH_BUCKET_MATCH_RADIUS_M,
minutes: DAWARICH_BUCKET_MATCH_MIN_MINUTES,
count: DAWARICH_BUCKET_MATCH_MIN_MINUTES,
})}
</span>
</p>
@@ -216,7 +216,7 @@ export default function DawarichAtlasDialog({
scan?.truncated ? t('dawarich.bucket.truncated') : null,
t('dawarich.bucket.rule', {
meters: DAWARICH_BUCKET_MATCH_RADIUS_M,
minutes: DAWARICH_BUCKET_MATCH_MIN_MINUTES,
count: DAWARICH_BUCKET_MATCH_MIN_MINUTES,
}),
]}
>
@@ -208,7 +208,7 @@ export function Arrival({ entry }: { entry: ScheduleEntry }): React.ReactElement
*/
export function LateBadge({ late }: { late: ScheduleWarning }): React.ReactElement {
const { t } = useTranslation()
const label = t(late.code === 'missedLeave' ? 'roadtrip.warn.missedLeave' : 'roadtrip.warn.late', { minutes: late.minutes ?? 0 })
const label = t(late.code === 'missedLeave' ? 'roadtrip.warn.missedLeave' : 'roadtrip.warn.late', { count: late.minutes ?? 0 })
// The same two-part shell the stay and the drive findings wear, so a row of badges reads
// as one set instead of a pill among boxes.
return (
@@ -264,7 +264,7 @@ function LeaveTime({ stop, leave, until: leaveAt, is12h, onClose }: {
{leave.missedBy !== null ? (
<p className="flex items-center justify-center gap-1.5 text-center text-body font-medium text-warning">
<AlertTriangle size={14} className="shrink-0" aria-hidden />
{t('roadtrip.warn.missedLeave', { minutes: leave.missedBy })}
{t('roadtrip.warn.missedLeave', { count: leave.missedBy })}
</p>
) : (
<p className="text-center text-body text-content-secondary">
@@ -250,7 +250,7 @@ export default function ApiKeysSection(): React.ReactElement {
{atLimit && (
<div className="flex gap-2.5 rounded-[12px] bg-warning-soft px-3.5 py-2.5 text-content" style={fs(12.5, 'body')}>
<AlertTriangle size={15} className="mt-px flex-none text-warning" />
<p className="m-0 leading-snug">{t('settings.apiKeys.limitReached', { max: MAX_KEYS })}</p>
<p className="m-0 leading-snug">{t('settings.apiKeys.limitReached', { count: MAX_KEYS })}</p>
</div>
)}
@@ -169,7 +169,7 @@ export default function TripFormModal({ isOpen, onClose, onSave, trip, onCoverUp
// Only a range being set is held to the limit, as on the server: a trip that
// already carries a longer one can still be renamed.
const datesTouched = !trip || formData.start_date !== (trip.start_date || '') || formData.end_date !== (trip.end_date || '')
if (datesTouched && span > MAX_TRIP_DAYS) { setError(t('dashboard.tripTooLong', { days: MAX_TRIP_DAYS })); return }
if (datesTouched && span > MAX_TRIP_DAYS) { setError(t('dashboard.tripTooLong', { count: MAX_TRIP_DAYS })); return }
}
if (!formData.start_date && !formData.end_date) {
const dc = Number(formData.day_count)
@@ -703,7 +703,7 @@ export default function MAdminStoragePanel(): React.ReactElement {
{m.reclaimable && (
<p className="mt-1 font-geist text-[0.625rem] text-m-muted">
{t('storage.migrate.reclaimable', {
objects: String(m.reclaimable.objects),
count: m.reclaimable.objects,
size: formatBytes(m.reclaimable.bytes),
from: m.from,
})}
@@ -834,7 +834,7 @@ export default function MAdminStoragePanel(): React.ReactElement {
})
: t('storage.migrate.promptLine', {
category: t(`storage.category.${c.category}`),
objects: String(c.objects),
count: c.objects,
size: formatBytes(c.bytes ?? 0),
from: c.from,
to: c.to,
@@ -127,7 +127,7 @@ export default function MNewTripSheet({ open, trip, onClose, onSave, onCoverUpda
const span = tripSpanDays(startDate, endDate)
if (span < 1) { setError(t('dashboard.endDateError')); return null }
const datesTouched = !trip || startDate !== (trip.start_date || '') || endDate !== (trip.end_date || '')
if (datesTouched && span > MAX_TRIP_DAYS) { setError(t('dashboard.tripTooLong', { days: MAX_TRIP_DAYS })); return null }
if (datesTouched && span > MAX_TRIP_DAYS) { setError(t('dashboard.tripTooLong', { count: MAX_TRIP_DAYS })); return null }
}
return {
title: title.trim(),
@@ -345,7 +345,7 @@ function RtLeaveTime({ name, leave, arrival, departure, onClose }: {
{leave.missedBy !== null ? (
<div className="mt-[10px] flex items-center justify-center gap-1.5 text-center text-[0.8125rem] font-medium leading-snug text-[color:var(--m-st-pending)]">
<AlertTriangle size={14} strokeWidth={2} className="flex-none" aria-hidden="true" />
{t('roadtrip.warn.missedLeave', { minutes: leave.missedBy })}
{t('roadtrip.warn.missedLeave', { count: leave.missedBy })}
</div>
) : (
<div className="mt-[10px] text-center text-[0.8125rem] leading-snug text-m-muted">
@@ -209,8 +209,8 @@ export default function MRtStopSheet({ planner, shell }: MTripSheetsProps) {
...day.driveWarnings.filter(w => w.index === index && (w.code === 'leg' || w.code === 'range')),
]
const warningText = (w: ScheduleWarning): string => {
if (w.code === 'late') return t('roadtrip.warn.late', { minutes: w.minutes ?? 0 })
if (w.code === 'missedLeave') return t('roadtrip.warn.missedLeave', { minutes: w.minutes ?? 0 })
if (w.code === 'late') return t('roadtrip.warn.late', { count: w.minutes ?? 0 })
if (w.code === 'missedLeave') return t('roadtrip.warn.missedLeave', { count: w.minutes ?? 0 })
if (w.code === 'range') return t('roadtrip.limit.range', { distance: formatDistance(w.sinceKm ?? 0, unit) })
return t('roadtrip.limit.legOver', { time: formatDurationShort((w.overMinutes ?? 0) * 60) })
}
@@ -162,7 +162,7 @@ export default function MCollabChat({ planner }: MCollabChatProps) {
const addImageFiles = (incoming: FileList | File[]) => {
images.add(incoming, ({ rejected, overflow }) => {
if (rejected) toast.error(t('collab.chat.imageRejected'))
if (overflow) toast.error(t('collab.chat.imageLimit', { max: MAX_CHAT_IMAGES }))
if (overflow) toast.error(t('collab.chat.imageLimit', { count: MAX_CHAT_IMAGES }))
})
}
+1 -1
View File
@@ -33,5 +33,5 @@ export function datedDayOption(
): Pick<DayAddControls, 'nextDate' | 'datedBlocked'> {
const plan = trip ? planDatedAppend(trip, days.map(d => d.date)) : null
if (!plan) return { nextDate: null, datedBlocked: null }
return { nextDate: plan.date, datedBlocked: plan.fits ? null : t('dashboard.tripTooLong', { days: MAX_TRIP_DAYS }) }
return { nextDate: plan.date, datedBlocked: plan.fits ? null : t('dashboard.tripTooLong', { count: MAX_TRIP_DAYS }) }
}
@@ -415,7 +415,7 @@ describe('MAdminStoragePanel', () => {
);
expect(screen.getByText(/Moving Trip documents… 2\/5/)).toBeInTheDocument();
expect(screen.getByText(/Move finished: 4 copied, 0 skipped/)).toBeInTheDocument();
expect(screen.getByText(/4 objects \(4\.0 KB\) remain on uploads-local — reclaim manually/)).toBeInTheDocument();
expect(screen.getByText(/4 objects \(4\.0 KB\) remain on uploads-local, reclaim manually/)).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Cancel move' }));
await waitFor(() => expect(cancelledCategory).toBe('files'));
});
+1 -1
View File
@@ -51,7 +51,7 @@ The parity rule governs **existing** routes; it is not a license to mint new deb
- **`en/` is canonical.** Every other locale must have the identical file set and top-level keys — `i18n:parity:strict` enforces it in CI. When you add or rename a key, update every locale.
- **Every locale gets a real translation; an English placeholder is not acceptable.** Write native phrasing with the locale's own punctuation and the same `{placeholders}` as `en`; if you genuinely cannot translate one, say so instead of filling it with English. Parity only checks that the key exists, so `i18n:parity:strict` also runs a ratchet (`scripts/i18n-untranslated.mjs`): per locale and file, the `// en-fallback` markers and the unmarked values identical to `en` may not grow past `scripts/i18n-untranslated-baseline.json`. An identical value is excused only when, after dropping placeholders, markup, URLs/e-mail/paths, the product names in `BRAND_NAMES` (Google Maps, Atlas, Immich) and the unit symbols in `UNITS`, no plain word is left (a word with a capital after its first letter, an acronym or a single letter does not count), so "{count} km", "GPX", "OAuth" and "Atlas" pass while "Settings" and "Source code" count. A single ordinary word that really is the locale's own (German "Status", French "Transport") carries `// same-as-en` on its declaration; the marker is honoured only on a value holding one plain word once the excused parts are dropped (on a phrase it is ignored, so it cannot wave a copied sentence through) and only in Latin-script locales (`LATIN_LOCALES`), because a Latin word in Japanese or Arabic is English whatever it says. A plural form en has no key for (Russian `.few`, Arabic `.two`) counts when it equals any form en spells out for that group. Every locale folder must be on `LATIN_LOCALES` or `NON_LATIN_LOCALES`, so a new language is classified there first. Deleting a marker without translating moves the string from one count to the other and still fails. After translating, run `npm run i18n:untranslated:update` to lower the baseline; nothing raises it.
- **Count-bearing strings use plural forms, never `count === 1` in the caller.** The key holds the general form (the CLDR "other" category) and `key.zero|one|two|few|many` hold the forms the language's `Intl.PluralRules` selects for whole numbers (`plural.ts`); `t(key, { count })` (or `{ n }`) picks one. Pass the count as a number. Russian needs `.one`, `.few` and `.many`, Arabic all five, Japanese none. `i18n:parity` checks that every locale has exactly the forms its language reaches with whole numbers and none it never selects. A form may spell the number out (Arabic "ملف واحد") only where its category is that one number; French `one` also covers 0 and Russian `one` covers 21, so they keep `{count}`. A string whose wording changes with the number (the old "Copy to trip" for one place, "Copy {count} to trip" for more) is a plural group whose `one` drops the number where the language allows it, not two keys switched in the caller.
- **An `en` string carrying `{count}` or `{n}` must be a plural group**, even when the forms read the same (a label such as "Notes: {count}"), so every language can inflect. `i18n:parity` refuses one that is not, unless `NOT_PLURAL` in `scripts/i18n-parity.mjs` lists it with a reason: only for a number that is never a quantity (a day number, a step, a multiplier). An entry that stops matching fails too.
- **An `en` string carrying `{count}` or `{n}` must be a plural group**, even when the forms read the same (a label such as "Notes: {count}"), so every language can inflect. **A quantity is always named `{count}` (or `{n}`)**, the only params `t()` picks a form by: "All {days} days" read "Все 3 дней" in Russian. `i18n:parity` refuses an ungrouped string carrying `{count}`, `{n}` or one of the quantity names in `QUANTITY_PARAMS` (`{days}`, `{minutes}`, `{objects}`, `{max}` …), unless `NOT_PLURAL` in `scripts/i18n-parity.mjs` lists it with a reason: only for a number that is never a quantity (a day number, a step, a multiplier) or one followed by nothing but a unit symbol ("{minutes} min", "{max} MB"). A second number in the same string keeps its own name ("{over} of {count} days"). An entry that stops matching fails too.
- **Further i18n specs run in `npm test`** and fail changes that parity lets through: placeholder parity (every `{placeholder}` in an `en` string must appear in each translation), no `en` string may call TREK "self-hosted" (the same build runs on managed installs; exemptions live in the spec), and a wording regression test for plugin permission descriptions (currently one string; add a case there when a permission's real behavior changes). None of these are part of `i18n:parity`, which checks key sets, plural forms, count strings and the untranslated ratchet.
- **The i18n scripts read the tables as text** through `scripts/i18n-catalog.mjs`: a value must be one string literal on the key's line or the next, and anything else (a concatenation, a template literal) is an error rather than a skipped key. Their specs use the fixtures in `scripts/fixtures/`. That en holds every key the client names is checked on the client side (`npm run lint:i18n-keys` in `client/`).
+1
View File
@@ -10,5 +10,6 @@ const a = {
'a.quote': 'Es ist hier',
'a.multi':
'A long sentence that wraps onto the next line',
'a.stay': '{days} Tage bleiben',
};
export default a;
+1
View File
@@ -10,5 +10,6 @@ const a = {
'a.quote': "It's here",
'a.multi':
'A long sentence that wraps onto the next line',
'a.stay': 'Stay {days} days',
};
export default a;
+36 -7
View File
@@ -21,8 +21,11 @@
// `Intl.PluralRules`, through the same helpers the client resolves with.
// 4. Count strings: an en string carrying {count} or {n} (the two params
// `t()` picks a plural form by) must be a plural group, so every
// language gets the forms it needs. The exceptions are NOT_PLURAL: a
// number that is never a quantity ("Day {n}", a step, a multiplier).
// language gets the forms it needs. So must one carrying a quantity
// under another name ({days}, {minutes}, {objects}, QUANTITY_PARAMS),
// which no form is picked by: it is renamed to {count}. The exceptions
// are NOT_PLURAL: a number that is never a quantity ("Day {n}", a step,
// a multiplier), or one followed only by a unit symbol.
// 5. Untranslated strings: the marked `// en-fallback` and unmarked English
// copies per locale and file may only shrink (i18n-untranslated.mjs,
// which also lowers the baseline with --update).
@@ -94,13 +97,37 @@ export const NOT_PLURAL = [
{ key: 'system_notice.pager.goto', because: 'the position of a notice in the pager' },
{ key: 'tours.addedToDay', because: 'the number of the day a tour was added to' },
{ key: 'tours.planner.waypointLabel', because: 'the position of a waypoint on the tour' },
{ key: 'dawarich.duration.minutes', because: 'a duration in a unit symbol (min), the same for any number' },
{ key: 'dawarich.duration.hours', because: 'a duration in a unit symbol (h), the same for any number' },
{ key: 'dawarich.duration.hoursMinutes', because: 'a duration in unit symbols (h, min), the same for any number' },
{ key: 'files.uploadErrorSize', because: 'a size limit in a unit symbol (MB), the same for any number' },
];
const COUNT_PARAM_RE = /\{(?:count|n)\}/;
/**
* Param names that hold a quantity of something but are not the ones `t()`
* picks a plural form by. A string using one keeps a single form in every
* language ("Все {days} дней" for 2 days), so it is held like {count}: rename
* the param to {count} and make the string a plural group.
*/
export const QUANTITY_PARAMS = [
'days',
'nights',
'weeks',
'months',
'years',
'hours',
'minutes',
'seconds',
'items',
'objects',
'max',
];
const COUNT_PARAM_RE = new RegExp(`\\{(?:count|n|${QUANTITY_PARAMS.join('|')})\\}`);
/**
* en strings carrying a count param outside any plural group, and NOT_PLURAL
* entries that no longer name such a string.
* en strings carrying a count or quantity param outside any plural group,
* and NOT_PLURAL entries that no longer name such a string.
*/
function checkCountStrings(enFiles, notPlural = NOT_PLURAL, root = I18N_ROOT) {
const allowed = new Set(notPlural.map((e) => e.key));
@@ -233,9 +260,11 @@ function formatReport(report) {
if (ungrouped.length === 0 && stale.length === 0) {
lines.push('Count strings: OK');
} else {
lines.push('Count strings: en strings with {count} or {n} that are no plural group');
lines.push('Count strings: en strings with {count}, {n} or a quantity param that are no plural group');
for (const { file, key } of ungrouped) {
lines.push(` en/${file}: ${key} (add ${key}.one and the other forms, see shared/CLAUDE.md)`);
lines.push(
` en/${file}: ${key} (name the quantity {count} and add ${key}.one and the other forms, see shared/CLAUDE.md)`,
);
}
for (const key of stale) lines.push(` NOT_PLURAL lists ${key}, which is no ungrouped count string: remove it`);
}
+6 -1
View File
@@ -116,7 +116,12 @@ const collab: TranslationStrings = {
'collab.polls.delete': 'حذف',
'collab.polls.closedSection': 'مغلق',
'collab.notes.websitePlaceholder': 'https://...', // en-fallback
'collab.chat.imageLimit': 'يمكنك إرفاق ما يصل إلى {max} صور لكل رسالة',
'collab.chat.imageLimit': 'يمكنك إرفاق ما يصل إلى {count} صورة لكل رسالة',
'collab.chat.imageLimit.zero': 'يمكنك إرفاق ما يصل إلى {count} صورة لكل رسالة',
'collab.chat.imageLimit.one': 'يمكنك إرفاق صورة واحدة فقط لكل رسالة',
'collab.chat.imageLimit.two': 'يمكنك إرفاق صورتين كحد أقصى لكل رسالة',
'collab.chat.imageLimit.few': 'يمكنك إرفاق ما يصل إلى {count} صور لكل رسالة',
'collab.chat.imageLimit.many': 'يمكنك إرفاق ما يصل إلى {count} صورة لكل رسالة',
'collab.chat.uploading': 'جارٍ الرفع {percent}%',
'collab.chat.attachImages': 'إرفاق صور',
'collab.chat.attachedImage': 'صورة مرفقة',
+6 -1
View File
@@ -127,7 +127,12 @@ const dashboard: TranslationStrings = {
'dashboard.useUnsplashPhoto': 'استخدم صورة من Unsplash بواسطة {photographer}',
'dashboard.titleRequired': 'العنوان مطلوب',
'dashboard.endDateError': 'يجب أن يكون تاريخ النهاية بعد البداية',
'dashboard.tripTooLong': 'لا يمكن أن تتجاوز مدة الرحلة {days} يومًا',
'dashboard.tripTooLong': 'لا يمكن أن تتجاوز مدة الرحلة {count} يوم',
'dashboard.tripTooLong.zero': 'لا يمكن أن تتجاوز مدة الرحلة {count} يوم',
'dashboard.tripTooLong.one': 'لا يمكن أن تتجاوز مدة الرحلة يومًا واحدًا',
'dashboard.tripTooLong.two': 'لا يمكن أن تتجاوز مدة الرحلة يومين',
'dashboard.tripTooLong.few': 'لا يمكن أن تتجاوز مدة الرحلة {count} أيام',
'dashboard.tripTooLong.many': 'لا يمكن أن تتجاوز مدة الرحلة {count} يومًا',
'dashboard.dateShiftTitle': 'تاريخ بداية جديد',
'dashboard.dateShiftIntro': 'لقد غيّرت موعد بداية هذه الرحلة. كيف تريد أن تتبع خططك التواريخ الجديدة؟',
'dashboard.dateShiftKeepBookings': 'إبقاء الحجوزات في تواريخها',
+12 -2
View File
@@ -132,7 +132,12 @@ const dawarich: TranslationStrings = {
'dawarich.accept.confirm.journal': 'إضافة المدوّنة',
'dawarich.accept.confirm.bucket_list': 'اشطبها',
'dawarich.accept.recorded': 'مسجَّلة من {from} حتى {to}',
'dawarich.accept.duration': '{minutes} دقيقة',
'dawarich.accept.duration': '{count} دقيقة',
'dawarich.accept.duration.zero': '{count} دقيقة',
'dawarich.accept.duration.one': 'دقيقة واحدة',
'dawarich.accept.duration.two': 'دقيقتان',
'dawarich.accept.duration.few': '{count} دقائق',
'dawarich.accept.duration.many': '{count} دقيقة',
'dawarich.accept.name': 'الاسم',
'dawarich.accept.date': 'التاريخ',
'dawarich.accept.from': 'الوصول',
@@ -225,7 +230,12 @@ const dawarich: TranslationStrings = {
'dawarich.again': 'تحقق مرة أخرى',
'dawarich.bucket.metersAway': 'على بُعد {meters} م',
'dawarich.bucket.kilometersAway': 'على بُعد {km} كم',
'dawarich.bucket.rule': 'تُحتسب الأمنية محققة ضمن {meters} م وبعد {minutes} دقيقة في المكان.',
'dawarich.bucket.rule': 'تُحتسب الأمنية محققة ضمن {meters} م وبعد {count} دقيقة في المكان.',
'dawarich.bucket.rule.zero': 'تُحتسب الأمنية محققة ضمن {meters} م وبعد {count} دقيقة في المكان.',
'dawarich.bucket.rule.one': 'تُحتسب الأمنية محققة ضمن {meters} م وبعد دقيقة واحدة في المكان.',
'dawarich.bucket.rule.two': 'تُحتسب الأمنية محققة ضمن {meters} م وبعد دقيقتين في المكان.',
'dawarich.bucket.rule.few': 'تُحتسب الأمنية محققة ضمن {meters} م وبعد {count} دقائق في المكان.',
'dawarich.bucket.rule.many': 'تُحتسب الأمنية محققة ضمن {meters} م وبعد {count} دقيقة في المكان.',
'dawarich.journey.dayStays.other': '{count} إقامة من Dawarich',
'dawarich.journey.dayStays.zero': 'لا توجد إقامات من Dawarich',
'dawarich.journey.dayStays.one': 'إقامة واحدة من Dawarich',
+30 -5
View File
@@ -54,7 +54,12 @@ const roadtrip: TranslationStrings = {
'roadtrip.window.incomplete': 'يلزم توفر جميع المسارات الرابطة لحساب الاستراحات اليومية.',
'roadtrip.window.conflict':
'يتعارض وقت ثابت مع القيادة والاستراحات. تبقى الأوقات اليدوية كما هي؛ تم إيقاف التخطيط التلقائي مؤقتًا.',
'roadtrip.window.tooLong': 'تتطلب هذه الأوقات أكثر من {days} يومًا إضافيًا. اختر فترة يومية أطول.',
'roadtrip.window.tooLong': 'تتطلب هذه الأوقات أكثر من {count} يوم إضافي. اختر فترة يومية أطول.',
'roadtrip.window.tooLong.zero': 'تتطلب هذه الأوقات أكثر من {count} يوم إضافي. اختر فترة يومية أطول.',
'roadtrip.window.tooLong.one': 'تتطلب هذه الأوقات أكثر من يوم إضافي واحد. اختر فترة يومية أطول.',
'roadtrip.window.tooLong.two': 'تتطلب هذه الأوقات أكثر من يومين إضافيين. اختر فترة يومية أطول.',
'roadtrip.window.tooLong.few': 'تتطلب هذه الأوقات أكثر من {count} أيام إضافية. اختر فترة يومية أطول.',
'roadtrip.window.tooLong.many': 'تتطلب هذه الأوقات أكثر من {count} يومًا إضافيًا. اختر فترة يومية أطول.',
'roadtrip.mode.roadtrip': 'رحلة برية',
'roadtrip.mode.days': 'الأيام',
'roadtrip.mode.label': 'عرض الخطة',
@@ -105,8 +110,18 @@ const roadtrip: TranslationStrings = {
'roadtrip.limit.minutes': 'دقيقة',
'roadtrip.limit.off': 'معطل',
'roadtrip.limit.blockNote': 'كتلة واحدة = {distance}',
'roadtrip.limit.echoOver': '{over} من {days} أيام تتجاوز حدّك.',
'roadtrip.limit.echoNone': 'كل الأيام {days} ضمن حدودك.',
'roadtrip.limit.echoOver': '{over} من {count} يوم تتجاوز حدّك.',
'roadtrip.limit.echoOver.zero': '{over} من {count} يوم تتجاوز حدّك.',
'roadtrip.limit.echoOver.one': '{over} من يوم واحد يتجاوز حدّك.',
'roadtrip.limit.echoOver.two': '{over} من يومين تتجاوز حدّك.',
'roadtrip.limit.echoOver.few': '{over} من {count} أيام تتجاوز حدّك.',
'roadtrip.limit.echoOver.many': '{over} من {count} يومًا تتجاوز حدّك.',
'roadtrip.limit.echoNone': 'كل الأيام {count} ضمن حدودك.',
'roadtrip.limit.echoNone.zero': 'كل الأيام {count} ضمن حدودك.',
'roadtrip.limit.echoNone.one': 'اليوم الوحيد ضمن حدودك.',
'roadtrip.limit.echoNone.two': 'اليومان كلاهما ضمن حدودك.',
'roadtrip.limit.echoNone.few': 'كل الأيام {count} ضمن حدودك.',
'roadtrip.limit.echoNone.many': 'كل الأيام {count} ضمن حدودك.',
'roadtrip.limit.echoDry': '{count} مقطع بلا محطة يمكن بلوغها.',
'roadtrip.limit.echoDry.zero': 'لا توجد مقاطع بلا محطة يمكن بلوغها.',
'roadtrip.limit.echoDry.one': 'مقطع واحد بلا محطة يمكن بلوغها.',
@@ -308,8 +323,18 @@ const roadtrip: TranslationStrings = {
'roadtrip.stop.computed': 'محسوب من زمن القيادة',
'roadtrip.stop.endIsLeave': 'في الرحلة البرية، تنطلق الرحلة في هذا الوقت.',
'roadtrip.via.hint': 'اسحب لإعادة تشكيل المسار، وانقر بالزر الأيمن للإزالة',
'roadtrip.warn.late': 'الوصول بعد {minutes} دقيقة من الوقت المحدد',
'roadtrip.warn.missedLeave': 'الوصول بعد {minutes} دقيقة من وقت المغادرة المحدد',
'roadtrip.warn.late': 'الوصول بعد {count} دقيقة من الوقت المحدد',
'roadtrip.warn.late.zero': 'الوصول بعد {count} دقيقة من الوقت المحدد',
'roadtrip.warn.late.one': 'الوصول بعد دقيقة واحدة من الوقت المحدد',
'roadtrip.warn.late.two': 'الوصول بعد دقيقتين من الوقت المحدد',
'roadtrip.warn.late.few': 'الوصول بعد {count} دقائق من الوقت المحدد',
'roadtrip.warn.late.many': 'الوصول بعد {count} دقيقة من الوقت المحدد',
'roadtrip.warn.missedLeave': 'الوصول بعد {count} دقيقة من وقت المغادرة المحدد',
'roadtrip.warn.missedLeave.zero': 'الوصول بعد {count} دقيقة من وقت المغادرة المحدد',
'roadtrip.warn.missedLeave.one': 'الوصول بعد دقيقة واحدة من وقت المغادرة المحدد',
'roadtrip.warn.missedLeave.two': 'الوصول بعد دقيقتين من وقت المغادرة المحدد',
'roadtrip.warn.missedLeave.few': 'الوصول بعد {count} دقائق من وقت المغادرة المحدد',
'roadtrip.warn.missedLeave.many': 'الوصول بعد {count} دقيقة من وقت المغادرة المحدد',
'roadtrip.warn.overnight': 'اليوم التالي',
'roadtrip.spill.title': 'من اليوم {number}',
'roadtrip.spill.departs': 'المغادرة {time}',
+12 -1
View File
@@ -640,7 +640,18 @@ const settings: TranslationStrings = {
'settings.apiKeys.endpoint': 'نقطة النهاية',
'settings.apiKeys.neverUsed': 'لم يُستخدم قط',
'settings.apiKeys.loadFailed': 'تعذّر تحميل مفاتيحك. أعد تحميل الصفحة للمحاولة مرة أخرى.',
'settings.apiKeys.limitReached': 'لديك {max} مفاتيح، وهو الحد الأقصى للحساب الواحد. احذف مفتاحًا لم تعد تستخدمه لإنشاء مفتاح جديد.',
'settings.apiKeys.limitReached':
'لديك {count} مفتاح، وهو الحد الأقصى للحساب الواحد. احذف مفتاحًا لم تعد تستخدمه لإنشاء مفتاح جديد.',
'settings.apiKeys.limitReached.zero':
'لديك {count} مفتاح، وهو الحد الأقصى للحساب الواحد. احذف مفتاحًا لم تعد تستخدمه لإنشاء مفتاح جديد.',
'settings.apiKeys.limitReached.one':
'لديك مفتاح واحد، وهو الحد الأقصى للحساب الواحد. احذفه إن لم تعد تستخدمه لإنشاء مفتاح جديد.',
'settings.apiKeys.limitReached.two':
'لديك مفتاحان، وهو الحد الأقصى للحساب الواحد. احذف مفتاحًا لم تعد تستخدمه لإنشاء مفتاح جديد.',
'settings.apiKeys.limitReached.few':
'لديك {count} مفاتيح، وهو الحد الأقصى للحساب الواحد. احذف مفتاحًا لم تعد تستخدمه لإنشاء مفتاح جديد.',
'settings.apiKeys.limitReached.many':
'لديك {count} مفتاحًا، وهو الحد الأقصى للحساب الواحد. احذف مفتاحًا لم تعد تستخدمه لإنشاء مفتاح جديد.',
'settings.apiKeys.copyFailed': 'تعذّر النسخ. حدّد النص وانسخه يدويًا.',
'settings.apiKeys.modal.createTitle': 'إنشاء مفتاح API',
'settings.apiKeys.modal.name': 'الاسم',
+12 -2
View File
@@ -124,7 +124,12 @@ const storage: TranslationStrings = {
// Category migration (copy → flip → delta sweep)
'storage.migrate.promptTitle': 'هل تريد نقل الكائنات الحالية إلى الواجهة الخلفية الجديدة؟',
'storage.migrate.promptLine': '{category}: {objects} كائن ({size}) من {from} إلى {to}',
'storage.migrate.promptLine': '{category}: {count} كائن ({size}) من {from} إلى {to}',
'storage.migrate.promptLine.zero': '{category}: {count} كائن ({size}) من {from} إلى {to}',
'storage.migrate.promptLine.one': '{category}: كائن واحد ({size}) من {from} إلى {to}',
'storage.migrate.promptLine.two': '{category}: كائنان ({size}) من {from} إلى {to}',
'storage.migrate.promptLine.few': '{category}: {count} كائنات ({size}) من {from} إلى {to}',
'storage.migrate.promptLine.many': '{category}: {count} كائنًا ({size}) من {from} إلى {to}',
'storage.migrate.promptLineUnknown': '{category}: حجم غير معروف (لم يُجرَ فحص استخدام بعد) من {from} إلى {to}',
'storage.migrate.move': 'نقل الكائنات الحالية',
'storage.migrate.routeOnly': 'توجيه الكتابات الجديدة فقط',
@@ -133,7 +138,12 @@ const storage: TranslationStrings = {
'storage.migrate.doneFailures': '{failed} فشل — لم يتم نسخ تلك الكائنات إلى الواجهة الخلفية الجديدة',
'storage.migrate.failed': 'فشل النقل: {error} — لم يتم تبديل الفئة',
'storage.migrate.cancelled': 'تم إلغاء النقل — لم يتم تبديل أي شيء',
'storage.migrate.reclaimable': '{objects} كائن ({size}) لا تزال على {from} — استرجعها يدويًا',
'storage.migrate.reclaimable': '{count} كائن ({size}) لا تزال على {from}، استرجعها يدويًا',
'storage.migrate.reclaimable.zero': '{count} كائن ({size}) لا تزال على {from}، استرجعها يدويًا',
'storage.migrate.reclaimable.one': 'كائن واحد ({size}) لا يزال على {from}، استرجعه يدويًا',
'storage.migrate.reclaimable.two': 'كائنان ({size}) لا يزالان على {from}، استرجعهما يدويًا',
'storage.migrate.reclaimable.few': '{count} كائنات ({size}) لا تزال على {from}، استرجعها يدويًا',
'storage.migrate.reclaimable.many': '{count} كائنًا ({size}) لا تزال على {from}، استرجعها يدويًا',
'storage.migrate.cancel': 'إلغاء النقل',
'storage.migrate.promptCancel': 'إلغاء',
'storage.migrate.queued': 'في قائمة الانتظار: {categories}',
+2 -2
View File
@@ -112,8 +112,8 @@ const collab: TranslationStrings = {
'collab.polls.delete': 'Sil',
'collab.polls.closedSection': 'Bağlanmış sorğular',
'collab.chat.imageLimit':
'Hər mesaja maksimum {max} şəkil əlavə edə bilərsiniz',
'collab.chat.imageLimit': 'Hər mesaja maksimum {count} şəkil əlavə edə bilərsiniz',
'collab.chat.imageLimit.one': 'Hər mesaja yalnız {count} şəkil əlavə edə bilərsiniz',
'collab.chat.uploading': 'Yüklənir: {percent}%',
'collab.chat.attachImages': 'Şəkillər əlavə et',
'collab.chat.attachedImage': 'Əlavə edilmiş şəkil',
+2 -1
View File
@@ -113,7 +113,8 @@ const dashboard: TranslationStrings = {
'dashboard.useUnsplashPhoto': '{photographer} tərəfindən çəkilmiş Unsplash şəklindən istifadə et',
'dashboard.titleRequired': 'Başlıq tələb olunur',
'dashboard.endDateError': 'Bitmə tarixi başlama tarixindən sonra olmalıdır',
'dashboard.tripTooLong': 'Səyahət ən çox {days} gün davam edə bilər',
'dashboard.tripTooLong': 'Səyahət ən çox {count} gün davam edə bilər',
'dashboard.tripTooLong.one': 'Səyahət ən çox {count} gün davam edə bilər',
'dashboard.dateShiftTitle': 'Yeni başlama tarixi',
'dashboard.dateShiftIntro': 'Səyahətin başlama tarixini dəyişdiniz. Planlarınız yeni tarixlərə necə uyğunlaşdırılsın?',
'dashboard.dateShiftKeepBookings': 'Rezervasiyaların tarixlərini saxla',
+6 -2
View File
@@ -116,7 +116,8 @@ const dawarich: TranslationStrings = {
'dawarich.accept.confirm.journal': 'Qeyd əlavə et',
'dawarich.accept.confirm.bucket_list': 'Tamamlandı kimi işarələ',
'dawarich.accept.recorded': '{from}–{to} aralığında qeydə alınıb',
'dawarich.accept.duration': '{minutes} dəq.',
'dawarich.accept.duration': '{count} dəq.',
'dawarich.accept.duration.one': '{count} dəq.',
'dawarich.accept.name': 'Ad',
'dawarich.accept.date': 'Tarix',
'dawarich.accept.from': 'Çatma',
@@ -181,7 +182,10 @@ const dawarich: TranslationStrings = {
'dawarich.again': 'Yenidən yoxla',
'dawarich.bucket.metersAway': '{meters} m uzaqda',
'dawarich.bucket.kilometersAway': '{km} km uzaqda',
'dawarich.bucket.rule': 'Arzu məkandan {meters} m məsafə daxilində və orada ən azı {minutes} dəqiqə qaldıqda tamamlanmış sayılır.',
'dawarich.bucket.rule':
'Arzu məkandan {meters} m məsafə daxilində və orada ən azı {count} dəqiqə qaldıqda tamamlanmış sayılır.',
'dawarich.bucket.rule.one':
'Arzu məkandan {meters} m məsafə daxilində və orada ən azı {count} dəqiqə qaldıqda tamamlanmış sayılır.',
'dawarich.journey.dayStays.one': 'Dawarich-dən 1 dayanma qeydi',
'dawarich.journey.dayStays.other': 'Dawarich-dən {count} dayanma qeydi',
+11 -5
View File
@@ -54,7 +54,9 @@ const roadtrip: TranslationStrings = {
'roadtrip.window.conflict':
'Sabit vaxt yol və günlük fasilələrlə ziddiyyət təşkil edir. Əl ilə təyin edilmiş vaxtlar dəyişdirilmir; avtomatik planlaşdırma dayandırılıb.',
'roadtrip.window.tooLong':
'Bu səyahət vaxtları {days} ədəddən çox əlavə gün tələb edəcək. Daha uzun günlük vaxt aralığı seçin.',
'Bu səyahət vaxtları {count} gündən çox əlavə vaxt tələb edəcək. Daha uzun günlük vaxt aralığı seçin.',
'roadtrip.window.tooLong.one':
'Bu səyahət vaxtları {count} gündən çox əlavə vaxt tələb edəcək. Daha uzun günlük vaxt aralığı seçin.',
'roadtrip.mode.roadtrip': 'Avtomobil səyahəti',
'roadtrip.mode.days': 'Günlər',
'roadtrip.mode.label': 'Plan görünüşü',
@@ -101,8 +103,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.limit.minutes': 'dəq.',
'roadtrip.limit.off': 'deaktiv',
'roadtrip.limit.blockNote': 'Bir blok = {distance}',
'roadtrip.limit.echoOver': '{days} gündən {over} günü limitinizi keçir.',
'roadtrip.limit.echoNone': 'Bütün {days} gün limitləriniz daxilindədir.',
'roadtrip.limit.echoOver': '{count} gündən {over} günü limitinizi keçir.',
'roadtrip.limit.echoOver.one': '{count} gündən {over} günü limitinizi keçir.',
'roadtrip.limit.echoNone': 'Bütün {count} gün limitləriniz daxilindədir.',
'roadtrip.limit.echoNone.one': 'Yeganə gün limitləriniz daxilindədir.',
'roadtrip.limit.echoDry': 'Çatmaq mümkün olan dayanacağı olmayan {count} marşrut hissəsi.',
'roadtrip.limit.echoDry.one': 'Çatmaq mümkün olan dayanacağı olmayan {count} marşrut hissəsi.',
'roadtrip.limit.echoEmpty': 'Bu tənzimləmələrin təsirini görmək üçün avtomobil səyahəti açın.',
@@ -268,8 +272,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.stop.computed': 'Yoldan hesablanıb',
'roadtrip.stop.endIsLeave': 'Avtomobil səyahətində yol bu vaxtda davam edir.',
'roadtrip.via.hint': 'Marşrutu dəyişmək üçün sürükləyin, silmək üçün sağ klikləyin',
'roadtrip.warn.late': 'Təyin etdiyiniz vaxtdan {minutes} dəq. sonra çatır',
'roadtrip.warn.missedLeave': 'Təyin etdiyiniz çıxış vaxtından {minutes} dəq. sonra çatır',
'roadtrip.warn.late': 'Təyin etdiyiniz vaxtdan {count} dəq. sonra çatır',
'roadtrip.warn.late.one': 'Təyin etdiyiniz vaxtdan {count} dəq. sonra çatır',
'roadtrip.warn.missedLeave': 'Təyin etdiyiniz çıxış vaxtından {count} dəq. sonra çatır',
'roadtrip.warn.missedLeave.one': 'Təyin etdiyiniz çıxış vaxtından {count} dəq. sonra çatır',
'roadtrip.warn.overnight': 'Növbəti gün',
'roadtrip.spill.title': '{number}-ci gündən',
'roadtrip.spill.departs': 'saat {time}-da yola düşür',
+3 -1
View File
@@ -761,7 +761,9 @@ const settings: TranslationStrings = {
'settings.apiKeys.loadFailed':
'Açarlarınızı yükləmək mümkün olmadı. Yenidən sınamaq üçün səhifəni yeniləyin.',
'settings.apiKeys.limitReached':
'Hesab üçün maksimum say olan {max} açarınız var. Yeni açar yaratmaq üçün artıq istifadə etmədiyiniz açarlardan birini silin.',
'Hesab üçün maksimum say olan {count} açarınız var. Yeni açar yaratmaq üçün artıq istifadə etmədiyiniz açarlardan birini silin.',
'settings.apiKeys.limitReached.one':
'Hesab üçün maksimum say olan {count} açarınız var. Yeni açar yaratmaq üçün ondan artıq istifadə etmirsinizsə, onu silin.',
'settings.apiKeys.copyFailed':
'Kopyalamaq mümkün olmadı. Mətni seçərək əl ilə kopyalayın.',
+4 -4
View File
@@ -136,8 +136,8 @@ const storage: TranslationStrings = {
'storage.usage.legacyNote': 'köhnə foto kitabxanası daxildir',
// Category migration (copy → flip → delta sweep)
'storage.migrate.promptTitle': 'Mövcud obyektlər yeni saxlama sisteminə köçürülsün?',
'storage.migrate.promptLine':
'{category}: {from} sistemindən {to} sisteminə {objects} obyekt ({size})',
'storage.migrate.promptLine': '{category}: {from} sistemindən {to} sisteminə {count} obyekt ({size})',
'storage.migrate.promptLine.one': '{category}: {from} sistemindən {to} sisteminə {count} obyekt ({size})',
'storage.migrate.promptLineUnknown':
'{category}: {from} sistemindən {to} sisteminə naməlum ölçü (istifadə hələ skan edilməyib)',
'storage.migrate.move': 'Mövcud obyektləri köçür',
@@ -149,8 +149,8 @@ const storage: TranslationStrings = {
'storage.migrate.failed':
'Köçürmə uğursuz oldu: {error} — kateqoriya dəyişdirilmədi',
'storage.migrate.cancelled': 'Köçürmə ləğv edildi — heç nə dəyişdirilmədi',
'storage.migrate.reclaimable':
'{objects} obyekt ({size}) {from} üzərində qalıb — əl ilə geri qazanın',
'storage.migrate.reclaimable': '{count} obyekt ({size}) {from} üzərində qalıb, əl ilə geri qazanın',
'storage.migrate.reclaimable.one': '{count} obyekt ({size}) {from} üzərində qalıb, əl ilə geri qazanın',
'storage.migrate.cancel': 'Köçürməni ləğv et',
'storage.migrate.promptCancel': 'Ləğv et',
'storage.migrate.queued': 'Növbədədir: {categories}',
+2 -1
View File
@@ -100,7 +100,8 @@ const collab: TranslationStrings = {
'collab.polls.options': 'Opções',
'collab.polls.delete': 'Excluir',
'collab.polls.closedSection': 'Encerradas',
'collab.chat.imageLimit': 'Podes anexar até {max} imagens por mensagem',
'collab.chat.imageLimit': 'Podes anexar até {count} imagens por mensagem',
'collab.chat.imageLimit.one': 'Podes anexar até {count} imagem por mensagem',
'collab.chat.uploading': 'A enviar {percent}%',
'collab.chat.attachImages': 'Anexar imagens',
'collab.chat.attachedImage': 'Imagem anexada',
+2 -1
View File
@@ -99,7 +99,8 @@ const dashboard: TranslationStrings = {
'dashboard.useUnsplashPhoto': 'Usar foto do Unsplash por {photographer}',
'dashboard.titleRequired': 'O título é obrigatório',
'dashboard.endDateError': 'A data final deve ser depois da inicial',
'dashboard.tripTooLong': 'Uma viagem pode ter no máximo {days} dias',
'dashboard.tripTooLong': 'Uma viagem pode ter no máximo {count} dias',
'dashboard.tripTooLong.one': 'Uma viagem pode ter no máximo {count} dia',
'dashboard.dateShiftTitle': 'Nova data de início',
'dashboard.dateShiftIntro': 'Você alterou o início desta viagem. Como seus planos devem acompanhar as novas datas?',
'dashboard.dateShiftKeepBookings': 'Manter reservas em suas datas',
+4 -2
View File
@@ -118,7 +118,8 @@ const dawarich: TranslationStrings = {
'dawarich.accept.confirm.journal': 'Adicionar entrada',
'dawarich.accept.confirm.bucket_list': 'Riscar',
'dawarich.accept.recorded': 'Gravado de {from} até {to}',
'dawarich.accept.duration': '{minutes} min',
'dawarich.accept.duration': '{count} min',
'dawarich.accept.duration.one': '{count} min',
'dawarich.accept.name': 'Nome',
'dawarich.accept.date': 'Data',
'dawarich.accept.from': 'Chegada',
@@ -183,7 +184,8 @@ const dawarich: TranslationStrings = {
'dawarich.again': 'Verificar de novo',
'dawarich.bucket.metersAway': 'a {meters} m',
'dawarich.bucket.kilometersAway': 'a {km} km',
'dawarich.bucket.rule': 'Um desejo conta como alcançado a até {meters} m e após {minutes} minutos no local.',
'dawarich.bucket.rule': 'Um desejo conta como alcançado a até {meters} m e após {count} minutos no local.',
'dawarich.bucket.rule.one': 'Um desejo conta como alcançado a até {meters} m e após {count} minuto no local.',
'dawarich.journey.dayStays.one': '{count} estadia do Dawarich',
'dawarich.journey.dayStays.other': '{count} estadias do Dawarich',
+10 -5
View File
@@ -55,7 +55,8 @@ const roadtrip: TranslationStrings = {
'roadtrip.window.incomplete': 'Todas as rotas de conexão são necessárias para calcular as pausas diárias.',
'roadtrip.window.conflict':
'Um horário fixo conflita com o trajeto e as pausas. Os horários manuais são mantidos; o planejamento automático está pausado.',
'roadtrip.window.tooLong': 'Esses horários exigiriam mais de {days} dias extras. Escolha um período diário maior.',
'roadtrip.window.tooLong': 'Esses horários exigiriam mais de {count} dias extras. Escolha um período diário maior.',
'roadtrip.window.tooLong.one': 'Esses horários exigiriam mais de {count} dia extra. Escolha um período diário maior.',
'roadtrip.mode.roadtrip': 'Viagem de carro',
'roadtrip.mode.days': 'Dias',
'roadtrip.mode.label': 'Visão do plano',
@@ -102,8 +103,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.limit.minutes': 'min',
'roadtrip.limit.off': 'desligado',
'roadtrip.limit.blockNote': 'Um bloco = {distance}',
'roadtrip.limit.echoOver': '{over} de {days} dias acima do seu limite.',
'roadtrip.limit.echoNone': 'Todos os {days} dias dentro dos limites.',
'roadtrip.limit.echoOver': '{over} de {count} dias acima do seu limite.',
'roadtrip.limit.echoOver.one': '{over} de {count} dia acima do seu limite.',
'roadtrip.limit.echoNone': 'Todos os {count} dias dentro dos limites.',
'roadtrip.limit.echoNone.one': '{count} dia dentro dos limites.',
'roadtrip.limit.echoDry': '{count} trechos sem parada alcançável.',
'roadtrip.limit.echoDry.one': '{count} trecho sem parada alcançável.',
'roadtrip.limit.echoEmpty': 'Abra uma viagem para ver o efeito.',
@@ -273,8 +276,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.stop.computed': 'Calculado a partir do trajeto',
'roadtrip.stop.endIsLeave': 'Na viagem de carro, a partida é neste horário.',
'roadtrip.via.hint': 'Arraste para remodelar a rota, clique com o botão direito para remover',
'roadtrip.warn.late': 'Chega {minutes} min depois do horário definido',
'roadtrip.warn.missedLeave': 'Chega {minutes} min depois do horário de partida definido',
'roadtrip.warn.late': 'Chega {count} min depois do horário definido',
'roadtrip.warn.late.one': 'Chega {count} min depois do horário definido',
'roadtrip.warn.missedLeave': 'Chega {count} min depois do horário de partida definido',
'roadtrip.warn.missedLeave.one': 'Chega {count} min depois do horário de partida definido',
'roadtrip.warn.overnight': 'Dia seguinte',
'roadtrip.spill.title': 'Do dia {number}',
'roadtrip.spill.departs': 'saída {time}',
+4 -1
View File
@@ -633,7 +633,10 @@ const settings: TranslationStrings = {
'settings.apiKeys.endpoint': 'Endpoint',
'settings.apiKeys.neverUsed': 'nunca usada',
'settings.apiKeys.loadFailed': 'Não foi possível carregar suas chaves. Recarregue a página para tentar de novo.',
'settings.apiKeys.limitReached': 'Você tem {max} chaves, o máximo por conta. Exclua uma que não usa mais para criar outra.',
'settings.apiKeys.limitReached':
'Você tem {count} chaves, o máximo por conta. Exclua uma que não usa mais para criar outra.',
'settings.apiKeys.limitReached.one':
'Você tem {count} chave, o máximo por conta. Exclua-a se não a usa mais para criar outra.',
'settings.apiKeys.copyFailed': 'Não foi possível copiar. Selecione o texto e copie manualmente.',
'settings.apiKeys.modal.createTitle': 'Criar chave de API',
'settings.apiKeys.modal.name': 'Nome',
+4 -2
View File
@@ -121,7 +121,8 @@ const storage: TranslationStrings = {
// Category migration (copy → flip → delta sweep)
'storage.migrate.promptTitle': 'Mover objetos existentes para o novo backend?',
'storage.migrate.promptLine': '{category}: {objects} objetos ({size}) de {from} para {to}',
'storage.migrate.promptLine': '{category}: {count} objetos ({size}) de {from} para {to}',
'storage.migrate.promptLine.one': '{category}: {count} objeto ({size}) de {from} para {to}',
'storage.migrate.promptLineUnknown': '{category}: tamanho desconhecido (uso ainda não calculado) de {from} para {to}',
'storage.migrate.move': 'Mover objetos existentes',
'storage.migrate.routeOnly': 'Apenas rotear novas gravações',
@@ -130,7 +131,8 @@ const storage: TranslationStrings = {
'storage.migrate.doneFailures': '{failed} falharam — esses objetos não foram copiados para o novo backend',
'storage.migrate.failed': 'Falha ao mover: {error} — a categoria não foi alterada',
'storage.migrate.cancelled': 'Movimentação cancelada — nada foi alterado',
'storage.migrate.reclaimable': '{objects} objetos ({size}) permanecem em {from} — recupere manualmente',
'storage.migrate.reclaimable': '{count} objetos ({size}) permanecem em {from}, recupere manualmente',
'storage.migrate.reclaimable.one': '{count} objeto ({size}) permanece em {from}, recupere manualmente',
'storage.migrate.cancel': 'Cancelar movimentação',
'storage.migrate.promptCancel': 'Cancelar',
'storage.migrate.queued': 'Na fila: {categories}',
+2 -1
View File
@@ -100,7 +100,8 @@ const collab: TranslationStrings = {
'collab.polls.countdownHoursMinutes': '{h}h {m}m restants',
'collab.polls.countdownMinutes': '{m}m restants',
'collab.polls.clearDeadline': 'Esborra la data límit',
'collab.chat.imageLimit': 'Pots adjuntar fins a {max} imatges per missatge',
'collab.chat.imageLimit': 'Pots adjuntar fins a {count} imatges per missatge',
'collab.chat.imageLimit.one': 'Només pots adjuntar {count} imatge per missatge',
'collab.chat.uploading': 'Pujant {percent} %',
'collab.chat.attachImages': 'Adjunta imatges',
'collab.chat.attachedImage': 'Imatge adjunta',
+2 -1
View File
@@ -87,7 +87,8 @@ const dashboard: TranslationStrings = {
'dashboard.coverRemoveError': 'Error en eliminar la imatge',
'dashboard.titleRequired': 'El títol és obligatori',
'dashboard.endDateError': "La data de fi ha de ser posterior a la d'inici",
'dashboard.tripTooLong': 'Un viatge pot durar com a màxim {days} dies',
'dashboard.tripTooLong': 'Un viatge pot durar com a màxim {count} dies',
'dashboard.tripTooLong.one': 'Un viatge pot durar com a màxim {count} dia',
'dashboard.greeting.morning': 'Bon dia,',
'dashboard.greeting.afternoon': 'Bona tarda,',
'dashboard.greeting.evening': 'Bona nit,',
+4 -2
View File
@@ -117,7 +117,8 @@ const dawarich: TranslationStrings = {
'dawarich.accept.confirm.journal': 'Afegeix l’entrada',
'dawarich.accept.confirm.bucket_list': 'Marca’l',
'dawarich.accept.recorded': 'Enregistrat des de {from} fins a {to}',
'dawarich.accept.duration': '{minutes} min',
'dawarich.accept.duration': '{count} min',
'dawarich.accept.duration.one': '{count} min',
'dawarich.accept.name': 'Nom',
'dawarich.accept.date': 'Data',
'dawarich.accept.from': 'Arribada',
@@ -182,7 +183,8 @@ const dawarich: TranslationStrings = {
'dawarich.again': 'Tornar a comprovar',
'dawarich.bucket.metersAway': 'a {meters} m',
'dawarich.bucket.kilometersAway': 'a {km} km',
'dawarich.bucket.rule': 'Un desig es considera assolit a menys de {meters} m i després de {minutes} minuts al lloc.',
'dawarich.bucket.rule': 'Un desig es considera assolit a menys de {meters} m i després de {count} minuts al lloc.',
'dawarich.bucket.rule.one': 'Un desig es considera assolit a menys de {meters} m i després de {count} minut al lloc.',
'dawarich.journey.dayStays.other': '{count} estades de Dawarich',
'dawarich.journey.dayStays.one': '{count} estada de Dawarich',
+11 -5
View File
@@ -56,7 +56,9 @@ const roadtrip: TranslationStrings = {
'roadtrip.window.conflict':
'Una hora fixa entra en conflicte amb el trajecte i les pauses. Es mantenen les hores manuals; la planificació automàtica està en pausa.',
'roadtrip.window.tooLong':
'Aquests horaris requereixen més de {days} dies addicionals. Tria una franja diària més llarga.',
'Aquests horaris requereixen més de {count} dies addicionals. Tria una franja diària més llarga.',
'roadtrip.window.tooLong.one':
'Aquests horaris requereixen més de {count} dia addicional. Tria una franja diària més llarga.',
'roadtrip.mode.roadtrip': 'Viatge per carretera',
'roadtrip.mode.days': 'Dies',
'roadtrip.mode.label': 'Vista del pla',
@@ -103,8 +105,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.limit.minutes': 'min',
'roadtrip.limit.off': 'desactivat',
'roadtrip.limit.blockNote': 'Un bloc = {distance}',
'roadtrip.limit.echoOver': '{over} de {days} dies superen el teu límit.',
'roadtrip.limit.echoNone': 'Els {days} dies són dins dels límits.',
'roadtrip.limit.echoOver': '{over} de {count} dies superen el teu límit.',
'roadtrip.limit.echoOver.one': '{over} de {count} dia supera el teu límit.',
'roadtrip.limit.echoNone': 'Els {count} dies són dins dels límits.',
'roadtrip.limit.echoNone.one': 'L’únic dia és dins dels límits.',
'roadtrip.limit.echoDry': '{count} trams sense parada assolible.',
'roadtrip.limit.echoDry.one': '{count} tram sense parada assolible.',
'roadtrip.limit.echoEmpty': 'Obre una ruta per veure’n l’efecte.',
@@ -276,8 +280,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.stop.computed': 'Calculat a partir del trajecte',
'roadtrip.stop.endIsLeave': 'En el viatge per carretera, se surt a aquesta hora.',
'roadtrip.via.hint': 'Arrossega per remodelar la ruta, clic dret per eliminar',
'roadtrip.warn.late': 'Arriba {minutes} min després de l’hora fixada',
'roadtrip.warn.missedLeave': 'Arriba {minutes} min després de l’hora de sortida fixada',
'roadtrip.warn.late': 'Arriba {count} min després de l’hora fixada',
'roadtrip.warn.late.one': 'Arriba {count} min després de l’hora fixada',
'roadtrip.warn.missedLeave': 'Arriba {count} min després de l’hora de sortida fixada',
'roadtrip.warn.missedLeave.one': 'Arriba {count} min després de l’hora de sortida fixada',
'roadtrip.warn.overnight': 'Dia següent',
'roadtrip.spill.title': 'Del dia {number}',
'roadtrip.spill.departs': 'surt {time}',
+3 -1
View File
@@ -638,7 +638,9 @@ const settings: TranslationStrings = {
'settings.apiKeys.loadFailed':
"No s'han pogut carregar les teves claus. Torna a carregar la pàgina per tornar-ho a provar.",
'settings.apiKeys.limitReached':
"Tens {max} claus, el màxim per compte. Esborra'n una que ja no facis servir per crear-ne una altra.",
"Tens {count} claus, el màxim per compte. Esborra'n una que ja no facis servir per crear-ne una altra.",
'settings.apiKeys.limitReached.one':
'Tens {count} clau, el màxim per compte. Esborra-la si ja no la fas servir per crear-ne una altra.',
'settings.apiKeys.copyFailed': "No s'ha pogut copiar. Selecciona el text i copia'l a mà.",
'settings.apiKeys.modal.createTitle': 'Crea una clau API',
'settings.apiKeys.modal.name': 'Nom',
+4 -2
View File
@@ -125,7 +125,8 @@ const storage: TranslationStrings = {
// Category migration (copy → flip → delta sweep)
'storage.migrate.promptTitle': 'Vols moure els objectes existents al nou backend?',
'storage.migrate.promptLine': '{category}: {objects} objectes ({size}) de {from} a {to}',
'storage.migrate.promptLine': '{category}: {count} objectes ({size}) de {from} a {to}',
'storage.migrate.promptLine.one': '{category}: {count} objecte ({size}) de {from} a {to}',
'storage.migrate.promptLineUnknown': "{category}: mida desconeguda (encara no s'ha calculat l'ús) de {from} a {to}",
'storage.migrate.move': 'Mou els objectes existents',
'storage.migrate.routeOnly': 'Només enruta les escriptures noves',
@@ -134,7 +135,8 @@ const storage: TranslationStrings = {
'storage.migrate.doneFailures': "{failed} han fallat — aquests objectes no s'han copiat al nou backend",
'storage.migrate.failed': "Ha fallat el trasllat: {error} — la categoria no s'ha canviat",
'storage.migrate.cancelled': "Trasllat cancel·lat — no s'ha canviat res",
'storage.migrate.reclaimable': "{objects} objectes ({size}) romanen a {from} — recupera'ls manualment",
'storage.migrate.reclaimable': "{count} objectes ({size}) romanen a {from}, recupera'ls manualment",
'storage.migrate.reclaimable.one': "{count} objecte ({size}) roman a {from}, recupera'l manualment",
'storage.migrate.cancel': 'Cancel·la el trasllat',
'storage.migrate.promptCancel': 'Cancel·la',
'storage.migrate.queued': 'En cua: {categories}',
+3 -1
View File
@@ -104,7 +104,9 @@ const collab: TranslationStrings = {
'collab.polls.options': 'Možnosti',
'collab.polls.delete': 'Smazat',
'collab.polls.closedSection': 'Uzavřené',
'collab.chat.imageLimit': 'K jedné zprávě můžete připojit nejvýše {max} obrázků',
'collab.chat.imageLimit': 'K jedné zprávě můžete připojit nejvýše {count} obrázků',
'collab.chat.imageLimit.one': 'K jedné zprávě můžete připojit jen {count} obrázek',
'collab.chat.imageLimit.few': 'K jedné zprávě můžete připojit nejvýše {count} obrázky',
'collab.chat.uploading': 'Nahrávání {percent} %',
'collab.chat.attachImages': 'Připojit obrázky',
'collab.chat.attachedImage': 'Připojený obrázek',
+3 -1
View File
@@ -106,7 +106,9 @@ const dashboard: TranslationStrings = {
'dashboard.useUnsplashPhoto': 'Použít fotku z Unsplash od {photographer}',
'dashboard.titleRequired': 'Název je povinný',
'dashboard.endDateError': 'Datum konce musí být po datu začátku',
'dashboard.tripTooLong': 'Cesta může trvat nejvýše {days} dní',
'dashboard.tripTooLong': 'Cesta může trvat nejvýše {count} dní',
'dashboard.tripTooLong.one': 'Cesta může trvat nejvýše {count} den',
'dashboard.tripTooLong.few': 'Cesta může trvat nejvýše {count} dny',
'dashboard.dateShiftTitle': 'Nové datum začátku',
'dashboard.dateShiftIntro': 'Změnili jste začátek této cesty. Jak se mají vaše plány přizpůsobit novým termínům?',
'dashboard.dateShiftKeepBookings': 'Ponechat rezervace v původních termínech',
+6 -2
View File
@@ -121,7 +121,9 @@ const dawarich: TranslationStrings = {
'dawarich.accept.confirm.journal': 'Přidat záznam',
'dawarich.accept.confirm.bucket_list': 'Odškrtnout',
'dawarich.accept.recorded': 'Zaznamenáno od {from} do {to}',
'dawarich.accept.duration': '{minutes} min',
'dawarich.accept.duration': '{count} min',
'dawarich.accept.duration.one': '{count} min',
'dawarich.accept.duration.few': '{count} min',
'dawarich.accept.name': 'Název',
'dawarich.accept.date': 'Datum',
'dawarich.accept.from': 'Příchod',
@@ -193,7 +195,9 @@ const dawarich: TranslationStrings = {
'dawarich.again': 'Zkontrolovat znovu',
'dawarich.bucket.metersAway': '{meters} m daleko',
'dawarich.bucket.kilometersAway': '{km} km daleko',
'dawarich.bucket.rule': 'Přání se počítá jako splněné do {meters} m a po {minutes} minutách na místě.',
'dawarich.bucket.rule': 'Přání se počítá jako splněné do {meters} m a po {count} minutách na místě.',
'dawarich.bucket.rule.one': 'Přání se počítá jako splněné do {meters} m a po {count} minutě na místě.',
'dawarich.bucket.rule.few': 'Přání se počítá jako splněné do {meters} m a po {count} minutách na místě.',
'dawarich.journey.dayStays.other': '{count} pobytů z Dawarichu',
'dawarich.journey.dayStays.one': '{count} pobyt z Dawarichu',
'dawarich.journey.dayStays.few': '{count} pobyty z Dawarichu',
+15 -5
View File
@@ -55,7 +55,9 @@ const roadtrip: TranslationStrings = {
'roadtrip.window.incomplete': 'Denní přestávky lze spočítat až po získání všech navazujících tras.',
'roadtrip.window.conflict':
'Pevný čas je v rozporu s jízdou a přestávkami. Ruční časy zůstávají zachovány; automatické plánování je pozastaveno.',
'roadtrip.window.tooLong': 'Tyto časy vyžadují více než {days} dalších dní. Zvolte delší denní interval.',
'roadtrip.window.tooLong': 'Tyto časy vyžadují více než {count} dalších dní. Zvolte delší denní interval.',
'roadtrip.window.tooLong.one': 'Tyto časy vyžadují více než {count} další den. Zvolte delší denní interval.',
'roadtrip.window.tooLong.few': 'Tyto časy vyžadují více než {count} další dny. Zvolte delší denní interval.',
'roadtrip.mode.roadtrip': 'Cesta autem',
'roadtrip.mode.days': 'Dny',
'roadtrip.mode.label': 'Zobrazení plánu',
@@ -103,8 +105,12 @@ const roadtrip: TranslationStrings = {
'roadtrip.limit.minutes': 'min',
'roadtrip.limit.off': 'vypnuto',
'roadtrip.limit.blockNote': 'Jeden blok = {distance}',
'roadtrip.limit.echoOver': '{over} z {days} dnů nad tvým limitem.',
'roadtrip.limit.echoNone': 'Všech {days} dnů je v limitech.',
'roadtrip.limit.echoOver': '{over} z {count} dnů nad tvým limitem.',
'roadtrip.limit.echoOver.one': '{over} z {count} dne nad tvým limitem.',
'roadtrip.limit.echoOver.few': '{over} z {count} dnů nad tvým limitem.',
'roadtrip.limit.echoNone': 'Všech {count} dnů je v limitech.',
'roadtrip.limit.echoNone.one': 'Jediný den je v limitech.',
'roadtrip.limit.echoNone.few': 'Všechny {count} dny jsou v limitech.',
'roadtrip.limit.echoDry': '{count} úseků bez dosažitelné zastávky.',
'roadtrip.limit.echoDry.one': '{count} úsek bez dosažitelné zastávky.',
'roadtrip.limit.echoDry.few': '{count} úseky bez dosažitelné zastávky.',
@@ -281,8 +287,12 @@ const roadtrip: TranslationStrings = {
'roadtrip.stop.computed': 'Vypočteno z doby jízdy',
'roadtrip.stop.endIsLeave': 'Na cestě autem se v tento čas odjíždí.',
'roadtrip.via.hint': 'Tažením změníte trasu, pravým tlačítkem odeberete',
'roadtrip.warn.late': 'Příjezd {minutes} min po nastaveném čase',
'roadtrip.warn.missedLeave': 'Příjezd {minutes} min po nastaveném čase odjezdu',
'roadtrip.warn.late': 'Příjezd {count} min po nastaveném čase',
'roadtrip.warn.late.one': 'Příjezd {count} min po nastaveném čase',
'roadtrip.warn.late.few': 'Příjezd {count} min po nastaveném čase',
'roadtrip.warn.missedLeave': 'Příjezd {count} min po nastaveném čase odjezdu',
'roadtrip.warn.missedLeave.one': 'Příjezd {count} min po nastaveném čase odjezdu',
'roadtrip.warn.missedLeave.few': 'Příjezd {count} min po nastaveném čase odjezdu',
'roadtrip.warn.overnight': 'Následující den',
'roadtrip.spill.title': 'Ze dne {number}',
'roadtrip.spill.departs': 'odjezd {time}',
+6 -1
View File
@@ -628,7 +628,12 @@ const settings: TranslationStrings = {
'settings.apiKeys.endpoint': 'Endpoint',
'settings.apiKeys.neverUsed': 'nikdy nepoužit',
'settings.apiKeys.loadFailed': 'Klíče se nepodařilo načíst. Obnov stránku a zkus to znovu.',
'settings.apiKeys.limitReached': 'Máš {max} klíčů, víc jeden účet mít nemůže. Smaž klíč, který už nepotřebuješ, a vytvoř nový.',
'settings.apiKeys.limitReached':
'Máš {count} klíčů, víc jeden účet mít nemůže. Smaž klíč, který už nepotřebuješ, a vytvoř nový.',
'settings.apiKeys.limitReached.one':
'Máš {count} klíč, víc jeden účet mít nemůže. Smaž ho, pokud ho už nepotřebuješ, a vytvoř nový.',
'settings.apiKeys.limitReached.few':
'Máš {count} klíče, víc jeden účet mít nemůže. Smaž klíč, který už nepotřebuješ, a vytvoř nový.',
'settings.apiKeys.copyFailed': 'Kopírování se nepodařilo. Označ text a zkopíruj ho ručně.',
'settings.apiKeys.modal.createTitle': 'Vytvořit klíč API',
'settings.apiKeys.modal.name': 'Název',
+6 -2
View File
@@ -121,7 +121,9 @@ const storage: TranslationStrings = {
// Category migration (copy → flip → delta sweep)
'storage.migrate.promptTitle': 'Přesunout stávající objekty do nového backendu?',
'storage.migrate.promptLine': '{category}: {objects} objektů ({size}) z {from} do {to}',
'storage.migrate.promptLine': '{category}: {count} objektů ({size}) z {from} do {to}',
'storage.migrate.promptLine.one': '{category}: {count} objekt ({size}) z {from} do {to}',
'storage.migrate.promptLine.few': '{category}: {count} objekty ({size}) z {from} do {to}',
'storage.migrate.promptLineUnknown': '{category}: neznámá velikost (využití ještě nebylo vypočteno) z {from} do {to}',
'storage.migrate.move': 'Přesunout stávající objekty',
'storage.migrate.routeOnly': 'Pouze směrovat nové zápisy',
@@ -130,7 +132,9 @@ const storage: TranslationStrings = {
'storage.migrate.doneFailures': '{failed} se nezdařilo — tyto objekty nebyly zkopírovány do nového backendu',
'storage.migrate.failed': 'Přesun selhal: {error} — kategorie nebyla přepnuta',
'storage.migrate.cancelled': 'Přesun zrušen — nic nebylo přepnuto',
'storage.migrate.reclaimable': '{objects} objektů ({size}) zůstává na {from} — uvolněte ručně',
'storage.migrate.reclaimable': '{count} objektů ({size}) zůstává na {from}, uvolněte ručně',
'storage.migrate.reclaimable.one': '{count} objekt ({size}) zůstává na {from}, uvolněte ručně',
'storage.migrate.reclaimable.few': '{count} objekty ({size}) zůstávají na {from}, uvolněte ručně',
'storage.migrate.cancel': 'Zrušit přesun',
'storage.migrate.promptCancel': 'Zrušit',
'storage.migrate.queued': 'Ve frontě: {categories}',
+2 -1
View File
@@ -100,7 +100,8 @@ const collab: TranslationStrings = {
'collab.polls.options': 'Optionen',
'collab.polls.delete': 'Löschen',
'collab.polls.closedSection': 'Geschlossen',
'collab.chat.imageLimit': 'Pro Nachricht kannst du bis zu {max} Bilder anhängen',
'collab.chat.imageLimit': 'Pro Nachricht kannst du bis zu {count} Bilder anhängen',
'collab.chat.imageLimit.one': 'Pro Nachricht kannst du nur {count} Bild anhängen',
'collab.chat.uploading': 'Wird hochgeladen, {percent} %',
'collab.chat.attachImages': 'Bilder anhängen',
'collab.chat.attachedImage': 'Angehängtes Bild',
+2 -1
View File
@@ -100,7 +100,8 @@ const dashboard: TranslationStrings = {
'dashboard.useUnsplashPhoto': 'Unsplash-Foto von {photographer} verwenden',
'dashboard.titleRequired': 'Titel ist erforderlich',
'dashboard.endDateError': 'Enddatum muss nach dem Startdatum liegen',
'dashboard.tripTooLong': 'Eine Reise kann höchstens {days} Tage umfassen',
'dashboard.tripTooLong': 'Eine Reise kann höchstens {count} Tage umfassen',
'dashboard.tripTooLong.one': 'Eine Reise kann höchstens {count} Tag umfassen',
'dashboard.dateShiftTitle': 'Neues Startdatum',
'dashboard.dateShiftIntro':
'Du hast den Beginn dieser Reise geändert. Wie sollen deine Pläne den neuen Daten folgen?',
+4 -2
View File
@@ -120,7 +120,8 @@ const dawarich: TranslationStrings = {
'dawarich.accept.confirm.journal': 'Eintrag hinzufügen',
'dawarich.accept.confirm.bucket_list': 'Abhaken',
'dawarich.accept.recorded': 'Aufgezeichnet {from} bis {to}',
'dawarich.accept.duration': '{minutes} Min.',
'dawarich.accept.duration': '{count} Min.',
'dawarich.accept.duration.one': '{count} Min.',
'dawarich.accept.name': 'Name',
'dawarich.accept.date': 'Datum',
'dawarich.accept.from': 'Angekommen',
@@ -186,7 +187,8 @@ const dawarich: TranslationStrings = {
'dawarich.again': 'Erneut prüfen',
'dawarich.bucket.metersAway': '{meters} m entfernt',
'dawarich.bucket.kilometersAway': '{km} km entfernt',
'dawarich.bucket.rule': 'Ein Wunsch gilt ab {meters} m Nähe und {minutes} Minuten vor Ort als erreicht.',
'dawarich.bucket.rule': 'Ein Wunsch gilt ab {meters} m Nähe und {count} Minuten vor Ort als erreicht.',
'dawarich.bucket.rule.one': 'Ein Wunsch gilt ab {meters} m Nähe und {count} Minute vor Ort als erreicht.',
'dawarich.journey.dayStays.other': '{count} Aufenthalte aus Dawarich',
'dawarich.journey.dayStays.one': '{count} Aufenthalt aus Dawarich',
+11 -5
View File
@@ -54,7 +54,9 @@ const roadtrip: TranslationStrings = {
'roadtrip.window.conflict':
'Eine feste Uhrzeit passt nicht zu Fahrzeit und Tagespausen. Manuelle Zeiten bleiben unverändert; die automatische Aufteilung pausiert.',
'roadtrip.window.tooLong':
'Diese Reisezeiten erfordern mehr als {days} zusätzliche Tage. Wähle ein längeres Tagesfenster.',
'Diese Reisezeiten erfordern mehr als {count} zusätzliche Tage. Wähle ein längeres Tagesfenster.',
'roadtrip.window.tooLong.one':
'Diese Reisezeiten erfordern mehr als {count} zusätzlichen Tag. Wähle ein längeres Tagesfenster.',
'roadtrip.mode.roadtrip': 'Roadtrip',
'roadtrip.mode.days': 'Tage',
'roadtrip.mode.label': 'Plan-Ansicht',
@@ -101,8 +103,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.limit.minutes': 'Min',
'roadtrip.limit.off': 'aus',
'roadtrip.limit.blockNote': 'Ein Block = {distance}',
'roadtrip.limit.echoOver': '{over} von {days} Tagen über deinem Limit.',
'roadtrip.limit.echoNone': 'Alle {days} Tage innerhalb deiner Grenzen.',
'roadtrip.limit.echoOver': '{over} von {count} Tagen über deinem Limit.',
'roadtrip.limit.echoOver.one': '{over} von {count} Tag über deinem Limit.',
'roadtrip.limit.echoNone': 'Alle {count} Tage innerhalb deiner Grenzen.',
'roadtrip.limit.echoNone.one': 'Der eine Tag liegt innerhalb deiner Grenzen.',
'roadtrip.limit.echoDry': '{count} Abschnitte ohne erreichbaren Stopp.',
'roadtrip.limit.echoDry.one': '{count} Abschnitt ohne erreichbaren Stopp.',
'roadtrip.limit.echoEmpty': 'Öffne einen Roadtrip, um zu sehen, was das bewirkt.',
@@ -270,8 +274,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.stop.computed': 'Aus der Fahrzeit gerechnet',
'roadtrip.stop.endIsLeave': 'Im Roadtrip geht die Fahrt zu dieser Zeit weiter.',
'roadtrip.via.hint': 'Ziehen formt die Route um, Rechtsklick entfernt',
'roadtrip.warn.late': 'Ankunft {minutes} min nach der gesetzten Zeit',
'roadtrip.warn.missedLeave': 'Ankunft {minutes} min nach der gesetzten Abfahrtszeit',
'roadtrip.warn.late': 'Ankunft {count} min nach der gesetzten Zeit',
'roadtrip.warn.late.one': 'Ankunft {count} min nach der gesetzten Zeit',
'roadtrip.warn.missedLeave': 'Ankunft {count} min nach der gesetzten Abfahrtszeit',
'roadtrip.warn.missedLeave.one': 'Ankunft {count} min nach der gesetzten Abfahrtszeit',
'roadtrip.warn.overnight': 'Folgetag',
'roadtrip.spill.title': 'Von Tag {number}',
'roadtrip.spill.departs': 'ab {time}',
+4 -1
View File
@@ -636,7 +636,10 @@ const settings: TranslationStrings = {
'settings.apiKeys.endpoint': 'Endpunkt',
'settings.apiKeys.neverUsed': 'nie genutzt',
'settings.apiKeys.loadFailed': 'Deine Schlüssel konnten nicht geladen werden. Lade die Seite neu, um es noch einmal zu versuchen.',
'settings.apiKeys.limitReached': 'Du hast {max} Schlüssel, mehr geht pro Konto nicht. Lösche einen, den du nicht mehr brauchst, um einen neuen zu erstellen.',
'settings.apiKeys.limitReached':
'Du hast {count} Schlüssel, mehr geht pro Konto nicht. Lösche einen, den du nicht mehr brauchst, um einen neuen zu erstellen.',
'settings.apiKeys.limitReached.one':
'Du hast {count} Schlüssel, mehr geht pro Konto nicht. Lösche ihn, wenn du ihn nicht mehr brauchst, um einen neuen zu erstellen.',
'settings.apiKeys.copyFailed': 'Kopieren hat nicht geklappt. Markiere den Text und kopiere ihn von Hand.',
'settings.apiKeys.modal.createTitle': 'API-Schlüssel erstellen',
'settings.apiKeys.modal.name': 'Name',
+4 -2
View File
@@ -124,7 +124,8 @@ const storage: TranslationStrings = {
// Category migration (copy → flip → delta sweep)
'storage.migrate.promptTitle': 'Vorhandene Objekte auf das neue Backend verschieben?',
'storage.migrate.promptLine': '{category}: {objects} Objekte ({size}) von {from} nach {to}',
'storage.migrate.promptLine': '{category}: {count} Objekte ({size}) von {from} nach {to}',
'storage.migrate.promptLine.one': '{category}: {count} Objekt ({size}) von {from} nach {to}',
'storage.migrate.promptLineUnknown':
'{category}: unbekannte Größe (Nutzung noch nicht gescannt) von {from} nach {to}',
'storage.migrate.move': 'Vorhandene Objekte verschieben',
@@ -134,7 +135,8 @@ const storage: TranslationStrings = {
'storage.migrate.doneFailures': '{failed} fehlgeschlagen — diese Objekte wurden nicht auf das neue Backend kopiert',
'storage.migrate.failed': 'Verschieben fehlgeschlagen: {error} — die Kategorie wurde nicht umgestellt',
'storage.migrate.cancelled': 'Verschieben abgebrochen — nichts wurde umgestellt',
'storage.migrate.reclaimable': '{objects} Objekte ({size}) verbleiben auf {from} — manuell freigeben',
'storage.migrate.reclaimable': '{count} Objekte ({size}) verbleiben auf {from}, manuell freigeben',
'storage.migrate.reclaimable.one': '{count} Objekt ({size}) verbleibt auf {from}, manuell freigeben',
'storage.migrate.cancel': 'Verschieben abbrechen',
'storage.migrate.promptCancel': 'Abbrechen',
'storage.migrate.queued': 'In Warteschlange: {categories}',
+2 -1
View File
@@ -100,7 +100,8 @@ const collab: TranslationStrings = {
'collab.polls.options': 'Options',
'collab.polls.delete': 'Delete',
'collab.polls.closedSection': 'Closed',
'collab.chat.imageLimit': 'You can attach up to {max} images per message',
'collab.chat.imageLimit': 'You can attach up to {count} images per message',
'collab.chat.imageLimit.one': 'You can attach only {count} image per message',
'collab.chat.uploading': 'Uploading {percent}%',
'collab.chat.attachImages': 'Attach images',
'collab.chat.attachedImage': 'Attached image',
+2 -1
View File
@@ -113,7 +113,8 @@ const dashboard: TranslationStrings = {
'dashboard.useUnsplashPhoto': 'Use Unsplash photo by {photographer}',
'dashboard.titleRequired': 'Title is required',
'dashboard.endDateError': 'End date must be after start date',
'dashboard.tripTooLong': 'A trip can span at most {days} days',
'dashboard.tripTooLong': 'A trip can span at most {count} days',
'dashboard.tripTooLong.one': 'A trip can span at most {count} day',
'dashboard.dateShiftTitle': 'New start date',
'dashboard.dateShiftIntro': 'You changed when this trip starts. How should your plans follow the new dates?',
'dashboard.dateShiftKeepBookings': 'Keep bookings on their dates',
+4 -2
View File
@@ -116,7 +116,8 @@ const dawarich: TranslationStrings = {
'dawarich.accept.confirm.journal': 'Add entry',
'dawarich.accept.confirm.bucket_list': 'Tick it off',
'dawarich.accept.recorded': 'Recorded {from} until {to}',
'dawarich.accept.duration': '{minutes} min',
'dawarich.accept.duration': '{count} min',
'dawarich.accept.duration.one': '{count} min',
'dawarich.accept.name': 'Name',
'dawarich.accept.date': 'Date',
'dawarich.accept.from': 'Arrived',
@@ -181,7 +182,8 @@ const dawarich: TranslationStrings = {
'dawarich.again': 'Check again',
'dawarich.bucket.metersAway': '{meters} m away',
'dawarich.bucket.kilometersAway': '{km} km away',
'dawarich.bucket.rule': 'A wish counts as reached within {meters} m and after {minutes} minutes on the spot.',
'dawarich.bucket.rule': 'A wish counts as reached within {meters} m and after {count} minutes on the spot.',
'dawarich.bucket.rule.one': 'A wish counts as reached within {meters} m and after {count} minute on the spot.',
'dawarich.journey.dayStays.other': '{count} stays from Dawarich',
'dawarich.journey.dayStays.one': '{count} stay from Dawarich',
+11 -5
View File
@@ -54,7 +54,9 @@ const roadtrip: TranslationStrings = {
'roadtrip.window.conflict':
'A fixed time conflicts with the drive and daily breaks. Manual times are unchanged; automatic scheduling is paused.',
'roadtrip.window.tooLong':
'These travel times would require more than {days} extra days. Choose a longer daily window.',
'These travel times would require more than {count} extra days. Choose a longer daily window.',
'roadtrip.window.tooLong.one':
'These travel times would require more than {count} extra day. Choose a longer daily window.',
'roadtrip.mode.roadtrip': 'Road trip',
'roadtrip.mode.days': 'Days',
'roadtrip.mode.label': 'Plan view',
@@ -101,8 +103,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.limit.minutes': 'min',
'roadtrip.limit.off': 'off',
'roadtrip.limit.blockNote': 'One block = {distance}',
'roadtrip.limit.echoOver': '{over} of {days} days over your limit.',
'roadtrip.limit.echoNone': 'All {days} days within your limits.',
'roadtrip.limit.echoOver': '{over} of {count} days over your limit.',
'roadtrip.limit.echoOver.one': '{over} of {count} day over your limit.',
'roadtrip.limit.echoNone': 'All {count} days within your limits.',
'roadtrip.limit.echoNone.one': 'The one day is within your limits.',
'roadtrip.limit.echoDry': '{count} stretches with no reachable stop.',
'roadtrip.limit.echoDry.one': '{count} stretch with no reachable stop.',
'roadtrip.limit.echoEmpty': 'Open a road trip to see what these do to it.',
@@ -266,8 +270,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.stop.computed': 'Calculated from the drive',
'roadtrip.stop.endIsLeave': 'On the road trip, the drive leaves at this time.',
'roadtrip.via.hint': 'Drag to reshape the route, right-click to remove',
'roadtrip.warn.late': 'Arrives {minutes} min after the time you set',
'roadtrip.warn.missedLeave': 'Arrives {minutes} min after the time you set to leave',
'roadtrip.warn.late': 'Arrives {count} min after the time you set',
'roadtrip.warn.late.one': 'Arrives {count} min after the time you set',
'roadtrip.warn.missedLeave': 'Arrives {count} min after the time you set to leave',
'roadtrip.warn.missedLeave.one': 'Arrives {count} min after the time you set to leave',
'roadtrip.warn.overnight': 'Next day',
'roadtrip.spill.title': 'From day {number}',
'roadtrip.spill.departs': 'leaves {time}',
+4 -1
View File
@@ -622,7 +622,10 @@ const settings: TranslationStrings = {
'settings.apiKeys.endpoint': 'Endpoint',
'settings.apiKeys.neverUsed': 'never used',
'settings.apiKeys.loadFailed': 'Could not load your keys. Reload the page to try again.',
'settings.apiKeys.limitReached': 'You have {max} keys, the most an account can hold. Delete one you no longer use to create another.',
'settings.apiKeys.limitReached':
'You have {count} keys, the most an account can hold. Delete one you no longer use to create another.',
'settings.apiKeys.limitReached.one':
'You have {count} key, the most an account can hold. Delete it if you no longer use it to create another.',
'settings.apiKeys.copyFailed': 'Could not copy. Select the text and copy it by hand.',
'settings.apiKeys.modal.createTitle': 'Create API key',
'settings.apiKeys.modal.name': 'Name',
+4 -2
View File
@@ -136,7 +136,8 @@ const storage: TranslationStrings = {
// Category migration (copy → flip → delta sweep)
'storage.migrate.promptTitle': 'Move existing objects to the new backend?',
'storage.migrate.promptLine': '{category}: {objects} objects ({size}) from {from} to {to}',
'storage.migrate.promptLine': '{category}: {count} objects ({size}) from {from} to {to}',
'storage.migrate.promptLine.one': '{category}: {count} object ({size}) from {from} to {to}',
'storage.migrate.promptLineUnknown': '{category}: unknown size (no usage scan yet) from {from} to {to}',
'storage.migrate.move': 'Move existing objects',
'storage.migrate.routeOnly': 'Just route new writes',
@@ -145,7 +146,8 @@ const storage: TranslationStrings = {
'storage.migrate.doneFailures': '{failed} failed — those objects were not copied to the new backend',
'storage.migrate.failed': 'Move failed: {error} — the category was not switched',
'storage.migrate.cancelled': 'Move cancelled — nothing was switched',
'storage.migrate.reclaimable': '{objects} objects ({size}) remain on {from} — reclaim manually',
'storage.migrate.reclaimable': '{count} objects ({size}) remain on {from}, reclaim manually',
'storage.migrate.reclaimable.one': '{count} object ({size}) remains on {from}, reclaim manually',
'storage.migrate.cancel': 'Cancel move',
'storage.migrate.promptCancel': 'Cancel',
'storage.migrate.queued': 'Queued: {categories}',
+2 -1
View File
@@ -100,7 +100,8 @@ const collab: TranslationStrings = {
'collab.polls.options': 'Opciones',
'collab.polls.delete': 'Eliminar',
'collab.polls.closedSection': 'Cerradas',
'collab.chat.imageLimit': 'Puedes adjuntar hasta {max} imágenes por mensaje',
'collab.chat.imageLimit': 'Puedes adjuntar hasta {count} imágenes por mensaje',
'collab.chat.imageLimit.one': 'Solo puedes adjuntar {count} imagen por mensaje',
'collab.chat.uploading': 'Subiendo {percent} %',
'collab.chat.attachImages': 'Adjuntar imágenes',
'collab.chat.attachedImage': 'Imagen adjunta',
+2 -1
View File
@@ -99,7 +99,8 @@ const dashboard: TranslationStrings = {
'dashboard.useUnsplashPhoto': 'Usar foto de Unsplash de {photographer}',
'dashboard.titleRequired': 'El título es obligatorio',
'dashboard.endDateError': 'La fecha de fin debe ser posterior a la de inicio',
'dashboard.tripTooLong': 'Un viaje puede durar como máximo {days} días',
'dashboard.tripTooLong': 'Un viaje puede durar como máximo {count} días',
'dashboard.tripTooLong.one': 'Un viaje puede durar como máximo {count} día',
'dashboard.dateShiftTitle': 'Nueva fecha de inicio',
'dashboard.dateShiftIntro':
'Has cambiado cuándo empieza este viaje. ¿Cómo deben seguir tus planes las nuevas fechas?',
+4 -2
View File
@@ -118,7 +118,8 @@ const dawarich: TranslationStrings = {
'dawarich.accept.confirm.journal': 'Añadir entrada',
'dawarich.accept.confirm.bucket_list': 'Tacharlo',
'dawarich.accept.recorded': 'Grabado de {from} a {to}',
'dawarich.accept.duration': '{minutes} min',
'dawarich.accept.duration': '{count} min',
'dawarich.accept.duration.one': '{count} min',
'dawarich.accept.name': 'Nombre',
'dawarich.accept.date': 'Fecha',
'dawarich.accept.from': 'Llegada',
@@ -183,7 +184,8 @@ const dawarich: TranslationStrings = {
'dawarich.again': 'Volver a comprobar',
'dawarich.bucket.metersAway': 'a {meters} m',
'dawarich.bucket.kilometersAway': 'a {km} km',
'dawarich.bucket.rule': 'Un deseo cuenta como cumplido a menos de {meters} m y tras {minutes} minutos en el lugar.',
'dawarich.bucket.rule': 'Un deseo cuenta como cumplido a menos de {meters} m y tras {count} minutos en el lugar.',
'dawarich.bucket.rule.one': 'Un deseo cuenta como cumplido a menos de {meters} m y tras {count} minuto en el lugar.',
'dawarich.journey.dayStays.other': '{count} estancias de Dawarich',
'dawarich.journey.dayStays.one': '{count} estancia de Dawarich',
+10 -5
View File
@@ -56,7 +56,8 @@ const roadtrip: TranslationStrings = {
'roadtrip.window.incomplete': 'Las pausas diarias necesitan todas las rutas de conexión para calcularse.',
'roadtrip.window.conflict':
'Una hora fija entra en conflicto con el trayecto y las pausas. Las horas manuales se conservan; la planificación automática está en pausa.',
'roadtrip.window.tooLong': 'Estos horarios requieren más de {days} días adicionales. Amplía el horario diario.',
'roadtrip.window.tooLong': 'Estos horarios requieren más de {count} días adicionales. Amplía el horario diario.',
'roadtrip.window.tooLong.one': 'Estos horarios requieren más de {count} día adicional. Amplía el horario diario.',
'roadtrip.mode.roadtrip': 'Viaje por carretera',
'roadtrip.mode.days': 'Días',
'roadtrip.mode.label': 'Vista del plan',
@@ -103,8 +104,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.limit.minutes': 'min',
'roadtrip.limit.off': 'desactivado',
'roadtrip.limit.blockNote': 'Un bloque = {distance}',
'roadtrip.limit.echoOver': '{over} de {days} días superan tu límite.',
'roadtrip.limit.echoNone': 'Los {days} días están dentro de tus límites.',
'roadtrip.limit.echoOver': '{over} de {count} días superan tu límite.',
'roadtrip.limit.echoOver.one': '{over} de {count} día supera tu límite.',
'roadtrip.limit.echoNone': 'Los {count} días están dentro de tus límites.',
'roadtrip.limit.echoNone.one': 'El único día está dentro de tus límites.',
'roadtrip.limit.echoDry': '{count} tramos sin parada alcanzable.',
'roadtrip.limit.echoDry.one': '{count} tramo sin parada alcanzable.',
'roadtrip.limit.echoEmpty': 'Abre una ruta para ver el efecto.',
@@ -274,8 +277,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.stop.computed': 'Calculado a partir del trayecto',
'roadtrip.stop.endIsLeave': 'En el viaje por carretera, se sale a esta hora.',
'roadtrip.via.hint': 'Arrastra para remodelar la ruta, clic derecho para quitar',
'roadtrip.warn.late': 'Llega {minutes} min después de la hora fijada',
'roadtrip.warn.missedLeave': 'Llega {minutes} min después de la hora de salida fijada',
'roadtrip.warn.late': 'Llega {count} min después de la hora fijada',
'roadtrip.warn.late.one': 'Llega {count} min después de la hora fijada',
'roadtrip.warn.missedLeave': 'Llega {count} min después de la hora de salida fijada',
'roadtrip.warn.missedLeave.one': 'Llega {count} min después de la hora de salida fijada',
'roadtrip.warn.overnight': 'Día siguiente',
'roadtrip.spill.title': 'Del día {number}',
'roadtrip.spill.departs': 'sale {time}',
+4 -1
View File
@@ -633,7 +633,10 @@ const settings: TranslationStrings = {
'settings.apiKeys.endpoint': 'Endpoint',
'settings.apiKeys.neverUsed': 'nunca usada',
'settings.apiKeys.loadFailed': 'No se pudieron cargar tus claves. Recarga la página para volver a intentarlo.',
'settings.apiKeys.limitReached': 'Tienes {max} claves, el máximo por cuenta. Elimina una que ya no uses para crear otra.',
'settings.apiKeys.limitReached':
'Tienes {count} claves, el máximo por cuenta. Elimina una que ya no uses para crear otra.',
'settings.apiKeys.limitReached.one':
'Tienes {count} clave, el máximo por cuenta. Elimínala si ya no la usas para crear otra.',
'settings.apiKeys.copyFailed': 'No se pudo copiar. Selecciona el texto y cópialo a mano.',
'settings.apiKeys.modal.createTitle': 'Crear clave API',
'settings.apiKeys.modal.name': 'Nombre',
+4 -2
View File
@@ -126,7 +126,8 @@ const storage: TranslationStrings = {
// Category migration (copy → flip → delta sweep)
'storage.migrate.promptTitle': '¿Mover los objetos existentes al nuevo backend?',
'storage.migrate.promptLine': '{category}: {objects} objetos ({size}) de {from} a {to}',
'storage.migrate.promptLine': '{category}: {count} objetos ({size}) de {from} a {to}',
'storage.migrate.promptLine.one': '{category}: {count} objeto ({size}) de {from} a {to}',
'storage.migrate.promptLineUnknown': '{category}: tamaño desconocido (uso aún no calculado) de {from} a {to}',
'storage.migrate.move': 'Mover objetos existentes',
'storage.migrate.routeOnly': 'Solo enrutar las escrituras nuevas',
@@ -135,7 +136,8 @@ const storage: TranslationStrings = {
'storage.migrate.doneFailures': '{failed} fallaron — esos objetos no se copiaron al nuevo backend',
'storage.migrate.failed': 'Error al mover: {error} — la categoría no se cambió',
'storage.migrate.cancelled': 'Movimiento cancelado — no se cambió nada',
'storage.migrate.reclaimable': '{objects} objetos ({size}) permanecen en {from} — recupéralos manualmente',
'storage.migrate.reclaimable': '{count} objetos ({size}) permanecen en {from}, recupéralos manualmente',
'storage.migrate.reclaimable.one': '{count} objeto ({size}) permanece en {from}, recupéralo manualmente',
'storage.migrate.cancel': 'Cancelar movimiento',
'storage.migrate.promptCancel': 'Cancelar',
'storage.migrate.queued': 'En cola: {categories}',
+2 -1
View File
@@ -100,7 +100,8 @@ const collab: TranslationStrings = {
'collab.polls.options': 'Valikud',
'collab.polls.delete': 'Kustuta',
'collab.polls.closedSection': 'Suletud',
'collab.chat.imageLimit': 'Ühele sõnumile saad lisada kuni {max} pilti',
'collab.chat.imageLimit': 'Ühele sõnumile saad lisada kuni {count} pilti',
'collab.chat.imageLimit.one': 'Ühele sõnumile saad lisada vaid {count} pildi',
'collab.chat.uploading': 'Üleslaadimine {percent}%',
'collab.chat.attachImages': 'Lisa pildid',
'collab.chat.attachedImage': 'Lisatud pilt',
+2 -1
View File
@@ -113,7 +113,8 @@ const dashboard: TranslationStrings = {
'dashboard.useUnsplashPhoto': 'Kasuta Unsplashi fotot autorilt {photographer}',
'dashboard.titleRequired': 'Pealkiri on kohustuslik',
'dashboard.endDateError': 'Lõppkuupäev peab olema alguskuupäevast hilisem',
'dashboard.tripTooLong': 'Reis võib kesta kuni {days} päeva',
'dashboard.tripTooLong': 'Reis võib kesta kuni {count} päeva',
'dashboard.tripTooLong.one': 'Reis võib kesta kuni {count} päev',
'dashboard.dateShiftTitle': 'Uus alguskuupäev',
'dashboard.dateShiftIntro': 'Muutsid reisi algust. Kuidas peaksid plaanid uute kuupäevadega kohanema?',
'dashboard.dateShiftKeepBookings': 'Jäta broneeringud oma kuupäevadele',
+5 -2
View File
@@ -119,7 +119,8 @@ const dawarich: TranslationStrings = {
'dawarich.accept.confirm.journal': 'Lisa sissekanne',
'dawarich.accept.confirm.bucket_list': 'Märgi täidetuks',
'dawarich.accept.recorded': 'Salvestatud {from} kuni {to}',
'dawarich.accept.duration': '{minutes} min',
'dawarich.accept.duration': '{count} min',
'dawarich.accept.duration.one': '{count} min',
'dawarich.accept.name': 'Nimi',
'dawarich.accept.date': 'Kuupäev',
'dawarich.accept.from': 'Saabusid',
@@ -184,7 +185,9 @@ const dawarich: TranslationStrings = {
'dawarich.again': 'Kontrolli uuesti',
'dawarich.bucket.metersAway': '{meters} m kaugusel',
'dawarich.bucket.kilometersAway': '{km} km kaugusel',
'dawarich.bucket.rule': 'Soov loetakse täidetuks, kui viibid vähemalt {minutes} minutit kohast {meters} m raadiuses.',
'dawarich.bucket.rule': 'Soov loetakse täidetuks, kui viibid vähemalt {count} minutit kohast {meters} m raadiuses.',
'dawarich.bucket.rule.one':
'Soov loetakse täidetuks, kui viibid vähemalt {count} minuti kohast {meters} m raadiuses.',
'dawarich.journey.dayStays.other': '{count} peatumist Dawarichist',
'dawarich.journey.dayStays.one': '{count} peatumine Dawarichist',
+10 -5
View File
@@ -52,7 +52,8 @@ const roadtrip: TranslationStrings = {
'roadtrip.window.incomplete': 'Päevaseid pause ei saa arvutada, kuni kõik ühendavad marsruudid on saadaval.',
'roadtrip.window.conflict':
'Kindel kellaaeg on vastuolus sõidu ja päevapausidega. Käsitsi määratud ajad ei muutu; automaatne ajastamine on peatatud.',
'roadtrip.window.tooLong': 'Nende sõiduaegadega oleks vaja üle {days} lisapäeva. Vali pikem päevane ajavahemik.',
'roadtrip.window.tooLong': 'Nende sõiduaegadega oleks vaja üle {count} lisapäeva. Vali pikem päevane ajavahemik.',
'roadtrip.window.tooLong.one': 'Nende sõiduaegadega oleks vaja üle {count} lisapäeva. Vali pikem päevane ajavahemik.',
'roadtrip.mode.roadtrip': 'Autoreis',
'roadtrip.mode.days': 'Päevad',
'roadtrip.mode.label': 'Plaanivaade',
@@ -99,8 +100,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.limit.minutes': 'min',
'roadtrip.limit.off': 'väljas',
'roadtrip.limit.blockNote': 'Üks plokk = {distance}',
'roadtrip.limit.echoOver': '{over} päeva {days}-st ületab sinu piirangu.',
'roadtrip.limit.echoNone': 'Kõik {days} päeva jäävad sinu piirangutesse.',
'roadtrip.limit.echoOver': '{over} päeva {count}-st ületab sinu piirangu.',
'roadtrip.limit.echoOver.one': '{over} päeva {count}-st ületab sinu piirangu.',
'roadtrip.limit.echoNone': 'Kõik {count} päeva jäävad sinu piirangutesse.',
'roadtrip.limit.echoNone.one': 'Ainus päev jääb sinu piirangutesse.',
'roadtrip.limit.echoDry': '{count} lõiku ilma kättesaadava peatuseta.',
'roadtrip.limit.echoDry.one': '{count} lõik ilma kättesaadava peatuseta.',
'roadtrip.limit.echoEmpty': 'Ava autoreis, et näha nende mõju.',
@@ -266,8 +269,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.stop.computed': 'Arvutatud sõidu põhjal',
'roadtrip.stop.endIsLeave': 'Autoreisil algab sõit sel kellaajal.',
'roadtrip.via.hint': 'Marsruudi muutmiseks lohista, eemaldamiseks tee paremklõps',
'roadtrip.warn.late': 'Saabub {minutes} minutit pärast määratud aega',
'roadtrip.warn.missedLeave': 'Saabub {minutes} minutit pärast määratud väljumisaega',
'roadtrip.warn.late': 'Saabub {count} minutit pärast määratud aega',
'roadtrip.warn.late.one': 'Saabub {count} minut pärast määratud aega',
'roadtrip.warn.missedLeave': 'Saabub {count} minutit pärast määratud väljumisaega',
'roadtrip.warn.missedLeave.one': 'Saabub {count} minut pärast määratud väljumisaega',
'roadtrip.warn.overnight': 'Järgmine päev',
'roadtrip.spill.title': 'Päevast {number}',
'roadtrip.spill.departs': 'väljub {time}',
+3 -1
View File
@@ -633,7 +633,9 @@ const settings: TranslationStrings = {
'settings.apiKeys.neverUsed': 'pole kunagi kasutatud',
'settings.apiKeys.loadFailed': 'Võtmeid ei saanud laadida. Uuesti proovimiseks laadi leht uuesti.',
'settings.apiKeys.limitReached':
'Sul on {max} võtit — konto suurim lubatud arv. Uue loomiseks kustuta võti, mida enam ei kasuta.',
'Sul on {count} võtit, konto suurim lubatud arv. Uue loomiseks kustuta võti, mida enam ei kasuta.',
'settings.apiKeys.limitReached.one':
'Sul on {count} võti, konto suurim lubatud arv. Uue loomiseks kustuta see, kui sa seda enam ei kasuta.',
'settings.apiKeys.copyFailed': 'Kopeerimine ebaõnnestus. Vali tekst ja kopeeri käsitsi.',
'settings.apiKeys.modal.createTitle': 'Loo API-võti',
'settings.apiKeys.modal.name': 'Nimi',
+4 -2
View File
@@ -139,7 +139,8 @@ const storage: TranslationStrings = {
// Category migration (copy → flip → delta sweep)
'storage.migrate.promptTitle': 'Kas teisaldada olemasolevad objektid uude taustsüsteemi?',
'storage.migrate.promptLine': '{category}: {objects} objekti ({size}), {from} → {to}',
'storage.migrate.promptLine': '{category}: {count} objekti ({size}), {from} → {to}',
'storage.migrate.promptLine.one': '{category}: {count} objekt ({size}), {from} → {to}',
'storage.migrate.promptLineUnknown': '{category}: suurus teadmata (kasutust pole veel kontrollitud), {from} → {to}',
'storage.migrate.move': 'Teisalda olemasolevad objektid',
'storage.migrate.routeOnly': 'Suuna ainult uued kirjutused',
@@ -148,7 +149,8 @@ const storage: TranslationStrings = {
'storage.migrate.doneFailures': '{failed} ebaõnnestus — neid objekte ei kopeeritud uude taustsüsteemi',
'storage.migrate.failed': 'Teisaldamine ebaõnnestus: {error} — kategooriat ei suunatud ümber',
'storage.migrate.cancelled': 'Teisaldamine tühistatud — midagi ei suunatud ümber',
'storage.migrate.reclaimable': '{objects} objekti ({size}) jäid taustsüsteemi {from} — vabasta ruum käsitsi',
'storage.migrate.reclaimable': '{count} objekti ({size}) jäid taustsüsteemi {from}, vabasta ruum käsitsi',
'storage.migrate.reclaimable.one': '{count} objekt ({size}) jäi taustsüsteemi {from}, vabasta ruum käsitsi',
'storage.migrate.cancel': 'Tühista teisaldamine',
'storage.migrate.promptCancel': 'Tühista',
'storage.migrate.queued': 'Järjekorras: {categories}',
+2 -1
View File
@@ -100,7 +100,8 @@ const collab: TranslationStrings = {
'collab.polls.options': 'Options',
'collab.polls.delete': 'Supprimer',
'collab.polls.closedSection': 'Fermés',
'collab.chat.imageLimit': 'Vous pouvez joindre jusqu’à {max} images par message',
'collab.chat.imageLimit': 'Vous pouvez joindre jusqu’à {count} images par message',
'collab.chat.imageLimit.one': 'Vous pouvez joindre jusqu’à {count} image par message',
'collab.chat.uploading': 'Envoi {percent} %',
'collab.chat.attachImages': 'Joindre des images',
'collab.chat.attachedImage': 'Image jointe',
+2 -1
View File
@@ -101,7 +101,8 @@ const dashboard: TranslationStrings = {
'dashboard.useUnsplashPhoto': 'Utiliser la photo Unsplash de {photographer}',
'dashboard.titleRequired': 'Le titre est obligatoire',
'dashboard.endDateError': 'La date de fin doit être postérieure à la date de début',
'dashboard.tripTooLong': 'Un voyage ne peut pas dépasser {days} jours',
'dashboard.tripTooLong': 'Un voyage ne peut pas dépasser {count} jours',
'dashboard.tripTooLong.one': 'Un voyage ne peut pas dépasser {count} jour',
'dashboard.dateShiftTitle': 'Nouvelle date de début',
'dashboard.dateShiftIntro':
'Vous avez modifié la date de début de ce voyage. Comment vos plans doivent-ils suivre les nouvelles dates ?',
+4 -2
View File
@@ -119,7 +119,8 @@ const dawarich: TranslationStrings = {
'dawarich.accept.confirm.journal': 'Ajouter l’entrée',
'dawarich.accept.confirm.bucket_list': 'Le cocher',
'dawarich.accept.recorded': 'Enregistré du {from} au {to}',
'dawarich.accept.duration': '{minutes} min',
'dawarich.accept.duration': '{count} min',
'dawarich.accept.duration.one': '{count} min',
'dawarich.accept.name': 'Nom',
'dawarich.accept.date': 'Date',
'dawarich.accept.from': 'Arrivée',
@@ -185,7 +186,8 @@ const dawarich: TranslationStrings = {
'dawarich.again': 'Vérifier à nouveau',
'dawarich.bucket.metersAway': 'à {meters} m',
'dawarich.bucket.kilometersAway': 'à {km} km',
'dawarich.bucket.rule': 'Une envie est atteinte à moins de {meters} m et après {minutes} minutes sur place.',
'dawarich.bucket.rule': 'Une envie est atteinte à moins de {meters} m et après {count} minutes sur place.',
'dawarich.bucket.rule.one': 'Une envie est atteinte à moins de {meters} m et après {count} minute sur place.',
'dawarich.journey.dayStays.other': '{count} séjours issus de Dawarich',
'dawarich.journey.dayStays.one': '{count} séjour issu de Dawarich',
+11 -5
View File
@@ -57,7 +57,9 @@ const roadtrip: TranslationStrings = {
'roadtrip.window.conflict':
'Une heure fixe est incompatible avec le trajet et les pauses. Les heures manuelles sont conservées ; la planification automatique est suspendue.',
'roadtrip.window.tooLong':
'Ces horaires nécessitent plus de {days} jours supplémentaires. Élargissez la plage horaire quotidienne.',
'Ces horaires nécessitent plus de {count} jours supplémentaires. Élargissez la plage horaire quotidienne.',
'roadtrip.window.tooLong.one':
'Ces horaires nécessitent plus de {count} jour supplémentaire. Élargissez la plage horaire quotidienne.',
'roadtrip.mode.roadtrip': 'Road trip',
'roadtrip.mode.days': 'Jours',
'roadtrip.mode.label': 'Vue du plan',
@@ -104,8 +106,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.limit.minutes': 'min',
'roadtrip.limit.off': 'désactivé',
'roadtrip.limit.blockNote': 'Un bloc = {distance}',
'roadtrip.limit.echoOver': '{over} jours sur {days} dépassent ta limite.',
'roadtrip.limit.echoNone': 'Les {days} jours respectent tes limites.',
'roadtrip.limit.echoOver': '{over} jours sur {count} dépassent ta limite.',
'roadtrip.limit.echoOver.one': '{over} jours sur {count} dépassent ta limite.',
'roadtrip.limit.echoNone': 'Les {count} jours respectent tes limites.',
'roadtrip.limit.echoNone.one': '{count} jour, dans tes limites.',
'roadtrip.limit.echoDry': '{count} tronçons sans arrêt accessible.',
'roadtrip.limit.echoDry.one': '{count} tronçon sans arrêt accessible.',
'roadtrip.limit.echoEmpty': 'Ouvre un road trip pour voir l’effet.',
@@ -275,8 +279,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.stop.computed': 'Calculé d’après le trajet',
'roadtrip.stop.endIsLeave': 'Pendant le road trip, le départ se fait à cette heure.',
'roadtrip.via.hint': 'Faites glisser pour remodeler l’itinéraire, clic droit pour supprimer',
'roadtrip.warn.late': 'Arrivée {minutes} min après l’heure fixée',
'roadtrip.warn.missedLeave': 'Arrivée {minutes} min après l’heure de départ fixée',
'roadtrip.warn.late': 'Arrivée {count} min après l’heure fixée',
'roadtrip.warn.late.one': 'Arrivée {count} min après l’heure fixée',
'roadtrip.warn.missedLeave': 'Arrivée {count} min après l’heure de départ fixée',
'roadtrip.warn.missedLeave.one': 'Arrivée {count} min après l’heure de départ fixée',
'roadtrip.warn.overnight': 'Jour suivant',
'roadtrip.spill.title': 'Du jour {number}',
'roadtrip.spill.departs': 'départ {time}',
+3 -1
View File
@@ -644,7 +644,9 @@ const settings: TranslationStrings = {
'settings.apiKeys.neverUsed': 'jamais utilisée',
'settings.apiKeys.loadFailed': 'Impossible de charger vos clés. Rechargez la page pour réessayer.',
'settings.apiKeys.limitReached':
"Vous avez {max} clés, le maximum pour un compte. Supprimez-en une dont vous n'avez plus besoin pour en créer une autre.",
"Vous avez {count} clés, le maximum pour un compte. Supprimez-en une dont vous n'avez plus besoin pour en créer une autre.",
'settings.apiKeys.limitReached.one':
"Vous avez {count} clé, le maximum pour un compte. Supprimez-la si vous n'en avez plus besoin pour en créer une autre.",
'settings.apiKeys.copyFailed': 'Impossible de copier. Sélectionnez le texte et copiez-le manuellement.',
'settings.apiKeys.modal.createTitle': 'Créer une clé API',
'settings.apiKeys.modal.name': 'Nom',
+4 -2
View File
@@ -126,7 +126,8 @@ const storage: TranslationStrings = {
// Category migration (copy → flip → delta sweep)
'storage.migrate.promptTitle': 'Déplacer les objets existants vers le nouveau backend ?',
'storage.migrate.promptLine': '{category} : {objects} objets ({size}) de {from} vers {to}',
'storage.migrate.promptLine': '{category} : {count} objets ({size}) de {from} vers {to}',
'storage.migrate.promptLine.one': '{category} : {count} objet ({size}) de {from} vers {to}',
'storage.migrate.promptLineUnknown':
'{category} : taille inconnue (utilisation pas encore calculée) de {from} vers {to}',
'storage.migrate.move': 'Déplacer les objets existants',
@@ -136,7 +137,8 @@ const storage: TranslationStrings = {
'storage.migrate.doneFailures': "{failed} en échec — ces objets n'ont pas été copiés vers le nouveau backend",
'storage.migrate.failed': "Échec du déplacement : {error} — la catégorie n'a pas été changée",
'storage.migrate.cancelled': "Déplacement annulé — rien n'a été changé",
'storage.migrate.reclaimable': '{objects} objets ({size}) restent sur {from} — à récupérer manuellement',
'storage.migrate.reclaimable': '{count} objets ({size}) restent sur {from}, à récupérer manuellement',
'storage.migrate.reclaimable.one': '{count} objet ({size}) reste sur {from}, à récupérer manuellement',
'storage.migrate.cancel': 'Annuler le déplacement',
'storage.migrate.promptCancel': 'Annuler',
'storage.migrate.queued': "En file d'attente : {categories}",
+2 -1
View File
@@ -100,7 +100,8 @@ const collab: TranslationStrings = {
'collab.polls.options': 'Επιλογές',
'collab.polls.delete': 'Διαγραφή',
'collab.polls.closedSection': 'Κλειστές',
'collab.chat.imageLimit': 'Μπορείτε να επισυνάψετε έως {max} εικόνες ανά μήνυμα',
'collab.chat.imageLimit': 'Μπορείτε να επισυνάψετε έως {count} εικόνες ανά μήνυμα',
'collab.chat.imageLimit.one': 'Μπορείτε να επισυνάψετε μόνο {count} εικόνα ανά μήνυμα',
'collab.chat.uploading': 'Μεταφόρτωση {percent}%',
'collab.chat.attachImages': 'Επισύναψη εικόνων',
'collab.chat.attachedImage': 'Συνημμένη εικόνα',
+2 -1
View File
@@ -111,7 +111,8 @@ const dashboard: TranslationStrings = {
'dashboard.useUnsplashPhoto': 'Χρήση φωτογραφίας Unsplash από {photographer}',
'dashboard.titleRequired': 'Ο τίτλος είναι υποχρεωτικός',
'dashboard.endDateError': 'Η ημερομηνία λήξης πρέπει να είναι μετά την ημερομηνία έναρξης',
'dashboard.tripTooLong': 'Ένα ταξίδι μπορεί να διαρκεί το πολύ {days} ημέρες',
'dashboard.tripTooLong': 'Ένα ταξίδι μπορεί να διαρκεί το πολύ {count} ημέρες',
'dashboard.tripTooLong.one': 'Ένα ταξίδι μπορεί να διαρκεί το πολύ {count} ημέρα',
'dashboard.dateShiftTitle': 'Νέα ημερομηνία έναρξης',
'dashboard.dateShiftIntro':
'Αλλάξατε την ημερομηνία έναρξης αυτού του ταξιδιού. Πώς θέλετε να ακολουθήσουν τα σχέδιά σας τις νέες ημερομηνίες;',
+6 -2
View File
@@ -120,7 +120,8 @@ const dawarich: TranslationStrings = {
'dawarich.accept.confirm.journal': 'Προσθήκη καταχώρησης',
'dawarich.accept.confirm.bucket_list': 'Σημείωση ως εκπληρωμένης',
'dawarich.accept.recorded': 'Καταγράφηκε από {from} έως {to}',
'dawarich.accept.duration': '{minutes} λεπτά',
'dawarich.accept.duration': '{count} λεπτά',
'dawarich.accept.duration.one': '{count} λεπτό',
'dawarich.accept.name': 'Όνομα',
'dawarich.accept.date': 'Ημερομηνία',
'dawarich.accept.from': 'Άφιξη',
@@ -186,7 +187,10 @@ const dawarich: TranslationStrings = {
'dawarich.again': 'Έλεγχος ξανά',
'dawarich.bucket.metersAway': '{meters} μ. μακριά',
'dawarich.bucket.kilometersAway': '{km} χλμ. μακριά',
'dawarich.bucket.rule': 'Μια επιθυμία μετράει ως εκπληρωμένη σε απόσταση έως {meters} μ. και μετά από {minutes} λεπτά στο σημείο.',
'dawarich.bucket.rule':
'Μια επιθυμία μετράει ως εκπληρωμένη σε απόσταση έως {meters} μ. και μετά από {count} λεπτά στο σημείο.',
'dawarich.bucket.rule.one':
'Μια επιθυμία μετράει ως εκπληρωμένη σε απόσταση έως {meters} μ. και μετά από {count} λεπτό στο σημείο.',
'dawarich.journey.dayStays.other': '{count} στάσεις από το Dawarich',
'dawarich.journey.dayStays.one': '1 στάση από το Dawarich',
+11 -5
View File
@@ -58,7 +58,9 @@ const roadtrip: TranslationStrings = {
'roadtrip.window.conflict':
'Μια σταθερή ώρα συγκρούεται με την οδήγηση και τις παύσεις. Οι χειροκίνητες ώρες διατηρούνται· ο αυτόματος σχεδιασμός έχει παύσει.',
'roadtrip.window.tooLong':
'Αυτές οι ώρες απαιτούν πάνω από {days} επιπλέον ημέρες. Επιλέξτε μεγαλύτερο ημερήσιο διάστημα.',
'Αυτές οι ώρες απαιτούν πάνω από {count} επιπλέον ημέρες. Επιλέξτε μεγαλύτερο ημερήσιο διάστημα.',
'roadtrip.window.tooLong.one':
'Αυτές οι ώρες απαιτούν πάνω από {count} επιπλέον ημέρα. Επιλέξτε μεγαλύτερο ημερήσιο διάστημα.',
'roadtrip.mode.roadtrip': 'Οδικό ταξίδι',
'roadtrip.mode.days': 'Ημέρες',
'roadtrip.mode.label': 'Προβολή σχεδίου',
@@ -105,8 +107,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.limit.minutes': 'λεπ',
'roadtrip.limit.off': 'ανενεργό',
'roadtrip.limit.blockNote': 'Ένα μπλοκ = {distance}',
'roadtrip.limit.echoOver': '{over} από {days} ημέρες πάνω από το όριο.',
'roadtrip.limit.echoNone': 'Και οι {days} ημέρες εντός ορίων.',
'roadtrip.limit.echoOver': '{over} από {count} ημέρες πάνω από το όριο.',
'roadtrip.limit.echoOver.one': '{over} από {count} ημέρα πάνω από το όριο.',
'roadtrip.limit.echoNone': 'Και οι {count} ημέρες εντός ορίων.',
'roadtrip.limit.echoNone.one': 'Η μία ημέρα είναι εντός ορίων.',
'roadtrip.limit.echoDry': '{count} τμήματα χωρίς στάση εντός αυτονομίας.',
'roadtrip.limit.echoDry.one': '{count} τμήμα χωρίς στάση εντός αυτονομίας.',
'roadtrip.limit.echoEmpty': 'Άνοιξε ένα ταξίδι για να δεις το αποτέλεσμα.',
@@ -277,8 +281,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.stop.computed': 'Υπολογισμένη από τη διαδρομή',
'roadtrip.stop.endIsLeave': 'Στο οδικό ταξίδι, η αναχώρηση γίνεται αυτή την ώρα.',
'roadtrip.via.hint': 'Σύρετε για να αλλάξετε τη διαδρομή, δεξί κλικ για αφαίρεση',
'roadtrip.warn.late': 'Άφιξη {minutes} λεπτά μετά την ορισμένη ώρα',
'roadtrip.warn.missedLeave': 'Άφιξη {minutes} λεπτά μετά την ορισμένη ώρα αναχώρησης',
'roadtrip.warn.late': 'Άφιξη {count} λεπτά μετά την ορισμένη ώρα',
'roadtrip.warn.late.one': 'Άφιξη {count} λεπτό μετά την ορισμένη ώρα',
'roadtrip.warn.missedLeave': 'Άφιξη {count} λεπτά μετά την ορισμένη ώρα αναχώρησης',
'roadtrip.warn.missedLeave.one': 'Άφιξη {count} λεπτό μετά την ορισμένη ώρα αναχώρησης',
'roadtrip.warn.overnight': 'Επόμενη μέρα',
'roadtrip.spill.title': 'Από την ημέρα {number}',
'roadtrip.spill.departs': 'αναχώρηση {time}',
+4 -1
View File
@@ -642,7 +642,10 @@ const settings: TranslationStrings = {
'settings.apiKeys.endpoint': 'Endpoint',
'settings.apiKeys.neverUsed': 'δεν χρησιμοποιήθηκε ποτέ',
'settings.apiKeys.loadFailed': 'Δεν ήταν δυνατή η φόρτωση των κλειδιών σου. Φόρτωσε ξανά τη σελίδα για να δοκιμάσεις πάλι.',
'settings.apiKeys.limitReached': 'Έχεις {max} κλειδιά, το μέγιστο για έναν λογαριασμό. Διάγραψε ένα που δεν χρησιμοποιείς πια για να δημιουργήσεις νέο.',
'settings.apiKeys.limitReached':
'Έχεις {count} κλειδιά, το μέγιστο για έναν λογαριασμό. Διάγραψε ένα που δεν χρησιμοποιείς πια για να δημιουργήσεις νέο.',
'settings.apiKeys.limitReached.one':
'Έχεις {count} κλειδί, το μέγιστο για έναν λογαριασμό. Διάγραψέ το αν δεν το χρησιμοποιείς πια για να δημιουργήσεις νέο.',
'settings.apiKeys.copyFailed': 'Η αντιγραφή απέτυχε. Επίλεξε το κείμενο και αντίγραψέ το χειροκίνητα.',
'settings.apiKeys.modal.createTitle': 'Δημιουργία κλειδιού API',
'settings.apiKeys.modal.name': 'Όνομα',
+4 -2
View File
@@ -127,7 +127,8 @@ const storage: TranslationStrings = {
// Category migration (copy → flip → delta sweep)
'storage.migrate.promptTitle': 'Μετακίνηση των υπαρχόντων αντικειμένων στο νέο backend;',
'storage.migrate.promptLine': '{category}: {objects} αντικείμενα ({size}) από {from} σε {to}',
'storage.migrate.promptLine': '{category}: {count} αντικείμενα ({size}) από {from} σε {to}',
'storage.migrate.promptLine.one': '{category}: {count} αντικείμενο ({size}) από {from} σε {to}',
'storage.migrate.promptLineUnknown':
'{category}: άγνωστο μέγεθος (δεν έχει γίνει ακόμα σάρωση χρήσης) από {from} σε {to}',
'storage.migrate.move': 'Μετακίνηση υπαρχόντων αντικειμένων',
@@ -137,7 +138,8 @@ const storage: TranslationStrings = {
'storage.migrate.doneFailures': '{failed} απέτυχαν — αυτά τα αντικείμενα δεν αντιγράφηκαν στο νέο backend',
'storage.migrate.failed': 'Η μετακίνηση απέτυχε: {error} — η κατηγορία δεν άλλαξε',
'storage.migrate.cancelled': 'Η μετακίνηση ακυρώθηκε — τίποτα δεν άλλαξε',
'storage.migrate.reclaimable': '{objects} αντικείμενα ({size}) παραμένουν στο {from} — ανάκτησέ τα χειροκίνητα',
'storage.migrate.reclaimable': '{count} αντικείμενα ({size}) παραμένουν στο {from}, ανάκτησέ τα χειροκίνητα',
'storage.migrate.reclaimable.one': '{count} αντικείμενο ({size}) παραμένει στο {from}, ανάκτησέ το χειροκίνητα',
'storage.migrate.cancel': 'Ακύρωση μετακίνησης',
'storage.migrate.promptCancel': 'Ακύρωση',
'storage.migrate.queued': 'Σε αναμονή: {categories}',
+2 -1
View File
@@ -100,7 +100,8 @@ const collab: TranslationStrings = {
'collab.polls.options': 'Opciók',
'collab.polls.delete': 'Törlés',
'collab.polls.closedSection': 'Lezárva',
'collab.chat.imageLimit': 'Üzenetenként legfeljebb {max} képet csatolhatsz',
'collab.chat.imageLimit': 'Üzenetenként legfeljebb {count} képet csatolhatsz',
'collab.chat.imageLimit.one': 'Üzenetenként csak {count} képet csatolhatsz',
'collab.chat.uploading': 'Feltöltés {percent}%',
'collab.chat.attachImages': 'Képek csatolása',
'collab.chat.attachedImage': 'Csatolt kép',
+2 -1
View File
@@ -101,7 +101,8 @@ const dashboard: TranslationStrings = {
'dashboard.useUnsplashPhoto': '{photographer} Unsplash-fotójának használata',
'dashboard.titleRequired': 'A cím megadása kötelező',
'dashboard.endDateError': 'A záró dátumnak a kezdő dátum után kell lennie',
'dashboard.tripTooLong': 'Egy utazás legfeljebb {days} napos lehet',
'dashboard.tripTooLong': 'Egy utazás legfeljebb {count} napos lehet',
'dashboard.tripTooLong.one': 'Egy utazás legfeljebb {count} napos lehet',
'dashboard.dateShiftTitle': 'Új kezdő dátum',
'dashboard.dateShiftIntro': 'Megváltoztattad az út kezdetét. Hogyan kövessék a terveid az új dátumokat?',
'dashboard.dateShiftKeepBookings': 'A foglalások maradjanak a saját dátumaikon',
+6 -2
View File
@@ -119,7 +119,8 @@ const dawarich: TranslationStrings = {
'dawarich.accept.confirm.journal': 'Bejegyzés hozzáadása',
'dawarich.accept.confirm.bucket_list': 'Pipáld ki',
'dawarich.accept.recorded': 'Rögzítve {from} és {to} között',
'dawarich.accept.duration': '{minutes} perc',
'dawarich.accept.duration': '{count} perc',
'dawarich.accept.duration.one': '{count} perc',
'dawarich.accept.name': 'Név',
'dawarich.accept.date': 'Dátum',
'dawarich.accept.from': 'Érkezés',
@@ -184,7 +185,10 @@ const dawarich: TranslationStrings = {
'dawarich.again': 'Ellenőrzés újra',
'dawarich.bucket.metersAway': '{meters} m-re',
'dawarich.bucket.kilometersAway': '{km} km-re',
'dawarich.bucket.rule': 'Egy kívánság {meters} méteren belül és {minutes} perc helyszíni idő után számít teljesítettnek.',
'dawarich.bucket.rule':
'Egy kívánság {meters} méteren belül és {count} perc helyszíni idő után számít teljesítettnek.',
'dawarich.bucket.rule.one':
'Egy kívánság {meters} méteren belül és {count} perc helyszíni idő után számít teljesítettnek.',
'dawarich.journey.dayStays.other': '{count} tartózkodás a Dawarichból',
'dawarich.journey.dayStays.one': '{count} tartózkodás a Dawarichból',
+11 -5
View File
@@ -56,7 +56,9 @@ const roadtrip: TranslationStrings = {
'roadtrip.window.conflict':
'Egy rögzített időpont ütközik az utazással és a szünetekkel. A kézi időpontok megmaradnak; az automatikus tervezés szünetel.',
'roadtrip.window.tooLong':
'Ezek az időpontok több mint {days} további napot igényelnének. Válassz hosszabb napi időszakot.',
'Ezek az időpontok több mint {count} további napot igényelnének. Válassz hosszabb napi időszakot.',
'roadtrip.window.tooLong.one':
'Ezek az időpontok több mint {count} további napot igényelnének. Válassz hosszabb napi időszakot.',
'roadtrip.mode.roadtrip': 'Autós út',
'roadtrip.mode.days': 'Napok',
'roadtrip.mode.label': 'Terv nézet',
@@ -103,8 +105,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.limit.minutes': 'perc',
'roadtrip.limit.off': 'ki',
'roadtrip.limit.blockNote': 'Egy blokk = {distance}',
'roadtrip.limit.echoOver': '{days} napból {over} a határ felett.',
'roadtrip.limit.echoNone': 'Mind a(z) {days} nap a határokon belül.',
'roadtrip.limit.echoOver': '{count} napból {over} a határ felett.',
'roadtrip.limit.echoOver.one': '{count} napból {over} a határ felett.',
'roadtrip.limit.echoNone': 'Mind a(z) {count} nap a határokon belül.',
'roadtrip.limit.echoNone.one': 'Az egyetlen nap a határokon belül.',
'roadtrip.limit.echoDry': '{count} szakasz elérhető megálló nélkül.',
'roadtrip.limit.echoDry.one': '{count} szakasz elérhető megálló nélkül.',
'roadtrip.limit.echoEmpty': 'Nyiss meg egy utat, hogy lásd a hatást.',
@@ -274,8 +278,10 @@ const roadtrip: TranslationStrings = {
'roadtrip.stop.computed': 'A menetidőből számolva',
'roadtrip.stop.endIsLeave': 'Az autós úton ekkor indul tovább az út.',
'roadtrip.via.hint': 'Húzza az útvonal átalakításához, jobb gombbal eltávolítható',
'roadtrip.warn.late': 'Érkezés {minutes} perccel a megadott idő után',
'roadtrip.warn.missedLeave': 'Érkezés {minutes} perccel a megadott indulási idő után',
'roadtrip.warn.late': 'Érkezés {count} perccel a megadott idő után',
'roadtrip.warn.late.one': 'Érkezés {count} perccel a megadott idő után',
'roadtrip.warn.missedLeave': 'Érkezés {count} perccel a megadott indulási idő után',
'roadtrip.warn.missedLeave.one': 'Érkezés {count} perccel a megadott indulási idő után',
'roadtrip.warn.overnight': 'Következő nap',
'roadtrip.spill.title': 'A(z) {number}. napról',
'roadtrip.spill.departs': 'indulás {time}',
+4 -1
View File
@@ -631,7 +631,10 @@ const settings: TranslationStrings = {
'settings.apiKeys.endpoint': 'Végpont',
'settings.apiKeys.neverUsed': 'még nem használt',
'settings.apiKeys.loadFailed': 'Nem sikerült betölteni a kulcsaidat. Töltsd újra az oldalt, és próbáld újra.',
'settings.apiKeys.limitReached': '{max} kulcsod van, ennél több egy fiókhoz nem tartozhat. Törölj egy már nem használt kulcsot, hogy újat hozhass létre.',
'settings.apiKeys.limitReached':
'{count} kulcsod van, ennél több egy fiókhoz nem tartozhat. Törölj egy már nem használt kulcsot, hogy újat hozhass létre.',
'settings.apiKeys.limitReached.one':
'{count} kulcsod van, ennél több egy fiókhoz nem tartozhat. Töröld, ha már nem használod, hogy újat hozhass létre.',
'settings.apiKeys.copyFailed': 'A másolás nem sikerült. Jelöld ki a szöveget, és másold ki kézzel.',
'settings.apiKeys.modal.createTitle': 'API-kulcs létrehozása',
'settings.apiKeys.modal.name': 'Név',
+4 -2
View File
@@ -125,7 +125,8 @@ const storage: TranslationStrings = {
// Category migration (copy → flip → delta sweep)
'storage.migrate.promptTitle': 'Áthelyezed a meglévő objektumokat az új backendre?',
'storage.migrate.promptLine': '{category}: {objects} objektum ({size}) innen: {from} ide: {to}',
'storage.migrate.promptLine': '{category}: {count} objektum ({size}) innen: {from} ide: {to}',
'storage.migrate.promptLine.one': '{category}: {count} objektum ({size}) innen: {from} ide: {to}',
'storage.migrate.promptLineUnknown':
'{category}: ismeretlen méret (a használat még nincs kiszámítva) innen: {from} ide: {to}',
'storage.migrate.move': 'Meglévő objektumok áthelyezése',
@@ -135,7 +136,8 @@ const storage: TranslationStrings = {
'storage.migrate.doneFailures': '{failed} sikertelen — ezek az objektumok nem lettek átmásolva az új backendre',
'storage.migrate.failed': 'Az áthelyezés sikertelen: {error} — a kategória nem lett átváltva',
'storage.migrate.cancelled': 'Áthelyezés megszakítva — semmi sem lett átváltva',
'storage.migrate.reclaimable': '{objects} objektum ({size}) marad a(z) {from} helyen — kézzel szabadítsd fel',
'storage.migrate.reclaimable': '{count} objektum ({size}) marad a(z) {from} helyen, kézzel szabadítsd fel',
'storage.migrate.reclaimable.one': '{count} objektum ({size}) marad a(z) {from} helyen, kézzel szabadítsd fel',
'storage.migrate.cancel': 'Áthelyezés megszakítása',
'storage.migrate.promptCancel': 'Mégse',
'storage.migrate.queued': 'Várólistán: {categories}',
+7 -2
View File
@@ -35,11 +35,13 @@ describe('i18n parity', () => {
describe('i18n count strings', () => {
const ROOT = new URL('../../scripts/fixtures/i18n/', import.meta.url);
it('refuses an en string with {count} or {n} outside a plural group', () => {
it('refuses an en string with {count}, {n} or a quantity param outside a plural group', () => {
const { ungrouped, stale } = checkCountStrings(['a.ts'], [], ROOT);
// a.stay carries its quantity as {days}, which no plural form is picked by.
expect(ungrouped).toEqual([
{ file: 'a.ts', key: 'a.lone' },
{ file: 'a.ts', key: 'a.day' },
{ file: 'a.ts', key: 'a.stay' },
]);
expect(stale).toEqual([]);
});
@@ -50,7 +52,10 @@ describe('i18n count strings', () => {
{ key: 'a.count', because: 'a group now' },
];
expect(checkCountStrings(['a.ts'], allowed, ROOT)).toEqual({
ungrouped: [{ file: 'a.ts', key: 'a.lone' }],
ungrouped: [
{ file: 'a.ts', key: 'a.lone' },
{ file: 'a.ts', key: 'a.stay' },
],
stale: ['a.count'],
});
});
+1 -1
View File
@@ -96,7 +96,7 @@ const collab: TranslationStrings = {
'collab.polls.options': 'Pilihan',
'collab.polls.delete': 'Hapus',
'collab.polls.closedSection': 'Ditutup',
'collab.chat.imageLimit': 'Anda dapat melampirkan hingga {max} gambar per pesan',
'collab.chat.imageLimit': 'Anda dapat melampirkan hingga {count} gambar per pesan',
'collab.chat.uploading': 'Mengunggah {percent}%',
'collab.chat.attachImages': 'Lampirkan gambar',
'collab.chat.attachedImage': 'Gambar terlampir',
+1 -1
View File
@@ -92,7 +92,7 @@ const dashboard: TranslationStrings = {
'dashboard.useUnsplashPhoto': 'Gunakan foto Unsplash oleh {photographer}',
'dashboard.titleRequired': 'Judul wajib diisi',
'dashboard.endDateError': 'Tanggal selesai harus setelah tanggal mulai',
'dashboard.tripTooLong': 'Perjalanan paling lama {days} hari',
'dashboard.tripTooLong': 'Perjalanan paling lama {count} hari',
'dashboard.dateShiftTitle': 'Tanggal mulai baru',
'dashboard.dateShiftIntro':
'Kamu mengubah tanggal mulai perjalanan ini. Bagaimana rencanamu harus mengikuti tanggal baru?',
+3 -2
View File
@@ -114,7 +114,7 @@ const dawarich: TranslationStrings = {
'dawarich.accept.confirm.journal': 'Tambah entri',
'dawarich.accept.confirm.bucket_list': 'Coret',
'dawarich.accept.recorded': 'Terekam {from} sampai {to}',
'dawarich.accept.duration': '{minutes} mnt',
'dawarich.accept.duration': '{count} mnt',
'dawarich.accept.name': 'Nama',
'dawarich.accept.date': 'Tanggal',
'dawarich.accept.from': 'Tiba',
@@ -172,7 +172,8 @@ const dawarich: TranslationStrings = {
'dawarich.again': 'Periksa lagi',
'dawarich.bucket.metersAway': '{meters} m dari sana',
'dawarich.bucket.kilometersAway': '{km} km dari sana',
'dawarich.bucket.rule': 'Sebuah keinginan dianggap tercapai dalam radius {meters} m dan setelah {minutes} menit di lokasi.',
'dawarich.bucket.rule':
'Sebuah keinginan dianggap tercapai dalam radius {meters} m dan setelah {count} menit di lokasi.',
'dawarich.journey.dayStays.other': '{count} persinggahan dari Dawarich',
};
+5 -5
View File
@@ -56,7 +56,7 @@ const roadtrip: TranslationStrings = {
'roadtrip.window.conflict':
'Waktu tetap bertentangan dengan perjalanan dan jeda. Waktu manual tidak berubah; perencanaan otomatis dijeda.',
'roadtrip.window.tooLong':
'Waktu ini memerlukan lebih dari {days} hari tambahan. Pilih rentang waktu harian yang lebih panjang.',
'Waktu ini memerlukan lebih dari {count} hari tambahan. Pilih rentang waktu harian yang lebih panjang.',
'roadtrip.mode.roadtrip': 'Perjalanan darat',
'roadtrip.mode.days': 'Hari',
'roadtrip.mode.label': 'Tampilan rencana',
@@ -102,8 +102,8 @@ const roadtrip: TranslationStrings = {
'roadtrip.limit.minutes': 'mnt',
'roadtrip.limit.off': 'mati',
'roadtrip.limit.blockNote': 'Satu blok = {distance}',
'roadtrip.limit.echoOver': '{over} dari {days} hari melebihi batasmu.',
'roadtrip.limit.echoNone': 'Semua {days} hari dalam batasmu.',
'roadtrip.limit.echoOver': '{over} dari {count} hari melebihi batasmu.',
'roadtrip.limit.echoNone': 'Semua {count} hari dalam batasmu.',
'roadtrip.limit.echoDry': '{count} ruas tanpa perhentian terjangkau.',
'roadtrip.limit.echoEmpty': 'Buka sebuah road trip untuk melihat efeknya.',
'roadtrip.limit.specToggle': 'Hitung dari data mobil',
@@ -262,8 +262,8 @@ const roadtrip: TranslationStrings = {
'roadtrip.stop.computed': 'Dihitung dari waktu berkendara',
'roadtrip.stop.endIsLeave': 'Dalam perjalanan darat, keberangkatan pada waktu ini.',
'roadtrip.via.hint': 'Seret untuk mengubah rute, klik kanan untuk menghapus',
'roadtrip.warn.late': 'Tiba {minutes} menit setelah waktu yang ditetapkan',
'roadtrip.warn.missedLeave': 'Tiba {minutes} menit setelah waktu berangkat yang ditetapkan',
'roadtrip.warn.late': 'Tiba {count} menit setelah waktu yang ditetapkan',
'roadtrip.warn.missedLeave': 'Tiba {count} menit setelah waktu berangkat yang ditetapkan',
'roadtrip.warn.overnight': 'Hari berikutnya',
'roadtrip.spill.title': 'Dari hari {number}',
'roadtrip.spill.departs': 'berangkat {time}',
+2 -1
View File
@@ -621,7 +621,8 @@ const settings: TranslationStrings = {
'settings.apiKeys.endpoint': 'Endpoint',
'settings.apiKeys.neverUsed': 'belum pernah dipakai',
'settings.apiKeys.loadFailed': 'Kunci tidak dapat dimuat. Muat ulang halaman untuk mencoba lagi.',
'settings.apiKeys.limitReached': 'Kamu punya {max} kunci, batas maksimum per akun. Hapus kunci yang tidak lagi dipakai untuk membuat yang baru.',
'settings.apiKeys.limitReached':
'Kamu punya {count} kunci, batas maksimum per akun. Hapus kunci yang tidak lagi dipakai untuk membuat yang baru.',
'settings.apiKeys.copyFailed': 'Gagal menyalin. Pilih teksnya dan salin secara manual.',
'settings.apiKeys.modal.createTitle': 'Buat kunci API',
'settings.apiKeys.modal.name': 'Nama',
+2 -2
View File
@@ -122,7 +122,7 @@ const storage: TranslationStrings = {
// Category migration (copy → flip → delta sweep)
'storage.migrate.promptTitle': 'Pindahkan objek yang ada ke backend baru?',
'storage.migrate.promptLine': '{category}: {objects} objek ({size}) dari {from} ke {to}',
'storage.migrate.promptLine': '{category}: {count} objek ({size}) dari {from} ke {to}',
'storage.migrate.promptLineUnknown':
'{category}: ukuran tidak diketahui (penggunaan belum dipindai) dari {from} ke {to}',
'storage.migrate.move': 'Pindahkan objek yang ada',
@@ -132,7 +132,7 @@ const storage: TranslationStrings = {
'storage.migrate.doneFailures': '{failed} gagal — objek tersebut tidak disalin ke backend baru',
'storage.migrate.failed': 'Pemindahan gagal: {error} — kategori tidak diubah',
'storage.migrate.cancelled': 'Pemindahan dibatalkan — tidak ada yang diubah',
'storage.migrate.reclaimable': '{objects} objek ({size}) masih tersisa di {from} — ambil kembali secara manual',
'storage.migrate.reclaimable': '{count} objek ({size}) masih tersisa di {from}, ambil kembali secara manual',
'storage.migrate.cancel': 'Batalkan pemindahan',
'storage.migrate.promptCancel': 'Batal',
'storage.migrate.queued': 'Dalam antrean: {categories}',
+2 -1
View File
@@ -100,7 +100,8 @@ const collab: TranslationStrings = {
'collab.polls.options': 'Opzioni',
'collab.polls.delete': 'Elimina',
'collab.polls.closedSection': 'Chiusi',
'collab.chat.imageLimit': 'Puoi allegare fino a {max} immagini per messaggio',
'collab.chat.imageLimit': 'Puoi allegare fino a {count} immagini per messaggio',
'collab.chat.imageLimit.one': 'Puoi allegare solo {count} immagine per messaggio',
'collab.chat.uploading': 'Caricamento {percent}%',
'collab.chat.attachImages': 'Allega immagini',
'collab.chat.attachedImage': 'Immagine allegata',

Some files were not shown because too many files have changed in this diff Show More