diff --git a/client/src/components/Collab/CollabPolls.tsx b/client/src/components/Collab/CollabPolls.tsx
index 295aefbcf..0ee5d9afb 100644
--- a/client/src/components/Collab/CollabPolls.tsx
+++ b/client/src/components/Collab/CollabPolls.tsx
@@ -241,7 +241,7 @@ function PollCard({ poll, currentUser, canEdit, onVote, onClose, onDelete, t }:
{isClosed && }>{t('collab.polls.closed')}}
{remaining && !isClosed && }>{remaining}}
{poll.multiple_choice && {t('collab.polls.multiChoice')} }
- {t(total === 1 ? 'collab.polls.vote' : 'collab.polls.votes', { n: total })}
+ {t('collab.polls.votes', { n: total })}
{canEdit && (
diff --git a/client/src/components/Files/FileManagerFilesView.tsx b/client/src/components/Files/FileManagerFilesView.tsx
index 3c976b589..3456a7037 100644
--- a/client/src/components/Files/FileManagerFilesView.tsx
+++ b/client/src/components/Files/FileManagerFilesView.tsx
@@ -56,7 +56,7 @@ export function FilesView(S: FileManagerState) {
}}>{tab.icon ? : tab.label}
))}
- {filteredFiles.length === 1 ? t('files.countSingular') : t('files.count', { count: filteredFiles.length })}
+ {t('files.count', { count: filteredFiles.length })}
diff --git a/client/src/components/Journey/JourneyDayDawarich.tsx b/client/src/components/Journey/JourneyDayDawarich.tsx
index 821d20b49..b8b25dc9f 100644
--- a/client/src/components/Journey/JourneyDayDawarich.tsx
+++ b/client/src/components/Journey/JourneyDayDawarich.tsx
@@ -60,13 +60,7 @@ export default function JourneyDayDawarich({
- {/* Two keys picked by the count, the shape this codebase uses for a number
- that changes its noun: there is no plural engine in the i18n layer, and one
- key with {count} in it reads as "1 stays". */}
- {t(
- suggestions.length === 1 ? 'dawarich.journey.dayStays.one' : 'dawarich.journey.dayStays.other',
- { count: suggestions.length },
- )}
+ {t('dawarich.journey.dayStays', { count: suggestions.length })}
{span && {span} }
diff --git a/client/src/components/Journey/JourneyDetailPageGalleryView.test.tsx b/client/src/components/Journey/JourneyDetailPageGalleryView.test.tsx
index a16e004de..3d997b182 100644
--- a/client/src/components/Journey/JourneyDetailPageGalleryView.test.tsx
+++ b/client/src/components/Journey/JourneyDetailPageGalleryView.test.tsx
@@ -297,7 +297,7 @@ describe('GalleryView', () => {
await waitFor(() => expect(onRefresh).toHaveBeenCalledTimes(1))
expect(bodies[0]).toEqual({ provider: 'immich', asset_ids: ['asset-1'], media_types: ['image'] })
- expect(toastSpy).toHaveBeenCalledWith('1 photos added', 'success', undefined)
+ expect(toastSpy).toHaveBeenCalledWith('1 photo added', 'success', undefined)
expect(screen.queryByRole('heading', { name: 'Immich' })).not.toBeInTheDocument()
})
diff --git a/client/src/components/Planner/AirTrailImportModal.test.tsx b/client/src/components/Planner/AirTrailImportModal.test.tsx
index e994898bc..5bc103ffc 100644
--- a/client/src/components/Planner/AirTrailImportModal.test.tsx
+++ b/client/src/components/Planner/AirTrailImportModal.test.tsx
@@ -288,7 +288,7 @@ describe('AirTrailImportModal', () => {
await user.click(await screen.findByRole('button', { name: /Import 1/i }));
await waitFor(() => expect(pushUndo).toHaveBeenCalled());
- expect(toasts).toContainEqual(['success', '1 flight(s) imported']);
+ expect(toasts).toContainEqual(['success', '1 flight imported']);
expect(toasts).toContainEqual(['warning', '1 already in this trip, skipped']);
expect(pushUndo.mock.calls[0][0]).toBe('Import from AirTrail');
diff --git a/client/src/components/Settings/AirTrailConnectionSection.test.tsx b/client/src/components/Settings/AirTrailConnectionSection.test.tsx
index 2669ba1f8..cefc47a5c 100644
--- a/client/src/components/Settings/AirTrailConnectionSection.test.tsx
+++ b/client/src/components/Settings/AirTrailConnectionSection.test.tsx
@@ -247,7 +247,7 @@ describe('AirTrailConnectionSection', () => {
await screen.findByDisplayValue('https://air.example.com');
await user.click(screen.getByRole('button', { name: /Test connection/ }));
- await screen.findByText('Connected — 12 flight(s) found');
+ await screen.findByText('Connected. 12 flights found');
expect(screen.getByText('Connected')).toBeInTheDocument();
expect(body).toEqual({ url: 'https://air.example.com', allowInsecureTls: false });
});
@@ -263,7 +263,7 @@ describe('AirTrailConnectionSection', () => {
await screen.findByDisplayValue('https://air.example.com');
await user.click(screen.getByRole('button', { name: /Test connection/ }));
- expect(await screen.findByText('Connected — 0 flight(s) found')).toBeInTheDocument();
+ expect(await screen.findByText('Connected. 0 flights found')).toBeInTheDocument();
});
it('FE-COMP-AIRTRAIL-015: a refused test shows the returned error and keeps the badge off', async () => {
diff --git a/client/src/components/Settings/OfflineTab.test.tsx b/client/src/components/Settings/OfflineTab.test.tsx
index 2f4467f16..74c8613db 100644
--- a/client/src/components/Settings/OfflineTab.test.tsx
+++ b/client/src/components/Settings/OfflineTab.test.tsx
@@ -319,7 +319,7 @@ describe('OfflineTab', () => {
expect(screen.getByRole('button', { name: 'Downloading…' })).toBeDisabled();
await act(async () => { release(); });
- expect(await screen.findByText('Stored 4 trip(s) on this device')).toBeInTheDocument();
+ expect(await screen.findByText('Stored 4 trips on this device')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Download for offline use' })).toBeEnabled();
});
diff --git a/client/src/components/Tours/ToursSidebar.tsx b/client/src/components/Tours/ToursSidebar.tsx
index b7c741aca..1042c9078 100644
--- a/client/src/components/Tours/ToursSidebar.tsx
+++ b/client/src/components/Tours/ToursSidebar.tsx
@@ -70,7 +70,7 @@ export default function ToursSidebar({ tripId, days, tours, loading = false, sel
// enter the shared store used by the day map.
await onToursChanged?.(result.tours.map(tour => tour.place_id))
await loadTrip(tripId)
- toast.success(t(result.tours.length === 1 ? 'tours.import.successOne' : 'tours.import.success', { count: result.tours.length }))
+ toast.success(t('tours.import.success', { count: result.tours.length }))
if (result.caution) toast.info(t('tours.import.caution'))
} else {
toast.warning(t('places.importAllSkipped'))
diff --git a/client/src/components/Trips/TripMembersModal.tsx b/client/src/components/Trips/TripMembersModal.tsx
index 87112e4db..b11b7c33c 100644
--- a/client/src/components/Trips/TripMembersModal.tsx
+++ b/client/src/components/Trips/TripMembersModal.tsx
@@ -240,7 +240,7 @@ function TripMembersSheet({ isOpen, onClose, tripId, tripTitle, onMembersChanged
- {t('members.access')} ({realMembers.length} {realMembers.length === 1 ? t('members.person') : t('members.persons')})
+ {t('members.access')} ({realMembers.length} {t('members.persons', { count: realMembers.length })})
diff --git a/client/src/components/Trips/TripShareDialog.tsx b/client/src/components/Trips/TripShareDialog.tsx
index 61dd432da..adb421ec6 100644
--- a/client/src/components/Trips/TripShareDialog.tsx
+++ b/client/src/components/Trips/TripShareDialog.tsx
@@ -41,7 +41,7 @@ function OpenDialog({ onClose, tripId, tripTitle, onMembersChanged }: TripShareD
const labelId = useId()
const m = useTripMembers({ isOpen: true, tripId, onClose, onMembersChanged })
const [ask, setAsk] = useState(null)
- const persons = `${m.realMembers.length} ${m.realMembers.length === 1 ? t('members.person') : t('members.persons')}`
+ const persons = `${m.realMembers.length} ${t('members.persons', { count: m.realMembers.length })}`
return (
<>
diff --git a/client/src/i18n/TranslationContext.tsx b/client/src/i18n/TranslationContext.tsx
index 4ee437cdf..940b82312 100644
--- a/client/src/i18n/TranslationContext.tsx
+++ b/client/src/i18n/TranslationContext.tsx
@@ -12,6 +12,7 @@ import {
} from '@trek/shared'
import type { TranslationStrings } from '@trek/shared/i18n'
import { importChunk } from '../utils/chunkReload'
+import { resolveTemplate } from './resolveTemplate'
export { SUPPORTED_LANGUAGES }
@@ -134,8 +135,12 @@ export function TranslationProvider({ children }: TranslationProviderProps) {
}, [language])
const value = useMemo((): TranslationContextValue => {
+ const intlLanguage = getIntlLanguage(language)
+ const template = (key: string, params?: Record): string =>
+ resolveTemplate(strings, intlLanguage, key, params)
+
function t(key: string, params?: Record): string {
- let val: string = (strings[key] ?? en[key] ?? key) as string
+ let val = template(key, params)
if (params) {
Object.entries(params).forEach(([k, v]) => {
// Function replacement: a value carrying `$&` or `$1` (a trip named
@@ -147,7 +152,7 @@ export function TranslationProvider({ children }: TranslationProviderProps) {
}
function tHtml(key: string, params?: Record): string {
- let val: string = (strings[key] ?? en[key] ?? key) as string
+ let val = template(key, params)
if (params) {
Object.entries(params).forEach(([k, v]) => {
// Escape BEFORE substitution so a user-controlled value with `<` or
diff --git a/client/src/i18n/resolveTemplate.test.ts b/client/src/i18n/resolveTemplate.test.ts
new file mode 100644
index 000000000..7328251cb
--- /dev/null
+++ b/client/src/i18n/resolveTemplate.test.ts
@@ -0,0 +1,48 @@
+import { describe, expect, it } from 'vitest'
+import type { TranslationStrings } from '@trek/shared/i18n'
+import { resolveTemplate } from './resolveTemplate'
+
+const ru = {
+ 'x.places': '{count} места',
+ 'x.places.one': '{count} место',
+ 'x.places.few': '{count} места',
+ 'x.places.many': '{count} мест',
+} as unknown as TranslationStrings
+
+describe('resolveTemplate', () => {
+ it('picks the form the language selects, not count === 1', () => {
+ expect(resolveTemplate(ru, 'ru', 'x.places', { count: 1 })).toBe('{count} место')
+ expect(resolveTemplate(ru, 'ru', 'x.places', { count: 21 })).toBe('{count} место')
+ expect(resolveTemplate(ru, 'ru', 'x.places', { count: 3 })).toBe('{count} места')
+ expect(resolveTemplate(ru, 'ru', 'x.places', { count: 5 })).toBe('{count} мест')
+ expect(resolveTemplate(ru, 'ru', 'x.places', { count: 11 })).toBe('{count} мест')
+ })
+
+ it('a form the locale does not spell out falls back to its general form, never to English', () => {
+ const ja = { 'x.places': '{count}件の場所' } as unknown as TranslationStrings
+ expect(resolveTemplate(ja, 'ja', 'x.places', { count: 1 })).toBe('{count}件の場所')
+ const ruWithoutMany = { 'x.places': '{count} места', 'x.places.one': '{count} место' } as unknown as TranslationStrings
+ expect(resolveTemplate(ruWithoutMany, 'ru', 'x.places', { count: 5 })).toBe('{count} места')
+ })
+
+ it('a key the locale lacks resolves in English with English rules', () => {
+ // 'places.count' and its .one form ship in en.
+ const empty = {} as TranslationStrings
+ expect(resolveTemplate(empty, 'ru', 'places.count', { count: 1 })).toBe('{count} place')
+ expect(resolveTemplate(empty, 'ru', 'places.count', { count: 2 })).toBe('{count} places')
+ })
+
+ it('reads n when there is no count, and ignores a count that is not a number', () => {
+ expect(resolveTemplate(ru, 'ru', 'x.places', { n: 5 })).toBe('{count} мест')
+ expect(resolveTemplate(ru, 'ru', 'x.places', { count: 'five' })).toBe('{count} места')
+ expect(resolveTemplate(ru, 'ru', 'x.places', { count: Number.NaN })).toBe('{count} места')
+ })
+
+ it('a group with only category keys still resolves, and an unknown key is the key', () => {
+ const groupOnly = { 'x.stays.one': 'one stay', 'x.stays.other': '{count} stays' } as unknown as TranslationStrings
+ expect(resolveTemplate(groupOnly, 'en', 'x.stays', { count: 1 })).toBe('one stay')
+ expect(resolveTemplate(groupOnly, 'en', 'x.stays', { count: 4 })).toBe('{count} stays')
+ expect(resolveTemplate(groupOnly, 'en', 'x.nothing', { count: 4 })).toBe('x.nothing')
+ expect(resolveTemplate(groupOnly, 'en', 'x.nothing')).toBe('x.nothing')
+ })
+})
diff --git a/client/src/i18n/resolveTemplate.ts b/client/src/i18n/resolveTemplate.ts
new file mode 100644
index 000000000..f6f31d145
--- /dev/null
+++ b/client/src/i18n/resolveTemplate.ts
@@ -0,0 +1,28 @@
+import en from '@trek/shared/i18n/en'
+import { resolvePluralKey } from '@trek/shared'
+import type { TranslationStrings } from '@trek/shared/i18n'
+
+/**
+ * The template a key reads as. A numeric `count` (or `n`) picks the plural
+ * form the language's own rule selects (see `@trek/shared` plural helpers):
+ * `places.count.one`, `.few`, ... and the key itself as the general form.
+ * The active locale is asked with its rule first; English, with English's,
+ * only when the locale has none of the key's forms.
+ */
+export function resolveTemplate(
+ strings: TranslationStrings,
+ intlLanguage: string,
+ key: string,
+ params?: Record,
+): string {
+ const raw = params?.count ?? params?.n
+ const count = typeof raw === 'number' && Number.isFinite(raw) ? raw : undefined
+ if (count !== undefined) {
+ const own = resolvePluralKey(strings, key, count, intlLanguage)
+ if (own) return strings[own] as string
+ const fallback = resolvePluralKey(en, key, count, 'en')
+ if (fallback) return en[fallback] as string
+ return key
+ }
+ return (strings[key] ?? en[key] ?? key) as string
+}
diff --git a/client/src/mobile/screens/dashboard/MDashboard.tsx b/client/src/mobile/screens/dashboard/MDashboard.tsx
index ccfbc3a79..f5e357c41 100644
--- a/client/src/mobile/screens/dashboard/MDashboard.tsx
+++ b/client/src/mobile/screens/dashboard/MDashboard.tsx
@@ -403,9 +403,9 @@ function MSpotlightCard({ trip, t, onOpen, actions }: {
)}
- } label={days === 1 ? t('dashboard.mobile.spotlightDayOne', { count: days }) : t('dashboard.mobile.spotlightDaysMany', { count: days })} />
- } label={places === 1 ? t('dashboard.hero.destinationOne', { count: places }) : t('dashboard.hero.destinationMany', { count: places })} />
- } label={people === 1 ? t('dashboard.hero.travelerOne', { count: people }) : t('dashboard.hero.travelerMany', { count: people })} />
+ } label={t('dashboard.mobile.spotlightDays', { count: days })} />
+ } label={t('dashboard.hero.destinations', { count: places })} />
+ } label={t('dashboard.hero.travelers', { count: people })} />
@@ -564,7 +564,7 @@ function MTripListCard({ trip, locale, t, badge, pluginBadges, actions, onOpen }
-
+
diff --git a/client/src/mobile/screens/trip/places/MPlacesBrowser.tsx b/client/src/mobile/screens/trip/places/MPlacesBrowser.tsx
index 986736d94..3856794a4 100644
--- a/client/src/mobile/screens/trip/places/MPlacesBrowser.tsx
+++ b/client/src/mobile/screens/trip/places/MPlacesBrowser.tsx
@@ -331,7 +331,7 @@ export default function MPlacesBrowser({ planner, shell }: MPlacesBrowserProps)
- {filtered.length === 1 ? t('places.countSingular') : t('places.count', { count: filtered.length })}
+ {t('places.count', { count: filtered.length })}
diff --git a/client/src/mobile/screens/trip/roadtrip/MRtCorridorSheet.tsx b/client/src/mobile/screens/trip/roadtrip/MRtCorridorSheet.tsx
index 393dac72d..d2e510293 100644
--- a/client/src/mobile/screens/trip/roadtrip/MRtCorridorSheet.tsx
+++ b/client/src/mobile/screens/trip/roadtrip/MRtCorridorSheet.tsx
@@ -187,14 +187,8 @@ function StatusBand({ corridor, t }: { corridor: MRtCorridorController; t: Trans
// The phone's own wording, because the desk's ends in "narrow the corridor" and the
// corridor width is a control this screen deliberately does not have. What it does
// have is the category chips, and fewer of them really is what makes a box fit.
- // Two keys picked by the count: the i18n layer has no plural engine, so a single key
- // with {count} in a sentence that names the noun once reads as "1 stretches".
if (corridor.truncatedAreas > 0) {
- notes.push(
- t(corridor.truncatedAreas === 1 ? 'mobileTrip.rtTruncated.one' : 'mobileTrip.rtTruncated.other', {
- count: corridor.truncatedAreas,
- }),
- )
+ notes.push(t('mobileTrip.rtTruncated', { count: corridor.truncatedAreas }))
}
if (corridor.offline) return }>{t('mobileTrip.rtSearchOffline')}
diff --git a/client/src/mobile/screens/trip/tabs/MCollabPolls.tsx b/client/src/mobile/screens/trip/tabs/MCollabPolls.tsx
index c5031ff5d..50164c676 100644
--- a/client/src/mobile/screens/trip/tabs/MCollabPolls.tsx
+++ b/client/src/mobile/screens/trip/tabs/MCollabPolls.tsx
@@ -281,7 +281,7 @@ function PollCardRow({ poll, canEdit, currentUserId, t, onVote, onClosePoll, onD
)}
- {t(total === 1 ? 'collab.polls.vote' : 'collab.polls.votes', { n: total })}
+ {t('collab.polls.votes', { n: total })}
diff --git a/client/src/pages/AtlasPage.tsx b/client/src/pages/AtlasPage.tsx
index 88d7a9367..3354bd3fc 100644
--- a/client/src/pages/AtlasPage.tsx
+++ b/client/src/pages/AtlasPage.tsx
@@ -798,7 +798,7 @@ function SidebarContent({ data, stats, regionsVisited, countryRegions, onOpenPla
{streak}
- {streak === 1 ? t('atlas.yearInRow') : t('atlas.yearsInRow')}
+ {t('atlas.yearsInRow', { count: streak })}
)}
@@ -807,7 +807,7 @@ function SidebarContent({ data, stats, regionsVisited, countryRegions, onOpenPla
{tripsThisYear}
- {tripsThisYear === 1 ? t('atlas.tripIn') : t('atlas.tripsIn')} {thisYear}
+ {t('atlas.tripsIn', { count: tripsThisYear })} {thisYear}
)}
diff --git a/client/src/pages/AtlasPage.wiring.test.tsx b/client/src/pages/AtlasPage.wiring.test.tsx
index ad6fc1c02..74e86aaeb 100644
--- a/client/src/pages/AtlasPage.wiring.test.tsx
+++ b/client/src/pages/AtlasPage.wiring.test.tsx
@@ -428,9 +428,9 @@ describe('AtlasPage wiring', () => {
expect(screen.getAllByText('9')).toHaveLength(2);
expect(screen.getAllByText('40')).toHaveLength(2);
expect(screen.getByText('atlas.europe')).toBeInTheDocument();
- // Singular labels are picked when streak / tripsThisYear are 1.
- expect(screen.getByText('atlas.yearInRow')).toBeInTheDocument();
- expect(screen.getByText(/atlas\.tripIn/)).toBeInTheDocument();
+ // The labels get the counts, so the language's own rule picks the form.
+ expect(screen.getByText('atlas.yearsInRow:1')).toBeInTheDocument();
+ expect(screen.getByText(/atlas\.tripsIn:1/)).toBeInTheDocument();
// The last-trip tile used to sit in this row; the row is stats only now.
expect(screen.queryByText('Paris Trip')).not.toBeInTheDocument();
diff --git a/client/src/pages/DashboardPage.tsx b/client/src/pages/DashboardPage.tsx
index f15a82136..ae1594951 100644
--- a/client/src/pages/DashboardPage.tsx
+++ b/client/src/pages/DashboardPage.tsx
@@ -367,11 +367,11 @@ function BoardingPassHero({ trip, bundle, locale, onOpen, onEdit, onCopy, onArch
const daysLeft = Math.max(0, Math.round((endMid.getTime() - todayMid.getTime()) / MS_PER_DAY))
countdownTop = t('dashboard.status.ongoing')
countdownNumber = String(daysLeft)
- countdownLabel = daysLeft === 0 ? t('dashboard.hero.lastDay') : daysLeft === 1 ? t('dashboard.hero.dayLeft') : t('dashboard.hero.daysLeft')
+ countdownLabel = daysLeft === 0 ? t('dashboard.hero.lastDay') : t('dashboard.hero.daysLeft', { count: daysLeft })
} else if (until !== null && until >= 0) {
countdownTop = t('dashboard.hero.startsIn')
countdownNumber = String(until)
- countdownLabel = until === 1 ? t('dashboard.hero.dayUnitOne') : t('dashboard.hero.dayUnitMany')
+ countdownLabel = t('dashboard.hero.dayUnit', { count: until })
}
const members = bundle?.members || []
@@ -398,7 +398,7 @@ function BoardingPassHero({ trip, bundle, locale, onOpen, onEdit, onCopy, onArch
{members.length > 4 && +{members.length - 4}
}
{members.length === 0 && {initials(trip.owner_username)}
}
- {buddyCount === 1 ? t('dashboard.hero.travelerOne', { count: buddyCount }) : t('dashboard.hero.travelerMany', { count: buddyCount })}
+ {t('dashboard.hero.travelers', { count: buddyCount })}
@@ -433,7 +433,7 @@ function BoardingPassHero({ trip, bundle, locale, onOpen, onEdit, onCopy, onArch
{places.length === 0 &&
}
{places.length > 3 &&
+{places.length - 3}
}
- {placeCount === 1 ? t('dashboard.hero.destinationOne', { count: placeCount }) : t('dashboard.hero.destinationMany', { count: placeCount })}
+ {t('dashboard.hero.destinations', { count: placeCount })}
>
)
@@ -666,7 +666,7 @@ function TripCard({ trip, locale, badges, matchedPlaces, onOpen, onEdit, onCopy,
{trip.day_count ?? 0} {t('dashboard.days')}
{trip.place_count ?? 0} {t('dashboard.places')}
-
{trip.shared_count ?? 0} {trip.shared_count === 1 ? t('dashboard.card.buddyOne') : t('dashboard.members')}
+
{trip.shared_count ?? 0} {t('dashboard.card.buddies', { count: trip.shared_count ?? 0 })}
diff --git a/client/src/pages/InAppNotificationsPage.tsx b/client/src/pages/InAppNotificationsPage.tsx
index a34c3a706..294badbc4 100644
--- a/client/src/pages/InAppNotificationsPage.tsx
+++ b/client/src/pages/InAppNotificationsPage.tsx
@@ -37,7 +37,7 @@ function InAppNotificationsPageDesktop(): React.ReactElement {
)}
- {total} {total === 1 ? 'notification' : 'notifications'}
+ {total} {t('notifications.countLabel', { count: total })}
diff --git a/client/src/pages/OAuthAuthorizePage.tsx b/client/src/pages/OAuthAuthorizePage.tsx
index cbf6af6cd..4b751e299 100644
--- a/client/src/pages/OAuthAuthorizePage.tsx
+++ b/client/src/pages/OAuthAuthorizePage.tsx
@@ -112,12 +112,7 @@ export default function OAuthAuthorizePage(): React.ReactElement {
: validation?.scopeSelectable && selectedScopes.length === 0
? t('oauth.authorize.selectScope')
: validation?.scopeSelectable
- ? t(
- selectedScopes.length !== 1
- ? 'oauth.authorize.approveManyScopes'
- : 'oauth.authorize.approveOneScope',
- { count: selectedScopes.length },
- )
+ ? t('oauth.authorize.approveScopes', { count: selectedScopes.length })
: t('oauth.authorize.approveAccess')}
{
fireEvent.click(screen.getByRole('button', { name: /reset passkeys/i }));
confirmPasskeyReset();
- await waitFor(() => expect(admin.toast.success).toHaveBeenCalledWith('Removed 3 passkey(s)'));
+ await waitFor(() => expect(admin.toast.success).toHaveBeenCalledWith('Removed 3 passkeys'));
expect(deletedFor).toBe('2');
});
diff --git a/client/src/pages/atlas/useAtlas.ts b/client/src/pages/atlas/useAtlas.ts
index 7b9e9fa85..0fd4d4729 100644
--- a/client/src/pages/atlas/useAtlas.ts
+++ b/client/src/pages/atlas/useAtlas.ts
@@ -625,8 +625,8 @@ export function useAtlas() {
${name}${planned ? ` · ${t('atlas.planned')} ` : ''}
-
${c.tripCount} ${c.tripCount === 1 ? t('atlas.tripSingular') : t('atlas.tripPlural')}
-
${c.placeCount} ${c.placeCount === 1 ? t('atlas.placeVisited') : t('atlas.placesVisited')}
+
${c.tripCount} ${t('atlas.tripsLabel', { count: c.tripCount })}
+
${c.placeCount} ${t('atlas.placesVisited', { count: c.placeCount })}
${datesHtml}
@@ -895,7 +895,7 @@ export function useAtlas() {
tt.style.left = e.originalEvent.clientX + 12 + 'px'
tt.style.top = e.originalEvent.clientY - 10 + 'px'
tt.innerHTML = visited
- ? `
${regionName}
${countryName}
${count} ${count === 1 ? 'place' : 'places'}
`
+ ? `
${regionName}
${countryName}
${count} ${t('atlas.regionPlacesLabel', { count })}
`
: `
${regionName}
${countryName}
`
}
})
diff --git a/client/tests/unit/i18n/parity.test.ts b/client/tests/unit/i18n/parity.test.ts
index ec60b2eea..1dd5702ac 100644
--- a/client/tests/unit/i18n/parity.test.ts
+++ b/client/tests/unit/i18n/parity.test.ts
@@ -36,7 +36,21 @@ const NON_EN_LOCALES: Record
= {
ar, br, cs, pl, ja, ko, uk, gr,
}
-const enKeys = new Set(Object.keys(en))
+// A count-bearing string's category forms (`places.count.one`, `.few`, ...)
+// differ by language on purpose: Russian needs `.few` and `.many`, Japanese
+// none. They are left out of the key comparison here; the shared parity CLI
+// checks each locale has exactly the forms its plural rule needs.
+const VARIANTS = ['zero', 'one', 'two', 'few', 'many']
+const allEnKeys = new Set(Object.keys(en))
+const pluralGroups = new Set(
+ [...allEnKeys]
+ .filter((k) => VARIANTS.includes(k.slice(k.lastIndexOf('.') + 1)))
+ .map((k) => k.slice(0, k.lastIndexOf('.')))
+ .filter((base) => allEnKeys.has(base) || allEnKeys.has(`${base}.other`)),
+)
+const isVariant = (k: string) =>
+ VARIANTS.includes(k.slice(k.lastIndexOf('.') + 1)) && pluralGroups.has(k.slice(0, k.lastIndexOf('.')))
+const enKeys = new Set([...allEnKeys].filter((k) => !isVariant(k)))
describe('i18n locale key parity', () => {
it('covers every non-en locale', () => {
@@ -46,7 +60,7 @@ describe('i18n locale key parity', () => {
for (const [locale, strings] of Object.entries(NON_EN_LOCALES)) {
it(`${locale} has the exact same key set as en`, () => {
- const localeKeys = new Set(Object.keys(strings))
+ const localeKeys = new Set(Object.keys(strings).filter((k) => !isVariant(k)))
const missing = [...enKeys].filter((k) => !localeKeys.has(k))
const extra = [...localeKeys].filter((k) => !enKeys.has(k))
diff --git a/client/tests/unit/mobile/admin/MAdminPluginsPanel.test.tsx b/client/tests/unit/mobile/admin/MAdminPluginsPanel.test.tsx
index b546a215d..4f708c330 100644
--- a/client/tests/unit/mobile/admin/MAdminPluginsPanel.test.tsx
+++ b/client/tests/unit/mobile/admin/MAdminPluginsPanel.test.tsx
@@ -730,7 +730,7 @@ describe('MAdminPluginsPanel — operator-supplied egress hosts', () => {
mockPanel([plugin({ operatorEgress: true, egressHostCount: 2 })]);
render( );
- expect(await screen.findByRole('button', { name: '2 allowed host(s)' })).toBeInTheDocument();
+ expect(await screen.findByRole('button', { name: '2 allowed hosts' })).toBeInTheDocument();
});
it('FE-MOB-PLUGP-042: the sheet adds and removes hosts through the API', async () => {
@@ -747,7 +747,7 @@ describe('MAdminPluginsPanel — operator-supplied egress hosts', () => {
);
render( );
- fireEvent.click(await screen.findByRole('button', { name: '1 allowed host(s)' }));
+ fireEvent.click(await screen.findByRole('button', { name: '1 allowed host' }));
expect(await screen.findByText('gotify.mydomain.test')).toBeInTheDocument();
fireEvent.change(screen.getByPlaceholderText('gotify.example.com'), { target: { value: ' ntfy.mydomain.test ' } });
@@ -1511,7 +1511,7 @@ describe('MAdminPluginsPanel — edge paths', () => {
await escapeUntilGone('No errors logged.');
// Allowed hosts → Escape, from the row chip.
- fireEvent.click(screen.getByRole('button', { name: '1 allowed host(s)' }));
+ fireEvent.click(screen.getByRole('button', { name: '1 allowed host' }));
await screen.findByText('No hosts added yet.');
await escapeUntilGone('No hosts added yet.');
});
diff --git a/client/tests/unit/mobile/admin/MAdminSheets.test.tsx b/client/tests/unit/mobile/admin/MAdminSheets.test.tsx
index 2ccfd8c6a..415d73fe4 100644
--- a/client/tests/unit/mobile/admin/MAdminSheets.test.tsx
+++ b/client/tests/unit/mobile/admin/MAdminSheets.test.tsx
@@ -213,7 +213,7 @@ describe('MAdminSheets', () => {
await user.click(screen.getAllByRole('button', { name: 'Reset passkeys' })[1]);
- await waitFor(() => expect(admin.toast.success).toHaveBeenCalledWith('Removed 3 passkey(s)'));
+ await waitFor(() => expect(admin.toast.success).toHaveBeenCalledWith('Removed 3 passkeys'));
expect(hitId).toBe('2');
});
diff --git a/client/tests/unit/mobile/dashboard/MDashboard.test.tsx b/client/tests/unit/mobile/dashboard/MDashboard.test.tsx
index 3ed9d715a..dfab10df9 100644
--- a/client/tests/unit/mobile/dashboard/MDashboard.test.tsx
+++ b/client/tests/unit/mobile/dashboard/MDashboard.test.tsx
@@ -104,8 +104,8 @@ describe('MDashboard', () => {
expect(screen.getByText('dashboard.status.ongoing')).toBeInTheDocument();
expect(screen.getByText('dashboard.mobile.spotlightDayOf')).toBeInTheDocument();
// Stat pills reuse the desktop hero keys (no mobile-only duplicates).
- expect(screen.getByText('dashboard.hero.destinationMany')).toBeInTheDocument();
- expect(screen.getByText('dashboard.hero.travelerMany')).toBeInTheDocument();
+ expect(screen.getByText('dashboard.hero.destinations')).toBeInTheDocument();
+ expect(screen.getByText('dashboard.hero.travelers')).toBeInTheDocument();
expect(screen.getByText('Lisbon')).toBeInTheDocument();
});
@@ -263,7 +263,7 @@ describe('MDashboard', () => {
expect(screen.getByText('12')).toBeInTheDocument();
expect(screen.getByText('30')).toBeInTheDocument();
- expect(screen.getByText('dashboard.card.buddyOne')).toBeInTheDocument();
+ expect(screen.getByText('dashboard.card.buddies')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'dashboard.archive' }));
expect(handleArchive).toHaveBeenCalledWith(4);
@@ -368,7 +368,7 @@ describe('MDashboard', () => {
render( );
expect(screen.getByText('dashboard.hero.badgeRecent')).toBeInTheDocument();
- expect(screen.getByText('dashboard.mobile.spotlightDayOne')).toBeInTheDocument();
+ expect(screen.getByText('dashboard.mobile.spotlightDays')).toBeInTheDocument();
});
it('FE-MOB-DASH-024: the spotlight opens on Enter as well as on click', () => {
diff --git a/client/tests/unit/mobile/settings/MAirTrailConnectionSection.test.tsx b/client/tests/unit/mobile/settings/MAirTrailConnectionSection.test.tsx
index ef1d605a4..096aa8ece 100644
--- a/client/tests/unit/mobile/settings/MAirTrailConnectionSection.test.tsx
+++ b/client/tests/unit/mobile/settings/MAirTrailConnectionSection.test.tsx
@@ -188,7 +188,7 @@ describe('MAirTrailConnectionSection', () => {
renderSection();
await user.click(await screen.findByRole('button', { name: /Test connection/ }));
- await screen.findByText('Connected — 12 flight(s) found');
+ await screen.findByText('Connected. 12 flights found');
expect(screen.getByText('Connected')).toBeInTheDocument();
});
diff --git a/client/tests/unit/mobile/settings/MSettingsOffline.test.tsx b/client/tests/unit/mobile/settings/MSettingsOffline.test.tsx
index e9ab53de6..bcb41ccfa 100644
--- a/client/tests/unit/mobile/settings/MSettingsOffline.test.tsx
+++ b/client/tests/unit/mobile/settings/MSettingsOffline.test.tsx
@@ -270,7 +270,7 @@ describe('MSettingsOffline', () => {
expect(screen.getByRole('button', { name: 'Downloading…' })).toBeDisabled();
await act(async () => { release(); });
- expect(await screen.findByText('Stored 4 trip(s) on this device')).toBeInTheDocument();
+ expect(await screen.findByText('Stored 4 trips on this device')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Download for offline use' })).toBeEnabled();
});
diff --git a/client/tests/unit/mobile/trip/MPlacesBrowser.test.tsx b/client/tests/unit/mobile/trip/MPlacesBrowser.test.tsx
index 1077bba56..f85ef1535 100644
--- a/client/tests/unit/mobile/trip/MPlacesBrowser.test.tsx
+++ b/client/tests/unit/mobile/trip/MPlacesBrowser.test.tsx
@@ -111,7 +111,7 @@ describe('MPlacesBrowser', () => {
fireEvent.click(screen.getByRole('button', { name: 'places.planned' }))
expect(useTripStore.getState().placesFilter).toBe('planned')
- expect(screen.getByText('places.countSingular')).toBeInTheDocument()
+ expect(screen.getByText('places.count:1')).toBeInTheDocument()
expect(screen.getByText('Louvre')).toBeInTheDocument()
fireEvent.click(screen.getByRole('button', { name: 'places.all' }))
@@ -121,7 +121,7 @@ describe('MPlacesBrowser', () => {
it('FE-MOB-PBROW-006: the tracks chip only appears with a track and filters to it', () => {
renderBrowser()
fireEvent.click(screen.getByRole('button', { name: 'places.filterTracks' }))
- expect(screen.getByText('places.countSingular')).toBeInTheDocument()
+ expect(screen.getByText('places.count:1')).toBeInTheDocument()
expect(screen.getByText('Seine Track')).toBeInTheDocument()
expect(screen.queryByText('Louvre')).not.toBeInTheDocument()
})
diff --git a/shared/scripts/i18n-parity.mjs b/shared/scripts/i18n-parity.mjs
index 7d77ffb6a..b0db5e997 100644
--- a/shared/scripts/i18n-parity.mjs
+++ b/shared/scripts/i18n-parity.mjs
@@ -12,6 +12,13 @@
// returns exit-code 1 when any drift is present (intended for CI). Without
// `--strict` the script exits 0 and prints, so it can also run as a non-blocking
// audit during translation work.
+// 3. Plural forms: a count-bearing string (`key` or `key.other`, plus at
+// least one of `key.zero|one|two|few|many` in en) is a plural group. Its
+// category keys are not compared with en's, since Russian needs `.few`
+// and `.many` and Japanese needs none. Instead every locale must spell
+// 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.
//
// Limitations: we only parse *top-level* string keys (those declared as the
// first column of the file, matching the regex below). Nested objects, function
@@ -21,6 +28,8 @@
import { readdirSync, readFileSync, statSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
+import { getIntlLanguage } from '../src/i18n/languages.ts';
+import { allPluralCategories, integerPluralCategories } from '../src/i18n/plural.ts';
const here = dirname(fileURLToPath(import.meta.url));
const i18nRoot = join(here, '..', 'src', 'i18n');
@@ -52,6 +61,50 @@ function extractKeys(locale, file) {
return keys;
}
+const VARIANT_CATEGORIES = ['zero', 'one', 'two', 'few', 'many'];
+
+/** The plural groups en defines in one domain file: their base keys. */
+function pluralGroups(enKeys) {
+ const groups = new Set();
+ for (const key of enKeys) {
+ const dot = key.lastIndexOf('.');
+ if (dot < 0 || !VARIANT_CATEGORIES.includes(key.slice(dot + 1))) continue;
+ const base = key.slice(0, dot);
+ if (enKeys.has(base) || enKeys.has(`${base}.other`)) groups.add(base);
+ }
+ return groups;
+}
+
+/** `{ base, category }` when `key` is a category form of one of `groups`. */
+function asVariant(key, groups) {
+ const dot = key.lastIndexOf('.');
+ if (dot < 0) return null;
+ const category = key.slice(dot + 1);
+ const base = key.slice(0, dot);
+ return VARIANT_CATEGORIES.includes(category) && groups.has(base) ? { base, category } : null;
+}
+
+function withoutVariants(keys, groups) {
+ return new Set([...keys].filter((k) => !asVariant(k, groups)));
+}
+
+/** What one locale owes the plural groups of one file. */
+function checkPluralForms(locale, keys, groups) {
+ const intl = getIntlLanguage(locale);
+ const required = integerPluralCategories(intl).filter((c) => c !== 'other');
+ const allowed = new Set(allPluralCategories(intl));
+ const missing = [];
+ const invalid = [];
+ for (const base of groups) {
+ for (const c of required) if (!keys.has(`${base}.${c}`)) missing.push(`${base}.${c}`);
+ }
+ for (const key of keys) {
+ const v = asVariant(key, groups);
+ if (v && !allowed.has(v.category)) invalid.push(key);
+ }
+ return { missing, invalid };
+}
+
function diffSets(reference, candidate) {
const missing = [];
const extra = [];
@@ -69,7 +122,14 @@ function checkParity() {
const enKeysByDomain = new Map();
for (const f of enFiles) enKeysByDomain.set(f, extractKeys('en', f));
- const report = { fileDrift: [], keyDrift: [] };
+ const report = { fileDrift: [], keyDrift: [], pluralDrift: [] };
+ const groupsByDomain = new Map();
+ for (const f of enFiles) {
+ const groups = pluralGroups(enKeysByDomain.get(f));
+ groupsByDomain.set(f, groups);
+ const { missing, invalid } = checkPluralForms('en', enKeysByDomain.get(f), groups);
+ if (missing.length || invalid.length) report.pluralDrift.push({ locale: 'en', file: f, missing, invalid });
+ }
for (const locale of locales) {
if (locale === 'en') continue;
@@ -87,10 +147,18 @@ function checkParity() {
for (const file of enFiles) {
if (!localeFiles.includes(file)) continue;
const localeKeys = extractKeys(locale, file);
- const { missing, extra } = diffSets(enKeysByDomain.get(file), localeKeys);
+ const groups = groupsByDomain.get(file);
+ const { missing, extra } = diffSets(
+ withoutVariants(enKeysByDomain.get(file), groups),
+ withoutVariants(localeKeys, groups),
+ );
if (missing.length || extra.length) {
report.keyDrift.push({ locale, file, missing, extra });
}
+ const plural = checkPluralForms(locale, localeKeys, groups);
+ if (plural.missing.length || plural.invalid.length) {
+ report.pluralDrift.push({ locale, file, ...plural });
+ }
}
}
@@ -122,6 +190,18 @@ function formatReport(report) {
}
}
+ if (report.pluralDrift.length === 0) {
+ lines.push('Plural forms: OK');
+ } else {
+ lines.push(`Plural forms: ${report.pluralDrift.length} domain file(s) with missing or impossible forms`);
+ for (const { locale, file, missing, invalid } of report.pluralDrift) {
+ const parts = [];
+ if (missing.length) parts.push(`missing ${missing.join(', ')}`);
+ if (invalid.length) parts.push(`never selected ${invalid.join(', ')}`);
+ lines.push(` ${locale}/${file}: ${parts.join('; ')}`);
+ }
+ }
+
return lines.join('\n');
}
@@ -135,11 +215,11 @@ 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: [] } : report) + '\n');
+ process.stdout.write(formatReport(filesOnly ? { ...report, keyDrift: [], pluralDrift: [] } : report) + '\n');
if (strict) {
const hasFileDrift = report.fileDrift.length > 0;
- const hasKeyDrift = filesOnly ? false : report.keyDrift.length > 0;
+ const hasKeyDrift = filesOnly ? false : report.keyDrift.length > 0 || report.pluralDrift.length > 0;
if (hasFileDrift || hasKeyDrift) process.exit(1);
}
}
diff --git a/shared/src/i18n/ar/atlas.ts b/shared/src/i18n/ar/atlas.ts
index 07c872294..7eaff433e 100644
--- a/shared/src/i18n/ar/atlas.ts
+++ b/shared/src/i18n/ar/atlas.ts
@@ -43,10 +43,10 @@ const atlas: TranslationStrings = {
'atlas.daysLeft': 'يوم متبقٍ',
'atlas.streak': 'سلسلة',
'atlas.years': 'سنوات',
- 'atlas.yearInRow': 'سنة متتالية',
'atlas.yearsInRow': 'سنوات متتالية',
- 'atlas.tripIn': 'رحلة في',
+ 'atlas.yearsInRow.one': 'سنة متتالية',
'atlas.tripsIn': 'رحلات في',
+ 'atlas.tripsIn.one': 'رحلة في',
'atlas.since': 'منذ',
'atlas.europe': 'أوروبا',
'atlas.asia': 'آسيا',
@@ -57,10 +57,11 @@ const atlas: TranslationStrings = {
'atlas.other': 'أخرى',
'atlas.firstVisit': 'أول رحلة',
'atlas.lastVisitLabel': 'آخر رحلة',
- 'atlas.tripSingular': 'رحلة',
'atlas.tripPlural': 'رحلات',
- 'atlas.placeVisited': 'مكان تمت زيارته',
+ 'atlas.tripsLabel': 'رحلات',
+ 'atlas.tripsLabel.one': 'رحلة',
'atlas.placesVisited': 'أماكن تمت زيارتها',
+ 'atlas.placesVisited.one': 'مكان تمت زيارته',
'atlas.planned': 'مُخطط',
'atlas.showPlanned': 'إظهار الدول المخطط زيارتها',
'atlas.plannedFor': 'مخطط لـ',
diff --git a/shared/src/i18n/ar/collab.ts b/shared/src/i18n/ar/collab.ts
index 4f3a1abaa..3cf087c68 100644
--- a/shared/src/i18n/ar/collab.ts
+++ b/shared/src/i18n/ar/collab.ts
@@ -88,7 +88,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'إغلاق',
'collab.polls.closed': 'مغلق',
'collab.polls.votes': '{n} أصوات',
- 'collab.polls.vote': '{n} صوت',
+ 'collab.polls.votes.one': '{n} صوت',
'collab.polls.multipleChoice': 'اختيار متعدد',
'collab.polls.multiChoice': 'اختيار متعدد',
'collab.polls.deadline': 'الموعد النهائي',
diff --git a/shared/src/i18n/ar/dashboard.ts b/shared/src/i18n/ar/dashboard.ts
index 97160e57d..ea72355eb 100644
--- a/shared/src/i18n/ar/dashboard.ts
+++ b/shared/src/i18n/ar/dashboard.ts
@@ -34,6 +34,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'الأيام',
'dashboard.places': 'الأماكن',
'dashboard.members': 'ال חברים',
+ 'dashboard.card.buddies': 'ال חברים',
+ 'dashboard.card.buddies.one': 'رفيق',
'dashboard.archive': 'أرشفة',
'dashboard.copyTrip': 'نسخ',
'dashboard.copySuffix': 'نسخة',
@@ -125,14 +127,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'مؤخرًا',
'dashboard.hero.tripDates': 'تواريخ الرحلة',
'dashboard.hero.noDates': 'لا توجد تواريخ',
- 'dashboard.hero.travelerOne': '{count} مسافر',
- 'dashboard.hero.travelerMany': '{count} مسافرين',
- 'dashboard.hero.destinationOne': '{count} وجهة',
- 'dashboard.hero.destinationMany': '{count} وجهات',
- 'dashboard.hero.dayUnitOne': 'يوم',
- 'dashboard.hero.dayUnitMany': 'أيام',
- 'dashboard.hero.dayLeft': 'بقي يوم',
+ 'dashboard.hero.travelers': '{count} مسافرين',
+ 'dashboard.hero.travelers.one': '{count} مسافر',
+ 'dashboard.hero.destinations': '{count} وجهات',
+ 'dashboard.hero.destinations.one': '{count} وجهة',
+ 'dashboard.hero.dayUnit': 'أيام',
+ 'dashboard.hero.dayUnit.one': 'يوم',
'dashboard.hero.daysLeft': 'الأيام المتبقية',
+ 'dashboard.hero.daysLeft.one': 'بقي يوم',
'dashboard.hero.lastDay': 'اليوم الأخير',
'dashboard.hero.untilStart': 'حتى البداية',
'dashboard.hero.startsIn': 'تبدأ الرحلة بعد',
@@ -147,7 +149,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ {count}× حول خط الاستواء',
'dashboard.card.idea': 'فكرة',
- 'dashboard.card.buddyOne': 'رفيق',
'dashboard.fx.from': 'من',
'dashboard.fx.to': 'إلى',
'dashboard.fx.unavailable': 'السعر غير متاح',
@@ -194,8 +195,8 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': 'خلال {count} يوم',
'dashboard.mobile.inMonths': 'خلال {count} شهر',
'dashboard.mobile.spotlightDayOf': 'اليوم {day} من {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} يوم',
- 'dashboard.mobile.spotlightDaysMany': '{count} أيام',
+ 'dashboard.mobile.spotlightDays': '{count} أيام',
+ 'dashboard.mobile.spotlightDays.one': '{count} يوم',
'dashboard.mobile.completed': 'مكتملة',
'dashboard.mobile.currencyConverter': 'محوّل العملات',
'dashboard.aria.toggleView': 'تبديل العرض',
diff --git a/shared/src/i18n/ar/files.ts b/shared/src/i18n/ar/files.ts
index 5ac929d2a..c81049a33 100644
--- a/shared/src/i18n/ar/files.ts
+++ b/shared/src/i18n/ar/files.ts
@@ -14,7 +14,7 @@ const files: TranslationStrings = {
'files.openError': 'تعذر فتح الملف',
'files.downloadPdf': 'تنزيل PDF',
'files.count': '{count} ملفات',
- 'files.countSingular': 'ملف واحد',
+ 'files.count.one': 'ملف واحد',
'files.uploaded': 'تم رفع {count}',
'files.uploadError': 'فشل الرفع',
'files.uploadErrorType': 'نوع الملف هذا غير مدعوم',
diff --git a/shared/src/i18n/ar/members.ts b/shared/src/i18n/ar/members.ts
index d5eaeb7a5..572f8b931 100644
--- a/shared/src/i18n/ar/members.ts
+++ b/shared/src/i18n/ar/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'دعوة',
'members.allHaveAccess': 'جميع المستخدمين لديهم صلاحية الوصول بالفعل.',
'members.access': 'الصلاحية',
- 'members.person': 'شخص',
'members.persons': 'أشخاص',
+ 'members.persons.one': 'شخص',
'members.you': 'أنت',
'members.owner': 'المالك',
'members.leaveTrip': 'مغادرة الرحلة',
diff --git a/shared/src/i18n/ar/oauth.ts b/shared/src/i18n/ar/oauth.ts
index bd5c753e3..635bdcad7 100644
--- a/shared/src/i18n/ar/oauth.ts
+++ b/shared/src/i18n/ar/oauth.ts
@@ -84,8 +84,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes.one': 'Approve ({count} scope)', // en-fallback
'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
'oauth.authorize.deny': 'Deny', // en-fallback
'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
diff --git a/shared/src/i18n/ar/places.ts b/shared/src/i18n/ar/places.ts
index 1ab17583e..b981701d0 100644
--- a/shared/src/i18n/ar/places.ts
+++ b/shared/src/i18n/ar/places.ts
@@ -70,7 +70,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'فئات',
'places.clearFilter': 'مسح الفلتر',
'places.count': '{count} أماكن',
- 'places.countSingular': 'مكان واحد',
+ 'places.count.one': 'مكان واحد',
'places.allPlanned': 'تم تخطيط جميع الأماكن',
'places.noneFound': 'لم يتم العثور على أماكن',
'places.editPlace': 'تعديل المكان',
diff --git a/shared/src/i18n/ar/tours.ts b/shared/src/i18n/ar/tours.ts
index f205b01af..5d15fcb07 100644
--- a/shared/src/i18n/ar/tours.ts
+++ b/shared/src/i18n/ar/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'بيانات الارتفاع أو المقاييس غير مكتملة؛ تم الاستيراد مع تنبيه للحذر',
'tours.import.caution': 'تم الاستيراد مع تنبيه للحذر؛ بيانات الارتفاع أو المقاييس غير مكتملة',
'tours.import.success': 'تم استيراد {count} من الجولات من GPX',
- 'tours.import.successOne': 'تم استيراد جولة واحدة من GPX',
+ 'tours.import.success.one': 'تم استيراد جولة واحدة من GPX',
'tours.import.noTrack': 'لم يُعثر على أثر مسجّل أو مسار في ملف GPX هذا',
'tours.import.error': 'تعذّر استيراد ملف GPX',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/az/atlas.ts b/shared/src/i18n/az/atlas.ts
index a3c213579..f0fa49d61 100644
--- a/shared/src/i18n/az/atlas.ts
+++ b/shared/src/i18n/az/atlas.ts
@@ -58,10 +58,10 @@ const atlas: TranslationStrings = {
'atlas.daysLeft': 'gün qalıb',
'atlas.streak': 'Ardıcıllıq',
'atlas.years': 'il',
- 'atlas.yearInRow': 'ardıcıl il',
'atlas.yearsInRow': 'ardıcıl il',
- 'atlas.tripIn': 'səyahət ·',
+ 'atlas.yearsInRow.one': 'ardıcıl il',
'atlas.tripsIn': 'səyahət ·',
+ 'atlas.tripsIn.one': 'səyahət ·',
'atlas.since': 'etibarən',
'atlas.europe': 'Avropa',
@@ -74,10 +74,11 @@ const atlas: TranslationStrings = {
'atlas.firstVisit': 'İlk səyahət',
'atlas.lastVisitLabel': 'Son səyahət',
- 'atlas.tripSingular': 'Səyahət',
'atlas.tripPlural': 'Səyahətlər',
- 'atlas.placeVisited': 'Ziyarət edilmiş məkan',
+ 'atlas.tripsLabel': 'Səyahətlər',
+ 'atlas.tripsLabel.one': 'Səyahət',
'atlas.placesVisited': 'Ziyarət edilmiş məkanlar',
+ 'atlas.placesVisited.one': 'Ziyarət edilmiş məkan',
'atlas.planned': 'Planlaşdırılıb',
'atlas.showPlanned': 'Planlaşdırılan ölkələri göstər',
diff --git a/shared/src/i18n/az/collab.ts b/shared/src/i18n/az/collab.ts
index bf2e96bdc..99b7c3262 100644
--- a/shared/src/i18n/az/collab.ts
+++ b/shared/src/i18n/az/collab.ts
@@ -100,7 +100,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Bağla',
'collab.polls.closed': 'Bağlanıb',
'collab.polls.votes': '{n} səs',
- 'collab.polls.vote': '{n} səs',
+ 'collab.polls.votes.one': '{n} səs',
'collab.polls.multipleChoice': 'Çoxseçimli',
'collab.polls.multiChoice': 'Çoxseçimli',
'collab.polls.deadline': 'Son tarix',
diff --git a/shared/src/i18n/az/dashboard.ts b/shared/src/i18n/az/dashboard.ts
index 54ae0aabb..9e77e2d04 100644
--- a/shared/src/i18n/az/dashboard.ts
+++ b/shared/src/i18n/az/dashboard.ts
@@ -34,6 +34,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Günlər',
'dashboard.places': 'Məkanlar',
'dashboard.members': 'Yoldaşlar',
+ 'dashboard.card.buddies': 'Yoldaşlar',
+ 'dashboard.card.buddies.one': 'Yoldaş',
'dashboard.archive': 'Arxivləşdir',
'dashboard.copyTrip': 'Nüsxəsini yarat',
'dashboard.copySuffix': 'nüsxə',
@@ -159,8 +161,8 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': '{count} gün sonra',
'dashboard.mobile.inMonths': '{count} ay sonra',
'dashboard.mobile.spotlightDayOf': 'Gün {day}/{total}',
- 'dashboard.mobile.spotlightDayOne': '{count} gün',
- 'dashboard.mobile.spotlightDaysMany': '{count} gün',
+ 'dashboard.mobile.spotlightDays': '{count} gün',
+ 'dashboard.mobile.spotlightDays.one': '{count} gün',
'dashboard.mobile.completed': 'Tamamlanıb',
'dashboard.mobile.currencyConverter': 'Valyuta çeviricisi',
'dashboard.filter.planned': 'Planlaşdırılıb',
@@ -171,14 +173,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'SON',
'dashboard.hero.tripDates': 'Səyahət tarixləri',
'dashboard.hero.noDates': 'Tarixlər təyin edilməyib',
- 'dashboard.hero.travelerOne': '{count} səyahətçi',
- 'dashboard.hero.travelerMany': '{count} səyahətçi',
- 'dashboard.hero.destinationOne': '{count} istiqamət',
- 'dashboard.hero.destinationMany': '{count} istiqamət',
- 'dashboard.hero.dayUnitOne': 'gün',
- 'dashboard.hero.dayUnitMany': 'gün',
- 'dashboard.hero.dayLeft': 'Qalan gün',
+ 'dashboard.hero.travelers': '{count} səyahətçi',
+ 'dashboard.hero.travelers.one': '{count} səyahətçi',
+ 'dashboard.hero.destinations': '{count} istiqamət',
+ 'dashboard.hero.destinations.one': '{count} istiqamət',
+ 'dashboard.hero.dayUnit': 'gün',
+ 'dashboard.hero.dayUnit.one': 'gün',
'dashboard.hero.daysLeft': 'Qalan günlər',
+ 'dashboard.hero.daysLeft.one': 'Qalan gün',
'dashboard.hero.lastDay': 'Son gün',
'dashboard.hero.untilStart': 'Başlanmasına qalan',
'dashboard.hero.startsIn': 'Səyahətin başlanmasına',
@@ -193,7 +195,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ ekvator ətrafında {count} dövrə',
'dashboard.card.idea': 'İdeya',
- 'dashboard.card.buddyOne': 'Yoldaş',
'dashboard.fx.from': 'Mənbə',
'dashboard.fx.to': 'Hədəf',
'dashboard.fx.unavailable': 'Məzənnə mövcud deyil',
diff --git a/shared/src/i18n/az/files.ts b/shared/src/i18n/az/files.ts
index 87f605fbf..c2ed553d7 100644
--- a/shared/src/i18n/az/files.ts
+++ b/shared/src/i18n/az/files.ts
@@ -17,7 +17,7 @@ const files: TranslationStrings = {
'files.openError': 'Faylı açmaq mümkün olmadı',
'files.downloadPdf': 'PDF-i endir',
'files.count': '{count} fayl',
- 'files.countSingular': '1 fayl',
+ 'files.count.one': '{count} fayl',
'files.uploaded': '{count} fayl yükləndi',
'files.uploadError': 'Yükləmə uğursuz oldu',
'files.uploadErrorType': 'Bu fayl növü dəstəklənmir',
diff --git a/shared/src/i18n/az/members.ts b/shared/src/i18n/az/members.ts
index d740f425f..9904dc1c6 100644
--- a/shared/src/i18n/az/members.ts
+++ b/shared/src/i18n/az/members.ts
@@ -9,8 +9,8 @@ const members: TranslationStrings = {
'Bütün istifadəçilərin artıq giriş icazəsi var.',
'members.access': 'Giriş icazəsi',
- 'members.person': 'nəfər',
'members.persons': 'nəfər',
+ 'members.persons.one': 'nəfər',
'members.you': 'siz',
'members.owner': 'Sahib',
diff --git a/shared/src/i18n/az/oauth.ts b/shared/src/i18n/az/oauth.ts
index df59c8bb7..a8c3d496a 100644
--- a/shared/src/i18n/az/oauth.ts
+++ b/shared/src/i18n/az/oauth.ts
@@ -115,8 +115,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.trustNote':
'Yalnız etibar etdiyiniz tətbiqlərə giriş icazəsi verin. Məlumatlarınız serverinizdə qalır.',
'oauth.authorize.selectScope': 'Ən azı bir icazə sahəsi seçin',
- 'oauth.authorize.approveOneScope': 'Təsdiqlə ({count} icazə sahəsi)',
- 'oauth.authorize.approveManyScopes': 'Təsdiqlə ({count} icazə sahəsi)',
+ 'oauth.authorize.approveScopes': 'Təsdiqlə ({count} icazə sahəsi)',
+ 'oauth.authorize.approveScopes.one': 'Təsdiqlə ({count} icazə sahəsi)',
'oauth.authorize.approveAccess': 'Girişə icazə ver',
'oauth.authorize.deny': 'Rədd et',
'oauth.authorize.choosePermissions': 'Veriləcək icazələri seçin',
diff --git a/shared/src/i18n/az/places.ts b/shared/src/i18n/az/places.ts
index 1fdfe4332..5d19017f3 100644
--- a/shared/src/i18n/az/places.ts
+++ b/shared/src/i18n/az/places.ts
@@ -79,7 +79,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'kateqoriya',
'places.clearFilter': 'Filtri təmizlə',
'places.count': '{count} məkan',
- 'places.countSingular': '1 məkan',
+ 'places.count.one': '{count} məkan',
'places.allPlanned': 'Bütün məkanlar planlaşdırılıb',
'places.noneFound': 'Heç bir məkan tapılmadı',
diff --git a/shared/src/i18n/az/tours.ts b/shared/src/i18n/az/tours.ts
index 0c3521ac8..55c30eb8d 100644
--- a/shared/src/i18n/az/tours.ts
+++ b/shared/src/i18n/az/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Yüksəklik və göstəricilər natamamdır — ehtiyatla idxal edilib',
'tours.import.caution': 'Ehtiyatla idxal edilib — yüksəklik və göstəricilər natamamdır',
'tours.import.success': 'GPX-dən {count} tur idxal edildi',
- 'tours.import.successOne': 'GPX-dən 1 tur idxal edildi',
+ 'tours.import.success.one': 'GPX-dən {count} tur idxal edildi',
'tours.import.noTrack': 'Bu GPX faylında iz və ya marşrut tapılmadı',
'tours.import.error': 'GPX faylını idxal etmək mümkün olmadı',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/br/atlas.ts b/shared/src/i18n/br/atlas.ts
index 7648785d6..61ed1d0f3 100644
--- a/shared/src/i18n/br/atlas.ts
+++ b/shared/src/i18n/br/atlas.ts
@@ -43,10 +43,10 @@ const atlas: TranslationStrings = {
'atlas.daysLeft': 'dias restantes',
'atlas.streak': 'Sequência',
'atlas.years': 'anos',
- 'atlas.yearInRow': 'ano seguido',
'atlas.yearsInRow': 'anos seguidos',
- 'atlas.tripIn': 'viagem em',
+ 'atlas.yearsInRow.one': 'ano seguido',
'atlas.tripsIn': 'viagens em',
+ 'atlas.tripsIn.one': 'viagem em',
'atlas.since': 'desde',
'atlas.europe': 'Europa',
'atlas.asia': 'Ásia',
@@ -57,10 +57,11 @@ const atlas: TranslationStrings = {
'atlas.other': 'Outro',
'atlas.firstVisit': 'Primeira viagem',
'atlas.lastVisitLabel': 'Última viagem',
- 'atlas.tripSingular': 'Viagem',
'atlas.tripPlural': 'Viagens',
- 'atlas.placeVisited': 'Lugar visitado',
+ 'atlas.tripsLabel': 'Viagens',
+ 'atlas.tripsLabel.one': 'Viagem',
'atlas.placesVisited': 'Lugares visitados',
+ 'atlas.placesVisited.one': 'Lugar visitado',
'atlas.planned': 'Planejado',
'atlas.showPlanned': 'Mostrar países planejados',
'atlas.plannedFor': 'Planejado para',
diff --git a/shared/src/i18n/br/collab.ts b/shared/src/i18n/br/collab.ts
index 020225c14..63ed99cd0 100644
--- a/shared/src/i18n/br/collab.ts
+++ b/shared/src/i18n/br/collab.ts
@@ -89,7 +89,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Encerrar',
'collab.polls.closed': 'Encerrada',
'collab.polls.votes': '{n} votos',
- 'collab.polls.vote': '{n} voto',
+ 'collab.polls.votes.one': '{n} voto',
'collab.polls.multipleChoice': 'Múltipla escolha',
'collab.polls.multiChoice': 'Múltipla escolha',
'collab.polls.deadline': 'Prazo',
diff --git a/shared/src/i18n/br/dashboard.ts b/shared/src/i18n/br/dashboard.ts
index f16e68b40..5f2948b37 100644
--- a/shared/src/i18n/br/dashboard.ts
+++ b/shared/src/i18n/br/dashboard.ts
@@ -34,6 +34,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Dias',
'dashboard.places': 'Lugares',
'dashboard.members': 'Parceiros de viagem',
+ 'dashboard.card.buddies': 'Parceiros de viagem',
+ 'dashboard.card.buddies.one': 'Parceiro',
'dashboard.archive': 'Arquivar',
'dashboard.copyTrip': 'Copiar',
'dashboard.copySuffix': 'cópia',
@@ -143,8 +145,8 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': 'Em {count} dias',
'dashboard.mobile.inMonths': 'Em {count} meses',
'dashboard.mobile.spotlightDayOf': 'Dia {day} de {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} dia',
- 'dashboard.mobile.spotlightDaysMany': '{count} dias',
+ 'dashboard.mobile.spotlightDays': '{count} dias',
+ 'dashboard.mobile.spotlightDays.one': '{count} dia',
'dashboard.mobile.completed': 'Concluído',
'dashboard.mobile.currencyConverter': 'Conversor de moedas',
'dashboard.filter.planned': 'Planejadas',
@@ -155,14 +157,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'RECENTE',
'dashboard.hero.tripDates': 'Datas da viagem',
'dashboard.hero.noDates': 'Sem datas definidas',
- 'dashboard.hero.travelerOne': '{count} viajante',
- 'dashboard.hero.travelerMany': '{count} viajantes',
- 'dashboard.hero.destinationOne': '{count} destino',
- 'dashboard.hero.destinationMany': '{count} destinos',
- 'dashboard.hero.dayUnitOne': 'dia',
- 'dashboard.hero.dayUnitMany': 'dias',
- 'dashboard.hero.dayLeft': 'Dia restante',
+ 'dashboard.hero.travelers': '{count} viajantes',
+ 'dashboard.hero.travelers.one': '{count} viajante',
+ 'dashboard.hero.destinations': '{count} destinos',
+ 'dashboard.hero.destinations.one': '{count} destino',
+ 'dashboard.hero.dayUnit': 'dias',
+ 'dashboard.hero.dayUnit.one': 'dia',
'dashboard.hero.daysLeft': 'Dias restantes',
+ 'dashboard.hero.daysLeft.one': 'Dia restante',
'dashboard.hero.lastDay': 'Último dia',
'dashboard.hero.untilStart': 'Até o início',
'dashboard.hero.startsIn': 'A viagem começa em',
@@ -177,7 +179,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ {count}× ao redor do equador',
'dashboard.card.idea': 'Ideia',
- 'dashboard.card.buddyOne': 'Parceiro',
'dashboard.fx.from': 'De',
'dashboard.fx.to': 'Para',
'dashboard.fx.unavailable': 'Taxa indisponível',
diff --git a/shared/src/i18n/br/files.ts b/shared/src/i18n/br/files.ts
index 838f927c6..10950b968 100644
--- a/shared/src/i18n/br/files.ts
+++ b/shared/src/i18n/br/files.ts
@@ -14,7 +14,7 @@ const files: TranslationStrings = {
'files.openError': 'Não foi possível abrir o arquivo',
'files.downloadPdf': 'Baixar PDF',
'files.count': '{count} arquivos',
- 'files.countSingular': '1 arquivo',
+ 'files.count.one': '{count} arquivo',
'files.uploaded': '{count} enviado(s)',
'files.uploadError': 'Falha no envio',
'files.uploadErrorType': 'Esse tipo de arquivo não é suportado',
diff --git a/shared/src/i18n/br/members.ts b/shared/src/i18n/br/members.ts
index e4a1f2a42..269e99263 100644
--- a/shared/src/i18n/br/members.ts
+++ b/shared/src/i18n/br/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'Convidar',
'members.allHaveAccess': 'Todos os usuários já têm acesso.',
'members.access': 'Acesso',
- 'members.person': 'pessoa',
'members.persons': 'pessoas',
+ 'members.persons.one': 'pessoa',
'members.you': 'você',
'members.owner': 'Proprietário',
'members.leaveTrip': 'Sair da viagem',
diff --git a/shared/src/i18n/br/oauth.ts b/shared/src/i18n/br/oauth.ts
index eebc2bc2e..80ca372e7 100644
--- a/shared/src/i18n/br/oauth.ts
+++ b/shared/src/i18n/br/oauth.ts
@@ -86,8 +86,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes.one': 'Approve ({count} scope)', // en-fallback
'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
'oauth.authorize.deny': 'Deny', // en-fallback
'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
diff --git a/shared/src/i18n/br/places.ts b/shared/src/i18n/br/places.ts
index fa5fb5e4e..076c290e2 100644
--- a/shared/src/i18n/br/places.ts
+++ b/shared/src/i18n/br/places.ts
@@ -70,7 +70,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'categorias',
'places.clearFilter': 'Limpar filtro',
'places.count': '{count} lugares',
- 'places.countSingular': '1 lugar',
+ 'places.count.one': '{count} lugar',
'places.allPlanned': 'Todos os lugares estão planejados',
'places.noneFound': 'Nenhum lugar encontrado',
'places.editPlace': 'Editar lugar',
diff --git a/shared/src/i18n/br/tours.ts b/shared/src/i18n/br/tours.ts
index bc91b3046..0ee6908b6 100644
--- a/shared/src/i18n/br/tours.ts
+++ b/shared/src/i18n/br/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Elevação ou métricas incompletas: importado com ressalvas',
'tours.import.caution': 'Importado com ressalvas: elevação ou métricas incompletas',
'tours.import.success': '{count} passeios importados de GPX',
- 'tours.import.successOne': '1 passeio importado de GPX',
+ 'tours.import.success.one': '{count} passeio importado de GPX',
'tours.import.noTrack': 'Nenhum traçado ou rota encontrado nesse arquivo GPX',
'tours.import.error': 'Não foi possível importar o arquivo GPX',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/ca/atlas.ts b/shared/src/i18n/ca/atlas.ts
index ef1bbced9..04ac0cc60 100644
--- a/shared/src/i18n/ca/atlas.ts
+++ b/shared/src/i18n/ca/atlas.ts
@@ -19,10 +19,10 @@ const atlas: TranslationStrings = {
'atlas.streak': 'Ratxa',
'atlas.year': 'any',
'atlas.years': 'anys',
- 'atlas.yearInRow': 'any seguit',
'atlas.yearsInRow': 'anys seguits',
- 'atlas.tripIn': 'viatge a',
+ 'atlas.yearsInRow.one': 'any seguit',
'atlas.tripsIn': 'viatges a',
+ 'atlas.tripsIn.one': 'viatge a',
'atlas.since': 'des de',
'atlas.europe': 'Europa',
'atlas.asia': 'Àsia',
@@ -33,10 +33,11 @@ const atlas: TranslationStrings = {
'atlas.other': 'Altres',
'atlas.firstVisit': 'Primer viatge',
'atlas.lastVisitLabel': 'Darrer viatge',
- 'atlas.tripSingular': 'Viatge',
'atlas.tripPlural': 'Viatges',
- 'atlas.placeVisited': 'Lloc visitat',
+ 'atlas.tripsLabel': 'Viatges',
+ 'atlas.tripsLabel.one': 'Viatge',
'atlas.placesVisited': 'Llocs visitats',
+ 'atlas.placesVisited.one': 'Lloc visitat',
'atlas.statsTab': 'Estadístiques',
'atlas.bucketTab': 'Llista de desitjos',
'atlas.addBucket': 'Afegeix a la llista de desitjos',
diff --git a/shared/src/i18n/ca/collab.ts b/shared/src/i18n/ca/collab.ts
index 296f33d05..5c35baae3 100644
--- a/shared/src/i18n/ca/collab.ts
+++ b/shared/src/i18n/ca/collab.ts
@@ -81,7 +81,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Tanca',
'collab.polls.closed': 'Tancada',
'collab.polls.votes': '{n} vots',
- 'collab.polls.vote': '{n} vot',
+ 'collab.polls.votes.one': '{n} vot',
'collab.polls.multipleChoice': 'Selecció múltiple',
'collab.polls.multiChoice': 'Selecció múltiple',
'collab.polls.deadline': 'Data límit',
diff --git a/shared/src/i18n/ca/dashboard.ts b/shared/src/i18n/ca/dashboard.ts
index f264fbe5d..6e222d4a3 100644
--- a/shared/src/i18n/ca/dashboard.ts
+++ b/shared/src/i18n/ca/dashboard.ts
@@ -31,6 +31,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Dies',
'dashboard.places': 'Llocs',
'dashboard.members': 'Companys de viatge',
+ 'dashboard.card.buddies': 'Companys de viatge',
+ 'dashboard.card.buddies.one': 'Company',
'dashboard.archive': 'Arxiva',
'dashboard.copyTrip': 'Copia',
'dashboard.copySuffix': 'còpia',
@@ -113,14 +115,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'RECENT',
'dashboard.hero.tripDates': 'Dates del viatge',
'dashboard.hero.noDates': 'Sense dates',
- 'dashboard.hero.travelerOne': '{count} viatger',
- 'dashboard.hero.travelerMany': '{count} viatgers',
- 'dashboard.hero.destinationOne': '{count} destinació',
- 'dashboard.hero.destinationMany': '{count} destinacions',
- 'dashboard.hero.dayUnitOne': 'dia',
- 'dashboard.hero.dayUnitMany': 'dies',
- 'dashboard.hero.dayLeft': 'Dia restant',
+ 'dashboard.hero.travelers': '{count} viatgers',
+ 'dashboard.hero.travelers.one': '{count} viatger',
+ 'dashboard.hero.destinations': '{count} destinacions',
+ 'dashboard.hero.destinations.one': '{count} destinació',
+ 'dashboard.hero.dayUnit': 'dies',
+ 'dashboard.hero.dayUnit.one': 'dia',
'dashboard.hero.daysLeft': 'Dies restants',
+ 'dashboard.hero.daysLeft.one': 'Dia restant',
'dashboard.hero.lastDay': 'Últim dia',
'dashboard.hero.untilStart': "Fins a l'inici",
'dashboard.hero.startsIn': "Comença d'aquí a",
@@ -135,7 +137,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': "≈ {count}× al voltant de l'equador",
'dashboard.card.idea': 'Idea',
- 'dashboard.card.buddyOne': 'Company',
'dashboard.fx.from': 'De',
'dashboard.fx.to': 'A',
'dashboard.fx.unavailable': 'Tipus de canvi no disponible',
@@ -208,7 +209,7 @@ const dashboard: TranslationStrings = {
'Un sol canal de calendari per a tots els teus viatges actius, sincronitzat automàticament. Exclou els viatges arxivats i els que van acabar fa més de 90 dies.',
'dashboard.mobile.addCoverImage': 'Afegeix una imatge de portada',
'dashboard.mobile.spotlightDayOf': 'Dia {day} de {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} dia',
- 'dashboard.mobile.spotlightDaysMany': '{count} dies',
+ 'dashboard.mobile.spotlightDays': '{count} dies',
+ 'dashboard.mobile.spotlightDays.one': '{count} dia',
};
export default dashboard;
diff --git a/shared/src/i18n/ca/files.ts b/shared/src/i18n/ca/files.ts
index a71c63b71..75670493d 100644
--- a/shared/src/i18n/ca/files.ts
+++ b/shared/src/i18n/ca/files.ts
@@ -8,7 +8,7 @@ const files: TranslationStrings = {
'files.openError': "No s'ha pogut obrir el fitxer",
'files.downloadPdf': 'Baixa el PDF',
'files.count': '{count} fitxers',
- 'files.countSingular': '1 fitxer',
+ 'files.count.one': '{count} fitxer',
'files.uploaded': '{count} fitxers pujats',
'files.uploadError': 'La pujada ha fallat',
'files.dropzone': 'Arrossega els fitxers aquí',
diff --git a/shared/src/i18n/ca/members.ts b/shared/src/i18n/ca/members.ts
index b695f5589..ba94bea07 100644
--- a/shared/src/i18n/ca/members.ts
+++ b/shared/src/i18n/ca/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'Convida',
'members.allHaveAccess': 'Tots els usuaris ja hi tenen accés.',
'members.access': 'Accés',
- 'members.person': 'persona',
'members.persons': 'persones',
+ 'members.persons.one': 'persona',
'members.you': 'tu',
'members.owner': 'Propietari',
'members.leaveTrip': 'Abandona el viatge',
diff --git a/shared/src/i18n/ca/oauth.ts b/shared/src/i18n/ca/oauth.ts
index 5661b590c..8ad22d6d7 100644
--- a/shared/src/i18n/ca/oauth.ts
+++ b/shared/src/i18n/ca/oauth.ts
@@ -87,8 +87,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.trustNote':
'Només atorga accés a aplicacions de confiança. Les teves dades es mantenen al teu servidor.',
'oauth.authorize.selectScope': 'Selecciona almenys un àmbit',
- 'oauth.authorize.approveOneScope': 'Aprova ({count} àmbit)',
- 'oauth.authorize.approveManyScopes': 'Aprova ({count} àmbits)',
+ 'oauth.authorize.approveScopes': 'Aprova ({count} àmbits)',
+ 'oauth.authorize.approveScopes.one': 'Aprova ({count} àmbit)',
'oauth.authorize.approveAccess': "Aprova l'accés",
'oauth.authorize.deny': 'Denega',
'oauth.authorize.choosePermissions': 'Tria quins permisos atorgar',
diff --git a/shared/src/i18n/ca/places.ts b/shared/src/i18n/ca/places.ts
index f68f3bca8..883785bff 100644
--- a/shared/src/i18n/ca/places.ts
+++ b/shared/src/i18n/ca/places.ts
@@ -69,7 +69,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'categories',
'places.clearFilter': 'Esborra el filtre',
'places.count': '{count} llocs',
- 'places.countSingular': '1 lloc',
+ 'places.count.one': '{count} lloc',
'places.allPlanned': 'Tots els llocs estan planificats',
'places.noneFound': "No s'han trobat llocs",
'places.editPlace': 'Edita el lloc',
diff --git a/shared/src/i18n/ca/tours.ts b/shared/src/i18n/ca/tours.ts
index ef73e1424..be07e2f40 100644
--- a/shared/src/i18n/ca/tours.ts
+++ b/shared/src/i18n/ca/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Altitud o mètriques incompletes: importada amb reserves',
'tours.import.caution': 'Importada amb reserves: altitud o mètriques incompletes',
'tours.import.success': '{count} excursions importades de GPX',
- 'tours.import.successOne': '1 excursió importada de GPX',
+ 'tours.import.success.one': '{count} excursió importada de GPX',
'tours.import.noTrack': "No s'ha trobat cap traça ni ruta en aquest fitxer GPX",
'tours.import.error': "No s'ha pogut importar el fitxer GPX",
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/cs/atlas.ts b/shared/src/i18n/cs/atlas.ts
index 87e8577cf..839ae4a6b 100644
--- a/shared/src/i18n/cs/atlas.ts
+++ b/shared/src/i18n/cs/atlas.ts
@@ -41,10 +41,10 @@ const atlas: TranslationStrings = {
'atlas.streak': 'Série',
'atlas.year': 'rok',
'atlas.years': 'roky/let',
- 'atlas.yearInRow': 'rok v řadě',
'atlas.yearsInRow': 'let v řadě',
- 'atlas.tripIn': 'cesta v roce',
+ 'atlas.yearsInRow.one': 'rok v řadě',
'atlas.tripsIn': 'cest v roce',
+ 'atlas.tripsIn.one': 'cesta v roce',
'atlas.since': 'od',
'atlas.europe': 'Evropa',
'atlas.asia': 'Asie',
@@ -55,10 +55,11 @@ const atlas: TranslationStrings = {
'atlas.other': 'Ostatní',
'atlas.firstVisit': 'První cesta',
'atlas.lastVisitLabel': 'Poslední cesta',
- 'atlas.tripSingular': 'Cesta',
'atlas.tripPlural': 'Cesty',
- 'atlas.placeVisited': 'Navštívené místo',
+ 'atlas.tripsLabel': 'Cesty',
+ 'atlas.tripsLabel.one': 'Cesta',
'atlas.placesVisited': 'Navštívená místa',
+ 'atlas.placesVisited.one': 'Navštívené místo',
'atlas.searchCountry': 'Hledat zemi...',
'atlas.searchPlaces': 'Místa',
'atlas.planned': 'Plánováno',
diff --git a/shared/src/i18n/cs/collab.ts b/shared/src/i18n/cs/collab.ts
index 9fa356d84..1c8fd29b4 100644
--- a/shared/src/i18n/cs/collab.ts
+++ b/shared/src/i18n/cs/collab.ts
@@ -89,7 +89,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Uzavřít',
'collab.polls.closed': 'Uzavřeno',
'collab.polls.votes': '{n} hlasů',
- 'collab.polls.vote': '{n} hlas',
+ 'collab.polls.votes.one': '{n} hlas',
'collab.polls.multipleChoice': 'Více možností',
'collab.polls.multiChoice': 'Více možností',
'collab.polls.deadline': 'Termín',
diff --git a/shared/src/i18n/cs/dashboard.ts b/shared/src/i18n/cs/dashboard.ts
index 317000e57..fe8c2cd83 100644
--- a/shared/src/i18n/cs/dashboard.ts
+++ b/shared/src/i18n/cs/dashboard.ts
@@ -34,6 +34,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Dní',
'dashboard.places': 'Míst',
'dashboard.members': 'Cestovní parťáci',
+ 'dashboard.card.buddies': 'Cestovní parťáci',
+ 'dashboard.card.buddies.one': 'Parťák',
'dashboard.archive': 'Archivovat',
'dashboard.copyTrip': 'Kopírovat',
'dashboard.copySuffix': 'kopie',
@@ -143,8 +145,8 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': 'Za {count} dní',
'dashboard.mobile.inMonths': 'Za {count} měsíců',
'dashboard.mobile.spotlightDayOf': 'Den {day} z {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} den',
- 'dashboard.mobile.spotlightDaysMany': '{count} dní',
+ 'dashboard.mobile.spotlightDays': '{count} dní',
+ 'dashboard.mobile.spotlightDays.one': '{count} den',
'dashboard.mobile.completed': 'Dokončeno',
'dashboard.mobile.currencyConverter': 'Převodník měn',
'dashboard.filter.planned': 'Plánované',
@@ -155,14 +157,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'NEDÁVNO',
'dashboard.hero.tripDates': 'Termíny cesty',
'dashboard.hero.noDates': 'Bez termínu',
- 'dashboard.hero.travelerOne': '{count} cestovatel',
- 'dashboard.hero.travelerMany': '{count} cestovatelů',
- 'dashboard.hero.destinationOne': '{count} cíl',
- 'dashboard.hero.destinationMany': '{count} cílů',
- 'dashboard.hero.dayUnitOne': 'den',
- 'dashboard.hero.dayUnitMany': 'dní',
- 'dashboard.hero.dayLeft': 'Zbývá den',
+ 'dashboard.hero.travelers': '{count} cestovatelů',
+ 'dashboard.hero.travelers.one': '{count} cestovatel',
+ 'dashboard.hero.destinations': '{count} cílů',
+ 'dashboard.hero.destinations.one': '{count} cíl',
+ 'dashboard.hero.dayUnit': 'dní',
+ 'dashboard.hero.dayUnit.one': 'den',
'dashboard.hero.daysLeft': 'Zbývá dní',
+ 'dashboard.hero.daysLeft.one': 'Zbývá den',
'dashboard.hero.lastDay': 'Poslední den',
'dashboard.hero.untilStart': 'Do začátku',
'dashboard.hero.startsIn': 'Začíná za',
@@ -177,7 +179,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ {count}× kolem rovníku',
'dashboard.card.idea': 'Nápad',
- 'dashboard.card.buddyOne': 'Parťák',
'dashboard.fx.from': 'Z',
'dashboard.fx.to': 'Na',
'dashboard.fx.unavailable': 'Kurz není dostupný',
diff --git a/shared/src/i18n/cs/files.ts b/shared/src/i18n/cs/files.ts
index 5083bda14..6ff79d4b8 100644
--- a/shared/src/i18n/cs/files.ts
+++ b/shared/src/i18n/cs/files.ts
@@ -14,7 +14,7 @@ const files: TranslationStrings = {
'files.openError': 'Soubor nelze otevřít',
'files.downloadPdf': 'Stáhnout PDF',
'files.count': '{count} souborů',
- 'files.countSingular': '1 soubor',
+ 'files.count.one': '{count} soubor',
'files.uploaded': '{count} nahráno',
'files.uploadError': 'Nahrávání se nezdařilo',
'files.uploadErrorType': 'Tento typ souboru není podporován',
diff --git a/shared/src/i18n/cs/members.ts b/shared/src/i18n/cs/members.ts
index 795e3fad3..82f6ce69f 100644
--- a/shared/src/i18n/cs/members.ts
+++ b/shared/src/i18n/cs/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'Pozvat',
'members.allHaveAccess': 'Všichni uživatelé již mají přístup.',
'members.access': 'Přístup',
- 'members.person': 'osoba',
'members.persons': 'osob',
+ 'members.persons.one': 'osoba',
'members.you': 'vy',
'members.owner': 'Vlastník',
'members.leaveTrip': 'Opustit cestu',
diff --git a/shared/src/i18n/cs/oauth.ts b/shared/src/i18n/cs/oauth.ts
index 314f15cad..07d0c2ab8 100644
--- a/shared/src/i18n/cs/oauth.ts
+++ b/shared/src/i18n/cs/oauth.ts
@@ -84,8 +84,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes.one': 'Approve ({count} scope)', // en-fallback
'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
'oauth.authorize.deny': 'Deny', // en-fallback
'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
diff --git a/shared/src/i18n/cs/places.ts b/shared/src/i18n/cs/places.ts
index abc4656ad..1bd52194b 100644
--- a/shared/src/i18n/cs/places.ts
+++ b/shared/src/i18n/cs/places.ts
@@ -70,7 +70,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'kategorií',
'places.clearFilter': 'Vymazat filtr',
'places.count': '{count} míst',
- 'places.countSingular': '1 místo',
+ 'places.count.one': '{count} místo',
'places.allPlanned': 'Všechna místa jsou naplánována',
'places.noneFound': 'Žádná místa nebyla nalezena',
'places.editPlace': 'Upravit místo',
diff --git a/shared/src/i18n/cs/tours.ts b/shared/src/i18n/cs/tours.ts
index c0ac8607f..0806e427b 100644
--- a/shared/src/i18n/cs/tours.ts
+++ b/shared/src/i18n/cs/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Neúplné výškové údaje nebo metriky – importováno s výhradou',
'tours.import.caution': 'Importováno s výhradou – neúplné výškové údaje nebo metriky',
'tours.import.success': 'Počet výletů importovaných z GPX: {count}',
- 'tours.import.successOne': 'Z GPX byl importován 1 výlet',
+ 'tours.import.success.one': 'Z GPX byl importován {count} výlet',
'tours.import.noTrack': 'V tomto souboru GPX nebyla nalezena stopa ani trasa',
'tours.import.error': 'Soubor GPX se nepodařilo importovat',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/de/atlas.ts b/shared/src/i18n/de/atlas.ts
index 9f222f175..756b481b0 100644
--- a/shared/src/i18n/de/atlas.ts
+++ b/shared/src/i18n/de/atlas.ts
@@ -43,10 +43,10 @@ const atlas: TranslationStrings = {
'atlas.daysLeft': 'Tage',
'atlas.streak': 'Serie',
'atlas.years': 'Jahre',
- 'atlas.yearInRow': 'Jahr in Folge',
'atlas.yearsInRow': 'Jahre in Folge',
- 'atlas.tripIn': 'Reise in',
+ 'atlas.yearsInRow.one': 'Jahr in Folge',
'atlas.tripsIn': 'Reisen in',
+ 'atlas.tripsIn.one': 'Reise in',
'atlas.since': 'seit',
'atlas.europe': 'Europa',
'atlas.asia': 'Asien',
@@ -57,10 +57,11 @@ const atlas: TranslationStrings = {
'atlas.other': 'Andere',
'atlas.firstVisit': 'Erste Reise',
'atlas.lastVisitLabel': 'Letzte Reise',
- 'atlas.tripSingular': 'Reise',
'atlas.tripPlural': 'Reisen',
- 'atlas.placeVisited': 'Ort besucht',
+ 'atlas.tripsLabel': 'Reisen',
+ 'atlas.tripsLabel.one': 'Reise',
'atlas.placesVisited': 'Orte besucht',
+ 'atlas.placesVisited.one': 'Ort besucht',
'atlas.planned': 'Geplant',
'atlas.showPlanned': 'Geplante Länder anzeigen',
'atlas.plannedFor': 'Geplant für',
diff --git a/shared/src/i18n/de/collab.ts b/shared/src/i18n/de/collab.ts
index d7aca4aa6..bbd88b47b 100644
--- a/shared/src/i18n/de/collab.ts
+++ b/shared/src/i18n/de/collab.ts
@@ -89,7 +89,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Schließen',
'collab.polls.closed': 'Geschlossen',
'collab.polls.votes': '{n} Stimmen',
- 'collab.polls.vote': '{n} Stimme',
+ 'collab.polls.votes.one': '{n} Stimme',
'collab.polls.multipleChoice': 'Mehrfachauswahl',
'collab.polls.multiChoice': 'Mehrfachauswahl',
'collab.polls.deadline': 'Frist',
diff --git a/shared/src/i18n/de/dashboard.ts b/shared/src/i18n/de/dashboard.ts
index 01026b022..0da0cc382 100644
--- a/shared/src/i18n/de/dashboard.ts
+++ b/shared/src/i18n/de/dashboard.ts
@@ -35,6 +35,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Tage',
'dashboard.places': 'Orte',
'dashboard.members': 'Reise-Buddies',
+ 'dashboard.card.buddies': 'Reise-Buddies',
+ 'dashboard.card.buddies.one': 'Begleiter',
'dashboard.archive': 'Archivieren',
'dashboard.copyTrip': 'Kopieren',
'dashboard.copySuffix': 'Kopie',
@@ -146,8 +148,8 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.tomorrow': 'Morgen',
'dashboard.mobile.inDays': 'In {count} Tagen',
'dashboard.mobile.spotlightDayOf': 'Tag {day} von {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} Tag',
- 'dashboard.mobile.spotlightDaysMany': '{count} Tage',
+ 'dashboard.mobile.spotlightDays': '{count} Tage',
+ 'dashboard.mobile.spotlightDays.one': '{count} Tag',
'dashboard.mobile.inMonths': 'In {count} Monaten',
'dashboard.mobile.completed': 'Abgeschlossen',
'dashboard.mobile.currencyConverter': 'Währungsrechner',
@@ -159,14 +161,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'KÜRZLICH',
'dashboard.hero.tripDates': 'Reisedaten',
'dashboard.hero.noDates': 'Freie Planung',
- 'dashboard.hero.travelerOne': '{count} Reisender',
- 'dashboard.hero.travelerMany': '{count} Reisende',
- 'dashboard.hero.destinationOne': '{count} Ziel',
- 'dashboard.hero.destinationMany': '{count} Ziele',
- 'dashboard.hero.dayUnitOne': 'Tag',
- 'dashboard.hero.dayUnitMany': 'Tage',
- 'dashboard.hero.dayLeft': 'Tag übrig',
+ 'dashboard.hero.travelers': '{count} Reisende',
+ 'dashboard.hero.travelers.one': '{count} Reisender',
+ 'dashboard.hero.destinations': '{count} Ziele',
+ 'dashboard.hero.destinations.one': '{count} Ziel',
+ 'dashboard.hero.dayUnit': 'Tage',
+ 'dashboard.hero.dayUnit.one': 'Tag',
'dashboard.hero.daysLeft': 'Tage übrig',
+ 'dashboard.hero.daysLeft.one': 'Tag übrig',
'dashboard.hero.lastDay': 'Letzter Tag',
'dashboard.hero.untilStart': 'Bis Start',
'dashboard.hero.startsIn': 'Reise beginnt in',
@@ -181,7 +183,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ {count}× um den Äquator',
'dashboard.card.idea': 'Idee',
- 'dashboard.card.buddyOne': 'Begleiter',
'dashboard.fx.from': 'Von',
'dashboard.fx.to': 'Nach',
'dashboard.fx.unavailable': 'Kurs nicht verfügbar',
diff --git a/shared/src/i18n/de/files.ts b/shared/src/i18n/de/files.ts
index e34343b8b..dbd209199 100644
--- a/shared/src/i18n/de/files.ts
+++ b/shared/src/i18n/de/files.ts
@@ -14,7 +14,7 @@ const files: TranslationStrings = {
'files.openError': 'Datei konnte nicht geöffnet werden',
'files.downloadPdf': 'PDF herunterladen',
'files.count': '{count} Dateien',
- 'files.countSingular': '1 Datei',
+ 'files.count.one': '{count} Datei',
'files.uploaded': '{count} hochgeladen',
'files.uploadError': 'Fehler beim Hochladen',
'files.uploadErrorType': 'Dieser Dateityp wird nicht unterstützt',
diff --git a/shared/src/i18n/de/members.ts b/shared/src/i18n/de/members.ts
index 37fee68e1..3ebee7122 100644
--- a/shared/src/i18n/de/members.ts
+++ b/shared/src/i18n/de/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'Einladen',
'members.allHaveAccess': 'Alle Benutzer haben bereits Zugriff.',
'members.access': 'Zugriff',
- 'members.person': 'Person',
'members.persons': 'Personen',
+ 'members.persons.one': 'Person',
'members.you': 'du',
'members.owner': 'Eigentümer',
'members.leaveTrip': 'Reise verlassen',
diff --git a/shared/src/i18n/de/oauth.ts b/shared/src/i18n/de/oauth.ts
index 6e7cafa29..b408a103b 100644
--- a/shared/src/i18n/de/oauth.ts
+++ b/shared/src/i18n/de/oauth.ts
@@ -88,8 +88,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes.one': 'Approve ({count} scope)', // en-fallback
'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
'oauth.authorize.deny': 'Deny', // en-fallback
'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
diff --git a/shared/src/i18n/de/places.ts b/shared/src/i18n/de/places.ts
index d16b51d76..6018c6e68 100644
--- a/shared/src/i18n/de/places.ts
+++ b/shared/src/i18n/de/places.ts
@@ -70,7 +70,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'Kategorien',
'places.clearFilter': 'Filter zurücksetzen',
'places.count': '{count} Orte',
- 'places.countSingular': '1 Ort',
+ 'places.count.one': '{count} Ort',
'places.allPlanned': 'Alle Orte sind eingeplant',
'places.noneFound': 'Keine Orte gefunden',
'places.editPlace': 'Ort bearbeiten',
diff --git a/shared/src/i18n/de/tours.ts b/shared/src/i18n/de/tours.ts
index 992c90906..93be51b2f 100644
--- a/shared/src/i18n/de/tours.ts
+++ b/shared/src/i18n/de/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Höhendaten oder Streckenwerte unvollständig – mit Vorsicht importiert',
'tours.import.caution': 'Mit Vorsicht importiert – Höhendaten oder Streckenwerte unvollständig',
'tours.import.success': '{count} Touren aus GPX importiert',
- 'tours.import.successOne': '1 Tour aus GPX importiert',
+ 'tours.import.success.one': '{count} Tour aus GPX importiert',
'tours.import.noTrack': 'Keine Strecke oder Route in dieser GPX-Datei gefunden',
'tours.import.error': 'GPX-Datei konnte nicht importiert werden',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/en/admin.ts b/shared/src/i18n/en/admin.ts
index 89b65d7c3..5d24f54c4 100644
--- a/shared/src/i18n/en/admin.ts
+++ b/shared/src/i18n/en/admin.ts
@@ -336,7 +336,8 @@ const admin: TranslationStrings = {
'This plugin does not use operator-supplied hosts. Its allowed hosts are fixed in its manifest.',
'admin.plugins.allowedHosts.restartNote': 'Saving restarts the plugin so it picks up the new list.',
'admin.plugins.allowedHosts.add': 'Add allowed host',
- 'admin.plugins.allowedHosts.count': '{n} allowed host(s)',
+ 'admin.plugins.allowedHosts.count': '{n} allowed hosts',
+ 'admin.plugins.allowedHosts.count.one': '{n} allowed host',
'admin.plugins.operatorEgressPill': '+ hosts you add',
'admin.plugins.operatorEgressHint':
'This plugin talks to a service only you can name. After installing, add the hosts it may reach under ⋯ → Allowed hosts. It can reach no others.',
@@ -552,6 +553,7 @@ const admin: TranslationStrings = {
'admin.plugins.sortUpdates': 'Updates first',
'admin.plugins.sortDownloads': 'Most downloads',
'admin.plugins.updatesAvailable': '{count} updates available for your plugins.',
+ 'admin.plugins.updatesAvailable.one': '{count} update available for your plugins.',
'admin.plugins.newerNeedsTrek': 'v{version} available — needs TREK {range}',
'admin.plugins.updateAll': 'Update all',
'admin.plugins.versionsTitle': 'Versions',
@@ -800,7 +802,8 @@ const admin: TranslationStrings = {
'admin.passkey.resetHint':
"Remove all of this user's passkeys (e.g. on a lost device). They can still sign in with their password.",
'admin.passkey.resetConfirm': 'Remove all passkeys for {name}?',
- 'admin.passkey.resetDone': 'Removed {count} passkey(s)',
+ 'admin.passkey.resetDone': 'Removed {count} passkeys',
+ 'admin.passkey.resetDone.one': 'Removed {count} passkey',
'admin.group.users': 'Users',
'admin.group.config': 'Configuration',
'admin.group.integration': 'Integrations',
diff --git a/shared/src/i18n/en/atlas.ts b/shared/src/i18n/en/atlas.ts
index 7ec17102a..975e3b067 100644
--- a/shared/src/i18n/en/atlas.ts
+++ b/shared/src/i18n/en/atlas.ts
@@ -43,10 +43,10 @@ const atlas: TranslationStrings = {
'atlas.daysLeft': 'days left',
'atlas.streak': 'Streak',
'atlas.years': 'years',
- 'atlas.yearInRow': 'year in a row',
'atlas.yearsInRow': 'years in a row',
- 'atlas.tripIn': 'trip in',
+ 'atlas.yearsInRow.one': 'year in a row',
'atlas.tripsIn': 'trips in',
+ 'atlas.tripsIn.one': 'trip in',
'atlas.since': 'since',
'atlas.europe': 'Europe',
'atlas.asia': 'Asia',
@@ -57,13 +57,16 @@ const atlas: TranslationStrings = {
'atlas.other': 'Other',
'atlas.firstVisit': 'First trip',
'atlas.lastVisitLabel': 'Last trip',
- 'atlas.tripSingular': 'Trip',
'atlas.tripPlural': 'Trips',
- 'atlas.placeVisited': 'Place visited',
+ 'atlas.tripsLabel': 'Trips',
+ 'atlas.tripsLabel.one': 'Trip',
'atlas.placesVisited': 'Places visited',
+ 'atlas.placesVisited.one': 'Place visited',
'atlas.planned': 'Planned',
'atlas.showPlanned': 'Show planned countries',
'atlas.plannedFor': 'Planned for',
'atlas.antarctica': 'Antarctica',
+ 'atlas.regionPlacesLabel': 'places',
+ 'atlas.regionPlacesLabel.one': 'place',
};
export default atlas;
diff --git a/shared/src/i18n/en/budget.ts b/shared/src/i18n/en/budget.ts
index 9dcce1bd9..4b90a55bd 100644
--- a/shared/src/i18n/en/budget.ts
+++ b/shared/src/i18n/en/budget.ts
@@ -29,6 +29,7 @@ const budget: TranslationStrings = {
'budget.editTooltip': 'Click to edit',
'budget.linkedToReservation': 'Linked to a reservation — edit the name there',
'budget.confirm.deleteCategory': 'Are you sure you want to delete the category "{name}" with {count} entries?',
+ 'budget.confirm.deleteCategory.one': 'Are you sure you want to delete the category "{name}" with {count} entry?',
'budget.deleteCategory': 'Delete Category',
'budget.perPerson': 'Per Person',
'budget.paid': 'Paid',
@@ -59,6 +60,7 @@ const budget: TranslationStrings = {
'costs.outstandingItems': 'expenses need a payer',
'costs.expenses': 'Expenses',
'costs.entries': '{count} entries',
+ 'costs.entries.one': '{count} entry',
'costs.searchPlaceholder': 'Search expenses…',
'costs.filter.all': 'All',
'costs.filter.mine': 'Paid by me',
@@ -66,6 +68,7 @@ const budget: TranslationStrings = {
'costs.filter.allCategories': 'All categories',
'costs.filter.allDays': 'All days',
'costs.expensesCount': '{count} expenses',
+ 'costs.expensesCount.one': '{count} expense',
'costs.addExpense': 'Add expense',
'costs.view.label': 'View',
'costs.view.list': 'List',
@@ -105,6 +108,7 @@ const budget: TranslationStrings = {
'costs.settleHistory': 'Settle history',
'costs.noSettlements': 'No settled payments yet.',
'costs.paymentsSettled': '{count} payments settled',
+ 'costs.paymentsSettled.one': '{count} payment settled',
'costs.paid': 'paid',
'costs.undo': 'Undo',
'costs.whatFor': 'What was it for?',
@@ -140,6 +144,7 @@ const budget: TranslationStrings = {
'costs.splitHint.ticket': 'List what was on the receipt and tick who had each item.',
'costs.pickSomeone': 'Pick at least one person to split with.',
'costs.splitSummary': 'Split {count} ways · {amount} each',
+ 'costs.splitSummary.one': 'Split {count} way · {amount} each',
'costs.cat.accommodation': 'Accommodation',
'costs.cat.food': 'Food & drink',
'costs.cat.groceries': 'Groceries',
@@ -155,7 +160,9 @@ const budget: TranslationStrings = {
'costs.cat.parking': 'Parking',
'costs.cat.other': 'Other',
'costs.daysCount': '{count} days',
+ 'costs.daysCount.one': '{count} day',
'costs.travelers': '{count} travelers',
+ 'costs.travelers.one': '{count} traveler',
'costs.liveRate': 'live rate',
'costs.settleAll': 'Settle all',
'costs.payment': 'Payment',
diff --git a/shared/src/i18n/en/collab.ts b/shared/src/i18n/en/collab.ts
index 06d5f3bab..6deb92299 100644
--- a/shared/src/i18n/en/collab.ts
+++ b/shared/src/i18n/en/collab.ts
@@ -89,7 +89,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Close',
'collab.polls.closed': 'Closed',
'collab.polls.votes': '{n} votes',
- 'collab.polls.vote': '{n} vote',
+ 'collab.polls.votes.one': '{n} vote',
'collab.polls.multipleChoice': 'Multiple choice',
'collab.polls.multiChoice': 'Multiple choice',
'collab.polls.deadline': 'Deadline',
diff --git a/shared/src/i18n/en/collection.ts b/shared/src/i18n/en/collection.ts
index 04c7f5d70..ba67b1c79 100644
--- a/shared/src/i18n/en/collection.ts
+++ b/shared/src/i18n/en/collection.ts
@@ -19,8 +19,11 @@ const collection: TranslationStrings = {
'collections.duplicateToListTitle': 'Duplicate {count} to another list',
'collections.noOtherLists': 'No other lists yet',
'collections.movedCount': 'Moved {count} places',
+ 'collections.movedCount.one': 'Moved {count} place',
'collections.duplicatedCount': 'Duplicated {count} places',
+ 'collections.duplicatedCount.one': 'Duplicated {count} place',
'collections.placeCount': '{count} places',
+ 'collections.placeCount.one': '{count} place',
'collections.category': 'Category',
'collections.noCategory': 'No category',
'collections.editListTitle': 'Edit list',
@@ -42,6 +45,7 @@ const collection: TranslationStrings = {
'collections.importPickTripHint': 'Pick a trip and take its places into {name}.',
'collections.importNoTrips': 'No trips yet',
'collections.importPlacesCount': '{count} places',
+ 'collections.importPlacesCount.one': '{count} place',
'collections.importSearchPlaces': 'Search places…',
'collections.importOnlyNew': 'Only new',
'collections.importSelectAll': 'Select all',
@@ -50,11 +54,14 @@ const collection: TranslationStrings = {
'collections.importUnscheduled': 'Unscheduled',
'collections.importOnDay': 'Day {count}',
'collections.importSelectedCount': '{count} selected',
+ 'collections.importSelectedCount.one': '{count} selected',
'collections.importAction': 'Import {count}',
'collections.importEmptyTrip': 'This trip has no places yet',
'collections.importNothingNew': 'Everything from this trip is already on this list',
'collections.importDone': '{count} places added',
+ 'collections.importDone.one': '{count} place added',
'collections.importDoneSkipped': '{count} were already on the list and stayed as they were',
+ 'collections.importDoneSkipped.one': '{count} was already on the list and stayed as it was',
'collections.importDoneClean': 'They are all in {name} now',
'collections.sort.default': 'Default',
'collections.sort.nameAsc': 'Name (A–Z)',
@@ -103,6 +110,7 @@ const collection: TranslationStrings = {
'collections.markVisitedNone': 'None of these places are saved in a list',
'collections.markedVisited': 'Marked as visited',
'collections.markedVisitedTrip': '{count} places marked as visited',
+ 'collections.markedVisitedTrip.one': '{count} place marked as visited',
'collections.copyToTrip': 'Copy to trip',
'collections.copyToTripTitle': 'Copy to trip',
@@ -114,7 +122,9 @@ const collection: TranslationStrings = {
'collections.alreadyInTrip': 'Already in this trip',
'collections.copied': 'Copied to trip',
'collections.copiedCount': 'Copied {count} places',
+ 'collections.copiedCount.one': 'Copied {count} place',
'collections.skippedDuplicates': 'Skipped {count} duplicates',
+ 'collections.skippedDuplicates.one': 'Skipped {count} duplicate',
'collections.copyNothing': 'Nothing to copy',
'collections.removeFromList': 'Remove from list',
'collections.removeFromListConfirm': 'Remove this place from the list?',
@@ -128,6 +138,7 @@ const collection: TranslationStrings = {
'collections.search': 'Search places',
'collections.selectMode': 'Select',
'collections.selectedCount': '{count} selected',
+ 'collections.selectedCount.one': '{count} selected',
'collections.pickList': 'Save to list',
'collections.pickListPlaceholder': 'Choose a list',
@@ -159,14 +170,18 @@ const collection: TranslationStrings = {
'collections.file.intoHint': 'Places the list already has stay as they are, and so do its name and colour. Labels the file brings are added.',
'collections.file.confirmInto': 'Add to list',
'collections.file.doneInto': '{count} places added to {name}',
+ 'collections.file.doneInto.one': '{count} place added to {name}',
'collections.file.doneIntoDuplicates': '{count} added to {name}, {duplicates} were already there',
'collections.file.doneIntoNothing': 'Every place in the file is already in {name}',
'collections.file.change': 'Change',
'collections.file.labelCount': '{count} labels',
+ 'collections.file.labelCount.one': '{count} label',
'collections.file.hint':
'The places arrive as a new list of your own. Ratings, members and uploaded photos stay behind.',
'collections.file.done': '{count} places imported',
+ 'collections.file.done.one': '{count} place imported',
'collections.file.doneSkipped': '{count} places imported, {skipped} skipped',
+ 'collections.file.doneSkipped.one': '{count} place imported, {skipped} skipped',
'collections.file.errorTooLarge': 'That file is too large to be a list.',
'collections.file.errorUnreadable': 'That file could not be read.',
'collections.file.errorNotACollection': 'That is not a TREK list file.',
@@ -174,10 +189,13 @@ const collection: TranslationStrings = {
'collections.file.formatTrekHint': 'For another TREK, with labels and status',
'collections.file.formatGpxHint': 'Waypoints for OsmAnd, Organic Maps, Garmin and other map apps',
'collections.file.gpxOmitted': '{count} places have no coordinates and were left out of the GPX file',
+ 'collections.file.gpxOmitted.one': '{count} place has no coordinates and was left out of the GPX file',
'collections.file.gpxNothing': 'None of these places has coordinates, so there is nothing to put in a GPX file.',
'collections.file.reading': 'Reading the file…',
'collections.file.gpxSkipped': '{count} waypoints without usable coordinates are left out.',
+ 'collections.file.gpxSkipped.one': '{count} waypoint without usable coordinates is left out.',
'collections.file.gpxTrack': 'This file also has {count} track points. Tracks are not imported, only waypoints.',
+ 'collections.file.gpxTrack.one': 'This file also has {count} track point. Tracks are not imported, only waypoints.',
'collections.file.gpxEmpty': 'This GPX file has no waypoints, so there is nothing to import.',
'collections.file.errorNotGpx': 'That is not a GPX file.',
'collections.file.errorTooManyPlaces':
@@ -216,6 +234,7 @@ const collection: TranslationStrings = {
'collections.widget.title': 'Collections',
'collections.widget.empty': 'No saved places yet',
'collections.widget.savedCount': '{count} saved',
+ 'collections.widget.savedCount.one': '{count} saved',
'collections.labels.title': 'Labels',
'collections.labels.manage': 'Manage labels',
@@ -224,7 +243,9 @@ const collection: TranslationStrings = {
'collections.labels.namePlaceholder': 'e.g. Berlin',
'collections.labels.assign': 'Assign label',
'collections.labels.assignN': 'Add labels to {count} places',
+ 'collections.labels.assignN.one': 'Add labels to {count} place',
'collections.labels.assignedCount': 'Labelled {count} places',
+ 'collections.labels.assignedCount.one': 'Labelled {count} place',
'collections.labels.empty': 'No labels yet',
'collections.labels.emptyHint': 'Create a label first to group places in this list.',
};
diff --git a/shared/src/i18n/en/dashboard.ts b/shared/src/i18n/en/dashboard.ts
index 3c4dd3f5f..0bbf79115 100644
--- a/shared/src/i18n/en/dashboard.ts
+++ b/shared/src/i18n/en/dashboard.ts
@@ -4,10 +4,12 @@ const dashboard: TranslationStrings = {
'dashboard.title': 'My Trips',
'dashboard.subtitle.loading': 'Loading trips...',
'dashboard.subtitle.trips': '{count} trips ({archived} archived)',
+ 'dashboard.subtitle.trips.one': '{count} trip ({archived} archived)',
'dashboard.subtitle.empty': 'Start your first trip',
'dashboard.subtitle.activeOne': '{count} active trip',
'dashboard.subtitle.activeMany': '{count} active trips',
'dashboard.subtitle.archivedSuffix': ' · {count} archived',
+ 'dashboard.subtitle.archivedSuffix.one': ' · {count} archived',
'dashboard.newTrip': 'New Trip',
'dashboard.newTripSub': 'Plan a new trip from scratch',
'dashboard.subscribeAllTrips': 'Subscribe to all trips',
@@ -34,6 +36,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Days',
'dashboard.places': 'Places',
'dashboard.members': 'Buddies',
+ 'dashboard.card.buddies': 'Buddies',
+ 'dashboard.card.buddies.one': 'Buddy',
'dashboard.archive': 'Archive',
'dashboard.copyTrip': 'Copy',
'dashboard.copySuffix': 'copy',
@@ -48,6 +52,7 @@ const dashboard: TranslationStrings = {
'dashboard.status.tomorrow': 'Tomorrow',
'dashboard.status.past': 'Past',
'dashboard.status.daysLeft': '{count} days left',
+ 'dashboard.status.daysLeft.one': '{count} day left',
'dashboard.toast.loadError': 'Failed to load trips',
'dashboard.loadErrorBanner': "Couldn't reach the server. Your trips are safe — please try again.",
'dashboard.retry': 'Retry',
@@ -139,6 +144,7 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.liveNow': 'Live Now',
'dashboard.mobile.tripProgress': 'Trip progress',
'dashboard.mobile.daysLeft': '{count} days left',
+ 'dashboard.mobile.daysLeft.one': '{count} day left',
'dashboard.mobile.places': 'Places',
'dashboard.mobile.buddies': 'Buddies',
'dashboard.mobile.newTrip': 'New Trip',
@@ -156,10 +162,12 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.startsToday': 'Starts today',
'dashboard.mobile.tomorrow': 'Tomorrow',
'dashboard.mobile.inDays': 'In {count} days',
+ 'dashboard.mobile.inDays.one': 'In {count} day',
'dashboard.mobile.inMonths': 'In {count} months',
+ 'dashboard.mobile.inMonths.one': 'In {count} month',
'dashboard.mobile.spotlightDayOf': 'Day {day} of {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} day',
- 'dashboard.mobile.spotlightDaysMany': '{count} days',
+ 'dashboard.mobile.spotlightDays': '{count} days',
+ 'dashboard.mobile.spotlightDays.one': '{count} day',
'dashboard.mobile.completed': 'Completed',
'dashboard.mobile.currencyConverter': 'Currency Converter',
'dashboard.filter.planned': 'Planned',
@@ -170,14 +178,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'RECENT',
'dashboard.hero.tripDates': 'Trip dates',
'dashboard.hero.noDates': 'Open dates',
- 'dashboard.hero.travelerOne': '{count} traveler',
- 'dashboard.hero.travelerMany': '{count} travelers',
- 'dashboard.hero.destinationOne': '{count} destination',
- 'dashboard.hero.destinationMany': '{count} destinations',
- 'dashboard.hero.dayUnitOne': 'day',
- 'dashboard.hero.dayUnitMany': 'days',
- 'dashboard.hero.dayLeft': 'Day left',
+ 'dashboard.hero.travelers': '{count} travelers',
+ 'dashboard.hero.travelers.one': '{count} traveler',
+ 'dashboard.hero.destinations': '{count} destinations',
+ 'dashboard.hero.destinations.one': '{count} destination',
+ 'dashboard.hero.dayUnit': 'days',
+ 'dashboard.hero.dayUnit.one': 'day',
'dashboard.hero.daysLeft': 'Days left',
+ 'dashboard.hero.daysLeft.one': 'Day left',
'dashboard.hero.lastDay': 'Last day',
'dashboard.hero.untilStart': 'Until start',
'dashboard.hero.startsIn': 'Trip starts in',
@@ -185,6 +193,7 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.ofTotal': 'of {total}',
'dashboard.atlas.tripsTotal': 'Trips total',
'dashboard.atlas.placesMapped': '{count} places mapped',
+ 'dashboard.atlas.placesMapped.one': '{count} place mapped',
'dashboard.atlas.daysTraveled': 'Days traveled',
'dashboard.atlas.daysUnit': 'days',
'dashboard.atlas.acrossAllTrips': 'across all trips',
@@ -192,7 +201,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ {count}× around the equator',
'dashboard.card.idea': 'Idea',
- 'dashboard.card.buddyOne': 'Buddy',
'dashboard.fx.from': 'From',
'dashboard.fx.to': 'To',
'dashboard.fx.unavailable': 'Rate unavailable',
diff --git a/shared/src/i18n/en/dawarich.ts b/shared/src/i18n/en/dawarich.ts
index b9f16e240..7729600fb 100644
--- a/shared/src/i18n/en/dawarich.ts
+++ b/shared/src/i18n/en/dawarich.ts
@@ -15,6 +15,7 @@ const dawarich: TranslationStrings = {
'dawarich.syncEnabledHint': 'Off means TREK only reads Dawarich when you ask it to.',
'dawarich.test.button': 'Test connection',
'dawarich.test.success': 'Connected. {count} stays found in the last 30 days.',
+ 'dawarich.test.success.one': 'Connected. {count} stay found in the last 30 days.',
'dawarich.test.failed': 'Could not reach Dawarich.',
'dawarich.syncNow': 'Check now',
'dawarich.connected': 'Connected',
@@ -29,6 +30,7 @@ const dawarich: TranslationStrings = {
'dawarich.toast.saveError': 'Could not save the connection',
'dawarich.toast.disconnected': 'Dawarich disconnected',
'dawarich.toast.synced': '{count} new stays found',
+ 'dawarich.toast.synced.one': '{count} new stay found',
'dawarich.toast.syncError': 'Could not read Dawarich',
'dawarich.toast.syncRunning': 'A check is already running',
'dawarich.toast.acceptError': 'Could not add this',
@@ -80,6 +82,7 @@ const dawarich: TranslationStrings = {
'dawarich.suggestions.title': 'From Dawarich',
'dawarich.suggestions.pending': '{count} waiting for you',
+ 'dawarich.suggestions.pending.one': '{count} waiting for you',
'dawarich.suggestions.loading': 'Reading Dawarich…',
'dawarich.suggestions.notConnected': 'Connect Dawarich in Settings to see your stays here.',
'dawarich.suggestions.unavailable': 'Dawarich could not be read.',
@@ -142,7 +145,9 @@ const dawarich: TranslationStrings = {
'dawarich.bucket.alreadyVisited': 'Already ticked off',
'dawarich.bucket.confirm': 'Tick off {count}',
'dawarich.bucket.confirmed': '{count} wishes ticked off',
+ 'dawarich.bucket.confirmed.one': '{count} wish ticked off',
'dawarich.bucket.skipped': '{count} entries have no coordinates and could not be checked.',
+ 'dawarich.bucket.skipped.one': '{count} entry has no coordinates and could not be checked.',
'dawarich.bucket.truncated': 'Only the first entries were checked. Run it again for the rest.',
'dawarich.bucket.visitedFrom': 'Ticked off from your Dawarich recordings',
'dawarich.bucket.clearVisit': 'Undo',
@@ -155,9 +160,12 @@ const dawarich: TranslationStrings = {
'dawarich.atlas.loading': 'Reading your recordings…',
'dawarich.atlas.empty': 'Your recordings show no countries TREK does not already have.',
'dawarich.atlas.cities': '{count} cities',
+ 'dawarich.atlas.cities.one': '{count} city',
'dawarich.atlas.citiesOne': '1 city',
'dawarich.atlas.accept': 'Add {count} countries',
+ 'dawarich.atlas.accept.one': 'Add {count} country',
'dawarich.atlas.accepted': '{count} countries added',
+ 'dawarich.atlas.accepted.one': '{count} country added',
'dawarich.atlas.unresolved': 'TREK could not match these to a country: {names}.',
'dawarich.atlas.source': 'From Dawarich',
'dawarich.atlas.range': 'Looked at {from} to {to}',
@@ -168,6 +176,7 @@ const dawarich: TranslationStrings = {
'dawarich.atlas.tab.countries': 'Countries',
'dawarich.atlas.window': 'The last 12 months were looked at.',
'dawarich.selected': '{count} selected',
+ 'dawarich.selected.one': '{count} selected',
'dawarich.again': 'Check again',
'dawarich.bucket.metersAway': '{meters} m away',
'dawarich.bucket.kilometersAway': '{km} km away',
diff --git a/shared/src/i18n/en/files.ts b/shared/src/i18n/en/files.ts
index dc9b3a0a6..23256e1af 100644
--- a/shared/src/i18n/en/files.ts
+++ b/shared/src/i18n/en/files.ts
@@ -10,12 +10,14 @@ const files: TranslationStrings = {
'files.title': 'Files',
'files.pageTitle': 'Files & Documents',
'files.subtitle': '{count} files for {trip}',
+ 'files.subtitle.one': '{count} file for {trip}',
'files.download': 'Download',
'files.openError': 'Could not open file',
'files.downloadPdf': 'Download PDF',
'files.count': '{count} files',
- 'files.countSingular': '1 file',
+ 'files.count.one': '{count} file',
'files.uploaded': '{count} uploaded',
+ 'files.uploaded.one': '{count} uploaded',
'files.uploadError': 'Upload failed',
'files.uploadErrorType': "This file type isn't supported",
'files.dropzone': 'Drop files here',
diff --git a/shared/src/i18n/en/help.ts b/shared/src/i18n/en/help.ts
index 6e1fe0690..b3d3ce73e 100644
--- a/shared/src/i18n/en/help.ts
+++ b/shared/src/i18n/en/help.ts
@@ -15,8 +15,10 @@ const help: TranslationStrings = {
'help.center.screens': 'Screens',
'help.center.thisScreen': 'This screen',
'help.center.subScreens': '{count} sub-screens',
+ 'help.center.subScreens.one': '{count} sub-screen',
'help.center.subScreensLabel': 'Sub-screens',
'help.center.guidesCount': '{count} guides',
+ 'help.center.guidesCount.one': '{count} guide',
'help.center.goToScreen': 'Go to {screen}',
'help.center.overview': 'Overview',
'help.center.howTo': 'How do I…',
@@ -28,6 +30,7 @@ const help: TranslationStrings = {
'help.center.back': 'Back',
'help.center.close': 'Close help',
'help.center.steps': '{count} steps',
+ 'help.center.steps.one': '{count} step',
'help.center.step': 'Step {n}',
'help.center.stepsLabel': 'Steps',
'help.center.stepOf': 'Step {n} of {total}',
diff --git a/shared/src/i18n/en/inspector.ts b/shared/src/i18n/en/inspector.ts
index 0662641ee..9445aedbe 100644
--- a/shared/src/i18n/en/inspector.ts
+++ b/shared/src/i18n/en/inspector.ts
@@ -7,6 +7,7 @@ const inspector: TranslationStrings = {
'inspector.showHours': 'Show opening hours',
'inspector.files': 'Files',
'inspector.filesCount': '{count} files',
+ 'inspector.filesCount.one': '{count} file',
'inspector.remove': 'Remove',
'inspector.removeFromDay': 'Remove from Day',
'inspector.addToDay': 'Add to Day',
diff --git a/shared/src/i18n/en/journey.ts b/shared/src/i18n/en/journey.ts
index 4e703f15f..a8184e3ff 100644
--- a/shared/src/i18n/en/journey.ts
+++ b/shared/src/i18n/en/journey.ts
@@ -83,6 +83,7 @@ const journey: TranslationStrings = {
'journey.detail.jumpToLast': 'Jump to the last entry',
'journey.detail.dayJump': 'Jump to a day',
'journey.detail.dayJumpCount': '{count} days',
+ 'journey.detail.dayJumpCount.one': '{count} day',
'journey.detail.newEntry': 'New Entry',
'journey.detail.editEntry': 'Edit Entry',
'journey.detail.noEntries': 'No entries yet',
@@ -232,8 +233,10 @@ const journey: TranslationStrings = {
'journey.settings.failedToDelete': 'Failed to delete',
'journey.entries.deleteTitle': 'Delete Entry',
'journey.photosUploaded': '{count} photos uploaded',
+ 'journey.photosUploaded.one': '{count} photo uploaded',
'journey.photosUploadFailed': 'Some photos failed to upload',
'journey.photosAdded': '{count} photos added',
+ 'journey.photosAdded.one': '{count} photo added',
'journey.public.notFound': 'Not Found',
'journey.public.notFoundMessage': "This journey doesn't exist or the link has expired.",
'journey.public.readOnly': 'Read-only · Public Journey',
@@ -292,6 +295,7 @@ const journey: TranslationStrings = {
'journey.studio.exportSave': 'Save as PDF',
'journey.studio.exportPreparing': 'Preparing',
'journey.studio.exportSheetCount': '{count} sheets',
+ 'journey.studio.exportSheetCount.one': '{count} sheet',
'journey.studio.undo': 'Undo',
'journey.studio.redo': 'Redo',
'journey.studio.zoomIn': 'Zoom in',
@@ -636,6 +640,7 @@ const journey: TranslationStrings = {
'journey.studio.uploading': 'Uploading {done} of {total}',
'journey.studio.dropFilesHere': 'Drop to add the pictures',
'journey.studio.videosSkipped': '{count} videos were left out. A book holds pictures.',
+ 'journey.studio.videosSkipped.one': '{count} video was left out. A book holds pictures.',
'journey.studio.fillPage': 'Fill page',
'journey.studio.fillSpread': 'Fill spread',
'journey.studio.fillHint':
@@ -661,6 +666,7 @@ const journey: TranslationStrings = {
'journey.suggestions.restore': 'Bring back dismissed suggestions',
'journey.suggestions.restoreCount': 'Dismissed suggestions ({count})',
'journey.suggestions.restored': '{count} suggestions are back',
+ 'journey.suggestions.restored.one': '{count} suggestion is back',
'journey.detail.addOnThisDay': 'Add an entry on this day',
'journey.detail.jumpToDay': 'Jump to {date}',
'journey.detail.searchPlaceholder': 'Search this journey',
diff --git a/shared/src/i18n/en/map.ts b/shared/src/i18n/en/map.ts
index d25a85c6f..ff93cd2ea 100644
--- a/shared/src/i18n/en/map.ts
+++ b/shared/src/i18n/en/map.ts
@@ -29,7 +29,9 @@ const map: TranslationStrings = {
'map.overview.hide': 'Hide whole trip',
'map.overview.total': 'Total distance',
'map.attribution': 'Map credits',
- 'map.overview.unrouted': '{count} leg(s) could not be routed, so the distances are incomplete.',
- 'map.overview.dayUnrouted': '{count} leg(s) of this day could not be routed',
+ 'map.overview.unrouted': '{count} legs could not be routed, so the distances are incomplete.',
+ 'map.overview.unrouted.one': '{count} leg could not be routed, so the distances are incomplete.',
+ 'map.overview.dayUnrouted': '{count} legs of this day could not be routed',
+ 'map.overview.dayUnrouted.one': '{count} leg of this day could not be routed',
};
export default map;
diff --git a/shared/src/i18n/en/members.ts b/shared/src/i18n/en/members.ts
index dffb45ed8..dc4ccaadb 100644
--- a/shared/src/i18n/en/members.ts
+++ b/shared/src/i18n/en/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'Invite',
'members.allHaveAccess': 'All users already have access.',
'members.access': 'Access',
- 'members.person': 'person',
'members.persons': 'persons',
+ 'members.persons.one': 'person',
'members.you': 'you',
'members.owner': 'Owner',
'members.leaveTrip': 'Leave trip',
diff --git a/shared/src/i18n/en/memories.ts b/shared/src/i18n/en/memories.ts
index 867fecc04..2b9c69f5a 100644
--- a/shared/src/i18n/en/memories.ts
+++ b/shared/src/i18n/en/memories.ts
@@ -16,6 +16,7 @@ const memories: TranslationStrings = {
'memories.reviewTitle': 'Review your photos',
'memories.reviewHint': 'Click photos to exclude them from sharing.',
'memories.shareCount': 'Share {count} photos',
+ 'memories.shareCount.one': 'Share {count} photo',
'memories.providerUrl': 'Server URL',
'memories.providerApiKey': 'API Key',
'memories.providerUsername': 'Username',
@@ -48,6 +49,7 @@ const memories: TranslationStrings = {
'memories.selectHint': 'Tap photos to select them.',
'memories.selected': 'selected',
'memories.addSelected': 'Add {count} photos',
+ 'memories.addSelected.one': 'Add {count} photo',
'memories.alreadyAdded': 'Added',
'memories.private': 'Private',
'memories.stopSharing': 'Stop sharing',
diff --git a/shared/src/i18n/en/mobileJourney.ts b/shared/src/i18n/en/mobileJourney.ts
index 9e634b047..cb8b5d599 100644
--- a/shared/src/i18n/en/mobileJourney.ts
+++ b/shared/src/i18n/en/mobileJourney.ts
@@ -4,9 +4,13 @@ const mobileJourney: TranslationStrings = {
'mobileJourney.latestJourney': 'Latest journey',
'mobileJourney.otherJourneys': 'Other journeys',
'mobileJourney.entriesCount': '{count} entries',
+ 'mobileJourney.entriesCount.one': '{count} entry',
'mobileJourney.photosCount': '{count} photos',
+ 'mobileJourney.photosCount.one': '{count} photo',
'mobileJourney.placesCount': '{count} places',
+ 'mobileJourney.placesCount.one': '{count} place',
'mobileJourney.daysCount': '{count} days',
+ 'mobileJourney.daysCount.one': '{count} day',
'mobileJourney.time': 'Time',
'mobileJourney.tags': 'Tags',
'mobileJourney.addTag': 'Add tag',
diff --git a/shared/src/i18n/en/mobileTrip.ts b/shared/src/i18n/en/mobileTrip.ts
index cd04545a2..77ededb52 100644
--- a/shared/src/i18n/en/mobileTrip.ts
+++ b/shared/src/i18n/en/mobileTrip.ts
@@ -42,10 +42,13 @@ const mobileTrip: TranslationStrings = {
'mobileTrip.resBadge': 'Booking',
'mobileTrip.showOnMap': 'Show on map',
'mobileTrip.statDocuments': '{count} files',
+ 'mobileTrip.statDocuments.one': '{count} file',
'mobileTrip.statPeople': '{count} people',
+ 'mobileTrip.statPeople.one': '{count} person',
'mobileTrip.stay': 'Stay',
'mobileTrip.tapAgainToDelete': 'Tap again to delete',
'mobileTrip.todoOpenCount': '{count} open',
+ 'mobileTrip.todoOpenCount.one': '{count} open',
'mobileTrip.travel': 'Travel',
'mobileTrip.upNext': 'Up Next',
'mobileTrip.viewDetails': 'View details',
@@ -59,7 +62,9 @@ const mobileTrip: TranslationStrings = {
'mobileTrip.rtStart': 'Start',
'mobileTrip.rtStayScope': 'The stay belongs to the place, so it counts on every day this stop is planned.',
'mobileTrip.rtStayLess': '{count} minutes less',
+ 'mobileTrip.rtStayLess.one': '{count} minute less',
'mobileTrip.rtStayMore': '{count} minutes more',
+ 'mobileTrip.rtStayMore.one': '{count} minute more',
'mobileTrip.rtNightDesktopOnly':
'Booking a night at this place works in the desktop planner. Here you can only discard it.',
'mobileTrip.rtReach': 'How far',
diff --git a/shared/src/i18n/en/mobileVacay.ts b/shared/src/i18n/en/mobileVacay.ts
index 5702daf5f..b2565fc77 100644
--- a/shared/src/i18n/en/mobileVacay.ts
+++ b/shared/src/i18n/en/mobileVacay.ts
@@ -6,7 +6,9 @@ const mobileVacay: TranslationStrings = {
'mobileVacay.prevMonth': 'Previous month',
'mobileVacay.nextMonth': 'Next month',
'mobileVacay.usedCount': '{count} used',
+ 'mobileVacay.usedCount.one': '{count} used',
'mobileVacay.leftCount': '{count} left',
+ 'mobileVacay.leftCount.one': '{count} left',
'mobileVacay.left': 'left',
'mobileVacay.modeCompany': 'Company',
'mobileVacay.decreaseAllowance': 'Decrease allowance',
diff --git a/shared/src/i18n/en/notif.ts b/shared/src/i18n/en/notif.ts
index b0c8fd7a6..5c0519c4e 100644
--- a/shared/src/i18n/en/notif.ts
+++ b/shared/src/i18n/en/notif.ts
@@ -20,7 +20,8 @@ const notif: TranslationStrings = {
'notif.collection_invite.title': 'Collection invite',
'notif.collection_invite.text': '{actor} invited you to share a collection',
'notif.photos_shared.title': 'Photos Shared',
- 'notif.photos_shared.text': '{actor} shared {count} photo(s) in {trip}',
+ 'notif.photos_shared.text': '{actor} shared {count} photos in {trip}',
+ 'notif.photos_shared.text.one': '{actor} shared {count} photo in {trip}',
'notif.collab_message.title': 'New Message',
'notif.collab_message.text': '{actor} sent a message in {trip}',
'notif.packing_tagged.title': 'Packing Assignment',
diff --git a/shared/src/i18n/en/notifications.ts b/shared/src/i18n/en/notifications.ts
index f4451a960..241e7e6a4 100644
--- a/shared/src/i18n/en/notifications.ts
+++ b/shared/src/i18n/en/notifications.ts
@@ -32,5 +32,7 @@ const notifications: TranslationStrings = {
'notifications.test.adminText': '{actor} sent a test notification to all admins.',
'notifications.test.tripTitle': '{actor} posted in your trip',
'notifications.test.tripText': 'Test notification for trip "{trip}".',
+ 'notifications.countLabel': 'notifications',
+ 'notifications.countLabel.one': 'notification',
};
export default notifications;
diff --git a/shared/src/i18n/en/oauth.ts b/shared/src/i18n/en/oauth.ts
index 2c162552c..4f76f4ba0 100644
--- a/shared/src/i18n/en/oauth.ts
+++ b/shared/src/i18n/en/oauth.ts
@@ -85,8 +85,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.',
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.',
'oauth.authorize.selectScope': 'Select at least one scope',
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)',
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)',
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)',
+ 'oauth.authorize.approveScopes.one': 'Approve ({count} scope)',
'oauth.authorize.approveAccess': 'Approve Access',
'oauth.authorize.deny': 'Deny',
'oauth.authorize.choosePermissions': 'Choose which permissions to grant',
diff --git a/shared/src/i18n/en/packing.ts b/shared/src/i18n/en/packing.ts
index ee78585ab..9aa1303c9 100644
--- a/shared/src/i18n/en/packing.ts
+++ b/shared/src/i18n/en/packing.ts
@@ -32,6 +32,7 @@ const packing: TranslationStrings = {
'A Markdown list works too: a heading names the category, "- [ ]" and "- [x]" become items, and "3x" in front of a name sets the quantity.',
'packing.importAction': 'Import {count}',
'packing.importSuccess': '{count} items imported',
+ 'packing.importSuccess.one': '{count} item imported',
'packing.importError': 'Import failed',
'packing.importEmpty': 'No items to import',
'packing.progress': '{packed} of {total} packed ({percent}%)',
@@ -59,6 +60,7 @@ const packing: TranslationStrings = {
'packing.applyTemplate': 'Apply template',
'packing.template': 'Template',
'packing.templateApplied': '{count} items added from template',
+ 'packing.templateApplied.one': '{count} item added from template',
'packing.templateError': 'Failed to apply template',
'packing.saveAsTemplate': 'Save as template',
'packing.templateName': 'Template name',
@@ -91,7 +93,9 @@ const packing: TranslationStrings = {
'packing.alsoBringingStop': "I'm not bringing it",
'packing.cloneToMine': 'Copy to my list',
'packing.confirm.clearChecked': 'Are you sure you want to remove {count} checked items?',
+ 'packing.confirm.clearChecked.one': 'Are you sure you want to remove {count} checked item?',
'packing.confirm.deleteCat': 'Are you sure you want to delete the list "{name}" with {count} items?',
+ 'packing.confirm.deleteCat.one': 'Are you sure you want to delete the list "{name}" with {count} item?',
'packing.defaultCategory': 'Other',
'packing.toast.saveError': 'Failed to save',
'packing.toast.deleteError': 'Failed to delete',
diff --git a/shared/src/i18n/en/photos.ts b/shared/src/i18n/en/photos.ts
index 622b6c718..f35a907ae 100644
--- a/shared/src/i18n/en/photos.ts
+++ b/shared/src/i18n/en/photos.ts
@@ -3,6 +3,7 @@ import type { TranslationStrings } from '../types';
const photos: TranslationStrings = {
'photos.title': 'Photos',
'photos.subtitle': '{count} photos for {trip}',
+ 'photos.subtitle.one': '{count} photo for {trip}',
'photos.dropHere': 'Drop photos here...',
'photos.dropHereActive': 'Drop photos here',
'photos.captionForAll': 'Caption (for all)',
diff --git a/shared/src/i18n/en/places.ts b/shared/src/i18n/en/places.ts
index 188eea970..a5038a4e7 100644
--- a/shared/src/i18n/en/places.ts
+++ b/shared/src/i18n/en/places.ts
@@ -17,6 +17,7 @@ const places: TranslationStrings = {
'places.importFileError': 'Import failed',
'places.importAllSkipped': 'All places were already in the trip.',
'places.gpxImported': '{count} places imported from GPX',
+ 'places.gpxImported.one': '{count} place imported from GPX',
'places.gpxImportTypes': 'What do you want to import?',
'places.gpxImportWaypoints': 'Waypoints',
'places.gpxImportRoutes': 'Routes',
@@ -27,10 +28,13 @@ const places: TranslationStrings = {
'places.kmlImportPaths': 'Paths (LineStrings)',
'places.kmlImportNoneSelected': 'Select at least one type to import.',
'places.selectionCount': '{count} selected',
+ 'places.selectionCount.one': '{count} selected',
'places.deleteSelected': 'Delete selected',
'places.changeCategory': 'Change category',
'places.categoryChanged': '{count} places updated',
+ 'places.categoryChanged.one': '{count} place updated',
'places.kmlKmzImported': '{count} places imported from KMZ/KML',
+ 'places.kmlKmzImported.one': '{count} place imported from KMZ/KML',
'places.urlResolved': 'Place imported from URL',
'places.importList': 'List Import',
'places.kmlKmzSummaryValues': 'Placemarks: {total} • Imported: {created} • Skipped: {skipped}',
@@ -39,9 +43,11 @@ const places: TranslationStrings = {
'places.googleListHint': 'Paste a shared Google Maps list link to import all places.',
'places.googleDirHint': 'A directions link works too: its stops become places, in driving order.',
'places.googleListImported': '{count} places imported from "{list}"',
+ 'places.googleListImported.one': '{count} place imported from "{list}"',
'places.googleListError': 'Failed to import Google Maps list',
'places.naverListHint': 'Paste a shared Naver Maps list link to import all places.',
'places.naverListImported': '{count} places imported from "{list}"',
+ 'places.naverListImported.one': '{count} place imported from "{list}"',
'places.naverListError': 'Failed to import Naver Maps list',
'places.viewDetails': 'View Details',
'places.assignToDay': 'Add to which day?',
@@ -76,7 +82,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'categories',
'places.clearFilter': 'Clear filter',
'places.count': '{count} places',
- 'places.countSingular': '1 place',
+ 'places.count.one': '{count} place',
'places.allPlanned': 'All places are planned',
'places.noneFound': 'No places found',
'places.editPlace': 'Edit Place',
diff --git a/shared/src/i18n/en/planner.ts b/shared/src/i18n/en/planner.ts
index da60c1778..51382cc81 100644
--- a/shared/src/i18n/en/planner.ts
+++ b/shared/src/i18n/en/planner.ts
@@ -21,10 +21,12 @@ const planner: TranslationStrings = {
'planner.days': 'Days',
'planner.allPlaces': 'All Places',
'planner.totalPlaces': '{n} places total',
+ 'planner.totalPlaces.one': '{n} place total',
'planner.noDaysPlanned': 'No days planned yet',
'planner.editTrip': 'Edit trip →',
'planner.placeOne': '1 place',
'planner.placeN': '{n} places',
+ 'planner.placeN.one': '{n} place',
'planner.addNote': 'Add note',
'planner.noEntries': 'No entries for this day',
'planner.addPlace': 'Add place/activity',
@@ -59,6 +61,7 @@ const planner: TranslationStrings = {
'planner.noDays': 'No days yet',
'planner.editTripToAddDays': 'Edit trip to add days',
'planner.dayCount': '{n} Days',
+ 'planner.dayCount.one': '{n} Day',
'planner.clickToUnlock': 'Click to unlock',
'planner.keepPosition': 'Keep position during route optimization',
'planner.dayDetails': 'Day details',
diff --git a/shared/src/i18n/en/reservations.ts b/shared/src/i18n/en/reservations.ts
index 2054d864d..f129ee3c0 100644
--- a/shared/src/i18n/en/reservations.ts
+++ b/shared/src/i18n/en/reservations.ts
@@ -144,15 +144,18 @@ const reservations: TranslationStrings = {
'reservations.import.acceptedFormats': 'Accepted: EML, PDF, PKPass, HTML, TXT (max 10 MB each, up to 5 files)',
'reservations.import.acceptedPhotos': 'Photos (JPG, PNG, WEBP) are read by the AI model.',
'reservations.import.parsing': 'Parsing files…',
- 'reservations.import.previewHeading': '{count} reservation(s) found',
+ 'reservations.import.previewHeading': '{count} reservations found',
+ 'reservations.import.previewHeading.one': '{count} reservation found',
'reservations.import.previewEmpty': 'No reservations could be extracted from the uploaded files.',
'reservations.import.removeItem': 'Remove',
'reservations.import.needsReview': 'Review',
'reservations.import.tryAi': 'Try AI parsing',
'reservations.import.aiParsing': 'Parsing with AI…',
- 'reservations.import.confirm': 'Import {count} reservation(s)',
+ 'reservations.import.confirm': 'Import {count} reservations',
+ 'reservations.import.confirm.one': 'Import {count} reservation',
'reservations.import.back': 'Back',
- 'reservations.import.success': '{count} reservation(s) imported',
+ 'reservations.import.success': '{count} reservations imported',
+ 'reservations.import.success.one': '{count} reservation imported',
'reservations.import.partialFailure': '{created} imported, {failed} failed',
'reservations.import.error': 'Parsing failed. Make sure the file is a valid booking confirmation.',
'reservations.import.unavailable': 'Booking import is not available on this server.',
@@ -167,8 +170,10 @@ const reservations: TranslationStrings = {
'reservations.airtrail.layoverHint':
'Imported from AirTrail. A multi-leg flight with a layover has no single AirTrail flight to sync back to, so it stays as a one-time import.',
'reservations.airtrail.loadError': 'Could not load your AirTrail flights.',
- 'reservations.airtrail.imported': '{count} flight(s) imported',
+ 'reservations.airtrail.imported': '{count} flights imported',
+ 'reservations.airtrail.imported.one': '{count} flight imported',
'reservations.airtrail.skippedDuplicate': '{count} already in this trip, skipped',
+ 'reservations.airtrail.skippedDuplicate.one': '{count} already in this trip, skipped',
'reservations.airtrail.nothingImported': 'Nothing to import.',
'reservations.airtrail.importError': 'Import failed. Please try again.',
'reservations.airtrail.undo': 'Import from AirTrail',
@@ -231,6 +236,7 @@ const reservations: TranslationStrings = {
'reservations.sort.aToZ': 'A to Z',
'reservations.sort.zToA': 'Z to A',
'reservations.timeline.dayCount': '{count} days',
+ 'reservations.timeline.dayCount.one': '{count} day',
'reservations.timeline.zoom': 'Zoom',
'reservations.timeline.trip': 'Trip',
'reservations.timeline.day': 'Day',
diff --git a/shared/src/i18n/en/roadtrip.ts b/shared/src/i18n/en/roadtrip.ts
index e65a5c55e..565aa1695 100644
--- a/shared/src/i18n/en/roadtrip.ts
+++ b/shared/src/i18n/en/roadtrip.ts
@@ -68,6 +68,7 @@ const roadtrip: TranslationStrings = {
'roadtrip.poi.looking': 'Looking for',
'roadtrip.poi.searchingHint': 'Searching the route…',
'roadtrip.poi.found': '{count} on the way',
+ 'roadtrip.poi.found.one': '{count} on the way',
'roadtrip.poi.fuel': 'Fuel',
'roadtrip.poi.charging': 'Charging',
'roadtrip.stop.offRoad': '{distance} from the road',
@@ -103,6 +104,7 @@ const roadtrip: TranslationStrings = {
'roadtrip.limit.echoOver': '{over} of {days} days over your limit.',
'roadtrip.limit.echoNone': 'All {days} days within your limits.',
'roadtrip.limit.echoDry': '{count} stretches with no reachable stop.',
+ 'roadtrip.limit.echoDry.one': '{count} stretch with no reachable stop.',
'roadtrip.limit.echoEmpty': 'Open a road trip to see what these do to it.',
'roadtrip.limit.specToggle': 'Work it out from the car',
'roadtrip.limit.hint': 'Driving only, excluding stays and overnight legs.',
@@ -124,6 +126,7 @@ const roadtrip: TranslationStrings = {
'roadtrip.avoid.hint': 'Avoids where possible; exceptions are flagged.',
'roadtrip.avoid.unavailable': 'Your routing provider does not support avoidance.',
'roadtrip.avoid.badge': '{count} avoided',
+ 'roadtrip.avoid.badge.one': '{count} avoided',
'roadtrip.avoid.missed': '{classes} unavoidable',
'roadtrip.avoid.missedHint':
'This day has no route around it, so the drive uses it. Every other day still avoids what it can.',
@@ -171,11 +174,14 @@ const roadtrip: TranslationStrings = {
'roadtrip.track.action': 'Follow this track',
'roadtrip.track.working': 'Fitting the drive to the track, round {round}',
'roadtrip.track.done': '{count} via points placed. The drive stays within {distance} of the track.',
+ 'roadtrip.track.done.one': '{count} via point placed. The drive stays within {distance} of the track.',
'roadtrip.track.capped': '{count} via points placed. The drive gets no closer than {distance} at its widest.',
+ 'roadtrip.track.capped.one': '{count} via point placed. The drive gets no closer than {distance} at its widest.',
'roadtrip.track.already': 'The drive already followed this track.',
'roadtrip.track.errorRoute': 'The routing service did not answer.',
'roadtrip.track.errorSave': 'The via points could not be saved.',
'roadtrip.track.clear': 'Drop {count} via points',
+ 'roadtrip.track.clear.one': 'Drop {count} via point',
'roadtrip.track.current': 'Currently following {name}',
'roadtrip.poi.rest': 'Rest area',
'roadtrip.poi.campsite': 'Campsite',
@@ -186,11 +192,13 @@ const roadtrip: TranslationStrings = {
'roadtrip.poi.noMatch': 'Nothing on the way matches “{name}”.',
'roadtrip.poi.noneMatchFilters': 'Nothing on the way matches these filters.',
'roadtrip.poi.foundFiltered': '{count} of {total} on the way',
- 'roadtrip.poi.truncated': '{count} stretches had more than fits in one answer — narrow the corridor to see the rest.',
+ 'roadtrip.poi.truncated': '{count} stretches had more than fits in one answer. Narrow the corridor to see the rest.',
+ 'roadtrip.poi.truncated.one': '{count} stretch had more than fits in one answer. Narrow the corridor to see the rest.',
'roadtrip.poi.search': 'Search',
'roadtrip.poi.searching': 'Searching {done} of {total}',
'roadtrip.poi.capped': 'The route is long — only the first stretch was searched.',
- 'roadtrip.poi.partial': '{count} stretches could not be searched — the place search did not answer.',
+ 'roadtrip.poi.partial': '{count} stretches could not be searched. The place search did not answer.',
+ 'roadtrip.poi.partial.one': '{count} stretch could not be searched. The place search did not answer.',
'roadtrip.poi.failed': 'The place search is not answering right now.',
'roadtrip.poi.empty': 'Pick what you need and search.',
'roadtrip.poi.add': 'Add',
@@ -236,6 +244,7 @@ const roadtrip: TranslationStrings = {
'roadtrip.quietDay.one': 'Only {name} so far — drop a stop here',
'roadtrip.leg.driveText': '{distance} in {time}',
'roadtrip.day.stopCount': '{count} stops',
+ 'roadtrip.day.stopCount.one': '{count} stop',
'roadtrip.day.fold': 'Fold',
'roadtrip.day.folded': 'Folded',
'roadtrip.stop.stayShort': 'Stay',
diff --git a/shared/src/i18n/en/settings.ts b/shared/src/i18n/en/settings.ts
index af771f68c..a4dd2c786 100644
--- a/shared/src/i18n/en/settings.ts
+++ b/shared/src/i18n/en/settings.ts
@@ -400,7 +400,8 @@ const settings: TranslationStrings = {
'settings.airtrail.toast.saved': 'AirTrail connection saved',
'settings.airtrail.toast.saveError': 'Could not save the connection',
'settings.airtrail.test.button': 'Test connection',
- 'settings.airtrail.test.success': 'Connected — {count} flight(s) found',
+ 'settings.airtrail.test.success': 'Connected. {count} flights found',
+ 'settings.airtrail.test.success.one': 'Connected. {count} flight found',
'settings.airtrail.test.failed': 'Connection failed',
'settings.aiParsing.title': 'AI parsing',
'settings.aiParsing.hint':
@@ -540,7 +541,8 @@ const settings: TranslationStrings = {
'settings.offline.storage.tripOn': 'Stored offline',
'settings.offline.storage.tripOff': 'Not stored',
'settings.offline.storage.tripFinished': 'Finished. Only stored if you switch it on.',
- 'settings.offline.notice.stored': 'Stored {count} trip(s) on this device',
+ 'settings.offline.notice.stored': 'Stored {count} trips on this device',
+ 'settings.offline.notice.stored.one': 'Stored {count} trip on this device',
'settings.offline.notice.nothing': 'Nothing to store. Switch on the trips you want to keep.',
'settings.offline.notice.busy': 'A sync is already running. Try again in a moment.',
'settings.offline.notice.offline': 'No connection. Connect to store trips for offline use.',
diff --git a/shared/src/i18n/en/tours.ts b/shared/src/i18n/en/tours.ts
index 27db698fe..dd47b362a 100644
--- a/shared/src/i18n/en/tours.ts
+++ b/shared/src/i18n/en/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Elevation/metrics incomplete — imported with caution',
'tours.import.caution': 'Imported with caution — elevation/metrics incomplete',
'tours.import.success': '{count} tours imported from GPX',
- 'tours.import.successOne': '1 tour imported from GPX',
+ 'tours.import.success.one': '{count} tour imported from GPX',
'tours.import.noTrack': 'No track or route found in that GPX file',
'tours.import.error': 'Could not import GPX file',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/en/trip.ts b/shared/src/i18n/en/trip.ts
index d4da4a0aa..bff824069 100644
--- a/shared/src/i18n/en/trip.ts
+++ b/shared/src/i18n/en/trip.ts
@@ -32,7 +32,9 @@ const trip: TranslationStrings = {
'trip.toast.deleted': 'Deleted',
'trip.confirm.deletePlace': 'Are you sure you want to delete this place?',
'trip.confirm.deletePlaces': 'Delete {count} places?',
+ 'trip.confirm.deletePlaces.one': 'Delete {count} place?',
'trip.toast.placesDeleted': '{count} places deleted',
+ 'trip.toast.placesDeleted.one': '{count} place deleted',
'trip.invite.linkTitle': 'Trip invite link',
'trip.invite.linkHint':
'Anyone with a TREK account who opens this link joins the trip as a member. Regenerate to invalidate the old link.',
@@ -69,8 +71,10 @@ const trip: TranslationStrings = {
'transit.noResults': 'No connections found. Try a different time or filters.',
'transit.direct': 'Direct',
'transit.transfers': '{count} transfers',
+ 'transit.transfers.one': '{count} transfer',
'transit.min': '{count} min',
'transit.stops': '{count} stops',
+ 'transit.stops.one': '{count} stop',
'transit.walkTo': 'Walk to {name}',
'transit.platform': 'Platform {track}',
'transit.adding': 'Adding…',
diff --git a/shared/src/i18n/es/atlas.ts b/shared/src/i18n/es/atlas.ts
index 79c230948..45de80636 100644
--- a/shared/src/i18n/es/atlas.ts
+++ b/shared/src/i18n/es/atlas.ts
@@ -19,10 +19,10 @@ const atlas: TranslationStrings = {
'atlas.streak': 'Racha',
'atlas.year': 'año',
'atlas.years': 'años',
- 'atlas.yearInRow': 'año seguido',
'atlas.yearsInRow': 'años seguidos',
- 'atlas.tripIn': 'viaje en',
+ 'atlas.yearsInRow.one': 'año seguido',
'atlas.tripsIn': 'viajes en',
+ 'atlas.tripsIn.one': 'viaje en',
'atlas.since': 'desde',
'atlas.europe': 'Europa',
'atlas.asia': 'Asia',
@@ -33,10 +33,11 @@ const atlas: TranslationStrings = {
'atlas.other': 'Otros',
'atlas.firstVisit': 'Primer viaje',
'atlas.lastVisitLabel': 'Último viaje',
- 'atlas.tripSingular': 'Viaje',
'atlas.tripPlural': 'Viajes',
- 'atlas.placeVisited': 'Lugar visitado',
+ 'atlas.tripsLabel': 'Viajes',
+ 'atlas.tripsLabel.one': 'Viaje',
'atlas.placesVisited': 'Lugares visitados',
+ 'atlas.placesVisited.one': 'Lugar visitado',
'atlas.statsTab': 'Estadísticas',
'atlas.bucketTab': 'Lista de deseos',
'atlas.addBucket': 'Añadir a lista de deseos',
diff --git a/shared/src/i18n/es/collab.ts b/shared/src/i18n/es/collab.ts
index 452fd0f57..c83f97c59 100644
--- a/shared/src/i18n/es/collab.ts
+++ b/shared/src/i18n/es/collab.ts
@@ -89,7 +89,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Cerrar',
'collab.polls.closed': 'Cerrada',
'collab.polls.votes': '{n} votos',
- 'collab.polls.vote': '{n} voto',
+ 'collab.polls.votes.one': '{n} voto',
'collab.polls.multipleChoice': 'Selección múltiple',
'collab.polls.multiChoice': 'Selección múltiple',
'collab.polls.deadline': 'Fecha límite',
diff --git a/shared/src/i18n/es/dashboard.ts b/shared/src/i18n/es/dashboard.ts
index 619435b88..58ab149bb 100644
--- a/shared/src/i18n/es/dashboard.ts
+++ b/shared/src/i18n/es/dashboard.ts
@@ -34,6 +34,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Días',
'dashboard.places': 'Lugares',
'dashboard.members': 'Compañeros de viaje',
+ 'dashboard.card.buddies': 'Compañeros de viaje',
+ 'dashboard.card.buddies.one': 'Compañero',
'dashboard.archive': 'Archivar',
'dashboard.copyTrip': 'Copiar',
'dashboard.copySuffix': 'copia',
@@ -145,8 +147,8 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': 'En {count} días',
'dashboard.mobile.inMonths': 'En {count} meses',
'dashboard.mobile.spotlightDayOf': 'Día {day} de {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} día',
- 'dashboard.mobile.spotlightDaysMany': '{count} días',
+ 'dashboard.mobile.spotlightDays': '{count} días',
+ 'dashboard.mobile.spotlightDays.one': '{count} día',
'dashboard.mobile.completed': 'Completado',
'dashboard.mobile.currencyConverter': 'Conversor de monedas',
'dashboard.filter.planned': 'Planificados',
@@ -157,14 +159,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'RECIENTE',
'dashboard.hero.tripDates': 'Fechas del viaje',
'dashboard.hero.noDates': 'Sin fechas',
- 'dashboard.hero.travelerOne': '{count} viajero',
- 'dashboard.hero.travelerMany': '{count} viajeros',
- 'dashboard.hero.destinationOne': '{count} destino',
- 'dashboard.hero.destinationMany': '{count} destinos',
- 'dashboard.hero.dayUnitOne': 'día',
- 'dashboard.hero.dayUnitMany': 'días',
- 'dashboard.hero.dayLeft': 'Día restante',
+ 'dashboard.hero.travelers': '{count} viajeros',
+ 'dashboard.hero.travelers.one': '{count} viajero',
+ 'dashboard.hero.destinations': '{count} destinos',
+ 'dashboard.hero.destinations.one': '{count} destino',
+ 'dashboard.hero.dayUnit': 'días',
+ 'dashboard.hero.dayUnit.one': 'día',
'dashboard.hero.daysLeft': 'Días restantes',
+ 'dashboard.hero.daysLeft.one': 'Día restante',
'dashboard.hero.lastDay': 'Último día',
'dashboard.hero.untilStart': 'Hasta el inicio',
'dashboard.hero.startsIn': 'Empieza en',
@@ -179,7 +181,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ {count}× alrededor del ecuador',
'dashboard.card.idea': 'Idea',
- 'dashboard.card.buddyOne': 'Compañero',
'dashboard.fx.from': 'De',
'dashboard.fx.to': 'A',
'dashboard.fx.unavailable': 'Tipo de cambio no disponible',
diff --git a/shared/src/i18n/es/files.ts b/shared/src/i18n/es/files.ts
index c6ae6c614..8764a8a0e 100644
--- a/shared/src/i18n/es/files.ts
+++ b/shared/src/i18n/es/files.ts
@@ -14,7 +14,7 @@ const files: TranslationStrings = {
'files.openError': 'No se pudo abrir el archivo',
'files.downloadPdf': 'Descargar PDF',
'files.count': '{count} archivos',
- 'files.countSingular': '1 archivo',
+ 'files.count.one': '{count} archivo',
'files.uploaded': '{count} archivos subidos',
'files.uploadError': 'La subida falló',
'files.uploadErrorType': 'Este tipo de archivo no es compatible',
diff --git a/shared/src/i18n/es/members.ts b/shared/src/i18n/es/members.ts
index 43d0443b1..8eeed119e 100644
--- a/shared/src/i18n/es/members.ts
+++ b/shared/src/i18n/es/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'Invitar',
'members.allHaveAccess': 'Todos los usuarios ya tienen acceso.',
'members.access': 'Acceso',
- 'members.person': 'persona',
'members.persons': 'personas',
+ 'members.persons.one': 'persona',
'members.you': 'tú',
'members.owner': 'Propietario',
'members.leaveTrip': 'Abandonar viaje',
diff --git a/shared/src/i18n/es/oauth.ts b/shared/src/i18n/es/oauth.ts
index 40e749336..2c4ec8fa9 100644
--- a/shared/src/i18n/es/oauth.ts
+++ b/shared/src/i18n/es/oauth.ts
@@ -85,8 +85,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes.one': 'Approve ({count} scope)', // en-fallback
'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
'oauth.authorize.deny': 'Deny', // en-fallback
'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
diff --git a/shared/src/i18n/es/places.ts b/shared/src/i18n/es/places.ts
index 49dbf1a39..f1e79b55b 100644
--- a/shared/src/i18n/es/places.ts
+++ b/shared/src/i18n/es/places.ts
@@ -71,7 +71,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'categorías',
'places.clearFilter': 'Borrar filtro',
'places.count': '{count} lugares',
- 'places.countSingular': '1 lugar',
+ 'places.count.one': '{count} lugar',
'places.allPlanned': 'Todos los lugares están planificados',
'places.noneFound': 'No se encontraron lugares',
'places.editPlace': 'Editar lugar',
diff --git a/shared/src/i18n/es/tours.ts b/shared/src/i18n/es/tours.ts
index 795186d23..d2c19b83f 100644
--- a/shared/src/i18n/es/tours.ts
+++ b/shared/src/i18n/es/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Altitud o métricas incompletas: importada con reservas',
'tours.import.caution': 'Importada con reservas: altitud o métricas incompletas',
'tours.import.success': '{count} excursiones importadas desde GPX',
- 'tours.import.successOne': '1 excursión importada desde GPX',
+ 'tours.import.success.one': '{count} excursión importada desde GPX',
'tours.import.noTrack': 'No se ha encontrado ningún track ni ruta en ese archivo GPX',
'tours.import.error': 'No se ha podido importar el archivo GPX',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/et/atlas.ts b/shared/src/i18n/et/atlas.ts
index d4d256afb..a29d6f23e 100644
--- a/shared/src/i18n/et/atlas.ts
+++ b/shared/src/i18n/et/atlas.ts
@@ -43,10 +43,10 @@ const atlas: TranslationStrings = {
'atlas.daysLeft': 'päeva jäänud',
'atlas.streak': 'Järjestikused aastad',
'atlas.years': 'aastat',
- 'atlas.yearInRow': 'aasta järjest',
'atlas.yearsInRow': 'aastat järjest',
- 'atlas.tripIn': 'reis aastal',
+ 'atlas.yearsInRow.one': 'aasta järjest',
'atlas.tripsIn': 'reisi aastal',
+ 'atlas.tripsIn.one': 'reis aastal',
'atlas.since': 'alates',
'atlas.europe': 'Euroopa',
'atlas.asia': 'Aasia',
@@ -57,10 +57,11 @@ const atlas: TranslationStrings = {
'atlas.other': 'Muu',
'atlas.firstVisit': 'Esimene reis',
'atlas.lastVisitLabel': 'Viimane reis',
- 'atlas.tripSingular': 'Reis',
'atlas.tripPlural': 'Reisid',
- 'atlas.placeVisited': 'Külastatud koht',
+ 'atlas.tripsLabel': 'Reisid',
+ 'atlas.tripsLabel.one': 'Reis',
'atlas.placesVisited': 'Külastatud kohad',
+ 'atlas.placesVisited.one': 'Külastatud koht',
'atlas.planned': 'Plaanis',
'atlas.showPlanned': 'Näita plaanis olevaid riike',
'atlas.plannedFor': 'Plaanitud ajaks',
diff --git a/shared/src/i18n/et/collab.ts b/shared/src/i18n/et/collab.ts
index 0f2752b15..0b52acbaf 100644
--- a/shared/src/i18n/et/collab.ts
+++ b/shared/src/i18n/et/collab.ts
@@ -89,7 +89,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Sulge',
'collab.polls.closed': 'Suletud',
'collab.polls.votes': '{n} häält',
- 'collab.polls.vote': '{n} hääl',
+ 'collab.polls.votes.one': '{n} hääl',
'collab.polls.multipleChoice': 'Mitu valikut',
'collab.polls.multiChoice': 'Mitu valikut',
'collab.polls.deadline': 'Tähtaeg',
diff --git a/shared/src/i18n/et/dashboard.ts b/shared/src/i18n/et/dashboard.ts
index 6f03721be..2656d7ba3 100644
--- a/shared/src/i18n/et/dashboard.ts
+++ b/shared/src/i18n/et/dashboard.ts
@@ -34,6 +34,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Päevad',
'dashboard.places': 'Kohad',
'dashboard.members': 'Kaaslased',
+ 'dashboard.card.buddies': 'Kaaslased',
+ 'dashboard.card.buddies.one': 'Kaaslane',
'dashboard.archive': 'Arhiveeri',
'dashboard.copyTrip': 'Kopeeri',
'dashboard.copySuffix': 'koopia',
@@ -157,8 +159,8 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': '{count} päeva pärast',
'dashboard.mobile.inMonths': '{count} kuu pärast',
'dashboard.mobile.spotlightDayOf': 'Päev {day} / {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} päev',
- 'dashboard.mobile.spotlightDaysMany': '{count} päeva',
+ 'dashboard.mobile.spotlightDays': '{count} päeva',
+ 'dashboard.mobile.spotlightDays.one': '{count} päev',
'dashboard.mobile.completed': 'Lõppenud',
'dashboard.mobile.currencyConverter': 'Valuutakalkulaator',
'dashboard.filter.planned': 'Plaanis',
@@ -169,14 +171,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'HILJUTINE',
'dashboard.hero.tripDates': 'Reisi kuupäevad',
'dashboard.hero.noDates': 'Kuupäevad lahtised',
- 'dashboard.hero.travelerOne': '{count} reisija',
- 'dashboard.hero.travelerMany': '{count} reisijat',
- 'dashboard.hero.destinationOne': '{count} sihtkoht',
- 'dashboard.hero.destinationMany': '{count} sihtkohta',
- 'dashboard.hero.dayUnitOne': 'päev',
- 'dashboard.hero.dayUnitMany': 'päeva',
- 'dashboard.hero.dayLeft': 'Päev jäänud',
+ 'dashboard.hero.travelers': '{count} reisijat',
+ 'dashboard.hero.travelers.one': '{count} reisija',
+ 'dashboard.hero.destinations': '{count} sihtkohta',
+ 'dashboard.hero.destinations.one': '{count} sihtkoht',
+ 'dashboard.hero.dayUnit': 'päeva',
+ 'dashboard.hero.dayUnit.one': 'päev',
'dashboard.hero.daysLeft': 'Päeva jäänud',
+ 'dashboard.hero.daysLeft.one': 'Päev jäänud',
'dashboard.hero.lastDay': 'Viimane päev',
'dashboard.hero.untilStart': 'Alguseni',
'dashboard.hero.startsIn': 'Reisi alguseni',
@@ -191,7 +193,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ {count}× ümber ekvaatori',
'dashboard.card.idea': 'Idee',
- 'dashboard.card.buddyOne': 'Kaaslane',
'dashboard.fx.from': 'Lähtevaluuta',
'dashboard.fx.to': 'Sihtvaluuta',
'dashboard.fx.unavailable': 'Kurss pole saadaval',
diff --git a/shared/src/i18n/et/files.ts b/shared/src/i18n/et/files.ts
index cc834ab64..54a973cdb 100644
--- a/shared/src/i18n/et/files.ts
+++ b/shared/src/i18n/et/files.ts
@@ -14,7 +14,7 @@ const files: TranslationStrings = {
'files.openError': 'Faili avamine ebaõnnestus',
'files.downloadPdf': 'Laadi PDF alla',
'files.count': '{count} faili',
- 'files.countSingular': '1 fail',
+ 'files.count.one': '{count} fail',
'files.uploaded': '{count} üles laaditud',
'files.uploadError': 'Üleslaadimine ebaõnnestus',
'files.uploadErrorType': 'Seda failitüüpi ei toetata',
diff --git a/shared/src/i18n/et/members.ts b/shared/src/i18n/et/members.ts
index d8e96446e..205a7d1fa 100644
--- a/shared/src/i18n/et/members.ts
+++ b/shared/src/i18n/et/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'Kutsu',
'members.allHaveAccess': 'Kõigil kasutajatel on juba juurdepääs.',
'members.access': 'Juurdepääs',
- 'members.person': 'inimene',
'members.persons': 'inimest',
+ 'members.persons.one': 'inimene',
'members.you': 'sina',
'members.owner': 'Omanik',
'members.leaveTrip': 'Lahku reisilt',
diff --git a/shared/src/i18n/et/oauth.ts b/shared/src/i18n/et/oauth.ts
index e3b7d0669..ae3f98a4e 100644
--- a/shared/src/i18n/et/oauth.ts
+++ b/shared/src/i18n/et/oauth.ts
@@ -87,8 +87,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'See rakendus taotleb juurdepääsu sinu TREKi kontole.',
'oauth.authorize.trustNote': 'Anna juurdepääs ainult usaldatud rakendustele. Sinu andmed jäävad sinu serverisse.',
'oauth.authorize.selectScope': 'Vali vähemalt üks õiguste ulatus',
- 'oauth.authorize.approveOneScope': 'Luba ({count} ulatus)',
- 'oauth.authorize.approveManyScopes': 'Luba ({count} ulatust)',
+ 'oauth.authorize.approveScopes': 'Luba ({count} ulatust)',
+ 'oauth.authorize.approveScopes.one': 'Luba ({count} ulatus)',
'oauth.authorize.approveAccess': 'Luba juurdepääs',
'oauth.authorize.deny': 'Keela',
'oauth.authorize.choosePermissions': 'Vali antavad õigused',
diff --git a/shared/src/i18n/et/places.ts b/shared/src/i18n/et/places.ts
index 19d52bb72..712ffb224 100644
--- a/shared/src/i18n/et/places.ts
+++ b/shared/src/i18n/et/places.ts
@@ -71,7 +71,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'kategooriat',
'places.clearFilter': 'Tühjenda filter',
'places.count': '{count} kohta',
- 'places.countSingular': '1 koht',
+ 'places.count.one': '{count} koht',
'places.allPlanned': 'Kõik kohad on plaani lisatud',
'places.noneFound': 'Kohti ei leitud',
'places.editPlace': 'Muuda kohta',
diff --git a/shared/src/i18n/et/tours.ts b/shared/src/i18n/et/tours.ts
index c2adcfcbf..9ac79639e 100644
--- a/shared/src/i18n/et/tours.ts
+++ b/shared/src/i18n/et/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Kõrgusandmed või mõõdikud on puudulikud — imporditud hoiatusega',
'tours.import.caution': 'Imporditud hoiatusega — kõrgusandmed või mõõdikud on puudulikud',
'tours.import.success': 'GPX-failist imporditi {count} retke',
- 'tours.import.successOne': 'GPX-failist imporditi 1 retk',
+ 'tours.import.success.one': 'GPX-failist imporditi {count} retk',
'tours.import.noTrack': 'Sellest GPX-failist ei leitud rada ega marsruuti',
'tours.import.error': 'GPX-faili importimine ebaõnnestus',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/fr/atlas.ts b/shared/src/i18n/fr/atlas.ts
index f19ab26c4..388e00404 100644
--- a/shared/src/i18n/fr/atlas.ts
+++ b/shared/src/i18n/fr/atlas.ts
@@ -19,10 +19,10 @@ const atlas: TranslationStrings = {
'atlas.streak': 'Série',
'atlas.year': 'an',
'atlas.years': 'ans',
- 'atlas.yearInRow': 'année consécutive',
'atlas.yearsInRow': 'années consécutives',
- 'atlas.tripIn': 'voyage en',
+ 'atlas.yearsInRow.one': 'année consécutive',
'atlas.tripsIn': 'voyages en',
+ 'atlas.tripsIn.one': 'voyage en',
'atlas.since': 'depuis',
'atlas.europe': 'Europe',
'atlas.asia': 'Asie',
@@ -33,10 +33,11 @@ const atlas: TranslationStrings = {
'atlas.other': 'Autre',
'atlas.firstVisit': 'Premier voyage',
'atlas.lastVisitLabel': 'Dernier voyage',
- 'atlas.tripSingular': 'Voyage',
'atlas.tripPlural': 'Voyages',
- 'atlas.placeVisited': 'Lieu visité',
+ 'atlas.tripsLabel': 'Voyages',
+ 'atlas.tripsLabel.one': 'Voyage',
'atlas.placesVisited': 'Lieux visités',
+ 'atlas.placesVisited.one': 'Lieu visité',
'atlas.statsTab': 'Statistiques',
'atlas.bucketTab': 'Bucket List',
'atlas.addBucket': 'Ajouter à la bucket list',
diff --git a/shared/src/i18n/fr/collab.ts b/shared/src/i18n/fr/collab.ts
index bdb7011e3..0dd8853e8 100644
--- a/shared/src/i18n/fr/collab.ts
+++ b/shared/src/i18n/fr/collab.ts
@@ -89,7 +89,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Fermer',
'collab.polls.closed': 'Fermé',
'collab.polls.votes': '{n} votes',
- 'collab.polls.vote': '{n} vote',
+ 'collab.polls.votes.one': '{n} vote',
'collab.polls.multipleChoice': 'Choix multiples',
'collab.polls.multiChoice': 'Choix multiples',
'collab.polls.deadline': 'Date limite',
diff --git a/shared/src/i18n/fr/dashboard.ts b/shared/src/i18n/fr/dashboard.ts
index ffda64254..eb0f45d3e 100644
--- a/shared/src/i18n/fr/dashboard.ts
+++ b/shared/src/i18n/fr/dashboard.ts
@@ -34,6 +34,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Jours',
'dashboard.places': 'Lieux',
'dashboard.members': 'Compagnons de voyage',
+ 'dashboard.card.buddies': 'Compagnons de voyage',
+ 'dashboard.card.buddies.one': 'Compagnon',
'dashboard.archive': 'Archiver',
'dashboard.copyTrip': 'Copier',
'dashboard.copySuffix': 'copie',
@@ -147,8 +149,8 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': 'Dans {count} jours',
'dashboard.mobile.inMonths': 'Dans {count} mois',
'dashboard.mobile.spotlightDayOf': 'Jour {day} sur {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} jour',
- 'dashboard.mobile.spotlightDaysMany': '{count} jours',
+ 'dashboard.mobile.spotlightDays': '{count} jours',
+ 'dashboard.mobile.spotlightDays.one': '{count} jour',
'dashboard.mobile.completed': 'Terminé',
'dashboard.mobile.currencyConverter': 'Convertisseur de devises',
'dashboard.filter.planned': 'Planifiés',
@@ -159,14 +161,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'RÉCENT',
'dashboard.hero.tripDates': 'Dates du voyage',
'dashboard.hero.noDates': 'Aucune date définie',
- 'dashboard.hero.travelerOne': '{count} voyageur',
- 'dashboard.hero.travelerMany': '{count} voyageurs',
- 'dashboard.hero.destinationOne': '{count} destination',
- 'dashboard.hero.destinationMany': '{count} destinations',
- 'dashboard.hero.dayUnitOne': 'jour',
- 'dashboard.hero.dayUnitMany': 'jours',
- 'dashboard.hero.dayLeft': 'Jour restant',
+ 'dashboard.hero.travelers': '{count} voyageurs',
+ 'dashboard.hero.travelers.one': '{count} voyageur',
+ 'dashboard.hero.destinations': '{count} destinations',
+ 'dashboard.hero.destinations.one': '{count} destination',
+ 'dashboard.hero.dayUnit': 'jours',
+ 'dashboard.hero.dayUnit.one': 'jour',
'dashboard.hero.daysLeft': 'Jours restants',
+ 'dashboard.hero.daysLeft.one': 'Jour restant',
'dashboard.hero.lastDay': 'Dernier jour',
'dashboard.hero.untilStart': 'Avant le départ',
'dashboard.hero.startsIn': 'Départ dans',
@@ -181,7 +183,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': "≈ {count}× le tour de l'équateur",
'dashboard.card.idea': 'Idée',
- 'dashboard.card.buddyOne': 'Compagnon',
'dashboard.fx.from': 'De',
'dashboard.fx.to': 'Vers',
'dashboard.fx.unavailable': 'Taux indisponible',
diff --git a/shared/src/i18n/fr/files.ts b/shared/src/i18n/fr/files.ts
index 3e989d3b8..1c0be93d9 100644
--- a/shared/src/i18n/fr/files.ts
+++ b/shared/src/i18n/fr/files.ts
@@ -14,7 +14,7 @@ const files: TranslationStrings = {
'files.openError': "Impossible d'ouvrir le fichier",
'files.downloadPdf': 'Télécharger le PDF',
'files.count': '{count} fichiers',
- 'files.countSingular': '1 fichier',
+ 'files.count.one': '{count} fichier',
'files.uploaded': '{count} importés',
'files.uploadError': "Échec de l'import",
'files.uploadErrorType': "Ce type de fichier n'est pas pris en charge",
diff --git a/shared/src/i18n/fr/members.ts b/shared/src/i18n/fr/members.ts
index 6f4a9992c..13312f572 100644
--- a/shared/src/i18n/fr/members.ts
+++ b/shared/src/i18n/fr/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'Inviter',
'members.allHaveAccess': 'Tous les utilisateurs ont déjà accès.',
'members.access': 'Accès',
- 'members.person': 'personne',
'members.persons': 'personnes',
+ 'members.persons.one': 'personne',
'members.you': 'vous',
'members.owner': 'Propriétaire',
'members.leaveTrip': 'Quitter le voyage',
diff --git a/shared/src/i18n/fr/oauth.ts b/shared/src/i18n/fr/oauth.ts
index c2d43a920..934a47775 100644
--- a/shared/src/i18n/fr/oauth.ts
+++ b/shared/src/i18n/fr/oauth.ts
@@ -88,8 +88,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes.one': 'Approve ({count} scope)', // en-fallback
'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
'oauth.authorize.deny': 'Deny', // en-fallback
'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
diff --git a/shared/src/i18n/fr/places.ts b/shared/src/i18n/fr/places.ts
index 97238e2d7..bd25c7d49 100644
--- a/shared/src/i18n/fr/places.ts
+++ b/shared/src/i18n/fr/places.ts
@@ -71,7 +71,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'catégories',
'places.clearFilter': 'Effacer le filtre',
'places.count': '{count} lieux',
- 'places.countSingular': '1 lieu',
+ 'places.count.one': '{count} lieu',
'places.allPlanned': 'Tous les lieux sont planifiés',
'places.noneFound': 'Aucun lieu trouvé',
'places.editPlace': 'Modifier le lieu',
diff --git a/shared/src/i18n/fr/tours.ts b/shared/src/i18n/fr/tours.ts
index cbc3f7833..acef794b0 100644
--- a/shared/src/i18n/fr/tours.ts
+++ b/shared/src/i18n/fr/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Altitude ou mesures incomplètes : importée avec réserves',
'tours.import.caution': 'Importée avec réserves : altitude ou mesures incomplètes',
'tours.import.success': '{count} sorties importées depuis GPX',
- 'tours.import.successOne': '1 sortie importée depuis GPX',
+ 'tours.import.success.one': '{count} sortie importée depuis GPX',
'tours.import.noTrack': 'Aucune trace ni aucun itinéraire trouvé dans ce fichier GPX',
'tours.import.error': "Impossible d'importer le fichier GPX",
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/gr/atlas.ts b/shared/src/i18n/gr/atlas.ts
index 0b4f32f55..36c27b318 100644
--- a/shared/src/i18n/gr/atlas.ts
+++ b/shared/src/i18n/gr/atlas.ts
@@ -43,10 +43,10 @@ const atlas: TranslationStrings = {
'atlas.daysLeft': 'ημέρες απομένουν',
'atlas.streak': 'Σερί',
'atlas.years': 'έτη',
- 'atlas.yearInRow': 'έτος στη σειρά',
'atlas.yearsInRow': 'έτη στη σειρά',
- 'atlas.tripIn': 'ταξίδι σε',
+ 'atlas.yearsInRow.one': 'έτος στη σειρά',
'atlas.tripsIn': 'ταξίδια σε',
+ 'atlas.tripsIn.one': 'ταξίδι σε',
'atlas.since': 'από',
'atlas.europe': 'Ευρώπη',
'atlas.asia': 'Ασία',
@@ -57,10 +57,11 @@ const atlas: TranslationStrings = {
'atlas.other': 'Άλλο',
'atlas.firstVisit': 'Πρώτο ταξίδι',
'atlas.lastVisitLabel': 'Τελευταίο ταξίδι',
- 'atlas.tripSingular': 'Ταξίδι',
'atlas.tripPlural': 'Ταξίδια',
- 'atlas.placeVisited': 'Μέρος που επισκεφθήκατε',
+ 'atlas.tripsLabel': 'Ταξίδια',
+ 'atlas.tripsLabel.one': 'Ταξίδι',
'atlas.placesVisited': 'Μέρη που επισκεφθήκατε',
+ 'atlas.placesVisited.one': 'Μέρος που επισκεφθήκατε',
'atlas.planned': 'Προγραμματισμένο',
'atlas.showPlanned': 'Εμφάνιση προγραμματισμένων χωρών',
'atlas.plannedFor': 'Προγραμματισμένο για',
diff --git a/shared/src/i18n/gr/collab.ts b/shared/src/i18n/gr/collab.ts
index 76e9ea8b5..776127e2d 100644
--- a/shared/src/i18n/gr/collab.ts
+++ b/shared/src/i18n/gr/collab.ts
@@ -89,7 +89,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Κλείσιμο',
'collab.polls.closed': 'Κλειστή',
'collab.polls.votes': '{n} ψήφοι',
- 'collab.polls.vote': '{n} ψήφος',
+ 'collab.polls.votes.one': '{n} ψήφος',
'collab.polls.multipleChoice': 'Πολλαπλή επιλογή',
'collab.polls.multiChoice': 'Πολλαπλή επιλογή',
'collab.polls.deadline': 'Προθεσμία',
diff --git a/shared/src/i18n/gr/dashboard.ts b/shared/src/i18n/gr/dashboard.ts
index 85e3ed1fb..45846d9e5 100644
--- a/shared/src/i18n/gr/dashboard.ts
+++ b/shared/src/i18n/gr/dashboard.ts
@@ -30,6 +30,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Ημέρες',
'dashboard.places': 'Τόποι',
'dashboard.members': 'Μέλη',
+ 'dashboard.card.buddies': 'Μέλη',
+ 'dashboard.card.buddies.one': 'Buddy', // en-fallback
'dashboard.archive': 'Αρχειοθήκευση',
'dashboard.copyTrip': 'Αντιγραφή',
'dashboard.copySuffix': 'αντιγραφή',
@@ -158,8 +160,8 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': 'Σε {count} ημέρες',
'dashboard.mobile.inMonths': 'Σε {count} μήνες',
'dashboard.mobile.spotlightDayOf': 'Ημέρα {day} από {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} ημέρα',
- 'dashboard.mobile.spotlightDaysMany': '{count} ημέρες',
+ 'dashboard.mobile.spotlightDays': '{count} ημέρες',
+ 'dashboard.mobile.spotlightDays.one': '{count} ημέρα',
'dashboard.mobile.completed': 'Ολοκληρώθηκε',
'dashboard.mobile.currencyConverter': 'Μετατροπέας Νομισμάτων',
'dashboard.newTripSub': 'Plan a new trip from scratch', // en-fallback
@@ -174,14 +176,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'RECENT', // en-fallback
'dashboard.hero.tripDates': 'Trip dates', // en-fallback
'dashboard.hero.noDates': 'No dates set', // en-fallback
- 'dashboard.hero.travelerOne': '{count} traveler', // en-fallback
- 'dashboard.hero.travelerMany': '{count} travelers', // en-fallback
- 'dashboard.hero.destinationOne': '{count} destination', // en-fallback
- 'dashboard.hero.destinationMany': '{count} destinations', // en-fallback
- 'dashboard.hero.dayUnitOne': 'day', // en-fallback
- 'dashboard.hero.dayUnitMany': 'days', // en-fallback
- 'dashboard.hero.dayLeft': 'Day left', // en-fallback
+ 'dashboard.hero.travelers': '{count} travelers', // en-fallback
+ 'dashboard.hero.travelers.one': '{count} traveler', // en-fallback
+ 'dashboard.hero.destinations': '{count} destinations', // en-fallback
+ 'dashboard.hero.destinations.one': '{count} destination', // en-fallback
+ 'dashboard.hero.dayUnit': 'days', // en-fallback
+ 'dashboard.hero.dayUnit.one': 'day', // en-fallback
'dashboard.hero.daysLeft': 'Days left', // en-fallback
+ 'dashboard.hero.daysLeft.one': 'Day left', // en-fallback
'dashboard.hero.lastDay': 'Last day', // en-fallback
'dashboard.hero.untilStart': 'Until start', // en-fallback
'dashboard.hero.startsIn': 'Trip starts in', // en-fallback
@@ -196,7 +198,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km', // en-fallback
'dashboard.atlas.aroundEquator': '≈ {count}× around the equator', // en-fallback
'dashboard.card.idea': 'Idea', // en-fallback
- 'dashboard.card.buddyOne': 'Buddy', // en-fallback
'dashboard.fx.from': 'From', // en-fallback
'dashboard.fx.to': 'To', // en-fallback
'dashboard.fx.unavailable': 'Rate unavailable', // en-fallback
diff --git a/shared/src/i18n/gr/files.ts b/shared/src/i18n/gr/files.ts
index 3392e44fb..700aaf431 100644
--- a/shared/src/i18n/gr/files.ts
+++ b/shared/src/i18n/gr/files.ts
@@ -14,7 +14,7 @@ const files: TranslationStrings = {
'files.openError': 'Δεν ήταν δυνατό το άνοιγμα του αρχείου',
'files.downloadPdf': 'Λήψη PDF',
'files.count': '{count} αρχεία',
- 'files.countSingular': '1 αρχείο',
+ 'files.count.one': '{count} αρχείο',
'files.uploaded': '{count} μεταφορτώθηκαν',
'files.uploadError': 'Η μεταφόρτωση απέτυχε',
'files.uploadErrorType': 'Αυτός ο τύπος αρχείου δεν υποστηρίζεται',
diff --git a/shared/src/i18n/gr/members.ts b/shared/src/i18n/gr/members.ts
index 8f8becf86..013966675 100644
--- a/shared/src/i18n/gr/members.ts
+++ b/shared/src/i18n/gr/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'Πρόσκληση',
'members.allHaveAccess': 'Όλοι οι χρήστες έχουν ήδη πρόσβαση.',
'members.access': 'Πρόσβαση',
- 'members.person': 'άτομο',
'members.persons': 'άτομα',
+ 'members.persons.one': 'άτομο',
'members.you': 'εσείς',
'members.owner': 'Ιδιοκτήτης',
'members.leaveTrip': 'Αποχώρηση από ταξίδι',
diff --git a/shared/src/i18n/gr/oauth.ts b/shared/src/i18n/gr/oauth.ts
index b38c55a70..8fd370e3d 100644
--- a/shared/src/i18n/gr/oauth.ts
+++ b/shared/src/i18n/gr/oauth.ts
@@ -90,8 +90,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes.one': 'Approve ({count} scope)', // en-fallback
'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
'oauth.authorize.deny': 'Deny', // en-fallback
'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
diff --git a/shared/src/i18n/gr/places.ts b/shared/src/i18n/gr/places.ts
index 498af63f5..207a33abb 100644
--- a/shared/src/i18n/gr/places.ts
+++ b/shared/src/i18n/gr/places.ts
@@ -71,7 +71,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'κατηγορίες',
'places.clearFilter': 'Καθαρισμός φίλτρου',
'places.count': '{count} μέρη',
- 'places.countSingular': '1 μέρος',
+ 'places.count.one': '{count} μέρος',
'places.allPlanned': 'Όλα τα μέρη είναι προγραμματισμένα',
'places.noneFound': 'Δεν βρέθηκαν μέρη',
'places.editPlace': 'Επεξεργασία Μέρους',
diff --git a/shared/src/i18n/gr/tours.ts b/shared/src/i18n/gr/tours.ts
index c9ca4e7af..ea2040228 100644
--- a/shared/src/i18n/gr/tours.ts
+++ b/shared/src/i18n/gr/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Ελλιπή υψομετρικά δεδομένα ή μετρήσεις – εισαγωγή με επιφύλαξη',
'tours.import.caution': 'Εισαγωγή με επιφύλαξη – ελλιπή υψομετρικά δεδομένα ή μετρήσεις',
'tours.import.success': 'Αριθμός εκδρομών που εισήχθησαν από GPX: {count}',
- 'tours.import.successOne': 'Εισήχθη 1 εκδρομή από GPX',
+ 'tours.import.success.one': 'Εισήχθη {count} εκδρομή από GPX',
'tours.import.noTrack': 'Δεν βρέθηκε ίχνος ή διαδρομή σε αυτό το αρχείο GPX',
'tours.import.error': 'Δεν ήταν δυνατή η εισαγωγή του αρχείου GPX',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/hu/atlas.ts b/shared/src/i18n/hu/atlas.ts
index a8803420d..9c27ace25 100644
--- a/shared/src/i18n/hu/atlas.ts
+++ b/shared/src/i18n/hu/atlas.ts
@@ -43,10 +43,10 @@ const atlas: TranslationStrings = {
'atlas.streak': 'Sorozat',
'atlas.year': 'év',
'atlas.years': 'év',
- 'atlas.yearInRow': 'egymást követő év',
'atlas.yearsInRow': 'egymást követő év',
- 'atlas.tripIn': 'utazás',
+ 'atlas.yearsInRow.one': 'egymást követő év',
'atlas.tripsIn': 'utazás',
+ 'atlas.tripsIn.one': 'utazás',
'atlas.since': 'óta',
'atlas.europe': 'Európa',
'atlas.asia': 'Ázsia',
@@ -57,10 +57,11 @@ const atlas: TranslationStrings = {
'atlas.other': 'Egyéb',
'atlas.firstVisit': 'Első utazás',
'atlas.lastVisitLabel': 'Utolsó utazás',
- 'atlas.tripSingular': 'Utazás',
'atlas.tripPlural': 'Utazások',
- 'atlas.placeVisited': 'Meglátogatott hely',
+ 'atlas.tripsLabel': 'Utazások',
+ 'atlas.tripsLabel.one': 'Utazás',
'atlas.placesVisited': 'Meglátogatott helyek',
+ 'atlas.placesVisited.one': 'Meglátogatott hely',
'atlas.planned': 'Tervezett',
'atlas.showPlanned': 'Tervezett országok megjelenítése',
'atlas.plannedFor': 'Tervezve erre',
diff --git a/shared/src/i18n/hu/collab.ts b/shared/src/i18n/hu/collab.ts
index 6596bb926..cc756237e 100644
--- a/shared/src/i18n/hu/collab.ts
+++ b/shared/src/i18n/hu/collab.ts
@@ -89,7 +89,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Lezárás',
'collab.polls.closed': 'Lezárva',
'collab.polls.votes': '{n} szavazat',
- 'collab.polls.vote': '{n} szavazat',
+ 'collab.polls.votes.one': '{n} szavazat',
'collab.polls.multipleChoice': 'Többszörös választás',
'collab.polls.multiChoice': 'Többszörös választás',
'collab.polls.deadline': 'Határidő',
diff --git a/shared/src/i18n/hu/dashboard.ts b/shared/src/i18n/hu/dashboard.ts
index 4cf60e82e..4f8f6bf88 100644
--- a/shared/src/i18n/hu/dashboard.ts
+++ b/shared/src/i18n/hu/dashboard.ts
@@ -35,6 +35,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'nap',
'dashboard.places': 'hely',
'dashboard.members': 'Útitársak',
+ 'dashboard.card.buddies': 'Útitársak',
+ 'dashboard.card.buddies.one': 'Útitárs',
'dashboard.archive': 'Archiválás',
'dashboard.copyTrip': 'Másolás',
'dashboard.copySuffix': 'másolat',
@@ -147,8 +149,8 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': '{count} nap múlva',
'dashboard.mobile.inMonths': '{count} hónap múlva',
'dashboard.mobile.spotlightDayOf': '{day}. nap / {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} nap',
- 'dashboard.mobile.spotlightDaysMany': '{count} nap',
+ 'dashboard.mobile.spotlightDays': '{count} nap',
+ 'dashboard.mobile.spotlightDays.one': '{count} nap',
'dashboard.mobile.completed': 'Befejezett',
'dashboard.mobile.currencyConverter': 'Pénznemváltó',
'dashboard.filter.planned': 'Tervezett',
@@ -159,14 +161,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'LEGUTÓBBI',
'dashboard.hero.tripDates': 'Utazás dátumai',
'dashboard.hero.noDates': 'Nincs dátum megadva',
- 'dashboard.hero.travelerOne': '{count} utazó',
- 'dashboard.hero.travelerMany': '{count} utazó',
- 'dashboard.hero.destinationOne': '{count} úti cél',
- 'dashboard.hero.destinationMany': '{count} úti cél',
- 'dashboard.hero.dayUnitOne': 'nap',
- 'dashboard.hero.dayUnitMany': 'nap',
- 'dashboard.hero.dayLeft': 'nap van hátra',
+ 'dashboard.hero.travelers': '{count} utazó',
+ 'dashboard.hero.travelers.one': '{count} utazó',
+ 'dashboard.hero.destinations': '{count} úti cél',
+ 'dashboard.hero.destinations.one': '{count} úti cél',
+ 'dashboard.hero.dayUnit': 'nap',
+ 'dashboard.hero.dayUnit.one': 'nap',
'dashboard.hero.daysLeft': 'nap van hátra',
+ 'dashboard.hero.daysLeft.one': 'nap van hátra',
'dashboard.hero.lastDay': 'Utolsó nap',
'dashboard.hero.untilStart': 'Indulásig',
'dashboard.hero.startsIn': 'Indulásig',
@@ -181,7 +183,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ {count}× az Egyenlítő körül',
'dashboard.card.idea': 'Ötlet',
- 'dashboard.card.buddyOne': 'Útitárs',
'dashboard.fx.from': 'Erről',
'dashboard.fx.to': 'Erre',
'dashboard.fx.unavailable': 'Árfolyam nem elérhető',
diff --git a/shared/src/i18n/hu/files.ts b/shared/src/i18n/hu/files.ts
index 96920723c..a0493bb67 100644
--- a/shared/src/i18n/hu/files.ts
+++ b/shared/src/i18n/hu/files.ts
@@ -14,7 +14,7 @@ const files: TranslationStrings = {
'files.openError': 'A fájl megnyitása sikertelen',
'files.downloadPdf': 'PDF letöltése',
'files.count': '{count} fájl',
- 'files.countSingular': '1 fájl',
+ 'files.count.one': '{count} fájl',
'files.uploaded': '{count} feltöltve',
'files.uploadError': 'Feltöltés sikertelen',
'files.uploadErrorType': 'Ez a fájltípus nem támogatott',
diff --git a/shared/src/i18n/hu/members.ts b/shared/src/i18n/hu/members.ts
index 18fec507e..24aa9ede7 100644
--- a/shared/src/i18n/hu/members.ts
+++ b/shared/src/i18n/hu/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'Meghívás',
'members.allHaveAccess': 'Minden felhasználónak már van hozzáférése.',
'members.access': 'Hozzáférés',
- 'members.person': 'személy',
'members.persons': 'személy',
+ 'members.persons.one': 'személy',
'members.you': 'te',
'members.owner': 'Tulajdonos',
'members.leaveTrip': 'Utazás elhagyása',
diff --git a/shared/src/i18n/hu/oauth.ts b/shared/src/i18n/hu/oauth.ts
index 03fa54edf..2202925d2 100644
--- a/shared/src/i18n/hu/oauth.ts
+++ b/shared/src/i18n/hu/oauth.ts
@@ -88,8 +88,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes.one': 'Approve ({count} scope)', // en-fallback
'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
'oauth.authorize.deny': 'Deny', // en-fallback
'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
diff --git a/shared/src/i18n/hu/places.ts b/shared/src/i18n/hu/places.ts
index 7f44a986a..2779f19e7 100644
--- a/shared/src/i18n/hu/places.ts
+++ b/shared/src/i18n/hu/places.ts
@@ -70,7 +70,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'kategória',
'places.clearFilter': 'Szűrő törlése',
'places.count': '{count} hely',
- 'places.countSingular': '1 hely',
+ 'places.count.one': '{count} hely',
'places.allPlanned': 'Minden hely be van tervezve',
'places.noneFound': 'Nem találhatók helyek',
'places.editPlace': 'Hely szerkesztése',
diff --git a/shared/src/i18n/hu/tours.ts b/shared/src/i18n/hu/tours.ts
index 3d9f54494..f0c367e38 100644
--- a/shared/src/i18n/hu/tours.ts
+++ b/shared/src/i18n/hu/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Hiányos magassági adatok vagy mutatók – fenntartással importálva',
'tours.import.caution': 'Fenntartással importálva – hiányos magassági adatok vagy mutatók',
'tours.import.success': 'GPX-ből importált túrák száma: {count}',
- 'tours.import.successOne': '1 túra importálva GPX-ből',
+ 'tours.import.success.one': '{count} túra importálva GPX-ből',
'tours.import.noTrack': 'Ebben a GPX-fájlban nem található nyomvonal vagy útvonal',
'tours.import.error': 'Nem sikerült importálni a GPX-fájlt',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/id/atlas.ts b/shared/src/i18n/id/atlas.ts
index aafd3fad8..3df5c13bd 100644
--- a/shared/src/i18n/id/atlas.ts
+++ b/shared/src/i18n/id/atlas.ts
@@ -43,9 +43,7 @@ const atlas: TranslationStrings = {
'atlas.daysLeft': 'hari lagi',
'atlas.streak': 'Rentetan',
'atlas.years': 'tahun',
- 'atlas.yearInRow': 'tahun berturut-turut',
'atlas.yearsInRow': 'tahun berturut-turut',
- 'atlas.tripIn': 'perjalanan ke',
'atlas.tripsIn': 'perjalanan ke',
'atlas.since': 'sejak',
'atlas.europe': 'Eropa',
@@ -57,9 +55,8 @@ const atlas: TranslationStrings = {
'atlas.other': 'Lainnya',
'atlas.firstVisit': 'Perjalanan pertama',
'atlas.lastVisitLabel': 'Perjalanan terakhir',
- 'atlas.tripSingular': 'Perjalanan',
'atlas.tripPlural': 'Perjalanan',
- 'atlas.placeVisited': 'Tempat dikunjungi',
+ 'atlas.tripsLabel': 'Perjalanan',
'atlas.placesVisited': 'Tempat dikunjungi',
'atlas.planned': 'Direncanakan',
'atlas.showPlanned': 'Tampilkan negara yang direncanakan',
diff --git a/shared/src/i18n/id/collab.ts b/shared/src/i18n/id/collab.ts
index 28cd555f3..5eba1191f 100644
--- a/shared/src/i18n/id/collab.ts
+++ b/shared/src/i18n/id/collab.ts
@@ -89,7 +89,6 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Tutup',
'collab.polls.closed': 'Ditutup',
'collab.polls.votes': '{n} suara',
- 'collab.polls.vote': '{n} suara',
'collab.polls.multipleChoice': 'Pilihan ganda',
'collab.polls.multiChoice': 'Pilihan ganda',
'collab.polls.deadline': 'Tenggat waktu',
diff --git a/shared/src/i18n/id/dashboard.ts b/shared/src/i18n/id/dashboard.ts
index 1506a4c58..1e5344923 100644
--- a/shared/src/i18n/id/dashboard.ts
+++ b/shared/src/i18n/id/dashboard.ts
@@ -34,6 +34,7 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Hari',
'dashboard.places': 'Tempat',
'dashboard.members': 'Teman perjalanan',
+ 'dashboard.card.buddies': 'Teman perjalanan',
'dashboard.archive': 'Arsipkan',
'dashboard.copyTrip': 'Salin',
'dashboard.copySuffix': 'salinan',
@@ -145,8 +146,7 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': 'Dalam {count} hari',
'dashboard.mobile.inMonths': 'Dalam {count} bulan',
'dashboard.mobile.spotlightDayOf': 'Hari {day} dari {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} hari',
- 'dashboard.mobile.spotlightDaysMany': '{count} hari',
+ 'dashboard.mobile.spotlightDays': '{count} hari',
'dashboard.mobile.completed': 'Selesai',
'dashboard.mobile.currencyConverter': 'Konverter Mata Uang',
'dashboard.filter.planned': 'Direncanakan',
@@ -157,13 +157,9 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'TERBARU',
'dashboard.hero.tripDates': 'Tanggal perjalanan',
'dashboard.hero.noDates': 'Tanggal belum diatur',
- 'dashboard.hero.travelerOne': '{count} pelancong',
- 'dashboard.hero.travelerMany': '{count} pelancong',
- 'dashboard.hero.destinationOne': '{count} destinasi',
- 'dashboard.hero.destinationMany': '{count} destinasi',
- 'dashboard.hero.dayUnitOne': 'hari',
- 'dashboard.hero.dayUnitMany': 'hari',
- 'dashboard.hero.dayLeft': 'Hari tersisa',
+ 'dashboard.hero.travelers': '{count} pelancong',
+ 'dashboard.hero.destinations': '{count} destinasi',
+ 'dashboard.hero.dayUnit': 'hari',
'dashboard.hero.daysLeft': 'Hari tersisa',
'dashboard.hero.lastDay': 'Hari terakhir',
'dashboard.hero.untilStart': 'Hingga mulai',
@@ -179,7 +175,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ {count}× keliling khatulistiwa',
'dashboard.card.idea': 'Ide',
- 'dashboard.card.buddyOne': 'Teman',
'dashboard.fx.from': 'Dari',
'dashboard.fx.to': 'Ke',
'dashboard.fx.unavailable': 'Kurs tidak tersedia',
diff --git a/shared/src/i18n/id/files.ts b/shared/src/i18n/id/files.ts
index 97b786cf4..9ed47eb32 100644
--- a/shared/src/i18n/id/files.ts
+++ b/shared/src/i18n/id/files.ts
@@ -14,7 +14,6 @@ const files: TranslationStrings = {
'files.openError': 'Tidak dapat membuka file',
'files.downloadPdf': 'Unduh PDF',
'files.count': '{count} file',
- 'files.countSingular': '1 berkas',
'files.uploaded': '{count} diunggah',
'files.uploadError': 'Gagal mengunggah',
'files.uploadErrorType': 'Tipe file ini tidak didukung',
diff --git a/shared/src/i18n/id/members.ts b/shared/src/i18n/id/members.ts
index 197b911d2..7921f4165 100644
--- a/shared/src/i18n/id/members.ts
+++ b/shared/src/i18n/id/members.ts
@@ -7,7 +7,6 @@ const members: TranslationStrings = {
'members.invite': 'Undang',
'members.allHaveAccess': 'Semua pengguna sudah punya akses.',
'members.access': 'Akses',
- 'members.person': 'orang',
'members.persons': 'orang',
'members.you': 'kamu',
'members.owner': 'Pemilik',
diff --git a/shared/src/i18n/id/oauth.ts b/shared/src/i18n/id/oauth.ts
index 32ebb5b3b..6d743ac7e 100644
--- a/shared/src/i18n/id/oauth.ts
+++ b/shared/src/i18n/id/oauth.ts
@@ -85,8 +85,7 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)', // en-fallback
'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
'oauth.authorize.deny': 'Deny', // en-fallback
'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
diff --git a/shared/src/i18n/id/places.ts b/shared/src/i18n/id/places.ts
index 1a804f524..c47adc354 100644
--- a/shared/src/i18n/id/places.ts
+++ b/shared/src/i18n/id/places.ts
@@ -69,7 +69,6 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'kategori',
'places.clearFilter': 'Hapus filter',
'places.count': '{count} tempat',
- 'places.countSingular': '1 tempat',
'places.allPlanned': 'Semua tempat sudah direncanakan',
'places.noneFound': 'Tidak ada tempat ditemukan',
'places.editPlace': 'Edit Tempat',
diff --git a/shared/src/i18n/id/tours.ts b/shared/src/i18n/id/tours.ts
index e9f1add6b..9469f99c7 100644
--- a/shared/src/i18n/id/tours.ts
+++ b/shared/src/i18n/id/tours.ts
@@ -20,7 +20,6 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Data elevasi atau metrik belum lengkap; diimpor dengan peringatan',
'tours.import.caution': 'Diimpor dengan peringatan; data elevasi atau metrik belum lengkap',
'tours.import.success': '{count} tur diimpor dari GPX',
- 'tours.import.successOne': '1 tur diimpor dari GPX',
'tours.import.noTrack': 'Tidak ditemukan jejak atau rute dalam berkas GPX tersebut',
'tours.import.error': 'Berkas GPX tidak dapat diimpor',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/it/atlas.ts b/shared/src/i18n/it/atlas.ts
index 443fbe512..ab6e454c3 100644
--- a/shared/src/i18n/it/atlas.ts
+++ b/shared/src/i18n/it/atlas.ts
@@ -41,10 +41,10 @@ const atlas: TranslationStrings = {
'atlas.streak': 'Serie',
'atlas.year': 'anno',
'atlas.years': 'anni',
- 'atlas.yearInRow': 'anno consecutivo',
'atlas.yearsInRow': 'anni consecutivi',
- 'atlas.tripIn': 'viaggio in',
+ 'atlas.yearsInRow.one': 'anno consecutivo',
'atlas.tripsIn': 'viaggi in',
+ 'atlas.tripsIn.one': 'viaggio in',
'atlas.since': 'dal',
'atlas.europe': 'Europa',
'atlas.asia': 'Asia',
@@ -55,10 +55,11 @@ const atlas: TranslationStrings = {
'atlas.other': 'Altro',
'atlas.firstVisit': 'Primo viaggio',
'atlas.lastVisitLabel': 'Ultimo viaggio',
- 'atlas.tripSingular': 'Viaggio',
'atlas.tripPlural': 'Viaggi',
- 'atlas.placeVisited': 'Luogo visitato',
+ 'atlas.tripsLabel': 'Viaggi',
+ 'atlas.tripsLabel.one': 'Viaggio',
'atlas.placesVisited': 'Luoghi visitati',
+ 'atlas.placesVisited.one': 'Luogo visitato',
'atlas.searchCountry': 'Cerca un paese...',
'atlas.searchPlaces': 'Luoghi',
'atlas.planned': 'In programma',
diff --git a/shared/src/i18n/it/collab.ts b/shared/src/i18n/it/collab.ts
index 6e307e01a..e7152ac45 100644
--- a/shared/src/i18n/it/collab.ts
+++ b/shared/src/i18n/it/collab.ts
@@ -89,7 +89,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Chiudi',
'collab.polls.closed': 'Chiuso',
'collab.polls.votes': '{n} voti',
- 'collab.polls.vote': '{n} voto',
+ 'collab.polls.votes.one': '{n} voto',
'collab.polls.multipleChoice': 'Scelta multipla',
'collab.polls.multiChoice': 'Scelta multipla',
'collab.polls.deadline': 'Scadenza',
diff --git a/shared/src/i18n/it/dashboard.ts b/shared/src/i18n/it/dashboard.ts
index a3e7b41eb..143d48433 100644
--- a/shared/src/i18n/it/dashboard.ts
+++ b/shared/src/i18n/it/dashboard.ts
@@ -34,6 +34,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Giorni',
'dashboard.places': 'Luoghi',
'dashboard.members': 'Compagni di viaggio',
+ 'dashboard.card.buddies': 'Compagni di viaggio',
+ 'dashboard.card.buddies.one': 'Compagno',
'dashboard.archive': 'Archivia',
'dashboard.copyTrip': 'Copia',
'dashboard.copySuffix': 'copia',
@@ -147,8 +149,8 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': 'Tra {count} giorni',
'dashboard.mobile.inMonths': 'Tra {count} mesi',
'dashboard.mobile.spotlightDayOf': 'Giorno {day} di {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} giorno',
- 'dashboard.mobile.spotlightDaysMany': '{count} giorni',
+ 'dashboard.mobile.spotlightDays': '{count} giorni',
+ 'dashboard.mobile.spotlightDays.one': '{count} giorno',
'dashboard.mobile.completed': 'Completato',
'dashboard.mobile.currencyConverter': 'Convertitore di valuta',
'dashboard.filter.planned': 'Pianificati',
@@ -159,14 +161,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'RECENTE',
'dashboard.hero.tripDates': 'Date del viaggio',
'dashboard.hero.noDates': 'Nessuna data impostata',
- 'dashboard.hero.travelerOne': '{count} viaggiatore',
- 'dashboard.hero.travelerMany': '{count} viaggiatori',
- 'dashboard.hero.destinationOne': '{count} destinazione',
- 'dashboard.hero.destinationMany': '{count} destinazioni',
- 'dashboard.hero.dayUnitOne': 'giorno',
- 'dashboard.hero.dayUnitMany': 'giorni',
- 'dashboard.hero.dayLeft': 'Giorno rimasto',
+ 'dashboard.hero.travelers': '{count} viaggiatori',
+ 'dashboard.hero.travelers.one': '{count} viaggiatore',
+ 'dashboard.hero.destinations': '{count} destinazioni',
+ 'dashboard.hero.destinations.one': '{count} destinazione',
+ 'dashboard.hero.dayUnit': 'giorni',
+ 'dashboard.hero.dayUnit.one': 'giorno',
'dashboard.hero.daysLeft': 'Giorni rimasti',
+ 'dashboard.hero.daysLeft.one': 'Giorno rimasto',
'dashboard.hero.lastDay': 'Ultimo giorno',
'dashboard.hero.untilStart': "All'inizio",
'dashboard.hero.startsIn': 'Si parte tra',
@@ -181,7 +183,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': "≈ {count}× intorno all'equatore",
'dashboard.card.idea': 'Idea',
- 'dashboard.card.buddyOne': 'Compagno',
'dashboard.fx.from': 'Da',
'dashboard.fx.to': 'A',
'dashboard.fx.unavailable': 'Tasso non disponibile',
diff --git a/shared/src/i18n/it/files.ts b/shared/src/i18n/it/files.ts
index 9d54fcf7c..1cd548af9 100644
--- a/shared/src/i18n/it/files.ts
+++ b/shared/src/i18n/it/files.ts
@@ -14,7 +14,7 @@ const files: TranslationStrings = {
'files.openError': 'Impossibile aprire il file',
'files.downloadPdf': 'Scarica PDF',
'files.count': '{count} file',
- 'files.countSingular': '1 documento',
+ 'files.count.one': '{count} documento',
'files.uploaded': '{count} caricati',
'files.uploadError': 'Caricamento non riuscito',
'files.uploadErrorType': 'Questo tipo di file non è supportato',
diff --git a/shared/src/i18n/it/members.ts b/shared/src/i18n/it/members.ts
index 4409e631b..8f90f8b58 100644
--- a/shared/src/i18n/it/members.ts
+++ b/shared/src/i18n/it/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'Invita',
'members.allHaveAccess': 'Tutti gli utenti hanno già accesso.',
'members.access': 'Accesso',
- 'members.person': 'persona',
'members.persons': 'persone',
+ 'members.persons.one': 'persona',
'members.you': 'tu',
'members.owner': 'Proprietario',
'members.leaveTrip': 'Abbandona viaggio',
diff --git a/shared/src/i18n/it/oauth.ts b/shared/src/i18n/it/oauth.ts
index 76116f88a..95b5aad94 100644
--- a/shared/src/i18n/it/oauth.ts
+++ b/shared/src/i18n/it/oauth.ts
@@ -86,8 +86,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes.one': 'Approve ({count} scope)', // en-fallback
'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
'oauth.authorize.deny': 'Deny', // en-fallback
'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
diff --git a/shared/src/i18n/it/places.ts b/shared/src/i18n/it/places.ts
index b917c03be..6b8b706ca 100644
--- a/shared/src/i18n/it/places.ts
+++ b/shared/src/i18n/it/places.ts
@@ -70,7 +70,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'categorie',
'places.clearFilter': 'Cancella filtro',
'places.count': '{count} luoghi',
- 'places.countSingular': '1 luogo',
+ 'places.count.one': '{count} luogo',
'places.allPlanned': 'Tutti i luoghi sono programmati',
'places.noneFound': 'Nessun luogo trovato',
'places.editPlace': 'Modifica luogo',
diff --git a/shared/src/i18n/it/tours.ts b/shared/src/i18n/it/tours.ts
index 0a3ee576d..77ef7b276 100644
--- a/shared/src/i18n/it/tours.ts
+++ b/shared/src/i18n/it/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Quote o metriche incomplete: importata con riserva',
'tours.import.caution': 'Importata con riserva: quote o metriche incomplete',
'tours.import.success': '{count} escursioni importate da GPX',
- 'tours.import.successOne': '1 escursione importata da GPX',
+ 'tours.import.success.one': '{count} escursione importata da GPX',
'tours.import.noTrack': 'Nessuna traccia o percorso trovato nel file GPX',
'tours.import.error': 'Impossibile importare il file GPX',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/ja/atlas.ts b/shared/src/i18n/ja/atlas.ts
index 74683e643..c23c58fcc 100644
--- a/shared/src/i18n/ja/atlas.ts
+++ b/shared/src/i18n/ja/atlas.ts
@@ -43,9 +43,7 @@ const atlas: TranslationStrings = {
'atlas.daysLeft': '残り日数',
'atlas.streak': '連続',
'atlas.years': '年',
- 'atlas.yearInRow': '年連続',
'atlas.yearsInRow': '年連続',
- 'atlas.tripIn': '旅行',
'atlas.tripsIn': '旅行',
'atlas.since': '開始',
'atlas.europe': 'ヨーロッパ',
@@ -57,9 +55,8 @@ const atlas: TranslationStrings = {
'atlas.other': 'その他',
'atlas.firstVisit': '最初の旅行',
'atlas.lastVisitLabel': '最後の旅行',
- 'atlas.tripSingular': '旅行',
'atlas.tripPlural': '旅行',
- 'atlas.placeVisited': '訪問した場所',
+ 'atlas.tripsLabel': '旅行',
'atlas.placesVisited': '訪問した場所',
'atlas.planned': '予定',
'atlas.showPlanned': '予定の国を表示',
diff --git a/shared/src/i18n/ja/collab.ts b/shared/src/i18n/ja/collab.ts
index 57159b6b4..47efbb4df 100644
--- a/shared/src/i18n/ja/collab.ts
+++ b/shared/src/i18n/ja/collab.ts
@@ -89,7 +89,6 @@ const collab: TranslationStrings = {
'collab.polls.close': '閉じる',
'collab.polls.closed': '終了',
'collab.polls.votes': '{n}票',
- 'collab.polls.vote': '{n}票',
'collab.polls.multipleChoice': '複数選択',
'collab.polls.multiChoice': '複数選択',
'collab.polls.deadline': '締切',
diff --git a/shared/src/i18n/ja/dashboard.ts b/shared/src/i18n/ja/dashboard.ts
index ed2442ad3..fa53e61c3 100644
--- a/shared/src/i18n/ja/dashboard.ts
+++ b/shared/src/i18n/ja/dashboard.ts
@@ -34,6 +34,7 @@ const dashboard: TranslationStrings = {
'dashboard.days': '日数',
'dashboard.places': '場所',
'dashboard.members': '同行者',
+ 'dashboard.card.buddies': '同行者',
'dashboard.archive': 'アーカイブ',
'dashboard.copyTrip': 'コピー',
'dashboard.copySuffix': 'コピー',
@@ -157,8 +158,7 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': '{count}日後',
'dashboard.mobile.inMonths': '{count}か月後',
'dashboard.mobile.spotlightDayOf': '{total} 日中 {day} 日目',
- 'dashboard.mobile.spotlightDayOne': '{count} 日',
- 'dashboard.mobile.spotlightDaysMany': '{count} 日',
+ 'dashboard.mobile.spotlightDays': '{count} 日',
'dashboard.mobile.completed': '完了',
'dashboard.mobile.currencyConverter': '通貨換算',
'dashboard.filter.planned': '予定',
@@ -169,13 +169,9 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': '最近',
'dashboard.hero.tripDates': '旅行の日程',
'dashboard.hero.noDates': '日程未設定',
- 'dashboard.hero.travelerOne': '{count}人の旅行者',
- 'dashboard.hero.travelerMany': '{count}人の旅行者',
- 'dashboard.hero.destinationOne': '{count}件の目的地',
- 'dashboard.hero.destinationMany': '{count}件の目的地',
- 'dashboard.hero.dayUnitOne': '日',
- 'dashboard.hero.dayUnitMany': '日',
- 'dashboard.hero.dayLeft': '残り1日',
+ 'dashboard.hero.travelers': '{count}人の旅行者',
+ 'dashboard.hero.destinations': '{count}件の目的地',
+ 'dashboard.hero.dayUnit': '日',
'dashboard.hero.daysLeft': '残り日数',
'dashboard.hero.lastDay': '最終日',
'dashboard.hero.untilStart': '開始まで',
@@ -191,7 +187,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ 赤道{count}周',
'dashboard.card.idea': 'アイデア',
- 'dashboard.card.buddyOne': '同行者',
'dashboard.fx.from': '変換元',
'dashboard.fx.to': '変換先',
'dashboard.fx.unavailable': 'レートを取得できません',
diff --git a/shared/src/i18n/ja/files.ts b/shared/src/i18n/ja/files.ts
index fb848944d..79faf59aa 100644
--- a/shared/src/i18n/ja/files.ts
+++ b/shared/src/i18n/ja/files.ts
@@ -14,7 +14,6 @@ const files: TranslationStrings = {
'files.openError': 'ファイルを開けませんでした',
'files.downloadPdf': 'PDFをダウンロード',
'files.count': '{count}件のファイル',
- 'files.countSingular': '1件のファイル',
'files.uploaded': '{count}件アップロード',
'files.uploadError': 'アップロードに失敗しました',
'files.uploadErrorType': 'このファイル形式はサポートされていません',
diff --git a/shared/src/i18n/ja/members.ts b/shared/src/i18n/ja/members.ts
index 503f84f62..d83383a80 100644
--- a/shared/src/i18n/ja/members.ts
+++ b/shared/src/i18n/ja/members.ts
@@ -7,7 +7,6 @@ const members: TranslationStrings = {
'members.invite': '招待',
'members.allHaveAccess': 'すでに全員がアクセスできます。',
'members.access': 'アクセス',
- 'members.person': '人',
'members.persons': '人',
'members.you': 'あなた',
'members.owner': 'オーナー',
diff --git a/shared/src/i18n/ja/oauth.ts b/shared/src/i18n/ja/oauth.ts
index 373e059ab..ab475c29c 100644
--- a/shared/src/i18n/ja/oauth.ts
+++ b/shared/src/i18n/ja/oauth.ts
@@ -85,8 +85,7 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)', // en-fallback
'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
'oauth.authorize.deny': 'Deny', // en-fallback
'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
diff --git a/shared/src/i18n/ja/places.ts b/shared/src/i18n/ja/places.ts
index 7f35bc3bf..8175c2f51 100644
--- a/shared/src/i18n/ja/places.ts
+++ b/shared/src/i18n/ja/places.ts
@@ -70,7 +70,6 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'カテゴリ',
'places.clearFilter': 'フィルター解除',
'places.count': '{count} 件の場所',
- 'places.countSingular': '1 件の場所',
'places.allPlanned': 'すべての場所が計画済みです',
'places.noneFound': '場所が見つかりません',
'places.editPlace': '場所を編集',
diff --git a/shared/src/i18n/ja/tours.ts b/shared/src/i18n/ja/tours.ts
index 026e3916a..27f03adbf 100644
--- a/shared/src/i18n/ja/tours.ts
+++ b/shared/src/i18n/ja/tours.ts
@@ -20,7 +20,6 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': '標高・各種データが不完全です。注意が必要な状態でインポートされました。',
'tours.import.caution': '標高・各種データが不完全なため、注意が必要な状態でインポートしました。',
'tours.import.success': 'GPXから{count}件のツアーをインポートしました',
- 'tours.import.successOne': 'GPXから1件のツアーをインポートしました',
'tours.import.noTrack': 'このGPXファイルにトラックやルートが見つかりません',
'tours.import.error': 'GPXファイルをインポートできませんでした',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/ko/atlas.ts b/shared/src/i18n/ko/atlas.ts
index 2cde9b6c4..e292d77ae 100644
--- a/shared/src/i18n/ko/atlas.ts
+++ b/shared/src/i18n/ko/atlas.ts
@@ -43,9 +43,7 @@ const atlas: TranslationStrings = {
'atlas.daysLeft': '일 남음',
'atlas.streak': '연속',
'atlas.years': '년',
- 'atlas.yearInRow': '연 연속',
'atlas.yearsInRow': '년 연속',
- 'atlas.tripIn': '에서 여행',
'atlas.tripsIn': '에서 여행',
'atlas.since': '부터',
'atlas.europe': '유럽',
@@ -57,9 +55,8 @@ const atlas: TranslationStrings = {
'atlas.other': '기타',
'atlas.firstVisit': '첫 번째 여행',
'atlas.lastVisitLabel': '마지막 여행',
- 'atlas.tripSingular': '여행',
'atlas.tripPlural': '여행',
- 'atlas.placeVisited': '방문한 장소',
+ 'atlas.tripsLabel': '여행',
'atlas.placesVisited': '방문한 장소',
'atlas.planned': '예정',
'atlas.showPlanned': '예정된 국가 표시',
diff --git a/shared/src/i18n/ko/collab.ts b/shared/src/i18n/ko/collab.ts
index 729d06631..ef9cf6ddb 100644
--- a/shared/src/i18n/ko/collab.ts
+++ b/shared/src/i18n/ko/collab.ts
@@ -89,7 +89,6 @@ const collab: TranslationStrings = {
'collab.polls.close': '닫기',
'collab.polls.closed': '종료됨',
'collab.polls.votes': '{n}표',
- 'collab.polls.vote': '{n}표',
'collab.polls.multipleChoice': '복수 선택',
'collab.polls.multiChoice': '복수 선택',
'collab.polls.deadline': '마감일',
diff --git a/shared/src/i18n/ko/dashboard.ts b/shared/src/i18n/ko/dashboard.ts
index f987ec6ce..9cc559f5c 100644
--- a/shared/src/i18n/ko/dashboard.ts
+++ b/shared/src/i18n/ko/dashboard.ts
@@ -34,6 +34,7 @@ const dashboard: TranslationStrings = {
'dashboard.days': '일',
'dashboard.places': '장소',
'dashboard.members': '동행자',
+ 'dashboard.card.buddies': '동행자',
'dashboard.archive': '보관',
'dashboard.copyTrip': '복사',
'dashboard.copySuffix': '사본',
@@ -157,8 +158,7 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': '{count}일 후',
'dashboard.mobile.inMonths': '{count}개월 후',
'dashboard.mobile.spotlightDayOf': '{total}일 중 {day}일째',
- 'dashboard.mobile.spotlightDayOne': '{count}일',
- 'dashboard.mobile.spotlightDaysMany': '{count}일',
+ 'dashboard.mobile.spotlightDays': '{count}일',
'dashboard.mobile.completed': '완료됨',
'dashboard.mobile.currencyConverter': '환율 계산기',
'dashboard.filter.planned': '예정',
@@ -169,13 +169,9 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': '최근',
'dashboard.hero.tripDates': '여행 날짜',
'dashboard.hero.noDates': '날짜 미설정',
- 'dashboard.hero.travelerOne': '여행자 {count}명',
- 'dashboard.hero.travelerMany': '여행자 {count}명',
- 'dashboard.hero.destinationOne': '목적지 {count}곳',
- 'dashboard.hero.destinationMany': '목적지 {count}곳',
- 'dashboard.hero.dayUnitOne': '일',
- 'dashboard.hero.dayUnitMany': '일',
- 'dashboard.hero.dayLeft': '하루 남음',
+ 'dashboard.hero.travelers': '여행자 {count}명',
+ 'dashboard.hero.destinations': '목적지 {count}곳',
+ 'dashboard.hero.dayUnit': '일',
'dashboard.hero.daysLeft': '남은 일수',
'dashboard.hero.lastDay': '마지막 날',
'dashboard.hero.untilStart': '시작까지',
@@ -191,7 +187,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ 적도 {count}바퀴',
'dashboard.card.idea': '아이디어',
- 'dashboard.card.buddyOne': '동행자',
'dashboard.fx.from': '보낼 통화',
'dashboard.fx.to': '받을 통화',
'dashboard.fx.unavailable': '환율을 사용할 수 없음',
diff --git a/shared/src/i18n/ko/files.ts b/shared/src/i18n/ko/files.ts
index ddec8c741..8de20a72e 100644
--- a/shared/src/i18n/ko/files.ts
+++ b/shared/src/i18n/ko/files.ts
@@ -14,7 +14,6 @@ const files: TranslationStrings = {
'files.openError': '파일을 열 수 없습니다',
'files.downloadPdf': 'PDF 다운로드',
'files.count': '파일 {count}개',
- 'files.countSingular': '파일 1개',
'files.uploaded': '{count}개 업로드됨',
'files.uploadError': '업로드 실패',
'files.uploadErrorType': '지원되지 않는 파일 형식입니다',
diff --git a/shared/src/i18n/ko/members.ts b/shared/src/i18n/ko/members.ts
index c75348385..fc8c7119f 100644
--- a/shared/src/i18n/ko/members.ts
+++ b/shared/src/i18n/ko/members.ts
@@ -7,7 +7,6 @@ const members: TranslationStrings = {
'members.invite': '초대',
'members.allHaveAccess': '모든 사용자가 이미 접근 권한을 가지고 있습니다.',
'members.access': '접근',
- 'members.person': '명',
'members.persons': '명',
'members.you': '나',
'members.owner': '소유자',
diff --git a/shared/src/i18n/ko/oauth.ts b/shared/src/i18n/ko/oauth.ts
index 95b00a466..bffd46fb8 100644
--- a/shared/src/i18n/ko/oauth.ts
+++ b/shared/src/i18n/ko/oauth.ts
@@ -83,8 +83,7 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)', // en-fallback
'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
'oauth.authorize.deny': 'Deny', // en-fallback
'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
diff --git a/shared/src/i18n/ko/places.ts b/shared/src/i18n/ko/places.ts
index 348332064..e94560c24 100644
--- a/shared/src/i18n/ko/places.ts
+++ b/shared/src/i18n/ko/places.ts
@@ -69,7 +69,6 @@ const places: TranslationStrings = {
'places.categoriesSelected': '카테고리',
'places.clearFilter': '필터 지우기',
'places.count': '장소 {count}개',
- 'places.countSingular': '장소 1개',
'places.allPlanned': '모든 장소가 계획되었습니다',
'places.noneFound': '장소를 찾을 수 없습니다',
'places.editPlace': '장소 편집',
diff --git a/shared/src/i18n/ko/tours.ts b/shared/src/i18n/ko/tours.ts
index 2b7df6881..01641abaa 100644
--- a/shared/src/i18n/ko/tours.ts
+++ b/shared/src/i18n/ko/tours.ts
@@ -20,7 +20,6 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': '고도 또는 측정 데이터가 불완전하여 주의가 필요한 상태로 가져왔습니다',
'tours.import.caution': '고도 또는 측정 데이터가 불완전하여 주의가 필요한 상태로 가져왔습니다',
'tours.import.success': 'GPX에서 투어 {count}개를 가져왔습니다',
- 'tours.import.successOne': 'GPX에서 투어 1개를 가져왔습니다',
'tours.import.noTrack': '이 GPX 파일에서 트랙이나 경로를 찾을 수 없습니다',
'tours.import.error': 'GPX 파일을 가져오지 못했습니다',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/nl/atlas.ts b/shared/src/i18n/nl/atlas.ts
index 011e6b058..9bae002d9 100644
--- a/shared/src/i18n/nl/atlas.ts
+++ b/shared/src/i18n/nl/atlas.ts
@@ -19,10 +19,10 @@ const atlas: TranslationStrings = {
'atlas.streak': 'Reeks',
'atlas.year': 'jaar',
'atlas.years': 'jaar',
- 'atlas.yearInRow': 'jaar op rij',
'atlas.yearsInRow': 'jaar op rij',
- 'atlas.tripIn': 'reis in',
+ 'atlas.yearsInRow.one': 'jaar op rij',
'atlas.tripsIn': 'reizen in',
+ 'atlas.tripsIn.one': 'reis in',
'atlas.since': 'sinds',
'atlas.europe': 'Europa',
'atlas.asia': 'Azië',
@@ -33,10 +33,11 @@ const atlas: TranslationStrings = {
'atlas.other': 'Overig',
'atlas.firstVisit': 'Eerste reis',
'atlas.lastVisitLabel': 'Laatste reis',
- 'atlas.tripSingular': 'Reis',
'atlas.tripPlural': 'Reizen',
- 'atlas.placeVisited': 'Bezochte plaats',
+ 'atlas.tripsLabel': 'Reizen',
+ 'atlas.tripsLabel.one': 'Reis',
'atlas.placesVisited': 'Bezochte plaatsen',
+ 'atlas.placesVisited.one': 'Bezochte plaats',
'atlas.statsTab': 'Statistieken',
'atlas.bucketTab': 'Bucketlist',
'atlas.addBucket': 'Toevoegen aan bucket list',
diff --git a/shared/src/i18n/nl/collab.ts b/shared/src/i18n/nl/collab.ts
index 91b63b5b6..41ca7528b 100644
--- a/shared/src/i18n/nl/collab.ts
+++ b/shared/src/i18n/nl/collab.ts
@@ -89,7 +89,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Sluiten',
'collab.polls.closed': 'Gesloten',
'collab.polls.votes': '{n} stemmen',
- 'collab.polls.vote': '{n} stem',
+ 'collab.polls.votes.one': '{n} stem',
'collab.polls.multipleChoice': 'Meerkeuze',
'collab.polls.multiChoice': 'Meerkeuze',
'collab.polls.deadline': 'Deadline',
diff --git a/shared/src/i18n/nl/dashboard.ts b/shared/src/i18n/nl/dashboard.ts
index ae798529e..7331d121d 100644
--- a/shared/src/i18n/nl/dashboard.ts
+++ b/shared/src/i18n/nl/dashboard.ts
@@ -34,6 +34,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Dagen',
'dashboard.places': 'Plaatsen',
'dashboard.members': 'Reisgenoten',
+ 'dashboard.card.buddies': 'Reisgenoten',
+ 'dashboard.card.buddies.one': 'Reisgenoot',
'dashboard.archive': 'Archiveren',
'dashboard.copyTrip': 'Kopiëren',
'dashboard.copySuffix': 'kopie',
@@ -146,8 +148,8 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': 'Over {count} dagen',
'dashboard.mobile.inMonths': 'Over {count} maanden',
'dashboard.mobile.spotlightDayOf': 'Dag {day} van {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} dag',
- 'dashboard.mobile.spotlightDaysMany': '{count} dagen',
+ 'dashboard.mobile.spotlightDays': '{count} dagen',
+ 'dashboard.mobile.spotlightDays.one': '{count} dag',
'dashboard.mobile.completed': 'Voltooid',
'dashboard.mobile.currencyConverter': 'Valutaomzetter',
'dashboard.filter.planned': 'Gepland',
@@ -158,14 +160,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'RECENT',
'dashboard.hero.tripDates': 'Reisdata',
'dashboard.hero.noDates': 'Geen data ingesteld',
- 'dashboard.hero.travelerOne': '{count} reiziger',
- 'dashboard.hero.travelerMany': '{count} reizigers',
- 'dashboard.hero.destinationOne': '{count} bestemming',
- 'dashboard.hero.destinationMany': '{count} bestemmingen',
- 'dashboard.hero.dayUnitOne': 'dag',
- 'dashboard.hero.dayUnitMany': 'dagen',
- 'dashboard.hero.dayLeft': 'Dag over',
+ 'dashboard.hero.travelers': '{count} reizigers',
+ 'dashboard.hero.travelers.one': '{count} reiziger',
+ 'dashboard.hero.destinations': '{count} bestemmingen',
+ 'dashboard.hero.destinations.one': '{count} bestemming',
+ 'dashboard.hero.dayUnit': 'dagen',
+ 'dashboard.hero.dayUnit.one': 'dag',
'dashboard.hero.daysLeft': 'Dagen over',
+ 'dashboard.hero.daysLeft.one': 'Dag over',
'dashboard.hero.lastDay': 'Laatste dag',
'dashboard.hero.untilStart': 'Tot start',
'dashboard.hero.startsIn': 'Reis begint over',
@@ -180,7 +182,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ {count}× rond de evenaar',
'dashboard.card.idea': 'Idee',
- 'dashboard.card.buddyOne': 'Reisgenoot',
'dashboard.fx.from': 'Van',
'dashboard.fx.to': 'Naar',
'dashboard.fx.unavailable': 'Koers niet beschikbaar',
diff --git a/shared/src/i18n/nl/files.ts b/shared/src/i18n/nl/files.ts
index 21c1f0720..4ef7b8891 100644
--- a/shared/src/i18n/nl/files.ts
+++ b/shared/src/i18n/nl/files.ts
@@ -14,7 +14,7 @@ const files: TranslationStrings = {
'files.openError': 'Bestand kon niet worden geopend',
'files.downloadPdf': 'PDF downloaden',
'files.count': '{count} bestanden',
- 'files.countSingular': '1 bestand',
+ 'files.count.one': '{count} bestand',
'files.uploaded': '{count} geüpload',
'files.uploadError': 'Uploaden mislukt',
'files.uploadErrorType': 'Dit bestandstype wordt niet ondersteund',
diff --git a/shared/src/i18n/nl/members.ts b/shared/src/i18n/nl/members.ts
index 06c867064..b2eaefdff 100644
--- a/shared/src/i18n/nl/members.ts
+++ b/shared/src/i18n/nl/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'Uitnodigen',
'members.allHaveAccess': 'Alle gebruikers hebben al toegang.',
'members.access': 'Toegang',
- 'members.person': 'persoon',
'members.persons': 'personen',
+ 'members.persons.one': 'persoon',
'members.you': 'jij',
'members.owner': 'Eigenaar',
'members.leaveTrip': 'Reis verlaten',
diff --git a/shared/src/i18n/nl/oauth.ts b/shared/src/i18n/nl/oauth.ts
index 0b3c4d5b4..63ca0ac6f 100644
--- a/shared/src/i18n/nl/oauth.ts
+++ b/shared/src/i18n/nl/oauth.ts
@@ -87,8 +87,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes.one': 'Approve ({count} scope)', // en-fallback
'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
'oauth.authorize.deny': 'Deny', // en-fallback
'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
diff --git a/shared/src/i18n/nl/places.ts b/shared/src/i18n/nl/places.ts
index 86849da59..2b989bd09 100644
--- a/shared/src/i18n/nl/places.ts
+++ b/shared/src/i18n/nl/places.ts
@@ -70,7 +70,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'categorieën',
'places.clearFilter': 'Filter wissen',
'places.count': '{count} plaatsen',
- 'places.countSingular': '1 plaats',
+ 'places.count.one': '{count} plaats',
'places.allPlanned': 'Alle plaatsen zijn gepland',
'places.noneFound': 'Geen plaatsen gevonden',
'places.editPlace': 'Plaats bewerken',
diff --git a/shared/src/i18n/nl/tours.ts b/shared/src/i18n/nl/tours.ts
index 5b7bf0ecf..496480565 100644
--- a/shared/src/i18n/nl/tours.ts
+++ b/shared/src/i18n/nl/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Hoogtegegevens of meetwaarden onvolledig: met voorbehoud geïmporteerd',
'tours.import.caution': 'Met voorbehoud geïmporteerd: hoogtegegevens of meetwaarden onvolledig',
'tours.import.success': '{count} tochten geïmporteerd uit GPX',
- 'tours.import.successOne': '1 tocht geïmporteerd uit GPX',
+ 'tours.import.success.one': '{count} tocht geïmporteerd uit GPX',
'tours.import.noTrack': 'Geen track of route gevonden in dit GPX-bestand',
'tours.import.error': 'GPX-bestand kon niet worden geïmporteerd',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/pl/atlas.ts b/shared/src/i18n/pl/atlas.ts
index 2f2aaebd6..d7fedfaf0 100644
--- a/shared/src/i18n/pl/atlas.ts
+++ b/shared/src/i18n/pl/atlas.ts
@@ -41,10 +41,10 @@ const atlas: TranslationStrings = {
'atlas.daysLeft': 'dni do wyjazdu',
'atlas.streak': 'Seria',
'atlas.years': 'lata',
- 'atlas.yearInRow': 'rok z rzędu',
'atlas.yearsInRow': 'lat z rzędu',
- 'atlas.tripIn': 'podróż w',
+ 'atlas.yearsInRow.one': 'rok z rzędu',
'atlas.tripsIn': 'podróży w',
+ 'atlas.tripsIn.one': 'podróż w',
'atlas.since': 'od',
'atlas.europe': 'Europa',
'atlas.asia': 'Azja',
@@ -55,10 +55,11 @@ const atlas: TranslationStrings = {
'atlas.other': 'Inne',
'atlas.firstVisit': 'Pierwsza podróż',
'atlas.lastVisitLabel': 'Ostatnia podróż',
- 'atlas.tripSingular': 'Podróż',
'atlas.tripPlural': 'Podróże',
- 'atlas.placeVisited': 'Odwiedzone miejsce',
+ 'atlas.tripsLabel': 'Podróże',
+ 'atlas.tripsLabel.one': 'Podróż',
'atlas.placesVisited': 'Odwiedzone miejsca',
+ 'atlas.placesVisited.one': 'Odwiedzone miejsce',
'atlas.searchCountry': 'Szukaj kraju...',
'atlas.searchPlaces': 'Miejsca',
'atlas.planned': 'Zaplanowane',
diff --git a/shared/src/i18n/pl/collab.ts b/shared/src/i18n/pl/collab.ts
index 3b3174913..c48da2d18 100644
--- a/shared/src/i18n/pl/collab.ts
+++ b/shared/src/i18n/pl/collab.ts
@@ -88,7 +88,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Zamknij',
'collab.polls.closed': 'Zamknięta',
'collab.polls.votes': '{n} głosów',
- 'collab.polls.vote': '{n} głos',
+ 'collab.polls.votes.one': '{n} głos',
'collab.polls.multipleChoice': 'Wielokrotny wybór',
'collab.polls.multiChoice': 'Wielokrotny wybór',
'collab.polls.deadline': 'Koniec',
diff --git a/shared/src/i18n/pl/dashboard.ts b/shared/src/i18n/pl/dashboard.ts
index ebeaac9a9..a274293ea 100644
--- a/shared/src/i18n/pl/dashboard.ts
+++ b/shared/src/i18n/pl/dashboard.ts
@@ -116,6 +116,8 @@ const dashboard: TranslationStrings = {
'dashboard.shrinkUnknown':
'Nie udało się sprawdzić dni tej podróży. Jeśli nowe daty obejmują mniej dni, zapisanie usunie ostatnie dni i wszystko, co na nie zaplanowano.',
'dashboard.members': 'Współpodróżnicy',
+ 'dashboard.card.buddies': 'Współpodróżnicy',
+ 'dashboard.card.buddies.one': 'Współpodróżnik',
'dashboard.copyTrip': 'Kopiuj',
'dashboard.copySuffix': 'kopia',
'dashboard.toast.copied': 'Podróż skopiowana!',
@@ -145,8 +147,8 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': 'Za {count} dni',
'dashboard.mobile.inMonths': 'Za {count} miesięcy',
'dashboard.mobile.spotlightDayOf': 'Dzień {day} z {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} dzień',
- 'dashboard.mobile.spotlightDaysMany': '{count} dni',
+ 'dashboard.mobile.spotlightDays': '{count} dni',
+ 'dashboard.mobile.spotlightDays.one': '{count} dzień',
'dashboard.mobile.completed': 'Zakończone',
'dashboard.mobile.currencyConverter': 'Przelicznik walut',
'dashboard.filter.planned': 'Zaplanowane',
@@ -157,14 +159,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'NIEDAWNO',
'dashboard.hero.tripDates': 'Daty podróży',
'dashboard.hero.noDates': 'Brak ustawionych dat',
- 'dashboard.hero.travelerOne': '{count} podróżny',
- 'dashboard.hero.travelerMany': '{count} podróżnych',
- 'dashboard.hero.destinationOne': '{count} cel',
- 'dashboard.hero.destinationMany': '{count} celów',
- 'dashboard.hero.dayUnitOne': 'dzień',
- 'dashboard.hero.dayUnitMany': 'dni',
- 'dashboard.hero.dayLeft': 'Pozostał dzień',
+ 'dashboard.hero.travelers': '{count} podróżnych',
+ 'dashboard.hero.travelers.one': '{count} podróżny',
+ 'dashboard.hero.destinations': '{count} celów',
+ 'dashboard.hero.destinations.one': '{count} cel',
+ 'dashboard.hero.dayUnit': 'dni',
+ 'dashboard.hero.dayUnit.one': 'dzień',
'dashboard.hero.daysLeft': 'Pozostało dni',
+ 'dashboard.hero.daysLeft.one': 'Pozostał dzień',
'dashboard.hero.lastDay': 'Ostatni dzień',
'dashboard.hero.untilStart': 'Do startu',
'dashboard.hero.startsIn': 'Start za',
@@ -179,7 +181,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ {count}× dookoła równika',
'dashboard.card.idea': 'Pomysł',
- 'dashboard.card.buddyOne': 'Współpodróżnik',
'dashboard.fx.from': 'Z',
'dashboard.fx.to': 'Na',
'dashboard.fx.unavailable': 'Kurs niedostępny',
diff --git a/shared/src/i18n/pl/files.ts b/shared/src/i18n/pl/files.ts
index 72bf38912..a82b34a83 100644
--- a/shared/src/i18n/pl/files.ts
+++ b/shared/src/i18n/pl/files.ts
@@ -14,7 +14,7 @@ const files: TranslationStrings = {
'files.openError': 'Nie można otworzyć pliku',
'files.downloadPdf': 'Pobierz PDF',
'files.count': '{count} plików',
- 'files.countSingular': '1 plik',
+ 'files.count.one': '{count} plik',
'files.uploaded': '{count} przesłanych',
'files.uploadError': 'Przesyłanie nie powiodło się',
'files.uploadErrorType': 'Ten typ pliku nie jest obsługiwany',
diff --git a/shared/src/i18n/pl/members.ts b/shared/src/i18n/pl/members.ts
index e54e4081b..a8a1dc800 100644
--- a/shared/src/i18n/pl/members.ts
+++ b/shared/src/i18n/pl/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'Zaproś',
'members.allHaveAccess': 'Wszyscy użytkownicy mają już dostęp.',
'members.access': 'Dostęp',
- 'members.person': 'osoba',
'members.persons': 'osoby',
+ 'members.persons.one': 'osoba',
'members.you': 'ty',
'members.owner': 'Właściciel',
'members.leaveTrip': 'Opuść podróż',
diff --git a/shared/src/i18n/pl/oauth.ts b/shared/src/i18n/pl/oauth.ts
index 1b5430b7a..294c49c28 100644
--- a/shared/src/i18n/pl/oauth.ts
+++ b/shared/src/i18n/pl/oauth.ts
@@ -86,8 +86,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes.one': 'Approve ({count} scope)', // en-fallback
'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
'oauth.authorize.deny': 'Deny', // en-fallback
'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
diff --git a/shared/src/i18n/pl/places.ts b/shared/src/i18n/pl/places.ts
index 2b329514f..b5c8f6fc8 100644
--- a/shared/src/i18n/pl/places.ts
+++ b/shared/src/i18n/pl/places.ts
@@ -60,7 +60,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'kategorii',
'places.clearFilter': 'Wyczyść filtr',
'places.count': '{count} miejsc',
- 'places.countSingular': '1 miejsce',
+ 'places.count.one': '{count} miejsce',
'places.allPlanned': 'Wszystkie miejsca są zaplanowane',
'places.noneFound': 'Nie znaleziono miejsc',
'places.editPlace': 'Edytuj miejsce',
diff --git a/shared/src/i18n/pl/tours.ts b/shared/src/i18n/pl/tours.ts
index 6c7b07200..6f1fdbbd4 100644
--- a/shared/src/i18n/pl/tours.ts
+++ b/shared/src/i18n/pl/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Niepełne dane wysokościowe lub parametry – import z zastrzeżeniami',
'tours.import.caution': 'Import z zastrzeżeniami – niepełne dane wysokościowe lub parametry',
'tours.import.success': 'Liczba wycieczek zaimportowanych z GPX: {count}',
- 'tours.import.successOne': 'Zaimportowano 1 wycieczkę z GPX',
+ 'tours.import.success.one': 'Zaimportowano {count} wycieczkę z GPX',
'tours.import.noTrack': 'W tym pliku GPX nie znaleziono śladu ani trasy',
'tours.import.error': 'Nie udało się zaimportować pliku GPX',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/plural.ts b/shared/src/i18n/plural.ts
new file mode 100644
index 000000000..23cecb8bd
--- /dev/null
+++ b/shared/src/i18n/plural.ts
@@ -0,0 +1,65 @@
+/**
+ * Plural forms for count-bearing strings.
+ *
+ * A string that depends on a number keeps its key as the general form and
+ * adds one key per CLDR category the language needs: `places.count` reads
+ * "{count} places", `places.count.one` reads "{count} place". Russian adds
+ * `.few` and `.many`, Arabic `.zero`, `.two`, `.few` and `.many`, Japanese
+ * none at all. Which category a number takes is the language's own rule, read
+ * from `Intl.PluralRules`, never a `count === 1` in the caller: 21 is "one" in
+ * Russian and 0 is "one" in French.
+ */
+
+export const PLURAL_CATEGORIES = ['zero', 'one', 'two', 'few', 'many', 'other'] as const;
+export type PluralCategory = (typeof PLURAL_CATEGORIES)[number];
+
+const rules = new Map();
+
+function rulesFor(intlLocale: string): Intl.PluralRules {
+ let r = rules.get(intlLocale);
+ if (!r) {
+ r = new Intl.PluralRules(intlLocale);
+ rules.set(intlLocale, r);
+ }
+ return r;
+}
+
+/** The CLDR category `count` takes in `intlLocale` (a BCP 47 tag, see `getIntlLanguage`). */
+export function pluralCategory(count: number, intlLocale: string): PluralCategory {
+ return rulesFor(intlLocale).select(count) as PluralCategory;
+}
+
+/**
+ * The key a count-bearing string resolves to in `strings`: the form for the
+ * count's category, else the `.other` form, else the key itself. Undefined
+ * when `strings` has none of them, so the caller can fall back to another
+ * locale with that locale's own rules.
+ */
+export function resolvePluralKey(
+ strings: Readonly>,
+ key: string,
+ count: number,
+ intlLocale: string,
+): string | undefined {
+ for (const candidate of [`${key}.${pluralCategory(count, intlLocale)}`, `${key}.other`, key]) {
+ if (typeof strings[candidate] === 'string') return candidate;
+ }
+ return undefined;
+}
+
+/**
+ * The categories whole counts from 0 to 200 reach: the forms a translation
+ * has to spell out. Categories only fractions or millions reach (French
+ * `many`, Russian `other`) fall back to the general form.
+ */
+export function integerPluralCategories(intlLocale: string): PluralCategory[] {
+ const r = rulesFor(intlLocale);
+ const seen = new Set();
+ for (let n = 0; n <= 200; n++) seen.add(r.select(n) as PluralCategory);
+ return PLURAL_CATEGORIES.filter((c) => seen.has(c));
+}
+
+/** Every category the language has at all, for refusing a form it can never select. */
+export function allPluralCategories(intlLocale: string): PluralCategory[] {
+ return rulesFor(intlLocale).resolvedOptions().pluralCategories as PluralCategory[];
+}
diff --git a/shared/src/i18n/ru/atlas.ts b/shared/src/i18n/ru/atlas.ts
index 76530ee9a..4298f809a 100644
--- a/shared/src/i18n/ru/atlas.ts
+++ b/shared/src/i18n/ru/atlas.ts
@@ -19,10 +19,10 @@ const atlas: TranslationStrings = {
'atlas.streak': 'Серия',
'atlas.year': 'год',
'atlas.years': 'лет',
- 'atlas.yearInRow': 'год подряд',
'atlas.yearsInRow': 'лет подряд',
- 'atlas.tripIn': 'поездка в',
+ 'atlas.yearsInRow.one': 'год подряд',
'atlas.tripsIn': 'поездок в',
+ 'atlas.tripsIn.one': 'поездка в',
'atlas.since': 'с',
'atlas.europe': 'Европа',
'atlas.asia': 'Азия',
@@ -33,10 +33,11 @@ const atlas: TranslationStrings = {
'atlas.other': 'Другое',
'atlas.firstVisit': 'Первая поездка',
'atlas.lastVisitLabel': 'Последняя поездка',
- 'atlas.tripSingular': 'Поездка',
'atlas.tripPlural': 'Поездки',
- 'atlas.placeVisited': 'Посещённое место',
+ 'atlas.tripsLabel': 'Поездки',
+ 'atlas.tripsLabel.one': 'Поездка',
'atlas.placesVisited': 'Посещённые места',
+ 'atlas.placesVisited.one': 'Посещённое место',
'atlas.statsTab': 'Статистика',
'atlas.bucketTab': 'Список желаний',
'atlas.addBucket': 'Добавить в список желаний',
diff --git a/shared/src/i18n/ru/collab.ts b/shared/src/i18n/ru/collab.ts
index 4eb5564e4..b93961540 100644
--- a/shared/src/i18n/ru/collab.ts
+++ b/shared/src/i18n/ru/collab.ts
@@ -89,7 +89,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Закрыть',
'collab.polls.closed': 'Закрыт',
'collab.polls.votes': '{n} голосов',
- 'collab.polls.vote': '{n} голос',
+ 'collab.polls.votes.one': '{n} голос',
'collab.polls.multipleChoice': 'Множественный выбор',
'collab.polls.multiChoice': 'Множественный выбор',
'collab.polls.deadline': 'Срок',
diff --git a/shared/src/i18n/ru/dashboard.ts b/shared/src/i18n/ru/dashboard.ts
index d736ce133..f6e380edc 100644
--- a/shared/src/i18n/ru/dashboard.ts
+++ b/shared/src/i18n/ru/dashboard.ts
@@ -34,6 +34,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Дни',
'dashboard.places': 'Места',
'dashboard.members': 'Попутчики',
+ 'dashboard.card.buddies': 'Попутчики',
+ 'dashboard.card.buddies.one': 'Попутчик',
'dashboard.archive': 'Архивировать',
'dashboard.copyTrip': 'Копировать',
'dashboard.copySuffix': 'копия',
@@ -143,8 +145,8 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': 'Через {count} дн.',
'dashboard.mobile.inMonths': 'Через {count} мес.',
'dashboard.mobile.spotlightDayOf': 'День {day} из {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} день',
- 'dashboard.mobile.spotlightDaysMany': '{count} дней',
+ 'dashboard.mobile.spotlightDays': '{count} дней',
+ 'dashboard.mobile.spotlightDays.one': '{count} день',
'dashboard.mobile.completed': 'Завершено',
'dashboard.mobile.currencyConverter': 'Конвертер валют',
'dashboard.filter.planned': 'Запланированные',
@@ -155,14 +157,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'НЕДАВНО',
'dashboard.hero.tripDates': 'Даты поездки',
'dashboard.hero.noDates': 'Даты не заданы',
- 'dashboard.hero.travelerOne': '{count} путешественник',
- 'dashboard.hero.travelerMany': '{count} путешественников',
- 'dashboard.hero.destinationOne': '{count} направление',
- 'dashboard.hero.destinationMany': '{count} направлений',
- 'dashboard.hero.dayUnitOne': 'день',
- 'dashboard.hero.dayUnitMany': 'дн.',
- 'dashboard.hero.dayLeft': 'День остался',
+ 'dashboard.hero.travelers': '{count} путешественников',
+ 'dashboard.hero.travelers.one': '{count} путешественник',
+ 'dashboard.hero.destinations': '{count} направлений',
+ 'dashboard.hero.destinations.one': '{count} направление',
+ 'dashboard.hero.dayUnit': 'дн.',
+ 'dashboard.hero.dayUnit.one': 'день',
'dashboard.hero.daysLeft': 'Дней осталось',
+ 'dashboard.hero.daysLeft.one': 'День остался',
'dashboard.hero.lastDay': 'Последний день',
'dashboard.hero.untilStart': 'До начала',
'dashboard.hero.startsIn': 'Старт через',
@@ -177,7 +179,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ {count}× вокруг экватора',
'dashboard.card.idea': 'Идея',
- 'dashboard.card.buddyOne': 'Попутчик',
'dashboard.fx.from': 'Из',
'dashboard.fx.to': 'В',
'dashboard.fx.unavailable': 'Курс недоступен',
diff --git a/shared/src/i18n/ru/files.ts b/shared/src/i18n/ru/files.ts
index 324e6e21a..97a20876f 100644
--- a/shared/src/i18n/ru/files.ts
+++ b/shared/src/i18n/ru/files.ts
@@ -14,7 +14,7 @@ const files: TranslationStrings = {
'files.openError': 'Не удалось открыть файл',
'files.downloadPdf': 'Скачать PDF',
'files.count': '{count} файлов',
- 'files.countSingular': '1 файл',
+ 'files.count.one': '{count} файл',
'files.uploaded': '{count} загружено',
'files.uploadError': 'Ошибка загрузки',
'files.uploadErrorType': 'Этот тип файла не поддерживается',
diff --git a/shared/src/i18n/ru/members.ts b/shared/src/i18n/ru/members.ts
index fdcc8de8d..e14c6871b 100644
--- a/shared/src/i18n/ru/members.ts
+++ b/shared/src/i18n/ru/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'Пригласить',
'members.allHaveAccess': 'У всех пользователей уже есть доступ.',
'members.access': 'Доступ',
- 'members.person': 'человек',
'members.persons': 'человек',
+ 'members.persons.one': 'человек',
'members.you': 'вы',
'members.owner': 'Владелец',
'members.leaveTrip': 'Покинуть поездку',
diff --git a/shared/src/i18n/ru/oauth.ts b/shared/src/i18n/ru/oauth.ts
index 8826b1af9..426af8ef8 100644
--- a/shared/src/i18n/ru/oauth.ts
+++ b/shared/src/i18n/ru/oauth.ts
@@ -86,8 +86,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes.one': 'Approve ({count} scope)', // en-fallback
'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
'oauth.authorize.deny': 'Deny', // en-fallback
'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
diff --git a/shared/src/i18n/ru/places.ts b/shared/src/i18n/ru/places.ts
index b96d20cd2..5265a5afa 100644
--- a/shared/src/i18n/ru/places.ts
+++ b/shared/src/i18n/ru/places.ts
@@ -70,7 +70,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'категорий',
'places.clearFilter': 'Сбросить фильтр',
'places.count': '{count} мест',
- 'places.countSingular': '1 место',
+ 'places.count.one': '{count} место',
'places.allPlanned': 'Все места запланированы',
'places.noneFound': 'Места не найдены',
'places.editPlace': 'Редактировать место',
diff --git a/shared/src/i18n/ru/tours.ts b/shared/src/i18n/ru/tours.ts
index b69c767ab..65001326d 100644
--- a/shared/src/i18n/ru/tours.ts
+++ b/shared/src/i18n/ru/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Данные о высоте или показателях неполные — импорт с оговорками',
'tours.import.caution': 'Импорт с оговорками — данные о высоте или показателях неполные',
'tours.import.success': 'Импортировано туров из GPX: {count}',
- 'tours.import.successOne': 'Импортирован 1 тур из GPX',
+ 'tours.import.success.one': 'Импортирован {count} тур из GPX',
'tours.import.noTrack': 'В этом файле GPX не найден трек или маршрут',
'tours.import.error': 'Не удалось импортировать файл GPX',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/sk/atlas.ts b/shared/src/i18n/sk/atlas.ts
index cc8329dde..3d7c1c5d2 100644
--- a/shared/src/i18n/sk/atlas.ts
+++ b/shared/src/i18n/sk/atlas.ts
@@ -43,10 +43,10 @@ const atlas: TranslationStrings = {
'atlas.daysLeft': 'dní zostáva',
'atlas.streak': 'Séria',
'atlas.years': 'rokov',
- 'atlas.yearInRow': 'rok po sebe',
'atlas.yearsInRow': 'rokov po sebe',
- 'atlas.tripIn': 'cesta v roku',
+ 'atlas.yearsInRow.one': 'rok po sebe',
'atlas.tripsIn': 'ciest v roku',
+ 'atlas.tripsIn.one': 'cesta v roku',
'atlas.since': 'od',
'atlas.europe': 'Európa',
'atlas.asia': 'Ázia',
@@ -57,10 +57,11 @@ const atlas: TranslationStrings = {
'atlas.other': 'Ostatné',
'atlas.firstVisit': 'Prvá cesta',
'atlas.lastVisitLabel': 'Posledná cesta',
- 'atlas.tripSingular': 'Cesta',
'atlas.tripPlural': 'Cesty',
- 'atlas.placeVisited': 'Navštívené miesto',
+ 'atlas.tripsLabel': 'Cesty',
+ 'atlas.tripsLabel.one': 'Cesta',
'atlas.placesVisited': 'Navštívené miesta',
+ 'atlas.placesVisited.one': 'Navštívené miesto',
'atlas.planned': 'Plánované',
'atlas.showPlanned': 'Zobraziť plánované krajiny',
'atlas.plannedFor': 'Plánované na',
diff --git a/shared/src/i18n/sk/collab.ts b/shared/src/i18n/sk/collab.ts
index 2df0e25f6..509a8091f 100644
--- a/shared/src/i18n/sk/collab.ts
+++ b/shared/src/i18n/sk/collab.ts
@@ -89,7 +89,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Uzavrieť',
'collab.polls.closed': 'Uzavreté',
'collab.polls.votes': '{n} hlasov',
- 'collab.polls.vote': '{n} hlas',
+ 'collab.polls.votes.one': '{n} hlas',
'collab.polls.multipleChoice': 'Viac možností',
'collab.polls.multiChoice': 'Viac možností',
'collab.polls.deadline': 'Termín',
diff --git a/shared/src/i18n/sk/dashboard.ts b/shared/src/i18n/sk/dashboard.ts
index dd7b0b8e2..d1810ba43 100644
--- a/shared/src/i18n/sk/dashboard.ts
+++ b/shared/src/i18n/sk/dashboard.ts
@@ -34,6 +34,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Dní',
'dashboard.places': 'Miest',
'dashboard.members': 'Cestovní parťáci',
+ 'dashboard.card.buddies': 'Cestovní parťáci',
+ 'dashboard.card.buddies.one': 'Parťák',
'dashboard.archive': 'Archivovať',
'dashboard.copyTrip': 'Kopírovať',
'dashboard.copySuffix': 'kópia',
@@ -157,8 +159,8 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': 'O {count} dní',
'dashboard.mobile.inMonths': 'O {count} mesiacov',
'dashboard.mobile.spotlightDayOf': 'Deň {day} z {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} deň',
- 'dashboard.mobile.spotlightDaysMany': '{count} dní',
+ 'dashboard.mobile.spotlightDays': '{count} dní',
+ 'dashboard.mobile.spotlightDays.one': '{count} deň',
'dashboard.mobile.completed': 'Dokončené',
'dashboard.mobile.currencyConverter': 'Prevodník mien',
'dashboard.filter.planned': 'Plánované',
@@ -169,14 +171,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'NEDÁVNO',
'dashboard.hero.tripDates': 'Termíny cesty',
'dashboard.hero.noDates': 'Bez termínu',
- 'dashboard.hero.travelerOne': '{count} cestovateľ',
- 'dashboard.hero.travelerMany': '{count} cestovateľov',
- 'dashboard.hero.destinationOne': '{count} cieľ',
- 'dashboard.hero.destinationMany': '{count} cieľov',
- 'dashboard.hero.dayUnitOne': 'deň',
- 'dashboard.hero.dayUnitMany': 'dní',
- 'dashboard.hero.dayLeft': 'Zostáva deň',
+ 'dashboard.hero.travelers': '{count} cestovateľov',
+ 'dashboard.hero.travelers.one': '{count} cestovateľ',
+ 'dashboard.hero.destinations': '{count} cieľov',
+ 'dashboard.hero.destinations.one': '{count} cieľ',
+ 'dashboard.hero.dayUnit': 'dní',
+ 'dashboard.hero.dayUnit.one': 'deň',
'dashboard.hero.daysLeft': 'Zostáva dní',
+ 'dashboard.hero.daysLeft.one': 'Zostáva deň',
'dashboard.hero.lastDay': 'Posledný deň',
'dashboard.hero.untilStart': 'Do začiatku',
'dashboard.hero.startsIn': 'Začína o',
@@ -191,7 +193,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ {count}× okolo rovníka',
'dashboard.card.idea': 'Nápad',
- 'dashboard.card.buddyOne': 'Parťák',
'dashboard.fx.from': 'Z',
'dashboard.fx.to': 'Na',
'dashboard.fx.unavailable': 'Kurz nie je dostupný',
diff --git a/shared/src/i18n/sk/files.ts b/shared/src/i18n/sk/files.ts
index 063e4ae0e..f937783e6 100644
--- a/shared/src/i18n/sk/files.ts
+++ b/shared/src/i18n/sk/files.ts
@@ -14,7 +14,7 @@ const files: TranslationStrings = {
'files.openError': 'Súbor sa nedá otvoriť',
'files.downloadPdf': 'Stiahnuť PDF',
'files.count': '{count} súborov',
- 'files.countSingular': '1 súbor',
+ 'files.count.one': '{count} súbor',
'files.uploaded': '{count} nahraných',
'files.uploadError': 'Nahrávanie zlyhalo',
'files.uploadErrorType': 'Tento typ súboru nie je podporovaný',
diff --git a/shared/src/i18n/sk/members.ts b/shared/src/i18n/sk/members.ts
index fd0f2f4fd..ce72c49ae 100644
--- a/shared/src/i18n/sk/members.ts
+++ b/shared/src/i18n/sk/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'Pozvať',
'members.allHaveAccess': 'Všetci používatelia už majú prístup.',
'members.access': 'Prístup',
- 'members.person': 'osoba',
'members.persons': 'osôb',
+ 'members.persons.one': 'osoba',
'members.you': 'vy',
'members.owner': 'Vlastník',
'members.leaveTrip': 'Opustiť cestu',
diff --git a/shared/src/i18n/sk/oauth.ts b/shared/src/i18n/sk/oauth.ts
index ec6987931..afb303f44 100644
--- a/shared/src/i18n/sk/oauth.ts
+++ b/shared/src/i18n/sk/oauth.ts
@@ -86,8 +86,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.trustNote':
'Prístup udeľujte len aplikáciám, ktorým dôverujete. Vaše dáta zostávajú na vašom serveri.',
'oauth.authorize.selectScope': 'Vyberte aspoň jedno oprávnenie',
- 'oauth.authorize.approveOneScope': 'Schváliť ({count} oprávnenie)',
- 'oauth.authorize.approveManyScopes': 'Schváliť ({count} oprávnení)',
+ 'oauth.authorize.approveScopes': 'Schváliť ({count} oprávnení)',
+ 'oauth.authorize.approveScopes.one': 'Schváliť ({count} oprávnenie)',
'oauth.authorize.approveAccess': 'Schváliť prístup',
'oauth.authorize.deny': 'Zamietnuť',
'oauth.authorize.choosePermissions': 'Vyberte, ktoré oprávnenia udeliť',
diff --git a/shared/src/i18n/sk/places.ts b/shared/src/i18n/sk/places.ts
index 08418207a..60ef6a5a6 100644
--- a/shared/src/i18n/sk/places.ts
+++ b/shared/src/i18n/sk/places.ts
@@ -71,7 +71,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'kategórií',
'places.clearFilter': 'Vymazať filter',
'places.count': '{count} miest',
- 'places.countSingular': '1 miesto',
+ 'places.count.one': '{count} miesto',
'places.allPlanned': 'Všetky miesta sú naplánované',
'places.noneFound': 'Žiadne miesta sa nenašli',
'places.editPlace': 'Upraviť miesto',
diff --git a/shared/src/i18n/sk/tours.ts b/shared/src/i18n/sk/tours.ts
index 57b0a75de..511dae0cb 100644
--- a/shared/src/i18n/sk/tours.ts
+++ b/shared/src/i18n/sk/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Neúplné výškové údaje alebo metriky – importované s upozornením',
'tours.import.caution': 'Importované s upozornením – neúplné výškové údaje alebo metriky',
'tours.import.success': 'Počet túr importovaných z GPX: {count}',
- 'tours.import.successOne': '1 túra importovaná z GPX',
+ 'tours.import.success.one': '{count} túra importovaná z GPX',
'tours.import.noTrack': 'V tomto súbore GPX sa nenašla žiadna stopa ani trasa',
'tours.import.error': 'Súbor GPX sa nepodarilo importovať',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/sv/atlas.ts b/shared/src/i18n/sv/atlas.ts
index ff89feeb8..c685e6913 100644
--- a/shared/src/i18n/sv/atlas.ts
+++ b/shared/src/i18n/sv/atlas.ts
@@ -43,10 +43,10 @@ const atlas: TranslationStrings = {
'atlas.daysLeft': 'dagar kvar',
'atlas.streak': 'Streak',
'atlas.years': 'år',
- 'atlas.yearInRow': 'år i rad',
'atlas.yearsInRow': 'år i rad',
- 'atlas.tripIn': 'resa inom',
+ 'atlas.yearsInRow.one': 'år i rad',
'atlas.tripsIn': 'resor inom',
+ 'atlas.tripsIn.one': 'resa inom',
'atlas.since': 'sedan',
'atlas.europe': 'Europa',
'atlas.asia': 'Asien',
@@ -57,10 +57,11 @@ const atlas: TranslationStrings = {
'atlas.other': 'Andra',
'atlas.firstVisit': 'Första resan',
'atlas.lastVisitLabel': 'Den senaste resan',
- 'atlas.tripSingular': 'Resa',
'atlas.tripPlural': 'Resor',
- 'atlas.placeVisited': 'Plats besökt',
+ 'atlas.tripsLabel': 'Resor',
+ 'atlas.tripsLabel.one': 'Resa',
'atlas.placesVisited': 'Platser besökta',
+ 'atlas.placesVisited.one': 'Plats besökt',
'atlas.planned': 'Planerat',
'atlas.showPlanned': 'Visa planerade länder',
'atlas.plannedFor': 'Planerat till',
diff --git a/shared/src/i18n/sv/collab.ts b/shared/src/i18n/sv/collab.ts
index 73a5b6885..7664fd15c 100644
--- a/shared/src/i18n/sv/collab.ts
+++ b/shared/src/i18n/sv/collab.ts
@@ -89,7 +89,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Stäng',
'collab.polls.closed': 'Stängd',
'collab.polls.votes': '{n} röster',
- 'collab.polls.vote': '{n} rösta',
+ 'collab.polls.votes.one': '{n} rösta',
'collab.polls.multipleChoice': 'Flera val',
'collab.polls.multiChoice': 'Flera val',
'collab.polls.deadline': 'Tidsfrist',
diff --git a/shared/src/i18n/sv/dashboard.ts b/shared/src/i18n/sv/dashboard.ts
index 1f5f9fba5..07faa1dae 100644
--- a/shared/src/i18n/sv/dashboard.ts
+++ b/shared/src/i18n/sv/dashboard.ts
@@ -34,6 +34,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Dagar',
'dashboard.places': 'Platser',
'dashboard.members': 'Vänner',
+ 'dashboard.card.buddies': 'Vänner',
+ 'dashboard.card.buddies.one': 'Kompis',
'dashboard.archive': 'Arkiv',
'dashboard.copyTrip': 'Kopiera',
'dashboard.copySuffix': 'kopiera',
@@ -157,8 +159,8 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': 'Inom {count} dagar',
'dashboard.mobile.inMonths': 'Inom {count} månader',
'dashboard.mobile.spotlightDayOf': 'Dag {day} av {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} dag',
- 'dashboard.mobile.spotlightDaysMany': '{count} dagar',
+ 'dashboard.mobile.spotlightDays': '{count} dagar',
+ 'dashboard.mobile.spotlightDays.one': '{count} dag',
'dashboard.mobile.completed': 'Slutförd',
'dashboard.mobile.currencyConverter': 'Valutaomvandlare',
'dashboard.filter.planned': 'Planerad',
@@ -169,14 +171,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'SENASTE',
'dashboard.hero.tripDates': 'Resedatum',
'dashboard.hero.noDates': 'Tillgängliga datum',
- 'dashboard.hero.travelerOne': '{count} resenär',
- 'dashboard.hero.travelerMany': '{count} resenärer',
- 'dashboard.hero.destinationOne': '{count} destionation',
- 'dashboard.hero.destinationMany': '{count} destinationer',
- 'dashboard.hero.dayUnitOne': 'dag',
- 'dashboard.hero.dayUnitMany': 'dagar',
- 'dashboard.hero.dayLeft': 'Dag kvar',
+ 'dashboard.hero.travelers': '{count} resenärer',
+ 'dashboard.hero.travelers.one': '{count} resenär',
+ 'dashboard.hero.destinations': '{count} destinationer',
+ 'dashboard.hero.destinations.one': '{count} destionation',
+ 'dashboard.hero.dayUnit': 'dagar',
+ 'dashboard.hero.dayUnit.one': 'dag',
'dashboard.hero.daysLeft': 'Dagar kvar',
+ 'dashboard.hero.daysLeft.one': 'Dag kvar',
'dashboard.hero.lastDay': 'Sista dagen',
'dashboard.hero.untilStart': 'Fram till start',
'dashboard.hero.startsIn': 'Resan börjar i',
@@ -191,7 +193,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ {count}× runt ekvatorn',
'dashboard.card.idea': 'Idé',
- 'dashboard.card.buddyOne': 'Kompis',
'dashboard.fx.from': 'Från',
'dashboard.fx.to': 'Till',
'dashboard.fx.unavailable': 'Pris ej tillgängligt',
diff --git a/shared/src/i18n/sv/files.ts b/shared/src/i18n/sv/files.ts
index cc00d5f12..22c3b3434 100644
--- a/shared/src/i18n/sv/files.ts
+++ b/shared/src/i18n/sv/files.ts
@@ -14,7 +14,7 @@ const files: TranslationStrings = {
'files.openError': 'Kunde inte öppna fil',
'files.downloadPdf': 'Ladda ner PDF',
'files.count': '{count} filer',
- 'files.countSingular': '1 fil',
+ 'files.count.one': '{count} fil',
'files.uploaded': '{count} uppladdade',
'files.uploadError': 'Uppladdning misslyckades',
'files.uploadErrorType': 'Den här filtypen stöds inte',
diff --git a/shared/src/i18n/sv/members.ts b/shared/src/i18n/sv/members.ts
index 1e0edfdf3..55215a4af 100644
--- a/shared/src/i18n/sv/members.ts
+++ b/shared/src/i18n/sv/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'Bjud in',
'members.allHaveAccess': 'Alla användare har redan åtkomst.',
'members.access': 'Åtkomst',
- 'members.person': 'person',
'members.persons': 'personer',
+ 'members.persons.one': 'person',
'members.you': 'du',
'members.owner': 'Ägare',
'members.leaveTrip': 'Lämna resan',
diff --git a/shared/src/i18n/sv/oauth.ts b/shared/src/i18n/sv/oauth.ts
index 7166ee9e9..a90c8da8a 100644
--- a/shared/src/i18n/sv/oauth.ts
+++ b/shared/src/i18n/sv/oauth.ts
@@ -90,8 +90,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'Den här applikationen begär åtkomst till ditt TREK konto.',
'oauth.authorize.trustNote': 'Ge endast åtkomst till applikationer som du litar på. Dina data förblir på din server.',
'oauth.authorize.selectScope': 'Välj minst ett tillämpningsområde',
- 'oauth.authorize.approveOneScope': 'Godkänn ({count} tillämpningsområde)',
- 'oauth.authorize.approveManyScopes': 'Godkänn ({count} tillämpningsområden)',
+ 'oauth.authorize.approveScopes': 'Godkänn ({count} tillämpningsområden)',
+ 'oauth.authorize.approveScopes.one': 'Godkänn ({count} tillämpningsområde)',
'oauth.authorize.approveAccess': 'Godkänn åtkomst',
'oauth.authorize.deny': 'Neka',
'oauth.authorize.choosePermissions': 'Välj vilka behörigheter du vill bevilja',
diff --git a/shared/src/i18n/sv/places.ts b/shared/src/i18n/sv/places.ts
index 748cd9b8c..7f076c74a 100644
--- a/shared/src/i18n/sv/places.ts
+++ b/shared/src/i18n/sv/places.ts
@@ -70,7 +70,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'kategorier',
'places.clearFilter': 'Rensa filter',
'places.count': '{count} platser',
- 'places.countSingular': '1 plats',
+ 'places.count.one': '{count} plats',
'places.allPlanned': 'Alla platser är planerade',
'places.noneFound': 'Inga platser hittades',
'places.editPlace': 'Redigera plats',
diff --git a/shared/src/i18n/sv/tours.ts b/shared/src/i18n/sv/tours.ts
index eae02216e..d8ab6fc84 100644
--- a/shared/src/i18n/sv/tours.ts
+++ b/shared/src/i18n/sv/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Ofullständiga höjddata eller mätvärden: importerad med förbehåll',
'tours.import.caution': 'Importerad med förbehåll: ofullständiga höjddata eller mätvärden',
'tours.import.success': '{count} turer importerade från GPX',
- 'tours.import.successOne': '1 tur importerad från GPX',
+ 'tours.import.success.one': '{count} tur importerad från GPX',
'tours.import.noTrack': 'Varken något spår eller någon rutt hittades i GPX-filen',
'tours.import.error': 'Det gick inte att importera GPX-filen',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/th/atlas.ts b/shared/src/i18n/th/atlas.ts
index b545c29ba..72c962d42 100644
--- a/shared/src/i18n/th/atlas.ts
+++ b/shared/src/i18n/th/atlas.ts
@@ -43,9 +43,7 @@ const atlas: TranslationStrings = {
'atlas.daysLeft': "วันคงเหลือ",
'atlas.streak': "ต่อเนื่อง",
'atlas.years': "ปี",
- 'atlas.yearInRow': "ปีติดต่อกัน",
'atlas.yearsInRow': "ปีติดต่อกัน",
- 'atlas.tripIn': "ทริปใน",
'atlas.tripsIn': "ทริปใน",
'atlas.since': "ตั้งแต่",
'atlas.europe': "ยุโรป",
@@ -56,9 +54,8 @@ const atlas: TranslationStrings = {
'atlas.oceania': "โอเชียเนีย",
'atlas.other': "อื่นๆ",
'atlas.firstVisit': "การเดินทางครั้งแรก",
- 'atlas.tripSingular': "การเดินทาง",
'atlas.tripPlural': "ทริป",
- 'atlas.placeVisited': "สถานที่ที่เยี่ยมชม",
+ 'atlas.tripsLabel': "ทริป",
'atlas.placesVisited': "สถานที่เยี่ยมชม",
'atlas.planned': "วางแผนแล้ว",
'atlas.showPlanned': "แสดงประเทศที่วางแผนไว้",
diff --git a/shared/src/i18n/th/collab.ts b/shared/src/i18n/th/collab.ts
index 5141c010c..83586e165 100644
--- a/shared/src/i18n/th/collab.ts
+++ b/shared/src/i18n/th/collab.ts
@@ -74,7 +74,6 @@ const collab: TranslationStrings = {
'collab.polls.close': "ปิด",
'collab.polls.closed': "ปิดแล้ว",
'collab.polls.votes': "{n} โหวต",
- 'collab.polls.vote': "{n} โหวต",
'collab.polls.multipleChoice': "หลายทางเลือก",
'collab.polls.multiChoice': "หลายทางเลือก",
'collab.polls.deadline': "กำหนดเวลา",
diff --git a/shared/src/i18n/th/dashboard.ts b/shared/src/i18n/th/dashboard.ts
index 92460f5bc..765e1b986 100644
--- a/shared/src/i18n/th/dashboard.ts
+++ b/shared/src/i18n/th/dashboard.ts
@@ -34,6 +34,7 @@ const dashboard: TranslationStrings = {
'dashboard.days': "วัน",
'dashboard.places': "สถานที่",
'dashboard.members': "เพื่อน",
+ 'dashboard.card.buddies': "เพื่อน",
'dashboard.archive': "เก็บถาวร",
'dashboard.copyTrip': "คัดลอก",
'dashboard.copySuffix': "คัดลอก",
@@ -154,8 +155,7 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': "ในอีก {count} วัน",
'dashboard.mobile.inMonths': "ในอีก {count} เดือน",
'dashboard.mobile.spotlightDayOf': "วันที่ {day} จาก {total}",
- 'dashboard.mobile.spotlightDayOne': "{count} วัน",
- 'dashboard.mobile.spotlightDaysMany': "{count} วัน",
+ 'dashboard.mobile.spotlightDays': "{count} วัน",
'dashboard.mobile.completed': "เสร็จสิ้น",
'dashboard.mobile.currencyConverter': "แปลงสกุลเงิน",
'dashboard.filter.planned': "วางแผนแล้ว",
@@ -166,13 +166,9 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': "ล่าสุด",
'dashboard.hero.tripDates': "วันเดินทาง",
'dashboard.hero.noDates': "ยังไม่กำหนดวัน",
- 'dashboard.hero.travelerOne': "{count} นักเดินทาง",
- 'dashboard.hero.travelerMany': "นักเดินทาง {count}",
- 'dashboard.hero.destinationOne': "{count} ปลายทาง",
- 'dashboard.hero.destinationMany': "{count} จุดหมายปลายทาง",
- 'dashboard.hero.dayUnitOne': "วัน",
- 'dashboard.hero.dayUnitMany': "วัน",
- 'dashboard.hero.dayLeft': "วันคงเหลือ",
+ 'dashboard.hero.travelers': "นักเดินทาง {count}",
+ 'dashboard.hero.destinations': "{count} จุดหมายปลายทาง",
+ 'dashboard.hero.dayUnit': "วัน",
'dashboard.hero.daysLeft': "วันคงเหลือ",
'dashboard.hero.lastDay': "วันสุดท้าย",
'dashboard.hero.untilStart': "จนกระทั่งเริ่มต้น",
@@ -188,7 +184,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': "กม",
'dashboard.atlas.aroundEquator': "ประมาณ {count}× รอบเส้นศูนย์สูตร",
'dashboard.card.idea': "ไอเดีย",
- 'dashboard.card.buddyOne': "บัดดี้",
'dashboard.fx.from': "จาก",
'dashboard.fx.to': "ถึง",
'dashboard.fx.unavailable': "อัตราไม่พร้อมใช้งาน",
diff --git a/shared/src/i18n/th/files.ts b/shared/src/i18n/th/files.ts
index 39ee9ffc9..09ebd296c 100644
--- a/shared/src/i18n/th/files.ts
+++ b/shared/src/i18n/th/files.ts
@@ -14,7 +14,6 @@ const files: TranslationStrings = {
'files.openError': "ไม่สามารถเปิดไฟล์ได้",
'files.downloadPdf': "ดาวน์โหลดไฟล์ PDF",
'files.count': "{count} ไฟล์",
- 'files.countSingular': "1 ไฟล์",
'files.uploaded': "{count} อัปโหลดแล้ว",
'files.uploadError': "การอัปโหลดล้มเหลว",
'files.uploadErrorType': "ไม่รองรับไฟล์ประเภทนี้",
diff --git a/shared/src/i18n/th/members.ts b/shared/src/i18n/th/members.ts
index 6bf1de05d..95eb66643 100644
--- a/shared/src/i18n/th/members.ts
+++ b/shared/src/i18n/th/members.ts
@@ -7,7 +7,6 @@ const members: TranslationStrings = {
'members.invite': "เชิญ",
'members.allHaveAccess': "ผู้ใช้ทุกคนมีสิทธิ์เข้าถึงแล้ว",
'members.access': "การเข้าถึง",
- 'members.person': "คน",
'members.persons': "บุคคล",
'members.you': "คุณ",
'members.owner': "เจ้าของ",
diff --git a/shared/src/i18n/th/oauth.ts b/shared/src/i18n/th/oauth.ts
index a8bdd2139..8e64c313b 100644
--- a/shared/src/i18n/th/oauth.ts
+++ b/shared/src/i18n/th/oauth.ts
@@ -85,8 +85,7 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': "แอปพลิเคชันนี้กำลังขอเข้าถึงบัญชี TREK ของคุณ",
'oauth.authorize.trustNote': "ให้สิทธิ์การเข้าถึงเฉพาะแอปพลิเคชันที่คุณเชื่อถือเท่านั้น ข้อมูลของคุณอยู่บนเซิร์ฟเวอร์ของคุณ",
'oauth.authorize.selectScope': "เลือกอย่างน้อยหนึ่งขอบเขต",
- 'oauth.authorize.approveOneScope': "อนุมัติ ({count} ขอบเขต)",
- 'oauth.authorize.approveManyScopes': "อนุมัติ ({count} ขอบเขต)",
+ 'oauth.authorize.approveScopes': "อนุมัติ ({count} ขอบเขต)",
'oauth.authorize.approveAccess': "อนุมัติการเข้าถึง",
'oauth.authorize.deny': "ปฏิเสธ",
'oauth.authorize.choosePermissions': "เลือกสิทธิ์ที่จะให้",
diff --git a/shared/src/i18n/th/places.ts b/shared/src/i18n/th/places.ts
index 11a45cdf4..c49031ef9 100644
--- a/shared/src/i18n/th/places.ts
+++ b/shared/src/i18n/th/places.ts
@@ -64,7 +64,6 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'หมวดหมู่',
'places.clearFilter': 'ล้างตัวกรอง',
'places.count': '{count} สถานที่',
- 'places.countSingular': '1 แห่ง',
'places.allPlanned': 'มีการวางแผนสถานที่ทั้งหมดแล้ว',
'places.noneFound': 'ไม่พบสถานที่',
'places.editPlace': 'แก้ไขสถานที่',
diff --git a/shared/src/i18n/th/tours.ts b/shared/src/i18n/th/tours.ts
index 04b98d3bb..b9f61d756 100644
--- a/shared/src/i18n/th/tours.ts
+++ b/shared/src/i18n/th/tours.ts
@@ -20,7 +20,6 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'ข้อมูลระดับความสูงหรือค่าสถิติไม่ครบถ้วน ใช้ข้อมูลที่นำเข้าด้วยความระมัดระวัง',
'tours.import.caution': 'นำเข้าแล้ว แต่ข้อมูลระดับความสูงหรือค่าสถิติไม่ครบถ้วน โปรดใช้ด้วยความระมัดระวัง',
'tours.import.success': 'นำเข้าทัวร์ {count} รายการจาก GPX แล้ว',
- 'tours.import.successOne': 'นำเข้าทัวร์ 1 รายการจาก GPX แล้ว',
'tours.import.noTrack': 'ไม่พบแทร็กหรือเส้นทางในไฟล์ GPX นี้',
'tours.import.error': 'ไม่สามารถนำเข้าไฟล์ GPX ได้',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/tr/atlas.ts b/shared/src/i18n/tr/atlas.ts
index 6198f1b92..e68e06b6c 100644
--- a/shared/src/i18n/tr/atlas.ts
+++ b/shared/src/i18n/tr/atlas.ts
@@ -43,10 +43,10 @@ const atlas: TranslationStrings = {
'atlas.daysLeft': 'kalan günler',
'atlas.streak': 'Rüzgâr gibi geçmek',
'atlas.years': 'yıllar',
- 'atlas.yearInRow': 'yıl üst üste',
'atlas.yearsInRow': 'yıllar üst üste',
- 'atlas.tripIn': 'yolculuk',
+ 'atlas.yearsInRow.one': 'yıl üst üste',
'atlas.tripsIn': 'geziler',
+ 'atlas.tripsIn.one': 'yolculuk',
'atlas.since': 'o zamandan beri',
'atlas.europe': 'Avrupa',
'atlas.asia': 'Asya',
@@ -57,10 +57,11 @@ const atlas: TranslationStrings = {
'atlas.other': 'Diğer',
'atlas.firstVisit': 'İlk yolculuk',
'atlas.lastVisitLabel': 'Son yolculuk',
- 'atlas.tripSingular': 'Seyahat',
'atlas.tripPlural': 'Geziler',
- 'atlas.placeVisited': 'Ziyaret edilen yer',
+ 'atlas.tripsLabel': 'Geziler',
+ 'atlas.tripsLabel.one': 'Seyahat',
'atlas.placesVisited': 'Ziyaret edilen yerler',
+ 'atlas.placesVisited.one': 'Ziyaret edilen yer',
'atlas.planned': 'Planlandı',
'atlas.showPlanned': 'Planlanan ülkeleri göster',
'atlas.plannedFor': 'Planlanan tarih',
diff --git a/shared/src/i18n/tr/collab.ts b/shared/src/i18n/tr/collab.ts
index 3cb69011e..d1c238104 100644
--- a/shared/src/i18n/tr/collab.ts
+++ b/shared/src/i18n/tr/collab.ts
@@ -89,7 +89,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Kapat',
'collab.polls.closed': 'Kapalı',
'collab.polls.votes': '{n} oy',
- 'collab.polls.vote': '{n} oy',
+ 'collab.polls.votes.one': '{n} oy',
'collab.polls.multipleChoice': 'Çoklu seçim',
'collab.polls.multiChoice': 'Çoklu seçim',
'collab.polls.deadline': 'Son tarih',
diff --git a/shared/src/i18n/tr/dashboard.ts b/shared/src/i18n/tr/dashboard.ts
index f1a17e8dc..e36a34d76 100644
--- a/shared/src/i18n/tr/dashboard.ts
+++ b/shared/src/i18n/tr/dashboard.ts
@@ -34,6 +34,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Günler',
'dashboard.places': 'Yerler',
'dashboard.members': 'Arkadaşlar',
+ 'dashboard.card.buddies': 'Arkadaşlar',
+ 'dashboard.card.buddies.one': 'Arkadaş',
'dashboard.archive': 'Arşivle',
'dashboard.copyTrip': 'Kopyala',
'dashboard.copySuffix': 'kopya',
@@ -158,8 +160,8 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': '{count} gün içinde',
'dashboard.mobile.inMonths': '{count} ay içinde',
'dashboard.mobile.spotlightDayOf': 'Gün {day}/{total}',
- 'dashboard.mobile.spotlightDayOne': '{count} gün',
- 'dashboard.mobile.spotlightDaysMany': '{count} gün',
+ 'dashboard.mobile.spotlightDays': '{count} gün',
+ 'dashboard.mobile.spotlightDays.one': '{count} gün',
'dashboard.mobile.completed': 'Tamamlandı',
'dashboard.mobile.currencyConverter': 'Döviz çevirici',
'dashboard.filter.planned': 'Planlanan',
@@ -170,14 +172,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'YAKIN ZAMANDA',
'dashboard.hero.tripDates': 'Gezi tarihleri',
'dashboard.hero.noDates': 'Tarih ayarlanmadı',
- 'dashboard.hero.travelerOne': '{count} gezgin',
- 'dashboard.hero.travelerMany': '{count} gezgin',
- 'dashboard.hero.destinationOne': '{count} varış noktası',
- 'dashboard.hero.destinationMany': '{count} varış noktası',
- 'dashboard.hero.dayUnitOne': 'gün',
- 'dashboard.hero.dayUnitMany': 'gün',
- 'dashboard.hero.dayLeft': 'gün kaldı',
+ 'dashboard.hero.travelers': '{count} gezgin',
+ 'dashboard.hero.travelers.one': '{count} gezgin',
+ 'dashboard.hero.destinations': '{count} varış noktası',
+ 'dashboard.hero.destinations.one': '{count} varış noktası',
+ 'dashboard.hero.dayUnit': 'gün',
+ 'dashboard.hero.dayUnit.one': 'gün',
'dashboard.hero.daysLeft': 'gün kaldı',
+ 'dashboard.hero.daysLeft.one': 'gün kaldı',
'dashboard.hero.lastDay': 'Son gün',
'dashboard.hero.untilStart': 'Başlangıca',
'dashboard.hero.startsIn': 'Başlamasına',
@@ -192,7 +194,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ ekvatorun {count}× çevresi',
'dashboard.card.idea': 'Fikir',
- 'dashboard.card.buddyOne': 'Arkadaş',
'dashboard.fx.from': 'Kaynak',
'dashboard.fx.to': 'Hedef',
'dashboard.fx.unavailable': 'Kur kullanılamıyor',
diff --git a/shared/src/i18n/tr/files.ts b/shared/src/i18n/tr/files.ts
index abb43d4ee..92c35c728 100644
--- a/shared/src/i18n/tr/files.ts
+++ b/shared/src/i18n/tr/files.ts
@@ -14,7 +14,7 @@ const files: TranslationStrings = {
'files.openError': 'Dosya açılamadı',
'files.downloadPdf': 'PDF İndir',
'files.count': '{count} dosya',
- 'files.countSingular': '1 dosya',
+ 'files.count.one': '{count} dosya',
'files.uploaded': '{count} yüklendi',
'files.uploadError': 'Yükleme başarısız oldu',
'files.uploadErrorType': 'Bu dosya türü desteklenmiyor',
diff --git a/shared/src/i18n/tr/members.ts b/shared/src/i18n/tr/members.ts
index 8d56db8aa..a62b89a44 100644
--- a/shared/src/i18n/tr/members.ts
+++ b/shared/src/i18n/tr/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'Davet et',
'members.allHaveAccess': 'Tüm kullanıcıların zaten erişimi var.',
'members.access': 'Erişim',
- 'members.person': 'kişi',
'members.persons': 'kişi',
+ 'members.persons.one': 'kişi',
'members.you': 'siz',
'members.owner': 'Sahip',
'members.leaveTrip': 'Seyahatten ayrıl',
diff --git a/shared/src/i18n/tr/oauth.ts b/shared/src/i18n/tr/oauth.ts
index db0551d55..beb3076cc 100644
--- a/shared/src/i18n/tr/oauth.ts
+++ b/shared/src/i18n/tr/oauth.ts
@@ -89,8 +89,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes.one': 'Approve ({count} scope)', // en-fallback
'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
'oauth.authorize.deny': 'Deny', // en-fallback
'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
diff --git a/shared/src/i18n/tr/places.ts b/shared/src/i18n/tr/places.ts
index ae125af18..61a1637da 100644
--- a/shared/src/i18n/tr/places.ts
+++ b/shared/src/i18n/tr/places.ts
@@ -72,7 +72,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'kategoriler',
'places.clearFilter': 'Filtreyi temizle',
'places.count': '{count} yer',
- 'places.countSingular': '1 yer',
+ 'places.count.one': '{count} yer',
'places.allPlanned': 'Bütün yerler planlandı',
'places.noneFound': 'Hiçbir yer bulunamadı',
'places.editPlace': 'Yeri Düzenle',
diff --git a/shared/src/i18n/tr/tours.ts b/shared/src/i18n/tr/tours.ts
index 77019bbf7..c04cb58ab 100644
--- a/shared/src/i18n/tr/tours.ts
+++ b/shared/src/i18n/tr/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Yükseklik verileri veya ölçümler eksik – çekinceyle içe aktarıldı',
'tours.import.caution': 'Çekinceyle içe aktarıldı – yükseklik verileri veya ölçümler eksik',
'tours.import.success': 'GPX dosyasından içe aktarılan tur sayısı: {count}',
- 'tours.import.successOne': 'GPX dosyasından 1 tur içe aktarıldı',
+ 'tours.import.success.one': 'GPX dosyasından {count} tur içe aktarıldı',
'tours.import.noTrack': 'Bu GPX dosyasında iz veya rota bulunamadı',
'tours.import.error': 'GPX dosyası içe aktarılamadı',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/uk/atlas.ts b/shared/src/i18n/uk/atlas.ts
index f1787f2aa..5db25fc2a 100644
--- a/shared/src/i18n/uk/atlas.ts
+++ b/shared/src/i18n/uk/atlas.ts
@@ -19,10 +19,10 @@ const atlas: TranslationStrings = {
'atlas.streak': 'Серія',
'atlas.year': 'рік',
'atlas.years': 'років',
- 'atlas.yearInRow': 'рік підряд',
'atlas.yearsInRow': 'років підряд',
- 'atlas.tripIn': 'подорожі до',
+ 'atlas.yearsInRow.one': 'рік підряд',
'atlas.tripsIn': 'подорожей до',
+ 'atlas.tripsIn.one': 'подорожі до',
'atlas.since': 'з',
'atlas.europe': 'Європа',
'atlas.asia': 'Азія',
@@ -33,10 +33,11 @@ const atlas: TranslationStrings = {
'atlas.other': 'Інше',
'atlas.firstVisit': 'Перша поїздка',
'atlas.lastVisitLabel': 'Остання поїздка',
- 'atlas.tripSingular': 'Поїздка',
'atlas.tripPlural': 'Поїздки',
- 'atlas.placeVisited': 'Відвідане місце',
+ 'atlas.tripsLabel': 'Поїздки',
+ 'atlas.tripsLabel.one': 'Поїздка',
'atlas.placesVisited': 'Відвідані місця',
+ 'atlas.placesVisited.one': 'Відвідане місце',
'atlas.statsTab': 'Статистика',
'atlas.bucketTab': 'Список бажань',
'atlas.addBucket': 'Додати до списку бажань',
diff --git a/shared/src/i18n/uk/collab.ts b/shared/src/i18n/uk/collab.ts
index bb208cee7..d4e2f21ce 100644
--- a/shared/src/i18n/uk/collab.ts
+++ b/shared/src/i18n/uk/collab.ts
@@ -89,7 +89,7 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Закрити',
'collab.polls.closed': 'Закрите',
'collab.polls.votes': '{n} голосів',
- 'collab.polls.vote': '{n} голос',
+ 'collab.polls.votes.one': '{n} голос',
'collab.polls.multipleChoice': 'Багатоваріантний вибір',
'collab.polls.multiChoice': 'Багатоваріантний вибір',
'collab.polls.deadline': 'Термін',
diff --git a/shared/src/i18n/uk/dashboard.ts b/shared/src/i18n/uk/dashboard.ts
index 234e7407a..96afe4920 100644
--- a/shared/src/i18n/uk/dashboard.ts
+++ b/shared/src/i18n/uk/dashboard.ts
@@ -34,6 +34,8 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Дні',
'dashboard.places': 'Місця',
'dashboard.members': 'Попутники',
+ 'dashboard.card.buddies': 'Попутники',
+ 'dashboard.card.buddies.one': 'Супутник',
'dashboard.archive': 'Архівувати',
'dashboard.copyTrip': 'Копіювати',
'dashboard.copySuffix': 'копія',
@@ -159,8 +161,8 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': 'Через {count} дн.',
'dashboard.mobile.inMonths': 'Через {count} міс.',
'dashboard.mobile.spotlightDayOf': 'День {day} з {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} день',
- 'dashboard.mobile.spotlightDaysMany': '{count} днів',
+ 'dashboard.mobile.spotlightDays': '{count} днів',
+ 'dashboard.mobile.spotlightDays.one': '{count} день',
'dashboard.mobile.completed': 'Завершено',
'dashboard.mobile.currencyConverter': 'Конвертер валют',
'dashboard.filter.planned': 'Заплановані',
@@ -171,14 +173,14 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'НЕЩОДАВНО',
'dashboard.hero.tripDates': 'Дати поїздки',
'dashboard.hero.noDates': 'Дати не задано',
- 'dashboard.hero.travelerOne': '{count} мандрівник',
- 'dashboard.hero.travelerMany': '{count} мандрівників',
- 'dashboard.hero.destinationOne': '{count} напрямок',
- 'dashboard.hero.destinationMany': '{count} напрямків',
- 'dashboard.hero.dayUnitOne': 'день',
- 'dashboard.hero.dayUnitMany': 'дн.',
- 'dashboard.hero.dayLeft': 'Залишився день',
+ 'dashboard.hero.travelers': '{count} мандрівників',
+ 'dashboard.hero.travelers.one': '{count} мандрівник',
+ 'dashboard.hero.destinations': '{count} напрямків',
+ 'dashboard.hero.destinations.one': '{count} напрямок',
+ 'dashboard.hero.dayUnit': 'дн.',
+ 'dashboard.hero.dayUnit.one': 'день',
'dashboard.hero.daysLeft': 'Залишилось днів',
+ 'dashboard.hero.daysLeft.one': 'Залишився день',
'dashboard.hero.lastDay': 'Останній день',
'dashboard.hero.untilStart': 'До початку',
'dashboard.hero.startsIn': 'Старт через',
@@ -193,7 +195,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ {count}× навколо екватора',
'dashboard.card.idea': 'Ідея',
- 'dashboard.card.buddyOne': 'Супутник',
'dashboard.fx.from': 'З',
'dashboard.fx.to': 'У',
'dashboard.fx.unavailable': 'Курс недоступний',
diff --git a/shared/src/i18n/uk/files.ts b/shared/src/i18n/uk/files.ts
index 23f088a38..6b24ddd8f 100644
--- a/shared/src/i18n/uk/files.ts
+++ b/shared/src/i18n/uk/files.ts
@@ -14,7 +14,7 @@ const files: TranslationStrings = {
'files.openError': 'Не вдалося відкрити файл',
'files.downloadPdf': 'Завантажити PDF',
'files.count': '{count} файлів',
- 'files.countSingular': '1 файл',
+ 'files.count.one': '{count} файл',
'files.uploaded': '{count} завантажено',
'files.uploadError': 'Помилка завантаження',
'files.uploadErrorType': 'Цей тип файлу не підтримується',
diff --git a/shared/src/i18n/uk/members.ts b/shared/src/i18n/uk/members.ts
index 49ce7c1e7..444d32080 100644
--- a/shared/src/i18n/uk/members.ts
+++ b/shared/src/i18n/uk/members.ts
@@ -7,8 +7,8 @@ const members: TranslationStrings = {
'members.invite': 'Запросити',
'members.allHaveAccess': 'У всіх користувачів вже є доступ.',
'members.access': 'Доступ',
- 'members.person': 'особа',
'members.persons': 'осіб',
+ 'members.persons.one': 'особа',
'members.you': 'ви',
'members.owner': 'Власник',
'members.leaveTrip': 'Покинути поїздку',
diff --git a/shared/src/i18n/uk/oauth.ts b/shared/src/i18n/uk/oauth.ts
index 79cfcdaba..65f08a92c 100644
--- a/shared/src/i18n/uk/oauth.ts
+++ b/shared/src/i18n/uk/oauth.ts
@@ -85,8 +85,8 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes.one': 'Approve ({count} scope)', // en-fallback
'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
'oauth.authorize.deny': 'Deny', // en-fallback
'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
diff --git a/shared/src/i18n/uk/places.ts b/shared/src/i18n/uk/places.ts
index d6ab8b737..e40e104ea 100644
--- a/shared/src/i18n/uk/places.ts
+++ b/shared/src/i18n/uk/places.ts
@@ -70,7 +70,7 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'категорій',
'places.clearFilter': 'Скинути фільтр',
'places.count': '{count} місць',
- 'places.countSingular': '1 місце',
+ 'places.count.one': '{count} місце',
'places.allPlanned': 'Усі місця заплановані',
'places.noneFound': 'Місця не знайдені',
'places.editPlace': 'Редагувати місце',
diff --git a/shared/src/i18n/uk/tours.ts b/shared/src/i18n/uk/tours.ts
index e1ad31971..b7dc6eec1 100644
--- a/shared/src/i18n/uk/tours.ts
+++ b/shared/src/i18n/uk/tours.ts
@@ -20,7 +20,7 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Дані про висоту або показники неповні — імпорт із застереженнями',
'tours.import.caution': 'Імпорт із застереженнями — дані про висоту або показники неповні',
'tours.import.success': 'Імпортовано турів із GPX: {count}',
- 'tours.import.successOne': 'Імпортовано 1 тур із GPX',
+ 'tours.import.success.one': 'Імпортовано {count} тур із GPX',
'tours.import.noTrack': 'У цьому файлі GPX не знайдено треку або маршруту',
'tours.import.error': 'Не вдалося імпортувати файл GPX',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/vi/atlas.ts b/shared/src/i18n/vi/atlas.ts
index fb1f2e46c..b8f7f8c92 100644
--- a/shared/src/i18n/vi/atlas.ts
+++ b/shared/src/i18n/vi/atlas.ts
@@ -43,9 +43,7 @@ const atlas: TranslationStrings = {
'atlas.daysLeft': 'ngày còn lại',
'atlas.streak': 'Vệt',
'atlas.years': 'năm',
- 'atlas.yearInRow': 'năm liên tiếp',
'atlas.yearsInRow': 'năm liên tiếp',
- 'atlas.tripIn': 'chuyến đi vào',
'atlas.tripsIn': 'chuyến đi trong',
'atlas.since': 'từ',
'atlas.europe': 'Châu Âu',
@@ -57,9 +55,8 @@ const atlas: TranslationStrings = {
'atlas.other': 'Khác',
'atlas.firstVisit': 'Chuyến đi đầu tiên',
'atlas.lastVisitLabel': 'Chuyến đi cuối cùng',
- 'atlas.tripSingular': 'Chuyến đi',
'atlas.tripPlural': 'Chuyến đi',
- 'atlas.placeVisited': 'Nơi đã ghé thăm',
+ 'atlas.tripsLabel': 'Chuyến đi',
'atlas.placesVisited': 'Địa điểm đã ghé thăm',
'atlas.planned': 'Đã lên kế hoạch',
'atlas.showPlanned': 'Hiện các quốc gia đã lên kế hoạch',
diff --git a/shared/src/i18n/vi/collab.ts b/shared/src/i18n/vi/collab.ts
index 06b47e4dd..75bbdce47 100644
--- a/shared/src/i18n/vi/collab.ts
+++ b/shared/src/i18n/vi/collab.ts
@@ -89,7 +89,6 @@ const collab: TranslationStrings = {
'collab.polls.close': 'Đóng',
'collab.polls.closed': 'Đã đóng',
'collab.polls.votes': '{n} phiếu bầu',
- 'collab.polls.vote': '{n} bầu chọn',
'collab.polls.multipleChoice': 'Nhiều lựa chọn',
'collab.polls.multiChoice': 'Nhiều lựa chọn',
'collab.polls.deadline': 'Thời hạn',
diff --git a/shared/src/i18n/vi/dashboard.ts b/shared/src/i18n/vi/dashboard.ts
index ff1f2fa9b..ffd4fef83 100644
--- a/shared/src/i18n/vi/dashboard.ts
+++ b/shared/src/i18n/vi/dashboard.ts
@@ -34,6 +34,7 @@ const dashboard: TranslationStrings = {
'dashboard.days': 'Ngày',
'dashboard.places': 'Địa điểm',
'dashboard.members': 'thành viên',
+ 'dashboard.card.buddies': 'thành viên',
'dashboard.archive': 'Lưu trữ',
'dashboard.copyTrip': 'Sao chép',
'dashboard.copySuffix': 'sao chép',
@@ -152,8 +153,7 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': 'Trong {count} ngày',
'dashboard.mobile.inMonths': 'Trong {count} tháng',
'dashboard.mobile.spotlightDayOf': 'Ngày {day} / {total}',
- 'dashboard.mobile.spotlightDayOne': '{count} ngày',
- 'dashboard.mobile.spotlightDaysMany': '{count} ngày',
+ 'dashboard.mobile.spotlightDays': '{count} ngày',
'dashboard.mobile.completed': 'Hoàn thành',
'dashboard.mobile.currencyConverter': 'Chuyển đổi tiền tệ',
'dashboard.filter.planned': 'Đã lên kế hoạch',
@@ -164,13 +164,9 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': 'GẦN ĐÂY',
'dashboard.hero.tripDates': 'Ngày chuyến đi',
'dashboard.hero.noDates': 'Ngày mở',
- 'dashboard.hero.travelerOne': '{count} người',
- 'dashboard.hero.travelerMany': '{count} người',
- 'dashboard.hero.destinationOne': '{count} điểm đến',
- 'dashboard.hero.destinationMany': '{count} điểm đến',
- 'dashboard.hero.dayUnitOne': 'ngày',
- 'dashboard.hero.dayUnitMany': 'ngày',
- 'dashboard.hero.dayLeft': 'Ngày còn lại',
+ 'dashboard.hero.travelers': '{count} người',
+ 'dashboard.hero.destinations': '{count} điểm đến',
+ 'dashboard.hero.dayUnit': 'ngày',
'dashboard.hero.daysLeft': 'Số ngày còn lại',
'dashboard.hero.lastDay': 'Ngày cuối cùng',
'dashboard.hero.untilStart': 'Cho đến khi bắt đầu',
@@ -186,7 +182,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ {count}× quanh xích đạo',
'dashboard.card.idea': 'Ý tưởng',
- 'dashboard.card.buddyOne': 'Bạn bè',
'dashboard.fx.from': 'Từ',
'dashboard.fx.to': 'ĐẾN',
'dashboard.fx.unavailable': 'Không có giá',
diff --git a/shared/src/i18n/vi/files.ts b/shared/src/i18n/vi/files.ts
index bdea060d3..60a06f438 100644
--- a/shared/src/i18n/vi/files.ts
+++ b/shared/src/i18n/vi/files.ts
@@ -15,7 +15,6 @@ const files: TranslationStrings = {
'files.openError': 'Không thể mở tập tin',
'files.downloadPdf': 'Tải xuống PDF',
'files.count': '{count} tập tin',
- 'files.countSingular': '1 tập tin',
'files.uploaded': '{count} đã tải lên',
'files.uploadError': 'Tải lên không thành công',
'files.dropzone': 'Thả tập tin ở đây',
diff --git a/shared/src/i18n/vi/members.ts b/shared/src/i18n/vi/members.ts
index 0939e3237..8fe04da0f 100644
--- a/shared/src/i18n/vi/members.ts
+++ b/shared/src/i18n/vi/members.ts
@@ -7,7 +7,6 @@ const members: TranslationStrings = {
'members.invite': 'Mời',
'members.allHaveAccess': 'Tất cả người dùng đã có quyền truy cập.',
'members.access': 'Truy cập',
- 'members.person': 'người',
'members.persons': 'người',
'members.you': 'Bạn',
'members.owner': 'Người sở hữu',
diff --git a/shared/src/i18n/vi/oauth.ts b/shared/src/i18n/vi/oauth.ts
index 2e775a8f6..c51f5707d 100644
--- a/shared/src/i18n/vi/oauth.ts
+++ b/shared/src/i18n/vi/oauth.ts
@@ -87,8 +87,7 @@ const oauth: TranslationStrings = {
'oauth.authorize.trustNote':
'Chỉ cấp quyền truy cập cho các ứng dụng mà bạn tin tưởng. Dữ liệu của bạn vẫn còn trên máy chủ của bạn.',
'oauth.authorize.selectScope': 'Chọn ít nhất một phạm vi',
- 'oauth.authorize.approveOneScope': 'Phê duyệt (phạm vi {count})',
- 'oauth.authorize.approveManyScopes': 'Phê duyệt ({count} phạm vi)',
+ 'oauth.authorize.approveScopes': 'Phê duyệt ({count} phạm vi)',
'oauth.authorize.approveAccess': 'Phê duyệt quyền truy cập',
'oauth.authorize.deny': 'Từ chối',
'oauth.authorize.choosePermissions': 'Chọn quyền nào để cấp',
diff --git a/shared/src/i18n/vi/places.ts b/shared/src/i18n/vi/places.ts
index 411e6d7d4..c19843758 100644
--- a/shared/src/i18n/vi/places.ts
+++ b/shared/src/i18n/vi/places.ts
@@ -70,7 +70,6 @@ const places: TranslationStrings = {
'places.categoriesSelected': 'Thể loại',
'places.clearFilter': 'Xóa bộ lọc',
'places.count': '{count} địa điểm',
- 'places.countSingular': '1 nơi',
'places.allPlanned': 'Tất cả các địa điểm đều được quy hoạch',
'places.noneFound': 'Không tìm thấy địa điểm nào',
'places.editPlace': 'Chỉnh sửa địa điểm',
diff --git a/shared/src/i18n/vi/tours.ts b/shared/src/i18n/vi/tours.ts
index 89b4e16dd..9a6a46ed0 100644
--- a/shared/src/i18n/vi/tours.ts
+++ b/shared/src/i18n/vi/tours.ts
@@ -20,7 +20,6 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': 'Dữ liệu độ cao hoặc thông số chưa đầy đủ; đã nhập kèm cảnh báo',
'tours.import.caution': 'Đã nhập kèm cảnh báo; dữ liệu độ cao hoặc thông số chưa đầy đủ',
'tours.import.success': 'Đã nhập {count} chuyến tham quan từ GPX',
- 'tours.import.successOne': 'Đã nhập 1 chuyến tham quan từ GPX',
'tours.import.noTrack': 'Không tìm thấy vệt hành trình hoặc tuyến đường trong tệp GPX này',
'tours.import.error': 'Không thể nhập tệp GPX',
'tours.delete.confirmBody':
diff --git a/shared/src/i18n/zh-TW/atlas.ts b/shared/src/i18n/zh-TW/atlas.ts
index ee6c6b8b7..a2bb23f50 100644
--- a/shared/src/i18n/zh-TW/atlas.ts
+++ b/shared/src/i18n/zh-TW/atlas.ts
@@ -19,9 +19,7 @@ const atlas: TranslationStrings = {
'atlas.streak': '連續',
'atlas.year': '年',
'atlas.years': '年',
- 'atlas.yearInRow': '年連續',
'atlas.yearsInRow': '年連續',
- 'atlas.tripIn': '次旅行在',
'atlas.tripsIn': '次旅行在',
'atlas.since': '自',
'atlas.europe': '歐洲',
@@ -33,9 +31,8 @@ const atlas: TranslationStrings = {
'atlas.other': '其他',
'atlas.firstVisit': '首次旅行',
'atlas.lastVisitLabel': '最近旅行',
- 'atlas.tripSingular': '次旅行',
'atlas.tripPlural': '次旅行',
- 'atlas.placeVisited': '個地點已訪問',
+ 'atlas.tripsLabel': '次旅行',
'atlas.placesVisited': '個地點已訪問',
'atlas.statsTab': '統計',
'atlas.bucketTab': '心願單',
diff --git a/shared/src/i18n/zh-TW/collab.ts b/shared/src/i18n/zh-TW/collab.ts
index 9a8a80a7e..f2aec4e45 100644
--- a/shared/src/i18n/zh-TW/collab.ts
+++ b/shared/src/i18n/zh-TW/collab.ts
@@ -89,7 +89,6 @@ const collab: TranslationStrings = {
'collab.polls.close': '關閉',
'collab.polls.closed': '已關閉',
'collab.polls.votes': '{n} 票',
- 'collab.polls.vote': '{n} 票',
'collab.polls.multipleChoice': '多選',
'collab.polls.multiChoice': '多選',
'collab.polls.deadline': '截止時間',
diff --git a/shared/src/i18n/zh-TW/dashboard.ts b/shared/src/i18n/zh-TW/dashboard.ts
index c0ba18bad..218a398a6 100644
--- a/shared/src/i18n/zh-TW/dashboard.ts
+++ b/shared/src/i18n/zh-TW/dashboard.ts
@@ -34,6 +34,7 @@ const dashboard: TranslationStrings = {
'dashboard.days': '天',
'dashboard.places': '地點',
'dashboard.members': '旅伴',
+ 'dashboard.card.buddies': '旅伴',
'dashboard.archive': '歸檔',
'dashboard.copyTrip': '複製',
'dashboard.copySuffix': '副本',
@@ -141,8 +142,7 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': '{count} 天後',
'dashboard.mobile.inMonths': '{count} 個月後',
'dashboard.mobile.spotlightDayOf': '第 {day} 天,共 {total} 天',
- 'dashboard.mobile.spotlightDayOne': '{count} 天',
- 'dashboard.mobile.spotlightDaysMany': '{count} 天',
+ 'dashboard.mobile.spotlightDays': '{count} 天',
'dashboard.mobile.completed': '已完成',
'dashboard.mobile.currencyConverter': '匯率轉換',
'dashboard.filter.planned': '已規劃',
@@ -153,13 +153,9 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': '最近',
'dashboard.hero.tripDates': '旅行日期',
'dashboard.hero.noDates': '未設定日期',
- 'dashboard.hero.travelerOne': '{count} 位旅客',
- 'dashboard.hero.travelerMany': '{count} 位旅客',
- 'dashboard.hero.destinationOne': '{count} 個目的地',
- 'dashboard.hero.destinationMany': '{count} 個目的地',
- 'dashboard.hero.dayUnitOne': '天',
- 'dashboard.hero.dayUnitMany': '天',
- 'dashboard.hero.dayLeft': '剩 1 天',
+ 'dashboard.hero.travelers': '{count} 位旅客',
+ 'dashboard.hero.destinations': '{count} 個目的地',
+ 'dashboard.hero.dayUnit': '天',
'dashboard.hero.daysLeft': '剩餘天數',
'dashboard.hero.lastDay': '最後一天',
'dashboard.hero.untilStart': '距開始',
@@ -175,7 +171,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ 繞赤道 {count} 圈',
'dashboard.card.idea': '想法',
- 'dashboard.card.buddyOne': '旅伴',
'dashboard.fx.from': '從',
'dashboard.fx.to': '到',
'dashboard.fx.unavailable': '無法取得匯率',
diff --git a/shared/src/i18n/zh-TW/files.ts b/shared/src/i18n/zh-TW/files.ts
index cc885f9e3..afd7c1b77 100644
--- a/shared/src/i18n/zh-TW/files.ts
+++ b/shared/src/i18n/zh-TW/files.ts
@@ -14,7 +14,6 @@ const files: TranslationStrings = {
'files.openError': '無法開啟檔案',
'files.downloadPdf': '下載 PDF',
'files.count': '{count} 個檔案',
- 'files.countSingular': '1 個檔案',
'files.uploaded': '已上傳 {count} 個',
'files.uploadError': '上傳失敗',
'files.uploadErrorType': '不支援此檔案類型',
diff --git a/shared/src/i18n/zh-TW/members.ts b/shared/src/i18n/zh-TW/members.ts
index b7033a86b..271da1454 100644
--- a/shared/src/i18n/zh-TW/members.ts
+++ b/shared/src/i18n/zh-TW/members.ts
@@ -7,7 +7,6 @@ const members: TranslationStrings = {
'members.invite': '邀請',
'members.allHaveAccess': '所有使用者均已擁有訪問許可權。',
'members.access': '訪問許可權',
- 'members.person': '人',
'members.persons': '人',
'members.you': '你',
'members.owner': '所有者',
diff --git a/shared/src/i18n/zh-TW/oauth.ts b/shared/src/i18n/zh-TW/oauth.ts
index 588bb4641..b35f8a596 100644
--- a/shared/src/i18n/zh-TW/oauth.ts
+++ b/shared/src/i18n/zh-TW/oauth.ts
@@ -83,8 +83,7 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': 'This application is requesting access to your TREK account.', // en-fallback
'oauth.authorize.trustNote': 'Only grant access to applications you trust. Your data stays on your server.', // en-fallback
'oauth.authorize.selectScope': 'Select at least one scope', // en-fallback
- 'oauth.authorize.approveOneScope': 'Approve ({count} scope)', // en-fallback
- 'oauth.authorize.approveManyScopes': 'Approve ({count} scopes)', // en-fallback
+ 'oauth.authorize.approveScopes': 'Approve ({count} scopes)', // en-fallback
'oauth.authorize.approveAccess': 'Approve Access', // en-fallback
'oauth.authorize.deny': 'Deny', // en-fallback
'oauth.authorize.choosePermissions': 'Choose which permissions to grant', // en-fallback
diff --git a/shared/src/i18n/zh-TW/places.ts b/shared/src/i18n/zh-TW/places.ts
index 646a8cf3b..4d2d3578d 100644
--- a/shared/src/i18n/zh-TW/places.ts
+++ b/shared/src/i18n/zh-TW/places.ts
@@ -69,7 +69,6 @@ const places: TranslationStrings = {
'places.categoriesSelected': '個分類',
'places.clearFilter': '清除篩選',
'places.count': '{count} 個地點',
- 'places.countSingular': '1 個地點',
'places.allPlanned': '所有地點已規劃',
'places.noneFound': '未找到地點',
'places.editPlace': '編輯地點',
diff --git a/shared/src/i18n/zh-TW/tours.ts b/shared/src/i18n/zh-TW/tours.ts
index b92ef4b0f..a926229cb 100644
--- a/shared/src/i18n/zh-TW/tours.ts
+++ b/shared/src/i18n/zh-TW/tours.ts
@@ -20,7 +20,6 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': '海拔或指標資料不完整,已附警告匯入',
'tours.import.caution': '已附警告匯入,海拔或指標資料不完整',
'tours.import.success': '已從 GPX 匯入 {count} 個遊程',
- 'tours.import.successOne': '已從 GPX 匯入 1 個遊程',
'tours.import.noTrack': '這個 GPX 檔案中找不到軌跡或路線',
'tours.import.error': '無法匯入 GPX 檔案',
'tours.delete.confirmBody': '要永久刪除此行程嗎?其路線、可編輯的途經點和日期指派都會一併移除。此操作無法復原。',
diff --git a/shared/src/i18n/zh/atlas.ts b/shared/src/i18n/zh/atlas.ts
index 42216a481..2f6d80bd3 100644
--- a/shared/src/i18n/zh/atlas.ts
+++ b/shared/src/i18n/zh/atlas.ts
@@ -19,9 +19,7 @@ const atlas: TranslationStrings = {
'atlas.streak': '连续',
'atlas.year': '年',
'atlas.years': '年',
- 'atlas.yearInRow': '年连续',
'atlas.yearsInRow': '年连续',
- 'atlas.tripIn': '次旅行在',
'atlas.tripsIn': '次旅行在',
'atlas.since': '自',
'atlas.europe': '欧洲',
@@ -33,9 +31,8 @@ const atlas: TranslationStrings = {
'atlas.other': '其他',
'atlas.firstVisit': '首次旅行',
'atlas.lastVisitLabel': '最近旅行',
- 'atlas.tripSingular': '次旅行',
'atlas.tripPlural': '次旅行',
- 'atlas.placeVisited': '个地点已访问',
+ 'atlas.tripsLabel': '次旅行',
'atlas.placesVisited': '个地点已访问',
'atlas.statsTab': '统计',
'atlas.bucketTab': '心愿单',
diff --git a/shared/src/i18n/zh/collab.ts b/shared/src/i18n/zh/collab.ts
index 69e1985c9..5edba8643 100644
--- a/shared/src/i18n/zh/collab.ts
+++ b/shared/src/i18n/zh/collab.ts
@@ -89,7 +89,6 @@ const collab: TranslationStrings = {
'collab.polls.close': '关闭',
'collab.polls.closed': '已关闭',
'collab.polls.votes': '{n} 票',
- 'collab.polls.vote': '{n} 票',
'collab.polls.multipleChoice': '多选',
'collab.polls.multiChoice': '多选',
'collab.polls.deadline': '截止时间',
diff --git a/shared/src/i18n/zh/dashboard.ts b/shared/src/i18n/zh/dashboard.ts
index 862bf3e9d..7dab954d9 100644
--- a/shared/src/i18n/zh/dashboard.ts
+++ b/shared/src/i18n/zh/dashboard.ts
@@ -34,6 +34,7 @@ const dashboard: TranslationStrings = {
'dashboard.days': '天',
'dashboard.places': '地点',
'dashboard.members': '旅伴',
+ 'dashboard.card.buddies': '旅伴',
'dashboard.archive': '归档',
'dashboard.copyTrip': '复制',
'dashboard.copySuffix': '副本',
@@ -141,8 +142,7 @@ const dashboard: TranslationStrings = {
'dashboard.mobile.inDays': '{count} 天后',
'dashboard.mobile.inMonths': '{count} 个月后',
'dashboard.mobile.spotlightDayOf': '第 {day} 天,共 {total} 天',
- 'dashboard.mobile.spotlightDayOne': '{count} 天',
- 'dashboard.mobile.spotlightDaysMany': '{count} 天',
+ 'dashboard.mobile.spotlightDays': '{count} 天',
'dashboard.mobile.completed': '已完成',
'dashboard.mobile.currencyConverter': '汇率转换',
'dashboard.filter.planned': '已计划',
@@ -153,13 +153,9 @@ const dashboard: TranslationStrings = {
'dashboard.hero.badgeRecent': '最近',
'dashboard.hero.tripDates': '旅行日期',
'dashboard.hero.noDates': '未设置日期',
- 'dashboard.hero.travelerOne': '{count} 位旅客',
- 'dashboard.hero.travelerMany': '{count} 位旅客',
- 'dashboard.hero.destinationOne': '{count} 个目的地',
- 'dashboard.hero.destinationMany': '{count} 个目的地',
- 'dashboard.hero.dayUnitOne': '天',
- 'dashboard.hero.dayUnitMany': '天',
- 'dashboard.hero.dayLeft': '剩 1 天',
+ 'dashboard.hero.travelers': '{count} 位旅客',
+ 'dashboard.hero.destinations': '{count} 个目的地',
+ 'dashboard.hero.dayUnit': '天',
'dashboard.hero.daysLeft': '剩余天数',
'dashboard.hero.lastDay': '最后一天',
'dashboard.hero.untilStart': '距开始',
@@ -175,7 +171,6 @@ const dashboard: TranslationStrings = {
'dashboard.atlas.kmUnit': 'km',
'dashboard.atlas.aroundEquator': '≈ 绕赤道 {count} 圈',
'dashboard.card.idea': '想法',
- 'dashboard.card.buddyOne': '旅伴',
'dashboard.fx.from': '从',
'dashboard.fx.to': '到',
'dashboard.fx.unavailable': '无法获取汇率',
diff --git a/shared/src/i18n/zh/files.ts b/shared/src/i18n/zh/files.ts
index bc3008346..e6b71ea8e 100644
--- a/shared/src/i18n/zh/files.ts
+++ b/shared/src/i18n/zh/files.ts
@@ -14,7 +14,6 @@ const files: TranslationStrings = {
'files.openError': '无法打开文件',
'files.downloadPdf': '下载 PDF',
'files.count': '{count} 个文件',
- 'files.countSingular': '1 个文件',
'files.uploaded': '已上传 {count} 个',
'files.uploadError': '上传失败',
'files.uploadErrorType': '不支持此文件类型',
diff --git a/shared/src/i18n/zh/members.ts b/shared/src/i18n/zh/members.ts
index 62f08acf1..289438066 100644
--- a/shared/src/i18n/zh/members.ts
+++ b/shared/src/i18n/zh/members.ts
@@ -7,7 +7,6 @@ const members: TranslationStrings = {
'members.invite': '邀请',
'members.allHaveAccess': '所有用户均已拥有访问权限。',
'members.access': '访问权限',
- 'members.person': '人',
'members.persons': '人',
'members.you': '你',
'members.owner': '所有者',
diff --git a/shared/src/i18n/zh/oauth.ts b/shared/src/i18n/zh/oauth.ts
index 5d94de9db..5de7f2925 100644
--- a/shared/src/i18n/zh/oauth.ts
+++ b/shared/src/i18n/zh/oauth.ts
@@ -83,8 +83,7 @@ const oauth: TranslationStrings = {
'oauth.authorize.requestDescription': '此应用正在请求访问你的 TREK 账户。',
'oauth.authorize.trustNote': '只向你信任的应用授权,你的数据仍保留在自己的服务器上。',
'oauth.authorize.selectScope': '请至少选择一项权限范围',
- 'oauth.authorize.approveOneScope': '批准({count} 项权限)',
- 'oauth.authorize.approveManyScopes': '批准({count} 项权限)',
+ 'oauth.authorize.approveScopes': '批准({count} 项权限)',
'oauth.authorize.approveAccess': '批准访问',
'oauth.authorize.deny': '拒绝',
'oauth.authorize.choosePermissions': '选择要授予的权限',
diff --git a/shared/src/i18n/zh/places.ts b/shared/src/i18n/zh/places.ts
index c1f4dbde1..10ef22841 100644
--- a/shared/src/i18n/zh/places.ts
+++ b/shared/src/i18n/zh/places.ts
@@ -69,7 +69,6 @@ const places: TranslationStrings = {
'places.categoriesSelected': '个分类',
'places.clearFilter': '清除筛选',
'places.count': '{count} 个地点',
- 'places.countSingular': '1 个地点',
'places.allPlanned': '所有地点已规划',
'places.noneFound': '未找到地点',
'places.editPlace': '编辑地点',
diff --git a/shared/src/i18n/zh/tours.ts b/shared/src/i18n/zh/tours.ts
index 289db09c6..d8f31fa23 100644
--- a/shared/src/i18n/zh/tours.ts
+++ b/shared/src/i18n/zh/tours.ts
@@ -20,7 +20,6 @@ const tours: TranslationStrings = {
'tours.caution.tooltip': '海拔或指标数据不完整,已带警告导入',
'tours.import.caution': '已带警告导入,海拔或指标数据不完整',
'tours.import.success': '已从 GPX 导入 {count} 项游览活动',
- 'tours.import.successOne': '已从 GPX 导入 1 项游览活动',
'tours.import.noTrack': '该 GPX 文件中未找到轨迹或路线',
'tours.import.error': '无法导入 GPX 文件',
'tours.delete.confirmBody': '要永久删除此行程吗?其路线、可编辑的途经点和日期安排都将被删除。此操作无法撤销。',
diff --git a/shared/src/index.ts b/shared/src/index.ts
index 03ca72c4c..7a68328a5 100644
--- a/shared/src/index.ts
+++ b/shared/src/index.ts
@@ -87,6 +87,7 @@ export * from './geo/gcj02';
// i18n registry (language list + pure helpers — no locale data)
export * from './i18n/languages';
+export * from './i18n/plural';
// Plugin permission list, generated from the host's protocol/envelope.ts
// (server/scripts/gen-plugin-facts.ts). The admin consent screens render from it.