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': '评分',