mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-13 15:51:00 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e90ece087 | |||
| 65e5d4ab72 | |||
| 154620b99a | |||
| adbe3a40f6 | |||
| 44c6fbf372 |
@@ -15,7 +15,7 @@
|
||||
"@typescript-eslint/no-unused-vars": 124,
|
||||
"react-hooks/immutability": 28,
|
||||
"max-lines-per-function": 130,
|
||||
"react-hooks/static-components": 78,
|
||||
"react-hooks/static-components": 50,
|
||||
"react-hooks/preserve-manual-memoization": 12,
|
||||
"no-useless-assignment": 7,
|
||||
"no-empty": 19,
|
||||
@@ -25,7 +25,7 @@
|
||||
"max-depth": 21,
|
||||
"react-hooks/void-use-memo": 1,
|
||||
"@typescript-eslint/no-unused-expressions": 2,
|
||||
"react-hooks/purity": 2,
|
||||
"react-hooks/purity": 1,
|
||||
"preserve-caught-error": 53,
|
||||
"no-useless-escape": 1
|
||||
},
|
||||
|
||||
@@ -983,6 +983,7 @@
|
||||
"src/mobile/screens/admin/MAdminOidcCard.tsx": "Markup only; the OIDC card of MAdminSettingsSection, with the form state from useAdmin and the save from its useAdminSettingsActions handed in through props.",
|
||||
"src/mobile/screens/admin/MAdminResetMfaSheet.tsx": "Markup only; the 2FA reset confirm that MAdminSheets opens, with the reset handed in through props from its useAdminUserActions.",
|
||||
"src/mobile/screens/admin/MAdminUi.tsx": "Phone only admin UI primitives (button, card, field, input, secret input, sheet frame).",
|
||||
"src/mobile/screens/admin/MPluginVersionDate.tsx": "Markup only; the published date of a plugin version in MAdminPluginsPanel, formatted with the user's date format through useFormatDate.",
|
||||
"src/mobile/screens/admin/MStorageReclaimCard.tsx": "Markup only; renders the reclaim view and handlers that MAdminStoragePanel takes from useStoragePanel, the twin of StorageReclaimSection.",
|
||||
"src/mobile/screens/atlas/MAtlasSearch.tsx": "Phone full screen country search overlay; options, suggestions and selection come through props from MAtlas.",
|
||||
"src/mobile/screens/atlas/MAtlasStatsCard.tsx": "Markup only; renders the AtlasStats that MAtlas passes in.",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"e2e/help/fixtures.ts": 2291,
|
||||
"src/api/client.ts": 1873,
|
||||
"src/components/Admin/AdminPluginsPanel.test.tsx": 2704,
|
||||
"src/components/Admin/AdminPluginsPanel.tsx": 1277,
|
||||
"src/components/Admin/AdminPluginsPanel.tsx": 1266,
|
||||
"src/components/Budget/CostsPanel.test.tsx": 2730,
|
||||
"src/components/Budget/CostsPanel.tsx": 1798,
|
||||
"src/components/Map/MapView.tsx": 1385,
|
||||
|
||||
@@ -284,7 +284,7 @@
|
||||
"mobile/screens/vacay/MVacay.tsx": 23,
|
||||
"mobile/screens/vacay/MVacayInviteSheet.tsx": 9,
|
||||
"mobile/screens/vacay/MVacayMonth.tsx": 3,
|
||||
"mobile/screens/vacay/MVacaySettingsSheet.tsx": 36,
|
||||
"mobile/screens/vacay/MVacaySettingsSheet.tsx": 35,
|
||||
"mobile/screens/vacay/MVacayShareSheet.tsx": 11,
|
||||
"mobile/screens/vacay/vacayDayModel.ts": 1,
|
||||
"pages/AtlasPage.tsx": 32,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { type ReactNode } from 'react'
|
||||
import { Key, Trash2, User, Loader2, Shield, KeyRound, Bot } from 'lucide-react'
|
||||
import { useTranslation } from '../../i18n'
|
||||
import { useFormatDate } from '../../hooks/useDateFormat'
|
||||
import ConfirmDialog from '../shared/ConfirmDialog'
|
||||
import { Tooltip } from '../shared/Tooltip'
|
||||
import { fs } from '../shared/DialogShell'
|
||||
@@ -64,9 +65,10 @@ export default function AdminMcpTokensPanel() {
|
||||
sessions, sessionsLoading, tokens, tokensLoading, expandedScopes, toggleScopes,
|
||||
revokeConfirmId, setRevokeConfirmId, deleteConfirmId, setDeleteConfirmId, handleRevoke, handleDelete,
|
||||
} = useMcpTokensAdmin()
|
||||
const { t, locale } = useTranslation()
|
||||
const { t } = useTranslation()
|
||||
const { formatDate } = useFormatDate()
|
||||
|
||||
const date = (iso: string) => new Date(iso).toLocaleDateString(locale)
|
||||
const date = (iso: string) => formatDate(new Date(iso))
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { type RangeWarning } from './useRangeBypass'
|
||||
import { deriveCaps } from './pluginCaps'
|
||||
import PluginPoiCategoryList from './PluginPoiCategoryList'
|
||||
import { useTranslation } from '../../i18n'
|
||||
import { useFormatDate } from '../../hooks/useDateFormat'
|
||||
import ConfirmDialog from '../shared/ConfirmDialog'
|
||||
import ToggleSwitch from '../Settings/ToggleSwitch'
|
||||
import CustomSelect from '../shared/CustomSelect'
|
||||
@@ -33,6 +34,7 @@ import { usePluginsAdmin } from './plugins/usePluginsAdmin'
|
||||
import PluginFilterMenu from './plugins/PluginFilterMenu'
|
||||
import PluginInstalledRow from './plugins/PluginInstalledRow'
|
||||
import PluginSecurityDialog from './plugins/PluginSecurityDialog'
|
||||
import VersionLine from './plugins/VersionLine'
|
||||
|
||||
/**
|
||||
* Admin → Plugins (#plugins). A full plugin-management surface: a segmented
|
||||
@@ -303,7 +305,7 @@ export default function AdminPluginsPanel() {
|
||||
|
||||
{versionPicker && (
|
||||
<VersionPickerDialog plugin={versionPicker.plugin} versions={versionPicker.versions} failed={versionPicker.failed}
|
||||
busy={busy} t={t} locale={locale} onPick={pickVersion} onClose={() => setVersionPicker(null)} />
|
||||
busy={busy} t={t} onPick={pickVersion} onClose={() => setVersionPicker(null)} />
|
||||
)}
|
||||
|
||||
<ConfirmDialog
|
||||
@@ -745,6 +747,7 @@ function PluginDetailModal({ item, installed, busy, onInstall, onClose, t, local
|
||||
onInstall: (id: string, version?: string, warn?: RangeWarning) => void; onClose: () => void; t: T; locale: string
|
||||
}) {
|
||||
const { detail, failed } = usePluginDetail(item.id)
|
||||
const { formatDate } = useFormatDate()
|
||||
|
||||
const manifest = detail?.manifest ?? null
|
||||
const permissions = manifest?.permissions ?? []
|
||||
@@ -910,7 +913,7 @@ function PluginDetailModal({ item, installed, busy, onInstall, onClose, t, local
|
||||
{(item.trek || item.minTrekVersion) && (
|
||||
<Meta k={t('admin.plugins.metaRequires')} v={item.trek ? `TREK ${item.trek}` : `TREK ${item.minTrekVersion}+`} />
|
||||
)}
|
||||
{item.reviewedAt && <Meta k={t('admin.plugins.metaReviewed')} v={new Date(item.reviewedAt).toLocaleDateString(locale)} />}
|
||||
{item.reviewedAt && <Meta k={t('admin.plugins.metaReviewed')} v={formatDate(new Date(item.reviewedAt))} />}
|
||||
{typeof item.downloadCount === 'number' && item.downloadCount > 0 && (
|
||||
<Meta k={t('admin.plugins.downloads')} v={item.downloadCount.toLocaleString(locale)} />
|
||||
)}
|
||||
@@ -934,7 +937,7 @@ function PluginDetailModal({ item, installed, busy, onInstall, onClose, t, local
|
||||
) : (
|
||||
<span className="text-content-faint" style={fs(11.5)}>{t('admin.plugins.versionNeedsTrek', { range: v.trek ?? '' })}</span>
|
||||
)}>
|
||||
<VersionLine v={v} locale={locale} />
|
||||
<VersionLine v={v} />
|
||||
</DialogListItem>
|
||||
))}
|
||||
</DialogList>
|
||||
@@ -944,17 +947,6 @@ function PluginDetailModal({ item, installed, busy, onInstall, onClose, t, local
|
||||
)
|
||||
}
|
||||
|
||||
/** A version number with its date and, when the author signed it, a quiet tick. */
|
||||
function VersionLine({ v, locale }: { v: VersionInfo; locale: string }) {
|
||||
return (
|
||||
<span className="flex flex-wrap items-center gap-x-2.5 gap-y-0.5">
|
||||
<span className={`font-geist font-semibold tabular-nums ${v.compatible ? 'text-content' : 'text-content-faint'}`} style={fs(12.5)}>v{v.version}</span>
|
||||
{v.publishedAt && <span className="text-content-faint" style={fs(11.5)}>{new Date(v.publishedAt).toLocaleDateString(locale)}</span>}
|
||||
{v.signed && <ShieldCheck size={13} className="flex-none text-success" />}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
function Meta({ k, v }: { k: string; v: string }) {
|
||||
return (
|
||||
<div className="min-w-0 rounded-[12px] border border-edge-faint bg-surface-card px-3 py-2.5">
|
||||
@@ -970,9 +962,9 @@ function Meta({ k, v }: { k: string; v: string }) {
|
||||
* caller routes a DOWNGRADE through the data-risk confirm), an incompatible one is
|
||||
* explained and never offered.
|
||||
*/
|
||||
function VersionPickerDialog({ plugin, versions, failed, busy, t, locale, onPick, onClose }: {
|
||||
function VersionPickerDialog({ plugin, versions, failed, busy, t, onPick, onClose }: {
|
||||
plugin: PluginRow; versions: VersionInfo[] | null; failed: boolean; busy: string | null
|
||||
t: T; locale: string; onPick: (version: string) => void; onClose: () => void
|
||||
t: T; onPick: (version: string) => void; onClose: () => void
|
||||
}) {
|
||||
return (
|
||||
<DialogShell
|
||||
@@ -994,7 +986,7 @@ function VersionPickerDialog({ plugin, versions, failed, busy, t, locale, onPick
|
||||
const current = v.version === plugin.version
|
||||
return (
|
||||
<div key={v.version} data-testid={`version-row-${v.version}`} className="flex items-center gap-3 px-3.5 py-2.5">
|
||||
<div className="min-w-0 flex-1"><VersionLine v={v} locale={locale} /></div>
|
||||
<div className="min-w-0 flex-1"><VersionLine v={v} /></div>
|
||||
{current ? (
|
||||
<StatusPill tone="success" icon={<Check size={11} />}>{t('admin.plugins.installed')}</StatusPill>
|
||||
) : v.compatible ? (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// FE-ADMIN-DUS-001 to FE-ADMIN-DUS-029
|
||||
// FE-ADMIN-DUS-001 to FE-ADMIN-DUS-031
|
||||
import { render, screen, waitFor, within, fireEvent } from '../../../tests/helpers/render';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { http, HttpResponse } from 'msw';
|
||||
@@ -70,7 +70,7 @@ function cartoInput(): HTMLInputElement {
|
||||
}
|
||||
|
||||
/** Opens a CustomSelect by its trigger label and picks an option from the portal. */
|
||||
async function pickFromSelect(user: ReturnType<typeof userEvent.setup>, trigger: string, option: string) {
|
||||
async function pickFromSelect(user: ReturnType<typeof userEvent.setup>, trigger: string, option: string | RegExp) {
|
||||
await user.click(screen.getByRole('button', { name: trigger }));
|
||||
const choices = await screen.findAllByRole('button', { name: option });
|
||||
await user.click(choices[choices.length - 1]);
|
||||
@@ -481,3 +481,34 @@ describe('DefaultUserSettingsTab week start (#2029)', () => {
|
||||
await waitFor(() => expect(puts).toEqual([{ week_start: 'sunday' }, { week_start: null }]));
|
||||
});
|
||||
});
|
||||
|
||||
describe('DefaultUserSettingsTab date format (#2971)', () => {
|
||||
beforeEach(() => {
|
||||
resetAllStores();
|
||||
seedStore(useAuthStore, { isAuthenticated: true, user: buildAdmin() });
|
||||
});
|
||||
|
||||
it('FE-ADMIN-DUS-030: the date-format default saves its own key', async () => {
|
||||
const user = userEvent.setup();
|
||||
const { puts } = stubDefaults({ date_format: 'YYYY-MM-DD' });
|
||||
render(<DefaultUserSettingsTab />);
|
||||
await screen.findByText('Default User Settings');
|
||||
|
||||
// The choices are labelled by how they write 31 December.
|
||||
await pickFromSelect(user, '2026-12-31', '31.12.2026');
|
||||
|
||||
await waitFor(() => expect(puts).toEqual([{ date_format: 'DD.MM.YYYY' }]));
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '31.12.2026' })).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('FE-ADMIN-DUS-031: auto is offered with the example of the interface language', async () => {
|
||||
const user = userEvent.setup();
|
||||
const { puts } = stubDefaults({ date_format: 'DD/MM/YYYY' });
|
||||
render(<DefaultUserSettingsTab />);
|
||||
await screen.findByText('Default User Settings');
|
||||
|
||||
await pickFromSelect(user, '31/12/2026', /\(12\/31\/2026\)/);
|
||||
|
||||
await waitFor(() => expect(puts).toEqual([{ date_format: 'auto' }]));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import { MapView } from '../Map/MapView'
|
||||
import { SYMBOLS, currenciesWith } from '../Budget/BudgetPanel.constants'
|
||||
import type { DistanceUnit, WeekStart } from '../../types'
|
||||
import { weekStartOptions } from '../../utils/calendarWeek'
|
||||
import { dateFormatOptions } from '../../utils/dateFormat'
|
||||
import { withTileApiKey } from '../../utils/tileUrl'
|
||||
import { defaultStyleForProvider } from '../Map/glProviders'
|
||||
import type { MapProvider } from '../Settings/mapSettingsModel'
|
||||
@@ -66,6 +67,27 @@ function errorText(err: unknown, fallback: string): string {
|
||||
return err instanceof Error ? err.message : fallback
|
||||
}
|
||||
|
||||
interface ResetProps {
|
||||
shown: boolean
|
||||
onReset: () => void
|
||||
label: string
|
||||
}
|
||||
|
||||
// Inside the row's own <label>, after the name: the link only shows once the
|
||||
// default is set, and the name stays the label's text.
|
||||
function ResetButton({ shown, onReset, label }: ResetProps): React.ReactElement | null {
|
||||
if (!shown) return null
|
||||
return (
|
||||
<button type="button"
|
||||
onClick={onReset}
|
||||
className="ms-2 align-baseline font-geist font-medium normal-case tracking-normal text-content-muted underline decoration-edge underline-offset-2 hover:text-content"
|
||||
style={fs(11)}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default function DefaultUserSettingsTab(): React.ReactElement {
|
||||
const { t, locale } = useTranslation()
|
||||
const {
|
||||
@@ -74,18 +96,11 @@ export default function DefaultUserSettingsTab(): React.ReactElement {
|
||||
saveMapProvider, pickTilePreset, pickStylePreset, commitStyle,
|
||||
} = useDefaultUserSettings({ errorMessage: errorText, previewCreatedAt: () => String(new Date()) })
|
||||
|
||||
// Inside the row's own <label>, after the name: the link only shows once the
|
||||
// default is set, and the name stays the label's text.
|
||||
const ResetButton = ({ field }: { field: keyof Defaults }) =>
|
||||
isSet(field) ? (
|
||||
<button type="button"
|
||||
onClick={() => reset(field)}
|
||||
className="ms-2 align-baseline font-geist font-medium normal-case tracking-normal text-content-muted underline decoration-edge underline-offset-2 hover:text-content"
|
||||
style={fs(11)}
|
||||
>
|
||||
{t('admin.defaultSettings.resetToBuiltIn')}
|
||||
</button>
|
||||
) : null
|
||||
const resetOf = (field: keyof Defaults): ResetProps => ({
|
||||
shown: isSet(field),
|
||||
onReset: () => reset(field),
|
||||
label: t('admin.defaultSettings.resetToBuiltIn'),
|
||||
})
|
||||
|
||||
if (!loaded) {
|
||||
return (
|
||||
@@ -118,7 +133,7 @@ export default function DefaultUserSettingsTab(): React.ReactElement {
|
||||
<Section title={t('admin.defaultSettings.title')} icon={Settings2}>
|
||||
<SettingRows>
|
||||
{/* Color Mode */}
|
||||
<ChoiceRow label={<>{t('settings.colorMode')} <ResetButton field="dark_mode" /></>}>
|
||||
<ChoiceRow label={<>{t('settings.colorMode')} <ResetButton {...resetOf('dark_mode')} /></>}>
|
||||
<Segmented<string>
|
||||
label={t('settings.colorMode')}
|
||||
value={colorModeValue(darkMode)}
|
||||
@@ -132,7 +147,7 @@ export default function DefaultUserSettingsTab(): React.ReactElement {
|
||||
</ChoiceRow>
|
||||
|
||||
{/* Temperature */}
|
||||
<ChoiceRow label={<>{t('settings.temperature')} <ResetButton field="temperature_unit" /></>}>
|
||||
<ChoiceRow label={<>{t('settings.temperature')} <ResetButton {...resetOf('temperature_unit')} /></>}>
|
||||
<Segmented<string>
|
||||
label={t('settings.temperature')}
|
||||
value={defaults.temperature_unit ?? ''}
|
||||
@@ -145,7 +160,7 @@ export default function DefaultUserSettingsTab(): React.ReactElement {
|
||||
</ChoiceRow>
|
||||
|
||||
{/* Distance */}
|
||||
<ChoiceRow label={<>{t('settings.distance')} <ResetButton field="distance_unit" /></>}>
|
||||
<ChoiceRow label={<>{t('settings.distance')} <ResetButton {...resetOf('distance_unit')} /></>}>
|
||||
<Segmented<DistanceUnit | ''>
|
||||
label={t('settings.distance')}
|
||||
value={defaults.distance_unit ?? ''}
|
||||
@@ -158,7 +173,7 @@ export default function DefaultUserSettingsTab(): React.ReactElement {
|
||||
</ChoiceRow>
|
||||
|
||||
{/* Time Format */}
|
||||
<ChoiceRow label={<>{t('settings.timeFormat')} <ResetButton field="time_format" /></>}>
|
||||
<ChoiceRow label={<>{t('settings.timeFormat')} <ResetButton {...resetOf('time_format')} /></>}>
|
||||
<Segmented<string>
|
||||
label={t('settings.timeFormat')}
|
||||
value={defaults.time_format ?? ''}
|
||||
@@ -171,7 +186,7 @@ export default function DefaultUserSettingsTab(): React.ReactElement {
|
||||
</ChoiceRow>
|
||||
|
||||
{/* Week start: the first column of every date picker (#2029) */}
|
||||
<ChoiceRow label={<>{t('settings.weekStart')} <ResetButton field="week_start" /></>}>
|
||||
<ChoiceRow label={<>{t('settings.weekStart')} <ResetButton {...resetOf('week_start')} /></>}>
|
||||
<Segmented<WeekStart | ''>
|
||||
label={t('settings.weekStart')}
|
||||
value={defaults.week_start ?? ''}
|
||||
@@ -180,8 +195,19 @@ export default function DefaultUserSettingsTab(): React.ReactElement {
|
||||
/>
|
||||
</ChoiceRow>
|
||||
|
||||
{/* Date format: every date field and numeric date (#2971) */}
|
||||
<ChoiceRow label={<>{t('settings.dateFormat')} <ResetButton {...resetOf('date_format')} /></>}>
|
||||
<div className="w-[220px] max-w-full">
|
||||
<CustomSelect
|
||||
value={defaults.date_format ?? ''}
|
||||
onChange={value => { if (value) void save({ date_format: String(value) as Defaults['date_format'] }) }}
|
||||
options={dateFormatOptions(locale, t('settings.dateFormatAuto'))}
|
||||
/>
|
||||
</div>
|
||||
</ChoiceRow>
|
||||
|
||||
{/* Default Currency */}
|
||||
<ChoiceRow label={<>{t('settings.currency')} <ResetButton field="default_currency" /></>}>
|
||||
<ChoiceRow label={<>{t('settings.currency')} <ResetButton {...resetOf('default_currency')} /></>}>
|
||||
<div className="w-[220px] max-w-full">
|
||||
<CustomSelect
|
||||
value={defaults.default_currency || ''}
|
||||
@@ -194,7 +220,7 @@ export default function DefaultUserSettingsTab(): React.ReactElement {
|
||||
</ChoiceRow>
|
||||
|
||||
{/* Blur Booking Codes */}
|
||||
<ChoiceRow label={<>{t('settings.blurBookingCodes')} <ResetButton field="blur_booking_codes" /></>}>
|
||||
<ChoiceRow label={<>{t('settings.blurBookingCodes')} <ResetButton {...resetOf('blur_booking_codes')} /></>}>
|
||||
<Segmented<string>
|
||||
label={t('settings.blurBookingCodes')}
|
||||
value={onOffValue(defaults.blur_booking_codes)}
|
||||
@@ -211,7 +237,7 @@ export default function DefaultUserSettingsTab(): React.ReactElement {
|
||||
|
||||
<Section title={t('settings.map')} icon={MapIcon}>
|
||||
{/* Map Tile URL */}
|
||||
<EditorField label={<>{t('settings.mapTemplate')}<ResetButton field="map_tile_url" /></>} hint={t('settings.mapDefaultHint')}>
|
||||
<EditorField label={<>{t('settings.mapTemplate')}<ResetButton {...resetOf('map_tile_url')} /></>} hint={t('settings.mapDefaultHint')}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<CustomSelect
|
||||
value={mapTileUrl}
|
||||
@@ -232,7 +258,7 @@ export default function DefaultUserSettingsTab(): React.ReactElement {
|
||||
{/* The key comes with the instance on a managed install, injected when the
|
||||
settings are read. A field here would only let somebody save a worse one. */}
|
||||
{!managed && (
|
||||
<EditorField label={<>{t('admin.defaultSettings.cartoKey')}<ResetButton field="carto_api_key" /></>} hint={t('admin.defaultSettings.cartoKeyHint')}>
|
||||
<EditorField label={<>{t('admin.defaultSettings.cartoKey')}<ResetButton {...resetOf('carto_api_key')} /></>} hint={t('admin.defaultSettings.cartoKeyHint')}>
|
||||
<input
|
||||
type="text"
|
||||
value={cartoKey}
|
||||
@@ -273,7 +299,7 @@ export default function DefaultUserSettingsTab(): React.ReactElement {
|
||||
<SettingRows>
|
||||
<ChoiceRow
|
||||
stacked
|
||||
label={<>{t('admin.defaultSettings.mapProvider')} <ResetButton field="map_provider" /></>}
|
||||
label={<>{t('admin.defaultSettings.mapProvider')} <ResetButton {...resetOf('map_provider')} /></>}
|
||||
hint={t('admin.defaultSettings.mapProviderHint')}
|
||||
>
|
||||
<Segmented<MapProvider>
|
||||
@@ -293,7 +319,7 @@ export default function DefaultUserSettingsTab(): React.ReactElement {
|
||||
settings are read. A field here would only let somebody save a worse one. */}
|
||||
{mapProvider === 'mapbox-gl' && !managed && (
|
||||
<div className={FIELD_ROW}>
|
||||
<EditorField label={<>{t('admin.defaultSettings.mapboxToken')}<ResetButton field="mapbox_access_token" /></>} hint={t('admin.defaultSettings.mapboxTokenHint')}>
|
||||
<EditorField label={<>{t('admin.defaultSettings.mapboxToken')}<ResetButton {...resetOf('mapbox_access_token')} /></>} hint={t('admin.defaultSettings.mapboxTokenHint')}>
|
||||
<input
|
||||
type="text"
|
||||
value={mapboxToken}
|
||||
@@ -310,7 +336,7 @@ export default function DefaultUserSettingsTab(): React.ReactElement {
|
||||
|
||||
{mapProvider !== 'leaflet' && (
|
||||
<div className={FIELD_ROW}>
|
||||
<EditorField label={<>{t('admin.defaultSettings.mapboxStyle')}<ResetButton field={styleKey} /></>}>
|
||||
<EditorField label={<>{t('admin.defaultSettings.mapboxStyle')}<ResetButton {...resetOf(styleKey)} /></>}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<CustomSelect
|
||||
value={mapboxStyle}
|
||||
@@ -333,7 +359,7 @@ export default function DefaultUserSettingsTab(): React.ReactElement {
|
||||
|
||||
{mapProvider === 'mapbox-gl' && (
|
||||
<>
|
||||
<ChoiceRow label={<>{t('admin.defaultSettings.mapbox3d')} <ResetButton field="mapbox_3d_enabled" /></>}>
|
||||
<ChoiceRow label={<>{t('admin.defaultSettings.mapbox3d')} <ResetButton {...resetOf('mapbox_3d_enabled')} /></>}>
|
||||
<Segmented<string>
|
||||
label={t('admin.defaultSettings.mapbox3d')}
|
||||
value={onOffValue(defaults.mapbox_3d_enabled ?? true)}
|
||||
@@ -345,7 +371,7 @@ export default function DefaultUserSettingsTab(): React.ReactElement {
|
||||
/>
|
||||
</ChoiceRow>
|
||||
|
||||
<ChoiceRow label={<>{t('admin.defaultSettings.mapboxQuality')} <ResetButton field="mapbox_quality_mode" /></>}>
|
||||
<ChoiceRow label={<>{t('admin.defaultSettings.mapboxQuality')} <ResetButton {...resetOf('mapbox_quality_mode')} /></>}>
|
||||
<Segmented<string>
|
||||
label={t('admin.defaultSettings.mapboxQuality')}
|
||||
value={onOffValue(defaults.mapbox_quality_mode ?? false)}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ShieldCheck } from 'lucide-react';
|
||||
import { useFormatDate } from '../../../hooks/useDateFormat';
|
||||
import { fs } from '../../shared/DialogShell';
|
||||
import type { VersionInfo } from './pluginModel';
|
||||
|
||||
/** A version number with its date and, when the author signed it, a quiet tick. */
|
||||
export default function VersionLine({ v }: { v: VersionInfo }) {
|
||||
const { formatDate } = useFormatDate();
|
||||
return (
|
||||
<span className="flex flex-wrap items-center gap-x-2.5 gap-y-0.5">
|
||||
<span
|
||||
className={`font-geist font-semibold tabular-nums ${v.compatible ? 'text-content' : 'text-content-faint'}`}
|
||||
style={fs(12.5)}
|
||||
>
|
||||
v{v.version}
|
||||
</span>
|
||||
{v.publishedAt && (
|
||||
<span className="text-content-faint" style={fs(11.5)}>
|
||||
{formatDate(new Date(v.publishedAt))}
|
||||
</span>
|
||||
)}
|
||||
{v.signed && <ShieldCheck size={13} className="flex-none text-success" />}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { adminApi } from '../../api/client';
|
||||
import { useTranslation } from '../../i18n';
|
||||
import { useFormatDate } from '../../hooks/useDateFormat';
|
||||
|
||||
export interface AuditEntry {
|
||||
id: number;
|
||||
@@ -41,7 +41,7 @@ export function auditUserLabel(e: AuditEntry): string {
|
||||
* cards render their own markup over it.
|
||||
*/
|
||||
export function useAuditLog(serverTimezone?: string) {
|
||||
const { locale } = useTranslation();
|
||||
const { formatDateTime } = useFormatDate();
|
||||
const [entries, setEntries] = useState<AuditEntry[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -92,7 +92,7 @@ export function useAuditLog(serverTimezone?: string) {
|
||||
|
||||
const fmtTime = (iso: string) => {
|
||||
try {
|
||||
return new Date(iso.endsWith('Z') ? iso : iso + 'Z').toLocaleString(locale, {
|
||||
return formatDateTime(new Date(iso.endsWith('Z') ? iso : iso + 'Z'), {
|
||||
dateStyle: 'short',
|
||||
timeStyle: 'medium',
|
||||
timeZone: serverTimezone || undefined,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { type ChangeEvent, useEffect, useEffectEvent, useRef, useState } from 'react';
|
||||
|
||||
import { backupApi } from '../../api/client';
|
||||
import { useFormatDate } from '../../hooks/useDateFormat';
|
||||
import { useTranslation } from '../../i18n';
|
||||
import { useSettingsStore } from '../../store/settingsStore';
|
||||
import { getApiErrorMessage } from '../../types';
|
||||
@@ -104,7 +105,8 @@ export function useBackupAdmin({ keepSettingsWhenMissing = false }: Options = {}
|
||||
const [deleteTarget, setDeleteTarget] = useState<string | null>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const toast = useToast();
|
||||
const { t, locale } = useTranslation();
|
||||
const { t } = useTranslation();
|
||||
const { formatDateTime } = useFormatDate();
|
||||
const is12h = useSettingsStore((s) => s.settings.time_format) === '12h';
|
||||
|
||||
const loadBackups = async () => {
|
||||
@@ -241,7 +243,7 @@ export function useBackupAdmin({ keepSettingsWhenMissing = false }: Options = {}
|
||||
minute: '2-digit',
|
||||
};
|
||||
if (serverTimezone) opts.timeZone = serverTimezone;
|
||||
return new Date(dateStr).toLocaleString(locale, opts);
|
||||
return formatDateTime(new Date(dateStr), opts);
|
||||
} catch {
|
||||
return dateStr;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { adminApi } from '../../api/client';
|
||||
import { useTranslation } from '../../i18n';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
import { useSettingsStore } from '../../store/settingsStore';
|
||||
import type { DistanceUnit, Place, WeekStart } from '../../types';
|
||||
import type { DateFormat, DistanceUnit, Place, WeekStart } from '../../types';
|
||||
import { normalizeTileUrl } from '../../utils/tileUrl';
|
||||
import {
|
||||
defaultStyleForProvider,
|
||||
@@ -28,6 +28,7 @@ export type Defaults = RoutingDefaults & {
|
||||
dark_mode?: string | boolean;
|
||||
time_format?: string;
|
||||
week_start?: WeekStart;
|
||||
date_format?: DateFormat;
|
||||
default_currency?: string;
|
||||
blur_booking_codes?: boolean;
|
||||
map_tile_url?: string;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { BudgetParticipantFinal, BudgetUnconverted, CostCategory } from '@trek/shared';
|
||||
import { readDateFormat } from '../../hooks/useDateFormat';
|
||||
import { convertBooked, convertedLine, inDisplayCurrency, tripAmountOf } from '../../hooks/useExchangeRates';
|
||||
import type { BudgetItem } from '../../types';
|
||||
import { formatDisplayDate } from '../../utils/dateFormat';
|
||||
import { currencyDecimals } from '../../utils/formatters';
|
||||
import { currenciesWith, SYMBOLS } from './BudgetPanel.constants';
|
||||
import { paidByUser, readUserNote, settlementDate, splitEqualShares } from './CostsPanel.helpers';
|
||||
@@ -378,12 +380,17 @@ export function buildCostsCsv(items: BudgetItem[], opts: CsvBuildOptions): { fil
|
||||
const fmtDate = (iso: string) => {
|
||||
if (!iso) return '';
|
||||
try {
|
||||
return new Date(iso + 'T00:00:00Z').toLocaleDateString(opts.locale, {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
timeZone: 'UTC',
|
||||
});
|
||||
return formatDisplayDate(
|
||||
new Date(iso + 'T00:00:00Z'),
|
||||
opts.locale,
|
||||
{
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
timeZone: 'UTC',
|
||||
},
|
||||
readDateFormat()
|
||||
);
|
||||
} catch {
|
||||
return iso;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Gauge, Hourglass, CircleDashed, Star } from 'lucide-react'
|
||||
import type { DawarichAccept, DawarichSuggestion, DawarichSuggestionTarget } from '@trek/shared'
|
||||
import { DAWARICH_BUCKET_MATCH_MIN_MINUTES, DAWARICH_BUCKET_MATCH_RADIUS_M } from '@trek/shared'
|
||||
import { useTranslation } from '../../i18n'
|
||||
import { useFormatDate } from '../../hooks/useDateFormat'
|
||||
import { DialogButton, DialogFooter, DialogHeader, DialogShell, FooterSpacer, NEUTRAL_TINT } from '../shared/DialogShell'
|
||||
import MSheet from '../../mobile/components/MSheet'
|
||||
import { FormSheetFooter, FormSheetHeader } from '../../mobile/screens/trip/sheets/PlSheetChrome'
|
||||
@@ -64,6 +65,7 @@ export default function DawarichAcceptDialog({
|
||||
onConfirm,
|
||||
}: DawarichAcceptDialogProps): React.ReactElement | null {
|
||||
const { t, locale } = useTranslation()
|
||||
const { formatDateTime } = useFormatDate()
|
||||
const phone = useIsPhone()
|
||||
const labelId = useId()
|
||||
|
||||
@@ -140,7 +142,7 @@ export default function DawarichAcceptDialog({
|
||||
</span>
|
||||
<p className="min-w-0 flex-1 text-caption text-content">
|
||||
{t('dawarich.accept.recorded', {
|
||||
from: new Date(suggestion.startedAt).toLocaleString(locale),
|
||||
from: formatDateTime(new Date(suggestion.startedAt)),
|
||||
to: new Date(suggestion.endedAt).toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit' }),
|
||||
})}
|
||||
</p>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { FileText, FileImage, File, FileVideo, Plane, Train, Car, Ship, Bus, Sailboat, CableCar, Bike, CarTaxiFront, Route } from 'lucide-react'
|
||||
import { downloadFile } from '../../utils/fileDownload'
|
||||
import { readDateFormat } from '../../hooks/useDateFormat'
|
||||
import { formatDisplayDate } from '../../utils/dateFormat'
|
||||
|
||||
export function isImage(mimeType?: string | null) {
|
||||
if (!mimeType) return false
|
||||
@@ -57,10 +59,10 @@ export function triggerDownload(url: string, filename: string) {
|
||||
downloadFile(url, filename).catch(() => {})
|
||||
}
|
||||
|
||||
export function formatDateWithLocale(dateStr?: string | null, locale?: string) {
|
||||
export function formatDateWithLocale(dateStr?: string | null, locale?: string, dateFormat: string = readDateFormat()) {
|
||||
if (!dateStr) return ''
|
||||
try {
|
||||
return new Date(dateStr).toLocaleDateString(locale, { day: '2-digit', month: '2-digit', year: 'numeric' })
|
||||
return formatDisplayDate(new Date(dateStr), locale, { day: '2-digit', month: '2-digit', year: 'numeric' }, dateFormat)
|
||||
} catch { return '' }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { useTranslation } from '../../../i18n/TranslationContext'
|
||||
import { useFormatDate } from '../../../hooks/useDateFormat'
|
||||
import { relativeTime } from '../../../utils/relativeTime'
|
||||
import { Tooltip } from '../../shared/Tooltip'
|
||||
|
||||
@@ -83,11 +84,12 @@ export function StateBadge({ state, compact }: { state: string; compact?: boolea
|
||||
|
||||
/** "2h ago", or "never" when a binding has not run yet. */
|
||||
export function LastRun({ at }: { at: string | null }) {
|
||||
const { t, language, locale } = useTranslation()
|
||||
const { t, language } = useTranslation()
|
||||
const { formatDateTime } = useFormatDate()
|
||||
if (!at) return <span className="text-content-faint">{t('docsync.binding.neverRun')}</span>
|
||||
const ms = Date.parse(at)
|
||||
if (Number.isNaN(ms)) return null
|
||||
return <Tooltip label={new Date(ms).toLocaleString(locale)}><span>{relativeTime(ms, language)}</span></Tooltip>
|
||||
return <Tooltip label={formatDateTime(new Date(ms))}><span>{relativeTime(ms, language)}</span></Tooltip>
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,8 @@ import { airtrailApi } from '../../api/client'
|
||||
import { useTripStore } from '../../store/tripStore'
|
||||
import { isEffectivelyOffline } from '../../sync/networkMode'
|
||||
import { parseReservationMetadata } from '../../utils/flightLegs'
|
||||
import { useDateFormat } from '../../hooks/useDateFormat'
|
||||
import { formatDisplayDate } from '../../utils/dateFormat'
|
||||
|
||||
interface AirTrailImportModalProps {
|
||||
isOpen: boolean
|
||||
@@ -52,15 +54,15 @@ export function detectConnections(flights: AirtrailFlight[]): AirtrailFlight[][]
|
||||
}
|
||||
|
||||
/** Locale-aware date (e.g. de → 13.06.2026, en-US → 06/13/2026). */
|
||||
function fmtDate(d: string | null, locale: string): string {
|
||||
function fmtDate(d: string | null, locale: string, dateFormat: string): string {
|
||||
if (!d) return ''
|
||||
try {
|
||||
return new Date(d + 'T00:00:00Z').toLocaleDateString(locale, {
|
||||
return formatDisplayDate(new Date(d + 'T00:00:00Z'), locale, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
timeZone: 'UTC',
|
||||
})
|
||||
}, dateFormat)
|
||||
} catch {
|
||||
return d
|
||||
}
|
||||
@@ -68,6 +70,7 @@ function fmtDate(d: string | null, locale: string): string {
|
||||
|
||||
export default function AirTrailImportModal({ isOpen, onClose, tripId, pushUndo }: AirTrailImportModalProps) {
|
||||
const { t, locale } = useTranslation()
|
||||
const dateFormat = useDateFormat()
|
||||
const toast = useToast()
|
||||
const trip = useTripStore(s => s.trip)
|
||||
const reservations = useTripStore(s => s.reservations)
|
||||
@@ -270,7 +273,7 @@ export default function AirTrailImportModal({ isOpen, onClose, tripId, pushUndo
|
||||
<span style={{ flex: 1, minWidth: 0 }}>
|
||||
<span style={{ display: 'block', fontSize: 'calc(13px * var(--fs-scale-body, 1))', fontWeight: 600, color: 'var(--text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{label}</span>
|
||||
<span style={{ display: 'block', fontSize: 'calc(11px * var(--fs-scale-caption, 1))', color: 'var(--text-muted)' }}>
|
||||
{f.fromCode ?? f.fromName ?? '?'} → {f.toCode ?? f.toName ?? '?'}{f.date ? ` · ${fmtDate(f.date, locale)}` : ''}
|
||||
{f.fromCode ?? f.fromName ?? '?'} → {f.toCode ?? f.toName ?? '?'}{f.date ? ` · ${fmtDate(f.date, locale, dateFormat)}` : ''}
|
||||
</span>
|
||||
</span>
|
||||
{already && (
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { createPortal } from 'react-dom'
|
||||
import { CalendarDays, Pencil, Trash2, ChevronDown, Check, Eye } from 'lucide-react'
|
||||
import type { SidebarState } from './usePlacesSidebar'
|
||||
import { useDateFormat } from '../../hooks/useDateFormat'
|
||||
import { formatDisplayDate } from '../../utils/dateFormat'
|
||||
|
||||
export function MobileDayPickerSheet(S: SidebarState) {
|
||||
const {
|
||||
dayPickerPlace, setDayPickerPlace, setMobileShowDays, onPlaceClick, canEditPlaces, onEditPlace,
|
||||
t, days, mobileShowDays, onAssignToDay, assignments, onDeletePlace,
|
||||
} = S
|
||||
const dateFormat = useDateFormat()
|
||||
return createPortal(
|
||||
<div
|
||||
role="presentation"
|
||||
@@ -64,7 +67,7 @@ export function MobileDayPickerSheet(S: SidebarState) {
|
||||
<div className="bg-surface-tertiary text-content" style={{ width: 28, height: 28, borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 'calc(12px * var(--fs-scale-body, 1))', fontWeight: 700, flexShrink: 0 }}>{i + 1}</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div className="text-content" style={{ fontSize: 'calc(13px * var(--fs-scale-body, 1))', fontWeight: 500 }}>{day.title || t('dayplan.dayN', { n: i + 1 })}</div>
|
||||
{day.date && <div className="text-content-faint" style={{ fontSize: 'calc(11px * var(--fs-scale-caption, 1))' }}>{new Date(day.date + 'T00:00:00Z').toLocaleDateString(undefined, { timeZone: 'UTC' })}</div>}
|
||||
{day.date && <div className="text-content-faint" style={{ fontSize: 'calc(11px * var(--fs-scale-caption, 1))' }}>{formatDisplayDate(new Date(day.date + 'T00:00:00Z'), undefined, { timeZone: 'UTC' }, dateFormat)}</div>}
|
||||
</div>
|
||||
{(assignments[String(day.id)] || []).some(a => a.place?.id === dayPickerPlace.id) && <Check size={14} color="var(--text-faint)" />}
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { cheapestEnergy, type ChargingEnergyPrice, type ChargingInfo as Info } from '@trek/shared';
|
||||
import { ChevronDown, Coins, PlugZap } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useFormatDate } from '../../hooks/useDateFormat';
|
||||
import { useTranslation } from '../../i18n/TranslationContext';
|
||||
import { chargingRepo } from '../../repo/chargingRepo';
|
||||
import { useTripStore } from '../../store/tripStore';
|
||||
@@ -76,6 +77,7 @@ export default function ChargingInfo({
|
||||
}: ChargingTarget & { compact?: boolean }) {
|
||||
const tripId = useTripStore((s) => s.trip?.id);
|
||||
const { t } = useTranslation();
|
||||
const { formatDateTime } = useFormatDate();
|
||||
const [info, setInfo] = useState<Info | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
useEffect(() => {
|
||||
@@ -133,7 +135,7 @@ export default function ChargingInfo({
|
||||
const cheapest = info ? cheapestEnergy(info.tariffs) : null;
|
||||
const price = (value: number, currency: string) =>
|
||||
new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(value);
|
||||
const label = `${status}${info?.updatedAt ? ` (${new Date(info.updatedAt).toLocaleString()})` : ''}`;
|
||||
const label = `${status}${info?.updatedAt ? ` (${formatDateTime(new Date(info.updatedAt))})` : ''}`;
|
||||
if (compact)
|
||||
return (
|
||||
<ChargingBadges
|
||||
@@ -211,7 +213,7 @@ export default function ChargingInfo({
|
||||
</a>
|
||||
<br />
|
||||
{info.license}
|
||||
{info.updatedAt && ` · ${new Date(info.updatedAt).toLocaleString()}`}
|
||||
{info.updatedAt && ` · ${formatDateTime(new Date(info.updatedAt))}`}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,8 @@ import { DialogButton, DialogFooter, DialogHeader, DialogShell, DialogTile, Foot
|
||||
import { EditorField, GRID_2, INPUT, LABEL } from '../shared/dialogParts'
|
||||
import { SETTINGS_BUTTON_PRIMARY, SETTINGS_ICON_BUTTON, SettingsHint, StatusPill } from './settingsKit'
|
||||
import { useTranslation } from '../../i18n'
|
||||
import { useDateFormat } from '../../hooks/useDateFormat'
|
||||
import { formatDisplayDate } from '../../utils/dateFormat'
|
||||
import { useToast } from '../shared/Toast'
|
||||
import { authApi } from '../../api/client'
|
||||
|
||||
@@ -55,10 +57,10 @@ const SCOPE_ICONS: Record<PublicApiScope, LucideIcon> = {
|
||||
}
|
||||
|
||||
/** SQLite hands out "YYYY-MM-DD HH:MM:SS" in UTC, which Safari will not parse as it stands. */
|
||||
function formatStamp(ts: string, locale: string): string {
|
||||
function formatStamp(ts: string, locale: string, dateFormat: string): string {
|
||||
const iso = ts.includes('T') ? ts : ts.replace(' ', 'T')
|
||||
const d = new Date(/(Z|[+-]\d{2}:?\d{2})$/.test(iso) ? iso : `${iso}Z`)
|
||||
return Number.isNaN(d.getTime()) ? ts : d.toLocaleDateString(locale)
|
||||
return Number.isNaN(d.getTime()) ? ts : formatDisplayDate(d, locale, {}, dateFormat)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -380,6 +382,7 @@ export default function ApiKeysSection(): React.ReactElement {
|
||||
|
||||
function ApiKeyRow({ apiKey, locale, onDelete }: { apiKey: ApiKey; locale: string; onDelete: () => void }): React.ReactElement {
|
||||
const { t } = useTranslation()
|
||||
const dateFormat = useDateFormat()
|
||||
const scopes = apiKey.scope_mode === 'limited' ? apiKey.scopes : undefined
|
||||
return (
|
||||
<li className="flex items-start gap-3 px-3.5 py-3">
|
||||
@@ -394,10 +397,10 @@ function ApiKeyRow({ apiKey, locale, onDelete }: { apiKey: ApiKey; locale: strin
|
||||
</code>
|
||||
</div>
|
||||
<p className="m-0 mt-0.5 tabular-nums text-content-faint first-letter:uppercase" style={fs(11.5)}>
|
||||
{t('settings.apiKeys.createdAt')} {formatStamp(apiKey.created_at, locale)}
|
||||
{t('settings.apiKeys.createdAt')} {formatStamp(apiKey.created_at, locale, dateFormat)}
|
||||
{', '}
|
||||
{apiKey.last_used_at
|
||||
? `${t('settings.apiKeys.usedAt')} ${formatStamp(apiKey.last_used_at, locale)}`
|
||||
? `${t('settings.apiKeys.usedAt')} ${formatStamp(apiKey.last_used_at, locale, dateFormat)}`
|
||||
: t('settings.apiKeys.neverUsed')}
|
||||
</p>
|
||||
{/* What the key may read. Shown on the row rather than behind a detail
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react'
|
||||
import { Loader2, Save, Plug, RefreshCw, Unplug } from 'lucide-react'
|
||||
import { useTranslation } from '../../i18n'
|
||||
import { useFormatDate } from '../../hooks/useDateFormat'
|
||||
import { useDawarichConnection } from '../../hooks/useDawarichConnection'
|
||||
import DawarichIcon from '../shared/DawarichIcon'
|
||||
import Section from './Section'
|
||||
@@ -23,7 +24,7 @@ import { fs } from '../shared/DialogShell'
|
||||
* no reason is the version of this card that generates support questions.
|
||||
*/
|
||||
export default function DawarichConnectionSection(): React.ReactElement {
|
||||
const { t, locale } = useTranslation()
|
||||
const { t } = useTranslation()
|
||||
const S = useDawarichConnection()
|
||||
|
||||
return (
|
||||
@@ -123,7 +124,7 @@ export default function DawarichConnectionSection(): React.ReactElement {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DawarichConnectionStatus state={S} locale={locale} />
|
||||
<DawarichConnectionStatus state={S} />
|
||||
</Section>
|
||||
)
|
||||
}
|
||||
@@ -138,12 +139,11 @@ export default function DawarichConnectionSection(): React.ReactElement {
|
||||
*/
|
||||
function DawarichConnectionStatus({
|
||||
state,
|
||||
locale,
|
||||
}: {
|
||||
state: ReturnType<typeof useDawarichConnection>
|
||||
locale: string
|
||||
}): React.ReactElement | null {
|
||||
const { t } = useTranslation()
|
||||
const { formatDateTime } = useFormatDate()
|
||||
if (!state.connected && !state.probeMessage) return null
|
||||
|
||||
const missing: string[] = []
|
||||
@@ -161,7 +161,7 @@ function DawarichConnectionStatus({
|
||||
{state.connected && (
|
||||
<p className="m-0 text-content-secondary">
|
||||
{state.lastSyncAt
|
||||
? t('dawarich.lastSync', { when: new Date(state.lastSyncAt).toLocaleString(locale) })
|
||||
? t('dawarich.lastSync', { when: formatDateTime(new Date(state.lastSyncAt)) })
|
||||
: t('dawarich.neverSynced')}
|
||||
{state.lastSyncState === 'partial' && `, ${t('dawarich.syncPartial')}`}
|
||||
</p>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// FE-COMP-DISPLAY-001 to FE-COMP-DISPLAY-055
|
||||
// FE-COMP-DISPLAY-001 to FE-COMP-DISPLAY-057
|
||||
import { render, screen, within, fireEvent } from '../../../tests/helpers/render';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { http, HttpResponse } from 'msw';
|
||||
@@ -484,6 +484,35 @@ describe('DisplaySettingsTab week start (#2029)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ── Date format (056, 057) ────────────────────────────────────────────────────
|
||||
|
||||
describe('DisplaySettingsTab date format (#2971)', () => {
|
||||
it('FE-COMP-DISPLAY-056: shows the language format when unset and saves the format picked', async () => {
|
||||
const user = userEvent.setup();
|
||||
const updateSetting = vi.fn().mockResolvedValue(undefined);
|
||||
seedStore(useSettingsStore, { settings: buildSettings({ date_format: undefined }), updateSetting });
|
||||
render(<DisplaySettingsTab />);
|
||||
|
||||
// Each choice is labelled by how it writes 31 December; auto carries the language's own way.
|
||||
await user.click(screen.getByRole('button', { name: /\(12\/31\/2026\)/ }));
|
||||
await user.click(await screen.findByText('31.12.2026'));
|
||||
|
||||
expect(updateSetting).toHaveBeenCalledWith('date_format', 'DD.MM.YYYY');
|
||||
});
|
||||
|
||||
it('FE-COMP-DISPLAY-057: a rejected change surfaces the error', async () => {
|
||||
const user = userEvent.setup();
|
||||
const updateSetting = vi.fn().mockRejectedValue(new Error('Save failed'));
|
||||
seedStore(useSettingsStore, { settings: buildSettings({ date_format: 'YYYY-MM-DD' }), updateSetting });
|
||||
render(<><ToastContainer /><DisplaySettingsTab /></>);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /2026-12-31/ }));
|
||||
await user.click(await screen.findByText('31/12/2026'));
|
||||
|
||||
expect(await screen.findByText('Save failed')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('DisplaySettingsTab – the demo account', () => {
|
||||
it('FE-COMP-DISPLAY-DEMO-001: the shared demo account sees English under the demo lock', () => {
|
||||
seedStore(useAuthStore, { user: buildUser({ email: 'demo@trek.app' }), isAuthenticated: true, demoMode: true });
|
||||
|
||||
@@ -16,8 +16,9 @@ import { useSettingSaver } from './useSettingSaver'
|
||||
import { TRIP_TAB_IDS, TRIP_TAB_LABEL_KEYS } from '../../constants/tripTabs'
|
||||
import { DEFAULT_START_PAGE, DEFAULT_START_TRIP_TAB } from '../../utils/startDestination'
|
||||
import type { DistanceUnit } from '../../types'
|
||||
import { DEFAULT_WEEK_START } from '@trek/shared'
|
||||
import { DEFAULT_DATE_FORMAT, DEFAULT_WEEK_START } from '@trek/shared'
|
||||
import { weekStartOptions } from '../../utils/calendarWeek'
|
||||
import { dateFormatOptions } from '../../utils/dateFormat'
|
||||
import DemoLock from '../Demo/DemoLock'
|
||||
import { useDemoAccountLock } from '../Demo/useDemoLock'
|
||||
|
||||
@@ -265,6 +266,23 @@ export default function DisplaySettingsTab(): React.ReactElement {
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Date format: every date field and every date written in numbers (#2971) */}
|
||||
<SettingRow
|
||||
label={t('settings.dateFormat')}
|
||||
hint={t('settings.dateFormatHint')}
|
||||
control={
|
||||
<div className={SELECT_BOX}>
|
||||
<CustomSelect
|
||||
value={settings.date_format || DEFAULT_DATE_FORMAT}
|
||||
onChange={v => {
|
||||
void save('date_format', String(v))
|
||||
}}
|
||||
options={dateFormatOptions(locale, t('settings.dateFormatAuto'))}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</SettingRows>
|
||||
</Section>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Section from './Section'
|
||||
import React, { useId } from 'react'
|
||||
import { useTranslation } from '../../i18n'
|
||||
import { useFormatDate } from '../../hooks/useDateFormat'
|
||||
import { useToast } from '../shared/Toast'
|
||||
import { Trash2, Copy, Terminal, Plus, Check, Key, KeyRound, ChevronRight, RefreshCw, AlertTriangle, Link2 } from 'lucide-react'
|
||||
import PhotoProvidersSection from './PhotoProvidersSection'
|
||||
@@ -62,12 +63,13 @@ export default function IntegrationsTab(): React.ReactElement {
|
||||
|
||||
function useIntegrations() {
|
||||
const { t, locale } = useTranslation()
|
||||
const { formatDate } = useFormatDate()
|
||||
const toast = useToast()
|
||||
const { mcpEnabled, airtrailEnabled, llmEnabled, aiAssistantEnabled, aiConnectionEnabled, ownParsing, dawarichEnabled, wandererEnabled, managed } = useIntegrationGates({ reloadAddons: true })
|
||||
|
||||
const mcp = useMcpIntegration({ enabled: mcpEnabled, reloadClientsAfterWrite: false })
|
||||
|
||||
return { t, locale, toast, mcpEnabled, airtrailEnabled, llmEnabled, aiAssistantEnabled, aiConnectionEnabled, ownParsing, dawarichEnabled,
|
||||
return { t, locale, formatDate, toast, mcpEnabled, airtrailEnabled, llmEnabled, aiAssistantEnabled, aiConnectionEnabled, ownParsing, dawarichEnabled,
|
||||
wandererEnabled,
|
||||
managed, ...mcp,
|
||||
}
|
||||
@@ -171,7 +173,7 @@ function ConfigBlock({ open, onToggle, json, hint, copied, onCopy, t }: {
|
||||
|
||||
/** One registered OAuth client: what it is, the scopes it may use, and its two actions. */
|
||||
function OAuthClientRow({ client, S }: { client: OAuthClient; S: IntegrationsState }) {
|
||||
const { t, locale, oauthScopesExpanded, setOauthScopesExpanded, setOauthRotateId, setOauthDeleteId } = S
|
||||
const { t, formatDate, oauthScopesExpanded, setOauthScopesExpanded, setOauthRotateId, setOauthDeleteId } = S
|
||||
const expanded = !!oauthScopesExpanded[client.id]
|
||||
return (
|
||||
<div className={`${ROW} items-start`}>
|
||||
@@ -185,7 +187,7 @@ function OAuthClientRow({ client, S }: { client: OAuthClient; S: IntegrationsSta
|
||||
</div>
|
||||
<div className="mt-0.5 flex min-w-0 flex-wrap gap-x-3 text-content-faint" style={fs(11.5)}>
|
||||
<span className="min-w-0 truncate font-geist">{t('settings.oauth.clientId')}: {client.client_id}</span>
|
||||
<span className="tabular-nums">{t('settings.mcp.tokenCreatedAt')} {new Date(client.created_at).toLocaleDateString(locale)}</span>
|
||||
<span className="tabular-nums">{t('settings.mcp.tokenCreatedAt')} {formatDate(new Date(client.created_at))}</span>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-wrap gap-1" style={fs(10.5)}>
|
||||
{(expanded ? client.allowed_scopes : client.allowed_scopes.slice(0, 5)).map(s => (
|
||||
@@ -216,7 +218,7 @@ function OAuthClientRow({ client, S }: { client: OAuthClient; S: IntegrationsSta
|
||||
/** The OAuth tab: the snippet, the registered clients and the sessions they hold. */
|
||||
function OAuthTab({ S }: { S: IntegrationsState }) {
|
||||
const {
|
||||
t, locale, oauthClients, oauthSessions, setOauthCreateOpen, setOauthNewName, setOauthNewUris, setOauthNewScopes, setOauthCreatedClient, setOauthRevokeId, setOauthIsMachine, configOpenOAuth, setConfigOpenOAuth, copiedKey, mcpJsonConfigOAuth, handleCopy,
|
||||
t, formatDate, oauthClients, oauthSessions, setOauthCreateOpen, setOauthNewName, setOauthNewUris, setOauthNewScopes, setOauthCreatedClient, setOauthRevokeId, setOauthIsMachine, configOpenOAuth, setConfigOpenOAuth, copiedKey, mcpJsonConfigOAuth, handleCopy,
|
||||
} = S
|
||||
return (
|
||||
<>
|
||||
@@ -260,7 +262,7 @@ function OAuthTab({ S }: { S: IntegrationsState }) {
|
||||
<p className="m-0 truncate font-semibold text-content" style={fs(13, 'body')}>{session.client_name}</p>
|
||||
<p className="m-0 mt-0.5 flex min-w-0 flex-wrap gap-x-3 text-content-faint" style={fs(11.5)}>
|
||||
<span className="min-w-0 truncate">{t('settings.oauth.sessionScopes')}: {session.scopes.join(', ')}</span>
|
||||
<span className="tabular-nums">{t('settings.oauth.sessionExpires')} {new Date(session.access_token_expires_at).toLocaleDateString(locale)}</span>
|
||||
<span className="tabular-nums">{t('settings.oauth.sessionExpires')} {formatDate(new Date(session.access_token_expires_at))}</span>
|
||||
</p>
|
||||
</div>
|
||||
<button type="button" onClick={() => setOauthRevokeId(session.id)}
|
||||
@@ -278,7 +280,7 @@ function OAuthTab({ S }: { S: IntegrationsState }) {
|
||||
|
||||
/** The deprecated API tokens tab: the warning, the snippet and the tokens. */
|
||||
function ApiTokensTab({ S }: { S: IntegrationsState }) {
|
||||
const { t, locale, configOpenToken, setConfigOpenToken, mcpTokens, setMcpModalOpen, setMcpNewName, setMcpCreatedToken, setMcpDeleteId, copiedKey, mcpJsonConfig, handleCopy } = S
|
||||
const { t, formatDate, configOpenToken, setConfigOpenToken, mcpTokens, setMcpModalOpen, setMcpNewName, setMcpCreatedToken, setMcpDeleteId, copiedKey, mcpJsonConfig, handleCopy } = S
|
||||
return (
|
||||
<>
|
||||
<WarningNote>{t('settings.mcp.apiTokensDeprecated')}</WarningNote>
|
||||
@@ -311,9 +313,9 @@ function ApiTokensTab({ S }: { S: IntegrationsState }) {
|
||||
<p className="m-0 truncate font-semibold text-content" style={fs(13, 'body')}>{token.name}</p>
|
||||
<p className="m-0 mt-0.5 flex min-w-0 flex-wrap gap-x-3 text-content-faint" style={fs(11.5)}>
|
||||
<span className="font-geist">{token.token_prefix}...</span>
|
||||
<span className="tabular-nums">{t('settings.mcp.tokenCreatedAt')} {new Date(token.created_at).toLocaleDateString(locale)}</span>
|
||||
<span className="tabular-nums">{t('settings.mcp.tokenCreatedAt')} {formatDate(new Date(token.created_at))}</span>
|
||||
{token.last_used_at && (
|
||||
<span className="tabular-nums">{t('settings.mcp.tokenUsedAt')} {new Date(token.last_used_at).toLocaleDateString(locale)}</span>
|
||||
<span className="tabular-nums">{t('settings.mcp.tokenUsedAt')} {formatDate(new Date(token.last_used_at))}</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -2,9 +2,11 @@ import { escapeHtml } from '@trek/shared';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router';
|
||||
import { adminApi, authApi } from '../../api/client';
|
||||
import { useDateFormat } from '../../hooks/useDateFormat';
|
||||
import { useTranslation } from '../../i18n';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
import { getApiErrorMessage, type UserWithOidc } from '../../types';
|
||||
import { formatDisplayDateTime } from '../../utils/dateFormat';
|
||||
import { passwordErrorKey } from '../../utils/passwordError';
|
||||
import { useToast } from '../shared/Toast';
|
||||
|
||||
@@ -35,6 +37,7 @@ export function useAccountSettings({ avatarRemoveErrorKey, ignoreEmptyBackupCode
|
||||
const [searchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
const dateFormat = useDateFormat();
|
||||
const toast = useToast();
|
||||
const avatarInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
@@ -126,7 +129,7 @@ export function useAccountSettings({ avatarRemoveErrorKey, ignoreEmptyBackupCode
|
||||
if (skipBackupCodes) return;
|
||||
const html = `<!doctype html><html><head><meta charset="utf-8"/><title>TREK MFA Backup Codes</title>
|
||||
<style>body{font-family:Arial,sans-serif;padding:32px}h1{font-size:20px}pre{font-size:16px;line-height:1.6}</style>
|
||||
</head><body><h1>TREK MFA Backup Codes</h1><p>${escapeHtml(new Date().toLocaleString())}</p><pre>${escapeHtml(backupCodesText)}</pre></body></html>`;
|
||||
</head><body><h1>TREK MFA Backup Codes</h1><p>${escapeHtml(formatDisplayDateTime(new Date(), undefined, {}, dateFormat))}</p><pre>${escapeHtml(backupCodesText)}</pre></body></html>`;
|
||||
const w = window.open('', '_blank', 'width=900,height=700');
|
||||
if (!w) return;
|
||||
w.document.open();
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { startRegistration } from '@simplewebauthn/browser';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { authApi, type PasskeyCredential } from '../../api/client';
|
||||
import { readDateFormat } from '../../hooks/useDateFormat';
|
||||
import { useTranslation } from '../../i18n';
|
||||
import { getApiErrorMessage } from '../../types';
|
||||
import { formatDisplayDate } from '../../utils/dateFormat';
|
||||
import { useToast } from '../shared/Toast';
|
||||
|
||||
/** Parse a SQLite UTC timestamp ("YYYY-MM-DD HH:MM:SS") into a local date string. */
|
||||
@@ -10,7 +12,7 @@ export function fmtDate(ts: string | null): string | null {
|
||||
if (!ts) return null;
|
||||
const iso = ts.includes('T') ? ts : ts.replace(' ', 'T');
|
||||
const d = new Date(iso.endsWith('Z') ? iso : iso + 'Z');
|
||||
return Number.isNaN(d.getTime()) ? null : d.toLocaleDateString();
|
||||
return Number.isNaN(d.getTime()) ? null : formatDisplayDate(d, undefined, {}, readDateFormat());
|
||||
}
|
||||
|
||||
/** True when the browser cancellation / no-matching-credential DOMExceptions fire. */
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { pluginsApi } from '../../api/client';
|
||||
import { useDateFormat } from '../../hooks/useDateFormat';
|
||||
import { useTranslation } from '../../i18n';
|
||||
import { formatDisplayDateTime } from '../../utils/dateFormat';
|
||||
|
||||
export interface ActivityRow {
|
||||
ts: string;
|
||||
@@ -18,6 +20,7 @@ export interface ActivityRow {
|
||||
*/
|
||||
export function usePluginActivity() {
|
||||
const { locale } = useTranslation();
|
||||
const dateFormat = useDateFormat();
|
||||
const [rows, setRows] = useState<ActivityRow[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -36,7 +39,7 @@ export function usePluginActivity() {
|
||||
|
||||
const fmtWhen = (ts: string): string => {
|
||||
const d = new Date(ts);
|
||||
return Number.isNaN(d.getTime()) ? ts : d.toLocaleString(locale);
|
||||
return Number.isNaN(d.getTime()) ? ts : formatDisplayDateTime(d, locale, {}, dateFormat);
|
||||
};
|
||||
|
||||
return { rows, loading, load, fmtWhen };
|
||||
|
||||
@@ -18,8 +18,10 @@ import {
|
||||
} from 'lucide-react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { useDateFormat } from '../../../hooks/useDateFormat';
|
||||
import { useTranslation } from '../../../i18n';
|
||||
import { useSettingsStore } from '../../../store/settingsStore';
|
||||
import { formatDisplayDate } from '../../../utils/dateFormat';
|
||||
import { formatDistance, formatElevation } from '../../../utils/units';
|
||||
import { BarButton, WhiteBadge } from '../../Planner/planParts';
|
||||
import { DialogButton, NEUTRAL_TINT, fs } from '../../shared/DialogShell';
|
||||
@@ -428,12 +430,13 @@ function SnapshotNotice({
|
||||
onOpen: (placeId: number) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const dateFormat = useDateFormat();
|
||||
const snapshot = preview.snapshot;
|
||||
if (preview.state === 'new' || !snapshot) {
|
||||
return <TourNotice>{t('wanderer.preview.newHint')}</TourNotice>;
|
||||
}
|
||||
const when = snapshot.importedAt
|
||||
? new Date(`${snapshot.importedAt.replace(' ', 'T')}Z`).toLocaleDateString(locale)
|
||||
? formatDisplayDate(new Date(`${snapshot.importedAt.replace(' ', 'T')}Z`), locale, {}, dateFormat)
|
||||
: '';
|
||||
const open = (
|
||||
<button
|
||||
|
||||
@@ -78,6 +78,19 @@ afterEach(() => {
|
||||
delete window.__addToast
|
||||
})
|
||||
|
||||
// The hire date is the shared date picker (#2971): typed through its manual
|
||||
// field, cleared from its calendar.
|
||||
function typeDate(trigger: HTMLElement, value: string) {
|
||||
fireEvent.click(within(trigger.parentElement as HTMLElement).getByRole('button', { name: /enterManually|Enter date manually/ }))
|
||||
const input = screen.getByRole('textbox', { name: /enterManually|Enter date manually/ })
|
||||
fireEvent.change(input, { target: { value } })
|
||||
fireEvent.keyDown(input, { key: 'Enter' })
|
||||
}
|
||||
function clearDate(trigger: HTMLElement) {
|
||||
fireEvent.click(trigger)
|
||||
fireEvent.click(screen.getByRole('button', { name: /clearDate|Clear date/ }))
|
||||
}
|
||||
|
||||
describe('VacaySettings calendars', () => {
|
||||
it('FE-COMP-VCYSET-001: the week-start buttons save the other day', () => {
|
||||
const updatePlan = vi.fn(async (_u: Partial<VacayPlan>) => {})
|
||||
@@ -312,14 +325,15 @@ describe('VacaySettings calendars', () => {
|
||||
})
|
||||
render(<VacaySettings onClose={vi.fn()} />)
|
||||
|
||||
expect(screen.getByLabelText('Hire date')).toHaveValue('2020-09-14')
|
||||
const hire = () => screen.getByRole('button', { name: /^Hire date: / })
|
||||
expect(hire()).toHaveAccessibleName(/2020/)
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Hire date'), { target: { value: '2021-03-02' } })
|
||||
typeDate(hire(), '2021-03-02')
|
||||
expect(updateYearSettings).toHaveBeenLastCalledWith({
|
||||
year_type: 'anniversary', year_start_month: 1, year_start_day: 1, hire_date: '2021-03-02',
|
||||
})
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Hire date'), { target: { value: '' } })
|
||||
clearDate(hire())
|
||||
expect(updateYearSettings).toHaveBeenLastCalledWith({
|
||||
year_type: 'anniversary', year_start_month: 1, year_start_day: 1, hire_date: null,
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import { type LucideIcon, CalendarOff, AlertCircle, Building2, Unlink, ArrowRigh
|
||||
import { useVacayStore } from '../../store/vacayStore'
|
||||
import { useTranslation } from '../../i18n'
|
||||
import CustomSelect from '../shared/CustomSelect'
|
||||
import { CustomDatePicker } from '../shared/CustomDateTimePicker'
|
||||
import ConfirmDialog from '../shared/ConfirmDialog'
|
||||
import { useSchoolHolidayCountries } from './useSchoolHolidayCountries'
|
||||
import type { VacayHolidayCalendar } from '../../types'
|
||||
@@ -355,12 +356,12 @@ function YearTypePicker() {
|
||||
|
||||
{type === 'anniversary' && (
|
||||
<div style={{ paddingInlineStart: 36, marginTop: 8 }}>
|
||||
<input
|
||||
type="date"
|
||||
<CustomDatePicker
|
||||
value={yearSettings.hire_date || ''}
|
||||
onChange={e => save({ hire_date: e.target.value || null })}
|
||||
aria-label={t('vacay.hireDate')}
|
||||
style={{ width: '100%', fontSize: 'calc(13px * var(--fs-scale-body, 1))', fontWeight: 500, padding: '8px 14px', borderRadius: 10, background: 'var(--bg-input)', border: '1px solid var(--border-primary)', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none' }}
|
||||
onChange={v => save({ hire_date: v || null })}
|
||||
placeholder={t('vacay.hireDate')}
|
||||
ariaLabel={t('vacay.hireDate')}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
{!yearSettings.hire_date && (
|
||||
<p className="text-[11px] mt-1 text-content-faint">{t('vacay.hireDateHint')}</p>
|
||||
|
||||
@@ -819,3 +819,82 @@ describe('CustomDatePicker week start (#2029)', () => {
|
||||
expect(await openMarch2026('saturday')).toEqual({ header: 'SSMTWTF', blanks: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Date format (#2971) ──────────────────────────────────────────────────────
|
||||
|
||||
describe('CustomDatePicker date format (#2971)', () => {
|
||||
const onChange = vi.fn();
|
||||
const original = useSettingsStore.getState().settings;
|
||||
|
||||
beforeEach(() => vi.clearAllMocks());
|
||||
afterEach(() => useSettingsStore.setState({ settings: original }));
|
||||
|
||||
const seed = (dateFormat: 'auto' | 'YYYY-MM-DD' | 'DD.MM.YYYY') =>
|
||||
useSettingsStore.setState({ settings: { ...original, date_format: dateFormat } });
|
||||
|
||||
function typeDate(value: string) {
|
||||
fireEvent.click(screen.getByRole('button', { name: /enter date manually/i }));
|
||||
const input = screen.getByRole('textbox', { name: 'Enter date manually' });
|
||||
fireEvent.change(input, { target: { value } });
|
||||
fireEvent.keyDown(input, { key: 'Enter' });
|
||||
}
|
||||
|
||||
it('FE-COMP-DATEPICKER-036: the trigger writes the date in the chosen format', () => {
|
||||
seed('YYYY-MM-DD');
|
||||
render(<CustomDatePicker value="2026-11-03" onChange={onChange} />);
|
||||
expect(screen.getAllByRole('button')[0].textContent).toContain('2026-11-03');
|
||||
});
|
||||
|
||||
it('FE-COMP-DATEPICKER-037: compact mode writes the chosen format with the whole year', () => {
|
||||
seed('DD.MM.YYYY');
|
||||
render(<CustomDatePicker value="2026-11-03" onChange={onChange} compact />);
|
||||
expect(screen.getByText('03.11.2026')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('FE-COMP-DATEPICKER-038: the manual entry is hinted and pre-filled in the chosen format', () => {
|
||||
seed('YYYY-MM-DD');
|
||||
render(<CustomDatePicker value="2026-11-03" onChange={onChange} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /enter date manually/i }));
|
||||
const input = screen.getByRole('textbox', { name: 'Enter date manually' });
|
||||
expect(input).toHaveProperty('placeholder', 'YYYY-MM-DD');
|
||||
expect(input).toHaveProperty('value', '2026-11-03');
|
||||
});
|
||||
|
||||
it('FE-COMP-DATEPICKER-039: a typed date is read in the order of the chosen format', () => {
|
||||
seed('YYYY-MM-DD');
|
||||
render(<CustomDatePicker value="" onChange={onChange} />);
|
||||
typeDate('2026/11/04');
|
||||
expect(onChange).toHaveBeenCalledWith('2026-11-04');
|
||||
});
|
||||
|
||||
it('FE-COMP-DATEPICKER-040: day first under DD.MM.YYYY, even with an English interface', () => {
|
||||
seed('DD.MM.YYYY');
|
||||
render(<CustomDatePicker value="" onChange={onChange} />);
|
||||
typeDate('04.11.2026');
|
||||
expect(onChange).toHaveBeenCalledWith('2026-11-04');
|
||||
});
|
||||
|
||||
it('FE-COMP-DATEPICKER-043: a four-digit year written first is read as year, month, day in any format', () => {
|
||||
seed('DD.MM.YYYY');
|
||||
render(<CustomDatePicker value="" onChange={onChange} />);
|
||||
typeDate('2026/11/04');
|
||||
expect(onChange).toHaveBeenCalledWith('2026-11-04');
|
||||
});
|
||||
|
||||
it('FE-COMP-DATEPICKER-041: auto keeps the short month of the language on the trigger', () => {
|
||||
seed('auto');
|
||||
render(<CustomDatePicker value="2026-11-03" onChange={onChange} />);
|
||||
expect(screen.getAllByRole('button')[0].textContent).toMatch(/Nov/);
|
||||
});
|
||||
|
||||
it('FE-COMP-DATEPICKER-042: auto keeps the language for typing: month first in English', () => {
|
||||
seed('auto');
|
||||
render(<CustomDatePicker value="" onChange={onChange} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /enter date manually/i }));
|
||||
const input = screen.getByRole('textbox', { name: 'Enter date manually' });
|
||||
expect(input).toHaveProperty('placeholder', 'MM/DD/YYYY');
|
||||
fireEvent.change(input, { target: { value: '04.11.2026' } });
|
||||
fireEvent.keyDown(input, { key: 'Enter' });
|
||||
expect(onChange).toHaveBeenCalledWith('2026-04-11');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,8 @@ import React, { useEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useTranslation } from '../../i18n';
|
||||
import { dateInputPattern } from '../../utils/dateInputPattern';
|
||||
import { type DateField, dateFieldOrder, dateLayout, formatDisplayDate } from '../../utils/dateFormat';
|
||||
import { useDateFormat } from '../../hooks/useDateFormat';
|
||||
import { useRemeasureSignal } from '../../hooks/useAnchoredPosition';
|
||||
import { useWeekStartDay } from '../../hooks/useWeekStartDay';
|
||||
import { leadingBlanks, weekdayLabels } from '../../utils/calendarWeek';
|
||||
@@ -27,6 +29,8 @@ interface CustomDatePickerProps {
|
||||
// disabled in the calendar and rejected on manual entry.
|
||||
min?: string;
|
||||
max?: string;
|
||||
/** What the field is, for a picker without a visible label around it; the trigger reads it with its date. */
|
||||
ariaLabel?: string;
|
||||
}
|
||||
|
||||
export function CustomDatePicker({
|
||||
@@ -39,9 +43,11 @@ export function CustomDatePicker({
|
||||
disabled = false,
|
||||
min,
|
||||
max,
|
||||
ariaLabel,
|
||||
}: CustomDatePickerProps) {
|
||||
const { locale, t } = useTranslation();
|
||||
const weekStart = useWeekStartDay();
|
||||
const dateFormat = useDateFormat();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [view, setView] = useState<CalendarView>('days');
|
||||
const [yearPageStart, setYearPageStart] = useState(0);
|
||||
@@ -149,22 +155,17 @@ export function CustomDatePicker({
|
||||
);
|
||||
const years = Array.from({ length: YEAR_PAGE_SIZE }, (_, i) => yearPageStart + i);
|
||||
|
||||
const displayValue = parsed
|
||||
? parsed.toLocaleDateString(
|
||||
locale,
|
||||
compact
|
||||
? { day: '2-digit', month: '2-digit', year: '2-digit', timeZone: 'UTC' }
|
||||
: { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' }
|
||||
)
|
||||
: null;
|
||||
// A chosen date format (#2971) writes the field in numbers, the whole year
|
||||
// included; 'auto' keeps the language's short month as before.
|
||||
const fieldOptions: Intl.DateTimeFormatOptions = dateLayout(dateFormat)
|
||||
? { day: '2-digit', month: '2-digit', year: 'numeric', timeZone: 'UTC' }
|
||||
: compact
|
||||
? { day: '2-digit', month: '2-digit', year: '2-digit', timeZone: 'UTC' }
|
||||
: { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' };
|
||||
const displayValue = parsed ? formatDisplayDate(parsed, locale, fieldOptions, dateFormat) : null;
|
||||
|
||||
const inputValue = parsed
|
||||
? parsed.toLocaleDateString(locale, {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
timeZone: 'UTC',
|
||||
})
|
||||
? formatDisplayDate(parsed, locale, { day: '2-digit', month: '2-digit', year: 'numeric', timeZone: 'UTC' }, dateFormat)
|
||||
: '';
|
||||
|
||||
// Inclusive range check on ISO (YYYY-MM-DD) strings — lexicographic order
|
||||
@@ -212,13 +213,11 @@ export function CustomDatePicker({
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine field order for active locale via Intl
|
||||
const parts = new Intl.DateTimeFormat(locale, { day: 'numeric', month: 'numeric', year: 'numeric' }).formatToParts(
|
||||
new Date(2001, 5, 15)
|
||||
); // known date: June 15, 2001
|
||||
const order = parts
|
||||
.filter((p) => ['day', 'month', 'year'].includes(p.type))
|
||||
.map((p) => p.type as 'day' | 'month' | 'year');
|
||||
// The order of the chosen date format (#2971), or the active locale's. A
|
||||
// four-digit first number is a year in every format, so 2026/11/04 or
|
||||
// 2026.11.04 reads as year, month, day whichever format is set.
|
||||
const yearFirst = /^\D*\d{4}\D/.test(input);
|
||||
const order: readonly DateField[] = yearFirst ? ['year', 'month', 'day'] : dateFieldOrder(locale, dateFormat);
|
||||
|
||||
// Strip non-numeric chars (handles RTL marks, dots, slashes, spaces)
|
||||
const nums = input
|
||||
@@ -277,7 +276,7 @@ export function CustomDatePicker({
|
||||
if (e.key === 'Escape') setIsTyping(false);
|
||||
}}
|
||||
onBlur={handleTextSubmit}
|
||||
placeholder={dateInputPattern(locale)}
|
||||
placeholder={dateInputPattern(locale, dateFormat)}
|
||||
aria-label={t('common.datepicker.enterManually')}
|
||||
style={{
|
||||
width: '100%',
|
||||
@@ -302,7 +301,11 @@ export function CustomDatePicker({
|
||||
type="button"
|
||||
onClick={() => setOpen((o) => !o)}
|
||||
disabled={disabled}
|
||||
aria-label={displayValue || placeholder || t('common.date')}
|
||||
aria-label={
|
||||
ariaLabel
|
||||
? [ariaLabel, displayValue].filter(Boolean).join(': ')
|
||||
: displayValue || placeholder || t('common.date')
|
||||
}
|
||||
aria-expanded={open}
|
||||
aria-haspopup="dialog"
|
||||
style={{
|
||||
@@ -700,9 +703,12 @@ interface CustomDateTimePickerProps {
|
||||
onChange: (value: string) => void;
|
||||
placeholder?: string;
|
||||
style?: React.CSSProperties;
|
||||
/** Earliest and latest day (YYYY-MM-DD), as CustomDatePicker takes them. */
|
||||
min?: string;
|
||||
max?: string;
|
||||
}
|
||||
|
||||
export function CustomDateTimePicker({ value, onChange, placeholder, style = {} }: CustomDateTimePickerProps) {
|
||||
export function CustomDateTimePicker({ value, onChange, placeholder, style = {}, min, max }: CustomDateTimePickerProps) {
|
||||
const { locale } = useTranslation();
|
||||
const [datePart, timePart] = (value || '').split('T');
|
||||
|
||||
@@ -716,7 +722,13 @@ export function CustomDateTimePicker({ value, onChange, placeholder, style = {}
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 8, ...style }}>
|
||||
<CustomDatePicker value={datePart || ''} onChange={handleDateChange} style={{ flex: 1, minWidth: 0 }} />
|
||||
<CustomDatePicker
|
||||
value={datePart || ''}
|
||||
onChange={handleDateChange}
|
||||
style={{ flex: 1, minWidth: 0 }}
|
||||
min={min}
|
||||
max={max}
|
||||
/>
|
||||
<div style={{ width: 110, flexShrink: 0 }}>
|
||||
<CustomTimePicker value={timePart || ''} onChange={handleTimeChange} />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { DEFAULT_DATE_FORMAT, type DateFormat } from '@trek/shared';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from '../i18n';
|
||||
import { useSettingsStore } from '../store/settingsStore';
|
||||
import { formatDisplayDate, formatDisplayDateTime } from '../utils/dateFormat';
|
||||
|
||||
/** The current user's date format (#2971); 'auto' follows the interface language. */
|
||||
export function useDateFormat(): DateFormat {
|
||||
return useSettingsStore((s) => s.settings.date_format) ?? DEFAULT_DATE_FORMAT;
|
||||
}
|
||||
|
||||
/** The same value outside React (stores, exports, plain helpers), read when called. */
|
||||
export function readDateFormat(): DateFormat {
|
||||
return useSettingsStore.getState().settings.date_format ?? DEFAULT_DATE_FORMAT;
|
||||
}
|
||||
|
||||
/**
|
||||
* `toLocaleDateString` and `toLocaleString` bound to the interface language and
|
||||
* the user's date format, for components that write dates.
|
||||
*/
|
||||
export function useFormatDate() {
|
||||
const { locale } = useTranslation();
|
||||
const format = useDateFormat();
|
||||
const date = useCallback(
|
||||
(value: Date, opts?: Intl.DateTimeFormatOptions) => formatDisplayDate(value, locale, opts, format),
|
||||
[locale, format]
|
||||
);
|
||||
const dateTime = useCallback(
|
||||
(value: Date, opts?: Intl.DateTimeFormatOptions) => formatDisplayDateTime(value, locale, opts, format),
|
||||
[locale, format]
|
||||
);
|
||||
return { formatDate: date, formatDateTime: dateTime, locale, dateFormat: format };
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { MapView } from '../../../components/Map/MapView'
|
||||
import { SYMBOLS, currenciesWith } from '../../../components/Budget/BudgetPanel.constants'
|
||||
import { getApiErrorMessage, type DistanceUnit, type WeekStart } from '../../../types'
|
||||
import { weekStartOptions } from '../../../utils/calendarWeek'
|
||||
import { dateFormatOptions } from '../../../utils/dateFormat'
|
||||
import { withTileApiKey } from '../../../utils/tileUrl'
|
||||
import { defaultStyleForProvider } from '../../../components/Map/glProviders'
|
||||
import type { MapProvider } from '../../../components/Settings/mapSettingsModel'
|
||||
@@ -19,6 +20,25 @@ import RoutingInstanceFields from '../../../components/Admin/RoutingInstanceFiel
|
||||
// Mobile-native rebuild of the desktop DefaultUserSettingsTab. Identical data
|
||||
// layer (adminApi defaults, per-change auto-save, reset-to-built-in) — only the
|
||||
// presentation is relaid on the admin mobile design system.
|
||||
interface ResetProps {
|
||||
shown: boolean
|
||||
onReset: () => void
|
||||
label: string
|
||||
}
|
||||
|
||||
function ResetButton({ shown, onReset, label }: ResetProps): React.ReactElement | null {
|
||||
if (!shown) return null
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onReset}
|
||||
className="ms-2 font-geist text-[0.625rem] font-medium text-m-faint underline"
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default function MAdminDefaultUserSettings(): React.ReactElement {
|
||||
const { t, locale } = useTranslation()
|
||||
const {
|
||||
@@ -27,19 +47,15 @@ export default function MAdminDefaultUserSettings(): React.ReactElement {
|
||||
saveMapProvider, pickTilePreset, pickStylePreset, commitStyle,
|
||||
} = useDefaultUserSettings({ errorMessage: getApiErrorMessage, previewCreatedAt: () => new Date().toISOString() })
|
||||
const [currencyOpen, setCurrencyOpen] = useState(false)
|
||||
const [dateFormatOpen, setDateFormatOpen] = useState(false)
|
||||
const [presetOpen, setPresetOpen] = useState(false)
|
||||
const [styleOpen, setStyleOpen] = useState(false)
|
||||
|
||||
const ResetButton = ({ field }: { field: keyof Defaults }) =>
|
||||
isSet(field) ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => reset(field)}
|
||||
className="ms-2 font-geist text-[0.625rem] font-medium text-m-faint underline"
|
||||
>
|
||||
{t('admin.defaultSettings.resetToBuiltIn')}
|
||||
</button>
|
||||
) : null
|
||||
const resetOf = (field: keyof Defaults): ResetProps => ({
|
||||
shown: isSet(field),
|
||||
onReset: () => reset(field),
|
||||
label: t('admin.defaultSettings.resetToBuiltIn'),
|
||||
})
|
||||
|
||||
if (!loaded) {
|
||||
return (
|
||||
@@ -90,6 +106,7 @@ export default function MAdminDefaultUserSettings(): React.ReactElement {
|
||||
const tilePresetLabel = presets.find((p) => p.url === mapTileUrl)?.name || t('settings.mapTemplatePlaceholder.select')
|
||||
const stylePresetLabel = glStylePresets.find((p) => p.url === mapboxStyle)?.name || t('admin.defaultSettings.mapboxStylePlaceholder')
|
||||
const chevron = <ChevronDown size={13} strokeWidth={2} className="flex-none text-m-faint" />
|
||||
const dateFormats = dateFormatOptions(locale, t('settings.dateFormatAuto'))
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
@@ -99,33 +116,42 @@ export default function MAdminDefaultUserSettings(): React.ReactElement {
|
||||
|
||||
<div className="mt-2 space-y-[14px]">
|
||||
{/* Color Mode */}
|
||||
<MAdminField label={<>{t('settings.colorMode')} <ResetButton field="dark_mode" /></>}>
|
||||
<MAdminField label={<>{t('settings.colorMode')} <ResetButton {...resetOf('dark_mode')} /></>}>
|
||||
<MSegmented value={colorModeValue} onChange={(v) => save({ dark_mode: v })} options={modeOptions} />
|
||||
</MAdminField>
|
||||
|
||||
{/* Temperature */}
|
||||
<MAdminField label={<>{t('settings.temperature')} <ResetButton field="temperature_unit" /></>}>
|
||||
<MAdminField label={<>{t('settings.temperature')} <ResetButton {...resetOf('temperature_unit')} /></>}>
|
||||
<MSegmented value={defaults.temperature_unit || ''} onChange={(v) => save({ temperature_unit: v })} options={tempOptions} />
|
||||
</MAdminField>
|
||||
|
||||
{/* Distance */}
|
||||
<MAdminField label={<>{t('settings.distance')} <ResetButton field="distance_unit" /></>}>
|
||||
<MAdminField label={<>{t('settings.distance')} <ResetButton {...resetOf('distance_unit')} /></>}>
|
||||
<MSegmented value={defaults.distance_unit || ''} onChange={(v) => save({ distance_unit: v as DistanceUnit })} options={distanceOptions} />
|
||||
</MAdminField>
|
||||
|
||||
{/* Time Format */}
|
||||
<MAdminField label={<>{t('settings.timeFormat')} <ResetButton field="time_format" /></>}>
|
||||
<MAdminField label={<>{t('settings.timeFormat')} <ResetButton {...resetOf('time_format')} /></>}>
|
||||
<MSegmented value={defaults.time_format || ''} onChange={(v) => save({ time_format: v })} options={timeOptions} />
|
||||
</MAdminField>
|
||||
|
||||
{/* Week start (#2029) */}
|
||||
<MAdminField label={<>{t('settings.weekStart')} <ResetButton field="week_start" /></>}>
|
||||
<MAdminField label={<>{t('settings.weekStart')} <ResetButton {...resetOf('week_start')} /></>}>
|
||||
<MSegmented value={defaults.week_start || ''} onChange={(v) => save({ week_start: v as WeekStart })} options={weekStartOptions(locale)} />
|
||||
</MAdminField>
|
||||
|
||||
{/* Date format (#2971) */}
|
||||
<MAdminField label={<>{t('settings.dateFormat')} <ResetButton {...resetOf('date_format')} /></>}>
|
||||
<MSetSelectRow
|
||||
label={dateFormats.find((o) => o.value === defaults.date_format)?.label ?? t('settings.dateFormat')}
|
||||
trailing={chevron}
|
||||
onClick={() => setDateFormatOpen(true)}
|
||||
/>
|
||||
</MAdminField>
|
||||
|
||||
{/* Default Currency */}
|
||||
<MAdminField
|
||||
label={<>{t('settings.currency')} <ResetButton field="default_currency" /></>}
|
||||
label={<>{t('settings.currency')} <ResetButton {...resetOf('default_currency')} /></>}
|
||||
hint={t('settings.currencyHint')}
|
||||
>
|
||||
<MSetSelectRow label={currencyLabel} trailing={chevron} onClick={() => setCurrencyOpen(true)} />
|
||||
@@ -134,7 +160,7 @@ export default function MAdminDefaultUserSettings(): React.ReactElement {
|
||||
{/* Blur Booking Codes */}
|
||||
<MAdminRow
|
||||
first
|
||||
title={<>{t('settings.blurBookingCodes')} <ResetButton field="blur_booking_codes" /></>}
|
||||
title={<>{t('settings.blurBookingCodes')} <ResetButton {...resetOf('blur_booking_codes')} /></>}
|
||||
trailing={
|
||||
<MToggle
|
||||
checked={defaults.blur_booking_codes === true}
|
||||
@@ -156,7 +182,7 @@ export default function MAdminDefaultUserSettings(): React.ReactElement {
|
||||
<div className="mt-2 space-y-[14px]">
|
||||
{/* Map Tile URL */}
|
||||
<MAdminField
|
||||
label={<>{t('settings.mapTemplate')} <ResetButton field="map_tile_url" /></>}
|
||||
label={<>{t('settings.mapTemplate')} <ResetButton {...resetOf('map_tile_url')} /></>}
|
||||
hint={t('settings.mapDefaultHint')}
|
||||
>
|
||||
<MSetSelectRow label={tilePresetLabel} trailing={chevron} onClick={() => setPresetOpen(true)} />
|
||||
@@ -174,7 +200,7 @@ export default function MAdminDefaultUserSettings(): React.ReactElement {
|
||||
settings are read. A field here would only let somebody save a worse one. */}
|
||||
{!managed && (
|
||||
<MAdminField
|
||||
label={<>{t('admin.defaultSettings.cartoKey')} <ResetButton field="carto_api_key" /></>}
|
||||
label={<>{t('admin.defaultSettings.cartoKey')} <ResetButton {...resetOf('carto_api_key')} /></>}
|
||||
hint={t('admin.defaultSettings.cartoKeyHint')}
|
||||
>
|
||||
<MAdminInput
|
||||
@@ -218,7 +244,7 @@ export default function MAdminDefaultUserSettings(): React.ReactElement {
|
||||
{/* Map provider */}
|
||||
<div className="border-t border-[color:var(--m-rowbr)] pt-[14px]">
|
||||
<MAdminField
|
||||
label={<>{t('admin.defaultSettings.mapProvider')} <ResetButton field="map_provider" /></>}
|
||||
label={<>{t('admin.defaultSettings.mapProvider')} <ResetButton {...resetOf('map_provider')} /></>}
|
||||
hint={t('admin.defaultSettings.mapProviderHint')}
|
||||
>
|
||||
<MSegmented value={mapProvider} onChange={(v) => saveMapProvider(v as MapProvider)} options={providerOptions} />
|
||||
@@ -229,7 +255,7 @@ export default function MAdminDefaultUserSettings(): React.ReactElement {
|
||||
<div className="space-y-[14px]">
|
||||
{mapProvider === 'mapbox-gl' && !managed && (
|
||||
<MAdminField
|
||||
label={<>{t('admin.defaultSettings.mapboxToken')} <ResetButton field="mapbox_access_token" /></>}
|
||||
label={<>{t('admin.defaultSettings.mapboxToken')} <ResetButton {...resetOf('mapbox_access_token')} /></>}
|
||||
hint={t('admin.defaultSettings.mapboxTokenHint')}
|
||||
>
|
||||
<MAdminInput
|
||||
@@ -244,7 +270,7 @@ export default function MAdminDefaultUserSettings(): React.ReactElement {
|
||||
</MAdminField>
|
||||
)}
|
||||
|
||||
<MAdminField label={<>{t('admin.defaultSettings.mapboxStyle')} <ResetButton field={styleKey} /></>}>
|
||||
<MAdminField label={<>{t('admin.defaultSettings.mapboxStyle')} <ResetButton {...resetOf(styleKey)} /></>}>
|
||||
<MSetSelectRow label={stylePresetLabel} trailing={chevron} onClick={() => setStyleOpen(true)} />
|
||||
<MAdminInput
|
||||
type="text"
|
||||
@@ -260,7 +286,7 @@ export default function MAdminDefaultUserSettings(): React.ReactElement {
|
||||
<div>
|
||||
<MAdminRow
|
||||
first
|
||||
title={<>{t('admin.defaultSettings.mapbox3d')} <ResetButton field="mapbox_3d_enabled" /></>}
|
||||
title={<>{t('admin.defaultSettings.mapbox3d')} <ResetButton {...resetOf('mapbox_3d_enabled')} /></>}
|
||||
trailing={
|
||||
<MToggle
|
||||
checked={defaults.mapbox_3d_enabled ?? true}
|
||||
@@ -270,7 +296,7 @@ export default function MAdminDefaultUserSettings(): React.ReactElement {
|
||||
}
|
||||
/>
|
||||
<MAdminRow
|
||||
title={<>{t('admin.defaultSettings.mapboxQuality')} <ResetButton field="mapbox_quality_mode" /></>}
|
||||
title={<>{t('admin.defaultSettings.mapboxQuality')} <ResetButton {...resetOf('mapbox_quality_mode')} /></>}
|
||||
trailing={
|
||||
<MToggle
|
||||
checked={defaults.mapbox_quality_mode ?? false}
|
||||
@@ -287,6 +313,15 @@ export default function MAdminDefaultUserSettings(): React.ReactElement {
|
||||
</MAdminCard>
|
||||
|
||||
{/* Pickers */}
|
||||
<MSetPickerSheet
|
||||
open={dateFormatOpen}
|
||||
onClose={() => setDateFormatOpen(false)}
|
||||
title={t('settings.dateFormat')}
|
||||
value={defaults.date_format || ''}
|
||||
onSelect={(value) => { if (value) void save({ date_format: value as Defaults['date_format'] }) }}
|
||||
options={dateFormats}
|
||||
/>
|
||||
|
||||
<MSetPickerSheet
|
||||
open={currencyOpen}
|
||||
onClose={() => setCurrencyOpen(false)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Key, Trash2, User, Loader2, Shield } from 'lucide-react'
|
||||
import { SCOPES_PREVIEW, useMcpTokensAdmin } from '../../../components/Admin/useMcpTokensAdmin'
|
||||
import { useTranslation } from '../../../i18n'
|
||||
import { useFormatDate } from '../../../hooks/useDateFormat'
|
||||
import { MAdminCard } from './MAdminUi'
|
||||
import MConfirmSheet from '../settings/MConfirmSheet'
|
||||
|
||||
@@ -13,7 +14,8 @@ export default function MAdminMcpTokensPanel() {
|
||||
sessions, sessionsLoading, tokens, tokensLoading, expandedScopes, toggleScopes,
|
||||
revokeConfirmId, setRevokeConfirmId, deleteConfirmId, setDeleteConfirmId, handleRevoke, handleDelete,
|
||||
} = useMcpTokensAdmin()
|
||||
const { t, locale } = useTranslation()
|
||||
const { t } = useTranslation()
|
||||
const { formatDate } = useFormatDate()
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
@@ -58,7 +60,7 @@ export default function MAdminMcpTokensPanel() {
|
||||
{session.username}
|
||||
</span>
|
||||
<span className="text-m-faint">·</span>
|
||||
<span>{new Date(session.created_at).toLocaleDateString(locale)}</span>
|
||||
<span>{formatDate(new Date(session.created_at))}</span>
|
||||
</div>
|
||||
<div className="mt-[6px] flex flex-wrap gap-1">
|
||||
{visible.map(scope => (
|
||||
@@ -135,11 +137,11 @@ export default function MAdminMcpTokensPanel() {
|
||||
{token.username}
|
||||
</span>
|
||||
<span className="text-m-faint">·</span>
|
||||
<span>{new Date(token.created_at).toLocaleDateString(locale)}</span>
|
||||
<span>{formatDate(new Date(token.created_at))}</span>
|
||||
<span className="text-m-faint">·</span>
|
||||
<span>
|
||||
{token.last_used_at
|
||||
? new Date(token.last_used_at).toLocaleDateString(locale)
|
||||
? formatDate(new Date(token.last_used_at))
|
||||
: t('admin.mcpTokens.never')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import { useFormatDate } from '../../../hooks/useDateFormat'
|
||||
import MPluginVersionDate from './MPluginVersionDate'
|
||||
import {
|
||||
Blocks, AlertTriangle, PackageOpen, RefreshCw, Trash2, Download, Bug, X, ShieldCheck, UploadCloud,
|
||||
ArrowUpCircle, Github, ExternalLink, ChevronDown, Check, Lock, Search, Link2, KeyRound, ShieldAlert,
|
||||
@@ -311,7 +313,7 @@ export default function MAdminPluginsPanel() {
|
||||
{/* Version picker sheet */}
|
||||
{versionPicker && (
|
||||
<VersionPickerSheet plugin={versionPicker.plugin} versions={versionPicker.versions} failed={versionPicker.failed}
|
||||
busy={busy} t={t} locale={locale} onPick={pickVersion} onClose={() => setVersionPicker(null)} />
|
||||
busy={busy} t={t} onPick={pickVersion} onClose={() => setVersionPicker(null)} />
|
||||
)}
|
||||
|
||||
{/* Downgrade confirm */}
|
||||
@@ -806,9 +808,9 @@ function RowActionsSheet({ p, t, onClose, onRestart, onErrors, onEgress, onSetti
|
||||
* caller routes a DOWNGRADE through the data-risk confirm), an incompatible one is
|
||||
* explained and never offered.
|
||||
*/
|
||||
function VersionPickerSheet({ plugin, versions, failed, busy, t, locale, onPick, onClose }: {
|
||||
function VersionPickerSheet({ plugin, versions, failed, busy, t, onPick, onClose }: {
|
||||
plugin: PluginRow; versions: VersionInfo[] | null; failed: boolean; busy: string | null
|
||||
t: T; locale: string; onPick: (version: string) => void; onClose: () => void
|
||||
t: T; onPick: (version: string) => void; onClose: () => void
|
||||
}) {
|
||||
return (
|
||||
<MSheet open onClose={onClose} variant="bottom" material="opaque" ariaLabel={t('admin.plugins.versionPickerTitle', { name: plugin.name })}>
|
||||
@@ -823,7 +825,7 @@ function VersionPickerSheet({ plugin, versions, failed, busy, t, locale, onPick,
|
||||
<div key={v.version} data-testid={`version-row-${v.version}`}
|
||||
className="flex items-center gap-2.5 rounded-xl px-2.5 py-2.5">
|
||||
<span className={`text-[13px] font-semibold tabular-nums ${v.compatible ? 'text-m-ink' : 'text-m-faint'}`}>v{v.version}</span>
|
||||
{v.publishedAt && <span className="text-[11.5px] text-m-faint">{new Date(v.publishedAt).toLocaleDateString(locale)}</span>}
|
||||
<MPluginVersionDate at={v.publishedAt} className="text-[11.5px] text-m-faint" />
|
||||
{v.signed && <ShieldCheck size={13} className="shrink-0 text-[color:var(--m-st-confirmed)]" />}
|
||||
<span className="ms-auto" />
|
||||
{current ? (
|
||||
@@ -956,6 +958,7 @@ function PluginDetailSheet({ item, installed, busy, onInstall, onClose, t, local
|
||||
item: RegistryItem; installed: boolean; busy: string | null; ignoreTrekRange: boolean
|
||||
onInstall: (id: string, version?: string, warn?: RangeWarning) => void; onClose: () => void; t: T; locale: string
|
||||
}) {
|
||||
const { formatDate: dateOf } = useFormatDate()
|
||||
const { detail, failed } = usePluginDetail<MRegistryDetail>(item.id)
|
||||
|
||||
const manifest = detail?.manifest ?? null
|
||||
@@ -1109,7 +1112,7 @@ function PluginDetailSheet({ item, installed, busy, onInstall, onClose, t, local
|
||||
{(item.trek || item.minTrekVersion) && (
|
||||
<Meta k={t('admin.plugins.metaRequires')} v={item.trek ? `TREK ${item.trek}` : `TREK ${item.minTrekVersion}+`} />
|
||||
)}
|
||||
{item.reviewedAt && <Meta k={t('admin.plugins.metaReviewed')} v={new Date(item.reviewedAt).toLocaleDateString(locale)} />}
|
||||
{item.reviewedAt && <Meta k={t('admin.plugins.metaReviewed')} v={dateOf(new Date(item.reviewedAt))} />}
|
||||
{typeof item.downloadCount === 'number' && item.downloadCount > 0 && (
|
||||
<Meta k={t('admin.plugins.downloads')} v={item.downloadCount.toLocaleString(locale)} />
|
||||
)}
|
||||
@@ -1125,7 +1128,7 @@ function PluginDetailSheet({ item, installed, busy, onInstall, onClose, t, local
|
||||
{detail!.versions!.map(v => (
|
||||
<div key={v.version} className="flex items-center gap-2.5 py-1">
|
||||
<span className={`text-[12.5px] font-medium tabular-nums ${v.compatible ? 'text-m-ink' : 'text-m-faint'}`}>v{v.version}</span>
|
||||
{v.publishedAt && <span className="text-[11.5px] text-m-faint">{new Date(v.publishedAt).toLocaleDateString(locale)}</span>}
|
||||
<MPluginVersionDate at={v.publishedAt} className="text-[11.5px] text-m-faint" />
|
||||
{v.signed && <ShieldCheck size={13} className="shrink-0 text-[color:var(--m-st-confirmed)]" />}
|
||||
<span className="ms-auto" />
|
||||
{v.compatible ? (
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { useFormatDate } from '../../../hooks/useDateFormat';
|
||||
|
||||
/** The day a plugin version was published, in the user's date format (#2971). */
|
||||
export default function MPluginVersionDate({ at, className }: { at?: string | null; className?: string }) {
|
||||
const { formatDate } = useFormatDate();
|
||||
if (!at) return null;
|
||||
return <span className={className}>{formatDate(new Date(at))}</span>;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react'
|
||||
import { Plug, Save, RefreshCw, Unplug } from 'lucide-react'
|
||||
import { useTranslation } from '../../../i18n'
|
||||
import { useFormatDate } from '../../../hooks/useDateFormat'
|
||||
import { useDawarichConnection } from '../../../hooks/useDawarichConnection'
|
||||
import { MSetCard, MSetRow, MSetInput, MSetButton, MSetHint } from './MSettingsUi'
|
||||
import MToggle from '../../components/MToggle'
|
||||
@@ -14,7 +15,8 @@ import MToggle from '../../components/MToggle'
|
||||
* sync logic would spend the whole budget on its own.
|
||||
*/
|
||||
export default function MDawarichConnectionSection(): React.ReactElement {
|
||||
const { t, locale } = useTranslation()
|
||||
const { t } = useTranslation()
|
||||
const { formatDateTime } = useFormatDate()
|
||||
const S = useDawarichConnection()
|
||||
|
||||
return (
|
||||
@@ -92,7 +94,7 @@ export default function MDawarichConnectionSection(): React.ReactElement {
|
||||
{S.connected && (
|
||||
<p className="font-geist text-[0.625rem] leading-relaxed text-m-muted">
|
||||
{S.lastSyncAt
|
||||
? t('dawarich.lastSync', { when: new Date(S.lastSyncAt).toLocaleString(locale) })
|
||||
? t('dawarich.lastSync', { when: formatDateTime(new Date(S.lastSyncAt)) })
|
||||
: t('dawarich.neverSynced')}
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -6,8 +6,9 @@ import { SYMBOLS, currenciesWith } from '../../../components/Budget/BudgetPanel.
|
||||
import { TRIP_TAB_IDS, TRIP_TAB_LABEL_KEYS, isTripTabId } from '../../../constants/tripTabs'
|
||||
import { DEFAULT_START_PAGE, DEFAULT_START_TRIP_TAB, type StartPage } from '../../../utils/startDestination'
|
||||
import type { Settings, DistanceUnit, WeekStart } from '../../../types'
|
||||
import { DEFAULT_WEEK_START } from '@trek/shared'
|
||||
import { DEFAULT_DATE_FORMAT, DEFAULT_WEEK_START } from '@trek/shared'
|
||||
import { weekStartOptions } from '../../../utils/calendarWeek'
|
||||
import { dateFormatOptions } from '../../../utils/dateFormat'
|
||||
import { MSetCard, MSetEyebrow, MSetHint, MSetSelectRow, MSetSegments, MSetRow } from './MSettingsUi'
|
||||
import MToggle from '../../components/MToggle'
|
||||
import MSetPickerSheet from './MSetPickerSheet'
|
||||
@@ -27,6 +28,7 @@ export default function MSettingsGeneral() {
|
||||
const [langOpen, setLangOpen] = useState(false)
|
||||
const [startTabOpen, setStartTabOpen] = useState(false)
|
||||
const [navAppOpen, setNavAppOpen] = useState(false)
|
||||
const [dateFormatOpen, setDateFormatOpen] = useState(false)
|
||||
const navAppOptions = preferredNavAppOptions(t)
|
||||
const navAppValue = settings.preferred_nav_app || ''
|
||||
|
||||
@@ -37,6 +39,7 @@ export default function MSettingsGeneral() {
|
||||
const language = SUPPORTED_LANGUAGES.find((l) => l.value === settings.language) || SUPPORTED_LANGUAGES[0]
|
||||
|
||||
const chevron = <ChevronDown size={13} strokeWidth={2} className="flex-none text-m-faint" />
|
||||
const dateFormats = dateFormatOptions(locale, t('settings.dateFormatAuto'))
|
||||
|
||||
const travelRows: {
|
||||
key: keyof Settings
|
||||
@@ -166,6 +169,15 @@ export default function MSettingsGeneral() {
|
||||
onChange={(v) => save('week_start', v)}
|
||||
options={weekStartOptions(locale)}
|
||||
/>
|
||||
|
||||
{/* Date format (#2971) */}
|
||||
<MSetEyebrow className="mb-[6px] mt-[14px]">{t('settings.dateFormat')}</MSetEyebrow>
|
||||
<MSetSelectRow
|
||||
label={dateFormats.find((o) => o.value === (settings.date_format || DEFAULT_DATE_FORMAT))?.label ?? ''}
|
||||
trailing={chevron}
|
||||
onClick={() => setDateFormatOpen(true)}
|
||||
/>
|
||||
<MSetHint>{t('settings.dateFormatHint')}</MSetHint>
|
||||
</MSetCard>
|
||||
|
||||
<MSetCard title={t('settings.general.travelMap')} icon={Map} className="mt-3">
|
||||
@@ -206,6 +218,17 @@ export default function MSettingsGeneral() {
|
||||
]}
|
||||
/>
|
||||
|
||||
<MSetPickerSheet
|
||||
open={dateFormatOpen}
|
||||
onClose={() => setDateFormatOpen(false)}
|
||||
title={t('settings.dateFormat')}
|
||||
value={settings.date_format || DEFAULT_DATE_FORMAT}
|
||||
onSelect={(v) => {
|
||||
void save('date_format', v)
|
||||
}}
|
||||
options={dateFormats}
|
||||
/>
|
||||
|
||||
<MSetPickerSheet
|
||||
open={langOpen}
|
||||
onClose={() => setLangOpen(false)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Check, ChevronDown, ChevronRight, Copy, KeyRound, Plus, RefreshCw, Terminal, Trash2 } from 'lucide-react'
|
||||
import { useTranslation } from '../../../i18n'
|
||||
import { useFormatDate } from '../../../hooks/useDateFormat'
|
||||
import { OAUTH_PRESETS, useMcpIntegration } from '../../../components/Settings/useMcpIntegration'
|
||||
import MScopeGroupPicker from './MScopeGroupPicker'
|
||||
import MSheet from '../../components/MSheet'
|
||||
@@ -38,7 +39,8 @@ function CodeBlock({ children }: { children: string }) {
|
||||
* tokens, with the desktop modals as floating sheets.
|
||||
*/
|
||||
export default function MSettingsMcp() {
|
||||
const { t, locale } = useTranslation()
|
||||
const { t } = useTranslation()
|
||||
const { formatDate } = useFormatDate()
|
||||
const demoLocked = useDemoAccountLock()
|
||||
const {
|
||||
activeMcpTab: activeTab,
|
||||
@@ -190,7 +192,7 @@ export default function MSettingsMcp() {
|
||||
{t('settings.oauth.clientId')}: {client.client_id}
|
||||
</p>
|
||||
<p className="mt-[2px] font-geist text-[0.625rem] text-m-faint">
|
||||
{t('settings.mcp.tokenCreatedAt')} {new Date(client.created_at).toLocaleDateString(locale)}
|
||||
{t('settings.mcp.tokenCreatedAt')} {formatDate(new Date(client.created_at))}
|
||||
</p>
|
||||
<div className="mt-[6px] flex flex-wrap gap-1">
|
||||
{(scopesExpanded[client.id] ? client.allowed_scopes : client.allowed_scopes.slice(0, 5)).map((s) => (
|
||||
@@ -226,7 +228,7 @@ export default function MSettingsMcp() {
|
||||
{t('settings.oauth.sessionScopes')}: {session.scopes.join(', ')}
|
||||
</p>
|
||||
<p className="font-geist text-[0.625rem] text-m-faint">
|
||||
{t('settings.oauth.sessionExpires')} {new Date(session.access_token_expires_at).toLocaleDateString(locale)}
|
||||
{t('settings.oauth.sessionExpires')} {formatDate(new Date(session.access_token_expires_at))}
|
||||
</p>
|
||||
</div>
|
||||
<MSetButton variant="danger" onClick={() => setRevokeId(session.id)}>
|
||||
@@ -272,11 +274,11 @@ export default function MSettingsMcp() {
|
||||
<p className="mt-[2px] font-mono text-[0.625rem] text-m-faint">
|
||||
{token.token_prefix}...
|
||||
<span className="ms-2 font-geist">
|
||||
{t('settings.mcp.tokenCreatedAt')} {new Date(token.created_at).toLocaleDateString(locale)}
|
||||
{t('settings.mcp.tokenCreatedAt')} {formatDate(new Date(token.created_at))}
|
||||
</span>
|
||||
{token.last_used_at && (
|
||||
<span className="font-geist">
|
||||
, {t('settings.mcp.tokenUsedAt')} {new Date(token.last_used_at).toLocaleDateString(locale)}
|
||||
, {t('settings.mcp.tokenUsedAt')} {formatDate(new Date(token.last_used_at))}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
|
||||
@@ -8,8 +8,10 @@ import MDancingTrek from '../../../components/MDancingTrek'
|
||||
import { useCollabPolls } from '../../../../components/Collab/useCollabPolls'
|
||||
import { useAuthStore } from '../../../../store/authStore'
|
||||
import ToggleSwitch from '../../../../components/Settings/ToggleSwitch'
|
||||
import { CustomDateTimePicker } from '../../../../components/shared/CustomDateTimePicker'
|
||||
import { localIsoDate } from '../../../../utils/localDate'
|
||||
import MSheet from '../../../components/MSheet'
|
||||
import { Eyebrow, FIELD_AREA_CLS, FIELD_CLS, FormSheetFooter, FormSheetHeader } from '../sheets/PlSheetChrome'
|
||||
import { Eyebrow, FIELD_AREA_CLS, FormSheetFooter, FormSheetHeader } from '../sheets/PlSheetChrome'
|
||||
import MConfirmSheet from '../../settings/MConfirmSheet'
|
||||
import type { TripPlanner } from '../MTripShell'
|
||||
import { SectionHeader, TabScroller } from './tabChrome'
|
||||
@@ -338,8 +340,6 @@ function PollFormSheet({ open, onClose, onSubmit, t }: {
|
||||
}
|
||||
}
|
||||
|
||||
const minDeadline = new Date(Date.now() - new Date().getTimezoneOffset() * 60000).toISOString().slice(0, 16)
|
||||
|
||||
return (
|
||||
<MSheet open={open} onClose={onClose} ariaLabel={t('collab.polls.new')}>
|
||||
<FormSheetHeader icon={BarChart3} title={t('collab.polls.new')} onClose={onClose} closeLabel={t('common.close')} />
|
||||
@@ -396,12 +396,11 @@ function PollFormSheet({ open, onClose, onSubmit, t }: {
|
||||
|
||||
<Eyebrow className="mb-[5px] mt-3 uppercase">{t('collab.polls.deadline')}</Eyebrow>
|
||||
<div className="flex items-center gap-[6px]">
|
||||
<input
|
||||
type="datetime-local"
|
||||
<CustomDateTimePicker
|
||||
value={deadline}
|
||||
min={minDeadline}
|
||||
onChange={e => setDeadline(e.target.value)}
|
||||
className={`${FIELD_CLS} flex-1`}
|
||||
onChange={setDeadline}
|
||||
min={localIsoDate()}
|
||||
style={{ flex: 1, minWidth: 0 }}
|
||||
/>
|
||||
{deadline && (
|
||||
<button
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ArrowRightLeft, Building2, Calendar, CalendarRange, CalendarX, ChevronD
|
||||
import MSheet from '../../components/MSheet'
|
||||
import MIconBtn from '../../components/MIconBtn'
|
||||
import MToggle from '../../components/MToggle'
|
||||
import { CustomDatePicker } from '../../../components/shared/CustomDateTimePicker'
|
||||
import ConfirmDialog from '../../../components/shared/ConfirmDialog'
|
||||
import { useVacayStore } from '../../../store/vacayStore'
|
||||
import { useTranslation } from '../../../i18n'
|
||||
@@ -338,13 +339,14 @@ function YearTypeRows() {
|
||||
|
||||
{type === 'anniversary' && (
|
||||
<div className="ms-7">
|
||||
<input
|
||||
type="date"
|
||||
value={yearSettings.hire_date || ''}
|
||||
onChange={e => save({ hire_date: e.target.value || null })}
|
||||
aria-label={t('vacay.hireDate')}
|
||||
className="mt-[7px] w-full rounded-[10px] border border-[color:var(--m-rowbr)] bg-[color:var(--m-sheetop)] px-[11px] py-[9px] font-[inherit] text-[0.78125rem] font-semibold text-m-ink outline-none"
|
||||
/>
|
||||
<div className="mt-[7px]">
|
||||
<CustomDatePicker
|
||||
value={yearSettings.hire_date || ''}
|
||||
onChange={v => save({ hire_date: v || null })}
|
||||
placeholder={t('vacay.hireDate')}
|
||||
ariaLabel={t('vacay.hireDate')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import HelpAnchor from '../components/Help/HelpAnchor'
|
||||
import AtlasCountryPlaces from '../components/Atlas/AtlasCountryPlaces'
|
||||
import { AtlasTourActivityPane } from '../components/Atlas/tourActivity/AtlasTourActivityPane'
|
||||
import { useAddonStore } from '../store/addonStore'
|
||||
import { useFormatDate } from '../hooks/useDateFormat'
|
||||
import { getIntlLanguage } from '@trek/shared'
|
||||
|
||||
// Fixed ids rather than useId: the page body stays free of hooks, and there is only one atlas.
|
||||
@@ -448,6 +449,7 @@ interface SidebarContentProps {
|
||||
|
||||
function SidebarContent({ data, stats, regionsVisited, countryRegions, onOpenPlaces, countries, selectedCountry, countryDetail, resolveName, onTripClick, onUnmarkCountry, bucketList, bucketTab, setBucketTab, showBucketAdd, setShowBucketAdd, bucketForm, setBucketForm, onAddBucket, onDeleteBucket, onClearBucketVisit, onSearchBucket, onSelectBucketPoi, bucketSearchResults, setBucketSearchResults, bucketPoiMonth, setBucketPoiMonth, bucketPoiYear, setBucketPoiYear, bucketSearching, bucketSearch, setBucketSearch, t, dark }: SidebarContentProps): React.ReactElement {
|
||||
const { language } = useTranslation()
|
||||
const { formatDate } = useFormatDate()
|
||||
const toursEnabled = useAddonStore(s => s.isEnabled('tours'))
|
||||
const statsContentRef = useRef<HTMLDivElement>(null)
|
||||
const bucketSearchRowRef = useRef<HTMLDivElement>(null)
|
||||
@@ -548,7 +550,7 @@ ${t('dawarich.bucket.clearVisit')}`
|
||||
style={{ fontSize: 'calc(9px * var(--fs-scale-caption, 1))', background: 'none', border: 'none', padding: 0, cursor: 'pointer', fontFamily: 'inherit' }}
|
||||
>
|
||||
<Check size={9} />
|
||||
{new Date(item.visited_at).toLocaleDateString(getIntlLanguage(language))}
|
||||
{formatDate(new Date(item.visited_at))}
|
||||
</button>
|
||||
) : (
|
||||
<span
|
||||
@@ -556,7 +558,7 @@ ${t('dawarich.bucket.clearVisit')}`
|
||||
style={{ fontSize: 'calc(9px * var(--fs-scale-caption, 1))' }}
|
||||
>
|
||||
<Check size={9} />
|
||||
{new Date(item.visited_at).toLocaleDateString(getIntlLanguage(language))}
|
||||
{formatDate(new Date(item.visited_at))}
|
||||
</span>
|
||||
)}
|
||||
</Tooltip>
|
||||
|
||||
@@ -10,6 +10,8 @@ import { SettingsCard, SettingRows, SettingsHint, StatusPill, SETTINGS_BUTTON, S
|
||||
import type { TranslationFn } from '../../types'
|
||||
import type { useAdmin } from './useAdmin'
|
||||
import { formatLastActive, inviteMeta, inviteStatus } from './adminModel'
|
||||
import { useDateFormat } from '../../hooks/useDateFormat'
|
||||
import { formatDisplayDate } from '../../utils/dateFormat'
|
||||
|
||||
interface AdminUsersTabProps {
|
||||
admin: ReturnType<typeof useAdmin>
|
||||
@@ -36,6 +38,7 @@ function RowIconButton({ label, onClick, disabled, danger, children }: { label:
|
||||
// "Users" admin tab: user table, invite links, permissions panel + the
|
||||
// create-invite modal. Pure layout around the useAdmin hook — no logic of its own.
|
||||
export default function AdminUsersTab({ admin, t, locale }: AdminUsersTabProps): React.ReactElement {
|
||||
const dateFormat = useDateFormat()
|
||||
const {
|
||||
hour12, currentUser,
|
||||
users, isLoading,
|
||||
@@ -112,7 +115,7 @@ export default function AdminUsersTab({ admin, t, locale }: AdminUsersTabProps):
|
||||
: <StatusPill>{t('settings.roleUser')}</StatusPill>}
|
||||
</td>
|
||||
<td className={`${TD} whitespace-nowrap font-geist tabular-nums text-content-muted`} style={fs(12)}>
|
||||
{new Date(u.created_at).toLocaleDateString(locale)}
|
||||
{formatDisplayDate(new Date(u.created_at), locale, {}, dateFormat)}
|
||||
</td>
|
||||
<td className={`${TD} whitespace-nowrap font-geist tabular-nums text-content-muted`} style={fs(12)}>
|
||||
{/* Activity, not only sign-ins: a renewing cookie keeps people signed in for weeks. */}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/** Shared types and pure helpers for the admin page + its data hook. No React, no side effects. */
|
||||
import { readDateFormat } from '../../hooks/useDateFormat'
|
||||
import { formatDisplayDate } from '../../utils/dateFormat'
|
||||
|
||||
export interface AdminUser {
|
||||
id: number
|
||||
@@ -87,7 +89,7 @@ export function inviteMeta(
|
||||
locale: string,
|
||||
): string {
|
||||
const parts = [`${invite.used_count}/${invite.max_uses === 0 ? '∞' : invite.max_uses} ${t('admin.invite.uses')}`]
|
||||
if (invite.expires_at) parts.push(`${t('admin.invite.expiresAt')} ${new Date(invite.expires_at).toLocaleDateString(locale)}`)
|
||||
if (invite.expires_at) parts.push(`${t('admin.invite.expiresAt')} ${formatDisplayDate(new Date(invite.expires_at), locale, {}, readDateFormat())}`)
|
||||
if (invite.trip_title) parts.push(t('admin.invite.boundTo', { trip: invite.trip_title }))
|
||||
parts.push(`${t('admin.invite.createdBy')} ${invite.created_by_name ?? ''}`)
|
||||
return parts.join(', ')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { create } from 'zustand'
|
||||
import { settingsApi } from '../api/client'
|
||||
import type { Settings } from '../types'
|
||||
import { DEFAULT_APPEARANCE, DEFAULT_WEEK_START } from '@trek/shared'
|
||||
import { DEFAULT_APPEARANCE, DEFAULT_DATE_FORMAT, DEFAULT_WEEK_START } from '@trek/shared'
|
||||
import { getApiErrorMessage } from '../types'
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '../i18n/supportedLanguages'
|
||||
import { detectBrowserLanguage } from '../i18n/detectBrowserLanguage'
|
||||
@@ -88,6 +88,7 @@ export const DEFAULT_SETTINGS: Settings = {
|
||||
distance_unit: 'metric',
|
||||
time_format: '24h',
|
||||
week_start: DEFAULT_WEEK_START,
|
||||
date_format: DEFAULT_DATE_FORMAT,
|
||||
show_place_description: false,
|
||||
optimize_from_accommodation: true,
|
||||
map_provider: 'leaflet',
|
||||
|
||||
@@ -32,6 +32,7 @@ import type {
|
||||
AppearanceConfig,
|
||||
ValhallaProvider,
|
||||
WeekStart,
|
||||
DateFormat,
|
||||
} from '@trek/shared'
|
||||
|
||||
export type {
|
||||
@@ -57,6 +58,7 @@ export type {
|
||||
Category,
|
||||
AppearanceConfig,
|
||||
WeekStart,
|
||||
DateFormat,
|
||||
}
|
||||
|
||||
export interface User {
|
||||
@@ -143,6 +145,8 @@ export interface Settings {
|
||||
time_format: string
|
||||
/** First column of every date picker (#2029); missing means Monday. */
|
||||
week_start?: WeekStart
|
||||
/** How dates are written in numbers (#2971); missing means the language's own format. */
|
||||
date_format?: DateFormat
|
||||
show_place_description: boolean
|
||||
blur_booking_codes?: boolean
|
||||
/** Day headings lead with the calendar date and name the trip day second (#1953). */
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
// FE-UTIL-DATEFMT-001 to FE-UTIL-DATEFMT-020
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
dateFieldOrder,
|
||||
dateFormatOptions,
|
||||
dateLayout,
|
||||
displayDateFormatter,
|
||||
formatDisplayDate,
|
||||
formatDisplayDateTime,
|
||||
isNumericDate,
|
||||
} from './dateFormat';
|
||||
|
||||
// 31 December 2026, 15:04 UTC: no day of it reads as a month, so every layout is unambiguous.
|
||||
const NYE = new Date(Date.UTC(2026, 11, 31, 15, 4));
|
||||
const UTC = { timeZone: 'UTC' } as const;
|
||||
const NUMERIC: Intl.DateTimeFormatOptions = { day: '2-digit', month: '2-digit', year: 'numeric', timeZone: 'UTC' };
|
||||
|
||||
describe('dateFormat (#2971)', () => {
|
||||
describe('formatDisplayDate', () => {
|
||||
it('FE-UTIL-DATEFMT-001: auto, an unknown value or none is exactly toLocaleDateString', () => {
|
||||
const cases: [string, Intl.DateTimeFormatOptions | undefined][] = [
|
||||
['en-US', undefined],
|
||||
['en-US', NUMERIC],
|
||||
['de-DE', NUMERIC],
|
||||
['ko-KR', { day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' }],
|
||||
['en-GB', { day: '2-digit', month: '2-digit', year: '2-digit', timeZone: 'UTC' }],
|
||||
];
|
||||
for (const [locale, opts] of cases) {
|
||||
const expected = NYE.toLocaleDateString(locale, opts);
|
||||
expect(formatDisplayDate(NYE, locale, opts, 'auto')).toBe(expected);
|
||||
expect(formatDisplayDate(NYE, locale, opts, 'DD-MM-YY')).toBe(expected);
|
||||
expect(formatDisplayDate(NYE, locale, opts, null)).toBe(expected);
|
||||
expect(formatDisplayDate(NYE, locale, opts)).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it('FE-UTIL-DATEFMT-002: each format writes 31 December 2026 in its own layout', () => {
|
||||
expect(formatDisplayDate(NYE, 'en-US', UTC, 'YYYY-MM-DD')).toBe('2026-12-31');
|
||||
expect(formatDisplayDate(NYE, 'en-US', UTC, 'YYYY/MM/DD')).toBe('2026/12/31');
|
||||
expect(formatDisplayDate(NYE, 'en-US', UTC, 'DD.MM.YYYY')).toBe('31.12.2026');
|
||||
expect(formatDisplayDate(NYE, 'en-US', UTC, 'DD/MM/YYYY')).toBe('31/12/2026');
|
||||
expect(formatDisplayDate(NYE, 'de-DE', UTC, 'MM/DD/YYYY')).toBe('12/31/2026');
|
||||
});
|
||||
|
||||
it('FE-UTIL-DATEFMT-003: the language does not change a chosen format', () => {
|
||||
for (const locale of ['en-US', 'de-DE', 'ko-KR', 'ar', 'fr-FR']) {
|
||||
expect(formatDisplayDate(NYE, locale, NUMERIC, 'YYYY-MM-DD')).toBe('2026-12-31');
|
||||
}
|
||||
});
|
||||
|
||||
it('FE-UTIL-DATEFMT-004: single-digit days and months are padded', () => {
|
||||
const march = new Date(Date.UTC(2026, 2, 5));
|
||||
expect(
|
||||
formatDisplayDate(
|
||||
march,
|
||||
'en-US',
|
||||
{ day: 'numeric', month: 'numeric', year: 'numeric', timeZone: 'UTC' },
|
||||
'DD.MM.YYYY'
|
||||
)
|
||||
).toBe('05.03.2026');
|
||||
});
|
||||
|
||||
it('FE-UTIL-DATEFMT-005: year 2-digit keeps two digits of the year', () => {
|
||||
const opts: Intl.DateTimeFormatOptions = { day: '2-digit', month: '2-digit', year: '2-digit', timeZone: 'UTC' };
|
||||
expect(formatDisplayDate(NYE, 'en-US', opts, 'DD.MM.YYYY')).toBe('31.12.26');
|
||||
expect(formatDisplayDate(NYE, 'en-US', opts, 'YYYY-MM-DD')).toBe('26-12-31');
|
||||
});
|
||||
|
||||
it('FE-UTIL-DATEFMT-006: day and month without a year write only those two, in layout order', () => {
|
||||
const opts: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'numeric', timeZone: 'UTC' };
|
||||
expect(formatDisplayDate(NYE, 'en-US', opts, 'DD.MM.YYYY')).toBe('31.12');
|
||||
expect(formatDisplayDate(NYE, 'en-US', opts, 'YYYY-MM-DD')).toBe('12-31');
|
||||
expect(formatDisplayDate(NYE, 'en-US', opts, 'MM/DD/YYYY')).toBe('12/31');
|
||||
});
|
||||
|
||||
it('FE-UTIL-DATEFMT-007: a month name or a long dateStyle is left to Intl', () => {
|
||||
const named: Intl.DateTimeFormatOptions[] = [
|
||||
{ weekday: 'short', day: 'numeric', month: 'short', timeZone: 'UTC' },
|
||||
{ day: 'numeric', month: 'short', year: 'numeric', timeZone: 'UTC' },
|
||||
{ month: 'long', year: 'numeric', timeZone: 'UTC' },
|
||||
{ dateStyle: 'medium', timeZone: 'UTC' },
|
||||
];
|
||||
for (const opts of named) {
|
||||
expect(formatDisplayDate(NYE, 'de-DE', opts, 'YYYY-MM-DD')).toBe(NYE.toLocaleDateString('de-DE', opts));
|
||||
}
|
||||
});
|
||||
|
||||
it('FE-UTIL-DATEFMT-021: a weekday beside a numeric date keeps the language, the date takes the format', () => {
|
||||
const opts: Intl.DateTimeFormatOptions = {
|
||||
weekday: 'short',
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
timeZone: 'UTC',
|
||||
};
|
||||
expect(formatDisplayDate(NYE, 'de-DE', opts, 'YYYY-MM-DD')).toBe(
|
||||
`${NYE.toLocaleDateString('de-DE', { weekday: 'short', timeZone: 'UTC' })}, 2026-12-31`
|
||||
);
|
||||
expect(formatDisplayDate(NYE, 'de-DE', opts, 'auto')).toBe(NYE.toLocaleDateString('de-DE', opts));
|
||||
});
|
||||
|
||||
it('FE-UTIL-DATEFMT-008: dateStyle short counts as numeric', () => {
|
||||
expect(formatDisplayDate(NYE, 'en-US', { dateStyle: 'short', timeZone: 'UTC' }, 'DD/MM/YYYY')).toBe('31/12/2026');
|
||||
});
|
||||
|
||||
it('FE-UTIL-DATEFMT-009: an invalid date falls back to toLocaleDateString', () => {
|
||||
const invalid = new Date('nope');
|
||||
expect(formatDisplayDate(invalid, 'en-US', NUMERIC, 'YYYY-MM-DD')).toBe(
|
||||
invalid.toLocaleDateString('en-US', NUMERIC)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatDisplayDateTime', () => {
|
||||
it('FE-UTIL-DATEFMT-010: auto, an unknown value or none is exactly toLocaleString', () => {
|
||||
const opts: Intl.DateTimeFormatOptions = { ...NUMERIC, hour: '2-digit', minute: '2-digit' };
|
||||
for (const locale of ['en-US', 'de-DE']) {
|
||||
expect(formatDisplayDateTime(NYE, locale, opts, 'auto')).toBe(NYE.toLocaleString(locale, opts));
|
||||
expect(formatDisplayDateTime(NYE, locale, opts, 'nope')).toBe(NYE.toLocaleString(locale, opts));
|
||||
expect(formatDisplayDateTime(NYE, locale, UTC)).toBe(NYE.toLocaleString(locale, UTC));
|
||||
}
|
||||
});
|
||||
|
||||
it('FE-UTIL-DATEFMT-011: the date follows the format, the time of day stays Intl', () => {
|
||||
const opts: Intl.DateTimeFormatOptions = { ...NUMERIC, hour: '2-digit', minute: '2-digit' };
|
||||
const time = NYE.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', timeZone: 'UTC' });
|
||||
expect(formatDisplayDateTime(NYE, 'en-US', opts, 'YYYY-MM-DD')).toBe(`2026-12-31 ${time}`);
|
||||
const hour12 = { ...opts, hour12: false };
|
||||
const time24 = NYE.toLocaleTimeString('en-US', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false,
|
||||
timeZone: 'UTC',
|
||||
});
|
||||
expect(formatDisplayDateTime(NYE, 'en-US', hour12, 'DD.MM.YYYY')).toBe(`31.12.2026 ${time24}`);
|
||||
});
|
||||
|
||||
it('FE-UTIL-DATEFMT-012: without options it writes the date and the default time', () => {
|
||||
expect(formatDisplayDateTime(NYE, 'en-US', UTC, 'DD/MM/YYYY')).toBe(
|
||||
`31/12/2026 ${NYE.toLocaleTimeString('en-US', UTC)}`
|
||||
);
|
||||
});
|
||||
|
||||
it('FE-UTIL-DATEFMT-013: time-only options are passed through untouched', () => {
|
||||
const opts: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit', timeZone: 'UTC' };
|
||||
expect(formatDisplayDateTime(NYE, 'de-DE', opts, 'YYYY-MM-DD')).toBe(NYE.toLocaleString('de-DE', opts));
|
||||
});
|
||||
|
||||
it('FE-UTIL-DATEFMT-014: date-only options give just the date, named dates stay Intl', () => {
|
||||
expect(formatDisplayDateTime(NYE, 'en-US', NUMERIC, 'YYYY/MM/DD')).toBe('2026/12/31');
|
||||
const named: Intl.DateTimeFormatOptions = {
|
||||
weekday: 'short',
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
timeZone: 'UTC',
|
||||
};
|
||||
expect(formatDisplayDateTime(NYE, 'en-US', named, 'YYYY-MM-DD')).toBe(NYE.toLocaleString('en-US', named));
|
||||
});
|
||||
});
|
||||
|
||||
describe('displayDateFormatter', () => {
|
||||
it('FE-UTIL-DATEFMT-015: auto behaves like Intl.DateTimeFormat', () => {
|
||||
const intl = new Intl.DateTimeFormat('en-US', NUMERIC);
|
||||
expect(displayDateFormatter('en-US', NUMERIC, 'auto').format(NYE)).toBe(intl.format(NYE));
|
||||
expect(displayDateFormatter('en-US', NUMERIC).format(NYE.getTime())).toBe(intl.format(NYE));
|
||||
});
|
||||
|
||||
it('FE-UTIL-DATEFMT-016: numeric dates follow the format, for Date and timestamp alike', () => {
|
||||
const f = displayDateFormatter('en-US', NUMERIC, 'DD.MM.YYYY');
|
||||
expect(f.format(NYE)).toBe('31.12.2026');
|
||||
expect(f.format(NYE.getTime())).toBe('31.12.2026');
|
||||
const withTime = displayDateFormatter('en-US', { ...NUMERIC, hour: '2-digit', minute: '2-digit' }, 'YYYY-MM-DD');
|
||||
expect(withTime.format(NYE)).toBe(
|
||||
`2026-12-31 ${NYE.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', timeZone: 'UTC' })}`
|
||||
);
|
||||
});
|
||||
|
||||
it('FE-UTIL-DATEFMT-017: a named month is left to Intl', () => {
|
||||
const opts: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'long', timeZone: 'UTC' };
|
||||
expect(displayDateFormatter('de-DE', opts, 'YYYY-MM-DD').format(NYE)).toBe(
|
||||
new Intl.DateTimeFormat('de-DE', opts).format(NYE)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dateFieldOrder, dateLayout and isNumericDate', () => {
|
||||
it('FE-UTIL-DATEFMT-018: a chosen format sets the order, auto asks the language', () => {
|
||||
expect(dateFieldOrder('en-US', 'DD.MM.YYYY')).toEqual(['day', 'month', 'year']);
|
||||
expect(dateFieldOrder('de-DE', 'MM/DD/YYYY')).toEqual(['month', 'day', 'year']);
|
||||
expect(dateFieldOrder('en-US', 'YYYY/MM/DD')).toEqual(['year', 'month', 'day']);
|
||||
expect(dateFieldOrder('de-DE', 'auto')).toEqual(['day', 'month', 'year']);
|
||||
expect(dateFieldOrder('en-US')).toEqual(['month', 'day', 'year']);
|
||||
expect(dateFieldOrder('ko-KR', null)).toEqual(['year', 'month', 'day']);
|
||||
});
|
||||
|
||||
it('FE-UTIL-DATEFMT-019: dateLayout knows the five formats only, isNumericDate spots names', () => {
|
||||
expect(dateLayout('DD/MM/YYYY')).toEqual({ order: ['day', 'month', 'year'], separator: '/' });
|
||||
expect(dateLayout('auto')).toBeNull();
|
||||
expect(dateLayout('nope')).toBeNull();
|
||||
expect(dateLayout(undefined)).toBeNull();
|
||||
expect(isNumericDate()).toBe(true);
|
||||
expect(isNumericDate({ month: '2-digit' })).toBe(true);
|
||||
expect(isNumericDate({ month: 'short' })).toBe(false);
|
||||
expect(isNumericDate({ weekday: 'long' })).toBe(false);
|
||||
expect(isNumericDate({ era: 'short' })).toBe(false);
|
||||
expect(isNumericDate({ dateStyle: 'short' })).toBe(true);
|
||||
expect(isNumericDate({ dateStyle: 'long' })).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dateFormatOptions', () => {
|
||||
it('FE-UTIL-DATEFMT-020: every choice is labelled by how it writes 31 December, auto with its example', () => {
|
||||
expect(dateFormatOptions('en-US', 'Automatic')).toEqual([
|
||||
{ value: 'auto', label: 'Automatic (12/31/2026)' },
|
||||
{ value: 'YYYY-MM-DD', label: '2026-12-31' },
|
||||
{ value: 'YYYY/MM/DD', label: '2026/12/31' },
|
||||
{ value: 'DD.MM.YYYY', label: '31.12.2026' },
|
||||
{ value: 'DD/MM/YYYY', label: '31/12/2026' },
|
||||
{ value: 'MM/DD/YYYY', label: '12/31/2026' },
|
||||
]);
|
||||
expect(dateFormatOptions('de-DE', 'Automatisch')[0]).toEqual({
|
||||
value: 'auto',
|
||||
label: 'Automatisch (31.12.2026)',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,182 @@
|
||||
import { DATE_FORMAT_VALUES, type DateFormat } from '@trek/shared';
|
||||
|
||||
/**
|
||||
* The user's date format (#2971), applied wherever a date is written in numbers.
|
||||
*
|
||||
* Every function here takes the format as an argument and stays pure; the
|
||||
* current user's value comes from `useDateFormat()` (or `readDateFormat()`
|
||||
* outside React) in hooks/useDateFormat.ts. 'auto', an unknown value or none
|
||||
* at all leaves everything to `Intl` and the interface language, exactly as
|
||||
* TREK wrote dates before the setting existed.
|
||||
*
|
||||
* Only the numeric part follows the setting. A date written with a weekday or
|
||||
* a month name ("Tue, 3 Nov") keeps the language's own wording and order: the
|
||||
* setting answers "how do I read 03/11", not "what is November called".
|
||||
*/
|
||||
|
||||
export type DateField = 'day' | 'month' | 'year';
|
||||
|
||||
interface DateLayout {
|
||||
order: DateField[];
|
||||
separator: string;
|
||||
}
|
||||
|
||||
const LAYOUTS: Record<Exclude<DateFormat, 'auto'>, DateLayout> = {
|
||||
'YYYY-MM-DD': { order: ['year', 'month', 'day'], separator: '-' },
|
||||
'YYYY/MM/DD': { order: ['year', 'month', 'day'], separator: '/' },
|
||||
'DD.MM.YYYY': { order: ['day', 'month', 'year'], separator: '.' },
|
||||
'DD/MM/YYYY': { order: ['day', 'month', 'year'], separator: '/' },
|
||||
'MM/DD/YYYY': { order: ['month', 'day', 'year'], separator: '/' },
|
||||
};
|
||||
|
||||
/** The order and separator of a chosen format; null for 'auto' or anything unknown. */
|
||||
export function dateLayout(format: string | null | undefined): DateLayout | null {
|
||||
if (!format || format === 'auto') return null;
|
||||
return LAYOUTS[format as keyof typeof LAYOUTS] ?? null;
|
||||
}
|
||||
|
||||
const DATE_KEYS = ['day', 'month', 'year'] as const;
|
||||
const TIME_KEYS = ['hour', 'minute', 'second', 'dayPeriod', 'fractionalSecondDigits'] as const;
|
||||
|
||||
/**
|
||||
* Whether these options write the date in numbers only: no weekday, no month
|
||||
* name, no era. `dateStyle: 'short'` is numeric in every language; the longer
|
||||
* styles name the month.
|
||||
*/
|
||||
export function isNumericDate(opts: Intl.DateTimeFormatOptions = {}): boolean {
|
||||
if (opts.weekday || opts.era) return false;
|
||||
if (opts.dateStyle) return opts.dateStyle === 'short';
|
||||
return !opts.month || opts.month === 'numeric' || opts.month === '2-digit';
|
||||
}
|
||||
|
||||
const hasAny = (opts: Intl.DateTimeFormatOptions, keys: readonly string[]) =>
|
||||
keys.some((key) => (opts as Record<string, unknown>)[key] !== undefined);
|
||||
|
||||
/** The date in the layout's order, each field the options ask for (all three when they name none). */
|
||||
function numericDate(date: Date, opts: Intl.DateTimeFormatOptions, layout: DateLayout): string {
|
||||
const named = opts.dateStyle ? DATE_KEYS : DATE_KEYS.filter((key) => opts[key] !== undefined);
|
||||
const fields = named.length > 0 ? named : DATE_KEYS;
|
||||
const parts = new Intl.DateTimeFormat('en-US', {
|
||||
timeZone: opts.timeZone,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
numberingSystem: 'latn',
|
||||
}).formatToParts(date);
|
||||
const value = (type: DateField) => parts.find((part) => part.type === type)?.value ?? '';
|
||||
const shortYear = opts.year === '2-digit';
|
||||
return layout.order
|
||||
.filter((field) => (fields as readonly string[]).includes(field))
|
||||
.map((field) => (field === 'year' && shortYear ? value('year').slice(-2) : value(field)))
|
||||
.join(layout.separator);
|
||||
}
|
||||
|
||||
/** Only the time-of-day options, for the clock part beside a numeric date. */
|
||||
function timeOptions(opts: Intl.DateTimeFormatOptions): Intl.DateTimeFormatOptions {
|
||||
const time: Intl.DateTimeFormatOptions = {};
|
||||
for (const key of [...TIME_KEYS, 'hour12', 'hourCycle', 'timeZone', 'timeZoneName'] as const) {
|
||||
const value = (opts as Record<string, unknown>)[key];
|
||||
if (value !== undefined) (time as Record<string, unknown>)[key] = value;
|
||||
}
|
||||
if (opts.timeStyle) time.timeStyle = opts.timeStyle;
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* `date.toLocaleDateString(locale, opts)`, with a numeric date written in the
|
||||
* chosen format. Anything with a weekday or a month name is left to `Intl`.
|
||||
*/
|
||||
export function formatDisplayDate(
|
||||
date: Date,
|
||||
locale: string | undefined,
|
||||
opts: Intl.DateTimeFormatOptions = {},
|
||||
format?: string | null
|
||||
): string {
|
||||
const layout = dateLayout(format);
|
||||
if (!layout || Number.isNaN(date.getTime())) return date.toLocaleDateString(locale, opts);
|
||||
if (opts.weekday && isNumericWithWeekday(opts)) {
|
||||
const weekday = date.toLocaleDateString(locale, { weekday: opts.weekday, timeZone: opts.timeZone });
|
||||
return `${weekday}, ${numericDate(date, opts, layout)}`;
|
||||
}
|
||||
if (!isNumericDate(opts)) return date.toLocaleDateString(locale, opts);
|
||||
return numericDate(date, opts, layout);
|
||||
}
|
||||
|
||||
/** A weekday beside a date that is otherwise all numbers ("Tue, 03.11.2026"). */
|
||||
function isNumericWithWeekday(opts: Intl.DateTimeFormatOptions): boolean {
|
||||
if (opts.era || opts.dateStyle) return false;
|
||||
return (opts.month === 'numeric' || opts.month === '2-digit') && opts.day !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* `date.toLocaleString(locale, opts)`, with a numeric date part written in the
|
||||
* chosen format and the time of day left to `Intl` (12h/24h stays the
|
||||
* caller's option). Options naming only a time are passed through untouched.
|
||||
*/
|
||||
export function formatDisplayDateTime(
|
||||
date: Date,
|
||||
locale: string | undefined,
|
||||
opts: Intl.DateTimeFormatOptions = {},
|
||||
format?: string | null
|
||||
): string {
|
||||
const layout = dateLayout(format);
|
||||
const named = hasAny(opts, DATE_KEYS) || !!opts.dateStyle;
|
||||
const timed = hasAny(opts, TIME_KEYS) || !!opts.timeStyle;
|
||||
if (!layout || !isNumericDate(opts) || Number.isNaN(date.getTime())) return date.toLocaleString(locale, opts);
|
||||
if (!named && timed) return date.toLocaleString(locale, opts);
|
||||
const day = numericDate(date, opts, layout);
|
||||
if (named && !timed) return day;
|
||||
const time = date.toLocaleTimeString(locale, timed ? timeOptions(opts) : { timeZone: opts.timeZone });
|
||||
return `${day} ${time}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* A drop-in for `new Intl.DateTimeFormat(locale, opts)` where only `.format()`
|
||||
* is used: numeric dates in the chosen format, the rest by `Intl`.
|
||||
*/
|
||||
export function displayDateFormatter(
|
||||
locale: string | undefined,
|
||||
opts: Intl.DateTimeFormatOptions = {},
|
||||
format?: string | null
|
||||
): { format: (date: Date | number) => string } {
|
||||
const intl = new Intl.DateTimeFormat(locale, opts);
|
||||
const layout = dateLayout(format);
|
||||
if (!layout || !isNumericDate(opts)) return { format: (date) => intl.format(date) };
|
||||
return {
|
||||
format: (date) => {
|
||||
const value = typeof date === 'number' ? new Date(date) : date;
|
||||
return hasAny(opts, TIME_KEYS) || opts.timeStyle
|
||||
? formatDisplayDateTime(value, locale, opts, format)
|
||||
: formatDisplayDate(value, locale, opts, format);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** The order the fields of a typed date are read in: the chosen format's, or the language's. */
|
||||
export function dateFieldOrder(locale: string | undefined, format?: string | null): DateField[] {
|
||||
const layout = dateLayout(format);
|
||||
if (layout) return layout.order;
|
||||
return new Intl.DateTimeFormat(locale, { day: 'numeric', month: 'numeric', year: 'numeric' })
|
||||
.formatToParts(new Date(2001, 5, 15))
|
||||
.filter((part) => part.type === 'day' || part.type === 'month' || part.type === 'year')
|
||||
.map((part) => part.type as DateField);
|
||||
}
|
||||
|
||||
/** 31 December: no day of it reads as a month, so every option's example is unambiguous. */
|
||||
const SAMPLE = new Date(Date.UTC(2026, 11, 31));
|
||||
|
||||
/**
|
||||
* The choices of the setting, each labelled by how it writes 31 December, and
|
||||
* 'auto' by `autoLabel` with the language's own way in brackets.
|
||||
*/
|
||||
export function dateFormatOptions(
|
||||
locale: string | undefined,
|
||||
autoLabel: string
|
||||
): { value: DateFormat; label: string }[] {
|
||||
const example = (format: DateFormat) =>
|
||||
formatDisplayDate(SAMPLE, locale, { day: '2-digit', month: '2-digit', year: 'numeric', timeZone: 'UTC' }, format);
|
||||
return DATE_FORMAT_VALUES.map((value) => ({
|
||||
value,
|
||||
label: value === 'auto' ? `${autoLabel} (${example(value)})` : example(value),
|
||||
}));
|
||||
}
|
||||
@@ -15,4 +15,18 @@ describe('dateInputPattern', () => {
|
||||
expect(dateInputPattern('ar')).not.toMatch(/[٠-٩\d]/);
|
||||
expect(dateInputPattern('ar')).toContain('YYYY');
|
||||
});
|
||||
|
||||
it('writes a chosen date format (#2971) as its own pattern, whatever the language', () => {
|
||||
expect(dateInputPattern('en-US', 'YYYY-MM-DD')).toBe('YYYY-MM-DD');
|
||||
expect(dateInputPattern('en-US', 'DD.MM.YYYY')).toBe('DD.MM.YYYY');
|
||||
expect(dateInputPattern('de-DE', 'MM/DD/YYYY')).toBe('MM/DD/YYYY');
|
||||
expect(dateInputPattern('ko-KR', 'YYYY/MM/DD')).toBe('YYYY/MM/DD');
|
||||
});
|
||||
|
||||
it('keeps the locale pattern for auto, an unknown value or none', () => {
|
||||
expect(dateInputPattern('de-DE', 'auto')).toBe('DD.MM.YYYY');
|
||||
expect(dateInputPattern('en-US', 'auto')).toBe('MM/DD/YYYY');
|
||||
expect(dateInputPattern('en-GB', 'DD-MM-YY')).toBe('DD/MM/YYYY');
|
||||
expect(dateInputPattern('en-GB', null)).toBe('DD/MM/YYYY');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { dateLayout } from './dateFormat';
|
||||
|
||||
const LETTERS: Record<string, string> = { day: 'DD', month: 'MM', year: 'YYYY' };
|
||||
|
||||
/**
|
||||
@@ -5,9 +7,11 @@ const LETTERS: Record<string, string> = { day: 'DD', month: 'MM', year: 'YYYY' }
|
||||
* locale's own separators. It is the hint of the manual date field, which is
|
||||
* pre-filled and read back in this same order, so a Korean user sees
|
||||
* "YYYY. MM. DD." rather than a German "DD.MM.YYYY". Direction marks are dropped:
|
||||
* the hint shows the shape, the field itself carries the script.
|
||||
* the hint shows the shape, the field itself carries the script. A chosen date
|
||||
* format (#2971) is its own pattern.
|
||||
*/
|
||||
export function dateInputPattern(locale: string): string {
|
||||
export function dateInputPattern(locale: string, format?: string | null): string {
|
||||
if (dateLayout(format)) return String(format);
|
||||
return new Intl.DateTimeFormat(locale, { day: '2-digit', month: '2-digit', year: 'numeric', timeZone: 'UTC' })
|
||||
.formatToParts(new Date(Date.UTC(2001, 5, 15)))
|
||||
.map((part) => LETTERS[part.type] ?? part.value.replace(/[]/g, ''))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// FE-MOB-MDUS-001 to FE-MOB-MDUS-030
|
||||
// FE-MOB-MDUS-001 to FE-MOB-MDUS-032
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { render, screen, waitFor, within, fireEvent } from '../../../helpers/render';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
@@ -500,3 +500,36 @@ describe('MAdminDefaultUserSettings, the routing service', () => {
|
||||
expect(screen.queryByRole('combobox', { name: 'Routing service' })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('MAdminDefaultUserSettings date format (#2971)', () => {
|
||||
beforeEach(() => {
|
||||
resetAllStores();
|
||||
seedStore(useAuthStore, { isAuthenticated: true, user: buildAdmin() });
|
||||
});
|
||||
|
||||
it('FE-MOB-MDUS-031: the date-format sheet saves its own key', async () => {
|
||||
const user = userEvent.setup();
|
||||
const { puts } = stubDefaults({ date_format: 'YYYY-MM-DD' });
|
||||
render(<MAdminDefaultUserSettings />);
|
||||
await screen.findByText('Default User Settings');
|
||||
|
||||
// The choices are labelled by how they write 31 December.
|
||||
await user.click(screen.getByRole('button', { name: '2026-12-31' }));
|
||||
const sheet = await screen.findByRole('dialog');
|
||||
await user.click(within(sheet).getByRole('button', { name: '31.12.2026' }));
|
||||
|
||||
await waitFor(() => expect(puts).toEqual([{ date_format: 'DD.MM.YYYY' }]));
|
||||
});
|
||||
|
||||
it('FE-MOB-MDUS-032: a stored date format can be reset', async () => {
|
||||
const user = userEvent.setup();
|
||||
const { puts } = stubDefaults({ date_format: 'DD/MM/YYYY' });
|
||||
render(<MAdminDefaultUserSettings />);
|
||||
await screen.findByText('Default User Settings');
|
||||
|
||||
// The only default set, so the only reset link on the page.
|
||||
await user.click(screen.getByRole('button', { name: 'reset' }));
|
||||
|
||||
await waitFor(() => expect(puts).toEqual([{ date_format: null }]));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -231,3 +231,24 @@ describe('MSettingsGeneral week start (#2029)', () => {
|
||||
expect(updateSetting).toHaveBeenCalledWith('week_start', 'saturday');
|
||||
});
|
||||
});
|
||||
|
||||
describe('MSettingsGeneral date format (#2971)', () => {
|
||||
it('FE-MOB-SET-018: the date-format row shows the language format when unset and the sheet saves a pick', async () => {
|
||||
const user = userEvent.setup();
|
||||
const updateSetting = vi.fn().mockResolvedValue(undefined);
|
||||
seedStore(useSettingsStore, { settings: buildSettings({ language: 'en', date_format: undefined }), updateSetting });
|
||||
render(<MSettingsGeneral />);
|
||||
|
||||
// The choices are labelled by how they write 31 December; auto adds the language's own way.
|
||||
await user.click(screen.getByRole('button', { name: /\(12\/31\/2026\)/ }));
|
||||
await user.click(await screen.findByRole('button', { name: '31.12.2026' }));
|
||||
expect(updateSetting).toHaveBeenCalledWith('date_format', 'DD.MM.YYYY');
|
||||
});
|
||||
|
||||
it('FE-MOB-SET-019: a stored format is shown on its row', () => {
|
||||
seedStore(useSettingsStore, { settings: buildSettings({ language: 'en', date_format: 'YYYY/MM/DD' }) });
|
||||
render(<MSettingsGeneral />);
|
||||
|
||||
expect(screen.getByRole('button', { name: '2026/12/31' })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,8 +53,13 @@ function wsHandler() {
|
||||
return calls[calls.length - 1][0]
|
||||
}
|
||||
|
||||
function deadlineInput(): HTMLInputElement {
|
||||
return document.querySelector('input[type="datetime-local"]') as HTMLInputElement
|
||||
// The deadline is the shared date and time picker (#2971): the day typed in its
|
||||
// manual field, the time left at the noon it starts from.
|
||||
function setDeadline(day: string) {
|
||||
fireEvent.click(screen.getByRole('button', { name: /enterManually|Enter date manually/ }))
|
||||
const input = screen.getByRole('textbox', { name: /enterManually|Enter date manually/ })
|
||||
fireEvent.change(input, { target: { value: day } })
|
||||
fireEvent.keyDown(input, { key: 'Enter' })
|
||||
}
|
||||
|
||||
describe('MCollabPolls', () => {
|
||||
@@ -335,7 +340,7 @@ describe('MCollabPolls', () => {
|
||||
fireEvent.change(screen.getByPlaceholderText('collab.polls.optionPlaceholder:1'), { target: { value: 'Ryokan' } })
|
||||
fireEvent.change(screen.getByPlaceholderText('collab.polls.optionPlaceholder:2'), { target: { value: ' Capsule ' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'collab.polls.multiChoice' }))
|
||||
fireEvent.change(deadlineInput(), { target: { value: '2026-08-01T12:00' } })
|
||||
setDeadline('2030-08-01')
|
||||
fireEvent.click(screen.getByRole('button', { name: 'collab.polls.create' }))
|
||||
|
||||
await waitFor(() => expect(createPoll).toHaveBeenCalledWith(1, {
|
||||
@@ -343,7 +348,7 @@ describe('MCollabPolls', () => {
|
||||
options: ['Ryokan', 'Capsule'],
|
||||
multiple: true,
|
||||
multiple_choice: true,
|
||||
deadline: new Date('2026-08-01T12:00').toISOString(),
|
||||
deadline: new Date('2030-08-01T12:00').toISOString(),
|
||||
}))
|
||||
expect(await screen.findByText('Which hotel?')).toBeInTheDocument()
|
||||
})
|
||||
@@ -381,10 +386,9 @@ describe('MCollabPolls', () => {
|
||||
fireEvent.click(screen.getAllByRole('button', { name: 'common.delete' })[0])
|
||||
expect(screen.queryByPlaceholderText('collab.polls.optionPlaceholder:3')).not.toBeInTheDocument()
|
||||
|
||||
fireEvent.change(deadlineInput(), { target: { value: '2026-08-01T12:00' } })
|
||||
expect(deadlineInput()).toHaveValue('2026-08-01T12:00')
|
||||
setDeadline('2030-08-01')
|
||||
fireEvent.click(screen.getByRole('button', { name: 'collab.polls.clearDeadline' }))
|
||||
expect(deadlineInput()).toHaveValue('')
|
||||
expect(screen.queryByRole('button', { name: 'collab.polls.clearDeadline' })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('FE-MOB-POLLS-025: keeps the form open and toasts when creating fails', async () => {
|
||||
|
||||
@@ -69,6 +69,19 @@ async function waitForCountries() {
|
||||
await waitFor(() => expect(screen.getAllByRole('option', { name: 'Germany' }).length).toBeGreaterThan(0));
|
||||
}
|
||||
|
||||
// The hire date is the shared date picker (#2971): typed through its manual
|
||||
// field, cleared from its calendar.
|
||||
function typeDate(trigger: HTMLElement, value: string) {
|
||||
fireEvent.click(within(trigger.parentElement as HTMLElement).getByRole('button', { name: /enterManually|Enter date manually/ }))
|
||||
const input = screen.getByRole('textbox', { name: /enterManually|Enter date manually/ })
|
||||
fireEvent.change(input, { target: { value } })
|
||||
fireEvent.keyDown(input, { key: 'Enter' })
|
||||
}
|
||||
function clearDate(trigger: HTMLElement) {
|
||||
fireEvent.click(trigger)
|
||||
fireEvent.click(screen.getByRole('button', { name: /clearDate|Clear date/ }))
|
||||
}
|
||||
|
||||
describe('MVacaySettingsSheet', () => {
|
||||
it('FE-MOB-MVACSET-001: renders nothing without a plan', () => {
|
||||
useVacayStore.setState({ plan: null });
|
||||
@@ -217,15 +230,15 @@ describe('MVacaySettingsSheet', () => {
|
||||
});
|
||||
renderSheet();
|
||||
|
||||
const hire = screen.getByLabelText('Hire date');
|
||||
expect(hire).toHaveValue('2020-09-14');
|
||||
const hire = () => screen.getByRole('button', { name: /^Hire date: / });
|
||||
expect(hire()).toHaveAccessibleName(/2020/);
|
||||
|
||||
fireEvent.change(hire, { target: { value: '2021-03-02' } });
|
||||
typeDate(hire(), '2021-03-02');
|
||||
expect(updateYearSettings).toHaveBeenLastCalledWith({
|
||||
year_type: 'anniversary', year_start_month: 1, year_start_day: 1, hire_date: '2021-03-02',
|
||||
});
|
||||
|
||||
fireEvent.change(hire, { target: { value: '' } });
|
||||
clearDate(hire());
|
||||
expect(updateYearSettings).toHaveBeenLastCalledWith({
|
||||
year_type: 'anniversary', year_start_month: 1, year_start_day: 1, hire_date: null,
|
||||
});
|
||||
|
||||
@@ -156,6 +156,7 @@ export const MANAGED_CUSTOMER_KEYS = [
|
||||
'temperature_unit',
|
||||
'time_format',
|
||||
'week_start',
|
||||
'date_format',
|
||||
] as const;
|
||||
|
||||
const LOCKED = new Set<string>(MANAGED_LOCKED_SETTING_KEYS);
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
SUPPORTED_LANGUAGE_CODES,
|
||||
settingsBulkRequestSchema,
|
||||
weekStartSchema,
|
||||
dateFormatSchema,
|
||||
} from '@trek/shared';
|
||||
|
||||
import { z } from 'zod';
|
||||
@@ -67,6 +68,7 @@ const DISPLAY_PREFERENCES = {
|
||||
distance_unit: z.enum(['metric', 'imperial']),
|
||||
time_format: z.enum(['12h', '24h']),
|
||||
week_start: weekStartSchema,
|
||||
date_format: dateFormatSchema,
|
||||
// Booleans are the pre-'auto' form and still sit in older rows, so they stay
|
||||
// writable: this is exactly the VALID_VALUES.dark_mode set the admin-defaults
|
||||
// path accepts, and refusing them here would make a value TREK itself wrote
|
||||
@@ -132,7 +134,7 @@ export class SettingsMcp {
|
||||
|
||||
@Tool({
|
||||
name: 'get_display_settings',
|
||||
description: `Read the current user's display preferences: ${KEY_LIST}. Call this before rendering a temperature, a distance, a time of day or an amount, so the answer matches what the person sees inside TREK instead of being guessed, and call it before update_display_settings whenever the request is relative ("switch me back", "use the other unit"). Only keys that are actually set are returned; TREK's own fallbacks for the rest are celsius, metric, 24h, week_start monday, language en, start_page dashboard, start_trip_tab plan, and no personal default currency, which means amounts fall back to the currency of the trip they belong to. Stored credentials (map tokens, LLM API keys, webhook URLs, SMTP) are deliberately not part of this surface and never appear in the result.`,
|
||||
description: `Read the current user's display preferences: ${KEY_LIST}. Call this before rendering a temperature, a distance, a time of day or an amount, so the answer matches what the person sees inside TREK instead of being guessed, and call it before update_display_settings whenever the request is relative ("switch me back", "use the other unit"). Only keys that are actually set are returned; TREK's own fallbacks for the rest are celsius, metric, 24h, week_start monday, date_format auto (the interface language's own format), language en, start_page dashboard, start_trip_tab plan, and no personal default currency, which means amounts fall back to the currency of the trip they belong to. Stored credentials (map tokens, LLM API keys, webhook URLs, SMTP) are deliberately not part of this surface and never appear in the result.`,
|
||||
inputSchema: {},
|
||||
annotations: TOOL_ANNOTATIONS_READONLY,
|
||||
access: { group: 'settings', mode: 'read' },
|
||||
@@ -143,7 +145,7 @@ export class SettingsMcp {
|
||||
|
||||
@Tool({
|
||||
name: 'update_display_settings',
|
||||
description: `Change one or more of the current user's display preferences. Pass a settings object holding only the keys to change; anything left out keeps its current value. Writable keys: ${KEY_LIST}. start_page is dashboard or active_trip, start_trip_tab is one of plan, transports, buchungen, listen, finanzplan, dateien, collab (the ids are the planner's internal German ones) or plugin:<id> for a plugin tab, default_currency is a three-letter ISO code or "" to fall back to each trip's own currency, dark_mode is light, dark or auto, week_start is monday, sunday or saturday and sets the first column of every date picker in TREK. Every other key is refused, including API keys, map tokens, LLM endpoint settings and SMTP: those belong to the account owner or to whoever operates the instance and are set in TREK itself, not here. Prefer get_display_settings when you only need to read a value.`,
|
||||
description: `Change one or more of the current user's display preferences. Pass a settings object holding only the keys to change; anything left out keeps its current value. Writable keys: ${KEY_LIST}. start_page is dashboard or active_trip, start_trip_tab is one of plan, transports, buchungen, listen, finanzplan, dateien, collab (the ids are the planner's internal German ones) or plugin:<id> for a plugin tab, default_currency is a three-letter ISO code or "" to fall back to each trip's own currency, dark_mode is light, dark or auto, week_start is monday, sunday or saturday and sets the first column of every date picker in TREK, date_format is auto (the interface language's format), YYYY-MM-DD, YYYY/MM/DD, DD.MM.YYYY, DD/MM/YYYY or MM/DD/YYYY and sets how every date field and numeric date in TREK is written. Every other key is refused, including API keys, map tokens, LLM endpoint settings and SMTP: those belong to the account owner or to whoever operates the instance and are set in TREK itself, not here. Prefer get_display_settings when you only need to read a value.`,
|
||||
inputSchema: {
|
||||
settings: settingsBulkRequestSchema.shape.settings.describe(
|
||||
`Object of preference key to new value, e.g. {"temperature_unit": "fahrenheit", "distance_unit": "imperial"}. At least one key, and every key must be one of: ${KEY_LIST}.`,
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
MASKED_SETTING_VALUE,
|
||||
PLANNER_TRIP_VIEWS_KEY,
|
||||
WEEK_START_VALUES,
|
||||
DATE_FORMAT_VALUES,
|
||||
compactPlannerTripViews,
|
||||
normalizeAppearance,
|
||||
plannerTripViewsSchema,
|
||||
@@ -43,6 +44,8 @@ export const DEFAULTABLE_USER_SETTING_KEYS = [
|
||||
'time_format',
|
||||
// First day of the week in every date picker (#2029).
|
||||
'week_start',
|
||||
// How dates are written in numbers, in every date field and list (#2971).
|
||||
'date_format',
|
||||
// Instance-wide default currency for Costs (new users inherit it until they
|
||||
// pick their own). Free-form ISO code, validated on the client.
|
||||
'default_currency',
|
||||
@@ -110,6 +113,7 @@ const VALID_VALUES: Partial<Record<DefaultableKey, unknown[]>> = {
|
||||
distance_unit: ['metric', 'imperial'],
|
||||
time_format: ['12h', '24h'],
|
||||
week_start: [...WEEK_START_VALUES],
|
||||
date_format: [...DATE_FORMAT_VALUES],
|
||||
dark_mode: [true, false, 'light', 'dark', 'auto'],
|
||||
map_provider: ['leaflet', 'mapbox-gl', 'maplibre-gl'],
|
||||
llm_provider: ['local', 'openai', 'anthropic'],
|
||||
|
||||
@@ -338,6 +338,28 @@ describe('Settings e2e (real auth guard + temp SQLite)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('date_format (#2971)', () => {
|
||||
it('an admin default reaches a user who never picked a date format', async () => {
|
||||
const put = await request(server)
|
||||
.put('/api/admin/default-user-settings')
|
||||
.set('Cookie', sessionCookie(2))
|
||||
.send({ date_format: 'YYYY-MM-DD' });
|
||||
expect(put.status).toBe(200);
|
||||
const res = await request(server).get('/api/settings').set('Cookie', sessionCookie(1));
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.settings.date_format).toBe('YYYY-MM-DD');
|
||||
});
|
||||
|
||||
it('PUT /api/admin/default-user-settings 400 for a format the app does not offer', async () => {
|
||||
const res = await request(server)
|
||||
.put('/api/admin/default-user-settings')
|
||||
.set('Cookie', sessionCookie(2))
|
||||
.send({ date_format: 'YY.MM.DD' });
|
||||
expect(res.status).toBe(400);
|
||||
expect(await findRow(orm, AppSettings, { key: 'default_user_setting_date_format' })).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('planner_trip_views (#2639)', () => {
|
||||
beforeEach(async () => {
|
||||
await deleteRows(orm, TripMembers);
|
||||
|
||||
@@ -264,6 +264,19 @@ describe('Tool: update_display_settings', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('writes date_format and refuses a format the app does not offer (#2971)', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
await withHarness(user.id, async (h) => {
|
||||
const ok = parseToolResult(await update(h, { date_format: 'DD.MM.YYYY' })) as {
|
||||
settings: Record<string, unknown>;
|
||||
};
|
||||
expect(ok.settings.date_format).toBe('DD.MM.YYYY');
|
||||
const refused = await update(h, { date_format: 'dd-mm' });
|
||||
expect(refused.isError).toBe(true);
|
||||
expect(await readSetting(user.id, 'date_format')).toBe('DD.MM.YYYY');
|
||||
});
|
||||
});
|
||||
|
||||
it('writes week_start and refuses a day outside the three the app offers (#2029)', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
await withHarness(user.id, async (h) => {
|
||||
|
||||
@@ -387,6 +387,22 @@ describe('legacy quirk fixes', () => {
|
||||
expect((await svc.getUserSettings(own.id)).week_start).toBe('saturday');
|
||||
});
|
||||
|
||||
it('SET-SVC-037 — a date_format default reaches users without their own, and theirs wins (#2971)', async () => {
|
||||
const { user: plain } = createUser(testDb);
|
||||
const { user: own } = createUser(testDb);
|
||||
await svc.setAdminUserDefaults({ date_format: 'YYYY-MM-DD' });
|
||||
await svc.upsertSetting(own.id, 'date_format', 'DD/MM/YYYY');
|
||||
expect((await svc.getUserSettings(plain.id)).date_format).toBe('YYYY-MM-DD');
|
||||
expect((await svc.getUserSettings(own.id)).date_format).toBe('DD/MM/YYYY');
|
||||
});
|
||||
|
||||
it('SET-SVC-038 — a date_format default the app does not offer is refused and not stored', async () => {
|
||||
await expect(svc.setAdminUserDefaults({ date_format: 'YY-MM-DD' })).rejects.toThrow(
|
||||
/Invalid value for date_format/,
|
||||
);
|
||||
expect((await svc.getAdminUserDefaults()).date_format).toBeUndefined();
|
||||
});
|
||||
|
||||
it('SET-SVC-036 — a week_start default outside monday/sunday/saturday is refused and not stored', async () => {
|
||||
await expect(svc.setAdminUserDefaults({ week_start: 'friday' })).rejects.toThrow(/Invalid value for week_start/);
|
||||
// Vacay's 0/1 numbers are not this setting's values either.
|
||||
|
||||
@@ -1063,7 +1063,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
'الشريط الجانبي على اليسار: «العرض» و«المظهر» و«الخريطة» و«الإشعارات» و«التكاملات» و«غير متصل» و«الحساب». ويظهر «الإضافات الخارجية» عند وجود إضافة نشطة. ويظهر «حول» ما لم يخفه المشغّل. ويظهر الإصدار في الأسفل.',
|
||||
'help.ctx.settings.bullet.2':
|
||||
'يفتح «العرض» أولًا. ويضم اللغة، ولغة أسماء الأماكن، والوحدات، وتنسيق الوقت، وبداية الأسبوع، وعملة العرض، إضافة إلى سلوك خريطة الرحلة. ويضم «المظهر» السمة والألوان وسهولة القراءة وحجم النص وأدوات لوحة التحكم.',
|
||||
'يفتح «العرض» أولًا. ويضم اللغة، ولغة أسماء الأماكن، والوحدات، وتنسيق الوقت، وتنسيق التاريخ، وبداية الأسبوع، وعملة العرض، إضافة إلى سلوك خريطة الرحلة. ويضم «المظهر» السمة والألوان وسهولة القراءة وحجم النص وأدوات لوحة التحكم.',
|
||||
'help.ctx.settings.bullet.3':
|
||||
'«الخريطة» تحدد محرك الخريطة ونمطها؛ و«الإشعارات» تحدد أي الأحداث تصل عبر أي قناة؛ و«التكاملات» تربط مكتبات الصور وAirTrail وDawarich وwanderer، وفيها مفاتيح API وMCP؛ و«غير متصل» يحدد ما يخزّنه هذا الجهاز.',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1074,7 +1074,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'«البدء»: «صفحة البدء» تفتح على «لوحة التحكم» أو «الرحلة النشطة»؛ ومع «الرحلة النشطة»، تختار «علامة تبويب البدء» التبويب الذي تفتح عليه تلك الرحلة.',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
'اللغة والمنطقة: «عملة العرض» (أو «عملة الرحلة» دون تحويل)، و«اللغة»، و«أسماء الأماكن»، و«وحدة الحرارة»، و«وحدة المسافة»، و«تنسيق الوقت»، و«يبدأ الأسبوع في».',
|
||||
'اللغة والمنطقة: «عملة العرض» (أو «عملة الرحلة» دون تحويل)، و«اللغة»، و«أسماء الأماكن»، و«وحدة الحرارة»، و«وحدة المسافة»، و«تنسيق الوقت»، و«يبدأ الأسبوع في»، و«تنسيق التاريخ».',
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
'«السفر والخريطة»: «فتح الأماكن في»، و«التاريخ أولًا في عناوين الأيام»، و«تسميات مسارات الحجوزات»، و«إظهار مسارات الحجوزات دائمًا»، و«علامات مصغّرة للأماكن غير المخطط لها»، و«استكشاف الأماكن على الخريطة»، و«إخفاء رموز الحجز»، و«تحسين المسار انطلاقًا من مكان الإقامة».',
|
||||
'help.ctx.settings-appearance.title': 'المظهر',
|
||||
@@ -1375,9 +1375,9 @@ const help: TranslationStrings = {
|
||||
'«إعدادات الصلاحيات» في الأسفل: لكل إجراء، من يحق له تنفيذه («الجميع» أو «أعضاء الرحلة» أو «مالك الرحلة» أو «المسؤول فقط»). انقر على «حفظ» أعلى البطاقة لحفظها.',
|
||||
'help.ctx.admin-defaults.title': 'الإعدادات الافتراضية',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
'ما يبدأ به الحساب الجديد، حتى لا يضطر أحد إلى ضبطه أولًا: نمط الألوان والوحدات وتنسيق الوقت وبداية الأسبوع وعملة العرض وإخفاء رموز الحجز، إضافة إلى الخريطة ومفاتيحها وخوادم حساب المسارات.',
|
||||
'ما يبدأ به الحساب الجديد، حتى لا يضطر أحد إلى ضبطه أولًا: نمط الألوان والوحدات وتنسيق الوقت وتنسيق التاريخ وبداية الأسبوع وعملة العرض وإخفاء رموز الحجز، إضافة إلى الخريطة ومفاتيحها وخوادم حساب المسارات.',
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
'«إعدادات المستخدم الافتراضية» على اليسار: «نمط الألوان» و«وحدة الحرارة» و«وحدة المسافة» و«تنسيق الوقت» و«يبدأ الأسبوع في» و«عملة العرض» و«إخفاء رموز الحجز».',
|
||||
'«إعدادات المستخدم الافتراضية» على اليسار: «نمط الألوان» و«وحدة الحرارة» و«وحدة المسافة» و«تنسيق الوقت» و«يبدأ الأسبوع في» و«تنسيق التاريخ» و«عملة العرض» و«إخفاء رموز الحجز».',
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
'«خريطة» على اليمين: «قالب الخريطة» و«مفتاح CARTO المشترك» و«محرك مسارات خاص» و«خادم Valhalla خاص بك»، ومعاينة، ثم «محرك الخرائط» مع «نمط الخريطة» و«رمز Mapbox المشترك» و«المباني والتضاريس ثلاثية الأبعاد» و«وضع الجودة العالية».',
|
||||
'help.ctx.admin-config.title': 'التخصيص',
|
||||
@@ -4032,7 +4032,7 @@ const help: TranslationStrings = {
|
||||
'إعداداتك الخاصة للإضافات التي ثبّتها المسؤول: المفاتيح والتفضيلات التي تطلبها منك كل إضافة، وسجلّ بما فعلته الإضافات نيابةً عنك. لا يظهر هذا التبويب إلا عند وجود إضافة نشطة.',
|
||||
'help.ctx.settings-about.bullet.3': 'عبر Ko-fi وBuy Me a Coffee وDiscord يمكنك دعم TREK أو التحدث مع صانعيه.',
|
||||
'help.guide.language-region.step.3':
|
||||
'اضبط «عملة العرض»، أو أبقِ «عملة الرحلة» لترى كل رحلة بعملتها. ثم اختر «وحدة الحرارة» و«وحدة المسافة» و«تنسيق الوقت»، واليوم الذي يبدأ به الأسبوع في «يبدأ الأسبوع في».',
|
||||
'اضبط «عملة العرض»، أو أبقِ «عملة الرحلة» لترى كل رحلة بعملتها. ثم اختر «وحدة الحرارة» و«وحدة المسافة» و«تنسيق الوقت»، واليوم الذي يبدأ به الأسبوع في «يبدأ الأسبوع في»، و«تنسيق التاريخ».',
|
||||
'help.guide.travel-map-prefs.step.3':
|
||||
'«استكشاف الأماكن على الخريطة» يُظهر شريط الفئات للبحث عن أماكن قريبة. و«إخفاء رموز الحجز» يخفي أرقام التأكيد حتى تمرّر المؤشر فوقها أو تلمسها. و«تحسين المسار انطلاقًا من مكان الإقامة» يبدأ اليوم وينهيه عند فندقك.',
|
||||
'help.guide.web-push-device.step.2':
|
||||
@@ -5906,7 +5906,7 @@ const help: TranslationStrings = {
|
||||
'رابط «إعادة تعيين» بجانب حقل ضبطته يعيد قيمة TREK الافتراضية. وإعداد المستخدم نفسه يتقدم دائمًا على هذه القيم.',
|
||||
'help.ctx.admin-settings.bullet.5': '«منطقة الخطر»: «تدوير سر JWT» يسجّل خروج كل المستخدمين، وأنت منهم.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
'تحت «إعدادات المستخدم الافتراضية»، اختر «نمط الألوان» («فاتح» أو «داكن» أو «تلقائي»)، و«وحدة الحرارة»، و«وحدة المسافة»، و«تنسيق الوقت»، و«يبدأ الأسبوع في»، و«عملة العرض»، وهل «إخفاء رموز الحجز» مفعّل أم «إيقاف». يُحفظ كل اختيار فورًا.',
|
||||
'تحت «إعدادات المستخدم الافتراضية»، اختر «نمط الألوان» («فاتح» أو «داكن» أو «تلقائي»)، و«وحدة الحرارة»، و«وحدة المسافة»، و«تنسيق الوقت»، و«يبدأ الأسبوع في»، و«تنسيق التاريخ»، و«عملة العرض»، وهل «إخفاء رموز الحجز» مفعّل أم «إيقاف». يُحفظ كل اختيار فورًا.',
|
||||
'help.guide.admin-passkeys.step.2':
|
||||
'أدخل «Relying Party ID (النطاق)»، مثل trek.example.org، و«الأصول المسموح بها» التي يُفتح منها TREK لديك، ثم انقر على «حفظ». واتركهما فارغين لاستخدام APP_URL.',
|
||||
'help.guide.rotate-jwt-secret.title': 'تسجيل خروج الجميع دفعة واحدة',
|
||||
|
||||
@@ -84,6 +84,10 @@ const settings: TranslationStrings = {
|
||||
'settings.timeFormat': 'تنسيق الوقت',
|
||||
'settings.weekStart': 'يبدأ الأسبوع في',
|
||||
'settings.weekStartHint': 'أول أيام الأسبوع في جميع منتقيات التاريخ. لدى Vacay إعداد خاص به.',
|
||||
'settings.dateFormat': 'تنسيق التاريخ',
|
||||
'settings.dateFormatHint':
|
||||
'طريقة كتابة التواريخ في جميع حقول التاريخ والقوائم. التواريخ التي تتضمن اسم الشهر تحتفظ بصياغة لغتك.',
|
||||
'settings.dateFormatAuto': 'تلقائي',
|
||||
'settings.preferredNavApp': 'فتح الأماكن في',
|
||||
'settings.preferredNavAppAsk': 'اسأل في كل مرة',
|
||||
'settings.preferredNavAppHint': 'عند اختيار تطبيق، يفتحه زر التنقل مباشرةً بدلاً من عرض جميع تطبيقات الخرائط.',
|
||||
|
||||
@@ -1183,7 +1183,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
'Soldakı yan panel: «Ümumi», «Görünüş», «Xəritə», «Bildirişlər», «İnteqrasiyalar», «Oflayn» və «Hesab». «Plaginlər» aktiv plagin olduqda görünür. «Haqqında» operator onu gizlətməyibsə görünür. Versiya aşağıda göstərilir.',
|
||||
'help.ctx.settings.bullet.2':
|
||||
'Birinci «Ümumi» açılır. Burada dil, məkan adlarının dili, vahidlər, vaxt formatı, həftənin başlanğıcı və göstərilən valyuta, həmçinin səyahət xəritəsinin necə işlədiyi var. «Görünüş»də tema, rənglər, oxunaqlılıq, mətn ölçüsü və idarə paneli vidcetləri var.',
|
||||
'Birinci «Ümumi» açılır. Burada dil, məkan adlarının dili, vahidlər, vaxt formatı, tarix formatı, həftənin başlanğıcı və göstərilən valyuta, həmçinin səyahət xəritəsinin necə işlədiyi var. «Görünüş»də tema, rənglər, oxunaqlılıq, mətn ölçüsü və idarə paneli vidcetləri var.',
|
||||
'help.ctx.settings.bullet.3':
|
||||
'«Xəritə» xəritə mühərrikini və üslubunu seçir; «Bildirişlər» hansı hadisənin hansı kanala göndəriləcəyini təyin edir; «İnteqrasiyalar» foto kitabxanalarını, AirTrail, Dawarich və wanderer xidmətlərini qoşur, burada həmçinin API açarları və MCP var; «Oflayn» bu cihazda nələrin saxlanacağını təyin edir.',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1194,7 +1194,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'«Başlanğıc»: «Başlanğıc səhifəsi» «İdarə paneli» və ya «Aktiv səyahət» ilə açılır; «Aktiv səyahət» seçildikdə «Başlanğıc tabı» həmin səyahətin hansı vərəqdə açılacağını təyin edir.',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
'«Dil və region»: «Göstərilən valyuta» (çevirmə istəmirsinizsə, «Səyahət valyutası»), «Dil», «Məkan adları», «Temperatur vahidi», «Məsafə vahidi», «Vaxt formatı» və «Həftənin başlanğıcı».',
|
||||
'«Dil və region»: «Göstərilən valyuta» (çevirmə istəmirsinizsə, «Səyahət valyutası»), «Dil», «Məkan adları», «Temperatur vahidi», «Məsafə vahidi», «Vaxt formatı», «Həftənin başlanğıcı» və «Tarix formatı».',
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
'«Səyahət və xəritə»: «Yerləri açmaq üçün», «Gün başlıqlarında əvvəlcə tarix», «Rezervasiya marşrutu etiketləri», «Rezervasiya marşrutlarını həmişə göstər», «Planlaşdırılmamış məkanlar üçün kiçik nişanlar», «Xəritədə məkanları kəşf et», «Rezervasiya kodlarını bulanıqlaşdır» və «Marşrutu yaşayış yerindən başlayaraq optimallaşdır».',
|
||||
'help.ctx.settings-appearance.title': 'Görünüş',
|
||||
@@ -1516,9 +1516,9 @@ const help: TranslationStrings = {
|
||||
'Aşağıdakı «İcazə tənzimləmələri»: hər əməliyyatı kimin edə biləcəyi («Hər kəs», «Səyahət üzvləri», «Səyahətin sahibi» və ya «Yalnız administrator»). Saxlamaq üçün kartın yuxarısındakı «Yadda saxla» düyməsinə klikləyin.',
|
||||
'help.ctx.admin-defaults.title': 'İstifadəçi standartları',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
'Yeni hesabın başlanğıc ayarları, beləliklə, heç kim əvvəlcə onları qurmalı olmur: rəng rejimi, vahidlər, vaxt formatı, həftənin başlanğıcı, göstərilən valyuta və bulanıqlaşdırılmış rezervasiya kodları, həmçinin xəritə, onun açarları və marşrut serverləri.',
|
||||
'Yeni hesabın başlanğıc ayarları, beləliklə, heç kim əvvəlcə onları qurmalı olmur: rəng rejimi, vahidlər, vaxt formatı, tarix formatı, həftənin başlanğıcı, göstərilən valyuta və bulanıqlaşdırılmış rezervasiya kodları, həmçinin xəritə, onun açarları və marşrut serverləri.',
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
'Solda «Standart istifadəçi tənzimləmələri»: «Rəng rejimi», «Temperatur vahidi», «Məsafə vahidi», «Vaxt formatı», «Həftənin başlanğıcı», «Göstərilən valyuta» və «Rezervasiya kodlarını bulanıqlaşdır».',
|
||||
'Solda «Standart istifadəçi tənzimləmələri»: «Rəng rejimi», «Temperatur vahidi», «Məsafə vahidi», «Vaxt formatı», «Həftənin başlanğıcı», «Tarix formatı», «Göstərilən valyuta» və «Rezervasiya kodlarını bulanıqlaşdır».',
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
'Sağda «Xəritə»: «Xəritə şablonu», «Paylaşılan CARTO açarı», «Şəxsi marşrut mühərriki» və «Şəxsi Valhalla instansiyası», önizləmə, sonra «Xəritə üslubu» ilə «Xəritə mühərriki», «Paylaşılan Mapbox tokeni», «3D binalar və relyef» və «Yüksək keyfiyyət rejimi».',
|
||||
'help.ctx.admin-config.title': 'Fərdiləşdirmə',
|
||||
@@ -4329,7 +4329,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-about.bullet.3':
|
||||
'Ko-fi, «Mənə qəhvə al» və Discord vasitəsilə TREK-i dəstəkləyə və ya onu hazırlayan insanlarla danışa bilərsiniz.',
|
||||
'help.guide.language-region.step.3':
|
||||
'«Göstərilən valyuta»nı seçin və ya hər səyahəti öz valyutasında görmək üçün «Səyahət valyutası»nı saxlayın. Sonra «Temperatur vahidi», «Məsafə vahidi», «Vaxt formatı» və həftənin hansı gündən başladığını seçin.',
|
||||
'«Göstərilən valyuta»nı seçin və ya hər səyahəti öz valyutasında görmək üçün «Səyahət valyutası»nı saxlayın. Sonra «Temperatur vahidi», «Məsafə vahidi», «Vaxt formatı», həftənin hansı gündən başladığını və «Tarix formatı»nı seçin.',
|
||||
'help.guide.travel-map-prefs.step.3':
|
||||
'«Xəritədə məkanları kəşf et» yaxınlıqdakı məkanları tapmaq üçün kateqoriya düymələrini göstərir. «Rezervasiya kodlarını bulanıqlaşdır» təsdiq nömrələrini kursoru üzərinə gətirənə və ya toxunana qədər gizlədir. «Marşrutu yaşayış yerindən başlayaraq optimallaşdır» günü otelinizdə başladır və bitirir.',
|
||||
'help.guide.web-push-device.step.2':
|
||||
@@ -6374,7 +6374,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.admin-settings.bullet.5':
|
||||
'«Təhlükəli zona»: «JWT sirrini yenilə» siz də daxil olmaqla bütün istifadəçiləri hesabdan çıxarır.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
'«Standart istifadəçi tənzimləmələri» altında «Rəng rejimi» («İşıqlı», «Tünd» və ya «Avtomatik»), «Temperatur vahidi», «Məsafə vahidi», «Vaxt formatı», «Həftənin başlanğıcı», «Göstərilən valyuta» və «Rezervasiya kodlarını bulanıqlaşdır» seçiminin aktiv və ya deaktiv olmasını seçin. Hər seçim dərhal yadda saxlanır.',
|
||||
'«Standart istifadəçi tənzimləmələri» altında «Rəng rejimi» («İşıqlı», «Tünd» və ya «Avtomatik»), «Temperatur vahidi», «Məsafə vahidi», «Vaxt formatı», «Həftənin başlanğıcı», «Tarix formatı», «Göstərilən valyuta» və «Rezervasiya kodlarını bulanıqlaşdır» seçiminin aktiv və ya deaktiv olmasını seçin. Hər seçim dərhal yadda saxlanır.',
|
||||
'help.guide.admin-passkeys.step.2':
|
||||
'«Relying Party ID (domen)» (məsələn, trek.example.org) və TREK-inizin açıldığı «İcazə verilən mənbələr»i daxil edin, sonra «Yadda saxla» düyməsinə klikləyin. APP_URL istifadə etmək üçün hər ikisini boş buraxın.',
|
||||
'help.guide.rotate-jwt-secret.title': 'Hamını birdəfəlik hesabdan çıxarmaq',
|
||||
|
||||
@@ -108,6 +108,10 @@ const settings: TranslationStrings = {
|
||||
'settings.timeFormat': 'Vaxt formatı',
|
||||
'settings.weekStart': 'Həftənin başlanğıcı',
|
||||
'settings.weekStartHint': 'Bütün tarix seçicilərində həftənin ilk günü. Vacay-ın öz ayarı var.',
|
||||
'settings.dateFormat': 'Tarix formatı',
|
||||
'settings.dateFormatHint':
|
||||
'Tarixlərin bütün tarix sahələrində və siyahılarda necə yazıldığı. Ay adı olan tarixlər dilinizin ifadə tərzini saxlayır.',
|
||||
'settings.dateFormatAuto': 'Avtomatik',
|
||||
'settings.preferredNavApp': 'Yerləri açmaq üçün',
|
||||
'settings.preferredNavAppAsk': 'Hər dəfə soruş',
|
||||
'settings.preferredNavAppHint':
|
||||
|
||||
@@ -1157,7 +1157,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
'Barra lateral à esquerda: Exibição, Aparência, Mapa, Notificações, Integrações, Offline e Conta. Plugins aparece quando há um plugin ativo. Sobre aparece, a menos que o operador o tenha ocultado. A versão aparece no fim.',
|
||||
'help.ctx.settings.bullet.2':
|
||||
'Exibição abre primeiro. Ela reúne o idioma, o idioma dos nomes de lugares, as unidades, o formato de hora, o início da semana e a moeda de exibição, além do comportamento do mapa da viagem. Aparência reúne tema, cores, legibilidade, tamanho do texto e os widgets do painel.',
|
||||
'Exibição abre primeiro. Ela reúne o idioma, o idioma dos nomes de lugares, as unidades, o formato de hora, o formato de data, o início da semana e a moeda de exibição, além do comportamento do mapa da viagem. Aparência reúne tema, cores, legibilidade, tamanho do texto e os widgets do painel.',
|
||||
'help.ctx.settings.bullet.3':
|
||||
'Mapa define o motor de mapa e o estilo; Notificações define quais eventos vão para qual canal; Integrações conecta bibliotecas de fotos, AirTrail, Dawarich e wanderer, e reúne as chaves de API e o MCP; Offline define o que este dispositivo armazena.',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1168,7 +1168,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'Inicialização: “Página inicial” abre no Painel ou na “Viagem ativa”; com “Viagem ativa”, “Aba inicial” escolhe em que aba essa viagem abre.',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
'Idioma e região: “Moeda de exibição” (ou “Moeda da viagem” para não converter), Idioma, “Nomes de lugares”, “Unidade de temperatura”, “Unidade de distância”, “Formato de hora” e “Semana começa em”.',
|
||||
'Idioma e região: “Moeda de exibição” (ou “Moeda da viagem” para não converter), Idioma, “Nomes de lugares”, “Unidade de temperatura”, “Unidade de distância”, “Formato de hora”, “Semana começa em” e “Formato de data”.',
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
'Viagem e mapa: “Abrir lugares em”, “Data primeiro nos títulos dos dias”, “Rótulos das rotas de reservas”, “Sempre mostrar rotas de reserva”, “Marcadores compactos para lugares não planejados”, “Explorar lugares no mapa”, “Ocultar códigos de reserva” e “Otimizar rota a partir da hospedagem”.',
|
||||
'help.ctx.settings-appearance.title': 'Aparência',
|
||||
@@ -1484,9 +1484,9 @@ const help: TranslationStrings = {
|
||||
'“Configurações de Permissões”, no fim: para cada ação, quem pode realizá-la (Todos, “Membros da viagem”, “Dono da viagem” ou “Apenas administrador”). Clique em Salvar no topo do cartão para salvar.',
|
||||
'help.ctx.admin-defaults.title': 'Padrões do usuário',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
'Com o que uma conta nova já começa, para ninguém precisar configurar antes: tema de cores, unidades, formato de hora, início da semana, moeda de exibição e códigos de reserva ocultos, além do mapa, das chaves dele e dos servidores de rotas.',
|
||||
'Com o que uma conta nova já começa, para ninguém precisar configurar antes: tema de cores, unidades, formato de hora, formato de data, início da semana, moeda de exibição e códigos de reserva ocultos, além do mapa, das chaves dele e dos servidores de rotas.',
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
'“Configurações padrão do usuário”, à esquerda: “Tema de cores”, “Unidade de temperatura”, “Unidade de distância”, “Formato de hora”, “Semana começa em”, “Moeda de exibição” e “Ocultar códigos de reserva”.',
|
||||
'“Configurações padrão do usuário”, à esquerda: “Tema de cores”, “Unidade de temperatura”, “Unidade de distância”, “Formato de hora”, “Semana começa em”, “Formato de data”, “Moeda de exibição” e “Ocultar códigos de reserva”.',
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
'Mapa, à direita: “Modelo de mapa”, “Chave CARTO compartilhada”, “Servidor de rotas próprio” e “Instância Valhalla própria”, uma pré-visualização e depois “Motor de mapas”, com “Estilo do mapa”, “Token compartilhado do Mapbox”, “Edifícios & relevo em 3D” e “Modo de alta qualidade”.',
|
||||
'help.ctx.admin-config.title': 'Personalização',
|
||||
@@ -4304,7 +4304,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-about.bullet.3':
|
||||
'No Ko-fi, no Buy Me a Coffee e no Discord você pode apoiar o TREK ou conversar com quem o desenvolve.',
|
||||
'help.guide.language-region.step.3':
|
||||
'Defina a “Moeda de exibição” ou mantenha “Moeda da viagem” para ver cada viagem na própria moeda. Depois escolha “Unidade de temperatura”, “Unidade de distância”, “Formato de hora” e o dia em “Semana começa em”.',
|
||||
'Defina a “Moeda de exibição” ou mantenha “Moeda da viagem” para ver cada viagem na própria moeda. Depois escolha “Unidade de temperatura”, “Unidade de distância”, “Formato de hora”, o dia em “Semana começa em” e o “Formato de data”.',
|
||||
'help.guide.travel-map-prefs.step.3':
|
||||
'“Explorar lugares no mapa” mostra a pílula de categorias para encontrar lugares próximos. “Ocultar códigos de reserva” esconde os números de confirmação até você passar o mouse ou tocar neles. “Otimizar rota a partir da hospedagem” faz o dia começar e terminar no seu hotel.',
|
||||
'help.guide.web-push-device.step.2':
|
||||
@@ -6377,7 +6377,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.admin-settings.bullet.5':
|
||||
'Zona de perigo: “Rotacionar segredo JWT” desconecta todos os usuários, inclusive você.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
'Em “Configurações padrão do usuário”, escolha “Tema de cores” (Claro, Escuro ou Automático), “Unidade de temperatura”, “Unidade de distância”, “Formato de hora”, “Semana começa em”, a “Moeda de exibição” e se “Ocultar códigos de reserva” fica ativado ou desativado. Cada escolha é salva na hora.',
|
||||
'Em “Configurações padrão do usuário”, escolha “Tema de cores” (Claro, Escuro ou Automático), “Unidade de temperatura”, “Unidade de distância”, “Formato de hora”, “Semana começa em”, o “Formato de data”, a “Moeda de exibição” e se “Ocultar códigos de reserva” fica ativado ou desativado. Cada escolha é salva na hora.',
|
||||
'help.guide.admin-passkeys.step.2':
|
||||
'Informe o “Relying Party ID (domínio)”, como trek.example.org, e as “Origens permitidas” pelas quais o seu TREK é acessado, e clique em Salvar. Deixe os dois em branco para usar APP_URL.',
|
||||
'help.guide.rotate-jwt-secret.title': 'Desconectar todos de uma vez',
|
||||
|
||||
@@ -89,6 +89,10 @@ const settings: TranslationStrings = {
|
||||
'settings.weekStart': 'Semana começa em',
|
||||
'settings.weekStartHint':
|
||||
'Primeiro dia da semana em todos os seletores de data. O Vacay tem sua própria configuração.',
|
||||
'settings.dateFormat': 'Formato de data',
|
||||
'settings.dateFormatHint':
|
||||
'Como as datas são escritas em todos os campos de data e listas. Datas com o nome do mês mantêm a escrita do seu idioma.',
|
||||
'settings.dateFormatAuto': 'Automático',
|
||||
'settings.preferredNavApp': 'Abrir lugares em',
|
||||
'settings.preferredNavAppAsk': 'Perguntar sempre',
|
||||
'settings.preferredNavAppHint':
|
||||
|
||||
@@ -1170,7 +1170,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
"Barra lateral de l'esquerra: «Pantalla», «Aparença», «Mapa», «Notificacions», «Integracions», «Fora de línia» i «Compte». «Connectors» apareix quan hi ha algun connector actiu. «Quant a» apareix tret que l'operador l'hagi amagat. La versió es mostra a baix de tot.",
|
||||
'help.ctx.settings.bullet.2':
|
||||
"«Pantalla» s'obre primer. Inclou l'idioma, l'idioma dels noms de llocs, les unitats, el format d'hora, el primer dia de la setmana i la divisa de visualització, i també com es comporta el mapa del viatge. «Aparença» inclou el tema, els colors, la llegibilitat, la mida del text i els ginys del tauler.",
|
||||
"«Pantalla» s'obre primer. Inclou l'idioma, l'idioma dels noms de llocs, les unitats, el format d'hora, el format de data, el primer dia de la setmana i la divisa de visualització, i també com es comporta el mapa del viatge. «Aparença» inclou el tema, els colors, la llegibilitat, la mida del text i els ginys del tauler.",
|
||||
'help.ctx.settings.bullet.3':
|
||||
'«Mapa» defineix el motor del mapa i el seu estil; «Notificacions», quins esdeveniments van a cada canal; «Integracions» connecta biblioteques de fotos, AirTrail, Dawarich i wanderer, i inclou les claus API i MCP; «Fora de línia» defineix què desa aquest dispositiu.',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1181,7 +1181,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
"«Inici»: «Pàgina d'inici» obre el «Tauler» o el «Viatge actiu»; amb «Viatge actiu», «Pestanya d'inici» tria la pestanya amb què s'obre aquell viatge.",
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
"«Idioma i regió»: «Divisa de visualització» (o «Divisa del viatge» per no convertir), «Idioma», «Noms de llocs», «Unitat de temperatura», «Unitat de distància», «Format d'hora» i «La setmana comença el».",
|
||||
"«Idioma i regió»: «Divisa de visualització» (o «Divisa del viatge» per no convertir), «Idioma», «Noms de llocs», «Unitat de temperatura», «Unitat de distància», «Format d'hora», «La setmana comença el» i «Format de data».",
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
"«Viatge i mapa»: «Obre els llocs a», «Data primer als títols dels dies», «Etiquetes de rutes de reserves», «Mostra sempre les rutes de reserva», «Marcadors compactes per als llocs sense planificar», «Explora llocs al mapa», «Difumina els codis de reserva» i «Optimitza la ruta des de l'allotjament».",
|
||||
'help.ctx.settings-appearance.title': 'Aparença',
|
||||
@@ -1505,9 +1505,9 @@ const help: TranslationStrings = {
|
||||
"«Configuració de permisos», a baix de tot: per a cada acció, qui la pot fer («Tothom», «Membres del viatge», «Propietari del viatge» o «Només l'administrador»). Fes clic a «Desa», a dalt de la targeta, per desar-ho.",
|
||||
'help.ctx.admin-defaults.title': 'Valors per defecte',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
"Amb què comença un compte nou, perquè ningú no l'hagi de configurar abans: mode de color, unitats, format d'hora, primer dia de la setmana, divisa de visualització i codis de reserva difuminats, i també el mapa, les seves claus i els servidors de rutes.",
|
||||
"Amb què comença un compte nou, perquè ningú no l'hagi de configurar abans: mode de color, unitats, format d'hora, format de data, primer dia de la setmana, divisa de visualització i codis de reserva difuminats, i també el mapa, les seves claus i els servidors de rutes.",
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
"«Configuració per defecte dels usuaris», a l'esquerra: «Mode de color», «Unitat de temperatura», «Unitat de distància», «Format d'hora», «La setmana comença el», «Divisa de visualització» i «Difumina els codis de reserva».",
|
||||
"«Configuració per defecte dels usuaris», a l'esquerra: «Mode de color», «Unitat de temperatura», «Unitat de distància», «Format d'hora», «La setmana comença el», «Format de data», «Divisa de visualització» i «Difumina els codis de reserva».",
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
"«Mapa», a la dreta: «Plantilla del mapa», «Clau CARTO compartida», «Motor de rutes propi» i «Instància Valhalla pròpia», una vista prèvia, i després «Motor de mapes» amb «Estil de mapa», «Token de Mapbox compartit», «Edificis i terreny en 3D» i «Mode d'alta qualitat».",
|
||||
'help.ctx.admin-config.title': 'Personalització',
|
||||
@@ -4355,7 +4355,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-about.bullet.3':
|
||||
'A Ko-fi, Buy Me a Coffee i Discord pots donar suport a TREK o parlar amb la gent que el fa.',
|
||||
'help.guide.language-region.step.3':
|
||||
"Tria la «Divisa de visualització», o deixa «Divisa del viatge» per veure cada viatge en la seva divisa. Després tria «Unitat de temperatura», «Unitat de distància», «Format d'hora» i, a «La setmana comença el», el dia en què comença la setmana.",
|
||||
"Tria la «Divisa de visualització», o deixa «Divisa del viatge» per veure cada viatge en la seva divisa. Després tria «Unitat de temperatura», «Unitat de distància», «Format d'hora», a «La setmana comença el» el dia en què comença la setmana i el «Format de data».",
|
||||
'help.guide.travel-map-prefs.step.3':
|
||||
"«Explora llocs al mapa» mostra la pastilla de categories per trobar llocs propers. «Difumina els codis de reserva» amaga els números de confirmació fins que hi passes el ratolí per sobre o els toques. «Optimitza la ruta des de l'allotjament» fa que el dia comenci i acabi al teu hotel.",
|
||||
'help.guide.web-push-device.step.2':
|
||||
@@ -6437,7 +6437,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.admin-settings.bullet.5':
|
||||
'«Zona de perill»: «Rota el secret JWT» tanca la sessió de tots els usuaris, tu inclòs.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
"A «Configuració per defecte dels usuaris», tria «Mode de color» («Clar», «Fosc» o «Automàtic»), «Unitat de temperatura», «Unitat de distància», «Format d'hora», «La setmana comença el», la «Divisa de visualització» i si «Difumina els codis de reserva» està activat o desactivat. Cada opció es desa a l'instant.",
|
||||
"A «Configuració per defecte dels usuaris», tria «Mode de color» («Clar», «Fosc» o «Automàtic»), «Unitat de temperatura», «Unitat de distància», «Format d'hora», «La setmana comença el», el «Format de data», la «Divisa de visualització» i si «Difumina els codis de reserva» està activat o desactivat. Cada opció es desa a l'instant.",
|
||||
'help.guide.admin-passkeys.step.2':
|
||||
"Introdueix l'«ID de la part confiable (domini)», com ara trek.example.org, i els «Orígens permesos» des d'on s'accedeix al teu TREK, i fes clic a «Desa». Deixa'ls tots dos buits per fer servir APP_URL.",
|
||||
'help.guide.rotate-jwt-secret.title': 'Tancar la sessió de tothom alhora',
|
||||
|
||||
@@ -73,6 +73,10 @@ const settings: TranslationStrings = {
|
||||
'settings.timeFormat': "Format d'hora",
|
||||
'settings.weekStart': 'La setmana comença el',
|
||||
'settings.weekStartHint': 'Primer dia de la setmana a tots els selectors de data. Vacay té la seva pròpia opció.',
|
||||
'settings.dateFormat': 'Format de data',
|
||||
'settings.dateFormatHint':
|
||||
"Com s'escriuen les dates a tots els camps de data i a les llistes. Les dates amb el nom del mes mantenen la redacció del teu idioma.",
|
||||
'settings.dateFormatAuto': 'Automàtic',
|
||||
'settings.preferredNavApp': 'Obre els llocs a',
|
||||
'settings.preferredNavAppAsk': 'Pregunta cada vegada',
|
||||
'settings.preferredNavAppHint':
|
||||
|
||||
@@ -1120,7 +1120,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
'Postranní panel vlevo: „Zobrazení“, „Vzhled“, „Mapa“, „Oznámení“, „Integrace“, „Offline“ a „Účet“. „Pluginy“ se zobrazí, jakmile je aktivní nějaký plugin. „O aplikaci“ se zobrazí, pokud ho provozovatel neskryl. Verze je uvedená dole.',
|
||||
'help.ctx.settings.bullet.2':
|
||||
'Jako první se otevře „Zobrazení“. Obsahuje jazyk, jazyk názvů míst, jednotky, formát času, začátek týdne a zobrazovanou měnu a také chování mapy cesty. „Vzhled“ obsahuje motiv, barvy, čitelnost, velikost písma a widgety přehledu.',
|
||||
'Jako první se otevře „Zobrazení“. Obsahuje jazyk, jazyk názvů míst, jednotky, formát času, formát data, začátek týdne a zobrazovanou měnu a také chování mapy cesty. „Vzhled“ obsahuje motiv, barvy, čitelnost, velikost písma a widgety přehledu.',
|
||||
'help.ctx.settings.bullet.3':
|
||||
'„Mapa“ určuje, čím se mapa vykresluje, a její styl; „Oznámení“ určuje, které události chodí kterým kanálem; „Integrace“ propojuje knihovny fotek, AirTrail, Dawarich a wanderer a obsahuje API klíče a MCP; „Offline“ určuje, co si toto zařízení ukládá.',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1131,7 +1131,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'„Spuštění“: „Úvodní stránka“ otevře „Přehled“ nebo „Aktivní cesta“; u „Aktivní cesta“ vyberete v „Úvodní karta“, na které kartě se cesta otevře.',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
'Jazyk a region: „Zobrazovaná měna“ (nebo „Měna cesty“ bez přepočtu), „Jazyk“, „Názvy míst“, „Jednotky teploty“, „Jednotky vzdálenosti“, „Formát času“ a „Týden začíná“.',
|
||||
'Jazyk a region: „Zobrazovaná měna“ (nebo „Měna cesty“ bez přepočtu), „Jazyk“, „Názvy míst“, „Jednotky teploty“, „Jednotky vzdálenosti“, „Formát času“, „Týden začíná“ a „Formát data“.',
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
'Cestování a mapa: „Otevírat místa v“, „Datum na začátku nadpisů dnů“, „Popisky tras rezervací“, „Vždy zobrazovat trasy rezervací“, „Kompaktní značky pro nenaplánovaná místa“, „Objevovat místa na mapě“, „Skrýt rezervační kódy“ a „Optimalizovat trasu od ubytování“.',
|
||||
'help.ctx.settings-appearance.title': 'Vzhled',
|
||||
@@ -1445,9 +1445,9 @@ const help: TranslationStrings = {
|
||||
'„Nastavení oprávnění“ dole: u každé akce, kdo ji smí provádět („Všichni“, „Členové výletu“, „Vlastník výletu“ nebo „Pouze administrátor“). Uložíte tlačítkem „Uložit“ nahoře na kartě.',
|
||||
'help.ctx.admin-defaults.title': 'Výchozí nastavení uživatele',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
'S čím nový účet začíná, aby nikdo nemusel nic nastavovat hned na začátku: barevné schéma, jednotky, formát času, začátek týdne, zobrazovaná měna a skryté rezervační kódy, a k tomu mapa, její klíče a servery pro plánování tras.',
|
||||
'S čím nový účet začíná, aby nikdo nemusel nic nastavovat hned na začátku: barevné schéma, jednotky, formát času, formát data, začátek týdne, zobrazovaná měna a skryté rezervační kódy, a k tomu mapa, její klíče a servery pro plánování tras.',
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
'„Výchozí nastavení uživatele“ vlevo: „Barevné schéma“, „Jednotky teploty“, „Jednotky vzdálenosti“, „Formát času“, „Týden začíná“, „Zobrazovaná měna“ a „Skrýt rezervační kódy“.',
|
||||
'„Výchozí nastavení uživatele“ vlevo: „Barevné schéma“, „Jednotky teploty“, „Jednotky vzdálenosti“, „Formát času“, „Týden začíná“, „Formát data“, „Zobrazovaná měna“ a „Skrýt rezervační kódy“.',
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
'Mapa vpravo: „Šablona mapy“, „Sdílený klíč CARTO“, „Vlastní směrovací server“ a „Vlastní instance Valhalla“, náhled, pak „Mapový engine“ se „Styl mapy“, „Sdílený token Mapbox“, „3D budovy & terén“ a „Režim vysoké kvality“.',
|
||||
'help.ctx.admin-config.title': 'Personalizace',
|
||||
@@ -4224,7 +4224,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-about.bullet.3':
|
||||
'Přes Ko-fi, Buy Me a Coffee a Discord můžete TREK podpořit nebo si promluvit s lidmi, kteří ho vyvíjejí.',
|
||||
'help.guide.language-region.step.3':
|
||||
'Nastavte „Zobrazovaná měna“, nebo ponechte „Měna cesty“, aby se každá cesta zobrazovala ve své vlastní měně. Pak zvolte „Jednotky teploty“, „Jednotky vzdálenosti“, „Formát času“ a den, kterým začíná týden („Týden začíná“).',
|
||||
'Nastavte „Zobrazovaná měna“, nebo ponechte „Měna cesty“, aby se každá cesta zobrazovala ve své vlastní měně. Pak zvolte „Jednotky teploty“, „Jednotky vzdálenosti“, „Formát času“, den, kterým začíná týden („Týden začíná“), a „Formát data“.',
|
||||
'help.guide.travel-map-prefs.step.3':
|
||||
'„Objevovat místa na mapě“ zobrazí pilulku s kategoriemi pro hledání míst v okolí. „Skrýt rezervační kódy“ skryje čísla potvrzení, dokud na ně nenajedete myší nebo neklepnete. „Optimalizovat trasu od ubytování“ začíná a končí den u vašeho hotelu.',
|
||||
'help.guide.web-push-device.step.2':
|
||||
@@ -6256,7 +6256,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.admin-settings.bullet.5':
|
||||
'„Nebezpečná zóna“: „Obnovit tajný klíč JWT“ odhlásí všechny uživatele včetně vás.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
'V části „Výchozí nastavení uživatele“ vyberte „Barevné schéma“ („Světlé“, „Tmavá“ nebo „Automatické“), „Jednotky teploty“, „Jednotky vzdálenosti“, „Formát času“, „Týden začíná“, „Zobrazovaná měna“ a zda je „Skrýt rezervační kódy“ zapnuté, nebo vypnuté. Každá volba se uloží okamžitě.',
|
||||
'V části „Výchozí nastavení uživatele“ vyberte „Barevné schéma“ („Světlé“, „Tmavá“ nebo „Automatické“), „Jednotky teploty“, „Jednotky vzdálenosti“, „Formát času“, „Týden začíná“, „Formát data“, „Zobrazovaná měna“ a zda je „Skrýt rezervační kódy“ zapnuté, nebo vypnuté. Každá volba se uloží okamžitě.',
|
||||
'help.guide.admin-passkeys.step.2':
|
||||
'Zadejte „Relying Party ID (doména)“, například trek.example.org, a „Povolené origins“, na kterých je váš TREK dostupný, a klikněte na „Uložit“. Pokud obě pole necháte prázdná, použije se APP_URL.',
|
||||
'help.guide.rotate-jwt-secret.title': 'Odhlášení všech najednou',
|
||||
|
||||
@@ -85,6 +85,10 @@ const settings: TranslationStrings = {
|
||||
'settings.timeFormat': 'Formát času',
|
||||
'settings.weekStart': 'Týden začíná',
|
||||
'settings.weekStartHint': 'První den týdne ve všech výběrech data. Vacay má vlastní nastavení.',
|
||||
'settings.dateFormat': 'Formát data',
|
||||
'settings.dateFormatHint':
|
||||
'Jak se zapisují data ve všech datových polích a seznamech. Data s názvem měsíce zachovávají znění vašeho jazyka.',
|
||||
'settings.dateFormatAuto': 'Automaticky',
|
||||
'settings.preferredNavApp': 'Otevírat místa v',
|
||||
'settings.preferredNavAppAsk': 'Pokaždé se zeptat',
|
||||
'settings.preferredNavAppHint':
|
||||
|
||||
@@ -1180,7 +1180,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
'Seitenleiste links: „Allgemein“, „Darstellung“, „Karte“, „Benachrichtigungen“, „Integrationen“, „Offline“ und „Konto“. „Plugins“ erscheint, sobald ein Plugin aktiv ist, „Über“ überall dort, wo der Betreiber es nicht ausgeblendet hat, und ganz unten steht die Version.',
|
||||
'help.ctx.settings.bullet.2':
|
||||
'Unter Allgemein legst du fest, womit TREK startet: Sprache, Sprache der Ortsnamen, Einheiten, Zeitformat, Wochenbeginn und Anzeigewährung, dazu das Verhalten der Reisekarte. Unter Darstellung findest du Theme, Farben, Lesbarkeit, Schriftgröße und die Dashboard-Widgets.',
|
||||
'Unter Allgemein legst du fest, womit TREK startet: Sprache, Sprache der Ortsnamen, Einheiten, Zeitformat, Datumsformat, Wochenbeginn und Anzeigewährung, dazu das Verhalten der Reisekarte. Unter Darstellung findest du Theme, Farben, Lesbarkeit, Schriftgröße und die Dashboard-Widgets.',
|
||||
'help.ctx.settings.bullet.3':
|
||||
'„Karte“ wählt die Engine und ihren Stil. Unter „Benachrichtigungen“ legst du fest, welche Ereignisse über welchen Kanal kommen. „Integrationen“ verbindet Fotobibliotheken, AirTrail, Dawarich und wanderer und enthält API-Schlüssel und MCP. Unter „Offline“ legst du fest, was dieses Gerät speichert.',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1191,7 +1191,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'Start: Unter „Startseite“ legst du fest, ob TREK mit dem Dashboard oder der aktiven Reise startet; bei „Aktive Reise“ wählst du unter „Start-Tab“, mit welchem Tab sich diese Reise öffnet.',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
'Sprache & Region: „Anzeigewährung“ (oder „Reisewährung“, wenn nicht umgerechnet werden soll), „Sprache“, „Ortsnamen“, „Temperatureinheit“, „Entfernungseinheit“, „Zeitformat“ und „Woche beginnt am“.',
|
||||
'Sprache & Region: „Anzeigewährung“ (oder „Reisewährung“, wenn nicht umgerechnet werden soll), „Sprache“, „Ortsnamen“, „Temperatureinheit“, „Entfernungseinheit“, „Zeitformat“, „Woche beginnt am“ und „Datumsformat“.',
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
'Reise & Karte: „Orte öffnen in“, „Datum zuerst in Tagesüberschriften“, „Orts-Labels auf Buchungsrouten“, „Buchungsrouten immer anzeigen“, „Kompakte Markierungen für ungeplante Orte“, „Orte auf der Karte entdecken“, „Buchungscodes verbergen“ und „Route ab der Unterkunft optimieren“.',
|
||||
'help.ctx.settings-appearance.title': 'Darstellung',
|
||||
@@ -1517,9 +1517,9 @@ const help: TranslationStrings = {
|
||||
'„Berechtigungseinstellungen“ unten: Für jede Aktion legst du fest, wer sie ausführen darf („Alle“, „Reise-Mitglieder“, „Reise-Eigentümer“ oder „Nur Administrator“), und speicherst mit „Speichern“ oben in der Karte.',
|
||||
'help.ctx.admin-defaults.title': 'Benutzer-Standards',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
'Womit ein neues Konto startet, damit niemand erst alles einrichten muss: Farbmodus, Einheiten, Zeitformat, Wochenbeginn, Anzeigewährung und verborgene Buchungscodes, dazu die Karte, ihre Schlüssel und die Routing-Server.',
|
||||
'Womit ein neues Konto startet, damit niemand erst alles einrichten muss: Farbmodus, Einheiten, Zeitformat, Datumsformat, Wochenbeginn, Anzeigewährung und verborgene Buchungscodes, dazu die Karte, ihre Schlüssel und die Routing-Server.',
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
'Links die Standard-Benutzereinstellungen: „Farbmodus“, „Temperatureinheit“, „Entfernungseinheit“, „Zeitformat“, „Woche beginnt am“, „Anzeigewährung“ und „Buchungscodes verbergen“.',
|
||||
'Links die Standard-Benutzereinstellungen: „Farbmodus“, „Temperatureinheit“, „Entfernungseinheit“, „Zeitformat“, „Woche beginnt am“, „Datumsformat“, „Anzeigewährung“ und „Buchungscodes verbergen“.',
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
'Karte rechts: „Karten-Vorlage“, „Gemeinsamer CARTO-Key“, „Eigene Routing-Instanz“ und „Eigene Valhalla-Instanz“, eine Vorschau, danach „Kartendienst“ mit „Kartenstil“, „Gemeinsames Mapbox-Token“, „3D-Gebäude & Gelände“ und „Hochqualitätsmodus“.',
|
||||
'help.ctx.admin-config.title': 'Personalisierung',
|
||||
@@ -4386,7 +4386,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-about.bullet.3':
|
||||
'Über Ko-fi, Buy Me a Coffee und Discord kannst du TREK unterstützen oder die Leute dahinter kennenlernen.',
|
||||
'help.guide.language-region.step.3':
|
||||
'Leg die „Anzeigewährung“ fest oder behalte „Reisewährung“, damit jede Reise in ihrer eigenen Währung angezeigt wird. Wähl dann „Temperatureinheit“, „Entfernungseinheit“, „Zeitformat“ und unter „Woche beginnt am“ den ersten Wochentag.',
|
||||
'Leg die „Anzeigewährung“ fest oder behalte „Reisewährung“, damit jede Reise in ihrer eigenen Währung angezeigt wird. Wähl dann „Temperatureinheit“, „Entfernungseinheit“, „Zeitformat“, unter „Woche beginnt am“ den ersten Wochentag und das „Datumsformat“.',
|
||||
'help.guide.travel-map-prefs.step.3':
|
||||
'„Orte auf der Karte entdecken“ blendet die Kategorie-Pille ein, mit der du Orte in der Nähe findest. „Buchungscodes verbergen“ macht Bestätigungsnummern unkenntlich, bis du mit der Maus darüberfährst oder sie antippst. „Route ab der Unterkunft optimieren“ lässt jeden Tag an deinem Hotel beginnen und enden.',
|
||||
'help.guide.web-push-device.step.2':
|
||||
@@ -6495,7 +6495,7 @@ const help: TranslationStrings = {
|
||||
'Über den Link zum Zurücksetzen neben einem geänderten Feld stellst du den TREK-Standard wieder her. Die eigenen Einstellungen der Benutzer haben immer Vorrang.',
|
||||
'help.ctx.admin-settings.bullet.5': 'Gefahrenzone: „JWT-Secret erneuern“ meldet alle Benutzer ab, auch dich.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
'Wähl unter „Standard-Benutzereinstellungen“ den „Farbmodus“ (Hell, Dunkel oder Automatisch), „Temperatureinheit“, „Entfernungseinheit“, „Zeitformat“, „Woche beginnt am“, die „Anzeigewährung“ und ob „Buchungscodes verbergen“ auf „Ein“ oder „Aus“ steht. Jede Auswahl wird sofort gespeichert.',
|
||||
'Wähl unter „Standard-Benutzereinstellungen“ den „Farbmodus“ (Hell, Dunkel oder Automatisch), „Temperatureinheit“, „Entfernungseinheit“, „Zeitformat“, „Woche beginnt am“, das „Datumsformat“, die „Anzeigewährung“ und ob „Buchungscodes verbergen“ auf „Ein“ oder „Aus“ steht. Jede Auswahl wird sofort gespeichert.',
|
||||
'help.guide.admin-passkeys.step.2':
|
||||
'Gib die „Relying Party ID (Domain)“ ein, etwa trek.example.org, und unter „Erlaubte Origins“ die Adressen, unter denen dein TREK erreichbar ist. Klick dann auf „Speichern“. Lässt du beide leer, wird APP_URL verwendet.',
|
||||
'help.guide.rotate-jwt-secret.title': 'Alle auf einmal abmelden',
|
||||
|
||||
@@ -87,6 +87,10 @@ const settings: TranslationStrings = {
|
||||
'settings.timeFormat': 'Zeitformat',
|
||||
'settings.weekStart': 'Woche beginnt am',
|
||||
'settings.weekStartHint': 'Erster Tag der Woche in jedem Datumswähler. Vacay hat eine eigene Einstellung.',
|
||||
'settings.dateFormat': 'Datumsformat',
|
||||
'settings.dateFormatHint':
|
||||
'Wie Daten in jedem Datumsfeld und jeder Liste geschrieben werden. Daten mit Monatsnamen behalten die Schreibweise deiner Sprache.',
|
||||
'settings.dateFormatAuto': 'Automatisch',
|
||||
'settings.preferredNavApp': 'Orte öffnen in',
|
||||
'settings.preferredNavAppAsk': 'Jedes Mal fragen',
|
||||
'settings.preferredNavAppHint':
|
||||
|
||||
@@ -1336,7 +1336,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
'Sidebar on the left: General, Appearance, Map, Notifications, Integrations, Offline and Account. Plugins appears once a plugin is active. About appears unless the operator has hidden it. The version is shown at the bottom.',
|
||||
'help.ctx.settings.bullet.2':
|
||||
'General opens first. It holds the language, the language of place names, units, time format, week start and display currency, plus how the trip map behaves. Appearance holds theme, colours, readability, text size and the dashboard widgets.',
|
||||
'General opens first. It holds the language, the language of place names, units, time format, date format, week start and display currency, plus how the trip map behaves. Appearance holds theme, colours, readability, text size and the dashboard widgets.',
|
||||
'help.ctx.settings.bullet.3':
|
||||
'Map sets the map engine and its style; Notifications sets which events go to which channel; Integrations connects photo libraries, AirTrail, Dawarich and wanderer, and contains API keys and MCP; Offline sets what this device stores.',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1347,7 +1347,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'Startup: Start page opens on the Dashboard or on the Active trip; with Active trip, Start tab picks the tab that trip opens on.',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
'Language & region: Display currency (or Trip currency for none), Language, Place names, Temperature Unit, Distance Unit, Time Format and Week starts on.',
|
||||
'Language & region: Display currency (or Trip currency for none), Language, Place names, Temperature Unit, Distance Unit, Time Format, Week starts on and Date format.',
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
'Travel & map: Open places in, Date first in day headings, Booking route labels, Always show booking routes, Compact markers for unplanned places, Explore places on the map, Blur Booking Codes and Optimize route from accommodation.',
|
||||
'help.ctx.settings-appearance.title': 'Appearance',
|
||||
@@ -1445,7 +1445,7 @@ const help: TranslationStrings = {
|
||||
'help.guide.language-region.step.2':
|
||||
'Place names sets the language that place search, suggestions and addresses use; Same as the app follows the interface language.',
|
||||
'help.guide.language-region.step.3':
|
||||
'Set the Display currency, or keep Trip currency to see each trip in its own. Then choose Temperature Unit, Distance Unit, Time Format and the day the Week starts on.',
|
||||
'Set the Display currency, or keep Trip currency to see each trip in its own. Then choose Temperature Unit, Distance Unit, Time Format, the day the Week starts on and the Date format.',
|
||||
'help.guide.language-region.result':
|
||||
'Dates, distances and money are shown in the format you expect. In Costs, amounts are shown in your display currency for display only; the amounts that were entered do not change.',
|
||||
'help.guide.language-region.tip.1': 'Trip currency shows every trip in its own currency, without converting.',
|
||||
@@ -1751,9 +1751,9 @@ const help: TranslationStrings = {
|
||||
'Permission Settings at the bottom: per action, who may do it (Everyone, Trip members, Trip owner or Admin only). Click Save at the top of the card to save it.',
|
||||
'help.ctx.admin-defaults.title': 'User Defaults',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
'What a new account starts with, so nobody has to set it up first: colour mode, units, time format, week start, display currency and blurred booking codes, plus the map, its keys and the routing servers.',
|
||||
'What a new account starts with, so nobody has to set it up first: colour mode, units, time format, date format, week start, display currency and blurred booking codes, plus the map, its keys and the routing servers.',
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
'Default User Settings on the left: Color Mode, Temperature Unit, Distance Unit, Time Format, Week starts on, Display currency and Blur Booking Codes.',
|
||||
'Default User Settings on the left: Color Mode, Temperature Unit, Distance Unit, Time Format, Week starts on, Date format, Display currency and Blur Booking Codes.',
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
'Map on the right: Map Template, Shared CARTO key, Own routing engine and Own Valhalla instance, a preview, then Map engine with Map style, Shared Mapbox token, 3D buildings & terrain and High-quality mode.',
|
||||
'help.ctx.admin-defaults.bullet.3':
|
||||
@@ -1903,7 +1903,7 @@ const help: TranslationStrings = {
|
||||
'help.guide.default-user-settings.goal':
|
||||
'Give every account sensible units, colour mode and currency without anyone opening Settings.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
'Under Default User Settings, pick Color Mode (Light, Dark or Auto), Temperature Unit, Distance Unit, Time Format, Week starts on, the Display currency and whether Blur Booking Codes is On or Off. Each choice saves at once.',
|
||||
'Under Default User Settings, pick Color Mode (Light, Dark or Auto), Temperature Unit, Distance Unit, Time Format, Week starts on, Date format, the Display currency and whether Blur Booking Codes is On or Off. Each choice saves at once.',
|
||||
'help.guide.default-user-settings.step.2':
|
||||
"A field you set gets a reset link beside its name; it restores TREK's built-in default.",
|
||||
'help.guide.default-user-settings.result':
|
||||
|
||||
@@ -94,6 +94,10 @@ const settings: TranslationStrings = {
|
||||
'settings.timeFormat': 'Time Format',
|
||||
'settings.weekStart': 'Week starts on',
|
||||
'settings.weekStartHint': 'First day of the week in every date picker. Vacay has its own setting.',
|
||||
'settings.dateFormat': 'Date format',
|
||||
'settings.dateFormatHint':
|
||||
'How dates are written in every date field and list. Dates with a month name keep the wording of your language.',
|
||||
'settings.dateFormatAuto': 'Automatic',
|
||||
'settings.preferredNavApp': 'Open places in',
|
||||
'settings.preferredNavAppAsk': 'Ask every time',
|
||||
'settings.preferredNavAppHint':
|
||||
|
||||
@@ -1172,7 +1172,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
'Barra lateral izquierda: «Pantalla», «Apariencia», «Mapa», «Notificaciones», «Integraciones», «Sin conexión» y «Cuenta». «Plugins» aparece cuando hay algún plugin activo. «Acerca de» aparece salvo que el administrador del servidor la haya ocultado. La versión se muestra abajo del todo.',
|
||||
'help.ctx.settings.bullet.2':
|
||||
'«Pantalla» se abre primero. Incluye el idioma, el idioma de los nombres de lugares, las unidades, el formato de hora, el primer día de la semana y la moneda de visualización, además del comportamiento del mapa del viaje. «Apariencia» incluye el tema, los colores, la legibilidad, el tamaño del texto y los widgets del panel.',
|
||||
'«Pantalla» se abre primero. Incluye el idioma, el idioma de los nombres de lugares, las unidades, el formato de hora, el formato de fecha, el primer día de la semana y la moneda de visualización, además del comportamiento del mapa del viaje. «Apariencia» incluye el tema, los colores, la legibilidad, el tamaño del texto y los widgets del panel.',
|
||||
'help.ctx.settings.bullet.3':
|
||||
'«Mapa» define el motor de mapas y su estilo; «Notificaciones», qué eventos van a cada canal; «Integraciones» conecta bibliotecas de fotos, AirTrail, Dawarich y wanderer, e incluye las claves de API y MCP; «Sin conexión» define qué guarda este dispositivo.',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1183,7 +1183,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'«Inicio»: «Página de inicio» abre el «Panel» o el «Viaje activo»; con «Viaje activo», «Pestaña de inicio» elige la pestaña con la que se abre ese viaje.',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
'Idioma y región: «Moneda de visualización» (o «Moneda del viaje» para no convertir), «Idioma», «Nombres de lugares», «Unidad de temperatura», «Unidad de distancia», «Formato de hora» y «La semana comienza el».',
|
||||
'Idioma y región: «Moneda de visualización» (o «Moneda del viaje» para no convertir), «Idioma», «Nombres de lugares», «Unidad de temperatura», «Unidad de distancia», «Formato de hora», «La semana comienza el» y «Formato de fecha».',
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
'«Viaje y mapa»: «Abrir lugares en», «Fecha primero en los títulos de día», «Etiquetas de rutas de reservas», «Mostrar siempre las rutas de reserva», «Marcadores compactos para lugares sin planificar», «Explorar lugares en el mapa», «Difuminar códigos de reserva» y «Optimizar la ruta desde el alojamiento».',
|
||||
'help.ctx.settings-appearance.title': 'Apariencia',
|
||||
@@ -1507,9 +1507,9 @@ const help: TranslationStrings = {
|
||||
'«Configuración de permisos», abajo: para cada acción, quién puede hacerla («Todos», «Miembros del viaje», «Propietario del viaje» o «Solo administrador»). Haz clic en Guardar, arriba de la tarjeta, para guardarlo.',
|
||||
'help.ctx.admin-defaults.title': 'Valores predeterminados',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
'Con qué empieza una cuenta nueva, para que nadie tenga que configurarlo antes: modo de color, unidades, formato de hora, primer día de la semana, moneda de visualización y códigos de reserva difuminados, además del mapa, sus claves y los servidores de rutas.',
|
||||
'Con qué empieza una cuenta nueva, para que nadie tenga que configurarlo antes: modo de color, unidades, formato de hora, formato de fecha, primer día de la semana, moneda de visualización y códigos de reserva difuminados, además del mapa, sus claves y los servidores de rutas.',
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
'«Configuración predeterminada de usuarios», a la izquierda: «Modo de color», «Unidad de temperatura», «Unidad de distancia», «Formato de hora», «La semana comienza el», «Moneda de visualización» y «Difuminar códigos de reserva».',
|
||||
'«Configuración predeterminada de usuarios», a la izquierda: «Modo de color», «Unidad de temperatura», «Unidad de distancia», «Formato de hora», «La semana comienza el», «Formato de fecha», «Moneda de visualización» y «Difuminar códigos de reserva».',
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
'«Mapa», a la derecha: «Plantilla del mapa», «Clave de CARTO compartida», «Motor de rutas propio» e «Instancia de Valhalla propia», una vista previa y, después, «Motor de mapas» con «Estilo de mapa», «Token de Mapbox compartido», «Edificios y terreno en 3D» y «Modo de alta calidad».',
|
||||
'help.ctx.admin-config.title': 'Personalización',
|
||||
@@ -4362,7 +4362,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-about.bullet.3':
|
||||
'En Ko-fi, Buy Me a Coffee y Discord puedes apoyar a TREK o hablar con las personas que lo desarrollan.',
|
||||
'help.guide.language-region.step.3':
|
||||
'Elige la «Moneda de visualización», o deja «Moneda del viaje» para ver cada viaje en su propia moneda. Después elige «Unidad de temperatura», «Unidad de distancia», «Formato de hora» y el día en que empieza la semana en «La semana comienza el».',
|
||||
'Elige la «Moneda de visualización», o deja «Moneda del viaje» para ver cada viaje en su propia moneda. Después elige «Unidad de temperatura», «Unidad de distancia», «Formato de hora», el día en que empieza la semana en «La semana comienza el» y el «Formato de fecha».',
|
||||
'help.guide.travel-map-prefs.step.3':
|
||||
'«Explorar lugares en el mapa» muestra la píldora de categorías para buscar lugares cercanos. «Difuminar códigos de reserva» oculta los números de confirmación hasta que pasas el cursor por encima o los tocas. «Optimizar la ruta desde el alojamiento» hace que el día empiece y termine en tu hotel.',
|
||||
'help.guide.web-push-device.step.2':
|
||||
@@ -6457,7 +6457,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.admin-settings.bullet.5':
|
||||
'«Zona de peligro»: «Rotar secreto JWT» cierra la sesión de todos los usuarios, también la tuya.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
'En «Configuración predeterminada de usuarios», elige «Modo de color» («Claro», «Oscuro» o «Automático»), «Unidad de temperatura», «Unidad de distancia», «Formato de hora», «La semana comienza el», la «Moneda de visualización» y si «Difuminar códigos de reserva» está activado o desactivado. Cada opción se guarda al momento.',
|
||||
'En «Configuración predeterminada de usuarios», elige «Modo de color» («Claro», «Oscuro» o «Automático»), «Unidad de temperatura», «Unidad de distancia», «Formato de hora», «La semana comienza el», el «Formato de fecha», la «Moneda de visualización» y si «Difuminar códigos de reserva» está activado o desactivado. Cada opción se guarda al momento.',
|
||||
'help.guide.admin-passkeys.step.2':
|
||||
'Introduce el «Relying Party ID (dominio)», por ejemplo trek.example.org, y los «Orígenes permitidos» desde los que se accede a tu TREK, y haz clic en Guardar. Déjalos vacíos para usar APP_URL.',
|
||||
'help.guide.rotate-jwt-secret.title': 'Cerrar la sesión de todos a la vez',
|
||||
|
||||
@@ -88,6 +88,10 @@ const settings: TranslationStrings = {
|
||||
'settings.timeFormat': 'Formato de hora',
|
||||
'settings.weekStart': 'La semana comienza el',
|
||||
'settings.weekStartHint': 'Primer día de la semana en todos los selectores de fecha. Vacay tiene su propio ajuste.',
|
||||
'settings.dateFormat': 'Formato de fecha',
|
||||
'settings.dateFormatHint':
|
||||
'Cómo se escriben las fechas en todos los campos de fecha y listas. Las fechas con el nombre del mes mantienen la redacción de tu idioma.',
|
||||
'settings.dateFormatAuto': 'Automático',
|
||||
'settings.preferredNavApp': 'Abrir lugares en',
|
||||
'settings.preferredNavAppAsk': 'Preguntar siempre',
|
||||
'settings.preferredNavAppHint':
|
||||
|
||||
@@ -1138,7 +1138,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
'Vasakul olev külgriba: „Üldine“, „Välimus“, „Kaart“, „Teavitused“, „Integratsioonid“, „Võrguühenduseta“ ja „Konto“. „Pluginad“ ilmub, kui mõni plugin on aktiivne. „Teave“ on näha, kui haldaja pole seda peitnud. Versioon on näidatud allosas.',
|
||||
'help.ctx.settings.bullet.2':
|
||||
'Esimesena avaneb „Üldine“. Seal on keel, kohanimede keel, ühikud, ajavorming, nädala alguspäev ja kuvamisvaluuta ning reisikaardi käitumine. „Välimus“ sisaldab teemat, värve, loetavust, teksti suurust ja avalehe vidinaid.',
|
||||
'Esimesena avaneb „Üldine“. Seal on keel, kohanimede keel, ühikud, ajavorming, kuupäevavorming, nädala alguspäev ja kuvamisvaluuta ning reisikaardi käitumine. „Välimus“ sisaldab teemat, värve, loetavust, teksti suurust ja avalehe vidinaid.',
|
||||
'help.ctx.settings.bullet.3':
|
||||
'„Kaart“ määrab kaardimootori ja selle stiili; „Teavitused“ määrab, millised sündmused millisesse kanalisse lähevad; „Integratsioonid“ ühendab fototeegid, AirTraili, Dawarichi ja wandereri ning sisaldab API-võtmeid ja MCP-d; „Võrguühenduseta“ määrab, mida see seade salvestab.',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1149,7 +1149,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'„Käivitamine“: „Avaleht“ avab „Ülevaate“ või „Aktiivse reisi“; valikuga „Aktiivne reis“ määrab „Avatav vahekaart“, milline vaheleht reisil avaneb.',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
'„Keel ja piirkond“: „Kuvamisvaluuta“ (või „Reisi valuuta“, kui teisendust ei soovi), „Keel“, „Kohtade nimed“, „Temperatuuriühik“, „Vahemaaühik“, „Ajavorming“ ja „Nädala alguspäev“.',
|
||||
'„Keel ja piirkond“: „Kuvamisvaluuta“ (või „Reisi valuuta“, kui teisendust ei soovi), „Keel“, „Kohtade nimed“, „Temperatuuriühik“, „Vahemaaühik“, „Ajavorming“, „Nädala alguspäev“ ja „Kuupäevavorming“.',
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
'„Reisimine ja kaart“: „Ava kohad rakenduses“, „Kuupäev päevapealkirjades eespool“, „Broneeringute marsruutide sildid“, „Näita alati broneeringute marsruute“, „Kompaktsed tähised planeerimata kohtadele“, „Avasta kaardil kohti“, „Hägusta broneerimiskoodid“ ja „Optimeeri marsruut majutuskohast“.',
|
||||
'help.ctx.settings-appearance.title': 'Välimus',
|
||||
@@ -1463,9 +1463,9 @@ const help: TranslationStrings = {
|
||||
'All olev „Õiguste seaded“: iga toimingu puhul, kes tohib seda teha („Kõik“, „Reisi liikmed“, „Reisi omanik“ või „Ainult administraator“). Salvestamiseks klõpsa kaardi ülaosas „Salvesta“.',
|
||||
'help.ctx.admin-defaults.title': 'Kasutaja vaikevalikud',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
'Millega uus konto alustab, et keegi ei peaks seda kõigepealt seadistama: värvirežiim, ühikud, ajavorming, nädala alguspäev, kuvamisvaluuta ja hägustatud broneerimiskoodid ning lisaks kaart, selle võtmed ja marsruutimisserverid.',
|
||||
'Millega uus konto alustab, et keegi ei peaks seda kõigepealt seadistama: värvirežiim, ühikud, ajavorming, kuupäevavorming, nädala alguspäev, kuvamisvaluuta ja hägustatud broneerimiskoodid ning lisaks kaart, selle võtmed ja marsruutimisserverid.',
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
'Vasakul olev „Kasutajate vaikeseaded“: „Värvirežiim“, „Temperatuuriühik“, „Vahemaaühik“, „Ajavorming“, „Nädala alguspäev“, „Kuvamisvaluuta“ ja „Hägusta broneerimiskoodid“.',
|
||||
'Vasakul olev „Kasutajate vaikeseaded“: „Värvirežiim“, „Temperatuuriühik“, „Vahemaaühik“, „Ajavorming“, „Nädala alguspäev“, „Kuupäevavorming“, „Kuvamisvaluuta“ ja „Hägusta broneerimiskoodid“.',
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
'Paremal olev „Kaart“: „Kaardimall“, „Ühine CARTO võti“, „Oma marsruutimootor“ ja „Oma Valhalla server“, eelvaade, seejärel „Kaardimootor“ koos väljadega „Kaardi stiil“, „Ühine Mapboxi pääsutõend“, „3D-hooned ja maastik“ ning „Kõrge kvaliteediga režiim“.',
|
||||
'help.ctx.admin-config.title': 'Isikupärastamine',
|
||||
@@ -4236,7 +4236,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-about.bullet.3':
|
||||
'Ko-fi, „Osta mulle kohv“ ja Discordi kaudu saad TREKi toetada või selle tegijatega suhelda.',
|
||||
'help.guide.language-region.step.3':
|
||||
'Määra „Kuvamisvaluuta“ või jäta „Reisi valuuta“, et näha iga reisi tema enda valuutas. Seejärel vali „Temperatuuriühik“, „Vahemaaühik“, „Ajavorming“ ja „Nädala alguspäev“.',
|
||||
'Määra „Kuvamisvaluuta“ või jäta „Reisi valuuta“, et näha iga reisi tema enda valuutas. Seejärel vali „Temperatuuriühik“, „Vahemaaühik“, „Ajavorming“, „Nädala alguspäev“ ja „Kuupäevavorming“.',
|
||||
'help.guide.travel-map-prefs.step.3':
|
||||
'„Avasta kaardil kohti“ näitab kategooriasilti lähedal asuvate kohtade leidmiseks. „Hägusta broneerimiskoodid“ peidab kinnitusnumbrid, kuni viid kursori nende kohale või puudutad neid. „Optimeeri marsruut majutuskohast“ alustab ja lõpetab päeva sinu hotellis.',
|
||||
'help.guide.web-push-device.step.2':
|
||||
@@ -6243,7 +6243,7 @@ const help: TranslationStrings = {
|
||||
'Määratud välja kõrval olev lähtestuslink taastab TREKi vaikeväärtuse. Kasutaja enda seade on alati nende ees ülimuslik.',
|
||||
'help.ctx.admin-settings.bullet.5': '„Ohutsoon“: „Roteeri JWT saladus“ logib välja kõik kasutajad, ka sinu.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
'Vali jaotises „Kasutajate vaikeseaded“ „Värvirežiim“ („Hele“, „Tume“ või „Automaatne“), „Temperatuuriühik“, „Vahemaaühik“, „Ajavorming“, „Nädala alguspäev“, „Kuvamisvaluuta“ ja see, kas „Hägusta broneerimiskoodid“ on sees või „Väljas“. Iga valik salvestub kohe.',
|
||||
'Vali jaotises „Kasutajate vaikeseaded“ „Värvirežiim“ („Hele“, „Tume“ või „Automaatne“), „Temperatuuriühik“, „Vahemaaühik“, „Ajavorming“, „Nädala alguspäev“, „Kuupäevavorming“, „Kuvamisvaluuta“ ja see, kas „Hägusta broneerimiskoodid“ on sees või „Väljas“. Iga valik salvestub kohe.',
|
||||
'help.guide.admin-passkeys.step.2':
|
||||
'Sisesta „Teenuseosutaja ID (domeen)“, näiteks trek.example.org, ja „Lubatud päritolud“, mille kaudu sinu TREK on kättesaadav, seejärel klõpsa „Salvesta“. Jäta mõlemad tühjaks, et kasutada APP_URL-i.',
|
||||
'help.guide.rotate-jwt-secret.title': 'Kõigi korraga väljalogimine',
|
||||
|
||||
@@ -97,6 +97,10 @@ const settings: TranslationStrings = {
|
||||
'settings.timeFormat': 'Ajavorming',
|
||||
'settings.weekStart': 'Nädala alguspäev',
|
||||
'settings.weekStartHint': 'Nädala esimene päev kõigis kuupäevavalijates. Vacayl on oma seade.',
|
||||
'settings.dateFormat': 'Kuupäevavorming',
|
||||
'settings.dateFormatHint':
|
||||
'Kuidas kuupäevi kõigis kuupäevaväljades ja loendites kirjutatakse. Kuu nimega kuupäevad järgivad sinu keele sõnastust.',
|
||||
'settings.dateFormatAuto': 'Automaatne',
|
||||
'settings.preferredNavApp': 'Ava kohad rakenduses',
|
||||
'settings.preferredNavAppAsk': 'Küsi iga kord',
|
||||
'settings.preferredNavAppHint':
|
||||
|
||||
@@ -1190,7 +1190,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
'La barre latérale à gauche : « Affichage », « Apparence », « Carte », « Notifications », « Intégrations », « Hors ligne » et « Compte ». « Plugins » apparaît dès qu’un plugin est actif. « À propos » apparaît sauf si l’exploitant l’a masqué. La version est indiquée en bas.',
|
||||
'help.ctx.settings.bullet.2':
|
||||
'« Affichage » s’ouvre en premier. Il regroupe la langue, la langue des noms de lieux, les unités, le format de l’heure, le premier jour de la semaine et la devise d’affichage, ainsi que le comportement de la carte du voyage. « Apparence » regroupe le thème, les couleurs, la lisibilité, la taille du texte et les widgets du tableau de bord.',
|
||||
'« Affichage » s’ouvre en premier. Il regroupe la langue, la langue des noms de lieux, les unités, le format de l’heure, le format de date, le premier jour de la semaine et la devise d’affichage, ainsi que le comportement de la carte du voyage. « Apparence » regroupe le thème, les couleurs, la lisibilité, la taille du texte et les widgets du tableau de bord.',
|
||||
'help.ctx.settings.bullet.3':
|
||||
'« Carte » règle le moteur de carte et son style ; « Notifications » détermine quels événements passent par quel canal ; « Intégrations » connecte les photothèques, AirTrail, Dawarich et wanderer, et regroupe les clés API et MCP ; « Hors ligne » détermine ce que cet appareil garde en mémoire.',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1201,7 +1201,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'« Démarrage » : « Page de démarrage » ouvre le « Tableau de bord » ou le « Voyage en cours » ; avec « Voyage en cours », « Onglet de démarrage » détermine l’onglet sur lequel s’ouvre ce voyage.',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
"« Langue et région » : « Devise d'affichage » (ou « Devise du voyage » pour ne rien convertir), « Langue », « Noms de lieux », « Unité de température », « Unité de distance », « Format de l'heure » et « La semaine commence le ».",
|
||||
"« Langue et région » : « Devise d'affichage » (ou « Devise du voyage » pour ne rien convertir), « Langue », « Noms de lieux », « Unité de température », « Unité de distance », « Format de l'heure », « La semaine commence le » et « Format de date ».",
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
"« Voyage et carte » : « Ouvrir les lieux dans », « Date en premier dans les titres de jour », « Étiquettes des itinéraires », « Toujours afficher les itinéraires de réservation », « Repères compacts pour les lieux non planifiés », « Explorer les lieux sur la carte », « Masquer les codes de réservation » et « Optimiser l'itinéraire depuis l'hébergement ».",
|
||||
'help.ctx.settings-appearance.title': 'Apparence',
|
||||
@@ -1535,9 +1535,9 @@ const help: TranslationStrings = {
|
||||
'« Paramètres des permissions », en bas : pour chaque action, qui peut l’effectuer (« Tout le monde », « Membres du voyage », « Propriétaire du voyage » ou « Administrateur uniquement »). Cliquez sur « Enregistrer » en haut de la carte pour valider.',
|
||||
'help.ctx.admin-defaults.title': 'Valeurs par défaut',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
'Les réglages de départ d’un nouveau compte, pour que personne n’ait à tout configurer d’abord : mode de couleur, unités, format de l’heure, premier jour de la semaine, devise d’affichage et codes de réservation masqués, ainsi que la carte, ses clés et les serveurs de calcul d’itinéraire.',
|
||||
'Les réglages de départ d’un nouveau compte, pour que personne n’ait à tout configurer d’abord : mode de couleur, unités, format de l’heure, format de date, premier jour de la semaine, devise d’affichage et codes de réservation masqués, ainsi que la carte, ses clés et les serveurs de calcul d’itinéraire.',
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
"« Paramètres utilisateur par défaut », à gauche : « Mode de couleur », « Unité de température », « Unité de distance », « Format de l'heure », « La semaine commence le », « Devise d'affichage » et « Masquer les codes de réservation ».",
|
||||
"« Paramètres utilisateur par défaut », à gauche : « Mode de couleur », « Unité de température », « Unité de distance », « Format de l'heure », « La semaine commence le », « Format de date », « Devise d'affichage » et « Masquer les codes de réservation ».",
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
'« Carte », à droite : « Modèle de carte », « Clé CARTO partagée », « Moteur de calcul d’itinéraire propre » et « Instance Valhalla propre », un aperçu, puis « Moteur cartographique » avec « Style de carte », « Jeton Mapbox partagé », « Bâtiments & terrain en 3D » et « Mode haute qualité ».',
|
||||
'help.ctx.admin-config.title': 'Personnalisation',
|
||||
@@ -4415,7 +4415,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-about.bullet.3':
|
||||
'Ko-fi, Buy Me a Coffee et Discord : c’est là que vous pouvez soutenir TREK ou échanger avec l’équipe qui le développe.',
|
||||
'help.guide.language-region.step.3':
|
||||
"Réglez la « Devise d'affichage », ou gardez « Devise du voyage » pour voir chaque voyage dans sa propre devise. Choisissez ensuite « Unité de température », « Unité de distance », « Format de l'heure » et le jour où commence la semaine.",
|
||||
"Réglez la « Devise d'affichage », ou gardez « Devise du voyage » pour voir chaque voyage dans sa propre devise. Choisissez ensuite « Unité de température », « Unité de distance », « Format de l'heure », le jour où commence la semaine et le « Format de date ».",
|
||||
'help.guide.travel-map-prefs.step.3':
|
||||
"« Explorer les lieux sur la carte » affiche la pastille de catégories pour trouver des lieux à proximité. « Masquer les codes de réservation » cache les numéros de confirmation jusqu’à ce que vous les survoliez ou les touchiez. « Optimiser l'itinéraire depuis l'hébergement » fait commencer et finir chaque journée à votre hôtel.",
|
||||
'help.guide.web-push-device.step.2':
|
||||
@@ -6537,7 +6537,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.admin-settings.bullet.5':
|
||||
'« Zone de danger » : « Renouveler le secret JWT » déconnecte tous les utilisateurs, vous compris.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
"Sous « Paramètres utilisateur par défaut », choisissez « Mode de couleur » (« Clair », « Sombre » ou « Auto »), « Unité de température », « Unité de distance », « Format de l'heure », « La semaine commence le », la « Devise d'affichage » et si « Masquer les codes de réservation » est activé ou désactivé. Chaque choix est enregistré immédiatement.",
|
||||
"Sous « Paramètres utilisateur par défaut », choisissez « Mode de couleur » (« Clair », « Sombre » ou « Auto »), « Unité de température », « Unité de distance », « Format de l'heure », « La semaine commence le », le « Format de date », la « Devise d'affichage » et si « Masquer les codes de réservation » est activé ou désactivé. Chaque choix est enregistré immédiatement.",
|
||||
'help.guide.admin-passkeys.step.2':
|
||||
'Saisissez le « Relying Party ID (domaine) », par exemple trek.example.org, et les « Origines autorisées » par lesquelles on accède à votre TREK, puis cliquez sur « Enregistrer ». Laissez les deux champs vides pour utiliser APP_URL.',
|
||||
'help.guide.rotate-jwt-secret.title': 'Déconnecter tout le monde d’un coup',
|
||||
|
||||
@@ -90,6 +90,10 @@ const settings: TranslationStrings = {
|
||||
'settings.timeFormat': "Format de l'heure",
|
||||
'settings.weekStart': 'La semaine commence le',
|
||||
'settings.weekStartHint': 'Premier jour de la semaine dans tous les sélecteurs de date. Vacay a son propre réglage.',
|
||||
'settings.dateFormat': 'Format de date',
|
||||
'settings.dateFormatHint':
|
||||
"Façon dont les dates s'écrivent dans tous les champs de date et toutes les listes. Les dates avec un nom de mois gardent la formulation de votre langue.",
|
||||
'settings.dateFormatAuto': 'Automatique',
|
||||
'settings.preferredNavApp': 'Ouvrir les lieux dans',
|
||||
'settings.preferredNavAppAsk': 'Demander à chaque fois',
|
||||
'settings.preferredNavAppHint':
|
||||
|
||||
@@ -1187,7 +1187,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
'Πλαϊνή στήλη αριστερά: «Γενικά», «Εμφάνιση», «Χάρτης», «Ειδοποιήσεις», «Ενσωματώσεις», «Εκτός σύνδεσης» και «Λογαριασμός». Τα Plugins εμφανίζονται μόλις ενεργοποιηθεί ένα plugin. Το «Σχετικά» εμφανίζεται, εκτός αν ο διαχειριστής της εγκατάστασης το έχει κρύψει. Η έκδοση φαίνεται στο κάτω μέρος.',
|
||||
'help.ctx.settings.bullet.2':
|
||||
'Πρώτη ανοίγει η καρτέλα «Γενικά». Εκεί ορίζετε τη γλώσσα, τη γλώσσα των ονομάτων μερών, τις μονάδες, τη μορφή ώρας, την πρώτη ημέρα της εβδομάδας και το νόμισμα εμφάνισης, καθώς και τη συμπεριφορά του χάρτη του ταξιδιού. Στην «Εμφάνιση» θα βρείτε θέμα, χρώματα, αναγνωσιμότητα, μέγεθος κειμένου και τα widgets του πίνακα ελέγχου.',
|
||||
'Πρώτη ανοίγει η καρτέλα «Γενικά». Εκεί ορίζετε τη γλώσσα, τη γλώσσα των ονομάτων μερών, τις μονάδες, τη μορφή ώρας, τη μορφή ημερομηνίας, την πρώτη ημέρα της εβδομάδας και το νόμισμα εμφάνισης, καθώς και τη συμπεριφορά του χάρτη του ταξιδιού. Στην «Εμφάνιση» θα βρείτε θέμα, χρώματα, αναγνωσιμότητα, μέγεθος κειμένου και τα widgets του πίνακα ελέγχου.',
|
||||
'help.ctx.settings.bullet.3':
|
||||
'Στον «Χάρτη» ορίζετε τη μηχανή χάρτη και το στυλ της. Στις «Ειδοποιήσεις» ορίζετε ποια συμβάντα πηγαίνουν σε ποιο κανάλι. Οι «Ενσωματώσεις» συνδέουν βιβλιοθήκες φωτογραφιών, AirTrail, Dawarich και wanderer και περιέχουν τα κλειδιά API και το MCP. Στο «Εκτός σύνδεσης» ορίζετε τι αποθηκεύει αυτή η συσκευή.',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1198,7 +1198,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'«Εκκίνηση»: η «Αρχική σελίδα» ανοίγει στον «Πίνακα ελέγχου» ή στο «Ενεργό ταξίδι». Με «Ενεργό ταξίδι», η «Αρχική καρτέλα» ορίζει σε ποια καρτέλα ανοίγει το ταξίδι.',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
'Γλώσσα και περιοχή: «Νόμισμα εμφάνισης» (ή «Νόμισμα ταξιδιού» χωρίς μετατροπή), «Γλώσσα», «Ονόματα μερών», «Μονάδα Θερμοκρασίας», «Μονάδα Απόστασης», «Μορφή Ώρας» και «Η εβδομάδα ξεκινά την».',
|
||||
'Γλώσσα και περιοχή: «Νόμισμα εμφάνισης» (ή «Νόμισμα ταξιδιού» χωρίς μετατροπή), «Γλώσσα», «Ονόματα μερών», «Μονάδα Θερμοκρασίας», «Μονάδα Απόστασης», «Μορφή Ώρας», «Η εβδομάδα ξεκινά την» και «Μορφή Ημερομηνίας».',
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
'Ταξίδι και χάρτης: «Άνοιγμα τοποθεσιών σε», «Πρώτα η ημερομηνία στους τίτλους ημερών», «Ετικέτες διαδρομής κρατήσεων», «Πάντα εμφάνιση διαδρομών κράτησης», «Συμπαγείς δείκτες για μη προγραμματισμένα μέρη», «Εξερεύνηση μερών στον χάρτη», «Θόλωμα Κωδικών Κρατήσεων» και «Βελτιστοποίηση διαδρομής από το κατάλυμα».',
|
||||
'help.ctx.settings-appearance.title': 'Εμφάνιση',
|
||||
@@ -1530,9 +1530,9 @@ const help: TranslationStrings = {
|
||||
'«Ρυθμίσεις Δικαιωμάτων» στο κάτω μέρος: για κάθε ενέργεια, ποιος μπορεί να την εκτελεί («Όλοι», «Μέλη ταξιδιού», «Ιδιοκτήτης ταξιδιού» ή «Μόνο διαχειριστής»). Κάντε κλικ στο «Αποθήκευση» στην κορυφή της κάρτας για να αποθηκευτούν.',
|
||||
'help.ctx.admin-defaults.title': 'Προεπιλογές Χρήστη',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
'Με τι ξεκινά ένας νέος λογαριασμός, ώστε να μη χρειάζεται να το ρυθμίσει κανείς πρώτα: λειτουργία χρώματος, μονάδες, μορφή ώρας, πρώτη ημέρα της εβδομάδας, νόμισμα εμφάνισης και θόλωμα κωδικών κρατήσεων, καθώς και ο χάρτης, τα κλειδιά του και οι διακομιστές δρομολόγησης.',
|
||||
'Με τι ξεκινά ένας νέος λογαριασμός, ώστε να μη χρειάζεται να το ρυθμίσει κανείς πρώτα: λειτουργία χρώματος, μονάδες, μορφή ώρας, μορφή ημερομηνίας, πρώτη ημέρα της εβδομάδας, νόμισμα εμφάνισης και θόλωμα κωδικών κρατήσεων, καθώς και ο χάρτης, τα κλειδιά του και οι διακομιστές δρομολόγησης.',
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
'«Προεπιλεγμένες Ρυθμίσεις Χρήστη» αριστερά: «Λειτουργία Χρώματος», «Μονάδα Θερμοκρασίας», «Μονάδα Απόστασης», «Μορφή Ώρας», «Η εβδομάδα ξεκινά την», «Νόμισμα εμφάνισης» και «Θόλωμα Κωδικών Κρατήσεων».',
|
||||
'«Προεπιλεγμένες Ρυθμίσεις Χρήστη» αριστερά: «Λειτουργία Χρώματος», «Μονάδα Θερμοκρασίας», «Μονάδα Απόστασης», «Μορφή Ώρας», «Η εβδομάδα ξεκινά την», «Μορφή Ημερομηνίας», «Νόμισμα εμφάνισης» και «Θόλωμα Κωδικών Κρατήσεων».',
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
'Χάρτης δεξιά: «Πρότυπο Χάρτη», «Κοινόχρηστο κλειδί CARTO», «Δική σας μηχανή δρομολόγησης» και «Δική σας υπηρεσία Valhalla», μια προεπισκόπηση και μετά «Μηχανή χάρτη» με «Στυλ χάρτη», «Κοινόχρηστο διακριτικό Mapbox», «Κτίρια & ανάγλυφο 3D» και «Λειτουργία υψηλής ποιότητας».',
|
||||
'help.ctx.admin-config.title': 'Εξατομίκευση',
|
||||
@@ -4409,7 +4409,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-about.bullet.3':
|
||||
'Στο Ko-fi, στο Buy Me a Coffee και στο Discord μπορείτε να στηρίξετε το TREK ή να μιλήσετε με τους ανθρώπους που το φτιάχνουν.',
|
||||
'help.guide.language-region.step.3':
|
||||
'Ορίστε το «Νόμισμα εμφάνισης» ή κρατήστε το «Νόμισμα ταξιδιού» για να βλέπετε κάθε ταξίδι στο δικό του νόμισμα. Μετά επιλέξτε «Μονάδα Θερμοκρασίας», «Μονάδα Απόστασης», «Μορφή Ώρας» και την ημέρα στο «Η εβδομάδα ξεκινά την».',
|
||||
'Ορίστε το «Νόμισμα εμφάνισης» ή κρατήστε το «Νόμισμα ταξιδιού» για να βλέπετε κάθε ταξίδι στο δικό του νόμισμα. Μετά επιλέξτε «Μονάδα Θερμοκρασίας», «Μονάδα Απόστασης», «Μορφή Ώρας», την ημέρα στο «Η εβδομάδα ξεκινά την» και τη «Μορφή Ημερομηνίας».',
|
||||
'help.guide.travel-map-prefs.step.3':
|
||||
'Το «Εξερεύνηση μερών στον χάρτη» εμφανίζει το κουμπί κατηγοριών για να βρίσκετε μέρη κοντά σας. Το «Θόλωμα Κωδικών Κρατήσεων» κρύβει τους αριθμούς επιβεβαίωσης μέχρι να περάσετε τον δείκτη από πάνω ή να τους πατήσετε. Με το «Βελτιστοποίηση διαδρομής από το κατάλυμα» η ημέρα ξεκινά και τελειώνει στο ξενοδοχείο σας.',
|
||||
'help.guide.web-push-device.step.2':
|
||||
@@ -6523,7 +6523,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.admin-settings.bullet.5':
|
||||
'«Επικίνδυνη ζώνη»: το «Εναλλαγή μυστικού JWT» αποσυνδέει όλους τους χρήστες, μαζί κι εσάς.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
'Στις «Προεπιλεγμένες Ρυθμίσεις Χρήστη», επιλέξτε «Λειτουργία Χρώματος» («Φωτεινό», «Σκούρο» ή «Αυτόματο»), «Μονάδα Θερμοκρασίας», «Μονάδα Απόστασης», «Μορφή Ώρας», «Η εβδομάδα ξεκινά την», «Νόμισμα εμφάνισης» και αν το «Θόλωμα Κωδικών Κρατήσεων» είναι ενεργό ή «Ανενεργό». Κάθε επιλογή αποθηκεύεται αμέσως.',
|
||||
'Στις «Προεπιλεγμένες Ρυθμίσεις Χρήστη», επιλέξτε «Λειτουργία Χρώματος» («Φωτεινό», «Σκούρο» ή «Αυτόματο»), «Μονάδα Θερμοκρασίας», «Μονάδα Απόστασης», «Μορφή Ώρας», «Η εβδομάδα ξεκινά την», «Μορφή Ημερομηνίας», «Νόμισμα εμφάνισης» και αν το «Θόλωμα Κωδικών Κρατήσεων» είναι ενεργό ή «Ανενεργό». Κάθε επιλογή αποθηκεύεται αμέσως.',
|
||||
'help.guide.admin-passkeys.step.2':
|
||||
'Συμπληρώστε το «Relying Party ID (τομέας)», π.χ. trek.example.org, και τις «Επιτρεπόμενες προελεύσεις» από τις οποίες ανοίγει το TREK σας και κάντε κλικ στο «Αποθήκευση». Αφήστε τα κενά για να χρησιμοποιηθεί το APP_URL.',
|
||||
'help.guide.rotate-jwt-secret.title': 'Αποσύνδεση όλων μαζί',
|
||||
|
||||
@@ -92,6 +92,10 @@ const settings: TranslationStrings = {
|
||||
'settings.timeFormat': 'Μορφή Ώρας',
|
||||
'settings.weekStart': 'Η εβδομάδα ξεκινά την',
|
||||
'settings.weekStartHint': 'Πρώτη ημέρα της εβδομάδας σε κάθε επιλογέα ημερομηνίας. Το Vacay έχει δική του ρύθμιση.',
|
||||
'settings.dateFormat': 'Μορφή Ημερομηνίας',
|
||||
'settings.dateFormatHint':
|
||||
'Πώς γράφονται οι ημερομηνίες σε κάθε πεδίο ημερομηνίας και λίστα. Οι ημερομηνίες με όνομα μήνα διατηρούν τη διατύπωση της γλώσσας σας.',
|
||||
'settings.dateFormatAuto': 'Αυτόματο',
|
||||
'settings.preferredNavApp': 'Άνοιγμα τοποθεσιών σε',
|
||||
'settings.preferredNavAppAsk': 'Ερώτηση κάθε φορά',
|
||||
'settings.preferredNavAppHint':
|
||||
|
||||
@@ -1238,7 +1238,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
'סרגל הצד משמאל: כללי, מראה, מפה, התראות, שילובים, אופליין וחשבון. תוספים מופיע כשיש תוסף פעיל. אודות מופיע אלא אם מפעילי המערכת הסתירו אותו. הגרסה מוצגת בתחתית.',
|
||||
'help.ctx.settings.bullet.2':
|
||||
'כללי נפתח ראשון. הוא כולל את השפה, שפת שמות המקומות, יחידות המידה, פורמט השעה, תחילת השבוע ומטבע התצוגה, וגם את אופן הפעולה של מפת הטיול. מראה כולל ערכת נושא, צבעים, קריאות, גודל טקסט ואת הווידג׳טים בלוח הבקרה.',
|
||||
'כללי נפתח ראשון. הוא כולל את השפה, שפת שמות המקומות, יחידות המידה, פורמט השעה, פורמט התאריך, תחילת השבוע ומטבע התצוגה, וגם את אופן הפעולה של מפת הטיול. מראה כולל ערכת נושא, צבעים, קריאות, גודל טקסט ואת הווידג׳טים בלוח הבקרה.',
|
||||
'help.ctx.settings.bullet.3':
|
||||
'מפה מגדיר את מנוע המפה ואת הסגנון שלו; התראות מגדיר אילו אירועים נשלחים לכל ערוץ; שילובים מחבר ספריות תמונות, AirTrail, Dawarich ו-wanderer, וכולל מפתחות API ו-MCP; אופליין מגדיר מה נשמר במכשיר הזה.',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1249,7 +1249,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'הפעלה: עמוד פתיחה פותח את לוח הבקרה או את הטיול הפעיל; כשבוחרים בטיול הפעיל, לשונית פתיחה קובעת באיזו לשונית הטיול ייפתח.',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
'שפה ואזור: מטבע תצוגה (או מטבע הטיול אם לא נבחר מטבע), שפה, שמות מקומות, יחידת טמפרטורה, יחידת מרחק, פורמט שעה והשבוע מתחיל ביום.',
|
||||
'שפה ואזור: מטבע תצוגה (או מטבע הטיול אם לא נבחר מטבע), שפה, שמות מקומות, יחידת טמפרטורה, יחידת מרחק, פורמט שעה, השבוע מתחיל ביום ופורמט תאריך.',
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
'טיול ומפה: פתיחת מקומות ב-, התאריך קודם בכותרות הימים, תוויות במסלולי הזמנות, הצגת מסלולי הזמנות תמיד, סמנים קטנים למקומות שלא תוכננו, גילוי מקומות על המפה, טשטוש קודי הזמנה ואופטימיזציית מסלול מהלינה.',
|
||||
'help.ctx.settings-appearance.title': 'מראה',
|
||||
@@ -1347,7 +1347,7 @@ const help: TranslationStrings = {
|
||||
'help.guide.language-region.step.2':
|
||||
'שמות מקומות קובע את השפה של חיפוש המקומות, ההצעות והכתובות; כמו באפליקציה משתמש בשפת הממשק.',
|
||||
'help.guide.language-region.step.3':
|
||||
'הגדירו את מטבע התצוגה, או השאירו את מטבע הטיול כדי לראות כל טיול במטבע שלו. לאחר מכן בחרו יחידת טמפרטורה, יחידת מרחק, פורמט שעה ואת היום שבו השבוע מתחיל.',
|
||||
'הגדירו את מטבע התצוגה, או השאירו את מטבע הטיול כדי לראות כל טיול במטבע שלו. לאחר מכן בחרו יחידת טמפרטורה, יחידת מרחק, פורמט שעה, את היום שבו השבוע מתחיל ואת פורמט התאריך.',
|
||||
'help.guide.language-region.result':
|
||||
'תאריכים, מרחקים וסכומי כסף מוצגים בפורמט שאתם רגילים אליו. בהוצאות, הסכומים מוצגים במטבע התצוגה שלכם לצורכי תצוגה בלבד; הסכומים שהוזנו לא משתנים.',
|
||||
'help.guide.language-region.tip.1': 'מטבע הטיול מציג כל טיול במטבע שלו, ללא המרה.',
|
||||
@@ -1630,9 +1630,9 @@ const help: TranslationStrings = {
|
||||
'הגדרות הרשאות בתחתית: מי רשאי לבצע כל פעולה (כולם, חברי הטיול, בעלי הטיול או מנהלים בלבד). לחצו על שמירה בראש הכרטיסייה כדי לשמור.',
|
||||
'help.ctx.admin-defaults.title': 'ברירות מחדל למשתמשים',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
'ההגדרות שחשבון חדש מתחיל איתן, כדי שלא יהיה צורך להגדיר אותן מראש: מצב צבע, יחידות מידה, פורמט שעה, תחילת השבוע, מטבע תצוגה וטשטוש קודי הזמנה, וכן המפה, המפתחות שלה ושרתי הניווט.',
|
||||
'ההגדרות שחשבון חדש מתחיל איתן, כדי שלא יהיה צורך להגדיר אותן מראש: מצב צבע, יחידות מידה, פורמט שעה, פורמט תאריך, תחילת השבוע, מטבע תצוגה וטשטוש קודי הזמנה, וכן המפה, המפתחות שלה ושרתי הניווט.',
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
'הגדרות ברירת מחדל למשתמשים משמאל: מצב צבע, יחידת טמפרטורה, יחידת מרחק, פורמט שעה, השבוע מתחיל ביום, מטבע תצוגה וטשטוש קודי הזמנה.',
|
||||
'הגדרות ברירת מחדל למשתמשים משמאל: מצב צבע, יחידת טמפרטורה, יחידת מרחק, פורמט שעה, השבוע מתחיל ביום, פורמט תאריך, מטבע תצוגה וטשטוש קודי הזמנה.',
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
'מפה מימין: תבנית מפה, מפתח CARTO משותף, מנוע ניווט משלכם ומופע Valhalla משלכם, תצוגה מקדימה, ואז מנוע מפה עם סגנון מפה, אסימון Mapbox משותף, בניינים ותבליט בתלת-ממד ומצב איכות גבוהה.',
|
||||
'help.ctx.admin-defaults.bullet.3':
|
||||
@@ -1774,7 +1774,7 @@ const help: TranslationStrings = {
|
||||
'help.guide.default-user-settings.goal':
|
||||
'הגדירו לכל חשבון יחידות מידה, מצב צבע ומטבע מתאימים, בלי שהמשתמשים יצטרכו לפתוח את ההגדרות.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
'תחת הגדרות ברירת מחדל למשתמשים, בחרו מצב צבע (בהיר, כהה או אוטומטי), יחידת טמפרטורה, יחידת מרחק, תבנית שעה, יום תחילת השבוע ומטבע תצוגה, והגדירו אם טשטוש קודי הזמנה פועל או כבוי. כל בחירה נשמרת מיד.',
|
||||
'תחת הגדרות ברירת מחדל למשתמשים, בחרו מצב צבע (בהיר, כהה או אוטומטי), יחידת טמפרטורה, יחידת מרחק, תבנית שעה, יום תחילת השבוע, פורמט תאריך ומטבע תצוגה, והגדירו אם טשטוש קודי הזמנה פועל או כבוי. כל בחירה נשמרת מיד.',
|
||||
'help.guide.default-user-settings.step.2':
|
||||
'לצד שמו של שדה שהגדרתם מופיע קישור איפוס, שמחזיר את ברירת המחדל המובנית של TREK.',
|
||||
'help.guide.default-user-settings.result':
|
||||
|
||||
@@ -94,6 +94,10 @@ const settings: TranslationStrings = {
|
||||
'settings.timeFormat': 'פורמט שעה',
|
||||
'settings.weekStart': 'השבוע מתחיל ביום',
|
||||
'settings.weekStartHint': 'היום הראשון בשבוע בכל בוחר תאריכים. ל-Vacay יש הגדרה משלו.',
|
||||
'settings.dateFormat': 'פורמט תאריך',
|
||||
'settings.dateFormatHint':
|
||||
'איך תאריכים נכתבים בכל שדה תאריך וברשימות. תאריכים עם שם חודש שומרים על הניסוח של השפה שלך.',
|
||||
'settings.dateFormatAuto': 'אוטומטי',
|
||||
'settings.preferredNavApp': 'פתיחת מקומות ב-',
|
||||
'settings.preferredNavAppAsk': 'לשאול בכל פעם',
|
||||
'settings.preferredNavAppHint': 'כשנבחרה אפליקציה, כפתור הניווט פותח אותה מיד במקום להציע את כל אפליקציות המפות.',
|
||||
|
||||
@@ -1177,7 +1177,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
'Bal oldalsáv: „Általános”, „Megjelenés”, „Térkép”, „Értesítések”, „Integráció”, „Offline” és „Fiók”. A „Pluginok” akkor jelenik meg, ha van aktív plugin. A „Névjegy” akkor látszik, ha az üzemeltető nem rejtette el. A verziószám alul látható.',
|
||||
'help.ctx.settings.bullet.2':
|
||||
'Elsőként az „Általános” fül nyílik meg. Itt van a nyelv, a helynevek nyelve, a mértékegységek, az időformátum, a hét kezdőnapja és a megjelenítési pénznem, valamint az utazástérkép viselkedése. A „Megjelenés” fülön a téma, a színek, az olvashatóság, a szövegméret és az irányítópult-widgetek találhatók.',
|
||||
'Elsőként az „Általános” fül nyílik meg. Itt van a nyelv, a helynevek nyelve, a mértékegységek, az időformátum, a dátumformátum, a hét kezdőnapja és a megjelenítési pénznem, valamint az utazástérkép viselkedése. A „Megjelenés” fülön a téma, a színek, az olvashatóság, a szövegméret és az irányítópult-widgetek találhatók.',
|
||||
'help.ctx.settings.bullet.3':
|
||||
'A „Térkép” fülön a térképmotort és a stílusát állítod be; az „Értesítések” fülön azt, melyik esemény melyik csatornára menjen; az „Integráció” fülön fotókönyvtárakat, az AirTrailt, a Dawarichot és a wanderert csatlakoztathatod, és itt vannak az API-kulcsok és az MCP is; az „Offline” fülön azt, mit tároljon ez az eszköz.',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1188,7 +1188,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'„Indítás”: a „Kezdőoldal” az „Irányítópult” vagy az „Aktív utazás” lehet; az „Aktív utazás” választásakor a „Kezdő fül” határozza meg, melyik fülön nyíljon meg az utazás.',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
'Nyelv és régió: „Megjelenítési pénznem” (átváltás nélkül „Az utazás pénzneme”), „Nyelv”, „Helynevek”, „Hőmérséklet egység”, „Távolság egység”, „Időformátum” és „A hét kezdőnapja”.',
|
||||
'Nyelv és régió: „Megjelenítési pénznem” (átváltás nélkül „Az utazás pénzneme”), „Nyelv”, „Helynevek”, „Hőmérséklet egység”, „Távolság egység”, „Időformátum”, „A hét kezdőnapja” és „Dátumformátum”.',
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
'Utazás és térkép: „Helyek megnyitása itt”, „Dátum elöl a napok fejlécében”, „Útvonal-címkék a foglalásokhoz”, „Mindig jelenjenek meg a foglalási útvonalak”, „Kompakt jelölők a nem tervezett helyekhez”, „Helyek felfedezése a térképen”, „Foglalási kódok elrejtése” és „Útvonal optimalizálása a szállástól”.',
|
||||
'help.ctx.settings-appearance.title': 'Megjelenés',
|
||||
@@ -1516,9 +1516,9 @@ const help: TranslationStrings = {
|
||||
'Alul a „Jogosultsági beállítások”: műveletenként megadhatod, ki végezheti el („Mindenki”, „Utazás tagjai”, „Utazás tulajdonosa” vagy „Csak adminisztrátor”). A mentéshez kattints a kártya tetején lévő „Mentés” gombra.',
|
||||
'help.ctx.admin-defaults.title': 'Alapértelmezett beállítások',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
'Itt állítod be, mivel induljon egy új fiók, hogy senkinek ne kelljen előbb beállítgatnia: színmód, mértékegységek, időformátum, a hét kezdőnapja, megjelenítési pénznem és a foglalási kódok elrejtése, valamint a térkép, a kulcsai és az útvonaltervező szerverek.',
|
||||
'Itt állítod be, mivel induljon egy új fiók, hogy senkinek ne kelljen előbb beállítgatnia: színmód, mértékegységek, időformátum, dátumformátum, a hét kezdőnapja, megjelenítési pénznem és a foglalási kódok elrejtése, valamint a térkép, a kulcsai és az útvonaltervező szerverek.',
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
'Bal oldalon az „Alapértelmezett felhasználói beállítások”: „Színmód”, „Hőmérséklet egység”, „Távolság egység”, „Időformátum”, „A hét kezdőnapja”, „Megjelenítési pénznem” és „Foglalási kódok elrejtése”.',
|
||||
'Bal oldalon az „Alapértelmezett felhasználói beállítások”: „Színmód”, „Hőmérséklet egység”, „Távolság egység”, „Időformátum”, „A hét kezdőnapja”, „Dátumformátum”, „Megjelenítési pénznem” és „Foglalási kódok elrejtése”.',
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
'Jobb oldalon a „Térkép”: „Térkép sablon”, „Megosztott CARTO-kulcs”, „Saját útvonaltervező” és „Saját Valhalla-példány”, egy előnézet, majd a „Térképmotor” a „Térképstílus”, a „Megosztott Mapbox-token”, a „3D épületek & domborzat” és a „Kiváló minőségű mód” beállítással.',
|
||||
'help.ctx.admin-config.title': 'Személyre szabás',
|
||||
@@ -4376,7 +4376,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-about.bullet.3':
|
||||
'A Ko-fi, a Buy Me a Coffee és a Discord oldalán támogathatod a TREK-et, vagy beszélgethetsz a fejlesztőkkel.',
|
||||
'help.guide.language-region.step.3':
|
||||
'Állítsd be a „Megjelenítési pénznem” értékét, vagy hagyd „Az utazás pénzneme” beállításon, hogy minden utazást a saját pénznemében láss. Ezután válaszd ki a „Hőmérséklet egység”, a „Távolság egység” és az „Időformátum” értékét, valamint azt, melyik nappal kezdődjön a hét („A hét kezdőnapja”).',
|
||||
'Állítsd be a „Megjelenítési pénznem” értékét, vagy hagyd „Az utazás pénzneme” beállításon, hogy minden utazást a saját pénznemében láss. Ezután válaszd ki a „Hőmérséklet egység”, a „Távolság egység”, az „Időformátum” és a „Dátumformátum” értékét, valamint azt, melyik nappal kezdődjön a hét („A hét kezdőnapja”).',
|
||||
'help.guide.travel-map-prefs.step.3':
|
||||
'A „Helyek felfedezése a térképen” megjeleníti a kategóriagombot, amellyel a közeli helyeket keresheted. A „Foglalási kódok elrejtése” addig takarja a visszaigazolási számokat, amíg föléjük nem viszed az egeret, vagy rájuk nem koppintasz. Az „Útvonal optimalizálása a szállástól” beállítással a nap a szállásodon kezdődik és ott is ér véget.',
|
||||
'help.guide.web-push-device.step.2':
|
||||
@@ -6466,7 +6466,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.admin-settings.bullet.5':
|
||||
'„Veszélyzóna”: a „JWT-titok cseréje” minden felhasználót kijelentkeztet, téged is.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
'Az „Alapértelmezett felhasználói beállítások” részben válaszd ki a „Színmód” („Világos”, „Sötét” vagy „Automatikus”), a „Hőmérséklet egység”, a „Távolság egység”, az „Időformátum”, „A hét kezdőnapja” és a „Megjelenítési pénznem” értékét, valamint azt, hogy a „Foglalási kódok elrejtése” be vagy ki legyen kapcsolva. Minden választás azonnal mentődik.',
|
||||
'Az „Alapértelmezett felhasználói beállítások” részben válaszd ki a „Színmód” („Világos”, „Sötét” vagy „Automatikus”), a „Hőmérséklet egység”, a „Távolság egység”, az „Időformátum”, „A hét kezdőnapja”, a „Dátumformátum” és a „Megjelenítési pénznem” értékét, valamint azt, hogy a „Foglalási kódok elrejtése” be vagy ki legyen kapcsolva. Minden választás azonnal mentődik.',
|
||||
'help.guide.admin-passkeys.step.2':
|
||||
'Add meg a Relying Party ID (domain) értékét, például trek.example.org, és az „Engedélyezett origók” közé azokat a címeket, amelyeken a TREK-ed elérhető, majd kattints a „Mentés” gombra. Ha mindkettőt üresen hagyod, a TREK az APP_URL értékét használja.',
|
||||
'help.guide.rotate-jwt-secret.title': 'Mindenki kijelentkeztetése egyszerre',
|
||||
|
||||
@@ -88,6 +88,10 @@ const settings: TranslationStrings = {
|
||||
'settings.timeFormat': 'Időformátum',
|
||||
'settings.weekStart': 'A hét kezdőnapja',
|
||||
'settings.weekStartHint': 'A hét első napja minden dátumválasztóban. A Vacay saját beállítást használ.',
|
||||
'settings.dateFormat': 'Dátumformátum',
|
||||
'settings.dateFormatHint':
|
||||
'Hogyan jelennek meg a dátumok minden dátummezőben és listában. A hónapnevet tartalmazó dátumok megtartják a nyelved megfogalmazását.',
|
||||
'settings.dateFormatAuto': 'Automatikus',
|
||||
'settings.preferredNavApp': 'Helyek megnyitása itt',
|
||||
'settings.preferredNavAppAsk': 'Mindig kérdezzen',
|
||||
'settings.preferredNavAppHint':
|
||||
|
||||
@@ -1149,7 +1149,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
'Sidebar di kiri: “Tampilan” (umum), “Tampilan” (visual), “Peta”, “Notifikasi”, “Integrasi”, Offline, dan “Akun”. Plugin muncul begitu ada plugin yang aktif. “Tentang” muncul kecuali operator menyembunyikannya. Versinya tampil di bagian bawah.',
|
||||
'help.ctx.settings.bullet.2':
|
||||
'“Tampilan” (umum) terbuka lebih dulu. Isinya bahasa, bahasa nama tempat, satuan, format waktu, awal minggu, dan mata uang tampilan, serta cara kerja peta perjalanan. “Tampilan” (visual) berisi tema, warna, keterbacaan, ukuran teks, dan widget dasbor.',
|
||||
'“Tampilan” (umum) terbuka lebih dulu. Isinya bahasa, bahasa nama tempat, satuan, format waktu, format tanggal, awal minggu, dan mata uang tampilan, serta cara kerja peta perjalanan. “Tampilan” (visual) berisi tema, warna, keterbacaan, ukuran teks, dan widget dasbor.',
|
||||
'help.ctx.settings.bullet.3':
|
||||
'“Peta” mengatur mesin peta dan gayanya; “Notifikasi” mengatur peristiwa mana yang dikirim ke saluran mana; “Integrasi” menghubungkan pustaka foto, AirTrail, Dawarich, dan wanderer, serta berisi kunci API dan MCP; “Offline” mengatur apa saja yang disimpan di perangkat ini.',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1160,7 +1160,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'“Mulai”: “Halaman awal” membuka “Dasbor” atau “Perjalanan aktif”; dengan “Perjalanan aktif”, “Tab awal” menentukan tab yang dibuka perjalanan itu.',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
'Bahasa & wilayah: “Mata uang tampilan” (atau “Mata uang perjalanan” agar tidak dikonversi), “Bahasa”, “Nama tempat”, “Satuan Suhu”, “Satuan Jarak”, “Format Waktu”, dan “Awal minggu”.',
|
||||
'Bahasa & wilayah: “Mata uang tampilan” (atau “Mata uang perjalanan” agar tidak dikonversi), “Bahasa”, “Nama tempat”, “Satuan Suhu”, “Satuan Jarak”, “Format Waktu”, “Awal minggu”, dan “Format Tanggal”.',
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
'“Perjalanan & peta”: “Buka tempat di”, “Tanggal di depan pada judul hari”, “Label rute pemesanan”, “Selalu tampilkan rute pemesanan”, “Penanda ringkas untuk tempat yang belum direncanakan”, “Jelajahi tempat di peta”, “Sembunyikan Kode Pemesanan”, dan “Optimalkan rute dari akomodasi”.',
|
||||
'help.ctx.settings-appearance.title': 'Tampilan',
|
||||
@@ -1479,9 +1479,9 @@ const help: TranslationStrings = {
|
||||
'“Pengaturan Izin” di bagian bawah: untuk setiap tindakan, siapa yang boleh melakukannya (“Semua orang”, “Anggota perjalanan”, “Pemilik perjalanan”, atau “Hanya Admin”). Klik “Simpan” di bagian atas kartu untuk menyimpannya.',
|
||||
'help.ctx.admin-defaults.title': 'Pengaturan Default Pengguna',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
'Pengaturan awal akun baru, agar tidak ada yang perlu mengaturnya dulu: mode warna, satuan, format waktu, awal minggu, mata uang tampilan, dan kode pemesanan yang disamarkan, ditambah peta, kuncinya, dan server rute.',
|
||||
'Pengaturan awal akun baru, agar tidak ada yang perlu mengaturnya dulu: mode warna, satuan, format waktu, format tanggal, awal minggu, mata uang tampilan, dan kode pemesanan yang disamarkan, ditambah peta, kuncinya, dan server rute.',
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
'“Pengaturan Default Pengguna” di kiri: “Mode Warna”, “Satuan Suhu”, “Satuan Jarak”, “Format Waktu”, “Awal minggu”, “Mata uang tampilan”, dan “Sembunyikan Kode Pemesanan”.',
|
||||
'“Pengaturan Default Pengguna” di kiri: “Mode Warna”, “Satuan Suhu”, “Satuan Jarak”, “Format Waktu”, “Awal minggu”, “Format Tanggal”, “Mata uang tampilan”, dan “Sembunyikan Kode Pemesanan”.',
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
'“Peta” di kanan: “Template Peta”, “Kunci CARTO bersama”, “Mesin rute sendiri” dan “Instans Valhalla sendiri”, pratinjau, lalu “Mesin peta” dengan “Gaya peta”, “Token Mapbox bersama”, “Bangunan & medan 3D”, dan “Mode kualitas tinggi”.',
|
||||
'help.ctx.admin-config.title': 'Personalisasi',
|
||||
@@ -4322,7 +4322,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-about.bullet.3':
|
||||
'Lewat Ko-fi, Buy Me a Coffee, dan Discord, kamu bisa mendukung TREK atau mengobrol dengan orang-orang yang membuatnya.',
|
||||
'help.guide.language-region.step.3':
|
||||
'Atur “Mata uang tampilan”, atau biarkan “Mata uang perjalanan” agar setiap perjalanan tampil dalam mata uangnya sendiri. Lalu pilih “Satuan Suhu”, “Satuan Jarak”, “Format Waktu”, dan hari “Awal minggu”.',
|
||||
'Atur “Mata uang tampilan”, atau biarkan “Mata uang perjalanan” agar setiap perjalanan tampil dalam mata uangnya sendiri. Lalu pilih “Satuan Suhu”, “Satuan Jarak”, “Format Waktu”, hari “Awal minggu”, dan “Format Tanggal”.',
|
||||
'help.guide.travel-map-prefs.step.3':
|
||||
'“Jelajahi tempat di peta” menampilkan pil kategori untuk mencari tempat di sekitar. “Sembunyikan Kode Pemesanan” menyamarkan nomor konfirmasi sampai kamu mengarahkan kursor ke atasnya atau mengetuknya. “Optimalkan rute dari akomodasi” membuat setiap hari dimulai dan diakhiri di hotelmu.',
|
||||
'help.guide.web-push-device.step.2':
|
||||
@@ -6396,7 +6396,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.admin-settings.bullet.5':
|
||||
'“Zona Berbahaya”: “Putar Secret JWT” mengeluarkan semua pengguna, termasuk kamu.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
'Di bawah “Pengaturan Default Pengguna”, pilih “Mode Warna” (“Terang”, “Gelap”, atau “Otomatis”), “Satuan Suhu”, “Satuan Jarak”, “Format Waktu”, “Awal minggu”, “Mata uang tampilan”, dan apakah “Sembunyikan Kode Pemesanan” “Aktif” atau “Mati”. Setiap pilihan langsung tersimpan.',
|
||||
'Di bawah “Pengaturan Default Pengguna”, pilih “Mode Warna” (“Terang”, “Gelap”, atau “Otomatis”), “Satuan Suhu”, “Satuan Jarak”, “Format Waktu”, “Awal minggu”, “Format Tanggal”, “Mata uang tampilan”, dan apakah “Sembunyikan Kode Pemesanan” “Aktif” atau “Mati”. Setiap pilihan langsung tersimpan.',
|
||||
'help.guide.admin-passkeys.step.2':
|
||||
'Isi Relying Party ID (domain), misalnya trek.example.org, dan “Origin yang diizinkan” tempat TREK-mu diakses, lalu klik “Simpan”. Biarkan keduanya kosong untuk memakai APP_URL.',
|
||||
'help.guide.rotate-jwt-secret.title': 'Mengeluarkan semua orang sekaligus',
|
||||
|
||||
@@ -86,6 +86,10 @@ const settings: TranslationStrings = {
|
||||
'settings.timeFormat': 'Format Waktu',
|
||||
'settings.weekStart': 'Awal minggu',
|
||||
'settings.weekStartHint': 'Hari pertama dalam seminggu di setiap pemilih tanggal. Vacay punya pengaturannya sendiri.',
|
||||
'settings.dateFormat': 'Format Tanggal',
|
||||
'settings.dateFormatHint':
|
||||
'Cara tanggal ditulis di setiap kolom tanggal dan daftar. Tanggal dengan nama bulan tetap mengikuti penulisan bahasamu.',
|
||||
'settings.dateFormatAuto': 'Otomatis',
|
||||
'settings.preferredNavApp': 'Buka tempat di',
|
||||
'settings.preferredNavAppAsk': 'Tanya setiap kali',
|
||||
'settings.preferredNavAppHint':
|
||||
|
||||
@@ -1165,7 +1165,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
"La barra laterale a sinistra: «Visualizzazione», «Aspetto», «Mappa», «Notifiche», «Integrazioni», Offline e Account. «Plugin» compare quando c'è un plugin attivo. «Informazioni» compare, a meno che il gestore non l'abbia nascosta. La versione è indicata in fondo.",
|
||||
'help.ctx.settings.bullet.2':
|
||||
"All'apertura c'è «Visualizzazione», con lingua, lingua dei nomi dei luoghi, unità di misura, formato dell'ora, inizio della settimana e valuta di visualizzazione, più il comportamento della mappa del viaggio. «Aspetto» contiene tema, colori, leggibilità, dimensione del testo e widget della dashboard.",
|
||||
"All'apertura c'è «Visualizzazione», con lingua, lingua dei nomi dei luoghi, unità di misura, formato dell'ora, formato della data, inizio della settimana e valuta di visualizzazione, più il comportamento della mappa del viaggio. «Aspetto» contiene tema, colori, leggibilità, dimensione del testo e widget della dashboard.",
|
||||
'help.ctx.settings.bullet.3':
|
||||
'«Mappa» imposta il motore della mappa e il suo stile; «Notifiche» stabilisce quali eventi arrivano su quale canale; «Integrazioni» collega le librerie di foto, AirTrail, Dawarich e wanderer, e contiene chiavi API e MCP; «Offline» stabilisce cosa salva questo dispositivo.',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1176,7 +1176,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'«Avvio»: «Pagina iniziale» apre la Dashboard o il «Viaggio attivo»; con «Viaggio attivo», «Scheda iniziale» sceglie la scheda con cui si apre quel viaggio.',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
'Lingua e regione: «Valuta di visualizzazione» (o «Valuta del viaggio» per non convertire), «Lingua», «Nomi dei luoghi», «Unità di Temperatura», «Unità di Distanza», «Formato Ora» e «La settimana inizia il».',
|
||||
'Lingua e regione: «Valuta di visualizzazione» (o «Valuta del viaggio» per non convertire), «Lingua», «Nomi dei luoghi», «Unità di Temperatura», «Unità di Distanza», «Formato Ora», «La settimana inizia il» e «Formato Data».',
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
"Viaggio e mappa: «Apri i luoghi in», «Data prima nei titoli dei giorni», «Etichette percorsi prenotati», «Mostra sempre i percorsi delle prenotazioni», «Segnaposti compatti per i luoghi non pianificati», «Esplora luoghi sulla mappa», «Nascondi codici di prenotazione» e «Ottimizza il percorso dall'alloggio».",
|
||||
'help.ctx.settings-appearance.title': 'Aspetto',
|
||||
@@ -1500,9 +1500,9 @@ const help: TranslationStrings = {
|
||||
'In fondo, «Impostazioni dei permessi»: per ogni azione, chi può eseguirla («Tutti», «Membri del viaggio», «Proprietario del viaggio» o «Solo amministratore»). Clicca su «Salva» in cima alla scheda per salvare.',
|
||||
'help.ctx.admin-defaults.title': 'Impostazioni predefinite',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
"Le impostazioni di partenza di un nuovo account, così nessuno deve configurarle da zero: modalità colore, unità di misura, formato dell'ora, inizio della settimana, valuta di visualizzazione e codici di prenotazione oscurati, più la mappa, le sue chiavi e i server per il calcolo dei percorsi.",
|
||||
"Le impostazioni di partenza di un nuovo account, così nessuno deve configurarle da zero: modalità colore, unità di misura, formato dell'ora, formato della data, inizio della settimana, valuta di visualizzazione e codici di prenotazione oscurati, più la mappa, le sue chiavi e i server per il calcolo dei percorsi.",
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
'A sinistra, «Impostazioni predefinite utente»: «Modalità Colore», «Unità di Temperatura», «Unità di Distanza», «Formato Ora», «La settimana inizia il», «Valuta di visualizzazione» e «Nascondi codici di prenotazione».',
|
||||
'A sinistra, «Impostazioni predefinite utente»: «Modalità Colore», «Unità di Temperatura», «Unità di Distanza», «Formato Ora», «La settimana inizia il», «Formato Data», «Valuta di visualizzazione» e «Nascondi codici di prenotazione».',
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
"A destra, la mappa: «Modello Mappa», «Chiave CARTO condivisa», «Motore di percorso proprio» e «Istanza Valhalla propria», un'anteprima, poi «Motore mappe» con «Stile mappa», «Token Mapbox condiviso», «Edifici & terreno in 3D» e «Modalità alta qualità».",
|
||||
'help.ctx.admin-config.title': 'Personalizzazione',
|
||||
@@ -4348,7 +4348,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-about.bullet.3':
|
||||
'Su Ko-fi, Buy Me a Coffee e Discord puoi sostenere TREK o parlare con le persone che lo sviluppano.',
|
||||
'help.guide.language-region.step.3':
|
||||
'Imposta la «Valuta di visualizzazione», oppure lascia «Valuta del viaggio» per vedere ogni viaggio nella sua valuta. Poi scegli «Unità di Temperatura», «Unità di Distanza», «Formato Ora» e il giorno in cui inizia la settimana («La settimana inizia il»).',
|
||||
'Imposta la «Valuta di visualizzazione», oppure lascia «Valuta del viaggio» per vedere ogni viaggio nella sua valuta. Poi scegli «Unità di Temperatura», «Unità di Distanza», «Formato Ora», il giorno in cui inizia la settimana («La settimana inizia il») e il «Formato Data».',
|
||||
'help.guide.travel-map-prefs.step.3':
|
||||
"«Esplora luoghi sulla mappa» mostra la pillola delle categorie per trovare luoghi nelle vicinanze. «Nascondi codici di prenotazione» oscura i numeri di conferma finché non ci passi sopra con il mouse o li tocchi. «Ottimizza il percorso dall'alloggio» fa iniziare e finire la giornata al tuo hotel.",
|
||||
'help.guide.web-push-device.step.2':
|
||||
@@ -6451,7 +6451,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.admin-settings.bullet.5':
|
||||
'«Zona pericolosa»: «Ruota il secret JWT» disconnette tutti gli utenti, te compreso.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
'Sotto «Impostazioni predefinite utente», scegli «Modalità Colore» («Chiara», «Scuro» o «Automatica»), «Unità di Temperatura», «Unità di Distanza», «Formato Ora», «La settimana inizia il», la «Valuta di visualizzazione» e se «Nascondi codici di prenotazione» è attivo o «Disattivato». Ogni scelta si salva subito.',
|
||||
'Sotto «Impostazioni predefinite utente», scegli «Modalità Colore» («Chiara», «Scuro» o «Automatica»), «Unità di Temperatura», «Unità di Distanza», «Formato Ora», «La settimana inizia il», il «Formato Data», la «Valuta di visualizzazione» e se «Nascondi codici di prenotazione» è attivo o «Disattivato». Ogni scelta si salva subito.',
|
||||
'help.guide.admin-passkeys.step.2':
|
||||
'Inserisci il «Relying Party ID (dominio)», ad esempio trek.example.org, e le «Origini consentite» da cui si raggiunge il tuo TREK, poi clicca su «Salva». Lasciali entrambi vuoti per usare APP_URL.',
|
||||
'help.guide.rotate-jwt-secret.title': 'Disconnettere tutti in una volta',
|
||||
|
||||
@@ -88,6 +88,10 @@ const settings: TranslationStrings = {
|
||||
'settings.timeFormat': 'Formato Ora',
|
||||
'settings.weekStart': 'La settimana inizia il',
|
||||
'settings.weekStartHint': 'Primo giorno della settimana in ogni selettore di date. Vacay ha una sua impostazione.',
|
||||
'settings.dateFormat': 'Formato Data',
|
||||
'settings.dateFormatHint':
|
||||
'Come vengono scritte le date in ogni campo data e in ogni elenco. Le date con il nome del mese mantengono la forma della tua lingua.',
|
||||
'settings.dateFormatAuto': 'Automatico',
|
||||
'settings.preferredNavApp': 'Apri i luoghi in',
|
||||
'settings.preferredNavAppAsk': 'Chiedi ogni volta',
|
||||
'settings.preferredNavAppHint':
|
||||
|
||||
@@ -1083,7 +1083,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
'左のサイドバー:「表示」「外観」「マップ」「通知」「連携」「オフライン」「アカウント」があります。「プラグイン」はプラグインが有効なときに表示されます。「情報」は運営者が非表示にしていなければ表示されます。下部にバージョンが表示されます。',
|
||||
'help.ctx.settings.bullet.2':
|
||||
'最初に開くのは「表示」です。言語、場所の名前の言語、単位、時刻形式、週の開始、表示通貨と、旅行の地図の動作を設定します。「外観」では、テーマ、色、読みやすさ、文字サイズ、ダッシュボードのウィジェットを設定します。',
|
||||
'最初に開くのは「表示」です。言語、場所の名前の言語、単位、時刻形式、日付形式、週の開始、表示通貨と、旅行の地図の動作を設定します。「外観」では、テーマ、色、読みやすさ、文字サイズ、ダッシュボードのウィジェットを設定します。',
|
||||
'help.ctx.settings.bullet.3':
|
||||
'「マップ」では地図エンジンとスタイルを選びます。「通知」では、どのイベントをどのチャンネルに送るかを設定します。「連携」では写真ライブラリ、AirTrail、Dawarich、wandererを接続でき、APIキーとMCPもここにあります。「オフライン」では、この端末に保存する内容を設定します。',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1094,7 +1094,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'「起動」:「起動時の画面」で「ダッシュボード」か「進行中の旅行」を選びます。「進行中の旅行」の場合は、「起動時のタブ」でその旅行を開いたときのタブを選びます。',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
'言語と地域:「表示通貨」(換算しない場合は「旅行の通貨」)、「言語」「場所の名前」「温度単位」「距離単位」「時刻形式」「週の開始」があります。',
|
||||
'言語と地域:「表示通貨」(換算しない場合は「旅行の通貨」)、「言語」「場所の名前」「温度単位」「距離単位」「時刻形式」「週の開始」「日付形式」があります。',
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
'「旅行と地図」:「場所を開くアプリ」「日の見出しで日付を先に表示」「予約ルートのラベル」「予約ルートを常に表示」「未計画の場所を小さなマーカーで表示」「地図でスポットを探す」「予約コードをぼかす」「宿泊先を起点にルートを最適化」があります。',
|
||||
'help.ctx.settings-appearance.title': '外観',
|
||||
@@ -1407,9 +1407,9 @@ const help: TranslationStrings = {
|
||||
'下部の「権限設定」:操作ごとに、実行できる人(「全員」「旅行メンバー」「旅行オーナー」「管理者のみ」)を設定します。カード上部の「保存」で保存します。',
|
||||
'help.ctx.admin-defaults.title': 'ユーザーのデフォルト',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
'新しいアカウントの初期設定です。これで、ユーザーが最初に設定する手間が省けます。カラーモード、単位、時刻形式、週の開始、表示通貨、予約コードのぼかし、さらに地図、そのキー、ルーティングサーバーを設定できます。',
|
||||
'新しいアカウントの初期設定です。これで、ユーザーが最初に設定する手間が省けます。カラーモード、単位、時刻形式、日付形式、週の開始、表示通貨、予約コードのぼかし、さらに地図、そのキー、ルーティングサーバーを設定できます。',
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
'左側の「既定のユーザー設定」:「カラーモード」「温度単位」「距離単位」「時刻形式」「週の開始」「表示通貨」「予約コードをぼかす」があります。',
|
||||
'左側の「既定のユーザー設定」:「カラーモード」「温度単位」「距離単位」「時刻形式」「週の開始」「日付形式」「表示通貨」「予約コードをぼかす」があります。',
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
'右側の「マップ」:「地図テンプレート」「共有 CARTO キー」「自前のルーティングサーバー」「自前の Valhalla インスタンス」、プレビュー、続いて「地図エンジン」と「地図スタイル」「共有 Mapbox トークン」「3D の建物と地形」「高品質モード」があります。',
|
||||
'help.ctx.admin-config.title': 'カスタマイズ',
|
||||
@@ -4167,7 +4167,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-about.bullet.3':
|
||||
'Ko-fi、Buy Me a Coffee、Discordから、TREKを支援したり、開発者と話したりできます。',
|
||||
'help.guide.language-region.step.3':
|
||||
'「表示通貨」を設定します。旅行ごとにその旅行の通貨で見たい場合は「旅行の通貨」のままにします。続けて「温度単位」「距離単位」「時刻形式」と、「週の開始」の曜日を選びます。',
|
||||
'「表示通貨」を設定します。旅行ごとにその旅行の通貨で見たい場合は「旅行の通貨」のままにします。続けて「温度単位」「距離単位」「時刻形式」、「週の開始」の曜日、「日付形式」を選びます。',
|
||||
'help.guide.travel-map-prefs.step.3':
|
||||
'「地図でスポットを探す」をオンにすると、近くの場所を探すカテゴリのピルが表示されます。「予約コードをぼかす」は、カーソルを合わせるかタップするまで予約番号を隠します。「宿泊先を起点にルートを最適化」は、1日の始まりと終わりをホテルにします。',
|
||||
'help.guide.web-push-device.step.2': '上部の「通知」カードの「プッシュ」列で、端末に送るイベントを選びます。',
|
||||
@@ -6154,7 +6154,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.admin-settings.bullet.5':
|
||||
'「危険ゾーン」:「JWTシークレットをローテーション」を使うと、自分を含むすべてのユーザーがサインアウトされます。',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
'「既定のユーザー設定」で、「カラーモード」(「ライト」「ダーク」「自動」)、「温度単位」「距離単位」「時刻形式」「週の開始」「表示通貨」と、「予約コードをぼかす」のオン・オフを選びます。選択はそれぞれすぐに保存されます。',
|
||||
'「既定のユーザー設定」で、「カラーモード」(「ライト」「ダーク」「自動」)、「温度単位」「距離単位」「時刻形式」「週の開始」「日付形式」「表示通貨」と、「予約コードをぼかす」のオン・オフを選びます。選択はそれぞれすぐに保存されます。',
|
||||
'help.guide.admin-passkeys.step.2':
|
||||
'「Relying Party ID(ドメイン)」(例:trek.example.org)と、TREKにアクセスするときの「許可するオリジン」を入力し、「保存」をクリックします。両方を空欄にすると、APP_URLが使われます。',
|
||||
'help.guide.rotate-jwt-secret.title': '全員をまとめてサインアウトさせる',
|
||||
|
||||
@@ -85,6 +85,9 @@ const settings: TranslationStrings = {
|
||||
'settings.timeFormat': '時刻形式',
|
||||
'settings.weekStart': '週の開始',
|
||||
'settings.weekStartHint': 'すべての日付選択で週の最初の曜日になります。Vacay には専用の設定があります。',
|
||||
'settings.dateFormat': '日付形式',
|
||||
'settings.dateFormatHint': 'すべての日付欄と一覧での日付の書き方です。月名を含む日付は言語の表記のままです。',
|
||||
'settings.dateFormatAuto': '自動',
|
||||
'settings.preferredNavApp': '場所を開くアプリ',
|
||||
'settings.preferredNavAppAsk': '毎回確認する',
|
||||
'settings.preferredNavAppHint':
|
||||
|
||||
@@ -1036,7 +1036,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
'왼쪽 사이드바: ‘화면’, ‘모양’, ‘지도’, ‘알림’, ‘통합’, ‘오프라인’, ‘계정’. 플러그인이 활성화되면 ‘플러그인’이 생기고, 운영자가 숨기지 않았다면 ‘정보’도 있어요. 맨 아래에 버전이 보여요.',
|
||||
'help.ctx.settings.bullet.2':
|
||||
'처음에는 ‘화면’이 열려요. 언어, 장소 이름 언어, 단위, 시간 형식, 주 시작 요일, 표시 통화, 그리고 여행 지도 동작이 있어요. ‘모양’에는 테마, 색상, 가독성, 글자 크기, 대시보드 위젯이 있어요.',
|
||||
'처음에는 ‘화면’이 열려요. 언어, 장소 이름 언어, 단위, 시간 형식, 날짜 형식, 주 시작 요일, 표시 통화, 그리고 여행 지도 동작이 있어요. ‘모양’에는 테마, 색상, 가독성, 글자 크기, 대시보드 위젯이 있어요.',
|
||||
'help.ctx.settings.bullet.3':
|
||||
'‘지도’에서는 지도 엔진과 스타일을 정하고, ‘알림’에서는 어떤 이벤트를 어느 채널로 받을지 정해요. ‘통합’에서는 사진 라이브러리, AirTrail, Dawarich, wanderer를 연결하고 API 키와 MCP도 관리해요. ‘오프라인’에서는 이 기기에 무엇을 저장할지 정해요.',
|
||||
'help.ctx.settings.bullet.4': '“계정”에서는 프로필, 비밀번호, 2단계 인증, 패스키를 관리하고 계정을 삭제할 수 있어요.',
|
||||
@@ -1046,7 +1046,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'‘시작’: ‘시작 화면’을 ‘대시보드’나 ‘진행 중인 여행’으로 정해요. ‘진행 중인 여행’을 고르면 ‘시작 탭’에서 그 여행이 열릴 탭을 골라요.',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
'언어 및 지역: ‘표시 통화’(환산하지 않으려면 ‘여행 통화’), ‘언어’, ‘장소 이름’, ‘온도 단위’, ‘거리 단위’, ‘시간 형식’, ‘주 시작 요일’.',
|
||||
'언어 및 지역: ‘표시 통화’(환산하지 않으려면 ‘여행 통화’), ‘언어’, ‘장소 이름’, ‘온도 단위’, ‘거리 단위’, ‘시간 형식’, ‘주 시작 요일’, ‘날짜 형식’.',
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
'여행 및 지도: ‘장소 열기 앱’, ‘날 제목에 날짜를 먼저 표시’, ‘예약 경로 레이블’, ‘예약 경로 항상 표시’, ‘계획되지 않은 장소를 작은 마커로 표시’, ‘지도에서 장소 탐색’, ‘예약 코드 흐리게’, ‘숙소 기준으로 경로 최적화’.',
|
||||
'help.ctx.settings-appearance.title': '모양',
|
||||
@@ -1341,9 +1341,9 @@ const help: TranslationStrings = {
|
||||
'맨 아래 ‘권한 설정’: 작업마다 누가 할 수 있는지(‘모든 사람’, ‘여행 멤버’, ‘여행 소유자’, ‘관리자만’) 정해요. 카드 위쪽 ‘저장’을 클릭해 저장하세요.',
|
||||
'help.ctx.admin-defaults.title': '기본값',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
'새 계정이 처음 갖는 값이라 아무도 따로 설정할 필요가 없어요: 색상 모드, 단위, 시간 형식, 주 시작 요일, 표시 통화, 예약 코드 흐리게, 그리고 지도와 지도 키, 경로 서버.',
|
||||
'새 계정이 처음 갖는 값이라 아무도 따로 설정할 필요가 없어요: 색상 모드, 단위, 시간 형식, 날짜 형식, 주 시작 요일, 표시 통화, 예약 코드 흐리게, 그리고 지도와 지도 키, 경로 서버.',
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
'왼쪽 ‘기본 사용자 설정’: ‘색상 모드’, ‘온도 단위’, ‘거리 단위’, ‘시간 형식’, ‘주 시작 요일’, ‘표시 통화’, ‘예약 코드 흐리게’.',
|
||||
'왼쪽 ‘기본 사용자 설정’: ‘색상 모드’, ‘온도 단위’, ‘거리 단위’, ‘시간 형식’, ‘주 시작 요일’, ‘날짜 형식’, ‘표시 통화’, ‘예약 코드 흐리게’.',
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
'오른쪽 ‘지도’: ‘지도 템플릿’, ‘공유 CARTO 키’, ‘자체 경로 서버’와 ‘자체 Valhalla 인스턴스’, 미리보기, 그리고 ‘지도 엔진’과 ‘지도 스타일’, ‘공유 Mapbox 토큰’, ‘3D 건물 & 지형’, ‘고품질 모드’.',
|
||||
'help.ctx.admin-config.title': '개인 설정',
|
||||
@@ -3972,7 +3972,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-about.bullet.3':
|
||||
'Ko-fi, Buy Me a Coffee, Discord에서 TREK를 후원하거나 만드는 사람들과 이야기할 수 있어요.',
|
||||
'help.guide.language-region.step.3':
|
||||
'‘표시 통화’를 정하거나, 여행마다 그 여행의 통화로 보려면 ‘여행 통화’를 그대로 두세요. 그다음 ‘온도 단위’, ‘거리 단위’, ‘시간 형식’, ‘주 시작 요일’을 고르세요.',
|
||||
'‘표시 통화’를 정하거나, 여행마다 그 여행의 통화로 보려면 ‘여행 통화’를 그대로 두세요. 그다음 ‘온도 단위’, ‘거리 단위’, ‘시간 형식’, ‘주 시작 요일’, ‘날짜 형식’을 고르세요.',
|
||||
'help.guide.travel-map-prefs.step.3':
|
||||
'‘지도에서 장소 탐색’을 켜면 주변 장소를 찾는 카테고리 버튼이 보여요. ‘예약 코드 흐리게’는 마우스를 올리거나 탭하기 전까지 예약 번호를 가려요. ‘숙소 기준으로 경로 최적화’는 하루를 호텔에서 시작하고 호텔에서 끝내요.',
|
||||
'help.guide.web-push-device.step.2': '맨 위 ‘알림’ 카드의 ‘푸시’ 열에서 어떤 이벤트를 내 기기로 보낼지 정해요.',
|
||||
@@ -5848,7 +5848,7 @@ const help: TranslationStrings = {
|
||||
'설정한 칸 옆의 초기화 링크로 TREK 기본값으로 되돌려요. 사용자가 직접 정한 설정이 항상 우선이에요.',
|
||||
'help.ctx.admin-settings.bullet.5': '‘위험 구역’: ‘JWT 시크릿 교체’는 나를 포함한 모든 사용자를 로그아웃시켜요.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
'‘기본 사용자 설정’에서 ‘색상 모드’(‘라이트’, ‘다크’, ‘자동’), ‘온도 단위’, ‘거리 단위’, ‘시간 형식’, ‘주 시작 요일’, ‘표시 통화’를 고르고, ‘예약 코드 흐리게’를 켤지 끌지 정하세요. 고르는 즉시 저장돼요.',
|
||||
'‘기본 사용자 설정’에서 ‘색상 모드’(‘라이트’, ‘다크’, ‘자동’), ‘온도 단위’, ‘거리 단위’, ‘시간 형식’, ‘주 시작 요일’, ‘날짜 형식’, ‘표시 통화’를 고르고, ‘예약 코드 흐리게’를 켤지 끌지 정하세요. 고르는 즉시 저장돼요.',
|
||||
'help.guide.admin-passkeys.step.2':
|
||||
'trek.example.org 같은 ‘Relying Party ID (도메인)’와 TREK에 접속하는 주소인 ‘허용된 오리진’을 입력하고 ‘저장’을 클릭하세요. 둘 다 비워 두면 APP_URL을 써요.',
|
||||
'help.guide.rotate-jwt-secret.title': '모두 한 번에 로그아웃시키기',
|
||||
|
||||
@@ -86,6 +86,10 @@ const settings: TranslationStrings = {
|
||||
'settings.timeFormat': '시간 형식',
|
||||
'settings.weekStart': '주 시작 요일',
|
||||
'settings.weekStartHint': '모든 날짜 선택기에서 한 주의 첫 요일입니다. Vacay에는 별도의 설정이 있습니다.',
|
||||
'settings.dateFormat': '날짜 형식',
|
||||
'settings.dateFormatHint':
|
||||
'모든 날짜 입력란과 목록에서 날짜를 쓰는 방식입니다. 월 이름이 들어간 날짜는 언어 표기를 그대로 따릅니다.',
|
||||
'settings.dateFormatAuto': '자동',
|
||||
'settings.preferredNavApp': '장소 열기 앱',
|
||||
'settings.preferredNavAppAsk': '매번 묻기',
|
||||
'settings.preferredNavAppHint': '앱을 선택하면 길찾기 버튼이 모든 지도 앱을 보여주는 대신 바로 그 앱을 엽니다.',
|
||||
|
||||
@@ -1161,7 +1161,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
'Zijbalk links: Weergave, Weergave, Kaart, Meldingen, Integraties, Offline en Account. Plugins verschijnt zodra er een plugin actief is. Over verschijnt, tenzij de beheerder van de server het heeft verborgen. Onderaan staat de versie.',
|
||||
'help.ctx.settings.bullet.2':
|
||||
'Weergave opent als eerste. Daar vind je de taal, de taal van plaatsnamen, eenheden, tijdnotatie, de eerste dag van de week en de weergavevaluta, en hoe de reiskaart werkt. Onder Weergave staan thema, kleuren, leesbaarheid, tekstgrootte en de dashboardwidgets.',
|
||||
'Weergave opent als eerste. Daar vind je de taal, de taal van plaatsnamen, eenheden, tijdnotatie, datumnotatie, de eerste dag van de week en de weergavevaluta, en hoe de reiskaart werkt. Onder Weergave staan thema, kleuren, leesbaarheid, tekstgrootte en de dashboardwidgets.',
|
||||
'help.ctx.settings.bullet.3':
|
||||
'‘Kaart’ stelt de kaartengine en de stijl in; ‘Meldingen’ bepaalt welke gebeurtenissen naar welk kanaal gaan; ‘Integraties’ koppelt fotobibliotheken, AirTrail, Dawarich en wanderer en bevat API-sleutels en MCP; ‘Offline’ bepaalt wat dit apparaat opslaat.',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1172,7 +1172,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'Opstarten: ‘Startpagina’ opent het Dashboard of de ‘Actieve reis’; kies je ‘Actieve reis’, dan bepaal je met ‘Starttabblad’ op welk tabblad die reis opent.',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
'Taal en regio: ‘Weergavevaluta’ (of ‘Valuta van de reis’ om niet om te rekenen), ‘Taal’, ‘Plaatsnamen’, ‘Temperatuureenheid’, ‘Afstandseenheid’, ‘Tijdnotatie’ en ‘Week begint op’.',
|
||||
'Taal en regio: ‘Weergavevaluta’ (of ‘Valuta van de reis’ om niet om te rekenen), ‘Taal’, ‘Plaatsnamen’, ‘Temperatuureenheid’, ‘Afstandseenheid’, ‘Tijdnotatie’, ‘Week begint op’ en ‘Datumnotatie’.',
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
'Reis en kaart: ‘Plekken openen in’, ‘Datum eerst in dagkoppen’, ‘Routelabels voor boekingen’, ‘Boekingsroutes altijd tonen’, ‘Compacte markeringen voor ongeplande plaatsen’, ‘Plaatsen op de kaart ontdekken’, ‘Boekingscodes vervagen’ en ‘Route optimaliseren vanaf accommodatie’.',
|
||||
'help.ctx.settings-appearance.title': 'Weergave',
|
||||
@@ -1494,9 +1494,9 @@ const help: TranslationStrings = {
|
||||
'‘Rechtinstellingen’ onderaan: per actie wie die mag uitvoeren (Iedereen, Reisleden, Reiseigenaar of ‘Alleen beheerder’). Klik bovenaan de kaart op ‘Opslaan’ om dit op te slaan.',
|
||||
'help.ctx.admin-defaults.title': 'Standaardinstellingen',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
'Waarmee een nieuw account begint, zodat niemand eerst alles hoeft in te stellen: kleurmodus, eenheden, tijdnotatie, eerste dag van de week, weergavevaluta en vervaagde boekingscodes, plus de kaart, de sleutels ervan en de routeservers.',
|
||||
'Waarmee een nieuw account begint, zodat niemand eerst alles hoeft in te stellen: kleurmodus, eenheden, tijdnotatie, datumnotatie, eerste dag van de week, weergavevaluta en vervaagde boekingscodes, plus de kaart, de sleutels ervan en de routeservers.',
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
'Links de Standaard gebruikersinstellingen: Kleurmodus, Temperatuureenheid, Afstandseenheid, Tijdnotatie, ‘Week begint op’, Weergavevaluta en ‘Boekingscodes vervagen’.',
|
||||
'Links de Standaard gebruikersinstellingen: Kleurmodus, Temperatuureenheid, Afstandseenheid, Tijdnotatie, ‘Week begint op’, Datumnotatie, Weergavevaluta en ‘Boekingscodes vervagen’.',
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
'Rechts de kaart: Kaartsjabloon, Gedeelde CARTO-sleutel, Eigen routeserver en Eigen Valhalla-instantie, een voorbeeld, en daarna Kaartmotor met Kaartstijl, ‘Gedeeld Mapbox-token’, ‘3D-gebouwen & terrein’ en Hogekwaliteitsmodus.',
|
||||
'help.ctx.admin-config.title': 'Personalisatie',
|
||||
@@ -4346,7 +4346,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-about.bullet.3':
|
||||
'Via Ko-fi, Buy Me a Coffee en Discord kun je TREK steunen of met de makers praten.',
|
||||
'help.guide.language-region.step.3':
|
||||
'Stel de ‘Weergavevaluta’ in, of kies ‘Valuta van de reis’ om elke reis in zijn eigen valuta te zien. Kies daarna ‘Temperatuureenheid’, ‘Afstandseenheid’, ‘Tijdnotatie’ en bij ‘Week begint op’ de eerste dag van de week.',
|
||||
'Stel de ‘Weergavevaluta’ in, of kies ‘Valuta van de reis’ om elke reis in zijn eigen valuta te zien. Kies daarna ‘Temperatuureenheid’, ‘Afstandseenheid’, ‘Tijdnotatie’, bij ‘Week begint op’ de eerste dag van de week en de ‘Datumnotatie’.',
|
||||
'help.guide.travel-map-prefs.step.3':
|
||||
'‘Plaatsen op de kaart ontdekken’ toont de categoriepil om plaatsen in de buurt te vinden. ‘Boekingscodes vervagen’ verbergt bevestigingsnummers tot je er met de muis overheen gaat of erop tikt. ‘Route optimaliseren vanaf accommodatie’ laat een dag bij je hotel beginnen en eindigen.',
|
||||
'help.guide.web-push-device.step.2':
|
||||
@@ -6433,7 +6433,7 @@ const help: TranslationStrings = {
|
||||
'Met de resetlink naast een veld dat je hebt ingesteld, zet je de standaardwaarde van TREK terug. De eigen instelling van een gebruiker gaat hier altijd boven.',
|
||||
'help.ctx.admin-settings.bullet.5': 'Gevarenzone: met ‘JWT-geheim roteren’ log je alle gebruikers uit, jezelf ook.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
'Kies onder Standaard gebruikersinstellingen de Kleurmodus (Licht, Donker of Automatisch), Temperatuureenheid, Afstandseenheid, Tijdnotatie, ‘Week begint op’, de Weergavevaluta en of ‘Boekingscodes vervagen’ aan of uit staat. Elke keuze wordt meteen opgeslagen.',
|
||||
'Kies onder Standaard gebruikersinstellingen de Kleurmodus (Licht, Donker of Automatisch), Temperatuureenheid, Afstandseenheid, Tijdnotatie, ‘Week begint op’, de Datumnotatie, de Weergavevaluta en of ‘Boekingscodes vervagen’ aan of uit staat. Elke keuze wordt meteen opgeslagen.',
|
||||
'help.guide.admin-passkeys.step.2':
|
||||
'Vul de ‘Relying Party ID (domein)’ in, bijvoorbeeld trek.example.org, en de ‘Toegestane origins’ waarop je TREK bereikbaar is, en klik op ‘Opslaan’. Laat beide leeg om APP_URL te gebruiken.',
|
||||
'help.guide.rotate-jwt-secret.title': 'Iedereen tegelijk uitloggen',
|
||||
|
||||
@@ -87,6 +87,10 @@ const settings: TranslationStrings = {
|
||||
'settings.timeFormat': 'Tijdnotatie',
|
||||
'settings.weekStart': 'Week begint op',
|
||||
'settings.weekStartHint': 'Eerste dag van de week in elke datumkiezer. Vacay heeft een eigen instelling.',
|
||||
'settings.dateFormat': 'Datumnotatie',
|
||||
'settings.dateFormatHint':
|
||||
'Hoe datums in elk datumveld en elke lijst worden geschreven. Datums met een maandnaam houden de schrijfwijze van je taal.',
|
||||
'settings.dateFormatAuto': 'Automatisch',
|
||||
'settings.preferredNavApp': 'Plekken openen in',
|
||||
'settings.preferredNavAppAsk': 'Elke keer vragen',
|
||||
'settings.preferredNavAppHint':
|
||||
|
||||
@@ -1145,7 +1145,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
'Pasek boczny po lewej: Preferencje, Wygląd, Mapa, Powiadomienia, Integracje, Offline i Konto. Wtyczki pojawiają się, gdy aktywna jest jakaś wtyczka. O aplikacji widać, chyba że operator to ukrył. Na dole jest numer wersji.',
|
||||
'help.ctx.settings.bullet.2':
|
||||
'Najpierw otwierają się Preferencje. Znajdziesz tam język, język nazw miejsc, jednostki, format czasu, początek tygodnia i walutę wyświetlania, a także zachowanie mapy podróży. Wygląd obejmuje motyw, kolory, czytelność, wielkość tekstu i widżety panelu.',
|
||||
'Najpierw otwierają się Preferencje. Znajdziesz tam język, język nazw miejsc, jednostki, format czasu, format daty, początek tygodnia i walutę wyświetlania, a także zachowanie mapy podróży. Wygląd obejmuje motyw, kolory, czytelność, wielkość tekstu i widżety panelu.',
|
||||
'help.ctx.settings.bullet.3':
|
||||
'„Mapa” ustawia silnik mapy i jej styl; „Powiadomienia” określają, które zdarzenia trafiają na który kanał; „Integracje” łączą biblioteki zdjęć, AirTrail, Dawarich i wanderer, a także zawierają klucze API i MCP; „Offline” określa, co przechowuje to urządzenie.',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1156,7 +1156,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'„Uruchamianie”: „Strona startowa” otwiera „Panel” albo „Aktywną podróż”; przy „Aktywnej podróży” opcja „Karta startowa” wybiera kartę, na której otwiera się ta podróż.',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
'Język i region: „Waluta wyświetlania” (lub „Waluta wyjazdu”, jeśli nie chcesz przeliczać), „Język”, „Nazwy miejsc”, „Jednostka temperatury”, „Jednostka odległości”, „Format czasu” i „Tydzień zaczyna się w”.',
|
||||
'Język i region: „Waluta wyświetlania” (lub „Waluta wyjazdu”, jeśli nie chcesz przeliczać), „Język”, „Nazwy miejsc”, „Jednostka temperatury”, „Jednostka odległości”, „Format czasu”, „Tydzień zaczyna się w” i „Format daty”.',
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
'Podróże i mapa: „Otwieraj miejsca w”, „Data na początku nagłówków dni”, „Etykiety tras rezerwacji”, „Zawsze pokazuj trasy rezerwacji”, „Kompaktowe znaczniki dla niezaplanowanych miejsc”, „Odkrywaj miejsca na mapie”, „Rozmyj kody rezerwacji” i „Optymalizuj trasę od zakwaterowania”.',
|
||||
'help.ctx.settings-appearance.title': 'Wygląd',
|
||||
@@ -1475,9 +1475,9 @@ const help: TranslationStrings = {
|
||||
'Na dole „Ustawienia uprawnień”: dla każdej akcji określasz, kto może ją wykonywać („Wszyscy”, „Członkowie podróży”, „Właściciel podróży” lub „Tylko admin”). Zmiany zapisujesz przyciskiem „Zapisz” u góry karty.',
|
||||
'help.ctx.admin-defaults.title': 'Domyślne ustawienia',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
'Ustawienia, z którymi startuje nowe konto, żeby nikt nie musiał ich najpierw konfigurować: motyw, jednostki, format czasu, pierwszy dzień tygodnia, waluta wyświetlania i rozmywanie kodów rezerwacji, a także mapa, jej klucze i serwery wyznaczania tras.',
|
||||
'Ustawienia, z którymi startuje nowe konto, żeby nikt nie musiał ich najpierw konfigurować: motyw, jednostki, format czasu, format daty, pierwszy dzień tygodnia, waluta wyświetlania i rozmywanie kodów rezerwacji, a także mapa, jej klucze i serwery wyznaczania tras.',
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
'„Domyślne ustawienia użytkownika” po lewej: „Motyw”, „Jednostka temperatury”, „Jednostka odległości”, „Format czasu”, „Tydzień zaczyna się w”, „Waluta wyświetlania” i „Rozmyj kody rezerwacji”.',
|
||||
'„Domyślne ustawienia użytkownika” po lewej: „Motyw”, „Jednostka temperatury”, „Jednostka odległości”, „Format czasu”, „Tydzień zaczyna się w”, „Format daty”, „Waluta wyświetlania” i „Rozmyj kody rezerwacji”.',
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
'Mapa po prawej: „Szablon mapy”, „Współdzielony klucz CARTO”, „Własny silnik tras” i „Własna instancja Valhalli”, podgląd, a dalej „Silnik map” ze „Styl mapy”, „Współdzielony token Mapbox”, „Budynki i teren 3D” oraz „Tryb wysokiej jakości”.',
|
||||
'help.ctx.admin-config.title': 'Personalizacja',
|
||||
@@ -4288,7 +4288,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-about.bullet.3':
|
||||
'Przez Ko-fi, Buy Me a Coffee i Discord możesz wesprzeć TREK albo porozmawiać z jego twórcami.',
|
||||
'help.guide.language-region.step.3':
|
||||
'Ustaw „Waluta wyświetlania” albo zostaw „Waluta wyjazdu”, żeby każda podróż była pokazywana we własnej walucie. Potem wybierz „Jednostka temperatury”, „Jednostka odległości”, „Format czasu” i dzień, od którego zaczyna się tydzień („Tydzień zaczyna się w”).',
|
||||
'Ustaw „Waluta wyświetlania” albo zostaw „Waluta wyjazdu”, żeby każda podróż była pokazywana we własnej walucie. Potem wybierz „Jednostka temperatury”, „Jednostka odległości”, „Format czasu”, dzień, od którego zaczyna się tydzień („Tydzień zaczyna się w”), oraz „Format daty”.',
|
||||
'help.guide.travel-map-prefs.step.3':
|
||||
'„Odkrywaj miejsca na mapie” pokazuje pigułkę kategorii do wyszukiwania miejsc w pobliżu. „Rozmyj kody rezerwacji” ukrywa numery potwierdzeń, dopóki ich nie najedziesz lub nie dotkniesz. „Optymalizuj trasę od zakwaterowania” zaczyna i kończy dzień w twoim hotelu.',
|
||||
'help.guide.web-push-device.step.2':
|
||||
@@ -6361,7 +6361,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.admin-settings.bullet.5':
|
||||
'Strefa zagrożenia: Zmień sekret JWT wylogowuje wszystkich użytkowników, Ciebie też.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
'W sekcji Domyślne ustawienia użytkownika wybierz Motyw (Jasny, Ciemna lub Automatyczny), Jednostka temperatury, Jednostka odległości, Format czasu, Tydzień zaczyna się w, Waluta wyświetlania oraz to, czy Rozmyj kody rezerwacji jest włączone, czy wyłączone. Każdy wybór zapisuje się od razu.',
|
||||
'W sekcji Domyślne ustawienia użytkownika wybierz Motyw (Jasny, Ciemna lub Automatyczny), Jednostka temperatury, Jednostka odległości, Format czasu, Tydzień zaczyna się w, Format daty, Waluta wyświetlania oraz to, czy Rozmyj kody rezerwacji jest włączone, czy wyłączone. Każdy wybór zapisuje się od razu.',
|
||||
'help.guide.admin-passkeys.step.2':
|
||||
'Wpisz Relying Party ID (domena), na przykład trek.example.org, oraz Dozwolone origins, pod którymi TREK jest dostępny, a potem kliknij Zapisz. Zostaw oba pola puste, aby użyć APP_URL.',
|
||||
'help.guide.rotate-jwt-secret.title': 'Wyloguj wszystkich naraz',
|
||||
|
||||
@@ -86,6 +86,10 @@ const settings: TranslationStrings = {
|
||||
'settings.timeFormat': 'Format czasu',
|
||||
'settings.weekStart': 'Tydzień zaczyna się w',
|
||||
'settings.weekStartHint': 'Pierwszy dzień tygodnia we wszystkich wyborach daty. Vacay ma własne ustawienie.',
|
||||
'settings.dateFormat': 'Format daty',
|
||||
'settings.dateFormatHint':
|
||||
'Sposób zapisu dat we wszystkich polach daty i na listach. Daty z nazwą miesiąca zachowują brzmienie Twojego języka.',
|
||||
'settings.dateFormatAuto': 'Automatycznie',
|
||||
'settings.preferredNavApp': 'Otwieraj miejsca w',
|
||||
'settings.preferredNavAppAsk': 'Pytaj za każdym razem',
|
||||
'settings.preferredNavAppHint':
|
||||
|
||||
@@ -1148,7 +1148,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings.bullet.1':
|
||||
'Боковая панель слева: «Основные», «Вид», «Карта», «Уведомления», «Интеграции», «Офлайн-режим» и «Аккаунт». «Плагины» появляются, когда активен плагин, а «О приложении», если только оператор не скрыл его. Версия указана внизу.',
|
||||
'help.ctx.settings.bullet.2':
|
||||
'Первыми открываются «Основные»: язык, язык названий мест, единицы измерения, формат времени, начало недели и валюта отображения, а также поведение карты поездки. В «Вид» собраны тема, цвета, читаемость, размер текста и элементы главной страницы.',
|
||||
'Первыми открываются «Основные»: язык, язык названий мест, единицы измерения, формат времени, формат даты, начало недели и валюта отображения, а также поведение карты поездки. В «Вид» собраны тема, цвета, читаемость, размер текста и элементы главной страницы.',
|
||||
'help.ctx.settings.bullet.3':
|
||||
'«Карта»: движок карты и его стиль. «Уведомления»: какие события в какой канал отправлять. «Интеграции»: подключение фотобиблиотек, AirTrail, Dawarich и wanderer, а также ключи API и MCP. «Офлайн-режим»: что хранится на этом устройстве.',
|
||||
'help.ctx.settings.bullet.4':
|
||||
@@ -1159,7 +1159,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-display.bullet.1':
|
||||
'«Запуск»: «Стартовая страница» открывается на «Панель управления» или на «Активная поездка». Если выбрана «Активная поездка», в «Стартовая вкладка» можно выбрать, на какой вкладке открывать поездку.',
|
||||
'help.ctx.settings-display.bullet.2':
|
||||
'«Язык и регион»: «Валюта отображения» (или «Валюта поездки», если пересчёт не нужен), «Язык», «Названия мест», «Единица температуры», «Единица расстояния», «Формат времени» и «Неделя начинается с».',
|
||||
'«Язык и регион»: «Валюта отображения» (или «Валюта поездки», если пересчёт не нужен), «Язык», «Названия мест», «Единица температуры», «Единица расстояния», «Формат времени», «Неделя начинается с» и «Формат даты».',
|
||||
'help.ctx.settings-display.bullet.3':
|
||||
'«Карта и путешествия»: «Открывать места в», «Дата первой в заголовках дней», «Подписи маршрутов бронирований», «Всегда показывать маршруты бронирований», «Компактные метки для незапланированных мест», «Поиск мест на карте», «Скрыть коды бронирования» и «Оптимизировать маршрут от места проживания».',
|
||||
'help.ctx.settings-appearance.title': 'Вид',
|
||||
@@ -1480,9 +1480,9 @@ const help: TranslationStrings = {
|
||||
'«Настройки разрешений» внизу: для каждого действия задаётся, кто может его выполнять («Все», «Участники поездки», «Владелец поездки» или «Только администратор»). Чтобы сохранить, нажмите «Сохранить» вверху карточки.',
|
||||
'help.ctx.admin-defaults.title': 'Настройки по умолчанию',
|
||||
'help.ctx.admin-defaults.summary':
|
||||
'С чем начинается новый аккаунт, чтобы никому не пришлось всё настраивать с нуля: цветовая схема, единицы измерения, формат времени, начало недели, валюта отображения и скрытие кодов бронирования, а также карта, её ключи и серверы маршрутов.',
|
||||
'С чем начинается новый аккаунт, чтобы никому не пришлось всё настраивать с нуля: цветовая схема, единицы измерения, формат времени, формат даты, начало недели, валюта отображения и скрытие кодов бронирования, а также карта, её ключи и серверы маршрутов.',
|
||||
'help.ctx.admin-defaults.bullet.1':
|
||||
'«Настройки пользователей по умолчанию» слева: «Цветовая схема», «Единица температуры», «Единица расстояния», «Формат времени», «Неделя начинается с», «Валюта отображения» и «Скрыть коды бронирования».',
|
||||
'«Настройки пользователей по умолчанию» слева: «Цветовая схема», «Единица температуры», «Единица расстояния», «Формат времени», «Неделя начинается с», «Формат даты», «Валюта отображения» и «Скрыть коды бронирования».',
|
||||
'help.ctx.admin-defaults.bullet.2':
|
||||
'Справа «Карта»: «Шаблон карты», «Общий ключ CARTO», «Собственный сервер маршрутов» и «Свой сервер Valhalla», превью, затем «Картографический движок» со «Стилем карты», «Общим токеном Mapbox», «3D-здания и рельеф» и «Режим высокого качества».',
|
||||
'help.ctx.admin-config.title': 'Персонализация',
|
||||
@@ -4283,7 +4283,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.settings-about.bullet.3':
|
||||
'Через Ko-fi, Buy Me a Coffee и Discord можно поддержать TREK или пообщаться с его создателями.',
|
||||
'help.guide.language-region.step.3':
|
||||
'Выберите «Валюта отображения» или оставьте «Валюта поездки», чтобы суммы каждой поездки показывались в её собственной валюте. Затем задайте «Единица температуры», «Единица расстояния», «Формат времени» и день в поле «Неделя начинается с».',
|
||||
'Выберите «Валюта отображения» или оставьте «Валюта поездки», чтобы суммы каждой поездки показывались в её собственной валюте. Затем задайте «Единица температуры», «Единица расстояния», «Формат времени», день в поле «Неделя начинается с» и «Формат даты».',
|
||||
'help.guide.travel-map-prefs.step.3':
|
||||
'«Поиск мест на карте» показывает панель категорий для поиска мест поблизости. «Скрыть коды бронирования» прячет номера подтверждения, пока вы не наведёте на них курсор или не коснётесь их. «Оптимизировать маршрут от места проживания» начинает и заканчивает день у вашего отеля.',
|
||||
'help.guide.web-push-device.step.2':
|
||||
@@ -6329,7 +6329,7 @@ const help: TranslationStrings = {
|
||||
'help.ctx.admin-settings.bullet.5':
|
||||
'«Опасная зона»: «Сменить секрет JWT» выводит из системы всех пользователей, включая вас.',
|
||||
'help.guide.default-user-settings.step.1':
|
||||
'В разделе «Настройки пользователей по умолчанию» выберите «Цветовая схема» («Светлая», «Тёмная» или «Авто»), «Единица температуры», «Единица расстояния», «Формат времени», «Неделя начинается с», «Валюта отображения» и включите или выключите «Скрыть коды бронирования». Каждый выбор сохраняется сразу.',
|
||||
'В разделе «Настройки пользователей по умолчанию» выберите «Цветовая схема» («Светлая», «Тёмная» или «Авто»), «Единица температуры», «Единица расстояния», «Формат времени», «Неделя начинается с», «Формат даты», «Валюта отображения» и включите или выключите «Скрыть коды бронирования». Каждый выбор сохраняется сразу.',
|
||||
'help.guide.admin-passkeys.step.2':
|
||||
'Введите «Relying Party ID (домен)», например trek.example.org, и «Разрешённые источники», по которым доступен ваш TREK, затем нажмите «Сохранить». Если оставить оба поля пустыми, будет использоваться APP_URL.',
|
||||
'help.guide.rotate-jwt-secret.title': 'Выведите всех из системы сразу',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user