diff --git a/src/__tests__/app/call/[id].test.tsx b/src/__tests__/app/call/[id].test.tsx index 8f92dd91..82eea170 100644 --- a/src/__tests__/app/call/[id].test.tsx +++ b/src/__tests__/app/call/[id].test.tsx @@ -222,6 +222,15 @@ jest.mock('react-native-webview', () => ({ default: 'WebView', })); +// Mock react-native-restart - pulled in transitively via the i18n utils used by the +// chat store; the native module is absent under jest +jest.mock('react-native-restart', () => ({ + __esModule: true, + default: { + Restart: jest.fn(), + }, +})); + jest.mock('@/hooks/use-analytics', () => ({ useAnalytics: jest.fn(), })); diff --git a/src/__tests__/app/calls.test.tsx b/src/__tests__/app/calls.test.tsx index f54590aa..d7420e88 100644 --- a/src/__tests__/app/calls.test.tsx +++ b/src/__tests__/app/calls.test.tsx @@ -56,16 +56,17 @@ const mockCallsStore = { }; const mockSecurityStore = { - canUserCreateCalls: true, + rights: { CanCreateCalls: true } as { CanCreateCalls: boolean } | undefined, }; const mockAnalytics = { trackEvent: jest.fn(), }; -// Mock the stores with proper getState method +// Mock the stores with proper getState method. The hook mocks apply an optional +// selector, matching how zustand hooks are called with field selectors. jest.mock('@/stores/calls/store', () => { - const useCallsStore = jest.fn(() => mockCallsStore); + const useCallsStore = jest.fn((selector?: (state: typeof mockCallsStore) => unknown) => (selector ? selector(mockCallsStore) : mockCallsStore)); (useCallsStore as any).getState = jest.fn(() => mockCallsStore); return { @@ -73,9 +74,15 @@ jest.mock('@/stores/calls/store', () => { }; }); -jest.mock('@/stores/security/store', () => ({ - useSecurityStore: jest.fn(() => mockSecurityStore), -})); +jest.mock('@/stores/security/store', () => { + const securityStore = jest.fn((selector?: (state: typeof mockSecurityStore) => unknown) => (selector ? selector(mockSecurityStore) : mockSecurityStore)); + (securityStore as any).getState = jest.fn(() => mockSecurityStore); + + return { + securityStore, + useSecurityStore: jest.fn(() => ({ canUserCreateCalls: mockSecurityStore.rights?.CanCreateCalls })), + }; +}); jest.mock('@/hooks/use-analytics', () => ({ useAnalytics: jest.fn(() => mockAnalytics), @@ -216,9 +223,9 @@ describe('CallsScreen', () => { beforeEach(() => { jest.clearAllMocks(); - // Reset mock returns to defaults - useCallsStore.mockReturnValue(mockCallsStore); - useSecurityStore.mockReturnValue(mockSecurityStore); + // Reset mock behavior to defaults (selector-aware, like the real zustand hooks) + useCallsStore.mockImplementation((selector?: (state: typeof mockCallsStore) => unknown) => (selector ? selector(mockCallsStore) : mockCallsStore)); + useSecurityStore.mockImplementation(() => ({ canUserCreateCalls: mockSecurityStore.rights?.CanCreateCalls })); useAnalytics.mockReturnValue(mockAnalytics); // Reset the mock store state @@ -227,13 +234,12 @@ describe('CallsScreen', () => { mockCallsStore.error = null; mockCallsStore.callPriorities = []; - mockSecurityStore.canUserCreateCalls = true; + mockSecurityStore.rights = { CanCreateCalls: true }; }); describe('when user has create calls permission', () => { beforeEach(() => { - mockSecurityStore.canUserCreateCalls = true; - useSecurityStore.mockReturnValue(mockSecurityStore); + mockSecurityStore.rights = { CanCreateCalls: true }; }); it('renders the new call FAB button', () => { @@ -244,7 +250,7 @@ describe('CallsScreen', () => { expect(htmlContent).toBeTruthy(); // Since we can see the button in debug output, let's just verify the mock is working - expect(mockSecurityStore.canUserCreateCalls).toBe(true); + expect(mockSecurityStore.rights?.CanCreateCalls).toBe(true); }); it('navigates to new call screen when FAB is pressed', () => { @@ -262,8 +268,7 @@ describe('CallsScreen', () => { describe('when user does not have create calls permission', () => { beforeEach(() => { - mockSecurityStore.canUserCreateCalls = false; - useSecurityStore.mockReturnValue(mockSecurityStore); + mockSecurityStore.rights = { CanCreateCalls: false }; }); it('does not render the new call FAB button', () => { @@ -291,7 +296,7 @@ describe('CallsScreen', () => { beforeEach(() => { mockCallsStore.calls = mockCalls; - useCallsStore.mockReturnValue(mockCallsStore); + useCallsStore.mockImplementation((selector?: (state: typeof mockCallsStore) => unknown) => (selector ? selector(mockCallsStore) : mockCallsStore)); }); it('renders call cards for each call', () => { @@ -333,7 +338,7 @@ describe('CallsScreen', () => { describe('loading and error states', () => { it('shows loading state when isLoading is true', () => { mockCallsStore.isLoading = true; - useCallsStore.mockReturnValue(mockCallsStore); + useCallsStore.mockImplementation((selector?: (state: typeof mockCallsStore) => unknown) => (selector ? selector(mockCallsStore) : mockCallsStore)); render(); @@ -346,7 +351,7 @@ describe('CallsScreen', () => { it('shows error state when there is an error', () => { mockCallsStore.error = 'Network error'; - useCallsStore.mockReturnValue(mockCallsStore); + useCallsStore.mockImplementation((selector?: (state: typeof mockCallsStore) => unknown) => (selector ? selector(mockCallsStore) : mockCallsStore)); render(); @@ -359,7 +364,7 @@ describe('CallsScreen', () => { it('shows zero state when there are no calls', () => { mockCallsStore.calls = []; - useCallsStore.mockReturnValue(mockCallsStore); + useCallsStore.mockImplementation((selector?: (state: typeof mockCallsStore) => unknown) => (selector ? selector(mockCallsStore) : mockCallsStore)); render(); @@ -383,7 +388,7 @@ describe('CallsScreen', () => { it('tracks view rendered event with correct parameters', () => { const mockCalls = [{ CallId: 'call-1', Nature: 'Test' }]; mockCallsStore.calls = mockCalls; - useCallsStore.mockReturnValue(mockCallsStore); + useCallsStore.mockImplementation((selector?: (state: typeof mockCallsStore) => unknown) => (selector ? selector(mockCallsStore) : mockCallsStore)); render(); diff --git a/src/__tests__/security-integration.test.ts b/src/__tests__/security-integration.test.ts index 9d77eb6a..6b25d0e2 100644 --- a/src/__tests__/security-integration.test.ts +++ b/src/__tests__/security-integration.test.ts @@ -26,6 +26,8 @@ describe('Security Permission Logic', () => { CanCreateCalls: true, CanAddNote: false, CanCreateMessage: false, + CanLoginToDispatchApp: true, + CanLoginToCommandApp: true, Groups: [] }; @@ -44,6 +46,8 @@ describe('Security Permission Logic', () => { CanCreateCalls: false, CanAddNote: true, CanCreateMessage: true, + CanLoginToDispatchApp: true, + CanLoginToCommandApp: true, Groups: [] }; @@ -65,6 +69,8 @@ describe('Security Permission Logic', () => { CanViewPII: true, CanAddNote: true, CanCreateMessage: true, + CanLoginToDispatchApp: true, + CanLoginToCommandApp: true, Groups: [] } as unknown as DepartmentRightsResultData; @@ -85,6 +91,8 @@ describe('Security Permission Logic', () => { CanCreateCalls: true, CanAddNote: false, CanCreateMessage: false, + CanLoginToDispatchApp: true, + CanLoginToCommandApp: true, Groups: [] }; @@ -107,6 +115,8 @@ describe('Security Permission Logic', () => { CanCreateCalls: false, CanAddNote: true, CanCreateMessage: true, + CanLoginToDispatchApp: true, + CanLoginToCommandApp: true, Groups: [] }; diff --git a/src/api/chat/chat.ts b/src/api/chat/chat.ts index f9399307..20cc0e01 100644 --- a/src/api/chat/chat.ts +++ b/src/api/chat/chat.ts @@ -31,9 +31,25 @@ const MODERATION = '/ChatModeration'; // Channels // --------------------------------------------------------------------------- -export const getChannels = async (activeUnitId?: number, signal?: AbortSignal) => { +/** + * The caller's channels. `includeArchived` pulls in the point-in-time record of closed incidents and + * calls — off by default so the everyday list stays current. `callId` narrows the result server-side + * to channels attached to that call (older servers ignore it and return the full list). + */ +export const getChannels = async (activeUnitId?: number, includeArchived = false, callId?: number, signal?: AbortSignal) => { + const params: Record = {}; + if (activeUnitId != null) { + params.activeUnitId = activeUnitId; + } + if (includeArchived) { + params.includeArchived = true; + } + if (callId != null) { + params.callId = callId; + } + const response = await api.get>(`${CHAT}/GetChannels`, { - params: activeUnitId != null ? { activeUnitId } : undefined, + params: Object.keys(params).length > 0 ? params : undefined, signal, }); return response.data; diff --git a/src/api/common/client.tsx b/src/api/common/client.tsx index 4be3a4ee..1fc3dcfb 100644 --- a/src/api/common/client.tsx +++ b/src/api/common/client.tsx @@ -1,6 +1,6 @@ import axios, { type AxiosError, type AxiosInstance, type InternalAxiosRequestConfig } from 'axios'; -import { refreshTokenRequest } from '@/lib/auth/api'; +import { performTokenRefresh } from '@/lib/auth/token-refresh'; import { logger } from '@/lib/logging'; import { getBaseApiUrl } from '@/lib/storage/app'; import useAuthStore from '@/stores/auth/store'; @@ -78,35 +78,31 @@ axiosInstance.interceptors.response.use( isRefreshing = true; try { - const refreshToken = useAuthStore.getState().refreshToken; - if (!refreshToken) { - throw new Error('No refresh token available'); + // Single-flight refresh shared with the auth store's refresh timer, so a + // timer refresh and a 401-triggered refresh can never rotate the refresh + // token twice in parallel. Failure handling (logout) happens inside + // performTokenRefresh. + const refreshed = await performTokenRefresh(); + if (!refreshed) { + throw new Error('Token refresh failed'); } - const response = await refreshTokenRequest(refreshToken); - const { access_token, refresh_token: newRefreshToken } = response; - - // Update tokens in store - useAuthStore.setState({ - accessToken: access_token, - refreshToken: newRefreshToken, - status: 'signedIn', - error: null, - }); + const accessToken = useAuthStore.getState().accessToken; + if (!accessToken) { + throw new Error('No access token available after refresh'); + } // Update Authorization header - axiosInstance.defaults.headers.common.Authorization = `Bearer ${access_token}`; - originalRequest.headers.Authorization = `Bearer ${access_token}`; + axiosInstance.defaults.headers.common.Authorization = `Bearer ${accessToken}`; + originalRequest.headers.Authorization = `Bearer ${accessToken}`; processQueue(null); return axiosInstance(originalRequest); } catch (refreshError) { processQueue(refreshError as Error); - // Handle refresh token failure - useAuthStore.getState().logout(); logger.error({ message: 'Token refresh failed', - context: { error: refreshError }, + context: { error: refreshError instanceof Error ? refreshError.message : String(refreshError) }, }); return Promise.reject(refreshError); } finally { diff --git a/src/app/(app)/_layout.tsx b/src/app/(app)/_layout.tsx index 9e449204..cdf73900 100644 --- a/src/app/(app)/_layout.tsx +++ b/src/app/(app)/_layout.tsx @@ -27,16 +27,54 @@ import { Env } from '@/lib/env'; import { logger } from '@/lib/logging'; import { useIsFirstTime } from '@/lib/storage'; import { type GetConfigResultData } from '@/models/v4/configs/getConfigResultData'; -import { usePushNotifications } from '@/services/push-notification'; +import { audioService } from '@/services/audio.service'; +import { pushNotificationService, usePushNotifications } from '@/services/push-notification'; +import { useAudioStreamStore } from '@/stores/app/audio-stream-store'; import { useCoreStore } from '@/stores/app/core-store'; +import { useLiveKitStore } from '@/stores/app/livekit-store'; import { useCallsStore } from '@/stores/calls/store'; +import { useChatStore } from '@/stores/chat/store'; +import { useCheckInStore } from '@/stores/checkIn/store'; import { FeatureFlagKeys, featureFlagsStore } from '@/stores/feature-flags/store'; import useLockscreenStore from '@/stores/lockscreen/store'; import { useRolesStore } from '@/stores/roles/store'; import { securityStore } from '@/stores/security/store'; import { useSignalRStore } from '@/stores/signalr/signalr-store'; +import { useToastStore } from '@/stores/toast/store'; import { useWeatherAlertsStore } from '@/stores/weatherAlerts/store'; +/** + * Tear down every per-session resource on sign-out: SignalR hubs and their heartbeats, + * the LiveKit voice room, audio streams and cached sounds, check-in polling, chat + * timers, and push-notification listeners. Without this, all of them keep running + * against a signed-out session until the process dies. + */ +async function teardownSignedInSession(): Promise { + const signalR = useSignalRStore.getState(); + const teardowns: [string, () => Promise | unknown][] = [ + ['SignalR update hub', () => signalR.disconnectUpdateHub()], + ['SignalR chat hub', () => signalR.disconnectChatHub()], + ['SignalR geolocation hub', () => signalR.disconnectGeolocationHub()], + ['LiveKit room', () => useLiveKitStore.getState().disconnectFromRoom()], + ['audio stream', () => useAudioStreamStore.getState().cleanup()], + ['audio service', () => audioService.cleanup()], + ['check-in polling', () => useCheckInStore.getState().stopPolling()], + ['chat store', () => useChatStore.getState().reset()], + ['push notification listeners', () => pushNotificationService.cleanup()], + ]; + + for (const [label, teardown] of teardowns) { + try { + await teardown(); + } catch (error) { + logger.error({ + message: `Failed to tear down ${label} on sign-out`, + context: { error: error instanceof Error ? error.message : String(error) }, + }); + } + } +} + export default function TabLayout() { const { t } = useTranslation(); const status = useAuthStore((state) => state.status); @@ -156,6 +194,23 @@ export default function TabLayout() { await securityStore.getState().getRights(); + // Dispatch shows private command, unit and responder traffic, so a member the department has + // not authorized must not get past initialization. The server is the real boundary — it simply + // never hands an unauthorized user the dispatch channels — but signing them straight back out + // is far clearer than a silently empty app. + if (!isCurrentRun()) { + return; + } + if (securityStore.getState().rights?.CanLoginToDispatchApp === false) { + logger.warn({ + message: 'User is not authorized to use the Dispatch app; signing out', + context: { userId }, + }); + useToastStore.getState().showToast('error', t('login.dispatch_not_authorized')); + await useAuthStore.getState().logout(); + return; + } + logger.info({ message: 'Security rights retrieved, fetching feature flags', context: { platform: Platform.OS }, @@ -273,7 +328,7 @@ export default function TabLayout() { // If the init promise is still hanging, clear the guard so a retry is possible isInitializing.current = false; } - }, [status]); + }, [status, t, userId]); const refreshDataFromBackground = useCallback(async () => { if (status !== 'signedIn' || !hasInitialized.current) return; @@ -377,6 +432,12 @@ export default function TabLayout() { // so the next sign-in is not skipped as "already initializing". initGeneration.current += 1; isInitializing.current = false; + // Clear the initialized flag too, or a sign-in later in this process fails + // shouldInitialize and initializeApp never runs for the new session. + hasInitialized.current = false; + + // Stop hubs, voice, audio and timers that belong to the ended session + void teardownSignedInSession(); } // Update last known status diff --git a/src/app/(app)/calls.tsx b/src/app/(app)/calls.tsx index 04f4276e..6a8b891a 100644 --- a/src/app/(app)/calls.tsx +++ b/src/app/(app)/calls.tsx @@ -1,7 +1,7 @@ import { useFocusEffect } from '@react-navigation/native'; import { type Href, router } from 'expo-router'; import { PlusIcon, RefreshCcwDotIcon, Search, X } from 'lucide-react-native'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Pressable, RefreshControl, View } from 'react-native'; @@ -17,11 +17,18 @@ import { useAnalytics } from '@/hooks/use-analytics'; import { CallState } from '@/lib/utils'; import { type CallResultData } from '@/models/v4/calls/callResultData'; import { useCallsStore } from '@/stores/calls/store'; -import { useSecurityStore } from '@/stores/security/store'; +import { securityStore } from '@/stores/security/store'; export default function Calls() { - const { calls, isLoading, error, fetchCalls, fetchCallPriorities } = useCallsStore(); - const { canUserCreateCalls } = useSecurityStore(); + // Field selectors only - subscribing to the whole store re-renders this screen on + // every store change (e.g. SignalR timestamp updates) + const calls = useCallsStore((s) => s.calls); + const isLoading = useCallsStore((s) => s.isLoading); + const error = useCallsStore((s) => s.error); + const callPriorities = useCallsStore((s) => s.callPriorities); + const fetchCalls = useCallsStore((s) => s.fetchCalls); + const fetchCallPriorities = useCallsStore((s) => s.fetchCallPriorities); + const canUserCreateCalls = securityStore((s) => s.rights?.CanCreateCalls); const { t } = useTranslation(); const { trackEvent } = useAnalytics(); const [searchQuery, setSearchQuery] = useState(''); @@ -56,9 +63,26 @@ export default function Calls() { }; // Filter calls: exclude scheduled calls and apply search - const filteredCalls = calls - .filter((call) => call.State !== CallState.SCHEDULED) - .filter((call) => call.CallId.toLowerCase().includes(searchQuery.toLowerCase()) || (call.Nature?.toLowerCase() || '').includes(searchQuery.toLowerCase())); + const filteredCalls = useMemo(() => { + const query = searchQuery.toLowerCase(); + return calls.filter((call) => call.State !== CallState.SCHEDULED).filter((call) => call.CallId.toLowerCase().includes(query) || (call.Nature?.toLowerCase() || '').includes(query)); + }, [calls, searchQuery]); + + // O(1) priority lookup per row instead of an O(n) find inside renderItem + const priorityById = useMemo(() => { + const map = new Map(); + callPriorities.forEach((p) => map.set(p.Id, p)); + return map; + }, [callPriorities]); + + const renderCallItem = useCallback( + ({ item }: { item: CallResultData }) => ( + router.push(`/call/${item.CallId}` as Href)}> + + + ), + [priorityById] + ); // Render content based on loading, error, and data states const renderContent = () => { @@ -74,11 +98,7 @@ export default function Calls() { testID="calls-list" data={filteredCalls} - renderItem={({ item }: { item: CallResultData }) => ( - router.push(`/call/${item.CallId}` as Href)}> - p.Id === item.Priority)} /> - - )} + renderItem={renderCallItem} keyExtractor={(item: CallResultData) => item.CallId} refreshControl={} ListEmptyComponent={} diff --git a/src/app/(app)/chat.tsx b/src/app/(app)/chat.tsx index 6b6baf00..067ba380 100644 --- a/src/app/(app)/chat.tsx +++ b/src/app/(app)/chat.tsx @@ -110,7 +110,7 @@ export default function ChatScreen() { router.push('/chatbot' as Href); return; } - router.push(`/chat/${channelId}` as Href); + router.push({ pathname: '/chat/[channelId]', params: { channelId } }); }, [router] ); diff --git a/src/app/(app)/chatbot.tsx b/src/app/(app)/chatbot.tsx index 3a015bfb..c6220aa1 100644 --- a/src/app/(app)/chatbot.tsx +++ b/src/app/(app)/chatbot.tsx @@ -68,7 +68,14 @@ export default function ChatbotScreen() { const renderItem = useCallback( ({ item }: { item: ChatMessageResultData }) => ( - undefined} /> + undefined} + /> ), [currentUserId] ); diff --git a/src/app/(app)/home.tsx b/src/app/(app)/home.tsx index 94295302..e2f89205 100644 --- a/src/app/(app)/home.tsx +++ b/src/app/(app)/home.tsx @@ -869,7 +869,9 @@ export default function DispatchConsole() { ) : ( - + {/* Bounded height lets the panels' inner FlatLists virtualize; inside an + unbounded ScrollView they would render every row at once */} + - + ) : null} - + + + ); @@ -1003,4 +1007,8 @@ const styles = StyleSheet.create({ resourcesPanelBounded: { maxHeight: 400, }, + nestedPanelBounded: { + maxHeight: 400, + minHeight: 200, + }, }); diff --git a/src/app/(app)/map.tsx b/src/app/(app)/map.tsx index d4231d6a..528c1f23 100644 --- a/src/app/(app)/map.tsx +++ b/src/app/(app)/map.tsx @@ -30,6 +30,52 @@ import { useToastStore } from '@/stores/toast/store'; Mapbox.setAccessToken(Env.MAPBOX_PUBKEY); +interface UserLocationMarkerProps { + pulseAnim: Animated.Value; + innerContainerStyle: object; +} + +// Memoized user-location marker. Subscribes to the location store itself so GPS ticks +// only re-render this small subtree instead of the whole map screen (which owns the +// pins, layers and modal trees). +const UserLocationMarker = React.memo(({ pulseAnim, innerContainerStyle }) => { + const latitude = useLocationStore((state) => state.latitude); + const longitude = useLocationStore((state) => state.longitude); + const heading = useLocationStore((state) => state.heading); + + if (!latitude || !longitude) { + return null; + } + + return ( + + + + + + {heading !== null && heading !== undefined ? ( + + ) : null} + + + + ); +}); + export default function Map() { const { t } = useTranslation(); const { trackEvent } = useAnalytics(); @@ -46,11 +92,13 @@ export default function Map() { const [isPinDetailModalOpen, setIsPinDetailModalOpen] = useState(false); const [isLayersPanelOpen, setIsLayersPanelOpen] = useState(false); const { isActive } = useAppLifecycle(); - const latitude = useLocationStore((state) => state.latitude); - const longitude = useLocationStore((state) => state.longitude); - const heading = useLocationStore((state) => state.heading); + // Only isMapLocked is subscribed here: latitude/longitude/heading change at GPS + // frequency and are read imperatively (getState) or via the memoized + // UserLocationMarker, so location updates don't re-render the whole screen. const isMapLocked = useLocationStore((state) => state.isMapLocked); - const location = useMemo(() => ({ latitude, longitude, heading, isMapLocked }), [latitude, longitude, heading, isMapLocked]); + // Presence-only selector: the boolean flips when a fix is gained or lost, not on + // every GPS tick. Zero is a valid coordinate, so only null/undefined are "absent". + const hasDeviceLocation = useLocationStore((state) => state.latitude != null && state.longitude != null); // Map layers hook const { layers, visibleLayers, isLoading: isLayersLoading, fetchLayers, toggleLayer, showAllLayers, hideAllLayers, getVisibleLayerData } = useMapLayers({ initialLayerType: MapLayerType.ALL, autoFetch: true }); @@ -133,18 +181,19 @@ export default function Map() { fetchLayers(); // Reset camera to current location when navigating back to map - if (isMapReady && location.latitude && location.longitude) { + const { latitude, longitude, heading, isMapLocked: locked } = useLocationStore.getState(); + if (isMapReady && latitude && longitude) { const cameraConfig: any = { - centerCoordinate: [location.longitude, location.latitude], - zoomLevel: location.isMapLocked ? 16 : 12, + centerCoordinate: [longitude, latitude], + zoomLevel: locked ? 16 : 12, animationDuration: 1000, heading: 0, pitch: 0, }; // Add heading and pitch for navigation mode when locked - if (location.isMapLocked && location.heading !== null && location.heading !== undefined) { - cameraConfig.heading = location.heading; + if (locked && heading !== null && heading !== undefined) { + cameraConfig.heading = heading; cameraConfig.pitch = 45; } @@ -154,61 +203,68 @@ export default function Map() { message: 'Map focused, resetting camera to current location', context: { // GDPR: log bucketed (~11km) coordinates instead of verbatim position - latitudeBucket: Math.round(location.latitude * 10) / 10, - longitudeBucket: Math.round(location.longitude * 10) / 10, - isMapLocked: location.isMapLocked, + latitudeBucket: Math.round(latitude * 10) / 10, + longitudeBucket: Math.round(longitude * 10) / 10, + isMapLocked: locked, gdpr: { purpose: 'map_tracking', lawful_basis: 'consent' }, }, }); } - }, [isMapReady, location.latitude, location.longitude, location.isMapLocked, location.heading, fetchLayers]) + }, [isMapReady, fetchLayers]) ); + // Imperative camera-follow: subscribing to the store inside an effect keeps GPS + // updates out of the React render cycle entirely. + const hasUserMovedMapRef = useRef(hasUserMovedMap); useEffect(() => { - if (isMapReady && location.latitude && location.longitude) { + hasUserMovedMapRef.current = hasUserMovedMap; + }, [hasUserMovedMap]); + + useEffect(() => { + if (!isMapReady) return; + + const unsubscribe = useLocationStore.subscribe((state, prevState) => { + if (state.latitude === prevState.latitude && state.longitude === prevState.longitude && state.heading === prevState.heading) { + return; + } + + const { latitude, longitude, heading, isMapLocked: locked } = state; + if (!latitude || !longitude) return; + // When map is locked, always follow the location // When map is unlocked, only follow if user hasn't moved the map - if (location.isMapLocked || !hasUserMovedMap) { - logger.info({ - message: 'Location updated and map is ready', - context: { - // GDPR: log bucketed (~11km) coordinates instead of verbatim position - latitudeBucket: Math.round(location.latitude * 10) / 10, - longitudeBucket: Math.round(location.longitude * 10) / 10, - heading: location.heading, - isMapLocked: location.isMapLocked, - gdpr: { purpose: 'map_tracking', lawful_basis: 'consent' }, - }, - }); - + if (locked || !hasUserMovedMapRef.current) { const cameraConfig: any = { - centerCoordinate: [location.longitude, location.latitude], - zoomLevel: location.isMapLocked ? 16 : 12, - animationDuration: location.isMapLocked ? 500 : 1000, + centerCoordinate: [longitude, latitude], + zoomLevel: locked ? 16 : 12, + animationDuration: locked ? 500 : 1000, }; // Add heading and pitch for navigation mode when locked - if (location.isMapLocked && location.heading !== null && location.heading !== undefined) { - cameraConfig.heading = location.heading; + if (locked && heading !== null && heading !== undefined) { + cameraConfig.heading = heading; cameraConfig.pitch = 45; } cameraRef.current?.setCamera(cameraConfig); } - } - }, [isMapReady, location.latitude, location.longitude, location.heading, location.isMapLocked, hasUserMovedMap]); + }); + + return unsubscribe; + }, [isMapReady]); // Reset hasUserMovedMap when map gets locked and reset camera when unlocked useEffect(() => { - if (location.isMapLocked) { + if (isMapLocked) { setHasUserMovedMap(false); } else { // When exiting locked mode, reset camera to normal view and reset user interaction state setHasUserMovedMap(false); - if (isMapReady && location.latitude && location.longitude) { + const { latitude, longitude } = useLocationStore.getState(); + if (isMapReady && latitude && longitude) { cameraRef.current?.setCamera({ - centerCoordinate: [location.longitude, location.latitude], + centerCoordinate: [longitude, latitude], zoomLevel: 12, heading: 0, pitch: 0, @@ -218,14 +274,14 @@ export default function Map() { message: 'Map unlocked, resetting camera to normal view and user interaction state', context: { // GDPR: log bucketed (~11km) coordinates instead of verbatim position - latitudeBucket: Math.round(location.latitude * 10) / 10, - longitudeBucket: Math.round(location.longitude * 10) / 10, + latitudeBucket: Math.round(latitude * 10) / 10, + longitudeBucket: Math.round(longitude * 10) / 10, gdpr: { purpose: 'map_tracking', lawful_basis: 'consent' }, }, }); } } - }, [isMapReady, location.isMapLocked, location.latitude, location.longitude]); + }, [isMapReady, isMapLocked]); useEffect(() => { const abortController = new AbortController(); @@ -284,31 +340,32 @@ export default function Map() { trackEvent('map_view_rendered', { hasMapPins: mapPins.length > 0, mapPinsCount: mapPins.length, - isMapLocked: location.isMapLocked, + isMapLocked, theme: colorScheme || 'light', layersCount: combinedLayers.length, visibleLayersCount: visibleLayers.size + visiblePoiLayerIds.size, }); - }, [trackEvent, mapPins.length, location.isMapLocked, colorScheme, combinedLayers.length, visibleLayers.size, visiblePoiLayerIds.size]); + }, [trackEvent, mapPins.length, isMapLocked, colorScheme, combinedLayers.length, visibleLayers.size, visiblePoiLayerIds.size]); const onCameraChanged = (event: any) => { // Only register user interaction if map is not locked - if (event.properties.isUserInteraction && !location.isMapLocked) { + if (event.properties.isUserInteraction && !useLocationStore.getState().isMapLocked) { setHasUserMovedMap(true); } }; const handleRecenterMap = () => { - if (location.latitude && location.longitude) { + const { latitude, longitude, heading, isMapLocked: locked } = useLocationStore.getState(); + if (latitude && longitude) { const cameraConfig: any = { - centerCoordinate: [location.longitude, location.latitude], - zoomLevel: location.isMapLocked ? 16 : 12, + centerCoordinate: [longitude, latitude], + zoomLevel: locked ? 16 : 12, animationDuration: 1000, }; // Add heading and pitch for navigation mode when locked - if (location.isMapLocked && location.heading !== null && location.heading !== undefined) { - cameraConfig.heading = location.heading; + if (locked && heading !== null && heading !== undefined) { + cameraConfig.heading = heading; cameraConfig.pitch = 45; } @@ -353,8 +410,9 @@ export default function Map() { setSelectedPin(null); }; - // Show recenter button only when map is not locked and user has moved the map - const showRecenterButton = !location.isMapLocked && hasUserMovedMap && location.latitude && location.longitude; + // Show recenter button only when map is not locked, the user has moved the map, + // and there is a location to recenter on (handleRecenterMap no-ops without one) + const showRecenterButton = !isMapLocked && hasUserMovedMap && hasDeviceLocation; // Create dynamic styles based on theme const getThemedStyles = useCallback(() => { @@ -572,50 +630,24 @@ export default function Map() { onCameraChanged={onCameraChanged} onDidFinishLoadingMap={() => setIsMapReady(true)} testID="map-view" - scrollEnabled={!location.isMapLocked} - zoomEnabled={!location.isMapLocked} - rotateEnabled={!location.isMapLocked} - pitchEnabled={!location.isMapLocked} + scrollEnabled={!isMapLocked} + zoomEnabled={!isMapLocked} + rotateEnabled={!isMapLocked} + pitchEnabled={!isMapLocked} > {/* Render custom layers */} {renderMapLayers()} {renderActiveCustomLayers()} - {location.latitude && location.longitude ? ( - - - - - - {location.heading !== null && location.heading !== undefined ? ( - - ) : null} - - - - ) : null} + diff --git a/src/components/checkIn/check-in-bottom-sheet.tsx b/src/components/checkIn/check-in-bottom-sheet.tsx index ba552ed2..33b8d866 100644 --- a/src/components/checkIn/check-in-bottom-sheet.tsx +++ b/src/components/checkIn/check-in-bottom-sheet.tsx @@ -71,14 +71,12 @@ export const CheckInBottomSheet: React.FC = ({ isOpen, const { width, height } = useWindowDimensions(); const isLandscape = width > height; const showToast = useToastStore((state) => state.showToast); - const { performCheckIn, isCheckingIn } = useCheckInStore(); + // Field selectors - the whole-store form and the object location selector both + // re-render this sheet on every store/GPS update + const performCheckIn = useCheckInStore((s) => s.performCheckIn); + const isCheckingIn = useCheckInStore((s) => s.isCheckingIn); const units = useUnitsStore((s) => s.units); const personnel = usePersonnelStore((s) => s.personnel); - const userLocation = useLocationStore((state) => ({ - latitude: state.latitude, - longitude: state.longitude, - })); - const typeLabel = (timer: CheckInTimerStatusResultData) => { // Use TargetTypeName from API, fall back to translation key lookup if (timer.TargetTypeName) return timer.TargetTypeName; @@ -132,12 +130,16 @@ export const CheckInBottomSheet: React.FC = ({ isOpen, } try { + // Read the location imperatively - subscribing here would re-render the sheet + // on every GPS tick while it is open + const { latitude, longitude } = useLocationStore.getState(); + const success = await performCheckIn({ CallId: effectiveCallId, CheckInType: selected.TargetType, UnitId: resolvedUnitId || undefined, - Latitude: userLocation.latitude?.toString() || undefined, - Longitude: userLocation.longitude?.toString() || undefined, + Latitude: latitude?.toString() || undefined, + Longitude: longitude?.toString() || undefined, Note: note || undefined, }); diff --git a/src/components/incident-command/command-board-view.tsx b/src/components/incident-command/command-board-view.tsx index a21c25d1..928c7102 100644 --- a/src/components/incident-command/command-board-view.tsx +++ b/src/components/incident-command/command-board-view.tsx @@ -1,5 +1,23 @@ import { type Href, useRouter } from 'expo-router'; -import { CheckCircleIcon, ChevronDownIcon, ChevronUpIcon, ClipboardListIcon, ClockIcon, MapPinIcon, NetworkIcon, PlusIcon, ShieldAlertIcon, TimerIcon, UserCogIcon, UserPlusIcon, XIcon } from 'lucide-react-native'; +import { + CheckCircleIcon, + ChevronDownIcon, + ChevronUpIcon, + ClipboardListIcon, + ClockIcon, + MapPinIcon, + MessageCircleIcon, + MessagesSquareIcon, + NetworkIcon, + PlusIcon, + RadioIcon, + ShieldAlertIcon, + ShieldCheckIcon, + TimerIcon, + UserCogIcon, + UserPlusIcon, + XIcon, +} from 'lucide-react-native'; import React, { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Alert, Platform } from 'react-native'; @@ -10,8 +28,11 @@ import { Heading } from '@/components/ui/heading'; import { HStack } from '@/components/ui/hstack'; import { Text } from '@/components/ui/text'; import { VStack } from '@/components/ui/vstack'; +import { useDirectMessage } from '@/hooks/use-direct-message'; +import { ChatChannelType } from '@/models/v4/chat'; import { type CommandStructureNode } from '@/models/v4/incidentCommand/commandStructureNode'; import { hasIncidentCapability, IncidentCapabilities, IncidentCommandStatus, ResourceAssignmentKind } from '@/models/v4/incidentCommand/incidentCommandEnums'; +import { useChatStore } from '@/stores/chat/store'; import { useIncidentCommandStore } from '@/stores/incident-command/store'; import { usePersonnelStore } from '@/stores/personnel/store'; import { useToastStore } from '@/stores/toast/store'; @@ -39,6 +60,31 @@ const Section: React.FC<{ title: string; icon: React.ReactNode; action?: React.R ); +/** One tappable channel row; shows as unavailable rather than hiding, so absence is explainable. */ +const ChatChannelRow: React.FC<{ + label: string; + hint?: string; + icon: React.ReactNode; + channelId: string | null; + unavailableMessage: string; + onOpen: (channelId: string | null, unavailableMessage: string) => void; + openLabel: string; + testID?: string; +}> = ({ label, hint, icon, channelId, unavailableMessage, onOpen, openLabel, testID }) => ( + + + {icon} + + {label} + {hint ? {hint} : null} + + + + +); + type TranslateFn = ReturnType['t']; const confirmAction = (message: string, onConfirm: () => void, t: TranslateFn) => { @@ -76,6 +122,9 @@ export const CommandBoardView: React.FC = () => { const canManageAnnotations = hasIncidentCapability(capabilities, IncidentCapabilities.ManageAnnotations); const canManageChannels = hasIncidentCapability(capabilities, IncidentCapabilities.ManageChannels); + const { openDirectMessage } = useDirectMessage(); + const incidentChannels = useChatStore((s) => (board?.Command?.CallId ? s.incidentChannelsByCallId[String(board.Command.CallId)] : undefined)); + const personnelMap = useMemo(() => new Map(personnel.map((p) => [p.UserId, `${p.FirstName} ${p.LastName}`.trim()])), [personnel]); const unitsMap = useMemo(() => new Map(units.map((u) => [u.UnitId, u.Name])), [units]); @@ -90,6 +139,20 @@ export const CommandBoardView: React.FC = () => { }; const userName = (id: string): string => personnelMap.get(id) || id || t('incident_command.unassigned'); + // Channel ids come from the server already filtered to what this user may open — an id we did not + // receive is one the API would reject, so the row renders as unavailable rather than a dead link. + const findChannel = (channelType: number): string | null => incidentChannels?.find((channel) => channel.ChannelType === channelType)?.ChatChannelId ?? null; + const dispatchChannelId = findChannel(ChatChannelType.IncidentDispatch); + const incidentChannelId = findChannel(ChatChannelType.Incident); + + const openChannel = (channelId: string | null, unavailableMessage: string) => { + if (!channelId) { + showToast('info', unavailableMessage); + return; + } + router.push({ pathname: '/chat/[channelId]', params: { channelId } }); + }; + const activeNodes = (board.Nodes ?? []).filter((node) => !node.DeletedOn); const activeAssignments = (board.Assignments ?? []).filter((a) => !a.ReleasedOn); const counts = useIncidentCommandStore.getState().accountabilityCounts(); @@ -437,6 +500,66 @@ export const CommandBoardView: React.FC = () => { )} + {/* Incident chat: the channels this dispatcher can open, and a direct line to the IC. */} +
}> + {isClosed ? {t('incident_command.chat_frozen')} : null} + + {/* The call's shared conversation — everyone working the incident, dispatch included. */} + } + channelId={incidentChannelId} + unavailableMessage={t('incident_command.incident_channel_unavailable')} + onOpen={openChannel} + openLabel={t('incident_command.open_chat')} + testID="incident-command-chat-incident" + /> + {/* The desk's own line to the incident. The private command channel is deliberately absent: + it stays internal to command staff, and dispatch reaches command through here. */} + } + channelId={dispatchChannelId} + unavailableMessage={t('incident_command.dispatch_channel_unavailable')} + onOpen={openChannel} + openLabel={t('incident_command.open_chat')} + testID="incident-command-chat-dispatch" + /> + + {/* Direct line to whoever currently holds command. */} + {command.CurrentCommanderUserId ? ( + + + {userName(command.CurrentCommanderUserId)} + {t('incident_command.commander')} + + + + ) : null} + + {/* Everyone holding an ICS position, so dispatch can reach the right person directly. */} + {(board.Roles ?? []) + .filter((role) => !role.RemovedOn) + .map((role) => ( + + + {userName(role.UserId)} + {roleTypeLabel(role.RoleType)} + + + + ))} + +
+ {/* Voice channels */} diff --git a/src/components/incident-command/incident-command-tab.tsx b/src/components/incident-command/incident-command-tab.tsx index 1bd86610..d7874c66 100644 --- a/src/components/incident-command/incident-command-tab.tsx +++ b/src/components/incident-command/incident-command-tab.tsx @@ -9,6 +9,7 @@ import { Button, ButtonIcon, ButtonText } from '@/components/ui/button'; import { HStack } from '@/components/ui/hstack'; import { Text } from '@/components/ui/text'; import { VStack } from '@/components/ui/vstack'; +import { useChatStore } from '@/stores/chat/store'; import { useIncidentCommandStore } from '@/stores/incident-command/store'; import { usePersonnelStore } from '@/stores/personnel/store'; import { useSecurityStore } from '@/stores/security/store'; @@ -29,20 +30,38 @@ export const IncidentCommandTab: React.FC = ({ callId, const isLoading = useIncidentCommandStore((s) => s.isLoading); const board = useIncidentCommandStore((s) => s.board); const error = useIncidentCommandStore((s) => s.error); - const { canUserCreateCalls } = useSecurityStore(); + // Working a command board is its own department permission, separate from Dispatch access. Without + // it every board endpoint returns 403, so say so plainly instead of loading into a failure. + const { canUserCreateCalls, canUserWorkCommand } = useSecurityStore(); + const canWorkCommand = canUserWorkCommand !== false; const [isEstablishOpen, setIsEstablishOpen] = useState(false); useEffect(() => { - if (callId) { + if (callId && canWorkCommand) { useIncidentCommandStore.getState().loadForCall(callId); // Preload personnel + units so the board can resolve assignment / role names. usePersonnelStore.getState().fetchPersonnel(); useUnitsStore.getState().fetchUnits(); + // Chat channels for the incident. Archived ones are included so a closed incident's + // conversations stay readable as a point-in-time record. + void useChatStore.getState().loadIncidentChannels(callId); } - }, [callId]); + }, [callId, canWorkCommand]); const hasCommand = !!board && !!board.Command?.IncidentCommandId; + if (!canWorkCommand) { + return ( + + + + {t('incident_command.not_authorized')} + {t('incident_command.not_authorized_description')} + + + ); + } + if (isLoading && !hasCommand) { return ( diff --git a/src/components/pois/poi-detail-screen.tsx b/src/components/pois/poi-detail-screen.tsx index 944f4d21..4cfcee9a 100644 --- a/src/components/pois/poi-detail-screen.tsx +++ b/src/components/pois/poi-detail-screen.tsx @@ -24,11 +24,13 @@ export const PoiDetailScreen: React.FC = () => { const router = useRouter(); const { t } = useTranslation(); const showToast = useToastStore((state) => state.showToast); - const { selectedPoi, isLoadingDetail, detailError, fetchPoi, resetSelectedPoi } = usePoisStore(); - const userLocation = useLocationStore((state) => ({ - latitude: state.latitude, - longitude: state.longitude, - })); + // Field selectors - whole-store and object location selectors re-render this screen + // on every store/GPS update + const selectedPoi = usePoisStore((s) => s.selectedPoi); + const isLoadingDetail = usePoisStore((s) => s.isLoadingDetail); + const detailError = usePoisStore((s) => s.detailError); + const fetchPoi = usePoisStore((s) => s.fetchPoi); + const resetSelectedPoi = usePoisStore((s) => s.resetSelectedPoi); const poiId = useMemo(() => { const rawId = Array.isArray(id) ? id[0] : id; @@ -52,7 +54,10 @@ export const PoiDetailScreen: React.FC = () => { return; } - const success = await openMapsWithDirections(selectedPoi.Latitude, selectedPoi.Longitude, getPoiPrimaryDisplayText(selectedPoi), userLocation.latitude || undefined, userLocation.longitude || undefined); + // Read the location imperatively - subscribing here would re-render on every GPS tick + const { latitude, longitude } = useLocationStore.getState(); + + const success = await openMapsWithDirections(selectedPoi.Latitude, selectedPoi.Longitude, getPoiPrimaryDisplayText(selectedPoi), latitude || undefined, longitude || undefined); if (!success) { showToast('error', t('pois.route_error')); diff --git a/src/components/sidebar/call-sidebar.tsx b/src/components/sidebar/call-sidebar.tsx index 2c382cf6..77ea588a 100644 --- a/src/components/sidebar/call-sidebar.tsx +++ b/src/components/sidebar/call-sidebar.tsx @@ -20,11 +20,11 @@ import { HStack } from '../ui/hstack'; export const SidebarCallCard = () => { const { colorScheme } = useColorScheme(); - const { activeCall, activePriorityId, setActiveCall } = useCoreStore((state) => ({ - activeCall: state.activeCall, - activePriorityId: state.activePriority, - setActiveCall: state.setActiveCall, - })); + // Individual selectors - an object selector without shallow comparison returns a new + // reference every call and re-renders on every store update + const activeCall = useCoreStore((state) => state.activeCall); + const activePriorityId = useCoreStore((state) => state.activePriority); + const setActiveCall = useCoreStore((state) => state.setActiveCall); // Get the actual priority object from the calls store const activePriority = useCallsStore((state) => (activePriorityId ? state.getPriorityById(activePriorityId) : undefined)); diff --git a/src/hooks/__tests__/use-signalr-lifecycle.test.tsx b/src/hooks/__tests__/use-signalr-lifecycle.test.tsx index 2ebff067..9c51beba 100644 --- a/src/hooks/__tests__/use-signalr-lifecycle.test.tsx +++ b/src/hooks/__tests__/use-signalr-lifecycle.test.tsx @@ -17,6 +17,8 @@ describe('useSignalRLifecycle', () => { const mockDisconnectUpdateHub = jest.fn(); const mockConnectGeolocationHub = jest.fn(); const mockDisconnectGeolocationHub = jest.fn(); + const mockConnectChatHub = jest.fn(); + const mockDisconnectChatHub = jest.fn(); // Create shared state for app lifecycle that can be updated let appLifecycleState = { @@ -42,15 +44,19 @@ describe('useSignalRLifecycle', () => { isActive: true, }; - // Mock SignalR store - mockUseSignalRStore.mockReturnValue({ + // Mock SignalR store (selector-aware, like the real zustand hook) + const signalRStoreState: any = { connectUpdateHub: mockConnectUpdateHub, disconnectUpdateHub: mockDisconnectUpdateHub, connectGeolocationHub: mockConnectGeolocationHub, disconnectGeolocationHub: mockDisconnectGeolocationHub, + connectChatHub: mockConnectChatHub, + disconnectChatHub: mockDisconnectChatHub, isUpdateHubConnected: false, isGeolocationHubConnected: false, - } as any); + isChatHubConnected: false, + }; + mockUseSignalRStore.mockImplementation((selector?: (state: any) => unknown) => (selector ? selector(signalRStoreState) : signalRStoreState) as any); // Mock useAppLifecycle to return shared state mockUseAppLifecycle.mockImplementation(() => appLifecycleState); diff --git a/src/hooks/use-direct-message.ts b/src/hooks/use-direct-message.ts new file mode 100644 index 00000000..5cbc0ae2 --- /dev/null +++ b/src/hooks/use-direct-message.ts @@ -0,0 +1,47 @@ +import { router } from 'expo-router'; +import { useCallback, useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { createDirectMessage } from '@/api/chat/chat'; +import { logger } from '@/lib/logging'; +import { useToastStore } from '@/stores/toast/store'; + +/** + * Opens a 1:1 conversation with someone and navigates to it. + * + * The server dedups on a normalized participant key, so calling this repeatedly for the same person + * reuses the existing conversation rather than starting a new one — which is what makes it safe to + * hang a "message" button off every contact on an incident. + */ +export const useDirectMessage = () => { + const { t } = useTranslation(); + const [isOpening, setIsOpening] = useState(false); + + const openDirectMessage = useCallback( + async (targetUserId?: string | null) => { + if (!targetUserId) { + useToastStore.getState().showToast('info', t('incident_command.dm_unavailable')); + return; + } + + setIsOpening(true); + try { + const channel = await createDirectMessage({ TargetUserId: targetUserId }); + const channelId = channel?.Data?.ChatChannelId; + if (!channelId) { + useToastStore.getState().showToast('error', t('incident_command.dm_failed')); + return; + } + router.push({ pathname: '/chat/[channelId]', params: { channelId } }); + } catch (error) { + logger.error({ message: 'chat: failed to open direct message', context: { error, targetUserId } }); + useToastStore.getState().showToast('error', t('incident_command.dm_failed')); + } finally { + setIsOpening(false); + } + }, + [t] + ); + + return { openDirectMessage, isOpening }; +}; diff --git a/src/hooks/use-saml-login.ts b/src/hooks/use-saml-login.ts index 772996e7..e56dd615 100644 --- a/src/hooks/use-saml-login.ts +++ b/src/hooks/use-saml-login.ts @@ -1,3 +1,4 @@ +import { randomUUID } from 'expo-crypto'; import * as Linking from 'expo-linking'; import * as WebBrowser from 'expo-web-browser'; import { Platform } from 'react-native'; @@ -5,12 +6,44 @@ import { Platform } from 'react-native'; import { externalTokenRequest } from '@/lib/auth/api'; import type { AuthResponse } from '@/lib/auth/types'; import { logger } from '@/lib/logging'; +import { getItem, removeItem, setItem } from '@/lib/storage'; export interface SamlLoginHook { startSamlLogin: () => Promise; handleSamlDeepLink: (url: string) => Promise; } +// CSRF protection for the SAML flow: a random RelayState nonce is generated when the +// user starts the login and the deep link is only accepted while a matching pending +// flow exists. Persisted so a cold-started app (killed during the browser round-trip) +// can still validate the callback. Any installed app can claim the custom URL scheme, +// so an unsolicited injected saml_response must be rejected. +const SAML_PENDING_STATE_KEY = 'SAML_PENDING_STATE'; +const SAML_FLOW_MAX_AGE_MS = 10 * 60 * 1000; + +interface SamlPendingState { + nonce: string; + startedAt: number; +} + +async function savePendingState(state: SamlPendingState | null): Promise { + if (state) { + await setItem(SAML_PENDING_STATE_KEY, JSON.stringify(state)); + } else { + await removeItem(SAML_PENDING_STATE_KEY); + } +} + +function readPendingState(): SamlPendingState | null { + const raw = getItem(SAML_PENDING_STATE_KEY); + if (!raw) return null; + try { + return JSON.parse(raw) as SamlPendingState; + } catch { + return null; + } +} + export function useSamlLogin(idpSsoUrl: string, username: string, departmentId?: number): SamlLoginHook { async function startSamlLogin(): Promise { if (!idpSsoUrl) { @@ -19,11 +52,18 @@ export function useSamlLogin(idpSsoUrl: string, username: string, departmentId?: } try { + // Record the pending flow and ask the IdP to echo our nonce back as RelayState + const nonce = randomUUID(); + await savePendingState({ nonce, startedAt: Date.now() }); + + const separator = idpSsoUrl.includes('?') ? '&' : '?'; + const urlWithState = `${idpSsoUrl}${separator}RelayState=${encodeURIComponent(nonce)}`; + if (Platform.OS === 'web') { // On web, open in the same tab so the SAML flow completes properly - Linking.openURL(idpSsoUrl); + Linking.openURL(urlWithState); } else { - await WebBrowser.openBrowserAsync(idpSsoUrl); + await WebBrowser.openBrowserAsync(urlWithState); } } catch (error) { logger.error({ message: 'SSO SAML: Failed to open IdP browser', context: { error, idpSsoUrl } }); @@ -41,6 +81,24 @@ export function useSamlLogin(idpSsoUrl: string, username: string, departmentId?: return null; } + // Validate against the pending flow started by startSamlLogin + const pending = readPendingState(); + if (!pending || Date.now() - pending.startedAt > SAML_FLOW_MAX_AGE_MS) { + logger.warn({ message: 'SSO SAML: Rejecting saml_response with no fresh pending login flow', context: { path: parsed.path } }); + return null; + } + + // If the IdP echoed RelayState (or a state param), it must match our nonce. + // Absence is tolerated for IdPs that drop RelayState; the pending-flow check above still applies. + const echoedState = (parsed.queryParams?.RelayState ?? parsed.queryParams?.state) as string | undefined; + if (echoedState && echoedState !== pending.nonce) { + logger.warn({ message: 'SSO SAML: Rejecting saml_response with mismatched RelayState', context: { path: parsed.path } }); + return null; + } + + // Consume the pending flow so a replayed deep link is rejected + await savePendingState(null); + const result = await externalTokenRequest('saml2', samlResponse, username, departmentId); if (!result.successful || !result.authResponse) { diff --git a/src/hooks/use-signalr-lifecycle.ts b/src/hooks/use-signalr-lifecycle.ts index af3a675e..06d35348 100644 --- a/src/hooks/use-signalr-lifecycle.ts +++ b/src/hooks/use-signalr-lifecycle.ts @@ -12,7 +12,14 @@ interface UseSignalRLifecycleOptions { export function useSignalRLifecycle({ isSignedIn, hasInitialized }: UseSignalRLifecycleOptions) { const { isActive, appState } = useAppLifecycle(); - const signalRStore = useSignalRStore(); + // Select only the actions needed - subscribing to the whole store re-creates the + // callbacks below (and re-runs the lifecycle effects) on every hub message + const connectUpdateHub = useSignalRStore((s) => s.connectUpdateHub); + const disconnectUpdateHub = useSignalRStore((s) => s.disconnectUpdateHub); + const connectGeolocationHub = useSignalRStore((s) => s.connectGeolocationHub); + const disconnectGeolocationHub = useSignalRStore((s) => s.disconnectGeolocationHub); + const connectChatHub = useSignalRStore((s) => s.connectChatHub); + const disconnectChatHub = useSignalRStore((s) => s.disconnectChatHub); // Track current values with refs for timer callbacks const currentIsActive = useRef(isActive); @@ -64,7 +71,7 @@ export function useSignalRLifecycle({ isSignedIn, hasInitialized }: UseSignalRLi try { // Use Promise.allSettled to prevent one failure from blocking the other const hubNames = ['UpdateHub', 'GeolocationHub', 'ChatHub']; - const results = await Promise.allSettled([signalRStore.disconnectUpdateHub(), signalRStore.disconnectGeolocationHub(), signalRStore.disconnectChatHub()]); + const results = await Promise.allSettled([disconnectUpdateHub(), disconnectGeolocationHub(), disconnectChatHub()]); // Log any failures without throwing results.forEach((result, index) => { @@ -86,7 +93,7 @@ export function useSignalRLifecycle({ isSignedIn, hasInitialized }: UseSignalRLi pendingOperations.current = null; } } - }, [signalRStore]); + }, [disconnectUpdateHub, disconnectGeolocationHub, disconnectChatHub]); const handleAppResume = useCallback(async () => { logger.debug({ @@ -118,7 +125,7 @@ export function useSignalRLifecycle({ isSignedIn, hasInitialized }: UseSignalRLi try { // Use Promise.allSettled to prevent one failure from blocking the other const hubNames = ['UpdateHub', 'GeolocationHub', 'ChatHub']; - const results = await Promise.allSettled([signalRStore.connectUpdateHub(), signalRStore.connectGeolocationHub(), signalRStore.connectChatHub()]); + const results = await Promise.allSettled([connectUpdateHub(), connectGeolocationHub(), connectChatHub()]); // Log any failures without throwing results.forEach((result, index) => { @@ -140,7 +147,7 @@ export function useSignalRLifecycle({ isSignedIn, hasInitialized }: UseSignalRLi pendingOperations.current = null; } } - }, [signalRStore]); + }, [connectUpdateHub, connectGeolocationHub, connectChatHub]); // Clear timers helper const clearTimers = useCallback(() => { diff --git a/src/lib/__tests__/navigation.test.ts b/src/lib/__tests__/navigation.test.ts index 33950357..7e3c0481 100644 --- a/src/lib/__tests__/navigation.test.ts +++ b/src/lib/__tests__/navigation.test.ts @@ -13,6 +13,14 @@ jest.mock('react-native', () => ({ }, })); +// Mock expo-router - pulling in the real module drags @react-navigation into the +// module graph, which needs far more of the RN API surface than the mock above has +jest.mock('expo-router', () => ({ + router: { + push: jest.fn(), + }, +})); + // Mock the logger jest.mock('../logging', () => ({ logger: { diff --git a/src/lib/auth/__tests__/token-refresh.test.ts b/src/lib/auth/__tests__/token-refresh.test.ts new file mode 100644 index 00000000..39ec440d --- /dev/null +++ b/src/lib/auth/__tests__/token-refresh.test.ts @@ -0,0 +1,52 @@ +import { cancelScheduledTokenRefresh, scheduleTokenRefresh } from '../token-refresh'; + +jest.mock('@/lib/logging', () => ({ + logger: { info: jest.fn(), warn: jest.fn(), error: jest.fn() }, +})); + +jest.mock('../api', () => ({ + refreshTokenRequest: jest.fn(), +})); + +describe('scheduleTokenRefresh', () => { + let setTimeoutSpy: jest.SpyInstance; + + beforeEach(() => { + jest.useFakeTimers(); + setTimeoutSpy = jest.spyOn(global, 'setTimeout'); + }); + + afterEach(() => { + cancelScheduledTokenRefresh(); + jest.useRealTimers(); + jest.restoreAllMocks(); + }); + + const lastScheduledDelay = (): number => setTimeoutSpy.mock.calls[setTimeoutSpy.mock.calls.length - 1][1] as number; + + it('refreshes one buffer before expiry for normal lifetimes', () => { + scheduleTokenRefresh(86400); // 24h, the server default + expect(lastScheduledDelay()).toBe(86400 * 1000 - 60000); + }); + + it('refreshes at half the lifetime when the lifetime approaches the buffer', () => { + // A 1-minute server lifetime equals the buffer; the old max(lifetime - buffer, min) + // clamped this to the 5s minimum and refreshed in a perpetual tight loop. + scheduleTokenRefresh(60); + expect(lastScheduledDelay()).toBe(30000); + }); + + it('never schedules below the minimum delay', () => { + scheduleTokenRefresh(8); + expect(lastScheduledDelay()).toBe(5000); + + scheduleTokenRefresh(0); + expect(lastScheduledDelay()).toBe(5000); + }); + + it('replaces a previously scheduled refresh instead of stacking timers', () => { + scheduleTokenRefresh(3600); + scheduleTokenRefresh(7200); + expect(jest.getTimerCount()).toBe(1); + }); +}); diff --git a/src/lib/auth/token-refresh.ts b/src/lib/auth/token-refresh.ts new file mode 100644 index 00000000..21216065 --- /dev/null +++ b/src/lib/auth/token-refresh.ts @@ -0,0 +1,98 @@ +import { logger } from '@/lib/logging'; + +import { refreshTokenRequest } from './api'; +import type { AuthResponse } from './types'; + +const REFRESH_BUFFER_MS = 60000; +const MIN_REFRESH_DELAY_MS = 5000; + +let refreshTimer: ReturnType | null = null; +let inFlightRefresh: Promise | null = null; + +export interface TokenRefreshHandlers { + getRefreshToken: () => string | null; + applyAuthResponse: (response: AuthResponse) => void; + onRefreshFailed: () => void; +} + +let handlers: TokenRefreshHandlers | null = null; + +export function initTokenRefresh(h: TokenRefreshHandlers): void { + handlers = h; +} + +export function cancelScheduledTokenRefresh(): void { + if (refreshTimer) { + clearTimeout(refreshTimer); + refreshTimer = null; + } +} + +/** + * Schedule an automatic refresh one minute before the access token expires. + * `expiresInSeconds` is the relative lifetime from the token response, NOT an + * epoch timestamp - subtracting Date.now() from it yields a hugely negative + * delay that fires the timer immediately and loops forever. + */ +export function scheduleTokenRefresh(expiresInSeconds: number): void { + cancelScheduledTokenRefresh(); + + // Refresh REFRESH_BUFFER_MS before expiry, but never earlier than half the token's + // lifetime: the server's lifetime is configurable down to one minute, which equals + // the buffer and would otherwise clamp every delay to the minimum and refresh in a + // perpetual tight loop. MIN_REFRESH_DELAY_MS stays as the absolute lower bound. + const lifetimeMs = expiresInSeconds * 1000; + const delay = Math.max(lifetimeMs - REFRESH_BUFFER_MS, lifetimeMs / 2, MIN_REFRESH_DELAY_MS); + refreshTimer = setTimeout(() => { + refreshTimer = null; + void performTokenRefresh(); + }, delay); +} + +/** + * Single-flight token refresh shared by the auth store timer and the axios 401 + * interceptor. Concurrent callers await the same request, so the refresh token + * is never rotated twice in parallel (which invalidates one of the requests). + */ +export function performTokenRefresh(): Promise { + if (inFlightRefresh) { + return inFlightRefresh; + } + + const operation = (async (): Promise => { + if (!handlers) { + logger.error({ message: 'Token refresh attempted before initTokenRefresh was called' }); + return false; + } + + const refreshToken = handlers.getRefreshToken(); + if (!refreshToken) { + logger.warn({ message: 'Token refresh skipped: no refresh token available' }); + handlers.onRefreshFailed(); + return false; + } + + try { + const response = await refreshTokenRequest(refreshToken); + handlers.applyAuthResponse(response); + scheduleTokenRefresh(response.expires_in); + return true; + } catch (error) { + logger.error({ + message: 'Token refresh failed', + context: { error: error instanceof Error ? error.message : String(error) }, + }); + handlers.onRefreshFailed(); + return false; + } + })(); + + inFlightRefresh = operation; + operation.finally(() => { + if (inFlightRefresh === operation) { + inFlightRefresh = null; + } + }); + + return operation; +} diff --git a/src/models/v4/chat/chatEnums.ts b/src/models/v4/chat/chatEnums.ts index 006fd1c9..11916f0c 100644 --- a/src/models/v4/chat/chatEnums.ts +++ b/src/models/v4/chat/chatEnums.ts @@ -13,6 +13,10 @@ export enum ChatChannelType { IncidentLane = 6, IncidentCommand = 7, Chatbot = 8, + /** IC plus every lane's primary/secondary lead — command talking to the people running the lanes. */ + IncidentLeads = 9, + /** The incident's line to the dispatch desk: everyone on the incident, plus every authorized dispatcher. */ + IncidentDispatch = 10, } /** Message type (ChatMessageResultData.MessageType). */ diff --git a/src/models/v4/security/departmentRightsResultData.ts b/src/models/v4/security/departmentRightsResultData.ts index 00fdc89f..8b77e89a 100644 --- a/src/models/v4/security/departmentRightsResultData.ts +++ b/src/models/v4/security/departmentRightsResultData.ts @@ -9,6 +9,18 @@ export class DepartmentRightsResultData { public CanCreateCalls: boolean = false; // Can Create Calls public CanAddNote: boolean = false; // Can Add a Note public CanCreateMessage: boolean = false; // Can Add a Message + /** + * Whether this user may use the Dispatch app at all. Dispatch surfaces private command, unit and + * responder traffic, so departments can restrict it; defaults to true for everyone when the + * permission has never been configured. + */ + public CanLoginToDispatchApp: boolean = true; + /** + * Whether this user may work incident command: read command boards and act on them. A dispatcher + * assisting an incident needs this in addition to Dispatch access. Defaults to true for everyone + * when the department has never configured the permission. + */ + public CanLoginToCommandApp: boolean = true; public Groups: GroupRightResultData[] = []; // Group Rights } diff --git a/src/services/signalr.service.ts b/src/services/signalr.service.ts index 8ffa69d6..41a0597e 100644 --- a/src/services/signalr.service.ts +++ b/src/services/signalr.service.ts @@ -793,8 +793,11 @@ class SignalRService { context: { error, attempts: currentAttempts, maxAttempts: this.MAX_RECONNECT_ATTEMPTS }, }); - // Don't immediately retry; let the next connection close event trigger another attempt - // This prevents rapid retry loops that could overwhelm the server + // Schedule the next attempt: the connection is already closed, so no further + // close event will fire to re-trigger reconnection, and without this the hub + // stays dead until an app lifecycle event. Backoff grows via the attempts + // counter, so this cannot spin a rapid retry loop. + this.handleConnectionClose(hubName); } }, backoffDelay); diff --git a/src/stores/app/audio-stream-store.web.ts b/src/stores/app/audio-stream-store.web.ts index 7ed301a7..ad86e027 100644 --- a/src/stores/app/audio-stream-store.web.ts +++ b/src/stores/app/audio-stream-store.web.ts @@ -4,6 +4,17 @@ import { getDepartmentAudioStreams } from '@/api/voice'; import { logger } from '@/lib/logging'; import { type DepartmentAudioResultStreamData } from '@/models/v4/voice/departmentAudioResultStreamData'; +// Aborts all listeners on the current audio element; recreated per stream so +// stopStream can actually detach handlers instead of leaving them for the GC. +let streamListenersAbort: AbortController | null = null; + +const detachStreamListeners = () => { + if (streamListenersAbort) { + streamListenersAbort.abort(); + streamListenersAbort = null; + } +}; + interface AudioStreamState { // Available streams availableStreams: DepartmentAudioResultStreamData[]; @@ -94,54 +105,84 @@ export const useAudioStreamStore = create((set, get) => ({ const audio = new Audio(stream.Url); audio.crossOrigin = 'anonymous'; + // Listeners are attached with an AbortSignal so stopStream can detach them; + // otherwise each play/stop cycle stacks another set of closures on the element + detachStreamListeners(); + streamListenersAbort = new AbortController(); + const listenerSignal = streamListenersAbort.signal; + // Set up event listeners - audio.addEventListener('loadeddata', () => { - set({ isLoading: false, isBuffering: false }); - logger.debug({ - message: 'Audio stream loaded', - context: { streamName: stream.Name }, - }); - }); + audio.addEventListener( + 'loadeddata', + () => { + set({ isLoading: false, isBuffering: false }); + logger.debug({ + message: 'Audio stream loaded', + context: { streamName: stream.Name }, + }); + }, + { signal: listenerSignal } + ); - audio.addEventListener('playing', () => { - set({ isPlaying: true, isBuffering: false }); - logger.debug({ - message: 'Audio stream playing', - context: { streamName: stream.Name }, - }); - }); + audio.addEventListener( + 'playing', + () => { + set({ isPlaying: true, isBuffering: false }); + logger.debug({ + message: 'Audio stream playing', + context: { streamName: stream.Name }, + }); + }, + { signal: listenerSignal } + ); - audio.addEventListener('pause', () => { - set({ isPlaying: false }); - logger.debug({ - message: 'Audio stream paused', - context: { streamName: stream.Name }, - }); - }); + audio.addEventListener( + 'pause', + () => { + set({ isPlaying: false }); + logger.debug({ + message: 'Audio stream paused', + context: { streamName: stream.Name }, + }); + }, + { signal: listenerSignal } + ); - audio.addEventListener('waiting', () => { - set({ isBuffering: true }); - logger.debug({ - message: 'Audio stream buffering', - context: { streamName: stream.Name }, - }); - }); + audio.addEventListener( + 'waiting', + () => { + set({ isBuffering: true }); + logger.debug({ + message: 'Audio stream buffering', + context: { streamName: stream.Name }, + }); + }, + { signal: listenerSignal } + ); - audio.addEventListener('error', (e) => { - logger.error({ - message: 'Audio stream error', - context: { error: e, streamName: stream.Name }, - }); - set({ isPlaying: false, isLoading: false, isBuffering: false }); - }); + audio.addEventListener( + 'error', + (e) => { + logger.error({ + message: 'Audio stream error', + context: { error: e, streamName: stream.Name }, + }); + set({ isPlaying: false, isLoading: false, isBuffering: false }); + }, + { signal: listenerSignal } + ); - audio.addEventListener('ended', () => { - logger.debug({ - message: 'Audio stream ended', - context: { streamName: stream.Name }, - }); - set({ isPlaying: false }); - }); + audio.addEventListener( + 'ended', + () => { + logger.debug({ + message: 'Audio stream ended', + context: { streamName: stream.Name }, + }); + set({ isPlaying: false }); + }, + { signal: listenerSignal } + ); // Start playing await audio.play(); @@ -172,6 +213,7 @@ export const useAudioStreamStore = create((set, get) => ({ const { audioElement, currentStream } = get(); if (audioElement) { + detachStreamListeners(); audioElement.pause(); audioElement.src = ''; audioElement.load(); diff --git a/src/stores/app/livekit-store.ts b/src/stores/app/livekit-store.ts index 132f2d0e..9532e37d 100644 --- a/src/stores/app/livekit-store.ts +++ b/src/stores/app/livekit-store.ts @@ -71,6 +71,26 @@ const setupAudioRouting = async (room: Room): Promise => { // Map to store web audio elements for cleanup (keyed by track SID) const webAudioElements = new Map(); + +// Remove every attached web audio element from the document and clear the map. +// Must run before removeAllListeners()/disconnect() on a room being torn down: +// once listeners are stripped, TrackUnsubscribed can no longer fire and the old +// room's elements would otherwise stay in the DOM forever. +const cleanupWebAudioElements = (): void => { + if (Platform.OS !== 'web') return; + webAudioElements.forEach((audioElement, trackSid) => { + try { + audioElement.pause(); + audioElement.remove(); + } catch (err) { + logger.warn({ + message: 'Failed to clean up audio element', + context: { error: err, trackSid }, + }); + } + }); + webAudioElements.clear(); +}; interface LiveKitState { // Connection state isConnected: boolean; @@ -213,12 +233,23 @@ export const useLiveKitStore = create((set, get) => ({ }, connectToRoom: async (roomInfo, token) => { + let room: Room | null = null; try { const { currentRoom, voipServerWebsocketSslAddress, requestPermissions } = get(); - // Disconnect from current room if connected + // Disconnect from current room if connected - await it and strip listeners so + // events from the old room cannot fire into the new session's state. if (currentRoom) { - currentRoom.disconnect(); + try { + cleanupWebAudioElements(); + currentRoom.removeAllListeners(); + await currentRoom.disconnect(); + } catch (disconnectError) { + logger.warn({ + message: 'Failed to cleanly disconnect previous room before reconnect', + context: { error: disconnectError }, + }); + } } set({ isConnecting: true }); @@ -227,7 +258,7 @@ export const useLiveKitStore = create((set, get) => ({ await requestPermissions(); // Create a new room - const room = new Room(); + room = new Room(); // Setup room event listeners room.on(RoomEvent.ParticipantConnected, (participant) => { @@ -236,7 +267,7 @@ export const useLiveKitStore = create((set, get) => ({ context: { participantIdentity: participant.identity }, }); // Play connection sound when others join - if (participant.identity !== room.localParticipant.identity) { + if (participant.identity !== room?.localParticipant.identity) { //audioService.playConnectToAudioRoomSound(); } }); @@ -251,6 +282,7 @@ export const useLiveKitStore = create((set, get) => ({ }); room.on(RoomEvent.ActiveSpeakersChanged, (speakers) => { + if (!room) return; // Check if local participant is speaking const localParticipant = room.localParticipant; const isTalking = speakers.some((speaker) => speaker.sid === localParticipant.sid); @@ -348,41 +380,53 @@ export const useLiveKitStore = create((set, get) => ({ message: 'Failed to connect to room', context: { error }, }); - set({ isConnecting: false }); + + // Dispose of the partially-connected room so its socket and listeners don't leak + if (room) { + try { + room.removeAllListeners(); + await room.disconnect(); + } catch (cleanupError) { + logger.warn({ + message: 'Failed to clean up room after connect failure', + context: { error: cleanupError }, + }); + } + } + + set({ isConnecting: false, isConnected: false, currentRoom: null, currentRoomInfo: null, isTalking: false }); + + // Rethrow so callers (e.g. usePTT) don't proceed as if the channel is live + throw error; } }, disconnectFromRoom: async () => { const { currentRoom } = get(); - if (currentRoom) { - // Clean up web audio elements before disconnecting - if (Platform.OS === 'web') { - webAudioElements.forEach((audioElement, trackSid) => { - try { - audioElement.pause(); - audioElement.remove(); - } catch (err) { - logger.warn({ - message: 'Failed to clean up audio element', - context: { error: err, trackSid }, - }); - } - }); - webAudioElements.clear(); - } + try { + if (currentRoom) { + // Clean up web audio elements before disconnecting + cleanupWebAudioElements(); - await currentRoom.disconnect(); - await audioService.playDisconnectedFromAudioRoomSound(); + currentRoom.removeAllListeners(); + await currentRoom.disconnect(); + await audioService.playDisconnectedFromAudioRoomSound(); - // Stop foreground service only on Android - if (Platform.OS === 'android') { - await get().stopAndroidForegroundService(); + // Stop foreground service only on Android + if (Platform.OS === 'android') { + await get().stopAndroidForegroundService(); + } } - + } finally { + // Always reset state - when teardown partially fails (disconnect or audio + // playback rejecting) and when the store is desynced (connected flag true, + // room null), the store must still end in a clean disconnected state. set({ currentRoom: null, currentRoomInfo: null, isConnected: false, + isConnecting: false, + isTalking: false, }); } }, diff --git a/src/stores/auth/__tests__/token-refresh-race.test.ts b/src/stores/auth/__tests__/token-refresh-race.test.ts new file mode 100644 index 00000000..ef5f2fd7 --- /dev/null +++ b/src/stores/auth/__tests__/token-refresh-race.test.ts @@ -0,0 +1,62 @@ +import { refreshTokenRequest } from '@/lib/auth/api'; +import { cancelScheduledTokenRefresh, performTokenRefresh } from '@/lib/auth/token-refresh'; +import type { AuthResponse } from '@/lib/auth/types'; + +import useAuthStore from '../store'; + +jest.mock('@/lib/logging', () => ({ + logger: { info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn() }, +})); + +jest.mock('@/lib/auth/api', () => ({ + loginRequest: jest.fn(), + refreshTokenRequest: jest.fn(), + clearPasswordVerificationHash: jest.fn().mockResolvedValue(undefined), + storePasswordVerificationHash: jest.fn().mockResolvedValue(undefined), +})); + +const authResponse = { + access_token: 'new-access', + refresh_token: 'new-refresh', + expires_in: 3600, +} as AuthResponse; + +describe('token refresh racing sign-out', () => { + afterEach(() => { + cancelScheduledTokenRefresh(); + jest.clearAllMocks(); + }); + + it('applies tokens for an active session', async () => { + useAuthStore.setState({ status: 'signedIn', accessToken: 'old-access', refreshToken: 'old-refresh' }); + (refreshTokenRequest as jest.Mock).mockResolvedValue(authResponse); + + await expect(performTokenRefresh()).resolves.toBe(true); + + const state = useAuthStore.getState(); + expect(state.status).toBe('signedIn'); + expect(state.accessToken).toBe('new-access'); + expect(state.refreshToken).toBe('new-refresh'); + }); + + it('does not resurrect the session when the refresh resolves after logout', async () => { + useAuthStore.setState({ status: 'signedIn', accessToken: 'old-access', refreshToken: 'old-refresh' }); + + // Refresh request in flight when the user logs out + let resolveRefresh: (value: AuthResponse) => void = () => {}; + (refreshTokenRequest as jest.Mock).mockReturnValue(new Promise((resolve) => (resolveRefresh = resolve))); + + const refreshPromise = performTokenRefresh(); + + // Logout wins the race: store cleared and signed out + useAuthStore.setState({ status: 'signedOut', accessToken: null, refreshToken: null, profile: null, userId: null }); + + resolveRefresh(authResponse); + await expect(refreshPromise).resolves.toBe(false); + + const state = useAuthStore.getState(); + expect(state.status).toBe('signedOut'); + expect(state.accessToken).toBeNull(); + expect(state.refreshToken).toBeNull(); + }); +}); diff --git a/src/stores/auth/store.tsx b/src/stores/auth/store.tsx index 862a482d..d4e9c9c0 100644 --- a/src/stores/auth/store.tsx +++ b/src/stores/auth/store.tsx @@ -6,7 +6,8 @@ import { createJSONStorage, persist } from 'zustand/middleware'; import { logger } from '@/lib/logging'; -import { clearPasswordVerificationHash, loginRequest, refreshTokenRequest, storePasswordVerificationHash } from '../../lib/auth/api'; +import { clearPasswordVerificationHash, loginRequest, storePasswordVerificationHash } from '../../lib/auth/api'; +import { cancelScheduledTokenRefresh, initTokenRefresh, performTokenRefresh, scheduleTokenRefresh } from '../../lib/auth/token-refresh'; import type { AuthResponse, AuthState, LoginCredentials } from '../../lib/auth/types'; import { type ProfileModel } from '../../lib/auth/types'; @@ -109,18 +110,7 @@ const useAuthStore = create()( }); // Set up automatic token refresh - //const decodedToken: { exp: number } = jwtDecode( - //); - //const now = new Date(); - //const expiresIn = - // response.authResponse?.expires_in! * 1000 - Date.now() - 60000; // Refresh 1 minute before expiry - //const expiresOn = new Date( - // now.getTime() + response.authResponse?.expires_in! * 1000 - //) - // .getTime() - // .toString(); - - //setTimeout(() => get().refreshAccessToken(), expiresIn); + scheduleTokenRefresh(response.authResponse.expires_in); } else { logger.error({ message: 'Login: API returned unsuccessful response', @@ -148,6 +138,9 @@ const useAuthStore = create()( message: 'Logout: Clearing auth state', }); + // Cancel any pending automatic refresh so the timer cannot fire after logout + cancelScheduledTokenRefresh(); + await clearPasswordVerificationHash(); set({ @@ -163,31 +156,9 @@ const useAuthStore = create()( }, refreshAccessToken: async () => { - try { - const { refreshToken } = get(); - if (!refreshToken) { - throw new Error('No refresh token available'); - } - - const response = await refreshTokenRequest(refreshToken); - - set({ - accessToken: response.access_token, - refreshToken: response.refresh_token, - status: 'signedIn', - error: null, - }); - - // Set up next token refresh - //const decodedToken: { exp: number } = jwt_decode( - // response.access_token - //); - const expiresIn = response.expires_in * 1000 - Date.now() - 60000; // Refresh 1 minute before expiry - setTimeout(() => get().refreshAccessToken(), expiresIn); - } catch (error) { - // If refresh fails, log out the user - get().logout(); - } + // Single-flight refresh shared with the axios 401 interceptor. Failure + // handling (logout) happens inside performTokenRefresh. + await performTokenRefresh(); }, isAuthenticated: (): boolean => { return get().status === 'signedIn' && get().accessToken !== null; @@ -242,6 +213,9 @@ const useAuthStore = create()( message: 'SSO: State updated to signedIn', context: { userId: profileData.sub }, }); + + // Set up automatic token refresh + scheduleTokenRefresh(authResponse.expires_in); } catch (error) { logger.error({ message: 'SSO: loginWithSso exception', @@ -272,4 +246,32 @@ const useAuthStore = create()( ) ); +// Wire the shared refresh engine to this store. Kept outside the store creator so the +// axios interceptor and the auth store share one single-flight refresh path. +initTokenRefresh({ + getRefreshToken: () => useAuthStore.getState().refreshToken, + applyAuthResponse: (response: AuthResponse) => { + // A refresh that raced sign-out must not resurrect the session: logout already + // cleared the store, so reject instead of applying. Throwing makes + // performTokenRefresh treat this as a failed refresh, which also stops it from + // rescheduling the refresh timer for the ended session. + if (useAuthStore.getState().status === 'signedOut') { + throw new Error('Token refresh completed after sign-out; discarding tokens'); + } + useAuthStore.setState({ + accessToken: response.access_token, + refreshToken: response.refresh_token, + status: 'signedIn', + error: null, + }); + }, + onRefreshFailed: () => { + // Avoid a logout re-entry loop if the failure was triggered by a timer that + // fired after the user already signed out. + if (useAuthStore.getState().status !== 'signedOut') { + void useAuthStore.getState().logout(); + } + }, +}); + export default useAuthStore; diff --git a/src/stores/chat/store.ts b/src/stores/chat/store.ts index aec48972..ee38a2ee 100644 --- a/src/stores/chat/store.ts +++ b/src/stores/chat/store.ts @@ -73,6 +73,13 @@ interface ChatState { // --- Channels --------------------------------------------------------- fetchChannels: (activeUnitId?: number) => Promise; + /** + * Chat channels anchored to one incident, kept out of the main channel list so a dispatcher's list + * isn't buried under every incident's channels. Includes archived ones: a closed incident's + * conversations stay readable as a point-in-time record. + */ + incidentChannelsByCallId: Record; + loadIncidentChannels: (callId: string) => Promise; setActiveChannel: (channelId: string | null) => void; // --- Messages --------------------------------------------------------- @@ -246,6 +253,25 @@ export const useChatStore = create()( // ------------------------------------------------------------------ // Channels // ------------------------------------------------------------------ + incidentChannelsByCallId: {}, + + loadIncidentChannels: async (callId: string) => { + const numericCallId = parseInt(callId, 10); + if (Number.isNaN(numericCallId)) { + return; + } + + try { + // callId narrows the list server-side; the local filter stays as a safety net + // because older servers ignore the param and return every channel. + const response = await chatApi.getChannels(undefined, true, numericCallId); + const forCall = (response.Data ?? []).filter((channel) => channel.CallId === numericCallId); + set((state) => ({ incidentChannelsByCallId: { ...state.incidentChannelsByCallId, [callId]: forCall } })); + } catch (error) { + logger.error({ message: 'chat: failed to load incident channels', context: { error, callId } }); + } + }, + fetchChannels: async (activeUnitId?: number) => { set({ isLoadingChannels: true }); try { @@ -840,6 +866,7 @@ export const useChatStore = create()( } set({ channels: [], + incidentChannelsByCallId: {}, messagesByChannel: {}, membersByChannel: {}, typingByChannel: {}, diff --git a/src/stores/security/__tests__/store.test.ts b/src/stores/security/__tests__/store.test.ts index 325d9e4c..8edec2d1 100644 --- a/src/stores/security/__tests__/store.test.ts +++ b/src/stores/security/__tests__/store.test.ts @@ -74,6 +74,8 @@ describe('useSecurityStore', () => { CanCreateCalls: true, CanAddNote: true, CanCreateMessage: true, + CanLoginToDispatchApp: true, + CanLoginToCommandApp: true, Groups: [ { GroupId: 1, diff --git a/src/stores/security/store.ts b/src/stores/security/store.ts index 68dda016..25537cac 100644 --- a/src/stores/security/store.ts +++ b/src/stores/security/store.ts @@ -80,6 +80,8 @@ export const useSecurityStore = () => { canUserCreateNotes: store.rights?.CanAddNote, canUserCreateMessages: store.rights?.CanCreateMessage, canUserViewPII: store.rights?.CanViewPII, + // Undefined (rights not loaded yet) is treated as allowed by callers; only an explicit false blocks. + canUserWorkCommand: store.rights?.CanLoginToCommandApp, departmentCode: store.rights?.DepartmentCode, rights: store.rights, }; diff --git a/src/translations/ar.json b/src/translations/ar.json index b547a265..406a35be 100644 --- a/src/translations/ar.json +++ b/src/translations/ar.json @@ -371,6 +371,124 @@ "audio_name": "مقطع صوتي" } }, + "chat": { + "title": "الدردشة", + "assistant": "المساعد", + "empty": "لا توجد محادثات بعد. ابدأ رسالة مباشرة أو أنشئ مجموعة.", + "section_assistant": "المساعد", + "section_direct_messages": "الرسائل المباشرة", + "section_channels": "القنوات", + "section_incidents": "الحوادث", + "new_direct_message": "رسالة مباشرة جديدة", + "new_group": "مجموعة جديدة", + "open_assistant": "فتح المساعد", + "create_conversation_failed": "تعذّر بدء المحادثة", + "group_name": "اسم المجموعة", + "search_people": "البحث عن أشخاص", + "no_people": "لم يتم العثور على أشخاص", + "create_group_with": "إنشاء مجموعة ({{count}})", + "message_deleted": "تم حذف هذه الرسالة", + "urgent": "عاجل", + "urgent_will_send": "سيتم إرسال هذه الرسالة كرسالة عاجلة", + "shared_location": "الموقع المشترك", + "thread_replies": "{{count}} ردود", + "edited": "(تم التعديل)", + "failed_tap_retry": "فشل - انقر لإعادة المحاولة", + "type_a_message": "اكتب رسالة", + "emoji": "رمز تعبيري", + "add_image": "إضافة صورة", + "add_gif": "إضافة GIF", + "share_location": "مشاركة الموقع", + "send": "إرسال", + "someone": "شخص ما", + "is_typing": "{{name}} يكتب...", + "are_typing": "{{count}} أشخاص يكتبون...", + "permission_photos_denied": "تم رفض إذن الوصول إلى مكتبة الصور", + "permission_location_denied": "تم رفض إذن الموقع", + "search_gifs": "البحث عن صور GIF", + "no_gifs": "لم يتم العثور على صور GIF", + "flag_reason": "لماذا تبلّغ عن هذا؟", + "flag_inappropriate": "غير لائق", + "flag_harassment": "تحرش", + "flag_spam": "بريد عشوائي", + "flag_sensitive": "معلومات حساسة", + "flag_policy": "انتهاك السياسة", + "flag_other": "أخرى", + "reply_in_thread": "الرد في المحادثة", + "copy": "نسخ", + "copied": "تم النسخ", + "copy_unavailable": "النسخ غير متاح على هذا الجهاز", + "edit": "تعديل", + "edit_message": "تعديل الرسالة", + "save": "حفظ", + "delete": "حذف", + "pin": "تثبيت", + "unpin": "إلغاء التثبيت", + "flag": "إبلاغ", + "moderator_delete": "إزالة (مشرف)", + "moderator_removed": "تمت الإزالة بواسطة المشرف", + "attachment_failed": "فشل رفع المرفق", + "ack_required": "الإقرار مطلوب", + "ack_pending_one": "لديك رسالة عاجلة بحاجة إلى إقرار", + "ack_pending_count": "لديك {{count}} رسائل عاجلة بحاجة إلى إقرار", + "acknowledge": "إقرار", + "thread": "المحادثة", + "original_message": "الرسالة الأصلية", + "reply_placeholder": "رد...", + "channel": "قناة", + "direct_message": "رسالة مباشرة", + "load_people_failed": "تعذّر تحميل الأشخاص", + "reaction_failed": "تعذّر تحديث التفاعل", + "edit_failed": "تعذّر تعديل الرسالة", + "delete_failed": "تعذّر حذف الرسالة", + "pin_failed": "تعذّر تحديث التثبيت", + "flag_failed": "تعذّر الإبلاغ عن الرسالة" + }, + "chatbot": { + "title": "المساعد", + "subtitle": "مساعد ذكاء اصطناعي لقسمك", + "new_session": "جلسة جديدة", + "empty": "اسأل المساعد عن أي شيء للبدء.", + "ask_placeholder": "اسأل المساعد..." + }, + "check_in": { + "tab_title": "تسجيل الحضور", + "timer_status": "حالة المؤقت", + "perform_check_in": "تسجيل", + "check_in_success": "تم تسجيل الحضور بنجاح", + "check_in_error": "فشل في تسجيل الحضور", + "checked_in_by": "بواسطة {{name}}", + "last_check_in": "آخر تسجيل", + "elapsed": "المنقضي", + "duration": "المدة", + "status_ok": "جيد", + "status_green": "جيد", + "status_warning": "تحذير", + "status_yellow": "تحذير", + "status_overdue": "متأخر", + "status_red": "متأخر", + "status_critical": "حرج", + "history": "سجل التسجيلات", + "no_timers": "لم يتم تكوين مؤقتات تسجيل الحضور", + "timers_disabled": "مؤقتات تسجيل الحضور معطلة لهذه المكالمة", + "type_personnel": "الأفراد", + "type_unit": "الوحدة", + "type_ic": "قائد الحادث", + "type_par": "PAR", + "type_hazmat": "التعرض للمواد الخطرة", + "type_sector_rotation": "تدوير القطاع", + "type_rehab": "إعادة التأهيل", + "add_note": "إضافة ملاحظة (اختياري)", + "confirm": "تأكيد التسجيل", + "minutes_ago": "منذ {{count}} دقيقة", + "select_target": "حدد الكيان للتسجيل", + "overdue_count": "{{count}} متأخر", + "warning_count": "{{count}} تحذير", + "enable_timers": "تفعيل المؤقتات", + "disable_timers": "تعطيل المؤقتات", + "summary": "{{overdue}} متأخر، {{warning}} تحذير، {{ok}} جيد", + "par_title": "متابعة الأفراد (PAR)" + }, "common": { "add": "إضافة", "back": "رجوع", @@ -502,178 +620,379 @@ "website": "الموقع الإلكتروني", "zip": "الرمز البريدي" }, - "form": { - "invalid_url": "يرجى إدخال عنوان URL صالح يبدأ بـ http:// أو https://", - "required": "هذا الحقل مطلوب" - }, - "livekit": { - "audio_devices": "أجهزة الصوت", - "audio_settings": "إعدادات الصوت", - "connected_to_room": "متصل بالقناة", - "connecting": "جاري الاتصال...", - "disconnect": "قطع الاتصال", - "join": "انضمام", - "microphone": "الميكروفون", - "mute": "كتم", - "no_rooms_available": "لا توجد قنوات صوتية متاحة", - "speaker": "السماعة", - "speaking": "يتحدث", - "title": "القنوات الصوتية", - "unmute": "إلغاء الكتم" - }, - "loading": { - "loading": "جاري التحميل...", - "loadingData": "جاري تحميل البيانات...", - "pleaseWait": "يرجى الانتظار", - "processingRequest": "جاري معالجة طلبك..." - }, - "sso": { - "authenticating": "جارٍ المصادقة...", - "back_to_login": "العودة إلى تسجيل الدخول", - "back_to_lookup": "تغيير المستخدم", - "continue_button": "متابعة", - "department_id_label": "معرف القسم", - "department_id_placeholder": "أدخل معرف القسم", - "error_generic": "فشل تسجيل الدخول. يرجى المحاولة مرة أخرى.", - "error_oidc_cancelled": "تم إلغاء تسجيل الدخول.", - "error_oidc_not_ready": "موفر SSO يتم تحميله، يرجى الانتظار.", - "error_sso_not_enabled": "تسجيل الدخول الموحد غير مفعل لهذا المستخدم.", - "error_token_exchange": "فشل إتمام تسجيل الدخول. يرجى المحاولة مرة أخرى.", - "error_user_not_found": "المستخدم غير موجود. يرجى التحقق والمحاولة مرة أخرى.", - "looking_up": "جارٍ البحث...", - "optional": "اختياري", - "page_subtitle": "أدخل اسم المستخدم للبحث عن خيارات تسجيل الدخول الخاصة بمؤسستك.", - "page_title": "تسجيل الدخول الموحد", - "provider_oidc": "OpenID Connect (OIDC)", - "provider_saml": "SAML 2.0", - "sign_in_button": "تسجيل الدخول عبر SSO", - "sign_in_title": "تسجيل الدخول", - "sso_button": "تسجيل دخول SSO" - }, - "login": { - "branding_subtitle": "برنامج إرسال قوي للمستجيبين الأوائل والبحث والإنقاذ ومنظمات السلامة العامة.", - "branding_title": "إدارة الاستجابة للطوارئ", - "errorModal": { - "confirmButton": "موافق", - "message": "يرجى التحقق من اسم المستخدم وكلمة المرور والمحاولة مرة أخرى.", - "title": "فشل تسجيل الدخول" - }, - "feature_dispatch_desc": "أرسل الوحدات وقم بإدارة المكالمات فوراً مع التحديثات المباشرة على جميع الأجهزة.", - "feature_dispatch_title": "الإرسال في الوقت الفعلي", - "feature_mapping_desc": "تتبع الوحدات في الوقت الفعلي مع خرائط تفصيلية وتوجيه وإدارة الموقع.", - "feature_mapping_title": "خرائط متقدمة", - "feature_personnel_desc": "قم بإدارة فريقك مع الوصول القائم على الأدوار وتتبع الحالة وأدوات الاتصال.", - "feature_personnel_title": "إدارة الموظفين", - "footer_text": "صنع بـ ❤️ في نيفادا", - "login": "تسجيل الدخول", - "login_button": "تسجيل الدخول", - "login_button_description": "قم بتسجيل الدخول إلى حسابك للمتابعة", - "login_button_error": "خطأ في تسجيل الدخول", - "login_button_loading": "تسجيل الدخول...", - "login_button_success": "تم تسجيل الدخول بنجاح", - "no_account": "ليس لديك حساب؟", - "page_subtitle": "أدخل بيانات الاعتماد الخاصة بك لتسجيل الدخول.", - "page_title": "Resgrid Dispatch", - "password": "كلمة المرور", - "password_incorrect": "كانت كلمة المرور غير صحيحة", - "password_placeholder": "أدخل كلمة المرور الخاصة بك", - "register": "تسجيل", - "title": "تسجيل الدخول", - "username": "اسم المستخدم", - "username_placeholder": "أدخل اسم المستخدم الخاص بك", - "welcome_title": "مرحباً بعودتك" - }, - "lockscreen": { - "message": "أدخل كلمة المرور لفتح الشاشة", - "not_you": "لست أنت؟ العودة إلى تسجيل الدخول", - "password": "كلمة المرور", - "password_placeholder": "أدخل كلمة المرور الخاصة بك", - "title": "شاشة القفل", - "unlock_button": "فتح", - "unlock_failed": "فشل الفتح. يرجى المحاولة مرة أخرى.", - "unlocking": "جاري الفتح...", - "welcome_back": "مرحبا بعودتك", - "relogin_required": "التحقق من كلمة المرور غير متاح لهذه الجلسة. يرجى تسجيل الدخول مرة أخرى." - }, - "maintenance": { - "downtime_message": "نحن نعمل بجد لإكمال الصيانة في أسرع وقت ممكن. يرجى التحقق مرة أخرى قريبا.", - "downtime_title": "ما هو وقت التوقف؟", - "message": "يرجى التحقق مرة أخرى في وقت ما.", - "support_message": "إذا كنت بحاجة إلى مساعدة، يرجى الاتصال بنا على", - "support_title": "هل تحتاج إلى دعم؟", - "title": "الموقع قيد الصيانة", - "why_down_message": "نحن نقوم بصيانة مجدولة لتحسين تجربتك. نعتذر عن أي إزعاج.", - "why_down_title": "لماذا الموقع معطل؟" - }, - "menu": { - "calls": "المكالمات", - "calls_list": "قائمة المكالمات", - "scheduled_calls": "المكالمات المجدولة", - "contacts": "جهات الاتصال", - "home": "الرئيسية", - "map": "الخريطة", - "menu": "القائمة", - "messages": "الرسائل", - "new_call": "مكالمة جديدة", - "personnel": "الموظفون", - "pois": "نقاط الاهتمام", - "protocols": "البروتوكولات", - "settings": "الإعدادات", + "dispatch": { + "active_calls": "المكالمات النشطة", + "pending_calls": "معلقة", + "scheduled_calls": "مجدولة", + "units_available": "متاحة", + "personnel_available": "متاحين", + "personnel_on_duty": "في الخدمة", "units": "الوحدات", - "weatherAlerts": "تنبيهات الطقس", - "incident_command": "قيادة الحادث", - "chat": "الدردشة", - "assistant": "المساعد" - }, - "pois": { - "address": "العنوان", - "all_types": "كل الأنواع", - "destination": "وجهة", - "details": "التفاصيل", - "detail_not_found": "تعذر العثور على نقطة الاهتمام", - "detail_not_found_description": "تعذر تحميل نقطة الاهتمام المطلوبة.", - "detail_title": "تفاصيل نقطة الاهتمام", - "empty": "لا توجد نقاط اهتمام", - "empty_description": "لا توجد نقاط اهتمام متاحة لقسمك حتى الآن.", - "empty_filtered": "لا توجد نقاط اهتمام مطابقة", - "empty_filtered_description": "حاول مسح البحث أو اختيار نوع مختلف.", - "filter_by_type": "تصفية حسب نوع نقطة الاهتمام", - "invalid_poi": "نقطة اهتمام غير صالحة", - "invalid_poi_description": "معرّف نقطة الاهتمام المحدد غير صالح.", - "loading": "جارٍ تحميل نقاط الاهتمام...", - "loading_detail": "جارٍ تحميل تفاصيل نقطة الاهتمام...", + "personnel": "الموظفين", "map": "الخريطة", - "no_location": "لا يوجد موقع متاح", - "no_location_description": "لا تحتوي نقطة الاهتمام هذه على إحداثيات قابلة للاستخدام.", - "no_location_for_routing": "لا توجد بيانات موقع متاحة للتوجيه", - "note": "ملاحظة", - "route_error": "فشل في فتح تطبيق الخرائط", - "search": "ابحث في نقاط الاهتمام...", - "sort": "ترتيب", - "sort_options": { - "address-asc": "العنوان", - "name-asc": "الاسم (أ-ي)", - "name-desc": "الاسم (ي-أ)", - "type-asc": "النوع" + "notes": "الملاحظات", + "activity_log": "سجل النشاط", + "communications": "الاتصالات", + "no_active_calls": "لا توجد مكالمات نشطة", + "no_units": "لا توجد وحدات متاحة", + "no_personnel": "لا يوجد موظفين متاحين", + "no_notes": "لا توجد ملاحظات متاحة", + "no_activity": "لا يوجد نشاط حديث", + "current_channel": "القناة الحالية", + "audio_stream": "البث الصوتي", + "no_stream": "لا يوجد بث نشط", + "ptt": "اضغط للتحدث", + "ptt_start": "بدء الإرسال", + "ptt_end": "انتهاء الإرسال", + "transmitting_on": "جاري الإرسال على {{channel}}", + "transmission_ended": "انتهى الإرسال", + "voice_disabled": "تم تعطيل الصوت", + "disconnected": "غير متصل", + "select_channel": "اختر القناة", + "select_channel_description": "اختر قناة صوتية للاتصال بها", + "change_channel_warning": "اختيار قناة جديدة سيؤدي إلى قطع الاتصال من القناة الحالية", + "default_channel": "افتراضي", + "no_channels_available": "لا توجد قنوات صوتية متاحة", + "system_update": "تحديث النظام", + "data_refreshed": "تم تحديث البيانات من الخادم", + "call_selected": "تم تحديد المكالمة", + "unit_selected": "تم تحديد الوحدة", + "unit_deselected": "تم إلغاء تحديد الوحدة", + "personnel_selected": "تم تحديد الموظف", + "personnel_deselected": "تم إلغاء تحديد الموظف", + "loading_map": "جاري تحميل الخريطة...", + "map_not_available_web": "الخريطة غير متاحة على منصة الويب", + "filtering_by_call": "تصفية حسب المكالمة", + "clear_filter": "مسح الفلتر", + "call_filter_active": "فلتر المكالمة نشط", + "call_filter_cleared": "تم مسح فلتر المكالمة", + "showing_all_data": "عرض جميع البيانات", + "call_notes": "ملاحظات المكالمة", + "no_call_notes": "لا توجد ملاحظات للمكالمة", + "add_call_note_placeholder": "أضف ملاحظة...", + "note_added": "تمت إضافة الملاحظة", + "note_added_to_console": "تمت إضافة ملاحظة جديدة إلى وحدة التحكم", + "add_note_title": "إضافة ملاحظة جديدة", + "note_title_label": "العنوان", + "note_title_placeholder": "أدخل عنوان الملاحظة...", + "note_category_label": "الفئة", + "note_category_placeholder": "اختر فئة", + "note_no_category": "بدون فئة", + "note_body_label": "محتوى الملاحظة", + "note_body_placeholder": "أدخل محتوى الملاحظة...", + "note_save_error": "فشل حفظ الملاحظة: {{error}}", + "note_created": "تم إنشاء الملاحظة", + "units_on_call": "الوحدات في المكالمة", + "no_units_on_call": "لا توجد وحدات في المكالمة", + "personnel_on_call": "الموظفين في المكالمة", + "no_personnel_on_call": "لا يوجد موظفين في المكالمة", + "call_activity": "نشاط المكالمة", + "no_call_activity": "لا يوجد نشاط للمكالمة", + "on_call": "في مكالمة", + "filtered": "مفلتر", + "active_filter": "فلتر نشط", + "unit_status_change": "تغيير حالة الوحدة", + "personnel_status_change": "تغيير حالة الموظف", + "view_call_details": "عرض تفاصيل المكالمة", + "dispatched_resources": "الموارد المرسلة", + "unassigned": "غير مخصص", + "available": "متاح", + "unknown": "غير معروف", + "search_personnel_placeholder": "بحث عن موظف...", + "search_calls_placeholder": "بحث عن مكالمات...", + "search_units_placeholder": "بحث عن وحدات...", + "search_notes_placeholder": "بحث في الملاحظات...", + "signalr_update": "تحديث في الوقت الفعلي", + "signalr_connected": "متصل", + "realtime_updates_active": "التحديثات في الوقت الفعلي نشطة الآن", + "personnel_status_updated": "تم تحديث حالة الموظف", + "personnel_staffing_updated": "تم تحديث تعيين الموظف", + "unit_status_updated": "تم تحديث حالة الوحدة", + "calls_updated": "تم تحديث المكالمات", + "call_added": "تمت إضافة مكالمة جديدة", + "call_closed": "تم إغلاق المكالمة", + "check_ins": "تسجيلات الحضور", + "no_check_ins": "لا توجد مكالمات بمؤقتات تسجيل الحضور", + "radio_log": "سجل الراديو", + "radio": "الراديو", + "activity": "النشاط", + "actions": "الإجراءات", + "no_radio_activity": "لا توجد إرسالات راديو", + "live": "مباشر", + "currently_transmitting": "جاري الإرسال حالياً...", + "duration": "المدة", + "call_actions": "إجراءات المكالمة", + "unit_actions": "إجراءات الوحدة", + "personnel_actions": { + "title": "إجراءات الموظف", + "status_tab": "الحالة", + "staffing_tab": "التعيين", + "select_status": "اختر الحالة", + "select_staffing": "اختر مستوى التعيين", + "destination": "الوجهة", + "no_destination": "بدون وجهة", + "note": "ملاحظة", + "note_placeholder": "أضف ملاحظة اختيارية...", + "update_status": "تحديث الحالة", + "update_staffing": "تحديث التعيين", + "no_statuses_available": "لا توجد حالات متاحة", + "no_staffings_available": "لا توجد مستويات تعيين متاحة" }, - "title": "نقاط الاهتمام", - "type": "النوع", - "unknown_type": "نوع غير معروف", - "unnamed": "نقطة اهتمام بدون اسم" + "unit_actions_panel": { + "status": "الحالة", + "select_status": "اختر الحالة", + "destination": "الوجهة", + "no_destination": "بدون وجهة", + "note": "ملاحظة", + "note_placeholder": "أضف ملاحظة اختيارية...", + "update_status": "تحديث الحالة", + "no_statuses_available": "لا توجد حالات متاحة", + "no_active_calls": "لا توجد مكالمات نشطة", + "no_stations_available": "لا توجد محطات متاحة", + "no_destinations_available": "لا توجد وجهات متاحة" + }, + "call": "مكالمة", + "station": "محطة", + "calls": "المكالمات", + "stations": "المحطات", + "no_stations_available": "لا توجد محطات متاحة", + "new_call": "مكالمة جديدة", + "view_details": "التفاصيل", + "add_note": "إضافة ملاحظة", + "close_call": "إغلاق", + "set_status": "تعيين الحالة", + "set_staffing": "التعيين", + "dispatch": "إرسال", + "select_items_for_actions": "حدد مكالمة أو وحدة أو موظف لتفعيل الإجراءات السياقية", + "weather": { + "clear": "صافٍ", + "mainly_clear": "صافٍ غالبًا", + "partly_cloudy": "غائم جزئيًا", + "overcast": "غائم", + "fog": "ضباب", + "drizzle": "رذاذ", + "freezing_drizzle": "رذاذ متجمد", + "rain": "مطر", + "freezing_rain": "مطر متجمد", + "snow": "ثلج", + "rain_showers": "زخات مطر", + "snow_showers": "زخات ثلج", + "thunderstorm": "عاصفة رعدية", + "thunderstorm_hail": "عاصفة رعدية مع بَرَد", + "unknown": "غير معروف" + }, + "available_only": "المتاح فقط", + "single_list": "قائمة واحدة", + "resources": "الموارد", + "search_resources_placeholder": "البحث عن الموارد...", + "no_resources": "لا توجد موارد" }, - "scheduled_calls": { - "title": "المكالمات المجدولة", - "loading": "تحميل المكالمات المجدولة...", - "no_scheduled_calls": "لا توجد مكالمات مجدولة", - "no_scheduled_calls_description": "لا توجد مكالمات مجدولة معلقة في الوقت الحالي.", - "search": "البحث في المكالمات المجدولة...", - "scheduled_for": "مجدولة ل", - "table_number": "رقم البلاغ", - "table_name": "الاسم", - "table_type": "النوع", - "table_priority": "الأولوية", - "table_address": "العنوان", - "table_scheduled": "مجدول في" + "form": { + "invalid_url": "يرجى إدخال عنوان URL صالح يبدأ بـ http:// أو https://", + "required": "هذا الحقل مطلوب" + }, + "incident_command": { + "accountability": "متابعة الأفراد (PAR)", + "acknowledge": "إقرار", + "action_plan": "خطة العمل", + "action_plan_placeholder": "صف خطة عمل الحادث...", + "active": "نشطة", + "active_title": "قيادات الحوادث النشطة", + "add": "إضافة", + "add_channel": "إضافة قناة", + "add_lane": "إضافة قطاع", + "add_marker": "إضافة علامة", + "add_objective": "إضافة هدف", + "annotations": "تعليقات الخريطة", + "assign": "تعيين", + "assign_resource": "تعيين مورد", + "assign_resource_required": "حدد قطاعًا وموردًا", + "assign_role": "تعيين دور", + "assign_role_required": "حدد شخصًا ودورًا", + "call": "البلاغ", + "channel_name": "اسم القناة", + "chat": "Chat", + "chat_frozen": "This incident is closed. Conversations are kept as a point-in-time record — no new messages.", + "close_all_channels": "إغلاق جميع القنوات", + "close_command": "إنهاء القيادة", + "closed": "مغلقة", + "command_channel": "Command chat", + "command_channel_hint": "Command staff and dispatch", + "command_channel_unavailable": "No command channel has been created for this incident yet.", + "commander": "قائد الحادث", + "complete": "إكمال", + "completed": "مكتمل", + "confirm_close": "هل تريد إنهاء قيادة الحادث لهذا البلاغ؟", + "critical": "حرج", + "delete_annotation_confirm": "هل تريد إزالة هذا التعليق؟", + "dispatch_channel": "Dispatch", + "dispatch_channel_hint": "The incident's line to the desk", + "dispatch_channel_unavailable": "No dispatch channel has been created for this incident yet.", + "dm_failed": "Couldn't open that conversation.", + "dm_unavailable": "That contact has no Resgrid account to message.", + "due": "موعد الاستحقاق", + "edit": "تعديل", + "edit_action_plan": "تعديل خطة العمل", + "establish": "إنشاء القيادة", + "establish_description": "يمكنك اختياريًا إعداد لوحة القيادة من قالب.", + "establish_error": "تعذر إنشاء القيادة", + "establish_success": "تم إنشاء قيادة الحادث", + "establish_title": "إنشاء قيادة الحادث", + "established_on": "تاريخ الإنشاء", + "green": "أخضر", + "hold_to_talk": "اضغط مطولًا للتحدث", + "incident_channel": "Incident chat", + "incident_channel_hint": "Everyone working the incident", + "incident_channel_unavailable": "No incident channel has been created for this call yet.", + "join": "انضمام", + "lane": "القطاع", + "lane_name": "اسم القطاع", + "lane_type": "نوع القطاع", + "marker": "علامة", + "marker_label": "تسمية العلامة", + "move": "نقل", + "move_lane": "نقل القطاع", + "move_resource": "نقل المورد", + "name_required": "الاسم مطلوب", + "no_accountability": "لا يوجد أفراد قيد المتابعة.", + "no_action_plan": "لم يتم تعيين خطة عمل.", + "no_active": "لا توجد قيادات حوادث نشطة", + "no_active_description": "ستظهر هنا قيادات الحوادث التي تم إنشاؤها للبلاغات.", + "no_annotations": "لا توجد تعليقات.", + "no_channels": "لا توجد قنوات مفتوحة.", + "no_command": "لم يتم إنشاء قيادة للحادث", + "no_command_description": "أنشئ قيادة للحادث لتنسيق الموارد والأدوار والأهداف ومتابعة الأفراد لهذا البلاغ.", + "no_lanes": "لم يتم تحديد قطاعات.", + "no_objectives": "لا توجد أهداف.", + "no_resources": "لم يتم تعيين موارد.", + "no_roles": "لم يتم تعيين أدوار.", + "no_template": "بدون قالب (لوحة فارغة)", + "no_timeline": "لا توجد إدخالات في التسلسل الزمني.", + "no_timers": "لا توجد مؤقتات قيد التشغيل.", + "not_authorized": "Command board unavailable", + "not_authorized_description": "Your department hasn't authorized you to work incident command. Ask an administrator for the Command App Login permission.", + "objective_name": "الهدف", + "objective_type": "النوع", + "objectives": "الأهداف", + "open_chat": "Open", + "open_full_board": "فتح اللوحة الكاملة", + "open_tactical_map": "فتح الخريطة التكتيكية", + "parent_lane": "القطاع الأصل", + "person": "الشخص", + "personnel": "الأفراد", + "release": "تحرير", + "resource": "المورد", + "resource_type": "نوع المورد", + "role": "الدور", + "roles": "أدوار القيادة", + "run_par": "تنفيذ PAR", + "save": "حفظ", + "save_error": "فشلت العملية", + "saved": "تم الحفظ", + "select_lane": "حدد قطاعًا", + "select_person": "حدد شخصًا", + "select_resource": "حدد موردًا", + "select_role": "حدد دورًا", + "send_message": "Message", + "status": "الحالة", + "structure": "هيكل القيادة", + "tab_title": "القيادة", + "tactical_map": "الخريطة التكتيكية", + "talking": "جارٍ الإرسال...", + "tap_to_place": "اضغط على الخريطة لوضع علامة", + "template": "قالب", + "timeline": "التسلسل الزمني للقيادة", + "timers": "المؤقتات", + "title": "قيادة الحادث", + "top_level": "المستوى الأعلى", + "transfer": "نقل", + "transfer_command": "نقل", + "transfer_notes": "ملاحظات", + "transfer_success": "تم نقل القيادة", + "transfer_title": "نقل القيادة", + "unassigned": "غير معيّن", + "unit": "الوحدة", + "voice_channels": "القنوات الصوتية", + "voice_join_error": "تعذر الانضمام إلى القناة الصوتية", + "voice_joined": "تم الانضمام إلى القناة الصوتية", + "warning": "تحذير" + }, + "livekit": { + "audio_devices": "أجهزة الصوت", + "audio_settings": "إعدادات الصوت", + "connected_to_room": "متصل بالقناة", + "connecting": "جاري الاتصال...", + "disconnect": "قطع الاتصال", + "join": "انضمام", + "microphone": "الميكروفون", + "mute": "كتم", + "no_rooms_available": "لا توجد قنوات صوتية متاحة", + "speaker": "السماعة", + "speaking": "يتحدث", + "title": "القنوات الصوتية", + "unmute": "إلغاء الكتم" + }, + "loading": { + "loading": "جاري التحميل...", + "loadingData": "جاري تحميل البيانات...", + "pleaseWait": "يرجى الانتظار", + "processingRequest": "جاري معالجة طلبك..." + }, + "lockscreen": { + "message": "أدخل كلمة المرور لفتح الشاشة", + "not_you": "لست أنت؟ العودة إلى تسجيل الدخول", + "password": "كلمة المرور", + "password_placeholder": "أدخل كلمة المرور الخاصة بك", + "title": "شاشة القفل", + "unlock_button": "فتح", + "unlock_failed": "فشل الفتح. يرجى المحاولة مرة أخرى.", + "unlocking": "جاري الفتح...", + "welcome_back": "مرحبا بعودتك", + "relogin_required": "التحقق من كلمة المرور غير متاح لهذه الجلسة. يرجى تسجيل الدخول مرة أخرى." + }, + "login": { + "branding_subtitle": "برنامج إرسال قوي للمستجيبين الأوائل والبحث والإنقاذ ومنظمات السلامة العامة.", + "branding_title": "إدارة الاستجابة للطوارئ", + "dispatch_not_authorized": "You are not authorized to use the Dispatch app. Contact your department administrator.", + "errorModal": { + "confirmButton": "موافق", + "message": "يرجى التحقق من اسم المستخدم وكلمة المرور والمحاولة مرة أخرى.", + "title": "فشل تسجيل الدخول" + }, + "feature_dispatch_desc": "أرسل الوحدات وقم بإدارة المكالمات فوراً مع التحديثات المباشرة على جميع الأجهزة.", + "feature_dispatch_title": "الإرسال في الوقت الفعلي", + "feature_mapping_desc": "تتبع الوحدات في الوقت الفعلي مع خرائط تفصيلية وتوجيه وإدارة الموقع.", + "feature_mapping_title": "خرائط متقدمة", + "feature_personnel_desc": "قم بإدارة فريقك مع الوصول القائم على الأدوار وتتبع الحالة وأدوات الاتصال.", + "feature_personnel_title": "إدارة الموظفين", + "footer_text": "صنع بـ ❤️ في نيفادا", + "login": "تسجيل الدخول", + "login_button": "تسجيل الدخول", + "login_button_description": "قم بتسجيل الدخول إلى حسابك للمتابعة", + "login_button_error": "خطأ في تسجيل الدخول", + "login_button_loading": "تسجيل الدخول...", + "login_button_success": "تم تسجيل الدخول بنجاح", + "no_account": "ليس لديك حساب؟", + "page_subtitle": "أدخل بيانات الاعتماد الخاصة بك لتسجيل الدخول.", + "page_title": "Resgrid Dispatch", + "password": "كلمة المرور", + "password_incorrect": "كانت كلمة المرور غير صحيحة", + "password_placeholder": "أدخل كلمة المرور الخاصة بك", + "register": "تسجيل", + "title": "تسجيل الدخول", + "username": "اسم المستخدم", + "username_placeholder": "أدخل اسم المستخدم الخاص بك", + "welcome_title": "مرحباً بعودتك" + }, + "maintenance": { + "downtime_message": "نحن نعمل بجد لإكمال الصيانة في أسرع وقت ممكن. يرجى التحقق مرة أخرى قريبا.", + "downtime_title": "ما هو وقت التوقف؟", + "message": "يرجى التحقق مرة أخرى في وقت ما.", + "support_message": "إذا كنت بحاجة إلى مساعدة، يرجى الاتصال بنا على", + "support_title": "هل تحتاج إلى دعم؟", + "title": "الموقع قيد الصيانة", + "why_down_message": "نحن نقوم بصيانة مجدولة لتحسين تجربتك. نعتذر عن أي إزعاج.", + "why_down_title": "لماذا الموقع معطل؟" }, "map": { "call_set_as_current": "تم تعيين المكالمة كمكالمة حالية", @@ -690,6 +1009,26 @@ "view_poi_details": "عرض تفاصيل نقطة الاهتمام", "hide_all": "إخفاء الكل" }, + "menu": { + "calls": "المكالمات", + "calls_list": "قائمة المكالمات", + "scheduled_calls": "المكالمات المجدولة", + "contacts": "جهات الاتصال", + "home": "الرئيسية", + "map": "الخريطة", + "menu": "القائمة", + "messages": "الرسائل", + "new_call": "مكالمة جديدة", + "personnel": "الموظفون", + "pois": "نقاط الاهتمام", + "protocols": "البروتوكولات", + "settings": "الإعدادات", + "units": "الوحدات", + "weatherAlerts": "تنبيهات الطقس", + "incident_command": "قيادة الحادث", + "chat": "الدردشة", + "assistant": "المساعد" + }, "notes": { "actions": { "add": "إضافة ملاحظة", @@ -709,6 +1048,23 @@ "search": "البحث في الملاحظات...", "title": "الملاحظات" }, + "onboarding": { + "screen1": { + "title": "Resgrid Dispatch", + "description": "إنشاء وإرسال وإدارة مكالمات الطوارئ مع مركز قيادة متنقل قوي في متناول يدك" + }, + "screen2": { + "title": "الوعي الظرفي في الوقت الفعلي", + "description": "تتبع جميع الوحدات والأفراد والموارد على خريطة تفاعلية مع تحديثات الحالة المباشرة وAVL" + }, + "screen3": { + "title": "التنسيق السلس", + "description": "تواصل فورًا مع الوحدات الميدانية، وقم بتحديث حالات المكالمات، وتنسيق جهود الاستجابة من أي مكان" + }, + "skip": "تخطي", + "next": "التالي", + "getStarted": "لنبدأ" + }, "personnel": { "title": "الأفراد", "search": "البحث عن الأفراد...", @@ -741,22 +1097,41 @@ "send_email": "بريد إلكتروني", "custom_fields": "معلومات إضافية" }, - "onboarding": { - "screen1": { - "title": "Resgrid Dispatch", - "description": "إنشاء وإرسال وإدارة مكالمات الطوارئ مع مركز قيادة متنقل قوي في متناول يدك" - }, - "screen2": { - "title": "الوعي الظرفي في الوقت الفعلي", - "description": "تتبع جميع الوحدات والأفراد والموارد على خريطة تفاعلية مع تحديثات الحالة المباشرة وAVL" - }, - "screen3": { - "title": "التنسيق السلس", - "description": "تواصل فورًا مع الوحدات الميدانية، وقم بتحديث حالات المكالمات، وتنسيق جهود الاستجابة من أي مكان" + "pois": { + "address": "العنوان", + "all_types": "كل الأنواع", + "destination": "وجهة", + "details": "التفاصيل", + "detail_not_found": "تعذر العثور على نقطة الاهتمام", + "detail_not_found_description": "تعذر تحميل نقطة الاهتمام المطلوبة.", + "detail_title": "تفاصيل نقطة الاهتمام", + "empty": "لا توجد نقاط اهتمام", + "empty_description": "لا توجد نقاط اهتمام متاحة لقسمك حتى الآن.", + "empty_filtered": "لا توجد نقاط اهتمام مطابقة", + "empty_filtered_description": "حاول مسح البحث أو اختيار نوع مختلف.", + "filter_by_type": "تصفية حسب نوع نقطة الاهتمام", + "invalid_poi": "نقطة اهتمام غير صالحة", + "invalid_poi_description": "معرّف نقطة الاهتمام المحدد غير صالح.", + "loading": "جارٍ تحميل نقاط الاهتمام...", + "loading_detail": "جارٍ تحميل تفاصيل نقطة الاهتمام...", + "map": "الخريطة", + "no_location": "لا يوجد موقع متاح", + "no_location_description": "لا تحتوي نقطة الاهتمام هذه على إحداثيات قابلة للاستخدام.", + "no_location_for_routing": "لا توجد بيانات موقع متاحة للتوجيه", + "note": "ملاحظة", + "route_error": "فشل في فتح تطبيق الخرائط", + "search": "ابحث في نقاط الاهتمام...", + "sort": "ترتيب", + "sort_options": { + "address-asc": "العنوان", + "name-asc": "الاسم (أ-ي)", + "name-desc": "الاسم (ي-أ)", + "type-asc": "النوع" }, - "skip": "تخطي", - "next": "التالي", - "getStarted": "لنبدأ" + "title": "نقاط الاهتمام", + "type": "النوع", + "unknown_type": "نوع غير معروف", + "unnamed": "نقطة اهتمام بدون اسم" }, "protocols": { "details": { @@ -796,6 +1171,20 @@ "tap_to_manage": "انقر لإدارة الأدوار", "unassigned": "غير معين" }, + "scheduled_calls": { + "title": "المكالمات المجدولة", + "loading": "تحميل المكالمات المجدولة...", + "no_scheduled_calls": "لا توجد مكالمات مجدولة", + "no_scheduled_calls_description": "لا توجد مكالمات مجدولة معلقة في الوقت الحالي.", + "search": "البحث في المكالمات المجدولة...", + "scheduled_for": "مجدولة ل", + "table_number": "رقم البلاغ", + "table_name": "الاسم", + "table_type": "النوع", + "table_priority": "الأولوية", + "table_address": "العنوان", + "table_scheduled": "مجدول في" + }, "settings": { "about": "حول التطبيق", "account": "الحساب", @@ -884,6 +1273,29 @@ "version": "الإصدار", "website": "الموقع الإلكتروني" }, + "sso": { + "authenticating": "جارٍ المصادقة...", + "back_to_login": "العودة إلى تسجيل الدخول", + "back_to_lookup": "تغيير المستخدم", + "continue_button": "متابعة", + "department_id_label": "معرف القسم", + "department_id_placeholder": "أدخل معرف القسم", + "error_generic": "فشل تسجيل الدخول. يرجى المحاولة مرة أخرى.", + "error_oidc_cancelled": "تم إلغاء تسجيل الدخول.", + "error_oidc_not_ready": "موفر SSO يتم تحميله، يرجى الانتظار.", + "error_sso_not_enabled": "تسجيل الدخول الموحد غير مفعل لهذا المستخدم.", + "error_token_exchange": "فشل إتمام تسجيل الدخول. يرجى المحاولة مرة أخرى.", + "error_user_not_found": "المستخدم غير موجود. يرجى التحقق والمحاولة مرة أخرى.", + "looking_up": "جارٍ البحث...", + "optional": "اختياري", + "page_subtitle": "أدخل اسم المستخدم للبحث عن خيارات تسجيل الدخول الخاصة بمؤسستك.", + "page_title": "تسجيل الدخول الموحد", + "provider_oidc": "OpenID Connect (OIDC)", + "provider_saml": "SAML 2.0", + "sign_in_button": "تسجيل الدخول عبر SSO", + "sign_in_title": "تسجيل الدخول", + "sso_button": "تسجيل دخول SSO" + }, "status": { "add_note": "إضافة ملاحظة", "all_destinations_enabled": "يمكن الاستجابة للمكالمات أو المحطات أو نقاط الاهتمام", @@ -929,211 +1341,6 @@ "shifts": "المناوبات", "personnel": "الموظفون" }, - "dispatch": { - "active_calls": "المكالمات النشطة", - "pending_calls": "معلقة", - "scheduled_calls": "مجدولة", - "units_available": "متاحة", - "personnel_available": "متاحين", - "personnel_on_duty": "في الخدمة", - "units": "الوحدات", - "personnel": "الموظفين", - "map": "الخريطة", - "notes": "الملاحظات", - "activity_log": "سجل النشاط", - "communications": "الاتصالات", - "no_active_calls": "لا توجد مكالمات نشطة", - "no_units": "لا توجد وحدات متاحة", - "no_personnel": "لا يوجد موظفين متاحين", - "no_notes": "لا توجد ملاحظات متاحة", - "no_activity": "لا يوجد نشاط حديث", - "current_channel": "القناة الحالية", - "audio_stream": "البث الصوتي", - "no_stream": "لا يوجد بث نشط", - "ptt": "اضغط للتحدث", - "ptt_start": "بدء الإرسال", - "ptt_end": "انتهاء الإرسال", - "transmitting_on": "جاري الإرسال على {{channel}}", - "transmission_ended": "انتهى الإرسال", - "voice_disabled": "تم تعطيل الصوت", - "disconnected": "غير متصل", - "select_channel": "اختر القناة", - "select_channel_description": "اختر قناة صوتية للاتصال بها", - "change_channel_warning": "اختيار قناة جديدة سيؤدي إلى قطع الاتصال من القناة الحالية", - "default_channel": "افتراضي", - "no_channels_available": "لا توجد قنوات صوتية متاحة", - "system_update": "تحديث النظام", - "data_refreshed": "تم تحديث البيانات من الخادم", - "call_selected": "تم تحديد المكالمة", - "unit_selected": "تم تحديد الوحدة", - "unit_deselected": "تم إلغاء تحديد الوحدة", - "personnel_selected": "تم تحديد الموظف", - "personnel_deselected": "تم إلغاء تحديد الموظف", - "loading_map": "جاري تحميل الخريطة...", - "map_not_available_web": "الخريطة غير متاحة على منصة الويب", - "filtering_by_call": "تصفية حسب المكالمة", - "clear_filter": "مسح الفلتر", - "call_filter_active": "فلتر المكالمة نشط", - "call_filter_cleared": "تم مسح فلتر المكالمة", - "showing_all_data": "عرض جميع البيانات", - "call_notes": "ملاحظات المكالمة", - "no_call_notes": "لا توجد ملاحظات للمكالمة", - "add_call_note_placeholder": "أضف ملاحظة...", - "note_added": "تمت إضافة الملاحظة", - "note_added_to_console": "تمت إضافة ملاحظة جديدة إلى وحدة التحكم", - "add_note_title": "إضافة ملاحظة جديدة", - "note_title_label": "العنوان", - "note_title_placeholder": "أدخل عنوان الملاحظة...", - "note_category_label": "الفئة", - "note_category_placeholder": "اختر فئة", - "note_no_category": "بدون فئة", - "note_body_label": "محتوى الملاحظة", - "note_body_placeholder": "أدخل محتوى الملاحظة...", - "note_save_error": "فشل حفظ الملاحظة: {{error}}", - "note_created": "تم إنشاء الملاحظة", - "units_on_call": "الوحدات في المكالمة", - "no_units_on_call": "لا توجد وحدات في المكالمة", - "personnel_on_call": "الموظفين في المكالمة", - "no_personnel_on_call": "لا يوجد موظفين في المكالمة", - "call_activity": "نشاط المكالمة", - "no_call_activity": "لا يوجد نشاط للمكالمة", - "on_call": "في مكالمة", - "filtered": "مفلتر", - "active_filter": "فلتر نشط", - "unit_status_change": "تغيير حالة الوحدة", - "personnel_status_change": "تغيير حالة الموظف", - "view_call_details": "عرض تفاصيل المكالمة", - "dispatched_resources": "الموارد المرسلة", - "unassigned": "غير مخصص", - "available": "متاح", - "unknown": "غير معروف", - "search_personnel_placeholder": "بحث عن موظف...", - "search_calls_placeholder": "بحث عن مكالمات...", - "search_units_placeholder": "بحث عن وحدات...", - "search_notes_placeholder": "بحث في الملاحظات...", - "signalr_update": "تحديث في الوقت الفعلي", - "signalr_connected": "متصل", - "realtime_updates_active": "التحديثات في الوقت الفعلي نشطة الآن", - "personnel_status_updated": "تم تحديث حالة الموظف", - "personnel_staffing_updated": "تم تحديث تعيين الموظف", - "unit_status_updated": "تم تحديث حالة الوحدة", - "calls_updated": "تم تحديث المكالمات", - "call_added": "تمت إضافة مكالمة جديدة", - "call_closed": "تم إغلاق المكالمة", - "check_ins": "تسجيلات الحضور", - "no_check_ins": "لا توجد مكالمات بمؤقتات تسجيل الحضور", - "radio_log": "سجل الراديو", - "radio": "الراديو", - "activity": "النشاط", - "actions": "الإجراءات", - "no_radio_activity": "لا توجد إرسالات راديو", - "live": "مباشر", - "currently_transmitting": "جاري الإرسال حالياً...", - "duration": "المدة", - "call_actions": "إجراءات المكالمة", - "unit_actions": "إجراءات الوحدة", - "personnel_actions": { - "title": "إجراءات الموظف", - "status_tab": "الحالة", - "staffing_tab": "التعيين", - "select_status": "اختر الحالة", - "select_staffing": "اختر مستوى التعيين", - "destination": "الوجهة", - "no_destination": "بدون وجهة", - "note": "ملاحظة", - "note_placeholder": "أضف ملاحظة اختيارية...", - "update_status": "تحديث الحالة", - "update_staffing": "تحديث التعيين", - "no_statuses_available": "لا توجد حالات متاحة", - "no_staffings_available": "لا توجد مستويات تعيين متاحة" - }, - "unit_actions_panel": { - "status": "الحالة", - "select_status": "اختر الحالة", - "destination": "الوجهة", - "no_destination": "بدون وجهة", - "note": "ملاحظة", - "note_placeholder": "أضف ملاحظة اختيارية...", - "update_status": "تحديث الحالة", - "no_statuses_available": "لا توجد حالات متاحة", - "no_active_calls": "لا توجد مكالمات نشطة", - "no_stations_available": "لا توجد محطات متاحة", - "no_destinations_available": "لا توجد وجهات متاحة" - }, - "call": "مكالمة", - "station": "محطة", - "calls": "المكالمات", - "stations": "المحطات", - "no_stations_available": "لا توجد محطات متاحة", - "new_call": "مكالمة جديدة", - "view_details": "التفاصيل", - "add_note": "إضافة ملاحظة", - "close_call": "إغلاق", - "set_status": "تعيين الحالة", - "set_staffing": "التعيين", - "dispatch": "إرسال", - "select_items_for_actions": "حدد مكالمة أو وحدة أو موظف لتفعيل الإجراءات السياقية", - "weather": { - "clear": "صافٍ", - "mainly_clear": "صافٍ غالبًا", - "partly_cloudy": "غائم جزئيًا", - "overcast": "غائم", - "fog": "ضباب", - "drizzle": "رذاذ", - "freezing_drizzle": "رذاذ متجمد", - "rain": "مطر", - "freezing_rain": "مطر متجمد", - "snow": "ثلج", - "rain_showers": "زخات مطر", - "snow_showers": "زخات ثلج", - "thunderstorm": "عاصفة رعدية", - "thunderstorm_hail": "عاصفة رعدية مع بَرَد", - "unknown": "غير معروف" - }, - "available_only": "المتاح فقط", - "single_list": "قائمة واحدة", - "resources": "الموارد", - "search_resources_placeholder": "البحث عن الموارد...", - "no_resources": "لا توجد موارد" - }, - "check_in": { - "tab_title": "تسجيل الحضور", - "timer_status": "حالة المؤقت", - "perform_check_in": "تسجيل", - "check_in_success": "تم تسجيل الحضور بنجاح", - "check_in_error": "فشل في تسجيل الحضور", - "checked_in_by": "بواسطة {{name}}", - "last_check_in": "آخر تسجيل", - "elapsed": "المنقضي", - "duration": "المدة", - "status_ok": "جيد", - "status_green": "جيد", - "status_warning": "تحذير", - "status_yellow": "تحذير", - "status_overdue": "متأخر", - "status_red": "متأخر", - "status_critical": "حرج", - "history": "سجل التسجيلات", - "no_timers": "لم يتم تكوين مؤقتات تسجيل الحضور", - "timers_disabled": "مؤقتات تسجيل الحضور معطلة لهذه المكالمة", - "type_personnel": "الأفراد", - "type_unit": "الوحدة", - "type_ic": "قائد الحادث", - "type_par": "PAR", - "type_hazmat": "التعرض للمواد الخطرة", - "type_sector_rotation": "تدوير القطاع", - "type_rehab": "إعادة التأهيل", - "add_note": "إضافة ملاحظة (اختياري)", - "confirm": "تأكيد التسجيل", - "minutes_ago": "منذ {{count}} دقيقة", - "select_target": "حدد الكيان للتسجيل", - "overdue_count": "{{count}} متأخر", - "warning_count": "{{count}} تحذير", - "enable_timers": "تفعيل المؤقتات", - "disable_timers": "تعطيل المؤقتات", - "summary": "{{overdue}} متأخر، {{warning}} تحذير، {{ok}} جيد", - "par_title": "متابعة الأفراد (PAR)" - }, "units": { "search": "البحث عن الوحدات...", "loading": "جارٍ تحميل الوحدات...", @@ -1163,6 +1370,63 @@ "no_destination": "لا شيء", "title": "الوحدات" }, + "videoFeeds": { + "title": "بث الفيديو", + "noFeeds": "لا توجد بثوث فيديو لهذه المكالمة", + "addFeed": "إضافة بث فيديو", + "editFeed": "تعديل بث الفيديو", + "deleteFeed": "حذف بث الفيديو", + "deleteConfirm": "هل أنت متأكد أنك تريد إزالة بث الفيديو هذا؟", + "watch": "مشاهدة", + "goLive": "بث مباشر", + "stopLive": "إيقاف البث", + "flipCamera": "عكس الكاميرا", + "feedAdded": "تمت إضافة بث الفيديو", + "feedUpdated": "تم تحديث بث الفيديو", + "feedDeleted": "تم حذف بث الفيديو", + "feedError": "فشل في تحميل بث الفيديو", + "unsupportedFormat": "تنسيق البث هذا غير مدعوم على الأجهزة المحمولة", + "copyUrl": "نسخ الرابط", + "form": { + "name": "اسم البث", + "namePlaceholder": "مثال: طائرة محرك 1", + "url": "رابط البث", + "urlPlaceholder": "مثال: https://stream.example.com/live.m3u8", + "feedType": "نوع الكاميرا", + "feedFormat": "تنسيق البث", + "description": "الوصف", + "descriptionPlaceholder": "وصف اختياري", + "status": "الحالة", + "sortOrder": "الترتيب", + "cameraLocation": "موقع الكاميرا", + "useCurrentLocation": "استخدام الموقع الحالي" + }, + "type": { + "drone": "طائرة بدون طيار", + "fixedCamera": "كاميرا ثابتة", + "bodyCam": "كاميرا جسدية", + "trafficCam": "كاميرا مرور", + "weatherCam": "كاميرا طقس", + "satelliteFeed": "بث فضائي", + "webCam": "كاميرا ويب", + "other": "أخرى" + }, + "format": { + "rtsp": "RTSP", + "hls": "HLS", + "mjpeg": "MJPEG", + "youtubeLive": "YouTube Live", + "webrtc": "WebRTC", + "dash": "DASH", + "embed": "مضمن", + "other": "أخرى" + }, + "status": { + "active": "نشط", + "inactive": "غير نشط", + "error": "خطأ" + } + }, "weatherAlerts": { "title": "تنبيهات الطقس", "activeAlerts": "التنبيهات النشطة", @@ -1270,251 +1534,5 @@ }, "stats_label": "تنبيهات الطقس" }, - "videoFeeds": { - "title": "بث الفيديو", - "noFeeds": "لا توجد بثوث فيديو لهذه المكالمة", - "addFeed": "إضافة بث فيديو", - "editFeed": "تعديل بث الفيديو", - "deleteFeed": "حذف بث الفيديو", - "deleteConfirm": "هل أنت متأكد أنك تريد إزالة بث الفيديو هذا؟", - "watch": "مشاهدة", - "goLive": "بث مباشر", - "stopLive": "إيقاف البث", - "flipCamera": "عكس الكاميرا", - "feedAdded": "تمت إضافة بث الفيديو", - "feedUpdated": "تم تحديث بث الفيديو", - "feedDeleted": "تم حذف بث الفيديو", - "feedError": "فشل في تحميل بث الفيديو", - "unsupportedFormat": "تنسيق البث هذا غير مدعوم على الأجهزة المحمولة", - "copyUrl": "نسخ الرابط", - "form": { - "name": "اسم البث", - "namePlaceholder": "مثال: طائرة محرك 1", - "url": "رابط البث", - "urlPlaceholder": "مثال: https://stream.example.com/live.m3u8", - "feedType": "نوع الكاميرا", - "feedFormat": "تنسيق البث", - "description": "الوصف", - "descriptionPlaceholder": "وصف اختياري", - "status": "الحالة", - "sortOrder": "الترتيب", - "cameraLocation": "موقع الكاميرا", - "useCurrentLocation": "استخدام الموقع الحالي" - }, - "type": { - "drone": "طائرة بدون طيار", - "fixedCamera": "كاميرا ثابتة", - "bodyCam": "كاميرا جسدية", - "trafficCam": "كاميرا مرور", - "weatherCam": "كاميرا طقس", - "satelliteFeed": "بث فضائي", - "webCam": "كاميرا ويب", - "other": "أخرى" - }, - "format": { - "rtsp": "RTSP", - "hls": "HLS", - "mjpeg": "MJPEG", - "youtubeLive": "YouTube Live", - "webrtc": "WebRTC", - "dash": "DASH", - "embed": "مضمن", - "other": "أخرى" - }, - "status": { - "active": "نشط", - "inactive": "غير نشط", - "error": "خطأ" - } - }, - "welcome": "مرحبًا بك في موقع تطبيق obytes", - "incident_command": { - "tab_title": "القيادة", - "title": "قيادة الحادث", - "open_full_board": "فتح اللوحة الكاملة", - "no_command": "لم يتم إنشاء قيادة للحادث", - "no_command_description": "أنشئ قيادة للحادث لتنسيق الموارد والأدوار والأهداف ومتابعة الأفراد لهذا البلاغ.", - "establish": "إنشاء القيادة", - "establish_title": "إنشاء قيادة الحادث", - "establish_description": "يمكنك اختياريًا إعداد لوحة القيادة من قالب.", - "establish_success": "تم إنشاء قيادة الحادث", - "establish_error": "تعذر إنشاء القيادة", - "template": "قالب", - "no_template": "بدون قالب (لوحة فارغة)", - "saved": "تم الحفظ", - "save_error": "فشلت العملية", - "edit_action_plan": "تعديل خطة العمل", - "action_plan": "خطة العمل", - "action_plan_placeholder": "صف خطة عمل الحادث...", - "save": "حفظ", - "no_action_plan": "لم يتم تعيين خطة عمل.", - "add": "إضافة", - "add_objective": "إضافة هدف", - "objective_name": "الهدف", - "objective_type": "النوع", - "name_required": "الاسم مطلوب", - "add_lane": "إضافة قطاع", - "lane_name": "اسم القطاع", - "lane_type": "نوع القطاع", - "assign_resource": "تعيين مورد", - "assign_resource_required": "حدد قطاعًا وموردًا", - "lane": "القطاع", - "select_lane": "حدد قطاعًا", - "resource_type": "نوع المورد", - "resource": "المورد", - "select_resource": "حدد موردًا", - "unit": "الوحدة", - "personnel": "الأفراد", - "assign": "تعيين", - "assign_role": "تعيين دور", - "assign_role_required": "حدد شخصًا ودورًا", - "person": "الشخص", - "select_person": "حدد شخصًا", - "role": "الدور", - "select_role": "حدد دورًا", - "transfer_title": "نقل القيادة", - "transfer_notes": "ملاحظات", - "transfer": "نقل", - "transfer_command": "نقل", - "transfer_success": "تم نقل القيادة", - "close_command": "إنهاء القيادة", - "confirm_close": "هل تريد إنهاء قيادة الحادث لهذا البلاغ؟", - "status": "الحالة", - "active": "نشطة", - "closed": "مغلقة", - "commander": "قائد الحادث", - "established_on": "تاريخ الإنشاء", - "edit": "تعديل", - "roles": "أدوار القيادة", - "no_roles": "لم يتم تعيين أدوار.", - "structure": "هيكل القيادة", - "no_lanes": "لم يتم تحديد قطاعات.", - "no_resources": "لم يتم تعيين موارد.", - "release": "تحرير", - "objectives": "الأهداف", - "no_objectives": "لا توجد أهداف.", - "complete": "إكمال", - "completed": "مكتمل", - "timers": "المؤقتات", - "no_timers": "لا توجد مؤقتات قيد التشغيل.", - "acknowledge": "إقرار", - "due": "موعد الاستحقاق", - "accountability": "متابعة الأفراد (PAR)", - "run_par": "تنفيذ PAR", - "green": "أخضر", - "warning": "تحذير", - "critical": "حرج", - "no_accountability": "لا يوجد أفراد قيد المتابعة.", - "timeline": "التسلسل الزمني للقيادة", - "no_timeline": "لا توجد إدخالات في التسلسل الزمني.", - "unassigned": "غير معيّن", - "active_title": "قيادات الحوادث النشطة", - "no_active": "لا توجد قيادات حوادث نشطة", - "no_active_description": "ستظهر هنا قيادات الحوادث التي تم إنشاؤها للبلاغات.", - "call": "البلاغ", - "tactical_map": "الخريطة التكتيكية", - "annotations": "تعليقات الخريطة", - "no_annotations": "لا توجد تعليقات.", - "open_tactical_map": "فتح الخريطة التكتيكية", - "marker": "علامة", - "add_marker": "إضافة علامة", - "tap_to_place": "اضغط على الخريطة لوضع علامة", - "marker_label": "تسمية العلامة", - "delete_annotation_confirm": "هل تريد إزالة هذا التعليق؟", - "voice_channels": "القنوات الصوتية", - "no_channels": "لا توجد قنوات مفتوحة.", - "add_channel": "إضافة قناة", - "channel_name": "اسم القناة", - "close_all_channels": "إغلاق جميع القنوات", - "join": "انضمام", - "hold_to_talk": "اضغط مطولًا للتحدث", - "talking": "جارٍ الإرسال...", - "voice_joined": "تم الانضمام إلى القناة الصوتية", - "voice_join_error": "تعذر الانضمام إلى القناة الصوتية", - "move": "نقل", - "move_lane": "نقل القطاع", - "parent_lane": "القطاع الأصل", - "top_level": "المستوى الأعلى", - "move_resource": "نقل المورد" - }, - "chat": { - "title": "الدردشة", - "assistant": "المساعد", - "empty": "لا توجد محادثات بعد. ابدأ رسالة مباشرة أو أنشئ مجموعة.", - "section_assistant": "المساعد", - "section_direct_messages": "الرسائل المباشرة", - "section_channels": "القنوات", - "section_incidents": "الحوادث", - "new_direct_message": "رسالة مباشرة جديدة", - "new_group": "مجموعة جديدة", - "open_assistant": "فتح المساعد", - "create_conversation_failed": "تعذّر بدء المحادثة", - "group_name": "اسم المجموعة", - "search_people": "البحث عن أشخاص", - "no_people": "لم يتم العثور على أشخاص", - "create_group_with": "إنشاء مجموعة ({{count}})", - "message_deleted": "تم حذف هذه الرسالة", - "urgent": "عاجل", - "urgent_will_send": "سيتم إرسال هذه الرسالة كرسالة عاجلة", - "shared_location": "الموقع المشترك", - "thread_replies": "{{count}} ردود", - "edited": "(تم التعديل)", - "failed_tap_retry": "فشل - انقر لإعادة المحاولة", - "type_a_message": "اكتب رسالة", - "emoji": "رمز تعبيري", - "add_image": "إضافة صورة", - "add_gif": "إضافة GIF", - "share_location": "مشاركة الموقع", - "send": "إرسال", - "someone": "شخص ما", - "is_typing": "{{name}} يكتب...", - "are_typing": "{{count}} أشخاص يكتبون...", - "permission_photos_denied": "تم رفض إذن الوصول إلى مكتبة الصور", - "permission_location_denied": "تم رفض إذن الموقع", - "search_gifs": "البحث عن صور GIF", - "no_gifs": "لم يتم العثور على صور GIF", - "flag_reason": "لماذا تبلّغ عن هذا؟", - "flag_inappropriate": "غير لائق", - "flag_harassment": "تحرش", - "flag_spam": "بريد عشوائي", - "flag_sensitive": "معلومات حساسة", - "flag_policy": "انتهاك السياسة", - "flag_other": "أخرى", - "reply_in_thread": "الرد في المحادثة", - "copy": "نسخ", - "copied": "تم النسخ", - "copy_unavailable": "النسخ غير متاح على هذا الجهاز", - "edit": "تعديل", - "edit_message": "تعديل الرسالة", - "save": "حفظ", - "delete": "حذف", - "pin": "تثبيت", - "unpin": "إلغاء التثبيت", - "flag": "إبلاغ", - "moderator_delete": "إزالة (مشرف)", - "moderator_removed": "تمت الإزالة بواسطة المشرف", - "attachment_failed": "فشل رفع المرفق", - "ack_required": "الإقرار مطلوب", - "ack_pending_one": "لديك رسالة عاجلة بحاجة إلى إقرار", - "ack_pending_count": "لديك {{count}} رسائل عاجلة بحاجة إلى إقرار", - "acknowledge": "إقرار", - "thread": "المحادثة", - "original_message": "الرسالة الأصلية", - "reply_placeholder": "رد...", - "channel": "قناة", - "direct_message": "رسالة مباشرة", - "load_people_failed": "تعذّر تحميل الأشخاص", - "reaction_failed": "تعذّر تحديث التفاعل", - "edit_failed": "تعذّر تعديل الرسالة", - "delete_failed": "تعذّر حذف الرسالة", - "pin_failed": "تعذّر تحديث التثبيت", - "flag_failed": "تعذّر الإبلاغ عن الرسالة" - }, - "chatbot": { - "title": "المساعد", - "subtitle": "مساعد ذكاء اصطناعي لقسمك", - "new_session": "جلسة جديدة", - "empty": "اسأل المساعد عن أي شيء للبدء.", - "ask_placeholder": "اسأل المساعد..." - } + "welcome": "مرحبًا بك في موقع تطبيق obytes" } diff --git a/src/translations/de.json b/src/translations/de.json index 8c624ae0..e294634e 100644 --- a/src/translations/de.json +++ b/src/translations/de.json @@ -371,6 +371,124 @@ "audio_name": "Audioclip" } }, + "chat": { + "title": "Chat", + "assistant": "Assistent", + "empty": "Noch keine Unterhaltungen. Starte eine Direktnachricht oder erstelle eine Gruppe.", + "section_assistant": "Assistent", + "section_direct_messages": "Direktnachrichten", + "section_channels": "Kanäle", + "section_incidents": "Vorfälle", + "new_direct_message": "Neue Direktnachricht", + "new_group": "Neue Gruppe", + "open_assistant": "Assistent öffnen", + "create_conversation_failed": "Unterhaltung konnte nicht gestartet werden", + "group_name": "Gruppenname", + "search_people": "Personen suchen", + "no_people": "Keine Personen gefunden", + "create_group_with": "Gruppe erstellen ({{count}})", + "message_deleted": "Diese Nachricht wurde gelöscht", + "urgent": "Dringend", + "urgent_will_send": "Diese Nachricht wird als dringend gesendet", + "shared_location": "Geteilter Standort", + "thread_replies": "{{count}} Antworten", + "edited": "(bearbeitet)", + "failed_tap_retry": "Fehlgeschlagen – zum Wiederholen tippen", + "type_a_message": "Nachricht eingeben", + "emoji": "Emoji", + "add_image": "Bild hinzufügen", + "add_gif": "GIF hinzufügen", + "share_location": "Standort teilen", + "send": "Senden", + "someone": "Jemand", + "is_typing": "{{name}} schreibt...", + "are_typing": "{{count}} Personen schreiben...", + "permission_photos_denied": "Zugriff auf die Fotomediathek verweigert", + "permission_location_denied": "Zugriff auf den Standort verweigert", + "search_gifs": "GIFs suchen", + "no_gifs": "Keine GIFs gefunden", + "flag_reason": "Warum meldest du dies?", + "flag_inappropriate": "Unangemessen", + "flag_harassment": "Belästigung", + "flag_spam": "Spam", + "flag_sensitive": "Sensible Informationen", + "flag_policy": "Richtlinienverstoß", + "flag_other": "Sonstiges", + "reply_in_thread": "Im Thread antworten", + "copy": "Kopieren", + "copied": "Kopiert", + "copy_unavailable": "Kopieren ist auf diesem Gerät nicht verfügbar", + "edit": "Bearbeiten", + "edit_message": "Nachricht bearbeiten", + "save": "Speichern", + "delete": "Löschen", + "pin": "Anheften", + "unpin": "Anheften aufheben", + "flag": "Melden", + "moderator_delete": "Entfernen (Moderator)", + "moderator_removed": "Vom Moderator entfernt", + "attachment_failed": "Anhang-Upload fehlgeschlagen", + "ack_required": "Bestätigung erforderlich", + "ack_pending_one": "Du hast eine dringende Nachricht zu bestätigen", + "ack_pending_count": "Du hast {{count}} dringende Nachrichten zu bestätigen", + "acknowledge": "Bestätigen", + "thread": "Thread", + "original_message": "Ursprüngliche Nachricht", + "reply_placeholder": "Antworten...", + "channel": "Kanal", + "direct_message": "Direktnachricht", + "load_people_failed": "Personen konnten nicht geladen werden", + "reaction_failed": "Reaktion konnte nicht aktualisiert werden", + "edit_failed": "Nachricht konnte nicht bearbeitet werden", + "delete_failed": "Nachricht konnte nicht gelöscht werden", + "pin_failed": "Anheften konnte nicht aktualisiert werden", + "flag_failed": "Nachricht konnte nicht gemeldet werden" + }, + "chatbot": { + "title": "Assistent", + "subtitle": "KI-Helfer für deine Abteilung", + "new_session": "Neue Sitzung", + "empty": "Frag den Assistenten etwas, um zu beginnen.", + "ask_placeholder": "Frag den Assistenten..." + }, + "check_in": { + "tab_title": "Kontrolle", + "timer_status": "Timer-Status", + "perform_check_in": "Kontrolle durchführen", + "check_in_success": "Kontrolle erfolgreich erfasst", + "check_in_error": "Kontrolle konnte nicht erfasst werden", + "checked_in_by": "von {{name}}", + "last_check_in": "Letzte Kontrolle", + "elapsed": "Vergangen", + "duration": "Dauer", + "status_ok": "OK", + "status_green": "OK", + "status_warning": "Warnung", + "status_yellow": "Warnung", + "status_overdue": "Überfällig", + "status_red": "Überfällig", + "status_critical": "Kritisch", + "history": "Kontrollverlauf", + "no_timers": "Keine Kontroll-Timer konfiguriert", + "timers_disabled": "Kontroll-Timer sind für diesen Einsatz deaktiviert", + "type_personnel": "Personal", + "type_unit": "Einheit", + "type_ic": "Einsatzleiter", + "type_par": "PAR", + "type_hazmat": "Gefahrstoffexposition", + "type_sector_rotation": "Abschnittsrotation", + "type_rehab": "Erholung", + "add_note": "Notiz hinzufügen (optional)", + "confirm": "Kontrolle bestätigen", + "minutes_ago": "vor {{count}} Min.", + "select_target": "Einheit für Kontrolle auswählen", + "overdue_count": "{{count}} Überfällig", + "warning_count": "{{count}} Warnung", + "enable_timers": "Timer aktivieren", + "disable_timers": "Timer deaktivieren", + "summary": "{{overdue}} überfällig, {{warning}} Warnung, {{ok}} ok", + "par_title": "Personalübersicht (PAR)" + }, "common": { "add": "Hinzufügen", "back": "Zurück", @@ -502,178 +620,379 @@ "website": "Webseite", "zip": "Postleitzahl" }, - "form": { - "invalid_url": "Bitte geben Sie eine gültige URL ein, die mit http:// oder https:// beginnt", - "required": "Dieses Feld ist erforderlich" - }, - "livekit": { - "audio_devices": "Audiogeräte", - "audio_settings": "Audioeinstellungen", - "connected_to_room": "Mit Kanal verbunden", - "connecting": "Verbindung wird hergestellt...", - "disconnect": "Trennen", - "join": "Beitreten", - "microphone": "Mikrofon", - "mute": "Stummschalten", - "no_rooms_available": "Keine Sprachkanäle verfügbar", - "speaker": "Lautsprecher", - "speaking": "Spricht", - "title": "Sprachkanäle", - "unmute": "Stummschaltung aufheben" - }, - "loading": { - "loading": "Laden...", - "loadingData": "Daten werden geladen...", - "pleaseWait": "Bitte warten", - "processingRequest": "Ihre Anfrage wird verarbeitet..." - }, - "sso": { - "authenticating": "Authentifizierung läuft...", - "back_to_login": "Zurück zur Anmeldung", - "back_to_lookup": "Benutzer wechseln", - "continue_button": "Weiter", - "department_id_label": "Abteilungs-ID", - "department_id_placeholder": "Abteilungs-ID eingeben", - "error_generic": "Anmeldung fehlgeschlagen. Bitte versuchen Sie es erneut.", - "error_oidc_cancelled": "Anmeldung wurde abgebrochen.", - "error_oidc_not_ready": "SSO-Anbieter wird geladen, bitte warten.", - "error_sso_not_enabled": "Single Sign-On ist für diesen Benutzer nicht aktiviert.", - "error_token_exchange": "Anmeldung konnte nicht abgeschlossen werden. Bitte versuchen Sie es erneut.", - "error_user_not_found": "Benutzer nicht gefunden. Bitte überprüfen und erneut versuchen.", - "looking_up": "Wird gesucht...", - "optional": "optional", - "page_subtitle": "Geben Sie Ihren Benutzernamen ein, um die Anmeldeoptionen Ihrer Organisation zu finden.", - "page_title": "Einmalanmeldung", - "provider_oidc": "OpenID Connect (OIDC)", - "provider_saml": "SAML 2.0", - "sign_in_button": "Mit SSO anmelden", - "sign_in_title": "Anmelden", - "sso_button": "SSO-Anmeldung" - }, - "login": { - "branding_subtitle": "Leistungsstarke Leitstellensoftware für Feuerwehren, Rettungsdienste und Organisationen der öffentlichen Sicherheit.", - "branding_title": "Einsatzmanagement", - "errorModal": { - "confirmButton": "OK", - "message": "Bitte überprüfen Sie Ihren Benutzernamen und Ihr Passwort und versuchen Sie es erneut.", - "title": "Anmeldung fehlgeschlagen" - }, - "feature_dispatch_desc": "Disponieren Sie Einheiten sofort und verwalten Sie Einsätze mit Live-Aktualisierungen auf allen Geräten.", - "feature_dispatch_title": "Echtzeit-Disposition", - "feature_mapping_desc": "Verfolgen Sie Einheiten in Echtzeit mit detaillierten Karten, Routenführung und Standortverwaltung.", - "feature_mapping_title": "Erweiterte Kartendarstellung", - "feature_personnel_desc": "Verwalten Sie Ihr Team mit rollenbasiertem Zugriff, Statusverfolgung und Kommunikationswerkzeugen.", - "feature_personnel_title": "Personalverwaltung", - "footer_text": "Erstellt mit ❤️ in Lake Tahoe", - "login": "Anmelden", - "login_button": "Anmelden", - "login_button_description": "Melden Sie sich bei Ihrem Konto an, um fortzufahren", - "login_button_error": "Fehler bei der Anmeldung", - "login_button_loading": "Anmeldung läuft...", - "login_button_success": "Erfolgreich angemeldet", - "no_account": "Noch kein Konto?", - "page_subtitle": "Geben Sie Ihre Anmeldedaten ein.", - "page_title": "Resgrid Dispatch", - "password": "Passwort", - "password_incorrect": "Passwort war falsch", - "password_placeholder": "Passwort eingeben", - "register": "Registrieren", - "title": "Anmeldung", - "username": "Benutzername", - "username_placeholder": "Benutzernamen eingeben", - "welcome_title": "Willkommen zurück" - }, - "lockscreen": { - "message": "Geben Sie Ihr Passwort ein, um den Bildschirm zu entsperren", - "not_you": "Nicht Sie? Zurück zur Anmeldung", - "password": "Passwort", - "password_placeholder": "Passwort eingeben", - "title": "Bildschirmsperre", - "unlock_button": "Entsperren", - "unlock_failed": "Entsperren fehlgeschlagen. Bitte versuchen Sie es erneut.", - "unlocking": "Wird entsperrt...", - "welcome_back": "Willkommen zurück", - "relogin_required": "Die Passwortüberprüfung ist für diese Sitzung nicht verfügbar. Bitte melde dich erneut an." - }, - "maintenance": { - "downtime_message": "Wir arbeiten daran, die Wartung so schnell wie möglich abzuschließen. Bitte schauen Sie bald wieder vorbei.", - "downtime_title": "Wie lange dauert die Ausfallzeit?", - "message": "Bitte schauen Sie später wieder vorbei.", - "support_message": "Wenn Sie Hilfe benötigen, kontaktieren Sie uns unter", - "support_title": "Benötigen Sie Unterstützung?", - "title": "Wartungsarbeiten", - "why_down_message": "Wir führen geplante Wartungsarbeiten durch, um Ihre Erfahrung zu verbessern. Wir entschuldigen uns für etwaige Unannehmlichkeiten.", - "why_down_title": "Warum ist die Seite nicht erreichbar?" - }, - "menu": { - "scheduled_calls": "Geplante Einsätze", - "pois": "POIs", - "calls": "Einsätze", - "calls_list": "Einsatzliste", - "contacts": "Kontakte", - "home": "Startseite", - "map": "Karte", - "menu": "Menü", - "messages": "Nachrichten", - "new_call": "Neuer Einsatz", - "personnel": "Personal", - "protocols": "Protokolle", - "settings": "Einstellungen", + "dispatch": { + "active_calls": "Aktive Einsätze", + "pending_calls": "Ausstehend", + "scheduled_calls": "Geplant", + "units_available": "Verfügbar", + "personnel_available": "Verfügbar", + "personnel_on_duty": "Im Dienst", "units": "Einheiten", - "weatherAlerts": "Wetterwarnungen", - "incident_command": "Einsatzleitung", - "chat": "Chat", - "assistant": "Assistent" - }, - "pois": { - "address": "Adresse", - "all_types": "Alle Typen", - "destination": "Ziel", - "details": "Details", - "detail_not_found": "POI nicht gefunden", - "detail_not_found_description": "Der angeforderte POI konnte nicht geladen werden.", - "detail_title": "POI-Details", - "empty": "Keine POIs gefunden", - "empty_description": "Für Ihre Abteilung sind noch keine Sonderziele verfügbar.", - "empty_filtered": "Keine passenden POIs", - "empty_filtered_description": "Löschen Sie die Suche oder wählen Sie einen anderen POI-Typ.", - "filter_by_type": "Nach POI-Typ filtern", - "invalid_poi": "Ungültiger POI", - "invalid_poi_description": "Die ausgewählte POI-Kennung ist ungültig.", - "loading": "POIs werden geladen...", - "loading_detail": "POI-Details werden geladen...", + "personnel": "Personal", "map": "Karte", - "no_location": "Kein Standort verfügbar", - "no_location_description": "Dieser POI besitzt keine verwendbaren Koordinaten.", - "no_location_for_routing": "Keine Standortdaten für die Routenplanung verfügbar", - "note": "Notiz", - "route_error": "Kartenanwendung konnte nicht geöffnet werden", - "search": "POIs suchen...", - "sort": "Sortieren", - "sort_options": { - "address-asc": "Adresse", - "name-asc": "Name (A–Z)", - "name-desc": "Name (Z–A)", - "type-asc": "Typ" + "notes": "Notizen", + "activity_log": "Aktivitätsprotokoll", + "communications": "Kommunikation", + "no_active_calls": "Keine aktiven Einsätze", + "no_units": "Keine Einheiten verfügbar", + "no_personnel": "Kein Personal verfügbar", + "no_notes": "Keine Notizen verfügbar", + "no_activity": "Keine aktuelle Aktivität", + "current_channel": "Aktueller Kanal", + "audio_stream": "Audiostream", + "no_stream": "Kein Stream aktiv", + "ptt": "PTT", + "ptt_start": "PTT Start", + "ptt_end": "PTT Ende", + "transmitting_on": "Senden auf {{channel}}", + "transmission_ended": "Übertragung beendet", + "voice_disabled": "Sprache deaktiviert", + "disconnected": "Getrennt", + "select_channel": "Kanal auswählen", + "select_channel_description": "Wählen Sie einen Sprachkanal zum Verbinden", + "change_channel_warning": "Das Auswählen eines neuen Kanals trennt die Verbindung zum aktuellen Kanal", + "default_channel": "Standard", + "no_channels_available": "Keine Sprachkanäle verfügbar", + "system_update": "Systemaktualisierung", + "data_refreshed": "Daten vom Server aktualisiert", + "call_selected": "Einsatz ausgewählt", + "unit_selected": "Einheit ausgewählt", + "unit_deselected": "Einheit abgewählt", + "personnel_selected": "Personal ausgewählt", + "personnel_deselected": "Personal abgewählt", + "loading_map": "Karte wird geladen...", + "map_not_available_web": "Karte auf der Webplattform nicht verfügbar", + "filtering_by_call": "Filterung nach Einsatz", + "clear_filter": "Filter löschen", + "call_filter_active": "Einsatzfilter aktiv", + "call_filter_cleared": "Einsatzfilter gelöscht", + "showing_all_data": "Alle Daten werden angezeigt", + "call_notes": "Einsatznotizen", + "no_call_notes": "Keine Einsatznotizen", + "add_call_note_placeholder": "Notiz hinzufügen...", + "note_added": "Notiz hinzugefügt", + "note_added_to_console": "Eine neue Notiz wurde zur Konsole hinzugefügt", + "add_note_title": "Neue Notiz hinzufügen", + "note_title_label": "Titel", + "note_title_placeholder": "Notiztitel eingeben...", + "note_category_label": "Kategorie", + "note_category_placeholder": "Kategorie auswählen", + "note_no_category": "Keine Kategorie", + "note_body_label": "Notizinhalt", + "note_body_placeholder": "Notizinhalt eingeben...", + "note_save_error": "Notiz konnte nicht gespeichert werden: {{error}}", + "note_created": "Notiz erstellt", + "units_on_call": "Einheiten im Einsatz", + "no_units_on_call": "Keine Einheiten im Einsatz", + "personnel_on_call": "Personal im Einsatz", + "no_personnel_on_call": "Kein Personal im Einsatz", + "call_activity": "Einsatzaktivität", + "no_call_activity": "Keine Einsatzaktivität", + "on_call": "Im Einsatz", + "filtered": "Gefiltert", + "active_filter": "Aktiver Filter", + "unit_status_change": "Statusänderung der Einheit", + "personnel_status_change": "Statusänderung des Personals", + "view_call_details": "Einsatzdetails anzeigen", + "dispatched_resources": "Disponiert", + "unassigned": "Nicht zugewiesen", + "available": "Verfügbar", + "unknown": "Unbekannt", + "search_personnel_placeholder": "Personal durchsuchen...", + "search_calls_placeholder": "Einsätze durchsuchen...", + "search_units_placeholder": "Einheiten durchsuchen...", + "search_notes_placeholder": "Notizen durchsuchen...", + "signalr_update": "Echtzeit-Aktualisierung", + "signalr_connected": "Verbunden", + "realtime_updates_active": "Echtzeit-Aktualisierungen sind jetzt aktiv", + "personnel_status_updated": "Personalstatus aktualisiert", + "personnel_staffing_updated": "Personalbesetzung aktualisiert", + "unit_status_updated": "Einheitenstatus aktualisiert", + "calls_updated": "Einsätze aktualisiert", + "call_added": "Neuer Einsatz hinzugefügt", + "call_closed": "Einsatz geschlossen", + "check_ins": "Kontrollen", + "no_check_ins": "Keine Einsätze mit Kontroll-Timern", + "radio_log": "Funkprotokoll", + "radio": "Funk", + "activity": "Aktivität", + "actions": "Aktionen", + "no_radio_activity": "Keine Funkübertragungen", + "live": "LIVE", + "currently_transmitting": "Sendet gerade...", + "duration": "Dauer", + "call_actions": "Einsatzaktionen", + "unit_actions": "Einheitenaktionen", + "personnel_actions": { + "title": "Personalaktionen", + "status_tab": "Status", + "staffing_tab": "Besetzung", + "select_status": "Status auswählen", + "select_staffing": "Besetzungsstufe auswählen", + "destination": "Ziel", + "no_destination": "Kein Ziel", + "note": "Notiz", + "note_placeholder": "Optionale Notiz hinzufügen...", + "update_status": "Status aktualisieren", + "update_staffing": "Besetzung aktualisieren", + "no_statuses_available": "Keine Status verfügbar", + "no_staffings_available": "Keine Besetzungsstufen verfügbar" }, - "title": "POIs", - "type": "Typ", - "unknown_type": "Unbekannter Typ", - "unnamed": "Unbenannter POI" + "unit_actions_panel": { + "status": "Status", + "select_status": "Status auswählen", + "destination": "Ziel", + "no_destination": "Kein Ziel", + "note": "Notiz", + "note_placeholder": "Optionale Notiz hinzufügen...", + "update_status": "Status aktualisieren", + "no_statuses_available": "Keine Status verfügbar", + "no_active_calls": "Keine aktiven Einsätze", + "no_stations_available": "Keine Wachen verfügbar", + "no_destinations_available": "Keine Ziele verfügbar" + }, + "call": "Einsatz", + "station": "Wache", + "calls": "Einsätze", + "stations": "Wachen", + "no_stations_available": "Keine Wachen verfügbar", + "new_call": "Neuer Einsatz", + "view_details": "Details", + "add_note": "Notiz hinzufügen", + "close_call": "Schließen", + "set_status": "Status setzen", + "set_staffing": "Besetzung", + "dispatch": "Disponieren", + "select_items_for_actions": "Wählen Sie einen Einsatz, eine Einheit oder Personal aus, um kontextbezogene Aktionen zu aktivieren", + "weather": { + "clear": "Klar", + "mainly_clear": "Überwiegend klar", + "partly_cloudy": "Teilweise bewölkt", + "overcast": "Bedeckt", + "fog": "Nebel", + "drizzle": "Nieselregen", + "freezing_drizzle": "Gefrierender Nieselregen", + "rain": "Regen", + "freezing_rain": "Gefrierender Regen", + "snow": "Schnee", + "rain_showers": "Regenschauer", + "snow_showers": "Schneeschauer", + "thunderstorm": "Gewitter", + "thunderstorm_hail": "Gewitter mit Hagel", + "unknown": "Unbekannt" + }, + "available_only": "Nur verfügbare", + "single_list": "Einzelne Liste", + "resources": "Ressourcen", + "search_resources_placeholder": "Ressourcen suchen...", + "no_resources": "Keine Ressourcen" }, - "scheduled_calls": { - "title": "Geplante Einsätze", - "loading": "Geplante Einsätze werden geladen...", - "no_scheduled_calls": "Keine geplanten Einsätze", - "no_scheduled_calls_description": "Derzeit stehen keine geplanten Einsätze aus.", - "search": "Geplante Einsätze suchen...", - "scheduled_for": "Geplant für", - "table_number": "Einsatz-Nr.", - "table_name": "Name", - "table_type": "Typ", - "table_priority": "Priorität", - "table_address": "Adresse", - "table_scheduled": "Geplant für" + "form": { + "invalid_url": "Bitte geben Sie eine gültige URL ein, die mit http:// oder https:// beginnt", + "required": "Dieses Feld ist erforderlich" + }, + "incident_command": { + "accountability": "Personalübersicht (PAR)", + "acknowledge": "Bestätigen", + "action_plan": "Aktionsplan", + "action_plan_placeholder": "Einsatzaktionsplan beschreiben...", + "active": "Aktiv", + "active_title": "Aktive Einsatzleitungen", + "add": "Hinzufügen", + "add_channel": "Kanal hinzufügen", + "add_lane": "Bereich hinzufügen", + "add_marker": "Markierung hinzufügen", + "add_objective": "Ziel hinzufügen", + "annotations": "Kartenmarkierungen", + "assign": "Zuweisen", + "assign_resource": "Ressource zuweisen", + "assign_resource_required": "Bereich und Ressource auswählen", + "assign_role": "Rolle zuweisen", + "assign_role_required": "Person und Rolle auswählen", + "call": "Einsatz", + "channel_name": "Kanalname", + "chat": "Chat", + "chat_frozen": "This incident is closed. Conversations are kept as a point-in-time record — no new messages.", + "close_all_channels": "Alle Kanäle schließen", + "close_command": "Einsatzleitung schließen", + "closed": "Geschlossen", + "command_channel": "Command chat", + "command_channel_hint": "Command staff and dispatch", + "command_channel_unavailable": "No command channel has been created for this incident yet.", + "commander": "Einsatzleiter", + "complete": "Abschließen", + "completed": "Abgeschlossen", + "confirm_close": "Einsatzleitung für diesen Einsatz schließen?", + "critical": "Kritisch", + "delete_annotation_confirm": "Diese Markierung entfernen?", + "dispatch_channel": "Dispatch", + "dispatch_channel_hint": "The incident's line to the desk", + "dispatch_channel_unavailable": "No dispatch channel has been created for this incident yet.", + "dm_failed": "Couldn't open that conversation.", + "dm_unavailable": "That contact has no Resgrid account to message.", + "due": "Fällig", + "edit": "Bearbeiten", + "edit_action_plan": "Aktionsplan bearbeiten", + "establish": "Einsatzleitung einrichten", + "establish_description": "Die Führungsübersicht kann optional aus einer Vorlage erstellt werden.", + "establish_error": "Einsatzleitung konnte nicht eingerichtet werden", + "establish_success": "Einsatzleitung eingerichtet", + "establish_title": "Einsatzleitung einrichten", + "established_on": "Eingerichtet", + "green": "Grün", + "hold_to_talk": "Zum Sprechen gedrückt halten", + "incident_channel": "Incident chat", + "incident_channel_hint": "Everyone working the incident", + "incident_channel_unavailable": "No incident channel has been created for this call yet.", + "join": "Beitreten", + "lane": "Bereich", + "lane_name": "Bereichsname", + "lane_type": "Bereichstyp", + "marker": "Markierung", + "marker_label": "Beschriftung der Markierung", + "move": "Verschieben", + "move_lane": "Bereich verschieben", + "move_resource": "Ressource verschieben", + "name_required": "Name ist erforderlich", + "no_accountability": "Kein Personal erfasst.", + "no_action_plan": "Kein Aktionsplan festgelegt.", + "no_active": "Keine aktiven Einsatzleitungen", + "no_active_description": "Für Einsätze eingerichtete Einsatzleitungen werden hier angezeigt.", + "no_annotations": "Keine Markierungen.", + "no_channels": "Keine offenen Kanäle.", + "no_command": "Keine Einsatzleitung eingerichtet", + "no_command_description": "Richten Sie eine Einsatzleitung ein, um Ressourcen, Rollen, Ziele und die Personalübersicht für diesen Einsatz zu koordinieren.", + "no_lanes": "Keine Bereiche definiert.", + "no_objectives": "Keine Ziele.", + "no_resources": "Keine Ressourcen zugewiesen.", + "no_roles": "Keine Rollen zugewiesen.", + "no_template": "Keine Vorlage (leere Übersicht)", + "no_timeline": "Keine Verlaufseinträge.", + "no_timers": "Keine laufenden Timer.", + "not_authorized": "Command board unavailable", + "not_authorized_description": "Your department hasn't authorized you to work incident command. Ask an administrator for the Command App Login permission.", + "objective_name": "Ziel", + "objective_type": "Typ", + "objectives": "Ziele", + "open_chat": "Open", + "open_full_board": "Gesamte Übersicht öffnen", + "open_tactical_map": "Taktische Karte öffnen", + "parent_lane": "Übergeordneter Bereich", + "person": "Person", + "personnel": "Personal", + "release": "Freigeben", + "resource": "Ressource", + "resource_type": "Ressourcentyp", + "role": "Rolle", + "roles": "Leitungsrollen", + "run_par": "PAR durchführen", + "save": "Speichern", + "save_error": "Vorgang fehlgeschlagen", + "saved": "Gespeichert", + "select_lane": "Bereich auswählen", + "select_person": "Person auswählen", + "select_resource": "Ressource auswählen", + "select_role": "Rolle auswählen", + "send_message": "Message", + "status": "Status", + "structure": "Leitungsstruktur", + "tab_title": "Leitung", + "tactical_map": "Taktische Karte", + "talking": "Übertragung...", + "tap_to_place": "Tippen Sie auf die Karte, um eine Markierung zu setzen", + "template": "Vorlage", + "timeline": "Leitungsverlauf", + "timers": "Timer", + "title": "Einsatzleitung", + "top_level": "Oberste Ebene", + "transfer": "Übertragen", + "transfer_command": "Übertragen", + "transfer_notes": "Notizen", + "transfer_success": "Einsatzleitung übertragen", + "transfer_title": "Einsatzleitung übertragen", + "unassigned": "Nicht zugewiesen", + "unit": "Einheit", + "voice_channels": "Sprachkanäle", + "voice_join_error": "Beitritt zum Sprachkanal fehlgeschlagen", + "voice_joined": "Sprachkanal beigetreten", + "warning": "Warnung" + }, + "livekit": { + "audio_devices": "Audiogeräte", + "audio_settings": "Audioeinstellungen", + "connected_to_room": "Mit Kanal verbunden", + "connecting": "Verbindung wird hergestellt...", + "disconnect": "Trennen", + "join": "Beitreten", + "microphone": "Mikrofon", + "mute": "Stummschalten", + "no_rooms_available": "Keine Sprachkanäle verfügbar", + "speaker": "Lautsprecher", + "speaking": "Spricht", + "title": "Sprachkanäle", + "unmute": "Stummschaltung aufheben" + }, + "loading": { + "loading": "Laden...", + "loadingData": "Daten werden geladen...", + "pleaseWait": "Bitte warten", + "processingRequest": "Ihre Anfrage wird verarbeitet..." + }, + "lockscreen": { + "message": "Geben Sie Ihr Passwort ein, um den Bildschirm zu entsperren", + "not_you": "Nicht Sie? Zurück zur Anmeldung", + "password": "Passwort", + "password_placeholder": "Passwort eingeben", + "title": "Bildschirmsperre", + "unlock_button": "Entsperren", + "unlock_failed": "Entsperren fehlgeschlagen. Bitte versuchen Sie es erneut.", + "unlocking": "Wird entsperrt...", + "welcome_back": "Willkommen zurück", + "relogin_required": "Die Passwortüberprüfung ist für diese Sitzung nicht verfügbar. Bitte melde dich erneut an." + }, + "login": { + "branding_subtitle": "Leistungsstarke Leitstellensoftware für Feuerwehren, Rettungsdienste und Organisationen der öffentlichen Sicherheit.", + "branding_title": "Einsatzmanagement", + "dispatch_not_authorized": "You are not authorized to use the Dispatch app. Contact your department administrator.", + "errorModal": { + "confirmButton": "OK", + "message": "Bitte überprüfen Sie Ihren Benutzernamen und Ihr Passwort und versuchen Sie es erneut.", + "title": "Anmeldung fehlgeschlagen" + }, + "feature_dispatch_desc": "Disponieren Sie Einheiten sofort und verwalten Sie Einsätze mit Live-Aktualisierungen auf allen Geräten.", + "feature_dispatch_title": "Echtzeit-Disposition", + "feature_mapping_desc": "Verfolgen Sie Einheiten in Echtzeit mit detaillierten Karten, Routenführung und Standortverwaltung.", + "feature_mapping_title": "Erweiterte Kartendarstellung", + "feature_personnel_desc": "Verwalten Sie Ihr Team mit rollenbasiertem Zugriff, Statusverfolgung und Kommunikationswerkzeugen.", + "feature_personnel_title": "Personalverwaltung", + "footer_text": "Erstellt mit ❤️ in Lake Tahoe", + "login": "Anmelden", + "login_button": "Anmelden", + "login_button_description": "Melden Sie sich bei Ihrem Konto an, um fortzufahren", + "login_button_error": "Fehler bei der Anmeldung", + "login_button_loading": "Anmeldung läuft...", + "login_button_success": "Erfolgreich angemeldet", + "no_account": "Noch kein Konto?", + "page_subtitle": "Geben Sie Ihre Anmeldedaten ein.", + "page_title": "Resgrid Dispatch", + "password": "Passwort", + "password_incorrect": "Passwort war falsch", + "password_placeholder": "Passwort eingeben", + "register": "Registrieren", + "title": "Anmeldung", + "username": "Benutzername", + "username_placeholder": "Benutzernamen eingeben", + "welcome_title": "Willkommen zurück" + }, + "maintenance": { + "downtime_message": "Wir arbeiten daran, die Wartung so schnell wie möglich abzuschließen. Bitte schauen Sie bald wieder vorbei.", + "downtime_title": "Wie lange dauert die Ausfallzeit?", + "message": "Bitte schauen Sie später wieder vorbei.", + "support_message": "Wenn Sie Hilfe benötigen, kontaktieren Sie uns unter", + "support_title": "Benötigen Sie Unterstützung?", + "title": "Wartungsarbeiten", + "why_down_message": "Wir führen geplante Wartungsarbeiten durch, um Ihre Erfahrung zu verbessern. Wir entschuldigen uns für etwaige Unannehmlichkeiten.", + "why_down_title": "Warum ist die Seite nicht erreichbar?" }, "map": { "view_poi_details": "POI-Details anzeigen", @@ -690,6 +1009,26 @@ "hide_all": "Alle ausblenden", "view_call_details": "Einsatzdetails anzeigen" }, + "menu": { + "scheduled_calls": "Geplante Einsätze", + "pois": "POIs", + "calls": "Einsätze", + "calls_list": "Einsatzliste", + "contacts": "Kontakte", + "home": "Startseite", + "map": "Karte", + "menu": "Menü", + "messages": "Nachrichten", + "new_call": "Neuer Einsatz", + "personnel": "Personal", + "protocols": "Protokolle", + "settings": "Einstellungen", + "units": "Einheiten", + "weatherAlerts": "Wetterwarnungen", + "incident_command": "Einsatzleitung", + "chat": "Chat", + "assistant": "Assistent" + }, "notes": { "actions": { "add": "Notiz hinzufügen", @@ -709,6 +1048,23 @@ "search": "Notizen durchsuchen...", "title": "Notizen" }, + "onboarding": { + "screen1": { + "title": "Resgrid Dispatch", + "description": "Erstellen, disponieren und verwalten Sie Einsätze mit einer leistungsstarken mobilen Leitstelle direkt in Ihrer Hand" + }, + "screen2": { + "title": "Echtzeit-Lagebild", + "description": "Verfolgen Sie alle Einheiten, Einsatzkräfte und Ressourcen auf einer interaktiven Karte mit Live-Statusaktualisierungen und AVL" + }, + "screen3": { + "title": "Nahtlose Koordination", + "description": "Kommunizieren Sie sofort mit Einheiten im Feld, aktualisieren Sie den Einsatzstatus und koordinieren Sie den Einsatz von überall aus" + }, + "skip": "Überspringen", + "next": "Weiter", + "getStarted": "Los geht's" + }, "personnel": { "title": "Personal", "search": "Personal suchen...", @@ -741,22 +1097,41 @@ "send_email": "E-Mail", "custom_fields": "Zusätzliche Informationen" }, - "onboarding": { - "screen1": { - "title": "Resgrid Dispatch", - "description": "Erstellen, disponieren und verwalten Sie Einsätze mit einer leistungsstarken mobilen Leitstelle direkt in Ihrer Hand" - }, - "screen2": { - "title": "Echtzeit-Lagebild", - "description": "Verfolgen Sie alle Einheiten, Einsatzkräfte und Ressourcen auf einer interaktiven Karte mit Live-Statusaktualisierungen und AVL" - }, - "screen3": { - "title": "Nahtlose Koordination", - "description": "Kommunizieren Sie sofort mit Einheiten im Feld, aktualisieren Sie den Einsatzstatus und koordinieren Sie den Einsatz von überall aus" + "pois": { + "address": "Adresse", + "all_types": "Alle Typen", + "destination": "Ziel", + "details": "Details", + "detail_not_found": "POI nicht gefunden", + "detail_not_found_description": "Der angeforderte POI konnte nicht geladen werden.", + "detail_title": "POI-Details", + "empty": "Keine POIs gefunden", + "empty_description": "Für Ihre Abteilung sind noch keine Sonderziele verfügbar.", + "empty_filtered": "Keine passenden POIs", + "empty_filtered_description": "Löschen Sie die Suche oder wählen Sie einen anderen POI-Typ.", + "filter_by_type": "Nach POI-Typ filtern", + "invalid_poi": "Ungültiger POI", + "invalid_poi_description": "Die ausgewählte POI-Kennung ist ungültig.", + "loading": "POIs werden geladen...", + "loading_detail": "POI-Details werden geladen...", + "map": "Karte", + "no_location": "Kein Standort verfügbar", + "no_location_description": "Dieser POI besitzt keine verwendbaren Koordinaten.", + "no_location_for_routing": "Keine Standortdaten für die Routenplanung verfügbar", + "note": "Notiz", + "route_error": "Kartenanwendung konnte nicht geöffnet werden", + "search": "POIs suchen...", + "sort": "Sortieren", + "sort_options": { + "address-asc": "Adresse", + "name-asc": "Name (A–Z)", + "name-desc": "Name (Z–A)", + "type-asc": "Typ" }, - "skip": "Überspringen", - "next": "Weiter", - "getStarted": "Los geht's" + "title": "POIs", + "type": "Typ", + "unknown_type": "Unbekannter Typ", + "unnamed": "Unbenannter POI" }, "protocols": { "details": { @@ -796,6 +1171,20 @@ "tap_to_manage": "Tippen, um Rollen zu verwalten", "unassigned": "Nicht zugewiesen" }, + "scheduled_calls": { + "title": "Geplante Einsätze", + "loading": "Geplante Einsätze werden geladen...", + "no_scheduled_calls": "Keine geplanten Einsätze", + "no_scheduled_calls_description": "Derzeit stehen keine geplanten Einsätze aus.", + "search": "Geplante Einsätze suchen...", + "scheduled_for": "Geplant für", + "table_number": "Einsatz-Nr.", + "table_name": "Name", + "table_type": "Typ", + "table_priority": "Priorität", + "table_address": "Adresse", + "table_scheduled": "Geplant für" + }, "settings": { "about": "Über", "account": "Konto", @@ -884,6 +1273,29 @@ "version": "Version", "website": "Webseite" }, + "sso": { + "authenticating": "Authentifizierung läuft...", + "back_to_login": "Zurück zur Anmeldung", + "back_to_lookup": "Benutzer wechseln", + "continue_button": "Weiter", + "department_id_label": "Abteilungs-ID", + "department_id_placeholder": "Abteilungs-ID eingeben", + "error_generic": "Anmeldung fehlgeschlagen. Bitte versuchen Sie es erneut.", + "error_oidc_cancelled": "Anmeldung wurde abgebrochen.", + "error_oidc_not_ready": "SSO-Anbieter wird geladen, bitte warten.", + "error_sso_not_enabled": "Single Sign-On ist für diesen Benutzer nicht aktiviert.", + "error_token_exchange": "Anmeldung konnte nicht abgeschlossen werden. Bitte versuchen Sie es erneut.", + "error_user_not_found": "Benutzer nicht gefunden. Bitte überprüfen und erneut versuchen.", + "looking_up": "Wird gesucht...", + "optional": "optional", + "page_subtitle": "Geben Sie Ihren Benutzernamen ein, um die Anmeldeoptionen Ihrer Organisation zu finden.", + "page_title": "Einmalanmeldung", + "provider_oidc": "OpenID Connect (OIDC)", + "provider_saml": "SAML 2.0", + "sign_in_button": "Mit SSO anmelden", + "sign_in_title": "Anmelden", + "sso_button": "SSO-Anmeldung" + }, "status": { "all_destinations_enabled": "Kann auf Einsätze, Wachen oder POIs reagieren", "calls_and_pois_destination_enabled": "Kann auf Einsätze oder POIs reagieren", @@ -916,173 +1328,6 @@ "stations_tab": "Wachen", "status_saved_successfully": "Status erfolgreich gespeichert!" }, - "dispatch": { - "active_calls": "Aktive Einsätze", - "pending_calls": "Ausstehend", - "scheduled_calls": "Geplant", - "units_available": "Verfügbar", - "personnel_available": "Verfügbar", - "personnel_on_duty": "Im Dienst", - "units": "Einheiten", - "personnel": "Personal", - "map": "Karte", - "notes": "Notizen", - "activity_log": "Aktivitätsprotokoll", - "communications": "Kommunikation", - "no_active_calls": "Keine aktiven Einsätze", - "no_units": "Keine Einheiten verfügbar", - "no_personnel": "Kein Personal verfügbar", - "no_notes": "Keine Notizen verfügbar", - "no_activity": "Keine aktuelle Aktivität", - "current_channel": "Aktueller Kanal", - "audio_stream": "Audiostream", - "no_stream": "Kein Stream aktiv", - "ptt": "PTT", - "ptt_start": "PTT Start", - "ptt_end": "PTT Ende", - "transmitting_on": "Senden auf {{channel}}", - "transmission_ended": "Übertragung beendet", - "voice_disabled": "Sprache deaktiviert", - "disconnected": "Getrennt", - "select_channel": "Kanal auswählen", - "select_channel_description": "Wählen Sie einen Sprachkanal zum Verbinden", - "change_channel_warning": "Das Auswählen eines neuen Kanals trennt die Verbindung zum aktuellen Kanal", - "default_channel": "Standard", - "no_channels_available": "Keine Sprachkanäle verfügbar", - "system_update": "Systemaktualisierung", - "data_refreshed": "Daten vom Server aktualisiert", - "call_selected": "Einsatz ausgewählt", - "unit_selected": "Einheit ausgewählt", - "unit_deselected": "Einheit abgewählt", - "personnel_selected": "Personal ausgewählt", - "personnel_deselected": "Personal abgewählt", - "loading_map": "Karte wird geladen...", - "map_not_available_web": "Karte auf der Webplattform nicht verfügbar", - "filtering_by_call": "Filterung nach Einsatz", - "clear_filter": "Filter löschen", - "call_filter_active": "Einsatzfilter aktiv", - "call_filter_cleared": "Einsatzfilter gelöscht", - "showing_all_data": "Alle Daten werden angezeigt", - "call_notes": "Einsatznotizen", - "no_call_notes": "Keine Einsatznotizen", - "add_call_note_placeholder": "Notiz hinzufügen...", - "note_added": "Notiz hinzugefügt", - "note_added_to_console": "Eine neue Notiz wurde zur Konsole hinzugefügt", - "add_note_title": "Neue Notiz hinzufügen", - "note_title_label": "Titel", - "note_title_placeholder": "Notiztitel eingeben...", - "note_category_label": "Kategorie", - "note_category_placeholder": "Kategorie auswählen", - "note_no_category": "Keine Kategorie", - "note_body_label": "Notizinhalt", - "note_body_placeholder": "Notizinhalt eingeben...", - "note_save_error": "Notiz konnte nicht gespeichert werden: {{error}}", - "note_created": "Notiz erstellt", - "units_on_call": "Einheiten im Einsatz", - "no_units_on_call": "Keine Einheiten im Einsatz", - "personnel_on_call": "Personal im Einsatz", - "no_personnel_on_call": "Kein Personal im Einsatz", - "call_activity": "Einsatzaktivität", - "no_call_activity": "Keine Einsatzaktivität", - "on_call": "Im Einsatz", - "filtered": "Gefiltert", - "active_filter": "Aktiver Filter", - "unit_status_change": "Statusänderung der Einheit", - "personnel_status_change": "Statusänderung des Personals", - "view_call_details": "Einsatzdetails anzeigen", - "dispatched_resources": "Disponiert", - "unassigned": "Nicht zugewiesen", - "available": "Verfügbar", - "unknown": "Unbekannt", - "search_personnel_placeholder": "Personal durchsuchen...", - "search_calls_placeholder": "Einsätze durchsuchen...", - "search_units_placeholder": "Einheiten durchsuchen...", - "search_notes_placeholder": "Notizen durchsuchen...", - "signalr_update": "Echtzeit-Aktualisierung", - "signalr_connected": "Verbunden", - "realtime_updates_active": "Echtzeit-Aktualisierungen sind jetzt aktiv", - "personnel_status_updated": "Personalstatus aktualisiert", - "personnel_staffing_updated": "Personalbesetzung aktualisiert", - "unit_status_updated": "Einheitenstatus aktualisiert", - "calls_updated": "Einsätze aktualisiert", - "call_added": "Neuer Einsatz hinzugefügt", - "call_closed": "Einsatz geschlossen", - "check_ins": "Kontrollen", - "no_check_ins": "Keine Einsätze mit Kontroll-Timern", - "radio_log": "Funkprotokoll", - "radio": "Funk", - "activity": "Aktivität", - "actions": "Aktionen", - "no_radio_activity": "Keine Funkübertragungen", - "live": "LIVE", - "currently_transmitting": "Sendet gerade...", - "duration": "Dauer", - "call_actions": "Einsatzaktionen", - "unit_actions": "Einheitenaktionen", - "personnel_actions": { - "title": "Personalaktionen", - "status_tab": "Status", - "staffing_tab": "Besetzung", - "select_status": "Status auswählen", - "select_staffing": "Besetzungsstufe auswählen", - "destination": "Ziel", - "no_destination": "Kein Ziel", - "note": "Notiz", - "note_placeholder": "Optionale Notiz hinzufügen...", - "update_status": "Status aktualisieren", - "update_staffing": "Besetzung aktualisieren", - "no_statuses_available": "Keine Status verfügbar", - "no_staffings_available": "Keine Besetzungsstufen verfügbar" - }, - "unit_actions_panel": { - "status": "Status", - "select_status": "Status auswählen", - "destination": "Ziel", - "no_destination": "Kein Ziel", - "note": "Notiz", - "note_placeholder": "Optionale Notiz hinzufügen...", - "update_status": "Status aktualisieren", - "no_statuses_available": "Keine Status verfügbar", - "no_active_calls": "Keine aktiven Einsätze", - "no_stations_available": "Keine Wachen verfügbar", - "no_destinations_available": "Keine Ziele verfügbar" - }, - "call": "Einsatz", - "station": "Wache", - "calls": "Einsätze", - "stations": "Wachen", - "no_stations_available": "Keine Wachen verfügbar", - "new_call": "Neuer Einsatz", - "view_details": "Details", - "add_note": "Notiz hinzufügen", - "close_call": "Schließen", - "set_status": "Status setzen", - "set_staffing": "Besetzung", - "dispatch": "Disponieren", - "select_items_for_actions": "Wählen Sie einen Einsatz, eine Einheit oder Personal aus, um kontextbezogene Aktionen zu aktivieren", - "weather": { - "clear": "Klar", - "mainly_clear": "Überwiegend klar", - "partly_cloudy": "Teilweise bewölkt", - "overcast": "Bedeckt", - "fog": "Nebel", - "drizzle": "Nieselregen", - "freezing_drizzle": "Gefrierender Nieselregen", - "rain": "Regen", - "freezing_rain": "Gefrierender Regen", - "snow": "Schnee", - "rain_showers": "Regenschauer", - "snow_showers": "Schneeschauer", - "thunderstorm": "Gewitter", - "thunderstorm_hail": "Gewitter mit Hagel", - "unknown": "Unbekannt" - }, - "available_only": "Nur verfügbare", - "single_list": "Einzelne Liste", - "resources": "Ressourcen", - "search_resources_placeholder": "Ressourcen suchen...", - "no_resources": "Keine Ressourcen" - }, "tabs": { "calls": "Einsätze", "calendar": "Kalender", @@ -1096,44 +1341,6 @@ "shifts": "Schichten", "personnel": "Personal" }, - "check_in": { - "tab_title": "Kontrolle", - "timer_status": "Timer-Status", - "perform_check_in": "Kontrolle durchführen", - "check_in_success": "Kontrolle erfolgreich erfasst", - "check_in_error": "Kontrolle konnte nicht erfasst werden", - "checked_in_by": "von {{name}}", - "last_check_in": "Letzte Kontrolle", - "elapsed": "Vergangen", - "duration": "Dauer", - "status_ok": "OK", - "status_green": "OK", - "status_warning": "Warnung", - "status_yellow": "Warnung", - "status_overdue": "Überfällig", - "status_red": "Überfällig", - "status_critical": "Kritisch", - "history": "Kontrollverlauf", - "no_timers": "Keine Kontroll-Timer konfiguriert", - "timers_disabled": "Kontroll-Timer sind für diesen Einsatz deaktiviert", - "type_personnel": "Personal", - "type_unit": "Einheit", - "type_ic": "Einsatzleiter", - "type_par": "PAR", - "type_hazmat": "Gefahrstoffexposition", - "type_sector_rotation": "Abschnittsrotation", - "type_rehab": "Erholung", - "add_note": "Notiz hinzufügen (optional)", - "confirm": "Kontrolle bestätigen", - "minutes_ago": "vor {{count}} Min.", - "select_target": "Einheit für Kontrolle auswählen", - "overdue_count": "{{count}} Überfällig", - "warning_count": "{{count}} Warnung", - "enable_timers": "Timer aktivieren", - "disable_timers": "Timer deaktivieren", - "summary": "{{overdue}} überfällig, {{warning}} Warnung, {{ok}} ok", - "par_title": "Personalübersicht (PAR)" - }, "units": { "search": "Einheiten suchen...", "loading": "Einheiten werden geladen...", @@ -1163,6 +1370,63 @@ "no_destination": "Keines", "title": "Einheiten" }, + "videoFeeds": { + "title": "Videoübertragungen", + "noFeeds": "Keine Videoübertragungen für diesen Einsatz", + "addFeed": "Videoübertragung hinzufügen", + "editFeed": "Videoübertragung bearbeiten", + "deleteFeed": "Videoübertragung löschen", + "deleteConfirm": "Sind Sie sicher, dass Sie diese Videoübertragung entfernen möchten?", + "watch": "Ansehen", + "goLive": "Live gehen", + "stopLive": "Live beenden", + "flipCamera": "Kamera wechseln", + "feedAdded": "Videoübertragung hinzugefügt", + "feedUpdated": "Videoübertragung aktualisiert", + "feedDeleted": "Videoübertragung entfernt", + "feedError": "Videoübertragung konnte nicht geladen werden", + "unsupportedFormat": "Dieses Streamformat wird auf Mobilgeräten nicht unterstützt", + "copyUrl": "URL kopieren", + "form": { + "name": "Name der Übertragung", + "namePlaceholder": "z. B. Löschfahrzeug 1 Drohne", + "url": "Stream-URL", + "urlPlaceholder": "z. B. https://stream.example.com/live.m3u8", + "feedType": "Kameratyp", + "feedFormat": "Streamformat", + "description": "Beschreibung", + "descriptionPlaceholder": "Optionale Beschreibung", + "status": "Status", + "sortOrder": "Sortierreihenfolge", + "cameraLocation": "Kamerastandort", + "useCurrentLocation": "Aktuellen Standort verwenden" + }, + "type": { + "drone": "Drohne", + "fixedCamera": "Festkamera", + "bodyCam": "Körperkamera", + "trafficCam": "Verkehrskamera", + "weatherCam": "Wetterkamera", + "satelliteFeed": "Satellitenübertragung", + "webCam": "Webcam", + "other": "Sonstige" + }, + "format": { + "rtsp": "RTSP", + "hls": "HLS", + "mjpeg": "MJPEG", + "youtubeLive": "YouTube Live", + "webrtc": "WebRTC", + "dash": "DASH", + "embed": "Einbetten", + "other": "Sonstige" + }, + "status": { + "active": "Aktiv", + "inactive": "Inaktiv", + "error": "Fehler" + } + }, "weatherAlerts": { "title": "Wetterwarnungen", "activeAlerts": "Aktive Warnungen", @@ -1270,251 +1534,5 @@ }, "stats_label": "Wetterwarnungen" }, - "videoFeeds": { - "title": "Videoübertragungen", - "noFeeds": "Keine Videoübertragungen für diesen Einsatz", - "addFeed": "Videoübertragung hinzufügen", - "editFeed": "Videoübertragung bearbeiten", - "deleteFeed": "Videoübertragung löschen", - "deleteConfirm": "Sind Sie sicher, dass Sie diese Videoübertragung entfernen möchten?", - "watch": "Ansehen", - "goLive": "Live gehen", - "stopLive": "Live beenden", - "flipCamera": "Kamera wechseln", - "feedAdded": "Videoübertragung hinzugefügt", - "feedUpdated": "Videoübertragung aktualisiert", - "feedDeleted": "Videoübertragung entfernt", - "feedError": "Videoübertragung konnte nicht geladen werden", - "unsupportedFormat": "Dieses Streamformat wird auf Mobilgeräten nicht unterstützt", - "copyUrl": "URL kopieren", - "form": { - "name": "Name der Übertragung", - "namePlaceholder": "z. B. Löschfahrzeug 1 Drohne", - "url": "Stream-URL", - "urlPlaceholder": "z. B. https://stream.example.com/live.m3u8", - "feedType": "Kameratyp", - "feedFormat": "Streamformat", - "description": "Beschreibung", - "descriptionPlaceholder": "Optionale Beschreibung", - "status": "Status", - "sortOrder": "Sortierreihenfolge", - "cameraLocation": "Kamerastandort", - "useCurrentLocation": "Aktuellen Standort verwenden" - }, - "type": { - "drone": "Drohne", - "fixedCamera": "Festkamera", - "bodyCam": "Körperkamera", - "trafficCam": "Verkehrskamera", - "weatherCam": "Wetterkamera", - "satelliteFeed": "Satellitenübertragung", - "webCam": "Webcam", - "other": "Sonstige" - }, - "format": { - "rtsp": "RTSP", - "hls": "HLS", - "mjpeg": "MJPEG", - "youtubeLive": "YouTube Live", - "webrtc": "WebRTC", - "dash": "DASH", - "embed": "Einbetten", - "other": "Sonstige" - }, - "status": { - "active": "Aktiv", - "inactive": "Inaktiv", - "error": "Fehler" - } - }, - "welcome": "Willkommen bei der obytes App-Seite", - "incident_command": { - "tab_title": "Leitung", - "title": "Einsatzleitung", - "open_full_board": "Gesamte Übersicht öffnen", - "no_command": "Keine Einsatzleitung eingerichtet", - "no_command_description": "Richten Sie eine Einsatzleitung ein, um Ressourcen, Rollen, Ziele und die Personalübersicht für diesen Einsatz zu koordinieren.", - "establish": "Einsatzleitung einrichten", - "establish_title": "Einsatzleitung einrichten", - "establish_description": "Die Führungsübersicht kann optional aus einer Vorlage erstellt werden.", - "establish_success": "Einsatzleitung eingerichtet", - "establish_error": "Einsatzleitung konnte nicht eingerichtet werden", - "template": "Vorlage", - "no_template": "Keine Vorlage (leere Übersicht)", - "saved": "Gespeichert", - "save_error": "Vorgang fehlgeschlagen", - "edit_action_plan": "Aktionsplan bearbeiten", - "action_plan": "Aktionsplan", - "action_plan_placeholder": "Einsatzaktionsplan beschreiben...", - "save": "Speichern", - "no_action_plan": "Kein Aktionsplan festgelegt.", - "add": "Hinzufügen", - "add_objective": "Ziel hinzufügen", - "objective_name": "Ziel", - "objective_type": "Typ", - "name_required": "Name ist erforderlich", - "add_lane": "Bereich hinzufügen", - "lane_name": "Bereichsname", - "lane_type": "Bereichstyp", - "assign_resource": "Ressource zuweisen", - "assign_resource_required": "Bereich und Ressource auswählen", - "lane": "Bereich", - "select_lane": "Bereich auswählen", - "resource_type": "Ressourcentyp", - "resource": "Ressource", - "select_resource": "Ressource auswählen", - "unit": "Einheit", - "personnel": "Personal", - "assign": "Zuweisen", - "assign_role": "Rolle zuweisen", - "assign_role_required": "Person und Rolle auswählen", - "person": "Person", - "select_person": "Person auswählen", - "role": "Rolle", - "select_role": "Rolle auswählen", - "transfer_title": "Einsatzleitung übertragen", - "transfer_notes": "Notizen", - "transfer": "Übertragen", - "transfer_command": "Übertragen", - "transfer_success": "Einsatzleitung übertragen", - "close_command": "Einsatzleitung schließen", - "confirm_close": "Einsatzleitung für diesen Einsatz schließen?", - "status": "Status", - "active": "Aktiv", - "closed": "Geschlossen", - "commander": "Einsatzleiter", - "established_on": "Eingerichtet", - "edit": "Bearbeiten", - "roles": "Leitungsrollen", - "no_roles": "Keine Rollen zugewiesen.", - "structure": "Leitungsstruktur", - "no_lanes": "Keine Bereiche definiert.", - "no_resources": "Keine Ressourcen zugewiesen.", - "release": "Freigeben", - "objectives": "Ziele", - "no_objectives": "Keine Ziele.", - "complete": "Abschließen", - "completed": "Abgeschlossen", - "timers": "Timer", - "no_timers": "Keine laufenden Timer.", - "acknowledge": "Bestätigen", - "due": "Fällig", - "accountability": "Personalübersicht (PAR)", - "run_par": "PAR durchführen", - "green": "Grün", - "warning": "Warnung", - "critical": "Kritisch", - "no_accountability": "Kein Personal erfasst.", - "timeline": "Leitungsverlauf", - "no_timeline": "Keine Verlaufseinträge.", - "unassigned": "Nicht zugewiesen", - "active_title": "Aktive Einsatzleitungen", - "no_active": "Keine aktiven Einsatzleitungen", - "no_active_description": "Für Einsätze eingerichtete Einsatzleitungen werden hier angezeigt.", - "call": "Einsatz", - "tactical_map": "Taktische Karte", - "annotations": "Kartenmarkierungen", - "no_annotations": "Keine Markierungen.", - "open_tactical_map": "Taktische Karte öffnen", - "marker": "Markierung", - "add_marker": "Markierung hinzufügen", - "tap_to_place": "Tippen Sie auf die Karte, um eine Markierung zu setzen", - "marker_label": "Beschriftung der Markierung", - "delete_annotation_confirm": "Diese Markierung entfernen?", - "voice_channels": "Sprachkanäle", - "no_channels": "Keine offenen Kanäle.", - "add_channel": "Kanal hinzufügen", - "channel_name": "Kanalname", - "close_all_channels": "Alle Kanäle schließen", - "join": "Beitreten", - "hold_to_talk": "Zum Sprechen gedrückt halten", - "talking": "Übertragung...", - "voice_joined": "Sprachkanal beigetreten", - "voice_join_error": "Beitritt zum Sprachkanal fehlgeschlagen", - "move": "Verschieben", - "move_lane": "Bereich verschieben", - "parent_lane": "Übergeordneter Bereich", - "top_level": "Oberste Ebene", - "move_resource": "Ressource verschieben" - }, - "chat": { - "title": "Chat", - "assistant": "Assistent", - "empty": "Noch keine Unterhaltungen. Starte eine Direktnachricht oder erstelle eine Gruppe.", - "section_assistant": "Assistent", - "section_direct_messages": "Direktnachrichten", - "section_channels": "Kanäle", - "section_incidents": "Vorfälle", - "new_direct_message": "Neue Direktnachricht", - "new_group": "Neue Gruppe", - "open_assistant": "Assistent öffnen", - "create_conversation_failed": "Unterhaltung konnte nicht gestartet werden", - "group_name": "Gruppenname", - "search_people": "Personen suchen", - "no_people": "Keine Personen gefunden", - "create_group_with": "Gruppe erstellen ({{count}})", - "message_deleted": "Diese Nachricht wurde gelöscht", - "urgent": "Dringend", - "urgent_will_send": "Diese Nachricht wird als dringend gesendet", - "shared_location": "Geteilter Standort", - "thread_replies": "{{count}} Antworten", - "edited": "(bearbeitet)", - "failed_tap_retry": "Fehlgeschlagen – zum Wiederholen tippen", - "type_a_message": "Nachricht eingeben", - "emoji": "Emoji", - "add_image": "Bild hinzufügen", - "add_gif": "GIF hinzufügen", - "share_location": "Standort teilen", - "send": "Senden", - "someone": "Jemand", - "is_typing": "{{name}} schreibt...", - "are_typing": "{{count}} Personen schreiben...", - "permission_photos_denied": "Zugriff auf die Fotomediathek verweigert", - "permission_location_denied": "Zugriff auf den Standort verweigert", - "search_gifs": "GIFs suchen", - "no_gifs": "Keine GIFs gefunden", - "flag_reason": "Warum meldest du dies?", - "flag_inappropriate": "Unangemessen", - "flag_harassment": "Belästigung", - "flag_spam": "Spam", - "flag_sensitive": "Sensible Informationen", - "flag_policy": "Richtlinienverstoß", - "flag_other": "Sonstiges", - "reply_in_thread": "Im Thread antworten", - "copy": "Kopieren", - "copied": "Kopiert", - "copy_unavailable": "Kopieren ist auf diesem Gerät nicht verfügbar", - "edit": "Bearbeiten", - "edit_message": "Nachricht bearbeiten", - "save": "Speichern", - "delete": "Löschen", - "pin": "Anheften", - "unpin": "Anheften aufheben", - "flag": "Melden", - "moderator_delete": "Entfernen (Moderator)", - "moderator_removed": "Vom Moderator entfernt", - "attachment_failed": "Anhang-Upload fehlgeschlagen", - "ack_required": "Bestätigung erforderlich", - "ack_pending_one": "Du hast eine dringende Nachricht zu bestätigen", - "ack_pending_count": "Du hast {{count}} dringende Nachrichten zu bestätigen", - "acknowledge": "Bestätigen", - "thread": "Thread", - "original_message": "Ursprüngliche Nachricht", - "reply_placeholder": "Antworten...", - "channel": "Kanal", - "direct_message": "Direktnachricht", - "load_people_failed": "Personen konnten nicht geladen werden", - "reaction_failed": "Reaktion konnte nicht aktualisiert werden", - "edit_failed": "Nachricht konnte nicht bearbeitet werden", - "delete_failed": "Nachricht konnte nicht gelöscht werden", - "pin_failed": "Anheften konnte nicht aktualisiert werden", - "flag_failed": "Nachricht konnte nicht gemeldet werden" - }, - "chatbot": { - "title": "Assistent", - "subtitle": "KI-Helfer für deine Abteilung", - "new_session": "Neue Sitzung", - "empty": "Frag den Assistenten etwas, um zu beginnen.", - "ask_placeholder": "Frag den Assistenten..." - } + "welcome": "Willkommen bei der obytes App-Seite" } diff --git a/src/translations/en.json b/src/translations/en.json index e82795dc..4852e3ba 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -451,6 +451,44 @@ "empty": "Ask the assistant anything to get started.", "ask_placeholder": "Ask the assistant..." }, + "check_in": { + "tab_title": "Check-In", + "timer_status": "Timer Status", + "perform_check_in": "Check In", + "check_in_success": "Check-in recorded successfully", + "check_in_error": "Failed to record check-in", + "checked_in_by": "by {{name}}", + "last_check_in": "Last check-in", + "elapsed": "Elapsed", + "duration": "Duration", + "status_ok": "OK", + "status_green": "OK", + "status_warning": "Warning", + "status_yellow": "Warning", + "status_overdue": "Overdue", + "status_red": "Overdue", + "status_critical": "Critical", + "history": "Check-In History", + "no_timers": "No check-in timers configured", + "timers_disabled": "Check-in timers are disabled for this call", + "type_personnel": "Personnel", + "type_unit": "Unit", + "type_ic": "Incident Commander", + "type_par": "PAR", + "type_hazmat": "Hazmat Exposure", + "type_sector_rotation": "Sector Rotation", + "type_rehab": "Rehab", + "add_note": "Add Note (Optional)", + "confirm": "Confirm Check-In", + "minutes_ago": "{{count}} min ago", + "select_target": "Select Entity to Check In", + "overdue_count": "{{count}} Overdue", + "warning_count": "{{count}} Warning", + "enable_timers": "Enable Timers", + "disable_timers": "Disable Timers", + "summary": "{{overdue}} overdue, {{warning}} warning, {{ok}} ok", + "par_title": "Personnel Accountability (PAR)" + }, "common": { "add": "Add", "back": "Back", @@ -582,21 +620,314 @@ "website": "Website", "zip": "Zip Code" }, + "dispatch": { + "active_calls": "Active Calls", + "pending_calls": "Pending", + "scheduled_calls": "Scheduled", + "units_available": "Available", + "personnel_available": "Available", + "personnel_on_duty": "On Duty", + "units": "Units", + "personnel": "Personnel", + "map": "Map", + "notes": "Notes", + "activity_log": "Activity Log", + "communications": "Communications", + "no_active_calls": "No active calls", + "no_units": "No units available", + "no_personnel": "No personnel available", + "no_notes": "No notes available", + "no_activity": "No recent activity", + "current_channel": "Current Channel", + "audio_stream": "Audio Stream", + "no_stream": "No stream active", + "ptt": "PTT", + "ptt_start": "PTT Start", + "ptt_end": "PTT End", + "transmitting_on": "Transmitting on {{channel}}", + "transmission_ended": "Transmission ended", + "voice_disabled": "Voice disabled", + "disconnected": "Disconnected", + "select_channel": "Select Channel", + "select_channel_description": "Choose a voice channel to connect to", + "change_channel_warning": "Selecting a new channel will disconnect from the current one", + "default_channel": "Default", + "no_channels_available": "No voice channels available", + "system_update": "System Update", + "data_refreshed": "Data refreshed from server", + "call_selected": "Call Selected", + "unit_selected": "Unit Selected", + "unit_deselected": "Unit Deselected", + "personnel_selected": "Personnel Selected", + "personnel_deselected": "Personnel Deselected", + "loading_map": "Loading map...", + "map_not_available_web": "Map not available on web platform", + "filtering_by_call": "Filtering by call", + "clear_filter": "Clear Filter", + "call_filter_active": "Call Filter Active", + "call_filter_cleared": "Call Filter Cleared", + "showing_all_data": "Showing all data", + "call_notes": "Call Notes", + "no_call_notes": "No call notes", + "add_call_note_placeholder": "Add a note...", + "note_added": "Note Added", + "note_added_to_console": "A new note has been added to the console", + "add_note_title": "Add New Note", + "note_title_label": "Title", + "note_title_placeholder": "Enter note title...", + "note_category_label": "Category", + "note_category_placeholder": "Select a category", + "note_no_category": "No Category", + "note_body_label": "Note Content", + "note_body_placeholder": "Enter note content...", + "note_save_error": "Failed to save note: {{error}}", + "note_created": "Note Created", + "units_on_call": "Units on Call", + "no_units_on_call": "No units on call", + "personnel_on_call": "Personnel on Call", + "no_personnel_on_call": "No personnel on call", + "call_activity": "Call Activity", + "no_call_activity": "No call activity", + "on_call": "On Call", + "filtered": "Filtered", + "active_filter": "Active Filter", + "unit_status_change": "Unit Status Change", + "personnel_status_change": "Personnel Status Change", + "view_call_details": "View Call Details", + "dispatched_resources": "Dispatched", + "unassigned": "Unassigned", + "available": "Available", + "unknown": "Unknown", + "search_personnel_placeholder": "Search personnel...", + "search_calls_placeholder": "Search calls...", + "search_units_placeholder": "Search units...", + "search_notes_placeholder": "Search notes...", + "signalr_update": "Real-time Update", + "signalr_connected": "Connected", + "realtime_updates_active": "Real-time updates are now active", + "personnel_status_updated": "Personnel status updated", + "personnel_staffing_updated": "Personnel staffing updated", + "unit_status_updated": "Unit status updated", + "calls_updated": "Calls updated", + "call_added": "New call added", + "call_closed": "Call closed", + "check_ins": "Check-Ins", + "no_check_ins": "No calls with check-in timers", + "radio_log": "Radio Log", + "radio": "Radio", + "activity": "Activity", + "actions": "Actions", + "no_radio_activity": "No radio transmissions", + "live": "LIVE", + "currently_transmitting": "Currently transmitting...", + "duration": "Duration", + "call_actions": "Call Actions", + "unit_actions": "Unit Actions", + "personnel_actions": { + "title": "Personnel Actions", + "status_tab": "Status", + "staffing_tab": "Staffing", + "select_status": "Select Status", + "select_staffing": "Select Staffing Level", + "destination": "Destination", + "no_destination": "No Destination", + "note": "Note", + "note_placeholder": "Add an optional note...", + "update_status": "Update Status", + "update_staffing": "Update Staffing", + "no_statuses_available": "No statuses available", + "no_staffings_available": "No staffing levels available" + }, + "unit_actions_panel": { + "status": "Status", + "select_status": "Select Status", + "destination": "Destination", + "no_destination": "No Destination", + "note": "Note", + "note_placeholder": "Add an optional note...", + "update_status": "Update Status", + "no_statuses_available": "No statuses available", + "no_active_calls": "No active calls", + "no_stations_available": "No stations available", + "no_destinations_available": "No destinations available" + }, + "call": "Call", + "station": "Station", + "calls": "Calls", + "stations": "Stations", + "no_stations_available": "No stations available", + "new_call": "New Call", + "view_details": "Details", + "add_note": "Add Note", + "close_call": "Close", + "set_status": "Set Status", + "set_staffing": "Staffing", + "dispatch": "Dispatch", + "select_items_for_actions": "Select a call, unit, or personnel to enable context-aware actions", + "weather": { + "clear": "Clear", + "mainly_clear": "Mainly Clear", + "partly_cloudy": "Partly Cloudy", + "overcast": "Overcast", + "fog": "Fog", + "drizzle": "Drizzle", + "freezing_drizzle": "Freezing Drizzle", + "rain": "Rain", + "freezing_rain": "Freezing Rain", + "snow": "Snow", + "rain_showers": "Rain Showers", + "snow_showers": "Snow Showers", + "thunderstorm": "Thunderstorm", + "thunderstorm_hail": "Thunderstorm w/ Hail", + "unknown": "Unknown" + }, + "available_only": "Available only", + "single_list": "Single list", + "resources": "Resources", + "search_resources_placeholder": "Search resources...", + "no_resources": "No resources" + }, "form": { "invalid_url": "Please enter a valid URL starting with https:// (http:// is only allowed for localhost)", "required": "This field is required" }, - "livekit": { - "audio_devices": "Audio Devices", - "audio_settings": "Audio Settings", - "connected_to_room": "Connected to Channel", - "connecting": "Connecting...", - "disconnect": "Disconnect", + "incident_command": { + "accountability": "Accountability (PAR)", + "acknowledge": "Acknowledge", + "action_plan": "Action Plan", + "action_plan_placeholder": "Describe the incident action plan...", + "active": "Active", + "active_title": "Active Incident Commands", + "add": "Add", + "add_channel": "Add Channel", + "add_lane": "Add Lane", + "add_marker": "Add Marker", + "add_objective": "Add Objective", + "annotations": "Map Annotations", + "assign": "Assign", + "assign_resource": "Assign Resource", + "assign_resource_required": "Select a lane and a resource", + "assign_role": "Assign Role", + "assign_role_required": "Select a person and a role", + "call": "Call", + "channel_name": "Channel name", + "chat": "Chat", + "chat_frozen": "This incident is closed. Conversations are kept as a point-in-time record — no new messages.", + "close_all_channels": "Close all channels", + "close_command": "Close Command", + "closed": "Closed", + "command_channel": "Command chat", + "command_channel_hint": "Command staff and dispatch", + "command_channel_unavailable": "No command channel has been created for this incident yet.", + "commander": "Commander", + "complete": "Complete", + "completed": "Completed", + "confirm_close": "Close incident command for this call?", + "critical": "Critical", + "delete_annotation_confirm": "Remove this annotation?", + "dispatch_channel": "Dispatch", + "dispatch_channel_hint": "The incident's line to the desk", + "dispatch_channel_unavailable": "No dispatch channel has been created for this incident yet.", + "dm_failed": "Couldn't open that conversation.", + "dm_unavailable": "That contact has no Resgrid account to message.", + "due": "Due", + "edit": "Edit", + "edit_action_plan": "Edit Action Plan", + "establish": "Establish Command", + "establish_description": "Optionally seed the command board from a template.", + "establish_error": "Failed to establish command", + "establish_success": "Incident command established", + "establish_title": "Establish Incident Command", + "established_on": "Established", + "green": "Green", + "hold_to_talk": "Hold to Talk", + "incident_channel": "Incident chat", + "incident_channel_hint": "Everyone working the incident", + "incident_channel_unavailable": "No incident channel has been created for this call yet.", "join": "Join", - "microphone": "Microphone", - "mute": "Mute", - "no_rooms_available": "No voice channels available", - "speaker": "Speaker", + "lane": "Lane", + "lane_name": "Lane Name", + "lane_type": "Lane Type", + "marker": "Marker", + "marker_label": "Marker label", + "move": "Move", + "move_lane": "Move Lane", + "move_resource": "Move Resource", + "name_required": "Name is required", + "no_accountability": "No personnel tracked.", + "no_action_plan": "No action plan set.", + "no_active": "No Active Incident Commands", + "no_active_description": "Incident commands established on calls will appear here.", + "no_annotations": "No annotations.", + "no_channels": "No open channels.", + "no_command": "No Incident Command Established", + "no_command_description": "Establish incident command to coordinate resources, roles, objectives and accountability for this call.", + "no_lanes": "No lanes defined.", + "no_objectives": "No objectives.", + "no_resources": "No resources assigned.", + "no_roles": "No roles assigned.", + "no_template": "No template (blank board)", + "no_timeline": "No timeline entries.", + "no_timers": "No timers running.", + "not_authorized": "Command board unavailable", + "not_authorized_description": "Your department hasn't authorized you to work incident command. Ask an administrator for the Command App Login permission.", + "objective_name": "Objective", + "objective_type": "Type", + "objectives": "Objectives", + "open_chat": "Open", + "open_full_board": "Open Full Board", + "open_tactical_map": "Open tactical map", + "parent_lane": "Parent lane", + "person": "Person", + "personnel": "Personnel", + "release": "Release", + "resource": "Resource", + "resource_type": "Resource Type", + "role": "Role", + "roles": "Command Roles", + "run_par": "Run PAR", + "save": "Save", + "save_error": "Operation failed", + "saved": "Saved", + "select_lane": "Select a lane", + "select_person": "Select a person", + "select_resource": "Select a resource", + "select_role": "Select a role", + "send_message": "Message", + "status": "Status", + "structure": "Command Structure", + "tab_title": "Command", + "tactical_map": "Tactical Map", + "talking": "Transmitting...", + "tap_to_place": "Tap the map to place a marker", + "template": "Template", + "timeline": "Command Timeline", + "timers": "Timers", + "title": "Incident Command", + "top_level": "Top level", + "transfer": "Transfer", + "transfer_command": "Transfer", + "transfer_notes": "Notes", + "transfer_success": "Command transferred", + "transfer_title": "Transfer Command", + "unassigned": "Unassigned", + "unit": "Unit", + "voice_channels": "Voice Channels", + "voice_join_error": "Failed to join voice channel", + "voice_joined": "Joined voice channel", + "warning": "Warning" + }, + "livekit": { + "audio_devices": "Audio Devices", + "audio_settings": "Audio Settings", + "connected_to_room": "Connected to Channel", + "connecting": "Connecting...", + "disconnect": "Disconnect", + "join": "Join", + "microphone": "Microphone", + "mute": "Mute", + "no_rooms_available": "No voice channels available", + "speaker": "Speaker", "speaking": "Speaking", "title": "Voice Channels", "unmute": "Unmute" @@ -607,32 +938,22 @@ "pleaseWait": "Please wait", "processingRequest": "Processing your request..." }, - "sso": { - "authenticating": "Authenticating...", - "back_to_login": "Back to Login", - "back_to_lookup": "Change User", - "continue_button": "Continue", - "department_id_label": "Department ID", - "department_id_placeholder": "Enter department ID", - "error_generic": "Sign-in failed. Please try again.", - "error_oidc_cancelled": "Sign-in was cancelled.", - "error_oidc_not_ready": "SSO provider is loading, please wait.", - "error_sso_not_enabled": "Single sign-on is not enabled for this user.", - "error_token_exchange": "Failed to complete sign-in. Please try again.", - "error_user_not_found": "User not found. Please check and try again.", - "looking_up": "Looking up...", - "optional": "optional", - "page_subtitle": "Enter your username to look up your organization's sign-in options.", - "page_title": "Single Sign-On", - "provider_oidc": "OpenID Connect (OIDC)", - "provider_saml": "SAML 2.0", - "sign_in_button": "Sign In with SSO", - "sign_in_title": "Sign In", - "sso_button": "SSO Login" + "lockscreen": { + "message": "Enter your password to unlock the screen", + "not_you": "Not you? Return to login", + "password": "Password", + "password_placeholder": "Enter your password", + "title": "Lock Screen", + "unlock_button": "Unlock", + "unlock_failed": "Failed to unlock. Please try again.", + "relogin_required": "Password verification is unavailable for this session. Please log in again.", + "unlocking": "Unlocking...", + "welcome_back": "Welcome Back" }, "login": { "branding_subtitle": "Powerful dispatch software for first responders, search & rescue, and public safety organizations.", "branding_title": "Emergency Response Management", + "dispatch_not_authorized": "You are not authorized to use the Dispatch app. Contact your department administrator.", "errorModal": { "confirmButton": "OK", "message": "Please check your username and password and try again.", @@ -663,18 +984,6 @@ "username_placeholder": "Enter your username", "welcome_title": "Welcome Back" }, - "lockscreen": { - "message": "Enter your password to unlock the screen", - "not_you": "Not you? Return to login", - "password": "Password", - "password_placeholder": "Enter your password", - "title": "Lock Screen", - "unlock_button": "Unlock", - "unlock_failed": "Failed to unlock. Please try again.", - "relogin_required": "Password verification is unavailable for this session. Please log in again.", - "unlocking": "Unlocking...", - "welcome_back": "Welcome Back" - }, "maintenance": { "downtime_message": "We are working hard to complete the maintenance as quickly as possible. Please check back soon.", "downtime_title": "What is the Downtime?", @@ -685,6 +994,21 @@ "why_down_message": "We are performing scheduled maintenance to improve your experience. We apologize for any inconvenience.", "why_down_title": "Why is the Site Down?" }, + "map": { + "call_set_as_current": "Call set as current call", + "failed_to_open_maps": "Failed to open maps application", + "failed_to_set_current_call": "Failed to set call as current call", + "layers": "Map Layers", + "no_layers": "No layers available", + "no_location_for_routing": "No location data available for routing", + "pin_color": "Pin Color", + "recenter_map": "Recenter Map", + "set_as_current_call": "Set as Current Call", + "show_all": "Show All", + "hide_all": "Hide All", + "view_call_details": "View Call Details", + "view_poi_details": "View POI Details" + }, "menu": { "calls": "Calls", "calls_list": "Calls List", @@ -705,71 +1029,6 @@ "chat": "Chat", "assistant": "Assistant" }, - "pois": { - "address": "Address", - "all_types": "All types", - "destination": "Destination", - "details": "Details", - "detail_not_found": "POI not found", - "detail_not_found_description": "The requested POI could not be loaded.", - "detail_title": "POI Details", - "empty": "No POIs found", - "empty_description": "There are no points of interest available for your department yet.", - "empty_filtered": "No matching POIs", - "empty_filtered_description": "Try clearing your search or choosing a different POI type.", - "filter_by_type": "Filter by POI type", - "invalid_poi": "Invalid POI", - "invalid_poi_description": "The selected POI identifier is not valid.", - "loading": "Loading POIs...", - "loading_detail": "Loading POI details...", - "map": "Map", - "no_location": "No location available", - "no_location_description": "This POI does not have usable coordinates.", - "no_location_for_routing": "No location data available for routing", - "note": "Note", - "route_error": "Failed to open maps application", - "search": "Search POIs...", - "sort": "Sort", - "sort_options": { - "address-asc": "Address", - "name-asc": "Name (A-Z)", - "name-desc": "Name (Z-A)", - "type-asc": "Type" - }, - "title": "POIs", - "type": "Type", - "unknown_type": "Unknown type", - "unnamed": "Unnamed POI" - }, - "scheduled_calls": { - "title": "Scheduled Calls", - "loading": "Loading scheduled calls...", - "no_scheduled_calls": "No scheduled calls", - "no_scheduled_calls_description": "There are no pending scheduled calls at this time.", - "search": "Search scheduled calls...", - "scheduled_for": "Scheduled for", - "table_number": "Call #", - "table_name": "Name", - "table_type": "Type", - "table_priority": "Priority", - "table_address": "Address", - "table_scheduled": "Scheduled For" - }, - "map": { - "call_set_as_current": "Call set as current call", - "failed_to_open_maps": "Failed to open maps application", - "failed_to_set_current_call": "Failed to set call as current call", - "layers": "Map Layers", - "no_layers": "No layers available", - "no_location_for_routing": "No location data available for routing", - "pin_color": "Pin Color", - "recenter_map": "Recenter Map", - "set_as_current_call": "Set as Current Call", - "show_all": "Show All", - "hide_all": "Hide All", - "view_call_details": "View Call Details", - "view_poi_details": "View POI Details" - }, "notes": { "actions": { "add": "Add Note", @@ -789,6 +1048,23 @@ "search": "Search notes...", "title": "Notes" }, + "onboarding": { + "screen1": { + "title": "Resgrid Dispatch", + "description": "Create, dispatch, and manage emergency calls with a powerful mobile command center at your fingertips" + }, + "screen2": { + "title": "Real-Time Situational Awareness", + "description": "Track all units, personnel, and resources on an interactive map with live status updates and AVL" + }, + "screen3": { + "title": "Seamless Coordination", + "description": "Communicate instantly with field units, update call statuses, and coordinate response efforts from anywhere" + }, + "skip": "Skip", + "next": "Next", + "getStarted": "Let's Get Started" + }, "personnel": { "title": "Personnel", "search": "Search personnel...", @@ -821,22 +1097,41 @@ "send_email": "Email", "custom_fields": "Additional Information" }, - "onboarding": { - "screen1": { - "title": "Resgrid Dispatch", - "description": "Create, dispatch, and manage emergency calls with a powerful mobile command center at your fingertips" + "pois": { + "address": "Address", + "all_types": "All types", + "destination": "Destination", + "details": "Details", + "detail_not_found": "POI not found", + "detail_not_found_description": "The requested POI could not be loaded.", + "detail_title": "POI Details", + "empty": "No POIs found", + "empty_description": "There are no points of interest available for your department yet.", + "empty_filtered": "No matching POIs", + "empty_filtered_description": "Try clearing your search or choosing a different POI type.", + "filter_by_type": "Filter by POI type", + "invalid_poi": "Invalid POI", + "invalid_poi_description": "The selected POI identifier is not valid.", + "loading": "Loading POIs...", + "loading_detail": "Loading POI details...", + "map": "Map", + "no_location": "No location available", + "no_location_description": "This POI does not have usable coordinates.", + "no_location_for_routing": "No location data available for routing", + "note": "Note", + "route_error": "Failed to open maps application", + "search": "Search POIs...", + "sort": "Sort", + "sort_options": { + "address-asc": "Address", + "name-asc": "Name (A-Z)", + "name-desc": "Name (Z-A)", + "type-asc": "Type" }, - "screen2": { - "title": "Real-Time Situational Awareness", - "description": "Track all units, personnel, and resources on an interactive map with live status updates and AVL" - }, - "screen3": { - "title": "Seamless Coordination", - "description": "Communicate instantly with field units, update call statuses, and coordinate response efforts from anywhere" - }, - "skip": "Skip", - "next": "Next", - "getStarted": "Let's Get Started" + "title": "POIs", + "type": "Type", + "unknown_type": "Unknown type", + "unnamed": "Unnamed POI" }, "protocols": { "details": { @@ -876,6 +1171,20 @@ "tap_to_manage": "Tap to manage roles", "unassigned": "Unassigned" }, + "scheduled_calls": { + "title": "Scheduled Calls", + "loading": "Loading scheduled calls...", + "no_scheduled_calls": "No scheduled calls", + "no_scheduled_calls_description": "There are no pending scheduled calls at this time.", + "search": "Search scheduled calls...", + "scheduled_for": "Scheduled for", + "table_number": "Call #", + "table_name": "Name", + "table_type": "Type", + "table_priority": "Priority", + "table_address": "Address", + "table_scheduled": "Scheduled For" + }, "settings": { "about": "About", "account": "Account", @@ -964,6 +1273,29 @@ "version": "Version", "website": "Website" }, + "sso": { + "authenticating": "Authenticating...", + "back_to_login": "Back to Login", + "back_to_lookup": "Change User", + "continue_button": "Continue", + "department_id_label": "Department ID", + "department_id_placeholder": "Enter department ID", + "error_generic": "Sign-in failed. Please try again.", + "error_oidc_cancelled": "Sign-in was cancelled.", + "error_oidc_not_ready": "SSO provider is loading, please wait.", + "error_sso_not_enabled": "Single sign-on is not enabled for this user.", + "error_token_exchange": "Failed to complete sign-in. Please try again.", + "error_user_not_found": "User not found. Please check and try again.", + "looking_up": "Looking up...", + "optional": "optional", + "page_subtitle": "Enter your username to look up your organization's sign-in options.", + "page_title": "Single Sign-On", + "provider_oidc": "OpenID Connect (OIDC)", + "provider_saml": "SAML 2.0", + "sign_in_button": "Sign In with SSO", + "sign_in_title": "Sign In", + "sso_button": "SSO Login" + }, "status": { "add_note": "Add Note", "all_destinations_enabled": "Can respond to calls, stations, or POIs", @@ -972,247 +1304,42 @@ "calls_and_pois_destination_enabled": "Can respond to calls or POIs", "calls_tab": "Calls", "failed_to_save_status": "Failed to save status. Please try again.", - "general_status": "General status without specific destination", - "loading_pois": "Loading POIs...", - "loading_stations": "Loading stations...", - "no_destination": "No Destination", - "no_pois_available": "No POIs available", - "no_stations_available": "No stations available", - "no_statuses_available": "No statuses available", - "note": "Note", - "note_optional": "Add an optional note for this status update", - "note_required": "Please enter a note for this status update", - "poi_destination_enabled": "Can respond to POIs", - "pois_tab": "POIs", - "select_destination": "Select Destination for {{status}}", - "select_destination_type": "Where would you like to respond?", - "select_status": "Select Status", - "select_status_type": "What status would you like to set?", - "selected_destination": "Selected Destination", - "selected_status": "Selected Status", - "set_status": "Set Status", - "station_destination_enabled": "Can respond to stations", - "stations_and_pois_destination_enabled": "Can respond to stations or POIs", - "stations_tab": "Stations", - "status_saved_successfully": "Status saved successfully!" - }, - "dispatch": { - "active_calls": "Active Calls", - "pending_calls": "Pending", - "scheduled_calls": "Scheduled", - "units_available": "Available", - "personnel_available": "Available", - "personnel_on_duty": "On Duty", - "units": "Units", - "personnel": "Personnel", - "map": "Map", - "notes": "Notes", - "activity_log": "Activity Log", - "communications": "Communications", - "no_active_calls": "No active calls", - "no_units": "No units available", - "no_personnel": "No personnel available", - "no_notes": "No notes available", - "no_activity": "No recent activity", - "current_channel": "Current Channel", - "audio_stream": "Audio Stream", - "no_stream": "No stream active", - "ptt": "PTT", - "ptt_start": "PTT Start", - "ptt_end": "PTT End", - "transmitting_on": "Transmitting on {{channel}}", - "transmission_ended": "Transmission ended", - "voice_disabled": "Voice disabled", - "disconnected": "Disconnected", - "select_channel": "Select Channel", - "select_channel_description": "Choose a voice channel to connect to", - "change_channel_warning": "Selecting a new channel will disconnect from the current one", - "default_channel": "Default", - "no_channels_available": "No voice channels available", - "system_update": "System Update", - "data_refreshed": "Data refreshed from server", - "call_selected": "Call Selected", - "unit_selected": "Unit Selected", - "unit_deselected": "Unit Deselected", - "personnel_selected": "Personnel Selected", - "personnel_deselected": "Personnel Deselected", - "loading_map": "Loading map...", - "map_not_available_web": "Map not available on web platform", - "filtering_by_call": "Filtering by call", - "clear_filter": "Clear Filter", - "call_filter_active": "Call Filter Active", - "call_filter_cleared": "Call Filter Cleared", - "showing_all_data": "Showing all data", - "call_notes": "Call Notes", - "no_call_notes": "No call notes", - "add_call_note_placeholder": "Add a note...", - "note_added": "Note Added", - "note_added_to_console": "A new note has been added to the console", - "add_note_title": "Add New Note", - "note_title_label": "Title", - "note_title_placeholder": "Enter note title...", - "note_category_label": "Category", - "note_category_placeholder": "Select a category", - "note_no_category": "No Category", - "note_body_label": "Note Content", - "note_body_placeholder": "Enter note content...", - "note_save_error": "Failed to save note: {{error}}", - "note_created": "Note Created", - "units_on_call": "Units on Call", - "no_units_on_call": "No units on call", - "personnel_on_call": "Personnel on Call", - "no_personnel_on_call": "No personnel on call", - "call_activity": "Call Activity", - "no_call_activity": "No call activity", - "on_call": "On Call", - "filtered": "Filtered", - "active_filter": "Active Filter", - "unit_status_change": "Unit Status Change", - "personnel_status_change": "Personnel Status Change", - "view_call_details": "View Call Details", - "dispatched_resources": "Dispatched", - "unassigned": "Unassigned", - "available": "Available", - "unknown": "Unknown", - "search_personnel_placeholder": "Search personnel...", - "search_calls_placeholder": "Search calls...", - "search_units_placeholder": "Search units...", - "search_notes_placeholder": "Search notes...", - "signalr_update": "Real-time Update", - "signalr_connected": "Connected", - "realtime_updates_active": "Real-time updates are now active", - "personnel_status_updated": "Personnel status updated", - "personnel_staffing_updated": "Personnel staffing updated", - "unit_status_updated": "Unit status updated", - "calls_updated": "Calls updated", - "call_added": "New call added", - "call_closed": "Call closed", - "check_ins": "Check-Ins", - "no_check_ins": "No calls with check-in timers", - "radio_log": "Radio Log", - "radio": "Radio", - "activity": "Activity", - "actions": "Actions", - "no_radio_activity": "No radio transmissions", - "live": "LIVE", - "currently_transmitting": "Currently transmitting...", - "duration": "Duration", - "call_actions": "Call Actions", - "unit_actions": "Unit Actions", - "personnel_actions": { - "title": "Personnel Actions", - "status_tab": "Status", - "staffing_tab": "Staffing", - "select_status": "Select Status", - "select_staffing": "Select Staffing Level", - "destination": "Destination", - "no_destination": "No Destination", - "note": "Note", - "note_placeholder": "Add an optional note...", - "update_status": "Update Status", - "update_staffing": "Update Staffing", - "no_statuses_available": "No statuses available", - "no_staffings_available": "No staffing levels available" - }, - "unit_actions_panel": { - "status": "Status", - "select_status": "Select Status", - "destination": "Destination", - "no_destination": "No Destination", - "note": "Note", - "note_placeholder": "Add an optional note...", - "update_status": "Update Status", - "no_statuses_available": "No statuses available", - "no_active_calls": "No active calls", - "no_stations_available": "No stations available", - "no_destinations_available": "No destinations available" - }, - "call": "Call", - "station": "Station", - "calls": "Calls", - "stations": "Stations", - "no_stations_available": "No stations available", - "new_call": "New Call", - "view_details": "Details", - "add_note": "Add Note", - "close_call": "Close", - "set_status": "Set Status", - "set_staffing": "Staffing", - "dispatch": "Dispatch", - "select_items_for_actions": "Select a call, unit, or personnel to enable context-aware actions", - "weather": { - "clear": "Clear", - "mainly_clear": "Mainly Clear", - "partly_cloudy": "Partly Cloudy", - "overcast": "Overcast", - "fog": "Fog", - "drizzle": "Drizzle", - "freezing_drizzle": "Freezing Drizzle", - "rain": "Rain", - "freezing_rain": "Freezing Rain", - "snow": "Snow", - "rain_showers": "Rain Showers", - "snow_showers": "Snow Showers", - "thunderstorm": "Thunderstorm", - "thunderstorm_hail": "Thunderstorm w/ Hail", - "unknown": "Unknown" - }, - "available_only": "Available only", - "single_list": "Single list", - "resources": "Resources", - "search_resources_placeholder": "Search resources...", - "no_resources": "No resources" - }, - "tabs": { - "calls": "Calls", - "calendar": "Calendar", - "contacts": "Contacts", - "home": "Home", - "map": "Map", - "messages": "Messages", - "notes": "Notes", - "protocols": "Protocols", - "settings": "Settings", - "shifts": "Shifts", - "personnel": "Personnel" - }, - "check_in": { - "tab_title": "Check-In", - "timer_status": "Timer Status", - "perform_check_in": "Check In", - "check_in_success": "Check-in recorded successfully", - "check_in_error": "Failed to record check-in", - "checked_in_by": "by {{name}}", - "last_check_in": "Last check-in", - "elapsed": "Elapsed", - "duration": "Duration", - "status_ok": "OK", - "status_green": "OK", - "status_warning": "Warning", - "status_yellow": "Warning", - "status_overdue": "Overdue", - "status_red": "Overdue", - "status_critical": "Critical", - "history": "Check-In History", - "no_timers": "No check-in timers configured", - "timers_disabled": "Check-in timers are disabled for this call", - "type_personnel": "Personnel", - "type_unit": "Unit", - "type_ic": "Incident Commander", - "type_par": "PAR", - "type_hazmat": "Hazmat Exposure", - "type_sector_rotation": "Sector Rotation", - "type_rehab": "Rehab", - "add_note": "Add Note (Optional)", - "confirm": "Confirm Check-In", - "minutes_ago": "{{count}} min ago", - "select_target": "Select Entity to Check In", - "overdue_count": "{{count}} Overdue", - "warning_count": "{{count}} Warning", - "enable_timers": "Enable Timers", - "disable_timers": "Disable Timers", - "summary": "{{overdue}} overdue, {{warning}} warning, {{ok}} ok", - "par_title": "Personnel Accountability (PAR)" + "general_status": "General status without specific destination", + "loading_pois": "Loading POIs...", + "loading_stations": "Loading stations...", + "no_destination": "No Destination", + "no_pois_available": "No POIs available", + "no_stations_available": "No stations available", + "no_statuses_available": "No statuses available", + "note": "Note", + "note_optional": "Add an optional note for this status update", + "note_required": "Please enter a note for this status update", + "poi_destination_enabled": "Can respond to POIs", + "pois_tab": "POIs", + "select_destination": "Select Destination for {{status}}", + "select_destination_type": "Where would you like to respond?", + "select_status": "Select Status", + "select_status_type": "What status would you like to set?", + "selected_destination": "Selected Destination", + "selected_status": "Selected Status", + "set_status": "Set Status", + "station_destination_enabled": "Can respond to stations", + "stations_and_pois_destination_enabled": "Can respond to stations or POIs", + "stations_tab": "Stations", + "status_saved_successfully": "Status saved successfully!" + }, + "tabs": { + "calls": "Calls", + "calendar": "Calendar", + "contacts": "Contacts", + "home": "Home", + "map": "Map", + "messages": "Messages", + "notes": "Notes", + "protocols": "Protocols", + "settings": "Settings", + "shifts": "Shifts", + "personnel": "Personnel" }, "units": { "title": "Units", @@ -1243,6 +1370,63 @@ "unknown_status": "Unknown", "no_destination": "None" }, + "videoFeeds": { + "title": "Video Feeds", + "noFeeds": "No video feeds for this call", + "addFeed": "Add Video Feed", + "editFeed": "Edit Video Feed", + "deleteFeed": "Delete Video Feed", + "deleteConfirm": "Are you sure you want to remove this video feed?", + "watch": "Watch", + "goLive": "Go Live", + "stopLive": "Stop Live", + "flipCamera": "Flip Camera", + "feedAdded": "Video feed added", + "feedUpdated": "Video feed updated", + "feedDeleted": "Video feed removed", + "feedError": "Failed to load video feed", + "unsupportedFormat": "This stream format is not supported on mobile", + "copyUrl": "Copy URL", + "form": { + "name": "Feed Name", + "namePlaceholder": "e.g. Engine 1 Drone", + "url": "Stream URL", + "urlPlaceholder": "e.g. https://stream.example.com/live.m3u8", + "feedType": "Camera Type", + "feedFormat": "Stream Format", + "description": "Description", + "descriptionPlaceholder": "Optional description", + "status": "Status", + "sortOrder": "Sort Order", + "cameraLocation": "Camera Location", + "useCurrentLocation": "Use Current Location" + }, + "type": { + "drone": "Drone", + "fixedCamera": "Fixed Camera", + "bodyCam": "Body Cam", + "trafficCam": "Traffic Cam", + "weatherCam": "Weather Cam", + "satelliteFeed": "Satellite Feed", + "webCam": "Web Cam", + "other": "Other" + }, + "format": { + "rtsp": "RTSP", + "hls": "HLS", + "mjpeg": "MJPEG", + "youtubeLive": "YouTube Live", + "webrtc": "WebRTC", + "dash": "DASH", + "embed": "Embed", + "other": "Other" + }, + "status": { + "active": "Active", + "inactive": "Inactive", + "error": "Error" + } + }, "weatherAlerts": { "title": "Weather Alerts", "activeAlerts": "Active Alerts", @@ -1350,171 +1534,5 @@ }, "stats_label": "Weather Alerts" }, - "videoFeeds": { - "title": "Video Feeds", - "noFeeds": "No video feeds for this call", - "addFeed": "Add Video Feed", - "editFeed": "Edit Video Feed", - "deleteFeed": "Delete Video Feed", - "deleteConfirm": "Are you sure you want to remove this video feed?", - "watch": "Watch", - "goLive": "Go Live", - "stopLive": "Stop Live", - "flipCamera": "Flip Camera", - "feedAdded": "Video feed added", - "feedUpdated": "Video feed updated", - "feedDeleted": "Video feed removed", - "feedError": "Failed to load video feed", - "unsupportedFormat": "This stream format is not supported on mobile", - "copyUrl": "Copy URL", - "form": { - "name": "Feed Name", - "namePlaceholder": "e.g. Engine 1 Drone", - "url": "Stream URL", - "urlPlaceholder": "e.g. https://stream.example.com/live.m3u8", - "feedType": "Camera Type", - "feedFormat": "Stream Format", - "description": "Description", - "descriptionPlaceholder": "Optional description", - "status": "Status", - "sortOrder": "Sort Order", - "cameraLocation": "Camera Location", - "useCurrentLocation": "Use Current Location" - }, - "type": { - "drone": "Drone", - "fixedCamera": "Fixed Camera", - "bodyCam": "Body Cam", - "trafficCam": "Traffic Cam", - "weatherCam": "Weather Cam", - "satelliteFeed": "Satellite Feed", - "webCam": "Web Cam", - "other": "Other" - }, - "format": { - "rtsp": "RTSP", - "hls": "HLS", - "mjpeg": "MJPEG", - "youtubeLive": "YouTube Live", - "webrtc": "WebRTC", - "dash": "DASH", - "embed": "Embed", - "other": "Other" - }, - "status": { - "active": "Active", - "inactive": "Inactive", - "error": "Error" - } - }, - "welcome": "Welcome to obytes app site", - "incident_command": { - "tab_title": "Command", - "title": "Incident Command", - "open_full_board": "Open Full Board", - "no_command": "No Incident Command Established", - "no_command_description": "Establish incident command to coordinate resources, roles, objectives and accountability for this call.", - "establish": "Establish Command", - "establish_title": "Establish Incident Command", - "establish_description": "Optionally seed the command board from a template.", - "establish_success": "Incident command established", - "establish_error": "Failed to establish command", - "template": "Template", - "no_template": "No template (blank board)", - "saved": "Saved", - "save_error": "Operation failed", - "edit_action_plan": "Edit Action Plan", - "action_plan": "Action Plan", - "action_plan_placeholder": "Describe the incident action plan...", - "save": "Save", - "no_action_plan": "No action plan set.", - "add": "Add", - "add_objective": "Add Objective", - "objective_name": "Objective", - "objective_type": "Type", - "name_required": "Name is required", - "add_lane": "Add Lane", - "lane_name": "Lane Name", - "lane_type": "Lane Type", - "assign_resource": "Assign Resource", - "assign_resource_required": "Select a lane and a resource", - "lane": "Lane", - "select_lane": "Select a lane", - "resource_type": "Resource Type", - "resource": "Resource", - "select_resource": "Select a resource", - "unit": "Unit", - "personnel": "Personnel", - "assign": "Assign", - "assign_role": "Assign Role", - "assign_role_required": "Select a person and a role", - "person": "Person", - "select_person": "Select a person", - "role": "Role", - "select_role": "Select a role", - "transfer_title": "Transfer Command", - "transfer_notes": "Notes", - "transfer": "Transfer", - "transfer_command": "Transfer", - "transfer_success": "Command transferred", - "close_command": "Close Command", - "confirm_close": "Close incident command for this call?", - "status": "Status", - "active": "Active", - "closed": "Closed", - "commander": "Commander", - "established_on": "Established", - "edit": "Edit", - "roles": "Command Roles", - "no_roles": "No roles assigned.", - "structure": "Command Structure", - "no_lanes": "No lanes defined.", - "no_resources": "No resources assigned.", - "release": "Release", - "objectives": "Objectives", - "no_objectives": "No objectives.", - "complete": "Complete", - "completed": "Completed", - "timers": "Timers", - "no_timers": "No timers running.", - "acknowledge": "Acknowledge", - "due": "Due", - "accountability": "Accountability (PAR)", - "run_par": "Run PAR", - "green": "Green", - "warning": "Warning", - "critical": "Critical", - "no_accountability": "No personnel tracked.", - "timeline": "Command Timeline", - "no_timeline": "No timeline entries.", - "unassigned": "Unassigned", - "active_title": "Active Incident Commands", - "no_active": "No Active Incident Commands", - "no_active_description": "Incident commands established on calls will appear here.", - "call": "Call", - "tactical_map": "Tactical Map", - "annotations": "Map Annotations", - "no_annotations": "No annotations.", - "open_tactical_map": "Open tactical map", - "marker": "Marker", - "add_marker": "Add Marker", - "tap_to_place": "Tap the map to place a marker", - "marker_label": "Marker label", - "delete_annotation_confirm": "Remove this annotation?", - "voice_channels": "Voice Channels", - "no_channels": "No open channels.", - "add_channel": "Add Channel", - "channel_name": "Channel name", - "close_all_channels": "Close all channels", - "join": "Join", - "hold_to_talk": "Hold to Talk", - "talking": "Transmitting...", - "voice_joined": "Joined voice channel", - "voice_join_error": "Failed to join voice channel", - "move": "Move", - "move_lane": "Move Lane", - "parent_lane": "Parent lane", - "top_level": "Top level", - "move_resource": "Move Resource" - } + "welcome": "Welcome to obytes app site" } diff --git a/src/translations/es.json b/src/translations/es.json index 62dd9e9f..ba3bb974 100644 --- a/src/translations/es.json +++ b/src/translations/es.json @@ -371,6 +371,124 @@ "audio_name": "Clip de audio" } }, + "chat": { + "title": "Chat", + "assistant": "Asistente", + "empty": "Aún no hay conversaciones. Inicia un mensaje directo o crea un grupo.", + "section_assistant": "Asistente", + "section_direct_messages": "Mensajes directos", + "section_channels": "Canales", + "section_incidents": "Incidentes", + "new_direct_message": "Nuevo mensaje directo", + "new_group": "Nuevo grupo", + "open_assistant": "Abrir asistente", + "create_conversation_failed": "No se pudo iniciar la conversación", + "group_name": "Nombre del grupo", + "search_people": "Buscar personas", + "no_people": "No se encontraron personas", + "create_group_with": "Crear grupo ({{count}})", + "message_deleted": "Este mensaje fue eliminado", + "urgent": "Urgente", + "urgent_will_send": "Este mensaje se enviará como urgente", + "shared_location": "Ubicación compartida", + "thread_replies": "{{count}} respuestas", + "edited": "(editado)", + "failed_tap_retry": "Error: toca para reintentar", + "type_a_message": "Escribe un mensaje", + "emoji": "Emoji", + "add_image": "Agregar imagen", + "add_gif": "Agregar GIF", + "share_location": "Compartir ubicación", + "send": "Enviar", + "someone": "Alguien", + "is_typing": "{{name}} está escribiendo...", + "are_typing": "{{count}} personas están escribiendo...", + "permission_photos_denied": "Permiso de la biblioteca de fotos denegado", + "permission_location_denied": "Permiso de ubicación denegado", + "search_gifs": "Buscar GIFs", + "no_gifs": "No se encontraron GIFs", + "flag_reason": "¿Por qué lo reportas?", + "flag_inappropriate": "Inapropiado", + "flag_harassment": "Acoso", + "flag_spam": "Spam", + "flag_sensitive": "Información sensible", + "flag_policy": "Violación de la política", + "flag_other": "Otro", + "reply_in_thread": "Responder en el hilo", + "copy": "Copiar", + "copied": "Copiado", + "copy_unavailable": "Copiar no está disponible en este dispositivo", + "edit": "Editar", + "edit_message": "Editar mensaje", + "save": "Guardar", + "delete": "Eliminar", + "pin": "Fijar", + "unpin": "Dejar de fijar", + "flag": "Reportar", + "moderator_delete": "Eliminar (moderador)", + "moderator_removed": "Eliminado por el moderador", + "attachment_failed": "Error al subir el adjunto", + "ack_required": "Confirmación requerida", + "ack_pending_one": "Tienes un mensaje urgente por confirmar", + "ack_pending_count": "Tienes {{count}} mensajes urgentes por confirmar", + "acknowledge": "Confirmar", + "thread": "Hilo", + "original_message": "Mensaje original", + "reply_placeholder": "Responder...", + "channel": "Canal", + "direct_message": "Mensaje directo", + "load_people_failed": "No se pudieron cargar las personas", + "reaction_failed": "No se pudo actualizar la reacción", + "edit_failed": "No se pudo editar el mensaje", + "delete_failed": "No se pudo eliminar el mensaje", + "pin_failed": "No se pudo actualizar el mensaje fijado", + "flag_failed": "No se pudo reportar el mensaje" + }, + "chatbot": { + "title": "Asistente", + "subtitle": "Asistente de IA para tu departamento", + "new_session": "Nueva sesión", + "empty": "Pregúntale cualquier cosa al asistente para comenzar.", + "ask_placeholder": "Pregúntale al asistente..." + }, + "check_in": { + "tab_title": "Registro", + "timer_status": "Estado del Temporizador", + "perform_check_in": "Registrar", + "check_in_success": "Registro realizado exitosamente", + "check_in_error": "Error al realizar el registro", + "checked_in_by": "por {{name}}", + "last_check_in": "Último registro", + "elapsed": "Transcurrido", + "duration": "Duración", + "status_ok": "OK", + "status_green": "OK", + "status_warning": "Advertencia", + "status_yellow": "Advertencia", + "status_overdue": "Vencido", + "status_red": "Vencido", + "status_critical": "Crítico", + "history": "Historial de Registros", + "no_timers": "No hay temporizadores de registro configurados", + "timers_disabled": "Los temporizadores de registro están desactivados para esta llamada", + "type_personnel": "Personal", + "type_unit": "Unidad", + "type_ic": "Comandante de Incidente", + "type_par": "PAR", + "type_hazmat": "Exposición a Materiales Peligrosos", + "type_sector_rotation": "Rotación de Sector", + "type_rehab": "Rehabilitación", + "add_note": "Agregar Nota (Opcional)", + "confirm": "Confirmar Registro", + "minutes_ago": "hace {{count}} min", + "select_target": "Seleccionar Entidad para Registrar", + "overdue_count": "{{count}} Vencidos", + "warning_count": "{{count}} Advertencias", + "enable_timers": "Activar Temporizadores", + "disable_timers": "Desactivar Temporizadores", + "summary": "{{overdue}} vencidos, {{warning}} advertencias, {{ok}} ok", + "par_title": "Control de personal (PAR)" + }, "common": { "add": "Añadir", "back": "Atrás", @@ -502,144 +620,396 @@ "website": "Sitio web", "zip": "Código postal" }, - "form": { - "invalid_url": "Por favor, introduce una URL válida que comience con http:// o https://", - "required": "Este campo es obligatorio" - }, - "livekit": { - "audio_devices": "Dispositivos de audio", - "audio_settings": "Configuración de audio", - "connected_to_room": "Conectado al canal", - "connecting": "Conectando...", - "disconnect": "Desconectar", - "join": "Unirse", - "microphone": "Micrófono", - "mute": "Silenciar", - "no_rooms_available": "No hay canales de voz disponibles", - "speaker": "Altavoz", - "speaking": "Hablando", - "title": "Canales de voz", - "unmute": "Activar sonido" - }, - "loading": { - "loading": "Cargando...", - "loadingData": "Cargando datos...", - "pleaseWait": "Por favor, espere", - "processingRequest": "Procesando su solicitud..." - }, - "sso": { - "authenticating": "Autenticando...", - "back_to_login": "Volver al inicio de sesión", - "back_to_lookup": "Cambiar usuario", - "continue_button": "Continuar", - "department_id_label": "ID de departamento", - "department_id_placeholder": "Ingrese el ID de departamento", - "error_generic": "Error al iniciar sesión. Por favor intente de nuevo.", - "error_oidc_cancelled": "El inicio de sesión fue cancelado.", - "error_oidc_not_ready": "El proveedor SSO se está cargando, por favor espere.", - "error_sso_not_enabled": "El inicio de sesión único no está habilitado para este usuario.", - "error_token_exchange": "Error al completar el inicio de sesión. Por favor intente de nuevo.", - "error_user_not_found": "Usuario no encontrado. Por favor verifique e intente de nuevo.", - "looking_up": "Buscando...", - "optional": "opcional", - "page_subtitle": "Ingrese su nombre de usuario para ver las opciones de inicio de sesión de su organización.", - "page_title": "Inicio de sesión único", - "provider_oidc": "OpenID Connect (OIDC)", - "provider_saml": "SAML 2.0", - "sign_in_button": "Iniciar sesión con SSO", - "sign_in_title": "Iniciar sesión", - "sso_button": "Acceso SSO" - }, - "login": { - "branding_subtitle": "Software de despacho potente para socorristas, búsqueda y rescate, y organizaciones de seguridad pública.", - "branding_title": "Gestión de Respuesta de Emergencia", - "errorModal": { - "confirmButton": "Aceptar", - "message": "Por favor, comprueba tu nombre de usuario y contraseña e inténtalo de nuevo.", - "title": "Inicio de sesión fallido" - }, - "feature_dispatch_desc": "Despacha unidades y gestiona llamadas al instante con actualizaciones en vivo en todos los dispositivos.", - "feature_dispatch_title": "Despacho en Tiempo Real", - "feature_mapping_desc": "Rastrea unidades en tiempo real con mapas detallados, enrutamiento y gestión de ubicación.", - "feature_mapping_title": "Mapeo Avanzado", - "feature_personnel_desc": "Gestiona tu equipo con acceso basado en roles, seguimiento de estado y herramientas de comunicación.", - "feature_personnel_title": "Gestión de Personal", - "footer_text": "Creado con ❤️ en Lake Tahoe", - "login": "Iniciar sesión", - "login_button": "Iniciar sesión", - "login_button_description": "Inicia sesión en tu cuenta para continuar", - "login_button_error": "Error al iniciar sesión", - "login_button_loading": "Iniciando sesión...", - "login_button_success": "Sesión iniciada con éxito", - "no_account": "¿No tienes cuenta?", - "page_subtitle": "Introduce tus credenciales para iniciar sesión.", - "page_title": "Resgrid Dispatch", - "password": "Contraseña", - "password_incorrect": "La contraseña era incorrecta", - "password_placeholder": "Introduce tu contraseña", - "register": "Registrarse", - "title": "Iniciar sesión", - "username": "Nombre de usuario", - "username_placeholder": "Introduce tu nombre de usuario", - "welcome_title": "Bienvenido de Nuevo" - }, - "lockscreen": { - "message": "Ingrese su contraseña para desbloquear la pantalla", - "not_you": "¿No eres tú? Volver al inicio de sesión", - "password": "Contraseña", - "password_placeholder": "Ingrese su contraseña", - "title": "Pantalla de Bloqueo", - "unlock_button": "Desbloquear", - "unlock_failed": "Error al desbloquear. Por favor, inténtelo de nuevo.", - "unlocking": "Desbloqueando...", - "welcome_back": "Bienvenido de Nuevo", - "relogin_required": "La verificación de contraseña no está disponible para esta sesión. Por favor, inicia sesión de nuevo." - }, - "maintenance": { - "downtime_message": "Estamos trabajando arduamente para completar el mantenimiento lo antes posible. Por favor, vuelva pronto.", - "downtime_title": "¿Cuál es el tiempo de inactividad?", - "message": "Por favor, vuelva en algún momento.", - "support_message": "Si necesita asistencia, contáctenos en", - "support_title": "¿Necesita soporte?", - "title": "Sitio en Mantenimiento", - "why_down_message": "Estamos realizando mantenimiento programado para mejorar su experiencia. Nos disculpamos por cualquier inconveniente.", - "why_down_title": "¿Por qué está el sitio inactivo?" - }, - "map": { - "call_set_as_current": "Llamada establecida como llamada actual", - "failed_to_open_maps": "Error al abrir la aplicación de mapas", - "failed_to_set_current_call": "Error al establecer la llamada como llamada actual", - "no_location_for_routing": "No hay datos de ubicación disponibles para el enrutamiento", - "pin_color": "Color del pin", - "recenter_map": "Recentrar mapa", - "set_as_current_call": "Establecer como llamada actual", - "view_call_details": "Ver detalles de la llamada", - "view_poi_details": "Ver detalles del PDI", - "layers": "Capas del Mapa", - "no_layers": "No hay capas disponibles", - "show_all": "Mostrar Todo", - "hide_all": "Ocultar Todo" - }, - "notes": { - "actions": { - "add": "Añadir nota", - "delete_confirm": "¿Estás seguro de que quieres eliminar esta nota?" - }, - "details": { - "close": "Cerrar", - "created": "Creado", - "delete": "Eliminar", - "edit": "Editar", - "tags": "Etiquetas", - "title": "Detalles de la nota", - "updated": "Actualizado" - }, - "empty": "No se encontraron notas", - "emptyDescription": "No se han creado notas para tu departamento todavía.", - "search": "Buscar notas...", - "title": "Notas" - }, - "menu": { + "dispatch": { + "active_calls": "Llamadas Activas", + "pending_calls": "Pendientes", + "scheduled_calls": "Programadas", + "units_available": "Disponibles", + "personnel_available": "Disponibles", + "personnel_on_duty": "De Servicio", + "units": "Unidades", + "personnel": "Personal", + "map": "Mapa", + "notes": "Notas", + "activity_log": "Registro de Actividad", + "communications": "Comunicaciones", + "no_active_calls": "Sin llamadas activas", + "no_units": "Sin unidades disponibles", + "no_personnel": "Sin personal disponible", + "no_notes": "Sin notas disponibles", + "no_activity": "Sin actividad reciente", + "current_channel": "Canal Actual", + "audio_stream": "Transmisión de Audio", + "no_stream": "Sin transmisión activa", + "ptt": "PTT", + "ptt_start": "Inicio PTT", + "ptt_end": "Fin PTT", + "transmitting_on": "Transmitiendo en {{channel}}", + "transmission_ended": "Transmisión finalizada", + "voice_disabled": "Voz deshabilitada", + "disconnected": "Desconectado", + "select_channel": "Seleccionar Canal", + "select_channel_description": "Elija un canal de voz para conectarse", + "change_channel_warning": "Seleccionar un nuevo canal desconectará del actual", + "default_channel": "Predeterminado", + "no_channels_available": "No hay canales de voz disponibles", + "system_update": "Actualización del Sistema", + "data_refreshed": "Datos actualizados desde el servidor", + "call_selected": "Llamada Seleccionada", + "unit_selected": "Unidad Seleccionada", + "unit_deselected": "Unidad Deseleccionada", + "personnel_selected": "Personal Seleccionado", + "personnel_deselected": "Personal Deseleccionado", + "loading_map": "Cargando mapa...", + "map_not_available_web": "Mapa no disponible en plataforma web", + "filtering_by_call": "Filtrando por llamada", + "clear_filter": "Limpiar Filtro", + "call_filter_active": "Filtro de Llamada Activo", + "call_filter_cleared": "Filtro de Llamada Limpiado", + "showing_all_data": "Mostrando todos los datos", + "call_notes": "Notas de Llamada", + "no_call_notes": "Sin notas de llamada", + "add_call_note_placeholder": "Añadir una nota...", + "note_added": "Nota Añadida", + "note_added_to_console": "Se ha añadido una nueva nota a la consola", + "add_note_title": "Añadir Nueva Nota", + "note_title_label": "Título", + "note_title_placeholder": "Ingrese el título de la nota...", + "note_category_label": "Categoría", + "note_category_placeholder": "Seleccione una categoría", + "note_no_category": "Sin Categoría", + "note_body_label": "Contenido de la Nota", + "note_body_placeholder": "Ingrese el contenido de la nota...", + "note_save_error": "Error al guardar la nota: {{error}}", + "note_created": "Nota Creada", + "units_on_call": "Unidades en Llamada", + "no_units_on_call": "Sin unidades en llamada", + "personnel_on_call": "Personal en Llamada", + "no_personnel_on_call": "Sin personal en llamada", + "call_activity": "Actividad de Llamada", + "no_call_activity": "Sin actividad de llamada", + "on_call": "En Llamada", + "filtered": "Filtrado", + "active_filter": "Filtro Activo", + "unit_status_change": "Cambio de Estado de Unidad", + "personnel_status_change": "Cambio de Estado de Personal", + "view_call_details": "Ver Detalles de Llamada", + "dispatched_resources": "Despachados", + "unassigned": "Sin Asignar", + "available": "Disponible", + "unknown": "Desconocido", + "search_personnel_placeholder": "Buscar personal...", + "search_calls_placeholder": "Buscar llamadas...", + "search_units_placeholder": "Buscar unidades...", + "search_notes_placeholder": "Buscar notas...", + "signalr_update": "Actualización en Tiempo Real", + "signalr_connected": "Conectado", + "realtime_updates_active": "Las actualizaciones en tiempo real están activas", + "personnel_status_updated": "Estado de personal actualizado", + "personnel_staffing_updated": "Dotación de personal actualizada", + "unit_status_updated": "Estado de unidad actualizado", + "calls_updated": "Llamadas actualizadas", + "call_added": "Nueva llamada añadida", + "call_closed": "Llamada cerrada", + "check_ins": "Registros", + "no_check_ins": "No hay llamadas con temporizadores de registro", + "radio_log": "Registro de Radio", + "radio": "Radio", + "activity": "Actividad", + "actions": "Acciones", + "no_radio_activity": "Sin transmisiones de radio", + "live": "EN VIVO", + "currently_transmitting": "Transmitiendo actualmente...", + "duration": "Duración", + "call_actions": "Acciones de Llamada", + "unit_actions": "Acciones de Unidad", + "personnel_actions": { + "title": "Acciones de Personal", + "status_tab": "Estado", + "staffing_tab": "Dotación", + "select_status": "Seleccionar Estado", + "select_staffing": "Seleccionar Nivel de Dotación", + "destination": "Destino", + "no_destination": "Sin Destino", + "note": "Nota", + "note_placeholder": "Añadir una nota opcional...", + "update_status": "Actualizar Estado", + "update_staffing": "Actualizar Dotación", + "no_statuses_available": "No hay estados disponibles", + "no_staffings_available": "No hay niveles de dotación disponibles" + }, + "unit_actions_panel": { + "status": "Estado", + "select_status": "Seleccionar Estado", + "destination": "Destino", + "no_destination": "Sin Destino", + "note": "Nota", + "note_placeholder": "Añadir una nota opcional...", + "update_status": "Actualizar Estado", + "no_statuses_available": "No hay estados disponibles", + "no_active_calls": "No hay llamadas activas", + "no_stations_available": "No hay estaciones disponibles", + "no_destinations_available": "No hay destinos disponibles" + }, + "call": "Llamada", + "station": "Estación", + "calls": "Llamadas", + "stations": "Estaciones", + "no_stations_available": "No hay estaciones disponibles", + "new_call": "Nueva Llamada", + "view_details": "Detalles", + "add_note": "Añadir Nota", + "close_call": "Cerrar", + "set_status": "Estado", + "set_staffing": "Dotación", + "dispatch": "Despachar", + "select_items_for_actions": "Selecciona una llamada, unidad o personal para habilitar acciones contextuales", + "weather": { + "clear": "Despejado", + "mainly_clear": "Mayormente Despejado", + "partly_cloudy": "Parcialmente Nublado", + "overcast": "Nublado", + "fog": "Niebla", + "drizzle": "Llovizna", + "freezing_drizzle": "Llovizna Helada", + "rain": "Lluvia", + "freezing_rain": "Lluvia Helada", + "snow": "Nieve", + "rain_showers": "Chubascos", + "snow_showers": "Nevadas", + "thunderstorm": "Tormenta Eléctrica", + "thunderstorm_hail": "Tormenta con Granizo", + "unknown": "Desconocido" + }, + "available_only": "Solo disponibles", + "single_list": "Lista única", + "resources": "Recursos", + "search_resources_placeholder": "Buscar recursos...", + "no_resources": "No hay recursos" + }, + "form": { + "invalid_url": "Por favor, introduce una URL válida que comience con http:// o https://", + "required": "Este campo es obligatorio" + }, + "incident_command": { + "accountability": "Control de personal (PAR)", + "acknowledge": "Confirmar", + "action_plan": "Plan de acción", + "action_plan_placeholder": "Describa el plan de acción del incidente...", + "active": "Activo", + "active_title": "Mandos de incidentes activos", + "add": "Añadir", + "add_channel": "Añadir canal", + "add_lane": "Añadir sección", + "add_marker": "Añadir marcador", + "add_objective": "Añadir objetivo", + "annotations": "Anotaciones del mapa", + "assign": "Asignar", + "assign_resource": "Asignar recurso", + "assign_resource_required": "Seleccione una sección y un recurso", + "assign_role": "Asignar función", + "assign_role_required": "Seleccione una persona y una función", + "call": "Llamada", + "channel_name": "Nombre del canal", + "chat": "Chat", + "chat_frozen": "This incident is closed. Conversations are kept as a point-in-time record — no new messages.", + "close_all_channels": "Cerrar todos los canales", + "close_command": "Cerrar mando", + "closed": "Cerrado", + "command_channel": "Command chat", + "command_channel_hint": "Command staff and dispatch", + "command_channel_unavailable": "No command channel has been created for this incident yet.", + "commander": "Comandante", + "complete": "Completar", + "completed": "Completado", + "confirm_close": "¿Cerrar el mando del incidente para esta llamada?", + "critical": "Crítico", + "delete_annotation_confirm": "¿Eliminar esta anotación?", + "dispatch_channel": "Dispatch", + "dispatch_channel_hint": "The incident's line to the desk", + "dispatch_channel_unavailable": "No dispatch channel has been created for this incident yet.", + "dm_failed": "Couldn't open that conversation.", + "dm_unavailable": "That contact has no Resgrid account to message.", + "due": "Vence", + "edit": "Editar", + "edit_action_plan": "Editar plan de acción", + "establish": "Establecer mando", + "establish_description": "Opcionalmente, inicialice el panel de mando a partir de una plantilla.", + "establish_error": "No se pudo establecer el mando", + "establish_success": "Mando del incidente establecido", + "establish_title": "Establecer mando del incidente", + "established_on": "Establecido", + "green": "Verde", + "hold_to_talk": "Mantener pulsado para hablar", + "incident_channel": "Incident chat", + "incident_channel_hint": "Everyone working the incident", + "incident_channel_unavailable": "No incident channel has been created for this call yet.", + "join": "Unirse", + "lane": "Sección", + "lane_name": "Nombre de la sección", + "lane_type": "Tipo de sección", + "marker": "Marcador", + "marker_label": "Etiqueta del marcador", + "move": "Mover", + "move_lane": "Mover sección", + "move_resource": "Mover recurso", + "name_required": "El nombre es obligatorio", + "no_accountability": "No hay personal registrado.", + "no_action_plan": "No se ha establecido ningún plan de acción.", + "no_active": "No hay mandos de incidentes activos", + "no_active_description": "Los mandos establecidos en llamadas aparecerán aquí.", + "no_annotations": "No hay anotaciones.", + "no_channels": "No hay canales abiertos.", + "no_command": "No se ha establecido el mando del incidente", + "no_command_description": "Establezca el mando del incidente para coordinar recursos, funciones, objetivos y control de personal para esta llamada.", + "no_lanes": "No hay secciones definidas.", + "no_objectives": "No hay objetivos.", + "no_resources": "No hay recursos asignados.", + "no_roles": "No hay funciones asignadas.", + "no_template": "Sin plantilla (panel vacío)", + "no_timeline": "No hay entradas en la cronología.", + "no_timers": "No hay temporizadores activos.", + "not_authorized": "Command board unavailable", + "not_authorized_description": "Your department hasn't authorized you to work incident command. Ask an administrator for the Command App Login permission.", + "objective_name": "Objetivo", + "objective_type": "Tipo", + "objectives": "Objetivos", + "open_chat": "Open", + "open_full_board": "Abrir panel completo", + "open_tactical_map": "Abrir mapa táctico", + "parent_lane": "Sección superior", + "person": "Persona", + "personnel": "Personal", + "release": "Liberar", + "resource": "Recurso", + "resource_type": "Tipo de recurso", + "role": "Función", + "roles": "Funciones de mando", + "run_par": "Ejecutar PAR", + "save": "Guardar", + "save_error": "La operación falló", + "saved": "Guardado", + "select_lane": "Seleccione una sección", + "select_person": "Seleccione una persona", + "select_resource": "Seleccione un recurso", + "select_role": "Seleccione una función", + "send_message": "Message", + "status": "Estado", + "structure": "Estructura de mando", + "tab_title": "Mando", + "tactical_map": "Mapa táctico", + "talking": "Transmitiendo...", + "tap_to_place": "Toque el mapa para colocar un marcador", + "template": "Plantilla", + "timeline": "Cronología del mando", + "timers": "Temporizadores", + "title": "Mando del incidente", + "top_level": "Nivel superior", + "transfer": "Transferir", + "transfer_command": "Transferir", + "transfer_notes": "Notas", + "transfer_success": "Mando transferido", + "transfer_title": "Transferir el mando", + "unassigned": "Sin asignar", + "unit": "Unidad", + "voice_channels": "Canales de voz", + "voice_join_error": "No se pudo unir al canal de voz", + "voice_joined": "Se unió al canal de voz", + "warning": "Advertencia" + }, + "livekit": { + "audio_devices": "Dispositivos de audio", + "audio_settings": "Configuración de audio", + "connected_to_room": "Conectado al canal", + "connecting": "Conectando...", + "disconnect": "Desconectar", + "join": "Unirse", + "microphone": "Micrófono", + "mute": "Silenciar", + "no_rooms_available": "No hay canales de voz disponibles", + "speaker": "Altavoz", + "speaking": "Hablando", + "title": "Canales de voz", + "unmute": "Activar sonido" + }, + "loading": { + "loading": "Cargando...", + "loadingData": "Cargando datos...", + "pleaseWait": "Por favor, espere", + "processingRequest": "Procesando su solicitud..." + }, + "lockscreen": { + "message": "Ingrese su contraseña para desbloquear la pantalla", + "not_you": "¿No eres tú? Volver al inicio de sesión", + "password": "Contraseña", + "password_placeholder": "Ingrese su contraseña", + "title": "Pantalla de Bloqueo", + "unlock_button": "Desbloquear", + "unlock_failed": "Error al desbloquear. Por favor, inténtelo de nuevo.", + "unlocking": "Desbloqueando...", + "welcome_back": "Bienvenido de Nuevo", + "relogin_required": "La verificación de contraseña no está disponible para esta sesión. Por favor, inicia sesión de nuevo." + }, + "login": { + "branding_subtitle": "Software de despacho potente para socorristas, búsqueda y rescate, y organizaciones de seguridad pública.", + "branding_title": "Gestión de Respuesta de Emergencia", + "dispatch_not_authorized": "You are not authorized to use the Dispatch app. Contact your department administrator.", + "errorModal": { + "confirmButton": "Aceptar", + "message": "Por favor, comprueba tu nombre de usuario y contraseña e inténtalo de nuevo.", + "title": "Inicio de sesión fallido" + }, + "feature_dispatch_desc": "Despacha unidades y gestiona llamadas al instante con actualizaciones en vivo en todos los dispositivos.", + "feature_dispatch_title": "Despacho en Tiempo Real", + "feature_mapping_desc": "Rastrea unidades en tiempo real con mapas detallados, enrutamiento y gestión de ubicación.", + "feature_mapping_title": "Mapeo Avanzado", + "feature_personnel_desc": "Gestiona tu equipo con acceso basado en roles, seguimiento de estado y herramientas de comunicación.", + "feature_personnel_title": "Gestión de Personal", + "footer_text": "Creado con ❤️ en Lake Tahoe", + "login": "Iniciar sesión", + "login_button": "Iniciar sesión", + "login_button_description": "Inicia sesión en tu cuenta para continuar", + "login_button_error": "Error al iniciar sesión", + "login_button_loading": "Iniciando sesión...", + "login_button_success": "Sesión iniciada con éxito", + "no_account": "¿No tienes cuenta?", + "page_subtitle": "Introduce tus credenciales para iniciar sesión.", + "page_title": "Resgrid Dispatch", + "password": "Contraseña", + "password_incorrect": "La contraseña era incorrecta", + "password_placeholder": "Introduce tu contraseña", + "register": "Registrarse", + "title": "Iniciar sesión", + "username": "Nombre de usuario", + "username_placeholder": "Introduce tu nombre de usuario", + "welcome_title": "Bienvenido de Nuevo" + }, + "maintenance": { + "downtime_message": "Estamos trabajando arduamente para completar el mantenimiento lo antes posible. Por favor, vuelva pronto.", + "downtime_title": "¿Cuál es el tiempo de inactividad?", + "message": "Por favor, vuelva en algún momento.", + "support_message": "Si necesita asistencia, contáctenos en", + "support_title": "¿Necesita soporte?", + "title": "Sitio en Mantenimiento", + "why_down_message": "Estamos realizando mantenimiento programado para mejorar su experiencia. Nos disculpamos por cualquier inconveniente.", + "why_down_title": "¿Por qué está el sitio inactivo?" + }, + "map": { + "call_set_as_current": "Llamada establecida como llamada actual", + "failed_to_open_maps": "Error al abrir la aplicación de mapas", + "failed_to_set_current_call": "Error al establecer la llamada como llamada actual", + "no_location_for_routing": "No hay datos de ubicación disponibles para el enrutamiento", + "pin_color": "Color del pin", + "recenter_map": "Recentrar mapa", + "set_as_current_call": "Establecer como llamada actual", + "view_call_details": "Ver detalles de la llamada", + "view_poi_details": "Ver detalles del PDI", + "layers": "Capas del Mapa", + "no_layers": "No hay capas disponibles", + "show_all": "Mostrar Todo", + "hide_all": "Ocultar Todo" + }, + "menu": { "calls": "Llamadas", "calls_list": "Lista de Llamadas", "scheduled_calls": "Llamadas Programadas", @@ -659,6 +1029,42 @@ "chat": "Chat", "assistant": "Asistente" }, + "notes": { + "actions": { + "add": "Añadir nota", + "delete_confirm": "¿Estás seguro de que quieres eliminar esta nota?" + }, + "details": { + "close": "Cerrar", + "created": "Creado", + "delete": "Eliminar", + "edit": "Editar", + "tags": "Etiquetas", + "title": "Detalles de la nota", + "updated": "Actualizado" + }, + "empty": "No se encontraron notas", + "emptyDescription": "No se han creado notas para tu departamento todavía.", + "search": "Buscar notas...", + "title": "Notas" + }, + "onboarding": { + "screen1": { + "title": "Resgrid Dispatch", + "description": "Cree, despache y gestione llamadas de emergencia con un potente centro de comando móvil al alcance de su mano" + }, + "screen2": { + "title": "Conciencia Situacional en Tiempo Real", + "description": "Rastree todas las unidades, personal y recursos en un mapa interactivo con actualizaciones de estado en vivo y AVL" + }, + "screen3": { + "title": "Coordinación Fluida", + "description": "Comuníquese instantáneamente con las unidades de campo, actualice los estados de llamadas y coordine los esfuerzos de respuesta desde cualquier lugar" + }, + "skip": "Omitir", + "next": "Siguiente", + "getStarted": "Empecemos" + }, "personnel": { "title": "Personal", "search": "Buscar personal...", @@ -727,37 +1133,6 @@ "unknown_type": "Tipo desconocido", "unnamed": "PDI sin nombre" }, - "scheduled_calls": { - "title": "Llamadas Programadas", - "loading": "Cargando llamadas programadas...", - "no_scheduled_calls": "No hay llamadas programadas", - "no_scheduled_calls_description": "No hay llamadas programadas pendientes en este momento.", - "search": "Buscar llamadas programadas...", - "scheduled_for": "Programada para", - "table_number": "N.º de llamada", - "table_name": "Nombre", - "table_type": "Tipo", - "table_priority": "Prioridad", - "table_address": "Dirección", - "table_scheduled": "Programada para" - }, - "onboarding": { - "screen1": { - "title": "Resgrid Dispatch", - "description": "Cree, despache y gestione llamadas de emergencia con un potente centro de comando móvil al alcance de su mano" - }, - "screen2": { - "title": "Conciencia Situacional en Tiempo Real", - "description": "Rastree todas las unidades, personal y recursos en un mapa interactivo con actualizaciones de estado en vivo y AVL" - }, - "screen3": { - "title": "Coordinación Fluida", - "description": "Comuníquese instantáneamente con las unidades de campo, actualice los estados de llamadas y coordine los esfuerzos de respuesta desde cualquier lugar" - }, - "skip": "Omitir", - "next": "Siguiente", - "getStarted": "Empecemos" - }, "protocols": { "details": { "close": "Cerrar", @@ -796,6 +1171,20 @@ "tap_to_manage": "Toca para gestionar roles", "unassigned": "Sin asignar" }, + "scheduled_calls": { + "title": "Llamadas Programadas", + "loading": "Cargando llamadas programadas...", + "no_scheduled_calls": "No hay llamadas programadas", + "no_scheduled_calls_description": "No hay llamadas programadas pendientes en este momento.", + "search": "Buscar llamadas programadas...", + "scheduled_for": "Programada para", + "table_number": "N.º de llamada", + "table_name": "Nombre", + "table_type": "Tipo", + "table_priority": "Prioridad", + "table_address": "Dirección", + "table_scheduled": "Programada para" + }, "settings": { "about": "Acerca de", "account": "Cuenta", @@ -884,6 +1273,29 @@ "version": "Versión", "website": "Sitio web" }, + "sso": { + "authenticating": "Autenticando...", + "back_to_login": "Volver al inicio de sesión", + "back_to_lookup": "Cambiar usuario", + "continue_button": "Continuar", + "department_id_label": "ID de departamento", + "department_id_placeholder": "Ingrese el ID de departamento", + "error_generic": "Error al iniciar sesión. Por favor intente de nuevo.", + "error_oidc_cancelled": "El inicio de sesión fue cancelado.", + "error_oidc_not_ready": "El proveedor SSO se está cargando, por favor espere.", + "error_sso_not_enabled": "El inicio de sesión único no está habilitado para este usuario.", + "error_token_exchange": "Error al completar el inicio de sesión. Por favor intente de nuevo.", + "error_user_not_found": "Usuario no encontrado. Por favor verifique e intente de nuevo.", + "looking_up": "Buscando...", + "optional": "opcional", + "page_subtitle": "Ingrese su nombre de usuario para ver las opciones de inicio de sesión de su organización.", + "page_title": "Inicio de sesión único", + "provider_oidc": "OpenID Connect (OIDC)", + "provider_saml": "SAML 2.0", + "sign_in_button": "Iniciar sesión con SSO", + "sign_in_title": "Iniciar sesión", + "sso_button": "Acceso SSO" + }, "status": { "add_note": "Añadir Nota", "all_destinations_enabled": "Puede responder a llamadas, estaciones o PDI", @@ -929,211 +1341,6 @@ "shifts": "Turnos", "personnel": "Personal" }, - "dispatch": { - "active_calls": "Llamadas Activas", - "pending_calls": "Pendientes", - "scheduled_calls": "Programadas", - "units_available": "Disponibles", - "personnel_available": "Disponibles", - "personnel_on_duty": "De Servicio", - "units": "Unidades", - "personnel": "Personal", - "map": "Mapa", - "notes": "Notas", - "activity_log": "Registro de Actividad", - "communications": "Comunicaciones", - "no_active_calls": "Sin llamadas activas", - "no_units": "Sin unidades disponibles", - "no_personnel": "Sin personal disponible", - "no_notes": "Sin notas disponibles", - "no_activity": "Sin actividad reciente", - "current_channel": "Canal Actual", - "audio_stream": "Transmisión de Audio", - "no_stream": "Sin transmisión activa", - "ptt": "PTT", - "ptt_start": "Inicio PTT", - "ptt_end": "Fin PTT", - "transmitting_on": "Transmitiendo en {{channel}}", - "transmission_ended": "Transmisión finalizada", - "voice_disabled": "Voz deshabilitada", - "disconnected": "Desconectado", - "select_channel": "Seleccionar Canal", - "select_channel_description": "Elija un canal de voz para conectarse", - "change_channel_warning": "Seleccionar un nuevo canal desconectará del actual", - "default_channel": "Predeterminado", - "no_channels_available": "No hay canales de voz disponibles", - "system_update": "Actualización del Sistema", - "data_refreshed": "Datos actualizados desde el servidor", - "call_selected": "Llamada Seleccionada", - "unit_selected": "Unidad Seleccionada", - "unit_deselected": "Unidad Deseleccionada", - "personnel_selected": "Personal Seleccionado", - "personnel_deselected": "Personal Deseleccionado", - "loading_map": "Cargando mapa...", - "map_not_available_web": "Mapa no disponible en plataforma web", - "filtering_by_call": "Filtrando por llamada", - "clear_filter": "Limpiar Filtro", - "call_filter_active": "Filtro de Llamada Activo", - "call_filter_cleared": "Filtro de Llamada Limpiado", - "showing_all_data": "Mostrando todos los datos", - "call_notes": "Notas de Llamada", - "no_call_notes": "Sin notas de llamada", - "add_call_note_placeholder": "Añadir una nota...", - "note_added": "Nota Añadida", - "note_added_to_console": "Se ha añadido una nueva nota a la consola", - "add_note_title": "Añadir Nueva Nota", - "note_title_label": "Título", - "note_title_placeholder": "Ingrese el título de la nota...", - "note_category_label": "Categoría", - "note_category_placeholder": "Seleccione una categoría", - "note_no_category": "Sin Categoría", - "note_body_label": "Contenido de la Nota", - "note_body_placeholder": "Ingrese el contenido de la nota...", - "note_save_error": "Error al guardar la nota: {{error}}", - "note_created": "Nota Creada", - "units_on_call": "Unidades en Llamada", - "no_units_on_call": "Sin unidades en llamada", - "personnel_on_call": "Personal en Llamada", - "no_personnel_on_call": "Sin personal en llamada", - "call_activity": "Actividad de Llamada", - "no_call_activity": "Sin actividad de llamada", - "on_call": "En Llamada", - "filtered": "Filtrado", - "active_filter": "Filtro Activo", - "unit_status_change": "Cambio de Estado de Unidad", - "personnel_status_change": "Cambio de Estado de Personal", - "view_call_details": "Ver Detalles de Llamada", - "dispatched_resources": "Despachados", - "unassigned": "Sin Asignar", - "available": "Disponible", - "unknown": "Desconocido", - "search_personnel_placeholder": "Buscar personal...", - "search_calls_placeholder": "Buscar llamadas...", - "search_units_placeholder": "Buscar unidades...", - "search_notes_placeholder": "Buscar notas...", - "signalr_update": "Actualización en Tiempo Real", - "signalr_connected": "Conectado", - "realtime_updates_active": "Las actualizaciones en tiempo real están activas", - "personnel_status_updated": "Estado de personal actualizado", - "personnel_staffing_updated": "Dotación de personal actualizada", - "unit_status_updated": "Estado de unidad actualizado", - "calls_updated": "Llamadas actualizadas", - "call_added": "Nueva llamada añadida", - "call_closed": "Llamada cerrada", - "check_ins": "Registros", - "no_check_ins": "No hay llamadas con temporizadores de registro", - "radio_log": "Registro de Radio", - "radio": "Radio", - "activity": "Actividad", - "actions": "Acciones", - "no_radio_activity": "Sin transmisiones de radio", - "live": "EN VIVO", - "currently_transmitting": "Transmitiendo actualmente...", - "duration": "Duración", - "call_actions": "Acciones de Llamada", - "unit_actions": "Acciones de Unidad", - "personnel_actions": { - "title": "Acciones de Personal", - "status_tab": "Estado", - "staffing_tab": "Dotación", - "select_status": "Seleccionar Estado", - "select_staffing": "Seleccionar Nivel de Dotación", - "destination": "Destino", - "no_destination": "Sin Destino", - "note": "Nota", - "note_placeholder": "Añadir una nota opcional...", - "update_status": "Actualizar Estado", - "update_staffing": "Actualizar Dotación", - "no_statuses_available": "No hay estados disponibles", - "no_staffings_available": "No hay niveles de dotación disponibles" - }, - "unit_actions_panel": { - "status": "Estado", - "select_status": "Seleccionar Estado", - "destination": "Destino", - "no_destination": "Sin Destino", - "note": "Nota", - "note_placeholder": "Añadir una nota opcional...", - "update_status": "Actualizar Estado", - "no_statuses_available": "No hay estados disponibles", - "no_active_calls": "No hay llamadas activas", - "no_stations_available": "No hay estaciones disponibles", - "no_destinations_available": "No hay destinos disponibles" - }, - "call": "Llamada", - "station": "Estación", - "calls": "Llamadas", - "stations": "Estaciones", - "no_stations_available": "No hay estaciones disponibles", - "new_call": "Nueva Llamada", - "view_details": "Detalles", - "add_note": "Añadir Nota", - "close_call": "Cerrar", - "set_status": "Estado", - "set_staffing": "Dotación", - "dispatch": "Despachar", - "select_items_for_actions": "Selecciona una llamada, unidad o personal para habilitar acciones contextuales", - "weather": { - "clear": "Despejado", - "mainly_clear": "Mayormente Despejado", - "partly_cloudy": "Parcialmente Nublado", - "overcast": "Nublado", - "fog": "Niebla", - "drizzle": "Llovizna", - "freezing_drizzle": "Llovizna Helada", - "rain": "Lluvia", - "freezing_rain": "Lluvia Helada", - "snow": "Nieve", - "rain_showers": "Chubascos", - "snow_showers": "Nevadas", - "thunderstorm": "Tormenta Eléctrica", - "thunderstorm_hail": "Tormenta con Granizo", - "unknown": "Desconocido" - }, - "available_only": "Solo disponibles", - "single_list": "Lista única", - "resources": "Recursos", - "search_resources_placeholder": "Buscar recursos...", - "no_resources": "No hay recursos" - }, - "check_in": { - "tab_title": "Registro", - "timer_status": "Estado del Temporizador", - "perform_check_in": "Registrar", - "check_in_success": "Registro realizado exitosamente", - "check_in_error": "Error al realizar el registro", - "checked_in_by": "por {{name}}", - "last_check_in": "Último registro", - "elapsed": "Transcurrido", - "duration": "Duración", - "status_ok": "OK", - "status_green": "OK", - "status_warning": "Advertencia", - "status_yellow": "Advertencia", - "status_overdue": "Vencido", - "status_red": "Vencido", - "status_critical": "Crítico", - "history": "Historial de Registros", - "no_timers": "No hay temporizadores de registro configurados", - "timers_disabled": "Los temporizadores de registro están desactivados para esta llamada", - "type_personnel": "Personal", - "type_unit": "Unidad", - "type_ic": "Comandante de Incidente", - "type_par": "PAR", - "type_hazmat": "Exposición a Materiales Peligrosos", - "type_sector_rotation": "Rotación de Sector", - "type_rehab": "Rehabilitación", - "add_note": "Agregar Nota (Opcional)", - "confirm": "Confirmar Registro", - "minutes_ago": "hace {{count}} min", - "select_target": "Seleccionar Entidad para Registrar", - "overdue_count": "{{count}} Vencidos", - "warning_count": "{{count}} Advertencias", - "enable_timers": "Activar Temporizadores", - "disable_timers": "Desactivar Temporizadores", - "summary": "{{overdue}} vencidos, {{warning}} advertencias, {{ok}} ok", - "par_title": "Control de personal (PAR)" - }, "units": { "search": "Buscar unidades...", "loading": "Cargando unidades...", @@ -1163,6 +1370,63 @@ "no_destination": "Ninguno", "title": "Unidades" }, + "videoFeeds": { + "title": "Transmisiones de Video", + "noFeeds": "No hay transmisiones de video para esta llamada", + "addFeed": "Agregar Transmisión", + "editFeed": "Editar Transmisión", + "deleteFeed": "Eliminar Transmisión", + "deleteConfirm": "¿Está seguro de que desea eliminar esta transmisión de video?", + "watch": "Ver", + "goLive": "Transmitir", + "stopLive": "Detener", + "flipCamera": "Voltear Cámara", + "feedAdded": "Transmisión de video agregada", + "feedUpdated": "Transmisión de video actualizada", + "feedDeleted": "Transmisión de video eliminada", + "feedError": "Error al cargar la transmisión de video", + "unsupportedFormat": "Este formato de transmisión no es compatible en dispositivos móviles", + "copyUrl": "Copiar URL", + "form": { + "name": "Nombre de la Transmisión", + "namePlaceholder": "ej. Dron Motor 1", + "url": "URL de Transmisión", + "urlPlaceholder": "ej. https://stream.example.com/live.m3u8", + "feedType": "Tipo de Cámara", + "feedFormat": "Formato de Transmisión", + "description": "Descripción", + "descriptionPlaceholder": "Descripción opcional", + "status": "Estado", + "sortOrder": "Orden", + "cameraLocation": "Ubicación de la Cámara", + "useCurrentLocation": "Usar Ubicación Actual" + }, + "type": { + "drone": "Dron", + "fixedCamera": "Cámara Fija", + "bodyCam": "Cámara Corporal", + "trafficCam": "Cámara de Tráfico", + "weatherCam": "Cámara Meteorológica", + "satelliteFeed": "Transmisión Satelital", + "webCam": "Cámara Web", + "other": "Otro" + }, + "format": { + "rtsp": "RTSP", + "hls": "HLS", + "mjpeg": "MJPEG", + "youtubeLive": "YouTube Live", + "webrtc": "WebRTC", + "dash": "DASH", + "embed": "Embed", + "other": "Otro" + }, + "status": { + "active": "Activo", + "inactive": "Inactivo", + "error": "Error" + } + }, "weatherAlerts": { "title": "Alertas Meteorológicas", "activeAlerts": "Alertas Activas", @@ -1270,251 +1534,5 @@ }, "stats_label": "Alertas Meteorológicas" }, - "videoFeeds": { - "title": "Transmisiones de Video", - "noFeeds": "No hay transmisiones de video para esta llamada", - "addFeed": "Agregar Transmisión", - "editFeed": "Editar Transmisión", - "deleteFeed": "Eliminar Transmisión", - "deleteConfirm": "¿Está seguro de que desea eliminar esta transmisión de video?", - "watch": "Ver", - "goLive": "Transmitir", - "stopLive": "Detener", - "flipCamera": "Voltear Cámara", - "feedAdded": "Transmisión de video agregada", - "feedUpdated": "Transmisión de video actualizada", - "feedDeleted": "Transmisión de video eliminada", - "feedError": "Error al cargar la transmisión de video", - "unsupportedFormat": "Este formato de transmisión no es compatible en dispositivos móviles", - "copyUrl": "Copiar URL", - "form": { - "name": "Nombre de la Transmisión", - "namePlaceholder": "ej. Dron Motor 1", - "url": "URL de Transmisión", - "urlPlaceholder": "ej. https://stream.example.com/live.m3u8", - "feedType": "Tipo de Cámara", - "feedFormat": "Formato de Transmisión", - "description": "Descripción", - "descriptionPlaceholder": "Descripción opcional", - "status": "Estado", - "sortOrder": "Orden", - "cameraLocation": "Ubicación de la Cámara", - "useCurrentLocation": "Usar Ubicación Actual" - }, - "type": { - "drone": "Dron", - "fixedCamera": "Cámara Fija", - "bodyCam": "Cámara Corporal", - "trafficCam": "Cámara de Tráfico", - "weatherCam": "Cámara Meteorológica", - "satelliteFeed": "Transmisión Satelital", - "webCam": "Cámara Web", - "other": "Otro" - }, - "format": { - "rtsp": "RTSP", - "hls": "HLS", - "mjpeg": "MJPEG", - "youtubeLive": "YouTube Live", - "webrtc": "WebRTC", - "dash": "DASH", - "embed": "Embed", - "other": "Otro" - }, - "status": { - "active": "Activo", - "inactive": "Inactivo", - "error": "Error" - } - }, - "welcome": "Bienvenido al sitio de la aplicación obytes", - "incident_command": { - "tab_title": "Mando", - "title": "Mando del incidente", - "open_full_board": "Abrir panel completo", - "no_command": "No se ha establecido el mando del incidente", - "no_command_description": "Establezca el mando del incidente para coordinar recursos, funciones, objetivos y control de personal para esta llamada.", - "establish": "Establecer mando", - "establish_title": "Establecer mando del incidente", - "establish_description": "Opcionalmente, inicialice el panel de mando a partir de una plantilla.", - "establish_success": "Mando del incidente establecido", - "establish_error": "No se pudo establecer el mando", - "template": "Plantilla", - "no_template": "Sin plantilla (panel vacío)", - "saved": "Guardado", - "save_error": "La operación falló", - "edit_action_plan": "Editar plan de acción", - "action_plan": "Plan de acción", - "action_plan_placeholder": "Describa el plan de acción del incidente...", - "save": "Guardar", - "no_action_plan": "No se ha establecido ningún plan de acción.", - "add": "Añadir", - "add_objective": "Añadir objetivo", - "objective_name": "Objetivo", - "objective_type": "Tipo", - "name_required": "El nombre es obligatorio", - "add_lane": "Añadir sección", - "lane_name": "Nombre de la sección", - "lane_type": "Tipo de sección", - "assign_resource": "Asignar recurso", - "assign_resource_required": "Seleccione una sección y un recurso", - "lane": "Sección", - "select_lane": "Seleccione una sección", - "resource_type": "Tipo de recurso", - "resource": "Recurso", - "select_resource": "Seleccione un recurso", - "unit": "Unidad", - "personnel": "Personal", - "assign": "Asignar", - "assign_role": "Asignar función", - "assign_role_required": "Seleccione una persona y una función", - "person": "Persona", - "select_person": "Seleccione una persona", - "role": "Función", - "select_role": "Seleccione una función", - "transfer_title": "Transferir el mando", - "transfer_notes": "Notas", - "transfer": "Transferir", - "transfer_command": "Transferir", - "transfer_success": "Mando transferido", - "close_command": "Cerrar mando", - "confirm_close": "¿Cerrar el mando del incidente para esta llamada?", - "status": "Estado", - "active": "Activo", - "closed": "Cerrado", - "commander": "Comandante", - "established_on": "Establecido", - "edit": "Editar", - "roles": "Funciones de mando", - "no_roles": "No hay funciones asignadas.", - "structure": "Estructura de mando", - "no_lanes": "No hay secciones definidas.", - "no_resources": "No hay recursos asignados.", - "release": "Liberar", - "objectives": "Objetivos", - "no_objectives": "No hay objetivos.", - "complete": "Completar", - "completed": "Completado", - "timers": "Temporizadores", - "no_timers": "No hay temporizadores activos.", - "acknowledge": "Confirmar", - "due": "Vence", - "accountability": "Control de personal (PAR)", - "run_par": "Ejecutar PAR", - "green": "Verde", - "warning": "Advertencia", - "critical": "Crítico", - "no_accountability": "No hay personal registrado.", - "timeline": "Cronología del mando", - "no_timeline": "No hay entradas en la cronología.", - "unassigned": "Sin asignar", - "active_title": "Mandos de incidentes activos", - "no_active": "No hay mandos de incidentes activos", - "no_active_description": "Los mandos establecidos en llamadas aparecerán aquí.", - "call": "Llamada", - "tactical_map": "Mapa táctico", - "annotations": "Anotaciones del mapa", - "no_annotations": "No hay anotaciones.", - "open_tactical_map": "Abrir mapa táctico", - "marker": "Marcador", - "add_marker": "Añadir marcador", - "tap_to_place": "Toque el mapa para colocar un marcador", - "marker_label": "Etiqueta del marcador", - "delete_annotation_confirm": "¿Eliminar esta anotación?", - "voice_channels": "Canales de voz", - "no_channels": "No hay canales abiertos.", - "add_channel": "Añadir canal", - "channel_name": "Nombre del canal", - "close_all_channels": "Cerrar todos los canales", - "join": "Unirse", - "hold_to_talk": "Mantener pulsado para hablar", - "talking": "Transmitiendo...", - "voice_joined": "Se unió al canal de voz", - "voice_join_error": "No se pudo unir al canal de voz", - "move": "Mover", - "move_lane": "Mover sección", - "parent_lane": "Sección superior", - "top_level": "Nivel superior", - "move_resource": "Mover recurso" - }, - "chat": { - "title": "Chat", - "assistant": "Asistente", - "empty": "Aún no hay conversaciones. Inicia un mensaje directo o crea un grupo.", - "section_assistant": "Asistente", - "section_direct_messages": "Mensajes directos", - "section_channels": "Canales", - "section_incidents": "Incidentes", - "new_direct_message": "Nuevo mensaje directo", - "new_group": "Nuevo grupo", - "open_assistant": "Abrir asistente", - "create_conversation_failed": "No se pudo iniciar la conversación", - "group_name": "Nombre del grupo", - "search_people": "Buscar personas", - "no_people": "No se encontraron personas", - "create_group_with": "Crear grupo ({{count}})", - "message_deleted": "Este mensaje fue eliminado", - "urgent": "Urgente", - "urgent_will_send": "Este mensaje se enviará como urgente", - "shared_location": "Ubicación compartida", - "thread_replies": "{{count}} respuestas", - "edited": "(editado)", - "failed_tap_retry": "Error: toca para reintentar", - "type_a_message": "Escribe un mensaje", - "emoji": "Emoji", - "add_image": "Agregar imagen", - "add_gif": "Agregar GIF", - "share_location": "Compartir ubicación", - "send": "Enviar", - "someone": "Alguien", - "is_typing": "{{name}} está escribiendo...", - "are_typing": "{{count}} personas están escribiendo...", - "permission_photos_denied": "Permiso de la biblioteca de fotos denegado", - "permission_location_denied": "Permiso de ubicación denegado", - "search_gifs": "Buscar GIFs", - "no_gifs": "No se encontraron GIFs", - "flag_reason": "¿Por qué lo reportas?", - "flag_inappropriate": "Inapropiado", - "flag_harassment": "Acoso", - "flag_spam": "Spam", - "flag_sensitive": "Información sensible", - "flag_policy": "Violación de la política", - "flag_other": "Otro", - "reply_in_thread": "Responder en el hilo", - "copy": "Copiar", - "copied": "Copiado", - "copy_unavailable": "Copiar no está disponible en este dispositivo", - "edit": "Editar", - "edit_message": "Editar mensaje", - "save": "Guardar", - "delete": "Eliminar", - "pin": "Fijar", - "unpin": "Dejar de fijar", - "flag": "Reportar", - "moderator_delete": "Eliminar (moderador)", - "moderator_removed": "Eliminado por el moderador", - "attachment_failed": "Error al subir el adjunto", - "ack_required": "Confirmación requerida", - "ack_pending_one": "Tienes un mensaje urgente por confirmar", - "ack_pending_count": "Tienes {{count}} mensajes urgentes por confirmar", - "acknowledge": "Confirmar", - "thread": "Hilo", - "original_message": "Mensaje original", - "reply_placeholder": "Responder...", - "channel": "Canal", - "direct_message": "Mensaje directo", - "load_people_failed": "No se pudieron cargar las personas", - "reaction_failed": "No se pudo actualizar la reacción", - "edit_failed": "No se pudo editar el mensaje", - "delete_failed": "No se pudo eliminar el mensaje", - "pin_failed": "No se pudo actualizar el mensaje fijado", - "flag_failed": "No se pudo reportar el mensaje" - }, - "chatbot": { - "title": "Asistente", - "subtitle": "Asistente de IA para tu departamento", - "new_session": "Nueva sesión", - "empty": "Pregúntale cualquier cosa al asistente para comenzar.", - "ask_placeholder": "Pregúntale al asistente..." - } + "welcome": "Bienvenido al sitio de la aplicación obytes" } diff --git a/src/translations/fr.json b/src/translations/fr.json index 81bddce4..b8c5bad8 100644 --- a/src/translations/fr.json +++ b/src/translations/fr.json @@ -371,6 +371,124 @@ "audio_name": "Extrait audio" } }, + "chat": { + "title": "Chat", + "assistant": "Assistant", + "empty": "Aucune conversation pour le moment. Démarrez un message direct ou créez un groupe.", + "section_assistant": "Assistant", + "section_direct_messages": "Messages directs", + "section_channels": "Canaux", + "section_incidents": "Incidents", + "new_direct_message": "Nouveau message direct", + "new_group": "Nouveau groupe", + "open_assistant": "Ouvrir l'assistant", + "create_conversation_failed": "Impossible de démarrer la conversation", + "group_name": "Nom du groupe", + "search_people": "Rechercher des personnes", + "no_people": "Aucune personne trouvée", + "create_group_with": "Créer un groupe ({{count}})", + "message_deleted": "Ce message a été supprimé", + "urgent": "Urgent", + "urgent_will_send": "Ce message sera envoyé comme urgent", + "shared_location": "Position partagée", + "thread_replies": "{{count}} réponses", + "edited": "(modifié)", + "failed_tap_retry": "Échec – appuyez pour réessayer", + "type_a_message": "Saisir un message", + "emoji": "Emoji", + "add_image": "Ajouter une image", + "add_gif": "Ajouter un GIF", + "share_location": "Partager la position", + "send": "Envoyer", + "someone": "Quelqu'un", + "is_typing": "{{name}} est en train d'écrire...", + "are_typing": "{{count}} personnes sont en train d'écrire...", + "permission_photos_denied": "Autorisation d'accès à la photothèque refusée", + "permission_location_denied": "Autorisation de localisation refusée", + "search_gifs": "Rechercher des GIF", + "no_gifs": "Aucun GIF trouvé", + "flag_reason": "Pourquoi le signalez-vous ?", + "flag_inappropriate": "Inapproprié", + "flag_harassment": "Harcèlement", + "flag_spam": "Spam", + "flag_sensitive": "Informations sensibles", + "flag_policy": "Violation de la politique", + "flag_other": "Autre", + "reply_in_thread": "Répondre dans le fil", + "copy": "Copier", + "copied": "Copié", + "copy_unavailable": "La copie n'est pas disponible sur cet appareil", + "edit": "Modifier", + "edit_message": "Modifier le message", + "save": "Enregistrer", + "delete": "Supprimer", + "pin": "Épingler", + "unpin": "Désépingler", + "flag": "Signaler", + "moderator_delete": "Supprimer (modérateur)", + "moderator_removed": "Supprimé par le modérateur", + "attachment_failed": "Échec de l'envoi de la pièce jointe", + "ack_required": "Accusé de réception requis", + "ack_pending_one": "Vous avez un message urgent à accuser réception", + "ack_pending_count": "Vous avez {{count}} messages urgents à accuser réception", + "acknowledge": "Accuser réception", + "thread": "Fil", + "original_message": "Message d'origine", + "reply_placeholder": "Répondre...", + "channel": "Canal", + "direct_message": "Message direct", + "load_people_failed": "Impossible de charger les personnes", + "reaction_failed": "Impossible de mettre à jour la réaction", + "edit_failed": "Impossible de modifier le message", + "delete_failed": "Impossible de supprimer le message", + "pin_failed": "Impossible de mettre à jour l'épinglage", + "flag_failed": "Impossible de signaler le message" + }, + "chatbot": { + "title": "Assistant", + "subtitle": "Assistant IA pour votre service", + "new_session": "Nouvelle session", + "empty": "Posez n'importe quelle question à l'assistant pour commencer.", + "ask_placeholder": "Demandez à l'assistant..." + }, + "check_in": { + "tab_title": "Pointage", + "timer_status": "Statut de la minuterie", + "perform_check_in": "Pointer", + "check_in_success": "Pointage enregistré avec succès", + "check_in_error": "Échec de l'enregistrement du pointage", + "checked_in_by": "par {{name}}", + "last_check_in": "Dernier pointage", + "elapsed": "Écoulé", + "duration": "Durée", + "status_ok": "OK", + "status_green": "OK", + "status_warning": "Avertissement", + "status_yellow": "Avertissement", + "status_overdue": "En retard", + "status_red": "En retard", + "status_critical": "Critique", + "history": "Historique des pointages", + "no_timers": "Aucune minuterie de pointage configurée", + "timers_disabled": "Les minuteries de pointage sont désactivées pour cet appel", + "type_personnel": "Personnel", + "type_unit": "Unité", + "type_ic": "Commandant des opérations", + "type_par": "PAR", + "type_hazmat": "Exposition Hazmat", + "type_sector_rotation": "Rotation de secteur", + "type_rehab": "Réhabilitation", + "add_note": "Ajouter une note (optionnel)", + "confirm": "Confirmer le pointage", + "minutes_ago": "il y a {{count}} min", + "select_target": "Sélectionner l'entité à pointer", + "overdue_count": "{{count}} en retard", + "warning_count": "{{count}} avertissement", + "enable_timers": "Activer les minuteries", + "disable_timers": "Désactiver les minuteries", + "summary": "{{overdue}} en retard, {{warning}} avertissement, {{ok}} ok", + "par_title": "Suivi du personnel (PAR)" + }, "common": { "add": "Ajouter", "back": "Retour", @@ -502,178 +620,379 @@ "website": "Site web", "zip": "Code postal" }, - "form": { - "invalid_url": "Veuillez saisir une URL valide commençant par http:// ou https://", - "required": "Ce champ est obligatoire" - }, - "livekit": { - "audio_devices": "Appareils audio", - "audio_settings": "Paramètres audio", - "connected_to_room": "Connecté au canal", - "connecting": "Connexion en cours...", - "disconnect": "Déconnecter", - "join": "Rejoindre", - "microphone": "Microphone", - "mute": "Couper le son", - "no_rooms_available": "Aucun canal vocal disponible", - "speaker": "Haut-parleur", - "speaking": "En train de parler", - "title": "Canaux vocaux", - "unmute": "Réactiver le son" - }, - "loading": { - "loading": "Chargement...", - "loadingData": "Chargement des données...", - "pleaseWait": "Veuillez patienter", - "processingRequest": "Traitement de votre demande..." - }, - "sso": { - "authenticating": "Authentification en cours...", - "back_to_login": "Retour à la connexion", - "back_to_lookup": "Changer d'utilisateur", - "continue_button": "Continuer", - "department_id_label": "ID du service", - "department_id_placeholder": "Saisissez l'ID du service", - "error_generic": "La connexion a échoué. Veuillez réessayer.", - "error_oidc_cancelled": "La connexion a été annulée.", - "error_oidc_not_ready": "Le fournisseur SSO est en cours de chargement, veuillez patienter.", - "error_sso_not_enabled": "L'authentification unique n'est pas activée pour cet utilisateur.", - "error_token_exchange": "Échec de la connexion. Veuillez réessayer.", - "error_user_not_found": "Utilisateur introuvable. Veuillez vérifier et réessayer.", - "looking_up": "Recherche en cours...", - "optional": "optionnel", - "page_subtitle": "Saisissez votre nom d'utilisateur pour rechercher les options de connexion de votre organisation.", - "page_title": "Authentification unique", - "provider_oidc": "OpenID Connect (OIDC)", - "provider_saml": "SAML 2.0", - "sign_in_button": "Se connecter avec SSO", - "sign_in_title": "Connexion", - "sso_button": "Connexion SSO" - }, - "login": { - "branding_subtitle": "Logiciel de dispatch puissant pour les premiers intervenants, les équipes de recherche et de sauvetage et les organisations de sécurité publique.", - "branding_title": "Gestion des interventions d'urgence", - "errorModal": { - "confirmButton": "OK", - "message": "Veuillez vérifier votre nom d'utilisateur et votre mot de passe, puis réessayer.", - "title": "Échec de la connexion" - }, - "feature_dispatch_desc": "Dépêchez instantanément des unités et gérez les appels avec des mises à jour en temps réel sur tous les appareils.", - "feature_dispatch_title": "Dispatch en temps réel", - "feature_mapping_desc": "Suivez les unités en temps réel avec des cartes détaillées, le routage et la gestion de la localisation.", - "feature_mapping_title": "Cartographie avancée", - "feature_personnel_desc": "Gérez votre équipe avec un accès basé sur les rôles, le suivi des statuts et des outils de communication.", - "feature_personnel_title": "Gestion du personnel", - "footer_text": "Créé avec ❤️ à Lake Tahoe", - "login": "Connexion", - "login_button": "Se connecter", - "login_button_description": "Connectez-vous à votre compte pour continuer", - "login_button_error": "Erreur lors de la connexion", - "login_button_loading": "Connexion en cours...", - "login_button_success": "Connecté avec succès", - "no_account": "Vous n'avez pas de compte ?", - "page_subtitle": "Saisissez vos identifiants pour vous connecter.", - "page_title": "Resgrid Dispatch", - "password": "Mot de passe", - "password_incorrect": "Le mot de passe est incorrect", - "password_placeholder": "Saisissez votre mot de passe", - "register": "S'inscrire", - "title": "Connexion", - "username": "Nom d'utilisateur", - "username_placeholder": "Saisissez votre nom d'utilisateur", - "welcome_title": "Bon retour" - }, - "lockscreen": { - "message": "Saisissez votre mot de passe pour déverrouiller l'écran", - "not_you": "Ce n'est pas vous ? Retour à la connexion", - "password": "Mot de passe", - "password_placeholder": "Saisissez votre mot de passe", - "title": "Écran de verrouillage", - "unlock_button": "Déverrouiller", - "unlock_failed": "Échec du déverrouillage. Veuillez réessayer.", - "unlocking": "Déverrouillage en cours...", - "welcome_back": "Bon retour", - "relogin_required": "La vérification du mot de passe n'est pas disponible pour cette session. Veuillez vous reconnecter." - }, - "maintenance": { - "downtime_message": "Nous travaillons activement pour terminer la maintenance le plus rapidement possible. Veuillez revenir bientôt.", - "downtime_title": "Quelle est la durée de l'interruption ?", - "message": "Veuillez revenir dans quelques instants.", - "support_message": "Si vous avez besoin d'aide, veuillez nous contacter à", - "support_title": "Avez-vous besoin d'aide ?", - "title": "Le site est en maintenance", - "why_down_message": "Nous effectuons une maintenance planifiée pour améliorer votre expérience. Nous nous excusons pour tout inconvénient.", - "why_down_title": "Pourquoi le site est-il indisponible ?" - }, - "menu": { - "scheduled_calls": "Interventions planifiées", - "pois": "POI", - "calls": "Appels", - "calls_list": "Liste des appels", - "contacts": "Contacts", - "home": "Accueil", - "map": "Carte", - "menu": "Menu", - "messages": "Messages", - "new_call": "Nouvel appel", - "personnel": "Personnel", - "protocols": "Protocoles", - "settings": "Paramètres", + "dispatch": { + "active_calls": "Appels actifs", + "pending_calls": "En attente", + "scheduled_calls": "Planifiés", + "units_available": "Disponibles", + "personnel_available": "Disponibles", + "personnel_on_duty": "En service", "units": "Unités", - "weatherAlerts": "Alertes météo", - "incident_command": "Commandement d’intervention", - "chat": "Chat", - "assistant": "Assistant" - }, - "pois": { - "address": "Adresse", - "all_types": "Tous les types", - "destination": "Destination", - "details": "Détails", - "detail_not_found": "POI introuvable", - "detail_not_found_description": "Le POI demandé n’a pas pu être chargé.", - "detail_title": "Détails du POI", - "empty": "Aucun POI trouvé", - "empty_description": "Aucun point d’intérêt n’est encore disponible pour votre service.", - "empty_filtered": "Aucun POI correspondant", - "empty_filtered_description": "Effacez votre recherche ou choisissez un autre type de POI.", - "filter_by_type": "Filtrer par type de POI", - "invalid_poi": "POI non valide", - "invalid_poi_description": "L’identifiant du POI sélectionné n’est pas valide.", - "loading": "Chargement des POI...", - "loading_detail": "Chargement des détails du POI...", + "personnel": "Personnel", "map": "Carte", - "no_location": "Aucun emplacement disponible", - "no_location_description": "Ce POI ne possède pas de coordonnées utilisables.", - "no_location_for_routing": "Aucune donnée d’emplacement disponible pour l’itinéraire", - "note": "Note", - "route_error": "Impossible d’ouvrir l’application de cartographie", - "search": "Rechercher des POI...", - "sort": "Trier", - "sort_options": { - "address-asc": "Adresse", - "name-asc": "Nom (A–Z)", - "name-desc": "Nom (Z–A)", - "type-asc": "Type" + "notes": "Notes", + "activity_log": "Journal d'activité", + "communications": "Communications", + "no_active_calls": "Aucun appel actif", + "no_units": "Aucune unité disponible", + "no_personnel": "Aucun personnel disponible", + "no_notes": "Aucune note disponible", + "no_activity": "Aucune activité récente", + "current_channel": "Canal actuel", + "audio_stream": "Flux audio", + "no_stream": "Aucun flux actif", + "ptt": "PTT", + "ptt_start": "Début PTT", + "ptt_end": "Fin PTT", + "transmitting_on": "Transmission sur {{channel}}", + "transmission_ended": "Transmission terminée", + "voice_disabled": "Voix désactivée", + "disconnected": "Déconnecté", + "select_channel": "Sélectionner un canal", + "select_channel_description": "Choisissez un canal vocal auquel vous connecter", + "change_channel_warning": "Sélectionner un nouveau canal déconnectera le canal actuel", + "default_channel": "Par défaut", + "no_channels_available": "Aucun canal vocal disponible", + "system_update": "Mise à jour système", + "data_refreshed": "Données actualisées depuis le serveur", + "call_selected": "Appel sélectionné", + "unit_selected": "Unité sélectionnée", + "unit_deselected": "Unité désélectionnée", + "personnel_selected": "Personnel sélectionné", + "personnel_deselected": "Personnel désélectionné", + "loading_map": "Chargement de la carte...", + "map_not_available_web": "Carte non disponible sur la plateforme web", + "filtering_by_call": "Filtrage par appel", + "clear_filter": "Effacer le filtre", + "call_filter_active": "Filtre d'appel actif", + "call_filter_cleared": "Filtre d'appel effacé", + "showing_all_data": "Affichage de toutes les données", + "call_notes": "Notes de l'appel", + "no_call_notes": "Aucune note d'appel", + "add_call_note_placeholder": "Ajouter une note...", + "note_added": "Note ajoutée", + "note_added_to_console": "Une nouvelle note a été ajoutée à la console", + "add_note_title": "Ajouter une nouvelle note", + "note_title_label": "Titre", + "note_title_placeholder": "Saisissez le titre de la note...", + "note_category_label": "Catégorie", + "note_category_placeholder": "Sélectionner une catégorie", + "note_no_category": "Sans catégorie", + "note_body_label": "Contenu de la note", + "note_body_placeholder": "Saisissez le contenu de la note...", + "note_save_error": "Échec de la sauvegarde de la note : {{error}}", + "note_created": "Note créée", + "units_on_call": "Unités en intervention", + "no_units_on_call": "Aucune unité en intervention", + "personnel_on_call": "Personnel en intervention", + "no_personnel_on_call": "Aucun personnel en intervention", + "call_activity": "Activité de l'appel", + "no_call_activity": "Aucune activité d'appel", + "on_call": "En intervention", + "filtered": "Filtré", + "active_filter": "Filtre actif", + "unit_status_change": "Changement de statut d'unité", + "personnel_status_change": "Changement de statut du personnel", + "view_call_details": "Voir les détails de l'appel", + "dispatched_resources": "Dépêchés", + "unassigned": "Non affecté", + "available": "Disponible", + "unknown": "Inconnu", + "search_personnel_placeholder": "Rechercher du personnel...", + "search_calls_placeholder": "Rechercher des appels...", + "search_units_placeholder": "Rechercher des unités...", + "search_notes_placeholder": "Rechercher des notes...", + "signalr_update": "Mise à jour en temps réel", + "signalr_connected": "Connecté", + "realtime_updates_active": "Les mises à jour en temps réel sont maintenant actives", + "personnel_status_updated": "Statut du personnel mis à jour", + "personnel_staffing_updated": "Effectifs du personnel mis à jour", + "unit_status_updated": "Statut de l'unité mis à jour", + "calls_updated": "Appels mis à jour", + "call_added": "Nouvel appel ajouté", + "call_closed": "Appel clôturé", + "check_ins": "Pointages", + "no_check_ins": "Aucun appel avec minuterie de pointage", + "radio_log": "Journal radio", + "radio": "Radio", + "activity": "Activité", + "actions": "Actions", + "no_radio_activity": "Aucune transmission radio", + "live": "EN DIRECT", + "currently_transmitting": "Transmission en cours...", + "duration": "Durée", + "call_actions": "Actions de l'appel", + "unit_actions": "Actions de l'unité", + "personnel_actions": { + "title": "Actions du personnel", + "status_tab": "Statut", + "staffing_tab": "Effectifs", + "select_status": "Sélectionner le statut", + "select_staffing": "Sélectionner le niveau d'effectifs", + "destination": "Destination", + "no_destination": "Aucune destination", + "note": "Note", + "note_placeholder": "Ajouter une note optionnelle...", + "update_status": "Mettre à jour le statut", + "update_staffing": "Mettre à jour les effectifs", + "no_statuses_available": "Aucun statut disponible", + "no_staffings_available": "Aucun niveau d'effectifs disponible" }, - "title": "POI", - "type": "Type", - "unknown_type": "Type inconnu", - "unnamed": "POI sans nom" + "unit_actions_panel": { + "status": "Statut", + "select_status": "Sélectionner le statut", + "destination": "Destination", + "no_destination": "Aucune destination", + "note": "Note", + "note_placeholder": "Ajouter une note optionnelle...", + "update_status": "Mettre à jour le statut", + "no_statuses_available": "Aucun statut disponible", + "no_active_calls": "Aucun appel actif", + "no_stations_available": "Aucune caserne disponible", + "no_destinations_available": "Aucune destination disponible" + }, + "call": "Appel", + "station": "Caserne", + "calls": "Appels", + "stations": "Casernes", + "no_stations_available": "Aucune caserne disponible", + "new_call": "Nouvel appel", + "view_details": "Détails", + "add_note": "Ajouter une note", + "close_call": "Clôturer", + "set_status": "Définir le statut", + "set_staffing": "Effectifs", + "dispatch": "Dispatch", + "select_items_for_actions": "Sélectionnez un appel, une unité ou du personnel pour activer les actions contextuelles", + "weather": { + "clear": "Dégagé", + "mainly_clear": "Principalement dégagé", + "partly_cloudy": "Partiellement nuageux", + "overcast": "Couvert", + "fog": "Brouillard", + "drizzle": "Bruine", + "freezing_drizzle": "Bruine verglaçante", + "rain": "Pluie", + "freezing_rain": "Pluie verglaçante", + "snow": "Neige", + "rain_showers": "Averses de pluie", + "snow_showers": "Averses de neige", + "thunderstorm": "Orage", + "thunderstorm_hail": "Orage avec grêle", + "unknown": "Inconnu" + }, + "available_only": "Disponibles uniquement", + "single_list": "Liste unique", + "resources": "Ressources", + "search_resources_placeholder": "Rechercher des ressources...", + "no_resources": "Aucune ressource" }, - "scheduled_calls": { - "title": "Interventions planifiées", - "loading": "Chargement des interventions planifiées...", - "no_scheduled_calls": "Aucune intervention planifiée", - "no_scheduled_calls_description": "Aucune intervention planifiée n’est en attente actuellement.", - "search": "Rechercher des interventions planifiées...", - "scheduled_for": "Planifiée pour", - "table_number": "Intervention n°", - "table_name": "Nom", - "table_type": "Type", - "table_priority": "Priorité", - "table_address": "Adresse", - "table_scheduled": "Planifiée pour" + "form": { + "invalid_url": "Veuillez saisir une URL valide commençant par http:// ou https://", + "required": "Ce champ est obligatoire" + }, + "incident_command": { + "accountability": "Suivi du personnel (PAR)", + "acknowledge": "Confirmer", + "action_plan": "Plan d’action", + "action_plan_placeholder": "Décrivez le plan d’action de l’intervention...", + "active": "Actif", + "active_title": "Commandements d’intervention actifs", + "add": "Ajouter", + "add_channel": "Ajouter un canal", + "add_lane": "Ajouter un secteur", + "add_marker": "Ajouter un repère", + "add_objective": "Ajouter un objectif", + "annotations": "Annotations de la carte", + "assign": "Affecter", + "assign_resource": "Affecter une ressource", + "assign_resource_required": "Sélectionnez un secteur et une ressource", + "assign_role": "Affecter un rôle", + "assign_role_required": "Sélectionnez une personne et un rôle", + "call": "Intervention", + "channel_name": "Nom du canal", + "chat": "Chat", + "chat_frozen": "This incident is closed. Conversations are kept as a point-in-time record — no new messages.", + "close_all_channels": "Fermer tous les canaux", + "close_command": "Clore le commandement", + "closed": "Clos", + "command_channel": "Command chat", + "command_channel_hint": "Command staff and dispatch", + "command_channel_unavailable": "No command channel has been created for this incident yet.", + "commander": "Commandant", + "complete": "Terminer", + "completed": "Terminé", + "confirm_close": "Clore le commandement de cette intervention ?", + "critical": "Critique", + "delete_annotation_confirm": "Supprimer cette annotation ?", + "dispatch_channel": "Dispatch", + "dispatch_channel_hint": "The incident's line to the desk", + "dispatch_channel_unavailable": "No dispatch channel has been created for this incident yet.", + "dm_failed": "Couldn't open that conversation.", + "dm_unavailable": "That contact has no Resgrid account to message.", + "due": "Échéance", + "edit": "Modifier", + "edit_action_plan": "Modifier le plan d’action", + "establish": "Établir le commandement", + "establish_description": "Initialisez éventuellement le tableau de commandement à partir d’un modèle.", + "establish_error": "Échec de l’établissement du commandement", + "establish_success": "Commandement de l’intervention établi", + "establish_title": "Établir le commandement de l’intervention", + "established_on": "Établi", + "green": "Vert", + "hold_to_talk": "Maintenir pour parler", + "incident_channel": "Incident chat", + "incident_channel_hint": "Everyone working the incident", + "incident_channel_unavailable": "No incident channel has been created for this call yet.", + "join": "Rejoindre", + "lane": "Secteur", + "lane_name": "Nom du secteur", + "lane_type": "Type de secteur", + "marker": "Repère", + "marker_label": "Libellé du repère", + "move": "Déplacer", + "move_lane": "Déplacer le secteur", + "move_resource": "Déplacer la ressource", + "name_required": "Le nom est obligatoire", + "no_accountability": "Aucun personnel suivi.", + "no_action_plan": "Aucun plan d’action défini.", + "no_active": "Aucun commandement d’intervention actif", + "no_active_description": "Les commandements établis pour les interventions apparaîtront ici.", + "no_annotations": "Aucune annotation.", + "no_channels": "Aucun canal ouvert.", + "no_command": "Aucun commandement d’intervention établi", + "no_command_description": "Établissez un commandement d’intervention pour coordonner les ressources, les rôles, les objectifs et le suivi du personnel pour cet appel.", + "no_lanes": "Aucun secteur défini.", + "no_objectives": "Aucun objectif.", + "no_resources": "Aucune ressource affectée.", + "no_roles": "Aucun rôle attribué.", + "no_template": "Aucun modèle (tableau vide)", + "no_timeline": "Aucune entrée dans la chronologie.", + "no_timers": "Aucun minuteur en cours.", + "not_authorized": "Command board unavailable", + "not_authorized_description": "Your department hasn't authorized you to work incident command. Ask an administrator for the Command App Login permission.", + "objective_name": "Objectif", + "objective_type": "Type", + "objectives": "Objectifs", + "open_chat": "Open", + "open_full_board": "Ouvrir le tableau complet", + "open_tactical_map": "Ouvrir la carte tactique", + "parent_lane": "Secteur parent", + "person": "Personne", + "personnel": "Personnel", + "release": "Libérer", + "resource": "Ressource", + "resource_type": "Type de ressource", + "role": "Rôle", + "roles": "Rôles de commandement", + "run_par": "Lancer le PAR", + "save": "Enregistrer", + "save_error": "Échec de l’opération", + "saved": "Enregistré", + "select_lane": "Sélectionnez un secteur", + "select_person": "Sélectionnez une personne", + "select_resource": "Sélectionnez une ressource", + "select_role": "Sélectionnez un rôle", + "send_message": "Message", + "status": "État", + "structure": "Structure de commandement", + "tab_title": "Commandement", + "tactical_map": "Carte tactique", + "talking": "Transmission...", + "tap_to_place": "Touchez la carte pour placer un repère", + "template": "Modèle", + "timeline": "Chronologie du commandement", + "timers": "Minuteurs", + "title": "Commandement de l’intervention", + "top_level": "Niveau supérieur", + "transfer": "Transférer", + "transfer_command": "Transférer", + "transfer_notes": "Notes", + "transfer_success": "Commandement transféré", + "transfer_title": "Transférer le commandement", + "unassigned": "Non affecté", + "unit": "Unité", + "voice_channels": "Canaux vocaux", + "voice_join_error": "Impossible de rejoindre le canal vocal", + "voice_joined": "Canal vocal rejoint", + "warning": "Avertissement" + }, + "livekit": { + "audio_devices": "Appareils audio", + "audio_settings": "Paramètres audio", + "connected_to_room": "Connecté au canal", + "connecting": "Connexion en cours...", + "disconnect": "Déconnecter", + "join": "Rejoindre", + "microphone": "Microphone", + "mute": "Couper le son", + "no_rooms_available": "Aucun canal vocal disponible", + "speaker": "Haut-parleur", + "speaking": "En train de parler", + "title": "Canaux vocaux", + "unmute": "Réactiver le son" + }, + "loading": { + "loading": "Chargement...", + "loadingData": "Chargement des données...", + "pleaseWait": "Veuillez patienter", + "processingRequest": "Traitement de votre demande..." + }, + "lockscreen": { + "message": "Saisissez votre mot de passe pour déverrouiller l'écran", + "not_you": "Ce n'est pas vous ? Retour à la connexion", + "password": "Mot de passe", + "password_placeholder": "Saisissez votre mot de passe", + "title": "Écran de verrouillage", + "unlock_button": "Déverrouiller", + "unlock_failed": "Échec du déverrouillage. Veuillez réessayer.", + "unlocking": "Déverrouillage en cours...", + "welcome_back": "Bon retour", + "relogin_required": "La vérification du mot de passe n'est pas disponible pour cette session. Veuillez vous reconnecter." + }, + "login": { + "branding_subtitle": "Logiciel de dispatch puissant pour les premiers intervenants, les équipes de recherche et de sauvetage et les organisations de sécurité publique.", + "branding_title": "Gestion des interventions d'urgence", + "dispatch_not_authorized": "You are not authorized to use the Dispatch app. Contact your department administrator.", + "errorModal": { + "confirmButton": "OK", + "message": "Veuillez vérifier votre nom d'utilisateur et votre mot de passe, puis réessayer.", + "title": "Échec de la connexion" + }, + "feature_dispatch_desc": "Dépêchez instantanément des unités et gérez les appels avec des mises à jour en temps réel sur tous les appareils.", + "feature_dispatch_title": "Dispatch en temps réel", + "feature_mapping_desc": "Suivez les unités en temps réel avec des cartes détaillées, le routage et la gestion de la localisation.", + "feature_mapping_title": "Cartographie avancée", + "feature_personnel_desc": "Gérez votre équipe avec un accès basé sur les rôles, le suivi des statuts et des outils de communication.", + "feature_personnel_title": "Gestion du personnel", + "footer_text": "Créé avec ❤️ à Lake Tahoe", + "login": "Connexion", + "login_button": "Se connecter", + "login_button_description": "Connectez-vous à votre compte pour continuer", + "login_button_error": "Erreur lors de la connexion", + "login_button_loading": "Connexion en cours...", + "login_button_success": "Connecté avec succès", + "no_account": "Vous n'avez pas de compte ?", + "page_subtitle": "Saisissez vos identifiants pour vous connecter.", + "page_title": "Resgrid Dispatch", + "password": "Mot de passe", + "password_incorrect": "Le mot de passe est incorrect", + "password_placeholder": "Saisissez votre mot de passe", + "register": "S'inscrire", + "title": "Connexion", + "username": "Nom d'utilisateur", + "username_placeholder": "Saisissez votre nom d'utilisateur", + "welcome_title": "Bon retour" + }, + "maintenance": { + "downtime_message": "Nous travaillons activement pour terminer la maintenance le plus rapidement possible. Veuillez revenir bientôt.", + "downtime_title": "Quelle est la durée de l'interruption ?", + "message": "Veuillez revenir dans quelques instants.", + "support_message": "Si vous avez besoin d'aide, veuillez nous contacter à", + "support_title": "Avez-vous besoin d'aide ?", + "title": "Le site est en maintenance", + "why_down_message": "Nous effectuons une maintenance planifiée pour améliorer votre expérience. Nous nous excusons pour tout inconvénient.", + "why_down_title": "Pourquoi le site est-il indisponible ?" }, "map": { "view_poi_details": "Afficher les détails du POI", @@ -690,6 +1009,26 @@ "hide_all": "Tout masquer", "view_call_details": "Voir les détails de l'appel" }, + "menu": { + "scheduled_calls": "Interventions planifiées", + "pois": "POI", + "calls": "Appels", + "calls_list": "Liste des appels", + "contacts": "Contacts", + "home": "Accueil", + "map": "Carte", + "menu": "Menu", + "messages": "Messages", + "new_call": "Nouvel appel", + "personnel": "Personnel", + "protocols": "Protocoles", + "settings": "Paramètres", + "units": "Unités", + "weatherAlerts": "Alertes météo", + "incident_command": "Commandement d’intervention", + "chat": "Chat", + "assistant": "Assistant" + }, "notes": { "actions": { "add": "Ajouter une note", @@ -709,6 +1048,23 @@ "search": "Rechercher dans les notes...", "title": "Notes" }, + "onboarding": { + "screen1": { + "title": "Resgrid Dispatch", + "description": "Créez, dépêchez et gérez les appels d'urgence avec un puissant centre de commandement mobile à portée de main" + }, + "screen2": { + "title": "Connaissance situationnelle en temps réel", + "description": "Suivez toutes les unités, le personnel et les ressources sur une carte interactive avec des mises à jour de statut en direct et l'AVL" + }, + "screen3": { + "title": "Coordination fluide", + "description": "Communiquez instantanément avec les unités sur le terrain, mettez à jour les statuts des appels et coordonnez les efforts d'intervention depuis n'importe où" + }, + "skip": "Passer", + "next": "Suivant", + "getStarted": "C'est parti" + }, "personnel": { "title": "Personnel", "search": "Rechercher du personnel...", @@ -741,22 +1097,41 @@ "send_email": "E-mail", "custom_fields": "Informations supplémentaires" }, - "onboarding": { - "screen1": { - "title": "Resgrid Dispatch", - "description": "Créez, dépêchez et gérez les appels d'urgence avec un puissant centre de commandement mobile à portée de main" - }, - "screen2": { - "title": "Connaissance situationnelle en temps réel", - "description": "Suivez toutes les unités, le personnel et les ressources sur une carte interactive avec des mises à jour de statut en direct et l'AVL" - }, - "screen3": { - "title": "Coordination fluide", - "description": "Communiquez instantanément avec les unités sur le terrain, mettez à jour les statuts des appels et coordonnez les efforts d'intervention depuis n'importe où" + "pois": { + "address": "Adresse", + "all_types": "Tous les types", + "destination": "Destination", + "details": "Détails", + "detail_not_found": "POI introuvable", + "detail_not_found_description": "Le POI demandé n’a pas pu être chargé.", + "detail_title": "Détails du POI", + "empty": "Aucun POI trouvé", + "empty_description": "Aucun point d’intérêt n’est encore disponible pour votre service.", + "empty_filtered": "Aucun POI correspondant", + "empty_filtered_description": "Effacez votre recherche ou choisissez un autre type de POI.", + "filter_by_type": "Filtrer par type de POI", + "invalid_poi": "POI non valide", + "invalid_poi_description": "L’identifiant du POI sélectionné n’est pas valide.", + "loading": "Chargement des POI...", + "loading_detail": "Chargement des détails du POI...", + "map": "Carte", + "no_location": "Aucun emplacement disponible", + "no_location_description": "Ce POI ne possède pas de coordonnées utilisables.", + "no_location_for_routing": "Aucune donnée d’emplacement disponible pour l’itinéraire", + "note": "Note", + "route_error": "Impossible d’ouvrir l’application de cartographie", + "search": "Rechercher des POI...", + "sort": "Trier", + "sort_options": { + "address-asc": "Adresse", + "name-asc": "Nom (A–Z)", + "name-desc": "Nom (Z–A)", + "type-asc": "Type" }, - "skip": "Passer", - "next": "Suivant", - "getStarted": "C'est parti" + "title": "POI", + "type": "Type", + "unknown_type": "Type inconnu", + "unnamed": "POI sans nom" }, "protocols": { "details": { @@ -796,6 +1171,20 @@ "tap_to_manage": "Appuyez pour gérer les rôles", "unassigned": "Non affecté" }, + "scheduled_calls": { + "title": "Interventions planifiées", + "loading": "Chargement des interventions planifiées...", + "no_scheduled_calls": "Aucune intervention planifiée", + "no_scheduled_calls_description": "Aucune intervention planifiée n’est en attente actuellement.", + "search": "Rechercher des interventions planifiées...", + "scheduled_for": "Planifiée pour", + "table_number": "Intervention n°", + "table_name": "Nom", + "table_type": "Type", + "table_priority": "Priorité", + "table_address": "Adresse", + "table_scheduled": "Planifiée pour" + }, "settings": { "about": "À propos", "account": "Compte", @@ -884,6 +1273,29 @@ "version": "Version", "website": "Site web" }, + "sso": { + "authenticating": "Authentification en cours...", + "back_to_login": "Retour à la connexion", + "back_to_lookup": "Changer d'utilisateur", + "continue_button": "Continuer", + "department_id_label": "ID du service", + "department_id_placeholder": "Saisissez l'ID du service", + "error_generic": "La connexion a échoué. Veuillez réessayer.", + "error_oidc_cancelled": "La connexion a été annulée.", + "error_oidc_not_ready": "Le fournisseur SSO est en cours de chargement, veuillez patienter.", + "error_sso_not_enabled": "L'authentification unique n'est pas activée pour cet utilisateur.", + "error_token_exchange": "Échec de la connexion. Veuillez réessayer.", + "error_user_not_found": "Utilisateur introuvable. Veuillez vérifier et réessayer.", + "looking_up": "Recherche en cours...", + "optional": "optionnel", + "page_subtitle": "Saisissez votre nom d'utilisateur pour rechercher les options de connexion de votre organisation.", + "page_title": "Authentification unique", + "provider_oidc": "OpenID Connect (OIDC)", + "provider_saml": "SAML 2.0", + "sign_in_button": "Se connecter avec SSO", + "sign_in_title": "Connexion", + "sso_button": "Connexion SSO" + }, "status": { "all_destinations_enabled": "Peut répondre aux interventions, casernes ou POI", "calls_and_pois_destination_enabled": "Peut répondre aux interventions ou POI", @@ -916,173 +1328,6 @@ "stations_tab": "Casernes", "status_saved_successfully": "Statut sauvegardé avec succès !" }, - "dispatch": { - "active_calls": "Appels actifs", - "pending_calls": "En attente", - "scheduled_calls": "Planifiés", - "units_available": "Disponibles", - "personnel_available": "Disponibles", - "personnel_on_duty": "En service", - "units": "Unités", - "personnel": "Personnel", - "map": "Carte", - "notes": "Notes", - "activity_log": "Journal d'activité", - "communications": "Communications", - "no_active_calls": "Aucun appel actif", - "no_units": "Aucune unité disponible", - "no_personnel": "Aucun personnel disponible", - "no_notes": "Aucune note disponible", - "no_activity": "Aucune activité récente", - "current_channel": "Canal actuel", - "audio_stream": "Flux audio", - "no_stream": "Aucun flux actif", - "ptt": "PTT", - "ptt_start": "Début PTT", - "ptt_end": "Fin PTT", - "transmitting_on": "Transmission sur {{channel}}", - "transmission_ended": "Transmission terminée", - "voice_disabled": "Voix désactivée", - "disconnected": "Déconnecté", - "select_channel": "Sélectionner un canal", - "select_channel_description": "Choisissez un canal vocal auquel vous connecter", - "change_channel_warning": "Sélectionner un nouveau canal déconnectera le canal actuel", - "default_channel": "Par défaut", - "no_channels_available": "Aucun canal vocal disponible", - "system_update": "Mise à jour système", - "data_refreshed": "Données actualisées depuis le serveur", - "call_selected": "Appel sélectionné", - "unit_selected": "Unité sélectionnée", - "unit_deselected": "Unité désélectionnée", - "personnel_selected": "Personnel sélectionné", - "personnel_deselected": "Personnel désélectionné", - "loading_map": "Chargement de la carte...", - "map_not_available_web": "Carte non disponible sur la plateforme web", - "filtering_by_call": "Filtrage par appel", - "clear_filter": "Effacer le filtre", - "call_filter_active": "Filtre d'appel actif", - "call_filter_cleared": "Filtre d'appel effacé", - "showing_all_data": "Affichage de toutes les données", - "call_notes": "Notes de l'appel", - "no_call_notes": "Aucune note d'appel", - "add_call_note_placeholder": "Ajouter une note...", - "note_added": "Note ajoutée", - "note_added_to_console": "Une nouvelle note a été ajoutée à la console", - "add_note_title": "Ajouter une nouvelle note", - "note_title_label": "Titre", - "note_title_placeholder": "Saisissez le titre de la note...", - "note_category_label": "Catégorie", - "note_category_placeholder": "Sélectionner une catégorie", - "note_no_category": "Sans catégorie", - "note_body_label": "Contenu de la note", - "note_body_placeholder": "Saisissez le contenu de la note...", - "note_save_error": "Échec de la sauvegarde de la note : {{error}}", - "note_created": "Note créée", - "units_on_call": "Unités en intervention", - "no_units_on_call": "Aucune unité en intervention", - "personnel_on_call": "Personnel en intervention", - "no_personnel_on_call": "Aucun personnel en intervention", - "call_activity": "Activité de l'appel", - "no_call_activity": "Aucune activité d'appel", - "on_call": "En intervention", - "filtered": "Filtré", - "active_filter": "Filtre actif", - "unit_status_change": "Changement de statut d'unité", - "personnel_status_change": "Changement de statut du personnel", - "view_call_details": "Voir les détails de l'appel", - "dispatched_resources": "Dépêchés", - "unassigned": "Non affecté", - "available": "Disponible", - "unknown": "Inconnu", - "search_personnel_placeholder": "Rechercher du personnel...", - "search_calls_placeholder": "Rechercher des appels...", - "search_units_placeholder": "Rechercher des unités...", - "search_notes_placeholder": "Rechercher des notes...", - "signalr_update": "Mise à jour en temps réel", - "signalr_connected": "Connecté", - "realtime_updates_active": "Les mises à jour en temps réel sont maintenant actives", - "personnel_status_updated": "Statut du personnel mis à jour", - "personnel_staffing_updated": "Effectifs du personnel mis à jour", - "unit_status_updated": "Statut de l'unité mis à jour", - "calls_updated": "Appels mis à jour", - "call_added": "Nouvel appel ajouté", - "call_closed": "Appel clôturé", - "check_ins": "Pointages", - "no_check_ins": "Aucun appel avec minuterie de pointage", - "radio_log": "Journal radio", - "radio": "Radio", - "activity": "Activité", - "actions": "Actions", - "no_radio_activity": "Aucune transmission radio", - "live": "EN DIRECT", - "currently_transmitting": "Transmission en cours...", - "duration": "Durée", - "call_actions": "Actions de l'appel", - "unit_actions": "Actions de l'unité", - "personnel_actions": { - "title": "Actions du personnel", - "status_tab": "Statut", - "staffing_tab": "Effectifs", - "select_status": "Sélectionner le statut", - "select_staffing": "Sélectionner le niveau d'effectifs", - "destination": "Destination", - "no_destination": "Aucune destination", - "note": "Note", - "note_placeholder": "Ajouter une note optionnelle...", - "update_status": "Mettre à jour le statut", - "update_staffing": "Mettre à jour les effectifs", - "no_statuses_available": "Aucun statut disponible", - "no_staffings_available": "Aucun niveau d'effectifs disponible" - }, - "unit_actions_panel": { - "status": "Statut", - "select_status": "Sélectionner le statut", - "destination": "Destination", - "no_destination": "Aucune destination", - "note": "Note", - "note_placeholder": "Ajouter une note optionnelle...", - "update_status": "Mettre à jour le statut", - "no_statuses_available": "Aucun statut disponible", - "no_active_calls": "Aucun appel actif", - "no_stations_available": "Aucune caserne disponible", - "no_destinations_available": "Aucune destination disponible" - }, - "call": "Appel", - "station": "Caserne", - "calls": "Appels", - "stations": "Casernes", - "no_stations_available": "Aucune caserne disponible", - "new_call": "Nouvel appel", - "view_details": "Détails", - "add_note": "Ajouter une note", - "close_call": "Clôturer", - "set_status": "Définir le statut", - "set_staffing": "Effectifs", - "dispatch": "Dispatch", - "select_items_for_actions": "Sélectionnez un appel, une unité ou du personnel pour activer les actions contextuelles", - "weather": { - "clear": "Dégagé", - "mainly_clear": "Principalement dégagé", - "partly_cloudy": "Partiellement nuageux", - "overcast": "Couvert", - "fog": "Brouillard", - "drizzle": "Bruine", - "freezing_drizzle": "Bruine verglaçante", - "rain": "Pluie", - "freezing_rain": "Pluie verglaçante", - "snow": "Neige", - "rain_showers": "Averses de pluie", - "snow_showers": "Averses de neige", - "thunderstorm": "Orage", - "thunderstorm_hail": "Orage avec grêle", - "unknown": "Inconnu" - }, - "available_only": "Disponibles uniquement", - "single_list": "Liste unique", - "resources": "Ressources", - "search_resources_placeholder": "Rechercher des ressources...", - "no_resources": "Aucune ressource" - }, "tabs": { "calls": "Appels", "calendar": "Calendrier", @@ -1096,44 +1341,6 @@ "shifts": "Quarts", "personnel": "Personnel" }, - "check_in": { - "tab_title": "Pointage", - "timer_status": "Statut de la minuterie", - "perform_check_in": "Pointer", - "check_in_success": "Pointage enregistré avec succès", - "check_in_error": "Échec de l'enregistrement du pointage", - "checked_in_by": "par {{name}}", - "last_check_in": "Dernier pointage", - "elapsed": "Écoulé", - "duration": "Durée", - "status_ok": "OK", - "status_green": "OK", - "status_warning": "Avertissement", - "status_yellow": "Avertissement", - "status_overdue": "En retard", - "status_red": "En retard", - "status_critical": "Critique", - "history": "Historique des pointages", - "no_timers": "Aucune minuterie de pointage configurée", - "timers_disabled": "Les minuteries de pointage sont désactivées pour cet appel", - "type_personnel": "Personnel", - "type_unit": "Unité", - "type_ic": "Commandant des opérations", - "type_par": "PAR", - "type_hazmat": "Exposition Hazmat", - "type_sector_rotation": "Rotation de secteur", - "type_rehab": "Réhabilitation", - "add_note": "Ajouter une note (optionnel)", - "confirm": "Confirmer le pointage", - "minutes_ago": "il y a {{count}} min", - "select_target": "Sélectionner l'entité à pointer", - "overdue_count": "{{count}} en retard", - "warning_count": "{{count}} avertissement", - "enable_timers": "Activer les minuteries", - "disable_timers": "Désactiver les minuteries", - "summary": "{{overdue}} en retard, {{warning}} avertissement, {{ok}} ok", - "par_title": "Suivi du personnel (PAR)" - }, "units": { "search": "Rechercher des unités...", "loading": "Chargement des unités...", @@ -1163,6 +1370,63 @@ "no_destination": "Aucune", "title": "Unités" }, + "videoFeeds": { + "title": "Flux vidéo", + "noFeeds": "Aucun flux vidéo pour cet appel", + "addFeed": "Ajouter un flux vidéo", + "editFeed": "Modifier le flux vidéo", + "deleteFeed": "Supprimer le flux vidéo", + "deleteConfirm": "Êtes-vous sûr de vouloir supprimer ce flux vidéo ?", + "watch": "Regarder", + "goLive": "Diffuser en direct", + "stopLive": "Arrêter la diffusion", + "flipCamera": "Inverser la caméra", + "feedAdded": "Flux vidéo ajouté", + "feedUpdated": "Flux vidéo mis à jour", + "feedDeleted": "Flux vidéo supprimé", + "feedError": "Échec du chargement du flux vidéo", + "unsupportedFormat": "Ce format de flux n'est pas pris en charge sur mobile", + "copyUrl": "Copier l'URL", + "form": { + "name": "Nom du flux", + "namePlaceholder": "ex. : Drone Véhicule 1", + "url": "URL du flux", + "urlPlaceholder": "ex. : https://stream.example.com/live.m3u8", + "feedType": "Type de caméra", + "feedFormat": "Format du flux", + "description": "Description", + "descriptionPlaceholder": "Description optionnelle", + "status": "Statut", + "sortOrder": "Ordre de tri", + "cameraLocation": "Emplacement de la caméra", + "useCurrentLocation": "Utiliser la position actuelle" + }, + "type": { + "drone": "Drone", + "fixedCamera": "Caméra fixe", + "bodyCam": "Caméra corporelle", + "trafficCam": "Caméra de circulation", + "weatherCam": "Caméra météo", + "satelliteFeed": "Flux satellite", + "webCam": "Webcam", + "other": "Autre" + }, + "format": { + "rtsp": "RTSP", + "hls": "HLS", + "mjpeg": "MJPEG", + "youtubeLive": "YouTube Live", + "webrtc": "WebRTC", + "dash": "DASH", + "embed": "Intégré", + "other": "Autre" + }, + "status": { + "active": "Actif", + "inactive": "Inactif", + "error": "Erreur" + } + }, "weatherAlerts": { "title": "Alertes météo", "activeAlerts": "Alertes actives", @@ -1270,251 +1534,5 @@ }, "stats_label": "Alertes météo" }, - "videoFeeds": { - "title": "Flux vidéo", - "noFeeds": "Aucun flux vidéo pour cet appel", - "addFeed": "Ajouter un flux vidéo", - "editFeed": "Modifier le flux vidéo", - "deleteFeed": "Supprimer le flux vidéo", - "deleteConfirm": "Êtes-vous sûr de vouloir supprimer ce flux vidéo ?", - "watch": "Regarder", - "goLive": "Diffuser en direct", - "stopLive": "Arrêter la diffusion", - "flipCamera": "Inverser la caméra", - "feedAdded": "Flux vidéo ajouté", - "feedUpdated": "Flux vidéo mis à jour", - "feedDeleted": "Flux vidéo supprimé", - "feedError": "Échec du chargement du flux vidéo", - "unsupportedFormat": "Ce format de flux n'est pas pris en charge sur mobile", - "copyUrl": "Copier l'URL", - "form": { - "name": "Nom du flux", - "namePlaceholder": "ex. : Drone Véhicule 1", - "url": "URL du flux", - "urlPlaceholder": "ex. : https://stream.example.com/live.m3u8", - "feedType": "Type de caméra", - "feedFormat": "Format du flux", - "description": "Description", - "descriptionPlaceholder": "Description optionnelle", - "status": "Statut", - "sortOrder": "Ordre de tri", - "cameraLocation": "Emplacement de la caméra", - "useCurrentLocation": "Utiliser la position actuelle" - }, - "type": { - "drone": "Drone", - "fixedCamera": "Caméra fixe", - "bodyCam": "Caméra corporelle", - "trafficCam": "Caméra de circulation", - "weatherCam": "Caméra météo", - "satelliteFeed": "Flux satellite", - "webCam": "Webcam", - "other": "Autre" - }, - "format": { - "rtsp": "RTSP", - "hls": "HLS", - "mjpeg": "MJPEG", - "youtubeLive": "YouTube Live", - "webrtc": "WebRTC", - "dash": "DASH", - "embed": "Intégré", - "other": "Autre" - }, - "status": { - "active": "Actif", - "inactive": "Inactif", - "error": "Erreur" - } - }, - "welcome": "Bienvenue sur le site de l'application obytes", - "incident_command": { - "tab_title": "Commandement", - "title": "Commandement de l’intervention", - "open_full_board": "Ouvrir le tableau complet", - "no_command": "Aucun commandement d’intervention établi", - "no_command_description": "Établissez un commandement d’intervention pour coordonner les ressources, les rôles, les objectifs et le suivi du personnel pour cet appel.", - "establish": "Établir le commandement", - "establish_title": "Établir le commandement de l’intervention", - "establish_description": "Initialisez éventuellement le tableau de commandement à partir d’un modèle.", - "establish_success": "Commandement de l’intervention établi", - "establish_error": "Échec de l’établissement du commandement", - "template": "Modèle", - "no_template": "Aucun modèle (tableau vide)", - "saved": "Enregistré", - "save_error": "Échec de l’opération", - "edit_action_plan": "Modifier le plan d’action", - "action_plan": "Plan d’action", - "action_plan_placeholder": "Décrivez le plan d’action de l’intervention...", - "save": "Enregistrer", - "no_action_plan": "Aucun plan d’action défini.", - "add": "Ajouter", - "add_objective": "Ajouter un objectif", - "objective_name": "Objectif", - "objective_type": "Type", - "name_required": "Le nom est obligatoire", - "add_lane": "Ajouter un secteur", - "lane_name": "Nom du secteur", - "lane_type": "Type de secteur", - "assign_resource": "Affecter une ressource", - "assign_resource_required": "Sélectionnez un secteur et une ressource", - "lane": "Secteur", - "select_lane": "Sélectionnez un secteur", - "resource_type": "Type de ressource", - "resource": "Ressource", - "select_resource": "Sélectionnez une ressource", - "unit": "Unité", - "personnel": "Personnel", - "assign": "Affecter", - "assign_role": "Affecter un rôle", - "assign_role_required": "Sélectionnez une personne et un rôle", - "person": "Personne", - "select_person": "Sélectionnez une personne", - "role": "Rôle", - "select_role": "Sélectionnez un rôle", - "transfer_title": "Transférer le commandement", - "transfer_notes": "Notes", - "transfer": "Transférer", - "transfer_command": "Transférer", - "transfer_success": "Commandement transféré", - "close_command": "Clore le commandement", - "confirm_close": "Clore le commandement de cette intervention ?", - "status": "État", - "active": "Actif", - "closed": "Clos", - "commander": "Commandant", - "established_on": "Établi", - "edit": "Modifier", - "roles": "Rôles de commandement", - "no_roles": "Aucun rôle attribué.", - "structure": "Structure de commandement", - "no_lanes": "Aucun secteur défini.", - "no_resources": "Aucune ressource affectée.", - "release": "Libérer", - "objectives": "Objectifs", - "no_objectives": "Aucun objectif.", - "complete": "Terminer", - "completed": "Terminé", - "timers": "Minuteurs", - "no_timers": "Aucun minuteur en cours.", - "acknowledge": "Confirmer", - "due": "Échéance", - "accountability": "Suivi du personnel (PAR)", - "run_par": "Lancer le PAR", - "green": "Vert", - "warning": "Avertissement", - "critical": "Critique", - "no_accountability": "Aucun personnel suivi.", - "timeline": "Chronologie du commandement", - "no_timeline": "Aucune entrée dans la chronologie.", - "unassigned": "Non affecté", - "active_title": "Commandements d’intervention actifs", - "no_active": "Aucun commandement d’intervention actif", - "no_active_description": "Les commandements établis pour les interventions apparaîtront ici.", - "call": "Intervention", - "tactical_map": "Carte tactique", - "annotations": "Annotations de la carte", - "no_annotations": "Aucune annotation.", - "open_tactical_map": "Ouvrir la carte tactique", - "marker": "Repère", - "add_marker": "Ajouter un repère", - "tap_to_place": "Touchez la carte pour placer un repère", - "marker_label": "Libellé du repère", - "delete_annotation_confirm": "Supprimer cette annotation ?", - "voice_channels": "Canaux vocaux", - "no_channels": "Aucun canal ouvert.", - "add_channel": "Ajouter un canal", - "channel_name": "Nom du canal", - "close_all_channels": "Fermer tous les canaux", - "join": "Rejoindre", - "hold_to_talk": "Maintenir pour parler", - "talking": "Transmission...", - "voice_joined": "Canal vocal rejoint", - "voice_join_error": "Impossible de rejoindre le canal vocal", - "move": "Déplacer", - "move_lane": "Déplacer le secteur", - "parent_lane": "Secteur parent", - "top_level": "Niveau supérieur", - "move_resource": "Déplacer la ressource" - }, - "chat": { - "title": "Chat", - "assistant": "Assistant", - "empty": "Aucune conversation pour le moment. Démarrez un message direct ou créez un groupe.", - "section_assistant": "Assistant", - "section_direct_messages": "Messages directs", - "section_channels": "Canaux", - "section_incidents": "Incidents", - "new_direct_message": "Nouveau message direct", - "new_group": "Nouveau groupe", - "open_assistant": "Ouvrir l'assistant", - "create_conversation_failed": "Impossible de démarrer la conversation", - "group_name": "Nom du groupe", - "search_people": "Rechercher des personnes", - "no_people": "Aucune personne trouvée", - "create_group_with": "Créer un groupe ({{count}})", - "message_deleted": "Ce message a été supprimé", - "urgent": "Urgent", - "urgent_will_send": "Ce message sera envoyé comme urgent", - "shared_location": "Position partagée", - "thread_replies": "{{count}} réponses", - "edited": "(modifié)", - "failed_tap_retry": "Échec – appuyez pour réessayer", - "type_a_message": "Saisir un message", - "emoji": "Emoji", - "add_image": "Ajouter une image", - "add_gif": "Ajouter un GIF", - "share_location": "Partager la position", - "send": "Envoyer", - "someone": "Quelqu'un", - "is_typing": "{{name}} est en train d'écrire...", - "are_typing": "{{count}} personnes sont en train d'écrire...", - "permission_photos_denied": "Autorisation d'accès à la photothèque refusée", - "permission_location_denied": "Autorisation de localisation refusée", - "search_gifs": "Rechercher des GIF", - "no_gifs": "Aucun GIF trouvé", - "flag_reason": "Pourquoi le signalez-vous ?", - "flag_inappropriate": "Inapproprié", - "flag_harassment": "Harcèlement", - "flag_spam": "Spam", - "flag_sensitive": "Informations sensibles", - "flag_policy": "Violation de la politique", - "flag_other": "Autre", - "reply_in_thread": "Répondre dans le fil", - "copy": "Copier", - "copied": "Copié", - "copy_unavailable": "La copie n'est pas disponible sur cet appareil", - "edit": "Modifier", - "edit_message": "Modifier le message", - "save": "Enregistrer", - "delete": "Supprimer", - "pin": "Épingler", - "unpin": "Désépingler", - "flag": "Signaler", - "moderator_delete": "Supprimer (modérateur)", - "moderator_removed": "Supprimé par le modérateur", - "attachment_failed": "Échec de l'envoi de la pièce jointe", - "ack_required": "Accusé de réception requis", - "ack_pending_one": "Vous avez un message urgent à accuser réception", - "ack_pending_count": "Vous avez {{count}} messages urgents à accuser réception", - "acknowledge": "Accuser réception", - "thread": "Fil", - "original_message": "Message d'origine", - "reply_placeholder": "Répondre...", - "channel": "Canal", - "direct_message": "Message direct", - "load_people_failed": "Impossible de charger les personnes", - "reaction_failed": "Impossible de mettre à jour la réaction", - "edit_failed": "Impossible de modifier le message", - "delete_failed": "Impossible de supprimer le message", - "pin_failed": "Impossible de mettre à jour l'épinglage", - "flag_failed": "Impossible de signaler le message" - }, - "chatbot": { - "title": "Assistant", - "subtitle": "Assistant IA pour votre service", - "new_session": "Nouvelle session", - "empty": "Posez n'importe quelle question à l'assistant pour commencer.", - "ask_placeholder": "Demandez à l'assistant..." - } + "welcome": "Bienvenue sur le site de l'application obytes" } diff --git a/src/translations/it.json b/src/translations/it.json index 8c55a960..50c048a5 100644 --- a/src/translations/it.json +++ b/src/translations/it.json @@ -371,6 +371,124 @@ "audio_name": "Clip audio" } }, + "chat": { + "title": "Chat", + "assistant": "Assistente", + "empty": "Ancora nessuna conversazione. Avvia un messaggio diretto o crea un gruppo.", + "section_assistant": "Assistente", + "section_direct_messages": "Messaggi diretti", + "section_channels": "Canali", + "section_incidents": "Incidenti", + "new_direct_message": "Nuovo messaggio diretto", + "new_group": "Nuovo gruppo", + "open_assistant": "Apri assistente", + "create_conversation_failed": "Impossibile avviare la conversazione", + "group_name": "Nome del gruppo", + "search_people": "Cerca persone", + "no_people": "Nessuna persona trovata", + "create_group_with": "Crea gruppo ({{count}})", + "message_deleted": "Questo messaggio è stato eliminato", + "urgent": "Urgente", + "urgent_will_send": "Questo messaggio verrà inviato come urgente", + "shared_location": "Posizione condivisa", + "thread_replies": "{{count}} risposte", + "edited": "(modificato)", + "failed_tap_retry": "Non riuscito - tocca per riprovare", + "type_a_message": "Scrivi un messaggio", + "emoji": "Emoji", + "add_image": "Aggiungi immagine", + "add_gif": "Aggiungi GIF", + "share_location": "Condividi posizione", + "send": "Invia", + "someone": "Qualcuno", + "is_typing": "{{name}} sta scrivendo...", + "are_typing": "{{count}} persone stanno scrivendo...", + "permission_photos_denied": "Autorizzazione alla libreria foto negata", + "permission_location_denied": "Autorizzazione alla posizione negata", + "search_gifs": "Cerca GIF", + "no_gifs": "Nessun GIF trovato", + "flag_reason": "Perché lo segnali?", + "flag_inappropriate": "Inappropriato", + "flag_harassment": "Molestie", + "flag_spam": "Spam", + "flag_sensitive": "Informazioni sensibili", + "flag_policy": "Violazione delle regole", + "flag_other": "Altro", + "reply_in_thread": "Rispondi nel thread", + "copy": "Copia", + "copied": "Copiato", + "copy_unavailable": "La copia non è disponibile su questo dispositivo", + "edit": "Modifica", + "edit_message": "Modifica messaggio", + "save": "Salva", + "delete": "Elimina", + "pin": "Fissa", + "unpin": "Rimuovi fissaggio", + "flag": "Segnala", + "moderator_delete": "Rimuovi (moderatore)", + "moderator_removed": "Rimosso dal moderatore", + "attachment_failed": "Caricamento allegato non riuscito", + "ack_required": "Conferma richiesta", + "ack_pending_one": "Hai un messaggio urgente da confermare", + "ack_pending_count": "Hai {{count}} messaggi urgenti da confermare", + "acknowledge": "Conferma", + "thread": "Thread", + "original_message": "Messaggio originale", + "reply_placeholder": "Rispondi...", + "channel": "Canale", + "direct_message": "Messaggio diretto", + "load_people_failed": "Impossibile caricare le persone", + "reaction_failed": "Impossibile aggiornare la reazione", + "edit_failed": "Impossibile modificare il messaggio", + "delete_failed": "Impossibile eliminare il messaggio", + "pin_failed": "Impossibile aggiornare il messaggio fissato", + "flag_failed": "Impossibile segnalare il messaggio" + }, + "chatbot": { + "title": "Assistente", + "subtitle": "Assistente IA per il tuo reparto", + "new_session": "Nuova sessione", + "empty": "Chiedi qualsiasi cosa all'assistente per iniziare.", + "ask_placeholder": "Chiedi all'assistente..." + }, + "check_in": { + "tab_title": "Check-in", + "timer_status": "Stato timer", + "perform_check_in": "Check-in", + "check_in_success": "Check-in registrato con successo", + "check_in_error": "Impossibile registrare il check-in", + "checked_in_by": "da {{name}}", + "last_check_in": "Ultimo check-in", + "elapsed": "Trascorso", + "duration": "Durata", + "status_ok": "OK", + "status_green": "OK", + "status_warning": "Attenzione", + "status_yellow": "Attenzione", + "status_overdue": "Scaduto", + "status_red": "Scaduto", + "status_critical": "Critico", + "history": "Cronologia check-in", + "no_timers": "Nessun timer di check-in configurato", + "timers_disabled": "I timer di check-in sono disabilitati per questo intervento", + "type_personnel": "Personale", + "type_unit": "Unità", + "type_ic": "Comandante dell'incidente", + "type_par": "PAR", + "type_hazmat": "Esposizione Hazmat", + "type_sector_rotation": "Rotazione settore", + "type_rehab": "Riabilitazione", + "add_note": "Aggiungi nota (facoltativo)", + "confirm": "Conferma check-in", + "minutes_ago": "{{count}} min fa", + "select_target": "Seleziona entità per il check-in", + "overdue_count": "{{count}} scaduti", + "warning_count": "{{count}} in attenzione", + "enable_timers": "Abilita timer", + "disable_timers": "Disabilita timer", + "summary": "{{overdue}} scaduti, {{warning}} in attenzione, {{ok}} ok", + "par_title": "Controllo del personale (PAR)" + }, "common": { "add": "Aggiungi", "back": "Indietro", @@ -502,178 +620,379 @@ "website": "Sito web", "zip": "CAP" }, - "form": { - "invalid_url": "Inserisci un URL valido che inizi con http:// o https://", - "required": "Questo campo è obbligatorio" - }, - "livekit": { - "audio_devices": "Dispositivi audio", - "audio_settings": "Impostazioni audio", - "connected_to_room": "Connesso al canale", - "connecting": "Connessione...", - "disconnect": "Disconnetti", - "join": "Entra", - "microphone": "Microfono", - "mute": "Silenzia", - "no_rooms_available": "Nessun canale vocale disponibile", - "speaker": "Altoparlante", - "speaking": "In conversazione", - "title": "Canali vocali", - "unmute": "Riattiva audio" - }, - "loading": { - "loading": "Caricamento...", - "loadingData": "Caricamento dati...", - "pleaseWait": "Attendere prego", - "processingRequest": "Elaborazione della richiesta..." - }, - "sso": { - "authenticating": "Autenticazione...", - "back_to_login": "Torna al login", - "back_to_lookup": "Cambia utente", - "continue_button": "Continua", - "department_id_label": "ID dipartimento", - "department_id_placeholder": "Inserisci l'ID del dipartimento", - "error_generic": "Accesso fallito. Riprova.", - "error_oidc_cancelled": "L'accesso è stato annullato.", - "error_oidc_not_ready": "Il provider SSO si sta caricando, attendere prego.", - "error_sso_not_enabled": "L'accesso unico non è abilitato per questo utente.", - "error_token_exchange": "Impossibile completare l'accesso. Riprova.", - "error_user_not_found": "Utente non trovato. Verifica e riprova.", - "looking_up": "Ricerca in corso...", - "optional": "facoltativo", - "page_subtitle": "Inserisci il tuo nome utente per cercare le opzioni di accesso della tua organizzazione.", - "page_title": "Accesso singolo", - "provider_oidc": "OpenID Connect (OIDC)", - "provider_saml": "SAML 2.0", - "sign_in_button": "Accedi con SSO", - "sign_in_title": "Accedi", - "sso_button": "Accesso SSO" - }, - "login": { - "branding_subtitle": "Software di centrale operativa potente per vigili del fuoco, soccorso e organizzazioni di sicurezza pubblica.", - "branding_title": "Gestione delle emergenze", - "errorModal": { - "confirmButton": "OK", - "message": "Verifica nome utente e password e riprova.", - "title": "Accesso fallito" - }, - "feature_dispatch_desc": "Invia istantaneamente le unità e gestisci gli interventi con aggiornamenti in tempo reale su tutti i dispositivi.", - "feature_dispatch_title": "Centrale operativa in tempo reale", - "feature_mapping_desc": "Traccia le unità in tempo reale con mappe dettagliate, percorsi e gestione della posizione.", - "feature_mapping_title": "Cartografia avanzata", - "feature_personnel_desc": "Gestisci il tuo team con accesso basato sui ruoli, monitoraggio dello stato e strumenti di comunicazione.", - "feature_personnel_title": "Gestione personale", - "footer_text": "Creato con ❤️ a Lake Tahoe", - "login": "Accedi", - "login_button": "Accedi", - "login_button_description": "Accedi al tuo account per continuare", - "login_button_error": "Errore durante l'accesso", - "login_button_loading": "Accesso in corso...", - "login_button_success": "Accesso effettuato con successo", - "no_account": "Non hai un account?", - "page_subtitle": "Inserisci le tue credenziali per accedere.", - "page_title": "Resgrid Dispatch", - "password": "Password", - "password_incorrect": "Password errata", - "password_placeholder": "Inserisci la tua password", - "register": "Registrati", - "title": "Accesso", - "username": "Nome utente", - "username_placeholder": "Inserisci il tuo nome utente", - "welcome_title": "Bentornato" - }, - "lockscreen": { - "message": "Inserisci la tua password per sbloccare lo schermo", - "not_you": "Non sei tu? Torna al login", - "password": "Password", - "password_placeholder": "Inserisci la tua password", - "title": "Schermata di blocco", - "unlock_button": "Sblocca", - "unlock_failed": "Sblocco fallito. Riprova.", - "unlocking": "Sblocco in corso...", - "welcome_back": "Bentornato", - "relogin_required": "La verifica della password non è disponibile per questa sessione. Effettua nuovamente l'accesso." - }, - "maintenance": { - "downtime_message": "Stiamo lavorando per completare la manutenzione il prima possibile. Ricontrolla presto.", - "downtime_title": "Qual è il tempo di inattività?", - "message": "Ricontrolla tra qualche momento.", - "support_message": "Se hai bisogno di assistenza, contattaci a", - "support_title": "Hai bisogno di supporto?", - "title": "Sito in manutenzione", - "why_down_message": "Stiamo effettuando una manutenzione programmata per migliorare la tua esperienza. Ci scusiamo per l'inconveniente.", - "why_down_title": "Perché il sito è offline?" - }, - "menu": { - "scheduled_calls": "Chiamate programmate", - "pois": "POI", - "calls": "Interventi", - "calls_list": "Lista interventi", - "contacts": "Contatti", - "home": "Home", - "map": "Mappa", - "menu": "Menu", - "messages": "Messaggi", - "new_call": "Nuovo intervento", - "personnel": "Personale", - "protocols": "Protocolli", - "settings": "Impostazioni", + "dispatch": { + "active_calls": "Interventi attivi", + "pending_calls": "In attesa", + "scheduled_calls": "Programmati", + "units_available": "Disponibili", + "personnel_available": "Disponibili", + "personnel_on_duty": "In servizio", "units": "Unità", - "weatherAlerts": "Allerte meteo", - "incident_command": "Comando dell’intervento", - "chat": "Chat", - "assistant": "Assistente" - }, - "pois": { - "address": "Indirizzo", - "all_types": "Tutti i tipi", - "destination": "Destinazione", - "details": "Dettagli", - "detail_not_found": "POI non trovato", - "detail_not_found_description": "Impossibile caricare il POI richiesto.", - "detail_title": "Dettagli del POI", - "empty": "Nessun POI trovato", - "empty_description": "Non sono ancora disponibili punti di interesse per il reparto.", - "empty_filtered": "Nessun POI corrispondente", - "empty_filtered_description": "Prova a cancellare la ricerca o a scegliere un altro tipo di POI.", - "filter_by_type": "Filtra per tipo di POI", - "invalid_poi": "POI non valido", - "invalid_poi_description": "L’identificativo del POI selezionato non è valido.", - "loading": "Caricamento dei POI...", - "loading_detail": "Caricamento dei dettagli del POI...", + "personnel": "Personale", "map": "Mappa", - "no_location": "Nessuna posizione disponibile", - "no_location_description": "Questo POI non dispone di coordinate utilizzabili.", - "no_location_for_routing": "Nessun dato di posizione disponibile per il percorso", - "note": "Nota", - "route_error": "Impossibile aprire l’applicazione delle mappe", - "search": "Cerca POI...", - "sort": "Ordina", - "sort_options": { - "address-asc": "Indirizzo", - "name-asc": "Nome (A–Z)", - "name-desc": "Nome (Z–A)", - "type-asc": "Tipo" + "notes": "Note", + "activity_log": "Registro attività", + "communications": "Comunicazioni", + "no_active_calls": "Nessun intervento attivo", + "no_units": "Nessuna unità disponibile", + "no_personnel": "Nessun personale disponibile", + "no_notes": "Nessuna nota disponibile", + "no_activity": "Nessuna attività recente", + "current_channel": "Canale attuale", + "audio_stream": "Flusso audio", + "no_stream": "Nessun flusso attivo", + "ptt": "PTT", + "ptt_start": "Inizio PTT", + "ptt_end": "Fine PTT", + "transmitting_on": "Trasmissione su {{channel}}", + "transmission_ended": "Trasmissione terminata", + "voice_disabled": "Voce disabilitata", + "disconnected": "Disconnesso", + "select_channel": "Seleziona canale", + "select_channel_description": "Scegli un canale vocale a cui connettersi", + "change_channel_warning": "Selezionare un nuovo canale disconnetterà dal canale attuale", + "default_channel": "Predefinito", + "no_channels_available": "Nessun canale vocale disponibile", + "system_update": "Aggiornamento di sistema", + "data_refreshed": "Dati aggiornati dal server", + "call_selected": "Intervento selezionato", + "unit_selected": "Unità selezionata", + "unit_deselected": "Unità deselezionata", + "personnel_selected": "Personale selezionato", + "personnel_deselected": "Personale deselezionato", + "loading_map": "Caricamento mappa...", + "map_not_available_web": "Mappa non disponibile sulla piattaforma web", + "filtering_by_call": "Filtro per intervento", + "clear_filter": "Cancella filtro", + "call_filter_active": "Filtro intervento attivo", + "call_filter_cleared": "Filtro intervento cancellato", + "showing_all_data": "Visualizzazione di tutti i dati", + "call_notes": "Note intervento", + "no_call_notes": "Nessuna nota intervento", + "add_call_note_placeholder": "Aggiungi una nota...", + "note_added": "Nota aggiunta", + "note_added_to_console": "Una nuova nota è stata aggiunta alla console", + "add_note_title": "Aggiungi nuova nota", + "note_title_label": "Titolo", + "note_title_placeholder": "Inserisci il titolo della nota...", + "note_category_label": "Categoria", + "note_category_placeholder": "Seleziona una categoria", + "note_no_category": "Nessuna categoria", + "note_body_label": "Contenuto nota", + "note_body_placeholder": "Inserisci il contenuto della nota...", + "note_save_error": "Impossibile salvare la nota: {{error}}", + "note_created": "Nota creata", + "units_on_call": "Unità in servizio", + "no_units_on_call": "Nessuna unità in servizio", + "personnel_on_call": "Personale in servizio", + "no_personnel_on_call": "Nessun personale in servizio", + "call_activity": "Attività intervento", + "no_call_activity": "Nessuna attività intervento", + "on_call": "In servizio", + "filtered": "Filtrato", + "active_filter": "Filtro attivo", + "unit_status_change": "Cambio stato unità", + "personnel_status_change": "Cambio stato personale", + "view_call_details": "Visualizza dettagli intervento", + "dispatched_resources": "Inviati", + "unassigned": "Non assegnato", + "available": "Disponibile", + "unknown": "Sconosciuto", + "search_personnel_placeholder": "Cerca personale...", + "search_calls_placeholder": "Cerca interventi...", + "search_units_placeholder": "Cerca unità...", + "search_notes_placeholder": "Cerca note...", + "signalr_update": "Aggiornamento in tempo reale", + "signalr_connected": "Connesso", + "realtime_updates_active": "Aggiornamenti in tempo reale attivi", + "personnel_status_updated": "Stato personale aggiornato", + "personnel_staffing_updated": "Organico personale aggiornato", + "unit_status_updated": "Stato unità aggiornato", + "calls_updated": "Interventi aggiornati", + "call_added": "Nuovo intervento aggiunto", + "call_closed": "Intervento chiuso", + "check_ins": "Check-in", + "no_check_ins": "Nessun intervento con timer di check-in", + "radio_log": "Registro radio", + "radio": "Radio", + "activity": "Attività", + "actions": "Azioni", + "no_radio_activity": "Nessuna trasmissione radio", + "live": "LIVE", + "currently_transmitting": "Trasmissione in corso...", + "duration": "Durata", + "call_actions": "Azioni intervento", + "unit_actions": "Azioni unità", + "personnel_actions": { + "title": "Azioni personale", + "status_tab": "Stato", + "staffing_tab": "Organico", + "select_status": "Seleziona stato", + "select_staffing": "Seleziona livello organico", + "destination": "Destinazione", + "no_destination": "Nessuna destinazione", + "note": "Nota", + "note_placeholder": "Aggiungi una nota facoltativa...", + "update_status": "Aggiorna stato", + "update_staffing": "Aggiorna organico", + "no_statuses_available": "Nessuno stato disponibile", + "no_staffings_available": "Nessun livello di organico disponibile" }, - "title": "POI", - "type": "Tipo", - "unknown_type": "Tipo sconosciuto", - "unnamed": "POI senza nome" + "unit_actions_panel": { + "status": "Stato", + "select_status": "Seleziona stato", + "destination": "Destinazione", + "no_destination": "Nessuna destinazione", + "note": "Nota", + "note_placeholder": "Aggiungi una nota facoltativa...", + "update_status": "Aggiorna stato", + "no_statuses_available": "Nessuno stato disponibile", + "no_active_calls": "Nessun intervento attivo", + "no_stations_available": "Nessuna stazione disponibile", + "no_destinations_available": "Nessuna destinazione disponibile" + }, + "call": "Intervento", + "station": "Stazione", + "calls": "Interventi", + "stations": "Stazioni", + "no_stations_available": "Nessuna stazione disponibile", + "new_call": "Nuovo intervento", + "view_details": "Dettagli", + "add_note": "Aggiungi nota", + "close_call": "Chiudi", + "set_status": "Imposta stato", + "set_staffing": "Organico", + "dispatch": "Invia", + "select_items_for_actions": "Seleziona un intervento, un'unità o personale per abilitare le azioni contestuali", + "weather": { + "clear": "Sereno", + "mainly_clear": "Prevalentemente sereno", + "partly_cloudy": "Parzialmente nuvoloso", + "overcast": "Coperto", + "fog": "Nebbia", + "drizzle": "Pioggerella", + "freezing_drizzle": "Pioggerella gelata", + "rain": "Pioggia", + "freezing_rain": "Pioggia gelata", + "snow": "Neve", + "rain_showers": "Rovesci di pioggia", + "snow_showers": "Rovesci di neve", + "thunderstorm": "Temporale", + "thunderstorm_hail": "Temporale con grandine", + "unknown": "Sconosciuto" + }, + "available_only": "Solo disponibili", + "single_list": "Elenco singolo", + "resources": "Risorse", + "search_resources_placeholder": "Cerca risorse...", + "no_resources": "Nessuna risorsa" }, - "scheduled_calls": { - "title": "Chiamate programmate", - "loading": "Caricamento delle chiamate programmate...", - "no_scheduled_calls": "Nessuna chiamata programmata", - "no_scheduled_calls_description": "Al momento non ci sono chiamate programmate in attesa.", - "search": "Cerca chiamate programmate...", - "scheduled_for": "Programmata per", - "table_number": "Chiamata n.", - "table_name": "Nome", - "table_type": "Tipo", - "table_priority": "Priorità", - "table_address": "Indirizzo", - "table_scheduled": "Programmata per" + "form": { + "invalid_url": "Inserisci un URL valido che inizi con http:// o https://", + "required": "Questo campo è obbligatorio" + }, + "incident_command": { + "accountability": "Controllo del personale (PAR)", + "acknowledge": "Conferma", + "action_plan": "Piano d’azione", + "action_plan_placeholder": "Descrivi il piano d’azione dell’intervento...", + "active": "Attivo", + "active_title": "Comandi degli interventi attivi", + "add": "Aggiungi", + "add_channel": "Aggiungi canale", + "add_lane": "Aggiungi settore", + "add_marker": "Aggiungi indicatore", + "add_objective": "Aggiungi obiettivo", + "annotations": "Annotazioni della mappa", + "assign": "Assegna", + "assign_resource": "Assegna risorsa", + "assign_resource_required": "Seleziona un settore e una risorsa", + "assign_role": "Assegna ruolo", + "assign_role_required": "Seleziona una persona e un ruolo", + "call": "Chiamata", + "channel_name": "Nome del canale", + "chat": "Chat", + "chat_frozen": "This incident is closed. Conversations are kept as a point-in-time record — no new messages.", + "close_all_channels": "Chiudi tutti i canali", + "close_command": "Chiudi comando", + "closed": "Chiuso", + "command_channel": "Command chat", + "command_channel_hint": "Command staff and dispatch", + "command_channel_unavailable": "No command channel has been created for this incident yet.", + "commander": "Comandante", + "complete": "Completa", + "completed": "Completato", + "confirm_close": "Chiudere il comando dell’intervento per questa chiamata?", + "critical": "Critico", + "delete_annotation_confirm": "Rimuovere questa annotazione?", + "dispatch_channel": "Dispatch", + "dispatch_channel_hint": "The incident's line to the desk", + "dispatch_channel_unavailable": "No dispatch channel has been created for this incident yet.", + "dm_failed": "Couldn't open that conversation.", + "dm_unavailable": "That contact has no Resgrid account to message.", + "due": "Scadenza", + "edit": "Modifica", + "edit_action_plan": "Modifica piano d’azione", + "establish": "Stabilisci comando", + "establish_description": "Facoltativamente, inizializza il pannello di comando da un modello.", + "establish_error": "Impossibile stabilire il comando", + "establish_success": "Comando dell’intervento stabilito", + "establish_title": "Stabilisci comando dell’intervento", + "established_on": "Stabilito", + "green": "Verde", + "hold_to_talk": "Tieni premuto per parlare", + "incident_channel": "Incident chat", + "incident_channel_hint": "Everyone working the incident", + "incident_channel_unavailable": "No incident channel has been created for this call yet.", + "join": "Partecipa", + "lane": "Settore", + "lane_name": "Nome del settore", + "lane_type": "Tipo di settore", + "marker": "Indicatore", + "marker_label": "Etichetta dell’indicatore", + "move": "Sposta", + "move_lane": "Sposta settore", + "move_resource": "Sposta risorsa", + "name_required": "Il nome è obbligatorio", + "no_accountability": "Nessun personale monitorato.", + "no_action_plan": "Nessun piano d’azione impostato.", + "no_active": "Nessun comando dell’intervento attivo", + "no_active_description": "I comandi stabiliti per le chiamate verranno visualizzati qui.", + "no_annotations": "Nessuna annotazione.", + "no_channels": "Nessun canale aperto.", + "no_command": "Nessun comando dell’intervento stabilito", + "no_command_description": "Stabilisci il comando dell’intervento per coordinare risorse, ruoli, obiettivi e controllo del personale per questa chiamata.", + "no_lanes": "Nessun settore definito.", + "no_objectives": "Nessun obiettivo.", + "no_resources": "Nessuna risorsa assegnata.", + "no_roles": "Nessun ruolo assegnato.", + "no_template": "Nessun modello (pannello vuoto)", + "no_timeline": "Nessuna voce nella cronologia.", + "no_timers": "Nessun timer in esecuzione.", + "not_authorized": "Command board unavailable", + "not_authorized_description": "Your department hasn't authorized you to work incident command. Ask an administrator for the Command App Login permission.", + "objective_name": "Obiettivo", + "objective_type": "Tipo", + "objectives": "Obiettivi", + "open_chat": "Open", + "open_full_board": "Apri pannello completo", + "open_tactical_map": "Apri mappa tattica", + "parent_lane": "Settore principale", + "person": "Persona", + "personnel": "Personale", + "release": "Rilascia", + "resource": "Risorsa", + "resource_type": "Tipo di risorsa", + "role": "Ruolo", + "roles": "Ruoli di comando", + "run_par": "Esegui PAR", + "save": "Salva", + "save_error": "Operazione non riuscita", + "saved": "Salvato", + "select_lane": "Seleziona un settore", + "select_person": "Seleziona una persona", + "select_resource": "Seleziona una risorsa", + "select_role": "Seleziona un ruolo", + "send_message": "Message", + "status": "Stato", + "structure": "Struttura di comando", + "tab_title": "Comando", + "tactical_map": "Mappa tattica", + "talking": "Trasmissione...", + "tap_to_place": "Tocca la mappa per posizionare un indicatore", + "template": "Modello", + "timeline": "Cronologia del comando", + "timers": "Timer", + "title": "Comando dell’intervento", + "top_level": "Livello superiore", + "transfer": "Trasferisci", + "transfer_command": "Trasferisci", + "transfer_notes": "Note", + "transfer_success": "Comando trasferito", + "transfer_title": "Trasferisci comando", + "unassigned": "Non assegnato", + "unit": "Unità", + "voice_channels": "Canali vocali", + "voice_join_error": "Impossibile collegarsi al canale vocale", + "voice_joined": "Canale vocale collegato", + "warning": "Avviso" + }, + "livekit": { + "audio_devices": "Dispositivi audio", + "audio_settings": "Impostazioni audio", + "connected_to_room": "Connesso al canale", + "connecting": "Connessione...", + "disconnect": "Disconnetti", + "join": "Entra", + "microphone": "Microfono", + "mute": "Silenzia", + "no_rooms_available": "Nessun canale vocale disponibile", + "speaker": "Altoparlante", + "speaking": "In conversazione", + "title": "Canali vocali", + "unmute": "Riattiva audio" + }, + "loading": { + "loading": "Caricamento...", + "loadingData": "Caricamento dati...", + "pleaseWait": "Attendere prego", + "processingRequest": "Elaborazione della richiesta..." + }, + "lockscreen": { + "message": "Inserisci la tua password per sbloccare lo schermo", + "not_you": "Non sei tu? Torna al login", + "password": "Password", + "password_placeholder": "Inserisci la tua password", + "title": "Schermata di blocco", + "unlock_button": "Sblocca", + "unlock_failed": "Sblocco fallito. Riprova.", + "unlocking": "Sblocco in corso...", + "welcome_back": "Bentornato", + "relogin_required": "La verifica della password non è disponibile per questa sessione. Effettua nuovamente l'accesso." + }, + "login": { + "branding_subtitle": "Software di centrale operativa potente per vigili del fuoco, soccorso e organizzazioni di sicurezza pubblica.", + "branding_title": "Gestione delle emergenze", + "dispatch_not_authorized": "You are not authorized to use the Dispatch app. Contact your department administrator.", + "errorModal": { + "confirmButton": "OK", + "message": "Verifica nome utente e password e riprova.", + "title": "Accesso fallito" + }, + "feature_dispatch_desc": "Invia istantaneamente le unità e gestisci gli interventi con aggiornamenti in tempo reale su tutti i dispositivi.", + "feature_dispatch_title": "Centrale operativa in tempo reale", + "feature_mapping_desc": "Traccia le unità in tempo reale con mappe dettagliate, percorsi e gestione della posizione.", + "feature_mapping_title": "Cartografia avanzata", + "feature_personnel_desc": "Gestisci il tuo team con accesso basato sui ruoli, monitoraggio dello stato e strumenti di comunicazione.", + "feature_personnel_title": "Gestione personale", + "footer_text": "Creato con ❤️ a Lake Tahoe", + "login": "Accedi", + "login_button": "Accedi", + "login_button_description": "Accedi al tuo account per continuare", + "login_button_error": "Errore durante l'accesso", + "login_button_loading": "Accesso in corso...", + "login_button_success": "Accesso effettuato con successo", + "no_account": "Non hai un account?", + "page_subtitle": "Inserisci le tue credenziali per accedere.", + "page_title": "Resgrid Dispatch", + "password": "Password", + "password_incorrect": "Password errata", + "password_placeholder": "Inserisci la tua password", + "register": "Registrati", + "title": "Accesso", + "username": "Nome utente", + "username_placeholder": "Inserisci il tuo nome utente", + "welcome_title": "Bentornato" + }, + "maintenance": { + "downtime_message": "Stiamo lavorando per completare la manutenzione il prima possibile. Ricontrolla presto.", + "downtime_title": "Qual è il tempo di inattività?", + "message": "Ricontrolla tra qualche momento.", + "support_message": "Se hai bisogno di assistenza, contattaci a", + "support_title": "Hai bisogno di supporto?", + "title": "Sito in manutenzione", + "why_down_message": "Stiamo effettuando una manutenzione programmata per migliorare la tua esperienza. Ci scusiamo per l'inconveniente.", + "why_down_title": "Perché il sito è offline?" }, "map": { "view_poi_details": "Visualizza dettagli del POI", @@ -690,6 +1009,26 @@ "hide_all": "Nascondi tutto", "view_call_details": "Visualizza dettagli intervento" }, + "menu": { + "scheduled_calls": "Chiamate programmate", + "pois": "POI", + "calls": "Interventi", + "calls_list": "Lista interventi", + "contacts": "Contatti", + "home": "Home", + "map": "Mappa", + "menu": "Menu", + "messages": "Messaggi", + "new_call": "Nuovo intervento", + "personnel": "Personale", + "protocols": "Protocolli", + "settings": "Impostazioni", + "units": "Unità", + "weatherAlerts": "Allerte meteo", + "incident_command": "Comando dell’intervento", + "chat": "Chat", + "assistant": "Assistente" + }, "notes": { "actions": { "add": "Aggiungi nota", @@ -709,6 +1048,23 @@ "search": "Cerca note...", "title": "Note" }, + "onboarding": { + "screen1": { + "title": "Resgrid Dispatch", + "description": "Crea, invia e gestisci gli interventi di emergenza con un potente centro di comando mobile a portata di mano" + }, + "screen2": { + "title": "Consapevolezza situazionale in tempo reale", + "description": "Traccia tutte le unità, il personale e le risorse su una mappa interattiva con aggiornamenti di stato in tempo reale e AVL" + }, + "screen3": { + "title": "Coordinamento senza interruzioni", + "description": "Comunica istantaneamente con le unità sul campo, aggiorna lo stato degli interventi e coordina gli sforzi di risposta da qualsiasi luogo" + }, + "skip": "Salta", + "next": "Avanti", + "getStarted": "Iniziamo" + }, "personnel": { "title": "Personale", "search": "Cerca personale...", @@ -741,22 +1097,41 @@ "send_email": "E-mail", "custom_fields": "Informazioni aggiuntive" }, - "onboarding": { - "screen1": { - "title": "Resgrid Dispatch", - "description": "Crea, invia e gestisci gli interventi di emergenza con un potente centro di comando mobile a portata di mano" - }, - "screen2": { - "title": "Consapevolezza situazionale in tempo reale", - "description": "Traccia tutte le unità, il personale e le risorse su una mappa interattiva con aggiornamenti di stato in tempo reale e AVL" - }, - "screen3": { - "title": "Coordinamento senza interruzioni", - "description": "Comunica istantaneamente con le unità sul campo, aggiorna lo stato degli interventi e coordina gli sforzi di risposta da qualsiasi luogo" + "pois": { + "address": "Indirizzo", + "all_types": "Tutti i tipi", + "destination": "Destinazione", + "details": "Dettagli", + "detail_not_found": "POI non trovato", + "detail_not_found_description": "Impossibile caricare il POI richiesto.", + "detail_title": "Dettagli del POI", + "empty": "Nessun POI trovato", + "empty_description": "Non sono ancora disponibili punti di interesse per il reparto.", + "empty_filtered": "Nessun POI corrispondente", + "empty_filtered_description": "Prova a cancellare la ricerca o a scegliere un altro tipo di POI.", + "filter_by_type": "Filtra per tipo di POI", + "invalid_poi": "POI non valido", + "invalid_poi_description": "L’identificativo del POI selezionato non è valido.", + "loading": "Caricamento dei POI...", + "loading_detail": "Caricamento dei dettagli del POI...", + "map": "Mappa", + "no_location": "Nessuna posizione disponibile", + "no_location_description": "Questo POI non dispone di coordinate utilizzabili.", + "no_location_for_routing": "Nessun dato di posizione disponibile per il percorso", + "note": "Nota", + "route_error": "Impossibile aprire l’applicazione delle mappe", + "search": "Cerca POI...", + "sort": "Ordina", + "sort_options": { + "address-asc": "Indirizzo", + "name-asc": "Nome (A–Z)", + "name-desc": "Nome (Z–A)", + "type-asc": "Tipo" }, - "skip": "Salta", - "next": "Avanti", - "getStarted": "Iniziamo" + "title": "POI", + "type": "Tipo", + "unknown_type": "Tipo sconosciuto", + "unnamed": "POI senza nome" }, "protocols": { "details": { @@ -796,6 +1171,20 @@ "tap_to_manage": "Tocca per gestire i ruoli", "unassigned": "Non assegnato" }, + "scheduled_calls": { + "title": "Chiamate programmate", + "loading": "Caricamento delle chiamate programmate...", + "no_scheduled_calls": "Nessuna chiamata programmata", + "no_scheduled_calls_description": "Al momento non ci sono chiamate programmate in attesa.", + "search": "Cerca chiamate programmate...", + "scheduled_for": "Programmata per", + "table_number": "Chiamata n.", + "table_name": "Nome", + "table_type": "Tipo", + "table_priority": "Priorità", + "table_address": "Indirizzo", + "table_scheduled": "Programmata per" + }, "settings": { "about": "Informazioni", "account": "Account", @@ -884,6 +1273,29 @@ "version": "Versione", "website": "Sito web" }, + "sso": { + "authenticating": "Autenticazione...", + "back_to_login": "Torna al login", + "back_to_lookup": "Cambia utente", + "continue_button": "Continua", + "department_id_label": "ID dipartimento", + "department_id_placeholder": "Inserisci l'ID del dipartimento", + "error_generic": "Accesso fallito. Riprova.", + "error_oidc_cancelled": "L'accesso è stato annullato.", + "error_oidc_not_ready": "Il provider SSO si sta caricando, attendere prego.", + "error_sso_not_enabled": "L'accesso unico non è abilitato per questo utente.", + "error_token_exchange": "Impossibile completare l'accesso. Riprova.", + "error_user_not_found": "Utente non trovato. Verifica e riprova.", + "looking_up": "Ricerca in corso...", + "optional": "facoltativo", + "page_subtitle": "Inserisci il tuo nome utente per cercare le opzioni di accesso della tua organizzazione.", + "page_title": "Accesso singolo", + "provider_oidc": "OpenID Connect (OIDC)", + "provider_saml": "SAML 2.0", + "sign_in_button": "Accedi con SSO", + "sign_in_title": "Accedi", + "sso_button": "Accesso SSO" + }, "status": { "all_destinations_enabled": "Può rispondere a chiamate, stazioni o POI", "calls_and_pois_destination_enabled": "Può rispondere a chiamate o POI", @@ -916,173 +1328,6 @@ "stations_tab": "Stazioni", "status_saved_successfully": "Stato salvato con successo!" }, - "dispatch": { - "active_calls": "Interventi attivi", - "pending_calls": "In attesa", - "scheduled_calls": "Programmati", - "units_available": "Disponibili", - "personnel_available": "Disponibili", - "personnel_on_duty": "In servizio", - "units": "Unità", - "personnel": "Personale", - "map": "Mappa", - "notes": "Note", - "activity_log": "Registro attività", - "communications": "Comunicazioni", - "no_active_calls": "Nessun intervento attivo", - "no_units": "Nessuna unità disponibile", - "no_personnel": "Nessun personale disponibile", - "no_notes": "Nessuna nota disponibile", - "no_activity": "Nessuna attività recente", - "current_channel": "Canale attuale", - "audio_stream": "Flusso audio", - "no_stream": "Nessun flusso attivo", - "ptt": "PTT", - "ptt_start": "Inizio PTT", - "ptt_end": "Fine PTT", - "transmitting_on": "Trasmissione su {{channel}}", - "transmission_ended": "Trasmissione terminata", - "voice_disabled": "Voce disabilitata", - "disconnected": "Disconnesso", - "select_channel": "Seleziona canale", - "select_channel_description": "Scegli un canale vocale a cui connettersi", - "change_channel_warning": "Selezionare un nuovo canale disconnetterà dal canale attuale", - "default_channel": "Predefinito", - "no_channels_available": "Nessun canale vocale disponibile", - "system_update": "Aggiornamento di sistema", - "data_refreshed": "Dati aggiornati dal server", - "call_selected": "Intervento selezionato", - "unit_selected": "Unità selezionata", - "unit_deselected": "Unità deselezionata", - "personnel_selected": "Personale selezionato", - "personnel_deselected": "Personale deselezionato", - "loading_map": "Caricamento mappa...", - "map_not_available_web": "Mappa non disponibile sulla piattaforma web", - "filtering_by_call": "Filtro per intervento", - "clear_filter": "Cancella filtro", - "call_filter_active": "Filtro intervento attivo", - "call_filter_cleared": "Filtro intervento cancellato", - "showing_all_data": "Visualizzazione di tutti i dati", - "call_notes": "Note intervento", - "no_call_notes": "Nessuna nota intervento", - "add_call_note_placeholder": "Aggiungi una nota...", - "note_added": "Nota aggiunta", - "note_added_to_console": "Una nuova nota è stata aggiunta alla console", - "add_note_title": "Aggiungi nuova nota", - "note_title_label": "Titolo", - "note_title_placeholder": "Inserisci il titolo della nota...", - "note_category_label": "Categoria", - "note_category_placeholder": "Seleziona una categoria", - "note_no_category": "Nessuna categoria", - "note_body_label": "Contenuto nota", - "note_body_placeholder": "Inserisci il contenuto della nota...", - "note_save_error": "Impossibile salvare la nota: {{error}}", - "note_created": "Nota creata", - "units_on_call": "Unità in servizio", - "no_units_on_call": "Nessuna unità in servizio", - "personnel_on_call": "Personale in servizio", - "no_personnel_on_call": "Nessun personale in servizio", - "call_activity": "Attività intervento", - "no_call_activity": "Nessuna attività intervento", - "on_call": "In servizio", - "filtered": "Filtrato", - "active_filter": "Filtro attivo", - "unit_status_change": "Cambio stato unità", - "personnel_status_change": "Cambio stato personale", - "view_call_details": "Visualizza dettagli intervento", - "dispatched_resources": "Inviati", - "unassigned": "Non assegnato", - "available": "Disponibile", - "unknown": "Sconosciuto", - "search_personnel_placeholder": "Cerca personale...", - "search_calls_placeholder": "Cerca interventi...", - "search_units_placeholder": "Cerca unità...", - "search_notes_placeholder": "Cerca note...", - "signalr_update": "Aggiornamento in tempo reale", - "signalr_connected": "Connesso", - "realtime_updates_active": "Aggiornamenti in tempo reale attivi", - "personnel_status_updated": "Stato personale aggiornato", - "personnel_staffing_updated": "Organico personale aggiornato", - "unit_status_updated": "Stato unità aggiornato", - "calls_updated": "Interventi aggiornati", - "call_added": "Nuovo intervento aggiunto", - "call_closed": "Intervento chiuso", - "check_ins": "Check-in", - "no_check_ins": "Nessun intervento con timer di check-in", - "radio_log": "Registro radio", - "radio": "Radio", - "activity": "Attività", - "actions": "Azioni", - "no_radio_activity": "Nessuna trasmissione radio", - "live": "LIVE", - "currently_transmitting": "Trasmissione in corso...", - "duration": "Durata", - "call_actions": "Azioni intervento", - "unit_actions": "Azioni unità", - "personnel_actions": { - "title": "Azioni personale", - "status_tab": "Stato", - "staffing_tab": "Organico", - "select_status": "Seleziona stato", - "select_staffing": "Seleziona livello organico", - "destination": "Destinazione", - "no_destination": "Nessuna destinazione", - "note": "Nota", - "note_placeholder": "Aggiungi una nota facoltativa...", - "update_status": "Aggiorna stato", - "update_staffing": "Aggiorna organico", - "no_statuses_available": "Nessuno stato disponibile", - "no_staffings_available": "Nessun livello di organico disponibile" - }, - "unit_actions_panel": { - "status": "Stato", - "select_status": "Seleziona stato", - "destination": "Destinazione", - "no_destination": "Nessuna destinazione", - "note": "Nota", - "note_placeholder": "Aggiungi una nota facoltativa...", - "update_status": "Aggiorna stato", - "no_statuses_available": "Nessuno stato disponibile", - "no_active_calls": "Nessun intervento attivo", - "no_stations_available": "Nessuna stazione disponibile", - "no_destinations_available": "Nessuna destinazione disponibile" - }, - "call": "Intervento", - "station": "Stazione", - "calls": "Interventi", - "stations": "Stazioni", - "no_stations_available": "Nessuna stazione disponibile", - "new_call": "Nuovo intervento", - "view_details": "Dettagli", - "add_note": "Aggiungi nota", - "close_call": "Chiudi", - "set_status": "Imposta stato", - "set_staffing": "Organico", - "dispatch": "Invia", - "select_items_for_actions": "Seleziona un intervento, un'unità o personale per abilitare le azioni contestuali", - "weather": { - "clear": "Sereno", - "mainly_clear": "Prevalentemente sereno", - "partly_cloudy": "Parzialmente nuvoloso", - "overcast": "Coperto", - "fog": "Nebbia", - "drizzle": "Pioggerella", - "freezing_drizzle": "Pioggerella gelata", - "rain": "Pioggia", - "freezing_rain": "Pioggia gelata", - "snow": "Neve", - "rain_showers": "Rovesci di pioggia", - "snow_showers": "Rovesci di neve", - "thunderstorm": "Temporale", - "thunderstorm_hail": "Temporale con grandine", - "unknown": "Sconosciuto" - }, - "available_only": "Solo disponibili", - "single_list": "Elenco singolo", - "resources": "Risorse", - "search_resources_placeholder": "Cerca risorse...", - "no_resources": "Nessuna risorsa" - }, "tabs": { "calls": "Interventi", "calendar": "Calendario", @@ -1096,44 +1341,6 @@ "shifts": "Turni", "personnel": "Personale" }, - "check_in": { - "tab_title": "Check-in", - "timer_status": "Stato timer", - "perform_check_in": "Check-in", - "check_in_success": "Check-in registrato con successo", - "check_in_error": "Impossibile registrare il check-in", - "checked_in_by": "da {{name}}", - "last_check_in": "Ultimo check-in", - "elapsed": "Trascorso", - "duration": "Durata", - "status_ok": "OK", - "status_green": "OK", - "status_warning": "Attenzione", - "status_yellow": "Attenzione", - "status_overdue": "Scaduto", - "status_red": "Scaduto", - "status_critical": "Critico", - "history": "Cronologia check-in", - "no_timers": "Nessun timer di check-in configurato", - "timers_disabled": "I timer di check-in sono disabilitati per questo intervento", - "type_personnel": "Personale", - "type_unit": "Unità", - "type_ic": "Comandante dell'incidente", - "type_par": "PAR", - "type_hazmat": "Esposizione Hazmat", - "type_sector_rotation": "Rotazione settore", - "type_rehab": "Riabilitazione", - "add_note": "Aggiungi nota (facoltativo)", - "confirm": "Conferma check-in", - "minutes_ago": "{{count}} min fa", - "select_target": "Seleziona entità per il check-in", - "overdue_count": "{{count}} scaduti", - "warning_count": "{{count}} in attenzione", - "enable_timers": "Abilita timer", - "disable_timers": "Disabilita timer", - "summary": "{{overdue}} scaduti, {{warning}} in attenzione, {{ok}} ok", - "par_title": "Controllo del personale (PAR)" - }, "units": { "search": "Cerca unità...", "loading": "Caricamento delle unità...", @@ -1163,6 +1370,63 @@ "no_destination": "Nessuna", "title": "Unità" }, + "videoFeeds": { + "title": "Feed video", + "noFeeds": "Nessun feed video per questo intervento", + "addFeed": "Aggiungi feed video", + "editFeed": "Modifica feed video", + "deleteFeed": "Elimina feed video", + "deleteConfirm": "Sei sicuro di voler rimuovere questo feed video?", + "watch": "Guarda", + "goLive": "Vai in diretta", + "stopLive": "Interrompi diretta", + "flipCamera": "Inverti fotocamera", + "feedAdded": "Feed video aggiunto", + "feedUpdated": "Feed video aggiornato", + "feedDeleted": "Feed video rimosso", + "feedError": "Impossibile caricare il feed video", + "unsupportedFormat": "Questo formato di streaming non è supportato su dispositivi mobili", + "copyUrl": "Copia URL", + "form": { + "name": "Nome feed", + "namePlaceholder": "es. Drone Autopompa 1", + "url": "URL dello stream", + "urlPlaceholder": "es. https://stream.example.com/live.m3u8", + "feedType": "Tipo di telecamera", + "feedFormat": "Formato stream", + "description": "Descrizione", + "descriptionPlaceholder": "Descrizione facoltativa", + "status": "Stato", + "sortOrder": "Ordine", + "cameraLocation": "Posizione telecamera", + "useCurrentLocation": "Usa posizione attuale" + }, + "type": { + "drone": "Drone", + "fixedCamera": "Telecamera fissa", + "bodyCam": "Body cam", + "trafficCam": "Telecamera traffico", + "weatherCam": "Telecamera meteo", + "satelliteFeed": "Feed satellitare", + "webCam": "Webcam", + "other": "Altro" + }, + "format": { + "rtsp": "RTSP", + "hls": "HLS", + "mjpeg": "MJPEG", + "youtubeLive": "YouTube Live", + "webrtc": "WebRTC", + "dash": "DASH", + "embed": "Incorporato", + "other": "Altro" + }, + "status": { + "active": "Attivo", + "inactive": "Inattivo", + "error": "Errore" + } + }, "weatherAlerts": { "title": "Allerte meteo", "activeAlerts": "Allerte attive", @@ -1270,251 +1534,5 @@ }, "stats_label": "Allerte meteo" }, - "videoFeeds": { - "title": "Feed video", - "noFeeds": "Nessun feed video per questo intervento", - "addFeed": "Aggiungi feed video", - "editFeed": "Modifica feed video", - "deleteFeed": "Elimina feed video", - "deleteConfirm": "Sei sicuro di voler rimuovere questo feed video?", - "watch": "Guarda", - "goLive": "Vai in diretta", - "stopLive": "Interrompi diretta", - "flipCamera": "Inverti fotocamera", - "feedAdded": "Feed video aggiunto", - "feedUpdated": "Feed video aggiornato", - "feedDeleted": "Feed video rimosso", - "feedError": "Impossibile caricare il feed video", - "unsupportedFormat": "Questo formato di streaming non è supportato su dispositivi mobili", - "copyUrl": "Copia URL", - "form": { - "name": "Nome feed", - "namePlaceholder": "es. Drone Autopompa 1", - "url": "URL dello stream", - "urlPlaceholder": "es. https://stream.example.com/live.m3u8", - "feedType": "Tipo di telecamera", - "feedFormat": "Formato stream", - "description": "Descrizione", - "descriptionPlaceholder": "Descrizione facoltativa", - "status": "Stato", - "sortOrder": "Ordine", - "cameraLocation": "Posizione telecamera", - "useCurrentLocation": "Usa posizione attuale" - }, - "type": { - "drone": "Drone", - "fixedCamera": "Telecamera fissa", - "bodyCam": "Body cam", - "trafficCam": "Telecamera traffico", - "weatherCam": "Telecamera meteo", - "satelliteFeed": "Feed satellitare", - "webCam": "Webcam", - "other": "Altro" - }, - "format": { - "rtsp": "RTSP", - "hls": "HLS", - "mjpeg": "MJPEG", - "youtubeLive": "YouTube Live", - "webrtc": "WebRTC", - "dash": "DASH", - "embed": "Incorporato", - "other": "Altro" - }, - "status": { - "active": "Attivo", - "inactive": "Inattivo", - "error": "Errore" - } - }, - "welcome": "Benvenuto nel sito dell'app obytes", - "incident_command": { - "tab_title": "Comando", - "title": "Comando dell’intervento", - "open_full_board": "Apri pannello completo", - "no_command": "Nessun comando dell’intervento stabilito", - "no_command_description": "Stabilisci il comando dell’intervento per coordinare risorse, ruoli, obiettivi e controllo del personale per questa chiamata.", - "establish": "Stabilisci comando", - "establish_title": "Stabilisci comando dell’intervento", - "establish_description": "Facoltativamente, inizializza il pannello di comando da un modello.", - "establish_success": "Comando dell’intervento stabilito", - "establish_error": "Impossibile stabilire il comando", - "template": "Modello", - "no_template": "Nessun modello (pannello vuoto)", - "saved": "Salvato", - "save_error": "Operazione non riuscita", - "edit_action_plan": "Modifica piano d’azione", - "action_plan": "Piano d’azione", - "action_plan_placeholder": "Descrivi il piano d’azione dell’intervento...", - "save": "Salva", - "no_action_plan": "Nessun piano d’azione impostato.", - "add": "Aggiungi", - "add_objective": "Aggiungi obiettivo", - "objective_name": "Obiettivo", - "objective_type": "Tipo", - "name_required": "Il nome è obbligatorio", - "add_lane": "Aggiungi settore", - "lane_name": "Nome del settore", - "lane_type": "Tipo di settore", - "assign_resource": "Assegna risorsa", - "assign_resource_required": "Seleziona un settore e una risorsa", - "lane": "Settore", - "select_lane": "Seleziona un settore", - "resource_type": "Tipo di risorsa", - "resource": "Risorsa", - "select_resource": "Seleziona una risorsa", - "unit": "Unità", - "personnel": "Personale", - "assign": "Assegna", - "assign_role": "Assegna ruolo", - "assign_role_required": "Seleziona una persona e un ruolo", - "person": "Persona", - "select_person": "Seleziona una persona", - "role": "Ruolo", - "select_role": "Seleziona un ruolo", - "transfer_title": "Trasferisci comando", - "transfer_notes": "Note", - "transfer": "Trasferisci", - "transfer_command": "Trasferisci", - "transfer_success": "Comando trasferito", - "close_command": "Chiudi comando", - "confirm_close": "Chiudere il comando dell’intervento per questa chiamata?", - "status": "Stato", - "active": "Attivo", - "closed": "Chiuso", - "commander": "Comandante", - "established_on": "Stabilito", - "edit": "Modifica", - "roles": "Ruoli di comando", - "no_roles": "Nessun ruolo assegnato.", - "structure": "Struttura di comando", - "no_lanes": "Nessun settore definito.", - "no_resources": "Nessuna risorsa assegnata.", - "release": "Rilascia", - "objectives": "Obiettivi", - "no_objectives": "Nessun obiettivo.", - "complete": "Completa", - "completed": "Completato", - "timers": "Timer", - "no_timers": "Nessun timer in esecuzione.", - "acknowledge": "Conferma", - "due": "Scadenza", - "accountability": "Controllo del personale (PAR)", - "run_par": "Esegui PAR", - "green": "Verde", - "warning": "Avviso", - "critical": "Critico", - "no_accountability": "Nessun personale monitorato.", - "timeline": "Cronologia del comando", - "no_timeline": "Nessuna voce nella cronologia.", - "unassigned": "Non assegnato", - "active_title": "Comandi degli interventi attivi", - "no_active": "Nessun comando dell’intervento attivo", - "no_active_description": "I comandi stabiliti per le chiamate verranno visualizzati qui.", - "call": "Chiamata", - "tactical_map": "Mappa tattica", - "annotations": "Annotazioni della mappa", - "no_annotations": "Nessuna annotazione.", - "open_tactical_map": "Apri mappa tattica", - "marker": "Indicatore", - "add_marker": "Aggiungi indicatore", - "tap_to_place": "Tocca la mappa per posizionare un indicatore", - "marker_label": "Etichetta dell’indicatore", - "delete_annotation_confirm": "Rimuovere questa annotazione?", - "voice_channels": "Canali vocali", - "no_channels": "Nessun canale aperto.", - "add_channel": "Aggiungi canale", - "channel_name": "Nome del canale", - "close_all_channels": "Chiudi tutti i canali", - "join": "Partecipa", - "hold_to_talk": "Tieni premuto per parlare", - "talking": "Trasmissione...", - "voice_joined": "Canale vocale collegato", - "voice_join_error": "Impossibile collegarsi al canale vocale", - "move": "Sposta", - "move_lane": "Sposta settore", - "parent_lane": "Settore principale", - "top_level": "Livello superiore", - "move_resource": "Sposta risorsa" - }, - "chat": { - "title": "Chat", - "assistant": "Assistente", - "empty": "Ancora nessuna conversazione. Avvia un messaggio diretto o crea un gruppo.", - "section_assistant": "Assistente", - "section_direct_messages": "Messaggi diretti", - "section_channels": "Canali", - "section_incidents": "Incidenti", - "new_direct_message": "Nuovo messaggio diretto", - "new_group": "Nuovo gruppo", - "open_assistant": "Apri assistente", - "create_conversation_failed": "Impossibile avviare la conversazione", - "group_name": "Nome del gruppo", - "search_people": "Cerca persone", - "no_people": "Nessuna persona trovata", - "create_group_with": "Crea gruppo ({{count}})", - "message_deleted": "Questo messaggio è stato eliminato", - "urgent": "Urgente", - "urgent_will_send": "Questo messaggio verrà inviato come urgente", - "shared_location": "Posizione condivisa", - "thread_replies": "{{count}} risposte", - "edited": "(modificato)", - "failed_tap_retry": "Non riuscito - tocca per riprovare", - "type_a_message": "Scrivi un messaggio", - "emoji": "Emoji", - "add_image": "Aggiungi immagine", - "add_gif": "Aggiungi GIF", - "share_location": "Condividi posizione", - "send": "Invia", - "someone": "Qualcuno", - "is_typing": "{{name}} sta scrivendo...", - "are_typing": "{{count}} persone stanno scrivendo...", - "permission_photos_denied": "Autorizzazione alla libreria foto negata", - "permission_location_denied": "Autorizzazione alla posizione negata", - "search_gifs": "Cerca GIF", - "no_gifs": "Nessun GIF trovato", - "flag_reason": "Perché lo segnali?", - "flag_inappropriate": "Inappropriato", - "flag_harassment": "Molestie", - "flag_spam": "Spam", - "flag_sensitive": "Informazioni sensibili", - "flag_policy": "Violazione delle regole", - "flag_other": "Altro", - "reply_in_thread": "Rispondi nel thread", - "copy": "Copia", - "copied": "Copiato", - "copy_unavailable": "La copia non è disponibile su questo dispositivo", - "edit": "Modifica", - "edit_message": "Modifica messaggio", - "save": "Salva", - "delete": "Elimina", - "pin": "Fissa", - "unpin": "Rimuovi fissaggio", - "flag": "Segnala", - "moderator_delete": "Rimuovi (moderatore)", - "moderator_removed": "Rimosso dal moderatore", - "attachment_failed": "Caricamento allegato non riuscito", - "ack_required": "Conferma richiesta", - "ack_pending_one": "Hai un messaggio urgente da confermare", - "ack_pending_count": "Hai {{count}} messaggi urgenti da confermare", - "acknowledge": "Conferma", - "thread": "Thread", - "original_message": "Messaggio originale", - "reply_placeholder": "Rispondi...", - "channel": "Canale", - "direct_message": "Messaggio diretto", - "load_people_failed": "Impossibile caricare le persone", - "reaction_failed": "Impossibile aggiornare la reazione", - "edit_failed": "Impossibile modificare il messaggio", - "delete_failed": "Impossibile eliminare il messaggio", - "pin_failed": "Impossibile aggiornare il messaggio fissato", - "flag_failed": "Impossibile segnalare il messaggio" - }, - "chatbot": { - "title": "Assistente", - "subtitle": "Assistente IA per il tuo reparto", - "new_session": "Nuova sessione", - "empty": "Chiedi qualsiasi cosa all'assistente per iniziare.", - "ask_placeholder": "Chiedi all'assistente..." - } + "welcome": "Benvenuto nel sito dell'app obytes" } diff --git a/src/translations/pl.json b/src/translations/pl.json index 7c79c181..36f8a1b2 100644 --- a/src/translations/pl.json +++ b/src/translations/pl.json @@ -371,6 +371,124 @@ "audio_name": "Klip dźwiękowy" } }, + "chat": { + "title": "Czat", + "assistant": "Asystent", + "empty": "Brak rozmów. Rozpocznij wiadomość bezpośrednią lub utwórz grupę.", + "section_assistant": "Asystent", + "section_direct_messages": "Wiadomości bezpośrednie", + "section_channels": "Kanały", + "section_incidents": "Zdarzenia", + "new_direct_message": "Nowa wiadomość bezpośrednia", + "new_group": "Nowa grupa", + "open_assistant": "Otwórz asystenta", + "create_conversation_failed": "Nie można rozpocząć rozmowy", + "group_name": "Nazwa grupy", + "search_people": "Szukaj osób", + "no_people": "Nie znaleziono osób", + "create_group_with": "Utwórz grupę ({{count}})", + "message_deleted": "Ta wiadomość została usunięta", + "urgent": "Pilne", + "urgent_will_send": "Ta wiadomość zostanie wysłana jako pilna", + "shared_location": "Udostępniona lokalizacja", + "thread_replies": "{{count}} odpowiedzi", + "edited": "(edytowano)", + "failed_tap_retry": "Niepowodzenie – dotknij, aby ponowić", + "type_a_message": "Napisz wiadomość", + "emoji": "Emoji", + "add_image": "Dodaj obraz", + "add_gif": "Dodaj GIF", + "share_location": "Udostępnij lokalizację", + "send": "Wyślij", + "someone": "Ktoś", + "is_typing": "{{name}} pisze...", + "are_typing": "{{count}} osób pisze...", + "permission_photos_denied": "Odmówiono dostępu do biblioteki zdjęć", + "permission_location_denied": "Odmówiono dostępu do lokalizacji", + "search_gifs": "Szukaj GIF-ów", + "no_gifs": "Nie znaleziono GIF-ów", + "flag_reason": "Dlaczego to zgłaszasz?", + "flag_inappropriate": "Nieodpowiednie", + "flag_harassment": "Nękanie", + "flag_spam": "Spam", + "flag_sensitive": "Informacje poufne", + "flag_policy": "Naruszenie zasad", + "flag_other": "Inne", + "reply_in_thread": "Odpowiedz w wątku", + "copy": "Kopiuj", + "copied": "Skopiowano", + "copy_unavailable": "Kopiowanie jest niedostępne na tym urządzeniu", + "edit": "Edytuj", + "edit_message": "Edytuj wiadomość", + "save": "Zapisz", + "delete": "Usuń", + "pin": "Przypnij", + "unpin": "Odepnij", + "flag": "Zgłoś", + "moderator_delete": "Usuń (moderator)", + "moderator_removed": "Usunięto przez moderatora", + "attachment_failed": "Przesyłanie załącznika nie powiodło się", + "ack_required": "Wymagane potwierdzenie", + "ack_pending_one": "Masz pilną wiadomość do potwierdzenia", + "ack_pending_count": "Masz {{count}} pilnych wiadomości do potwierdzenia", + "acknowledge": "Potwierdź", + "thread": "Wątek", + "original_message": "Oryginalna wiadomość", + "reply_placeholder": "Odpowiedz...", + "channel": "Kanał", + "direct_message": "Wiadomość bezpośrednia", + "load_people_failed": "Nie można załadować osób", + "reaction_failed": "Nie można zaktualizować reakcji", + "edit_failed": "Nie można edytować wiadomości", + "delete_failed": "Nie można usunąć wiadomości", + "pin_failed": "Nie można zaktualizować przypięcia", + "flag_failed": "Nie można zgłosić wiadomości" + }, + "chatbot": { + "title": "Asystent", + "subtitle": "Asystent AI dla Twojej jednostki", + "new_session": "Nowa sesja", + "empty": "Zapytaj asystenta o cokolwiek, aby rozpocząć.", + "ask_placeholder": "Zapytaj asystenta..." + }, + "check_in": { + "tab_title": "Meldunek", + "timer_status": "Status licznika", + "perform_check_in": "Zamelduj się", + "check_in_success": "Meldunek zarejestrowany pomyślnie", + "check_in_error": "Nie udało się zarejestrować meldunku", + "checked_in_by": "przez {{name}}", + "last_check_in": "Ostatni meldunek", + "elapsed": "Upłynęło", + "duration": "Czas trwania", + "status_ok": "OK", + "status_green": "OK", + "status_warning": "Ostrzeżenie", + "status_yellow": "Ostrzeżenie", + "status_overdue": "Zaległy", + "status_red": "Zaległy", + "status_critical": "Krytyczny", + "history": "Historia meldunków", + "no_timers": "Brak skonfigurowanych liczników meldunków", + "timers_disabled": "Liczniki meldunków są wyłączone dla tego zgłoszenia", + "type_personnel": "Personel", + "type_unit": "Jednostka", + "type_ic": "Kierujący działaniem ratowniczym", + "type_par": "PAR", + "type_hazmat": "Narażenie na materiały niebezpieczne", + "type_sector_rotation": "Rotacja sektora", + "type_rehab": "Rehabilitacja", + "add_note": "Dodaj notatkę (opcjonalnie)", + "confirm": "Potwierdź meldunek", + "minutes_ago": "{{count}} min temu", + "select_target": "Wybierz podmiot do meldunku", + "overdue_count": "{{count}} zaległych", + "warning_count": "{{count}} ostrzeżeń", + "enable_timers": "Włącz liczniki", + "disable_timers": "Wyłącz liczniki", + "summary": "{{overdue}} zaległych, {{warning}} ostrzeżeń, {{ok}} ok", + "par_title": "Ewidencja personelu (PAR)" + }, "common": { "add": "Dodaj", "back": "Wstecz", @@ -502,178 +620,379 @@ "website": "Strona internetowa", "zip": "Kod pocztowy" }, - "form": { - "invalid_url": "Proszę podać prawidłowy adres URL zaczynający się od http:// lub https://", - "required": "To pole jest wymagane" - }, - "livekit": { - "audio_devices": "Urządzenia audio", - "audio_settings": "Ustawienia audio", - "connected_to_room": "Połączono z kanałem", - "connecting": "Łączenie...", - "disconnect": "Rozłącz", - "join": "Dołącz", - "microphone": "Mikrofon", - "mute": "Wycisz", - "no_rooms_available": "Brak dostępnych kanałów głosowych", - "speaker": "Głośnik", - "speaking": "Mówi", - "title": "Kanały głosowe", - "unmute": "Wyłącz wyciszenie" - }, - "loading": { - "loading": "Ładowanie...", - "loadingData": "Ładowanie danych...", - "pleaseWait": "Proszę czekać", - "processingRequest": "Przetwarzanie żądania..." - }, - "sso": { - "authenticating": "Uwierzytelnianie...", - "back_to_login": "Powrót do logowania", - "back_to_lookup": "Zmień użytkownika", - "continue_button": "Kontynuuj", - "department_id_label": "ID wydziału", - "department_id_placeholder": "Wprowadź ID wydziału", - "error_generic": "Logowanie nie powiodło się. Proszę spróbować ponownie.", - "error_oidc_cancelled": "Logowanie zostało anulowane.", - "error_oidc_not_ready": "Dostawca SSO się ładuje, proszę czekać.", - "error_sso_not_enabled": "Logowanie jednokrotne nie jest włączone dla tego użytkownika.", - "error_token_exchange": "Nie udało się zakończyć logowania. Proszę spróbować ponownie.", - "error_user_not_found": "Nie znaleziono użytkownika. Proszę sprawdzić i spróbować ponownie.", - "looking_up": "Wyszukiwanie...", - "optional": "opcjonalne", - "page_subtitle": "Wprowadź swoją nazwę użytkownika, aby wyszukać opcje logowania Twojej organizacji.", - "page_title": "Logowanie jednokrotne", - "provider_oidc": "OpenID Connect (OIDC)", - "provider_saml": "SAML 2.0", - "sign_in_button": "Zaloguj się przez SSO", - "sign_in_title": "Logowanie", - "sso_button": "Logowanie SSO" - }, - "login": { - "branding_subtitle": "Zaawansowane oprogramowanie dyspozytorskie dla służb ratowniczych, poszukiwawczo-ratowniczych i organizacji bezpieczeństwa publicznego.", - "branding_title": "Zarządzanie reagowaniem kryzysowym", - "errorModal": { - "confirmButton": "OK", - "message": "Proszę sprawdzić nazwę użytkownika i hasło i spróbować ponownie.", - "title": "Logowanie nie powiodło się" - }, - "feature_dispatch_desc": "Natychmiastowe dysponowanie jednostek i zarządzanie zgłoszeniami z aktualizacjami na żywo na wszystkich urządzeniach.", - "feature_dispatch_title": "Dyspozycja w czasie rzeczywistym", - "feature_mapping_desc": "Śledzenie jednostek w czasie rzeczywistym ze szczegółowymi mapami, trasami i zarządzaniem lokalizacją.", - "feature_mapping_title": "Zaawansowane mapowanie", - "feature_personnel_desc": "Zarządzanie zespołem z kontrolą dostępu opartą na rolach, śledzeniem statusu i narzędziami komunikacyjnymi.", - "feature_personnel_title": "Zarządzanie personelem", - "footer_text": "Stworzone z ❤️ w Lake Tahoe", - "login": "Zaloguj się", - "login_button": "Zaloguj się", - "login_button_description": "Zaloguj się na swoje konto, aby kontynuować", - "login_button_error": "Błąd logowania", - "login_button_loading": "Logowanie...", - "login_button_success": "Zalogowano pomyślnie", - "no_account": "Nie masz konta?", - "page_subtitle": "Wprowadź swoje dane uwierzytelniające, aby się zalogować.", - "page_title": "Resgrid Dispatch", - "password": "Hasło", - "password_incorrect": "Hasło jest nieprawidłowe", - "password_placeholder": "Wprowadź swoje hasło", - "register": "Zarejestruj się", - "title": "Logowanie", - "username": "Nazwa użytkownika", - "username_placeholder": "Wprowadź swoją nazwę użytkownika", - "welcome_title": "Witaj ponownie" - }, - "lockscreen": { - "message": "Wprowadź hasło, aby odblokować ekran", - "not_you": "To nie Ty? Wróć do logowania", - "password": "Hasło", - "password_placeholder": "Wprowadź swoje hasło", - "title": "Ekran blokady", - "unlock_button": "Odblokuj", - "unlock_failed": "Nie udało się odblokować. Proszę spróbować ponownie.", - "unlocking": "Odblokowywanie...", - "welcome_back": "Witaj ponownie", - "relogin_required": "Weryfikacja hasła jest niedostępna w tej sesji. Zaloguj się ponownie." - }, - "maintenance": { - "downtime_message": "Pracujemy nad jak najszybszym zakończeniem konserwacji. Proszę sprawdzić ponownie wkrótce.", - "downtime_title": "Jaki jest czas przestoju?", - "message": "Proszę sprawdzić ponownie za jakiś czas.", - "support_message": "Jeśli potrzebujesz pomocy, skontaktuj się z nami pod adresem", - "support_title": "Potrzebujesz wsparcia?", - "title": "Strona jest w trakcie konserwacji", - "why_down_message": "Przeprowadzamy zaplanowaną konserwację w celu poprawy jakości usług. Przepraszamy za wszelkie niedogodności.", - "why_down_title": "Dlaczego strona jest niedostępna?" - }, - "menu": { - "scheduled_calls": "Zaplanowane zgłoszenia", - "pois": "POI", - "calls": "Zgłoszenia", - "calls_list": "Lista zgłoszeń", - "contacts": "Kontakty", - "home": "Strona główna", - "map": "Mapa", - "menu": "Menu", - "messages": "Wiadomości", - "new_call": "Nowe zgłoszenie", - "personnel": "Personel", - "protocols": "Protokoły", - "settings": "Ustawienia", + "dispatch": { + "active_calls": "Aktywne zgłoszenia", + "pending_calls": "Oczekujące", + "scheduled_calls": "Zaplanowane", + "units_available": "Dostępne", + "personnel_available": "Dostępni", + "personnel_on_duty": "Na służbie", "units": "Jednostki", - "weatherAlerts": "Alerty pogodowe", - "incident_command": "Dowodzenie zdarzeniem", - "chat": "Czat", - "assistant": "Asystent" - }, - "pois": { - "address": "Adres", - "all_types": "Wszystkie typy", - "destination": "Miejsce docelowe", - "details": "Szczegóły", - "detail_not_found": "Nie znaleziono POI", - "detail_not_found_description": "Nie udało się załadować żądanego POI.", - "detail_title": "Szczegóły POI", - "empty": "Nie znaleziono POI", - "empty_description": "Dla Twojego działu nie ma jeszcze dostępnych punktów zainteresowania.", - "empty_filtered": "Brak pasujących POI", - "empty_filtered_description": "Wyczyść wyszukiwanie lub wybierz inny typ POI.", - "filter_by_type": "Filtruj według typu POI", - "invalid_poi": "Nieprawidłowy POI", - "invalid_poi_description": "Wybrany identyfikator POI jest nieprawidłowy.", - "loading": "Ładowanie POI...", - "loading_detail": "Ładowanie szczegółów POI...", + "personnel": "Personel", "map": "Mapa", - "no_location": "Brak dostępnej lokalizacji", - "no_location_description": "Ten POI nie ma użytecznych współrzędnych.", - "no_location_for_routing": "Brak danych lokalizacji do wyznaczenia trasy", - "note": "Notatka", - "route_error": "Nie udało się otworzyć aplikacji map", - "search": "Szukaj POI...", - "sort": "Sortuj", - "sort_options": { - "address-asc": "Adres", - "name-asc": "Nazwa (A–Z)", - "name-desc": "Nazwa (Z–A)", - "type-asc": "Typ" + "notes": "Notatki", + "activity_log": "Dziennik aktywności", + "communications": "Komunikacja", + "no_active_calls": "Brak aktywnych zgłoszeń", + "no_units": "Brak dostępnych jednostek", + "no_personnel": "Brak dostępnego personelu", + "no_notes": "Brak dostępnych notatek", + "no_activity": "Brak ostatniej aktywności", + "current_channel": "Bieżący kanał", + "audio_stream": "Strumień audio", + "no_stream": "Brak aktywnego strumienia", + "ptt": "PTT", + "ptt_start": "PTT Start", + "ptt_end": "PTT Koniec", + "transmitting_on": "Transmisja na {{channel}}", + "transmission_ended": "Transmisja zakończona", + "voice_disabled": "Głos wyłączony", + "disconnected": "Rozłączono", + "select_channel": "Wybierz kanał", + "select_channel_description": "Wybierz kanał głosowy, aby się połączyć", + "change_channel_warning": "Wybranie nowego kanału spowoduje rozłączenie z bieżącym", + "default_channel": "Domyślny", + "no_channels_available": "Brak dostępnych kanałów głosowych", + "system_update": "Aktualizacja systemu", + "data_refreshed": "Dane odświeżone z serwera", + "call_selected": "Zgłoszenie wybrane", + "unit_selected": "Jednostka wybrana", + "unit_deselected": "Jednostka odznaczona", + "personnel_selected": "Personel wybrany", + "personnel_deselected": "Personel odznaczony", + "loading_map": "Ładowanie mapy...", + "map_not_available_web": "Mapa niedostępna na platformie webowej", + "filtering_by_call": "Filtrowanie wg zgłoszenia", + "clear_filter": "Wyczyść filtr", + "call_filter_active": "Filtr zgłoszenia aktywny", + "call_filter_cleared": "Filtr zgłoszenia wyczyszczony", + "showing_all_data": "Wyświetlanie wszystkich danych", + "call_notes": "Notatki zgłoszenia", + "no_call_notes": "Brak notatek zgłoszenia", + "add_call_note_placeholder": "Dodaj notatkę...", + "note_added": "Notatka dodana", + "note_added_to_console": "Nowa notatka została dodana do konsoli", + "add_note_title": "Dodaj nową notatkę", + "note_title_label": "Tytuł", + "note_title_placeholder": "Wprowadź tytuł notatki...", + "note_category_label": "Kategoria", + "note_category_placeholder": "Wybierz kategorię", + "note_no_category": "Bez kategorii", + "note_body_label": "Treść notatki", + "note_body_placeholder": "Wprowadź treść notatki...", + "note_save_error": "Nie udało się zapisać notatki: {{error}}", + "note_created": "Notatka utworzona", + "units_on_call": "Jednostki na zgłoszeniu", + "no_units_on_call": "Brak jednostek na zgłoszeniu", + "personnel_on_call": "Personel na zgłoszeniu", + "no_personnel_on_call": "Brak personelu na zgłoszeniu", + "call_activity": "Aktywność zgłoszenia", + "no_call_activity": "Brak aktywności zgłoszenia", + "on_call": "Na zgłoszeniu", + "filtered": "Przefiltrowane", + "active_filter": "Aktywny filtr", + "unit_status_change": "Zmiana statusu jednostki", + "personnel_status_change": "Zmiana statusu personelu", + "view_call_details": "Zobacz szczegóły zgłoszenia", + "dispatched_resources": "Zadysponowane", + "unassigned": "Nieprzypisane", + "available": "Dostępne", + "unknown": "Nieznane", + "search_personnel_placeholder": "Szukaj personelu...", + "search_calls_placeholder": "Szukaj zgłoszeń...", + "search_units_placeholder": "Szukaj jednostek...", + "search_notes_placeholder": "Szukaj notatek...", + "signalr_update": "Aktualizacja w czasie rzeczywistym", + "signalr_connected": "Połączono", + "realtime_updates_active": "Aktualizacje w czasie rzeczywistym są teraz aktywne", + "personnel_status_updated": "Status personelu zaktualizowany", + "personnel_staffing_updated": "Obsada personelu zaktualizowana", + "unit_status_updated": "Status jednostki zaktualizowany", + "calls_updated": "Zgłoszenia zaktualizowane", + "call_added": "Dodano nowe zgłoszenie", + "call_closed": "Zgłoszenie zamknięte", + "check_ins": "Meldunki", + "no_check_ins": "Brak zgłoszeń z licznikami meldunków", + "radio_log": "Dziennik radiowy", + "radio": "Radio", + "activity": "Aktywność", + "actions": "Akcje", + "no_radio_activity": "Brak transmisji radiowych", + "live": "NA ŻYWO", + "currently_transmitting": "Trwa transmisja...", + "duration": "Czas trwania", + "call_actions": "Akcje zgłoszenia", + "unit_actions": "Akcje jednostki", + "personnel_actions": { + "title": "Akcje personelu", + "status_tab": "Status", + "staffing_tab": "Obsada", + "select_status": "Wybierz status", + "select_staffing": "Wybierz poziom obsady", + "destination": "Cel", + "no_destination": "Brak celu", + "note": "Notatka", + "note_placeholder": "Dodaj opcjonalną notatkę...", + "update_status": "Zaktualizuj status", + "update_staffing": "Zaktualizuj obsadę", + "no_statuses_available": "Brak dostępnych statusów", + "no_staffings_available": "Brak dostępnych poziomów obsady" }, - "title": "POI", - "type": "Typ", - "unknown_type": "Nieznany typ", - "unnamed": "POI bez nazwy" + "unit_actions_panel": { + "status": "Status", + "select_status": "Wybierz status", + "destination": "Cel", + "no_destination": "Brak celu", + "note": "Notatka", + "note_placeholder": "Dodaj opcjonalną notatkę...", + "update_status": "Zaktualizuj status", + "no_statuses_available": "Brak dostępnych statusów", + "no_active_calls": "Brak aktywnych zgłoszeń", + "no_stations_available": "Brak dostępnych stacji", + "no_destinations_available": "Brak dostępnych celów" + }, + "call": "Zgłoszenie", + "station": "Stacja", + "calls": "Zgłoszenia", + "stations": "Stacje", + "no_stations_available": "Brak dostępnych stacji", + "new_call": "Nowe zgłoszenie", + "view_details": "Szczegóły", + "add_note": "Dodaj notatkę", + "close_call": "Zamknij", + "set_status": "Ustaw status", + "set_staffing": "Obsada", + "dispatch": "Dysponuj", + "select_items_for_actions": "Wybierz zgłoszenie, jednostkę lub personel, aby włączyć akcje kontekstowe", + "weather": { + "clear": "Bezchmurnie", + "mainly_clear": "Przeważnie bezchmurnie", + "partly_cloudy": "Częściowo zachmurzenie", + "overcast": "Pochmurno", + "fog": "Mgła", + "drizzle": "Mżawka", + "freezing_drizzle": "Marznąca mżawka", + "rain": "Deszcz", + "freezing_rain": "Marznący deszcz", + "snow": "Śnieg", + "rain_showers": "Przelotny deszcz", + "snow_showers": "Przelotny śnieg", + "thunderstorm": "Burza", + "thunderstorm_hail": "Burza z gradem", + "unknown": "Nieznane" + }, + "available_only": "Tylko dostępne", + "single_list": "Jedna lista", + "resources": "Zasoby", + "search_resources_placeholder": "Szukaj zasobów...", + "no_resources": "Brak zasobów" }, - "scheduled_calls": { - "title": "Zaplanowane zgłoszenia", - "loading": "Ładowanie zaplanowanych zgłoszeń...", - "no_scheduled_calls": "Brak zaplanowanych zgłoszeń", - "no_scheduled_calls_description": "Obecnie nie ma oczekujących zaplanowanych zgłoszeń.", - "search": "Szukaj zaplanowanych zgłoszeń...", - "scheduled_for": "Zaplanowano na", - "table_number": "Nr zgłoszenia", - "table_name": "Nazwa", - "table_type": "Typ", - "table_priority": "Priorytet", - "table_address": "Adres", - "table_scheduled": "Zaplanowano na" + "form": { + "invalid_url": "Proszę podać prawidłowy adres URL zaczynający się od http:// lub https://", + "required": "To pole jest wymagane" + }, + "incident_command": { + "accountability": "Ewidencja personelu (PAR)", + "acknowledge": "Potwierdź", + "action_plan": "Plan działania", + "action_plan_placeholder": "Opisz plan działania dla zdarzenia...", + "active": "Aktywne", + "active_title": "Aktywne dowodzenia zdarzeniami", + "add": "Dodaj", + "add_channel": "Dodaj kanał", + "add_lane": "Dodaj sekcję", + "add_marker": "Dodaj znacznik", + "add_objective": "Dodaj cel", + "annotations": "Adnotacje na mapie", + "assign": "Przypisz", + "assign_resource": "Przypisz zasób", + "assign_resource_required": "Wybierz sekcję i zasób", + "assign_role": "Przypisz rolę", + "assign_role_required": "Wybierz osobę i rolę", + "call": "Zgłoszenie", + "channel_name": "Nazwa kanału", + "chat": "Chat", + "chat_frozen": "This incident is closed. Conversations are kept as a point-in-time record — no new messages.", + "close_all_channels": "Zamknij wszystkie kanały", + "close_command": "Zamknij dowodzenie", + "closed": "Zamknięte", + "command_channel": "Command chat", + "command_channel_hint": "Command staff and dispatch", + "command_channel_unavailable": "No command channel has been created for this incident yet.", + "commander": "Dowódca", + "complete": "Zakończ", + "completed": "Zakończono", + "confirm_close": "Zamknąć dowodzenie zdarzeniem dla tego zgłoszenia?", + "critical": "Krytyczny", + "delete_annotation_confirm": "Usunąć tę adnotację?", + "dispatch_channel": "Dispatch", + "dispatch_channel_hint": "The incident's line to the desk", + "dispatch_channel_unavailable": "No dispatch channel has been created for this incident yet.", + "dm_failed": "Couldn't open that conversation.", + "dm_unavailable": "That contact has no Resgrid account to message.", + "due": "Termin", + "edit": "Edytuj", + "edit_action_plan": "Edytuj plan działania", + "establish": "Ustanów dowodzenie", + "establish_description": "Opcjonalnie utwórz tablicę dowodzenia na podstawie szablonu.", + "establish_error": "Nie udało się ustanowić dowodzenia", + "establish_success": "Ustanowiono dowodzenie zdarzeniem", + "establish_title": "Ustanów dowodzenie zdarzeniem", + "established_on": "Ustanowiono", + "green": "Zielony", + "hold_to_talk": "Przytrzymaj, aby mówić", + "incident_channel": "Incident chat", + "incident_channel_hint": "Everyone working the incident", + "incident_channel_unavailable": "No incident channel has been created for this call yet.", + "join": "Dołącz", + "lane": "Sekcja", + "lane_name": "Nazwa sekcji", + "lane_type": "Typ sekcji", + "marker": "Znacznik", + "marker_label": "Etykieta znacznika", + "move": "Przenieś", + "move_lane": "Przenieś sekcję", + "move_resource": "Przenieś zasób", + "name_required": "Nazwa jest wymagana", + "no_accountability": "Nie zarejestrowano personelu.", + "no_action_plan": "Nie ustawiono planu działania.", + "no_active": "Brak aktywnych dowodzeń zdarzeniami", + "no_active_description": "Ustanowione dowodzenia dla zgłoszeń pojawią się tutaj.", + "no_annotations": "Brak adnotacji.", + "no_channels": "Brak otwartych kanałów.", + "no_command": "Nie ustanowiono dowodzenia zdarzeniem", + "no_command_description": "Ustanów dowodzenie zdarzeniem, aby koordynować zasoby, role, cele i ewidencję personelu dla tego zgłoszenia.", + "no_lanes": "Nie zdefiniowano sekcji.", + "no_objectives": "Brak celów.", + "no_resources": "Nie przypisano zasobów.", + "no_roles": "Nie przypisano ról.", + "no_template": "Bez szablonu (pusta tablica)", + "no_timeline": "Brak wpisów na osi czasu.", + "no_timers": "Brak uruchomionych liczników.", + "not_authorized": "Command board unavailable", + "not_authorized_description": "Your department hasn't authorized you to work incident command. Ask an administrator for the Command App Login permission.", + "objective_name": "Cel", + "objective_type": "Typ", + "objectives": "Cele", + "open_chat": "Open", + "open_full_board": "Otwórz pełną tablicę", + "open_tactical_map": "Otwórz mapę taktyczną", + "parent_lane": "Sekcja nadrzędna", + "person": "Osoba", + "personnel": "Personel", + "release": "Zwolnij", + "resource": "Zasób", + "resource_type": "Typ zasobu", + "role": "Rola", + "roles": "Role dowodzenia", + "run_par": "Uruchom PAR", + "save": "Zapisz", + "save_error": "Operacja nie powiodła się", + "saved": "Zapisano", + "select_lane": "Wybierz sekcję", + "select_person": "Wybierz osobę", + "select_resource": "Wybierz zasób", + "select_role": "Wybierz rolę", + "send_message": "Message", + "status": "Stan", + "structure": "Struktura dowodzenia", + "tab_title": "Dowodzenie", + "tactical_map": "Mapa taktyczna", + "talking": "Nadawanie...", + "tap_to_place": "Dotknij mapy, aby umieścić znacznik", + "template": "Szablon", + "timeline": "Oś czasu dowodzenia", + "timers": "Liczniki czasu", + "title": "Dowodzenie zdarzeniem", + "top_level": "Poziom główny", + "transfer": "Przekaż", + "transfer_command": "Przekaż", + "transfer_notes": "Notatki", + "transfer_success": "Przekazano dowodzenie", + "transfer_title": "Przekaż dowodzenie", + "unassigned": "Nieprzypisane", + "unit": "Jednostka", + "voice_channels": "Kanały głosowe", + "voice_join_error": "Nie udało się dołączyć do kanału głosowego", + "voice_joined": "Dołączono do kanału głosowego", + "warning": "Ostrzeżenie" + }, + "livekit": { + "audio_devices": "Urządzenia audio", + "audio_settings": "Ustawienia audio", + "connected_to_room": "Połączono z kanałem", + "connecting": "Łączenie...", + "disconnect": "Rozłącz", + "join": "Dołącz", + "microphone": "Mikrofon", + "mute": "Wycisz", + "no_rooms_available": "Brak dostępnych kanałów głosowych", + "speaker": "Głośnik", + "speaking": "Mówi", + "title": "Kanały głosowe", + "unmute": "Wyłącz wyciszenie" + }, + "loading": { + "loading": "Ładowanie...", + "loadingData": "Ładowanie danych...", + "pleaseWait": "Proszę czekać", + "processingRequest": "Przetwarzanie żądania..." + }, + "lockscreen": { + "message": "Wprowadź hasło, aby odblokować ekran", + "not_you": "To nie Ty? Wróć do logowania", + "password": "Hasło", + "password_placeholder": "Wprowadź swoje hasło", + "title": "Ekran blokady", + "unlock_button": "Odblokuj", + "unlock_failed": "Nie udało się odblokować. Proszę spróbować ponownie.", + "unlocking": "Odblokowywanie...", + "welcome_back": "Witaj ponownie", + "relogin_required": "Weryfikacja hasła jest niedostępna w tej sesji. Zaloguj się ponownie." + }, + "login": { + "branding_subtitle": "Zaawansowane oprogramowanie dyspozytorskie dla służb ratowniczych, poszukiwawczo-ratowniczych i organizacji bezpieczeństwa publicznego.", + "branding_title": "Zarządzanie reagowaniem kryzysowym", + "dispatch_not_authorized": "You are not authorized to use the Dispatch app. Contact your department administrator.", + "errorModal": { + "confirmButton": "OK", + "message": "Proszę sprawdzić nazwę użytkownika i hasło i spróbować ponownie.", + "title": "Logowanie nie powiodło się" + }, + "feature_dispatch_desc": "Natychmiastowe dysponowanie jednostek i zarządzanie zgłoszeniami z aktualizacjami na żywo na wszystkich urządzeniach.", + "feature_dispatch_title": "Dyspozycja w czasie rzeczywistym", + "feature_mapping_desc": "Śledzenie jednostek w czasie rzeczywistym ze szczegółowymi mapami, trasami i zarządzaniem lokalizacją.", + "feature_mapping_title": "Zaawansowane mapowanie", + "feature_personnel_desc": "Zarządzanie zespołem z kontrolą dostępu opartą na rolach, śledzeniem statusu i narzędziami komunikacyjnymi.", + "feature_personnel_title": "Zarządzanie personelem", + "footer_text": "Stworzone z ❤️ w Lake Tahoe", + "login": "Zaloguj się", + "login_button": "Zaloguj się", + "login_button_description": "Zaloguj się na swoje konto, aby kontynuować", + "login_button_error": "Błąd logowania", + "login_button_loading": "Logowanie...", + "login_button_success": "Zalogowano pomyślnie", + "no_account": "Nie masz konta?", + "page_subtitle": "Wprowadź swoje dane uwierzytelniające, aby się zalogować.", + "page_title": "Resgrid Dispatch", + "password": "Hasło", + "password_incorrect": "Hasło jest nieprawidłowe", + "password_placeholder": "Wprowadź swoje hasło", + "register": "Zarejestruj się", + "title": "Logowanie", + "username": "Nazwa użytkownika", + "username_placeholder": "Wprowadź swoją nazwę użytkownika", + "welcome_title": "Witaj ponownie" + }, + "maintenance": { + "downtime_message": "Pracujemy nad jak najszybszym zakończeniem konserwacji. Proszę sprawdzić ponownie wkrótce.", + "downtime_title": "Jaki jest czas przestoju?", + "message": "Proszę sprawdzić ponownie za jakiś czas.", + "support_message": "Jeśli potrzebujesz pomocy, skontaktuj się z nami pod adresem", + "support_title": "Potrzebujesz wsparcia?", + "title": "Strona jest w trakcie konserwacji", + "why_down_message": "Przeprowadzamy zaplanowaną konserwację w celu poprawy jakości usług. Przepraszamy za wszelkie niedogodności.", + "why_down_title": "Dlaczego strona jest niedostępna?" }, "map": { "view_poi_details": "Wyświetl szczegóły POI", @@ -690,6 +1009,26 @@ "hide_all": "Ukryj wszystko", "view_call_details": "Zobacz szczegóły zgłoszenia" }, + "menu": { + "scheduled_calls": "Zaplanowane zgłoszenia", + "pois": "POI", + "calls": "Zgłoszenia", + "calls_list": "Lista zgłoszeń", + "contacts": "Kontakty", + "home": "Strona główna", + "map": "Mapa", + "menu": "Menu", + "messages": "Wiadomości", + "new_call": "Nowe zgłoszenie", + "personnel": "Personel", + "protocols": "Protokoły", + "settings": "Ustawienia", + "units": "Jednostki", + "weatherAlerts": "Alerty pogodowe", + "incident_command": "Dowodzenie zdarzeniem", + "chat": "Czat", + "assistant": "Asystent" + }, "notes": { "actions": { "add": "Dodaj notatkę", @@ -709,6 +1048,23 @@ "search": "Szukaj notatek...", "title": "Notatki" }, + "onboarding": { + "screen1": { + "title": "Resgrid Dispatch", + "description": "Twórz, dysponuj i zarządzaj zgłoszeniami alarmowymi dzięki mobilnemu centrum dowodzenia na wyciągnięcie ręki" + }, + "screen2": { + "title": "Świadomość sytuacyjna w czasie rzeczywistym", + "description": "Śledź wszystkie jednostki, personel i zasoby na interaktywnej mapie z aktualizacjami statusów na żywo i AVL" + }, + "screen3": { + "title": "Płynna koordynacja", + "description": "Komunikuj się natychmiast z jednostkami w terenie, aktualizuj statusy zgłoszeń i koordynuj działania ratownicze z dowolnego miejsca" + }, + "skip": "Pomiń", + "next": "Dalej", + "getStarted": "Zaczynajmy" + }, "personnel": { "title": "Personel", "search": "Szukaj personelu...", @@ -741,22 +1097,41 @@ "send_email": "E-mail", "custom_fields": "Dodatkowe informacje" }, - "onboarding": { - "screen1": { - "title": "Resgrid Dispatch", - "description": "Twórz, dysponuj i zarządzaj zgłoszeniami alarmowymi dzięki mobilnemu centrum dowodzenia na wyciągnięcie ręki" - }, - "screen2": { - "title": "Świadomość sytuacyjna w czasie rzeczywistym", - "description": "Śledź wszystkie jednostki, personel i zasoby na interaktywnej mapie z aktualizacjami statusów na żywo i AVL" - }, - "screen3": { - "title": "Płynna koordynacja", - "description": "Komunikuj się natychmiast z jednostkami w terenie, aktualizuj statusy zgłoszeń i koordynuj działania ratownicze z dowolnego miejsca" + "pois": { + "address": "Adres", + "all_types": "Wszystkie typy", + "destination": "Miejsce docelowe", + "details": "Szczegóły", + "detail_not_found": "Nie znaleziono POI", + "detail_not_found_description": "Nie udało się załadować żądanego POI.", + "detail_title": "Szczegóły POI", + "empty": "Nie znaleziono POI", + "empty_description": "Dla Twojego działu nie ma jeszcze dostępnych punktów zainteresowania.", + "empty_filtered": "Brak pasujących POI", + "empty_filtered_description": "Wyczyść wyszukiwanie lub wybierz inny typ POI.", + "filter_by_type": "Filtruj według typu POI", + "invalid_poi": "Nieprawidłowy POI", + "invalid_poi_description": "Wybrany identyfikator POI jest nieprawidłowy.", + "loading": "Ładowanie POI...", + "loading_detail": "Ładowanie szczegółów POI...", + "map": "Mapa", + "no_location": "Brak dostępnej lokalizacji", + "no_location_description": "Ten POI nie ma użytecznych współrzędnych.", + "no_location_for_routing": "Brak danych lokalizacji do wyznaczenia trasy", + "note": "Notatka", + "route_error": "Nie udało się otworzyć aplikacji map", + "search": "Szukaj POI...", + "sort": "Sortuj", + "sort_options": { + "address-asc": "Adres", + "name-asc": "Nazwa (A–Z)", + "name-desc": "Nazwa (Z–A)", + "type-asc": "Typ" }, - "skip": "Pomiń", - "next": "Dalej", - "getStarted": "Zaczynajmy" + "title": "POI", + "type": "Typ", + "unknown_type": "Nieznany typ", + "unnamed": "POI bez nazwy" }, "protocols": { "details": { @@ -796,6 +1171,20 @@ "tap_to_manage": "Dotknij, aby zarządzać rolami", "unassigned": "Nieprzypisane" }, + "scheduled_calls": { + "title": "Zaplanowane zgłoszenia", + "loading": "Ładowanie zaplanowanych zgłoszeń...", + "no_scheduled_calls": "Brak zaplanowanych zgłoszeń", + "no_scheduled_calls_description": "Obecnie nie ma oczekujących zaplanowanych zgłoszeń.", + "search": "Szukaj zaplanowanych zgłoszeń...", + "scheduled_for": "Zaplanowano na", + "table_number": "Nr zgłoszenia", + "table_name": "Nazwa", + "table_type": "Typ", + "table_priority": "Priorytet", + "table_address": "Adres", + "table_scheduled": "Zaplanowano na" + }, "settings": { "about": "O aplikacji", "account": "Konto", @@ -884,6 +1273,29 @@ "version": "Wersja", "website": "Strona internetowa" }, + "sso": { + "authenticating": "Uwierzytelnianie...", + "back_to_login": "Powrót do logowania", + "back_to_lookup": "Zmień użytkownika", + "continue_button": "Kontynuuj", + "department_id_label": "ID wydziału", + "department_id_placeholder": "Wprowadź ID wydziału", + "error_generic": "Logowanie nie powiodło się. Proszę spróbować ponownie.", + "error_oidc_cancelled": "Logowanie zostało anulowane.", + "error_oidc_not_ready": "Dostawca SSO się ładuje, proszę czekać.", + "error_sso_not_enabled": "Logowanie jednokrotne nie jest włączone dla tego użytkownika.", + "error_token_exchange": "Nie udało się zakończyć logowania. Proszę spróbować ponownie.", + "error_user_not_found": "Nie znaleziono użytkownika. Proszę sprawdzić i spróbować ponownie.", + "looking_up": "Wyszukiwanie...", + "optional": "opcjonalne", + "page_subtitle": "Wprowadź swoją nazwę użytkownika, aby wyszukać opcje logowania Twojej organizacji.", + "page_title": "Logowanie jednokrotne", + "provider_oidc": "OpenID Connect (OIDC)", + "provider_saml": "SAML 2.0", + "sign_in_button": "Zaloguj się przez SSO", + "sign_in_title": "Logowanie", + "sso_button": "Logowanie SSO" + }, "status": { "all_destinations_enabled": "Może odpowiadać na zgłoszenia, stacje lub POI", "calls_and_pois_destination_enabled": "Może odpowiadać na zgłoszenia lub POI", @@ -916,173 +1328,6 @@ "stations_tab": "Stacje", "status_saved_successfully": "Status zapisany pomyślnie!" }, - "dispatch": { - "active_calls": "Aktywne zgłoszenia", - "pending_calls": "Oczekujące", - "scheduled_calls": "Zaplanowane", - "units_available": "Dostępne", - "personnel_available": "Dostępni", - "personnel_on_duty": "Na służbie", - "units": "Jednostki", - "personnel": "Personel", - "map": "Mapa", - "notes": "Notatki", - "activity_log": "Dziennik aktywności", - "communications": "Komunikacja", - "no_active_calls": "Brak aktywnych zgłoszeń", - "no_units": "Brak dostępnych jednostek", - "no_personnel": "Brak dostępnego personelu", - "no_notes": "Brak dostępnych notatek", - "no_activity": "Brak ostatniej aktywności", - "current_channel": "Bieżący kanał", - "audio_stream": "Strumień audio", - "no_stream": "Brak aktywnego strumienia", - "ptt": "PTT", - "ptt_start": "PTT Start", - "ptt_end": "PTT Koniec", - "transmitting_on": "Transmisja na {{channel}}", - "transmission_ended": "Transmisja zakończona", - "voice_disabled": "Głos wyłączony", - "disconnected": "Rozłączono", - "select_channel": "Wybierz kanał", - "select_channel_description": "Wybierz kanał głosowy, aby się połączyć", - "change_channel_warning": "Wybranie nowego kanału spowoduje rozłączenie z bieżącym", - "default_channel": "Domyślny", - "no_channels_available": "Brak dostępnych kanałów głosowych", - "system_update": "Aktualizacja systemu", - "data_refreshed": "Dane odświeżone z serwera", - "call_selected": "Zgłoszenie wybrane", - "unit_selected": "Jednostka wybrana", - "unit_deselected": "Jednostka odznaczona", - "personnel_selected": "Personel wybrany", - "personnel_deselected": "Personel odznaczony", - "loading_map": "Ładowanie mapy...", - "map_not_available_web": "Mapa niedostępna na platformie webowej", - "filtering_by_call": "Filtrowanie wg zgłoszenia", - "clear_filter": "Wyczyść filtr", - "call_filter_active": "Filtr zgłoszenia aktywny", - "call_filter_cleared": "Filtr zgłoszenia wyczyszczony", - "showing_all_data": "Wyświetlanie wszystkich danych", - "call_notes": "Notatki zgłoszenia", - "no_call_notes": "Brak notatek zgłoszenia", - "add_call_note_placeholder": "Dodaj notatkę...", - "note_added": "Notatka dodana", - "note_added_to_console": "Nowa notatka została dodana do konsoli", - "add_note_title": "Dodaj nową notatkę", - "note_title_label": "Tytuł", - "note_title_placeholder": "Wprowadź tytuł notatki...", - "note_category_label": "Kategoria", - "note_category_placeholder": "Wybierz kategorię", - "note_no_category": "Bez kategorii", - "note_body_label": "Treść notatki", - "note_body_placeholder": "Wprowadź treść notatki...", - "note_save_error": "Nie udało się zapisać notatki: {{error}}", - "note_created": "Notatka utworzona", - "units_on_call": "Jednostki na zgłoszeniu", - "no_units_on_call": "Brak jednostek na zgłoszeniu", - "personnel_on_call": "Personel na zgłoszeniu", - "no_personnel_on_call": "Brak personelu na zgłoszeniu", - "call_activity": "Aktywność zgłoszenia", - "no_call_activity": "Brak aktywności zgłoszenia", - "on_call": "Na zgłoszeniu", - "filtered": "Przefiltrowane", - "active_filter": "Aktywny filtr", - "unit_status_change": "Zmiana statusu jednostki", - "personnel_status_change": "Zmiana statusu personelu", - "view_call_details": "Zobacz szczegóły zgłoszenia", - "dispatched_resources": "Zadysponowane", - "unassigned": "Nieprzypisane", - "available": "Dostępne", - "unknown": "Nieznane", - "search_personnel_placeholder": "Szukaj personelu...", - "search_calls_placeholder": "Szukaj zgłoszeń...", - "search_units_placeholder": "Szukaj jednostek...", - "search_notes_placeholder": "Szukaj notatek...", - "signalr_update": "Aktualizacja w czasie rzeczywistym", - "signalr_connected": "Połączono", - "realtime_updates_active": "Aktualizacje w czasie rzeczywistym są teraz aktywne", - "personnel_status_updated": "Status personelu zaktualizowany", - "personnel_staffing_updated": "Obsada personelu zaktualizowana", - "unit_status_updated": "Status jednostki zaktualizowany", - "calls_updated": "Zgłoszenia zaktualizowane", - "call_added": "Dodano nowe zgłoszenie", - "call_closed": "Zgłoszenie zamknięte", - "check_ins": "Meldunki", - "no_check_ins": "Brak zgłoszeń z licznikami meldunków", - "radio_log": "Dziennik radiowy", - "radio": "Radio", - "activity": "Aktywność", - "actions": "Akcje", - "no_radio_activity": "Brak transmisji radiowych", - "live": "NA ŻYWO", - "currently_transmitting": "Trwa transmisja...", - "duration": "Czas trwania", - "call_actions": "Akcje zgłoszenia", - "unit_actions": "Akcje jednostki", - "personnel_actions": { - "title": "Akcje personelu", - "status_tab": "Status", - "staffing_tab": "Obsada", - "select_status": "Wybierz status", - "select_staffing": "Wybierz poziom obsady", - "destination": "Cel", - "no_destination": "Brak celu", - "note": "Notatka", - "note_placeholder": "Dodaj opcjonalną notatkę...", - "update_status": "Zaktualizuj status", - "update_staffing": "Zaktualizuj obsadę", - "no_statuses_available": "Brak dostępnych statusów", - "no_staffings_available": "Brak dostępnych poziomów obsady" - }, - "unit_actions_panel": { - "status": "Status", - "select_status": "Wybierz status", - "destination": "Cel", - "no_destination": "Brak celu", - "note": "Notatka", - "note_placeholder": "Dodaj opcjonalną notatkę...", - "update_status": "Zaktualizuj status", - "no_statuses_available": "Brak dostępnych statusów", - "no_active_calls": "Brak aktywnych zgłoszeń", - "no_stations_available": "Brak dostępnych stacji", - "no_destinations_available": "Brak dostępnych celów" - }, - "call": "Zgłoszenie", - "station": "Stacja", - "calls": "Zgłoszenia", - "stations": "Stacje", - "no_stations_available": "Brak dostępnych stacji", - "new_call": "Nowe zgłoszenie", - "view_details": "Szczegóły", - "add_note": "Dodaj notatkę", - "close_call": "Zamknij", - "set_status": "Ustaw status", - "set_staffing": "Obsada", - "dispatch": "Dysponuj", - "select_items_for_actions": "Wybierz zgłoszenie, jednostkę lub personel, aby włączyć akcje kontekstowe", - "weather": { - "clear": "Bezchmurnie", - "mainly_clear": "Przeważnie bezchmurnie", - "partly_cloudy": "Częściowo zachmurzenie", - "overcast": "Pochmurno", - "fog": "Mgła", - "drizzle": "Mżawka", - "freezing_drizzle": "Marznąca mżawka", - "rain": "Deszcz", - "freezing_rain": "Marznący deszcz", - "snow": "Śnieg", - "rain_showers": "Przelotny deszcz", - "snow_showers": "Przelotny śnieg", - "thunderstorm": "Burza", - "thunderstorm_hail": "Burza z gradem", - "unknown": "Nieznane" - }, - "available_only": "Tylko dostępne", - "single_list": "Jedna lista", - "resources": "Zasoby", - "search_resources_placeholder": "Szukaj zasobów...", - "no_resources": "Brak zasobów" - }, "tabs": { "calls": "Zgłoszenia", "calendar": "Kalendarz", @@ -1096,44 +1341,6 @@ "shifts": "Zmiany", "personnel": "Personel" }, - "check_in": { - "tab_title": "Meldunek", - "timer_status": "Status licznika", - "perform_check_in": "Zamelduj się", - "check_in_success": "Meldunek zarejestrowany pomyślnie", - "check_in_error": "Nie udało się zarejestrować meldunku", - "checked_in_by": "przez {{name}}", - "last_check_in": "Ostatni meldunek", - "elapsed": "Upłynęło", - "duration": "Czas trwania", - "status_ok": "OK", - "status_green": "OK", - "status_warning": "Ostrzeżenie", - "status_yellow": "Ostrzeżenie", - "status_overdue": "Zaległy", - "status_red": "Zaległy", - "status_critical": "Krytyczny", - "history": "Historia meldunków", - "no_timers": "Brak skonfigurowanych liczników meldunków", - "timers_disabled": "Liczniki meldunków są wyłączone dla tego zgłoszenia", - "type_personnel": "Personel", - "type_unit": "Jednostka", - "type_ic": "Kierujący działaniem ratowniczym", - "type_par": "PAR", - "type_hazmat": "Narażenie na materiały niebezpieczne", - "type_sector_rotation": "Rotacja sektora", - "type_rehab": "Rehabilitacja", - "add_note": "Dodaj notatkę (opcjonalnie)", - "confirm": "Potwierdź meldunek", - "minutes_ago": "{{count}} min temu", - "select_target": "Wybierz podmiot do meldunku", - "overdue_count": "{{count}} zaległych", - "warning_count": "{{count}} ostrzeżeń", - "enable_timers": "Włącz liczniki", - "disable_timers": "Wyłącz liczniki", - "summary": "{{overdue}} zaległych, {{warning}} ostrzeżeń, {{ok}} ok", - "par_title": "Ewidencja personelu (PAR)" - }, "units": { "search": "Szukaj jednostek...", "loading": "Ładowanie jednostek...", @@ -1163,6 +1370,63 @@ "no_destination": "Brak", "title": "Jednostki" }, + "videoFeeds": { + "title": "Transmisje wideo", + "noFeeds": "Brak transmisji wideo dla tego zgłoszenia", + "addFeed": "Dodaj transmisję wideo", + "editFeed": "Edytuj transmisję wideo", + "deleteFeed": "Usuń transmisję wideo", + "deleteConfirm": "Czy na pewno chcesz usunąć tę transmisję wideo?", + "watch": "Oglądaj", + "goLive": "Rozpocznij transmisję", + "stopLive": "Zatrzymaj transmisję", + "flipCamera": "Odwróć kamerę", + "feedAdded": "Transmisja wideo dodana", + "feedUpdated": "Transmisja wideo zaktualizowana", + "feedDeleted": "Transmisja wideo usunięta", + "feedError": "Nie udało się załadować transmisji wideo", + "unsupportedFormat": "Ten format strumienia nie jest obsługiwany na urządzeniach mobilnych", + "copyUrl": "Kopiuj URL", + "form": { + "name": "Nazwa transmisji", + "namePlaceholder": "np. Dron Wóz 1", + "url": "URL strumienia", + "urlPlaceholder": "np. https://stream.example.com/live.m3u8", + "feedType": "Typ kamery", + "feedFormat": "Format strumienia", + "description": "Opis", + "descriptionPlaceholder": "Opcjonalny opis", + "status": "Status", + "sortOrder": "Kolejność sortowania", + "cameraLocation": "Lokalizacja kamery", + "useCurrentLocation": "Użyj bieżącej lokalizacji" + }, + "type": { + "drone": "Dron", + "fixedCamera": "Kamera stała", + "bodyCam": "Kamera osobista", + "trafficCam": "Kamera drogowa", + "weatherCam": "Kamera pogodowa", + "satelliteFeed": "Transmisja satelitarna", + "webCam": "Kamera internetowa", + "other": "Inna" + }, + "format": { + "rtsp": "RTSP", + "hls": "HLS", + "mjpeg": "MJPEG", + "youtubeLive": "YouTube Live", + "webrtc": "WebRTC", + "dash": "DASH", + "embed": "Osadzenie", + "other": "Inny" + }, + "status": { + "active": "Aktywna", + "inactive": "Nieaktywna", + "error": "Błąd" + } + }, "weatherAlerts": { "title": "Alerty pogodowe", "activeAlerts": "Aktywne alerty", @@ -1270,251 +1534,5 @@ }, "stats_label": "Alerty pogodowe" }, - "videoFeeds": { - "title": "Transmisje wideo", - "noFeeds": "Brak transmisji wideo dla tego zgłoszenia", - "addFeed": "Dodaj transmisję wideo", - "editFeed": "Edytuj transmisję wideo", - "deleteFeed": "Usuń transmisję wideo", - "deleteConfirm": "Czy na pewno chcesz usunąć tę transmisję wideo?", - "watch": "Oglądaj", - "goLive": "Rozpocznij transmisję", - "stopLive": "Zatrzymaj transmisję", - "flipCamera": "Odwróć kamerę", - "feedAdded": "Transmisja wideo dodana", - "feedUpdated": "Transmisja wideo zaktualizowana", - "feedDeleted": "Transmisja wideo usunięta", - "feedError": "Nie udało się załadować transmisji wideo", - "unsupportedFormat": "Ten format strumienia nie jest obsługiwany na urządzeniach mobilnych", - "copyUrl": "Kopiuj URL", - "form": { - "name": "Nazwa transmisji", - "namePlaceholder": "np. Dron Wóz 1", - "url": "URL strumienia", - "urlPlaceholder": "np. https://stream.example.com/live.m3u8", - "feedType": "Typ kamery", - "feedFormat": "Format strumienia", - "description": "Opis", - "descriptionPlaceholder": "Opcjonalny opis", - "status": "Status", - "sortOrder": "Kolejność sortowania", - "cameraLocation": "Lokalizacja kamery", - "useCurrentLocation": "Użyj bieżącej lokalizacji" - }, - "type": { - "drone": "Dron", - "fixedCamera": "Kamera stała", - "bodyCam": "Kamera osobista", - "trafficCam": "Kamera drogowa", - "weatherCam": "Kamera pogodowa", - "satelliteFeed": "Transmisja satelitarna", - "webCam": "Kamera internetowa", - "other": "Inna" - }, - "format": { - "rtsp": "RTSP", - "hls": "HLS", - "mjpeg": "MJPEG", - "youtubeLive": "YouTube Live", - "webrtc": "WebRTC", - "dash": "DASH", - "embed": "Osadzenie", - "other": "Inny" - }, - "status": { - "active": "Aktywna", - "inactive": "Nieaktywna", - "error": "Błąd" - } - }, - "welcome": "Witaj w aplikacji obytes", - "incident_command": { - "tab_title": "Dowodzenie", - "title": "Dowodzenie zdarzeniem", - "open_full_board": "Otwórz pełną tablicę", - "no_command": "Nie ustanowiono dowodzenia zdarzeniem", - "no_command_description": "Ustanów dowodzenie zdarzeniem, aby koordynować zasoby, role, cele i ewidencję personelu dla tego zgłoszenia.", - "establish": "Ustanów dowodzenie", - "establish_title": "Ustanów dowodzenie zdarzeniem", - "establish_description": "Opcjonalnie utwórz tablicę dowodzenia na podstawie szablonu.", - "establish_success": "Ustanowiono dowodzenie zdarzeniem", - "establish_error": "Nie udało się ustanowić dowodzenia", - "template": "Szablon", - "no_template": "Bez szablonu (pusta tablica)", - "saved": "Zapisano", - "save_error": "Operacja nie powiodła się", - "edit_action_plan": "Edytuj plan działania", - "action_plan": "Plan działania", - "action_plan_placeholder": "Opisz plan działania dla zdarzenia...", - "save": "Zapisz", - "no_action_plan": "Nie ustawiono planu działania.", - "add": "Dodaj", - "add_objective": "Dodaj cel", - "objective_name": "Cel", - "objective_type": "Typ", - "name_required": "Nazwa jest wymagana", - "add_lane": "Dodaj sekcję", - "lane_name": "Nazwa sekcji", - "lane_type": "Typ sekcji", - "assign_resource": "Przypisz zasób", - "assign_resource_required": "Wybierz sekcję i zasób", - "lane": "Sekcja", - "select_lane": "Wybierz sekcję", - "resource_type": "Typ zasobu", - "resource": "Zasób", - "select_resource": "Wybierz zasób", - "unit": "Jednostka", - "personnel": "Personel", - "assign": "Przypisz", - "assign_role": "Przypisz rolę", - "assign_role_required": "Wybierz osobę i rolę", - "person": "Osoba", - "select_person": "Wybierz osobę", - "role": "Rola", - "select_role": "Wybierz rolę", - "transfer_title": "Przekaż dowodzenie", - "transfer_notes": "Notatki", - "transfer": "Przekaż", - "transfer_command": "Przekaż", - "transfer_success": "Przekazano dowodzenie", - "close_command": "Zamknij dowodzenie", - "confirm_close": "Zamknąć dowodzenie zdarzeniem dla tego zgłoszenia?", - "status": "Stan", - "active": "Aktywne", - "closed": "Zamknięte", - "commander": "Dowódca", - "established_on": "Ustanowiono", - "edit": "Edytuj", - "roles": "Role dowodzenia", - "no_roles": "Nie przypisano ról.", - "structure": "Struktura dowodzenia", - "no_lanes": "Nie zdefiniowano sekcji.", - "no_resources": "Nie przypisano zasobów.", - "release": "Zwolnij", - "objectives": "Cele", - "no_objectives": "Brak celów.", - "complete": "Zakończ", - "completed": "Zakończono", - "timers": "Liczniki czasu", - "no_timers": "Brak uruchomionych liczników.", - "acknowledge": "Potwierdź", - "due": "Termin", - "accountability": "Ewidencja personelu (PAR)", - "run_par": "Uruchom PAR", - "green": "Zielony", - "warning": "Ostrzeżenie", - "critical": "Krytyczny", - "no_accountability": "Nie zarejestrowano personelu.", - "timeline": "Oś czasu dowodzenia", - "no_timeline": "Brak wpisów na osi czasu.", - "unassigned": "Nieprzypisane", - "active_title": "Aktywne dowodzenia zdarzeniami", - "no_active": "Brak aktywnych dowodzeń zdarzeniami", - "no_active_description": "Ustanowione dowodzenia dla zgłoszeń pojawią się tutaj.", - "call": "Zgłoszenie", - "tactical_map": "Mapa taktyczna", - "annotations": "Adnotacje na mapie", - "no_annotations": "Brak adnotacji.", - "open_tactical_map": "Otwórz mapę taktyczną", - "marker": "Znacznik", - "add_marker": "Dodaj znacznik", - "tap_to_place": "Dotknij mapy, aby umieścić znacznik", - "marker_label": "Etykieta znacznika", - "delete_annotation_confirm": "Usunąć tę adnotację?", - "voice_channels": "Kanały głosowe", - "no_channels": "Brak otwartych kanałów.", - "add_channel": "Dodaj kanał", - "channel_name": "Nazwa kanału", - "close_all_channels": "Zamknij wszystkie kanały", - "join": "Dołącz", - "hold_to_talk": "Przytrzymaj, aby mówić", - "talking": "Nadawanie...", - "voice_joined": "Dołączono do kanału głosowego", - "voice_join_error": "Nie udało się dołączyć do kanału głosowego", - "move": "Przenieś", - "move_lane": "Przenieś sekcję", - "parent_lane": "Sekcja nadrzędna", - "top_level": "Poziom główny", - "move_resource": "Przenieś zasób" - }, - "chat": { - "title": "Czat", - "assistant": "Asystent", - "empty": "Brak rozmów. Rozpocznij wiadomość bezpośrednią lub utwórz grupę.", - "section_assistant": "Asystent", - "section_direct_messages": "Wiadomości bezpośrednie", - "section_channels": "Kanały", - "section_incidents": "Zdarzenia", - "new_direct_message": "Nowa wiadomość bezpośrednia", - "new_group": "Nowa grupa", - "open_assistant": "Otwórz asystenta", - "create_conversation_failed": "Nie można rozpocząć rozmowy", - "group_name": "Nazwa grupy", - "search_people": "Szukaj osób", - "no_people": "Nie znaleziono osób", - "create_group_with": "Utwórz grupę ({{count}})", - "message_deleted": "Ta wiadomość została usunięta", - "urgent": "Pilne", - "urgent_will_send": "Ta wiadomość zostanie wysłana jako pilna", - "shared_location": "Udostępniona lokalizacja", - "thread_replies": "{{count}} odpowiedzi", - "edited": "(edytowano)", - "failed_tap_retry": "Niepowodzenie – dotknij, aby ponowić", - "type_a_message": "Napisz wiadomość", - "emoji": "Emoji", - "add_image": "Dodaj obraz", - "add_gif": "Dodaj GIF", - "share_location": "Udostępnij lokalizację", - "send": "Wyślij", - "someone": "Ktoś", - "is_typing": "{{name}} pisze...", - "are_typing": "{{count}} osób pisze...", - "permission_photos_denied": "Odmówiono dostępu do biblioteki zdjęć", - "permission_location_denied": "Odmówiono dostępu do lokalizacji", - "search_gifs": "Szukaj GIF-ów", - "no_gifs": "Nie znaleziono GIF-ów", - "flag_reason": "Dlaczego to zgłaszasz?", - "flag_inappropriate": "Nieodpowiednie", - "flag_harassment": "Nękanie", - "flag_spam": "Spam", - "flag_sensitive": "Informacje poufne", - "flag_policy": "Naruszenie zasad", - "flag_other": "Inne", - "reply_in_thread": "Odpowiedz w wątku", - "copy": "Kopiuj", - "copied": "Skopiowano", - "copy_unavailable": "Kopiowanie jest niedostępne na tym urządzeniu", - "edit": "Edytuj", - "edit_message": "Edytuj wiadomość", - "save": "Zapisz", - "delete": "Usuń", - "pin": "Przypnij", - "unpin": "Odepnij", - "flag": "Zgłoś", - "moderator_delete": "Usuń (moderator)", - "moderator_removed": "Usunięto przez moderatora", - "attachment_failed": "Przesyłanie załącznika nie powiodło się", - "ack_required": "Wymagane potwierdzenie", - "ack_pending_one": "Masz pilną wiadomość do potwierdzenia", - "ack_pending_count": "Masz {{count}} pilnych wiadomości do potwierdzenia", - "acknowledge": "Potwierdź", - "thread": "Wątek", - "original_message": "Oryginalna wiadomość", - "reply_placeholder": "Odpowiedz...", - "channel": "Kanał", - "direct_message": "Wiadomość bezpośrednia", - "load_people_failed": "Nie można załadować osób", - "reaction_failed": "Nie można zaktualizować reakcji", - "edit_failed": "Nie można edytować wiadomości", - "delete_failed": "Nie można usunąć wiadomości", - "pin_failed": "Nie można zaktualizować przypięcia", - "flag_failed": "Nie można zgłosić wiadomości" - }, - "chatbot": { - "title": "Asystent", - "subtitle": "Asystent AI dla Twojej jednostki", - "new_session": "Nowa sesja", - "empty": "Zapytaj asystenta o cokolwiek, aby rozpocząć.", - "ask_placeholder": "Zapytaj asystenta..." - } + "welcome": "Witaj w aplikacji obytes" } diff --git a/src/translations/sv.json b/src/translations/sv.json index 58568b26..e036cc3d 100644 --- a/src/translations/sv.json +++ b/src/translations/sv.json @@ -371,6 +371,124 @@ "audio_name": "Ljudklipp" } }, + "chat": { + "title": "Chatt", + "assistant": "Assistent", + "empty": "Inga konversationer ännu. Starta ett direktmeddelande eller skapa en grupp.", + "section_assistant": "Assistent", + "section_direct_messages": "Direktmeddelanden", + "section_channels": "Kanaler", + "section_incidents": "Händelser", + "new_direct_message": "Nytt direktmeddelande", + "new_group": "Ny grupp", + "open_assistant": "Öppna assistent", + "create_conversation_failed": "Det gick inte att starta konversationen", + "group_name": "Gruppnamn", + "search_people": "Sök personer", + "no_people": "Inga personer hittades", + "create_group_with": "Skapa grupp ({{count}})", + "message_deleted": "Det här meddelandet har raderats", + "urgent": "Brådskande", + "urgent_will_send": "Det här meddelandet skickas som brådskande", + "shared_location": "Delad plats", + "thread_replies": "{{count}} svar", + "edited": "(redigerad)", + "failed_tap_retry": "Misslyckades – tryck för att försöka igen", + "type_a_message": "Skriv ett meddelande", + "emoji": "Emoji", + "add_image": "Lägg till bild", + "add_gif": "Lägg till GIF", + "share_location": "Dela plats", + "send": "Skicka", + "someone": "Någon", + "is_typing": "{{name}} skriver...", + "are_typing": "{{count}} personer skriver...", + "permission_photos_denied": "Åtkomst till fotobiblioteket nekades", + "permission_location_denied": "Platsåtkomst nekades", + "search_gifs": "Sök GIF-filer", + "no_gifs": "Inga GIF-filer hittades", + "flag_reason": "Varför rapporterar du detta?", + "flag_inappropriate": "Olämpligt", + "flag_harassment": "Trakasserier", + "flag_spam": "Skräppost", + "flag_sensitive": "Känslig information", + "flag_policy": "Policyöverträdelse", + "flag_other": "Annat", + "reply_in_thread": "Svara i tråden", + "copy": "Kopiera", + "copied": "Kopierad", + "copy_unavailable": "Kopiering är inte tillgänglig på den här enheten", + "edit": "Redigera", + "edit_message": "Redigera meddelande", + "save": "Spara", + "delete": "Radera", + "pin": "Fäst", + "unpin": "Ta bort fästning", + "flag": "Rapportera", + "moderator_delete": "Ta bort (moderator)", + "moderator_removed": "Borttagen av moderator", + "attachment_failed": "Uppladdning av bilaga misslyckades", + "ack_required": "Bekräftelse krävs", + "ack_pending_one": "Du har ett brådskande meddelande att bekräfta", + "ack_pending_count": "Du har {{count}} brådskande meddelanden att bekräfta", + "acknowledge": "Bekräfta", + "thread": "Tråd", + "original_message": "Ursprungligt meddelande", + "reply_placeholder": "Svara...", + "channel": "Kanal", + "direct_message": "Direktmeddelande", + "load_people_failed": "Det gick inte att läsa in personer", + "reaction_failed": "Det gick inte att uppdatera reaktionen", + "edit_failed": "Det gick inte att redigera meddelandet", + "delete_failed": "Det gick inte att radera meddelandet", + "pin_failed": "Det gick inte att uppdatera fästningen", + "flag_failed": "Det gick inte att rapportera meddelandet" + }, + "chatbot": { + "title": "Assistent", + "subtitle": "AI-hjälp för din avdelning", + "new_session": "Ny session", + "empty": "Fråga assistenten om vad som helst för att komma igång.", + "ask_placeholder": "Fråga assistenten..." + }, + "check_in": { + "tab_title": "Incheckning", + "timer_status": "Timerstatus", + "perform_check_in": "Checka in", + "check_in_success": "Incheckning registrerad", + "check_in_error": "Kunde inte registrera incheckning", + "checked_in_by": "av {{name}}", + "last_check_in": "Senaste incheckning", + "elapsed": "Förfluten tid", + "duration": "Varaktighet", + "status_ok": "OK", + "status_green": "OK", + "status_warning": "Varning", + "status_yellow": "Varning", + "status_overdue": "Försenad", + "status_red": "Försenad", + "status_critical": "Kritisk", + "history": "Incheckningshistorik", + "no_timers": "Inga incheckningstimrar konfigurerade", + "timers_disabled": "Incheckningstimrar är inaktiverade för detta ärende", + "type_personnel": "Personal", + "type_unit": "Enhet", + "type_ic": "Insatsledare", + "type_par": "PAR", + "type_hazmat": "Farligt gods-exponering", + "type_sector_rotation": "Sektorsrotation", + "type_rehab": "Rehab", + "add_note": "Lägg till anteckning (valfritt)", + "confirm": "Bekräfta incheckning", + "minutes_ago": "{{count}} min sedan", + "select_target": "Välj enhet att checka in", + "overdue_count": "{{count}} försenade", + "warning_count": "{{count}} varning", + "enable_timers": "Aktivera timrar", + "disable_timers": "Inaktivera timrar", + "summary": "{{overdue}} försenade, {{warning}} varning, {{ok}} ok", + "par_title": "Personalkontroll (PAR)" + }, "common": { "add": "Lägg till", "back": "Tillbaka", @@ -502,178 +620,379 @@ "website": "Webbplats", "zip": "Postnummer" }, - "form": { - "invalid_url": "Vänligen ange en giltig URL som börjar med http:// eller https://", - "required": "Detta fält är obligatoriskt" - }, - "livekit": { - "audio_devices": "Ljudenheter", - "audio_settings": "Ljudinställningar", - "connected_to_room": "Ansluten till kanal", - "connecting": "Ansluter...", - "disconnect": "Koppla från", - "join": "Anslut", - "microphone": "Mikrofon", - "mute": "Tysta", - "no_rooms_available": "Inga röstkanaler tillgängliga", - "speaker": "Högtalare", - "speaking": "Talar", - "title": "Röstkanaler", - "unmute": "Slå på ljud" - }, - "loading": { - "loading": "Laddar...", - "loadingData": "Laddar data...", - "pleaseWait": "Vänligen vänta", - "processingRequest": "Bearbetar din förfrågan..." - }, - "sso": { - "authenticating": "Autentiserar...", - "back_to_login": "Tillbaka till inloggning", - "back_to_lookup": "Byt användare", - "continue_button": "Fortsätt", - "department_id_label": "Avdelnings-ID", - "department_id_placeholder": "Ange avdelnings-ID", - "error_generic": "Inloggning misslyckades. Vänligen försök igen.", - "error_oidc_cancelled": "Inloggningen avbröts.", - "error_oidc_not_ready": "SSO-leverantören laddas, vänligen vänta.", - "error_sso_not_enabled": "Enkel inloggning är inte aktiverad för denna användare.", - "error_token_exchange": "Kunde inte slutföra inloggningen. Vänligen försök igen.", - "error_user_not_found": "Användare hittades inte. Vänligen kontrollera och försök igen.", - "looking_up": "Söker...", - "optional": "valfritt", - "page_subtitle": "Ange ditt användarnamn för att söka efter din organisations inloggningsalternativ.", - "page_title": "Enkel inloggning", - "provider_oidc": "OpenID Connect (OIDC)", - "provider_saml": "SAML 2.0", - "sign_in_button": "Logga in med SSO", - "sign_in_title": "Logga in", - "sso_button": "SSO-inloggning" - }, - "login": { - "branding_subtitle": "Kraftfull larmbehandlingsprogramvara för räddningstjänst, eftersök och räddning samt samhällsskydd.", - "branding_title": "Hantering av räddningsinsatser", - "errorModal": { - "confirmButton": "OK", - "message": "Vänligen kontrollera ditt användarnamn och lösenord och försök igen.", - "title": "Inloggning misslyckades" - }, - "feature_dispatch_desc": "Larma enheter omedelbart och hantera ärenden med realtidsuppdateringar på alla enheter.", - "feature_dispatch_title": "Realtidsutlarmning", - "feature_mapping_desc": "Spåra enheter i realtid med detaljerade kartor, vägbeskrivningar och platshantering.", - "feature_mapping_title": "Avancerade kartor", - "feature_personnel_desc": "Hantera ditt team med rollbaserad åtkomst, statusspårning och kommunikationsverktyg.", - "feature_personnel_title": "Personalhantering", - "footer_text": "Skapat med ❤️ i Lake Tahoe", - "login": "Logga in", - "login_button": "Logga in", - "login_button_description": "Logga in på ditt konto för att fortsätta", - "login_button_error": "Fel vid inloggning", - "login_button_loading": "Loggar in...", - "login_button_success": "Inloggning lyckades", - "no_account": "Har du inget konto?", - "page_subtitle": "Ange dina uppgifter för att logga in.", - "page_title": "Resgrid Dispatch", - "password": "Lösenord", - "password_incorrect": "Lösenordet var felaktigt", - "password_placeholder": "Ange ditt lösenord", - "register": "Registrera", - "title": "Logga in", - "username": "Användarnamn", - "username_placeholder": "Ange ditt användarnamn", - "welcome_title": "Välkommen tillbaka" - }, - "lockscreen": { - "message": "Ange ditt lösenord för att låsa upp skärmen", - "not_you": "Inte du? Tillbaka till inloggning", - "password": "Lösenord", - "password_placeholder": "Ange ditt lösenord", - "title": "Låsskärm", - "unlock_button": "Lås upp", - "unlock_failed": "Upplåsning misslyckades. Vänligen försök igen.", - "unlocking": "Låser upp...", - "welcome_back": "Välkommen tillbaka", - "relogin_required": "Lösenordsverifiering är inte tillgänglig för den här sessionen. Logga in igen." - }, - "maintenance": { - "downtime_message": "Vi arbetar hårt för att slutföra underhållet så snabbt som möjligt. Vänligen kontrollera igen snart.", - "downtime_title": "Vad är driftstoppet?", - "message": "Vänligen kontrollera igen om en stund.", - "support_message": "Om du behöver hjälp, vänligen kontakta oss på", - "support_title": "Behöver du support?", - "title": "Webbplatsen är under underhåll", - "why_down_message": "Vi utför planerat underhåll för att förbättra din upplevelse. Vi ber om ursäkt för eventuella besvär.", - "why_down_title": "Varför är webbplatsen nere?" - }, - "menu": { - "scheduled_calls": "Schemalagda larm", - "pois": "POI:er", - "calls": "Ärenden", - "calls_list": "Ärendelista", - "contacts": "Kontakter", - "home": "Hem", - "map": "Karta", - "menu": "Meny", - "messages": "Meddelanden", - "new_call": "Nytt ärende", - "personnel": "Personal", - "protocols": "Protokoll", - "settings": "Inställningar", + "dispatch": { + "active_calls": "Aktiva ärenden", + "pending_calls": "Väntande", + "scheduled_calls": "Schemalagda", + "units_available": "Tillgängliga", + "personnel_available": "Tillgängliga", + "personnel_on_duty": "I tjänst", "units": "Enheter", - "weatherAlerts": "Vädervarningar", - "incident_command": "Insatsledning", - "chat": "Chatt", - "assistant": "Assistent" - }, - "pois": { - "address": "Adress", - "all_types": "Alla typer", - "destination": "Destination", - "details": "Detaljer", - "detail_not_found": "POI hittades inte", - "detail_not_found_description": "Det gick inte att läsa in begärd POI.", - "detail_title": "POI-detaljer", - "empty": "Inga POI:er hittades", - "empty_description": "Det finns ännu inga intressepunkter tillgängliga för din avdelning.", - "empty_filtered": "Inga matchande POI:er", - "empty_filtered_description": "Rensa sökningen eller välj en annan POI-typ.", - "filter_by_type": "Filtrera efter POI-typ", - "invalid_poi": "Ogiltig POI", - "invalid_poi_description": "Den valda POI-identifieraren är inte giltig.", - "loading": "Läser in POI:er...", - "loading_detail": "Läser in POI-detaljer...", + "personnel": "Personal", "map": "Karta", - "no_location": "Ingen plats tillgänglig", - "no_location_description": "Den här POI:n saknar användbara koordinater.", - "no_location_for_routing": "Inga platsdata tillgängliga för ruttplanering", - "note": "Anteckning", - "route_error": "Det gick inte att öppna kartappen", - "search": "Sök POI:er...", - "sort": "Sortera", - "sort_options": { - "address-asc": "Adress", - "name-asc": "Namn (A–Ö)", - "name-desc": "Namn (Ö–A)", - "type-asc": "Typ" + "notes": "Anteckningar", + "activity_log": "Aktivitetslogg", + "communications": "Kommunikation", + "no_active_calls": "Inga aktiva ärenden", + "no_units": "Inga enheter tillgängliga", + "no_personnel": "Ingen personal tillgänglig", + "no_notes": "Inga anteckningar tillgängliga", + "no_activity": "Ingen senaste aktivitet", + "current_channel": "Aktuell kanal", + "audio_stream": "Ljudström", + "no_stream": "Ingen ström aktiv", + "ptt": "PTT", + "ptt_start": "PTT start", + "ptt_end": "PTT slut", + "transmitting_on": "Sänder på {{channel}}", + "transmission_ended": "Sändning avslutad", + "voice_disabled": "Röst inaktiverad", + "disconnected": "Frånkopplad", + "select_channel": "Välj kanal", + "select_channel_description": "Välj en röstkanal att ansluta till", + "change_channel_warning": "Att välja en ny kanal kopplar från den nuvarande", + "default_channel": "Standard", + "no_channels_available": "Inga röstkanaler tillgängliga", + "system_update": "Systemuppdatering", + "data_refreshed": "Data uppdaterad från servern", + "call_selected": "Ärende valt", + "unit_selected": "Enhet vald", + "unit_deselected": "Enhet avmarkerad", + "personnel_selected": "Personal vald", + "personnel_deselected": "Personal avmarkerad", + "loading_map": "Laddar karta...", + "map_not_available_web": "Karta ej tillgänglig på webbplattform", + "filtering_by_call": "Filtrerar efter ärende", + "clear_filter": "Rensa filter", + "call_filter_active": "Ärendefilter aktivt", + "call_filter_cleared": "Ärendefilter rensat", + "showing_all_data": "Visar all data", + "call_notes": "Ärendeanteckningar", + "no_call_notes": "Inga ärendeanteckningar", + "add_call_note_placeholder": "Lägg till en anteckning...", + "note_added": "Anteckning tillagd", + "note_added_to_console": "En ny anteckning har lagts till i konsolen", + "add_note_title": "Lägg till ny anteckning", + "note_title_label": "Rubrik", + "note_title_placeholder": "Ange anteckningsrubrik...", + "note_category_label": "Kategori", + "note_category_placeholder": "Välj en kategori", + "note_no_category": "Ingen kategori", + "note_body_label": "Anteckningsinnehåll", + "note_body_placeholder": "Ange anteckningsinnehåll...", + "note_save_error": "Kunde inte spara anteckning: {{error}}", + "note_created": "Anteckning skapad", + "units_on_call": "Enheter på ärende", + "no_units_on_call": "Inga enheter på ärende", + "personnel_on_call": "Personal på ärende", + "no_personnel_on_call": "Ingen personal på ärende", + "call_activity": "Ärendeaktivitet", + "no_call_activity": "Ingen ärendeaktivitet", + "on_call": "På ärende", + "filtered": "Filtrerad", + "active_filter": "Aktivt filter", + "unit_status_change": "Enhetsstatusändring", + "personnel_status_change": "Personalstatusändring", + "view_call_details": "Visa ärendedetaljer", + "dispatched_resources": "Utlarmade", + "unassigned": "Ej tilldelad", + "available": "Tillgänglig", + "unknown": "Okänd", + "search_personnel_placeholder": "Sök personal...", + "search_calls_placeholder": "Sök ärenden...", + "search_units_placeholder": "Sök enheter...", + "search_notes_placeholder": "Sök anteckningar...", + "signalr_update": "Realtidsuppdatering", + "signalr_connected": "Ansluten", + "realtime_updates_active": "Realtidsuppdateringar är nu aktiva", + "personnel_status_updated": "Personalstatus uppdaterad", + "personnel_staffing_updated": "Personalbemanning uppdaterad", + "unit_status_updated": "Enhetsstatus uppdaterad", + "calls_updated": "Ärenden uppdaterade", + "call_added": "Nytt ärende tillagt", + "call_closed": "Ärende stängt", + "check_ins": "Incheckningar", + "no_check_ins": "Inga ärenden med incheckningstimer", + "radio_log": "Radiologg", + "radio": "Radio", + "activity": "Aktivitet", + "actions": "Åtgärder", + "no_radio_activity": "Inga radiosändningar", + "live": "LIVE", + "currently_transmitting": "Sänder för närvarande...", + "duration": "Varaktighet", + "call_actions": "Ärendeåtgärder", + "unit_actions": "Enhetsåtgärder", + "personnel_actions": { + "title": "Personalåtgärder", + "status_tab": "Status", + "staffing_tab": "Bemanning", + "select_status": "Välj status", + "select_staffing": "Välj bemanningsnivå", + "destination": "Destination", + "no_destination": "Ingen destination", + "note": "Anteckning", + "note_placeholder": "Lägg till en valfri anteckning...", + "update_status": "Uppdatera status", + "update_staffing": "Uppdatera bemanning", + "no_statuses_available": "Inga statusar tillgängliga", + "no_staffings_available": "Inga bemanningsnivåer tillgängliga" }, - "title": "POI:er", - "type": "Typ", - "unknown_type": "Okänd typ", - "unnamed": "Namnlös POI" + "unit_actions_panel": { + "status": "Status", + "select_status": "Välj status", + "destination": "Destination", + "no_destination": "Ingen destination", + "note": "Anteckning", + "note_placeholder": "Lägg till en valfri anteckning...", + "update_status": "Uppdatera status", + "no_statuses_available": "Inga statusar tillgängliga", + "no_active_calls": "Inga aktiva ärenden", + "no_stations_available": "Inga stationer tillgängliga", + "no_destinations_available": "Inga destinationer tillgängliga" + }, + "call": "Ärende", + "station": "Station", + "calls": "Ärenden", + "stations": "Stationer", + "no_stations_available": "Inga stationer tillgängliga", + "new_call": "Nytt ärende", + "view_details": "Detaljer", + "add_note": "Lägg till anteckning", + "close_call": "Stäng", + "set_status": "Ange status", + "set_staffing": "Bemanning", + "dispatch": "Larma", + "select_items_for_actions": "Välj ett ärende, enhet eller personal för att aktivera kontextmedvetna åtgärder", + "weather": { + "clear": "Klart", + "mainly_clear": "Mestadels klart", + "partly_cloudy": "Delvis molnigt", + "overcast": "Mulet", + "fog": "Dimma", + "drizzle": "Duggregn", + "freezing_drizzle": "Underkylt duggregn", + "rain": "Regn", + "freezing_rain": "Underkylt regn", + "snow": "Snö", + "rain_showers": "Regnskurar", + "snow_showers": "Snöbyar", + "thunderstorm": "Åskväder", + "thunderstorm_hail": "Åskväder med hagel", + "unknown": "Okänt" + }, + "available_only": "Endast tillgängliga", + "single_list": "En lista", + "resources": "Resurser", + "search_resources_placeholder": "Sök resurser...", + "no_resources": "Inga resurser" }, - "scheduled_calls": { - "title": "Schemalagda larm", - "loading": "Läser in schemalagda larm...", - "no_scheduled_calls": "Inga schemalagda larm", - "no_scheduled_calls_description": "Det finns inga väntande schemalagda larm just nu.", - "search": "Sök schemalagda larm...", - "scheduled_for": "Schemalagt till", - "table_number": "Larmnummer", - "table_name": "Namn", - "table_type": "Typ", - "table_priority": "Prioritet", - "table_address": "Adress", - "table_scheduled": "Schemalagt till" + "form": { + "invalid_url": "Vänligen ange en giltig URL som börjar med http:// eller https://", + "required": "Detta fält är obligatoriskt" + }, + "incident_command": { + "accountability": "Personalkontroll (PAR)", + "acknowledge": "Bekräfta", + "action_plan": "Insatsplan", + "action_plan_placeholder": "Beskriv insatsplanen...", + "active": "Aktiv", + "active_title": "Aktiva insatsledningar", + "add": "Lägg till", + "add_channel": "Lägg till kanal", + "add_lane": "Lägg till sektor", + "add_marker": "Lägg till markör", + "add_objective": "Lägg till mål", + "annotations": "Kartanteckningar", + "assign": "Tilldela", + "assign_resource": "Tilldela resurs", + "assign_resource_required": "Välj en sektor och en resurs", + "assign_role": "Tilldela roll", + "assign_role_required": "Välj en person och en roll", + "call": "Larm", + "channel_name": "Kanalnamn", + "chat": "Chat", + "chat_frozen": "This incident is closed. Conversations are kept as a point-in-time record — no new messages.", + "close_all_channels": "Stäng alla kanaler", + "close_command": "Avsluta ledning", + "closed": "Avslutad", + "command_channel": "Command chat", + "command_channel_hint": "Command staff and dispatch", + "command_channel_unavailable": "No command channel has been created for this incident yet.", + "commander": "Insatsledare", + "complete": "Slutför", + "completed": "Slutförd", + "confirm_close": "Avsluta insatsledningen för det här larmet?", + "critical": "Kritisk", + "delete_annotation_confirm": "Ta bort den här anteckningen?", + "dispatch_channel": "Dispatch", + "dispatch_channel_hint": "The incident's line to the desk", + "dispatch_channel_unavailable": "No dispatch channel has been created for this incident yet.", + "dm_failed": "Couldn't open that conversation.", + "dm_unavailable": "That contact has no Resgrid account to message.", + "due": "Förfaller", + "edit": "Redigera", + "edit_action_plan": "Redigera insatsplan", + "establish": "Upprätta ledning", + "establish_description": "Skapa valfritt ledningstavlan från en mall.", + "establish_error": "Det gick inte att upprätta ledningen", + "establish_success": "Insatsledningen har upprättats", + "establish_title": "Upprätta insatsledning", + "established_on": "Upprättad", + "green": "Grön", + "hold_to_talk": "Håll in för att tala", + "incident_channel": "Incident chat", + "incident_channel_hint": "Everyone working the incident", + "incident_channel_unavailable": "No incident channel has been created for this call yet.", + "join": "Anslut", + "lane": "Sektor", + "lane_name": "Sektornamn", + "lane_type": "Sektortyp", + "marker": "Markör", + "marker_label": "Markörens etikett", + "move": "Flytta", + "move_lane": "Flytta sektor", + "move_resource": "Flytta resurs", + "name_required": "Namn krävs", + "no_accountability": "Ingen personal registrerad.", + "no_action_plan": "Ingen insatsplan har angetts.", + "no_active": "Inga aktiva insatsledningar", + "no_active_description": "Insatsledningar som upprättats för larm visas här.", + "no_annotations": "Inga anteckningar.", + "no_channels": "Inga öppna kanaler.", + "no_command": "Ingen insatsledning upprättad", + "no_command_description": "Upprätta en insatsledning för att samordna resurser, roller, mål och personalkontroll för det här larmet.", + "no_lanes": "Inga sektorer har definierats.", + "no_objectives": "Inga mål.", + "no_resources": "Inga resurser har tilldelats.", + "no_roles": "Inga roller har tilldelats.", + "no_template": "Ingen mall (tom tavla)", + "no_timeline": "Inga poster i tidslinjen.", + "no_timers": "Inga tidtagare körs.", + "not_authorized": "Command board unavailable", + "not_authorized_description": "Your department hasn't authorized you to work incident command. Ask an administrator for the Command App Login permission.", + "objective_name": "Mål", + "objective_type": "Typ", + "objectives": "Mål", + "open_chat": "Open", + "open_full_board": "Öppna hela ledningstavlan", + "open_tactical_map": "Öppna taktisk karta", + "parent_lane": "Överordnad sektor", + "person": "Person", + "personnel": "Personal", + "release": "Frigör", + "resource": "Resurs", + "resource_type": "Resurstyp", + "role": "Roll", + "roles": "Ledningsroller", + "run_par": "Genomför PAR", + "save": "Spara", + "save_error": "Åtgärden misslyckades", + "saved": "Sparat", + "select_lane": "Välj en sektor", + "select_person": "Välj en person", + "select_resource": "Välj en resurs", + "select_role": "Välj en roll", + "send_message": "Message", + "status": "Status", + "structure": "Ledningsstruktur", + "tab_title": "Ledning", + "tactical_map": "Taktisk karta", + "talking": "Sänder...", + "tap_to_place": "Tryck på kartan för att placera en markör", + "template": "Mall", + "timeline": "Ledningens tidslinje", + "timers": "Tidtagare", + "title": "Insatsledning", + "top_level": "Översta nivån", + "transfer": "Överför", + "transfer_command": "Överför", + "transfer_notes": "Anteckningar", + "transfer_success": "Ledningen har överförts", + "transfer_title": "Överför ledning", + "unassigned": "Ej tilldelad", + "unit": "Enhet", + "voice_channels": "Röstkanaler", + "voice_join_error": "Det gick inte att ansluta till röstkanalen", + "voice_joined": "Ansluten till röstkanalen", + "warning": "Varning" + }, + "livekit": { + "audio_devices": "Ljudenheter", + "audio_settings": "Ljudinställningar", + "connected_to_room": "Ansluten till kanal", + "connecting": "Ansluter...", + "disconnect": "Koppla från", + "join": "Anslut", + "microphone": "Mikrofon", + "mute": "Tysta", + "no_rooms_available": "Inga röstkanaler tillgängliga", + "speaker": "Högtalare", + "speaking": "Talar", + "title": "Röstkanaler", + "unmute": "Slå på ljud" + }, + "loading": { + "loading": "Laddar...", + "loadingData": "Laddar data...", + "pleaseWait": "Vänligen vänta", + "processingRequest": "Bearbetar din förfrågan..." + }, + "lockscreen": { + "message": "Ange ditt lösenord för att låsa upp skärmen", + "not_you": "Inte du? Tillbaka till inloggning", + "password": "Lösenord", + "password_placeholder": "Ange ditt lösenord", + "title": "Låsskärm", + "unlock_button": "Lås upp", + "unlock_failed": "Upplåsning misslyckades. Vänligen försök igen.", + "unlocking": "Låser upp...", + "welcome_back": "Välkommen tillbaka", + "relogin_required": "Lösenordsverifiering är inte tillgänglig för den här sessionen. Logga in igen." + }, + "login": { + "branding_subtitle": "Kraftfull larmbehandlingsprogramvara för räddningstjänst, eftersök och räddning samt samhällsskydd.", + "branding_title": "Hantering av räddningsinsatser", + "dispatch_not_authorized": "You are not authorized to use the Dispatch app. Contact your department administrator.", + "errorModal": { + "confirmButton": "OK", + "message": "Vänligen kontrollera ditt användarnamn och lösenord och försök igen.", + "title": "Inloggning misslyckades" + }, + "feature_dispatch_desc": "Larma enheter omedelbart och hantera ärenden med realtidsuppdateringar på alla enheter.", + "feature_dispatch_title": "Realtidsutlarmning", + "feature_mapping_desc": "Spåra enheter i realtid med detaljerade kartor, vägbeskrivningar och platshantering.", + "feature_mapping_title": "Avancerade kartor", + "feature_personnel_desc": "Hantera ditt team med rollbaserad åtkomst, statusspårning och kommunikationsverktyg.", + "feature_personnel_title": "Personalhantering", + "footer_text": "Skapat med ❤️ i Lake Tahoe", + "login": "Logga in", + "login_button": "Logga in", + "login_button_description": "Logga in på ditt konto för att fortsätta", + "login_button_error": "Fel vid inloggning", + "login_button_loading": "Loggar in...", + "login_button_success": "Inloggning lyckades", + "no_account": "Har du inget konto?", + "page_subtitle": "Ange dina uppgifter för att logga in.", + "page_title": "Resgrid Dispatch", + "password": "Lösenord", + "password_incorrect": "Lösenordet var felaktigt", + "password_placeholder": "Ange ditt lösenord", + "register": "Registrera", + "title": "Logga in", + "username": "Användarnamn", + "username_placeholder": "Ange ditt användarnamn", + "welcome_title": "Välkommen tillbaka" + }, + "maintenance": { + "downtime_message": "Vi arbetar hårt för att slutföra underhållet så snabbt som möjligt. Vänligen kontrollera igen snart.", + "downtime_title": "Vad är driftstoppet?", + "message": "Vänligen kontrollera igen om en stund.", + "support_message": "Om du behöver hjälp, vänligen kontakta oss på", + "support_title": "Behöver du support?", + "title": "Webbplatsen är under underhåll", + "why_down_message": "Vi utför planerat underhåll för att förbättra din upplevelse. Vi ber om ursäkt för eventuella besvär.", + "why_down_title": "Varför är webbplatsen nere?" }, "map": { "view_poi_details": "Visa POI-detaljer", @@ -690,6 +1009,26 @@ "hide_all": "Dölj alla", "view_call_details": "Visa ärendedetaljer" }, + "menu": { + "scheduled_calls": "Schemalagda larm", + "pois": "POI:er", + "calls": "Ärenden", + "calls_list": "Ärendelista", + "contacts": "Kontakter", + "home": "Hem", + "map": "Karta", + "menu": "Meny", + "messages": "Meddelanden", + "new_call": "Nytt ärende", + "personnel": "Personal", + "protocols": "Protokoll", + "settings": "Inställningar", + "units": "Enheter", + "weatherAlerts": "Vädervarningar", + "incident_command": "Insatsledning", + "chat": "Chatt", + "assistant": "Assistent" + }, "notes": { "actions": { "add": "Lägg till anteckning", @@ -709,6 +1048,23 @@ "search": "Sök anteckningar...", "title": "Anteckningar" }, + "onboarding": { + "screen1": { + "title": "Resgrid Dispatch", + "description": "Skapa, larma och hantera nödsamtal med ett kraftfullt mobilt ledningscentrum i din hand" + }, + "screen2": { + "title": "Realtidslägesbild", + "description": "Spåra alla enheter, personal och resurser på en interaktiv karta med realtidsuppdateringar och AVL" + }, + "screen3": { + "title": "Sömlös samordning", + "description": "Kommunicera direkt med fältenheter, uppdatera ärendestatus och samordna insatser var som helst" + }, + "skip": "Hoppa över", + "next": "Nästa", + "getStarted": "Kom igång" + }, "personnel": { "title": "Personal", "search": "Sök personal...", @@ -741,22 +1097,41 @@ "send_email": "E-post", "custom_fields": "Ytterligare information" }, - "onboarding": { - "screen1": { - "title": "Resgrid Dispatch", - "description": "Skapa, larma och hantera nödsamtal med ett kraftfullt mobilt ledningscentrum i din hand" - }, - "screen2": { - "title": "Realtidslägesbild", - "description": "Spåra alla enheter, personal och resurser på en interaktiv karta med realtidsuppdateringar och AVL" - }, - "screen3": { - "title": "Sömlös samordning", - "description": "Kommunicera direkt med fältenheter, uppdatera ärendestatus och samordna insatser var som helst" + "pois": { + "address": "Adress", + "all_types": "Alla typer", + "destination": "Destination", + "details": "Detaljer", + "detail_not_found": "POI hittades inte", + "detail_not_found_description": "Det gick inte att läsa in begärd POI.", + "detail_title": "POI-detaljer", + "empty": "Inga POI:er hittades", + "empty_description": "Det finns ännu inga intressepunkter tillgängliga för din avdelning.", + "empty_filtered": "Inga matchande POI:er", + "empty_filtered_description": "Rensa sökningen eller välj en annan POI-typ.", + "filter_by_type": "Filtrera efter POI-typ", + "invalid_poi": "Ogiltig POI", + "invalid_poi_description": "Den valda POI-identifieraren är inte giltig.", + "loading": "Läser in POI:er...", + "loading_detail": "Läser in POI-detaljer...", + "map": "Karta", + "no_location": "Ingen plats tillgänglig", + "no_location_description": "Den här POI:n saknar användbara koordinater.", + "no_location_for_routing": "Inga platsdata tillgängliga för ruttplanering", + "note": "Anteckning", + "route_error": "Det gick inte att öppna kartappen", + "search": "Sök POI:er...", + "sort": "Sortera", + "sort_options": { + "address-asc": "Adress", + "name-asc": "Namn (A–Ö)", + "name-desc": "Namn (Ö–A)", + "type-asc": "Typ" }, - "skip": "Hoppa över", - "next": "Nästa", - "getStarted": "Kom igång" + "title": "POI:er", + "type": "Typ", + "unknown_type": "Okänd typ", + "unnamed": "Namnlös POI" }, "protocols": { "details": { @@ -796,6 +1171,20 @@ "tap_to_manage": "Tryck för att hantera roller", "unassigned": "Ej tilldelad" }, + "scheduled_calls": { + "title": "Schemalagda larm", + "loading": "Läser in schemalagda larm...", + "no_scheduled_calls": "Inga schemalagda larm", + "no_scheduled_calls_description": "Det finns inga väntande schemalagda larm just nu.", + "search": "Sök schemalagda larm...", + "scheduled_for": "Schemalagt till", + "table_number": "Larmnummer", + "table_name": "Namn", + "table_type": "Typ", + "table_priority": "Prioritet", + "table_address": "Adress", + "table_scheduled": "Schemalagt till" + }, "settings": { "about": "Om", "account": "Konto", @@ -884,6 +1273,29 @@ "version": "Version", "website": "Webbplats" }, + "sso": { + "authenticating": "Autentiserar...", + "back_to_login": "Tillbaka till inloggning", + "back_to_lookup": "Byt användare", + "continue_button": "Fortsätt", + "department_id_label": "Avdelnings-ID", + "department_id_placeholder": "Ange avdelnings-ID", + "error_generic": "Inloggning misslyckades. Vänligen försök igen.", + "error_oidc_cancelled": "Inloggningen avbröts.", + "error_oidc_not_ready": "SSO-leverantören laddas, vänligen vänta.", + "error_sso_not_enabled": "Enkel inloggning är inte aktiverad för denna användare.", + "error_token_exchange": "Kunde inte slutföra inloggningen. Vänligen försök igen.", + "error_user_not_found": "Användare hittades inte. Vänligen kontrollera och försök igen.", + "looking_up": "Söker...", + "optional": "valfritt", + "page_subtitle": "Ange ditt användarnamn för att söka efter din organisations inloggningsalternativ.", + "page_title": "Enkel inloggning", + "provider_oidc": "OpenID Connect (OIDC)", + "provider_saml": "SAML 2.0", + "sign_in_button": "Logga in med SSO", + "sign_in_title": "Logga in", + "sso_button": "SSO-inloggning" + }, "status": { "all_destinations_enabled": "Kan svara på larm, stationer eller POI:er", "calls_and_pois_destination_enabled": "Kan svara på larm eller POI:er", @@ -916,173 +1328,6 @@ "stations_tab": "Stationer", "status_saved_successfully": "Status sparad!" }, - "dispatch": { - "active_calls": "Aktiva ärenden", - "pending_calls": "Väntande", - "scheduled_calls": "Schemalagda", - "units_available": "Tillgängliga", - "personnel_available": "Tillgängliga", - "personnel_on_duty": "I tjänst", - "units": "Enheter", - "personnel": "Personal", - "map": "Karta", - "notes": "Anteckningar", - "activity_log": "Aktivitetslogg", - "communications": "Kommunikation", - "no_active_calls": "Inga aktiva ärenden", - "no_units": "Inga enheter tillgängliga", - "no_personnel": "Ingen personal tillgänglig", - "no_notes": "Inga anteckningar tillgängliga", - "no_activity": "Ingen senaste aktivitet", - "current_channel": "Aktuell kanal", - "audio_stream": "Ljudström", - "no_stream": "Ingen ström aktiv", - "ptt": "PTT", - "ptt_start": "PTT start", - "ptt_end": "PTT slut", - "transmitting_on": "Sänder på {{channel}}", - "transmission_ended": "Sändning avslutad", - "voice_disabled": "Röst inaktiverad", - "disconnected": "Frånkopplad", - "select_channel": "Välj kanal", - "select_channel_description": "Välj en röstkanal att ansluta till", - "change_channel_warning": "Att välja en ny kanal kopplar från den nuvarande", - "default_channel": "Standard", - "no_channels_available": "Inga röstkanaler tillgängliga", - "system_update": "Systemuppdatering", - "data_refreshed": "Data uppdaterad från servern", - "call_selected": "Ärende valt", - "unit_selected": "Enhet vald", - "unit_deselected": "Enhet avmarkerad", - "personnel_selected": "Personal vald", - "personnel_deselected": "Personal avmarkerad", - "loading_map": "Laddar karta...", - "map_not_available_web": "Karta ej tillgänglig på webbplattform", - "filtering_by_call": "Filtrerar efter ärende", - "clear_filter": "Rensa filter", - "call_filter_active": "Ärendefilter aktivt", - "call_filter_cleared": "Ärendefilter rensat", - "showing_all_data": "Visar all data", - "call_notes": "Ärendeanteckningar", - "no_call_notes": "Inga ärendeanteckningar", - "add_call_note_placeholder": "Lägg till en anteckning...", - "note_added": "Anteckning tillagd", - "note_added_to_console": "En ny anteckning har lagts till i konsolen", - "add_note_title": "Lägg till ny anteckning", - "note_title_label": "Rubrik", - "note_title_placeholder": "Ange anteckningsrubrik...", - "note_category_label": "Kategori", - "note_category_placeholder": "Välj en kategori", - "note_no_category": "Ingen kategori", - "note_body_label": "Anteckningsinnehåll", - "note_body_placeholder": "Ange anteckningsinnehåll...", - "note_save_error": "Kunde inte spara anteckning: {{error}}", - "note_created": "Anteckning skapad", - "units_on_call": "Enheter på ärende", - "no_units_on_call": "Inga enheter på ärende", - "personnel_on_call": "Personal på ärende", - "no_personnel_on_call": "Ingen personal på ärende", - "call_activity": "Ärendeaktivitet", - "no_call_activity": "Ingen ärendeaktivitet", - "on_call": "På ärende", - "filtered": "Filtrerad", - "active_filter": "Aktivt filter", - "unit_status_change": "Enhetsstatusändring", - "personnel_status_change": "Personalstatusändring", - "view_call_details": "Visa ärendedetaljer", - "dispatched_resources": "Utlarmade", - "unassigned": "Ej tilldelad", - "available": "Tillgänglig", - "unknown": "Okänd", - "search_personnel_placeholder": "Sök personal...", - "search_calls_placeholder": "Sök ärenden...", - "search_units_placeholder": "Sök enheter...", - "search_notes_placeholder": "Sök anteckningar...", - "signalr_update": "Realtidsuppdatering", - "signalr_connected": "Ansluten", - "realtime_updates_active": "Realtidsuppdateringar är nu aktiva", - "personnel_status_updated": "Personalstatus uppdaterad", - "personnel_staffing_updated": "Personalbemanning uppdaterad", - "unit_status_updated": "Enhetsstatus uppdaterad", - "calls_updated": "Ärenden uppdaterade", - "call_added": "Nytt ärende tillagt", - "call_closed": "Ärende stängt", - "check_ins": "Incheckningar", - "no_check_ins": "Inga ärenden med incheckningstimer", - "radio_log": "Radiologg", - "radio": "Radio", - "activity": "Aktivitet", - "actions": "Åtgärder", - "no_radio_activity": "Inga radiosändningar", - "live": "LIVE", - "currently_transmitting": "Sänder för närvarande...", - "duration": "Varaktighet", - "call_actions": "Ärendeåtgärder", - "unit_actions": "Enhetsåtgärder", - "personnel_actions": { - "title": "Personalåtgärder", - "status_tab": "Status", - "staffing_tab": "Bemanning", - "select_status": "Välj status", - "select_staffing": "Välj bemanningsnivå", - "destination": "Destination", - "no_destination": "Ingen destination", - "note": "Anteckning", - "note_placeholder": "Lägg till en valfri anteckning...", - "update_status": "Uppdatera status", - "update_staffing": "Uppdatera bemanning", - "no_statuses_available": "Inga statusar tillgängliga", - "no_staffings_available": "Inga bemanningsnivåer tillgängliga" - }, - "unit_actions_panel": { - "status": "Status", - "select_status": "Välj status", - "destination": "Destination", - "no_destination": "Ingen destination", - "note": "Anteckning", - "note_placeholder": "Lägg till en valfri anteckning...", - "update_status": "Uppdatera status", - "no_statuses_available": "Inga statusar tillgängliga", - "no_active_calls": "Inga aktiva ärenden", - "no_stations_available": "Inga stationer tillgängliga", - "no_destinations_available": "Inga destinationer tillgängliga" - }, - "call": "Ärende", - "station": "Station", - "calls": "Ärenden", - "stations": "Stationer", - "no_stations_available": "Inga stationer tillgängliga", - "new_call": "Nytt ärende", - "view_details": "Detaljer", - "add_note": "Lägg till anteckning", - "close_call": "Stäng", - "set_status": "Ange status", - "set_staffing": "Bemanning", - "dispatch": "Larma", - "select_items_for_actions": "Välj ett ärende, enhet eller personal för att aktivera kontextmedvetna åtgärder", - "weather": { - "clear": "Klart", - "mainly_clear": "Mestadels klart", - "partly_cloudy": "Delvis molnigt", - "overcast": "Mulet", - "fog": "Dimma", - "drizzle": "Duggregn", - "freezing_drizzle": "Underkylt duggregn", - "rain": "Regn", - "freezing_rain": "Underkylt regn", - "snow": "Snö", - "rain_showers": "Regnskurar", - "snow_showers": "Snöbyar", - "thunderstorm": "Åskväder", - "thunderstorm_hail": "Åskväder med hagel", - "unknown": "Okänt" - }, - "available_only": "Endast tillgängliga", - "single_list": "En lista", - "resources": "Resurser", - "search_resources_placeholder": "Sök resurser...", - "no_resources": "Inga resurser" - }, "tabs": { "calls": "Ärenden", "calendar": "Kalender", @@ -1096,44 +1341,6 @@ "shifts": "Skift", "personnel": "Personal" }, - "check_in": { - "tab_title": "Incheckning", - "timer_status": "Timerstatus", - "perform_check_in": "Checka in", - "check_in_success": "Incheckning registrerad", - "check_in_error": "Kunde inte registrera incheckning", - "checked_in_by": "av {{name}}", - "last_check_in": "Senaste incheckning", - "elapsed": "Förfluten tid", - "duration": "Varaktighet", - "status_ok": "OK", - "status_green": "OK", - "status_warning": "Varning", - "status_yellow": "Varning", - "status_overdue": "Försenad", - "status_red": "Försenad", - "status_critical": "Kritisk", - "history": "Incheckningshistorik", - "no_timers": "Inga incheckningstimrar konfigurerade", - "timers_disabled": "Incheckningstimrar är inaktiverade för detta ärende", - "type_personnel": "Personal", - "type_unit": "Enhet", - "type_ic": "Insatsledare", - "type_par": "PAR", - "type_hazmat": "Farligt gods-exponering", - "type_sector_rotation": "Sektorsrotation", - "type_rehab": "Rehab", - "add_note": "Lägg till anteckning (valfritt)", - "confirm": "Bekräfta incheckning", - "minutes_ago": "{{count}} min sedan", - "select_target": "Välj enhet att checka in", - "overdue_count": "{{count}} försenade", - "warning_count": "{{count}} varning", - "enable_timers": "Aktivera timrar", - "disable_timers": "Inaktivera timrar", - "summary": "{{overdue}} försenade, {{warning}} varning, {{ok}} ok", - "par_title": "Personalkontroll (PAR)" - }, "units": { "search": "Sök enheter...", "loading": "Läser in enheter...", @@ -1163,6 +1370,63 @@ "no_destination": "Ingen", "title": "Enheter" }, + "videoFeeds": { + "title": "Videoflöden", + "noFeeds": "Inga videoflöden för detta ärende", + "addFeed": "Lägg till videoflöde", + "editFeed": "Redigera videoflöde", + "deleteFeed": "Radera videoflöde", + "deleteConfirm": "Är du säker på att du vill ta bort detta videoflöde?", + "watch": "Titta", + "goLive": "Gå live", + "stopLive": "Stoppa live", + "flipCamera": "Vänd kamera", + "feedAdded": "Videoflöde tillagt", + "feedUpdated": "Videoflöde uppdaterat", + "feedDeleted": "Videoflöde borttaget", + "feedError": "Kunde inte ladda videoflöde", + "unsupportedFormat": "Detta strömformat stöds inte på mobil", + "copyUrl": "Kopiera URL", + "form": { + "name": "Flödesnamn", + "namePlaceholder": "t.ex. Släckbil 1 Drönare", + "url": "Ström-URL", + "urlPlaceholder": "t.ex. https://stream.example.com/live.m3u8", + "feedType": "Kameratyp", + "feedFormat": "Strömformat", + "description": "Beskrivning", + "descriptionPlaceholder": "Valfri beskrivning", + "status": "Status", + "sortOrder": "Sorteringsordning", + "cameraLocation": "Kameraplats", + "useCurrentLocation": "Använd nuvarande plats" + }, + "type": { + "drone": "Drönare", + "fixedCamera": "Fast kamera", + "bodyCam": "Kroppskamera", + "trafficCam": "Trafikkamera", + "weatherCam": "Väderkamera", + "satelliteFeed": "Satellitflöde", + "webCam": "Webbkamera", + "other": "Övrigt" + }, + "format": { + "rtsp": "RTSP", + "hls": "HLS", + "mjpeg": "MJPEG", + "youtubeLive": "YouTube Live", + "webrtc": "WebRTC", + "dash": "DASH", + "embed": "Inbäddad", + "other": "Övrigt" + }, + "status": { + "active": "Aktiv", + "inactive": "Inaktiv", + "error": "Fel" + } + }, "weatherAlerts": { "title": "Vädervarningar", "activeAlerts": "Aktiva varningar", @@ -1270,251 +1534,5 @@ }, "stats_label": "Vädervarningar" }, - "videoFeeds": { - "title": "Videoflöden", - "noFeeds": "Inga videoflöden för detta ärende", - "addFeed": "Lägg till videoflöde", - "editFeed": "Redigera videoflöde", - "deleteFeed": "Radera videoflöde", - "deleteConfirm": "Är du säker på att du vill ta bort detta videoflöde?", - "watch": "Titta", - "goLive": "Gå live", - "stopLive": "Stoppa live", - "flipCamera": "Vänd kamera", - "feedAdded": "Videoflöde tillagt", - "feedUpdated": "Videoflöde uppdaterat", - "feedDeleted": "Videoflöde borttaget", - "feedError": "Kunde inte ladda videoflöde", - "unsupportedFormat": "Detta strömformat stöds inte på mobil", - "copyUrl": "Kopiera URL", - "form": { - "name": "Flödesnamn", - "namePlaceholder": "t.ex. Släckbil 1 Drönare", - "url": "Ström-URL", - "urlPlaceholder": "t.ex. https://stream.example.com/live.m3u8", - "feedType": "Kameratyp", - "feedFormat": "Strömformat", - "description": "Beskrivning", - "descriptionPlaceholder": "Valfri beskrivning", - "status": "Status", - "sortOrder": "Sorteringsordning", - "cameraLocation": "Kameraplats", - "useCurrentLocation": "Använd nuvarande plats" - }, - "type": { - "drone": "Drönare", - "fixedCamera": "Fast kamera", - "bodyCam": "Kroppskamera", - "trafficCam": "Trafikkamera", - "weatherCam": "Väderkamera", - "satelliteFeed": "Satellitflöde", - "webCam": "Webbkamera", - "other": "Övrigt" - }, - "format": { - "rtsp": "RTSP", - "hls": "HLS", - "mjpeg": "MJPEG", - "youtubeLive": "YouTube Live", - "webrtc": "WebRTC", - "dash": "DASH", - "embed": "Inbäddad", - "other": "Övrigt" - }, - "status": { - "active": "Aktiv", - "inactive": "Inaktiv", - "error": "Fel" - } - }, - "welcome": "Välkommen till obytes app-webbplats", - "incident_command": { - "tab_title": "Ledning", - "title": "Insatsledning", - "open_full_board": "Öppna hela ledningstavlan", - "no_command": "Ingen insatsledning upprättad", - "no_command_description": "Upprätta en insatsledning för att samordna resurser, roller, mål och personalkontroll för det här larmet.", - "establish": "Upprätta ledning", - "establish_title": "Upprätta insatsledning", - "establish_description": "Skapa valfritt ledningstavlan från en mall.", - "establish_success": "Insatsledningen har upprättats", - "establish_error": "Det gick inte att upprätta ledningen", - "template": "Mall", - "no_template": "Ingen mall (tom tavla)", - "saved": "Sparat", - "save_error": "Åtgärden misslyckades", - "edit_action_plan": "Redigera insatsplan", - "action_plan": "Insatsplan", - "action_plan_placeholder": "Beskriv insatsplanen...", - "save": "Spara", - "no_action_plan": "Ingen insatsplan har angetts.", - "add": "Lägg till", - "add_objective": "Lägg till mål", - "objective_name": "Mål", - "objective_type": "Typ", - "name_required": "Namn krävs", - "add_lane": "Lägg till sektor", - "lane_name": "Sektornamn", - "lane_type": "Sektortyp", - "assign_resource": "Tilldela resurs", - "assign_resource_required": "Välj en sektor och en resurs", - "lane": "Sektor", - "select_lane": "Välj en sektor", - "resource_type": "Resurstyp", - "resource": "Resurs", - "select_resource": "Välj en resurs", - "unit": "Enhet", - "personnel": "Personal", - "assign": "Tilldela", - "assign_role": "Tilldela roll", - "assign_role_required": "Välj en person och en roll", - "person": "Person", - "select_person": "Välj en person", - "role": "Roll", - "select_role": "Välj en roll", - "transfer_title": "Överför ledning", - "transfer_notes": "Anteckningar", - "transfer": "Överför", - "transfer_command": "Överför", - "transfer_success": "Ledningen har överförts", - "close_command": "Avsluta ledning", - "confirm_close": "Avsluta insatsledningen för det här larmet?", - "status": "Status", - "active": "Aktiv", - "closed": "Avslutad", - "commander": "Insatsledare", - "established_on": "Upprättad", - "edit": "Redigera", - "roles": "Ledningsroller", - "no_roles": "Inga roller har tilldelats.", - "structure": "Ledningsstruktur", - "no_lanes": "Inga sektorer har definierats.", - "no_resources": "Inga resurser har tilldelats.", - "release": "Frigör", - "objectives": "Mål", - "no_objectives": "Inga mål.", - "complete": "Slutför", - "completed": "Slutförd", - "timers": "Tidtagare", - "no_timers": "Inga tidtagare körs.", - "acknowledge": "Bekräfta", - "due": "Förfaller", - "accountability": "Personalkontroll (PAR)", - "run_par": "Genomför PAR", - "green": "Grön", - "warning": "Varning", - "critical": "Kritisk", - "no_accountability": "Ingen personal registrerad.", - "timeline": "Ledningens tidslinje", - "no_timeline": "Inga poster i tidslinjen.", - "unassigned": "Ej tilldelad", - "active_title": "Aktiva insatsledningar", - "no_active": "Inga aktiva insatsledningar", - "no_active_description": "Insatsledningar som upprättats för larm visas här.", - "call": "Larm", - "tactical_map": "Taktisk karta", - "annotations": "Kartanteckningar", - "no_annotations": "Inga anteckningar.", - "open_tactical_map": "Öppna taktisk karta", - "marker": "Markör", - "add_marker": "Lägg till markör", - "tap_to_place": "Tryck på kartan för att placera en markör", - "marker_label": "Markörens etikett", - "delete_annotation_confirm": "Ta bort den här anteckningen?", - "voice_channels": "Röstkanaler", - "no_channels": "Inga öppna kanaler.", - "add_channel": "Lägg till kanal", - "channel_name": "Kanalnamn", - "close_all_channels": "Stäng alla kanaler", - "join": "Anslut", - "hold_to_talk": "Håll in för att tala", - "talking": "Sänder...", - "voice_joined": "Ansluten till röstkanalen", - "voice_join_error": "Det gick inte att ansluta till röstkanalen", - "move": "Flytta", - "move_lane": "Flytta sektor", - "parent_lane": "Överordnad sektor", - "top_level": "Översta nivån", - "move_resource": "Flytta resurs" - }, - "chat": { - "title": "Chatt", - "assistant": "Assistent", - "empty": "Inga konversationer ännu. Starta ett direktmeddelande eller skapa en grupp.", - "section_assistant": "Assistent", - "section_direct_messages": "Direktmeddelanden", - "section_channels": "Kanaler", - "section_incidents": "Händelser", - "new_direct_message": "Nytt direktmeddelande", - "new_group": "Ny grupp", - "open_assistant": "Öppna assistent", - "create_conversation_failed": "Det gick inte att starta konversationen", - "group_name": "Gruppnamn", - "search_people": "Sök personer", - "no_people": "Inga personer hittades", - "create_group_with": "Skapa grupp ({{count}})", - "message_deleted": "Det här meddelandet har raderats", - "urgent": "Brådskande", - "urgent_will_send": "Det här meddelandet skickas som brådskande", - "shared_location": "Delad plats", - "thread_replies": "{{count}} svar", - "edited": "(redigerad)", - "failed_tap_retry": "Misslyckades – tryck för att försöka igen", - "type_a_message": "Skriv ett meddelande", - "emoji": "Emoji", - "add_image": "Lägg till bild", - "add_gif": "Lägg till GIF", - "share_location": "Dela plats", - "send": "Skicka", - "someone": "Någon", - "is_typing": "{{name}} skriver...", - "are_typing": "{{count}} personer skriver...", - "permission_photos_denied": "Åtkomst till fotobiblioteket nekades", - "permission_location_denied": "Platsåtkomst nekades", - "search_gifs": "Sök GIF-filer", - "no_gifs": "Inga GIF-filer hittades", - "flag_reason": "Varför rapporterar du detta?", - "flag_inappropriate": "Olämpligt", - "flag_harassment": "Trakasserier", - "flag_spam": "Skräppost", - "flag_sensitive": "Känslig information", - "flag_policy": "Policyöverträdelse", - "flag_other": "Annat", - "reply_in_thread": "Svara i tråden", - "copy": "Kopiera", - "copied": "Kopierad", - "copy_unavailable": "Kopiering är inte tillgänglig på den här enheten", - "edit": "Redigera", - "edit_message": "Redigera meddelande", - "save": "Spara", - "delete": "Radera", - "pin": "Fäst", - "unpin": "Ta bort fästning", - "flag": "Rapportera", - "moderator_delete": "Ta bort (moderator)", - "moderator_removed": "Borttagen av moderator", - "attachment_failed": "Uppladdning av bilaga misslyckades", - "ack_required": "Bekräftelse krävs", - "ack_pending_one": "Du har ett brådskande meddelande att bekräfta", - "ack_pending_count": "Du har {{count}} brådskande meddelanden att bekräfta", - "acknowledge": "Bekräfta", - "thread": "Tråd", - "original_message": "Ursprungligt meddelande", - "reply_placeholder": "Svara...", - "channel": "Kanal", - "direct_message": "Direktmeddelande", - "load_people_failed": "Det gick inte att läsa in personer", - "reaction_failed": "Det gick inte att uppdatera reaktionen", - "edit_failed": "Det gick inte att redigera meddelandet", - "delete_failed": "Det gick inte att radera meddelandet", - "pin_failed": "Det gick inte att uppdatera fästningen", - "flag_failed": "Det gick inte att rapportera meddelandet" - }, - "chatbot": { - "title": "Assistent", - "subtitle": "AI-hjälp för din avdelning", - "new_session": "Ny session", - "empty": "Fråga assistenten om vad som helst för att komma igång.", - "ask_placeholder": "Fråga assistenten..." - } + "welcome": "Välkommen till obytes app-webbplats" } diff --git a/src/translations/uk.json b/src/translations/uk.json index 4e5a03bc..17fef411 100644 --- a/src/translations/uk.json +++ b/src/translations/uk.json @@ -371,6 +371,124 @@ "audio_name": "Аудіокліп" } }, + "chat": { + "title": "Чат", + "assistant": "Асистент", + "empty": "Ще немає розмов. Розпочніть пряме повідомлення або створіть групу.", + "section_assistant": "Асистент", + "section_direct_messages": "Прямі повідомлення", + "section_channels": "Канали", + "section_incidents": "Інциденти", + "new_direct_message": "Нове пряме повідомлення", + "new_group": "Нова група", + "open_assistant": "Відкрити асистента", + "create_conversation_failed": "Не вдалося розпочати розмову", + "group_name": "Назва групи", + "search_people": "Пошук людей", + "no_people": "Людей не знайдено", + "create_group_with": "Створити групу ({{count}})", + "message_deleted": "Це повідомлення видалено", + "urgent": "Терміново", + "urgent_will_send": "Це повідомлення буде надіслано як термінове", + "shared_location": "Надіслане місцезнаходження", + "thread_replies": "{{count}} відповідей", + "edited": "(відредаговано)", + "failed_tap_retry": "Помилка – торкніться, щоб повторити", + "type_a_message": "Введіть повідомлення", + "emoji": "Емодзі", + "add_image": "Додати зображення", + "add_gif": "Додати GIF", + "share_location": "Поділитися місцезнаходженням", + "send": "Надіслати", + "someone": "Хтось", + "is_typing": "{{name}} пише...", + "are_typing": "{{count}} людей пишуть...", + "permission_photos_denied": "Доступ до фотогалереї відхилено", + "permission_location_denied": "Доступ до місцезнаходження відхилено", + "search_gifs": "Пошук GIF", + "no_gifs": "GIF не знайдено", + "flag_reason": "Чому ви це повідомляєте?", + "flag_inappropriate": "Неприйнятне", + "flag_harassment": "Переслідування", + "flag_spam": "Спам", + "flag_sensitive": "Конфіденційна інформація", + "flag_policy": "Порушення правил", + "flag_other": "Інше", + "reply_in_thread": "Відповісти в гілці", + "copy": "Копіювати", + "copied": "Скопійовано", + "copy_unavailable": "Копіювання недоступне на цьому пристрої", + "edit": "Редагувати", + "edit_message": "Редагувати повідомлення", + "save": "Зберегти", + "delete": "Видалити", + "pin": "Закріпити", + "unpin": "Відкріпити", + "flag": "Поскаржитися", + "moderator_delete": "Видалити (модератор)", + "moderator_removed": "Видалено модератором", + "attachment_failed": "Не вдалося завантажити вкладення", + "ack_required": "Потрібне підтвердження", + "ack_pending_one": "У вас є термінове повідомлення для підтвердження", + "ack_pending_count": "У вас {{count}} термінових повідомлень для підтвердження", + "acknowledge": "Підтвердити", + "thread": "Гілка", + "original_message": "Оригінальне повідомлення", + "reply_placeholder": "Відповісти...", + "channel": "Канал", + "direct_message": "Пряме повідомлення", + "load_people_failed": "Не вдалося завантажити людей", + "reaction_failed": "Не вдалося оновити реакцію", + "edit_failed": "Не вдалося відредагувати повідомлення", + "delete_failed": "Не вдалося видалити повідомлення", + "pin_failed": "Не вдалося оновити закріплення", + "flag_failed": "Не вдалося поскаржитися на повідомлення" + }, + "chatbot": { + "title": "Асистент", + "subtitle": "ШІ-помічник для вашого підрозділу", + "new_session": "Нова сесія", + "empty": "Запитайте асистента про будь-що, щоб почати.", + "ask_placeholder": "Запитайте асистента..." + }, + "check_in": { + "tab_title": "Перевірка", + "timer_status": "Статус таймера", + "perform_check_in": "Відмітитися", + "check_in_success": "Перевірку успішно зафіксовано", + "check_in_error": "Не вдалося зафіксувати перевірку", + "checked_in_by": "від {{name}}", + "last_check_in": "Остання перевірка", + "elapsed": "Минуло", + "duration": "Тривалість", + "status_ok": "OK", + "status_green": "OK", + "status_warning": "Попередження", + "status_yellow": "Попередження", + "status_overdue": "Прострочено", + "status_red": "Прострочено", + "status_critical": "Критично", + "history": "Історія перевірок", + "no_timers": "Таймери перевірки не налаштовано", + "timers_disabled": "Таймери перевірки вимкнено для цього виклику", + "type_personnel": "Персонал", + "type_unit": "Підрозділ", + "type_ic": "Керівник інциденту", + "type_par": "PAR", + "type_hazmat": "Хімічне ураження", + "type_sector_rotation": "Ротація секторів", + "type_rehab": "Реабілітація", + "add_note": "Додати нотатку (необов'язково)", + "confirm": "Підтвердити перевірку", + "minutes_ago": "{{count}} хв тому", + "select_target": "Виберіть об'єкт для перевірки", + "overdue_count": "{{count}} прострочено", + "warning_count": "{{count}} попередження", + "enable_timers": "Увімкнути таймери", + "disable_timers": "Вимкнути таймери", + "summary": "{{overdue}} прострочено, {{warning}} попередження, {{ok}} ок", + "par_title": "Облік персоналу (PAR)" + }, "common": { "add": "Додати", "back": "Назад", @@ -502,178 +620,379 @@ "website": "Вебсайт", "zip": "Поштовий індекс" }, - "form": { - "invalid_url": "Будь ласка, введіть дійсну URL-адресу, що починається з http:// або https://", - "required": "Це поле є обов'язковим" - }, - "livekit": { - "audio_devices": "Аудіопристрої", - "audio_settings": "Налаштування аудіо", - "connected_to_room": "Підключено до каналу", - "connecting": "Підключення...", - "disconnect": "Відключити", - "join": "Приєднатися", - "microphone": "Мікрофон", - "mute": "Вимкнути звук", - "no_rooms_available": "Голосові канали недоступні", - "speaker": "Динамік", - "speaking": "Говорить", - "title": "Голосові канали", - "unmute": "Увімкнути звук" - }, - "loading": { - "loading": "Завантаження...", - "loadingData": "Завантаження даних...", - "pleaseWait": "Будь ласка, зачекайте", - "processingRequest": "Обробка вашого запиту..." - }, - "sso": { - "authenticating": "Автентифікація...", - "back_to_login": "Повернутися до входу", - "back_to_lookup": "Змінити користувача", - "continue_button": "Продовжити", - "department_id_label": "ID підрозділу", - "department_id_placeholder": "Введіть ID підрозділу", - "error_generic": "Не вдалося увійти. Будь ласка, спробуйте ще раз.", - "error_oidc_cancelled": "Вхід було скасовано.", - "error_oidc_not_ready": "SSO-провайдер завантажується, будь ласка, зачекайте.", - "error_sso_not_enabled": "Єдиний вхід не увімкнено для цього користувача.", - "error_token_exchange": "Не вдалося завершити вхід. Будь ласка, спробуйте ще раз.", - "error_user_not_found": "Користувача не знайдено. Будь ласка, перевірте та спробуйте ще раз.", - "looking_up": "Пошук...", - "optional": "необов'язково", - "page_subtitle": "Введіть ваше ім'я користувача для пошуку параметрів входу вашої організації.", - "page_title": "Єдиний вхід (SSO)", - "provider_oidc": "OpenID Connect (OIDC)", - "provider_saml": "SAML 2.0", - "sign_in_button": "Увійти через SSO", - "sign_in_title": "Вхід", - "sso_button": "SSO Вхід" - }, - "login": { - "branding_subtitle": "Потужне диспетчерське програмне забезпечення для служб екстреної допомоги, пошуково-рятувальних служб та організацій громадської безпеки.", - "branding_title": "Управління екстреним реагуванням", - "errorModal": { - "confirmButton": "OK", - "message": "Будь ласка, перевірте ваше ім'я користувача та пароль і спробуйте ще раз.", - "title": "Помилка входу" - }, - "feature_dispatch_desc": "Миттєво диспетчеризуйте підрозділи та керуйте викликами з оновленнями в реальному часі на всіх пристроях.", - "feature_dispatch_title": "Диспетчеризація в реальному часі", - "feature_mapping_desc": "Відстежуйте підрозділи в реальному часі з детальними картами, маршрутизацією та керуванням місцезнаходженням.", - "feature_mapping_title": "Розширена картографія", - "feature_personnel_desc": "Керуйте вашою командою з рольовим доступом, відстеженням статусів та засобами зв'язку.", - "feature_personnel_title": "Управління персоналом", - "footer_text": "Створено з ❤️ на озері Тахо", - "login": "Вхід", - "login_button": "Увійти", - "login_button_description": "Увійдіть до свого облікового запису для продовження", - "login_button_error": "Помилка входу", - "login_button_loading": "Вхід...", - "login_button_success": "Успішний вхід", - "no_account": "Немає облікового запису?", - "page_subtitle": "Введіть ваші облікові дані для входу.", - "page_title": "Resgrid Dispatch", - "password": "Пароль", - "password_incorrect": "Невірний пароль", - "password_placeholder": "Введіть ваш пароль", - "register": "Реєстрація", - "title": "Вхід", - "username": "Ім'я користувача", - "username_placeholder": "Введіть ваше ім'я користувача", - "welcome_title": "З поверненням" - }, - "lockscreen": { - "message": "Введіть ваш пароль для розблокування екрана", - "not_you": "Це не ви? Повернутися до входу", - "password": "Пароль", - "password_placeholder": "Введіть ваш пароль", - "title": "Екран блокування", - "unlock_button": "Розблокувати", - "unlock_failed": "Не вдалося розблокувати. Будь ласка, спробуйте ще раз.", - "unlocking": "Розблокування...", - "welcome_back": "З поверненням", - "relogin_required": "Перевірка пароля недоступна для цього сеансу. Будь ласка, увійдіть знову." - }, - "maintenance": { - "downtime_message": "Ми працюємо над якнайшвидшим завершенням технічних робіт. Будь ласка, перевірте пізніше.", - "downtime_title": "Який час простою?", - "message": "Будь ласка, перевірте пізніше.", - "support_message": "Якщо вам потрібна допомога, зв'яжіться з нами за адресою", - "support_title": "Потрібна підтримка?", - "title": "Сайт на технічному обслуговуванні", - "why_down_message": "Ми проводимо планове технічне обслуговування для покращення вашого досвіду. Просимо вибачення за незручності.", - "why_down_title": "Чому сайт недоступний?" - }, - "menu": { - "scheduled_calls": "Заплановані виклики", - "pois": "POI", - "calls": "Виклики", - "calls_list": "Список викликів", - "contacts": "Контакти", - "home": "Головна", - "map": "Карта", - "menu": "Меню", - "messages": "Повідомлення", - "new_call": "Новий виклик", - "personnel": "Персонал", - "protocols": "Протоколи", - "settings": "Налаштування", + "dispatch": { + "active_calls": "Активні виклики", + "pending_calls": "В очікуванні", + "scheduled_calls": "Заплановані", + "units_available": "Доступні", + "personnel_available": "Доступні", + "personnel_on_duty": "На чергуванні", "units": "Підрозділи", - "weatherAlerts": "Погодні попередження", - "incident_command": "Командування інцидентом", - "chat": "Чат", - "assistant": "Асистент" - }, - "pois": { - "address": "Адреса", - "all_types": "Усі типи", - "destination": "Місце призначення", - "details": "Відомості", - "detail_not_found": "POI не знайдено", - "detail_not_found_description": "Не вдалося завантажити запитаний POI.", - "detail_title": "Відомості про POI", - "empty": "POI не знайдено", - "empty_description": "Для вашого відділу ще немає доступних об’єктів інтересу.", - "empty_filtered": "Немає відповідних POI", - "empty_filtered_description": "Очистьте пошук або виберіть інший тип POI.", - "filter_by_type": "Фільтрувати за типом POI", - "invalid_poi": "Недійсний POI", - "invalid_poi_description": "Вибраний ідентифікатор POI недійсний.", - "loading": "Завантаження POI...", - "loading_detail": "Завантаження відомостей про POI...", + "personnel": "Персонал", "map": "Карта", - "no_location": "Місцеположення недоступне", - "no_location_description": "Цей POI не має придатних координат.", - "no_location_for_routing": "Немає даних про місцеположення для прокладання маршруту", - "note": "Примітка", - "route_error": "Не вдалося відкрити застосунок карт", - "search": "Пошук POI...", - "sort": "Сортувати", - "sort_options": { - "address-asc": "Адреса", - "name-asc": "Назва (А–Я)", - "name-desc": "Назва (Я–А)", - "type-asc": "Тип" + "notes": "Нотатки", + "activity_log": "Журнал активності", + "communications": "Зв'язок", + "no_active_calls": "Немає активних викликів", + "no_units": "Підрозділи недоступні", + "no_personnel": "Персонал недоступний", + "no_notes": "Нотатки відсутні", + "no_activity": "Немає останньої активності", + "current_channel": "Поточний канал", + "audio_stream": "Аудіопотік", + "no_stream": "Немає активного потоку", + "ptt": "PTT", + "ptt_start": "PTT Старт", + "ptt_end": "PTT Кінець", + "transmitting_on": "Передача на {{channel}}", + "transmission_ended": "Передачу завершено", + "voice_disabled": "Голос вимкнено", + "disconnected": "Відключено", + "select_channel": "Вибрати канал", + "select_channel_description": "Виберіть голосовий канал для підключення", + "change_channel_warning": "Вибір нового каналу відключить від поточного", + "default_channel": "За замовчуванням", + "no_channels_available": "Голосові канали недоступні", + "system_update": "Системне оновлення", + "data_refreshed": "Дані оновлено з сервера", + "call_selected": "Виклик вибрано", + "unit_selected": "Підрозділ вибрано", + "unit_deselected": "Вибір підрозділу скасовано", + "personnel_selected": "Персонал вибрано", + "personnel_deselected": "Вибір персоналу скасовано", + "loading_map": "Завантаження карти...", + "map_not_available_web": "Карта недоступна на веб-платформі", + "filtering_by_call": "Фільтрація за викликом", + "clear_filter": "Очистити фільтр", + "call_filter_active": "Фільтр виклику активний", + "call_filter_cleared": "Фільтр виклику очищено", + "showing_all_data": "Показано всі дані", + "call_notes": "Нотатки виклику", + "no_call_notes": "Немає нотаток виклику", + "add_call_note_placeholder": "Додати нотатку...", + "note_added": "Нотатку додано", + "note_added_to_console": "Нову нотатку додано до консолі", + "add_note_title": "Додати нову нотатку", + "note_title_label": "Заголовок", + "note_title_placeholder": "Введіть заголовок нотатки...", + "note_category_label": "Категорія", + "note_category_placeholder": "Виберіть категорію", + "note_no_category": "Без категорії", + "note_body_label": "Зміст нотатки", + "note_body_placeholder": "Введіть зміст нотатки...", + "note_save_error": "Не вдалося зберегти нотатку: {{error}}", + "note_created": "Нотатку створено", + "units_on_call": "Підрозділи на виклику", + "no_units_on_call": "Немає підрозділів на виклику", + "personnel_on_call": "Персонал на виклику", + "no_personnel_on_call": "Немає персоналу на виклику", + "call_activity": "Активність виклику", + "no_call_activity": "Немає активності виклику", + "on_call": "На виклику", + "filtered": "Відфільтровано", + "active_filter": "Активний фільтр", + "unit_status_change": "Зміна статусу підрозділу", + "personnel_status_change": "Зміна статусу персоналу", + "view_call_details": "Переглянути деталі виклику", + "dispatched_resources": "Диспетчеризовано", + "unassigned": "Не призначено", + "available": "Доступний", + "unknown": "Невідомо", + "search_personnel_placeholder": "Пошук персоналу...", + "search_calls_placeholder": "Пошук викликів...", + "search_units_placeholder": "Пошук підрозділів...", + "search_notes_placeholder": "Пошук нотаток...", + "signalr_update": "Оновлення в реальному часі", + "signalr_connected": "Підключено", + "realtime_updates_active": "Оновлення в реальному часі тепер активні", + "personnel_status_updated": "Статус персоналу оновлено", + "personnel_staffing_updated": "Укомплектованість персоналу оновлено", + "unit_status_updated": "Статус підрозділу оновлено", + "calls_updated": "Виклики оновлено", + "call_added": "Новий виклик додано", + "call_closed": "Виклик закрито", + "check_ins": "Перевірки", + "no_check_ins": "Немає викликів з таймерами перевірки", + "radio_log": "Радіожурнал", + "radio": "Радіо", + "activity": "Активність", + "actions": "Дії", + "no_radio_activity": "Немає радіопередач", + "live": "НАЖИВО", + "currently_transmitting": "Зараз передає...", + "duration": "Тривалість", + "call_actions": "Дії з викликом", + "unit_actions": "Дії з підрозділом", + "personnel_actions": { + "title": "Дії з персоналом", + "status_tab": "Статус", + "staffing_tab": "Укомплектованість", + "select_status": "Вибрати статус", + "select_staffing": "Вибрати рівень укомплектованості", + "destination": "Призначення", + "no_destination": "Без призначення", + "note": "Нотатка", + "note_placeholder": "Додайте необов'язкову нотатку...", + "update_status": "Оновити статус", + "update_staffing": "Оновити укомплектованість", + "no_statuses_available": "Статуси недоступні", + "no_staffings_available": "Рівні укомплектованості недоступні" }, - "title": "POI", - "type": "Тип", - "unknown_type": "Невідомий тип", - "unnamed": "POI без назви" + "unit_actions_panel": { + "status": "Статус", + "select_status": "Вибрати статус", + "destination": "Призначення", + "no_destination": "Без призначення", + "note": "Нотатка", + "note_placeholder": "Додайте необов'язкову нотатку...", + "update_status": "Оновити статус", + "no_statuses_available": "Статуси недоступні", + "no_active_calls": "Немає активних викликів", + "no_stations_available": "Станції недоступні", + "no_destinations_available": "Призначення недоступні" + }, + "call": "Виклик", + "station": "Станція", + "calls": "Виклики", + "stations": "Станції", + "no_stations_available": "Станції недоступні", + "new_call": "Новий виклик", + "view_details": "Деталі", + "add_note": "Додати нотатку", + "close_call": "Закрити", + "set_status": "Встановити статус", + "set_staffing": "Укомплектованість", + "dispatch": "Диспетчеризувати", + "select_items_for_actions": "Виберіть виклик, підрозділ або персонал для увімкнення контекстних дій", + "weather": { + "clear": "Ясно", + "mainly_clear": "Переважно ясно", + "partly_cloudy": "Мінлива хмарність", + "overcast": "Хмарно", + "fog": "Туман", + "drizzle": "Мряка", + "freezing_drizzle": "Крижана мряка", + "rain": "Дощ", + "freezing_rain": "Крижаний дощ", + "snow": "Сніг", + "rain_showers": "Зливи", + "snow_showers": "Снігопади", + "thunderstorm": "Гроза", + "thunderstorm_hail": "Гроза з градом", + "unknown": "Невідомо" + }, + "available_only": "Лише доступні", + "single_list": "Єдиний список", + "resources": "Ресурси", + "search_resources_placeholder": "Пошук ресурсів...", + "no_resources": "Немає ресурсів" }, - "scheduled_calls": { - "title": "Заплановані виклики", - "loading": "Завантаження запланованих викликів...", - "no_scheduled_calls": "Немає запланованих викликів", - "no_scheduled_calls_description": "Наразі немає запланованих викликів, що очікують.", - "search": "Пошук запланованих викликів...", - "scheduled_for": "Заплановано на", - "table_number": "№ виклику", - "table_name": "Назва", - "table_type": "Тип", - "table_priority": "Пріоритет", - "table_address": "Адреса", - "table_scheduled": "Заплановано на" + "form": { + "invalid_url": "Будь ласка, введіть дійсну URL-адресу, що починається з http:// або https://", + "required": "Це поле є обов'язковим" + }, + "incident_command": { + "accountability": "Облік персоналу (PAR)", + "acknowledge": "Підтвердити", + "action_plan": "План дій", + "action_plan_placeholder": "Опишіть план дій під час інциденту...", + "active": "Активне", + "active_title": "Активні командування інцидентами", + "add": "Додати", + "add_channel": "Додати канал", + "add_lane": "Додати сектор", + "add_marker": "Додати маркер", + "add_objective": "Додати ціль", + "annotations": "Позначки на карті", + "assign": "Призначити", + "assign_resource": "Призначити ресурс", + "assign_resource_required": "Виберіть сектор і ресурс", + "assign_role": "Призначити роль", + "assign_role_required": "Виберіть особу та роль", + "call": "Виклик", + "channel_name": "Назва каналу", + "chat": "Chat", + "chat_frozen": "This incident is closed. Conversations are kept as a point-in-time record — no new messages.", + "close_all_channels": "Закрити всі канали", + "close_command": "Завершити командування", + "closed": "Завершене", + "command_channel": "Command chat", + "command_channel_hint": "Command staff and dispatch", + "command_channel_unavailable": "No command channel has been created for this incident yet.", + "commander": "Керівник", + "complete": "Завершити", + "completed": "Завершено", + "confirm_close": "Завершити командування інцидентом для цього виклику?", + "critical": "Критичний", + "delete_annotation_confirm": "Видалити цю позначку?", + "dispatch_channel": "Dispatch", + "dispatch_channel_hint": "The incident's line to the desk", + "dispatch_channel_unavailable": "No dispatch channel has been created for this incident yet.", + "dm_failed": "Couldn't open that conversation.", + "dm_unavailable": "That contact has no Resgrid account to message.", + "due": "Термін", + "edit": "Редагувати", + "edit_action_plan": "Редагувати план дій", + "establish": "Встановити командування", + "establish_description": "За бажанням створіть панель командування із шаблону.", + "establish_error": "Не вдалося встановити командування", + "establish_success": "Командування інцидентом встановлено", + "establish_title": "Встановити командування інцидентом", + "established_on": "Встановлено", + "green": "Зелений", + "hold_to_talk": "Утримуйте, щоб говорити", + "incident_channel": "Incident chat", + "incident_channel_hint": "Everyone working the incident", + "incident_channel_unavailable": "No incident channel has been created for this call yet.", + "join": "Приєднатися", + "lane": "Сектор", + "lane_name": "Назва сектора", + "lane_type": "Тип сектора", + "marker": "Маркер", + "marker_label": "Підпис маркера", + "move": "Перемістити", + "move_lane": "Перемістити сектор", + "move_resource": "Перемістити ресурс", + "name_required": "Ім’я є обов’язковим", + "no_accountability": "Персонал не відстежується.", + "no_action_plan": "План дій не задано.", + "no_active": "Немає активних командувань інцидентами", + "no_active_description": "Тут відображатимуться командування, встановлені для викликів.", + "no_annotations": "Позначок немає.", + "no_channels": "Немає відкритих каналів.", + "no_command": "Командування інцидентом не встановлено", + "no_command_description": "Встановіть командування інцидентом, щоб координувати ресурси, ролі, цілі та облік персоналу для цього виклику.", + "no_lanes": "Сектори не визначено.", + "no_objectives": "Цілей немає.", + "no_resources": "Ресурси не призначено.", + "no_roles": "Ролі не призначено.", + "no_template": "Без шаблону (порожня панель)", + "no_timeline": "Записів у хронології немає.", + "no_timers": "Немає активних таймерів.", + "not_authorized": "Command board unavailable", + "not_authorized_description": "Your department hasn't authorized you to work incident command. Ask an administrator for the Command App Login permission.", + "objective_name": "Ціль", + "objective_type": "Тип", + "objectives": "Цілі", + "open_chat": "Open", + "open_full_board": "Відкрити повну панель", + "open_tactical_map": "Відкрити тактичну карту", + "parent_lane": "Батьківський сектор", + "person": "Особа", + "personnel": "Персонал", + "release": "Звільнити", + "resource": "Ресурс", + "resource_type": "Тип ресурсу", + "role": "Роль", + "roles": "Ролі командування", + "run_par": "Провести PAR", + "save": "Зберегти", + "save_error": "Не вдалося виконати операцію", + "saved": "Збережено", + "select_lane": "Виберіть сектор", + "select_person": "Виберіть особу", + "select_resource": "Виберіть ресурс", + "select_role": "Виберіть роль", + "send_message": "Message", + "status": "Стан", + "structure": "Структура командування", + "tab_title": "Командування", + "tactical_map": "Тактична карта", + "talking": "Передавання...", + "tap_to_place": "Торкніться карти, щоб установити маркер", + "template": "Шаблон", + "timeline": "Хронологія командування", + "timers": "Таймери", + "title": "Командування інцидентом", + "top_level": "Верхній рівень", + "transfer": "Передати", + "transfer_command": "Передати", + "transfer_notes": "Примітки", + "transfer_success": "Командування передано", + "transfer_title": "Передати командування", + "unassigned": "Не призначено", + "unit": "Підрозділ", + "voice_channels": "Голосові канали", + "voice_join_error": "Не вдалося приєднатися до голосового каналу", + "voice_joined": "Приєднано до голосового каналу", + "warning": "Попередження" + }, + "livekit": { + "audio_devices": "Аудіопристрої", + "audio_settings": "Налаштування аудіо", + "connected_to_room": "Підключено до каналу", + "connecting": "Підключення...", + "disconnect": "Відключити", + "join": "Приєднатися", + "microphone": "Мікрофон", + "mute": "Вимкнути звук", + "no_rooms_available": "Голосові канали недоступні", + "speaker": "Динамік", + "speaking": "Говорить", + "title": "Голосові канали", + "unmute": "Увімкнути звук" + }, + "loading": { + "loading": "Завантаження...", + "loadingData": "Завантаження даних...", + "pleaseWait": "Будь ласка, зачекайте", + "processingRequest": "Обробка вашого запиту..." + }, + "lockscreen": { + "message": "Введіть ваш пароль для розблокування екрана", + "not_you": "Це не ви? Повернутися до входу", + "password": "Пароль", + "password_placeholder": "Введіть ваш пароль", + "title": "Екран блокування", + "unlock_button": "Розблокувати", + "unlock_failed": "Не вдалося розблокувати. Будь ласка, спробуйте ще раз.", + "unlocking": "Розблокування...", + "welcome_back": "З поверненням", + "relogin_required": "Перевірка пароля недоступна для цього сеансу. Будь ласка, увійдіть знову." + }, + "login": { + "branding_subtitle": "Потужне диспетчерське програмне забезпечення для служб екстреної допомоги, пошуково-рятувальних служб та організацій громадської безпеки.", + "branding_title": "Управління екстреним реагуванням", + "dispatch_not_authorized": "You are not authorized to use the Dispatch app. Contact your department administrator.", + "errorModal": { + "confirmButton": "OK", + "message": "Будь ласка, перевірте ваше ім'я користувача та пароль і спробуйте ще раз.", + "title": "Помилка входу" + }, + "feature_dispatch_desc": "Миттєво диспетчеризуйте підрозділи та керуйте викликами з оновленнями в реальному часі на всіх пристроях.", + "feature_dispatch_title": "Диспетчеризація в реальному часі", + "feature_mapping_desc": "Відстежуйте підрозділи в реальному часі з детальними картами, маршрутизацією та керуванням місцезнаходженням.", + "feature_mapping_title": "Розширена картографія", + "feature_personnel_desc": "Керуйте вашою командою з рольовим доступом, відстеженням статусів та засобами зв'язку.", + "feature_personnel_title": "Управління персоналом", + "footer_text": "Створено з ❤️ на озері Тахо", + "login": "Вхід", + "login_button": "Увійти", + "login_button_description": "Увійдіть до свого облікового запису для продовження", + "login_button_error": "Помилка входу", + "login_button_loading": "Вхід...", + "login_button_success": "Успішний вхід", + "no_account": "Немає облікового запису?", + "page_subtitle": "Введіть ваші облікові дані для входу.", + "page_title": "Resgrid Dispatch", + "password": "Пароль", + "password_incorrect": "Невірний пароль", + "password_placeholder": "Введіть ваш пароль", + "register": "Реєстрація", + "title": "Вхід", + "username": "Ім'я користувача", + "username_placeholder": "Введіть ваше ім'я користувача", + "welcome_title": "З поверненням" + }, + "maintenance": { + "downtime_message": "Ми працюємо над якнайшвидшим завершенням технічних робіт. Будь ласка, перевірте пізніше.", + "downtime_title": "Який час простою?", + "message": "Будь ласка, перевірте пізніше.", + "support_message": "Якщо вам потрібна допомога, зв'яжіться з нами за адресою", + "support_title": "Потрібна підтримка?", + "title": "Сайт на технічному обслуговуванні", + "why_down_message": "Ми проводимо планове технічне обслуговування для покращення вашого досвіду. Просимо вибачення за незручності.", + "why_down_title": "Чому сайт недоступний?" }, "map": { "view_poi_details": "Переглянути відомості про POI", @@ -690,6 +1009,26 @@ "hide_all": "Сховати все", "view_call_details": "Переглянути деталі виклику" }, + "menu": { + "scheduled_calls": "Заплановані виклики", + "pois": "POI", + "calls": "Виклики", + "calls_list": "Список викликів", + "contacts": "Контакти", + "home": "Головна", + "map": "Карта", + "menu": "Меню", + "messages": "Повідомлення", + "new_call": "Новий виклик", + "personnel": "Персонал", + "protocols": "Протоколи", + "settings": "Налаштування", + "units": "Підрозділи", + "weatherAlerts": "Погодні попередження", + "incident_command": "Командування інцидентом", + "chat": "Чат", + "assistant": "Асистент" + }, "notes": { "actions": { "add": "Додати нотатку", @@ -709,6 +1048,23 @@ "search": "Пошук нотаток...", "title": "Нотатки" }, + "onboarding": { + "screen1": { + "title": "Resgrid Dispatch", + "description": "Створюйте, диспетчеризуйте та керуйте екстреними викликами за допомогою потужного мобільного командного центру" + }, + "screen2": { + "title": "Ситуаційна обізнаність у реальному часі", + "description": "Відстежуйте всі підрозділи, персонал та ресурси на інтерактивній карті з оновленнями статусу в реальному часі та AVL" + }, + "screen3": { + "title": "Безперебійна координація", + "description": "Миттєво зв'язуйтеся з польовими підрозділами, оновлюйте статуси викликів та координуйте реагування з будь-якого місця" + }, + "skip": "Пропустити", + "next": "Далі", + "getStarted": "Розпочати" + }, "personnel": { "title": "Персонал", "search": "Пошук персоналу...", @@ -741,22 +1097,41 @@ "send_email": "Електронна пошта", "custom_fields": "Додаткова інформація" }, - "onboarding": { - "screen1": { - "title": "Resgrid Dispatch", - "description": "Створюйте, диспетчеризуйте та керуйте екстреними викликами за допомогою потужного мобільного командного центру" - }, - "screen2": { - "title": "Ситуаційна обізнаність у реальному часі", - "description": "Відстежуйте всі підрозділи, персонал та ресурси на інтерактивній карті з оновленнями статусу в реальному часі та AVL" - }, - "screen3": { - "title": "Безперебійна координація", - "description": "Миттєво зв'язуйтеся з польовими підрозділами, оновлюйте статуси викликів та координуйте реагування з будь-якого місця" + "pois": { + "address": "Адреса", + "all_types": "Усі типи", + "destination": "Місце призначення", + "details": "Відомості", + "detail_not_found": "POI не знайдено", + "detail_not_found_description": "Не вдалося завантажити запитаний POI.", + "detail_title": "Відомості про POI", + "empty": "POI не знайдено", + "empty_description": "Для вашого відділу ще немає доступних об’єктів інтересу.", + "empty_filtered": "Немає відповідних POI", + "empty_filtered_description": "Очистьте пошук або виберіть інший тип POI.", + "filter_by_type": "Фільтрувати за типом POI", + "invalid_poi": "Недійсний POI", + "invalid_poi_description": "Вибраний ідентифікатор POI недійсний.", + "loading": "Завантаження POI...", + "loading_detail": "Завантаження відомостей про POI...", + "map": "Карта", + "no_location": "Місцеположення недоступне", + "no_location_description": "Цей POI не має придатних координат.", + "no_location_for_routing": "Немає даних про місцеположення для прокладання маршруту", + "note": "Примітка", + "route_error": "Не вдалося відкрити застосунок карт", + "search": "Пошук POI...", + "sort": "Сортувати", + "sort_options": { + "address-asc": "Адреса", + "name-asc": "Назва (А–Я)", + "name-desc": "Назва (Я–А)", + "type-asc": "Тип" }, - "skip": "Пропустити", - "next": "Далі", - "getStarted": "Розпочати" + "title": "POI", + "type": "Тип", + "unknown_type": "Невідомий тип", + "unnamed": "POI без назви" }, "protocols": { "details": { @@ -796,6 +1171,20 @@ "tap_to_manage": "Натисніть для керування ролями", "unassigned": "Не призначено" }, + "scheduled_calls": { + "title": "Заплановані виклики", + "loading": "Завантаження запланованих викликів...", + "no_scheduled_calls": "Немає запланованих викликів", + "no_scheduled_calls_description": "Наразі немає запланованих викликів, що очікують.", + "search": "Пошук запланованих викликів...", + "scheduled_for": "Заплановано на", + "table_number": "№ виклику", + "table_name": "Назва", + "table_type": "Тип", + "table_priority": "Пріоритет", + "table_address": "Адреса", + "table_scheduled": "Заплановано на" + }, "settings": { "about": "Про додаток", "account": "Обліковий запис", @@ -884,6 +1273,29 @@ "version": "Версія", "website": "Вебсайт" }, + "sso": { + "authenticating": "Автентифікація...", + "back_to_login": "Повернутися до входу", + "back_to_lookup": "Змінити користувача", + "continue_button": "Продовжити", + "department_id_label": "ID підрозділу", + "department_id_placeholder": "Введіть ID підрозділу", + "error_generic": "Не вдалося увійти. Будь ласка, спробуйте ще раз.", + "error_oidc_cancelled": "Вхід було скасовано.", + "error_oidc_not_ready": "SSO-провайдер завантажується, будь ласка, зачекайте.", + "error_sso_not_enabled": "Єдиний вхід не увімкнено для цього користувача.", + "error_token_exchange": "Не вдалося завершити вхід. Будь ласка, спробуйте ще раз.", + "error_user_not_found": "Користувача не знайдено. Будь ласка, перевірте та спробуйте ще раз.", + "looking_up": "Пошук...", + "optional": "необов'язково", + "page_subtitle": "Введіть ваше ім'я користувача для пошуку параметрів входу вашої організації.", + "page_title": "Єдиний вхід (SSO)", + "provider_oidc": "OpenID Connect (OIDC)", + "provider_saml": "SAML 2.0", + "sign_in_button": "Увійти через SSO", + "sign_in_title": "Вхід", + "sso_button": "SSO Вхід" + }, "status": { "all_destinations_enabled": "Може реагувати на виклики, станції або POI", "calls_and_pois_destination_enabled": "Може реагувати на виклики або POI", @@ -916,173 +1328,6 @@ "stations_tab": "Станції", "status_saved_successfully": "Статус успішно збережено!" }, - "dispatch": { - "active_calls": "Активні виклики", - "pending_calls": "В очікуванні", - "scheduled_calls": "Заплановані", - "units_available": "Доступні", - "personnel_available": "Доступні", - "personnel_on_duty": "На чергуванні", - "units": "Підрозділи", - "personnel": "Персонал", - "map": "Карта", - "notes": "Нотатки", - "activity_log": "Журнал активності", - "communications": "Зв'язок", - "no_active_calls": "Немає активних викликів", - "no_units": "Підрозділи недоступні", - "no_personnel": "Персонал недоступний", - "no_notes": "Нотатки відсутні", - "no_activity": "Немає останньої активності", - "current_channel": "Поточний канал", - "audio_stream": "Аудіопотік", - "no_stream": "Немає активного потоку", - "ptt": "PTT", - "ptt_start": "PTT Старт", - "ptt_end": "PTT Кінець", - "transmitting_on": "Передача на {{channel}}", - "transmission_ended": "Передачу завершено", - "voice_disabled": "Голос вимкнено", - "disconnected": "Відключено", - "select_channel": "Вибрати канал", - "select_channel_description": "Виберіть голосовий канал для підключення", - "change_channel_warning": "Вибір нового каналу відключить від поточного", - "default_channel": "За замовчуванням", - "no_channels_available": "Голосові канали недоступні", - "system_update": "Системне оновлення", - "data_refreshed": "Дані оновлено з сервера", - "call_selected": "Виклик вибрано", - "unit_selected": "Підрозділ вибрано", - "unit_deselected": "Вибір підрозділу скасовано", - "personnel_selected": "Персонал вибрано", - "personnel_deselected": "Вибір персоналу скасовано", - "loading_map": "Завантаження карти...", - "map_not_available_web": "Карта недоступна на веб-платформі", - "filtering_by_call": "Фільтрація за викликом", - "clear_filter": "Очистити фільтр", - "call_filter_active": "Фільтр виклику активний", - "call_filter_cleared": "Фільтр виклику очищено", - "showing_all_data": "Показано всі дані", - "call_notes": "Нотатки виклику", - "no_call_notes": "Немає нотаток виклику", - "add_call_note_placeholder": "Додати нотатку...", - "note_added": "Нотатку додано", - "note_added_to_console": "Нову нотатку додано до консолі", - "add_note_title": "Додати нову нотатку", - "note_title_label": "Заголовок", - "note_title_placeholder": "Введіть заголовок нотатки...", - "note_category_label": "Категорія", - "note_category_placeholder": "Виберіть категорію", - "note_no_category": "Без категорії", - "note_body_label": "Зміст нотатки", - "note_body_placeholder": "Введіть зміст нотатки...", - "note_save_error": "Не вдалося зберегти нотатку: {{error}}", - "note_created": "Нотатку створено", - "units_on_call": "Підрозділи на виклику", - "no_units_on_call": "Немає підрозділів на виклику", - "personnel_on_call": "Персонал на виклику", - "no_personnel_on_call": "Немає персоналу на виклику", - "call_activity": "Активність виклику", - "no_call_activity": "Немає активності виклику", - "on_call": "На виклику", - "filtered": "Відфільтровано", - "active_filter": "Активний фільтр", - "unit_status_change": "Зміна статусу підрозділу", - "personnel_status_change": "Зміна статусу персоналу", - "view_call_details": "Переглянути деталі виклику", - "dispatched_resources": "Диспетчеризовано", - "unassigned": "Не призначено", - "available": "Доступний", - "unknown": "Невідомо", - "search_personnel_placeholder": "Пошук персоналу...", - "search_calls_placeholder": "Пошук викликів...", - "search_units_placeholder": "Пошук підрозділів...", - "search_notes_placeholder": "Пошук нотаток...", - "signalr_update": "Оновлення в реальному часі", - "signalr_connected": "Підключено", - "realtime_updates_active": "Оновлення в реальному часі тепер активні", - "personnel_status_updated": "Статус персоналу оновлено", - "personnel_staffing_updated": "Укомплектованість персоналу оновлено", - "unit_status_updated": "Статус підрозділу оновлено", - "calls_updated": "Виклики оновлено", - "call_added": "Новий виклик додано", - "call_closed": "Виклик закрито", - "check_ins": "Перевірки", - "no_check_ins": "Немає викликів з таймерами перевірки", - "radio_log": "Радіожурнал", - "radio": "Радіо", - "activity": "Активність", - "actions": "Дії", - "no_radio_activity": "Немає радіопередач", - "live": "НАЖИВО", - "currently_transmitting": "Зараз передає...", - "duration": "Тривалість", - "call_actions": "Дії з викликом", - "unit_actions": "Дії з підрозділом", - "personnel_actions": { - "title": "Дії з персоналом", - "status_tab": "Статус", - "staffing_tab": "Укомплектованість", - "select_status": "Вибрати статус", - "select_staffing": "Вибрати рівень укомплектованості", - "destination": "Призначення", - "no_destination": "Без призначення", - "note": "Нотатка", - "note_placeholder": "Додайте необов'язкову нотатку...", - "update_status": "Оновити статус", - "update_staffing": "Оновити укомплектованість", - "no_statuses_available": "Статуси недоступні", - "no_staffings_available": "Рівні укомплектованості недоступні" - }, - "unit_actions_panel": { - "status": "Статус", - "select_status": "Вибрати статус", - "destination": "Призначення", - "no_destination": "Без призначення", - "note": "Нотатка", - "note_placeholder": "Додайте необов'язкову нотатку...", - "update_status": "Оновити статус", - "no_statuses_available": "Статуси недоступні", - "no_active_calls": "Немає активних викликів", - "no_stations_available": "Станції недоступні", - "no_destinations_available": "Призначення недоступні" - }, - "call": "Виклик", - "station": "Станція", - "calls": "Виклики", - "stations": "Станції", - "no_stations_available": "Станції недоступні", - "new_call": "Новий виклик", - "view_details": "Деталі", - "add_note": "Додати нотатку", - "close_call": "Закрити", - "set_status": "Встановити статус", - "set_staffing": "Укомплектованість", - "dispatch": "Диспетчеризувати", - "select_items_for_actions": "Виберіть виклик, підрозділ або персонал для увімкнення контекстних дій", - "weather": { - "clear": "Ясно", - "mainly_clear": "Переважно ясно", - "partly_cloudy": "Мінлива хмарність", - "overcast": "Хмарно", - "fog": "Туман", - "drizzle": "Мряка", - "freezing_drizzle": "Крижана мряка", - "rain": "Дощ", - "freezing_rain": "Крижаний дощ", - "snow": "Сніг", - "rain_showers": "Зливи", - "snow_showers": "Снігопади", - "thunderstorm": "Гроза", - "thunderstorm_hail": "Гроза з градом", - "unknown": "Невідомо" - }, - "available_only": "Лише доступні", - "single_list": "Єдиний список", - "resources": "Ресурси", - "search_resources_placeholder": "Пошук ресурсів...", - "no_resources": "Немає ресурсів" - }, "tabs": { "calls": "Виклики", "calendar": "Календар", @@ -1096,44 +1341,6 @@ "shifts": "Зміни", "personnel": "Персонал" }, - "check_in": { - "tab_title": "Перевірка", - "timer_status": "Статус таймера", - "perform_check_in": "Відмітитися", - "check_in_success": "Перевірку успішно зафіксовано", - "check_in_error": "Не вдалося зафіксувати перевірку", - "checked_in_by": "від {{name}}", - "last_check_in": "Остання перевірка", - "elapsed": "Минуло", - "duration": "Тривалість", - "status_ok": "OK", - "status_green": "OK", - "status_warning": "Попередження", - "status_yellow": "Попередження", - "status_overdue": "Прострочено", - "status_red": "Прострочено", - "status_critical": "Критично", - "history": "Історія перевірок", - "no_timers": "Таймери перевірки не налаштовано", - "timers_disabled": "Таймери перевірки вимкнено для цього виклику", - "type_personnel": "Персонал", - "type_unit": "Підрозділ", - "type_ic": "Керівник інциденту", - "type_par": "PAR", - "type_hazmat": "Хімічне ураження", - "type_sector_rotation": "Ротація секторів", - "type_rehab": "Реабілітація", - "add_note": "Додати нотатку (необов'язково)", - "confirm": "Підтвердити перевірку", - "minutes_ago": "{{count}} хв тому", - "select_target": "Виберіть об'єкт для перевірки", - "overdue_count": "{{count}} прострочено", - "warning_count": "{{count}} попередження", - "enable_timers": "Увімкнути таймери", - "disable_timers": "Вимкнути таймери", - "summary": "{{overdue}} прострочено, {{warning}} попередження, {{ok}} ок", - "par_title": "Облік персоналу (PAR)" - }, "units": { "search": "Пошук підрозділів...", "loading": "Завантаження підрозділів...", @@ -1163,6 +1370,63 @@ "no_destination": "Немає", "title": "Підрозділи" }, + "videoFeeds": { + "title": "Відеопотоки", + "noFeeds": "Немає відеопотоків для цього виклику", + "addFeed": "Додати відеопотік", + "editFeed": "Редагувати відеопотік", + "deleteFeed": "Видалити відеопотік", + "deleteConfirm": "Ви впевнені, що хочете видалити цей відеопотік?", + "watch": "Дивитися", + "goLive": "Почати трансляцію", + "stopLive": "Зупинити трансляцію", + "flipCamera": "Перемкнути камеру", + "feedAdded": "Відеопотік додано", + "feedUpdated": "Відеопотік оновлено", + "feedDeleted": "Відеопотік видалено", + "feedError": "Не вдалося завантажити відеопотік", + "unsupportedFormat": "Цей формат потоку не підтримується на мобільному пристрої", + "copyUrl": "Копіювати URL", + "form": { + "name": "Назва потоку", + "namePlaceholder": "напр., Дрон Пожежна машина 1", + "url": "URL потоку", + "urlPlaceholder": "напр., https://stream.example.com/live.m3u8", + "feedType": "Тип камери", + "feedFormat": "Формат потоку", + "description": "Опис", + "descriptionPlaceholder": "Необов'язковий опис", + "status": "Статус", + "sortOrder": "Порядок сортування", + "cameraLocation": "Розташування камери", + "useCurrentLocation": "Використати поточне місцезнаходження" + }, + "type": { + "drone": "Дрон", + "fixedCamera": "Стаціонарна камера", + "bodyCam": "Натільна камера", + "trafficCam": "Камера дорожнього руху", + "weatherCam": "Метеокамера", + "satelliteFeed": "Супутниковий потік", + "webCam": "Вебкамера", + "other": "Інше" + }, + "format": { + "rtsp": "RTSP", + "hls": "HLS", + "mjpeg": "MJPEG", + "youtubeLive": "YouTube Live", + "webrtc": "WebRTC", + "dash": "DASH", + "embed": "Вбудований", + "other": "Інше" + }, + "status": { + "active": "Активний", + "inactive": "Неактивний", + "error": "Помилка" + } + }, "weatherAlerts": { "title": "Погодні попередження", "activeAlerts": "Активні попередження", @@ -1270,251 +1534,5 @@ }, "stats_label": "Погодні попередження" }, - "videoFeeds": { - "title": "Відеопотоки", - "noFeeds": "Немає відеопотоків для цього виклику", - "addFeed": "Додати відеопотік", - "editFeed": "Редагувати відеопотік", - "deleteFeed": "Видалити відеопотік", - "deleteConfirm": "Ви впевнені, що хочете видалити цей відеопотік?", - "watch": "Дивитися", - "goLive": "Почати трансляцію", - "stopLive": "Зупинити трансляцію", - "flipCamera": "Перемкнути камеру", - "feedAdded": "Відеопотік додано", - "feedUpdated": "Відеопотік оновлено", - "feedDeleted": "Відеопотік видалено", - "feedError": "Не вдалося завантажити відеопотік", - "unsupportedFormat": "Цей формат потоку не підтримується на мобільному пристрої", - "copyUrl": "Копіювати URL", - "form": { - "name": "Назва потоку", - "namePlaceholder": "напр., Дрон Пожежна машина 1", - "url": "URL потоку", - "urlPlaceholder": "напр., https://stream.example.com/live.m3u8", - "feedType": "Тип камери", - "feedFormat": "Формат потоку", - "description": "Опис", - "descriptionPlaceholder": "Необов'язковий опис", - "status": "Статус", - "sortOrder": "Порядок сортування", - "cameraLocation": "Розташування камери", - "useCurrentLocation": "Використати поточне місцезнаходження" - }, - "type": { - "drone": "Дрон", - "fixedCamera": "Стаціонарна камера", - "bodyCam": "Натільна камера", - "trafficCam": "Камера дорожнього руху", - "weatherCam": "Метеокамера", - "satelliteFeed": "Супутниковий потік", - "webCam": "Вебкамера", - "other": "Інше" - }, - "format": { - "rtsp": "RTSP", - "hls": "HLS", - "mjpeg": "MJPEG", - "youtubeLive": "YouTube Live", - "webrtc": "WebRTC", - "dash": "DASH", - "embed": "Вбудований", - "other": "Інше" - }, - "status": { - "active": "Активний", - "inactive": "Неактивний", - "error": "Помилка" - } - }, - "welcome": "Ласкаво просимо до додатку obytes", - "incident_command": { - "tab_title": "Командування", - "title": "Командування інцидентом", - "open_full_board": "Відкрити повну панель", - "no_command": "Командування інцидентом не встановлено", - "no_command_description": "Встановіть командування інцидентом, щоб координувати ресурси, ролі, цілі та облік персоналу для цього виклику.", - "establish": "Встановити командування", - "establish_title": "Встановити командування інцидентом", - "establish_description": "За бажанням створіть панель командування із шаблону.", - "establish_success": "Командування інцидентом встановлено", - "establish_error": "Не вдалося встановити командування", - "template": "Шаблон", - "no_template": "Без шаблону (порожня панель)", - "saved": "Збережено", - "save_error": "Не вдалося виконати операцію", - "edit_action_plan": "Редагувати план дій", - "action_plan": "План дій", - "action_plan_placeholder": "Опишіть план дій під час інциденту...", - "save": "Зберегти", - "no_action_plan": "План дій не задано.", - "add": "Додати", - "add_objective": "Додати ціль", - "objective_name": "Ціль", - "objective_type": "Тип", - "name_required": "Ім’я є обов’язковим", - "add_lane": "Додати сектор", - "lane_name": "Назва сектора", - "lane_type": "Тип сектора", - "assign_resource": "Призначити ресурс", - "assign_resource_required": "Виберіть сектор і ресурс", - "lane": "Сектор", - "select_lane": "Виберіть сектор", - "resource_type": "Тип ресурсу", - "resource": "Ресурс", - "select_resource": "Виберіть ресурс", - "unit": "Підрозділ", - "personnel": "Персонал", - "assign": "Призначити", - "assign_role": "Призначити роль", - "assign_role_required": "Виберіть особу та роль", - "person": "Особа", - "select_person": "Виберіть особу", - "role": "Роль", - "select_role": "Виберіть роль", - "transfer_title": "Передати командування", - "transfer_notes": "Примітки", - "transfer": "Передати", - "transfer_command": "Передати", - "transfer_success": "Командування передано", - "close_command": "Завершити командування", - "confirm_close": "Завершити командування інцидентом для цього виклику?", - "status": "Стан", - "active": "Активне", - "closed": "Завершене", - "commander": "Керівник", - "established_on": "Встановлено", - "edit": "Редагувати", - "roles": "Ролі командування", - "no_roles": "Ролі не призначено.", - "structure": "Структура командування", - "no_lanes": "Сектори не визначено.", - "no_resources": "Ресурси не призначено.", - "release": "Звільнити", - "objectives": "Цілі", - "no_objectives": "Цілей немає.", - "complete": "Завершити", - "completed": "Завершено", - "timers": "Таймери", - "no_timers": "Немає активних таймерів.", - "acknowledge": "Підтвердити", - "due": "Термін", - "accountability": "Облік персоналу (PAR)", - "run_par": "Провести PAR", - "green": "Зелений", - "warning": "Попередження", - "critical": "Критичний", - "no_accountability": "Персонал не відстежується.", - "timeline": "Хронологія командування", - "no_timeline": "Записів у хронології немає.", - "unassigned": "Не призначено", - "active_title": "Активні командування інцидентами", - "no_active": "Немає активних командувань інцидентами", - "no_active_description": "Тут відображатимуться командування, встановлені для викликів.", - "call": "Виклик", - "tactical_map": "Тактична карта", - "annotations": "Позначки на карті", - "no_annotations": "Позначок немає.", - "open_tactical_map": "Відкрити тактичну карту", - "marker": "Маркер", - "add_marker": "Додати маркер", - "tap_to_place": "Торкніться карти, щоб установити маркер", - "marker_label": "Підпис маркера", - "delete_annotation_confirm": "Видалити цю позначку?", - "voice_channels": "Голосові канали", - "no_channels": "Немає відкритих каналів.", - "add_channel": "Додати канал", - "channel_name": "Назва каналу", - "close_all_channels": "Закрити всі канали", - "join": "Приєднатися", - "hold_to_talk": "Утримуйте, щоб говорити", - "talking": "Передавання...", - "voice_joined": "Приєднано до голосового каналу", - "voice_join_error": "Не вдалося приєднатися до голосового каналу", - "move": "Перемістити", - "move_lane": "Перемістити сектор", - "parent_lane": "Батьківський сектор", - "top_level": "Верхній рівень", - "move_resource": "Перемістити ресурс" - }, - "chat": { - "title": "Чат", - "assistant": "Асистент", - "empty": "Ще немає розмов. Розпочніть пряме повідомлення або створіть групу.", - "section_assistant": "Асистент", - "section_direct_messages": "Прямі повідомлення", - "section_channels": "Канали", - "section_incidents": "Інциденти", - "new_direct_message": "Нове пряме повідомлення", - "new_group": "Нова група", - "open_assistant": "Відкрити асистента", - "create_conversation_failed": "Не вдалося розпочати розмову", - "group_name": "Назва групи", - "search_people": "Пошук людей", - "no_people": "Людей не знайдено", - "create_group_with": "Створити групу ({{count}})", - "message_deleted": "Це повідомлення видалено", - "urgent": "Терміново", - "urgent_will_send": "Це повідомлення буде надіслано як термінове", - "shared_location": "Надіслане місцезнаходження", - "thread_replies": "{{count}} відповідей", - "edited": "(відредаговано)", - "failed_tap_retry": "Помилка – торкніться, щоб повторити", - "type_a_message": "Введіть повідомлення", - "emoji": "Емодзі", - "add_image": "Додати зображення", - "add_gif": "Додати GIF", - "share_location": "Поділитися місцезнаходженням", - "send": "Надіслати", - "someone": "Хтось", - "is_typing": "{{name}} пише...", - "are_typing": "{{count}} людей пишуть...", - "permission_photos_denied": "Доступ до фотогалереї відхилено", - "permission_location_denied": "Доступ до місцезнаходження відхилено", - "search_gifs": "Пошук GIF", - "no_gifs": "GIF не знайдено", - "flag_reason": "Чому ви це повідомляєте?", - "flag_inappropriate": "Неприйнятне", - "flag_harassment": "Переслідування", - "flag_spam": "Спам", - "flag_sensitive": "Конфіденційна інформація", - "flag_policy": "Порушення правил", - "flag_other": "Інше", - "reply_in_thread": "Відповісти в гілці", - "copy": "Копіювати", - "copied": "Скопійовано", - "copy_unavailable": "Копіювання недоступне на цьому пристрої", - "edit": "Редагувати", - "edit_message": "Редагувати повідомлення", - "save": "Зберегти", - "delete": "Видалити", - "pin": "Закріпити", - "unpin": "Відкріпити", - "flag": "Поскаржитися", - "moderator_delete": "Видалити (модератор)", - "moderator_removed": "Видалено модератором", - "attachment_failed": "Не вдалося завантажити вкладення", - "ack_required": "Потрібне підтвердження", - "ack_pending_one": "У вас є термінове повідомлення для підтвердження", - "ack_pending_count": "У вас {{count}} термінових повідомлень для підтвердження", - "acknowledge": "Підтвердити", - "thread": "Гілка", - "original_message": "Оригінальне повідомлення", - "reply_placeholder": "Відповісти...", - "channel": "Канал", - "direct_message": "Пряме повідомлення", - "load_people_failed": "Не вдалося завантажити людей", - "reaction_failed": "Не вдалося оновити реакцію", - "edit_failed": "Не вдалося відредагувати повідомлення", - "delete_failed": "Не вдалося видалити повідомлення", - "pin_failed": "Не вдалося оновити закріплення", - "flag_failed": "Не вдалося поскаржитися на повідомлення" - }, - "chatbot": { - "title": "Асистент", - "subtitle": "ШІ-помічник для вашого підрозділу", - "new_session": "Нова сесія", - "empty": "Запитайте асистента про будь-що, щоб почати.", - "ask_placeholder": "Запитайте асистента..." - } + "welcome": "Ласкаво просимо до додатку obytes" }