diff --git a/client/src/pages/admin/AdminSettingsTab.test.tsx b/client/src/pages/admin/AdminSettingsTab.test.tsx index 19409649b..745698efc 100644 --- a/client/src/pages/admin/AdminSettingsTab.test.tsx +++ b/client/src/pages/admin/AdminSettingsTab.test.tsx @@ -25,6 +25,9 @@ function renderTab(overrides: Record = {}) { } /** The card (a settingsKit SettingsCard, a
) whose

matches the given heading text. */ +/** The card with the TREK services, the providers and the search log, apart from the keys. */ +const SERVICES = 'Place search and routing'; + function card(heading: string | RegExp): HTMLElement { return screen.getByRole('heading', { name: heading }).closest('section')!; } @@ -305,14 +308,14 @@ describe('AdminSettingsTab', () => { // failing, which is quiet enough to be mistaken for the provider working. renderTab({ placesProvider: 'amap', hasAmapKey: false }); expect( - within(card('API Keys')).getByText(/TREK index and OpenStreetMap alone/i), + within(card(SERVICES)).getByText(/TREK index and OpenStreetMap alone/i), ).toBeInTheDocument(); }); it('FE-ADMSET-022d: saving the provider goes through updateAppSettings, not the keys', () => { const admin = renderTab({ placesProvider: 'auto' }); // A CustomSelect: the trigger shows the current choice, the menu is portaled. - fireEvent.click(within(card('API Keys')).getByRole('button', { name: 'Automatic' })); + fireEvent.click(within(card(SERVICES)).getByRole('button', { name: 'Automatic' })); fireEvent.click(screen.getByRole('button', { name: 'Amap (高德地图)' })); expect(admin.handleSavePlacesProvider).toHaveBeenCalledWith('amap'); }); @@ -322,10 +325,9 @@ describe('AdminSettingsTab', () => { // still the admin's call (server managed.ts lists places_provider as theirs). renderTab({ managed: true, placesProvider: 'auto' }); - const keys = within(card('API Keys')); - expect(keys.queryByLabelText('Google Maps API Key')).not.toBeInTheDocument(); - expect(keys.queryByLabelText(/Amap/)).not.toBeInTheDocument(); - expect(keys.getByRole('button', { name: 'Automatic' })).toBeInTheDocument(); + expect(screen.queryByRole('heading', { name: 'API Keys' })).not.toBeInTheDocument(); + expect(screen.queryByLabelText('Google Maps API Key')).not.toBeInTheDocument(); + expect(within(card(SERVICES)).getByRole('button', { name: 'Automatic' })).toBeInTheDocument(); }); it('FE-ADMSET-022f: an operator key keeps the managed install quiet', () => { @@ -334,7 +336,7 @@ describe('AdminSettingsTab', () => { renderTab({ managed: true, placesProvider: 'google', mapsKey: '', hasMapsKey: true }); expect( - within(card('API Keys')).queryByText(/falls back to OpenStreetMap/i), + within(card(SERVICES)).queryByText(/falls back to OpenStreetMap/i), ).not.toBeInTheDocument(); }); @@ -479,10 +481,10 @@ describe('AdminSettingsTab', () => { expect(hint.textContent).toMatch(/provider/i); }); - it('FE-ADMSET-031: the API keys card leads with the TREK index and no weather panel', () => { + it('FE-ADMSET-031: the place search card leads with the TREK index and no weather panel', () => { renderTab(); - const keys = card('API Keys'); + const keys = card(SERVICES); expect(within(keys).getByRole('img', { name: 'TREK Places API' })).toBeInTheDocument(); expect(within(keys).getByText('Recommended default')).toBeInTheDocument(); // Weather needs no key, so it left a card about keys. The panel itself lives diff --git a/client/src/pages/admin/AdminSettingsTab.tsx b/client/src/pages/admin/AdminSettingsTab.tsx index c30f57fc4..10b309873 100644 --- a/client/src/pages/admin/AdminSettingsTab.tsx +++ b/client/src/pages/admin/AdminSettingsTab.tsx @@ -2,7 +2,7 @@ import React from 'react' import { adminApi } from '../../api/client' import { getApiErrorMessage } from '../../types' import { - AlertTriangle, CheckCircle, Eye, EyeOff, FileType, Fingerprint, KeyRound, Loader2, LockKeyhole, LogIn, + AlertTriangle, CheckCircle, Compass, Eye, EyeOff, FileType, Fingerprint, KeyRound, Loader2, LockKeyhole, LogIn, RefreshCw, Save, ShieldCheck, XCircle, } from 'lucide-react' import ToggleSwitch from '../../components/Settings/ToggleSwitch' @@ -350,9 +350,7 @@ export default function AdminSettingsTab({ admin, t }: AdminSettingsTabProps): R /> )} - -
{/* Allowed File Types */}
@@ -372,24 +370,134 @@ export default function AdminSettingsTab({ admin, t }: AdminSettingsTabProps): R onClick={saveFileTypes} /> +
- {/* API Keys. - The card itself stays on a managed install: the TREK index needs no key, - and which keyed provider answers place search is the admin's call there - too (server managed.ts). What folds away is the operator's: the keys, - what a lookup costs, and the per-place Google switches. Those switches - sit under the keys rather than flat beside them because they are set - once and then never touched, and side by side a rarely-used option - looked 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. */} +
+ + {/* Place search and routing: TREK's own services first, then which provider + answers search and transit, and the search log. Apart from the keys, so a + keyless install reads its whole setup in one card. Everything here saves + as it is changed. */} + + + {!managed && (<> + {/* 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. 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')} + {/* The app's own select rather than the browser's: a native option list + is drawn by the OS, so it ignores the scheme, the radius and the + text-size setting the rest of this card follows. The menu portals to + the body, which is what lets it escape the card's overflow-hidden. */} + { + // A native select stayed silent when the option already selected was + // picked again; this one reports every pick, and a no-op PUT is still + // a write on an audited settings route. + if (value === transitProvider) return + const next = value === 'google' ? 'google' : 'transitous' + const previous = transitProvider + setTransitProviderState(next) + try { + const saved = await adminApi.updateTransitProvider(next) + setTransitGoogleKeySource(saved.googleKeySource) + } catch (err: unknown) { + // The select springs back on its own; without a word that reads + // as a broken control, not as a save that failed. + setTransitProviderState(previous) + toast.error(getApiErrorMessage(err, t('common.error'))) + } + }} + options={[ + { value: 'transitous', label: t('admin.transitProvider.transitous') }, + { value: 'google', label: t('admin.transitProvider.google') }, + ]} + /> + + {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')} + )} + {transitProvider === 'google' && transitGoogleKeySource === 'user-row' && ( + {t('admin.transitProvider.personalKeyWarning')} + )} + + )} + {/* Which keyed provider answers place search. Outside the managed guard + on purpose: the operator owns the credentials, but what their users + search against is still the admin's call (see server managed.ts). */} + + {t('admin.placesProvider.subtitle')} +
+ { + if (value === placesProvider) return + void handleSavePlacesProvider(String(value)) + }} + options={[ + { value: 'auto', label: t('admin.placesProvider.auto') }, + { value: 'google', label: t('admin.placesProvider.google') }, + { value: 'amap', label: t('admin.placesProvider.amap') }, + { value: 'openstreetmap', label: t('admin.placesProvider.openstreetmap') }, + ]} + /> +
+ {/* A provider chosen without a key behind it answers with + OpenStreetMap rather than failing, which is quiet enough to be + mistaken for the provider working. Say so. Asked of app-config + and not of the fields above: those are empty on a managed + install and on an operator key that came from the environment, + and saying "falls back to OpenStreetMap" to an admin whose + search works is worse than saying nothing. */} + {((placesProvider === 'google' && !hasMapsKey) || (placesProvider === 'amap' && !hasAmapKey)) && ( + {t('admin.placesProvider.missingKey')} + )} +
+ + {/* The search log sits with the index rather than with a provider: + it records which result somebody picked, so a candidate index can + be judged against real searches later. Off unless switched on, + and nothing it records leaves the instance. */} + + togglePlaceShadow(!placeShadowEnabled)} + /> + } + /> + + +
+ + {/* API Keys: only the keyed providers, Google, Unsplash and Amap. They are + the operator's on a managed install, so the card is not shown there; + which provider answers search stays the admin's call in the card above + (server managed.ts). The per-place Google switches sit under the key + because they are set once and then never touched. */} + {!managed && ( {/* 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. */} @@ -530,113 +638,10 @@ export default function AdminSettingsTab({ admin, t }: AdminSettingsTabProps): R /> {t('admin.amapKeyHint')} - {/* 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. 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')} - {/* The app's own select rather than the browser's: a native option list - is drawn by the OS, so it ignores the scheme, the radius and the - text-size setting the rest of this card follows. The menu portals to - the body, which is what lets it escape the card's overflow-hidden. */} - { - // A native select stayed silent when the option already selected was - // picked again; this one reports every pick, and a no-op PUT is still - // a write on an audited settings route. - if (value === transitProvider) return - const next = value === 'google' ? 'google' : 'transitous' - const previous = transitProvider - setTransitProviderState(next) - try { - const saved = await adminApi.updateTransitProvider(next) - setTransitGoogleKeySource(saved.googleKeySource) - } catch (err: unknown) { - // The select springs back on its own; without a word that reads - // as a broken control, not as a save that failed. - setTransitProviderState(previous) - toast.error(getApiErrorMessage(err, t('common.error'))) - } - }} - options={[ - { value: 'transitous', label: t('admin.transitProvider.transitous') }, - { value: 'google', label: t('admin.transitProvider.google') }, - ]} - /> - - {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')} - )} - {transitProvider === 'google' && transitGoogleKeySource === 'user-row' && ( - {t('admin.transitProvider.personalKeyWarning')} - )} - - )} - - {/* Which keyed provider answers place search. Outside the managed guard - on purpose: the operator owns the credentials, but what their users - search against is still the admin's call (see server managed.ts). */} - - {t('admin.placesProvider.subtitle')} -
- { - if (value === placesProvider) return - void handleSavePlacesProvider(String(value)) - }} - options={[ - { value: 'auto', label: t('admin.placesProvider.auto') }, - { value: 'google', label: t('admin.placesProvider.google') }, - { value: 'amap', label: t('admin.placesProvider.amap') }, - { value: 'openstreetmap', label: t('admin.placesProvider.openstreetmap') }, - ]} - /> -
- {/* A provider chosen without a key behind it answers with - OpenStreetMap rather than failing, which is quiet enough to be - mistaken for the provider working. Say so. Asked of app-config - and not of the fields above: those are empty on a managed - install and on an operator key that came from the environment, - and saying "falls back to OpenStreetMap" to an admin whose - search works is worse than saying nothing. */} - {((placesProvider === 'google' && !hasMapsKey) || (placesProvider === 'amap' && !hasAmapKey)) && ( - {t('admin.placesProvider.missingKey')} - )} -
- - {/* The search log sits with the index rather than with a provider: - it records which result somebody picked, so a candidate index can - be judged against real searches later. Off unless switched on, - and nothing it records leaves the instance. */} - - togglePlaceShadow(!placeShadowEnabled)} - /> - } - /> - - - {!managed && } +
+ )}
diff --git a/shared/src/i18n/ar/admin.ts b/shared/src/i18n/ar/admin.ts index 570ff8340..dab66593b 100644 --- a/shared/src/i18n/ar/admin.ts +++ b/shared/src/i18n/ar/admin.ts @@ -1181,5 +1181,7 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'ليس الآن', 'admin.contribute.prompt.decline': 'لا، شكرًا', 'admin.contribute.prompt.thanks': 'شكرًا لك. تُشارك تفاصيل الأماكن الآن.', + 'admin.servicesCard.title': 'البحث عن الأماكن والتوجيه', + 'admin.servicesCard.hint': 'خدمات TREK الخاصة، والمزوّد الذي يجيب عن البحث والنقل العام، وسجل البحث.', }; export default admin; diff --git a/shared/src/i18n/az/admin.ts b/shared/src/i18n/az/admin.ts index 4a41fbf7c..f2a96c5f0 100644 --- a/shared/src/i18n/az/admin.ts +++ b/shared/src/i18n/az/admin.ts @@ -1169,6 +1169,9 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'İndi yox', 'admin.contribute.prompt.decline': 'Xeyr, təşəkkürlər', 'admin.contribute.prompt.thanks': 'Təşəkkür edirik. Məkan məlumatları indi paylaşılır.', + 'admin.servicesCard.title': 'Məkan axtarışı və marşrutlaşdırma', + 'admin.servicesCard.hint': + 'TREK-in öz xidmətləri, axtarışa və ictimai nəqliyyata hansı provayderin cavab verdiyi və axtarış jurnalı.', }; export default admin; diff --git a/shared/src/i18n/br/admin.ts b/shared/src/i18n/br/admin.ts index 4bf670085..14f35b6de 100644 --- a/shared/src/i18n/br/admin.ts +++ b/shared/src/i18n/br/admin.ts @@ -1144,5 +1144,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Agora não', 'admin.contribute.prompt.decline': 'Não, obrigado', 'admin.contribute.prompt.thanks': 'Obrigado. Os detalhes dos locais agora são compartilhados.', + 'admin.servicesCard.title': 'Busca de lugares e rotas', + 'admin.servicesCard.hint': + 'Os serviços próprios do TREK, qual provedor responde à busca e ao transporte público, e o registro de buscas.', }; export default admin; diff --git a/shared/src/i18n/ca/admin.ts b/shared/src/i18n/ca/admin.ts index 3a26dda8d..faea2c6e2 100644 --- a/shared/src/i18n/ca/admin.ts +++ b/shared/src/i18n/ca/admin.ts @@ -1158,5 +1158,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Ara no', 'admin.contribute.prompt.decline': 'No, gràcies', 'admin.contribute.prompt.thanks': 'Gràcies. Els detalls dels llocs ja es comparteixen.', + 'admin.servicesCard.title': 'Cerca de llocs i rutes', + 'admin.servicesCard.hint': + 'Els serveis propis de TREK, quin proveïdor respon la cerca i el transport públic, i el registre de cerques.', }; export default admin; diff --git a/shared/src/i18n/cs/admin.ts b/shared/src/i18n/cs/admin.ts index 4e11484b5..cacdc5650 100644 --- a/shared/src/i18n/cs/admin.ts +++ b/shared/src/i18n/cs/admin.ts @@ -1145,5 +1145,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Teď ne', 'admin.contribute.prompt.decline': 'Ne, děkuji', 'admin.contribute.prompt.thanks': 'Děkujeme. Údaje o místech se nyní sdílejí.', + 'admin.servicesCard.title': 'Vyhledávání míst a trasování', + 'admin.servicesCard.hint': + 'Vlastní služby TREK, který poskytovatel odpovídá na vyhledávání a veřejnou dopravu, a protokol vyhledávání.', }; export default admin; diff --git a/shared/src/i18n/de/admin.ts b/shared/src/i18n/de/admin.ts index aaf3cc69b..23a4d8fad 100644 --- a/shared/src/i18n/de/admin.ts +++ b/shared/src/i18n/de/admin.ts @@ -1152,5 +1152,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Nicht jetzt', 'admin.contribute.prompt.decline': 'Nein, danke', 'admin.contribute.prompt.thanks': 'Danke! Ortsdetails werden jetzt geteilt.', + 'admin.servicesCard.title': 'Ortssuche und Routing', + 'admin.servicesCard.hint': + 'TREKs eigene Dienste, welcher Anbieter Suche und ÖPNV beantwortet, und das Suchprotokoll.', }; export default admin; diff --git a/shared/src/i18n/en/admin.ts b/shared/src/i18n/en/admin.ts index 7eec35a1b..8d60b0da0 100644 --- a/shared/src/i18n/en/admin.ts +++ b/shared/src/i18n/en/admin.ts @@ -1116,5 +1116,7 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Not now', 'admin.contribute.prompt.decline': 'No, thanks', 'admin.contribute.prompt.thanks': 'Thank you. Place details are now shared.', + 'admin.servicesCard.title': 'Place search and routing', + 'admin.servicesCard.hint': "TREK's own services, which provider answers search and transit, and the search log.", }; export default admin; diff --git a/shared/src/i18n/es/admin.ts b/shared/src/i18n/es/admin.ts index 6f723d730..f05cadaf9 100644 --- a/shared/src/i18n/es/admin.ts +++ b/shared/src/i18n/es/admin.ts @@ -1162,5 +1162,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Ahora no', 'admin.contribute.prompt.decline': 'No, gracias', 'admin.contribute.prompt.thanks': 'Gracias. Los detalles de los lugares ya se comparten.', + 'admin.servicesCard.title': 'Búsqueda de lugares y rutas', + 'admin.servicesCard.hint': + 'Los servicios propios de TREK, qué proveedor responde a la búsqueda y al transporte público, y el registro de búsquedas.', }; export default admin; diff --git a/shared/src/i18n/et/admin.ts b/shared/src/i18n/et/admin.ts index c6453ed25..371d0c203 100644 --- a/shared/src/i18n/et/admin.ts +++ b/shared/src/i18n/et/admin.ts @@ -1138,5 +1138,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Mitte praegu', 'admin.contribute.prompt.decline': 'Ei, aitäh', 'admin.contribute.prompt.thanks': 'Aitäh. Kohtade andmeid nüüd jagatakse.', + 'admin.servicesCard.title': 'Kohtade otsing ja marsruutimine', + 'admin.servicesCard.hint': + 'TREKi enda teenused, milline teenusepakkuja vastab otsingule ja ühistranspordile, ning otsingulogi.', }; export default admin; diff --git a/shared/src/i18n/fr/admin.ts b/shared/src/i18n/fr/admin.ts index 443cdf579..744f328b2 100644 --- a/shared/src/i18n/fr/admin.ts +++ b/shared/src/i18n/fr/admin.ts @@ -1166,5 +1166,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Pas maintenant', 'admin.contribute.prompt.decline': 'Non, merci', 'admin.contribute.prompt.thanks': 'Merci. Les détails des lieux sont désormais partagés.', + 'admin.servicesCard.title': 'Recherche de lieux et itinéraires', + 'admin.servicesCard.hint': + 'Les services propres à TREK, le fournisseur qui répond à la recherche et aux transports en commun, et le journal des recherches.', }; export default admin; diff --git a/shared/src/i18n/gr/admin.ts b/shared/src/i18n/gr/admin.ts index c0cc78273..da104ed76 100644 --- a/shared/src/i18n/gr/admin.ts +++ b/shared/src/i18n/gr/admin.ts @@ -1171,5 +1171,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Όχι τώρα', 'admin.contribute.prompt.decline': 'Όχι, ευχαριστώ', 'admin.contribute.prompt.thanks': 'Σας ευχαριστούμε. Τα στοιχεία των τόπων κοινοποιούνται πλέον.', + 'admin.servicesCard.title': 'Αναζήτηση τοποθεσιών και δρομολόγηση', + 'admin.servicesCard.hint': + 'Οι δικές υπηρεσίες του TREK, ποιος πάροχος απαντά στην αναζήτηση και στα μέσα μεταφοράς, και το αρχείο αναζητήσεων.', }; export default admin; diff --git a/shared/src/i18n/hu/admin.ts b/shared/src/i18n/hu/admin.ts index cdd2b48dc..d334d7914 100644 --- a/shared/src/i18n/hu/admin.ts +++ b/shared/src/i18n/hu/admin.ts @@ -1158,5 +1158,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Most nem', 'admin.contribute.prompt.decline': 'Nem, köszönöm', 'admin.contribute.prompt.thanks': 'Köszönjük. A helyadatok megosztása elindult.', + 'admin.servicesCard.title': 'Helykeresés és útvonaltervezés', + 'admin.servicesCard.hint': + 'A TREK saját szolgáltatásai, melyik szolgáltató válaszol a keresésre és a tömegközlekedésre, valamint a keresési napló.', }; export default admin; diff --git a/shared/src/i18n/id/admin.ts b/shared/src/i18n/id/admin.ts index 124dc7531..56ffc5c1b 100644 --- a/shared/src/i18n/id/admin.ts +++ b/shared/src/i18n/id/admin.ts @@ -1128,5 +1128,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Nanti saja', 'admin.contribute.prompt.decline': 'Tidak, terima kasih', 'admin.contribute.prompt.thanks': 'Terima kasih. Detail tempat sekarang dibagikan.', + 'admin.servicesCard.title': 'Pencarian tempat dan rute', + 'admin.servicesCard.hint': + 'Layanan milik TREK sendiri, penyedia yang menjawab pencarian dan transportasi umum, serta log pencarian.', }; export default admin; diff --git a/shared/src/i18n/it/admin.ts b/shared/src/i18n/it/admin.ts index 62e4d6ebe..3e79a9c28 100644 --- a/shared/src/i18n/it/admin.ts +++ b/shared/src/i18n/it/admin.ts @@ -1154,5 +1154,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Non ora', 'admin.contribute.prompt.decline': 'No, grazie', 'admin.contribute.prompt.thanks': 'Grazie. I dettagli dei luoghi ora sono condivisi.', + 'admin.servicesCard.title': 'Ricerca luoghi e percorsi', + 'admin.servicesCard.hint': + 'I servizi propri di TREK, quale fornitore risponde alla ricerca e ai trasporti pubblici, e il registro delle ricerche.', }; export default admin; diff --git a/shared/src/i18n/ja/admin.ts b/shared/src/i18n/ja/admin.ts index 802033801..21de1b019 100644 --- a/shared/src/i18n/ja/admin.ts +++ b/shared/src/i18n/ja/admin.ts @@ -1087,5 +1087,7 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': '今はしない', 'admin.contribute.prompt.decline': 'いいえ', 'admin.contribute.prompt.thanks': 'ありがとうございます。場所の詳細の共有を開始しました。', + 'admin.servicesCard.title': '場所検索とルート案内', + 'admin.servicesCard.hint': 'TREK独自のサービス、検索と公共交通に応答するプロバイダー、そして検索ログ。', }; export default admin; diff --git a/shared/src/i18n/ko/admin.ts b/shared/src/i18n/ko/admin.ts index 2807ed57e..7239950e8 100644 --- a/shared/src/i18n/ko/admin.ts +++ b/shared/src/i18n/ko/admin.ts @@ -1076,5 +1076,7 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': '나중에', 'admin.contribute.prompt.decline': '아니요', 'admin.contribute.prompt.thanks': '감사합니다. 이제 장소 정보가 공유됩니다.', + 'admin.servicesCard.title': '장소 검색 및 경로 안내', + 'admin.servicesCard.hint': 'TREK 자체 서비스, 검색과 대중교통에 응답하는 제공자, 그리고 검색 기록.', }; export default admin; diff --git a/shared/src/i18n/nl/admin.ts b/shared/src/i18n/nl/admin.ts index 758afbb28..0f419a85d 100644 --- a/shared/src/i18n/nl/admin.ts +++ b/shared/src/i18n/nl/admin.ts @@ -1150,5 +1150,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Nu niet', 'admin.contribute.prompt.decline': 'Nee, bedankt', 'admin.contribute.prompt.thanks': 'Bedankt. Plaatsdetails worden nu gedeeld.', + 'admin.servicesCard.title': 'Plaatsen zoeken en routeplanning', + 'admin.servicesCard.hint': + 'De eigen diensten van TREK, welke aanbieder zoeken en openbaar vervoer beantwoordt, en het zoeklogboek.', }; export default admin; diff --git a/shared/src/i18n/pl/admin.ts b/shared/src/i18n/pl/admin.ts index d9fa14d3d..9a54aedfc 100644 --- a/shared/src/i18n/pl/admin.ts +++ b/shared/src/i18n/pl/admin.ts @@ -1194,5 +1194,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Nie teraz', 'admin.contribute.prompt.decline': 'Nie, dziękuję', 'admin.contribute.prompt.thanks': 'Dziękujemy. Dane miejsc są teraz udostępniane.', + 'admin.servicesCard.title': 'Wyszukiwanie miejsc i wyznaczanie tras', + 'admin.servicesCard.hint': + 'Własne usługi TREK, dostawca odpowiadający na wyszukiwanie i transport publiczny oraz dziennik wyszukiwań.', }; export default admin; diff --git a/shared/src/i18n/ru/admin.ts b/shared/src/i18n/ru/admin.ts index 0993778a0..e65568ca2 100644 --- a/shared/src/i18n/ru/admin.ts +++ b/shared/src/i18n/ru/admin.ts @@ -1184,5 +1184,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Не сейчас', 'admin.contribute.prompt.decline': 'Нет, спасибо', 'admin.contribute.prompt.thanks': 'Спасибо. Данные о местах теперь передаются.', + 'admin.servicesCard.title': 'Поиск мест и маршруты', + 'admin.servicesCard.hint': + 'Собственные сервисы TREK, какой провайдер отвечает за поиск и общественный транспорт, и журнал поиска.', }; export default admin; diff --git a/shared/src/i18n/sk/admin.ts b/shared/src/i18n/sk/admin.ts index f8c370f1b..610699237 100644 --- a/shared/src/i18n/sk/admin.ts +++ b/shared/src/i18n/sk/admin.ts @@ -1157,5 +1157,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Teraz nie', 'admin.contribute.prompt.decline': 'Nie, ďakujem', 'admin.contribute.prompt.thanks': 'Ďakujeme. Údaje o miestach sa teraz zdieľajú.', + 'admin.servicesCard.title': 'Vyhľadávanie miest a trasovanie', + 'admin.servicesCard.hint': + 'Vlastné služby TREK, ktorý poskytovateľ odpovedá na vyhľadávanie a verejnú dopravu, a protokol vyhľadávania.', }; export default admin; diff --git a/shared/src/i18n/sv/admin.ts b/shared/src/i18n/sv/admin.ts index 4d2af9cfd..7ccd79ecc 100644 --- a/shared/src/i18n/sv/admin.ts +++ b/shared/src/i18n/sv/admin.ts @@ -1141,5 +1141,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Inte nu', 'admin.contribute.prompt.decline': 'Nej tack', 'admin.contribute.prompt.thanks': 'Tack. Platsuppgifter delas nu.', + 'admin.servicesCard.title': 'Platssökning och vägvisning', + 'admin.servicesCard.hint': + 'TREKs egna tjänster, vilken leverantör som svarar på sökning och kollektivtrafik, och sökloggen.', }; export default admin; diff --git a/shared/src/i18n/th/admin.ts b/shared/src/i18n/th/admin.ts index b579ecacd..ad7d36cd9 100644 --- a/shared/src/i18n/th/admin.ts +++ b/shared/src/i18n/th/admin.ts @@ -1083,5 +1083,7 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'ไว้ก่อน', 'admin.contribute.prompt.decline': 'ไม่ ขอบคุณ', 'admin.contribute.prompt.thanks': 'ขอบคุณ ตอนนี้รายละเอียดสถานที่ถูกแชร์แล้ว', + 'admin.servicesCard.title': 'การค้นหาสถานที่และเส้นทาง', + 'admin.servicesCard.hint': 'บริการของ TREK เอง ผู้ให้บริการที่ตอบการค้นหาและการขนส่งสาธารณะ และบันทึกการค้นหา', }; export default admin; diff --git a/shared/src/i18n/tr/admin.ts b/shared/src/i18n/tr/admin.ts index 966f66eab..e7325a899 100644 --- a/shared/src/i18n/tr/admin.ts +++ b/shared/src/i18n/tr/admin.ts @@ -1150,5 +1150,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Şimdi değil', 'admin.contribute.prompt.decline': 'Hayır, teşekkürler', 'admin.contribute.prompt.thanks': 'Teşekkürler. Yer ayrıntıları artık paylaşılıyor.', + 'admin.servicesCard.title': 'Yer arama ve rota', + 'admin.servicesCard.hint': + "TREK'in kendi hizmetleri, aramaya ve toplu taşımaya hangi sağlayıcının yanıt verdiği ve arama kaydı.", }; export default admin; diff --git a/shared/src/i18n/uk/admin.ts b/shared/src/i18n/uk/admin.ts index abdd499c6..05f67532f 100644 --- a/shared/src/i18n/uk/admin.ts +++ b/shared/src/i18n/uk/admin.ts @@ -1181,5 +1181,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Не зараз', 'admin.contribute.prompt.decline': 'Ні, дякую', 'admin.contribute.prompt.thanks': 'Дякуємо. Дані про місця тепер передаються.', + 'admin.servicesCard.title': 'Пошук місць і маршрути', + 'admin.servicesCard.hint': + 'Власні сервіси TREK, який постачальник відповідає на пошук і громадський транспорт, та журнал пошуку.', }; export default admin; diff --git a/shared/src/i18n/vi/admin.ts b/shared/src/i18n/vi/admin.ts index fe5caa4dd..c26bf2228 100644 --- a/shared/src/i18n/vi/admin.ts +++ b/shared/src/i18n/vi/admin.ts @@ -1127,5 +1127,8 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': 'Để sau', 'admin.contribute.prompt.decline': 'Không, cảm ơn', 'admin.contribute.prompt.thanks': 'Cảm ơn bạn. Chi tiết địa điểm hiện đã được chia sẻ.', + 'admin.servicesCard.title': 'Tìm kiếm địa điểm và chỉ đường', + 'admin.servicesCard.hint': + 'Các dịch vụ riêng của TREK, nhà cung cấp trả lời tìm kiếm và giao thông công cộng, và nhật ký tìm kiếm.', }; export default admin; diff --git a/shared/src/i18n/zh-TW/admin.ts b/shared/src/i18n/zh-TW/admin.ts index b4184b29b..5e6ecf1ac 100644 --- a/shared/src/i18n/zh-TW/admin.ts +++ b/shared/src/i18n/zh-TW/admin.ts @@ -1030,5 +1030,7 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': '暫不', 'admin.contribute.prompt.decline': '不,謝謝', 'admin.contribute.prompt.thanks': '謝謝。地點詳情現在已開始分享。', + 'admin.servicesCard.title': '地點搜尋與路線', + 'admin.servicesCard.hint': 'TREK 自有服務、負責搜尋與大眾運輸的提供者,以及搜尋紀錄。', }; export default admin; diff --git a/shared/src/i18n/zh/admin.ts b/shared/src/i18n/zh/admin.ts index 953ed401c..17aed608d 100644 --- a/shared/src/i18n/zh/admin.ts +++ b/shared/src/i18n/zh/admin.ts @@ -1029,5 +1029,7 @@ const admin: TranslationStrings = { 'admin.contribute.prompt.snooze': '暂不', 'admin.contribute.prompt.decline': '不,谢谢', 'admin.contribute.prompt.thanks': '谢谢。地点详情现在已开始分享。', + 'admin.servicesCard.title': '地点搜索与路线', + 'admin.servicesCard.hint': 'TREK 自有服务、负责搜索和公共交通的提供方,以及搜索日志。', }; export default admin; diff --git a/wiki/Admin-Settings.md b/wiki/Admin-Settings.md index 2b15eee29..90172d809 100644 --- a/wiki/Admin-Settings.md +++ b/wiki/Admin-Settings.md @@ -68,9 +68,9 @@ Two rules apply whatever the field says: See [Documents-and-Files](Documents-and-Files). -## API Keys +## Place search and routing -Where place data comes from. The TREK index needs no key; the providers below it are optional. The card is a column of blocks, in the order TREK recommends them. +TREK's own services, which provider answers search and transit, and the Place Search Log. Everything in this card is saved as soon as it is changed. ### TREK Places API @@ -80,6 +80,36 @@ The first block, marked **Recommended default**. It is TREK's own place index: s Inside the TREK Places API block. One switch turns sharing on for the whole installation, one per field (website, phone, e-mail, opening hours, description, photo) chooses what goes along. The figures show the values accepted in the last 30 days, the ones waiting to be sent and the day of the last accepted one. The fold **Transmission log** lists every value sent with its result, and **Withdraw everything sent** takes all of it back out of the index. The first visit of an admin opens a dialog that asks once; nothing is sent before **Agree and share**. See [Sharing place details](TREK-Places-API#sharing-place-details). +### Transit Provider + +Which service answers public transit search. It saves as soon as you pick. + +- **Transitous (free)**, the default: community GTFS feeds, free and keyless, with the best coverage in Europe. +- **Google**: uses the Google API key below, for regions Transitous has no data for. Google bills each search. + +With **Google** picked but no Google key configured, a warning says transit search is still using Transitous. When only your own personal Google key is set, a warning says that other members still fall back to Transitous until the key is saved under **API Keys** as the instance key. See [Transport-Flights-Trains-Cars](Transport-Flights-Trains-Cars). + +When the picked service finds no connection, the search asks the other one before it reports an empty result. From Google, that is Transitous. From **Transitous**, Google is asked only when an instance key (saved under **API Keys** or set through `PLACES_API_KEY`) is available and the **Daily limit for Google calls** is not reached; a member's personal key is never billed for it. Each such fallback counts as one billed Google search. An error from the picked service is shown as before and does not trigger the fallback. + +### Place search provider + +TREK's own index and OpenStreetMap answer every search. This picks who else is asked when they find nothing: + +- **Automatic** (default): prefers Google where a key exists, then Amap. +- **Google Places** +- **Amap (高德地图)** +- **OpenStreetMap** + +The choice saves at once. If the selected provider has no API key, a warning says that place search is answered by the TREK index and OpenStreetMap alone. See [Places-and-Search](Places-and-Search). + +### Place Search Log + +One switch, **off by default**. When it is on, TREK records which search result was picked, so a different place index can be measured against real searches later: the search text, the picked place's name and rounded coordinates, and where it stood in the result list. No user is stored with it, and nothing leaves the instance. Rows are deleted after 180 days, also while the switch is off. An admin can read the log through `GET /api/place-shadow/summary` and `GET /api/place-shadow/export`, and delete it with `DELETE /api/place-shadow`. + +## API Keys + +The keys of the optional providers: Google Maps, Unsplash and Amap. None of them is needed, the card above works without any key. On a managed installation the keys are the operator's and the card is not shown. + ### Google Maps API Key Optional. Without a Google key the TREK API is used. With one, photos, ratings and opening hours can be loaded on top, and every such lookup goes to Google. Create a key at console.cloud.google.com. @@ -113,32 +143,6 @@ For place search inside mainland China, where Google is unreachable and OpenStre A key that comes from an environment variable makes its field read-only. The field stays empty and reads **Set via** and the variable's name: `PLACES_API_KEY` for Google, `UNSPLASH_ACCESS_KEY` for Unsplash, `AMAP_API_KEY` for Amap. **Save** leaves those keys alone, and **Test** still checks the Google key from the environment. See [Environment-Variables](Environment-Variables). -### Transit Provider - -Which service answers public transit search. It saves as soon as you pick. - -- **Transitous (free)**, the default: community GTFS feeds, free and keyless, with the best coverage in Europe. -- **Google**: uses the Google API key above, for regions Transitous has no data for. Google bills each search. - -With **Google** picked but no Google key configured, a warning says transit search is still using Transitous. When only your own personal Google key is set, a warning says that other members still fall back to Transitous until the key is saved here as the instance key. See [Transport-Flights-Trains-Cars](Transport-Flights-Trains-Cars). - -When the picked service finds no connection, the search asks the other one before it reports an empty result. From Google, that is Transitous. From **Transitous**, Google is asked only when an instance key (saved here or set through `PLACES_API_KEY`) is available and the **Daily limit for Google calls** is not reached; a member's personal key is never billed for it. Each such fallback counts as one billed Google search. An error from the picked service is shown as before and does not trigger the fallback. - -### Place search provider - -TREK's own index and OpenStreetMap answer every search. This picks who else is asked when they find nothing: - -- **Automatic** (default): prefers Google where a key exists, then Amap. -- **Google Places** -- **Amap (高德地图)** -- **OpenStreetMap** - -The choice saves at once. If the selected provider has no API key, a warning says that place search is answered by the TREK index and OpenStreetMap alone. See [Places-and-Search](Places-and-Search). - -### Place Search Log - -One switch, **off by default**. When it is on, TREK records which search result was picked, so a different place index can be measured against real searches later: the search text, the picked place's name and rounded coordinates, and where it stood in the result list. No user is stored with it, and nothing leaves the instance. Rows are deleted after 180 days, also while the switch is off. An admin can read the log through `GET /api/place-shadow/summary` and `GET /api/place-shadow/export`, and delete it with `DELETE /api/place-shadow`. - ### Saving the keys The **Save** button at the bottom of the card stores the Google, Unsplash and Amap keys. Everything else on the card (the switches, the daily limit, the two provider choices) saves on its own.