diff --git a/client/src/App.tsx b/client/src/App.tsx index fdbf90e54..b98133640 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -261,7 +261,7 @@ function RouteFallback() { } export default function App() { - const { loadUser, isAuthenticated, demoMode, setDemoMode, setDevMode, setIsPrerelease, setAppVersion, setHasMapsKey, setServerTimezone, setAppRequireMfa, setTripRemindersEnabled, setPlacesPhotosEnabled, setPlacesAutocompleteEnabled, setPlacesDetailsEnabled } = useAuthStore() + const { loadUser, isAuthenticated, demoMode, setDemoMode, setDevMode, setIsPrerelease, setAppVersion, setHasMapsKey, setServerTimezone, setAppRequireMfa, setTripRemindersEnabled, setPlacesPhotosEnabled, setPlacesAutocompleteEnabled, setPlacesDetailsEnabled, setPlacesEnrichEnabled } = useAuthStore() const { loadSettings } = useSettingsStore() const { loadAddons } = useAddonStore() const { loadPlugins } = usePluginStore() @@ -278,7 +278,7 @@ export default function App() { loadUser() } } - authApi.getAppConfig().then(async (config: { 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; permissions?: Record }) => { + authApi.getAppConfig().then(async (config: { 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; permissions?: Record }) => { setDemoMode(!!config?.demo_mode) if (config?.dev_mode) setDevMode(true) if (config?.is_prerelease !== undefined) setIsPrerelease(config.is_prerelease) @@ -290,6 +290,7 @@ export default function App() { if (config?.places_photos_enabled !== undefined) setPlacesPhotosEnabled(config.places_photos_enabled) if (config?.places_autocomplete_enabled !== undefined) setPlacesAutocompleteEnabled(config.places_autocomplete_enabled) if (config?.places_details_enabled !== undefined) setPlacesDetailsEnabled(config.places_details_enabled) + if (config?.places_enrich_enabled !== undefined) setPlacesEnrichEnabled(config.places_enrich_enabled) if (config?.permissions) usePermissionsStore.getState().setPermissions(config.permissions) if (config?.version) { diff --git a/client/src/api/client.ts b/client/src/api/client.ts index dcb2da327..3d65ccb94 100644 --- a/client/src/api/client.ts +++ b/client/src/api/client.ts @@ -8,6 +8,7 @@ import { channelTestResultSchema, mapsSearchResultSchema, mapsAutocompleteResultSchema, mapsPlaceDetailsResultSchema, mapsPlacePhotoResultSchema, mapsReverseResultSchema, mapsResolveUrlResultSchema, + mapsPlaceEnrichmentResultSchema, type NotificationRespondRequest, type SettingUpsertRequest, type SettingsBulkRequest, type JourneyCreateRequest, type JourneyAddTripRequest, @@ -599,6 +600,8 @@ export const adminApi = { updatePlacesAutocomplete: (enabled: boolean) => apiClient.put('/admin/places-autocomplete', { enabled }).then(r => r.data), getPlacesDetails: () => apiClient.get('/admin/places-details').then(r => r.data), updatePlacesDetails: (enabled: boolean) => apiClient.put('/admin/places-details', { enabled }).then(r => r.data), + getPlacesEnrich: () => apiClient.get('/admin/places-enrich').then(r => r.data), + updatePlacesEnrich: (enabled: boolean) => apiClient.put('/admin/places-enrich', { enabled }).then(r => r.data), getCollabFeatures: () => apiClient.get('/admin/collab-features').then(r => r.data), updateCollabFeatures: (features: Record) => apiClient.put('/admin/collab-features', features).then(r => r.data), packingTemplates: () => apiClient.get('/admin/packing-templates').then(r => r.data), @@ -926,6 +929,17 @@ export const mapsApi = { autocomplete: (input: string, lang?: string, locationBias?: { low: { lat: number; lng: number }; high: { lat: number; lng: number } }, signal?: AbortSignal) => apiClient.post('/maps/autocomplete', { input, lang, locationBias }, { signal }).then(r => checkInDev(mapsAutocompleteResultSchema, r.data, 'maps.autocomplete')), details: (placeId: string, lang?: string) => apiClient.get(`/maps/details/${encodeURIComponent(placeId)}`, { params: { lang } }).then(r => checkInDev(mapsPlaceDetailsResultSchema, r.data, 'maps.details')), + // Pictures and a description for a place that is being looked at but not yet + // saved. Fans out to several providers server-side, so it takes a signal and + // the caller is expected to abort it when the selection changes. + placeEnrichment: ( + body: { placeId?: string; lat: number; lng: number; name: string; lang?: string }, + signal?: AbortSignal, + ) => apiClient.post('/maps/enrichment', body, { signal }).then(r => checkInDev(mapsPlaceEnrichmentResultSchema, r.data, 'maps.placeEnrichment')), + // Author + licence for a picture already stored in the photo cache, keyed by + // the cache key embedded in its proxy URL. Local read, no provider call. + placePhotoCredit: (key: string) => + apiClient.get(`/maps/enrichment/credit/${encodeURIComponent(key)}`).then(r => r.data as { credit: string | null }), placePhoto: (placeId: string, lat?: number, lng?: number, name?: string) => apiClient.get(`/maps/place-photo/${encodeURIComponent(placeId)}`, { params: { lat, lng, name } }).then(r => checkInDev(mapsPlacePhotoResultSchema, r.data, 'maps.placePhoto')), reverse: (lat: number, lng: number, lang?: string) => apiClient.get('/maps/reverse', { params: { lat, lng, lang } }).then(r => checkInDev(mapsReverseResultSchema, r.data, 'maps.reverse')), resolveUrl: (url: string) => apiClient.post('/maps/resolve-url', { url }).then(r => checkInDev(mapsResolveUrlResultSchema, r.data, 'maps.resolveUrl')), diff --git a/client/src/mobile/screens/admin/MAdminSettingsSection.tsx b/client/src/mobile/screens/admin/MAdminSettingsSection.tsx index 0e61a2aa0..9fc762c55 100644 --- a/client/src/mobile/screens/admin/MAdminSettingsSection.tsx +++ b/client/src/mobile/screens/admin/MAdminSettingsSection.tsx @@ -25,10 +25,11 @@ interface MAdminSettingsSectionProps { export default function MAdminSettingsSection({ admin, t }: MAdminSettingsSectionProps) { const { toast, - setPlacesPhotosEnabled, setPlacesAutocompleteEnabled, setPlacesDetailsEnabled, + setPlacesPhotosEnabled, setPlacesAutocompleteEnabled, setPlacesDetailsEnabled, setPlacesEnrichEnabled, placesPhotosEnabled, setPlacesPhotosEnabledState, placesAutocompleteEnabled, setPlacesAutocompleteEnabledState, placesDetailsEnabled, setPlacesDetailsEnabledState, + placesEnrichEnabled, setPlacesEnrichEnabledState, oidcConfig, setOidcConfig, savingOidc, setSavingOidc, passwordLogin, setPasswordLogin, passwordRegistration, setPasswordRegistration, oidcLogin, setOidcLogin, oidcRegistration, setOidcRegistration, @@ -323,6 +324,26 @@ export default function MAdminSettingsSection({ admin, t }: MAdminSettingsSectio /> } /> + { + setPlacesEnrichEnabledState(next) + setPlacesEnrichEnabled(next) + try { + await adminApi.updatePlacesEnrich(next) + } catch { + setPlacesEnrichEnabledState(!next) + setPlacesEnrichEnabled(!next) + } + }} + /> + } + /> {/* Open-Meteo weather info */} diff --git a/client/src/pages/admin/AdminSettingsTab.test.tsx b/client/src/pages/admin/AdminSettingsTab.test.tsx index 22b185e47..078c6487e 100644 --- a/client/src/pages/admin/AdminSettingsTab.test.tsx +++ b/client/src/pages/admin/AdminSettingsTab.test.tsx @@ -353,6 +353,27 @@ describe('AdminSettingsTab', () => { expect(admin.setPlacesDetailsEnabled).toHaveBeenLastCalledWith(true); }); + it('FE-ADMSET-030b: places enrichment toggle updates state and rolls back on failure', async () => { + server.use(http.put('/api/admin/places-enrich', () => HttpResponse.json({}, { status: 500 }))); + const admin = renderTab({ placesEnrichEnabled: true }); + + fireEvent.click(toggleFor('Place Enrichment')); + + expect(admin.setPlacesEnrichEnabledState).toHaveBeenNthCalledWith(1, false); + await waitFor(() => expect(admin.setPlacesEnrichEnabledState).toHaveBeenLastCalledWith(true)); + expect(admin.setPlacesEnrichEnabled).toHaveBeenLastCalledWith(true); + }); + + it('FE-ADMSET-030c: the four places toggles expose their label to screen readers', () => { + // They moved from hand-rolled buttons to ToggleSwitch, which is what carries + // the aria-label and aria-pressed the bare markup never had. + renderTab({ placesPhotosEnabled: true, placesEnrichEnabled: false }); + + expect(toggleFor('Place Photos')).toHaveAttribute('aria-pressed', 'true'); + expect(toggleFor('Place Enrichment')).toHaveAttribute('aria-pressed', 'false'); + expect(toggleFor('Place Enrichment')).toHaveAttribute('aria-label', 'Place Enrichment'); + }); + it('FE-ADMSET-031: shows the Open-Meteo info block', () => { renderTab(); diff --git a/client/src/pages/admin/AdminSettingsTab.tsx b/client/src/pages/admin/AdminSettingsTab.tsx index 063865b9c..bed97b884 100644 --- a/client/src/pages/admin/AdminSettingsTab.tsx +++ b/client/src/pages/admin/AdminSettingsTab.tsx @@ -2,6 +2,7 @@ import React from 'react' import { adminApi, authApi } from '../../api/client' import { getApiErrorMessage } from '../../types' import { Eye, EyeOff, Save, CheckCircle, XCircle, Loader2, Sun, RefreshCw, AlertTriangle } from 'lucide-react' +import ToggleSwitch from '../../components/Settings/ToggleSwitch' import type { TranslationFn } from '../../types' import type { useAdmin } from './useAdmin' @@ -15,10 +16,11 @@ interface AdminSettingsTabProps { export default function AdminSettingsTab({ admin, t }: AdminSettingsTabProps): React.ReactElement { const { toast, - setPlacesPhotosEnabled, setPlacesAutocompleteEnabled, setPlacesDetailsEnabled, + setPlacesPhotosEnabled, setPlacesAutocompleteEnabled, setPlacesDetailsEnabled, setPlacesEnrichEnabled, placesPhotosEnabled, setPlacesPhotosEnabledState, placesAutocompleteEnabled, setPlacesAutocompleteEnabledState, placesDetailsEnabled, setPlacesDetailsEnabledState, + placesEnrichEnabled, setPlacesEnrichEnabledState, oidcConfig, setOidcConfig, savingOidc, setSavingOidc, passwordLogin, setPasswordLogin, passwordRegistration, setPasswordRegistration, oidcLogin, setOidcLogin, oidcRegistration, setOidcRegistration, @@ -334,17 +336,16 @@ export default function AdminSettingsTab({ admin, t }: AdminSettingsTabProps): R

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

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

- + /> {/* Place Autocomplete Toggle */} @@ -353,17 +354,16 @@ export default function AdminSettingsTab({ admin, t }: AdminSettingsTabProps): R

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

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

- + /> {/* Place Details Toggle */} @@ -372,17 +372,34 @@ export default function AdminSettingsTab({ admin, t }: AdminSettingsTabProps): R

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

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

- + /> + + + {/* Place Enrichment Toggle */} +
+
+

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

+

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

+
+ { + const next = !placesEnrichEnabled + setPlacesEnrichEnabledState(next) + setPlacesEnrichEnabled(next) + try { await adminApi.updatePlacesEnrich(next) } catch { setPlacesEnrichEnabledState(!next); setPlacesEnrichEnabled(!next) } + }} + />
{/* Open-Meteo Weather Info */} diff --git a/client/src/pages/admin/useAdmin.ts b/client/src/pages/admin/useAdmin.ts index c9642dfff..49ffdab86 100644 --- a/client/src/pages/admin/useAdmin.ts +++ b/client/src/pages/admin/useAdmin.ts @@ -46,7 +46,9 @@ export function useAdmin() { // Places details const [placesDetailsEnabled, setPlacesDetailsEnabledState] = useState(true) + const [placesEnrichEnabled, setPlacesEnrichEnabledState] = useState(true) useEffect(() => { adminApi.getPlacesDetails().then(d => setPlacesDetailsEnabledState(d.enabled)).catch(() => {}) }, []) + useEffect(() => { adminApi.getPlacesEnrich().then(d => setPlacesEnrichEnabledState(d.enabled)).catch(() => {}) }, []) // Collab features const [collabFeatures, setCollabFeatures] = useState<{ chat: boolean; notes: boolean; polls: boolean; whatsnext: boolean }>({ chat: true, notes: true, polls: true, whatsnext: true }) @@ -107,7 +109,7 @@ export function useAdmin() { const [updateInfo, setUpdateInfo] = useState(null) const [showUpdateModal, setShowUpdateModal] = useState(false) - const { user: currentUser, updateApiKeys, setAppRequireMfa, setTripRemindersEnabled, setPlacesPhotosEnabled, setPlacesAutocompleteEnabled, setPlacesDetailsEnabled, logout } = useAuthStore() + const { user: currentUser, updateApiKeys, setAppRequireMfa, setTripRemindersEnabled, setPlacesPhotosEnabled, setPlacesAutocompleteEnabled, setPlacesDetailsEnabled, setPlacesEnrichEnabled, logout } = useAuthStore() const navigate = useNavigate() const toast = useToast() @@ -361,7 +363,7 @@ export function useAdmin() { // store-derived demoMode, serverTimezone, hour12, mcpEnabled, devMode, currentUser, updateApiKeys, setAppRequireMfa, setTripRemindersEnabled, - setPlacesPhotosEnabled, setPlacesAutocompleteEnabled, setPlacesDetailsEnabled, logout, + setPlacesPhotosEnabled, setPlacesAutocompleteEnabled, setPlacesDetailsEnabled, setPlacesEnrichEnabled, logout, navigate, toast, // state + setters activeTab, setActiveTab, users, setUsers, stats, isLoading, @@ -371,6 +373,7 @@ export function useAdmin() { placesPhotosEnabled, setPlacesPhotosEnabledState, placesAutocompleteEnabled, setPlacesAutocompleteEnabledState, placesDetailsEnabled, setPlacesDetailsEnabledState, + placesEnrichEnabled, setPlacesEnrichEnabledState, collabFeatures, setCollabFeatures, oidcConfig, setOidcConfig, savingOidc, setSavingOidc, passwordLogin, setPasswordLogin, passwordRegistration, setPasswordRegistration, diff --git a/client/src/store/authStore.ts b/client/src/store/authStore.ts index b0e8f9d42..1eadbe1fa 100644 --- a/client/src/store/authStore.ts +++ b/client/src/store/authStore.ts @@ -50,6 +50,7 @@ interface AuthState { placesPhotosEnabled: boolean placesAutocompleteEnabled: boolean placesDetailsEnabled: boolean + placesEnrichEnabled: boolean login: (email: string, password: string, rememberMe?: boolean) => Promise completeMfaLogin: (mfaToken: string, code: string, rememberMe?: boolean) => Promise @@ -73,6 +74,7 @@ interface AuthState { setPlacesPhotosEnabled: (val: boolean) => void setPlacesAutocompleteEnabled: (val: boolean) => void setPlacesDetailsEnabled: (val: boolean) => void + setPlacesEnrichEnabled: (val: boolean) => void demoLogin: () => Promise } @@ -112,6 +114,7 @@ export const useAuthStore = create()( placesPhotosEnabled: true, placesAutocompleteEnabled: true, placesDetailsEnabled: true, + placesEnrichEnabled: true, login: async (email: string, password: string, rememberMe?: boolean) => { authSequence++ @@ -340,6 +343,7 @@ export const useAuthStore = create()( setPlacesPhotosEnabled: (val: boolean) => set({ placesPhotosEnabled: val }), setPlacesAutocompleteEnabled: (val: boolean) => set({ placesAutocompleteEnabled: val }), setPlacesDetailsEnabled: (val: boolean) => set({ placesDetailsEnabled: val }), + setPlacesEnrichEnabled: (val: boolean) => set({ placesEnrichEnabled: val }), demoLogin: async () => { authSequence++ diff --git a/client/tests/helpers/mobileAdmin.ts b/client/tests/helpers/mobileAdmin.ts index 20b5c69f1..e3eb88bbd 100644 --- a/client/tests/helpers/mobileAdmin.ts +++ b/client/tests/helpers/mobileAdmin.ts @@ -44,6 +44,7 @@ export function buildAdminHook(overrides: Record = {}): AdminHo setPlacesPhotosEnabled: vi.fn(), setPlacesAutocompleteEnabled: vi.fn(), setPlacesDetailsEnabled: vi.fn(), + setPlacesEnrichEnabled: vi.fn(), logout: vi.fn(async () => undefined), navigate: vi.fn(), toast: buildAdminToast(), @@ -73,6 +74,8 @@ export function buildAdminHook(overrides: Record = {}): AdminHo setPlacesAutocompleteEnabledState: vi.fn(), placesDetailsEnabled: false, setPlacesDetailsEnabledState: vi.fn(), + placesEnrichEnabled: true, + setPlacesEnrichEnabledState: vi.fn(), collabFeatures: { chat: true, notes: true, polls: true, whatsnext: true }, setCollabFeatures: vi.fn(), diff --git a/server/src/nest/admin/admin.controller.ts b/server/src/nest/admin/admin.controller.ts index 3dded6b4f..c4a069377 100644 --- a/server/src/nest/admin/admin.controller.ts +++ b/server/src/nest/admin/admin.controller.ts @@ -213,6 +213,16 @@ export class AdminController { return result; } + @Get('places-enrich') + getPlacesEnrich() { return this.admin.getPlacesEnrich(); } + + @Put('places-enrich') + updatePlacesEnrich(@CurrentUser() user: User, @Body() body: AdminFeatureToggleDto, @Req() req: Request) { + const result = this.admin.updatePlacesEnrich(body.enabled); + this.audit.writeAudit({ userId: user.id, action: 'admin.places_enrich', ip: getClientIp(req), details: { enabled: result.enabled } }); + return result; + } + @Get('collab-features') getCollabFeatures() { return this.addons.getCollabFeatures(); } diff --git a/server/src/nest/auth/auth.service.ts b/server/src/nest/auth/auth.service.ts index b3d150630..bfb6a6db4 100644 --- a/server/src/nest/auth/auth.service.ts +++ b/server/src/nest/auth/auth.service.ts @@ -264,6 +264,8 @@ export class AuthService implements OnModuleInit, OnModuleDestroy { const placesAutocompleteEnabled = placesAutocompleteSetting !== 'false'; const placesDetailsSetting = this.db.get<{ value: string }>("SELECT value FROM app_settings WHERE key = 'places_details_enabled'")?.value; const placesDetailsEnabled = placesDetailsSetting !== 'false'; + const placesEnrichSetting = this.db.get<{ value: string }>("SELECT value FROM app_settings WHERE key = 'places_enrich_enabled'")?.value; + const placesEnrichEnabled = placesEnrichSetting !== 'false'; const setupComplete = userCount > 0 && !this.db.get("SELECT id FROM users WHERE role = 'admin' AND must_change_password = 1 LIMIT 1"); return { @@ -302,6 +304,7 @@ export class AuthService implements OnModuleInit, OnModuleDestroy { places_photos_enabled: placesPhotosEnabled, places_autocomplete_enabled: placesAutocompleteEnabled, places_details_enabled: placesDetailsEnabled, + places_enrich_enabled: placesEnrichEnabled, permissions: authenticatedUser ? this.permissions.getAllPermissions() : undefined, // Case-sensitive on purpose (legacy parity). dev_mode: readEnv().app.nodeEnv === 'development', diff --git a/server/tests/unit/nest/admin.service.test.ts b/server/tests/unit/nest/admin.service.test.ts index 3a2edcb75..a4640418c 100644 --- a/server/tests/unit/nest/admin.service.test.ts +++ b/server/tests/unit/nest/admin.service.test.ts @@ -505,6 +505,23 @@ describe('admin.bridge', () => { describe('admin quirk fixes (post-fold)', () => { + it('ADMIN-SVC-071b — places enrichment is fail-OPEN, so no migration is needed for it', () => { + // Deliberately the other way round from the three above: unset means on, and + // PlaceEnrichmentService reads the same setting the same way. Reading it + // fail-closed here would show "off" in the panel while the feature ran. + expect(svc.getPlacesEnrich()).toEqual({ enabled: true }); + + svc.updatePlacesEnrich(false); + expect(svc.getPlacesEnrich()).toEqual({ enabled: false }); + + svc.updatePlacesEnrich(true); + expect(svc.getPlacesEnrich()).toEqual({ enabled: true }); + + // Only the literal 'false' switches it off; anything else stays on. + testDb.prepare("INSERT OR REPLACE INTO app_settings (key, value) VALUES ('places_enrich_enabled', 'garbage')").run(); + expect(svc.getPlacesEnrich()).toEqual({ enabled: true }); + }); + it('ADMIN-SVC-072 — updateUser rejects an empty username/email instead of silently no-opping', () => { const { user } = createUser(testDb); expect(updateUser(String(user.id), { username: '' }) as any).toMatchObject({ status: 400, error: 'Username cannot be empty' }); diff --git a/shared/src/i18n/ar/admin.ts b/shared/src/i18n/ar/admin.ts index d89bcd446..8f1101eb7 100644 --- a/shared/src/i18n/ar/admin.ts +++ b/shared/src/i18n/ar/admin.ts @@ -142,6 +142,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'تفاصيل الأماكن', 'admin.placesDetails.subtitle': 'جلب معلومات تفصيلية عن الأماكن (الساعات، التقييم، الموقع) من Google Places API. عطّلها للحفاظ على حصة API.', + 'admin.placesEnrich.title': 'إثراء الأماكن', + 'admin.placesEnrich.subtitle': + 'يعرض صورًا ووصفًا أثناء إضافة مكان. تُستخدم ويكيبيديا وOpenStreetMap دائمًا، وتُضاف Google عند تفعيل صور الأماكن أو تفاصيل الأماكن.', 'admin.bagTracking.title': 'تتبع الأمتعة', 'admin.bagTracking.subtitle': 'تفعيل الوزن وتعيين الأمتعة للعناصر', 'admin.collab.chat.title': 'الدردشة', diff --git a/shared/src/i18n/ar/places.ts b/shared/src/i18n/ar/places.ts index 12769143c..fce666cf5 100644 --- a/shared/src/i18n/ar/places.ts +++ b/shared/src/i18n/ar/places.ts @@ -86,6 +86,16 @@ const places: TranslationStrings = { 'يتم البحث عبر OpenStreetMap (بدون صور أو ساعات عمل أو تقييمات). أضف مفتاح Google API في الإعدادات للحصول على جميع التفاصيل.', 'places.osmActive': 'البحث عبر OpenStreetMap (بدون صور أو تقييمات أو ساعات عمل). أضف مفتاح Google API في الإعدادات لبيانات موسعة.', + 'places.details.title': 'تفاصيل المكان', + 'places.details.empty': 'اختر نتيجة بحث لعرض الصور والوصف.', + 'places.details.loading': 'جارٍ تحميل التفاصيل...', + 'places.details.error': 'تعذّر تحميل التفاصيل.', + 'places.details.disabled': 'الإثراء معطّل من قِبل المسؤول.', + 'places.details.noPhotos': 'لم يتم العثور على صور لهذا المكان.', + 'places.details.pickImage': 'اختر صورة', + 'places.details.description': 'الوصف', + 'places.details.adopt': 'استخدام هذا النص', + 'places.details.adoptBlocked': 'أفرغ حقل الوصف أولًا', 'places.categoryCreateError': 'فشل إنشاء الفئة', 'places.nameRequired': 'يرجى إدخال اسم', 'places.saveError': 'فشل الحفظ', diff --git a/shared/src/i18n/br/admin.ts b/shared/src/i18n/br/admin.ts index b8ba99adf..fb6aaf8ec 100644 --- a/shared/src/i18n/br/admin.ts +++ b/shared/src/i18n/br/admin.ts @@ -121,6 +121,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'Detalhes do Local', 'admin.placesDetails.subtitle': 'Busca informações detalhadas do local (horários, avaliação, site) da Google Places API. Desative para economizar cota da API.', + 'admin.placesEnrich.title': 'Enriquecimento de lugares', + 'admin.placesEnrich.subtitle': + 'Mostra imagens e uma descrição ao adicionar um lugar. Wikipédia e OpenStreetMap são sempre usados; o Google entra quando Fotos de lugares ou Detalhes de lugares estão ativos.', 'admin.bagTracking.title': 'Rastreamento de malas', 'admin.bagTracking.subtitle': 'Ativar peso e atribuição de mala para itens da lista', 'admin.collab.chat.title': 'Chat', diff --git a/shared/src/i18n/br/places.ts b/shared/src/i18n/br/places.ts index a358c74c8..845b3dfcf 100644 --- a/shared/src/i18n/br/places.ts +++ b/shared/src/i18n/br/places.ts @@ -86,6 +86,16 @@ const places: TranslationStrings = { 'Busca via OpenStreetMap (sem fotos, horários ou avaliações). Adicione uma chave Google nas configurações para detalhes completos.', 'places.osmActive': 'Busca via OpenStreetMap (sem fotos, avaliações ou horário de funcionamento). Adicione uma chave Google em Configurações para mais dados.', + 'places.details.title': 'Detalhes do lugar', + 'places.details.empty': 'Escolha um resultado para ver imagens e uma descrição.', + 'places.details.loading': 'Carregando detalhes...', + 'places.details.error': 'Não foi possível carregar os detalhes.', + 'places.details.disabled': 'O enriquecimento está desativado pelo administrador.', + 'places.details.noPhotos': 'Nenhuma imagem encontrada para este lugar.', + 'places.details.pickImage': 'Escolher uma imagem', + 'places.details.description': 'Descrição', + 'places.details.adopt': 'Usar este texto', + 'places.details.adoptBlocked': 'Esvazie primeiro o campo de descrição', 'places.categoryCreateError': 'Falha ao criar categoria', 'places.nameRequired': 'Digite um nome', 'places.saveError': 'Falha ao salvar', diff --git a/shared/src/i18n/ca/admin.ts b/shared/src/i18n/ca/admin.ts index feed4c42a..a1b747746 100644 --- a/shared/src/i18n/ca/admin.ts +++ b/shared/src/i18n/ca/admin.ts @@ -125,6 +125,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'Detalls del Lloc', 'admin.placesDetails.subtitle': "Obtén informació detallada del lloc (horaris, valoració, web) de la Google Places API. Desactiva per estalviar quota d'API.", + 'admin.placesEnrich.title': 'Enriquiment de llocs', + 'admin.placesEnrich.subtitle': + "Mostra imatges i una descripció en afegir un lloc. Wikipedia i OpenStreetMap s'usen sempre; Google s'hi afegeix si Fotos de llocs o Detalls de llocs estan actius.", 'admin.bagTracking.title': "Seguiment d'equipatge", 'admin.bagTracking.subtitle': "Activar pes i assignació d'equipatge per a articles de la llista", 'admin.collab.chat.title': 'Xat', diff --git a/shared/src/i18n/ca/places.ts b/shared/src/i18n/ca/places.ts index a0b562025..6d2661216 100644 --- a/shared/src/i18n/ca/places.ts +++ b/shared/src/i18n/ca/places.ts @@ -84,6 +84,16 @@ const places: TranslationStrings = { "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.osmActive': 'Cerca mitjançant OpenStreetMap (sense fotos, valoracions ni horaris). Afegeix una clau API de Google a Configuració per obtenir dades ampliades.', + 'places.details.title': 'Detalls del lloc', + 'places.details.empty': 'Tria un resultat per veure imatges i una descripció.', + 'places.details.loading': "S'estan carregant els detalls...", + 'places.details.error': "No s'han pogut carregar els detalls.", + 'places.details.disabled': "L'enriquiment està desactivat per l'administrador.", + 'places.details.noPhotos': "No s'han trobat imatges per a aquest lloc.", + 'places.details.pickImage': 'Tria una imatge', + 'places.details.description': 'Descripció', + 'places.details.adopt': 'Utilitza aquest text', + 'places.details.adoptBlocked': 'Buida primer el camp de descripció', 'places.categoryCreateError': "No s'ha pogut crear la categoria", 'places.nameRequired': 'Introdueix un nom', 'places.saveError': "No s'ha pogut desar", diff --git a/shared/src/i18n/cs/admin.ts b/shared/src/i18n/cs/admin.ts index 701dcd50b..0750fbb71 100644 --- a/shared/src/i18n/cs/admin.ts +++ b/shared/src/i18n/cs/admin.ts @@ -122,6 +122,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'Podrobnosti o místě', 'admin.placesDetails.subtitle': 'Načítání podrobných informací o místě (hodiny, hodnocení, web) z Google Places API. Zakázáním ušetříte kvótu API.', + 'admin.placesEnrich.title': 'Obohacení míst', + 'admin.placesEnrich.subtitle': + 'Zobrazí obrázky a popis při přidávání místa. Wikipedie a OpenStreetMap se používají vždy; Google se přidá, když jsou zapnuté Fotky míst nebo Detaily míst.', 'admin.bagTracking.title': 'Sledování zavazadel', 'admin.bagTracking.subtitle': 'Povolit váhu a přiřazení k zavazadlům u položek balení', 'admin.collab.chat.title': 'Chat', diff --git a/shared/src/i18n/cs/places.ts b/shared/src/i18n/cs/places.ts index 0c2da4527..a07fe24df 100644 --- a/shared/src/i18n/cs/places.ts +++ b/shared/src/i18n/cs/places.ts @@ -85,6 +85,16 @@ const places: TranslationStrings = { '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': 'Hledání přes OpenStreetMap.', + 'places.details.title': 'Podrobnosti místa', + 'places.details.empty': 'Vyber výsledek hledání a zobrazí se obrázky a popis.', + 'places.details.loading': 'Načítání podrobností...', + 'places.details.error': 'Podrobnosti se nepodařilo načíst.', + 'places.details.disabled': 'Obohacení je vypnuté správcem.', + 'places.details.noPhotos': 'Pro toto místo nebyly nalezeny žádné obrázky.', + 'places.details.pickImage': 'Vybrat obrázek', + 'places.details.description': 'Popis', + 'places.details.adopt': 'Použít tento text', + 'places.details.adoptBlocked': 'Nejdřív vyprázdni pole popisu', 'places.categoryCreateError': 'Nepodařilo se vytvořit kategorii', 'places.nameRequired': 'Prosím zadejte název', 'places.saveError': 'Uložení se nezdařilo', diff --git a/shared/src/i18n/de/admin.ts b/shared/src/i18n/de/admin.ts index 770328629..474ae4435 100644 --- a/shared/src/i18n/de/admin.ts +++ b/shared/src/i18n/de/admin.ts @@ -124,6 +124,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'Ortsdetails', 'admin.placesDetails.subtitle': 'Detaillierte Ortsinformationen (Öffnungszeiten, Bewertung, Website) von der Google Places API laden. Deaktivieren, um API-Kontingent zu sparen.', + 'admin.placesEnrich.title': 'Ortsanreicherung', + 'admin.placesEnrich.subtitle': + 'Bilder und eine Beschreibung beim Hinzufügen eines Ortes anzeigen. Wikipedia und OpenStreetMap werden immer genutzt; Google kommt zusätzlich dazu, wenn Ortsfotos oder Ortsdetails aktiv sind.', 'admin.bagTracking.title': 'Gepäck-Tracking', 'admin.bagTracking.subtitle': 'Gewicht und Gepäckstück-Zuordnung für Packlisteneinträge aktivieren', 'admin.collab.chat.title': 'Chat', diff --git a/shared/src/i18n/de/places.ts b/shared/src/i18n/de/places.ts index 45a669029..99831fbfe 100644 --- a/shared/src/i18n/de/places.ts +++ b/shared/src/i18n/de/places.ts @@ -86,6 +86,16 @@ const places: TranslationStrings = { 'OpenStreetMap-Suche aktiv (ohne Bilder, Öffnungszeiten, Bewertungen). Für erweiterte Daten Google API Key in den Einstellungen hinterlegen.', 'places.osmActive': 'Suche via OpenStreetMap (ohne Bilder, Bewertungen & Öffnungszeiten). Google API Key in den Einstellungen hinterlegen für erweiterte Daten.', + 'places.details.title': 'Ortsdetails', + 'places.details.empty': 'Wähle ein Suchergebnis, um Bilder und eine Beschreibung zu sehen.', + 'places.details.loading': 'Details werden geladen...', + 'places.details.error': 'Details konnten nicht geladen werden.', + 'places.details.disabled': 'Die Anreicherung ist vom Administrator deaktiviert.', + 'places.details.noPhotos': 'Keine Bilder für diesen Ort gefunden.', + 'places.details.pickImage': 'Bild auswählen', + 'places.details.description': 'Beschreibung', + 'places.details.adopt': 'Text übernehmen', + 'places.details.adoptBlocked': 'Erst das Beschreibungsfeld leeren', 'places.categoryCreateError': 'Fehler beim Erstellen der Kategorie', 'places.nameRequired': 'Bitte einen Namen eingeben', 'places.saveError': 'Fehler beim Speichern', diff --git a/shared/src/i18n/en/admin.ts b/shared/src/i18n/en/admin.ts index 450c9cf1b..65630506c 100644 --- a/shared/src/i18n/en/admin.ts +++ b/shared/src/i18n/en/admin.ts @@ -163,6 +163,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'Place Details', 'admin.placesDetails.subtitle': 'Fetch detailed place information (hours, rating, website) from the Google Places API. Disable to save API quota.', + 'admin.placesEnrich.title': 'Place Enrichment', + 'admin.placesEnrich.subtitle': + 'Show pictures and a description while adding a place. Wikipedia and OpenStreetMap are always used; Google is added on top when Place Photos or Place Details are on.', 'admin.bagTracking.title': 'Bag Tracking', 'admin.bagTracking.subtitle': 'Enable weight and bag assignment for packing items', 'admin.collab.chat.title': 'Chat', diff --git a/shared/src/i18n/en/places.ts b/shared/src/i18n/en/places.ts index b9824a2bb..3b5333762 100644 --- a/shared/src/i18n/en/places.ts +++ b/shared/src/i18n/en/places.ts @@ -86,6 +86,16 @@ const places: TranslationStrings = { 'Using OpenStreetMap search (no photos, opening hours, or ratings). Add a Google API key in settings for full details.', 'places.osmActive': 'Search via OpenStreetMap (no photos, ratings or opening hours). Add a Google API key in Settings for enhanced data.', + 'places.details.title': 'Place details', + 'places.details.empty': 'Pick a search result to see pictures and a description.', + 'places.details.loading': 'Loading details...', + 'places.details.error': 'Details could not be loaded.', + 'places.details.disabled': 'Enrichment is switched off by the administrator.', + 'places.details.noPhotos': 'No pictures found for this place.', + 'places.details.pickImage': 'Pick a picture', + 'places.details.description': 'Description', + 'places.details.adopt': 'Use this text', + 'places.details.adoptBlocked': 'Clear the description field first', 'places.categoryCreateError': 'Failed to create category', 'places.nameRequired': 'Please enter a name', 'places.saveError': 'Failed to save', diff --git a/shared/src/i18n/es/admin.ts b/shared/src/i18n/es/admin.ts index 2fcbe7007..36763441a 100644 --- a/shared/src/i18n/es/admin.ts +++ b/shared/src/i18n/es/admin.ts @@ -123,6 +123,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'Detalles del Lugar', 'admin.placesDetails.subtitle': 'Obtiene información detallada del lugar (horarios, valoración, web) de la Google Places API. Desactiva para ahorrar cuota de API.', + 'admin.placesEnrich.title': 'Enriquecimiento de lugares', + 'admin.placesEnrich.subtitle': + 'Muestra imágenes y una descripción al añadir un lugar. Wikipedia y OpenStreetMap se usan siempre; Google se añade cuando Fotos de lugares o Detalles de lugares están activados.', 'admin.bagTracking.title': 'Seguimiento de equipaje', 'admin.bagTracking.subtitle': 'Activar peso y asignación de equipaje para artículos de la lista', 'admin.collab.chat.title': 'Chat', diff --git a/shared/src/i18n/es/places.ts b/shared/src/i18n/es/places.ts index eab4386e1..5964f6698 100644 --- a/shared/src/i18n/es/places.ts +++ b/shared/src/i18n/es/places.ts @@ -86,6 +86,16 @@ const places: TranslationStrings = { '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': 'Búsqueda mediante OpenStreetMap (sin fotos, valoraciones ni horarios). Añade una clave API de Google en Ajustes para datos ampliados.', + 'places.details.title': 'Detalles del lugar', + 'places.details.empty': 'Elige un resultado para ver imágenes y una descripción.', + 'places.details.loading': 'Cargando detalles...', + 'places.details.error': 'No se pudieron cargar los detalles.', + 'places.details.disabled': 'El administrador ha desactivado el enriquecimiento.', + 'places.details.noPhotos': 'No se encontraron imágenes para este lugar.', + 'places.details.pickImage': 'Elegir una imagen', + 'places.details.description': 'Descripción', + 'places.details.adopt': 'Usar este texto', + 'places.details.adoptBlocked': 'Vacía primero el campo de descripción', 'places.categoryCreateError': 'No se pudo crear la categoría', 'places.nameRequired': 'Introduce un nombre', 'places.saveError': 'No se pudo guardar', diff --git a/shared/src/i18n/fr/admin.ts b/shared/src/i18n/fr/admin.ts index ea861349e..fab1c2e5e 100644 --- a/shared/src/i18n/fr/admin.ts +++ b/shared/src/i18n/fr/admin.ts @@ -125,6 +125,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'Détails du lieu', 'admin.placesDetails.subtitle': "Récupère les informations détaillées du lieu (horaires, note, site web) depuis l'API Google Places. Désactivez pour économiser le quota API.", + 'admin.placesEnrich.title': 'Enrichissement des lieux', + 'admin.placesEnrich.subtitle': + "Affiche des images et une description lors de l'ajout d'un lieu. Wikipédia et OpenStreetMap sont toujours utilisés ; Google s'y ajoute si Photos de lieux ou Détails de lieux sont activés.", 'admin.bagTracking.title': 'Suivi des bagages', 'admin.bagTracking.subtitle': "Activer le poids et l'attribution de bagages pour les articles", 'admin.collab.chat.title': 'Chat', diff --git a/shared/src/i18n/fr/places.ts b/shared/src/i18n/fr/places.ts index af14a39fa..43626ad59 100644 --- a/shared/src/i18n/fr/places.ts +++ b/shared/src/i18n/fr/places.ts @@ -86,6 +86,16 @@ const places: TranslationStrings = { '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': 'Recherche via OpenStreetMap (pas de photos, notes ni horaires). Ajoutez une clé API Google dans les paramètres pour des données enrichies.', + 'places.details.title': 'Détails du lieu', + 'places.details.empty': 'Choisis un résultat pour voir des images et une description.', + 'places.details.loading': 'Chargement des détails...', + 'places.details.error': 'Impossible de charger les détails.', + 'places.details.disabled': "L'enrichissement est désactivé par l'administrateur.", + 'places.details.noPhotos': 'Aucune image trouvée pour ce lieu.', + 'places.details.pickImage': 'Choisir une image', + 'places.details.description': 'Description', + 'places.details.adopt': 'Utiliser ce texte', + 'places.details.adoptBlocked': "Videz d'abord le champ description", 'places.categoryCreateError': 'Impossible de créer la catégorie', 'places.nameRequired': 'Veuillez saisir un nom', 'places.saveError': "Échec de l'enregistrement", diff --git a/shared/src/i18n/gr/admin.ts b/shared/src/i18n/gr/admin.ts index ed00761b4..b71f400ae 100644 --- a/shared/src/i18n/gr/admin.ts +++ b/shared/src/i18n/gr/admin.ts @@ -170,6 +170,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'Λεπτομέρειες Τόπου', 'admin.placesDetails.subtitle': 'Λήψη λεπτομερών πληροφοριών τόπου (ώρες, βαθμολογία, ιστοσελίδα) από το Google Places API. Απενεργοποιήστε για εξοικονόμηση ποσοστώσης API.', + 'admin.placesEnrich.title': 'Εμπλουτισμός τοποθεσιών', + 'admin.placesEnrich.subtitle': + 'Εμφανίζει εικόνες και περιγραφή κατά την προσθήκη τοποθεσίας. Η Wikipedia και το OpenStreetMap χρησιμοποιούνται πάντα· η Google προστίθεται όταν είναι ενεργές οι Φωτογραφίες ή οι Λεπτομέρειες τοποθεσίας.', 'admin.bagTracking.title': 'Παρακολούθηση Αποσκευών', 'admin.bagTracking.subtitle': 'Ενεργοποίηση βάρους και ανάθεσης τσάντας για αντικείμενα πακεταρίσματος', 'admin.collab.chat.title': 'Συνομιλία', diff --git a/shared/src/i18n/gr/places.ts b/shared/src/i18n/gr/places.ts index 3bd47754f..8f15e9df9 100644 --- a/shared/src/i18n/gr/places.ts +++ b/shared/src/i18n/gr/places.ts @@ -86,6 +86,16 @@ const places: TranslationStrings = { 'Χρήση αναζήτησης OpenStreetMap (χωρίς φωτογραφίες, ώρες λειτουργίας ή αξιολογήσεις). Προσθέστε ένα κλειδί Google API στις ρυθμίσεις για πλήρεις λεπτομέρειες.', 'places.osmActive': 'Αναζήτηση μέσω OpenStreetMap (χωρίς φωτογραφίες, αξιολογήσεις ή ώρες λειτουργίας). Προσθέστε ένα κλειδί Google API στις Ρυθμίσεις για εμπλουτισμένα δεδομένα.', + 'places.details.title': 'Λεπτομέρειες τοποθεσίας', + 'places.details.empty': 'Διάλεξε ένα αποτέλεσμα για να δεις εικόνες και περιγραφή.', + 'places.details.loading': 'Φόρτωση λεπτομερειών...', + 'places.details.error': 'Δεν ήταν δυνατή η φόρτωση των λεπτομερειών.', + 'places.details.disabled': 'Ο εμπλουτισμός είναι απενεργοποιημένος από τον διαχειριστή.', + 'places.details.noPhotos': 'Δεν βρέθηκαν εικόνες για αυτήν την τοποθεσία.', + 'places.details.pickImage': 'Διάλεξε εικόνα', + 'places.details.description': 'Περιγραφή', + 'places.details.adopt': 'Χρήση αυτού του κειμένου', + 'places.details.adoptBlocked': 'Άδειασε πρώτα το πεδίο περιγραφής', 'places.categoryCreateError': 'Αποτυχία δημιουργίας κατηγορίας', 'places.nameRequired': 'Παρακαλώ εισαγάγετε ένα όνομα', 'places.saveError': 'Αποτυχία αποθήκευσης', diff --git a/shared/src/i18n/hu/admin.ts b/shared/src/i18n/hu/admin.ts index 6e0ecf0d9..2dcc0203c 100644 --- a/shared/src/i18n/hu/admin.ts +++ b/shared/src/i18n/hu/admin.ts @@ -124,6 +124,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'Hely részletei', 'admin.placesDetails.subtitle': 'Részletes helyinformációk lekérése (nyitvatartás, értékelés, weboldal) a Google Places API-ból. Tiltsa le az API-kvóta megtakarításához.', + 'admin.placesEnrich.title': 'Helyek gazdagítása', + 'admin.placesEnrich.subtitle': + 'Képeket és leírást mutat hely hozzáadásakor. A Wikipédia és az OpenStreetMap mindig használatban van; a Google akkor jön hozzá, ha a Helyfotók vagy a Helyadatok be vannak kapcsolva.', 'admin.bagTracking.title': 'Poggyászkövetés', 'admin.bagTracking.subtitle': 'Súly- és táskahozzárendelés engedélyezése csomagolási tételeknél', 'admin.collab.chat.title': 'Chat', diff --git a/shared/src/i18n/hu/places.ts b/shared/src/i18n/hu/places.ts index 22aa26916..91412a144 100644 --- a/shared/src/i18n/hu/places.ts +++ b/shared/src/i18n/hu/places.ts @@ -86,6 +86,16 @@ const places: TranslationStrings = { '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': 'Keresés OpenStreetMap-en keresztül (képek, értékelések és nyitvatartás nélkül). Bővített adatokhoz add meg a Google API kulcsot a beállításokban.', + 'places.details.title': 'Hely részletei', + 'places.details.empty': 'Válassz egy találatot a képekhez és a leíráshoz.', + 'places.details.loading': 'Részletek betöltése...', + 'places.details.error': 'A részleteket nem sikerült betölteni.', + 'places.details.disabled': 'A gazdagítást a rendszergazda kikapcsolta.', + 'places.details.noPhotos': 'Nem található kép ehhez a helyhez.', + 'places.details.pickImage': 'Válassz képet', + 'places.details.description': 'Leírás', + 'places.details.adopt': 'Szöveg átvétele', + 'places.details.adoptBlocked': 'Előbb ürítsd ki a leírás mezőt', 'places.categoryCreateError': 'Nem sikerült létrehozni a kategóriát', 'places.nameRequired': 'Kérjük, adj meg egy nevet', 'places.saveError': 'Nem sikerült menteni', diff --git a/shared/src/i18n/id/admin.ts b/shared/src/i18n/id/admin.ts index ca4ff47eb..20e263142 100644 --- a/shared/src/i18n/id/admin.ts +++ b/shared/src/i18n/id/admin.ts @@ -166,6 +166,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'Detail Tempat', 'admin.placesDetails.subtitle': 'Mengambil informasi detail tempat (jam, penilaian, situs web) dari Google Places API. Nonaktifkan untuk menghemat kuota API.', + 'admin.placesEnrich.title': 'Pengayaan tempat', + 'admin.placesEnrich.subtitle': + 'Menampilkan gambar dan deskripsi saat menambahkan tempat. Wikipedia dan OpenStreetMap selalu digunakan; Google ditambahkan bila Foto Tempat atau Detail Tempat aktif.', 'admin.bagTracking.title': 'Pelacak Tas', 'admin.bagTracking.subtitle': 'Aktifkan berat dan penugasan tas untuk item packing', 'admin.collab.chat.title': 'Chat', diff --git a/shared/src/i18n/id/places.ts b/shared/src/i18n/id/places.ts index 54b3e6e05..33b4dde2d 100644 --- a/shared/src/i18n/id/places.ts +++ b/shared/src/i18n/id/places.ts @@ -85,6 +85,16 @@ const places: TranslationStrings = { 'Menggunakan pencarian OpenStreetMap (tanpa foto, jam buka, atau penilaian). Tambahkan Google API key di pengaturan untuk detail lengkap.', 'places.osmActive': 'Pencarian via OpenStreetMap (tanpa foto, penilaian, atau jam buka). Tambahkan Google API key di Pengaturan untuk data yang lebih lengkap.', + 'places.details.title': 'Detail tempat', + 'places.details.empty': 'Pilih hasil pencarian untuk melihat gambar dan deskripsi.', + 'places.details.loading': 'Memuat detail...', + 'places.details.error': 'Detail tidak dapat dimuat.', + 'places.details.disabled': 'Pengayaan dimatikan oleh administrator.', + 'places.details.noPhotos': 'Tidak ada gambar untuk tempat ini.', + 'places.details.pickImage': 'Pilih gambar', + 'places.details.description': 'Deskripsi', + 'places.details.adopt': 'Gunakan teks ini', + 'places.details.adoptBlocked': 'Kosongkan dulu kolom deskripsi', 'places.categoryCreateError': 'Gagal membuat kategori', 'places.nameRequired': 'Harap masukkan nama', 'places.saveError': 'Gagal menyimpan', diff --git a/shared/src/i18n/it/admin.ts b/shared/src/i18n/it/admin.ts index 723cce44a..da2935508 100644 --- a/shared/src/i18n/it/admin.ts +++ b/shared/src/i18n/it/admin.ts @@ -123,6 +123,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'Dettagli del luogo', 'admin.placesDetails.subtitle': "Recupera informazioni dettagliate sul luogo (orari, valutazione, sito web) dall'API Google Places. Disabilita per risparmiare la quota API.", + 'admin.placesEnrich.title': 'Arricchimento luoghi', + 'admin.placesEnrich.subtitle': + 'Mostra immagini e una descrizione mentre aggiungi un luogo. Wikipedia e OpenStreetMap sono sempre usati; Google si aggiunge se Foto luoghi o Dettagli luoghi sono attivi.', 'admin.bagTracking.title': 'Tracciamento valigia', 'admin.bagTracking.subtitle': "Abilita il peso e l'assegnazione della valigia per gli elementi della lista valigia", 'admin.collab.chat.title': 'Chat', diff --git a/shared/src/i18n/it/places.ts b/shared/src/i18n/it/places.ts index 8922e4b1f..db7d3c6aa 100644 --- a/shared/src/i18n/it/places.ts +++ b/shared/src/i18n/it/places.ts @@ -86,6 +86,16 @@ const places: TranslationStrings = { 'Uso della ricerca OpenStreetMap (senza foto, orari di apertura o valutazioni). Aggiungi una chiave API Google nelle impostazioni per i dettagli completi.', 'places.osmActive': 'Ricerca tramite OpenStreetMap (senza foto, valutazioni o orari di apertura). Aggiungi una chiave API Google nelle Impostazioni per dati avanzati.', + 'places.details.title': 'Dettagli del luogo', + 'places.details.empty': 'Scegli un risultato per vedere immagini e una descrizione.', + 'places.details.loading': 'Caricamento dettagli...', + 'places.details.error': 'Impossibile caricare i dettagli.', + 'places.details.disabled': "L'arricchimento è disattivato dall'amministratore.", + 'places.details.noPhotos': 'Nessuna immagine trovata per questo luogo.', + 'places.details.pickImage': "Scegli un'immagine", + 'places.details.description': 'Descrizione', + 'places.details.adopt': 'Usa questo testo', + 'places.details.adoptBlocked': 'Svuota prima il campo descrizione', 'places.categoryCreateError': 'Impossibile creare la categoria', 'places.nameRequired': 'Inserisci un nome', 'places.saveError': 'Impossibile salvare', diff --git a/shared/src/i18n/ja/admin.ts b/shared/src/i18n/ja/admin.ts index d1577d8f2..7ed8e74bf 100644 --- a/shared/src/i18n/ja/admin.ts +++ b/shared/src/i18n/ja/admin.ts @@ -155,6 +155,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': '場所の詳細', 'admin.placesDetails.subtitle': 'Google Places APIから営業時間、評価、ウェブサイトなどの詳細情報を取得します。APIクォータ節約のため無効にできます。', + 'admin.placesEnrich.title': '場所の情報補完', + 'admin.placesEnrich.subtitle': + '場所を追加するときに写真と説明を表示します。WikipediaとOpenStreetMapは常に使用され、「場所の写真」または「場所の詳細」が有効な場合はGoogleも追加されます。', 'admin.bagTracking.title': 'バッグ管理', 'admin.bagTracking.subtitle': '持ち物の重量とバッグ割り当てを有効化', 'admin.collab.chat.title': 'チャット', diff --git a/shared/src/i18n/ja/places.ts b/shared/src/i18n/ja/places.ts index b98476604..93050de36 100644 --- a/shared/src/i18n/ja/places.ts +++ b/shared/src/i18n/ja/places.ts @@ -85,6 +85,16 @@ const places: TranslationStrings = { 'places.osmHint': 'OpenStreetMapで検索しています(写真・営業時間・評価なし)。設定でGoogle APIキーを追加すると詳細が表示されます。', 'places.osmActive': 'OpenStreetMapで検索中(写真・評価・営業時間なし)。設定でGoogle APIキーを追加してください。', + 'places.details.title': '場所の詳細', + 'places.details.empty': '検索結果を選ぶと写真と説明が表示されます。', + 'places.details.loading': '詳細を読み込み中...', + 'places.details.error': '詳細を読み込めませんでした。', + 'places.details.disabled': '情報補完は管理者によって無効になっています。', + 'places.details.noPhotos': 'この場所の写真は見つかりませんでした。', + 'places.details.pickImage': '写真を選ぶ', + 'places.details.description': '説明', + 'places.details.adopt': 'このテキストを使う', + 'places.details.adoptBlocked': '先に説明欄を空にしてください', 'places.categoryCreateError': 'カテゴリの作成に失敗しました', 'places.nameRequired': '名前を入力してください', 'places.saveError': '保存に失敗しました', diff --git a/shared/src/i18n/ko/admin.ts b/shared/src/i18n/ko/admin.ts index 64c02fa1a..18f91d3e0 100644 --- a/shared/src/i18n/ko/admin.ts +++ b/shared/src/i18n/ko/admin.ts @@ -161,6 +161,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': '장소 상세 정보', 'admin.placesDetails.subtitle': 'Google Places API에서 상세 장소 정보 (영업 시간, 평점, 웹사이트)를 가져옵니다. API 할당량 절약을 위해 비활성화할 수 있습니다.', + 'admin.placesEnrich.title': '장소 정보 보강', + 'admin.placesEnrich.subtitle': + '장소를 추가할 때 사진과 설명을 표시합니다. Wikipedia와 OpenStreetMap은 항상 사용되며, 장소 사진 또는 장소 세부정보가 켜져 있으면 Google도 함께 사용됩니다.', 'admin.bagTracking.title': '가방 추적', 'admin.bagTracking.subtitle': '짐 항목에 무게 및 가방 배정을 활성화합니다', 'admin.collab.chat.title': '채팅', diff --git a/shared/src/i18n/ko/places.ts b/shared/src/i18n/ko/places.ts index 1c27967fa..43cb732b6 100644 --- a/shared/src/i18n/ko/places.ts +++ b/shared/src/i18n/ko/places.ts @@ -85,6 +85,16 @@ const places: TranslationStrings = { 'OpenStreetMap 검색 사용 중 (사진, 영업 시간, 평점 없음). 전체 정보를 위해 설정에서 Google API 키를 추가하세요.', 'places.osmActive': 'OpenStreetMap으로 검색 중 (사진, 평점, 영업 시간 없음). 향상된 데이터를 위해 설정에서 Google API 키를 추가하세요.', + 'places.details.title': '장소 세부정보', + 'places.details.empty': '검색 결과를 선택하면 사진과 설명이 표시됩니다.', + 'places.details.loading': '세부정보를 불러오는 중...', + 'places.details.error': '세부정보를 불러오지 못했습니다.', + 'places.details.disabled': '관리자가 정보 보강을 껐습니다.', + 'places.details.noPhotos': '이 장소의 사진을 찾지 못했습니다.', + 'places.details.pickImage': '사진 선택', + 'places.details.description': '설명', + 'places.details.adopt': '이 텍스트 사용', + 'places.details.adoptBlocked': '설명란을 먼저 비워 주세요', 'places.categoryCreateError': '카테고리 생성 실패', 'places.nameRequired': '이름을 입력하세요', 'places.saveError': '저장 실패', diff --git a/shared/src/i18n/nl/admin.ts b/shared/src/i18n/nl/admin.ts index 58c14086d..3382ca686 100644 --- a/shared/src/i18n/nl/admin.ts +++ b/shared/src/i18n/nl/admin.ts @@ -124,6 +124,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'Plaatsdetails', 'admin.placesDetails.subtitle': 'Haalt gedetailleerde plaatsinformatie (openingstijden, beoordeling, website) op via de Google Places API. Schakel uit om API-quota te besparen.', + 'admin.placesEnrich.title': 'Plaatsverrijking', + 'admin.placesEnrich.subtitle': + "Toont afbeeldingen en een beschrijving bij het toevoegen van een plaats. Wikipedia en OpenStreetMap worden altijd gebruikt; Google komt erbij als Plaatsfoto's of Plaatsdetails aanstaan.", 'admin.bagTracking.title': 'Bagagetracking', 'admin.bagTracking.subtitle': 'Gewicht en bagagetoewijzing inschakelen voor paklijstitems', 'admin.collab.chat.title': 'Chat', diff --git a/shared/src/i18n/nl/places.ts b/shared/src/i18n/nl/places.ts index 907050f7b..e072aa20f 100644 --- a/shared/src/i18n/nl/places.ts +++ b/shared/src/i18n/nl/places.ts @@ -86,6 +86,16 @@ const places: TranslationStrings = { "Zoeken via OpenStreetMap (geen foto's, openingstijden of beoordelingen). Voeg een Google API-sleutel toe in instellingen voor volledige details.", 'places.osmActive': "Zoeken via OpenStreetMap (geen foto's, beoordelingen of openingstijden). Voeg een Google API-sleutel toe in Instellingen voor uitgebreide gegevens.", + 'places.details.title': 'Plaatsdetails', + 'places.details.empty': 'Kies een zoekresultaat om afbeeldingen en een beschrijving te zien.', + 'places.details.loading': 'Details laden...', + 'places.details.error': 'Details konden niet worden geladen.', + 'places.details.disabled': 'Verrijking is uitgeschakeld door de beheerder.', + 'places.details.noPhotos': 'Geen afbeeldingen gevonden voor deze plaats.', + 'places.details.pickImage': 'Kies een afbeelding', + 'places.details.description': 'Beschrijving', + 'places.details.adopt': 'Deze tekst gebruiken', + 'places.details.adoptBlocked': 'Maak eerst het beschrijvingsveld leeg', 'places.categoryCreateError': 'Categorie aanmaken mislukt', 'places.nameRequired': 'Voer een naam in', 'places.saveError': 'Opslaan mislukt', diff --git a/shared/src/i18n/pl/admin.ts b/shared/src/i18n/pl/admin.ts index 14e07923e..dfab9d417 100644 --- a/shared/src/i18n/pl/admin.ts +++ b/shared/src/i18n/pl/admin.ts @@ -117,6 +117,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'Szczegóły miejsca', 'admin.placesDetails.subtitle': 'Pobiera szczegółowe informacje o miejscu (godziny, ocena, strona) z Google Places API. Wyłącz, aby zaoszczędzić limit API.', + 'admin.placesEnrich.title': 'Wzbogacanie miejsc', + 'admin.placesEnrich.subtitle': + 'Pokazuje zdjęcia i opis podczas dodawania miejsca. Wikipedia i OpenStreetMap są używane zawsze; Google dochodzi, gdy włączone są Zdjęcia miejsc lub Szczegóły miejsc.', 'admin.bagTracking.title': 'Kontrola bagażu', 'admin.bagTracking.subtitle': 'Włącz wagę i przypisywanie do toreb dla przedmiotów do pakowania', 'admin.collab.chat.title': 'Czat', diff --git a/shared/src/i18n/pl/places.ts b/shared/src/i18n/pl/places.ts index f2eb89bc0..8b71fb0b6 100644 --- a/shared/src/i18n/pl/places.ts +++ b/shared/src/i18n/pl/places.ts @@ -77,6 +77,16 @@ const places: TranslationStrings = { 'Korzystając z OpenStreetMap (brak zdjęć, godzin otwarcia czy ocen). Dodaj klucz API Google w ustawieniach aby uzyskać pełne dane.', 'places.osmActive': 'Szukaj przez OpenStreetMap (brak zdjęć, ocen czy godzin otwarcia). Dodaj klucz API Google w ustawieniach aby uzyskać pełne dane.', + 'places.details.title': 'Szczegóły miejsca', + 'places.details.empty': 'Wybierz wynik wyszukiwania, aby zobaczyć zdjęcia i opis.', + 'places.details.loading': 'Wczytywanie szczegółów...', + 'places.details.error': 'Nie udało się wczytać szczegółów.', + 'places.details.disabled': 'Wzbogacanie jest wyłączone przez administratora.', + 'places.details.noPhotos': 'Nie znaleziono zdjęć dla tego miejsca.', + 'places.details.pickImage': 'Wybierz zdjęcie', + 'places.details.description': 'Opis', + 'places.details.adopt': 'Użyj tego tekstu', + 'places.details.adoptBlocked': 'Najpierw wyczyść pole opisu', 'places.categoryCreateError': 'Nie udało się utworzyć kategorii', 'places.nameRequired': 'Proszę podać nazwę', 'places.saveError': 'Nie udało się zapisać', diff --git a/shared/src/i18n/ru/admin.ts b/shared/src/i18n/ru/admin.ts index 150220998..682ecf79b 100644 --- a/shared/src/i18n/ru/admin.ts +++ b/shared/src/i18n/ru/admin.ts @@ -124,6 +124,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'Сведения о месте', 'admin.placesDetails.subtitle': 'Загрузка подробной информации о месте (часы работы, рейтинг, веб-сайт) из Google Places API. Отключите для экономии квоты API.', + 'admin.placesEnrich.title': 'Обогащение мест', + 'admin.placesEnrich.subtitle': + 'Показывает изображения и описание при добавлении места. Wikipedia и OpenStreetMap используются всегда; Google добавляется, если включены «Фото мест» или «Детали мест».', 'admin.bagTracking.title': 'Отслеживание багажа', 'admin.bagTracking.subtitle': 'Включить вес и привязку к багажу для вещей', 'admin.collab.chat.title': 'Чат', diff --git a/shared/src/i18n/ru/places.ts b/shared/src/i18n/ru/places.ts index 67bae622c..4d83f7aee 100644 --- a/shared/src/i18n/ru/places.ts +++ b/shared/src/i18n/ru/places.ts @@ -86,6 +86,16 @@ const places: TranslationStrings = { 'Поиск через OpenStreetMap (без фото, часов работы и рейтингов). Добавьте API-ключ Google в настройках для полной информации.', 'places.osmActive': 'Поиск через OpenStreetMap (без фото, рейтингов и часов работы). Добавьте API-ключ Google в настройках для расширенных данных.', + 'places.details.title': 'Сведения о месте', + 'places.details.empty': 'Выберите результат поиска, чтобы увидеть изображения и описание.', + 'places.details.loading': 'Загрузка сведений...', + 'places.details.error': 'Не удалось загрузить сведения.', + 'places.details.disabled': 'Обогащение отключено администратором.', + 'places.details.noPhotos': 'Для этого места изображения не найдены.', + 'places.details.pickImage': 'Выбрать изображение', + 'places.details.description': 'Описание', + 'places.details.adopt': 'Использовать этот текст', + 'places.details.adoptBlocked': 'Сначала очистите поле описания', 'places.categoryCreateError': 'Не удалось создать категорию', 'places.nameRequired': 'Введите название', 'places.saveError': 'Ошибка сохранения', diff --git a/shared/src/i18n/sv/admin.ts b/shared/src/i18n/sv/admin.ts index 56db0a7a9..f70e3709f 100644 --- a/shared/src/i18n/sv/admin.ts +++ b/shared/src/i18n/sv/admin.ts @@ -167,6 +167,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'Platsinformation', 'admin.placesDetails.subtitle': 'Hämta detaljerad information om platsen (öppettider, betyg, webbplats) från Google Places API. Inaktivera funktionen för att spara på API-kvoten.', + 'admin.placesEnrich.title': 'Platsberikning', + 'admin.placesEnrich.subtitle': + 'Visar bilder och en beskrivning när du lägger till en plats. Wikipedia och OpenStreetMap används alltid; Google tillkommer när Platsfoton eller Platsdetaljer är på.', 'admin.bagTracking.title': 'Spårning av väskor', 'admin.bagTracking.subtitle': 'Aktivera vikt- och väskfördelning för packningsartiklar', 'admin.collab.chat.title': 'Chatt', diff --git a/shared/src/i18n/sv/places.ts b/shared/src/i18n/sv/places.ts index 9f8d0418d..ec229873a 100644 --- a/shared/src/i18n/sv/places.ts +++ b/shared/src/i18n/sv/places.ts @@ -86,6 +86,16 @@ const places: TranslationStrings = { '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.osmActive': 'Sök via OpenStreetMap (inga bilder, betyg eller öppettider). Lägg till en Google API-nyckel under Inställningar för utökad information.', + 'places.details.title': 'Platsdetaljer', + 'places.details.empty': 'Välj ett sökresultat för att se bilder och en beskrivning.', + 'places.details.loading': 'Laddar detaljer...', + 'places.details.error': 'Detaljerna kunde inte laddas.', + 'places.details.disabled': 'Berikning är avstängd av administratören.', + 'places.details.noPhotos': 'Inga bilder hittades för den här platsen.', + 'places.details.pickImage': 'Välj en bild', + 'places.details.description': 'Beskrivning', + 'places.details.adopt': 'Använd den här texten', + 'places.details.adoptBlocked': 'Töm beskrivningsfältet först', 'places.categoryCreateError': 'Det gick inte att skapa kategorin', 'places.nameRequired': 'Ange ett namn', 'places.saveError': 'Det gick inte att spara', diff --git a/shared/src/i18n/tr/admin.ts b/shared/src/i18n/tr/admin.ts index 2c8e5037c..ac36b38cc 100644 --- a/shared/src/i18n/tr/admin.ts +++ b/shared/src/i18n/tr/admin.ts @@ -168,6 +168,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'Yer Detayları', 'admin.placesDetails.subtitle': "Google Rehber API'sinden ayrıntılı yer bilgilerini (saat, puan, web sitesi) alın. API kotasını kaydetmeyi devre dışı bırakın.", + 'admin.placesEnrich.title': 'Yer zenginleştirme', + 'admin.placesEnrich.subtitle': + 'Bir yer eklerken görseller ve açıklama gösterir. Wikipedia ve OpenStreetMap her zaman kullanılır; Yer Fotoğrafları veya Yer Ayrıntıları açıkken Google da eklenir.', 'admin.bagTracking.title': 'Çanta Takibi', 'admin.bagTracking.subtitle': 'Paketleme öğeleri için ağırlık ve torba atamasını etkinleştirin', 'admin.collab.chat.title': 'Sohbet', diff --git a/shared/src/i18n/tr/places.ts b/shared/src/i18n/tr/places.ts index 242ff6fa4..545aa9412 100644 --- a/shared/src/i18n/tr/places.ts +++ b/shared/src/i18n/tr/places.ts @@ -88,6 +88,16 @@ const places: TranslationStrings = { '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 aracılığıyla arama yapın (fotoğraf, derecelendirme veya çalışma saatleri yok). Gelişmiş veriler için Ayarlar'a bir Google API anahtarı ekleyin.", + 'places.details.title': 'Yer ayrıntıları', + 'places.details.empty': 'Görseller ve açıklama için bir arama sonucu seç.', + 'places.details.loading': 'Ayrıntılar yükleniyor...', + 'places.details.error': 'Ayrıntılar yüklenemedi.', + 'places.details.disabled': 'Zenginleştirme yönetici tarafından kapatıldı.', + 'places.details.noPhotos': 'Bu yer için görsel bulunamadı.', + 'places.details.pickImage': 'Bir görsel seç', + 'places.details.description': 'Açıklama', + 'places.details.adopt': 'Bu metni kullan', + 'places.details.adoptBlocked': 'Önce açıklama alanını boşalt', 'places.categoryCreateError': 'Kategori oluşturulamadı', 'places.nameRequired': 'Lütfen bir ad girin', 'places.saveError': 'Kaydedilemedi', diff --git a/shared/src/i18n/uk/admin.ts b/shared/src/i18n/uk/admin.ts index 0dc2eb6f7..e90fa696c 100644 --- a/shared/src/i18n/uk/admin.ts +++ b/shared/src/i18n/uk/admin.ts @@ -124,6 +124,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'Відомості про місце', 'admin.placesDetails.subtitle': 'Завантаження детальної інформації про місце (години роботи, рейтинг, веб-сайт) з Google Places API. Вимкніть для економії квоти API.', + 'admin.placesEnrich.title': 'Збагачення місць', + 'admin.placesEnrich.subtitle': + 'Показує зображення та опис під час додавання місця. Wikipedia та OpenStreetMap використовуються завжди; Google додається, якщо ввімкнено «Фото місць» або «Деталі місць».', 'admin.bagTracking.title': 'Відстеження багажу', 'admin.bagTracking.subtitle': 'Увімкнути вагу та прив’язку до багажу для речей', 'admin.collab.chat.title': 'Чат', diff --git a/shared/src/i18n/uk/places.ts b/shared/src/i18n/uk/places.ts index 594d91185..ac1031914 100644 --- a/shared/src/i18n/uk/places.ts +++ b/shared/src/i18n/uk/places.ts @@ -86,6 +86,16 @@ const places: TranslationStrings = { 'Пошук через OpenStreetMap (без фото, годин роботи та рейтингів). Додайте API-ключ Google у налаштуваннях для повної інформації.', 'places.osmActive': 'Пошук через OpenStreetMap (без фото, рейтингів та годин роботи). Додайте API-ключ Google у налаштуваннях для розширених даних.', + 'places.details.title': 'Відомості про місце', + 'places.details.empty': 'Виберіть результат пошуку, щоб побачити зображення та опис.', + 'places.details.loading': 'Завантаження відомостей...', + 'places.details.error': 'Не вдалося завантажити відомості.', + 'places.details.disabled': 'Збагачення вимкнено адміністратором.', + 'places.details.noPhotos': 'Для цього місця зображень не знайдено.', + 'places.details.pickImage': 'Вибрати зображення', + 'places.details.description': 'Опис', + 'places.details.adopt': 'Використати цей текст', + 'places.details.adoptBlocked': 'Спершу очистіть поле опису', 'places.categoryCreateError': 'Не вдалося створити категорію', 'places.nameRequired': 'Введіть назву', 'places.saveError': 'Помилка збереження', diff --git a/shared/src/i18n/vi/admin.ts b/shared/src/i18n/vi/admin.ts index a3bb4f887..6d5af8658 100644 --- a/shared/src/i18n/vi/admin.ts +++ b/shared/src/i18n/vi/admin.ts @@ -166,6 +166,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': 'Chi tiết địa điểm', 'admin.placesDetails.subtitle': 'Tìm nạp thông tin chi tiết về địa điểm (giờ, xếp hạng, trang web) từ Google Địa điểm API. Tắt để lưu hạn ngạch API.', + 'admin.placesEnrich.title': 'Làm giàu địa điểm', + 'admin.placesEnrich.subtitle': + 'Hiển thị hình ảnh và mô tả khi thêm địa điểm. Wikipedia và OpenStreetMap luôn được dùng; Google được thêm vào khi bật Ảnh địa điểm hoặc Chi tiết địa điểm.', 'admin.bagTracking.title': 'Theo dõi túi', 'admin.bagTracking.subtitle': 'Cho phép phân bổ trọng lượng và túi để đóng gói các mặt hàng', 'admin.collab.chat.title': 'Trò chuyện', diff --git a/shared/src/i18n/vi/places.ts b/shared/src/i18n/vi/places.ts index a5c6382f1..7bf817b28 100644 --- a/shared/src/i18n/vi/places.ts +++ b/shared/src/i18n/vi/places.ts @@ -86,6 +86,16 @@ const places: TranslationStrings = { '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.osmActive': 'Tìm kiếm qua OpenStreetMap (không có ảnh, xếp hạng hoặc giờ mở cửa). Thêm khóa Google API trong Cài đặt để có dữ liệu nâng cao.', + 'places.details.title': 'Chi tiết địa điểm', + 'places.details.empty': 'Chọn một kết quả tìm kiếm để xem hình ảnh và mô tả.', + 'places.details.loading': 'Đang tải chi tiết...', + 'places.details.error': 'Không tải được chi tiết.', + 'places.details.disabled': 'Quản trị viên đã tắt tính năng làm giàu.', + 'places.details.noPhotos': 'Không tìm thấy hình ảnh cho địa điểm này.', + 'places.details.pickImage': 'Chọn một hình ảnh', + 'places.details.description': 'Mô tả', + 'places.details.adopt': 'Dùng văn bản này', + 'places.details.adoptBlocked': 'Hãy xóa trống ô mô tả trước', 'places.categoryCreateError': 'Không tạo được danh mục', 'places.nameRequired': 'Vui lòng nhập tên', 'places.saveError': 'Không lưu được', diff --git a/shared/src/i18n/zh-TW/admin.ts b/shared/src/i18n/zh-TW/admin.ts index 7456e1c6e..f3c99e973 100644 --- a/shared/src/i18n/zh-TW/admin.ts +++ b/shared/src/i18n/zh-TW/admin.ts @@ -158,6 +158,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': '地點詳情', 'admin.placesDetails.subtitle': '從 Google Places API 獲取地點詳細資訊(營業時間、評分、網站)。停用可節省 API 配額。', + 'admin.placesEnrich.title': '地點資訊補充', + 'admin.placesEnrich.subtitle': + '新增地點時顯示圖片與描述。一律使用維基百科與 OpenStreetMap;開啟「地點照片」或「地點詳細資料」時會額外使用 Google。', 'admin.bagTracking.title': '行李追蹤', 'admin.bagTracking.subtitle': '為打包物品啟用重量和行李分配', 'admin.collab.chat.title': '聊天', diff --git a/shared/src/i18n/zh-TW/places.ts b/shared/src/i18n/zh-TW/places.ts index 15645fedb..1b755e268 100644 --- a/shared/src/i18n/zh-TW/places.ts +++ b/shared/src/i18n/zh-TW/places.ts @@ -83,6 +83,16 @@ const places: TranslationStrings = { 'places.loadingDetails': '正在載入地點詳情…', 'places.osmHint': '使用 OpenStreetMap 搜尋(無照片、營業時間或評分)。在設定中新增 Google API 金鑰以獲取完整資訊。', 'places.osmActive': '透過 OpenStreetMap 搜尋(無照片、評分或營業時間)。在設定中新增 Google API 金鑰以獲取增強資料。', + 'places.details.title': '地點詳細資料', + 'places.details.empty': '選擇一筆搜尋結果即可查看圖片與描述。', + 'places.details.loading': '正在載入詳細資料...', + 'places.details.error': '無法載入詳細資料。', + 'places.details.disabled': '管理員已關閉資訊補充。', + 'places.details.noPhotos': '找不到此地點的圖片。', + 'places.details.pickImage': '選擇圖片', + 'places.details.description': '描述', + 'places.details.adopt': '使用此文字', + 'places.details.adoptBlocked': '請先清空描述欄位', 'places.categoryCreateError': '建立分類失敗', 'places.nameRequired': '請輸入名稱', 'places.saveError': '儲存失敗', diff --git a/shared/src/i18n/zh/admin.ts b/shared/src/i18n/zh/admin.ts index 848b8644f..98e618a57 100644 --- a/shared/src/i18n/zh/admin.ts +++ b/shared/src/i18n/zh/admin.ts @@ -119,6 +119,9 @@ const admin: TranslationStrings = { 'admin.placesDetails.title': '地点详情', 'admin.placesDetails.subtitle': '从 Google Places API 获取地点详细信息(营业时间、评分、网站)。禁用可节省 API 配额。', + 'admin.placesEnrich.title': '地点信息补充', + 'admin.placesEnrich.subtitle': + '添加地点时显示图片和描述。始终使用维基百科和 OpenStreetMap;开启「地点照片」或「地点详情」时会额外使用 Google。', 'admin.bagTracking.title': '行李追踪', 'admin.bagTracking.subtitle': '为打包物品启用重量和行李分配', 'admin.collab.chat.title': '聊天', diff --git a/shared/src/i18n/zh/places.ts b/shared/src/i18n/zh/places.ts index a09e679ef..333a55344 100644 --- a/shared/src/i18n/zh/places.ts +++ b/shared/src/i18n/zh/places.ts @@ -83,6 +83,16 @@ const places: TranslationStrings = { 'places.loadingDetails': '正在加载地点详情…', 'places.osmHint': '使用 OpenStreetMap 搜索(无照片、营业时间或评分)。在设置中添加 Google API 密钥以获取完整信息。', 'places.osmActive': '通过 OpenStreetMap 搜索(无照片、评分或营业时间)。在设置中添加 Google API 密钥以获取增强数据。', + 'places.details.title': '地点详情', + 'places.details.empty': '选择一条搜索结果即可查看图片和描述。', + 'places.details.loading': '正在加载详情...', + 'places.details.error': '无法加载详情。', + 'places.details.disabled': '管理员已关闭信息补充。', + 'places.details.noPhotos': '未找到该地点的图片。', + 'places.details.pickImage': '选择图片', + 'places.details.description': '描述', + 'places.details.adopt': '使用此文本', + 'places.details.adoptBlocked': '请先清空描述字段', 'places.categoryCreateError': '创建分类失败', 'places.nameRequired': '请输入名称', 'places.saveError': '保存失败',