From 84c277993c35c250e63defd683fec9edc9f84e35 Mon Sep 17 00:00:00 2001 From: Maurice Date: Sat, 12 Sep 2026 13:56:48 +0200 Subject: [PATCH] feat(maps): show where an Amap hit came from, and keep its id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An Amap row now carries the same source mark the TREK index, OpenStreetMap and Google rows get, named per locale because 高德地图 is the name it goes by where it matters. The "search is on OpenStreetMap" notices and the key hint in the details column are gone with the keys dev had already removed, and the client-side provider mirror with them. amap_poi_id travels with a picked result into the form, the details column and the shadow log, so a saved place keeps the id it was found by. A pasted Amap link goes to the resolver like a Google one. --- client/src/App.tsx | 6 +- .../Planner/PlaceDetailsColumn.test.tsx | 49 ++----------- .../components/Planner/PlaceDetailsColumn.tsx | 55 +++------------ .../Planner/PlaceFormModal.helpers.test.ts | 24 ++++++- .../Planner/PlaceFormModal.helpers.ts | 22 ++++++ .../Planner/PlaceFormModal.test.tsx | 49 ++++++------- .../src/components/Planner/PlaceFormModal.tsx | 53 ++++---------- .../screens/trip/sheets/PlPlaceSearch.tsx | 12 ++-- client/src/store/authStore.ts | 17 ----- client/src/utils/placeSource.ts | 25 +++++-- client/src/utils/placesProvider.test.ts | 63 ----------------- client/src/utils/placesProvider.ts | 69 ------------------- shared/src/i18n/ar/places.ts | 8 +-- shared/src/i18n/br/places.ts | 8 +-- shared/src/i18n/ca/places.ts | 1 + shared/src/i18n/cs/places.ts | 8 +-- shared/src/i18n/de/places.ts | 8 +-- shared/src/i18n/en/places.ts | 8 +-- shared/src/i18n/es/places.ts | 8 +-- shared/src/i18n/fr/places.ts | 8 +-- shared/src/i18n/gr/places.ts | 8 +-- shared/src/i18n/hu/places.ts | 8 +-- shared/src/i18n/id/places.ts | 8 +-- shared/src/i18n/it/places.ts | 8 +-- shared/src/i18n/ja/places.ts | 8 +-- shared/src/i18n/ko/places.ts | 8 +-- shared/src/i18n/nl/places.ts | 8 +-- shared/src/i18n/pl/places.ts | 8 +-- shared/src/i18n/ru/places.ts | 8 +-- shared/src/i18n/sv/places.ts | 1 + shared/src/i18n/tr/places.ts | 8 +-- shared/src/i18n/uk/places.ts | 8 +-- shared/src/i18n/vi/places.ts | 1 + shared/src/i18n/zh-TW/places.ts | 8 +-- shared/src/i18n/zh/places.ts | 8 +-- 35 files changed, 148 insertions(+), 459 deletions(-) delete mode 100644 client/src/utils/placesProvider.test.ts delete mode 100644 client/src/utils/placesProvider.ts diff --git a/client/src/App.tsx b/client/src/App.tsx index 8aff3bff8..ceadffc3d 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -284,7 +284,7 @@ function RouteFallback() { } export default function App() { - const { loadUser, isAuthenticated, demoMode, setManaged, setDemoMode, setDevMode, setIsPrerelease, setAppVersion, setHasMapsKey, setHasAmapKey, setPlacesProvider, setServerTimezone, setAppRequireMfa, setTripRemindersEnabled, setPlacesPhotosEnabled, setPlacesAutocompleteEnabled, setPlacesDetailsEnabled, setPlacesEnrichEnabled, setPlaceShadowEnabled } = useAuthStore() + const { loadUser, isAuthenticated, demoMode, setManaged, setDemoMode, setDevMode, setIsPrerelease, setAppVersion, setHasMapsKey, setServerTimezone, setAppRequireMfa, setTripRemindersEnabled, setPlacesPhotosEnabled, setPlacesAutocompleteEnabled, setPlacesDetailsEnabled, setPlacesEnrichEnabled, setPlaceShadowEnabled } = useAuthStore() const { loadSettings } = useSettingsStore() const { loadAddons } = useAddonStore() const { loadPlugins } = usePluginStore() @@ -301,15 +301,13 @@ export default function App() { loadUser() } } - authApi.getAppConfig().then(async (config: { managed?: boolean; demo_mode?: boolean; dev_mode?: boolean; is_prerelease?: boolean; has_maps_key?: boolean; has_amap_key?: boolean; places_provider?: string; version?: string; timezone?: string; require_mfa?: boolean; trip_reminders_enabled?: boolean; places_photos_enabled?: boolean; places_autocomplete_enabled?: boolean; places_details_enabled?: boolean; places_enrich_enabled?: boolean; place_shadow_enabled?: boolean; permissions?: Record }) => { + authApi.getAppConfig().then(async (config: { managed?: boolean; demo_mode?: boolean; dev_mode?: boolean; is_prerelease?: boolean; has_maps_key?: boolean; version?: string; timezone?: string; require_mfa?: boolean; trip_reminders_enabled?: boolean; places_photos_enabled?: boolean; places_autocomplete_enabled?: boolean; places_details_enabled?: boolean; places_enrich_enabled?: boolean; place_shadow_enabled?: boolean; permissions?: Record }) => { setManaged(!!config?.managed) setDemoMode(!!config?.demo_mode) if (config?.dev_mode) setDevMode(true) if (config?.is_prerelease !== undefined) setIsPrerelease(config.is_prerelease) if (config?.version) setAppVersion(config.version) if (config?.has_maps_key !== undefined) setHasMapsKey(config.has_maps_key) - if (config?.has_amap_key !== undefined) setHasAmapKey(config.has_amap_key) - if (config?.places_provider) setPlacesProvider(config.places_provider) if (config?.timezone) setServerTimezone(config.timezone) if (config?.require_mfa !== undefined) setAppRequireMfa(!!config.require_mfa) if (config?.trip_reminders_enabled !== undefined) setTripRemindersEnabled(config.trip_reminders_enabled) diff --git a/client/src/components/Planner/PlaceDetailsColumn.test.tsx b/client/src/components/Planner/PlaceDetailsColumn.test.tsx index 556e01c1c..6f97452f2 100644 --- a/client/src/components/Planner/PlaceDetailsColumn.test.tsx +++ b/client/src/components/Planner/PlaceDetailsColumn.test.tsx @@ -474,49 +474,12 @@ describe('PlaceDetailsColumn — hours and rating', () => { * same thing in the shape every other empty state in TREK uses, and whose key is * missing is not this panel's business. */ -describe('PlaceDetailsColumn — no key behind place search', () => { - const empty = () => placeEnrichment.mockResolvedValue({ photos: [], facts: [], description: null }) +describe('PlaceDetailsColumn — nothing found', () => { + it('FE-PDC-027: shows the empty state once, and no advice about API keys', async () => { + placeEnrichment.mockResolvedValue({ photos: [], facts: [], description: null }) + renderColumn() - it('FE-PDC-027: suggests a key when nothing was found and none is set', async () => { - empty() - renderColumn({ keySuggestion: 'google' }) - - expect(await screen.findByText('places.details.noKeyTitle')).toBeInTheDocument() - expect(screen.getByText('places.details.noKeyHint')).toBeInTheDocument() - }) - - it('FE-PDC-027b: suggests the Amap key when that is the one the admin chose', async () => { - // An install on Amap is not "missing a Google key" — the hint has to name - // the key that would actually move search off OpenStreetMap. - empty() - renderColumn({ keySuggestion: 'amap' }) - - expect(await screen.findByText('places.details.noKeyTitle')).toBeInTheDocument() - expect(screen.getByText('places.details.noKeyHintAmap')).toBeInTheDocument() - }) - - it('FE-PDC-027c: names both keys when the choice is automatic and neither is set', async () => { - empty() - renderColumn({ keySuggestion: 'any' }) - - expect(await screen.findByText('places.details.noKeyTitle')).toBeInTheDocument() - expect(screen.getByText('places.details.noKeyHintAny')).toBeInTheDocument() - }) - - it('FE-PDC-027d: stays quiet when OpenStreetMap was chosen on purpose', async () => { - // The null suggestion: an explicit admin decision, not a gap to fill. - empty() - renderColumn({ keySuggestion: null }) - - await screen.findByText('places.details.nothing') - expect(screen.queryByText('places.details.noKeyTitle')).not.toBeInTheDocument() - }) - - it('FE-PDC-028: stays quiet when a key is already configured', async () => { - empty() - renderColumn({ keySuggestion: null }) - - await screen.findByText('places.details.nothing') + expect(await screen.findByText('places.details.nothing')).toBeInTheDocument() expect(screen.queryByText('places.details.noKeyTitle')).not.toBeInTheDocument() expect(screen.queryByText('places.details.noKeyHint')).not.toBeInTheDocument() }) @@ -527,7 +490,7 @@ describe('PlaceDetailsColumn — no key behind place search', () => { facts: [], description: { text: 'Ein Museum.', source: 'wikipedia', sourceUrl: null, license: 'CC BY-SA 4.0' }, }) - renderColumn({ keySuggestion: 'google' }) + renderColumn() await screen.findByText('Ein Museum.') expect(screen.queryByText('places.details.nothing')).not.toBeInTheDocument() diff --git a/client/src/components/Planner/PlaceDetailsColumn.tsx b/client/src/components/Planner/PlaceDetailsColumn.tsx index 829bdd713..c992a98ce 100644 --- a/client/src/components/Planner/PlaceDetailsColumn.tsx +++ b/client/src/components/Planner/PlaceDetailsColumn.tsx @@ -15,7 +15,6 @@ import { Loader2, ScrollText, ShoppingBag, - Sparkles, Sprout, Star, Sun, @@ -27,19 +26,8 @@ import { resolveOpenNow, resolvePlaceTimeZone, placeWeekdayIndex } from './place import { convertHoursLine, isUnknownHoursLine, splitHoursLine } from './placeHoursFormat' import { safeHttpUrl } from '../../utils/safeUrl' import EmptyState from '../shared/EmptyState' -import type { KeySuggestion } from '../../utils/placesProvider' import type { TranslationFn } from '../../types' -/** - * The "add a key" hint line, per which key would move search off OpenStreetMap. - * The Google variant reuses the long-standing key every locale already carries. - */ -const NO_KEY_HINT_KEY: Record, string> = { - google: 'places.details.noKeyHint', - amap: 'places.details.noKeyHintAmap', - any: 'places.details.noKeyHintAny', -} - /** The place the column is describing. Null while nothing is selected. */ export interface PlaceDetailsSelection { placeId?: string @@ -63,12 +51,7 @@ interface PlaceDetailsColumnProps { timeFormat?: string /** For grouping the rating count's digits. */ locale?: string - /** - * Which API key the "more detail available" hint should suggest, or null when - * there is nothing to suggest — a configured provider answers, or the admin - * chose OpenStreetMap on purpose and the hint would be a nag. - */ - keySuggestion?: KeySuggestion | null + /** False on an instance with no Google key, which is most of them. */ t: TranslationFn } @@ -134,7 +117,6 @@ export default function PlaceDetailsColumn({ language, timeFormat = '24h', locale = 'en-US', - keySuggestion = null, t, }: PlaceDetailsColumnProps): React.ReactElement { const [data, setData] = useState(null) @@ -254,20 +236,14 @@ export default function PlaceDetailsColumn({ plainly; whose key is missing is not this panel's business, and on a place the free sources describe fine it read as an advert. */} {isEmpty && ( -
- - {/* Only when both are true. With a key behind the search there is - nothing to suggest, and on a place the free sources DID describe - the suggestion would be an advert. */} - {keySuggestion && } -
+ )} {selection && state === 'ready' && !data?.disabled && !isEmpty && ( @@ -496,19 +472,6 @@ function PhotoCredit({ photo }: { photo: PlacePhotoCandidate }): React.ReactElem * It also names who to ask: the key is an instance-wide setting, so on most * installs the person reading this cannot act on it themselves. */ -function NoKeyHint({ t, suggestion }: { t: TranslationFn; suggestion: KeySuggestion }): React.ReactElement { - return ( -
-
- - - -

{t('places.details.noKeyTitle')}

-
-

{t(NO_KEY_HINT_KEY[suggestion])}

-
- ) -} /** * The star rating, as stars. diff --git a/client/src/components/Planner/PlaceFormModal.helpers.test.ts b/client/src/components/Planner/PlaceFormModal.helpers.test.ts index ad26cfd57..b5e472693 100644 --- a/client/src/components/Planner/PlaceFormModal.helpers.test.ts +++ b/client/src/components/Planner/PlaceFormModal.helpers.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest' -import { isGoogleMapsUrl } from './PlaceFormModal.helpers' +import { isAmapUrl, isGoogleMapsUrl, isMapUrl } from './PlaceFormModal.helpers' describe('isGoogleMapsUrl', () => { it('accepts the short share hosts', () => { @@ -39,3 +39,25 @@ describe('isGoogleMapsUrl', () => { expect(isGoogleMapsUrl(' https://maps.app.goo.gl/abc123 ')).toBe(true) }) }) + +describe('isAmapUrl', () => { + it('accepts the share, web and short-link hosts', () => { + expect(isAmapUrl('https://uri.amap.com/marker?position=116.397,39.908&name=天安门')).toBe(true) + expect(isAmapUrl('https://www.amap.com/place/B000A7BD6C')).toBe(true) + expect(isAmapUrl('https://surl.amap.com/abc')).toBe(true) + }) + + it('is an exact host list, so a lookalike stays a search query', () => { + expect(isAmapUrl('https://amap.com.evil.example/place/x')).toBe(false) + expect(isAmapUrl('https://restapi.amap.com/v3/place/text')).toBe(false) + expect(isAmapUrl('天安门')).toBe(false) + }) +}) + +describe('isMapUrl', () => { + it('sends either provider\'s link to the resolver and nothing else', () => { + expect(isMapUrl('https://maps.app.goo.gl/abc123')).toBe(true) + expect(isMapUrl('https://uri.amap.com/marker?position=116.397,39.908')).toBe(true) + expect(isMapUrl('https://example.com/maps')).toBe(false) + }) +}) diff --git a/client/src/components/Planner/PlaceFormModal.helpers.ts b/client/src/components/Planner/PlaceFormModal.helpers.ts index a3ea5c490..44409a74a 100644 --- a/client/src/components/Planner/PlaceFormModal.helpers.ts +++ b/client/src/components/Planner/PlaceFormModal.helpers.ts @@ -17,6 +17,7 @@ export interface PlaceFormData { google_place_id?: string google_ftid?: string osm_id?: string + amap_poi_id?: string // Hero image picked from the detail column. Optional and absent from // DEFAULT_FORM on purpose: the mobile sheet shares this type and never sets // it, and places.service already writes image_url through on create/update. @@ -55,6 +56,26 @@ export function isGoogleMapsUrl(input: string): boolean { } } +/** + * Amap's own hosts, the same set the server resolves (amap.provider.ts). An + * exact list rather than a shape: Amap has no ccTLD family, so a pattern would + * only widen what the search box hands to the resolver. + */ +const AMAP_HOSTS = new Set(['amap.com', 'www.amap.com', 'uri.amap.com', 'wb.amap.com', 'surl.amap.com', 'gaode.com', 'www.gaode.com']) + +export function isAmapUrl(input: string): boolean { + try { + return AMAP_HOSTS.has(new URL(input.trim()).hostname.toLowerCase()) + } catch { + return false + } +} + +/** A pasted link the server can turn into a place, from either provider. */ +export function isMapUrl(input: string): boolean { + return isGoogleMapsUrl(input) || isAmapUrl(input) +} + export const DEFAULT_FORM: PlaceFormData = { name: '', description: '', @@ -81,6 +102,7 @@ export const RESULT_FIELDS = [ 'google_place_id', 'google_ftid', 'osm_id', + 'amap_poi_id', 'website', 'phone', ] as const diff --git a/client/src/components/Planner/PlaceFormModal.test.tsx b/client/src/components/Planner/PlaceFormModal.test.tsx index 122d0dbd2..462903053 100644 --- a/client/src/components/Planner/PlaceFormModal.test.tsx +++ b/client/src/components/Planner/PlaceFormModal.test.tsx @@ -462,33 +462,34 @@ describe('PlaceFormModal', () => { expect(await screen.findByText('OpenStreetMap')).toBeInTheDocument(); }); - // The notice says what is actually true per provider choice, not "add a - // Google key" on an install whose admin picked Amap. See utils/placesProvider. - describe('FE-PLANNER-PLACEFORM-022b: the OSM notice follows the provider choice', () => { - it('suggests the Amap key when the admin chose Amap and none is set', () => { - seedStore(useAuthStore, { user: buildUser(), isAuthenticated: true, hasMapsKey: false, hasAmapKey: false, placesProvider: 'amap' }); - render(); - expect(screen.getByText(/高德|Amap/i)).toBeInTheDocument(); - }); + it('FE-PLANNER-PLACEFORM-022d: an Amap list marks its rows, and a pick keeps the Amap id', async () => { + // Amap answers from the slot Google otherwise holds, so its rows get the + // same mark the other sources get. The id travels with the place: it is + // what lets the saved place keep opening against Amap after the admin + // switches providers. + const user = userEvent.setup(); + const onSave = vi.fn(); + server.use( + http.post('/api/maps/search', () => + HttpResponse.json({ + places: [ + { name: '天安门', address: '北京市东城区', lat: '39.9087', lng: '116.3975', amap_poi_id: 'amap:B000A7BD6C', source: 'amap' }, + ], + source: 'amap', + }), + ), + ); - it('stays quiet when the chosen provider has its key', () => { - seedStore(useAuthStore, { user: buildUser(), isAuthenticated: true, hasMapsKey: false, hasAmapKey: true, placesProvider: 'amap' }); - render(); - expect(screen.queryByText(/OpenStreetMap/i)).not.toBeInTheDocument(); - }); + render(); + await user.type(screen.getByPlaceholderText('Search places...'), 'Tiananmen'); + await user.keyboard('{Enter}'); - it('stays quiet when the admin explicitly chose OpenStreetMap', () => { - seedStore(useAuthStore, { user: buildUser(), isAuthenticated: true, hasMapsKey: false, hasAmapKey: false, placesProvider: 'openstreetmap' }); - render(); - // A deliberate choice is not a gap to fill — no upsell line at all. - expect(screen.queryByText(/OpenStreetMap/i)).not.toBeInTheDocument(); - }); + expect(await screen.findByText('Amap')).toBeInTheDocument(); + await user.click(screen.getByText('天安门')); + await user.click(screen.getByRole('button', { name: /^Add$/i })); - it('stays quiet under auto when a Google key is configured', () => { - seedStore(useAuthStore, { user: buildUser(), isAuthenticated: true, hasMapsKey: true, hasAmapKey: true, placesProvider: 'auto' }); - render(); - expect(screen.queryByText(/OpenStreetMap/i)).not.toBeInTheDocument(); - }); + await waitFor(() => expect(onSave).toHaveBeenCalled()); + expect(onSave.mock.calls[0][0].amap_poi_id).toBe('amap:B000A7BD6C'); }); // ── Category ───────────────────────────────────────────────────────────────── diff --git a/client/src/components/Planner/PlaceFormModal.tsx b/client/src/components/Planner/PlaceFormModal.tsx index 8e8ba332e..6d03d90b1 100644 --- a/client/src/components/Planner/PlaceFormModal.tsx +++ b/client/src/components/Planner/PlaceFormModal.tsx @@ -7,18 +7,6 @@ import { mapsApi } from '../../api/client' import { recordPlacePick } from '../../api/placeShadow' import { useAuthStore } from '../../store/authStore' import { useAddonStore } from '../../store/addonStore' -import { effectivePlacesProvider, osmKeySuggestion, type KeySuggestion } from '../../utils/placesProvider' - -/** - * The "search is on OpenStreetMap" line, per which key would actually move it - * off. The Google variant reuses the long-standing key, which every locale - * already carries; the other two are new with Amap. - */ -const OSM_ACTIVE_KEY: Record = { - google: 'places.osmActive', - amap: 'places.osmActiveAmap', - any: 'places.osmActiveAny', -} import { useCanDo } from '../../store/permissionsStore' import { useTripStore } from '../../store/tripStore' import { useSettingsStore } from '../../store/settingsStore' @@ -28,7 +16,7 @@ import { useToast } from '../shared/Toast' import { Search, Paperclip, X, AlertTriangle, Loader2, Plus } from 'lucide-react' import { useTranslation } from '../../i18n' import CustomTimePicker from '../shared/CustomTimePicker' -import { DEFAULT_FORM, isGoogleMapsUrl, mergeResult, type PlaceFormData, type ResultField } from './PlaceFormModal.helpers' +import { DEFAULT_FORM, isMapUrl, mergeResult, type PlaceFormData, type ResultField } from './PlaceFormModal.helpers' import { getApiErrorMessage } from '../../utils/apiError' import { sourceLabelFor } from '../../utils/placeSource' import { useLocationBias } from '../../hooks/useLocationBias' @@ -170,11 +158,7 @@ function usePlaceFormModal(props: PlaceFormModalProps) { const placesSessionRef = useRef(new PlacesSession()) const toast = useToast() const { t, language, locale } = useTranslation() - const { hasMapsKey, hasAmapKey, placesProvider, placesEnrichEnabled } = useAuthStore() - // What the search notices say depends on what will actually answer: an - // install whose admin picked Amap is not "missing a Google key". - const searchProvider = effectivePlacesProvider({ placesProvider, hasMapsKey, hasAmapKey }) - const keySuggestion = osmKeySuggestion(placesProvider) + const { hasMapsKey, placesEnrichEnabled } = useAuthStore() const can = useCanDo() const timeFormat = useSettingsStore((s) => s.settings.time_format) || '24h' const tripObj = useTripStore((s) => s.trip) @@ -251,7 +235,7 @@ function usePlaceFormModal(props: PlaceFormModalProps) { // because only handleSelectMapsResult ever set it. if (place && place.lat != null && place.lng != null) { setDetailsSelection({ - placeId: place.google_place_id || place.osm_id || undefined, + placeId: place.google_place_id || place.amap_poi_id || place.osm_id || undefined, lat: Number(place.lat), lng: Number(place.lng), name: place.name || '', @@ -290,7 +274,7 @@ function usePlaceFormModal(props: PlaceFormModalProps) { // Autocomplete fetch — aborts any in-flight request before starting a new one const fetchSuggestions = useCallback(async (query: string) => { - if (query.length < 2 || isGoogleMapsUrl(query)) { + if (query.length < 2 || isMapUrl(query)) { setAcSuggestions([]) setAcHighlight(-1) return @@ -317,7 +301,7 @@ function usePlaceFormModal(props: PlaceFormModalProps) { if (acDebounceRef.current) clearTimeout(acDebounceRef.current) const trimmed = mapsSearch.trim() - if (trimmed.length < 2 || isGoogleMapsUrl(trimmed)) { + if (trimmed.length < 2 || isMapUrl(trimmed)) { setAcSuggestions([]) setAcHighlight(-1) placesSessionRef.current.end() @@ -341,9 +325,9 @@ function usePlaceFormModal(props: PlaceFormModalProps) { if (!mapsSearch.trim()) return setIsSearchingMaps(true) try { - // Detect Google Maps URLs and resolve them directly + // A pasted Google Maps or Amap link resolves server-side into a place const trimmed = mapsSearch.trim() - if (isGoogleMapsUrl(trimmed)) { + if (isMapUrl(trimmed)) { const resolved = await mapsApi.resolveUrl(trimmed) if (resolved.lat && resolved.lng) { setForm(prev => ({ @@ -385,7 +369,7 @@ function usePlaceFormModal(props: PlaceFormModalProps) { const lng = Number(result.lng) if (Number.isFinite(lat) && Number.isFinite(lng)) { setDetailsSelection({ - placeId: result.google_place_id || result.osm_id || undefined, + placeId: result.google_place_id || result.amap_poi_id || result.osm_id || undefined, lat, lng, name: result.name || '', @@ -411,7 +395,7 @@ function usePlaceFormModal(props: PlaceFormModalProps) { pickedName: result.name || '', pickedLat: lat, pickedLng: lng, - pickedPlaceId: result.google_place_id || result.osm_id || null, + pickedPlaceId: result.google_place_id || result.amap_poi_id || result.osm_id || null, }) } } @@ -663,8 +647,7 @@ function usePlaceFormModal(props: PlaceFormModalProps) { language, locale, timeFormat, - searchProvider, - keySuggestion, + hasMapsKey, placesEnrichEnabled, can, tripObj, @@ -736,8 +719,7 @@ export default function PlaceFormModal(props: PlaceFormModalProps) { toast, t, language, - searchProvider, - keySuggestion, + hasMapsKey, placesEnrichEnabled, can, tripObj, @@ -811,21 +793,12 @@ export default function PlaceFormModal(props: PlaceFormModalProps) { language={language} timeFormat={S.timeFormat} locale={S.locale} - // Null when search genuinely has no key behind it — the column then - // explains which one would help, or stays quiet when OpenStreetMap - // was chosen on purpose. - keySuggestion={searchProvider === 'openstreetmap' ? keySuggestion : null} t={t} /> )}
{/* Place Search */}
- {searchProvider === 'openstreetmap' && keySuggestion && ( -

- {t(OSM_ACTIVE_KEY[keySuggestion])} -

- )}
{s.secondaryText}
)}
- +
))} @@ -896,7 +869,7 @@ export default function PlaceFormModal(props: PlaceFormModalProps) {
{result.name}
{result.address}
- + ))} diff --git a/client/src/mobile/screens/trip/sheets/PlPlaceSearch.tsx b/client/src/mobile/screens/trip/sheets/PlPlaceSearch.tsx index ec1b26f04..382d5f32f 100644 --- a/client/src/mobile/screens/trip/sheets/PlPlaceSearch.tsx +++ b/client/src/mobile/screens/trip/sheets/PlPlaceSearch.tsx @@ -4,7 +4,7 @@ import { mapsApi } from '../../../../api/client' import { sourceLabelFor } from '../../../../utils/placeSource' import { recordPlacePick } from '../../../../api/placeShadow' import { PlacesSession } from '../../../../utils/placesSession' -import { isGoogleMapsUrl } from '../../../../components/Planner/PlaceFormModal.helpers' +import { isMapUrl } from '../../../../components/Planner/PlaceFormModal.helpers' import { getApiErrorMessage } from '../../../../utils/apiError' import { pointFromBox } from '../../../../hooks/useLocationBias' import { FIELD_CLS } from './PlSheetChrome' @@ -19,6 +19,7 @@ export interface PlSearchPick { google_place_id?: string google_ftid?: string osm_id?: string + amap_poi_id?: string website?: string phone?: string } @@ -67,6 +68,7 @@ function placeToPick(place: MapsPlace): PlSearchPick { google_place_id: s(place.google_place_id), google_ftid: s(place.google_ftid), osm_id: s(place.osm_id), + amap_poi_id: s(place.amap_poi_id), website: s(place.website), phone: s(place.phone), } @@ -125,7 +127,7 @@ export default function PlPlaceSearch({ planner, locationBias, onPick, onResolvi useEffect(() => { if (debounceRef.current) clearTimeout(debounceRef.current) const trimmed = query.trim() - if (trimmed.length < 2 || isGoogleMapsUrl(trimmed) || COORD_RE.test(trimmed)) { + if (trimmed.length < 2 || isMapUrl(trimmed) || COORD_RE.test(trimmed)) { setSuggestions([]) return } @@ -157,7 +159,7 @@ export default function PlPlaceSearch({ planner, locationBias, onPick, onResolvi pickedName: String(place.name ?? ''), pickedLat: lat, pickedLng: lng, - pickedPlaceId: (place.google_place_id as string) || (place.osm_id as string) || null, + pickedPlaceId: (place.google_place_id as string) || (place.amap_poi_id as string) || (place.osm_id as string) || null, }) } } @@ -181,7 +183,7 @@ export default function PlPlaceSearch({ planner, locationBias, onPick, onResolvi setResolving(true) try { - if (isGoogleMapsUrl(trimmed)) { + if (isMapUrl(trimmed)) { const resolved = await mapsApi.resolveUrl(trimmed) if (resolved.lat && resolved.lng) { onPick({ @@ -308,7 +310,7 @@ export default function PlPlaceSearch({ planner, locationBias, onPick, onResolvi
{s.secondaryText}
)} - + ))} diff --git a/client/src/store/authStore.ts b/client/src/store/authStore.ts index 405dac5d0..987bb5741 100644 --- a/client/src/store/authStore.ts +++ b/client/src/store/authStore.ts @@ -46,15 +46,7 @@ interface AuthState { devMode: boolean isPrerelease: boolean appVersion: string - /** Google Maps API key. Please note that secondary map api provider is available, - * check `hasAmapKey`. */ hasMapsKey: boolean - /** The same question for the Amap key, and the admin's provider choice. Not - * boolean "hasSearchKey": a notice that says "add a Google key" on an install - * whose admin picked Amap is wrong twice, and the two facts together are what - * the place-search notices need to say something true. */ - hasAmapKey: boolean - placesProvider: string serverTimezone: string /** Server policy: all users must enable MFA */ appRequireMfa: boolean @@ -83,8 +75,6 @@ interface AuthState { setIsPrerelease: (val: boolean) => void setAppVersion: (val: string) => void setHasMapsKey: (val: boolean) => void - setHasAmapKey: (val: boolean) => void - setPlacesProvider: (val: string) => void setServerTimezone: (tz: string) => void setAppRequireMfa: (val: boolean) => void setTripRemindersEnabled: (val: boolean) => void @@ -135,8 +125,6 @@ export const useAuthStore = create()( isPrerelease: false, appVersion: '', hasMapsKey: false, - hasAmapKey: false, - placesProvider: 'auto', serverTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone, appRequireMfa: false, tripRemindersEnabled: false, @@ -355,9 +343,6 @@ export const useAuthStore = create()( if ('maps_api_key' in keys) { set({ hasMapsKey: !!keys.maps_api_key }) } - if ('amap_api_key' in keys) { - set({ hasAmapKey: !!keys.amap_api_key }) - } } catch (err: unknown) { throw new Error(getApiErrorMessage(err, 'Error saving API keys')) } @@ -401,8 +386,6 @@ export const useAuthStore = create()( setIsPrerelease: (val: boolean) => set({ isPrerelease: val }), setAppVersion: (val: string) => set({ appVersion: val }), setHasMapsKey: (val: boolean) => set({ hasMapsKey: val }), - setHasAmapKey: (val: boolean) => set({ hasAmapKey: val }), - setPlacesProvider: (val: string) => set({ placesProvider: val }), setServerTimezone: (tz: string) => set({ serverTimezone: tz }), setAppRequireMfa: (val: boolean) => set({ appRequireMfa: val }), setTripRemindersEnabled: (val: boolean) => set({ tripRemindersEnabled: val }), diff --git a/client/src/utils/placeSource.ts b/client/src/utils/placeSource.ts index 0d651cf5e..fa09a0b55 100644 --- a/client/src/utils/placeSource.ts +++ b/client/src/utils/placeSource.ts @@ -11,10 +11,11 @@ * nothing on the other is worse than either alone. */ +import type { TranslationFn } from '../types' + /** - * The three names are proper nouns, so they are not translated, and that is - * also why there is no fourth: a source without a name people already know - * would need a string in 23 languages to say less than nothing. + * Proper nouns, so they are not translated. A source without a name people + * already know would need a string in 23 languages to say less than nothing. */ export const SOURCE_LABELS: Record = { 'trek-places': 'TREK', @@ -23,6 +24,14 @@ export const SOURCE_LABELS: Record = { google: 'Google', } +/** + * The one name that changes with the reader: 高德地图 to the people it exists + * for, Amap to everyone else. Hence a locale string rather than a fourth noun. + */ +const SOURCE_KEYS: Record = { + amap: 'places.source.amap', +} + /** * The label for one row. * @@ -31,9 +40,11 @@ export const SOURCE_LABELS: Record = { * answered the call: Google never marks its places, and a merged list marks only * the index side, so an unmarked row in one is OpenStreetMap by elimination. */ -export function sourceLabelFor(place: unknown, listSource: string): string | null { +export function sourceLabelFor(place: unknown, listSource: string, t: TranslationFn): string | null { const own = (place as { source?: unknown } | null)?.source - if (typeof own === 'string' && own) return SOURCE_LABELS[own] ?? null - if (listSource.includes('openstreetmap')) return SOURCE_LABELS.openstreetmap - return SOURCE_LABELS[listSource] ?? null + const source = typeof own === 'string' && own + ? own + : listSource.includes('openstreetmap') ? 'openstreetmap' : listSource + if (SOURCE_KEYS[source]) return t(SOURCE_KEYS[source]) + return SOURCE_LABELS[source] ?? null } diff --git a/client/src/utils/placesProvider.test.ts b/client/src/utils/placesProvider.test.ts deleted file mode 100644 index 28a6a465e..000000000 --- a/client/src/utils/placesProvider.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { describe, expect, it } from 'vitest' -import { effectivePlacesProvider, osmKeySuggestion } from './placesProvider' - -/** - * These mirror MapsService.resolvePlacesProvider() on the server, case for case. - * The point of the mirror is honest notices, so every case where the notices - * would lie — "add a Google key" on an install whose admin picked Amap — is - * pinned here. - */ -describe('effectivePlacesProvider', () => { - it('auto keeps Google when a Google key is configured, Amap key or not', () => { - // The incumbent wins: an existing install must not move providers because - // a new one became available. - expect(effectivePlacesProvider({ placesProvider: 'auto', hasMapsKey: true, hasAmapKey: true })).toBe('google') - expect(effectivePlacesProvider({ placesProvider: 'auto', hasMapsKey: true, hasAmapKey: false })).toBe('google') - }) - - it('auto falls to Amap only when there is no Google key', () => { - expect(effectivePlacesProvider({ placesProvider: 'auto', hasMapsKey: false, hasAmapKey: true })).toBe('amap') - }) - - it('auto with neither key means OpenStreetMap', () => { - expect(effectivePlacesProvider({ placesProvider: 'auto', hasMapsKey: false, hasAmapKey: false })).toBe('openstreetmap') - }) - - it('an explicit amap choice wins even with a Google key configured', () => { - expect(effectivePlacesProvider({ placesProvider: 'amap', hasMapsKey: true, hasAmapKey: true })).toBe('amap') - }) - - it('an explicit google choice never silently uses Amap instead', () => { - // Misconfigured means "answer with OSM", matching the server — not "bill - // somebody else's provider". - expect(effectivePlacesProvider({ placesProvider: 'google', hasMapsKey: false, hasAmapKey: true })).toBe('openstreetmap') - }) - - it('openstreetmap ignores both keys', () => { - expect(effectivePlacesProvider({ placesProvider: 'openstreetmap', hasMapsKey: true, hasAmapKey: true })).toBe('openstreetmap') - }) - - it('a nonsense stored value degrades to auto rather than throwing', () => { - expect(effectivePlacesProvider({ placesProvider: 'not-a-provider', hasMapsKey: false, hasAmapKey: true })).toBe('amap') - expect(effectivePlacesProvider({ placesProvider: undefined, hasMapsKey: true, hasAmapKey: false })).toBe('google') - }) -}) - -describe('osmKeySuggestion', () => { - it('a chosen provider names its own key', () => { - expect(osmKeySuggestion('google')).toBe('google') - expect(osmKeySuggestion('amap')).toBe('amap') - }) - - it('auto names both, because landing on OSM means neither key is set', () => { - expect(osmKeySuggestion('auto')).toBe('any') - }) - - it('an explicit OpenStreetMap choice gets no upsell — it is a decision, not a gap', () => { - expect(osmKeySuggestion('openstreetmap')).toBeNull() - // An unrecognised value behaves like auto: the reader degrades it to auto, - // so the notice must not claim the admin chose OSM when they chose nothing. - expect(osmKeySuggestion('garbage')).toBe('any') - expect(osmKeySuggestion(undefined)).toBe('any') - }) -}) diff --git a/client/src/utils/placesProvider.ts b/client/src/utils/placesProvider.ts deleted file mode 100644 index ef034a7a0..000000000 --- a/client/src/utils/placesProvider.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Which provider place search will actually use, as far as the client can tell. - * - * This mirrors the server's MapsService.resolvePlacesProvider(): the instance's - * places_provider choice, the keys the server reported for this caller, and the - * same precedence. It exists so a notice can say the truth — "search is running - * on OpenStreetMap", not "you lack a Google key" on an install whose admin - * deliberately picked Amap. - * - * A mirror and not the source of truth on purpose: the server resolves keys per - * request against the database, and only it knows for sure. If the two ever - * disagree, the client shows a slightly wrong hint under a notice and nothing - * else — search itself keeps asking the server. - */ - -export type PlacesProviderChoice = 'auto' | 'google' | 'amap' | 'openstreetmap' -export type EffectiveSearchProvider = 'google' | 'amap' | 'openstreetmap' - -export interface PlacesProviderState { - /** The admin's places_provider choice; anything unrecognised means 'auto'. */ - placesProvider?: string | null - /** Whether a Google key resolves for this caller (operator env, instance, own row). */ - hasMapsKey: boolean - hasAmapKey: boolean -} - -export function effectivePlacesProvider(state: PlacesProviderState): EffectiveSearchProvider { - const choice: PlacesProviderChoice = - state.placesProvider === 'google' || state.placesProvider === 'amap' || state.placesProvider === 'openstreetmap' - ? state.placesProvider - : 'auto' - - if (choice === 'openstreetmap') return 'openstreetmap' - if (choice === 'google') return state.hasMapsKey ? 'google' : 'openstreetmap' - if (choice === 'amap') return state.hasAmapKey ? 'amap' : 'openstreetmap' - - // auto prefers Google, the incumbent: an existing install must not move to a - // different provider, with a different bill, just because Amap appeared. - if (state.hasMapsKey) return 'google' - if (state.hasAmapKey) return 'amap' - return 'openstreetmap' -} - -/** - * Which key the "still on OpenStreetMap" upsell should suggest. - * - * A chosen provider names the key that belongs to it — telling an admin who - * picked Amap to add a Google key is advice they already declined. `auto` with - * no key at all is the only case where both are worth naming. - * - * Null when the admin explicitly chose OpenStreetMap: that is a decision, not a - * gap to fill, and the notices stay quiet rather than nag. - */ -export type KeySuggestion = 'google' | 'amap' | 'any' - -export function osmKeySuggestion(placesProvider?: string | null): KeySuggestion | null { - switch (placesProvider) { - case 'google': - return 'google' - case 'amap': - return 'amap' - case 'openstreetmap': - return null - // auto resolving to OSM means neither key is configured (a Google key would - // have won), so both are honest suggestions. - default: - return 'any' - } -} diff --git a/shared/src/i18n/ar/places.ts b/shared/src/i18n/ar/places.ts index fd9763a06..35b8300df 100644 --- a/shared/src/i18n/ar/places.ts +++ b/shared/src/i18n/ar/places.ts @@ -92,9 +92,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'جارٍ تحميل تفاصيل المكان…', 'places.osmHint': 'يتم البحث عبر OpenStreetMap (بدون صور أو ساعات عمل أو تقييمات). أضف مفتاح Google API في الإعدادات للحصول على جميع التفاصيل.', - 'places.osmActive': 'يتم استخدام OpenStreetMap. مفتاح Google API يضيف التقييمات وساعات العمل.', - 'places.osmActiveAmap': 'تجري المشاركة عبر OpenStreetMap. يضيف مفتاح واجهة Amap (高德地图) التقييمات وساعات العمل.', - 'places.osmActiveAny': 'تجري المشاركة عبر OpenStreetMap. يضيف مفتاح واجهة Google أو Amap (高德地图) التقييمات وساعات العمل.', + 'places.source.amap': 'Amap', 'places.details.title': 'تفاصيل المكان', 'places.details.empty': 'اختر نتيجة لعرض التفاصيل', 'places.details.loading': 'جارٍ تحميل التفاصيل...', @@ -107,10 +105,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': 'أفرغ حقل الوصف أولًا', 'places.details.facts': 'معلومات مفيدة', 'places.details.nothing': 'لم يتم العثور على شيء لهذا المكان.', - 'places.details.noKeyTitle': 'تتوفر تفاصيل أكثر', - 'places.details.noKeyHint': 'بدون مفتاح Google API يستخدم هذا التثبيت المصادر الحرة فقط. اطلب من مسؤول TREK إضافة مفتاح للحصول على التقييمات وساعات العمل والصور.', - 'places.details.noKeyHintAmap': 'بدون مفتاح واجهة Amap (高德地图) يمكن لهذا المثيل استخدام المصادر المجانية فقط. اطلب من مسؤول TREK إضافة مفتاح للحصول على التقييمات وساعات العمل.', - 'places.details.noKeyHintAny': 'بدون مفتاح واجهة Google أو Amap (高德地图) يمكن لهذا المثيل استخدام المصادر المجانية فقط. اطلب من مسؤول TREK إضافة مفتاح للحصول على التقييمات وساعات العمل.', 'places.details.aboutBrand': 'عن السلسلة', 'places.details.aboutBrandNote': 'يصف السلسلة، لا هذا الفرع.', 'places.details.fact.rating': 'التقييم', diff --git a/shared/src/i18n/br/places.ts b/shared/src/i18n/br/places.ts index b317c0332..67362676f 100644 --- a/shared/src/i18n/br/places.ts +++ b/shared/src/i18n/br/places.ts @@ -92,9 +92,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'Carregando detalhes do lugar…', 'places.osmHint': 'Busca via OpenStreetMap (sem fotos, horários ou avaliações). Adicione uma chave Google nas configurações para detalhes completos.', - 'places.osmActive': 'Usando o OpenStreetMap. Uma chave de API do Google adiciona avaliações e horários.', - 'places.osmActiveAmap': 'Usando o OpenStreetMap. Uma chave de API do Amap (高德地图) adiciona avaliações e horários de funcionamento.', - 'places.osmActiveAny': 'Usando o OpenStreetMap. Uma chave de API do Google ou do Amap (高德地图) adiciona avaliações e horários de funcionamento.', + 'places.source.amap': 'Amap', 'places.details.title': 'Detalhes do lugar', 'places.details.empty': 'Escolha um resultado para ver mais', 'places.details.loading': 'Carregando detalhes...', @@ -107,10 +105,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': 'Esvazie primeiro o campo de descrição', 'places.details.facts': 'Bom saber', 'places.details.nothing': 'Nada encontrado para este lugar.', - 'places.details.noKeyTitle': 'Mais detalhes disponíveis', - 'places.details.noKeyHint': 'Sem uma chave de API do Google, esta instância usa apenas fontes livres. Peça ao administrador do TREK para adicionar uma para avaliações, horários e fotos.', - 'places.details.noKeyHintAmap': 'Sem uma chave de API do Amap (高德地图), esta instância só pode usar fontes gratuitas. Peça ao administrador do TREK para adicionar uma, para ter avaliações e horários.', - 'places.details.noKeyHintAny': 'Sem uma chave de API do Google ou do Amap (高德地图), esta instância só pode usar fontes gratuitas. Peça ao administrador do TREK para adicionar uma, para ter avaliações e horários.', 'places.details.aboutBrand': 'Sobre a rede', 'places.details.aboutBrandNote': 'Descreve a rede, não esta unidade.', 'places.details.fact.rating': 'Avaliação', diff --git a/shared/src/i18n/ca/places.ts b/shared/src/i18n/ca/places.ts index 7be892b52..6c6062822 100644 --- a/shared/src/i18n/ca/places.ts +++ b/shared/src/i18n/ca/places.ts @@ -90,6 +90,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'Carregant els detalls del lloc…', 'places.osmHint': "S'està utilitzant la cerca amb OpenStreetMap (sense fotos, horaris ni valoracions). Afegeix una clau API de Google a Configuració per obtenir tots els detalls.", + 'places.source.amap': 'Amap', 'places.details.title': 'Detalls del lloc', 'places.details.empty': 'Tria un resultat per veure’n més', 'places.details.loading': "S'estan carregant els detalls...", diff --git a/shared/src/i18n/cs/places.ts b/shared/src/i18n/cs/places.ts index fe93a08f1..5f6980349 100644 --- a/shared/src/i18n/cs/places.ts +++ b/shared/src/i18n/cs/places.ts @@ -92,9 +92,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'Načítání podrobností místa…', 'places.osmHint': 'Používáte hledání přes OpenStreetMap (bez fotek a hodnocení). Pro plné detaily přidejte Google API klíč v nastavení.', - 'places.osmActive': 'Používá se OpenStreetMap. Klíč Google API přidá hodnocení a otevírací dobu.', - 'places.osmActiveAmap': 'Používá se OpenStreetMap. Klíč API Amap (高德地图) přidá hodnocení a otevírací dobu.', - 'places.osmActiveAny': 'Používá se OpenStreetMap. Klíč API Google nebo Amap (高德地图) přidá hodnocení a otevírací dobu.', + 'places.source.amap': 'Amap', 'places.details.title': 'Podrobnosti místa', 'places.details.empty': 'Vyber výsledek a zobraz detaily', 'places.details.loading': 'Načítání podrobností...', @@ -107,10 +105,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': 'Nejdřív vyprázdni pole popisu', 'places.details.facts': 'Dobré vědět', 'places.details.nothing': 'Pro toto místo se nic nenašlo.', - 'places.details.noKeyTitle': 'K dispozici je více podrobností', - 'places.details.noKeyHint': 'Bez klíče Google API používá tato instance jen volné zdroje. Požádejte správce TREK o jeho přidání pro hodnocení, otevírací dobu a fotografie.', - 'places.details.noKeyHintAmap': 'Bez klíče API Amap (高德地图) může tato instance používat jen bezplatné zdroje. Požádejte správce TREK, aby klíč přidal — přibydou hodnocení a otevírací doba.', - 'places.details.noKeyHintAny': 'Bez klíče API Google nebo Amap (高德地图) může tato instance používat jen bezplatné zdroje. Požádejte správce TREK, aby klíč přidal — přibydou hodnocení a otevírací doba.', 'places.details.aboutBrand': 'O řetězci', 'places.details.aboutBrandNote': 'Popisuje řetězec, ne tuto pobočku.', 'places.details.fact.rating': 'Hodnocení', diff --git a/shared/src/i18n/de/places.ts b/shared/src/i18n/de/places.ts index 08cdc6794..b2d6d888a 100644 --- a/shared/src/i18n/de/places.ts +++ b/shared/src/i18n/de/places.ts @@ -92,9 +92,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'Ortsdetails werden geladen…', 'places.osmHint': 'OpenStreetMap-Suche aktiv (ohne Bilder, Öffnungszeiten, Bewertungen). Für erweiterte Daten Google API Key in den Einstellungen hinterlegen.', - 'places.osmActive': 'OpenStreetMap wird genutzt. Ein Google-API-Key ergänzt Bewertungen und Öffnungszeiten.', - 'places.osmActiveAmap': 'OpenStreetMap ist aktiv. Ein Amap-API-Key (高德地图) ergänzt Bewertungen und Öffnungszeiten.', - 'places.osmActiveAny': 'OpenStreetMap ist aktiv. Ein Google- oder Amap-API-Key (高德地图) ergänzt Bewertungen und Öffnungszeiten.', + 'places.source.amap': 'Amap', 'places.details.title': 'Ortsdetails', 'places.details.empty': 'Wähle ein Ergebnis für Details', 'places.details.loading': 'Details werden geladen...', @@ -107,10 +105,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': 'Erst das Beschreibungsfeld leeren', 'places.details.facts': 'Gut zu wissen', 'places.details.nothing': 'Zu diesem Ort wurde nichts gefunden.', - 'places.details.noKeyTitle': 'Mehr Details möglich', - 'places.details.noKeyHint': 'Ohne Google-API-Key nutzt diese Instanz nur freie Quellen. Wende dich an deine TREK-Administration, um Bewertungen, Öffnungszeiten und Fotos freizuschalten.', - 'places.details.noKeyHintAmap': 'Ohne Amap-API-Key (高德地图) kann diese Instanz nur freie Quellen nutzen. Bitte die TREK-Administration, einen Key für Bewertungen und Öffnungszeiten hinzuzufügen.', - 'places.details.noKeyHintAny': 'Ohne Google- oder Amap-API-Key (高德地图) kann diese Instanz nur freie Quellen nutzen. Bitte die TREK-Administration, einen Key für Bewertungen und Öffnungszeiten hinzuzufügen.', 'places.details.aboutBrand': 'Über die Kette', 'places.details.aboutBrandNote': 'Beschreibt die Kette, nicht diese Filiale.', 'places.details.fact.rating': 'Bewertung', diff --git a/shared/src/i18n/en/places.ts b/shared/src/i18n/en/places.ts index 403b0df48..1151931ca 100644 --- a/shared/src/i18n/en/places.ts +++ b/shared/src/i18n/en/places.ts @@ -93,9 +93,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'Loading place details…', 'places.osmHint': 'Using OpenStreetMap search (no photos, opening hours, or ratings). Add a Google API key in settings for full details.', - 'places.osmActive': 'Using OpenStreetMap. A Google API key adds ratings and opening hours.', - 'places.osmActiveAmap': 'Using OpenStreetMap. An Amap (高德地图) API key adds ratings and opening hours.', - 'places.osmActiveAny': 'Using OpenStreetMap. A Google or Amap (高德地图) API key adds ratings and opening hours.', + 'places.source.amap': 'Amap', 'places.details.title': 'Place details', 'places.details.empty': 'Pick a result to see more', 'places.details.loading': 'Loading details...', @@ -108,10 +106,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': 'Clear the description field first', 'places.details.facts': 'Good to know', 'places.details.nothing': 'Nothing found for this place.', - 'places.details.noKeyTitle': 'More detail is available', - 'places.details.noKeyHint': 'Without a Google API key this instance can only use free sources. Ask your TREK administrator to add one for ratings, opening hours and photos.', - 'places.details.noKeyHintAmap': 'Without an Amap (高德地图) API key this instance can only use free sources. Ask your TREK administrator to add one for ratings and opening hours.', - 'places.details.noKeyHintAny': 'Without a Google or Amap (高德地图) API key this instance can only use free sources. Ask your TREK administrator to add one for ratings and opening hours.', 'places.details.aboutBrand': 'About the chain', 'places.details.aboutBrandNote': 'This describes the chain, not this branch.', 'places.details.fact.rating': 'Rating', diff --git a/shared/src/i18n/es/places.ts b/shared/src/i18n/es/places.ts index 09209f28c..fd1823593 100644 --- a/shared/src/i18n/es/places.ts +++ b/shared/src/i18n/es/places.ts @@ -92,9 +92,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'Cargando detalles del lugar…', 'places.osmHint': 'Usando búsqueda con OpenStreetMap (sin fotos, horarios ni valoraciones). Añade una clave API de Google en Ajustes para obtener todos los detalles.', - 'places.osmActive': 'Se usa OpenStreetMap. Una clave de API de Google añade valoraciones y horarios.', - 'places.osmActiveAmap': 'Usando OpenStreetMap. Una clave de API de Amap (高德地图) añade valoraciones y horarios.', - 'places.osmActiveAny': 'Usando OpenStreetMap. Una clave de API de Google o de Amap (高德地图) añade valoraciones y horarios.', + 'places.source.amap': 'Amap', 'places.details.title': 'Detalles del lugar', 'places.details.empty': 'Elige un resultado para ver más', 'places.details.loading': 'Cargando detalles...', @@ -107,10 +105,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': 'Vacía primero el campo de descripción', 'places.details.facts': 'Bueno saber', 'places.details.nothing': 'No se encontró nada para este lugar.', - 'places.details.noKeyTitle': 'Hay más detalles disponibles', - 'places.details.noKeyHint': 'Sin una clave de API de Google, esta instancia solo usa fuentes libres. Pide a tu administrador de TREK que añada una para valoraciones, horarios y fotos.', - 'places.details.noKeyHintAmap': 'Sin una clave de API de Amap (高德地图) esta instancia solo puede usar fuentes gratuitas. Pide al administrador de TREK que añada una para valoraciones y horarios.', - 'places.details.noKeyHintAny': 'Sin una clave de API de Google o de Amap (高德地图) esta instancia solo puede usar fuentes gratuitas. Pide al administrador de TREK que añada una para valoraciones y horarios.', 'places.details.aboutBrand': 'Sobre la cadena', 'places.details.aboutBrandNote': 'Describe la cadena, no este local.', 'places.details.fact.rating': 'Valoración', diff --git a/shared/src/i18n/fr/places.ts b/shared/src/i18n/fr/places.ts index 11609db06..a9324a00a 100644 --- a/shared/src/i18n/fr/places.ts +++ b/shared/src/i18n/fr/places.ts @@ -92,9 +92,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'Chargement des détails du lieu…', 'places.osmHint': 'Recherche via OpenStreetMap (pas de photos, horaires ni notes). Ajoutez une clé API Google dans les paramètres pour plus de détails.', - 'places.osmActive': 'OpenStreetMap est utilisé. Une clé API Google ajoute notes et horaires.', - 'places.osmActiveAmap': 'OpenStreetMap est utilisé. Une clé API Amap (高德地图) ajoute notes et horaires d’ouverture.', - 'places.osmActiveAny': 'OpenStreetMap est utilisé. Une clé API Google ou Amap (高德地图) ajoute notes et horaires d’ouverture.', + 'places.source.amap': 'Amap', 'places.details.title': 'Détails du lieu', 'places.details.empty': 'Choisis un résultat pour en voir plus', 'places.details.loading': 'Chargement des détails...', @@ -107,10 +105,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': "Videz d'abord le champ description", 'places.details.facts': 'Bon à savoir', 'places.details.nothing': 'Rien trouvé pour ce lieu.', - 'places.details.noKeyTitle': 'Plus de détails disponibles', - 'places.details.noKeyHint': 'Sans clé API Google, cette instance n\'utilise que des sources libres. Demandez à votre administrateur TREK d\'en ajouter une pour les avis, les horaires et les photos.', - 'places.details.noKeyHintAmap': 'Sans clé API Amap (高德地图), cette instance ne peut utiliser que des sources gratuites. Demandez à l’administrateur TREK d’en ajouter une pour les notes et les horaires.', - 'places.details.noKeyHintAny': 'Sans clé API Google ou Amap (高德地图), cette instance ne peut utiliser que des sources gratuites. Demandez à l’administrateur TREK d’en ajouter une pour les notes et les horaires.', 'places.details.aboutBrand': 'À propos de la chaîne', 'places.details.aboutBrandNote': 'Décrit la chaîne, pas cet établissement.', 'places.details.fact.rating': 'Note', diff --git a/shared/src/i18n/gr/places.ts b/shared/src/i18n/gr/places.ts index 4a050c77d..dc0efdffd 100644 --- a/shared/src/i18n/gr/places.ts +++ b/shared/src/i18n/gr/places.ts @@ -92,9 +92,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'Φόρτωση λεπτομερειών μέρους…', 'places.osmHint': 'Χρήση αναζήτησης OpenStreetMap (χωρίς φωτογραφίες, ώρες λειτουργίας ή αξιολογήσεις). Προσθέστε ένα κλειδί Google API στις ρυθμίσεις για πλήρεις λεπτομέρειες.', - 'places.osmActive': 'Χρησιμοποιείται το OpenStreetMap. Ένα κλειδί Google API προσθέτει αξιολογήσεις και ωράρια.', - 'places.osmActiveAmap': 'Χρησιμοποιείται το OpenStreetMap. Ένα κλειδί API του Amap (高德地图) προσθέτει βαθμολογίες και ώρες λειτουργίας.', - 'places.osmActiveAny': 'Χρησιμοποιείται το OpenStreetMap. Ένα κλειδί API Google ή Amap (高德地图) προσθέτει βαθμολογίες και ώρες λειτουργίας.', + 'places.source.amap': 'Amap', 'places.details.title': 'Λεπτομέρειες τοποθεσίας', 'places.details.empty': 'Επίλεξε ένα αποτέλεσμα για περισσότερα', 'places.details.loading': 'Φόρτωση λεπτομερειών...', @@ -107,10 +105,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': 'Άδειασε πρώτα το πεδίο περιγραφής', 'places.details.facts': 'Καλό να ξέρεις', 'places.details.nothing': 'Δεν βρέθηκε τίποτα για αυτήν την τοποθεσία.', - 'places.details.noKeyTitle': 'Διατίθενται περισσότερες λεπτομέρειες', - 'places.details.noKeyHint': 'Χωρίς κλειδί Google API, αυτή η εγκατάσταση χρησιμοποιεί μόνο ελεύθερες πηγές. Ζητήστε από τον διαχειριστή TREK να προσθέσει ένα για βαθμολογίες, ώρες λειτουργίας και φωτογραφίες.', - 'places.details.noKeyHintAmap': 'Χωρίς κλειδί API Amap (高德地图) αυτή η εγκατάσταση μπορεί να χρησιμοποιεί μόνο δωρεάν πηγές. Ζητήστε από τον διαχειριστή TREK να προσθέσει ένα για βαθμολογίες και ώρες λειτουργίας.', - 'places.details.noKeyHintAny': 'Χωρίς κλειδί API Google ή Amap (高德地图) αυτή η εγκατάσταση μπορεί να χρησιμοποιεί μόνο δωρεάν πηγές. Ζητήστε από τον διαχειριστή TREK να προσθέσει ένα για βαθμολογίες και ώρες λειτουργίας.', 'places.details.aboutBrand': 'Σχετικά με την αλυσίδα', 'places.details.aboutBrandNote': 'Περιγράφει την αλυσίδα, όχι αυτό το κατάστημα.', 'places.details.fact.rating': 'Βαθμολογία', diff --git a/shared/src/i18n/hu/places.ts b/shared/src/i18n/hu/places.ts index 109779168..8f47b1ce2 100644 --- a/shared/src/i18n/hu/places.ts +++ b/shared/src/i18n/hu/places.ts @@ -92,9 +92,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'Hely adatainak betöltése…', 'places.osmHint': 'OpenStreetMap keresés aktív (képek, nyitvatartás és értékelések nélkül). Bővített adatokhoz add meg a Google API kulcsot a beállításokban.', - 'places.osmActive': 'Az OpenStreetMap van használatban. Egy Google API-kulcs értékeléseket és nyitvatartást ad hozzá.', - 'places.osmActiveAmap': 'OpenStreetMap használatban. Egy Amap (高德地图) API-kulcs értékeléseket és nyitvatartást ad.', - 'places.osmActiveAny': 'OpenStreetMap használatban. Egy Google vagy Amap (高德地图) API-kulcs értékeléseket és nyitvatartást ad.', + 'places.source.amap': 'Amap', 'places.details.title': 'Hely részletei', 'places.details.empty': 'Válassz egy találatot a részletekhez', 'places.details.loading': 'Részletek betöltése...', @@ -107,10 +105,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': 'Előbb ürítsd ki a leírás mezőt', 'places.details.facts': 'Jó tudni', 'places.details.nothing': 'Ehhez a helyhez nem található semmi.', - 'places.details.noKeyTitle': 'További részletek érhetők el', - 'places.details.noKeyHint': 'Google API-kulcs nélkül ez a példány csak szabad forrásokat használ. Kérd a TREK rendszergazdáját, hogy adjon hozzá egyet az értékelésekhez, nyitvatartáshoz és fotókhoz.', - 'places.details.noKeyHintAmap': 'Amap (高德地图) API-kulcs nélkül ez a példány csak ingyenes forrásokat használhat. Kérd a TREK rendszergazdáját, hogy adjon hozzá egyet értékelésekhez és nyitvatartáshoz.', - 'places.details.noKeyHintAny': 'Google vagy Amap (高德地图) API-kulcs nélkül ez a példány csak ingyenes forrásokat használhat. Kérd a TREK rendszergazdáját, hogy adjon hozzá egyet értékelésekhez és nyitvatartáshoz.', 'places.details.aboutBrand': 'A láncról', 'places.details.aboutBrandNote': 'A láncot írja le, nem ezt az egységet.', 'places.details.fact.rating': 'Értékelés', diff --git a/shared/src/i18n/id/places.ts b/shared/src/i18n/id/places.ts index e8fa82cf9..18681353c 100644 --- a/shared/src/i18n/id/places.ts +++ b/shared/src/i18n/id/places.ts @@ -91,9 +91,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'Memuat detail tempat…', 'places.osmHint': 'Menggunakan pencarian OpenStreetMap (tanpa foto, jam buka, atau penilaian). Tambahkan Google API key di pengaturan untuk detail lengkap.', - 'places.osmActive': 'Menggunakan OpenStreetMap. Kunci API Google menambahkan rating dan jam buka.', - 'places.osmActiveAmap': 'Menggunakan OpenStreetMap. Kunci API Amap (高德地图) menambahkan rating dan jam buka.', - 'places.osmActiveAny': 'Menggunakan OpenStreetMap. Kunci API Google atau Amap (高德地图) menambahkan rating dan jam buka.', + 'places.source.amap': 'Amap', 'places.details.title': 'Detail tempat', 'places.details.empty': 'Pilih hasil untuk melihat detail', 'places.details.loading': 'Memuat detail...', @@ -106,10 +104,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': 'Kosongkan dulu kolom deskripsi', 'places.details.facts': 'Perlu diketahui', 'places.details.nothing': 'Tidak ada yang ditemukan untuk tempat ini.', - 'places.details.noKeyTitle': 'Tersedia detail lebih lanjut', - 'places.details.noKeyHint': 'Tanpa kunci API Google, instans ini hanya memakai sumber bebas. Minta administrator TREK menambahkannya untuk rating, jam buka, dan foto.', - 'places.details.noKeyHintAmap': 'Tanpa kunci API Amap (高德地图), instansi ini hanya bisa memakai sumber gratis. Minta administrator TREK menambahkannya untuk rating dan jam buka.', - 'places.details.noKeyHintAny': 'Tanpa kunci API Google atau Amap (高德地图), instansi ini hanya bisa memakai sumber gratis. Minta administrator TREK menambahkannya untuk rating dan jam buka.', 'places.details.aboutBrand': 'Tentang jaringan', 'places.details.aboutBrandNote': 'Menjelaskan jaringannya, bukan cabang ini.', 'places.details.fact.rating': 'Peringkat', diff --git a/shared/src/i18n/it/places.ts b/shared/src/i18n/it/places.ts index 59b7d89a9..dfabf319f 100644 --- a/shared/src/i18n/it/places.ts +++ b/shared/src/i18n/it/places.ts @@ -92,9 +92,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'Caricamento dettagli del luogo…', 'places.osmHint': 'Uso della ricerca OpenStreetMap (senza foto, orari di apertura o valutazioni). Aggiungi una chiave API Google nelle impostazioni per i dettagli completi.', - 'places.osmActive': 'Si usa OpenStreetMap. Una chiave API Google aggiunge valutazioni e orari.', - 'places.osmActiveAmap': 'Uso OpenStreetMap. Una chiave API Amap (高德地图) aggiunge valutazioni e orari.', - 'places.osmActiveAny': 'Uso OpenStreetMap. Una chiave API Google o Amap (高德地图) aggiunge valutazioni e orari.', + 'places.source.amap': 'Amap', 'places.details.title': 'Dettagli del luogo', 'places.details.empty': 'Scegli un risultato per saperne di più', 'places.details.loading': 'Caricamento dettagli...', @@ -107,10 +105,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': 'Svuota prima il campo descrizione', 'places.details.facts': 'Da sapere', 'places.details.nothing': 'Nessun risultato per questo luogo.', - 'places.details.noKeyTitle': 'Sono disponibili più dettagli', - 'places.details.noKeyHint': 'Senza una chiave API di Google questa istanza usa solo fonti libere. Chiedi all\'amministratore di TREK di aggiungerne una per valutazioni, orari e foto.', - 'places.details.noKeyHintAmap': 'Senza una chiave API Amap (高德地图) questa istanza può usare solo fonti gratuite. Chiedi all’amministratore TREK di aggiungerne una per valutazioni e orari.', - 'places.details.noKeyHintAny': 'Senza una chiave API Google o Amap (高德地图) questa istanza può usare solo fonti gratuite. Chiedi all’amministratore TREK di aggiungerne una per valutazioni e orari.', 'places.details.aboutBrand': 'Sulla catena', 'places.details.aboutBrandNote': 'Descrive la catena, non questa sede.', 'places.details.fact.rating': 'Valutazione', diff --git a/shared/src/i18n/ja/places.ts b/shared/src/i18n/ja/places.ts index ddf9ff9fe..46d7e4aa1 100644 --- a/shared/src/i18n/ja/places.ts +++ b/shared/src/i18n/ja/places.ts @@ -92,9 +92,7 @@ const places: TranslationStrings = { 'places.loadingDetails': '詳細を読み込み中…', 'places.osmHint': 'OpenStreetMapで検索しています(写真・営業時間・評価なし)。設定でGoogle APIキーを追加すると詳細が表示されます。', - 'places.osmActive': 'OpenStreetMapを使用中。Google APIキーで評価と営業時間が追加されます。', - 'places.osmActiveAmap': 'OpenStreetMap を使用中です。Amap(高德地图)の API キーを追加すると評価や営業時間が表示されます。', - 'places.osmActiveAny': 'OpenStreetMap を使用中です。Google または Amap(高德地图)の API キーを追加すると評価や営業時間が表示されます。', + 'places.source.amap': 'Amap', 'places.details.title': '場所の詳細', 'places.details.empty': '検索結果を選ぶと詳細が表示されます', 'places.details.loading': '詳細を読み込み中...', @@ -107,10 +105,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': '先に説明欄を空にしてください', 'places.details.facts': '知っておくと便利', 'places.details.nothing': 'この場所の情報は見つかりませんでした。', - 'places.details.noKeyTitle': 'さらに詳しい情報を表示できます', - 'places.details.noKeyHint': 'Google API キーがない場合、このインスタンスは自由に使えるソースのみを利用します。評価・営業時間・写真のために、TREK の管理者にキーの追加を依頼してください。', - 'places.details.noKeyHintAmap': 'Amap(高德地图)の API キーがない場合、このサーバーは無料のソースのみ使用できます。評価や営業時間のために TREK 管理者にキーの追加を依頼してください。', - 'places.details.noKeyHintAny': 'Google または Amap(高德地图)の API キーがない場合、このサーバーは無料のソースのみ使用できます。評価や営業時間のために TREK 管理者にキーの追加を依頼してください。', 'places.details.aboutBrand': 'チェーンについて', 'places.details.aboutBrandNote': 'この店舗ではなくチェーン全体の説明です。', 'places.details.fact.rating': '評価', diff --git a/shared/src/i18n/ko/places.ts b/shared/src/i18n/ko/places.ts index 28cb50776..33c6f0131 100644 --- a/shared/src/i18n/ko/places.ts +++ b/shared/src/i18n/ko/places.ts @@ -91,9 +91,7 @@ const places: TranslationStrings = { 'places.loadingDetails': '장소 상세 정보 불러오는 중…', 'places.osmHint': 'OpenStreetMap 검색 사용 중 (사진, 영업 시간, 평점 없음). 전체 정보를 위해 설정에서 Google API 키를 추가하세요.', - 'places.osmActive': 'OpenStreetMap을 사용 중입니다. Google API 키를 추가하면 평점과 영업시간이 표시됩니다.', - 'places.osmActiveAmap': 'OpenStreetMap을 사용하고 있습니다. Amap(高德地图) API 키를 추가하면 평점과 영업시간을 볼 수 있습니다.', - 'places.osmActiveAny': 'OpenStreetMap을 사용하고 있습니다. Google 또는 Amap(高德地图) API 키를 추가하면 평점과 영업시간을 볼 수 있습니다.', + 'places.source.amap': 'Amap', 'places.details.title': '장소 세부정보', 'places.details.empty': '결과를 선택하면 자세히 볼 수 있어요', 'places.details.loading': '세부정보를 불러오는 중...', @@ -106,10 +104,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': '설명란을 먼저 비워 주세요', 'places.details.facts': '알아두면 좋아요', 'places.details.nothing': '이 장소에 대한 정보를 찾지 못했습니다.', - 'places.details.noKeyTitle': '더 자세한 정보를 볼 수 있습니다', - 'places.details.noKeyHint': 'Google API 키가 없으면 이 인스턴스는 무료 소스만 사용합니다. 평점, 영업시간, 사진을 보려면 TREK 관리자에게 키 추가를 요청하세요.', - 'places.details.noKeyHintAmap': 'Amap(高德地图) API 키가 없으면 이 서버는 무료 출처만 사용할 수 있습니다. 평점과 영업시간을 위해 TREK 관리자에게 키 추가를 요청하세요.', - 'places.details.noKeyHintAny': 'Google 또는 Amap(高德地图) API 키가 없으면 이 서버는 무료 출처만 사용할 수 있습니다. 평점과 영업시간을 위해 TREK 관리자에게 키 추가를 요청하세요.', 'places.details.aboutBrand': '체인 소개', 'places.details.aboutBrandNote': '이 지점이 아니라 체인에 대한 설명입니다.', 'places.details.fact.rating': '평점', diff --git a/shared/src/i18n/nl/places.ts b/shared/src/i18n/nl/places.ts index e09c206b0..ddaf811ed 100644 --- a/shared/src/i18n/nl/places.ts +++ b/shared/src/i18n/nl/places.ts @@ -92,9 +92,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'Plaatsgegevens laden…', 'places.osmHint': "Zoeken via OpenStreetMap (geen foto's, openingstijden of beoordelingen). Voeg een Google API-sleutel toe in instellingen voor volledige details.", - 'places.osmActive': 'OpenStreetMap wordt gebruikt. Een Google API-sleutel voegt beoordelingen en openingstijden toe.', - 'places.osmActiveAmap': 'OpenStreetMap is actief. Een Amap (高德地图) API-sleutel voegt beoordelingen en openingstijden toe.', - 'places.osmActiveAny': 'OpenStreetMap is actief. Een Google- of Amap (高德地图) API-sleutel voegt beoordelingen en openingstijden toe.', + 'places.source.amap': 'Amap', 'places.details.title': 'Plaatsdetails', 'places.details.empty': 'Kies een resultaat voor meer', 'places.details.loading': 'Details laden...', @@ -107,10 +105,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': 'Maak eerst het beschrijvingsveld leeg', 'places.details.facts': 'Goed om te weten', 'places.details.nothing': 'Niets gevonden voor deze plaats.', - 'places.details.noKeyTitle': 'Meer details beschikbaar', - 'places.details.noKeyHint': 'Zonder Google API-sleutel gebruikt deze installatie alleen vrije bronnen. Vraag je TREK-beheerder er een toe te voegen voor beoordelingen, openingstijden en foto\'s.', - 'places.details.noKeyHintAmap': 'Zonder Amap (高德地图) API-sleutel kan deze instantie alleen gratis bronnen gebruiken. Vraag de TREK-beheerder er een toe te voegen voor beoordelingen en openingstijden.', - 'places.details.noKeyHintAny': 'Zonder Google- of Amap (高德地图) API-sleutel kan deze instantie alleen gratis bronnen gebruiken. Vraag de TREK-beheerder er een toe te voegen voor beoordelingen en openingstijden.', 'places.details.aboutBrand': 'Over de keten', 'places.details.aboutBrandNote': 'Beschrijft de keten, niet deze vestiging.', 'places.details.fact.rating': 'Beoordeling', diff --git a/shared/src/i18n/pl/places.ts b/shared/src/i18n/pl/places.ts index 0b9f3edcf..f1ded22a8 100644 --- a/shared/src/i18n/pl/places.ts +++ b/shared/src/i18n/pl/places.ts @@ -82,9 +82,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'Ładowanie szczegółów miejsca…', 'places.osmHint': 'Korzystając z OpenStreetMap (brak zdjęć, godzin otwarcia czy ocen). Dodaj klucz API Google w ustawieniach aby uzyskać pełne dane.', - 'places.osmActive': 'Używany jest OpenStreetMap. Klucz Google API dodaje oceny i godziny otwarcia.', - 'places.osmActiveAmap': 'Korzystasz z OpenStreetMap. Klucz API Amap (高德地图) dodaje oceny i godziny otwarcia.', - 'places.osmActiveAny': 'Korzystasz z OpenStreetMap. Klucz API Google lub Amap (高德地图) dodaje oceny i godziny otwarcia.', + 'places.source.amap': 'Amap', 'places.details.title': 'Szczegóły miejsca', 'places.details.empty': 'Wybierz wynik, aby zobaczyć więcej', 'places.details.loading': 'Wczytywanie szczegółów...', @@ -97,10 +95,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': 'Najpierw wyczyść pole opisu', 'places.details.facts': 'Warto wiedzieć', 'places.details.nothing': 'Nic nie znaleziono dla tego miejsca.', - 'places.details.noKeyTitle': 'Dostępnych jest więcej szczegółów', - 'places.details.noKeyHint': 'Bez klucza API Google ta instancja korzysta tylko ze źródeł otwartych. Poproś administratora TREK o dodanie klucza, aby uzyskać oceny, godziny otwarcia i zdjęcia.', - 'places.details.noKeyHintAmap': 'Bez klucza API Amap (高德地图) ta instancja może korzystać tylko z darmowych źródeł. Poproś administratora TREK o dodanie klucza, aby uzyskać oceny i godziny otwarcia.', - 'places.details.noKeyHintAny': 'Bez klucza API Google lub Amap (高德地图) ta instancja może korzystać tylko z darmowych źródeł. Poproś administratora TREK o dodanie klucza, aby uzyskać oceny i godziny otwarcia.', 'places.details.aboutBrand': 'O sieci', 'places.details.aboutBrandNote': 'Opisuje sieć, a nie ten lokal.', 'places.details.fact.rating': 'Ocena', diff --git a/shared/src/i18n/ru/places.ts b/shared/src/i18n/ru/places.ts index c81743edc..11167ce08 100644 --- a/shared/src/i18n/ru/places.ts +++ b/shared/src/i18n/ru/places.ts @@ -92,9 +92,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'Загрузка данных о месте…', 'places.osmHint': 'Поиск через OpenStreetMap (без фото, часов работы и рейтингов). Добавьте API-ключ Google в настройках для полной информации.', - 'places.osmActive': 'Используется OpenStreetMap. Ключ Google API добавит рейтинги и часы работы.', - 'places.osmActiveAmap': 'Используется OpenStreetMap. Ключ API Amap (高德地图) добавит оценки и часы работы.', - 'places.osmActiveAny': 'Используется OpenStreetMap. Ключ API Google или Amap (高德地图) добавит оценки и часы работы.', + 'places.source.amap': 'Amap', 'places.details.title': 'Сведения о месте', 'places.details.empty': 'Выберите результат, чтобы узнать больше', 'places.details.loading': 'Загрузка сведений...', @@ -107,10 +105,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': 'Сначала очистите поле описания', 'places.details.facts': 'Полезно знать', 'places.details.nothing': 'Для этого места ничего не найдено.', - 'places.details.noKeyTitle': 'Доступно больше подробностей', - 'places.details.noKeyHint': 'Без ключа Google API этот экземпляр использует только свободные источники. Попросите администратора TREK добавить ключ для оценок, часов работы и фотографий.', - 'places.details.noKeyHintAmap': 'Без ключа API Amap (高德地图) этот сервер может использовать только бесплатные источники. Попросите администратора TREK добавить ключ — появятся оценки и часы работы.', - 'places.details.noKeyHintAny': 'Без ключа API Google или Amap (高德地图) этот сервер может использовать только бесплатные источники. Попросите администратора TREK добавить ключ — появятся оценки и часы работы.', 'places.details.aboutBrand': 'О сети', 'places.details.aboutBrandNote': 'Описывает сеть, а не это заведение.', 'places.details.fact.rating': 'Рейтинг', diff --git a/shared/src/i18n/sv/places.ts b/shared/src/i18n/sv/places.ts index 32179f3fc..777f44557 100644 --- a/shared/src/i18n/sv/places.ts +++ b/shared/src/i18n/sv/places.ts @@ -92,6 +92,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'Hämtar information om platsen…', 'places.osmHint': 'Använder OpenStreetMap-sökningen (inga bilder, öppettider eller betyg). Lägg till en Google API-nyckel i inställningarna för fullständig information.', + 'places.source.amap': 'Amap', 'places.details.title': 'Platsdetaljer', 'places.details.empty': 'Välj ett resultat för att se mer', 'places.details.loading': 'Laddar detaljer...', diff --git a/shared/src/i18n/tr/places.ts b/shared/src/i18n/tr/places.ts index d2d989625..a34b759d3 100644 --- a/shared/src/i18n/tr/places.ts +++ b/shared/src/i18n/tr/places.ts @@ -94,9 +94,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'Yer ayrıntıları yükleniyor…', 'places.osmHint': 'OpenStreetMap aramasını kullanma (fotoğraf, açılış saatleri veya derecelendirme yok). Tüm ayrıntılar için ayarlara bir Google API anahtarı ekleyin.', - 'places.osmActive': 'OpenStreetMap kullanılıyor. Google API anahtarı puan ve çalışma saatleri ekler.', - 'places.osmActiveAmap': 'OpenStreetMap kullanılıyor. Bir Amap (高德地图) API anahtarı puanlar ve çalışma saatleri ekler.', - 'places.osmActiveAny': 'OpenStreetMap kullanılıyor. Bir Google veya Amap (高德地图) API anahtarı puanlar ve çalışma saatleri ekler.', + 'places.source.amap': 'Amap', 'places.details.title': 'Yer ayrıntıları', 'places.details.empty': 'Ayrıntılar için bir sonuç seç', 'places.details.loading': 'Ayrıntılar yükleniyor...', @@ -109,10 +107,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': 'Önce açıklama alanını boşalt', 'places.details.facts': 'Bilmekte fayda var', 'places.details.nothing': 'Bu yer için bir şey bulunamadı.', - 'places.details.noKeyTitle': 'Daha fazla ayrıntı mevcut', - 'places.details.noKeyHint': 'Google API anahtarı olmadan bu örnek yalnızca özgür kaynakları kullanır. Puanlar, açılış saatleri ve fotoğraflar için TREK yöneticinizden bir anahtar eklemesini isteyin.', - 'places.details.noKeyHintAmap': 'Amap (高德地图) API anahtarı olmadan bu sunucu yalnızca ücretsiz kaynakları kullanabilir. Puanlar ve çalışma saatleri için TREK yöneticisinden bir anahtar eklemesini isteyin.', - 'places.details.noKeyHintAny': 'Google veya Amap (高德地图) API anahtarı olmadan bu sunucu yalnızca ücretsiz kaynakları kullanabilir. Puanlar ve çalışma saatleri için TREK yöneticisinden bir anahtar eklemesini isteyin.', 'places.details.aboutBrand': 'Zincir hakkında', 'places.details.aboutBrandNote': 'Bu şubeyi değil, zinciri anlatır.', 'places.details.fact.rating': 'Puan', diff --git a/shared/src/i18n/uk/places.ts b/shared/src/i18n/uk/places.ts index a577b93f5..eaeb2a12a 100644 --- a/shared/src/i18n/uk/places.ts +++ b/shared/src/i18n/uk/places.ts @@ -92,9 +92,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'Завантаження даних про місце…', 'places.osmHint': 'Пошук через OpenStreetMap (без фото, годин роботи та рейтингів). Додайте API-ключ Google у налаштуваннях для повної інформації.', - 'places.osmActive': 'Використовується OpenStreetMap. Ключ Google API додасть рейтинги та години роботи.', - 'places.osmActiveAmap': 'Використовується OpenStreetMap. Ключ API Amap (高德地图) додасть оцінки й години роботи.', - 'places.osmActiveAny': 'Використовується OpenStreetMap. Ключ API Google або Amap (高德地图) додасть оцінки й години роботи.', + 'places.source.amap': 'Amap', 'places.details.title': 'Відомості про місце', 'places.details.empty': 'Виберіть результат, щоб побачити більше', 'places.details.loading': 'Завантаження відомостей...', @@ -107,10 +105,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': 'Спершу очистіть поле опису', 'places.details.facts': 'Корисно знати', 'places.details.nothing': 'Для цього місця нічого не знайдено.', - 'places.details.noKeyTitle': 'Доступно більше деталей', - 'places.details.noKeyHint': 'Без ключа Google API цей екземпляр використовує лише вільні джерела. Попросіть адміністратора TREK додати ключ, щоб отримати оцінки, години роботи та фото.', - 'places.details.noKeyHintAmap': 'Без ключа API Amap (高德地图) цей сервер може використовувати лише безкоштовні джерела. Попросіть адміністратора TREK додати ключ — з’являться оцінки й години роботи.', - 'places.details.noKeyHintAny': 'Без ключа API Google або Amap (高德地图) цей сервер може використовувати лише безкоштовні джерела. Попросіть адміністратора TREK додати ключ — з’являться оцінки й години роботи.', 'places.details.aboutBrand': 'Про мережу', 'places.details.aboutBrandNote': 'Описує мережу, а не цей заклад.', 'places.details.fact.rating': 'Рейтинг', diff --git a/shared/src/i18n/vi/places.ts b/shared/src/i18n/vi/places.ts index fd1fd0838..ab072b202 100644 --- a/shared/src/i18n/vi/places.ts +++ b/shared/src/i18n/vi/places.ts @@ -92,6 +92,7 @@ const places: TranslationStrings = { 'places.loadingDetails': 'Đang tải chi tiết địa điểm…', 'places.osmHint': 'Sử dụng tìm kiếm OpenStreetMap (không có ảnh, giờ mở cửa hoặc xếp hạng). Thêm khóa Google API trong cài đặt để biết đầy đủ chi tiết.', + 'places.source.amap': 'Amap', 'places.details.title': 'Chi tiết địa điểm', 'places.details.empty': 'Chọn một kết quả để xem thêm', 'places.details.loading': 'Đang tải chi tiết...', diff --git a/shared/src/i18n/zh-TW/places.ts b/shared/src/i18n/zh-TW/places.ts index 2379c78d9..90bab3b92 100644 --- a/shared/src/i18n/zh-TW/places.ts +++ b/shared/src/i18n/zh-TW/places.ts @@ -90,9 +90,7 @@ const places: TranslationStrings = { 'places.mapsSearchError': '地點搜尋失敗。', 'places.loadingDetails': '正在載入地點詳情…', 'places.osmHint': '使用 OpenStreetMap 搜尋(無照片、營業時間或評分)。在設定中新增 Google API 金鑰以獲取完整資訊。', - 'places.osmActive': '正在使用 OpenStreetMap。加入 Google API 金鑰可取得評分與營業時間。', - 'places.osmActiveAmap': '正在使用 OpenStreetMap。新增高德 API 金鑰可獲得評分和營業時間。', - 'places.osmActiveAny': '正在使用 OpenStreetMap。新增 Google 或高德 API 金鑰可獲得評分和營業時間。', + 'places.source.amap': '高德地圖', 'places.details.title': '地點詳細資料', 'places.details.empty': '選擇一個結果查看詳細資訊', 'places.details.loading': '正在載入詳細資料...', @@ -105,10 +103,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': '請先清空描述欄位', 'places.details.facts': '值得一看', 'places.details.nothing': '找不到此地點的資訊。', - 'places.details.noKeyTitle': '可取得更多詳細資訊', - 'places.details.noKeyHint': '沒有 Google API 金鑰時,此執行個體僅使用免費來源。請聯絡 TREK 管理員新增金鑰,以取得評分、營業時間與照片。', - 'places.details.noKeyHintAmap': '沒有高德 API 金鑰時,此實例僅使用免費來源。請聯絡 TREK 管理員新增金鑰,以取得評分和營業時間。', - 'places.details.noKeyHintAny': '沒有 Google 或高德 API 金鑰時,此實例僅使用免費來源。請聯絡 TREK 管理員新增金鑰,以取得評分和營業時間。', 'places.details.aboutBrand': '關於該連鎖品牌', 'places.details.aboutBrandNote': '描述的是連鎖品牌,而非本分店。', 'places.details.fact.rating': '評分', diff --git a/shared/src/i18n/zh/places.ts b/shared/src/i18n/zh/places.ts index dc34578e2..0befd6993 100644 --- a/shared/src/i18n/zh/places.ts +++ b/shared/src/i18n/zh/places.ts @@ -90,9 +90,7 @@ const places: TranslationStrings = { 'places.mapsSearchError': '地点搜索失败。', 'places.loadingDetails': '正在加载地点详情…', 'places.osmHint': '使用 OpenStreetMap 搜索(无照片、营业时间或评分)。在设置中添加 Google API 密钥以获取完整信息。', - 'places.osmActive': '正在使用 OpenStreetMap。添加 Google API 密钥可获得评分和营业时间。', - 'places.osmActiveAmap': '正在使用 OpenStreetMap。添加高德 API 密钥可获得评分和营业时间。', - 'places.osmActiveAny': '正在使用 OpenStreetMap。添加 Google 或高德 API 密钥可获得评分和营业时间。', + 'places.source.amap': '高德地图', 'places.details.title': '地点详情', 'places.details.empty': '选择一个结果查看详情', 'places.details.loading': '正在加载详情...', @@ -105,10 +103,6 @@ const places: TranslationStrings = { 'places.details.adoptBlocked': '请先清空描述字段', 'places.details.facts': '值得一看', 'places.details.nothing': '未找到该地点的信息。', - 'places.details.noKeyTitle': '可获取更多详情', - 'places.details.noKeyHint': '没有 Google API 密钥时,此实例仅使用免费来源。请联系 TREK 管理员添加密钥,以获取评分、营业时间和照片。', - 'places.details.noKeyHintAmap': '没有高德 API 密钥时,此实例仅使用免费来源。请联系 TREK 管理员添加密钥,以获取评分和营业时间。', - 'places.details.noKeyHintAny': '没有 Google 或高德 API 密钥时,此实例仅使用免费来源。请联系 TREK 管理员添加密钥,以获取评分和营业时间。', 'places.details.aboutBrand': '关于该连锁品牌', 'places.details.aboutBrandNote': '描述的是连锁品牌,而非本门店。', 'places.details.fact.rating': '评分',