fix(i18n): every count-bearing string is a plural group

The plural parity check only knows the groups en already declares, so a
"{count} ..." string added as a plain key kept one form in every
language: Polish read "Usuń 1 spakowanych", Czech "Odstranit 1
hotových" and Greek "1 λεπτά". 44 such en keys become plural groups with their
forms in all 27 locales: the collection, packing, day plan and offline
banner counts, the relative times, the audit log, the tour and transit
minutes, the share and photo hints. Labels like "Notes: {count}" get
their forms too, so a language can still inflect them later. The
dashboard's hand-rolled activeOne/activeMany pair, unused, becomes one
group, dashboard.subtitle.active.

i18n-parity now refuses an en string with {count} or {n} that is no
plural group. NOT_PLURAL lists the eleven where the number is never a
quantity (a day number, a step, a waypoint, the equator multiplier),
each with its reason, and an entry that stops matching fails as well.

Callers that worked around the engine:
- Copy to trip switched between two keys on placeIds.length > 1;
  collections.copyN now carries the one-place wording as its `one` form,
  where the language's `one` is the number 1 alone.
- The plugin dependency toasts said "addon(s)" and "plugin(s)"; they are
  groups now and the panels pass the list length as the count.
- The dismissed suggestions count went in as a string and the comp days
  as a formatted string, so no form was picked; both pass numbers.
This commit is contained in:
Maurice
2026-10-08 11:58:49 +02:00
parent f00c354d0d
commit bdaddc44ac
383 changed files with 1743 additions and 170 deletions
+1 -1
View File
@@ -64,7 +64,7 @@ The offline core is flagship work surrounded by a periphery that ignores it. New
## Big-picture pieces
- **Maps** (`src/components/Map/`): two interchangeable renderers — Leaflet and Mapbox/MapLibre GL — chosen at runtime by `MapViewAuto.tsx`. Keep both in sync when changing map features, and keep `mapbox-gl` and `maplibre-gl` in separate chunks (`check:gl-split`). The raster prefetcher fetches tiles `no-cors` so custom tile providers without CORS headers keep working; the vector prefetcher deliberately uses `cors` because it has to read the responses — don't "align" one with the other.
- **i18n** (`src/i18n/TranslationContext.tsx`): `en` is bundled; every other locale is a dynamic `import('@trek/shared/i18n/<locale>')` so Vite emits one chunk per locale. Strings live in `shared/`, never here. `t()` returns an unknown key unchanged, so `lint:i18n-keys` (`scripts/i18n-keys.mjs`) resolves every literal key in `t()`, `tHtml()`, `tr()`, `<TransHtml html>`, `translateApiError` and `*Key` props against `shared/src/i18n/en`; a template key passes when its fixed parts match an en key, or through `DYNAMIC_ALLOWED` with a reason.
- **i18n** (`src/i18n/TranslationContext.tsx`): `en` is bundled; every other locale is a dynamic `import('@trek/shared/i18n/<locale>')` so Vite emits one chunk per locale. Strings live in `shared/`, never here. `t()` returns an unknown key unchanged, so `lint:i18n-keys` (`scripts/i18n-keys.mjs`) resolves every literal key in `t()`, `tHtml()`, `tr()`, `<TransHtml html>`, `translateApiError` and `*Key` props against `shared/src/i18n/en`; a template key passes when its fixed parts match an en key, or through `DYNAMIC_ALLOWED` with a reason. Pass counts as numbers, and let a plural group pick the wording instead of switching keys on the count.
- **Mobile shell** (`src/mobile/`): below the phone breakpoint (`useIsPhone`) `App.tsx` wraps routes in `MobileShell` and the `M*` screens under `mobile/screens/` take over. A UI change to a domain with an `M*` twin usually needs both — and the shared-hook rule below.
- **Plugins UI** (`src/components/Plugins/`): third-party surfaces render only inside the sandboxed `PluginFrame` iframe (postMessage bridge, live theme-token sync). Host-rendered contribution points (widgets, schedule rows, map layers, planner columns/actions) render server-normalized primitives only — plugin markup never runs inline. Authoring is covered by the `trek-plugin-dev` skill.
- **Managed installs** (`src/managed/index.tsx`): the attachment point for screens that only exist on a centrally administered install. Empty here by design — an operator replaces it at build time. Don't put features there.
@@ -924,7 +924,7 @@ describe('AdminPluginsPanel — enabling a plugin', () => {
fireEvent.click(rowToggle('Gotify'))
expect(await screen.findByText('Plugin activated')).toBeInTheDocument()
expect(await screen.findByText(/enabled required plugin\(s\) first: core/i)).toBeInTheDocument()
expect(await screen.findByText(/enabled the required plugin first: core/i)).toBeInTheDocument()
})
it('FE-COMP-PLUGINS-PANEL-023: a disabled required addon is reported as a toast, not a dialog', async () => {
@@ -936,7 +936,7 @@ describe('AdminPluginsPanel — enabling a plugin', () => {
fireEvent.click(rowToggle('Gotify'))
expect(await screen.findByText(/enable the required addon\(s\) first: journey/i)).toBeInTheDocument()
expect(await screen.findByText(/enable the required addon first: journey/i)).toBeInTheDocument()
})
it('FE-COMP-PLUGINS-PANEL-024: any other activation failure surfaces the server message', async () => {
@@ -984,7 +984,7 @@ describe('AdminPluginsPanel — enabling a plugin', () => {
expect(await screen.findByText('Downloaded trek-core')).toBeInTheDocument()
expect(installed).toEqual({ id: 'trek-core', constraint: '^1.0.0', withDependencies: true })
expect(await screen.findByText(/enable the required addon\(s\) first: journey/i)).toBeInTheDocument()
expect(await screen.findByText(/enable the required addon first: journey/i)).toBeInTheDocument()
await waitFor(() => expect(screen.queryByText('Missing dependencies')).not.toBeInTheDocument())
})
@@ -647,7 +647,7 @@ export default function AdminPluginsPanel() {
if (e?.response?.status === 409 && d?.code === 'CONSENT_REQUIRED') {
setConsentQueue(qq => [...qq, { plugin: p, version: latest[p.id] ?? p.version ?? '', newPermissions: d.newPermissions ?? [], newEgress: d.newEgress ?? [] }])
} else if (e?.response?.status === 409 && d?.code === 'ADDON_DISABLED') {
toast.error(t('admin.plugins.dep.addonDisabledToast', { addons: (d.addons ?? []).join(', ') }))
toast.error(t('admin.plugins.dep.addonDisabledToast', { count: (d.addons ?? []).length, addons: (d.addons ?? []).join(', ') }))
} else if (e?.response?.status === 409 && d?.code === 'DEPENDENCY_MISSING') {
setDepResolve({ plugin: p, missing: d.missing ?? [], versionMismatch: d.versionMismatch ?? [] })
} else {
@@ -661,7 +661,7 @@ export default function AdminPluginsPanel() {
return adminApi.pluginActivate(p.id)
.then(() => {
toast.success(t('admin.plugins.activated'))
if (cascaded.length) toast.success(t('admin.plugins.dep.autoEnabled', { plugins: cascaded.join(', ') }))
if (cascaded.length) toast.success(t('admin.plugins.dep.autoEnabled', { count: cascaded.length, plugins: cascaded.join(', ') }))
setDepResolve(null)
})
.catch((e: ActivateErr) => onActivateError(p, e))
@@ -686,7 +686,7 @@ export default function AdminPluginsPanel() {
adminApi.pluginInstall(depId, { constraint, withDependencies: true })
.then((r: { installed?: string[]; requiredAddons?: string[]; trekRangeBypassed?: TrekRangeBypass | null }) => {
toast.success(t('admin.plugins.dep.downloaded', { id: depId }))
if (r?.requiredAddons?.length) toast.error(t('admin.plugins.dep.addonDisabledToast', { addons: r.requiredAddons.join(', ') }))
if (r?.requiredAddons?.length) toast.error(t('admin.plugins.dep.addonDisabledToast', { count: r.requiredAddons.length, addons: r.requiredAddons.join(', ') }))
bypass.notice(depId, r?.trekRangeBypassed)
return attemptActivate(parent)
})
@@ -104,7 +104,7 @@ export default function CopyToTripModal({ isOpen, onClose, placeIds, onCopy, t }
tint={NEUTRAL_TINT}
labelId={labelId}
onClose={onClose}
title={placeIds.length > 1 ? t('collections.copyN', { count: placeIds.length }) : t('collections.copyToTripTitle')}
title={t('collections.copyN', { count: placeIds.length })}
/>
)}
footer={(
@@ -366,7 +366,7 @@ export function JourneySettingsDialog({ journey, onClose, onSaved, onOpenInvite,
<span className="min-w-0 flex-1">
<span className="block text-content" style={fs(13, 'body')}>{t('journey.suggestions.restore')}</span>
<span className="block text-content-faint" style={fs(11.5)}>
{t('journey.suggestions.restoreCount', { count: String(journey.dismissed_count) })}
{t('journey.suggestions.restoreCount', { count: journey.dismissed_count ?? 0 })}
</span>
</span>
</button>
+1 -1
View File
@@ -203,7 +203,7 @@ function StatCard({ stat: s, isMe, canEdit, selectedYear, isShiftedYear, onSave,
{compUsed > 0 && (
<div className="flex items-center gap-1.5 px-2 py-1 rounded-md" style={{ background: 'var(--vg-surf2)', border: '1px solid var(--vg-line)' }}>
<Clock size={9} style={{ color: 'var(--vg-ink3)' }} />
<span className="text-[10px]" style={{ color: 'var(--vg-ink2)' }}>{t('vacay.compUsedCount', { count: fmtDays(compUsed) })}</span>
<span className="text-[10px]" style={{ color: 'var(--vg-ink2)' }}>{t('vacay.compUsedCount', { count: Number(fmtDays(compUsed)) })}</span>
</div>
)}
</div>
@@ -628,7 +628,7 @@ export default function MAdminPluginsPanel() {
if (e?.response?.status === 409 && d?.code === 'CONSENT_REQUIRED') {
setConsentQueue(qq => [...qq, { plugin: p, version: latest[p.id] ?? p.version ?? '', newPermissions: d.newPermissions ?? [], newEgress: d.newEgress ?? [] }])
} else if (e?.response?.status === 409 && d?.code === 'ADDON_DISABLED') {
toast.error(t('admin.plugins.dep.addonDisabledToast', { addons: (d.addons ?? []).join(', ') }))
toast.error(t('admin.plugins.dep.addonDisabledToast', { count: (d.addons ?? []).length, addons: (d.addons ?? []).join(', ') }))
} else if (e?.response?.status === 409 && d?.code === 'DEPENDENCY_MISSING') {
setDepResolve({ plugin: p, missing: d.missing ?? [], versionMismatch: d.versionMismatch ?? [] })
} else {
@@ -642,7 +642,7 @@ export default function MAdminPluginsPanel() {
return adminApi.pluginActivate(p.id)
.then(() => {
toast.success(t('admin.plugins.activated'))
if (cascaded.length) toast.success(t('admin.plugins.dep.autoEnabled', { plugins: cascaded.join(', ') }))
if (cascaded.length) toast.success(t('admin.plugins.dep.autoEnabled', { count: cascaded.length, plugins: cascaded.join(', ') }))
setDepResolve(null)
})
.catch((e: ActivateErr) => onActivateError(p, e))
@@ -667,7 +667,7 @@ export default function MAdminPluginsPanel() {
adminApi.pluginInstall(depId, { constraint, withDependencies: true })
.then((r: { installed?: string[]; requiredAddons?: string[]; trekRangeBypassed?: TrekRangeBypass | null }) => {
toast.success(t('admin.plugins.dep.downloaded', { id: depId }))
if (r?.requiredAddons?.length) toast.error(t('admin.plugins.dep.addonDisabledToast', { addons: r.requiredAddons.join(', ') }))
if (r?.requiredAddons?.length) toast.error(t('admin.plugins.dep.addonDisabledToast', { count: r.requiredAddons.length, addons: r.requiredAddons.join(', ') }))
bypass.notice(depId, r?.trekRangeBypassed)
return attemptActivate(parent)
})
@@ -78,7 +78,7 @@ export default function MCollTripPickerSheet({ open, count, onCopy, onClose, t }
}
}
const title = count > 1 ? t('collections.copyN', { count }) : t('collections.copyToTripTitle')
const title = t('collections.copyN', { count })
return (
<MSheet open={open} onClose={onClose} material="opaque" ariaLabel={title}>
@@ -321,7 +321,7 @@ export default function MJourneySettingsSheet({
<div className="min-w-0 flex-1">
<div className="text-[0.8125rem] font-bold">{t('journey.suggestions.restore')}</div>
<div className="font-geist text-[0.625rem] text-m-muted">
{t('journey.suggestions.restoreCount', { count: String(journey.dismissed_count) })}
{t('journey.suggestions.restoreCount', { count: journey.dismissed_count ?? 0 })}
</div>
</div>
</button>
@@ -1170,7 +1170,7 @@ describe('MAdminPluginsPanel — enabling a plugin', () => {
await screen.findByText('Gotify');
fireEvent.click(screen.getAllByRole('switch', { name: 'Enable plugin' })[0]);
await waitFor(() => expect(toastMessages()).toContain('Enabled required plugin(s) first: Zzz Base'));
await waitFor(() => expect(toastMessages()).toContain('Enabled the required plugin first: Zzz Base'));
expect(toastMessages()).toContain('Plugin activated');
});
@@ -1193,7 +1193,7 @@ describe('MAdminPluginsPanel — enabling a plugin', () => {
render(<MAdminPluginsPanel />);
fireEvent.click(await screen.findByRole('switch', { name: 'Enable plugin' }));
await waitFor(() => expect(toastMessages()).toContain('Enable the required addon(s) first: budget, vacay'));
await waitFor(() => expect(toastMessages()).toContain('Enable the required addons first: budget, vacay'));
});
it('FE-MOB-PLUGP-066: any other activation failure surfaces the server message', async () => {
@@ -1239,7 +1239,7 @@ describe('MAdminPluginsPanel — enabling a plugin', () => {
await waitFor(() => expect(installBody).toEqual({ id: 'trek-base', constraint: '^1.0.0', withDependencies: true }));
await waitFor(() => expect(toastMessages()).toContain('Downloaded trek-base'));
// The dependency install revealed a disabled addon on top.
expect(toastMessages()).toContain('Enable the required addon(s) first: budget');
expect(toastMessages()).toContain('Enable the required addon first: budget');
await waitFor(() => expect(activateCalls).toBe(2));
});
+3 -2
View File
@@ -48,8 +48,9 @@ The parity rule governs **existing** routes; it is not a license to mint new deb
- **`<locale>/`** — one folder per language, one file per UI domain plus an `index.ts` barrel; each file exports a flat map of dot-namespaced keys typed as `TranslationStrings`. The runtime `t(key)` only resolves these top-level keys.
- **`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.** Parity only checks that the key exists, so copying the `en` string passes CI while shipping English to those users. 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.
- **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}`.
- **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 and plural forms only.
- **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.
- **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 and count strings.
- **en holds every key the client names.** `npm run lint:i18n-keys` in `client/` resolves the keys `src/` passes to `t()` and its helpers against `en/`, read through `scripts/i18n-catalog.mjs`. That reader takes the tables as text: 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. Its spec uses the fixtures in `scripts/fixtures/`.
## Sanitization (`src/sanitize/sanitize.ts`)
+70 -23
View File
@@ -19,20 +19,21 @@
// out each category whole counts reach in its language, and may not
// carry one its language never selects. The rules come from
// `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).
//
// Limitations: we only parse *top-level* string keys (those declared as the
// first column of the file, matching the regex below). Nested objects, function
// bodies, and inline comments are ignored. This matches how `t(key)` calls
// resolve at runtime in TranslationContext.
import { readdirSync, readFileSync, statSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { getIntlLanguage } from '../src/i18n/languages.ts';
import { allPluralCategories, integerPluralCategories, pluralFormOf, pluralGroups } from '../src/i18n/plural.ts';
const here = dirname(fileURLToPath(import.meta.url));
const i18nRoot = join(here, '..', 'src', 'i18n');
import { I18N_ROOT, listDomainFiles, listLocales, readCatalog } from './i18n-catalog.mjs';
// Match a top-level translation key declaration: leading whitespace, then a
// quoted key (must start with a lowercase letter), then a colon. This is the
@@ -41,21 +42,8 @@ const i18nRoot = join(here, '..', 'src', 'i18n');
// never compared, and two locales lacked dozens of them unnoticed.
const TOP_LEVEL_KEY_RE = /^\s*'([a-z][a-zA-Z0-9.\-_:]*)'\s*:/gm;
function listLocales() {
return readdirSync(i18nRoot)
.filter((name) => statSync(join(i18nRoot, name)).isDirectory())
// externalNotifications is a barrel module, not a locale.
.filter((name) => name !== 'externalNotifications');
}
function listDomainFiles(locale) {
return readdirSync(join(i18nRoot, locale))
.filter((f) => f.endsWith('.ts') && f !== 'index.ts')
.sort();
}
function extractKeys(locale, file) {
const content = readFileSync(join(i18nRoot, locale, file), 'utf8');
const content = readFileSync(join(I18N_ROOT, locale, file), 'utf8');
const keys = new Set();
for (const match of content.matchAll(TOP_LEVEL_KEY_RE)) {
keys.add(match[1]);
@@ -84,6 +72,49 @@ function checkPluralForms(locale, keys, groups) {
return { missing, invalid };
}
/**
* en strings that carry {count} or {n} without being a quantity of
* something, so no word in any language agrees with the number. Everything
* else with a count is a plural group. An entry that stops matching an
* ungrouped count string fails the check, so the list cannot go stale.
*/
export const NOT_PLURAL = [
{ key: 'collab.polls.optionPlaceholder', because: 'the position of a poll option ("Option 3")' },
{ key: 'collections.importOnDay', because: 'the number of the day a place is planned on' },
{ key: 'dashboard.atlas.aroundEquator', because: 'a decimal multiplier ("≈ 1.37×"), not a count of things' },
{ key: 'dayplan.dayN', because: 'the number of a day in the trip' },
{ key: 'planner.dayN', because: 'the number of a day in the trip' },
{ key: 'help.center.step', because: 'the position of a step in a guide' },
{ key: 'help.center.stepOf', because: 'the position of a step in a guide' },
{ key: 'help.center.imageAlt', because: 'the position of a step in a guide' },
{ 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' },
];
const COUNT_PARAM_RE = /\{(?:count|n)\}/;
/**
* en strings carrying a count 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));
const ungrouped = [];
const seen = new Set();
for (const file of enFiles) {
const entries = readCatalog('en', file, root);
const groups = pluralGroups(entries.map((e) => e.key));
for (const { key, value } of entries) {
if (!COUNT_PARAM_RE.test(value) || groups.has(key) || pluralFormOf(key, groups)) continue;
if (key.endsWith('.other') && groups.has(key.slice(0, -'.other'.length))) continue;
if (allowed.has(key)) seen.add(key);
else ungrouped.push({ file, key });
}
}
return { ungrouped, stale: [...allowed].filter((key) => !seen.has(key)) };
}
function diffSets(reference, candidate) {
const missing = [];
const extra = [];
@@ -141,6 +172,7 @@ function checkParity() {
}
}
report.countDrift = checkCountStrings(enFiles);
return report;
}
@@ -181,24 +213,39 @@ function formatReport(report) {
}
}
if (report.countDrift) {
const { ungrouped, stale } = report.countDrift;
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');
for (const { file, key } of ungrouped) {
lines.push(` en/${file}: ${key} (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`);
}
}
return lines.join('\n');
}
// Export a structured API for vitest. The CLI entry point only runs when
// executed directly (`node scripts/i18n-parity.mjs`), so importing this file
// from a spec does not produce side effects.
export { checkParity, formatReport };
export { checkCountStrings, checkParity, formatReport };
const isCli = process.argv[1] && process.argv[1].endsWith('i18n-parity.mjs');
if (isCli) {
const strict = process.argv.includes('--strict');
const filesOnly = process.argv.includes('--files-only');
const report = checkParity();
process.stdout.write(formatReport(filesOnly ? { ...report, keyDrift: [], pluralDrift: [] } : report) + '\n');
const shown = filesOnly ? { ...report, keyDrift: [], pluralDrift: [], countDrift: null } : report;
process.stdout.write(formatReport(shown) + '\n');
if (strict) {
const hasFileDrift = report.fileDrift.length > 0;
const hasKeyDrift = filesOnly ? false : report.keyDrift.length > 0 || report.pluralDrift.length > 0;
if (hasFileDrift || hasKeyDrift) process.exit(1);
const hasCountDrift = !filesOnly && (report.countDrift.ungrouped.length > 0 || report.countDrift.stale.length > 0);
if (hasFileDrift || hasKeyDrift || hasCountDrift) process.exit(1);
}
}
+16 -1
View File
@@ -615,7 +615,17 @@ const admin: TranslationStrings = {
'admin.plugins.cap.requiresAddon': 'يتطلب {addon}',
'admin.plugins.cap.dependsOn': 'يحتاج إلى {id} {version}',
'admin.plugins.dep.addonDisabledToast': 'فعّل الإضافات المطلوبة أولاً: {addons}',
'admin.plugins.dep.addonDisabledToast.zero': 'فعّل الإضافات المطلوبة أولاً: {addons}',
'admin.plugins.dep.addonDisabledToast.one': 'فعّل الإضافة المطلوبة أولاً: {addons}',
'admin.plugins.dep.addonDisabledToast.two': 'فعّل الإضافتين المطلوبتين أولاً: {addons}',
'admin.plugins.dep.addonDisabledToast.few': 'فعّل الإضافات المطلوبة أولاً: {addons}',
'admin.plugins.dep.addonDisabledToast.many': 'فعّل الإضافات المطلوبة أولاً: {addons}',
'admin.plugins.dep.autoEnabled': 'تم تفعيل الإضافات المطلوبة أولاً: {plugins}',
'admin.plugins.dep.autoEnabled.zero': 'تم تفعيل الإضافات المطلوبة أولاً: {plugins}',
'admin.plugins.dep.autoEnabled.one': 'تم تفعيل الإضافة المطلوبة أولاً: {plugins}',
'admin.plugins.dep.autoEnabled.two': 'تم تفعيل الإضافتين المطلوبتين أولاً: {plugins}',
'admin.plugins.dep.autoEnabled.few': 'تم تفعيل الإضافات المطلوبة أولاً: {plugins}',
'admin.plugins.dep.autoEnabled.many': 'تم تفعيل الإضافات المطلوبة أولاً: {plugins}',
'admin.plugins.dep.downloaded': 'تم تنزيل {id}',
'admin.plugins.dep.resolveTitle': 'تبعيات مفقودة',
'admin.plugins.dep.resolveBody': 'يحتاج “{name}” إلى تثبيت هذه الإضافات قبل تفعيله.',
@@ -738,7 +748,12 @@ const admin: TranslationStrings = {
'admin.audit.empty': 'لا توجد سجلات تدقيق بعد.',
'admin.audit.refresh': 'تحديث',
'admin.audit.loadMore': 'تحميل المزيد',
'admin.audit.showing': 'تم تحميل {count} · الإجمالي {total}',
'admin.audit.showing': 'تم تحميل {count} سجل · الإجمالي {total}',
'admin.audit.showing.zero': 'لم يُحمَّل أي سجل · الإجمالي {total}',
'admin.audit.showing.one': 'تم تحميل سجل واحد · الإجمالي {total}',
'admin.audit.showing.two': 'تم تحميل سجلين · الإجمالي {total}',
'admin.audit.showing.few': 'تم تحميل {count} سجلات · الإجمالي {total}',
'admin.audit.showing.many': 'تم تحميل {count} سجلًا · الإجمالي {total}',
'admin.audit.col.time': 'الوقت',
'admin.audit.col.user': 'المستخدم',
'admin.audit.col.action': 'الإجراء',
+17 -2
View File
@@ -44,8 +44,18 @@ const collab: TranslationStrings = {
'collab.chat.reply': 'رد',
'collab.chat.loadMore': 'تحميل الرسائل الأقدم',
'collab.chat.justNow': 'الآن',
'collab.chat.minutesAgo': 'منذ {n} د',
'collab.chat.hoursAgo': 'منذ {n} س',
'collab.chat.minutesAgo': 'منذ {n} دقيقة',
'collab.chat.minutesAgo.zero': 'منذ {n} دقيقة',
'collab.chat.minutesAgo.one': 'منذ دقيقة واحدة',
'collab.chat.minutesAgo.two': 'منذ دقيقتين',
'collab.chat.minutesAgo.few': 'منذ {n} دقائق',
'collab.chat.minutesAgo.many': 'منذ {n} دقيقة',
'collab.chat.hoursAgo': 'منذ {n} ساعة',
'collab.chat.hoursAgo.zero': 'منذ {n} ساعة',
'collab.chat.hoursAgo.one': 'منذ ساعة واحدة',
'collab.chat.hoursAgo.two': 'منذ ساعتين',
'collab.chat.hoursAgo.few': 'منذ {n} ساعات',
'collab.chat.hoursAgo.many': 'منذ {n} ساعة',
'collab.notes.title': 'الملاحظات',
'collab.notes.new': 'ملاحظة جديدة',
'collab.notes.empty': 'لا توجد ملاحظات بعد',
@@ -69,6 +79,11 @@ const collab: TranslationStrings = {
'collab.notes.pin': 'تثبيت',
'collab.notes.unpin': 'إلغاء التثبيت',
'collab.notes.daysAgo': 'منذ {n} يوم',
'collab.notes.daysAgo.zero': 'منذ {n} يوم',
'collab.notes.daysAgo.one': 'منذ يوم واحد',
'collab.notes.daysAgo.two': 'منذ يومين',
'collab.notes.daysAgo.few': 'منذ {n} أيام',
'collab.notes.daysAgo.many': 'منذ {n} يومًا',
'collab.notes.categorySettings': 'إدارة الفئات',
'collab.notes.create': 'إنشاء',
'collab.notes.website': 'الموقع الإلكتروني',
+57 -7
View File
@@ -1,8 +1,18 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
'collections.saveNToList': 'حفظ {count} في قائمة',
'collections.addedNToList': 'تم حفظ {count} في {name}',
'collections.saveNToList': 'حفظ {count} مكان في قائمة',
'collections.saveNToList.zero': 'حفظ {count} مكان في قائمة',
'collections.saveNToList.one': 'حفظ مكان واحد في قائمة',
'collections.saveNToList.two': 'حفظ مكانين في قائمة',
'collections.saveNToList.few': 'حفظ {count} أماكن في قائمة',
'collections.saveNToList.many': 'حفظ {count} مكانًا في قائمة',
'collections.addedNToList': 'تم حفظ {count} مكان في {name}',
'collections.addedNToList.zero': 'لم يُحفظ أي مكان في {name}',
'collections.addedNToList.one': 'تم حفظ مكان واحد في {name}',
'collections.addedNToList.two': 'تم حفظ مكانين في {name}',
'collections.addedNToList.few': 'تم حفظ {count} أماكن في {name}',
'collections.addedNToList.many': 'تم حفظ {count} مكانًا في {name}',
'collections.noOwnLists': 'ليس لديك قوائم بعد',
'collections.saveToListHint': 'يتم تخطي التكرارات تلقائيًا',
'collections.role.label': 'الدور',
@@ -15,8 +25,18 @@ const collection: TranslationStrings = {
'collections.deselectAll': 'إلغاء التحديد',
'collections.moveToList': 'نقل إلى قائمة',
'collections.duplicateToList': 'تكرار إلى قائمة',
'collections.moveToListTitle': 'نقل {count} إلى قائمة أخرى',
'collections.duplicateToListTitle': 'تكرار {count} إلى قائمة أخرى',
'collections.moveToListTitle': 'نقل {count} مكان إلى قائمة أخرى',
'collections.moveToListTitle.zero': 'نقل {count} مكان إلى قائمة أخرى',
'collections.moveToListTitle.one': 'نقل مكان واحد إلى قائمة أخرى',
'collections.moveToListTitle.two': 'نقل مكانين إلى قائمة أخرى',
'collections.moveToListTitle.few': 'نقل {count} أماكن إلى قائمة أخرى',
'collections.moveToListTitle.many': 'نقل {count} مكانًا إلى قائمة أخرى',
'collections.duplicateToListTitle': 'تكرار {count} مكان إلى قائمة أخرى',
'collections.duplicateToListTitle.zero': 'تكرار {count} مكان إلى قائمة أخرى',
'collections.duplicateToListTitle.one': 'تكرار مكان واحد إلى قائمة أخرى',
'collections.duplicateToListTitle.two': 'تكرار مكانين إلى قائمة أخرى',
'collections.duplicateToListTitle.few': 'تكرار {count} أماكن إلى قائمة أخرى',
'collections.duplicateToListTitle.many': 'تكرار {count} مكانًا إلى قائمة أخرى',
'collections.noOtherLists': 'لا توجد قوائم أخرى بعد',
'collections.movedCount': 'تم نقل {count} مكان',
'collections.movedCount.zero': 'لم يتم نقل أي مكان',
@@ -75,7 +95,12 @@ const collection: TranslationStrings = {
'collections.importSelectedCount.two': 'تم تحديد {count}',
'collections.importSelectedCount.few': 'تم تحديد {count}',
'collections.importSelectedCount.many': 'تم تحديد {count}',
'collections.importAction': 'استيراد {count}',
'collections.importAction': 'استيراد {count} مكان',
'collections.importAction.zero': 'استيراد {count} مكان',
'collections.importAction.one': 'استيراد مكان واحد',
'collections.importAction.two': 'استيراد مكانين',
'collections.importAction.few': 'استيراد {count} أماكن',
'collections.importAction.many': 'استيراد {count} مكانًا',
'collections.importEmptyTrip': 'لا تحتوي هذه الرحلة على أماكن بعد',
'collections.importNothingNew': 'كل شيء من هذه الرحلة موجود بالفعل في القائمة',
'collections.importDone': 'تمت إضافة {count} مكان',
@@ -146,7 +171,12 @@ const collection: TranslationStrings = {
'collections.copyToTrip': 'نسخ إلى رحلة',
'collections.copyToTripTitle': 'نسخ إلى رحلة',
'collections.copyN': 'نسخ {count} إلى رحلة',
'collections.copyN': 'نسخ {count} مكان إلى رحلة',
'collections.copyN.zero': 'نسخ {count} مكان إلى رحلة',
'collections.copyN.one': 'نسخ إلى رحلة',
'collections.copyN.two': 'نسخ مكانين إلى رحلة',
'collections.copyN.few': 'نسخ {count} أماكن إلى رحلة',
'collections.copyN.many': 'نسخ {count} مكانًا إلى رحلة',
'collections.copyToTripSearch': 'البحث عن الرحلات',
'collections.searchLists': 'بحث في القوائم',
'collections.copyToTripConfirm': 'نسخ',
@@ -193,7 +223,12 @@ const collection: TranslationStrings = {
'collections.noListsYet': 'أنشئ قائمة أولًا لحفظ الأماكن.',
'collections.removedFromAll': 'تمت الإزالة من المجموعة',
'collections.picker.title': 'الأماكن المحفوظة',
'collections.picker.showMore': 'عرض {count} إضافية',
'collections.picker.showMore': 'عرض {count} عنصر آخر',
'collections.picker.showMore.zero': 'عرض {count} عنصر آخر',
'collections.picker.showMore.one': 'عرض عنصر واحد آخر',
'collections.picker.showMore.two': 'عرض عنصرين آخرين',
'collections.picker.showMore.few': 'عرض {count} عناصر أخرى',
'collections.picker.showMore.many': 'عرض {count} عنصرًا آخر',
'collections.picker.search': 'ابحث في أماكنك المحفوظة',
'collections.picker.empty': 'لا توجد أماكن محفوظة لإضافتها',
'collections.picker.hint': 'اختر مكانًا من مجموعاتك',
@@ -220,6 +255,11 @@ const collection: TranslationStrings = {
'collections.file.doneInto.few': 'تمت إضافة {count} أماكن إلى {name}',
'collections.file.doneInto.many': 'تمت إضافة {count} مكانًا إلى {name}',
'collections.file.doneIntoDuplicates': 'تمت إضافة {count} إلى {name}، و{duplicates} كانت موجودة بالفعل',
'collections.file.doneIntoDuplicates.zero': 'لم يُضف أي مكان إلى {name}، و{duplicates} كانت موجودة بالفعل',
'collections.file.doneIntoDuplicates.one': 'تمت إضافة مكان واحد إلى {name}، و{duplicates} كانت موجودة بالفعل',
'collections.file.doneIntoDuplicates.two': 'تمت إضافة مكانين إلى {name}، و{duplicates} كانت موجودة بالفعل',
'collections.file.doneIntoDuplicates.few': 'تمت إضافة {count} أماكن إلى {name}، و{duplicates} كانت موجودة بالفعل',
'collections.file.doneIntoDuplicates.many': 'تمت إضافة {count} مكانًا إلى {name}، و{duplicates} كانت موجودة بالفعل',
'collections.file.doneIntoNothing': 'كل الأماكن في الملف موجودة بالفعل في {name}',
'collections.file.change': 'تغيير',
'collections.file.labelCount': '{count} تسمية',
@@ -274,6 +314,16 @@ const collection: TranslationStrings = {
'collections.file.errorNotGpx': 'هذا ليس ملف GPX.',
'collections.file.errorTooManyPlaces':
'يحتوي هذا الملف على أكثر من {count} مكان. قسّمه واستورد الأجزاء واحدًا تلو الآخر.',
'collections.file.errorTooManyPlaces.zero':
'يحتوي هذا الملف على أكثر من {count} مكان. قسّمه واستورد الأجزاء واحدًا تلو الآخر.',
'collections.file.errorTooManyPlaces.one':
'يحتوي هذا الملف على أكثر من مكان واحد. قسّمه واستورد الأجزاء واحدًا تلو الآخر.',
'collections.file.errorTooManyPlaces.two':
'يحتوي هذا الملف على أكثر من مكانين. قسّمه واستورد الأجزاء واحدًا تلو الآخر.',
'collections.file.errorTooManyPlaces.few':
'يحتوي هذا الملف على أكثر من {count} أماكن. قسّمه واستورد الأجزاء واحدًا تلو الآخر.',
'collections.file.errorTooManyPlaces.many':
'يحتوي هذا الملف على أكثر من {count} مكانًا. قسّمه واستورد الأجزاء واحدًا تلو الآخر.',
'collections.share.title': 'مشاركة القائمة',
'collections.share.titleNamed': 'مشاركة «{name}»',
+12 -2
View File
@@ -54,8 +54,18 @@ const common: TranslationStrings = {
'common.copy': 'نسخ',
'common.copied': 'تم النسخ',
'common.justNow': 'just now', // en-fallback
'common.hoursAgo': '{count}h ago', // en-fallback
'common.daysAgo': '{count}d ago', // en-fallback
'common.hoursAgo': 'منذ {count} ساعة',
'common.hoursAgo.zero': 'منذ {count} ساعة',
'common.hoursAgo.one': 'منذ ساعة واحدة',
'common.hoursAgo.two': 'منذ ساعتين',
'common.hoursAgo.few': 'منذ {count} ساعات',
'common.hoursAgo.many': 'منذ {count} ساعة',
'common.daysAgo': 'منذ {count} يوم',
'common.daysAgo.zero': 'منذ {count} يوم',
'common.daysAgo.one': 'منذ يوم واحد',
'common.daysAgo.two': 'منذ يومين',
'common.daysAgo.few': 'منذ {count} أيام',
'common.daysAgo.many': 'منذ {count} يومًا',
'common.datepicker.prevMonth': 'Previous month', // en-fallback
'common.datepicker.nextMonth': 'Next month', // en-fallback
'common.datepicker.prevYear': 'Previous year', // en-fallback
+11 -2
View File
@@ -10,8 +10,12 @@ const dashboard: TranslationStrings = {
'dashboard.subtitle.trips.few': '{count} رحلات ({archived} مؤرشفة)',
'dashboard.subtitle.trips.many': '{count} رحلة ({archived} مؤرشفة)',
'dashboard.subtitle.empty': 'ابدأ رحلتك الأولى',
'dashboard.subtitle.activeOne': '{count} رحلة نشطة',
'dashboard.subtitle.activeMany': '{count} رحلات نشطة',
'dashboard.subtitle.active': '{count} رحلة نشطة',
'dashboard.subtitle.active.zero': 'لا توجد رحلات نشطة',
'dashboard.subtitle.active.one': 'رحلة نشطة واحدة',
'dashboard.subtitle.active.two': 'رحلتان نشطتان',
'dashboard.subtitle.active.few': '{count} رحلات نشطة',
'dashboard.subtitle.active.many': '{count} رحلة نشطة',
'dashboard.subtitle.archivedSuffix': ' · {count} رحلة مؤرشفة',
'dashboard.subtitle.archivedSuffix.zero': ' · لا توجد رحلات مؤرشفة',
'dashboard.subtitle.archivedSuffix.one': ' · رحلة مؤرشفة واحدة',
@@ -136,6 +140,11 @@ const dashboard: TranslationStrings = {
'dashboard.shrinkTitle': 'إزالة أيام؟',
'dashboard.shrinkIntro': 'عند حفظ التواريخ الجديدة تُزال هذه الأيام:',
'dashboard.shrinkMoreDays': '+{count} أخرى',
'dashboard.shrinkMoreDays.zero': '+{count} أخرى',
'dashboard.shrinkMoreDays.one': '+{count} أخرى',
'dashboard.shrinkMoreDays.two': '+{count} أخرى',
'dashboard.shrinkMoreDays.few': '+{count} أخرى',
'dashboard.shrinkMoreDays.many': '+{count} أخرى',
'dashboard.shrinkLastDays': 'تُزال الأيام الأخيرة، لا الأولى',
'dashboard.shrinkLastDaysHint':
'تنتقل خطط الأيام مع التواريخ الجديدة، لذا تُزال دائمًا الأيام الأخيرة من خطتك، حتى عندما تغيّر تاريخ البداية.',
+11 -1
View File
@@ -105,6 +105,11 @@ const dawarich: TranslationStrings = {
'dawarich.suggestions.dismissed': 'تم التجاهل',
'dawarich.suggestions.restore': 'إعادته',
'dawarich.suggestions.showHandled': 'إظهار {count} تمت معالجتها',
'dawarich.suggestions.showHandled.zero': 'إظهار {count} اقتراح تمت معالجته',
'dawarich.suggestions.showHandled.one': 'إظهار اقتراح واحد تمت معالجته',
'dawarich.suggestions.showHandled.two': 'إظهار اقتراحين تمت معالجتهما',
'dawarich.suggestions.showHandled.few': 'إظهار {count} اقتراحات تمت معالجتها',
'dawarich.suggestions.showHandled.many': 'إظهار {count} اقتراحًا تمت معالجته',
'dawarich.suggestions.hideHandled': 'إخفاء ما تمت معالجته',
'dawarich.suggestions.matchesWish': 'في قائمة أمنياتك: {name}',
'dawarich.suggestions.acceptedAs.place': 'تمت الإضافة كمكان',
@@ -155,7 +160,12 @@ const dawarich: TranslationStrings = {
'dawarich.bucket.scanning': 'جارٍ الفحص…',
'dawarich.bucket.noMatches': 'لم يظهر أي شيء من قائمة أمنياتك في تسجيلاتك.',
'dawarich.bucket.alreadyVisited': 'مشطوبة مسبقاً',
'dawarich.bucket.confirm': 'اشطب {count}',
'dawarich.bucket.confirm': 'اشطب {count} أمنية',
'dawarich.bucket.confirm.zero': 'اشطب {count} أمنية',
'dawarich.bucket.confirm.one': 'اشطب أمنية واحدة',
'dawarich.bucket.confirm.two': 'اشطب أمنيتين',
'dawarich.bucket.confirm.few': 'اشطب {count} أمنيات',
'dawarich.bucket.confirm.many': 'اشطب {count} أمنية',
'dawarich.bucket.confirmed': 'تم شطب {count} أمنية',
'dawarich.bucket.confirmed.zero': 'لم يتم شطب أي أمنية',
'dawarich.bucket.confirmed.one': 'تم شطب أمنية واحدة',
+30
View File
@@ -102,11 +102,31 @@ const dayplan: TranslationStrings = {
'dayplan.clearDayBody': 'ستُزال كل الأماكن من هذا اليوم. تبقى الأماكن في الرحلة، ويحتفظ اليوم بملاحظاته وحجوزاته.',
'dayplan.deleteDayEmpty': 'لا يوجد شيء مخطط لهذا اليوم.',
'dayplan.impactPlaces': 'الأماكن المخططة: {count}',
'dayplan.impactPlaces.zero': 'الأماكن المخططة: {count}',
'dayplan.impactPlaces.one': 'الأماكن المخططة: {count}',
'dayplan.impactPlaces.two': 'الأماكن المخططة: {count}',
'dayplan.impactPlaces.few': 'الأماكن المخططة: {count}',
'dayplan.impactPlaces.many': 'الأماكن المخططة: {count}',
'dayplan.impactPlacesHint': 'تبقى في قائمة الأماكن.',
'dayplan.impactNotes': 'الملاحظات: {count}',
'dayplan.impactNotes.zero': 'الملاحظات: {count}',
'dayplan.impactNotes.one': 'الملاحظات: {count}',
'dayplan.impactNotes.two': 'الملاحظات: {count}',
'dayplan.impactNotes.few': 'الملاحظات: {count}',
'dayplan.impactNotes.many': 'الملاحظات: {count}',
'dayplan.impactTexts': 'عناوين الأيام وأوصافها: {count}',
'dayplan.impactTexts.zero': 'عناوين الأيام وأوصافها: {count}',
'dayplan.impactTexts.one': 'عناوين الأيام وأوصافها: {count}',
'dayplan.impactTexts.two': 'عناوين الأيام وأوصافها: {count}',
'dayplan.impactTexts.few': 'عناوين الأيام وأوصافها: {count}',
'dayplan.impactTexts.many': 'عناوين الأيام وأوصافها: {count}',
'dayplan.impactDeletedHint': 'تُحذف أيضًا.',
'dayplan.impactBookings': 'الحجوزات: {count}',
'dayplan.impactBookings.zero': 'الحجوزات: {count}',
'dayplan.impactBookings.one': 'الحجوزات: {count}',
'dayplan.impactBookings.two': 'الحجوزات: {count}',
'dayplan.impactBookings.few': 'الحجوزات: {count}',
'dayplan.impactBookings.many': 'الحجوزات: {count}',
'dayplan.impactStay': 'الإقامة في {name}',
'dayplan.deleteDayBookingsHint': 'تبقى ضمن الحجوزات، دون يوم.',
'dayplan.deleteDayStayHint': 'يقع تسجيل الوصول أو المغادرة في هذا اليوم، لذا تُلغى الإقامة.',
@@ -117,8 +137,18 @@ const dayplan: TranslationStrings = {
'dayplan.deleteDayStayPaidHint':
'يقع تسجيل الوصول أو المغادرة في هذا اليوم، لذا تُلغى الإقامة مع الحجز "{booking}" ومصروفه البالغ {amount}.',
'dayplan.deleteDayShift': 'الأيام اللاحقة: {count}',
'dayplan.deleteDayShift.zero': 'الأيام اللاحقة: {count}',
'dayplan.deleteDayShift.one': 'الأيام اللاحقة: {count}',
'dayplan.deleteDayShift.two': 'الأيام اللاحقة: {count}',
'dayplan.deleteDayShift.few': 'الأيام اللاحقة: {count}',
'dayplan.deleteDayShift.many': 'الأيام اللاحقة: {count}',
'dayplan.deleteDayShiftHint': 'ينتقل كل منها إلى التاريخ السابق.',
'dayplan.deleteDayShiftBookingsHint': 'ينتقل كل منها إلى التاريخ السابق. الحجوزات التي تنتقل معها: {count}',
'dayplan.deleteDayShiftBookingsHint.zero': 'ينتقل كل منها إلى التاريخ السابق. الحجوزات التي تنتقل معها: {count}',
'dayplan.deleteDayShiftBookingsHint.one': 'ينتقل كل منها إلى التاريخ السابق. الحجوزات التي تنتقل معها: {count}',
'dayplan.deleteDayShiftBookingsHint.two': 'ينتقل كل منها إلى التاريخ السابق. الحجوزات التي تنتقل معها: {count}',
'dayplan.deleteDayShiftBookingsHint.few': 'ينتقل كل منها إلى التاريخ السابق. الحجوزات التي تنتقل معها: {count}',
'dayplan.deleteDayShiftBookingsHint.many': 'ينتقل كل منها إلى التاريخ السابق. الحجوزات التي تنتقل معها: {count}',
'dayplan.deleteDayShrink': 'تنتهي الرحلة الآن في {date}',
'dayplan.deleteDayShrinkHint': 'لا يوجد يوم بلا تاريخ ليأخذ التاريخ الأخير.',
'dayplan.impactStayShorter': 'الإقامة في {name}: ليلة أقل',
+6 -1
View File
@@ -68,7 +68,12 @@ const docsync: TranslationStrings = {
'docsync.state.local_deleted': 'محذوف في TREK',
'docsync.state.scope_drift': 'نُقل خارج المجلد',
'docsync.conflict.resolve': 'حلّ {count}',
'docsync.conflict.resolve': 'حلّ {count} تعارض',
'docsync.conflict.resolve.zero': 'حلّ {count} تعارض',
'docsync.conflict.resolve.one': 'حلّ تعارض واحد',
'docsync.conflict.resolve.two': 'حلّ تعارضين',
'docsync.conflict.resolve.few': 'حلّ {count} تعارضات',
'docsync.conflict.resolve.many': 'حلّ {count} تعارضًا',
'docsync.conflict.title': 'تغيّرت النسختان',
'docsync.conflict.keepTrek': 'الإبقاء على نسخة TREK',
+5
View File
@@ -707,6 +707,11 @@ const journey: TranslationStrings = {
'journey.suggestions.dismissed': 'تم تجاهل الاقتراح',
'journey.suggestions.restore': 'استعادة الاقتراحات المتجاهَلة',
'journey.suggestions.restoreCount': 'الاقتراحات المتجاهَلة ({count})',
'journey.suggestions.restoreCount.zero': 'الاقتراحات المتجاهَلة ({count})',
'journey.suggestions.restoreCount.one': 'الاقتراحات المتجاهَلة ({count})',
'journey.suggestions.restoreCount.two': 'الاقتراحات المتجاهَلة ({count})',
'journey.suggestions.restoreCount.few': 'الاقتراحات المتجاهَلة ({count})',
'journey.suggestions.restoreCount.many': 'الاقتراحات المتجاهَلة ({count})',
'journey.suggestions.restored': 'تمت استعادة {count} اقتراح',
'journey.suggestions.restored.zero': 'لم تتم استعادة أي اقتراح',
'journey.suggestions.restored.one': 'تمت استعادة اقتراح واحد',
+8 -1
View File
@@ -66,7 +66,14 @@ const memories: TranslationStrings = {
'memories.tripDates': 'تواريخ الرحلة',
'memories.allPhotos': 'جميع الصور',
'memories.confirmShareTitle': 'مشاركة مع أعضاء الرحلة؟',
'memories.confirmShareHint': '{count} صور ستكون مرئية لجميع أعضاء هذه الرحلة. يمكنك جعل الصور الفردية خاصة لاحقًا.',
'memories.confirmShareHint': 'ستكون {count} صورة مرئية لجميع أعضاء هذه الرحلة. يمكنك جعل الصور الفردية خاصة لاحقًا.',
'memories.confirmShareHint.zero': 'لن تكون أي صورة مرئية لأعضاء هذه الرحلة.',
'memories.confirmShareHint.one': 'ستكون صورة واحدة مرئية لجميع أعضاء هذه الرحلة. يمكنك جعلها خاصة لاحقًا.',
'memories.confirmShareHint.two': 'ستكون صورتان مرئيتين لجميع أعضاء هذه الرحلة. يمكنك جعل الصور الفردية خاصة لاحقًا.',
'memories.confirmShareHint.few':
'ستكون {count} صور مرئية لجميع أعضاء هذه الرحلة. يمكنك جعل الصور الفردية خاصة لاحقًا.',
'memories.confirmShareHint.many':
'ستكون {count} صورة مرئية لجميع أعضاء هذه الرحلة. يمكنك جعل الصور الفردية خاصة لاحقًا.',
'memories.confirmShareButton': 'مشاركة الصور',
'memories.error.loadAlbums': 'فشل تحميل الألبومات',
'memories.error.linkAlbum': 'فشل ربط الألبوم',
+24 -4
View File
@@ -30,7 +30,12 @@ const packing: TranslationStrings = {
'packing.printPacked': 'جُهّز',
'packing.importHintMarkdown':
'يمكن أيضًا استخدام قائمة Markdown: العنوان يحدد الفئة، و"- [ ]" و"- [x]" تصبح عناصر، و"3x" قبل الاسم تحدد الكمية.',
'packing.importAction': 'استيراد {count}',
'packing.importAction': 'استيراد {count} عنصر',
'packing.importAction.zero': 'استيراد {count} عنصر',
'packing.importAction.one': 'استيراد عنصر واحد',
'packing.importAction.two': 'استيراد عنصرين',
'packing.importAction.few': 'استيراد {count} عناصر',
'packing.importAction.many': 'استيراد {count} عنصرًا',
'packing.importSuccess': 'تم استيراد {count} عنصر',
'packing.importSuccess.zero': 'لم يتم استيراد أي عنصر',
'packing.importSuccess.one': 'تم استيراد عنصر واحد',
@@ -40,8 +45,18 @@ const packing: TranslationStrings = {
'packing.importError': 'فشل الاستيراد',
'packing.importEmpty': 'لا توجد عناصر للاستيراد',
'packing.progress': '{packed} من {total} جُهّز ({percent}%)',
'packing.clearChecked': 'إزالة {count} محدد',
'packing.clearCheckedShort': 'إزالة {count}',
'packing.clearChecked': 'إزالة {count} عنصر محدد',
'packing.clearChecked.zero': 'إزالة {count} عنصر محدد',
'packing.clearChecked.one': 'إزالة عنصر محدد واحد',
'packing.clearChecked.two': 'إزالة عنصرين محددين',
'packing.clearChecked.few': 'إزالة {count} عناصر محددة',
'packing.clearChecked.many': 'إزالة {count} عنصرًا محددًا',
'packing.clearCheckedShort': 'إزالة {count} عنصر',
'packing.clearCheckedShort.zero': 'إزالة {count} عنصر',
'packing.clearCheckedShort.one': 'إزالة عنصر واحد',
'packing.clearCheckedShort.two': 'إزالة عنصرين',
'packing.clearCheckedShort.few': 'إزالة {count} عناصر',
'packing.clearCheckedShort.many': 'إزالة {count} عنصرًا',
'packing.allPacked': 'تم تجهيز الكل!',
'packing.addPlaceholder': 'إضافة عنصر جديد...',
'packing.categoryPlaceholder': 'قائمة...',
@@ -112,7 +127,12 @@ const packing: TranslationStrings = {
'packing.tierShared': 'مشاركة مع…',
'packing.noOneToShare': 'لا أحد آخر في هذه الرحلة بعد',
'packing.takenCareOf': 'بواسطة {name}',
'packing.sharedWithCount': 'مشترك مع {count}',
'packing.sharedWithCount': 'مشترك مع {count} شخص',
'packing.sharedWithCount.zero': 'غير مشترك مع أحد',
'packing.sharedWithCount.one': 'مشترك مع شخص واحد',
'packing.sharedWithCount.two': 'مشترك مع شخصين',
'packing.sharedWithCount.few': 'مشترك مع {count} أشخاص',
'packing.sharedWithCount.many': 'مشترك مع {count} شخصًا',
'packing.broughtBy': 'يحضره {name}',
'packing.alsoBring': 'يمكنني إحضاره أيضًا',
'packing.alsoBringingStop': 'لن أحضره',
+5
View File
@@ -20,6 +20,11 @@ const photos: TranslationStrings = {
'photos.linkPlace': 'ربط بمكان',
'photos.noPlace': 'بلا مكان',
'photos.uploadN': 'رفع {n} صورة',
'photos.uploadN.zero': 'رفع {n} صورة',
'photos.uploadN.one': 'رفع صورة واحدة',
'photos.uploadN.two': 'رفع صورتين',
'photos.uploadN.few': 'رفع {n} صور',
'photos.uploadN.many': 'رفع {n} صورة',
'photos.linkDay': 'ربط اليوم',
'photos.noDay': 'لا يوم',
'photos.dayLabel': 'اليوم {number}',
+6 -1
View File
@@ -197,7 +197,12 @@ const reservations: TranslationStrings = {
'reservations.airtrail.duringTrip': 'خلال هذه الرحلة',
'reservations.airtrail.otherFlights': 'رحلات أخرى',
'reservations.airtrail.empty': 'لم يتم العثور على أي رحلات في حساب AirTrail الخاص بك.',
'reservations.airtrail.importCta': 'استيراد {count}',
'reservations.airtrail.importCta': 'استيراد {count} رحلة جوية',
'reservations.airtrail.importCta.zero': 'استيراد {count} رحلة جوية',
'reservations.airtrail.importCta.one': 'استيراد رحلة جوية واحدة',
'reservations.airtrail.importCta.two': 'استيراد رحلتين جويتين',
'reservations.airtrail.importCta.few': 'استيراد {count} رحلات جوية',
'reservations.airtrail.importCta.many': 'استيراد {count} رحلة جوية',
'reservations.airtrail.joinConnection': 'الاستيراد كرحلة واحدة مع توقف في {stops}',
'reservations.costsLabel': 'التكاليف',
'reservations.createExpense': 'إنشاء مصروف',
+5
View File
@@ -216,6 +216,11 @@ const roadtrip: TranslationStrings = {
'roadtrip.poi.noMatch': 'لا يوجد على الطريق ما يطابق «{name}».',
'roadtrip.poi.noneMatchFilters': 'لا يوجد شيء على الطريق يطابق عوامل التصفية هذه.',
'roadtrip.poi.foundFiltered': '{count} من {total} على الطريق',
'roadtrip.poi.foundFiltered.zero': '{count} من {total} على الطريق',
'roadtrip.poi.foundFiltered.one': '{count} من {total} على الطريق',
'roadtrip.poi.foundFiltered.two': '{count} من {total} على الطريق',
'roadtrip.poi.foundFiltered.few': '{count} من {total} على الطريق',
'roadtrip.poi.foundFiltered.many': '{count} من {total} على الطريق',
'roadtrip.poi.truncated': 'في {count} مقطع كانت النتائج أكثر مما تتسع له استجابة واحدة. قلّص الممر لرؤية البقية.',
'roadtrip.poi.truncated.zero': 'لم تتجاوز النتائج في أي مقطع ما تتسع له استجابة واحدة.',
'roadtrip.poi.truncated.one': 'في مقطع واحد كانت النتائج أكثر مما تتسع له استجابة واحدة. قلّص الممر لرؤية البقية.',
+25
View File
@@ -566,9 +566,29 @@ const settings: TranslationStrings = {
'settings.offline.banner.offline': 'دون اتصال',
'settings.offline.banner.forced': 'وضع عدم الاتصال',
'settings.offline.banner.queued': 'دون اتصال · {count} في قائمة الانتظار',
'settings.offline.banner.queued.zero': 'دون اتصال · {count} في قائمة الانتظار',
'settings.offline.banner.queued.one': 'دون اتصال · {count} في قائمة الانتظار',
'settings.offline.banner.queued.two': 'دون اتصال · {count} في قائمة الانتظار',
'settings.offline.banner.queued.few': 'دون اتصال · {count} في قائمة الانتظار',
'settings.offline.banner.queued.many': 'دون اتصال · {count} في قائمة الانتظار',
'settings.offline.banner.syncing': 'جارٍ مزامنة {count}…',
'settings.offline.banner.syncing.zero': 'جارٍ مزامنة {count}…',
'settings.offline.banner.syncing.one': 'جارٍ مزامنة {count}…',
'settings.offline.banner.syncing.two': 'جارٍ مزامنة {count}…',
'settings.offline.banner.syncing.few': 'جارٍ مزامنة {count}…',
'settings.offline.banner.syncing.many': 'جارٍ مزامنة {count}…',
'settings.offline.banner.failed': 'فشلت المزامنة: {count}',
'settings.offline.banner.failed.zero': 'فشلت المزامنة: {count}',
'settings.offline.banner.failed.one': 'فشلت المزامنة: {count}',
'settings.offline.banner.failed.two': 'فشلت المزامنة: {count}',
'settings.offline.banner.failed.few': 'فشلت المزامنة: {count}',
'settings.offline.banner.failed.many': 'فشلت المزامنة: {count}',
'settings.offline.banner.conflicts': 'تعارضات: {count}',
'settings.offline.banner.conflicts.zero': 'تعارضات: {count}',
'settings.offline.banner.conflicts.one': 'تعارضات: {count}',
'settings.offline.banner.conflicts.two': 'تعارضات: {count}',
'settings.offline.banner.conflicts.few': 'تعارضات: {count}',
'settings.offline.banner.conflicts.many': 'تعارضات: {count}',
'settings.pluginActivity.title': 'نشاط الإضافات',
'settings.pluginActivity.description': 'كل إجراء نفّذته إضافة نيابةً عنك، الأحدث أولاً.',
'settings.pluginActivity.empty': 'لا يوجد نشاط للإضافات بعد.',
@@ -590,6 +610,11 @@ const settings: TranslationStrings = {
'settings.apiScopes.all': 'كل شيء',
'settings.apiScopes.noneSelected': 'اختر مجالًا واحدًا على الأقل، وإلا فلن يستطيع المفتاح قراءة أي شيء.',
'settings.apiScopes.limited': '{count} من {total}',
'settings.apiScopes.limited.zero': '{count} من {total}',
'settings.apiScopes.limited.one': '{count} من {total}',
'settings.apiScopes.limited.two': '{count} من {total}',
'settings.apiScopes.limited.few': '{count} من {total}',
'settings.apiScopes.limited.many': '{count} من {total}',
'settings.apiScopes.trips': 'الرحلات',
'settings.apiScopes.days': 'الأيام',
'settings.apiScopes.places': 'الأماكن',
+5
View File
@@ -70,6 +70,11 @@ const tours: TranslationStrings = {
'tours.planner.waypoints': 'نقاط المرور',
'tours.planner.waypointLabel': 'نقطة المرور {n}',
'tours.durationMinutes': '{count} د',
'tours.durationMinutes.zero': '{count} د',
'tours.durationMinutes.one': '{count} د',
'tours.durationMinutes.two': '{count} د',
'tours.durationMinutes.few': '{count} د',
'tours.durationMinutes.many': '{count} د',
'tours.planner.empty': 'أضف نقطتين على الأقل لحساب مسار للمشي.',
'tours.planner.start': 'البداية',
'tours.planner.via': 'المرور عبر',
+5
View File
@@ -85,6 +85,11 @@ const trip: TranslationStrings = {
'transit.transfers.few': '{count} تحويلات',
'transit.transfers.many': '{count} تحويلة',
'transit.min': '{count} دقيقة',
'transit.min.zero': '{count} دقيقة',
'transit.min.one': 'دقيقة واحدة',
'transit.min.two': 'دقيقتان',
'transit.min.few': '{count} دقائق',
'transit.min.many': '{count} دقيقة',
'transit.stops': '{count} محطة',
'transit.stops.zero': 'دون توقف',
'transit.stops.one': 'محطة واحدة',
+6 -1
View File
@@ -86,7 +86,12 @@ const vacay: TranslationStrings = {
'vacay.carryOver': 'الترحيل',
'vacay.carryOverHint': 'ترحيل أيام الإجازة المتبقية تلقائيًا إلى السنة التالية',
'vacay.carriedOverPrevPeriod': 'من الفترة السابقة',
'vacay.compUsedCount': '{count} تعويض',
'vacay.compUsedCount': 'أيام التعويض: {count}',
'vacay.compUsedCount.zero': 'أيام التعويض: {count}',
'vacay.compUsedCount.one': 'أيام التعويض: {count}',
'vacay.compUsedCount.two': 'أيام التعويض: {count}',
'vacay.compUsedCount.few': 'أيام التعويض: {count}',
'vacay.compUsedCount.many': 'أيام التعويض: {count}',
'vacay.yearType': 'سنة الإجازة',
'vacay.yearTypeHint': 'متى تبدأ سنة إجازتك — يتبعها الاستحقاق والاستخدام والترحيل',
'vacay.yearTypeCalendar': 'تقويمية',
+3
View File
@@ -672,7 +672,9 @@ const admin: TranslationStrings = {
'admin.plugins.cap.requiresAddon': '{addon} əlavəsini tələb edir',
'admin.plugins.cap.dependsOn': '{id} {version} tələb edir',
'admin.plugins.dep.addonDisabledToast': 'Əvvəlcə tələb olunan əlavələri aktivləşdirin: {addons}',
'admin.plugins.dep.addonDisabledToast.one': 'Əvvəlcə tələb olunan əlavəni aktivləşdirin: {addons}',
'admin.plugins.dep.autoEnabled': 'Əvvəlcə tələb olunan plaginlər aktivləşdirildi: {plugins}',
'admin.plugins.dep.autoEnabled.one': 'Əvvəlcə tələb olunan plagin aktivləşdirildi: {plugins}',
'admin.plugins.dep.downloaded': '{id} endirildi',
'admin.plugins.dep.resolveTitle': 'Çatışmayan asılılıqlar',
'admin.plugins.dep.resolveBody':
@@ -821,6 +823,7 @@ const admin: TranslationStrings = {
'admin.audit.refresh': 'Yenilə',
'admin.audit.loadMore': 'Daha çox yüklə',
'admin.audit.showing': '{count} yüklənib · cəmi {total}',
'admin.audit.showing.one': '{count} yüklənib · cəmi {total}',
'admin.audit.col.time': 'Vaxt',
'admin.audit.col.user': 'İstifadəçi',
'admin.audit.col.action': 'Əməliyyat',
+3
View File
@@ -54,7 +54,9 @@ const collab: TranslationStrings = {
'collab.chat.loadMore': 'Əvvəlki mesajları yüklə',
'collab.chat.justNow': 'indicə',
'collab.chat.minutesAgo': '{n} dəq. əvvəl',
'collab.chat.minutesAgo.one': '{n} dəq. əvvəl',
'collab.chat.hoursAgo': '{n} saat əvvəl',
'collab.chat.hoursAgo.one': '{n} saat əvvəl',
'collab.notes.title': 'Qeydlər',
'collab.notes.new': 'Yeni qeyd',
@@ -79,6 +81,7 @@ const collab: TranslationStrings = {
'collab.notes.pin': 'Bərkit',
'collab.notes.unpin': 'Bərkidilməni ləğv et',
'collab.notes.daysAgo': '{n} gün əvvəl',
'collab.notes.daysAgo.one': '{n} gün əvvəl',
'collab.notes.categorySettings': 'Kateqoriyaları idarə et',
'collab.notes.create': 'Yarat',
'collab.notes.website': 'Veb-sayt',
+13 -1
View File
@@ -2,7 +2,9 @@ import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
'collections.saveNToList': '{count} məkanı siyahıya əlavə et',
'collections.saveNToList.one': '{count} məkanı siyahıya əlavə et',
'collections.addedNToList': '{count} məkan {name} siyahısında saxlanıldı',
'collections.addedNToList.one': '{count} məkan {name} siyahısında saxlanıldı',
'collections.noOwnLists': 'Hələ siyahınız yoxdur',
'collections.saveToListHint': 'Dublikatlar avtomatik buraxılır',
'collections.role.label': 'Rol',
@@ -16,7 +18,9 @@ const collection: TranslationStrings = {
'collections.moveToList': 'Siyahıya köçür',
'collections.duplicateToList': 'Siyahıya kopyala',
'collections.moveToListTitle': '{count} məkanı başqa siyahıya köçür',
'collections.moveToListTitle.one': '{count} məkanı başqa siyahıya köçür',
'collections.duplicateToListTitle': '{count} məkanı başqa siyahıya kopyala',
'collections.duplicateToListTitle.one': '{count} məkanı başqa siyahıya kopyala',
'collections.noOtherLists': 'Başqa siyahı yoxdur',
'collections.movedCount': '{count} məkan köçürüldü',
'collections.movedCount.one': '{count} məkan köçürüldü',
@@ -56,6 +60,7 @@ const collection: TranslationStrings = {
'collections.importSelectedCount': '{count} seçilib',
'collections.importSelectedCount.one': '{count} seçilib',
'collections.importAction': '{count} məkanı idxal et',
'collections.importAction.one': '{count} məkanı idxal et',
'collections.importEmptyTrip': 'Bu səyahətdə hələ məkan yoxdur',
'collections.importNothingNew': 'Bu səyahətdəki bütün məkanlar artıq siyahıdadır',
'collections.importDone': '{count} məkan əlavə edildi',
@@ -115,6 +120,7 @@ const collection: TranslationStrings = {
'collections.copyToTrip': 'Səyahətə kopyala',
'collections.copyToTripTitle': 'Səyahətə kopyala',
'collections.copyN': '{count} məkanı səyahətə kopyala',
'collections.copyN.one': 'Səyahətə kopyala',
'collections.copyToTripSearch': 'Səyahətləri axtar',
'collections.searchLists': 'Siyahıları axtar',
'collections.copyToTripConfirm': 'Kopyala',
@@ -150,6 +156,7 @@ const collection: TranslationStrings = {
'collections.removedFromAll': 'Kolleksiyadan silindi',
'collections.picker.title': 'Saxlanılan məkanlar',
'collections.picker.showMore': 'Daha {count} məkan göstər',
'collections.picker.showMore.one': 'Daha {count} məkan göstər',
'collections.picker.search': 'Saxlanılan məkanlarınızda axtar',
'collections.picker.empty': 'Əlavə ediləcək saxlanılan məkan yoxdur',
'collections.picker.hint': 'Kolleksiyalarınızdan məkan seçin',
@@ -171,7 +178,10 @@ const collection: TranslationStrings = {
'collections.file.confirmInto': 'Siyahıya əlavə et',
'collections.file.doneInto': '{count} məkan {name} siyahısına əlavə edildi',
'collections.file.doneInto.one': '{count} məkan {name} siyahısına əlavə edildi',
'collections.file.doneIntoDuplicates': '{count} məkan {name} siyahısına əlavə edildi, {duplicates} məkan artıq orada idi',
'collections.file.doneIntoDuplicates':
'{count} məkan {name} siyahısına əlavə edildi, {duplicates} məkan artıq orada idi',
'collections.file.doneIntoDuplicates.one':
'{count} məkan {name} siyahısına əlavə edildi, {duplicates} məkan artıq orada idi',
'collections.file.doneIntoNothing': 'Fayldakı bütün məkanlar artıq {name} siyahısındadır',
'collections.file.change': 'Dəyiş',
'collections.file.labelCount': '{count} etiket',
@@ -201,6 +211,8 @@ const collection: TranslationStrings = {
'collections.file.errorNotGpx': 'Bu, GPX faylı deyil.',
'collections.file.errorTooManyPlaces':
'Bu faylda {count} ədəddən çox məkan var. Faylı hissələrə bölün və hər hissəni ayrıca idxal edin.',
'collections.file.errorTooManyPlaces.one':
'Bu faylda {count} ədəddən çox məkan var. Faylı hissələrə bölün və hər hissəni ayrıca idxal edin.',
'collections.share.title': 'Siyahını paylaş',
'collections.share.titleNamed': '“{name}” siyahısını paylaş',
+2
View File
@@ -45,7 +45,9 @@ const common: TranslationStrings = {
'common.saving': 'Saxlanılır...',
'common.justNow': 'indicə',
'common.hoursAgo': '{count}saat əvvəl',
'common.hoursAgo.one': '{count}saat əvvəl',
'common.daysAgo': '{count}gün əvvəl',
'common.daysAgo.one': '{count}gün əvvəl',
'common.saved': 'Yadda saxlandı',
'common.update': 'Yenilə',
'common.change': 'Dəyiş',
+3 -2
View File
@@ -6,8 +6,8 @@ const dashboard: TranslationStrings = {
'dashboard.subtitle.trips': '{count} səyahət ({archived} arxivləşdirilib)',
'dashboard.subtitle.trips.one': '{count} səyahət ({archived} arxivləşdirilib)',
'dashboard.subtitle.empty': 'İlk səyahətinizə başlayın',
'dashboard.subtitle.activeOne': '{count} aktiv səyahət',
'dashboard.subtitle.activeMany': '{count} aktiv səyahət',
'dashboard.subtitle.active': '{count} aktiv səyahət',
'dashboard.subtitle.active.one': '{count} aktiv səyahət',
'dashboard.subtitle.archivedSuffix': ' · {count} arxivləşdirilib',
'dashboard.subtitle.archivedSuffix.one': ' · {count} arxivləşdirilib',
'dashboard.newTrip': 'Yeni səyahət',
@@ -127,6 +127,7 @@ const dashboard: TranslationStrings = {
'dashboard.shrinkTitle': 'Günlər silinsin?',
'dashboard.shrinkIntro': 'Yeni tarixləri yadda saxlamaq bu günləri siləcək:',
'dashboard.shrinkMoreDays': '+{count} daha',
'dashboard.shrinkMoreDays.one': '+{count} daha',
'dashboard.shrinkLastDays': 'İlk deyil, son günlər silinir',
'dashboard.shrinkLastDaysHint':
'Gün planları yeni tarixlərlə birlikdə hərəkət etdiyi üçün başlanğıc tarixi dəyişdirilsə belə, həmişə planın son günləri silinir.',
+2
View File
@@ -93,6 +93,7 @@ const dawarich: TranslationStrings = {
'dawarich.suggestions.dismissed': 'Rədd edilib',
'dawarich.suggestions.restore': 'Geri qaytar',
'dawarich.suggestions.showHandled': 'Nəzərdən keçirilmiş {count} təklifi göstər',
'dawarich.suggestions.showHandled.one': 'Nəzərdən keçirilmiş {count} təklifi göstər',
'dawarich.suggestions.hideHandled': 'Nəzərdən keçirilmiş təklifləri gizlət',
'dawarich.suggestions.matchesWish': 'Arzular siyahınızdadır: {name}',
'dawarich.suggestions.acceptedAs.place': 'Məkan kimi əlavə edilib',
@@ -144,6 +145,7 @@ const dawarich: TranslationStrings = {
'dawarich.bucket.noMatches': 'Arzular siyahınızdakı məkanlardan heç biri qeydlərinizdə tapılmadı.',
'dawarich.bucket.alreadyVisited': 'Artıq tamamlandı kimi işarələnib',
'dawarich.bucket.confirm': '{count} arzunu tamamlandı kimi işarələ',
'dawarich.bucket.confirm.one': '{count} arzunu tamamlandı kimi işarələ',
'dawarich.bucket.confirmed': '{count} arzu tamamlandı kimi işarələndi',
'dawarich.bucket.confirmed.one': '{count} arzu tamamlandı kimi işarələndi',
'dawarich.bucket.skipped': '{count} qeydin koordinatları olmadığından onları yoxlamaq mümkün olmadı.',
+7
View File
@@ -129,11 +129,15 @@ const dayplan: TranslationStrings = {
'Bütün məkanlar bu gündən çıxarılır. Məkanlar səyahətdə qalır, gün isə öz qeydlərini və rezervasiyalarını saxlayır.',
'dayplan.deleteDayEmpty': 'Bu gün üçün heç nə planlaşdırılmayıb.',
'dayplan.impactPlaces': 'Planlaşdırılmış məkanlar: {count}',
'dayplan.impactPlaces.one': 'Planlaşdırılmış məkanlar: {count}',
'dayplan.impactPlacesHint': 'Onlar məkanlar siyahısında qalır.',
'dayplan.impactNotes': 'Qeydlər: {count}',
'dayplan.impactNotes.one': 'Qeydlər: {count}',
'dayplan.impactTexts': 'Gün başlıqları və təsvirləri: {count}',
'dayplan.impactTexts.one': 'Gün başlıqları və təsvirləri: {count}',
'dayplan.impactDeletedHint': 'Onlar da silinəcək.',
'dayplan.impactBookings': 'Rezervasiyalar: {count}',
'dayplan.impactBookings.one': 'Rezervasiyalar: {count}',
'dayplan.impactStay': '{name} məkanında qalma',
'dayplan.deleteDayBookingsHint': 'Onlar gün təyin edilmədən Rezervasiyalar bölməsində qalır.',
'dayplan.deleteDayStayHint': 'Giriş və ya çıxış bu günə düşdüyü üçün qalma qeydi ləğv edilir.',
@@ -144,9 +148,12 @@ const dayplan: TranslationStrings = {
'dayplan.deleteDayStayPaidHint':
'Giriş və ya çıxış bu günə düşdüyü üçün qalma qeydi, “{booking}” rezervasiyası və onun {amount} məbləğində xərci birlikdə ləğv edilir.',
'dayplan.deleteDayShift': 'Sonrakı günlər: {count}',
'dayplan.deleteDayShift.one': 'Sonrakı günlər: {count}',
'dayplan.deleteDayShiftHint': 'Hər biri bir tarix əvvələ keçiriləcək.',
'dayplan.deleteDayShiftBookingsHint':
'Hər biri bir tarix əvvələ keçiriləcək. Birlikdə köçürülən rezervasiyalar: {count}',
'dayplan.deleteDayShiftBookingsHint.one':
'Hər biri bir tarix əvvələ keçiriləcək. Birlikdə köçürülən rezervasiyalar: {count}',
'dayplan.deleteDayShrink': 'Səyahət artıq {date} tarixində bitir',
'dayplan.deleteDayShrinkHint': 'Son tarixi qəbul edəcək tarixsiz gün yoxdur.',
'dayplan.impactStayShorter': '{name} məkanında qalma: bir gecə az',
+1
View File
@@ -77,6 +77,7 @@ const docsync: TranslationStrings = {
'docsync.state.scope_drift': 'Qovluqdan kənara köçürülüb',
'docsync.conflict.resolve': '{count} ziddiyyəti həll et',
'docsync.conflict.resolve.one': '{count} ziddiyyəti həll et',
'docsync.conflict.title': 'Hər iki nüsxə dəyişdirilib',
'docsync.conflict.keepTrek': 'TREK versiyasını saxla',
+1
View File
@@ -800,6 +800,7 @@ const journey: TranslationStrings = {
'journey.suggestions.dismissed': 'Təklif bağlandı',
'journey.suggestions.restore': 'Bağlanmış təklifləri geri qaytar',
'journey.suggestions.restoreCount': 'Bağlanmış təkliflər ({count})',
'journey.suggestions.restoreCount.one': 'Bağlanmış təkliflər ({count})',
'journey.suggestions.restored': '{count} təklif geri qaytarıldı',
'journey.suggestions.restored.one': '{count} təklif geri qaytarıldı',
+2
View File
@@ -80,6 +80,8 @@ const memories: TranslationStrings = {
'Səyahət üzvləri ilə paylaşılsın?',
'memories.confirmShareHint':
'{count} foto bu səyahətin bütün üzvlərinə görünəcək. Daha sonra ayrı-ayrı fotoları şəxsi edə bilərsiniz.',
'memories.confirmShareHint.one':
'{count} foto bu səyahətin bütün üzvlərinə görünəcək. Daha sonra onu şəxsi edə bilərsiniz.',
'memories.confirmShareButton': 'Fotoları paylaş',
'memories.error.loadAlbums':
+4
View File
@@ -33,6 +33,7 @@ const packing: TranslationStrings = {
'packing.importHintMarkdown':
'Markdown siyahısı da işləyir: başlıq kateqoriyanı adlandırır, "- [ ]" və "- [x]" elementə çevrilir, adın önündəki "3x" isə miqdarı təyin edir.',
'packing.importAction': '{count} elementi idxal et',
'packing.importAction.one': '{count} elementi idxal et',
'packing.importSuccess': '{count} element idxal edildi',
'packing.importSuccess.one': '{count} element idxal edildi',
'packing.importError': 'İdxal uğursuz oldu',
@@ -41,7 +42,9 @@ const packing: TranslationStrings = {
'packing.progress':
'{total} elementdən {packed} yığılıb ({percent}%)',
'packing.clearChecked': 'İşarələnmiş {count} elementi sil',
'packing.clearChecked.one': 'İşarələnmiş {count} elementi sil',
'packing.clearCheckedShort': '{count} elementi sil',
'packing.clearCheckedShort.one': '{count} elementi sil',
'packing.allPacked': 'Hər şey yığılıb!',
@@ -107,6 +110,7 @@ const packing: TranslationStrings = {
'packing.takenCareOf': '{name} cavabdehdir',
'packing.sharedWithCount': '{count} nəfərlə paylaşılıb',
'packing.sharedWithCount.one': '{count} nəfərlə paylaşılıb',
'packing.broughtBy': '{name} gətirir',
'packing.alsoBring': 'Mən də bunu gətirə bilərəm',
'packing.alsoBringingStop': 'Mən bunu gətirməyəcəyəm',
+1
View File
@@ -16,6 +16,7 @@ const photos: TranslationStrings = {
'photos.linkPlace': 'Məkanla əlaqələndir',
'photos.noPlace': 'Məkan seçilməyib',
'photos.uploadN': '{n} foto yüklə',
'photos.uploadN.one': '{n} foto yüklə',
'photos.linkDay': 'Günlə əlaqələndir',
'photos.noDay': 'Gün seçilməyib',
'photos.dayLabel': '{number}-ci gün',
+1
View File
@@ -212,6 +212,7 @@ const reservations: TranslationStrings = {
'reservations.airtrail.otherFlights': 'Digər uçuşlar',
'reservations.airtrail.empty': 'AirTrail hesabınızda heç bir uçuş tapılmadı.',
'reservations.airtrail.importCta': '{count} uçuşu idxal et',
'reservations.airtrail.importCta.one': '{count} uçuşu idxal et',
'reservations.airtrail.joinConnection': '{stops} dayanacağında tranzit gözləmə ilə bir uçuş kimi idxal et',
'reservations.costsLabel': 'Xərclər',
+1
View File
@@ -192,6 +192,7 @@ const roadtrip: TranslationStrings = {
'roadtrip.poi.noMatch': 'Yolboyu “{name}” ilə uyğun heç nə yoxdur.',
'roadtrip.poi.noneMatchFilters': 'Yolboyu bu filtrlərə uyğun heç nə yoxdur.',
'roadtrip.poi.foundFiltered': 'Yolboyu {total} nəticədən {count}',
'roadtrip.poi.foundFiltered.one': 'Yolboyu {total} nəticədən {count}',
'roadtrip.poi.truncated':
'{count} marşrut hissəsində bir cavaba sığmayacaq qədər nəticə var. Qalanlarını görmək üçün axtarış dəhlizini daraldın.',
'roadtrip.poi.truncated.one':
+6 -2
View File
@@ -706,10 +706,13 @@ const settings: TranslationStrings = {
'settings.offline.banner.offline': 'Oflayn',
'settings.offline.banner.forced': 'Oflayn rejim',
'settings.offline.banner.queued': 'Oflayn · növbədə {count}',
'settings.offline.banner.queued.one': 'Oflayn · növbədə {count}',
'settings.offline.banner.syncing': '{count} sinxronlaşdırılır…',
'settings.offline.banner.failed':
'Sinxronlaşdırmaq mümkün olmadı: {count}',
'settings.offline.banner.syncing.one': '{count} sinxronlaşdırılır…',
'settings.offline.banner.failed': 'Sinxronlaşdırmaq mümkün olmadı: {count}',
'settings.offline.banner.failed.one': 'Sinxronlaşdırmaq mümkün olmadı: {count}',
'settings.offline.banner.conflicts': 'Ziddiyyətlər: {count}',
'settings.offline.banner.conflicts.one': 'Ziddiyyətlər: {count}',
'settings.alwaysShowRoutes':
'Rezervasiya marşrutlarını həmişə göstər',
@@ -725,6 +728,7 @@ const settings: TranslationStrings = {
'settings.apiScopes.noneSelected':
'Ən azı bir sahə seçin, əks halda açar heç bir məlumatı oxuya bilməyəcək.',
'settings.apiScopes.limited': '{total} sahədən {count}',
'settings.apiScopes.limited.one': '{total} sahədən {count}',
'settings.apiScopes.trips': 'Səyahətlər',
'settings.apiScopes.days': 'Günlər',
'settings.apiScopes.places': 'Məkanlar',
+1
View File
@@ -68,6 +68,7 @@ const tours: TranslationStrings = {
'tours.planner.waypoints': 'Yol nöqtələri',
'tours.planner.waypointLabel': 'Yol nöqtəsi {n}',
'tours.durationMinutes': '{count} dəq.',
'tours.durationMinutes.one': '{count} dəq.',
'tours.planner.empty': 'Piyada marşrutunu hesablamaq üçün ən azı iki nöqtə əlavə edin.',
'tours.planner.start': 'Başlanğıc',
'tours.planner.via': 'Aralıq nöqtə',
+1
View File
@@ -81,6 +81,7 @@ const trip: TranslationStrings = {
'transit.transfers': '{count} dəyişmə',
'transit.transfers.one': '{count} dəyişmə',
'transit.min': '{count} dəq.',
'transit.min.one': '{count} dəq.',
'transit.stops': '{count} dayanacaq',
'transit.stops.one': '{count} dayanacaq',
'transit.walkTo': '{name} istiqamətinə piyada gedin',
+1
View File
@@ -89,6 +89,7 @@ const vacay: TranslationStrings = {
'vacay.carryOverHint': 'Qalan məzuniyyət günlərini avtomatik olaraq növbəti ilə keçirin',
'vacay.carriedOverPrevPeriod': 'əvvəlki dövrdən',
'vacay.compUsedCount': '{count} kompensasiya / çevik gün',
'vacay.compUsedCount.one': '{count} kompensasiya / çevik gün',
'vacay.yearType': 'Məzuniyyət ili',
'vacay.yearTypeHint': 'Məzuniyyət ilinin nə vaxt başladığını seçin — limit, istifadə və növbəti dövrə keçirmə buna əsaslanır',
'vacay.yearTypeCalendar': 'Təqvim ili',
+3
View File
@@ -566,7 +566,9 @@ const admin: TranslationStrings = {
'admin.plugins.cap.requiresAddon': 'Requer {addon}',
'admin.plugins.cap.dependsOn': 'Requer {id} {version}',
'admin.plugins.dep.addonDisabledToast': 'Ative primeiro os complementos necessários: {addons}',
'admin.plugins.dep.addonDisabledToast.one': 'Ative primeiro o complemento necessário: {addons}',
'admin.plugins.dep.autoEnabled': 'Plugins necessários ativados primeiro: {plugins}',
'admin.plugins.dep.autoEnabled.one': 'Plugin necessário ativado primeiro: {plugins}',
'admin.plugins.dep.downloaded': '{id} baixado',
'admin.plugins.dep.resolveTitle': 'Dependências ausentes',
'admin.plugins.dep.resolveBody': '“{name}” precisa que estes plugins estejam instalados antes de ser ativado.',
@@ -668,6 +670,7 @@ const admin: TranslationStrings = {
'admin.audit.refresh': 'Atualizar',
'admin.audit.loadMore': 'Carregar mais',
'admin.audit.showing': '{count} carregados · {total} no total',
'admin.audit.showing.one': '{count} carregado · {total} no total',
'admin.audit.col.time': 'Hora',
'admin.audit.col.user': 'Usuário',
'admin.audit.col.action': 'Ação',
+3
View File
@@ -45,7 +45,9 @@ const collab: TranslationStrings = {
'collab.chat.loadMore': 'Carregar mensagens antigas',
'collab.chat.justNow': 'agora mesmo',
'collab.chat.minutesAgo': 'há {n} min',
'collab.chat.minutesAgo.one': 'há {n} min',
'collab.chat.hoursAgo': 'há {n} h',
'collab.chat.hoursAgo.one': 'há {n} h',
'collab.notes.title': 'Notas',
'collab.notes.new': 'Nova nota',
'collab.notes.empty': 'Nenhuma nota ainda',
@@ -69,6 +71,7 @@ const collab: TranslationStrings = {
'collab.notes.pin': 'Fixar',
'collab.notes.unpin': 'Desafixar',
'collab.notes.daysAgo': 'há {n} d',
'collab.notes.daysAgo.one': 'há {n} d',
'collab.notes.categorySettings': 'Gerenciar categorias',
'collab.notes.create': 'Criar',
'collab.notes.website': 'Site',
+10
View File
@@ -2,7 +2,9 @@ import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
'collections.saveNToList': 'Salvar {count} em uma lista',
'collections.saveNToList.one': 'Salvar {count} em uma lista',
'collections.addedNToList': '{count} salvos em {name}',
'collections.addedNToList.one': '{count} salvo em {name}',
'collections.noOwnLists': 'Você ainda não tem listas',
'collections.saveToListHint': 'Duplicados são ignorados automaticamente',
'collections.role.label': 'Função',
@@ -16,7 +18,9 @@ const collection: TranslationStrings = {
'collections.moveToList': 'Mover para lista',
'collections.duplicateToList': 'Duplicar em lista',
'collections.moveToListTitle': 'Mover {count} para outra lista',
'collections.moveToListTitle.one': 'Mover {count} para outra lista',
'collections.duplicateToListTitle': 'Duplicar {count} em outra lista',
'collections.duplicateToListTitle.one': 'Duplicar {count} em outra lista',
'collections.noOtherLists': 'Nenhuma outra lista ainda',
'collections.movedCount': '{count} lugares movidos',
'collections.movedCount.one': '{count} lugar movido',
@@ -56,6 +60,7 @@ const collection: TranslationStrings = {
'collections.importSelectedCount': '{count} selecionados',
'collections.importSelectedCount.one': '{count} selecionado',
'collections.importAction': 'Importar {count}',
'collections.importAction.one': 'Importar {count}',
'collections.importEmptyTrip': 'Esta viagem ainda não tem lugares',
'collections.importNothingNew': 'Tudo desta viagem já está nesta lista',
'collections.importDone': '{count} lugares adicionados',
@@ -115,6 +120,7 @@ const collection: TranslationStrings = {
'collections.copyToTrip': 'Copiar para viagem',
'collections.copyToTripTitle': 'Copiar para viagem',
'collections.copyN': 'Copiar {count} para viagem',
'collections.copyN.one': 'Copiar {count} para viagem',
'collections.copyToTripSearch': 'Buscar viagens',
'collections.searchLists': 'Buscar listas',
'collections.copyToTripConfirm': 'Copiar',
@@ -150,6 +156,7 @@ const collection: TranslationStrings = {
'collections.removedFromAll': 'Removido da coleção',
'collections.picker.title': 'Lugares salvos',
'collections.picker.showMore': 'Mostrar mais {count}',
'collections.picker.showMore.one': 'Mostrar mais {count}',
'collections.picker.search': 'Buscar nos seus lugares salvos',
'collections.picker.empty': 'Nenhum lugar salvo para adicionar',
'collections.picker.hint': 'Escolha um lugar das suas coleções',
@@ -172,6 +179,7 @@ const collection: TranslationStrings = {
'collections.file.doneInto': '{count} lugares adicionados a {name}',
'collections.file.doneInto.one': '{count} lugar adicionado a {name}',
'collections.file.doneIntoDuplicates': '{count} adicionados a {name}, {duplicates} já estavam lá',
'collections.file.doneIntoDuplicates.one': '{count} adicionado a {name}, {duplicates} já estavam lá',
'collections.file.doneIntoNothing': 'Todos os lugares do arquivo já estão em {name}',
'collections.file.change': 'Trocar',
'collections.file.labelCount': '{count} etiquetas',
@@ -203,6 +211,8 @@ const collection: TranslationStrings = {
'collections.file.errorNotGpx': 'Esse não é um arquivo GPX.',
'collections.file.errorTooManyPlaces':
'Esse arquivo tem mais de {count} lugares. Divida-o e importe as partes uma de cada vez.',
'collections.file.errorTooManyPlaces.one':
'Esse arquivo tem mais de {count} lugar. Divida-o e importe as partes uma de cada vez.',
'collections.share.title': 'Compartilhar lista',
'collections.share.titleNamed': 'Compartilhar “{name}”',
+2
View File
@@ -55,7 +55,9 @@ const common: TranslationStrings = {
'common.copied': 'Copiado',
'common.justNow': 'agora mesmo',
'common.hoursAgo': 'há {count}h',
'common.hoursAgo.one': 'há {count}h',
'common.daysAgo': 'há {count}d',
'common.daysAgo.one': 'há {count}d',
'common.datepicker.prevMonth': 'Previous month', // en-fallback
'common.datepicker.nextMonth': 'Next month', // en-fallback
'common.datepicker.prevYear': 'Previous year', // en-fallback
+3 -2
View File
@@ -6,8 +6,8 @@ const dashboard: TranslationStrings = {
'dashboard.subtitle.trips': '{count} viagens (arquivadas: {archived})',
'dashboard.subtitle.trips.one': '{count} viagem (arquivadas: {archived})',
'dashboard.subtitle.empty': 'Comece sua primeira viagem',
'dashboard.subtitle.activeOne': '{count} viagem ativa',
'dashboard.subtitle.activeMany': '{count} viagens ativas',
'dashboard.subtitle.active': '{count} viagens ativas',
'dashboard.subtitle.active.one': '{count} viagem ativa',
'dashboard.subtitle.archivedSuffix': ' · {count} arquivadas',
'dashboard.subtitle.archivedSuffix.one': ' · {count} arquivada',
'dashboard.newTrip': 'Nova viagem',
@@ -112,6 +112,7 @@ const dashboard: TranslationStrings = {
'dashboard.shrinkTitle': 'Remover dias?',
'dashboard.shrinkIntro': 'Ao salvar as novas datas, estes dias serão removidos:',
'dashboard.shrinkMoreDays': '+{count} outros',
'dashboard.shrinkMoreDays.one': '+{count} outro',
'dashboard.shrinkLastDays': 'Saem os últimos dias, não os primeiros',
'dashboard.shrinkLastDaysHint':
'Os planos de cada dia acompanham as novas datas, por isso são sempre os últimos dias do plano que saem, mesmo quando o início mudou.',
+2
View File
@@ -95,6 +95,7 @@ const dawarich: TranslationStrings = {
'dawarich.suggestions.dismissed': 'Dispensado',
'dawarich.suggestions.restore': 'Colocar de volta',
'dawarich.suggestions.showHandled': 'Mostrar {count} já resolvidas',
'dawarich.suggestions.showHandled.one': 'Mostrar {count} já resolvida',
'dawarich.suggestions.hideHandled': 'Ocultar as que já foram resolvidas',
'dawarich.suggestions.matchesWish': 'Na sua lista de desejos: {name}',
'dawarich.suggestions.acceptedAs.place': 'Adicionado como lugar',
@@ -146,6 +147,7 @@ const dawarich: TranslationStrings = {
'dawarich.bucket.noMatches': 'Nada da sua lista de desejos apareceu nas suas gravações.',
'dawarich.bucket.alreadyVisited': 'Já riscado',
'dawarich.bucket.confirm': 'Riscar {count}',
'dawarich.bucket.confirm.one': 'Riscar {count}',
'dawarich.bucket.confirmed': '{count} desejos riscados',
'dawarich.bucket.confirmed.one': '{count} desejo riscado',
'dawarich.bucket.skipped': '{count} entradas não têm coordenadas e não puderam ser conferidas.',
+6
View File
@@ -104,11 +104,15 @@ const dayplan: TranslationStrings = {
'Todos os lugares saem deste dia. Os lugares continuam na viagem, e o dia mantém suas notas e reservas.',
'dayplan.deleteDayEmpty': 'Nada está planejado para este dia.',
'dayplan.impactPlaces': 'Lugares planejados: {count}',
'dayplan.impactPlaces.one': 'Lugares planejados: {count}',
'dayplan.impactPlacesHint': 'Eles continuam na lista de lugares.',
'dayplan.impactNotes': 'Notas: {count}',
'dayplan.impactNotes.one': 'Notas: {count}',
'dayplan.impactTexts': 'Títulos e descrições do dia: {count}',
'dayplan.impactTexts.one': 'Títulos e descrições do dia: {count}',
'dayplan.impactDeletedHint': 'Também são excluídos.',
'dayplan.impactBookings': 'Reservas: {count}',
'dayplan.impactBookings.one': 'Reservas: {count}',
'dayplan.impactStay': 'Hospedagem em {name}',
'dayplan.deleteDayBookingsHint': 'Elas continuam em Reservas, sem dia.',
'dayplan.deleteDayStayHint': 'O check-in ou o check-out é neste dia, então a hospedagem é cancelada.',
@@ -119,8 +123,10 @@ const dayplan: TranslationStrings = {
'dayplan.deleteDayStayPaidHint':
'O check-in ou o check-out é neste dia, então a hospedagem é cancelada, junto com a reserva “{booking}” e a despesa dela de {amount}.',
'dayplan.deleteDayShift': 'Dias seguintes: {count}',
'dayplan.deleteDayShift.one': 'Dias seguintes: {count}',
'dayplan.deleteDayShiftHint': 'Cada um passa para a data anterior.',
'dayplan.deleteDayShiftBookingsHint': 'Cada um passa para a data anterior. Reservas que vão junto: {count}',
'dayplan.deleteDayShiftBookingsHint.one': 'Cada um passa para a data anterior. Reservas que vão junto: {count}',
'dayplan.deleteDayShrink': 'A viagem agora termina em {date}',
'dayplan.deleteDayShrinkHint': 'Não há nenhum dia sem data para assumir a última data.',
'dayplan.impactStayShorter': 'Hospedagem em {name}: uma noite a menos',
+1
View File
@@ -71,6 +71,7 @@ const docsync: TranslationStrings = {
'docsync.state.scope_drift': 'Saiu da pasta',
'docsync.conflict.resolve': 'Resolver {count}',
'docsync.conflict.resolve.one': 'Resolver {count}',
'docsync.conflict.title': 'As duas cópias mudaram',
'docsync.conflict.keepTrek': 'Manter a versão do TREK',
+1
View File
@@ -674,6 +674,7 @@ const journey: TranslationStrings = {
'journey.suggestions.dismissed': 'Sugestão descartada',
'journey.suggestions.restore': 'Trazer de volta as sugestões descartadas',
'journey.suggestions.restoreCount': 'Sugestões descartadas ({count})',
'journey.suggestions.restoreCount.one': 'Sugestões descartadas ({count})',
'journey.suggestions.restored': '{count} sugestões voltaram',
'journey.suggestions.restored.one': '{count} sugestão voltou',
'journey.detail.addOnThisDay': 'Adicionar um registro neste dia',
+2
View File
@@ -62,6 +62,8 @@ const memories: TranslationStrings = {
'memories.confirmShareTitle': 'Compartilhar com membros da viagem?',
'memories.confirmShareHint':
'{count} fotos serão visíveis para todos os membros desta viagem. Você pode tornar fotos individuais privadas depois.',
'memories.confirmShareHint.one':
'{count} foto será visível para todos os membros desta viagem. Você pode torná-la privada depois.',
'memories.confirmShareButton': 'Compartilhar fotos',
'memories.error.loadAlbums': 'Falha ao carregar álbuns',
'memories.error.linkAlbum': 'Falha ao vincular álbum',
+5 -1
View File
@@ -30,13 +30,16 @@ const packing: TranslationStrings = {
'packing.importHintMarkdown':
'Uma lista em Markdown também funciona: um título define a categoria, "- [ ]" e "- [x]" viram itens e "3x" antes do nome define a quantidade.',
'packing.importAction': 'Importar {count}',
'packing.importAction.one': 'Importar {count}',
'packing.importSuccess': '{count} itens importados',
'packing.importSuccess.one': '{count} item importado',
'packing.importError': 'Falha na importação',
'packing.importEmpty': 'Nenhum item para importar',
'packing.progress': '{packed} de {total} na mala ({percent}%)',
'packing.clearChecked': 'Remover {count} marcado(s)',
'packing.clearChecked': 'Remover {count} marcados',
'packing.clearChecked.one': 'Remover {count} marcado',
'packing.clearCheckedShort': 'Remover {count}',
'packing.clearCheckedShort.one': 'Remover {count}',
'packing.allPacked': 'Tudo na mala!',
'packing.addPlaceholder': 'Adicionar item...',
'packing.categoryPlaceholder': 'Lista...',
@@ -96,6 +99,7 @@ const packing: TranslationStrings = {
'packing.noOneToShare': 'Ninguém mais nesta viagem ainda',
'packing.takenCareOf': 'por {name}',
'packing.sharedWithCount': 'compartilhado com {count}',
'packing.sharedWithCount.one': 'compartilhado com {count}',
'packing.broughtBy': 'trazido por {name}',
'packing.alsoBring': 'Eu também posso levar',
'packing.alsoBringingStop': 'Não vou levar',
+2 -1
View File
@@ -15,7 +15,8 @@ const photos: TranslationStrings = {
'photos.clickToSelect': 'ou clique para selecionar',
'photos.linkPlace': 'Vincular lugar',
'photos.noPlace': 'Sem lugar',
'photos.uploadN': 'Enviar {n} foto(s)',
'photos.uploadN': 'Enviar {n} fotos',
'photos.uploadN.one': 'Enviar {n} foto',
'photos.linkDay': 'Vincular dia',
'photos.noDay': 'Nenhum dia',
'photos.dayLabel': 'Dia {number}',
+1
View File
@@ -178,6 +178,7 @@ const reservations: TranslationStrings = {
'reservations.airtrail.otherFlights': 'Outros voos',
'reservations.airtrail.empty': 'Nenhum voo encontrado na sua conta do AirTrail.',
'reservations.airtrail.importCta': 'Importar {count}',
'reservations.airtrail.importCta.one': 'Importar {count}',
'reservations.airtrail.joinConnection': 'Importar como um único voo com escala em {stops}',
'reservations.costsLabel': 'Custos',
'reservations.createExpense': 'Criar despesa',
+1
View File
@@ -197,6 +197,7 @@ const roadtrip: TranslationStrings = {
'roadtrip.poi.noMatch': 'Nada no trajeto corresponde a “{name}”.',
'roadtrip.poi.noneMatchFilters': 'Nada no caminho corresponde a esses filtros.',
'roadtrip.poi.foundFiltered': '{count} de {total} no trajeto',
'roadtrip.poi.foundFiltered.one': '{count} de {total} no trajeto',
'roadtrip.poi.truncated':
'{count} trechos tinham mais do que cabe em uma resposta. Estreite o corredor para ver o restante.',
'roadtrip.poi.truncated.one':
+5
View File
@@ -579,9 +579,13 @@ const settings: TranslationStrings = {
'settings.offline.banner.offline': 'Offline',
'settings.offline.banner.forced': 'Modo offline',
'settings.offline.banner.queued': 'Offline · {count} na fila',
'settings.offline.banner.queued.one': 'Offline · {count} na fila',
'settings.offline.banner.syncing': 'Sincronizando {count}…',
'settings.offline.banner.syncing.one': 'Sincronizando {count}…',
'settings.offline.banner.failed': 'Falha ao sincronizar: {count}',
'settings.offline.banner.failed.one': 'Falha ao sincronizar: {count}',
'settings.offline.banner.conflicts': 'Conflitos: {count}',
'settings.offline.banner.conflicts.one': 'Conflitos: {count}',
'settings.pluginActivity.title': 'Atividade dos plugins',
'settings.pluginActivity.description': 'Cada ação que um plugin realizou em seu nome, das mais recentes primeiro.',
'settings.pluginActivity.empty': 'Ainda não há atividade de plugins.',
@@ -603,6 +607,7 @@ const settings: TranslationStrings = {
'settings.apiScopes.all': 'Tudo',
'settings.apiScopes.noneSelected': 'Escolha pelo menos uma área, senão a chave não poderia ler nada.',
'settings.apiScopes.limited': '{count} de {total}',
'settings.apiScopes.limited.one': '{count} de {total}',
'settings.apiScopes.trips': 'Viagens',
'settings.apiScopes.days': 'Dias',
'settings.apiScopes.places': 'Lugares',
+1
View File
@@ -69,6 +69,7 @@ const tours: TranslationStrings = {
'tours.planner.waypoints': 'Pontos de passagem',
'tours.planner.waypointLabel': 'Ponto de passagem {n}',
'tours.durationMinutes': '{count} min',
'tours.durationMinutes.one': '{count} min',
'tours.planner.empty': 'Adicione pelo menos dois pontos para calcular uma rota a pé.',
'tours.planner.start': 'Partida',
'tours.planner.via': 'Passagem por',
+1
View File
@@ -73,6 +73,7 @@ const trip: TranslationStrings = {
'transit.transfers': '{count} baldeações',
'transit.transfers.one': '{count} baldeação',
'transit.min': '{count} min',
'transit.min.one': '{count} min',
'transit.stops': '{count} paradas',
'transit.stops.one': '{count} parada',
'transit.walkTo': 'Caminhar até {name}',
+1
View File
@@ -89,6 +89,7 @@ const vacay: TranslationStrings = {
'vacay.carryOverHint': 'Levar automaticamente os dias de férias restantes para o ano seguinte',
'vacay.carriedOverPrevPeriod': 'do período anterior',
'vacay.compUsedCount': '{count} banco de horas',
'vacay.compUsedCount.one': '{count} banco de horas',
'vacay.yearType': 'Ano de férias',
'vacay.yearTypeHint': 'Quando começa seu ano de férias — direito, uso e acúmulo seguem essa data',
'vacay.yearTypeCalendar': 'Calendário',
+3
View File
@@ -312,6 +312,7 @@ const admin: TranslationStrings = {
'admin.audit.refresh': 'Actualitzar',
'admin.audit.loadMore': 'Carregar més',
'admin.audit.showing': '{count} carregats · {total} en total',
'admin.audit.showing.one': '{count} carregat · {total} en total',
'admin.audit.col.time': 'Data i hora',
'admin.audit.col.user': 'Usuari',
'admin.audit.col.action': 'Acció',
@@ -818,7 +819,9 @@ const admin: TranslationStrings = {
'admin.plugins.cap.requiresAddon': 'Requereix {addon}',
'admin.plugins.cap.dependsOn': 'Necessita {id} {version}',
'admin.plugins.dep.addonDisabledToast': 'Activa primer els complements necessaris: {addons}',
'admin.plugins.dep.addonDisabledToast.one': 'Activa primer el complement necessari: {addons}',
'admin.plugins.dep.autoEnabled': "S'han activat primer els connectors necessaris: {plugins}",
'admin.plugins.dep.autoEnabled.one': "S'ha activat primer el connector necessari: {plugins}",
'admin.plugins.dep.downloaded': "S'ha baixat {id}",
'admin.plugins.dep.resolveTitle': 'Dependències que falten',
'admin.plugins.dep.resolveBody':
+3
View File
@@ -37,7 +37,9 @@ const collab: TranslationStrings = {
'collab.chat.loadMore': 'Carrega missatges anteriors',
'collab.chat.justNow': 'ara mateix',
'collab.chat.minutesAgo': 'fa {n} min',
'collab.chat.minutesAgo.one': 'fa {n} min',
'collab.chat.hoursAgo': 'fa {n} h',
'collab.chat.hoursAgo.one': 'fa {n} h',
'collab.notes.title': 'Notes',
'collab.notes.new': 'Nota nova',
'collab.notes.empty': 'Encara no hi ha notes',
@@ -61,6 +63,7 @@ const collab: TranslationStrings = {
'collab.notes.pin': 'Fixa',
'collab.notes.unpin': 'Desfixa',
'collab.notes.daysAgo': 'fa {n} d',
'collab.notes.daysAgo.one': 'fa {n} d',
'collab.notes.categorySettings': 'Gestiona les categories',
'collab.notes.create': 'Crea',
'collab.notes.website': 'Lloc web',
+10
View File
@@ -2,7 +2,9 @@ import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
'collections.saveNToList': 'Desa {count} a una llista',
'collections.saveNToList.one': 'Desa {count} a una llista',
'collections.addedNToList': 'Desat {count} a {name}',
'collections.addedNToList.one': 'Desat {count} a {name}',
'collections.noOwnLists': 'Encara no tens cap llista',
'collections.saveToListHint': "Els duplicats s'ometen automàticament",
'collections.role.label': 'Rol',
@@ -16,7 +18,9 @@ const collection: TranslationStrings = {
'collections.moveToList': 'Moure a la llista',
'collections.duplicateToList': 'Duplicar a la llista',
'collections.moveToListTitle': 'Mou {count} a una altra llista',
'collections.moveToListTitle.one': 'Mou {count} a una altra llista',
'collections.duplicateToListTitle': 'Duplica {count} a una altra llista',
'collections.duplicateToListTitle.one': 'Duplica {count} a una altra llista',
'collections.noOtherLists': 'Encara no hi ha cap altra llista',
'collections.movedCount': "S'han mogut {count} llocs",
'collections.movedCount.one': "S'ha mogut {count} lloc",
@@ -56,6 +60,7 @@ const collection: TranslationStrings = {
'collections.importSelectedCount': '{count} seleccionats',
'collections.importSelectedCount.one': '{count} seleccionat',
'collections.importAction': 'Importa {count}',
'collections.importAction.one': 'Importa {count}',
'collections.importEmptyTrip': 'Aquest viatge encara no té llocs',
'collections.importNothingNew': 'Tot aquest viatge ja és en aquesta llista',
'collections.importDone': 'S’han afegit {count} llocs',
@@ -110,6 +115,7 @@ const collection: TranslationStrings = {
'collections.copyToTrip': 'Copiar al viatge',
'collections.copyToTripTitle': 'Copiar al viatge',
'collections.copyN': 'Copia {count} al viatge',
'collections.copyN.one': 'Copia al viatge',
'collections.copyToTripSearch': 'Cercar viatges',
'collections.searchLists': 'Cerca llistes',
'collections.copyToTripConfirm': 'Copiar',
@@ -142,6 +148,7 @@ const collection: TranslationStrings = {
'collections.removedFromAll': 'Eliminat de la col·lecció',
'collections.picker.title': 'Llocs desats',
'collections.picker.showMore': "Mostra'n {count} més",
'collections.picker.showMore.one': "Mostra'n {count} més",
'collections.picker.search': 'Cerca entre els teus llocs desats',
'collections.picker.empty': 'No hi ha llocs desats per afegir',
'collections.picker.hint': 'Tria un lloc de les teves col·leccions',
@@ -164,6 +171,7 @@ const collection: TranslationStrings = {
'collections.file.doneInto': '{count} llocs afegits a {name}',
'collections.file.doneInto.one': '{count} lloc afegit a {name}',
'collections.file.doneIntoDuplicates': '{count} afegits a {name}, {duplicates} ja hi eren',
'collections.file.doneIntoDuplicates.one': '{count} afegit a {name}, {duplicates} ja hi eren',
'collections.file.doneIntoNothing': 'Tots els llocs del fitxer ja són a {name}',
'collections.file.change': 'Canvia',
'collections.file.labelCount': '{count} etiquetes',
@@ -195,6 +203,8 @@ const collection: TranslationStrings = {
'collections.file.errorNotGpx': 'Això no és un fitxer GPX.',
'collections.file.errorTooManyPlaces':
'Aquest fitxer té més de {count} llocs. Divideix-lo i importa’n les parts d’una en una.',
'collections.file.errorTooManyPlaces.one':
'Aquest fitxer té més de {count} lloc. Divideix-lo i importa’n les parts d’una en una.',
'collections.share.title': 'Compartir llista',
'collections.share.titleNamed': 'Compartir «{name}»',
+2
View File
@@ -55,7 +55,9 @@ const common: TranslationStrings = {
'common.copied': 'Copiat',
'common.justNow': 'ara mateix',
'common.hoursAgo': 'fa {count} h',
'common.hoursAgo.one': 'fa {count} h',
'common.daysAgo': 'fa {count} d',
'common.daysAgo.one': 'fa {count} d',
'common.datepicker.prevMonth': 'Mes anterior',
'common.datepicker.nextMonth': 'Mes següent',
+3 -2
View File
@@ -6,8 +6,8 @@ const dashboard: TranslationStrings = {
'dashboard.subtitle.trips': '{count} viatges (arxivats: {archived})',
'dashboard.subtitle.trips.one': '{count} viatge (arxivats: {archived})',
'dashboard.subtitle.empty': 'Comença el teu primer viatge',
'dashboard.subtitle.activeOne': '{count} viatge actiu',
'dashboard.subtitle.activeMany': '{count} viatges actius',
'dashboard.subtitle.active': '{count} viatges actius',
'dashboard.subtitle.active.one': '{count} viatge actiu',
'dashboard.subtitle.archivedSuffix': ' · {count} arxivats',
'dashboard.subtitle.archivedSuffix.one': ' · {count} arxivat',
'dashboard.newTrip': 'Viatge nou',
@@ -200,6 +200,7 @@ const dashboard: TranslationStrings = {
'dashboard.shrinkTitle': 'Vols treure dies?',
'dashboard.shrinkIntro': 'En desar les noves dates es treuen aquests dies:',
'dashboard.shrinkMoreDays': '+{count} més',
'dashboard.shrinkMoreDays.one': '+{count} més',
'dashboard.shrinkLastDays': 'Es treuen els últims dies, no els primers',
'dashboard.shrinkLastDaysHint':
"Els plans de cada dia es mouen amb les noves dates, així que sempre es treuen els últims dies del pla, també quan ha canviat l'inici.",
+2
View File
@@ -94,6 +94,7 @@ const dawarich: TranslationStrings = {
'dawarich.suggestions.dismissed': 'Descartat',
'dawarich.suggestions.restore': 'Torna-ho a posar',
'dawarich.suggestions.showHandled': 'Mostra’n {count} de ja resolts',
'dawarich.suggestions.showHandled.one': 'Mostra’n {count} de ja resolt',
'dawarich.suggestions.hideHandled': 'Amaga els que ja estan resolts',
'dawarich.suggestions.matchesWish': 'A la teva llista de desitjos: {name}',
'dawarich.suggestions.acceptedAs.place': 'Afegit com a lloc',
@@ -145,6 +146,7 @@ const dawarich: TranslationStrings = {
'dawarich.bucket.noMatches': 'No ha aparegut res de la teva llista de desitjos als teus enregistraments.',
'dawarich.bucket.alreadyVisited': 'Ja marcat',
'dawarich.bucket.confirm': 'Marca’n {count}',
'dawarich.bucket.confirm.one': 'Marca’n {count}',
'dawarich.bucket.confirmed': '{count} desitjos marcats',
'dawarich.bucket.confirmed.one': '{count} desig marcat',
'dawarich.bucket.skipped': '{count} entrades no tenen coordenades i no s’han pogut comprovar.',
+7
View File
@@ -104,11 +104,15 @@ const dayplan: TranslationStrings = {
'Tots els llocs surten d’aquest dia. Els llocs es queden al viatge, i el dia conserva les notes i les reserves.',
'dayplan.deleteDayEmpty': 'No hi ha res planificat aquest dia.',
'dayplan.impactPlaces': 'Llocs planificats: {count}',
'dayplan.impactPlaces.one': 'Llocs planificats: {count}',
'dayplan.impactPlacesHint': 'Es queden a la llista de llocs.',
'dayplan.impactNotes': 'Notes: {count}',
'dayplan.impactNotes.one': 'Notes: {count}',
'dayplan.impactTexts': 'Títols i descripcions del dia: {count}',
'dayplan.impactTexts.one': 'Títols i descripcions del dia: {count}',
'dayplan.impactDeletedHint': "També s'eliminen.",
'dayplan.impactBookings': 'Reserves: {count}',
'dayplan.impactBookings.one': 'Reserves: {count}',
'dayplan.impactStay': 'Estada a {name}',
'dayplan.deleteDayBookingsHint': 'Es queden a Reserves, sense dia.',
'dayplan.deleteDayStayHint': "L'entrada o la sortida és aquest dia, així que l'estada es cancel·la.",
@@ -119,8 +123,11 @@ const dayplan: TranslationStrings = {
'dayplan.deleteDayStayPaidHint':
"L'entrada o la sortida és aquest dia, així que l'estada es cancel·la, juntament amb la reserva «{booking}» i la seva despesa de {amount}.",
'dayplan.deleteDayShift': 'Dies posteriors: {count}',
'dayplan.deleteDayShift.one': 'Dies posteriors: {count}',
'dayplan.deleteDayShiftHint': 'Cadascun passa a la data anterior.',
'dayplan.deleteDayShiftBookingsHint': 'Cadascun passa a la data anterior. Reserves que es mouen amb ells: {count}',
'dayplan.deleteDayShiftBookingsHint.one':
'Cadascun passa a la data anterior. Reserves que es mouen amb ells: {count}',
'dayplan.deleteDayShrink': 'Ara el viatge acaba el {date}',
'dayplan.deleteDayShrinkHint': "No queda cap dia sense data que pugui agafar l'última data.",
'dayplan.impactStayShorter': 'Estada a {name}: una nit menys',
+1
View File
@@ -72,6 +72,7 @@ const docsync: TranslationStrings = {
'docsync.state.scope_drift': 'Ha sortit de la carpeta',
'docsync.conflict.resolve': 'Resol {count}',
'docsync.conflict.resolve.one': 'Resol {count}',
'docsync.conflict.title': 'Han canviat totes dues còpies',
'docsync.conflict.keepTrek': 'Conserva la versió de TREK',
+1
View File
@@ -677,6 +677,7 @@ const journey: TranslationStrings = {
'journey.suggestions.dismissed': 'Suggeriment descartat',
'journey.suggestions.restore': 'Recupera els suggeriments descartats',
'journey.suggestions.restoreCount': 'Suggeriments descartats ({count})',
'journey.suggestions.restoreCount.one': 'Suggeriments descartats ({count})',
'journey.suggestions.restored': 'S’han recuperat {count} suggeriments',
'journey.suggestions.restored.one': 'S’ha recuperat {count} suggeriment',
'journey.detail.addOnThisDay': 'Afegeix una entrada en aquest dia',
+2
View File
@@ -65,6 +65,8 @@ const memories: TranslationStrings = {
'memories.confirmShareTitle': 'Vols compartir amb els membres del viatge?',
'memories.confirmShareHint':
"{count} fotos seran visibles per a tots els membres d'aquest viatge. Pots fer fotos individuals privades més tard.",
'memories.confirmShareHint.one':
"{count} foto serà visible per a tots els membres d'aquest viatge. La pots fer privada més tard.",
'memories.confirmShareButton': 'Comparteix les fotos',
'memories.error.loadAlbums': 'Error en carregar els àlbums',
'memories.error.linkAlbum': "Error en vincular l'àlbum",
+4
View File
@@ -19,13 +19,16 @@ const packing: TranslationStrings = {
'packing.importHintMarkdown':
'També funciona una llista en Markdown: un títol indica la categoria, "- [ ]" i "- [x]" es converteixen en elements, i "3x" davant d’un nom en fixa la quantitat.',
'packing.importAction': 'Importa {count}',
'packing.importAction.one': 'Importa {count}',
'packing.importSuccess': '{count} elements importats',
'packing.importSuccess.one': '{count} element importat',
'packing.importError': 'Error en importar',
'packing.importEmpty': 'Sense elements per importar',
'packing.progress': '{packed} de {total} preparats ({percent}%)',
'packing.clearChecked': 'Elimina {count} marcats',
'packing.clearChecked.one': 'Elimina {count} marcat',
'packing.clearCheckedShort': 'Elimina {count}',
'packing.clearCheckedShort.one': 'Elimina {count}',
'packing.allPacked': 'Tot preparat!',
'packing.addPlaceholder': 'Afegeix un element nou...',
'packing.categoryPlaceholder': 'Categoria...',
@@ -85,6 +88,7 @@ const packing: TranslationStrings = {
'packing.noOneToShare': 'Encara no hi ha ningú més en aquest viatge',
'packing.takenCareOf': 'per {name}',
'packing.sharedWithCount': 'compartit amb {count}',
'packing.sharedWithCount.one': 'compartit amb {count}',
'packing.broughtBy': 'portat per {name}',
'packing.alsoBring': 'Jo també puc portar això',
'packing.alsoBringingStop': 'Ja no ho porto',
+2 -1
View File
@@ -15,7 +15,8 @@ const photos: TranslationStrings = {
'photos.clickToSelect': 'o fes clic per seleccionar',
'photos.linkPlace': 'Vincula un lloc',
'photos.noPlace': 'Sense lloc',
'photos.uploadN': 'Pujada de {n} foto(es)',
'photos.uploadN': 'Pujada de {n} fotos',
'photos.uploadN.one': 'Pujada de {n} foto',
'photos.linkDay': 'Vincula un dia',
'photos.noDay': 'Cap dia',
'photos.dayLabel': 'Dia {number}',
+1
View File
@@ -183,6 +183,7 @@ const reservations: TranslationStrings = {
'reservations.airtrail.otherFlights': 'Altres vols',
'reservations.airtrail.empty': "No s'han trobat vols al teu compte d'AirTrail.",
'reservations.airtrail.importCta': 'Importar {count}',
'reservations.airtrail.importCta.one': 'Importar {count}',
'reservations.costsLabel': 'Despeses',
'reservations.createExpense': 'Crear despesa',
'reservations.createExpenseHint': "Desa la reserva i obre l'editor de Despeses.",
+1
View File
@@ -200,6 +200,7 @@ const roadtrip: TranslationStrings = {
'roadtrip.poi.noMatch': 'Res del trajecte no coincideix amb «{name}».',
'roadtrip.poi.noneMatchFilters': 'Res del camí no coincideix amb aquests filtres.',
'roadtrip.poi.foundFiltered': '{count} de {total} al trajecte',
'roadtrip.poi.foundFiltered.one': '{count} de {total} al trajecte',
'roadtrip.poi.truncated':
'En {count} trams hi havia més del que cap en una resposta: estreny el corredor per veure la resta.',
'roadtrip.poi.truncated.one':
+5
View File
@@ -554,9 +554,13 @@ const settings: TranslationStrings = {
'settings.offline.banner.offline': 'Fora de línia',
'settings.offline.banner.forced': 'Mode fora de línia',
'settings.offline.banner.queued': 'Fora de línia · {count} a la cua',
'settings.offline.banner.queued.one': 'Fora de línia · {count} a la cua',
'settings.offline.banner.syncing': 'Sincronitzant {count}…',
'settings.offline.banner.syncing.one': 'Sincronitzant {count}…',
'settings.offline.banner.failed': 'Error en sincronitzar: {count}',
'settings.offline.banner.failed.one': 'Error en sincronitzar: {count}',
'settings.offline.banner.conflicts': 'Conflictes: {count}',
'settings.offline.banner.conflicts.one': 'Conflictes: {count}',
'settings.plugins.oauth.connected': 'Connectat',
'settings.plugins.oauth.notConnected': 'No connectat',
'settings.plugins.oauth.connect': 'Connecta',
@@ -605,6 +609,7 @@ const settings: TranslationStrings = {
'settings.apiScopes.all': 'Tot',
'settings.apiScopes.noneSelected': 'Tria almenys un àmbit; si no, la clau no podria llegir res.',
'settings.apiScopes.limited': '{count} de {total}',
'settings.apiScopes.limited.one': '{count} de {total}',
'settings.apiScopes.trips': 'Viatges',
'settings.apiScopes.days': 'Dies',
'settings.apiScopes.places': 'Llocs',
+1
View File
@@ -68,6 +68,7 @@ const tours: TranslationStrings = {
'tours.planner.waypoints': 'Punts de pas',
'tours.planner.waypointLabel': 'Punt de pas {n}',
'tours.durationMinutes': '{count} min',
'tours.durationMinutes.one': '{count} min',
'tours.planner.empty': 'Afegeix almenys dos punts per calcular una ruta a peu.',
'tours.planner.start': 'Inici',
'tours.planner.via': 'Pas per',
+1
View File
@@ -69,6 +69,7 @@ const trip: TranslationStrings = {
'transit.transfers': '{count} transbords',
'transit.transfers.one': '{count} transbord',
'transit.min': '{count} min',
'transit.min.one': '{count} min',
'transit.stops': '{count} parades',
'transit.stops.one': '{count} parada',
'transit.walkTo': 'Camina fins a {name}',
+1
View File
@@ -84,6 +84,7 @@ const vacay: TranslationStrings = {
'vacay.carryOverHint': "Trasllada automàticament els dies restants a l'any següent",
'vacay.carriedOverPrevPeriod': 'del període anterior',
'vacay.compUsedCount': '{count} comp.',
'vacay.compUsedCount.one': '{count} comp.',
'vacay.yearType': 'Any de vacances',
'vacay.yearTypeHint': 'Quan comença el teu any de vacances — dret, ús i saldo arrossegat hi van lligats',
'vacay.yearTypeCalendar': 'Natural',
+7 -1
View File
@@ -572,7 +572,11 @@ const admin: TranslationStrings = {
'admin.plugins.cap.requiresAddon': 'Vyžaduje {addon}',
'admin.plugins.cap.dependsOn': 'Vyžaduje {id} {version}',
'admin.plugins.dep.addonDisabledToast': 'Nejprve povolte potřebné doplňky: {addons}',
'admin.plugins.dep.addonDisabledToast.one': 'Nejprve povolte potřebný doplněk: {addons}',
'admin.plugins.dep.addonDisabledToast.few': 'Nejprve povolte potřebné doplňky: {addons}',
'admin.plugins.dep.autoEnabled': 'Nejprve povoleny potřebné pluginy: {plugins}',
'admin.plugins.dep.autoEnabled.one': 'Nejprve povolen potřebný plugin: {plugins}',
'admin.plugins.dep.autoEnabled.few': 'Nejprve povoleny potřebné pluginy: {plugins}',
'admin.plugins.dep.downloaded': 'Staženo {id}',
'admin.plugins.dep.resolveTitle': 'Chybějící závislosti',
'admin.plugins.dep.resolveBody': '„{name}“ před povolením potřebuje nainstalovat tyto pluginy.',
@@ -659,7 +663,9 @@ const admin: TranslationStrings = {
'admin.audit.empty': 'Zatím žádné záznamy auditu.',
'admin.audit.refresh': 'Obnovit',
'admin.audit.loadMore': 'Načíst další',
'admin.audit.showing': '{count} načteno · {total} celkem',
'admin.audit.showing': 'Načteno {count} záznamů · celkem {total}',
'admin.audit.showing.one': 'Načten {count} záznam · celkem {total}',
'admin.audit.showing.few': 'Načteny {count} záznamy · celkem {total}',
'admin.audit.col.time': 'Čas',
'admin.audit.col.user': 'Uživatel',
'admin.audit.col.action': 'Akce',
+6
View File
@@ -45,7 +45,11 @@ const collab: TranslationStrings = {
'collab.chat.loadMore': 'Načíst starší zprávy',
'collab.chat.justNow': 'právě teď',
'collab.chat.minutesAgo': 'před {n} min',
'collab.chat.minutesAgo.one': 'před {n} min',
'collab.chat.minutesAgo.few': 'před {n} min',
'collab.chat.hoursAgo': 'před {n} h',
'collab.chat.hoursAgo.one': 'před {n} h',
'collab.chat.hoursAgo.few': 'před {n} h',
'collab.notes.title': 'Poznámky',
'collab.notes.new': 'Nová poznámka',
'collab.notes.empty': 'Zatím žádné poznámky',
@@ -63,6 +67,8 @@ const collab: TranslationStrings = {
'collab.notes.pin': 'Připnout',
'collab.notes.unpin': 'Odepnout',
'collab.notes.daysAgo': 'před {n} dny',
'collab.notes.daysAgo.one': 'před {n} dnem',
'collab.notes.daysAgo.few': 'před {n} dny',
'collab.notes.categorySettings': 'Spravovat kategorie',
'collab.notes.create': 'Vytvořit',
'collab.notes.website': 'Webové stránky',
+20
View File
@@ -2,7 +2,11 @@ import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
'collections.saveNToList': 'Uložit {count} do seznamu',
'collections.saveNToList.one': 'Uložit {count} do seznamu',
'collections.saveNToList.few': 'Uložit {count} do seznamu',
'collections.addedNToList': 'Uloženo {count} do {name}',
'collections.addedNToList.one': 'Uloženo {count} do {name}',
'collections.addedNToList.few': 'Uloženo {count} do {name}',
'collections.noOwnLists': 'Zatím nemáte žádné seznamy',
'collections.saveToListHint': 'Duplikáty se automaticky přeskočí',
'collections.role.label': 'Role',
@@ -16,7 +20,11 @@ const collection: TranslationStrings = {
'collections.moveToList': 'Přesunout do seznamu',
'collections.duplicateToList': 'Duplikovat do seznamu',
'collections.moveToListTitle': 'Přesunout {count} do jiného seznamu',
'collections.moveToListTitle.one': 'Přesunout {count} do jiného seznamu',
'collections.moveToListTitle.few': 'Přesunout {count} do jiného seznamu',
'collections.duplicateToListTitle': 'Duplikovat {count} do jiného seznamu',
'collections.duplicateToListTitle.one': 'Duplikovat {count} do jiného seznamu',
'collections.duplicateToListTitle.few': 'Duplikovat {count} do jiného seznamu',
'collections.noOtherLists': 'Zatím žádné další seznamy',
'collections.movedCount': 'Přesunuto {count} míst',
'collections.movedCount.one': 'Přesunuto {count} místo',
@@ -61,6 +69,8 @@ const collection: TranslationStrings = {
'collections.importSelectedCount.one': 'Vybráno: {count}',
'collections.importSelectedCount.few': 'Vybráno: {count}',
'collections.importAction': 'Importovat {count}',
'collections.importAction.one': 'Importovat {count}',
'collections.importAction.few': 'Importovat {count}',
'collections.importEmptyTrip': 'Tato cesta zatím nemá žádná místa',
'collections.importNothingNew': 'Vše z této cesty už na seznamu je',
'collections.importDone': 'Přidáno {count} míst',
@@ -122,6 +132,8 @@ const collection: TranslationStrings = {
'collections.copyToTrip': 'Kopírovat do výletu',
'collections.copyToTripTitle': 'Kopírovat do výletu',
'collections.copyN': 'Kopírovat {count} do výletu',
'collections.copyN.one': 'Kopírovat do výletu',
'collections.copyN.few': 'Kopírovat {count} do výletu',
'collections.copyToTripSearch': 'Hledat výlety',
'collections.searchLists': 'Hledat seznamy',
'collections.copyToTripConfirm': 'Kopírovat',
@@ -160,6 +172,8 @@ const collection: TranslationStrings = {
'collections.removedFromAll': 'Odebráno ze sbírky',
'collections.picker.title': 'Uložená místa',
'collections.picker.showMore': 'Zobrazit dalších {count}',
'collections.picker.showMore.one': 'Zobrazit ještě {count}',
'collections.picker.showMore.few': 'Zobrazit další {count}',
'collections.picker.search': 'Prohledat uložená místa',
'collections.picker.empty': 'Žádná uložená místa k přidání',
'collections.picker.hint': 'Vyberte místo ze svých sbírek',
@@ -183,6 +197,8 @@ const collection: TranslationStrings = {
'collections.file.doneInto.one': '{count} místo přidáno do {name}',
'collections.file.doneInto.few': '{count} místa přidána do {name}',
'collections.file.doneIntoDuplicates': '{count} přidáno do {name}, {duplicates} už tam bylo',
'collections.file.doneIntoDuplicates.one': '{count} přidáno do {name}, {duplicates} už tam bylo',
'collections.file.doneIntoDuplicates.few': '{count} přidáno do {name}, {duplicates} už tam bylo',
'collections.file.doneIntoNothing': 'Všechna místa ze souboru už v {name} jsou',
'collections.file.change': 'Změnit',
'collections.file.labelCount': '{count} štítků',
@@ -217,6 +233,10 @@ const collection: TranslationStrings = {
'collections.file.errorNotGpx': 'Toto není soubor GPX.',
'collections.file.errorTooManyPlaces':
'Tento soubor obsahuje více než {count} míst. Rozdělte ho a importujte části postupně.',
'collections.file.errorTooManyPlaces.one':
'Tento soubor obsahuje více než {count} místo. Rozdělte ho a importujte části postupně.',
'collections.file.errorTooManyPlaces.few':
'Tento soubor obsahuje více než {count} místa. Rozdělte ho a importujte části postupně.',
'collections.share.title': 'Sdílet seznam',
'collections.share.titleNamed': 'Sdílet „{name}“',
+4
View File
@@ -55,7 +55,11 @@ const common: TranslationStrings = {
'common.copied': 'Zkopírováno',
'common.justNow': 'právě teď',
'common.hoursAgo': 'před {count} h',
'common.hoursAgo.one': 'před {count} h',
'common.hoursAgo.few': 'před {count} h',
'common.daysAgo': 'před {count} d',
'common.daysAgo.one': 'před {count} d',
'common.daysAgo.few': 'před {count} d',
'common.datepicker.prevMonth': 'Previous month', // en-fallback
'common.datepicker.nextMonth': 'Next month', // en-fallback
'common.datepicker.prevYear': 'Previous year', // en-fallback
+6 -3
View File
@@ -7,8 +7,9 @@ const dashboard: TranslationStrings = {
'dashboard.subtitle.trips.one': '{count} cesta ({archived} v archivu)',
'dashboard.subtitle.trips.few': '{count} cesty ({archived} v archivu)',
'dashboard.subtitle.empty': 'Začněte svou první cestu',
'dashboard.subtitle.activeOne': '{count} aktivní cesta',
'dashboard.subtitle.activeMany': '{count} aktivních cest',
'dashboard.subtitle.active': '{count} aktivních cest',
'dashboard.subtitle.active.one': '{count} aktivní cesta',
'dashboard.subtitle.active.few': '{count} aktivní cesty',
'dashboard.subtitle.archivedSuffix': ' · {count} archivovaných',
'dashboard.subtitle.archivedSuffix.one': ' · {count} archivovaná',
'dashboard.subtitle.archivedSuffix.few': ' · {count} archivované',
@@ -117,7 +118,9 @@ const dashboard: TranslationStrings = {
'Tip: pokud chcete posunout jen část itineráře, otevřete v plánovači „Změnit pořadí dnů“, přidejte den po konci cesty tlačítkem s jeho datem a přesuňte ho tam, kde má vzniknout mezera.',
'dashboard.shrinkTitle': 'Odebrat dny?',
'dashboard.shrinkIntro': 'Uložením nových dat se odeberou tyto dny:',
'dashboard.shrinkMoreDays': '+{count} další',
'dashboard.shrinkMoreDays': '+{count} dalších',
'dashboard.shrinkMoreDays.one': '+{count} další',
'dashboard.shrinkMoreDays.few': '+{count} další',
'dashboard.shrinkLastDays': 'Odeberou se poslední dny, ne první',
'dashboard.shrinkLastDaysHint':
'Plány dnů se posouvají s novými daty, takže se vždy odeberou poslední dny plánu, i když se posunul začátek.',
+4
View File
@@ -97,6 +97,8 @@ const dawarich: TranslationStrings = {
'dawarich.suggestions.dismissed': 'Zamítnuto',
'dawarich.suggestions.restore': 'Vrátit zpět',
'dawarich.suggestions.showHandled': 'Zobrazit {count} již vyřízených',
'dawarich.suggestions.showHandled.one': 'Zobrazit {count} již vyřízený',
'dawarich.suggestions.showHandled.few': 'Zobrazit {count} již vyřízené',
'dawarich.suggestions.hideHandled': 'Skrýt už vyřízené',
'dawarich.suggestions.matchesWish': 'Na vašem seznamu přání: {name}',
'dawarich.suggestions.acceptedAs.place': 'Přidáno jako místo',
@@ -148,6 +150,8 @@ const dawarich: TranslationStrings = {
'dawarich.bucket.noMatches': 'Ve vašich záznamech se neobjevilo nic z vašeho seznamu přání.',
'dawarich.bucket.alreadyVisited': 'Již odškrtnuto',
'dawarich.bucket.confirm': 'Odškrtnout {count}',
'dawarich.bucket.confirm.one': 'Odškrtnout {count}',
'dawarich.bucket.confirm.few': 'Odškrtnout {count}',
'dawarich.bucket.confirmed': 'Odškrtnuto {count} přání',
'dawarich.bucket.confirmed.one': 'Odškrtnuto {count} přání',
'dawarich.bucket.confirmed.few': 'Odškrtnuta {count} přání',
+14
View File
@@ -104,11 +104,19 @@ const dayplan: TranslationStrings = {
'Všechna místa se z tohoto dne odeberou. Místa zůstanou v cestě a den si ponechá své poznámky i rezervace.',
'dayplan.deleteDayEmpty': 'Na tento den není nic naplánováno.',
'dayplan.impactPlaces': 'Naplánovaná místa: {count}',
'dayplan.impactPlaces.one': 'Naplánovaná místa: {count}',
'dayplan.impactPlaces.few': 'Naplánovaná místa: {count}',
'dayplan.impactPlacesHint': 'Zůstanou v seznamu míst.',
'dayplan.impactNotes': 'Poznámky: {count}',
'dayplan.impactNotes.one': 'Poznámky: {count}',
'dayplan.impactNotes.few': 'Poznámky: {count}',
'dayplan.impactTexts': 'Názvy a popisy dne: {count}',
'dayplan.impactTexts.one': 'Názvy a popisy dne: {count}',
'dayplan.impactTexts.few': 'Názvy a popisy dne: {count}',
'dayplan.impactDeletedHint': 'Budou také smazány.',
'dayplan.impactBookings': 'Rezervace: {count}',
'dayplan.impactBookings.one': 'Rezervace: {count}',
'dayplan.impactBookings.few': 'Rezervace: {count}',
'dayplan.impactStay': 'Ubytování v {name}',
'dayplan.deleteDayBookingsHint': 'Zůstanou v Rezervacích, bez dne.',
'dayplan.deleteDayStayHint': 'Příjezd nebo odjezd připadá na tento den, proto se ubytování zruší.',
@@ -119,9 +127,15 @@ const dayplan: TranslationStrings = {
'dayplan.deleteDayStayPaidHint':
'Příjezd nebo odjezd připadá na tento den, proto se ubytování zruší spolu s rezervací „{booking}“ a jejím výdajem {amount}.',
'dayplan.deleteDayShift': 'Pozdější dny: {count}',
'dayplan.deleteDayShift.one': 'Pozdější dny: {count}',
'dayplan.deleteDayShift.few': 'Pozdější dny: {count}',
'dayplan.deleteDayShiftHint': 'Každý se posune o jedno datum dříve.',
'dayplan.deleteDayShiftBookingsHint':
'Každý se posune o jedno datum dříve. Rezervace, které se posunou s nimi: {count}',
'dayplan.deleteDayShiftBookingsHint.one':
'Každý se posune o jedno datum dříve. Rezervace, které se posunou s nimi: {count}',
'dayplan.deleteDayShiftBookingsHint.few':
'Každý se posune o jedno datum dříve. Rezervace, které se posunou s nimi: {count}',
'dayplan.deleteDayShrink': 'Cesta nyní končí {date}',
'dayplan.deleteDayShrinkHint': 'Nezbývá žádný den bez data, který by převzal poslední datum.',
'dayplan.impactStayShorter': 'Ubytování v {name}: o noc kratší',
+2
View File
@@ -70,6 +70,8 @@ const docsync: TranslationStrings = {
'docsync.state.scope_drift': 'Přesunuto mimo složku',
'docsync.conflict.resolve': 'Vyřešit {count}',
'docsync.conflict.resolve.one': 'Vyřešit {count}',
'docsync.conflict.resolve.few': 'Vyřešit {count}',
'docsync.conflict.title': 'Změnily se obě kopie',
'docsync.conflict.keepTrek': 'Ponechat verzi z TREKu',
+2
View File
@@ -679,6 +679,8 @@ const journey: TranslationStrings = {
'journey.suggestions.dismissed': 'Návrh zahozen',
'journey.suggestions.restore': 'Vrátit zahozené návrhy',
'journey.suggestions.restoreCount': 'Zahozené návrhy ({count})',
'journey.suggestions.restoreCount.one': 'Zahozené návrhy ({count})',
'journey.suggestions.restoreCount.few': 'Zahozené návrhy ({count})',
'journey.suggestions.restored': 'Vrátilo se {count} návrhů',
'journey.suggestions.restored.one': 'Vrátil se {count} návrh',
'journey.suggestions.restored.few': 'Vrátily se {count} návrhy',
+4
View File
@@ -63,6 +63,10 @@ const memories: TranslationStrings = {
'memories.confirmShareTitle': 'Sdílet se členy cesty?',
'memories.confirmShareHint':
'{count} fotek bude viditelných pro všechny členy této cesty. Jednotlivé fotky můžete později nastavit jako soukromé.',
'memories.confirmShareHint.one':
'{count} fotka bude viditelná pro všechny členy této cesty. Později ji můžete nastavit jako soukromou.',
'memories.confirmShareHint.few':
'{count} fotky budou viditelné pro všechny členy této cesty. Jednotlivé fotky můžete později nastavit jako soukromé.',
'memories.confirmShareButton': 'Sdílet fotky',
'memories.error.loadAlbums': 'Načtení alb se nezdařilo',
'memories.error.linkAlbum': 'Propojení alba se nezdařilo',
+10 -2
View File
@@ -31,14 +31,20 @@ const packing: TranslationStrings = {
'packing.importHintMarkdown':
'Funguje i seznam v Markdownu: nadpis určuje kategorii, z "- [ ]" a "- [x]" se stanou položky a "3x" před názvem nastaví množství.',
'packing.importAction': 'Importovat {count}',
'packing.importAction.one': 'Importovat {count}',
'packing.importAction.few': 'Importovat {count}',
'packing.importSuccess': '{count} položek importováno',
'packing.importSuccess.one': '{count} položka importována',
'packing.importSuccess.few': '{count} položky importovány',
'packing.importError': 'Import se nezdařil',
'packing.importEmpty': 'Žádné položky k importu',
'packing.progress': '{packed} z {total} zabaleno ({percent} %)',
'packing.clearChecked': 'Odstranit {count} hotových',
'packing.clearChecked': 'Odstranit {count} hotových položek',
'packing.clearChecked.one': 'Odstranit {count} hotovou položku',
'packing.clearChecked.few': 'Odstranit {count} hotové položky',
'packing.clearCheckedShort': 'Odstranit {count}',
'packing.clearCheckedShort.one': 'Odstranit {count}',
'packing.clearCheckedShort.few': 'Odstranit {count}',
'packing.allPacked': 'Vše je zabaleno!',
'packing.addPlaceholder': 'Přidat novou položku...',
'packing.categoryPlaceholder': 'Seznam...',
@@ -100,7 +106,9 @@ const packing: TranslationStrings = {
'packing.tierShared': 'Sdílet s…',
'packing.noOneToShare': 'Zatím nikdo další na této cestě',
'packing.takenCareOf': 'od {name}',
'packing.sharedWithCount': 'sdíleno s {count}',
'packing.sharedWithCount': 'sdíleno s {count} osobami',
'packing.sharedWithCount.one': 'sdíleno s {count} osobou',
'packing.sharedWithCount.few': 'sdíleno s {count} osobami',
'packing.broughtBy': 'přináší {name}',
'packing.alsoBring': 'Můžu to vzít taky',
'packing.alsoBringingStop': 'Neberu to',
+2
View File
@@ -17,6 +17,8 @@ const photos: TranslationStrings = {
'photos.linkPlace': 'Propojit s místem',
'photos.noPlace': 'Žádné místo',
'photos.uploadN': 'Nahrát {n} fotek',
'photos.uploadN.one': 'Nahrát {n} fotku',
'photos.uploadN.few': 'Nahrát {n} fotky',
'photos.linkDay': 'Propojit den',
'photos.noDay': 'Žádný den',
'photos.dayLabel': 'Den {number}',
+2
View File
@@ -183,6 +183,8 @@ const reservations: TranslationStrings = {
'reservations.airtrail.otherFlights': 'Ostatní lety',
'reservations.airtrail.empty': 'Ve vašem účtu AirTrail nebyly nalezeny žádné lety.',
'reservations.airtrail.importCta': 'Importovat {count}',
'reservations.airtrail.importCta.one': 'Importovat {count}',
'reservations.airtrail.importCta.few': 'Importovat {count}',
'reservations.airtrail.joinConnection': 'Importovat jako jeden let s přestupem v {stops}',
'reservations.costsLabel': 'Náklady',
'reservations.createExpense': 'Vytvořit výdaj',
+2
View File
@@ -200,6 +200,8 @@ const roadtrip: TranslationStrings = {
'roadtrip.poi.noMatch': 'Na trase nic neodpovídá „{name}“.',
'roadtrip.poi.noneMatchFilters': 'Nic na trase neodpovídá těmto filtrům.',
'roadtrip.poi.foundFiltered': '{count} z {total} na trase',
'roadtrip.poi.foundFiltered.one': '{count} z {total} na trase',
'roadtrip.poi.foundFiltered.few': '{count} z {total} na trase',
'roadtrip.poi.truncated':
'Na {count} úsecích bylo více výsledků, než se vejde do jedné odpovědi. Zužte koridor, abyste viděli zbytek.',
'roadtrip.poi.truncated.one':
+10
View File
@@ -569,9 +569,17 @@ const settings: TranslationStrings = {
'settings.offline.banner.offline': 'Offline',
'settings.offline.banner.forced': 'Offline režim',
'settings.offline.banner.queued': 'Offline · {count} ve frontě',
'settings.offline.banner.queued.one': 'Offline · {count} ve frontě',
'settings.offline.banner.queued.few': 'Offline · {count} ve frontě',
'settings.offline.banner.syncing': 'Synchronizace {count}…',
'settings.offline.banner.syncing.one': 'Synchronizace {count}…',
'settings.offline.banner.syncing.few': 'Synchronizace {count}…',
'settings.offline.banner.failed': 'Synchronizace selhala: {count}',
'settings.offline.banner.failed.one': 'Synchronizace selhala: {count}',
'settings.offline.banner.failed.few': 'Synchronizace selhala: {count}',
'settings.offline.banner.conflicts': 'Konflikty: {count}',
'settings.offline.banner.conflicts.one': 'Konflikty: {count}',
'settings.offline.banner.conflicts.few': 'Konflikty: {count}',
'settings.pluginActivity.title': 'Aktivita pluginů',
'settings.pluginActivity.description': 'Každá akce, kterou plugin provedl vaším jménem, od nejnovějších.',
'settings.pluginActivity.empty': 'Zatím žádná aktivita pluginů.',
@@ -593,6 +601,8 @@ const settings: TranslationStrings = {
'settings.apiScopes.all': 'Vše',
'settings.apiScopes.noneSelected': 'Vyber aspoň jednu oblast, jinak by klíč nemohl číst nic.',
'settings.apiScopes.limited': '{count} z {total}',
'settings.apiScopes.limited.one': '{count} z {total}',
'settings.apiScopes.limited.few': '{count} z {total}',
'settings.apiScopes.trips': 'Cesty',
'settings.apiScopes.days': 'Dny',
'settings.apiScopes.places': 'Místa',
+2
View File
@@ -67,6 +67,8 @@ const tours: TranslationStrings = {
'tours.planner.waypoints': 'Průjezdní body',
'tours.planner.waypointLabel': 'Bod {n}',
'tours.durationMinutes': '{count} min',
'tours.durationMinutes.one': '{count} min',
'tours.durationMinutes.few': '{count} min',
'tours.planner.empty': 'Pro výpočet pěší trasy přidejte alespoň dva body.',
'tours.planner.start': 'Start',
'tours.planner.via': 'Přes',

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