mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 15:06:32 +00:00
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:
+1
-1
@@ -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>
|
||||
|
||||
@@ -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
@@ -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`)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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': 'الإجراء',
|
||||
|
||||
@@ -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': 'الموقع الإلكتروني',
|
||||
|
||||
@@ -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}»',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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':
|
||||
'تنتقل خطط الأيام مع التواريخ الجديدة، لذا تُزال دائمًا الأيام الأخيرة من خطتك، حتى عندما تغيّر تاريخ البداية.',
|
||||
|
||||
@@ -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': 'تم شطب أمنية واحدة',
|
||||
|
||||
@@ -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}: ليلة أقل',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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': 'تمت استعادة اقتراح واحد',
|
||||
|
||||
@@ -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': 'فشل ربط الألبوم',
|
||||
|
||||
@@ -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': 'لن أحضره',
|
||||
|
||||
@@ -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}',
|
||||
|
||||
@@ -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': 'إنشاء مصروف',
|
||||
|
||||
@@ -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': 'في مقطع واحد كانت النتائج أكثر مما تتسع له استجابة واحدة. قلّص الممر لرؤية البقية.',
|
||||
|
||||
@@ -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': 'الأماكن',
|
||||
|
||||
@@ -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': 'المرور عبر',
|
||||
|
||||
@@ -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': 'محطة واحدة',
|
||||
|
||||
@@ -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': 'تقويمية',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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ş',
|
||||
|
||||
@@ -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ş',
|
||||
|
||||
@@ -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.',
|
||||
|
||||
@@ -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ı.',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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ı',
|
||||
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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ə',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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}”',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.',
|
||||
|
||||
@@ -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.',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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}',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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}',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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}»',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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}',
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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}',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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}“',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.',
|
||||
|
||||
@@ -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í',
|
||||
|
||||
@@ -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ší',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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}',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user