From 0a2fa32fc15305d12fe4f821f3627a65153f1f6e Mon Sep 17 00:00:00 2001 From: Maurice Date: Sat, 5 Sep 2026 23:11:27 +0200 Subject: [PATCH] feat(admin): rebuild the API keys card around the TREK index The card was a flat list: two key fields, four Google switches and a large weather panel, all at the same level. That gave a switch you set once the same weight as the key it depends on, and gave the loudest treatment on the page to the one thing with nothing to configure. Now three comparable provider blocks. The TREK index carries the accent and the recommendation, because a key should be the exception rather than the expected setup. Google carries no border badge, so it does not stand out, and says plainly what it costs beyond money. Weather has left the card: it needs no key and had nothing to set. The "recommended" badge moved from Google to the index, which is the whole point of the change. Also here, and both are their own thing: - The settings tab is two columns from xl up. Nearly every card is "label left, switch right", so the middle stayed empty while the page scrolled for ages. Explicit columns rather than a grid over the flat list, because a grid pairs cards row by row and leaves a hole under the shorter one, and these differ by hundreds of pixels. - Pictures: the nearby fallback drops from 300m to 60m and is skipped entirely for shops and gastronomy. Measured across 600 places in six cities, 2 percent of ordinary businesses have a picture on Wikimedia against 70 percent of churches, so for a cafe the curated rungs never fire and the fallback always does. That is how the town hall ended up over the doner shop. It fails open on an unknown category, so landmarks keep the fallback that works for them. vite.config reads the dev port and the API target from the environment, defaulting to what they were. Several worktrees could not run side by side. --- client/src/components/shared/TrekMark.tsx | 21 + client/src/pages/admin/AdminSettingsTab.tsx | 995 +++++++++--------- client/src/pages/admin/GoogleOptions.tsx | 42 + client/src/pages/admin/ProviderBlock.tsx | 48 + client/src/pages/admin/TrekApiCard.tsx | 189 ++++ client/vite.config.js | 27 +- .../place-enrichment.service.ts | 49 +- shared/src/i18n/ar/admin.ts | 46 +- shared/src/i18n/br/admin.ts | 46 +- shared/src/i18n/ca/admin.ts | 46 +- shared/src/i18n/cs/admin.ts | 46 +- shared/src/i18n/de/admin.ts | 46 +- shared/src/i18n/en/admin.ts | 46 +- shared/src/i18n/es/admin.ts | 46 +- shared/src/i18n/fr/admin.ts | 46 +- shared/src/i18n/gr/admin.ts | 46 +- shared/src/i18n/hu/admin.ts | 46 +- shared/src/i18n/id/admin.ts | 46 +- shared/src/i18n/it/admin.ts | 46 +- shared/src/i18n/ja/admin.ts | 46 +- shared/src/i18n/ko/admin.ts | 46 +- shared/src/i18n/nl/admin.ts | 46 +- shared/src/i18n/pl/admin.ts | 46 +- shared/src/i18n/ru/admin.ts | 46 +- shared/src/i18n/sv/admin.ts | 46 +- shared/src/i18n/tr/admin.ts | 46 +- shared/src/i18n/uk/admin.ts | 46 +- shared/src/i18n/vi/admin.ts | 46 +- shared/src/i18n/zh-TW/admin.ts | 46 +- shared/src/i18n/zh/admin.ts | 46 +- 30 files changed, 1879 insertions(+), 550 deletions(-) create mode 100644 client/src/components/shared/TrekMark.tsx create mode 100644 client/src/pages/admin/GoogleOptions.tsx create mode 100644 client/src/pages/admin/ProviderBlock.tsx create mode 100644 client/src/pages/admin/TrekApiCard.tsx diff --git a/client/src/components/shared/TrekMark.tsx b/client/src/components/shared/TrekMark.tsx new file mode 100644 index 000000000..807582809 --- /dev/null +++ b/client/src/components/shared/TrekMark.tsx @@ -0,0 +1,21 @@ +import React from 'react' + +/** + * The TREK wordmark, including the pill. + * + * `fill="currentColor"` on the mark and the two pill classes below are what let + * one file serve both themes: the letters take the surrounding text colour, the + * pill inverts against it. No second asset, no dark-mode variant to keep in + * sync, and it stays correct if the accent or the surface ever changes. + */ +export default function TrekMark(props: React.SVGProps): React.ReactElement { + return ( + <> + + + + ) +} diff --git a/client/src/pages/admin/AdminSettingsTab.tsx b/client/src/pages/admin/AdminSettingsTab.tsx index adcb4371f..970411a3d 100644 --- a/client/src/pages/admin/AdminSettingsTab.tsx +++ b/client/src/pages/admin/AdminSettingsTab.tsx @@ -1,8 +1,11 @@ import React from 'react' import { adminApi, authApi } from '../../api/client' import { getApiErrorMessage } from '../../types' -import { Eye, EyeOff, Save, CheckCircle, XCircle, Loader2, Sun, RefreshCw, AlertTriangle } from 'lucide-react' +import { Eye, EyeOff, Save, CheckCircle, XCircle, Loader2, RefreshCw, AlertTriangle } from 'lucide-react' import ToggleSwitch from '../../components/Settings/ToggleSwitch' +import GoogleOptions from './GoogleOptions' +import ProviderBlock from './ProviderBlock' +import TrekApiCard from './TrekApiCard' import type { TranslationFn } from '../../types' import type { useAdmin } from './useAdmin' @@ -39,569 +42,579 @@ export default function AdminSettingsTab({ admin, t }: AdminSettingsTabProps): R return (
- {/* Authentication Methods */} -
-
-

{t('admin.authMethods')}

-
-
- {envOverrideOidcOnly && ( -

- {t('admin.envOverrideHint')} -

- )} - {/* Password Login */} -
-
-

{t('admin.passwordLogin')}

-

{t('admin.passwordLoginHint')}

-
- + {/* Two columns from xl up. Nearly every card here is "label left, + switch right", so on a wide screen the middle stayed empty while the + page scrolled for ages. Two explicit columns rather than a grid over + the flat list: a plain grid pairs cards row by row and leaves a hole + under whichever of the pair is shorter, and these differ by hundreds + of pixels. Grouped by subject, not by height — access and identity + left, integrations and data right. */} +
+
+ {/* Authentication Methods */} +
+
+

{t('admin.authMethods')}

- {/* Password Registration */} -
-
-

{t('admin.passwordRegistration')}

-

{t('admin.passwordRegistrationHint')}

-
- -
- {/* SSO Login (only when OIDC configured) */} - {oidcConfigured && ( +
+ {envOverrideOidcOnly && ( +

+ {t('admin.envOverrideHint')} +

+ )} + {/* Password Login */}
-

{t('admin.oidcLogin')}

-

{t('admin.oidcLoginHint')}

+

{t('admin.passwordLogin')}

+

{t('admin.passwordLoginHint')}

- )} - {/* SSO Registration (only when OIDC configured) */} - {oidcConfigured && ( + {/* Password Registration */}
-

{t('admin.oidcRegistration')}

-

{t('admin.oidcRegistrationHint')}

+

{t('admin.passwordRegistration')}

+

{t('admin.passwordRegistrationHint')}

- )} + {/* SSO Login (only when OIDC configured) */} + {oidcConfigured && ( +
+
+

{t('admin.oidcLogin')}

+

{t('admin.oidcLoginHint')}

+
+ +
+ )} + {/* SSO Registration (only when OIDC configured) */} + {oidcConfigured && ( +
+
+

{t('admin.oidcRegistration')}

+

{t('admin.oidcRegistrationHint')}

+
+ +
+ )} +
-
- {/* Passkey (WebAuthn) login */} -
-
-

{t('admin.passkey.title')}

-

{t('admin.passkey.cardHint')}

-
-
-
+ {/* Passkey (WebAuthn) login */} +
+
+

{t('admin.passkey.title')}

+

{t('admin.passkey.cardHint')}

+
+
+
+
+

{t('admin.passkey.login')}

+

{t('admin.passkey.loginHint')}

+
+ +
+ + {passkeyLogin && !passkeyConfigured && ( +

+ + {t('admin.passkey.notConfigured')} +

+ )} + + {/* The domain passkeys bind to and the origins that may present them + follow from the address the instance is served on, which the operator + owns. Getting either wrong invalidates every enrolled passkey, and on + a shared parent domain a wrong RP ID reaches past this instance + entirely — so they are pinned per container, not offered here. The + switch above stays: whether to offer passkeys at all is a house rule. */} + {!managed && (<>
-

{t('admin.passkey.login')}

-

{t('admin.passkey.loginHint')}

+ +

{t('admin.passkey.rpIdHint')}

+ setWebauthnRpId(e.target.value)} + placeholder="trek.example.org" + className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" + /> +
+
+ +

{t('admin.passkey.originsHint')}

+ setWebauthnOrigins(e.target.value)} + placeholder="https://trek.example.org" + className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" + />
+ )}
+
- {passkeyLogin && !passkeyConfigured && ( -

- - {t('admin.passkey.notConfigured')} -

- )} + {/* Require 2FA for all users */} +
+
+

{t('admin.requireMfa')}

+
+
+
+
+

{t('admin.requireMfa')}

+

{t('admin.requireMfaHint')}

+
+ +
+
+
- {/* The domain passkeys bind to and the origins that may present them - follow from the address the instance is served on, which the operator - owns. Getting either wrong invalidates every enrolled passkey, and on - a shared parent domain a wrong RP ID reaches past this instance - entirely — so they are pinned per container, not offered here. The - switch above stays: whether to offer passkeys at all is a house rule. */} + {/* An issuer the instance names can assert any address as verified, and the + discovery calls leave from inside the operator’s network. Sign-on is theirs + to wire, so the fields are not offered. */} {!managed && (<> -
- -

{t('admin.passkey.rpIdHint')}

- setWebauthnRpId(e.target.value)} - placeholder="trek.example.org" - className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" - /> + {/* OIDC / SSO Configuration */} +
+
+

{t('admin.oidcTitle')}

+

{t('admin.oidcSubtitle')}

+
+
+
+ + setOidcConfig(c => ({ ...c, display_name: e.target.value }))} + placeholder='z.B. Google, Authentik, Keycloak' + className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" + /> +
+
+ + setOidcConfig(c => ({ ...c, issuer: e.target.value }))} + placeholder='https://accounts.google.com' + className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" + /> +

{t('admin.oidcIssuerHint')}

+
+
+ + setOidcConfig(c => ({ ...c, discovery_url: e.target.value }))} + placeholder='https://auth.example.com/application/o/trek/.well-known/openid-configuration' + className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" + /> +

Override the auto-constructed discovery URL. Required for providers like Authentik where the endpoint is not at {'/.well-known/openid-configuration'}.

+
+
+ + setOidcConfig(c => ({ ...c, client_id: e.target.value }))} + className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" + /> +
+
+ + setOidcConfig(c => ({ ...c, client_secret: e.target.value }))} + placeholder={oidcConfig.client_secret_set ? '••••••••' : ''} + className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" + /> +
+ +
-
- -

{t('admin.passkey.originsHint')}

- setWebauthnOrigins(e.target.value)} - placeholder="https://trek.example.org" - className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" - /> -
- )}
-
- {/* Require 2FA for all users */} -
-
-

{t('admin.requireMfa')}

-
-
-
-
-

{t('admin.requireMfa')}

-

{t('admin.requireMfaHint')}

-
-
-
- {/* Allowed File Types */} -
-
-

{t('admin.fileTypes')}

-

{t('admin.fileTypesHint')}

-
-
- setAllowedFileTypes(e.target.value)} - placeholder="jpg,png,pdf,doc,docx,xls,xlsx,txt,csv" - className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" - /> -

{t('admin.fileTypesFormat')}

- -
-
- - {/* Google and Unsplash come with the instance, and so does what a lookup costs; - the per-place toggles only ever traded away quota that is not the customer’s - to spend. Weather needs no key at all and has nothing to configure. */} - {!managed && (<> - {/* API Keys */} -
-
-

{t('admin.apiKeys')}

-

{t('admin.apiKeysHint')}

-
-
- {/* The two key fields, not the toggles below them: on a centrally - administered install the operator supplies the keys, while whether - photos and lookups are offered at all stays the admin’s call. */} + {/* Google and Unsplash come with the instance, and so does what a lookup costs; + the per-place toggles only ever traded away quota that is not the customer’s + to spend. Weather needs no key at all and has nothing to configure. */} {!managed && (<> - {/* Google Maps Key */} -
- -
-
+ {/* API Keys. + The key fields stay visible; only the four Google switches fold away. + They are set once and then never touched, and putting them flat next to + the keys made a rarely-used option look as important as the key it + depends on. Weather is a quiet row for the same reason in reverse: it + had the loudest treatment on the card and is the one thing here with + nothing to configure. */} +
+
+

{t('admin.apiKeys')}

+

{t('admin.apiKeysHint')}

+
+
+ {/* Three comparable blocks, in the order we would have people choose + them. Weather moved out entirely: it needs no key and had nothing + to configure, so it had no business in a card about keys. */} + + + {!managed && (<> + + {/* Said before the field, not after it. Someone about to paste a key + should read this while deciding, not once they already have. */} +

+ {t('admin.googleCaveat.body')} +

+ +
+
+
+ setMapsKey(e.target.value)} + placeholder={t('settings.keyPlaceholder')} + className="w-full pr-10 px-3 py-2 border border-edge rounded-lg text-sm bg-surface-input text-content focus:ring-2 focus:ring-accent focus:border-transparent" + /> + +
+ +
+

{t('admin.mapsKeyHintLong')}

+ {validation.maps === true && ( +

+ + {t('admin.keyValid')} +

+ )} + {validation.maps === false && ( +

+ + {t('admin.keyInvalid')} +

+ )} +
+ + {/* What the key may be spent on, inside the block that owns the key + rather than beside it. Each row's own subtitle says which of them + still work without one; enrichment always does, from Wikipedia + and OpenStreetMap. */} + +
+
+
+

{t('admin.placesPhotos.title')}

+

{t('admin.placesPhotos.subtitle')}

+
+ { + const next = !placesPhotosEnabled + setPlacesPhotosEnabledState(next) + setPlacesPhotosEnabled(next) + try { await adminApi.updatePlacesPhotos(next) } catch { setPlacesPhotosEnabledState(!next); setPlacesPhotosEnabled(!next) } + }} + /> +
+ +
+
+

{t('admin.placesAutocomplete.title')}

+

{t('admin.placesAutocomplete.subtitle')}

+
+ { + const next = !placesAutocompleteEnabled + setPlacesAutocompleteEnabledState(next) + setPlacesAutocompleteEnabled(next) + try { await adminApi.updatePlacesAutocomplete(next) } catch { setPlacesAutocompleteEnabledState(!next); setPlacesAutocompleteEnabled(!next) } + }} + /> +
+ +
+
+

{t('admin.placesDetails.title')}

+

{t('admin.placesDetails.subtitle')}

+
+ { + const next = !placesDetailsEnabled + setPlacesDetailsEnabledState(next) + setPlacesDetailsEnabled(next) + try { await adminApi.updatePlacesDetails(next) } catch { setPlacesDetailsEnabledState(!next); setPlacesDetailsEnabled(!next) } + }} + /> +
+ +
+
+

{t('admin.placesEnrich.title')}

+

{t('admin.placesEnrich.subtitle')}

+
+ { + const next = !placesEnrichEnabled + setPlacesEnrichEnabledState(next) + setPlacesEnrichEnabled(next) + try { await adminApi.updatePlacesEnrich(next) } catch { setPlacesEnrichEnabledState(!next); setPlacesEnrichEnabled(!next) } + }} + /> +
+
+
+
+ + +
+
setMapsKey(e.target.value)} + type={showKeys.unsplash ? 'text' : 'password'} + value={unsplashKey} + onChange={e => setUnsplashKey(e.target.value)} placeholder={t('settings.keyPlaceholder')} - className="w-full pr-10 px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" + className="w-full pr-10 px-3 py-2 border border-edge rounded-lg text-sm bg-surface-input text-content focus:ring-2 focus:ring-accent focus:border-transparent" />
- +

{t('admin.unsplashKeyHint')}

-

{t('admin.mapsKeyHintLong')}

- {validation.maps === true && ( -

- - {t('admin.keyValid')} -

- )} - {validation.maps === false && ( -

- - {t('admin.keyInvalid')} -

- )} -
- - {/* Unsplash Key */} -
- -
- setUnsplashKey(e.target.value)} - placeholder={t('settings.keyPlaceholder')} - className="w-full pr-10 px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" - /> - -
-

{t('admin.unsplashKeyHint')}

-
+ )} - {/* Place Photos Toggle */} -
-
-

{t('admin.placesPhotos.title')}

-

{t('admin.placesPhotos.subtitle')}

-
- { - const next = !placesPhotosEnabled - setPlacesPhotosEnabledState(next) - setPlacesPhotosEnabled(next) - try { await adminApi.updatePlacesPhotos(next) } catch { setPlacesPhotosEnabledState(!next); setPlacesPhotosEnabled(!next) } - }} - /> -
- - {/* Place Autocomplete Toggle */} -
-
-

{t('admin.placesAutocomplete.title')}

-

{t('admin.placesAutocomplete.subtitle')}

-
- { - const next = !placesAutocompleteEnabled - setPlacesAutocompleteEnabledState(next) - setPlacesAutocompleteEnabled(next) - try { await adminApi.updatePlacesAutocomplete(next) } catch { setPlacesAutocompleteEnabledState(!next); setPlacesAutocompleteEnabled(!next) } - }} - /> -
- - {/* Place Details Toggle */} -
-
-

{t('admin.placesDetails.title')}

-

{t('admin.placesDetails.subtitle')}

-
- { - const next = !placesDetailsEnabled - setPlacesDetailsEnabledState(next) - setPlacesDetailsEnabled(next) - try { await adminApi.updatePlacesDetails(next) } catch { setPlacesDetailsEnabledState(!next); setPlacesDetailsEnabled(!next) } - }} - /> -
- - {/* Place Enrichment Toggle */} -
-
-

{t('admin.placesEnrich.title')}

-

{t('admin.placesEnrich.subtitle')}

-
- { - const next = !placesEnrichEnabled - setPlacesEnrichEnabledState(next) - setPlacesEnrichEnabled(next) - try { await adminApi.updatePlacesEnrich(next) } catch { setPlacesEnrichEnabledState(!next); setPlacesEnrichEnabled(!next) } - }} - /> -
{/* Transit Backend (#1699) — Transitous has no GTFS for much of Asia, so an install can point transit search at Google instead, on the - same key. Falls back to Transitous when no key resolves. */} -
-

{t('admin.transitProvider.title')}

-

{t('admin.transitProvider.subtitle')}

- -

- {transitProvider === 'google' ? t('admin.transitProvider.googleHint') : t('admin.transitProvider.transitousHint')} -

- - {/* Picking Google without a key that resolves changes nothing — the - request-time fallback is silent, so this is the only place it can - be said. 'user-row' is the subtler half: the resolver's last step - is the caller's own row, so a personal key serves this admin and - nobody else. */} - {transitProvider === 'google' && transitGoogleKeySource === null && ( -

- - {t('admin.transitProvider.noKeyWarning')} + same key. Falls back to Transitous when no key resolves. A block of + its own rather than a row inside the Google one: picking Transitous + is a decision about a provider that has nothing to do with a key. */} + +

+

{t('admin.transitProvider.subtitle')}

+ +

+ {transitProvider === 'google' ? t('admin.transitProvider.googleHint') : t('admin.transitProvider.transitousHint')}

- )} - {transitProvider === 'google' && transitGoogleKeySource === 'user-row' && ( -

- - {t('admin.transitProvider.personalKeyWarning')} -

- )} -
- {/* Open-Meteo Weather Info */} -
-
-
-
- -
- {t('admin.weather.title')} -
- {t('admin.weather.badge')} + {/* Picking Google without a key that resolves changes nothing — the + request-time fallback is silent, so this is the only place it can + be said. 'user-row' is the subtler half: the resolver's last step + is the caller's own row, so a personal key serves this admin and + nobody else. */} + {transitProvider === 'google' && transitGoogleKeySource === null && ( +

+ + {t('admin.transitProvider.noKeyWarning')} +

+ )} + {transitProvider === 'google' && transitGoogleKeySource === 'user-row' && ( +

+ + {t('admin.transitProvider.personalKeyWarning')} +

+ )}
-
-

{t('admin.weather.description')}

-

{t('admin.weather.locationHint')}

-
-
-

{t('admin.weather.forecast')}

-

{t('admin.weather.forecastDesc')}

-
-
-

{t('admin.weather.climate')}

-

{t('admin.weather.climateDesc')}

-
-
-

{t('admin.weather.requests')}

-

{t('admin.weather.requestsDesc')}

-
-
-
-
+ +
+
+ )}
- {/* An issuer the instance names can assert any address as verified, and the - discovery calls leave from inside the operator’s network. Sign-on is theirs - to wire, so the fields are not offered. */} - {!managed && (<> - )} - {/* OIDC / SSO Configuration */} -
-
-

{t('admin.oidcTitle')}

-

{t('admin.oidcSubtitle')}

-
-
-
- - setOidcConfig(c => ({ ...c, display_name: e.target.value }))} - placeholder='z.B. Google, Authentik, Keycloak' - className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" - /> -
-
- - setOidcConfig(c => ({ ...c, issuer: e.target.value }))} - placeholder='https://accounts.google.com' - className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" - /> -

{t('admin.oidcIssuerHint')}

-
-
- - setOidcConfig(c => ({ ...c, discovery_url: e.target.value }))} - placeholder='https://auth.example.com/application/o/trek/.well-known/openid-configuration' - className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" - /> -

Override the auto-constructed discovery URL. Required for providers like Authentik where the endpoint is not at {'/.well-known/openid-configuration'}.

-
-
- - setOidcConfig(c => ({ ...c, client_id: e.target.value }))} - className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" - /> -
-
- - setOidcConfig(c => ({ ...c, client_secret: e.target.value }))} - placeholder={oidcConfig.client_secret_set ? '••••••••' : ''} - className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent" - /> -
- -
-
+ {/* Full width, and last. A destructive action should not sit beside a + harmless toggle where a mis-aimed click can reach it. */} {/* Rotating the secret signs every user out and fixes nothing an instance admin can reach: the file it writes belongs to the host. */} {!managed && (<> - )} {/* Danger Zone */}
diff --git a/client/src/pages/admin/GoogleOptions.tsx b/client/src/pages/admin/GoogleOptions.tsx new file mode 100644 index 000000000..4b06af064 --- /dev/null +++ b/client/src/pages/admin/GoogleOptions.tsx @@ -0,0 +1,42 @@ +import React from 'react' +import { ChevronRight } from 'lucide-react' + +interface GoogleOptionsProps { + /** "Google options" — the heading of the fold. */ + title: string + /** Right-hand summary while folded, e.g. "3 of 4 on". */ + summary: string + children: React.ReactNode +} + +/** + * The fold that holds what a Google key is allowed to be spent on. + * + * Only these switches fold away. The key fields above stay visible: a field you + * have to go looking for is worse than a long page, and the keys are the reason + * an admin opens this card at all. The switches are the opposite — set once, + * then never touched, and there are four of them. + * + * A native
rather than a state hook: keyboard operable and announced + * to screen readers without writing any of that, and it survives a re-render + * without state. + */ +export default function GoogleOptions({ title, summary, children }: GoogleOptionsProps): React.ReactElement { + return ( +
+ + +
{children}
+
+ ) +} diff --git a/client/src/pages/admin/ProviderBlock.tsx b/client/src/pages/admin/ProviderBlock.tsx new file mode 100644 index 000000000..bcc65da65 --- /dev/null +++ b/client/src/pages/admin/ProviderBlock.tsx @@ -0,0 +1,48 @@ +import React from 'react' + +interface ProviderBlockProps { + title: string + /** Optional label sitting on the top border, like the one on the TREK block. */ + badge?: string + /** + * `accent` marks the recommended path, `muted` a neutral one, `caution` a + * provider that costs something other than money. Deliberately not `danger`: + * using a Google key is a legitimate choice, and a red badge would be the + * settings page shouting at the person who made it. + */ + tone?: 'muted' | 'caution' + children: React.ReactNode +} + +/** + * One provider inside the API card, in the same shape as the TREK block above + * it, so the page reads as a list of comparable options rather than one + * highlighted thing and some loose fields underneath. + */ +export default function ProviderBlock({ + title, badge, tone = 'muted', children, +}: ProviderBlockProps): React.ReactElement { + return ( +
+ {badge && ( + + {badge} + + )} +
+
+

{title}

+
{children}
+
+
+
+ ) +} diff --git a/client/src/pages/admin/TrekApiCard.tsx b/client/src/pages/admin/TrekApiCard.tsx new file mode 100644 index 000000000..e11b2a462 --- /dev/null +++ b/client/src/pages/admin/TrekApiCard.tsx @@ -0,0 +1,189 @@ +import React from 'react' +import { Check, ChevronRight, Globe2, KeyRound, Library, ShieldOff, WifiOff, X } from 'lucide-react' +import TrekMark from '../../components/shared/TrekMark' +import type { TranslationFn } from '../../types' + +interface TrekApiCardProps { + t: TranslationFn + /** + * Whether this instance can actually use it yet. False until the client is + * wired to the service; the card then says so instead of offering a switch + * that would do nothing. + */ + available?: boolean +} + +/** + * The TREK Places API, above the Google key because it is the alternative to it. + * + * Given real weight on the page on purpose: it is what makes a key optional + * instead of expected. The weight comes from the mark, one line of type and a + * ring — not from a tinted panel, which is what the weather block used to do + * and what made it shout over the settings it sat next to. + * + * Everything a reader can check is a measured number, not a claim: 73.6 million + * is the row count of the current index, and "no queries logged" is enforced in + * three places on the server rather than promised here. + */ +/** + * Proper nouns, so they are not translated. Naming them is also a licence + * obligation, not decoration: ODbL and CC BY-SA both require attribution + * wherever their content is shown. + */ +const SOURCES = ['Overture Maps Foundation', 'OpenStreetMap', 'Wikivoyage', 'Wikimedia'] + +export default function TrekApiCard({ t, available = false }: TrekApiCardProps): React.ReactElement { + // Reuses the words TREK already has for these fields wherever it has them, + // so the chip row costs two new strings instead of ten. + const fields = [ + t('places.formName'), + t('collections.coordinates'), + t('places.formCategory'), + t('places.formAddress'), + t('admin.trekApi.fieldPhone'), + t('common.email'), + t('places.formWebsite'), + t('places.formDescription'), + t('inspector.openingHours'), + t('admin.trekApi.fieldStableId'), + ] + + const facts = [ + { Icon: Globe2, text: t('admin.trekApi.factPlaces') }, + { Icon: KeyRound, text: t('admin.trekApi.factNoKey') }, + { Icon: WifiOff, text: t('admin.trekApi.factOffline') }, + { Icon: ShieldOff, text: t('admin.trekApi.factPrivacy') }, + ] + + return ( + /* Relative wrapper without overflow so the ribbon may sit ON the edge; the + clipping the rounded corners need happens one level in. */ +
+ {/* The recommendation, moved here from the Google field. It is the whole + point of the block: a key should be the exception, not the default. + Vertical and small on purpose — a wide coloured banner would shout + over the settings around it, which is what the weather panel used to + do and what this card is trying not to repeat. */} + + {t('admin.trekApi.badgeDefault')} + + +
+
+
+ + {available && ( + + {t('admin.trekApi.badgeActive')} + + )} +
+ +

+ {t('admin.trekApi.tagline')} +

+ +
    + {facts.map(({ Icon, text }) => ( +
  • +
  • + ))} +
+
+ +
+ + +
+ {/* The fields as chips rather than a paragraph. A list of what you + get is something you scan, not something you read, and a sentence + forces the reader to parse commas to answer "is the phone number + in there". */} +
+

+

+
    + {fields.map(field => ( +
  • + {field} +
  • + ))} +
+

+ {t('admin.trekApi.includedNote')} +

+
+ + {/* Named as plainly as what IS included. An admin who switches the + source expecting ratings and a photograph of every restaurant + should find that out here and not three weeks later. */} +
+

+

+
    + {[t('admin.trekApi.notRatings'), t('admin.trekApi.notPhotos')].map(item => ( +
  • + {item} +
  • + ))} +
+

+ {t('admin.trekApi.notIncludedNote')} +

+
+ + {/* Attribution, and not in the small print: the licences require the + sources to be named, and naming them is also the answer to "where + does this actually come from". */} +
+

+

+
    + {SOURCES.map(source => ( +
  • + {source} +
  • + ))} +
+

+ {t('admin.trekApi.sourcesNote')} +

+
+
+
+
+
+ ) +} diff --git a/client/vite.config.js b/client/vite.config.js index 6ca0171a1..c20f0e2e9 100644 --- a/client/vite.config.js +++ b/client/vite.config.js @@ -7,6 +7,11 @@ import { rtlTextAlias } from './rtlTextAlias.js'; // `npm run build:analyze` writes dist/stats.html — a treemap of what actually ended // up in each chunk. The plain build only reports chunk sizes, which tells you a chunk // is too big but not which dependency made it so. +// Mehrere Worktrees laufen hier parallel. Ohne diese beiden Variablen streiten +// sie sich um 5173 und 3001; mit ihnen bekommt jeder seinen eigenen Satz. +const DEV_PORT = Number(process.env.TREK_DEV_PORT) || 5173; +const API_TARGET = process.env.TREK_DEV_API || 'http://localhost:3001'; + export default defineConfig(({ mode }) => ({ plugins: [ react(), @@ -298,7 +303,7 @@ export default defineConfig(({ mode }) => ({ exclude: ['@trek/shared'], }, server: { - port: 5173, + port: DEV_PORT, // And watch the build output, so rebuilding shared reloads the page rather // than leaving a stale module graph behind. watch: { @@ -306,46 +311,46 @@ export default defineConfig(({ mode }) => ({ }, proxy: { '/api': { - target: 'http://localhost:3001', + target: API_TARGET, changeOrigin: true, }, '/plugin-frame': { - target: 'http://localhost:3001', + target: API_TARGET, changeOrigin: true, }, '/uploads': { - target: 'http://localhost:3001', + target: API_TARGET, changeOrigin: true, }, '/ws': { - target: 'http://localhost:3001', + target: API_TARGET, ws: true, }, '/mcp': { - target: 'http://localhost:3001', + target: API_TARGET, changeOrigin: true, }, // OAuth 2.1 endpoints handled by backend (SDK authorize handler + token/revoke) // /oauth/authorize goes to backend so the SDK can redirect to /oauth/consent // /oauth/consent is served by Vite as a SPA route (no proxy entry needed) '/oauth/authorize': { - target: 'http://localhost:3001', + target: API_TARGET, changeOrigin: true, }, '/oauth/token': { - target: 'http://localhost:3001', + target: API_TARGET, changeOrigin: true, }, '/oauth/register': { - target: 'http://localhost:3001', + target: API_TARGET, changeOrigin: true, }, '/oauth/revoke': { - target: 'http://localhost:3001', + target: API_TARGET, changeOrigin: true, }, '/.well-known': { - target: 'http://localhost:3001', + target: API_TARGET, changeOrigin: true, }, }, diff --git a/server/src/nest/place-enrichment/place-enrichment.service.ts b/server/src/nest/place-enrichment/place-enrichment.service.ts index ada4d3e9b..6fcebf5e1 100644 --- a/server/src/nest/place-enrichment/place-enrichment.service.ts +++ b/server/src/nest/place-enrichment/place-enrichment.service.ts @@ -139,6 +139,43 @@ interface CachePayload extends CachedEnrichment { v?: number; } +/** + * Categories where a picture taken nearby is almost certainly of something + * else, so the bottom rung of the ladder is skipped for them entirely. + * + * Measured across 600 places in six cities: 2 percent of ordinary businesses + * have a picture on Wikimedia, against 70 percent of churches. So for a cafe + * the curated rungs practically never fire and the fallback practically always + * does, which is how the town hall ends up over the doner shop. A missing + * picture is honest; a confident picture of the building opposite is not. + * + * Matched against the category the source reports: `basic_category` from the + * TREK index, the amenity/shop tag from OpenStreetMap, a Google type. + */ +const NEARBY_MISLEADS = [ + 'restaurant', 'cafe', 'coffee', 'bar', 'pub', 'bakery', 'fast_food', 'food', + 'eatery', 'biergarten', 'ice_cream', 'shop', 'store', 'supermarket', 'retail', + 'pharmacy', 'hairdresser', 'kiosk', 'convenience', 'butcher', 'greengrocer', + 'clothing', 'florist', 'bank', 'atm', 'nightclub', +]; + +/** + * True when a nearby picture would more likely mislead than inform. + * + * Fails OPEN on an unknown category: without one we cannot tell a cathedral + * from a kebab shop, and silently dropping pictures for everything unlabelled + * would take them away from the places where the fallback actually works. + */ +export function nearbyWouldMislead(details: Record | null): boolean { + if (!details) return false; + const haystack = [details.category, details.category_path, details.amenity, details.shop, details.cuisine] + .filter((v): v is string => typeof v === 'string') + .join(' ') + .toLowerCase(); + if (!haystack) return false; + return NEARBY_MISLEADS.some(word => haystack.includes(word)); +} + /** OSM yes/no tags; anything else (limited, only, designated) is shown verbatim. */ function yesNo(value: unknown): 'yes' | 'no' | string | null { return typeof value === 'string' && value.trim() ? value.trim().toLowerCase() : null; @@ -287,7 +324,7 @@ export class PlaceEnrichmentService { const identity = await this.resolveIdentity(req, details); const [photos, description] = await Promise.all([ - this.collectPhotos(userId, placeId, req, identity), + this.collectPhotos(userId, placeId, req, identity, details), this.collectDescription(userId, placeId, req, details, identity), ]); @@ -373,6 +410,8 @@ export class PlaceEnrichmentService { placeId: string, req: MapsPlaceEnrichmentRequest, identity: PlaceIdentity, + /** The record the caller already holds; only its category is read. */ + details: Record | null, ): Promise { const apiKey = this.maps.getMapsKey(userId); const wantsGoogle = !!apiKey && !this.maps.photosDisabled() && isGooglePlaceId(placeId); @@ -424,8 +463,14 @@ export class PlaceEnrichmentService { // used to push this endpoint past the client's timeout. Geosearch is free // and unmetered, so a speculative call that gets discarded costs nothing // anyone pays for. + // Skipped outright for the categories where it misleads, rather than + // fetched and then filtered: the request is the cost, and there is nothing + // in the answer worth looking at for a cafe. + const skipNearby = nearbyWouldMislead(details) || nearbyWouldMislead(identity.osmTags); const nearbyPending = - curated < 2 ? this.maps.fetchCommonsCandidates(req.lat, req.lng, COMMONS_CAP) : Promise.resolve([]); + curated < 2 && !skipNearby + ? this.maps.fetchCommonsCandidates(req.lat, req.lng, COMMONS_CAP) + : Promise.resolve([]); const googleRefs = await googlePending; if (curated < 2 && googleRefs.length < GOOGLE_CAP) { diff --git a/shared/src/i18n/ar/admin.ts b/shared/src/i18n/ar/admin.ts index 0a08927f2..9b696e3ad 100644 --- a/shared/src/i18n/ar/admin.ts +++ b/shared/src/i18n/ar/admin.ts @@ -108,11 +108,51 @@ const admin: TranslationStrings = { 'admin.requireMfa': 'فرض المصادقة الثنائية (2FA)', 'admin.requireMfaHint': 'يجب على المستخدمين الذين لا يملكون 2FA إكمال الإعداد في الإعدادات قبل استخدام التطبيق.', 'admin.apiKeys': 'مفاتيح API', - 'admin.apiKeysHint': 'اختياري. يُفعّل بيانات الأماكن الموسعة مثل الصور والطقس.', + 'admin.apiKeysHint': 'من أين تأتي بيانات الأماكن. فهرس TREK لا يحتاج مفتاحًا؛ والمزوّدان أدناه اختياريان.', + 'admin.trekApi.badgeDefault': 'الافتراضي الموصى به', + 'admin.googleCaveat.badge': 'غير مُستحسَن', + 'admin.googleCaveat.body': + 'TREK مفتوح المصدر ولسنا محايدين هنا. بهذا الحجم لا توجد التقييمات وصور المحال العادية إلا لدى Google، وهذا هو الاحتكار. الحقل موجود لانعدام البديل، لا لأننا نوصي به. عندئذ يذهب كل استعلام إلى Google.', + 'admin.trekApi.badgeSoon': + 'قريبًا', + 'admin.trekApi.badgeActive': + 'مُفعَّل', + 'admin.trekApi.tagline': + 'فهرس الأماكن الخاص بـ TREK. بحث بلا مفتاح من Google، بلا حصة، وبلا أن يُحصي أحد عمليات بحثك.', + 'admin.trekApi.factPlaces': + '73.6 مليون مكان حول العالم', + 'admin.trekApi.factNoKey': + 'بلا مفتاح وبلا حصة', + 'admin.trekApi.factOffline': + 'حِزَم الدول تعمل دون اتصال', + 'admin.trekApi.factPrivacy': + 'لا تُسجَّل عمليات البحث أبدًا', + 'admin.trekApi.more': + 'ماذا يحتوي', + 'admin.trekApi.fieldPhone': + 'هاتف', + 'admin.trekApi.fieldStableId': + 'معرّف ثابت', + 'admin.trekApi.includedNote': + 'الأوصاف تأتي من موقع المكان نفسه، وساعات العمل من OpenStreetMap حيث تكون مُدخَلة.', + 'admin.trekApi.notRatings': + 'التقييمات', + 'admin.trekApi.notPhotos': + 'صور المحال العادية', + 'admin.trekApi.notIncludedNote': + 'لا يوفّر أيًّا منهما أي مصدر مفتوح بأي ثمن. يبقى مفتاح Google هو السبيل الوحيد إليهما.', + 'admin.trekApi.sourcesLabel': + 'المصادر', + 'admin.trekApi.sourcesNote': + 'كل حقل في الاستجابة يذكر من أيٍّ منها جاء.', + 'admin.trekApi.included': + 'مشمول', + 'admin.trekApi.notIncluded': + 'غير مشمول', 'admin.mapsKey': 'مفتاح Google Maps API', 'admin.mapsKeyHint': 'مطلوب للبحث عن الأماكن. احصل عليه من console.cloud.google.com', 'admin.mapsKeyHintLong': - 'بدون مفتاح API، يُستخدم OpenStreetMap للبحث. مع مفتاح Google يمكن تحميل الصور والتقييمات وساعات العمل أيضًا. احصل عليه من console.cloud.google.com.', + 'بدون مفتاح Google API يُستخدَم TREK API المُوصى به. ومع المفتاح يمكن إضافةً تحميل الصور والتقييمات وساعات العمل. أنشئ مفتاحًا على console.cloud.google.com.', 'admin.recommended': 'مُوصى به', 'admin.weatherKey': 'مفتاح OpenWeatherMap API', 'admin.weatherKeyHint': 'لبيانات الطقس. مجاني من openweathermap.org', @@ -135,6 +175,8 @@ const admin: TranslationStrings = { 'admin.fileTypesHint': 'حدد أنواع الملفات التي يمكن للمستخدمين رفعها.', 'admin.fileTypesFormat': 'امتدادات مفصولة بفواصل (مثل jpg,png,pdf,doc). استخدم * للسماح بجميع الأنواع.', 'admin.fileTypesSaved': 'تم حفظ إعدادات أنواع الملفات', + 'admin.googleOptions': 'فيمَ يُستخدَم المفتاح', + 'admin.googleOptionsSummary': '{on} من {total} مُفعَّل', 'admin.placesPhotos.title': 'صور الأماكن', 'admin.placesPhotos.subtitle': 'جلب الصور من Google Places API. عطّلها للحفاظ على حصة API. صور Wikimedia غير متأثرة.', 'admin.placesAutocomplete.title': 'الإكمال التلقائي للأماكن', diff --git a/shared/src/i18n/br/admin.ts b/shared/src/i18n/br/admin.ts index 2dd298bdf..69d62cdf7 100644 --- a/shared/src/i18n/br/admin.ts +++ b/shared/src/i18n/br/admin.ts @@ -86,11 +86,51 @@ const admin: TranslationStrings = { 'admin.requireMfa': 'Exigir autenticação em dois fatores (2FA)', 'admin.requireMfaHint': 'Usuários sem 2FA precisam concluir a configuração em Configurações antes de usar o app.', 'admin.apiKeys': 'Chaves de API', - 'admin.apiKeysHint': 'Opcional. Habilita dados estendidos de lugares, como fotos e clima.', + 'admin.apiKeysHint': 'De onde vêm os dados de locais. O índice do TREK não precisa de chave; os dois provedores abaixo são opcionais.', + 'admin.trekApi.badgeDefault': 'Padrão recomendado', + 'admin.googleCaveat.badge': 'Não recomendado', + 'admin.googleCaveat.body': + 'O TREK é código aberto e aqui não somos neutros. Nesta escala, avaliações e fotos de estabelecimentos comuns só existem no Google, e isso é um monopólio. O campo está aqui por falta de alternativa, não porque o recomendamos. Aí cada consulta vai para o Google.', + 'admin.trekApi.badgeSoon': + 'Em breve', + 'admin.trekApi.badgeActive': + 'Ativo', + 'admin.trekApi.tagline': + 'O índice de locais do próprio TREK. Pesquisar sem chave do Google, sem cota e sem que ninguém conte as suas buscas.', + 'admin.trekApi.factPlaces': + '73,6 milhões de locais no mundo', + 'admin.trekApi.factNoKey': + 'Sem chave, sem cota', + 'admin.trekApi.factOffline': + 'Os pacotes por país funcionam offline', + 'admin.trekApi.factPrivacy': + 'As buscas nunca são registadas', + 'admin.trekApi.more': + 'O que vem dentro', + 'admin.trekApi.fieldPhone': + 'Telefone', + 'admin.trekApi.fieldStableId': + 'Identificador estável', + 'admin.trekApi.includedNote': + 'As descrições vêm do site do próprio local; os horários, do OpenStreetMap onde estiverem preenchidos.', + 'admin.trekApi.notRatings': + 'Avaliações', + 'admin.trekApi.notPhotos': + 'Fotos de estabelecimentos comuns', + 'admin.trekApi.notIncludedNote': + 'Nenhuma fonte aberta tem uma nem outra, a preço nenhum. Para essas duas uma chave do Google continua sendo o único caminho.', + 'admin.trekApi.sourcesLabel': + 'Fontes', + 'admin.trekApi.sourcesNote': + 'Cada campo de uma resposta diz de qual delas veio.', + 'admin.trekApi.included': + 'Incluído', + 'admin.trekApi.notIncluded': + 'Não incluído', 'admin.mapsKey': 'Chave da API Google Maps', 'admin.mapsKeyHint': 'Necessária para busca de lugares. Obtenha em console.cloud.google.com', 'admin.mapsKeyHintLong': - 'Sem chave de API, o OpenStreetMap é usado na busca. Com uma chave Google, também podem ser carregadas fotos, avaliações e horários. Obtenha em console.cloud.google.com.', + 'Sem chave de API do Google é usada a API do TREK recomendada. Com uma chave é possível carregar além disso fotos, avaliações e horários. Crie uma em console.cloud.google.com.', 'admin.recommended': 'Recomendado', 'admin.weatherKey': 'Chave OpenWeatherMap', 'admin.weatherKeyHint': 'Para dados meteorológicos. Grátis em openweathermap.org', @@ -112,6 +152,8 @@ const admin: TranslationStrings = { 'admin.fileTypesHint': 'Configure quais tipos de arquivo os usuários podem enviar.', 'admin.fileTypesFormat': 'Extensões separadas por vírgula (ex.: jpg,png,pdf,doc). Use * para permitir todos.', 'admin.fileTypesSaved': 'Configurações de tipos de arquivo salvas', + 'admin.googleOptions': 'Para que a chave é usada', + 'admin.googleOptionsSummary': '{on} de {total} ativados', 'admin.placesPhotos.title': 'Fotos de Locais', 'admin.placesPhotos.subtitle': 'Busca fotos da Google Places API. Desative para economizar cota da API. Fotos do Wikimedia não são afetadas.', diff --git a/shared/src/i18n/ca/admin.ts b/shared/src/i18n/ca/admin.ts index 5d9adc30b..c34a3ba93 100644 --- a/shared/src/i18n/ca/admin.ts +++ b/shared/src/i18n/ca/admin.ts @@ -93,11 +93,51 @@ const admin: TranslationStrings = { 'admin.requireMfaHint': "Els usuaris sense 2FA han de completar la configuració a Ajustos abans d'utilitzar l'aplicació.", 'admin.apiKeys': 'Claus API', - 'admin.apiKeysHint': 'Opcional. Activa dades ampliades de llocs, com ara fotos i previsió meteorològica.', + 'admin.apiKeysHint': "D'on vénen les dades de llocs. L'índex de TREK no necessita clau; els dos proveïdors de sota són opcionals.", + 'admin.trekApi.badgeDefault': 'Font recomanada per defecte', + 'admin.googleCaveat.badge': 'No recomanat', + 'admin.googleCaveat.body': + "TREK és programari lliure i aquí no som neutrals. En aquesta escala, les valoracions i les fotos de negocis corrents només existeixen a Google, i això és un monopoli. El camp hi és per manca d'alternativa, no perquè el recomanem. Llavors cada consulta va a Google.", + 'admin.trekApi.badgeSoon': + 'Aviat', + 'admin.trekApi.badgeActive': + 'Actiu', + 'admin.trekApi.tagline': + 'L\'índex de llocs propi de TREK. Cercar sense clau de Google, sense quota i sense que ningú compti les teves cerques.', + 'admin.trekApi.factPlaces': + '73,6 milions de llocs al món', + 'admin.trekApi.factNoKey': + 'Sense clau, sense quota', + 'admin.trekApi.factOffline': + 'Els paquets per país funcionen sense connexió', + 'admin.trekApi.factPrivacy': + 'Les cerques no es registren mai', + 'admin.trekApi.more': + 'Què hi ha a dins', + 'admin.trekApi.fieldPhone': + 'Telèfon', + 'admin.trekApi.fieldStableId': + 'Identificador estable', + 'admin.trekApi.includedNote': + 'Les descripcions vénen del web del lloc mateix; els horaris, d\'OpenStreetMap allà on hi són.', + 'admin.trekApi.notRatings': + 'Valoracions', + 'admin.trekApi.notPhotos': + 'Fotos de negocis corrents', + 'admin.trekApi.notIncludedNote': + 'Cap font oberta no té ni les unes ni les altres, a cap preu. Per a aquestes dues una clau de Google continua sent l\'únic camí.', + 'admin.trekApi.sourcesLabel': + 'Fonts', + 'admin.trekApi.sourcesNote': + 'Cada camp d\'una resposta diu de quina d\'elles ve.', + 'admin.trekApi.included': + 'Inclòs', + 'admin.trekApi.notIncluded': + 'No inclòs', 'admin.mapsKey': 'Clau API de Google Maps', 'admin.mapsKeyHint': 'Obligatòria per cercar llocs. Aconsegueix-la a console.cloud.google.com', 'admin.mapsKeyHintLong': - "Sense una clau API, la cerca de llocs fa servir OpenStreetMap. Amb una clau de Google també es poden carregar fotos, valoracions i horaris d'obertura. Aconsegueix-la a console.cloud.google.com.", + "Sense clau d'API de Google s'utilitza l'API de TREK recomanada. Amb una clau es poden carregar a més fotos, valoracions i horaris. Crea-la a console.cloud.google.com.", 'admin.recommended': 'Recomanat', 'admin.weatherKey': "Clau API d'OpenWeatherMap", 'admin.weatherKeyHint': 'Per a dades meteorològiques. Gratuït a openweathermap.org', @@ -116,6 +156,8 @@ const admin: TranslationStrings = { 'admin.fileTypesFormat': 'Extensions separades per comes (p. ex. jpg,png,pdf,doc). Utilitza * per permetre tots els tipus.', 'admin.fileTypesSaved': 'Ajustos de tipus de fitxer desats', + 'admin.googleOptions': 'Per a què s\'utilitza la clau', + 'admin.googleOptionsSummary': '{on} de {total} actius', 'admin.placesPhotos.title': 'Fotos de Llocs', 'admin.placesPhotos.subtitle': "Obtén fotos de la Google Places API. Desactiva per estalviar quota d'API. Les fotos de Wikimedia no es veuen afectades.", diff --git a/shared/src/i18n/cs/admin.ts b/shared/src/i18n/cs/admin.ts index 6cca82bd5..42873a79a 100644 --- a/shared/src/i18n/cs/admin.ts +++ b/shared/src/i18n/cs/admin.ts @@ -86,11 +86,51 @@ const admin: TranslationStrings = { 'admin.requireMfa': 'Vyžadovat dvoufázové ověření (2FA)', 'admin.requireMfaHint': 'Uživatelé bez 2FA musí dokončit nastavení v Nastavení před použitím aplikace.', 'admin.apiKeys': 'API klíče', - 'admin.apiKeysHint': 'Volitelné. Povoluje rozšířená data o místech (fotky, počasí).', + 'admin.apiKeysHint': 'Odkud pocházejí data míst. Index TREK nepotřebuje klíč; dva poskytovatelé níže jsou volitelní.', + 'admin.trekApi.badgeDefault': 'Doporučený výchozí zdroj', + 'admin.googleCaveat.badge': 'Nedoporučeno', + 'admin.googleCaveat.body': + 'TREK je otevřený software a nejsme v tom neutrální. V tomto měřítku existují hodnocení a fotografie běžných podniků jen u Googlu, a to je monopol. Pole tu je z nedostatku alternativy, ne proto, že bychom je doporučovali. Každý dotaz pak jde do Googlu.', + 'admin.trekApi.badgeSoon': + 'Brzy', + 'admin.trekApi.badgeActive': + 'Aktivní', + 'admin.trekApi.tagline': + 'Vlastní rejstřík míst TREKu. Hledání bez klíče od Googlu, bez kvóty a bez toho, aby někdo počítal vaše dotazy.', + 'admin.trekApi.factPlaces': + '73,6 milionu míst po celém světě', + 'admin.trekApi.factNoKey': + 'Bez klíče, bez kvóty', + 'admin.trekApi.factOffline': + 'Balíčky zemí fungují offline', + 'admin.trekApi.factPrivacy': + 'Dotazy se nikdy nezaznamenávají', + 'admin.trekApi.more': + 'Co v tom je', + 'admin.trekApi.fieldPhone': + 'Telefon', + 'admin.trekApi.fieldStableId': + 'Stálý identifikátor', + 'admin.trekApi.includedNote': + 'Popisy pocházejí z webu samotného místa, otevírací doba z OpenStreetMap tam, kde je vyplněná.', + 'admin.trekApi.notRatings': + 'Hodnocení', + 'admin.trekApi.notPhotos': + 'Fotografie běžných podniků', + 'admin.trekApi.notIncludedNote': + 'Ani jedno nemá žádný otevřený zdroj, za žádnou cenu. K těmto dvěma zůstává klíč od Googlu jedinou cestou.', + 'admin.trekApi.sourcesLabel': + 'Zdroje', + 'admin.trekApi.sourcesNote': + 'Každé pole v odpovědi uvádí, ze kterého z nich pochází.', + 'admin.trekApi.included': + 'Obsahuje', + 'admin.trekApi.notIncluded': + 'Neobsahuje', 'admin.mapsKey': 'Google Maps API klíč', 'admin.mapsKeyHint': 'Povinné pro hledání míst. Získáte na console.cloud.google.com', 'admin.mapsKeyHintLong': - 'Bez API klíče se pro hledání používá OpenStreetMap. S Google klíčem lze načítat fotky, hodnocení a otevírací dobu.', + 'Bez klíče Google API se používá doporučené API TREK. S klíčem lze navíc načíst fotografie, hodnocení a otevírací dobu. Klíč vytvoříte na console.cloud.google.com.', 'admin.recommended': 'Doporučeno', 'admin.weatherKey': 'OpenWeatherMap API klíč', 'admin.weatherKeyHint': 'Pro data o počasí. Zdarma na openweathermap.org', @@ -113,6 +153,8 @@ const admin: TranslationStrings = { 'admin.fileTypesHint': 'Nastavte, které typy souborů mohou uživatelé nahrávat.', 'admin.fileTypesFormat': 'Přípony oddělené čárkou (např. jpg,png,pdf,doc). Použijte * pro všechny typy.', 'admin.fileTypesSaved': 'Nastavení souborů uloženo', + 'admin.googleOptions': 'K čemu se klíč používá', + 'admin.googleOptionsSummary': '{on} ze {total} zapnuto', 'admin.placesPhotos.title': 'Fotografie míst', 'admin.placesPhotos.subtitle': 'Načítání fotografií z Google Places API. Zakázáním ušetříte kvótu API. Fotografie z Wikimedia nejsou ovlivněny.', diff --git a/shared/src/i18n/de/admin.ts b/shared/src/i18n/de/admin.ts index c3debb0b9..8cb2def9a 100644 --- a/shared/src/i18n/de/admin.ts +++ b/shared/src/i18n/de/admin.ts @@ -88,11 +88,51 @@ const admin: TranslationStrings = { 'admin.requireMfaHint': 'Benutzer ohne 2FA müssen die Einrichtung unter Einstellungen abschließen, bevor sie die App nutzen können.', 'admin.apiKeys': 'API-Schlüssel', - 'admin.apiKeysHint': 'Optional. Aktiviert erweiterte Ortsdaten wie Fotos und Wetter.', + 'admin.apiKeysHint': 'Woher die Ortsdaten kommen. Der TREK-Index braucht keinen Schlüssel, die beiden Anbieter darunter sind optional.', + 'admin.trekApi.badgeDefault': 'Empfohlene Standardquelle', + 'admin.googleCaveat.badge': 'Nicht empfohlen', + 'admin.googleCaveat.body': + 'TREK ist quelloffen und wir sind hier nicht neutral. Bewertungen und Fotos gewöhnlicher Läden gibt es in dieser Größenordnung nur bei Google, und genau das ist ein Monopol. Das Feld steht hier, weil es keine Alternative gibt, nicht weil wir es empfehlen. Jede Abfrage geht dann an Google.', + 'admin.trekApi.badgeSoon': + 'Bald', + 'admin.trekApi.badgeActive': + 'Aktiv', + 'admin.trekApi.tagline': + 'TREKs eigener Ortsindex. Suchen ohne Google-Schlüssel, ohne Kontingent und ohne dass jemand mitzählt.', + 'admin.trekApi.factPlaces': + '73,6 Millionen Orte weltweit', + 'admin.trekApi.factNoKey': + 'Kein Schlüssel, kein Kontingent', + 'admin.trekApi.factOffline': + 'Länderpakete auch offline', + 'admin.trekApi.factPrivacy': + 'Suchanfragen werden nie protokolliert', + 'admin.trekApi.more': + 'Was drin ist', + 'admin.trekApi.fieldPhone': + 'Telefon', + 'admin.trekApi.fieldStableId': + 'Stabile Kennung', + 'admin.trekApi.includedNote': + 'Beschreibungen kommen von der Website des Ortes selbst, Öffnungszeiten aus OpenStreetMap, wo sie eingetragen sind.', + 'admin.trekApi.notRatings': + 'Bewertungen', + 'admin.trekApi.notPhotos': + 'Fotos gewöhnlicher Läden', + 'admin.trekApi.notIncludedNote': + 'Beides gibt es in keiner offenen Quelle, zu keinem Preis. Dafür bleibt ein Google-Schlüssel der einzige Weg.', + 'admin.trekApi.sourcesLabel': + 'Quellen', + 'admin.trekApi.sourcesNote': + 'Jedes Feld einer Antwort nennt, aus welcher davon es stammt.', + 'admin.trekApi.included': + 'Enthalten', + 'admin.trekApi.notIncluded': + 'Nicht enthalten', 'admin.mapsKey': 'Google Maps API-Schlüssel', 'admin.mapsKeyHint': 'Für Ortsuche benötigt. Erstellen unter console.cloud.google.com', 'admin.mapsKeyHintLong': - 'Ohne API Key wird OpenStreetMap für die Ortssuche genutzt. Mit Google API Key können zusätzlich Bilder, Bewertungen und Öffnungszeiten geladen werden. Erstellen unter console.cloud.google.com.', + 'Auch ohne Google-API-Schlüssel wird die empfohlene TREK API verwendet. Mit Schlüssel können zusätzlich Bilder, Bewertungen und Öffnungszeiten geladen werden. Erstellen unter console.cloud.google.com.', 'admin.recommended': 'Empfohlen', 'admin.weatherKey': 'OpenWeatherMap API-Schlüssel', 'admin.weatherKeyHint': 'Für Wetterdaten. Kostenlos unter openweathermap.org', @@ -115,6 +155,8 @@ const admin: TranslationStrings = { 'admin.fileTypesHint': 'Konfiguriere welche Dateitypen hochgeladen werden dürfen.', 'admin.fileTypesFormat': 'Kommagetrennte Endungen (z.B. jpg,png,pdf,doc). Verwende * um alle Typen zu erlauben.', 'admin.fileTypesSaved': 'Dateityp-Einstellungen gespeichert', + 'admin.googleOptions': 'Wofür der Schlüssel genutzt wird', + 'admin.googleOptionsSummary': '{on} von {total} an', 'admin.placesPhotos.title': 'Ortsfotos', 'admin.placesPhotos.subtitle': 'Fotos von der Google Places API laden. Deaktivieren, um API-Kontingent zu sparen. Wikimedia-Fotos sind davon nicht betroffen.', diff --git a/shared/src/i18n/en/admin.ts b/shared/src/i18n/en/admin.ts index 4bbb276a6..9a5b9bd96 100644 --- a/shared/src/i18n/en/admin.ts +++ b/shared/src/i18n/en/admin.ts @@ -128,11 +128,51 @@ const admin: TranslationStrings = { 'admin.requireMfa': 'Require two-factor authentication (2FA)', 'admin.requireMfaHint': 'Users without 2FA must complete setup in Settings before using the app.', 'admin.apiKeys': 'API Keys', - 'admin.apiKeysHint': 'Optional. Enables extended place data like photos and weather.', + 'admin.apiKeysHint': 'Where place data comes from. The TREK index needs no key; the two providers below are optional.', + 'admin.trekApi.badgeDefault': 'Recommended default', + 'admin.googleCaveat.badge': 'Not recommended', + 'admin.googleCaveat.body': + 'TREK is open source and we are not neutral here. Ratings and photos of ordinary businesses exist at this scale only at Google, and that is what a monopoly is. The field is here because there is no alternative, not because we recommend it. Every lookup then goes to Google.', + 'admin.trekApi.badgeSoon': + 'Coming', + 'admin.trekApi.badgeActive': + 'Active', + 'admin.trekApi.tagline': + 'TREK\'s own place index. Search without a Google key, without a quota and without anyone counting your lookups.', + 'admin.trekApi.factPlaces': + '73.6 million places worldwide', + 'admin.trekApi.factNoKey': + 'No key, no quota', + 'admin.trekApi.factOffline': + 'Country packages work offline', + 'admin.trekApi.factPrivacy': + 'Searches are never logged', + 'admin.trekApi.more': + 'What is in it', + 'admin.trekApi.fieldPhone': + 'Phone', + 'admin.trekApi.fieldStableId': + 'Stable id', + 'admin.trekApi.includedNote': + 'Descriptions come from the place\'s own website; opening hours from OpenStreetMap where they are tagged.', + 'admin.trekApi.notRatings': + 'Ratings', + 'admin.trekApi.notPhotos': + 'Photos of ordinary businesses', + 'admin.trekApi.notIncludedNote': + 'No open dataset has either, at any price. A Google key stays the only way to those two.', + 'admin.trekApi.sourcesLabel': + 'Sources', + 'admin.trekApi.sourcesNote': + 'Every field in a response says which of these it came from.', + 'admin.trekApi.included': + 'Included', + 'admin.trekApi.notIncluded': + 'Not included', 'admin.mapsKey': 'Google Maps API Key', 'admin.mapsKeyHint': 'Required for place search. Get at console.cloud.google.com', 'admin.mapsKeyHintLong': - 'Without an API key, OpenStreetMap is used for place search. With a Google API key, photos, ratings, and opening hours can be loaded as well. Get one at console.cloud.google.com.', + 'Without a Google API key the recommended TREK API is used. With one, photos, ratings and opening hours can be loaded on top. Create a key at console.cloud.google.com.', 'admin.recommended': 'Recommended', 'admin.weatherKey': 'OpenWeatherMap API Key', 'admin.weatherKeyHint': 'For weather data. Free at openweathermap.org', @@ -155,6 +195,8 @@ const admin: TranslationStrings = { 'admin.fileTypesHint': 'Configure which file types users can upload.', 'admin.fileTypesFormat': 'Comma-separated extensions (e.g. jpg,png,pdf,doc). Use * to allow all types.', 'admin.fileTypesSaved': 'File type settings saved', + 'admin.googleOptions': 'What the key may be used for', + 'admin.googleOptionsSummary': '{on} of {total} on', 'admin.placesPhotos.title': 'Place Photos', 'admin.placesPhotos.subtitle': 'Fetch photos from the Google Places API. Disable to save API quota. Wikimedia photos are unaffected.', diff --git a/shared/src/i18n/es/admin.ts b/shared/src/i18n/es/admin.ts index 47a326d2a..118457cbb 100644 --- a/shared/src/i18n/es/admin.ts +++ b/shared/src/i18n/es/admin.ts @@ -88,11 +88,51 @@ const admin: TranslationStrings = { 'admin.requireMfaHint': 'Los usuarios sin 2FA deben completar la configuración en Ajustes antes de usar la aplicación.', 'admin.apiKeys': 'Claves API', - 'admin.apiKeysHint': 'Opcional. Activa datos ampliados de lugares, como fotos y previsión del tiempo.', + 'admin.apiKeysHint': 'De dónde vienen los datos de lugares. El índice de TREK no necesita clave; los dos proveedores de abajo son opcionales.', + 'admin.trekApi.badgeDefault': 'Fuente recomendada por defecto', + 'admin.googleCaveat.badge': 'No recomendado', + 'admin.googleCaveat.body': + 'TREK es software libre y aquí no somos neutrales. A esta escala, las valoraciones y las fotos de negocios corrientes solo existen en Google, y eso es un monopolio. El campo está aquí por falta de alternativa, no porque lo recomendemos. Cada consulta va entonces a Google.', + 'admin.trekApi.badgeSoon': + 'Pronto', + 'admin.trekApi.badgeActive': + 'Activo', + 'admin.trekApi.tagline': + 'El índice de lugares propio de TREK. Buscar sin clave de Google, sin cuota y sin que nadie cuente tus búsquedas.', + 'admin.trekApi.factPlaces': + '73,6 millones de lugares en el mundo', + 'admin.trekApi.factNoKey': + 'Sin clave, sin cuota', + 'admin.trekApi.factOffline': + 'Los paquetes por país funcionan sin conexión', + 'admin.trekApi.factPrivacy': + 'Las búsquedas nunca se registran', + 'admin.trekApi.more': + 'Qué contiene', + 'admin.trekApi.fieldPhone': + 'Teléfono', + 'admin.trekApi.fieldStableId': + 'Identificador estable', + 'admin.trekApi.includedNote': + 'Las descripciones vienen de la web del propio lugar; los horarios, de OpenStreetMap donde estén puestos.', + 'admin.trekApi.notRatings': + 'Valoraciones', + 'admin.trekApi.notPhotos': + 'Fotos de negocios corrientes', + 'admin.trekApi.notIncludedNote': + 'Ninguna fuente abierta tiene ninguna de las dos, a ningún precio. Una clave de Google sigue siendo el único camino.', + 'admin.trekApi.sourcesLabel': + 'Fuentes', + 'admin.trekApi.sourcesNote': + 'Cada campo de una respuesta dice de cuál de ellas viene.', + 'admin.trekApi.included': + 'Incluido', + 'admin.trekApi.notIncluded': + 'No incluido', 'admin.mapsKey': 'Clave API de Google Maps', 'admin.mapsKeyHint': 'Obligatoria para buscar lugares. Consíguela en console.cloud.google.com', 'admin.mapsKeyHintLong': - 'Sin una clave API, la búsqueda de lugares usa OpenStreetMap. Con una clave de Google también se pueden cargar fotos, valoraciones y horarios de apertura. Consíguela en console.cloud.google.com.', + 'Sin clave de API de Google se usa la API de TREK recomendada. Con una clave se pueden cargar además fotos, valoraciones y horarios. Créala en console.cloud.google.com.', 'admin.recommended': 'Recomendado', 'admin.weatherKey': 'Clave API de OpenWeatherMap', 'admin.weatherKeyHint': 'Para datos meteorológicos. Gratis en openweathermap.org', @@ -114,6 +154,8 @@ const admin: TranslationStrings = { 'admin.fileTypesFormat': 'Extensiones separadas por comas (p. ej. jpg,png,pdf,doc). Usa * para permitir todos los tipos.', 'admin.fileTypesSaved': 'Ajustes de tipos de archivo guardados', + 'admin.googleOptions': 'Para qué se usa la clave', + 'admin.googleOptionsSummary': '{on} de {total} activados', 'admin.placesPhotos.title': 'Fotos de Lugares', 'admin.placesPhotos.subtitle': 'Obtiene fotos de la Google Places API. Desactiva para ahorrar cuota de API. Las fotos de Wikimedia no se ven afectadas.', diff --git a/shared/src/i18n/fr/admin.ts b/shared/src/i18n/fr/admin.ts index 7fc42a24c..a38f09497 100644 --- a/shared/src/i18n/fr/admin.ts +++ b/shared/src/i18n/fr/admin.ts @@ -87,11 +87,51 @@ const admin: TranslationStrings = { 'admin.requireMfaHint': "Les utilisateurs sans 2FA doivent terminer la configuration dans Paramètres avant d'utiliser l'application.", 'admin.apiKeys': 'Clés API', - 'admin.apiKeysHint': 'Facultatif. Active les données de lieu étendues comme les photos et la météo.', + 'admin.apiKeysHint': "D'où viennent les données de lieux. L'index TREK ne demande aucune clé ; les deux fournisseurs ci-dessous sont facultatifs.", + 'admin.trekApi.badgeDefault': 'Source recommandée par défaut', + 'admin.googleCaveat.badge': 'Déconseillé', + 'admin.googleCaveat.body': + "TREK est un logiciel libre et nous ne sommes pas neutres ici. À cette échelle, les avis et les photos de commerces ordinaires n'existent que chez Google, et c'est cela, un monopole. Le champ est là faute d'alternative, pas parce que nous le recommandons. Chaque requête part alors chez Google.", + 'admin.trekApi.badgeSoon': + 'Bientôt', + 'admin.trekApi.badgeActive': + 'Actif', + 'admin.trekApi.tagline': + 'L\'index de lieux propre à TREK. Chercher sans clé Google, sans quota et sans que personne ne compte vos recherches.', + 'admin.trekApi.factPlaces': + '73,6 millions de lieux dans le monde', + 'admin.trekApi.factNoKey': + 'Sans clé, sans quota', + 'admin.trekApi.factOffline': + 'Paquets par pays utilisables hors ligne', + 'admin.trekApi.factPrivacy': + 'Les recherches ne sont jamais journalisées', + 'admin.trekApi.more': + 'Ce qu\'il contient', + 'admin.trekApi.fieldPhone': + 'Téléphone', + 'admin.trekApi.fieldStableId': + 'Identifiant stable', + 'admin.trekApi.includedNote': + 'Les descriptions viennent du site du lieu lui-même ; les horaires d\'OpenStreetMap là où ils sont renseignés.', + 'admin.trekApi.notRatings': + 'Avis', + 'admin.trekApi.notPhotos': + 'Photos des commerces ordinaires', + 'admin.trekApi.notIncludedNote': + 'Aucune source ouverte ne propose l\'un ni l\'autre, à aucun prix. Une clé Google reste le seul chemin vers ces deux-là.', + 'admin.trekApi.sourcesLabel': + 'Sources', + 'admin.trekApi.sourcesNote': + 'Chaque champ d\'une réponse indique de laquelle il provient.', + 'admin.trekApi.included': + 'Inclus', + 'admin.trekApi.notIncluded': + 'Non inclus', 'admin.mapsKey': 'Clé API Google Maps', 'admin.mapsKeyHint': 'Requise pour la recherche de lieux. Obtenez-la sur console.cloud.google.com', 'admin.mapsKeyHintLong': - "Sans clé API, OpenStreetMap est utilisé pour la recherche de lieux. Avec une clé Google API, les photos, notes et horaires d'ouverture peuvent également être chargés. Obtenez-en une sur console.cloud.google.com.", + "Sans clé API Google, l'API TREK recommandée est utilisée. Avec une clé, les photos, les avis et les horaires peuvent être chargés en plus. Créez-en une sur console.cloud.google.com.", 'admin.recommended': 'Recommandé', 'admin.weatherKey': 'Clé API OpenWeatherMap', 'admin.weatherKeyHint': 'Pour les données météo. Gratuit sur openweathermap.org', @@ -116,6 +156,8 @@ const admin: TranslationStrings = { 'admin.fileTypesFormat': 'Extensions séparées par des virgules (ex. jpg,png,pdf,doc). Utilisez * pour autoriser tous les types.', 'admin.fileTypesSaved': 'Paramètres des types de fichiers enregistrés', + 'admin.googleOptions': 'Ce à quoi sert la clé', + 'admin.googleOptionsSummary': '{on} sur {total} activés', 'admin.placesPhotos.title': 'Photos de lieux', 'admin.placesPhotos.subtitle': "Récupère les photos depuis l'API Google Places. Désactivez pour économiser le quota API. Les photos Wikimedia ne sont pas affectées.", diff --git a/shared/src/i18n/gr/admin.ts b/shared/src/i18n/gr/admin.ts index 36f7e0be6..ec4bd4c12 100644 --- a/shared/src/i18n/gr/admin.ts +++ b/shared/src/i18n/gr/admin.ts @@ -133,11 +133,51 @@ const admin: TranslationStrings = { 'admin.requireMfaHint': 'Οι χρήστες χωρίς 2FA πρέπει να ολοκληρώσουν τη ρύθμιση στις Ρυθμίσεις πριν χρησιμοποιήσουν την εφαρμογή.', 'admin.apiKeys': 'Κλειδιά API', - 'admin.apiKeysHint': 'Προαιρετικά. Ενεργοποιεί εκτεταμένα δεδομένα τόπων όπως φωτογραφίες και καιρό.', + 'admin.apiKeysHint': 'Από πού προέρχονται τα δεδομένα τόπων. Το ευρετήριο του TREK δεν χρειάζεται κλειδί· οι δύο πάροχοι παρακάτω είναι προαιρετικοί.', + 'admin.trekApi.badgeDefault': 'Προτεινόμενη προεπιλογή', + 'admin.googleCaveat.badge': 'Δεν συνιστάται', + 'admin.googleCaveat.body': + 'Το TREK είναι ανοιχτού κώδικα και εδώ δεν είμαστε ουδέτεροι. Σε αυτή την κλίμακα, βαθμολογίες και φωτογραφίες συνηθισμένων καταστημάτων υπάρχουν μόνο στη Google, και αυτό είναι μονοπώλιο. Το πεδίο υπάρχει ελλείψει εναλλακτικής, όχι επειδή το συνιστούμε. Τότε κάθε αναζήτηση πηγαίνει στη Google.', + 'admin.trekApi.badgeSoon': + 'Σύντομα', + 'admin.trekApi.badgeActive': + 'Ενεργό', + 'admin.trekApi.tagline': + 'Το δικό του ευρετήριο τόπων του TREK. Αναζήτηση χωρίς κλειδί Google, χωρίς όριο και χωρίς να μετράει κανείς τις αναζητήσεις σας.', + 'admin.trekApi.factPlaces': + '73,6 εκατομμύρια τόποι παγκοσμίως', + 'admin.trekApi.factNoKey': + 'Χωρίς κλειδί, χωρίς όριο', + 'admin.trekApi.factOffline': + 'Τα πακέτα χωρών δουλεύουν και εκτός σύνδεσης', + 'admin.trekApi.factPrivacy': + 'Οι αναζητήσεις δεν καταγράφονται ποτέ', + 'admin.trekApi.more': + 'Τι περιλαμβάνει', + 'admin.trekApi.fieldPhone': + 'Τηλέφωνο', + 'admin.trekApi.fieldStableId': + 'Σταθερό αναγνωριστικό', + 'admin.trekApi.includedNote': + 'Οι περιγραφές έρχονται από τον ιστότοπο του ίδιου του τόπου, οι ώρες λειτουργίας από το OpenStreetMap όπου έχουν καταχωρηθεί.', + 'admin.trekApi.notRatings': + 'Βαθμολογίες', + 'admin.trekApi.notPhotos': + 'Φωτογραφίες συνηθισμένων καταστημάτων', + 'admin.trekApi.notIncludedNote': + 'Καμία ανοιχτή πηγή δεν έχει ούτε το ένα ούτε το άλλο, σε καμία τιμή. Για αυτά τα δύο το κλειδί Google παραμένει ο μόνος δρόμος.', + 'admin.trekApi.sourcesLabel': + 'Πηγές', + 'admin.trekApi.sourcesNote': + 'Κάθε πεδίο μιας απάντησης λέει από ποια από αυτές προέρχεται.', + 'admin.trekApi.included': + 'Περιλαμβάνεται', + 'admin.trekApi.notIncluded': + 'Δεν περιλαμβάνεται', 'admin.mapsKey': 'Google Maps API Key', 'admin.mapsKeyHint': 'Απαιτείται για αναζήτηση τόπων. Λάβετε το από console.cloud.google.com', 'admin.mapsKeyHintLong': - 'Χωρίς κλειδί API, χρησιμοποιείται το OpenStreetMap για αναζήτηση τόπων. Με κλειδί Google API, μπορούν επίσης να φορτωθούν φωτογραφίες, βαθμολογίες και ώρες λειτουργίας. Λάβετε ένα στο console.cloud.google.com.', + 'Χωρίς κλειδί Google API χρησιμοποιείται το προτεινόμενο TREK API. Με κλειδί μπορούν επιπλέον να φορτωθούν φωτογραφίες, βαθμολογίες και ώρες λειτουργίας. Δημιουργήστε ένα στο console.cloud.google.com.', 'admin.recommended': 'Συνιστάται', 'admin.weatherKey': 'OpenWeatherMap API Key', 'admin.weatherKeyHint': 'Για δεδομένα καιρού. Δωρεάν στο openweathermap.org', @@ -161,6 +201,8 @@ const admin: TranslationStrings = { 'admin.fileTypesFormat': 'Επεκτάσεις χωρισμένες με κόμμα (π.χ. jpg,png,pdf,doc). Χρησιμοποιήστε * για να επιτρέπονται όλοι οι τύποι.', 'admin.fileTypesSaved': 'Οι ρυθμίσεις τύπων αρχείων αποθηκεύτηκαν', + 'admin.googleOptions': 'Για τι χρησιμοποιείται το κλειδί', + 'admin.googleOptionsSummary': '{on} από {total} ενεργά', 'admin.placesPhotos.title': 'Φωτογραφίες Τόπων', 'admin.placesPhotos.subtitle': 'Λήψη φωτογραφιών από το Google Places API. Απενεργοποιήστε για εξοικονόμηση ποσοστώσης API. Οι φωτογραφίες Wikimedia δεν επηρεάζονται.', diff --git a/shared/src/i18n/hu/admin.ts b/shared/src/i18n/hu/admin.ts index e55bc61ef..4314e4c30 100644 --- a/shared/src/i18n/hu/admin.ts +++ b/shared/src/i18n/hu/admin.ts @@ -87,11 +87,51 @@ const admin: TranslationStrings = { 'admin.requireMfaHint': 'A 2FA nélküli felhasználóknak a Beállításokban kell befejezniük a beállítást az alkalmazás használata előtt.', 'admin.apiKeys': 'API kulcsok', - 'admin.apiKeysHint': 'Opcionális. Bővített helyadatokat tesz lehetővé, például fotókat és időjárást.', + 'admin.apiKeysHint': 'Honnan jönnek a helyadatok. A TREK-indexhez nem kell kulcs; az alábbi két szolgáltató nem kötelező.', + 'admin.trekApi.badgeDefault': 'Ajánlott alapértelmezés', + 'admin.googleCaveat.badge': 'Nem ajánlott', + 'admin.googleCaveat.body': + 'A TREK nyílt forráskódú, és ebben nem vagyunk semlegesek. Ebben a méretben az értékelések és a hétköznapi üzletek fotói csak a Google-nél léteznek, és ez a monopólium. A mező alternatíva híján van itt, nem mert ajánljuk. Minden lekérdezés ilyenkor a Google-höz megy.', + 'admin.trekApi.badgeSoon': + 'Hamarosan', + 'admin.trekApi.badgeActive': + 'Aktív', + 'admin.trekApi.tagline': + 'A TREK saját helyindexe. Keresés Google-kulcs nélkül, kvóta nélkül, és anélkül, hogy bárki számolná a lekérdezéseidet.', + 'admin.trekApi.factPlaces': + '73,6 millió hely világszerte', + 'admin.trekApi.factNoKey': + 'Nincs kulcs, nincs kvóta', + 'admin.trekApi.factOffline': + 'Az országcsomagok offline is működnek', + 'admin.trekApi.factPrivacy': + 'A kereséseket soha nem naplózzuk', + 'admin.trekApi.more': + 'Mi van benne', + 'admin.trekApi.fieldPhone': + 'Telefon', + 'admin.trekApi.fieldStableId': + 'Állandó azonosító', + 'admin.trekApi.includedNote': + 'A leírások a hely saját webhelyéről jönnek, a nyitvatartás az OpenStreetMapből, ahol be van írva.', + 'admin.trekApi.notRatings': + 'Értékelések', + 'admin.trekApi.notPhotos': + 'Hétköznapi üzletek fotói', + 'admin.trekApi.notIncludedNote': + 'Egyiket sem kínálja nyílt forrás, semmilyen áron. Ehhez a kettőhöz a Google-kulcs marad az egyetlen út.', + 'admin.trekApi.sourcesLabel': + 'Források', + 'admin.trekApi.sourcesNote': + 'A válasz minden mezője megmondja, melyikükből származik.', + 'admin.trekApi.included': + 'Tartalmazza', + 'admin.trekApi.notIncluded': + 'Nem tartalmazza', 'admin.mapsKey': 'Google Maps API kulcs', 'admin.mapsKeyHint': 'Helykereséshez szükséges. Létrehozás: console.cloud.google.com', 'admin.mapsKeyHintLong': - 'API kulcs nélkül az OpenStreetMap szolgál helykeresésre. Google API kulccsal képek, értékelések és nyitvatartás is betölthetők. Létrehozás: console.cloud.google.com.', + 'Google API-kulcs nélkül az ajánlott TREK API-t használjuk. Kulccsal ezen felül fotók, értékelések és nyitvatartás is betölthető. Kulcsot a console.cloud.google.com oldalon hozhatsz létre.', 'admin.recommended': 'Ajánlott', 'admin.weatherKey': 'OpenWeatherMap API kulcs', 'admin.weatherKeyHint': 'Időjárás adatokhoz. Ingyenes: openweathermap.org', @@ -115,6 +155,8 @@ const admin: TranslationStrings = { 'admin.fileTypesFormat': 'Vesszővel elválasztott kiterjesztések (pl. jpg,png,pdf,doc). Használj *-ot az összes típus engedélyezéséhez.', 'admin.fileTypesSaved': 'Fájltípus-beállítások mentve', + 'admin.googleOptions': 'Mire használja a kulcsot', + 'admin.googleOptionsSummary': '{on} / {total} bekapcsolva', 'admin.placesPhotos.title': 'Helyfotók', 'admin.placesPhotos.subtitle': 'Fotók lekérése a Google Places API-ból. Tiltsa le az API-kvóta megtakarításához. A Wikimedia-fotók nem érintettek.', diff --git a/shared/src/i18n/id/admin.ts b/shared/src/i18n/id/admin.ts index 4cb7d3047..225d215eb 100644 --- a/shared/src/i18n/id/admin.ts +++ b/shared/src/i18n/id/admin.ts @@ -129,11 +129,51 @@ const admin: TranslationStrings = { 'admin.requireMfaHint': 'Pengguna tanpa 2FA harus menyelesaikan pengaturan di Pengaturan sebelum menggunakan aplikasi.', 'admin.apiKeys': 'Kunci API', - 'admin.apiKeysHint': 'Opsional. Mengaktifkan data tempat yang lebih lengkap seperti foto dan cuaca.', + 'admin.apiKeysHint': 'Dari mana data tempat berasal. Indeks TREK tidak butuh kunci; dua penyedia di bawah bersifat opsional.', + 'admin.trekApi.badgeDefault': 'Sumber baku yang disarankan', + 'admin.googleCaveat.badge': 'Tidak disarankan', + 'admin.googleCaveat.body': + 'TREK adalah perangkat lunak sumber terbuka dan di sini kami tidak netral. Pada skala ini ulasan dan foto usaha biasa hanya ada di Google, dan itulah monopoli. Kolom ini ada karena tidak ada penggantinya, bukan karena kami menyarankan. Lalu setiap pencarian pergi ke Google.', + 'admin.trekApi.badgeSoon': + 'Segera', + 'admin.trekApi.badgeActive': + 'Aktif', + 'admin.trekApi.tagline': + 'Indeks tempat milik TREK sendiri. Mencari tanpa kunci Google, tanpa kuota, dan tanpa ada yang menghitung pencarian Anda.', + 'admin.trekApi.factPlaces': + '73,6 juta tempat di seluruh dunia', + 'admin.trekApi.factNoKey': + 'Tanpa kunci, tanpa kuota', + 'admin.trekApi.factOffline': + 'Paket per negara jalan offline', + 'admin.trekApi.factPrivacy': + 'Pencarian tidak pernah dicatat', + 'admin.trekApi.more': + 'Apa isinya', + 'admin.trekApi.fieldPhone': + 'Telepon', + 'admin.trekApi.fieldStableId': + 'Id tetap', + 'admin.trekApi.includedNote': + 'Deskripsi berasal dari situs tempat itu sendiri, jam buka dari OpenStreetMap di mana sudah diisi.', + 'admin.trekApi.notRatings': + 'Ulasan', + 'admin.trekApi.notPhotos': + 'Foto usaha biasa', + 'admin.trekApi.notIncludedNote': + 'Tidak ada sumber terbuka yang punya keduanya, dengan harga berapa pun. Untuk keduanya kunci Google tetap satu-satunya jalan.', + 'admin.trekApi.sourcesLabel': + 'Sumber', + 'admin.trekApi.sourcesNote': + 'Setiap ruas dalam respons menyebutkan berasal dari yang mana.', + 'admin.trekApi.included': + 'Termasuk', + 'admin.trekApi.notIncluded': + 'Tidak termasuk', 'admin.mapsKey': 'Kunci API Google Maps', 'admin.mapsKeyHint': 'Diperlukan untuk pencarian tempat. Dapatkan di console.cloud.google.com', 'admin.mapsKeyHintLong': - 'Tanpa kunci API, OpenStreetMap digunakan untuk pencarian tempat. Dengan kunci API Google, foto, rating, dan jam buka juga bisa dimuat. Dapatkan di console.cloud.google.com.', + 'Tanpa kunci API Google, TREK API yang disarankan akan dipakai. Dengan kunci, foto, ulasan, dan jam buka bisa dimuat sebagai tambahan. Buat kunci di console.cloud.google.com.', 'admin.recommended': 'Direkomendasikan', 'admin.weatherKey': 'Kunci API OpenWeatherMap', 'admin.weatherKeyHint': 'Untuk data cuaca. Gratis di openweathermap.org', @@ -157,6 +197,8 @@ const admin: TranslationStrings = { 'admin.fileTypesFormat': 'Ekstensi dipisahkan koma (contoh: jpg,png,pdf,doc). Gunakan * untuk mengizinkan semua jenis.', 'admin.fileTypesSaved': 'Pengaturan jenis file disimpan', + 'admin.googleOptions': 'Kunci ini dipakai untuk apa', + 'admin.googleOptionsSummary': '{on} dari {total} aktif', 'admin.placesPhotos.title': 'Foto Tempat', 'admin.placesPhotos.subtitle': 'Mengambil foto dari Google Places API. Nonaktifkan untuk menghemat kuota API. Foto Wikimedia tidak terpengaruh.', diff --git a/shared/src/i18n/it/admin.ts b/shared/src/i18n/it/admin.ts index 612cd081a..2fffc55c3 100644 --- a/shared/src/i18n/it/admin.ts +++ b/shared/src/i18n/it/admin.ts @@ -87,11 +87,51 @@ const admin: TranslationStrings = { 'admin.requireMfaHint': "Gli utenti senza 2FA devono completare la configurazione in Impostazioni prima di usare l'app.", 'admin.apiKeys': 'Chiavi API', - 'admin.apiKeysHint': 'Opzionale. Abilita dati estesi per i luoghi come foto e meteo.', + 'admin.apiKeysHint': "Da dove arrivano i dati dei luoghi. L'indice di TREK non richiede chiavi; i due fornitori qui sotto sono facoltativi.", + 'admin.trekApi.badgeDefault': 'Fonte consigliata predefinita', + 'admin.googleCaveat.badge': 'Sconsigliato', + 'admin.googleCaveat.body': + "TREK è software libero e su questo non siamo neutrali. A questa scala recensioni e foto degli esercizi comuni esistono solo da Google, ed è questo un monopolio. Il campo c'è per mancanza di alternative, non perché lo consigliamo. Ogni interrogazione va allora a Google.", + 'admin.trekApi.badgeSoon': + 'Presto', + 'admin.trekApi.badgeActive': + 'Attivo', + 'admin.trekApi.tagline': + 'L\'indice di luoghi di TREK. Cercare senza chiave Google, senza quota e senza che nessuno conti le tue ricerche.', + 'admin.trekApi.factPlaces': + '73,6 milioni di luoghi nel mondo', + 'admin.trekApi.factNoKey': + 'Nessuna chiave, nessuna quota', + 'admin.trekApi.factOffline': + 'I pacchetti per Paese funzionano offline', + 'admin.trekApi.factPrivacy': + 'Le ricerche non vengono mai registrate', + 'admin.trekApi.more': + 'Che cosa contiene', + 'admin.trekApi.fieldPhone': + 'Telefono', + 'admin.trekApi.fieldStableId': + 'Identificativo stabile', + 'admin.trekApi.includedNote': + 'Le descrizioni vengono dal sito del luogo stesso; gli orari da OpenStreetMap dove sono indicati.', + 'admin.trekApi.notRatings': + 'Recensioni', + 'admin.trekApi.notPhotos': + 'Foto degli esercizi comuni', + 'admin.trekApi.notIncludedNote': + 'Nessuna fonte aperta ha né le une né le altre, a nessun prezzo. Per quelle due una chiave Google resta l\'unica strada.', + 'admin.trekApi.sourcesLabel': + 'Fonti', + 'admin.trekApi.sourcesNote': + 'Ogni campo di una risposta dichiara da quale di esse proviene.', + 'admin.trekApi.included': + 'Incluso', + 'admin.trekApi.notIncluded': + 'Non incluso', 'admin.mapsKey': 'Chiave API Google Maps', 'admin.mapsKeyHint': 'Richiesta per la ricerca dei luoghi. Ottienila su console.cloud.google.com', 'admin.mapsKeyHintLong': - 'Senza una chiave API, OpenStreetMap viene utilizzato per la ricerca dei luoghi. Con una chiave API di Google, è possibile caricare anche foto, valutazioni e orari di apertura. Ottienine una su console.cloud.google.com.', + "Senza chiave API di Google viene usata l'API TREK consigliata. Con una chiave si possono caricare in più foto, recensioni e orari. Creala su console.cloud.google.com.", 'admin.recommended': 'Consigliato', 'admin.weatherKey': 'Chiave API OpenWeatherMap', 'admin.weatherKeyHint': 'Per i dati meteo. Gratuita su openweathermap.org', @@ -114,6 +154,8 @@ const admin: TranslationStrings = { 'admin.fileTypesHint': 'Configura quali tipi di file gli utenti possono caricare.', 'admin.fileTypesFormat': 'Estensioni separate da virgola (es. jpg,png,pdf,doc). Usa * per consentire tutti i tipi.', 'admin.fileTypesSaved': 'Impostazioni dei tipi di file salvate', + 'admin.googleOptions': 'A cosa serve la chiave', + 'admin.googleOptionsSummary': '{on} di {total} attivi', 'admin.placesPhotos.title': 'Foto dei luoghi', 'admin.placesPhotos.subtitle': "Recupera le foto dall'API Google Places. Disabilita per risparmiare la quota API. Le foto di Wikimedia non sono interessate.", diff --git a/shared/src/i18n/ja/admin.ts b/shared/src/i18n/ja/admin.ts index be8b235b9..0d1c713d1 100644 --- a/shared/src/i18n/ja/admin.ts +++ b/shared/src/i18n/ja/admin.ts @@ -121,11 +121,51 @@ const admin: TranslationStrings = { 'admin.requireMfa': '二要素認証(2FA)を必須にする', 'admin.requireMfaHint': '2FA未設定のユーザーは、利用前に設定が必要です。', 'admin.apiKeys': 'APIキー', - 'admin.apiKeysHint': '任意。写真や天気などの拡張データを有効化します。', + 'admin.apiKeysHint': '場所データの出どころ。TREK のインデックスにキーは不要で、下の二つの提供元は任意です。', + 'admin.trekApi.badgeDefault': '推奨の既定ソース', + 'admin.googleCaveat.badge': '推奨しません', + 'admin.googleCaveat.body': + 'TREK はオープンソースで、この点で中立ではありません。この規模でレビューとふつうの店舗の写真があるのは Google だけで、それが独占です。この欄は代わりがないから置いてあるのであって、勧めているからではありません。使えば問い合わせはすべて Google に届きます。', + 'admin.trekApi.badgeSoon': + 'まもなく', + 'admin.trekApi.badgeActive': + '有効', + 'admin.trekApi.tagline': + 'TREK 自前の場所インデックス。Google のキーなし、上限なし、そして誰にも検索を数えられずに探せます。', + 'admin.trekApi.factPlaces': + '世界で 7,360 万件の場所', + 'admin.trekApi.factNoKey': + 'キーも上限も不要', + 'admin.trekApi.factOffline': + '国別パッケージはオフラインでも使えます', + 'admin.trekApi.factPrivacy': + '検索内容は一切記録しません', + 'admin.trekApi.more': + '何が入っているか', + 'admin.trekApi.fieldPhone': + '電話', + 'admin.trekApi.fieldStableId': + '不変の識別子', + 'admin.trekApi.includedNote': + '説明はその場所自身のサイトから、営業時間は記入されている範囲で OpenStreetMap から取得します。', + 'admin.trekApi.notRatings': + 'レビュー', + 'admin.trekApi.notPhotos': + 'ふつうの店舗の写真', + 'admin.trekApi.notIncludedNote': + 'どちらもどんな価格でも公開データセットには存在しません。この二つには Google のキーが唯一の道として残ります。', + 'admin.trekApi.sourcesLabel': + '出典', + 'admin.trekApi.sourcesNote': + '応答のどの項目も、このうちどれに由来するかを示します。', + 'admin.trekApi.included': + '含まれるもの', + 'admin.trekApi.notIncluded': + '含まれないもの', 'admin.mapsKey': 'Google Maps APIキー', 'admin.mapsKeyHint': '場所検索に必要。console.cloud.google.com で取得', 'admin.mapsKeyHintLong': - 'APIキーなしではOpenStreetMapを使用します。Google APIキーがあれば写真、評価、営業時間も表示できます。', + 'Google の API キーがなくても、推奨の TREK API が使われます。キーがあれば、写真・評価・営業時間を追加で読み込めます。キーは console.cloud.google.com で作成できます。', 'admin.recommended': '推奨', 'admin.weatherKey': 'OpenWeatherMap APIキー', 'admin.weatherKeyHint': '天気データ用。openweathermap.org で無料', @@ -147,6 +187,8 @@ const admin: TranslationStrings = { 'admin.fileTypesHint': 'ユーザーがアップロードできるファイル形式を設定します。', 'admin.fileTypesFormat': '拡張子をカンマ区切り(例:jpg,png,pdf,doc)。すべて許可する場合は *。', 'admin.fileTypesSaved': 'ファイル形式の設定を保存しました', + 'admin.googleOptions': 'キーの使いみち', + 'admin.googleOptionsSummary': '{total} 件中 {on} 件が有効', 'admin.placesPhotos.title': '場所の写真', 'admin.placesPhotos.subtitle': 'Google Places APIから写真を取得します。APIクォータ節約のため無効にできます。Wikimediaの写真には影響しません。', diff --git a/shared/src/i18n/ko/admin.ts b/shared/src/i18n/ko/admin.ts index 20dab5251..8b63fb903 100644 --- a/shared/src/i18n/ko/admin.ts +++ b/shared/src/i18n/ko/admin.ts @@ -126,11 +126,51 @@ const admin: TranslationStrings = { 'admin.requireMfa': '2단계 인증 (2FA) 요구', 'admin.requireMfaHint': '2FA가 없는 사용자는 앱을 사용하기 전에 설정에서 설정을 완료해야 합니다.', 'admin.apiKeys': 'API 키', - 'admin.apiKeysHint': '선택 사항. 사진 및 날씨 등 확장된 장소 데이터를 활성화합니다.', + 'admin.apiKeysHint': '장소 데이터의 출처. TREK 색인에는 키가 필요 없으며, 아래 두 제공자는 선택 사항입니다.', + 'admin.trekApi.badgeDefault': '권장 기본 소스', + 'admin.googleCaveat.badge': '권장하지 않음', + 'admin.googleCaveat.body': + 'TREK은 오픈 소스이며 이 점에서 중립이 아닙니다. 이 규모에서 평점과 일반 상점 사진은 Google에만 있고, 그것이 독점입니다. 이 입력란은 대안이 없어서 있는 것이지 권해서가 아닙니다. 쓰면 모든 조회가 Google로 갑니다.', + 'admin.trekApi.badgeSoon': + '곧 제공', + 'admin.trekApi.badgeActive': + '사용 중', + 'admin.trekApi.tagline': + 'TREK 자체 장소 색인. Google 키 없이, 할당량 없이, 누구도 검색 횟수를 세지 않는 채로 찾습니다.', + 'admin.trekApi.factPlaces': + '전 세계 7,363만 개 장소', + 'admin.trekApi.factNoKey': + '키도 할당량도 없음', + 'admin.trekApi.factOffline': + '국가별 패키지는 오프라인에서도 동작', + 'admin.trekApi.factPrivacy': + '검색어는 결코 기록하지 않음', + 'admin.trekApi.more': + '무엇이 들어 있나', + 'admin.trekApi.fieldPhone': + '전화', + 'admin.trekApi.fieldStableId': + '고정 식별자', + 'admin.trekApi.includedNote': + '설명은 장소 자체의 웹사이트에서, 영업시간은 입력되어 있는 경우 OpenStreetMap에서 가져옵니다.', + 'admin.trekApi.notRatings': + '평점', + 'admin.trekApi.notPhotos': + '일반 상점의 사진', + 'admin.trekApi.notIncludedNote': + '둘 다 어떤 값을 치르더라도 공개 데이터에는 없습니다. 이 둘에는 Google 키가 유일한 길로 남습니다.', + 'admin.trekApi.sourcesLabel': + '출처', + 'admin.trekApi.sourcesNote': + '응답의 모든 항목은 이 가운데 어디에서 왔는지 밝힙니다.', + 'admin.trekApi.included': + '포함', + 'admin.trekApi.notIncluded': + '미포함', 'admin.mapsKey': 'Google Maps API 키', 'admin.mapsKeyHint': '장소 검색에 필요합니다. console.cloud.google.com에서 발급', 'admin.mapsKeyHintLong': - 'API 키 없이는 장소 검색에 OpenStreetMap이 사용됩니다. Google API 키가 있으면 사진, 평점, 영업 시간도 불러올 수 있습니다. console.cloud.google.com에서 발급하세요.', + 'Google API 키가 없어도 권장되는 TREK API가 사용됩니다. 키가 있으면 사진, 평점, 영업시간을 추가로 불러올 수 있습니다. 키는 console.cloud.google.com에서 만들 수 있습니다.', 'admin.recommended': '권장', 'admin.weatherKey': 'OpenWeatherMap API 키', 'admin.weatherKeyHint': '날씨 데이터용. openweathermap.org에서 무료 발급', @@ -152,6 +192,8 @@ const admin: TranslationStrings = { 'admin.fileTypesHint': '사용자가 업로드할 수 있는 파일 형식을 설정합니다.', 'admin.fileTypesFormat': '쉼표로 구분된 확장자 (예: jpg,png,pdf,doc). 모든 형식을 허용하려면 *를 사용하세요.', 'admin.fileTypesSaved': '파일 형식 설정이 저장되었습니다', + 'admin.googleOptions': '키의 사용 범위', + 'admin.googleOptionsSummary': '{total}개 중 {on}개 켜짐', 'admin.placesPhotos.title': '장소 사진', 'admin.placesPhotos.subtitle': 'Google Places API에서 사진을 가져옵니다. API 할당량 절약을 위해 비활성화할 수 있습니다. Wikimedia 사진은 영향을 받지 않습니다.', diff --git a/shared/src/i18n/nl/admin.ts b/shared/src/i18n/nl/admin.ts index 41feaedb0..9bacf3c28 100644 --- a/shared/src/i18n/nl/admin.ts +++ b/shared/src/i18n/nl/admin.ts @@ -88,11 +88,51 @@ const admin: TranslationStrings = { 'admin.requireMfaHint': 'Gebruikers zonder 2FA moeten de installatie in Instellingen voltooien voordat ze de app kunnen gebruiken.', 'admin.apiKeys': 'API-sleutels', - 'admin.apiKeysHint': "Optioneel. Schakelt uitgebreide plaatsgegevens in zoals foto's en weer.", + 'admin.apiKeysHint': 'Waar plaatsgegevens vandaan komen. De TREK-index heeft geen sleutel nodig; de twee aanbieders hieronder zijn optioneel.', + 'admin.trekApi.badgeDefault': 'Aanbevolen standaardbron', + 'admin.googleCaveat.badge': 'Niet aanbevolen', + 'admin.googleCaveat.body': + "TREK is open source en wij zijn hier niet neutraal. Op deze schaal bestaan beoordelingen en foto's van gewone zaken alleen bij Google, en dat is een monopolie. Het veld staat er bij gebrek aan alternatief, niet omdat wij het aanraden. Elke opvraging gaat dan naar Google.", + 'admin.trekApi.badgeSoon': + 'Binnenkort', + 'admin.trekApi.badgeActive': + 'Actief', + 'admin.trekApi.tagline': + 'TREK\'s eigen plaatsenindex. Zoeken zonder Google-sleutel, zonder quotum en zonder dat iemand meetelt.', + 'admin.trekApi.factPlaces': + '73,6 miljoen plaatsen wereldwijd', + 'admin.trekApi.factNoKey': + 'Geen sleutel, geen quotum', + 'admin.trekApi.factOffline': + 'Landpakketten werken offline', + 'admin.trekApi.factPrivacy': + 'Zoekopdrachten worden nooit vastgelegd', + 'admin.trekApi.more': + 'Wat erin zit', + 'admin.trekApi.fieldPhone': + 'Telefoon', + 'admin.trekApi.fieldStableId': + 'Stabiele id', + 'admin.trekApi.includedNote': + 'Beschrijvingen komen van de website van de plaats zelf; openingstijden uit OpenStreetMap waar ze zijn ingevuld.', + 'admin.trekApi.notRatings': + 'Beoordelingen', + 'admin.trekApi.notPhotos': + 'Foto\'s van gewone zaken', + 'admin.trekApi.notIncludedNote': + 'Geen enkele open bron heeft ze, voor geen prijs. Een Google-sleutel blijft de enige weg naar die twee.', + 'admin.trekApi.sourcesLabel': + 'Bronnen', + 'admin.trekApi.sourcesNote': + 'Elk veld in een antwoord vermeldt uit welke ervan het komt.', + 'admin.trekApi.included': + 'Inbegrepen', + 'admin.trekApi.notIncluded': + 'Niet inbegrepen', 'admin.mapsKey': 'Google Maps API-sleutel', 'admin.mapsKeyHint': 'Vereist voor het zoeken van plaatsen. Verkrijgbaar op console.cloud.google.com', 'admin.mapsKeyHintLong': - "Zonder API-sleutel wordt OpenStreetMap gebruikt voor het zoeken van plaatsen. Met een Google API-sleutel kunnen ook foto's, beoordelingen en openingstijden worden geladen. Verkrijgbaar op console.cloud.google.com.", + "Zonder Google-API-sleutel wordt de aanbevolen TREK-API gebruikt. Met een sleutel kunnen daarnaast foto's, beoordelingen en openingstijden worden geladen. Maak er een aan op console.cloud.google.com.", 'admin.recommended': 'Aanbevolen', 'admin.weatherKey': 'OpenWeatherMap API-sleutel', 'admin.weatherKeyHint': 'Voor weergegevens. Gratis op openweathermap.org', @@ -115,6 +155,8 @@ const admin: TranslationStrings = { 'admin.fileTypesHint': 'Configureer welke bestandstypen gebruikers kunnen uploaden.', 'admin.fileTypesFormat': 'Kommagescheiden extensies (bijv. jpg,png,pdf,doc). Gebruik * om alle typen toe te staan.', 'admin.fileTypesSaved': 'Bestandstype-instellingen opgeslagen', + 'admin.googleOptions': 'Waarvoor de sleutel wordt gebruikt', + 'admin.googleOptionsSummary': '{on} van {total} aan', 'admin.placesPhotos.title': "Plaatsfoto's", 'admin.placesPhotos.subtitle': "Haalt foto's op via de Google Places API. Schakel uit om API-quota te besparen. Wikimedia-foto's worden niet beïnvloed.", diff --git a/shared/src/i18n/pl/admin.ts b/shared/src/i18n/pl/admin.ts index 45ccb06fe..b223d616a 100644 --- a/shared/src/i18n/pl/admin.ts +++ b/shared/src/i18n/pl/admin.ts @@ -79,11 +79,51 @@ const admin: TranslationStrings = { 'Użytkownicy bez 2FA muszą ukończyć konfigurację w Ustawieniach zanim zaczną korzystać z aplikacji.', 'admin.apiKeys': 'Klucze API', 'admin.apiKeysHint': - 'Opcjonalne. Umożliwiają pobieranie większej ilości danych o miejscach, takich jak zdjęcia i pogoda.', + 'Skąd pochodzą dane miejsc. Indeks TREK nie potrzebuje klucza; dwaj dostawcy poniżej są opcjonalni.', + 'admin.trekApi.badgeDefault': 'Zalecane źródło domyślne', + 'admin.googleCaveat.badge': 'Niezalecane', + 'admin.googleCaveat.body': + 'TREK jest otwartym oprogramowaniem i nie jesteśmy tu neutralni. W tej skali oceny i zdjęcia zwykłych lokali istnieją tylko u Google, i to jest właśnie monopol. Pole jest tu z braku alternatywy, a nie dlatego, że je zalecamy. Każde zapytanie idzie wtedy do Google.', + 'admin.trekApi.badgeSoon': + 'Wkrótce', + 'admin.trekApi.badgeActive': + 'Aktywne', + 'admin.trekApi.tagline': + 'Własny indeks miejsc TREK-a. Wyszukiwanie bez klucza Google, bez limitu i bez tego, żeby ktoś liczył twoje zapytania.', + 'admin.trekApi.factPlaces': + '73,6 miliona miejsc na świecie', + 'admin.trekApi.factNoKey': + 'Bez klucza, bez limitu', + 'admin.trekApi.factOffline': + 'Pakiety krajowe działają offline', + 'admin.trekApi.factPrivacy': + 'Zapytania nigdy nie są zapisywane', + 'admin.trekApi.more': + 'Co w tym jest', + 'admin.trekApi.fieldPhone': + 'Telefon', + 'admin.trekApi.fieldStableId': + 'Stały identyfikator', + 'admin.trekApi.includedNote': + 'Opisy pochodzą ze strony samego miejsca, a godziny otwarcia z OpenStreetMap tam, gdzie je wpisano.', + 'admin.trekApi.notRatings': + 'Oceny', + 'admin.trekApi.notPhotos': + 'Zdjęcia zwykłych lokali', + 'admin.trekApi.notIncludedNote': + 'Żadne otwarte źródło nie ma ani jednego, ani drugiego, za żadną cenę. Do tych dwóch klucz Google pozostaje jedyną drogą.', + 'admin.trekApi.sourcesLabel': + 'Źródła', + 'admin.trekApi.sourcesNote': + 'Każde pole w odpowiedzi mówi, z którego z nich pochodzi.', + 'admin.trekApi.included': + 'Zawiera', + 'admin.trekApi.notIncluded': + 'Nie zawiera', 'admin.mapsKey': 'Klucz Google Maps API', 'admin.mapsKeyHint': 'Wymagany do wyszukiwania miejsc. Uzyskaj go na console.cloud.google.com', 'admin.mapsKeyHintLong': - 'Bez klucza API, OpenStreetMap jest wykorzystywany do wyszukiwania miejsc. Z kluczem API Google, zdjęcia, oceny i godziny otwarcia również mogą być pobierane. Uzyskaj go na console.cloud.google.com.', + 'Bez klucza API Google używane jest zalecane API TREK. Z kluczem można dodatkowo wczytać zdjęcia, oceny i godziny otwarcia. Klucz utworzysz na console.cloud.google.com.', 'admin.recommended': 'Polecane', 'admin.weatherKey': 'Klucz OpenWeatherMap API', 'admin.weatherKeyHint': 'Do danych pogodowych. Uzyskaj go bezpłatnie na openweathermap.org', @@ -108,6 +148,8 @@ const admin: TranslationStrings = { 'admin.fileTypesFormat': 'Rozszerzenia oddzielone przecinkami (np. jpg,png,pdf,doc). Użyj * aby zezwolić na wszystkie typy.', 'admin.fileTypesSaved': 'Ustawienia typów plików zostały zapisane', + 'admin.googleOptions': 'Do czego służy klucz', + 'admin.googleOptionsSummary': '{on} z {total} włączone', 'admin.placesPhotos.title': 'Zdjęcia miejsc', 'admin.placesPhotos.subtitle': 'Pobiera zdjęcia z Google Places API. Wyłącz, aby zaoszczędzić limit API. Zdjęcia z Wikimedia nie są objęte.', diff --git a/shared/src/i18n/ru/admin.ts b/shared/src/i18n/ru/admin.ts index 3a0d38546..a1a8432f7 100644 --- a/shared/src/i18n/ru/admin.ts +++ b/shared/src/i18n/ru/admin.ts @@ -88,11 +88,51 @@ const admin: TranslationStrings = { 'admin.requireMfaHint': 'Пользователи без 2FA должны завершить настройку в разделе «Настройки» перед использованием приложения.', 'admin.apiKeys': 'API-ключи', - 'admin.apiKeysHint': 'Необязательно. Включает расширенные данные о местах, такие как фото и погода.', + 'admin.apiKeysHint': 'Откуда берутся данные о местах. Указателю TREK ключ не нужен; два поставщика ниже необязательны.', + 'admin.trekApi.badgeDefault': 'Рекомендуемый источник', + 'admin.googleCaveat.badge': 'Не рекомендуется', + 'admin.googleCaveat.body': + 'TREK с открытым исходным кодом, и мы здесь не нейтральны. В таком масштабе отзывы и фотографии обычных заведений есть только у Google, и это и есть монополия. Поле здесь из-за отсутствия альтернативы, а не потому, что мы его советуем. Тогда каждый запрос уходит в Google.', + 'admin.trekApi.badgeSoon': + 'Скоро', + 'admin.trekApi.badgeActive': + 'Включено', + 'admin.trekApi.tagline': + 'Собственный указатель мест TREK. Поиск без ключа Google, без квоты и без того, чтобы кто-то считал ваши запросы.', + 'admin.trekApi.factPlaces': + '73,6 миллиона мест по всему миру', + 'admin.trekApi.factNoKey': + 'Без ключа, без квоты', + 'admin.trekApi.factOffline': + 'Страновые пакеты работают офлайн', + 'admin.trekApi.factPrivacy': + 'Запросы никогда не записываются', + 'admin.trekApi.more': + 'Что внутри', + 'admin.trekApi.fieldPhone': + 'Телефон', + 'admin.trekApi.fieldStableId': + 'Постоянный идентификатор', + 'admin.trekApi.includedNote': + 'Описания берутся с сайта самого места, часы работы — из OpenStreetMap там, где они указаны.', + 'admin.trekApi.notRatings': + 'Отзывы', + 'admin.trekApi.notPhotos': + 'Фотографии обычных заведений', + 'admin.trekApi.notIncludedNote': + 'Ни того, ни другого нет ни в одном открытом источнике ни за какие деньги. К этим двум ключ Google остаётся единственной дорогой.', + 'admin.trekApi.sourcesLabel': + 'Источники', + 'admin.trekApi.sourcesNote': + 'Каждое поле в ответе указывает, из какого из них оно пришло.', + 'admin.trekApi.included': + 'Входит', + 'admin.trekApi.notIncluded': + 'Не входит', 'admin.mapsKey': 'API-ключ Google Maps', 'admin.mapsKeyHint': 'Необходим для поиска мест. Получите на console.cloud.google.com', 'admin.mapsKeyHintLong': - 'Без API-ключа используется OpenStreetMap для поиска мест. С ключом Google API можно загружать фото, рейтинги и часы работы. Получите ключ на console.cloud.google.com.', + 'Без ключа Google API используется рекомендуемый TREK API. С ключом дополнительно загружаются фотографии, оценки и часы работы. Создать ключ можно на console.cloud.google.com.', 'admin.recommended': 'Рекомендуется', 'admin.weatherKey': 'API-ключ OpenWeatherMap', 'admin.weatherKeyHint': 'Для данных о погоде. Бесплатно на openweathermap.org', @@ -115,6 +155,8 @@ const admin: TranslationStrings = { 'admin.fileTypesHint': 'Настройте, какие типы файлов могут загружать пользователи.', 'admin.fileTypesFormat': 'Расширения через запятую (напр. jpg,png,pdf,doc). Используйте * для разрешения всех типов.', 'admin.fileTypesSaved': 'Настройки типов файлов сохранены', + 'admin.googleOptions': 'Для чего используется ключ', + 'admin.googleOptionsSummary': 'включено {on} из {total}', 'admin.placesPhotos.title': 'Фотографии мест', 'admin.placesPhotos.subtitle': 'Загрузка фотографий из Google Places API. Отключите для экономии квоты API. Фотографии Wikimedia не затронуты.', diff --git a/shared/src/i18n/sv/admin.ts b/shared/src/i18n/sv/admin.ts index c74df39bb..f4d92c107 100644 --- a/shared/src/i18n/sv/admin.ts +++ b/shared/src/i18n/sv/admin.ts @@ -130,11 +130,51 @@ const admin: TranslationStrings = { 'admin.requireMfaHint': 'Användare som inte har tvåfaktorsautentisering måste slutföra inställningarna under inställningar innan de använder appen.', 'admin.apiKeys': 'API Nycklar', - 'admin.apiKeysHint': 'Valfritt. Aktiverar utökade platsuppgifter, såsom foton och väderinformation.', + 'admin.apiKeysHint': 'Var platsdata kommer ifrån. TREK-indexet behöver ingen nyckel; de två leverantörerna nedan är valfria.', + 'admin.trekApi.badgeDefault': 'Rekommenderad standardkälla', + 'admin.googleCaveat.badge': 'Avrådes', + 'admin.googleCaveat.body': + 'TREK är öppen källkod och vi är inte neutrala här. I den här skalan finns omdömen och foton på vanliga verksamheter bara hos Google, och det är ett monopol. Fältet finns i brist på alternativ, inte för att vi rekommenderar det. Varje uppslagning går då till Google.', + 'admin.trekApi.badgeSoon': + 'Snart', + 'admin.trekApi.badgeActive': + 'Aktiv', + 'admin.trekApi.tagline': + 'TREK:s eget platsregister. Sök utan Google-nyckel, utan kvot och utan att någon räknar dina sökningar.', + 'admin.trekApi.factPlaces': + '73,6 miljoner platser i världen', + 'admin.trekApi.factNoKey': + 'Ingen nyckel, ingen kvot', + 'admin.trekApi.factOffline': + 'Landpaket fungerar offline', + 'admin.trekApi.factPrivacy': + 'Sökningar loggas aldrig', + 'admin.trekApi.more': + 'Vad som ingår', + 'admin.trekApi.fieldPhone': + 'Telefon', + 'admin.trekApi.fieldStableId': + 'Stabilt id', + 'admin.trekApi.includedNote': + 'Beskrivningar kommer från platsens egen webbplats, öppettider från OpenStreetMap där de är ifyllda.', + 'admin.trekApi.notRatings': + 'Omdömen', + 'admin.trekApi.notPhotos': + 'Foton på vanliga verksamheter', + 'admin.trekApi.notIncludedNote': + 'Ingen öppen källa har någotdera, till något pris. En Google-nyckel förblir enda vägen till de två.', + 'admin.trekApi.sourcesLabel': + 'Källor', + 'admin.trekApi.sourcesNote': + 'Varje fält i ett svar anger vilken av dem det kommer från.', + 'admin.trekApi.included': + 'Ingår', + 'admin.trekApi.notIncluded': + 'Ingår inte', 'admin.mapsKey': 'Google Maps API Nyckel', 'admin.mapsKeyHint': 'Krävs för att söka efter platser. Hämta på console.cloud.google.com', 'admin.mapsKeyHintLong': - 'Utan en API-nyckel används OpenStreetMap för platssökning. Med en Google API-nyckel kan även foton, betyg och öppettider hämtas. Skaffa en på console.cloud.google.com.', + 'Utan Google-API-nyckel används det rekommenderade TREK-API:et. Med en nyckel kan foton, omdömen och öppettider laddas dessutom. Skapa en på console.cloud.google.com.', 'admin.recommended': 'Rekommenderat', 'admin.weatherKey': 'OpenWeatherMap API Nyckel', 'admin.weatherKeyHint': 'För väderdata. Gratis på openweathermap.org', @@ -158,6 +198,8 @@ const admin: TranslationStrings = { 'admin.fileTypesFormat': 'Filändelser separerade med kommatecken (t.ex. jpg, png, pdf, doc). Använd * för att tillåta alla filtyper.', 'admin.fileTypesSaved': 'Inställningar för filtyper har sparats', + 'admin.googleOptions': 'Vad nyckeln används till', + 'admin.googleOptionsSummary': '{on} av {total} på', 'admin.placesPhotos.title': 'Plats Foton', 'admin.placesPhotos.subtitle': 'Hämta bilder från Google Places API. Inaktivera för att spara API-kvoten. Wikimedia-bilder påverkas inte.', diff --git a/shared/src/i18n/tr/admin.ts b/shared/src/i18n/tr/admin.ts index 64e3c91d9..c1787ad6b 100644 --- a/shared/src/i18n/tr/admin.ts +++ b/shared/src/i18n/tr/admin.ts @@ -130,11 +130,51 @@ const admin: TranslationStrings = { 'admin.requireMfaHint': "2FA'sı olmayan kullanıcıların uygulamayı kullanmadan önce Ayarlar'da kurulumu tamamlaması gerekir.", 'admin.apiKeys': 'API Anahtarları', - 'admin.apiKeysHint': 'İsteğe bağlı. Fotoğraflar ve hava durumu gibi genişletilmiş yer verilerini etkinleştirir.', + 'admin.apiKeysHint': 'Yer verilerinin nereden geldiği. TREK dizini anahtar istemez; aşağıdaki iki sağlayıcı isteğe bağlıdır.', + 'admin.trekApi.badgeDefault': 'Önerilen varsayılan', + 'admin.googleCaveat.badge': 'Önerilmez', + 'admin.googleCaveat.body': + "TREK açık kaynaklıdır ve burada tarafsız değiliz. Bu ölçekte değerlendirmeler ve sıradan işletme fotoğrafları yalnızca Google'da vardır; tekel budur. Alan, başka seçenek olmadığı için burada; önerdiğimiz için değil. O zaman her sorgu Google'a gider.", + 'admin.trekApi.badgeSoon': + 'Yakında', + 'admin.trekApi.badgeActive': + 'Etkin', + 'admin.trekApi.tagline': + 'TREK\'in kendi yer dizini. Google anahtarı olmadan, kota olmadan ve kimse aramalarınızı saymadan arama.', + 'admin.trekApi.factPlaces': + 'Dünya genelinde 73,6 milyon yer', + 'admin.trekApi.factNoKey': + 'Anahtar yok, kota yok', + 'admin.trekApi.factOffline': + 'Ülke paketleri çevrimdışı da çalışır', + 'admin.trekApi.factPrivacy': + 'Aramalar hiçbir zaman kaydedilmez', + 'admin.trekApi.more': + 'İçinde ne var', + 'admin.trekApi.fieldPhone': + 'Telefon', + 'admin.trekApi.fieldStableId': + 'Kalıcı kimlik', + 'admin.trekApi.includedNote': + 'Açıklamalar yerin kendi sitesinden, çalışma saatleri girilmiş olduğu yerlerde OpenStreetMap\'ten gelir.', + 'admin.trekApi.notRatings': + 'Değerlendirmeler', + 'admin.trekApi.notPhotos': + 'Sıradan işletmelerin fotoğrafları', + 'admin.trekApi.notIncludedNote': + 'İkisini de hiçbir açık kaynak hiçbir fiyata sunmuyor. Bu ikisi için Google anahtarı tek yol olmayı sürdürüyor.', + 'admin.trekApi.sourcesLabel': + 'Kaynaklar', + 'admin.trekApi.sourcesNote': + 'Yanıttaki her alan, bunlardan hangisinden geldiğini söyler.', + 'admin.trekApi.included': + 'Dahil', + 'admin.trekApi.notIncluded': + 'Dahil değil', 'admin.mapsKey': 'Google Haritalar API Anahtarı', 'admin.mapsKeyHint': 'Yer arama için gereklidir. console.cloud.google.com adresinden erişin', 'admin.mapsKeyHintLong': - 'API anahtarı olmadan OpenStreetMap yer arama için kullanılır. Bir Google API anahtarıyla fotoğraflar, derecelendirmeler ve açılış saatleri de yüklenebilir. console.cloud.google.com adresinden bir tane edinin.', + 'Google API anahtarı olmadan önerilen TREK API kullanılır. Anahtarla ek olarak fotoğraflar, değerlendirmeler ve çalışma saatleri yüklenebilir. Anahtarı console.cloud.google.com adresinde oluşturabilirsiniz.', 'admin.recommended': 'Tavsiye edilen', 'admin.weatherKey': 'OpenWeatherMap API Anahtarı', 'admin.weatherKeyHint': "Hava durumu verileri için. openweathermap.org'da ücretsiz", @@ -159,6 +199,8 @@ const admin: TranslationStrings = { 'admin.fileTypesFormat': 'Virgülle ayrılmış uzantılar (ör. jpg,png,pdf,doc). Tüm türlere izin vermek için * kullanın.', 'admin.fileTypesSaved': 'Dosya türü ayarları kaydedildi', + 'admin.googleOptions': 'Anahtarın ne için kullanıldığı', + 'admin.googleOptionsSummary': '{total} ayardan {on} tanesi açık', 'admin.placesPhotos.title': 'Fotoğrafları Yerleştir', 'admin.placesPhotos.subtitle': "Fotoğrafları Google Rehber API'sinden alın. API kotasını kaydetmeyi devre dışı bırakın. Wikimedia fotoğrafları etkilenmez.", diff --git a/shared/src/i18n/uk/admin.ts b/shared/src/i18n/uk/admin.ts index 68d666bb4..7670803e7 100644 --- a/shared/src/i18n/uk/admin.ts +++ b/shared/src/i18n/uk/admin.ts @@ -88,11 +88,51 @@ const admin: TranslationStrings = { 'admin.requireMfaHint': 'Користувачі без 2FA повинні завершити налаштування в розділі «Налаштування» перед використанням програми.', 'admin.apiKeys': 'API-ключі', - 'admin.apiKeysHint': 'Необов’язково. Включає розширені дані про місця, такі як фото та погода.', + 'admin.apiKeysHint': "Звідки беруться дані про місця. Покажчику TREK ключ не потрібен; два постачальники нижче необов'язкові.", + 'admin.trekApi.badgeDefault': 'Рекомендоване джерело', + 'admin.googleCaveat.badge': 'Не рекомендовано', + 'admin.googleCaveat.body': + 'TREK має відкритий код, і ми тут не нейтральні. У такому масштабі відгуки та світлини звичайних закладів є лише в Google, і це і є монополія. Поле тут через брак альтернативи, а не тому, що ми його радимо. Тоді кожен запит іде до Google.', + 'admin.trekApi.badgeSoon': + 'Незабаром', + 'admin.trekApi.badgeActive': + 'Увімкнено', + 'admin.trekApi.tagline': + 'Власний покажчик місць TREK. Пошук без ключа Google, без квоти й без того, щоб хтось рахував ваші запити.', + 'admin.trekApi.factPlaces': + '73,6 мільйона місць у світі', + 'admin.trekApi.factNoKey': + 'Без ключа, без квоти', + 'admin.trekApi.factOffline': + 'Пакети країн працюють офлайн', + 'admin.trekApi.factPrivacy': + 'Запити ніколи не записуються', + 'admin.trekApi.more': + 'Що всередині', + 'admin.trekApi.fieldPhone': + 'Телефон', + 'admin.trekApi.fieldStableId': + 'Стала позначка', + 'admin.trekApi.includedNote': + 'Описи беруться із сайту самого місця, години роботи — з OpenStreetMap там, де їх зазначено.', + 'admin.trekApi.notRatings': + 'Відгуки', + 'admin.trekApi.notPhotos': + 'Світлини звичайних закладів', + 'admin.trekApi.notIncludedNote': + 'Ані того, ані іншого немає в жодному відкритому джерелі за жодну ціну. До цих двох ключ Google лишається єдиним шляхом.', + 'admin.trekApi.sourcesLabel': + 'Джерела', + 'admin.trekApi.sourcesNote': + 'Кожне поле у відповіді вказує, з якого з них воно походить.', + 'admin.trekApi.included': + 'Входить', + 'admin.trekApi.notIncluded': + 'Не входить', 'admin.mapsKey': 'API-ключ Google Maps', 'admin.mapsKeyHint': 'Необхідний для пошуку місць. Отримайте на console.cloud.google.com', 'admin.mapsKeyHintLong': - 'Без API-ключа для пошуку місць використовується OpenStreetMap. З ключем Google API можна завантажувати фото, рейтинги та години роботи. Отримайте ключ на console.cloud.google.com.', + 'Без ключа Google API використовується рекомендований TREK API. З ключем додатково завантажуються світлини, оцінки та години роботи. Створити ключ можна на console.cloud.google.com.', 'admin.recommended': 'Рекомендується', 'admin.weatherKey': 'API-ключ OpenWeatherMap', 'admin.weatherKeyHint': 'Для даних про погоду. Безкоштовно на openweathermap.org', @@ -115,6 +155,8 @@ const admin: TranslationStrings = { 'admin.fileTypesHint': 'Налаштуйте, які типи файлів можуть завантажувати користувачі.', 'admin.fileTypesFormat': 'Розширення через кому (напр. jpg,png,pdf,doc). Використовуйте * для дозволу всіх типів.', 'admin.fileTypesSaved': 'Налаштування типів файлів збережено', + 'admin.googleOptions': 'Для чого використовується ключ', + 'admin.googleOptionsSummary': 'увімкнено {on} з {total}', 'admin.placesPhotos.title': 'Фотографії місць', 'admin.placesPhotos.subtitle': 'Завантаження фотографій з Google Places API. Вимкніть для економії квоти API. Фотографії Wikimedia не зачіпаються.', diff --git a/shared/src/i18n/vi/admin.ts b/shared/src/i18n/vi/admin.ts index 84cec64d2..2b0e00969 100644 --- a/shared/src/i18n/vi/admin.ts +++ b/shared/src/i18n/vi/admin.ts @@ -129,11 +129,51 @@ const admin: TranslationStrings = { 'admin.requireMfa': 'Yêu cầu xác thực hai yếu tố (2FA)', 'admin.requireMfaHint': 'Người dùng không có 2FA phải hoàn tất thiết lập trong Cài đặt trước khi sử dụng ứng dụng.', 'admin.apiKeys': 'API Key', - 'admin.apiKeysHint': 'Không bắt buộc. Cho phép dữ liệu địa điểm mở rộng như ảnh và thời tiết.', + 'admin.apiKeysHint': 'Dữ liệu địa điểm đến từ đâu. Chỉ mục TREK không cần khóa; hai nhà cung cấp bên dưới là tùy chọn.', + 'admin.trekApi.badgeDefault': 'Nguồn mặc định khuyến nghị', + 'admin.googleCaveat.badge': 'Không khuyến nghị', + 'admin.googleCaveat.body': + 'TREK là phần mềm nguồn mở và ở đây chúng tôi không trung lập. Ở quy mô này, đánh giá và ảnh của các cửa hàng thông thường chỉ có ở Google, và đó là độc quyền. Ô này có mặt vì không có lựa chọn khác, không phải vì chúng tôi khuyến nghị. Khi đó mọi truy vấn đều đi tới Google.', + 'admin.trekApi.badgeSoon': + 'Sắp có', + 'admin.trekApi.badgeActive': + 'Đang bật', + 'admin.trekApi.tagline': + 'Chỉ mục địa điểm của riêng TREK. Tìm kiếm không cần khóa Google, không hạn mức, và không ai đếm lượt tìm của bạn.', + 'admin.trekApi.factPlaces': + '73,6 triệu địa điểm trên toàn thế giới', + 'admin.trekApi.factNoKey': + 'Không khóa, không hạn mức', + 'admin.trekApi.factOffline': + 'Gói theo quốc gia dùng được ngoại tuyến', + 'admin.trekApi.factPrivacy': + 'Không bao giờ ghi lại nội dung tìm kiếm', + 'admin.trekApi.more': + 'Bên trong có gì', + 'admin.trekApi.fieldPhone': + 'Điện thoại', + 'admin.trekApi.fieldStableId': + 'Mã định danh ổn định', + 'admin.trekApi.includedNote': + 'Mô tả lấy từ trang web của chính địa điểm, giờ mở cửa lấy từ OpenStreetMap ở nơi đã được điền.', + 'admin.trekApi.notRatings': + 'Đánh giá', + 'admin.trekApi.notPhotos': + 'Ảnh của cửa hàng thông thường', + 'admin.trekApi.notIncludedNote': + 'Không nguồn mở nào có cả hai, với bất kỳ giá nào. Với hai thứ đó khóa Google vẫn là con đường duy nhất.', + 'admin.trekApi.sourcesLabel': + 'Nguồn', + 'admin.trekApi.sourcesNote': + 'Mọi trường trong phản hồi đều cho biết nó đến từ nguồn nào.', + 'admin.trekApi.included': + 'Có', + 'admin.trekApi.notIncluded': + 'Không có', 'admin.mapsKey': 'Google Maps API Key', 'admin.mapsKeyHint': 'Cần thiết cho tìm kiếm địa điểm. Truy cập tại console.cloud.google.com', 'admin.mapsKeyHintLong': - 'Nếu không có khóa API, OpenStreetMap sẽ được sử dụng để tìm kiếm địa điểm. Với khóa Google API, bạn cũng có thể tải ảnh, xếp hạng và giờ mở cửa. Nhận một cái tại console.cloud.google.com.', + 'Không có khóa API của Google thì TREK API được khuyến nghị sẽ được dùng. Có khóa thì có thể tải thêm ảnh, đánh giá và giờ mở cửa. Tạo khóa tại console.cloud.google.com.', 'admin.recommended': 'Khuyến khích', 'admin.weatherKey': 'OpenWeatherMap API Key', 'admin.weatherKeyHint': 'Đối với dữ liệu thời tiết. Miễn phí tại openweathermap.org', @@ -157,6 +197,8 @@ const admin: TranslationStrings = { 'admin.fileTypesFormat': 'Các phần mở rộng được phân tách bằng dấu phẩy (ví dụ: jpg,png,pdf,doc). Sử dụng * để cho phép tất cả các loại.', 'admin.fileTypesSaved': 'Đã lưu cài đặt loại tệp', + 'admin.googleOptions': 'Khóa được dùng vào việc gì', + 'admin.googleOptionsSummary': '{on}/{total} đang bật', 'admin.placesPhotos.title': 'Đặt ảnh', 'admin.placesPhotos.subtitle': 'Tìm nạp ảnh từ Google Địa điểm API. Tắt để lưu hạn ngạch API. Hình ảnh của Wikimedia không bị ảnh hưởng.', diff --git a/shared/src/i18n/zh-TW/admin.ts b/shared/src/i18n/zh-TW/admin.ts index 0fa1b1c92..bc7d1bb9c 100644 --- a/shared/src/i18n/zh-TW/admin.ts +++ b/shared/src/i18n/zh-TW/admin.ts @@ -125,11 +125,51 @@ const admin: TranslationStrings = { 'admin.requireMfa': '要求雙因素身份驗證(2FA)', 'admin.requireMfaHint': '未啟用 2FA 的使用者必須先完成設定中的配置才能使用應用。', 'admin.apiKeys': 'API 金鑰', - 'admin.apiKeysHint': '可選。啟用地點的擴充套件資料,如照片和天氣。', + 'admin.apiKeysHint': '地點資料從哪裡來。TREK 索引不需要金鑰,下面兩個提供者是選用的。', + 'admin.trekApi.badgeDefault': '推薦的預設來源', + 'admin.googleCaveat.badge': '不建議', + 'admin.googleCaveat.body': + 'TREK 是開源軟體,在這件事上我們並不中立。在這個規模上,評分和一般店家的照片只有 Google 有,這就是壟斷。這個欄位存在是因為沒有替代品,而不是因為我們推薦。填了它,每次查詢都會送往 Google。', + 'admin.trekApi.badgeSoon': + '即將推出', + 'admin.trekApi.badgeActive': + '已啟用', + 'admin.trekApi.tagline': + 'TREK 自己的地點索引。不用 Google 金鑰,沒有配額,也沒有人統計你搜了什麼。', + 'admin.trekApi.factPlaces': + '全球 7363 萬個地點', + 'admin.trekApi.factNoKey': + '不需金鑰,沒有配額', + 'admin.trekApi.factOffline': + '國家資料包可離線使用', + 'admin.trekApi.factPrivacy': + '搜尋內容從不記錄', + 'admin.trekApi.more': + '裡面有什麼', + 'admin.trekApi.fieldPhone': + '電話', + 'admin.trekApi.fieldStableId': + '穩定識別碼', + 'admin.trekApi.includedNote': + '簡介取自地點自己的網站,營業時間在已填寫之處取自 OpenStreetMap。', + 'admin.trekApi.notRatings': + '評分', + 'admin.trekApi.notPhotos': + '一般店家的照片', + 'admin.trekApi.notIncludedNote': + '兩者在任何開放資料集中都沒有,出多少錢也沒有。這兩項仍然只能走 Google 金鑰。', + 'admin.trekApi.sourcesLabel': + '來源', + 'admin.trekApi.sourcesNote': + '回應裡的每個欄位都會標明它來自其中哪一個。', + 'admin.trekApi.included': + '包含', + 'admin.trekApi.notIncluded': + '不包含', 'admin.mapsKey': 'Google Maps API 金鑰', 'admin.mapsKeyHint': '用於地點搜尋。在 console.cloud.google.com 獲取', 'admin.mapsKeyHintLong': - '沒有 API 金鑰時,使用 OpenStreetMap 搜尋地點。有了 Google API 金鑰,還可以載入照片、評分和營業時間。在 console.cloud.google.com 獲取。', + '沒有 Google API 金鑰時,會使用推薦的 TREK API。有金鑰時可以額外載入照片、評分和營業時間。可在 console.cloud.google.com 建立金鑰。', 'admin.recommended': '推薦', 'admin.weatherKey': 'OpenWeatherMap API 金鑰', 'admin.weatherKeyHint': '用於天氣資料。在 openweathermap.org 免費獲取', @@ -151,6 +191,8 @@ const admin: TranslationStrings = { 'admin.fileTypesHint': '配置使用者可以上傳的檔案型別。', 'admin.fileTypesFormat': '以逗號分隔的副檔名(如 jpg,png,pdf,doc)。使用 * 允許所有型別。', 'admin.fileTypesSaved': '檔案型別設定已儲存', + 'admin.googleOptions': '該金鑰的用途', + 'admin.googleOptionsSummary': '已開啟 {on}/{total}', 'admin.placesPhotos.title': '地點照片', 'admin.placesPhotos.subtitle': '從 Google Places API 獲取照片。停用可節省 API 配額。Wikimedia 照片不受影響。', 'admin.placesAutocomplete.title': '地點自動補全', diff --git a/shared/src/i18n/zh/admin.ts b/shared/src/i18n/zh/admin.ts index 6a742838c..bf0cbc1b0 100644 --- a/shared/src/i18n/zh/admin.ts +++ b/shared/src/i18n/zh/admin.ts @@ -86,11 +86,51 @@ const admin: TranslationStrings = { 'admin.requireMfa': '要求双因素身份验证(2FA)', 'admin.requireMfaHint': '未启用 2FA 的用户必须先完成设置中的配置才能使用应用。', 'admin.apiKeys': 'API 密钥', - 'admin.apiKeysHint': '可选。启用地点的扩展数据,如照片和天气。', + 'admin.apiKeysHint': '地点数据从哪里来。TREK 索引不需要密钥,下面两个提供方是可选的。', + 'admin.trekApi.badgeDefault': '推荐的默认来源', + 'admin.googleCaveat.badge': '不推荐', + 'admin.googleCaveat.body': + 'TREK 是开源软件,在这件事上我们并不中立。在这个规模上,评分和普通店铺的照片只有 Google 有,这就是垄断。这个输入框存在是因为没有替代品,而不是因为我们推荐。填了它,每次查询都会发往 Google。', + 'admin.trekApi.badgeSoon': + '即将推出', + 'admin.trekApi.badgeActive': + '已启用', + 'admin.trekApi.tagline': + 'TREK 自己的地点索引。不用 Google 密钥,没有配额,也没有人统计你搜了什么。', + 'admin.trekApi.factPlaces': + '全球 7363 万个地点', + 'admin.trekApi.factNoKey': + '无需密钥,没有配额', + 'admin.trekApi.factOffline': + '国家数据包可离线使用', + 'admin.trekApi.factPrivacy': + '搜索内容从不记录', + 'admin.trekApi.more': + '里面有什么', + 'admin.trekApi.fieldPhone': + '电话', + 'admin.trekApi.fieldStableId': + '稳定标识', + 'admin.trekApi.includedNote': + '简介取自地点自己的网站,营业时间在已填写的地方取自 OpenStreetMap。', + 'admin.trekApi.notRatings': + '评分', + 'admin.trekApi.notPhotos': + '普通店铺的照片', + 'admin.trekApi.notIncludedNote': + '两者在任何开放数据集中都没有,出多少钱也没有。这两项仍然只能走 Google 密钥。', + 'admin.trekApi.sourcesLabel': + '来源', + 'admin.trekApi.sourcesNote': + '响应里的每个字段都会标明它来自其中哪一个。', + 'admin.trekApi.included': + '包含', + 'admin.trekApi.notIncluded': + '不包含', 'admin.mapsKey': 'Google Maps API 密钥', 'admin.mapsKeyHint': '用于地点搜索。在 console.cloud.google.com 获取', 'admin.mapsKeyHintLong': - '没有 API 密钥时,使用 OpenStreetMap 搜索地点。有了 Google API 密钥,还可以加载照片、评分和营业时间。在 console.cloud.google.com 获取。', + '没有 Google API 密钥时,会使用推荐的 TREK API。有密钥时可以额外加载照片、评分和营业时间。可在 console.cloud.google.com 创建密钥。', 'admin.recommended': '推荐', 'admin.weatherKey': 'OpenWeatherMap API 密钥', 'admin.weatherKeyHint': '用于天气数据。在 openweathermap.org 免费获取', @@ -112,6 +152,8 @@ const admin: TranslationStrings = { 'admin.fileTypesHint': '配置用户可以上传的文件类型。', 'admin.fileTypesFormat': '以逗号分隔的扩展名(如 jpg,png,pdf,doc)。使用 * 允许所有类型。', 'admin.fileTypesSaved': '文件类型设置已保存', + 'admin.googleOptions': '该密钥的用途', + 'admin.googleOptionsSummary': '已开启 {on}/{total}', 'admin.placesPhotos.title': '地点照片', 'admin.placesPhotos.subtitle': '从 Google Places API 获取照片。禁用可节省 API 配额。Wikimedia 照片不受影响。', 'admin.placesAutocomplete.title': '地点自动补全',