diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/notification-history.tsx b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/notification-history.tsx new file mode 100644 index 00000000000..a7a5f7ebbdc --- /dev/null +++ b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/notification-history.tsx @@ -0,0 +1,5 @@ +import { NotificationHistoryScreen } from '@/components/notification-history-screen'; + +export default function NotificationHistoryRoute() { + return ; +} diff --git a/apps/mobile/src/components/notification-history-row.tsx b/apps/mobile/src/components/notification-history-row.tsx new file mode 100644 index 00000000000..b51d68de76b --- /dev/null +++ b/apps/mobile/src/components/notification-history-row.tsx @@ -0,0 +1,45 @@ +import { type inferRouterOutputs, type MobileRouter } from '@kilocode/trpc/mobile'; +import { useTranslation } from 'react-i18next'; +import { Pressable, View } from 'react-native'; + +import { Text } from '@/components/ui/text'; +import { notificationHistoryCopy } from '@/lib/notification-history'; +import { parseTimestamp, timeAgo } from '@/lib/utils'; + +type HistoryPage = inferRouterOutputs['user']['getNotificationHistory']; +export type HistoryRow = HistoryPage['items'][number]; + +/** One recorded notification: the copy a push showed, and how long ago. */ +export function HistoryRowView({ + row, + now, + onOpen, +}: Readonly<{ + row: HistoryRow; + now: number; + onOpen: (payload: Record) => void; +}>) { + const { i18n } = useTranslation(); + const copy = notificationHistoryCopy(row, i18n.language); + return ( + { + onOpen(row.payload); + }} + > + + + {copy.title} + + + {timeAgo(parseTimestamp(row.createdAt), undefined, now)} + + + + {copy.body} + + + ); +} diff --git a/apps/mobile/src/components/notification-history-screen.mounted.test.tsx b/apps/mobile/src/components/notification-history-screen.mounted.test.tsx new file mode 100644 index 00000000000..65519b30654 --- /dev/null +++ b/apps/mobile/src/components/notification-history-screen.mounted.test.tsx @@ -0,0 +1,454 @@ +/* eslint-disable max-lines -- one mounted harness: the paged list, its states, the chip filter, the row tap and the push banner share one fake tRPC surface */ +import { createElement, type ReactElement } from 'react'; +import { onlineManager } from '@tanstack/react-query'; +import { act, type ReactTestInstance } from '@/test/renderer'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { NotificationHistoryScreen } from '@/components/notification-history-screen'; +import { renderWithProviders, waitFor } from '@/test/render-with-providers'; + +type Row = { + id: string; + category: string | null; + pushType: string; + payload: Record; + i18nKey: string | null; + i18nParams: Record | null; + title: string; + body: string; + createdAt: string; +}; + +const HISTORY_PATH = ['user', 'getNotificationHistory']; + +const NOW = Date.parse('2026-10-24T10:00:00.000Z'); + +type PreferencesFixture = { capabilities: Record }; +type GateFixture = { + loading: boolean; + settled: boolean; + permissionGranted: boolean; + pushEnabled: boolean; +}; + +const state = vi.hoisted(() => { + const preferences: PreferencesFixture | undefined = { capabilities: {} }; + const gate: GateFixture = { + loading: false, + settled: true, + permissionGranted: true, + pushEnabled: true, + }; + return { + rows: [] as Row[], + historyError: undefined as Error | undefined, + preferencesError: undefined as Error | undefined, + preferences, + gate, + /** The inputs `infiniteQueryOptions` was built with, newest last. */ + historyInputs: [] as { category?: string }[], + /** The option objects the screen built, newest last. */ + historyOptions: [] as Record[], + /** Rows on page one while a fixture wants a second page. */ + pageSize: undefined as number | undefined, + /** A failure for a non-first page only. */ + pageError: undefined as Error | undefined, + /** The rendered list's paging hook, so a test can drive a next page. */ + listProps: undefined as { onEndReached?: () => void; onRefresh?: () => void } | undefined, + }; +}); + +const mocks = vi.hoisted(() => ({ + push: vi.fn(), + setPendingDeepLink: vi.fn(), + toastError: vi.fn(), +})); + +vi.mock('@shopify/flash-list', () => ({ + FlashList: ({ + data, + renderItem, + onEndReached, + onRefresh, + ListFooterComponent, + }: { + data: Row[]; + renderItem: (info: { item: Row }) => ReactElement; + onEndReached?: () => void; + onRefresh?: () => void; + ListFooterComponent?: ReactElement; + }) => { + state.listProps = { onEndReached, onRefresh }; + return createElement( + 'FlashList', + null, + data.map(item => createElement('Cell', { key: item.id }, renderItem({ item }))), + ListFooterComponent ?? null + ); + }, +})); + +vi.mock('react-native', () => ({ + View: 'View', + Pressable: 'Pressable', + ScrollView: 'ScrollView', +})); + +vi.mock('expo-router', () => ({ useRouter: () => ({ push: mocks.push }) })); +vi.mock('sonner-native', () => ({ toast: { error: mocks.toastError } })); +vi.mock('@/components/ui/icons', () => ({ BellOff: 'BellOff' })); +vi.mock('@/components/screen-header', () => ({ ScreenHeader: 'ScreenHeader' })); +vi.mock('@/components/empty-state', () => ({ EmptyState: 'EmptyState' })); +vi.mock('@/components/query-error', () => ({ QueryError: 'QueryError' })); +vi.mock('@/components/ui/button', () => ({ Button: 'Button' })); +vi.mock('@/components/ui/refresh-control', () => ({ RefreshControl: 'RefreshControl' })); +vi.mock('@/components/ui/skeleton', () => ({ Skeleton: 'Skeleton' })); +vi.mock('@/components/ui/text', () => ({ Text: 'Text' })); +vi.mock('@/components/tab-screen', () => ({ useTabBarBottomPadding: () => 24 })); +vi.mock('@/lib/auth/auth-context', () => ({ useAuth: () => ({ token: 'jwt' }) })); +vi.mock('@/lib/hooks/use-now-ticker', () => ({ useNowTicker: () => NOW })); +vi.mock('@/lib/hooks/use-notification-master-gate', () => ({ + useNotificationMasterGate: () => state.gate, +})); +vi.mock('@/lib/hooks/use-theme-colors', () => ({ + useThemeColors: () => ({ mutedForeground: '#71717a' }), +})); +vi.mock('@/lib/deep-link-launch', () => ({ + setPendingDeepLink: mocks.setPendingDeepLink, +})); +vi.mock('@/lib/notification-actions', () => ({ + pendingDeepLinkOptionsForData: () => ({ sessionBound: false }), +})); +vi.mock('@/lib/trpc', () => ({ + useTRPC: () => ({ + user: { + getNotificationHistory: { + // tRPC's real shapes: the path is the first element, and only the path + // prefix matches every variant of the read. + pathKey: () => [HISTORY_PATH], + infiniteQueryKey: (input: { category?: string }) => [ + HISTORY_PATH, + { input, type: 'infinite' }, + ], + infiniteQueryOptions: (input: { category?: string }, options: Record) => { + state.historyInputs.push(input); + state.historyOptions.push(options); + return { + queryKey: [HISTORY_PATH, { input, type: 'infinite' }], + initialPageParam: undefined, + queryFn: ({ pageParam }: { pageParam: string | undefined }) => { + if (pageParam !== undefined && state.pageError) { + throw state.pageError; + } + if (state.historyError) { + throw state.historyError; + } + const items = state.rows.filter( + entry => input.category === undefined || entry.category === input.category + ); + if (state.pageSize === undefined) { + return { items, nextCursor: null }; + } + if (pageParam === undefined) { + return { + items: items.slice(0, state.pageSize), + nextCursor: items.length > state.pageSize ? 'page-2' : null, + }; + } + return { items: items.slice(state.pageSize), nextCursor: null }; + }, + ...options, + }; + }, + }, + getNotificationPreferences: { + queryOptions: () => ({ + queryKey: ['user', 'getNotificationPreferences'], + queryFn: () => { + if (state.preferencesError) { + throw state.preferencesError; + } + return state.preferences; + }, + }), + }, + }, + }), +})); + +function row(overrides: Partial = {}): Row { + return { + id: 'row-1', + category: 'balanceAlerts', + pushType: 'low_balance', + payload: { type: 'low_balance', organizationId: 'org-acme' }, + i18nKey: 'internal.lowBalance', + i18nParams: { organizationName: 'Acme', minimumBalanceUsd: '5' }, + title: 'stored title', + body: 'stored body', + createdAt: new Date(NOW - 5 * 60_000).toISOString(), + ...overrides, + }; +} + +let view: Awaited> | undefined = undefined; + +async function mount(): Promise>> { + view = await renderWithProviders(); + await waitFor(() => view?.renderer.root.findAllByType('Skeleton').length === 0); + return view; +} + +function byType(type: string) { + const root = view?.renderer.root; + if (!root) { + throw new Error('screen not mounted'); + } + return root.findAll(node => node.type === type); +} + +/** Press a control; a missing control fails the test instead of a no-op. */ +function press(node: ReactTestInstance | undefined) { + if (!node) { + throw new Error('control not mounted'); + } + act(() => { + (node.props.onPress as () => void)(); + }); +} + +/** The screen's button props, read once so the chip assertions stay typed. */ +type ChipProps = { role?: string; accessibilityState?: { selected?: boolean } }; + +function chipProps(node: ReactTestInstance | undefined): ChipProps | undefined { + return node?.props as ChipProps | undefined; +} + +/** The footer's bottom padding, read once so the clearance assertion stays typed. */ +function footerPadding(node: ReactTestInstance): number | undefined { + return (node.props as { style?: { paddingBottom?: number } }).style?.paddingBottom; +} + +/** The node whose `Text` child holds `value`. */ +function withText(nodes: ReactTestInstance[], value: string) { + return nodes.find( + node => + node.findAll( + child => + typeof child.type === 'string' && + (child.type as string) === 'Text' && + child.props.children === value + ).length > 0 + ); +} + +beforeEach(() => { + vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); + vi.stubGlobal('__DEV__', false); + state.rows = []; + state.historyError = undefined; + state.preferencesError = undefined; + state.preferences = { capabilities: {} }; + state.gate = { loading: false, settled: true, permissionGranted: true, pushEnabled: true }; + state.historyInputs = []; + state.historyOptions = []; + state.pageSize = undefined; + state.pageError = undefined; + state.listProps = undefined; + vi.clearAllMocks(); +}); + +afterEach(() => { + onlineManager.setOnline(true); + view?.unmount(); + view = undefined; + vi.unstubAllGlobals(); +}); + +describe('NotificationHistoryScreen', () => { + it('renders each row from the stored catalog key in the app language', async () => { + state.rows = [row()]; + await mount(); + + const texts = byType('Text').map(node => node.props.children); + expect(texts).toContain('Low balance alert'); + expect(texts).toContain('Acme balance fell below $5'); + expect(texts).toContain('5 minutes ago'); + expect(texts).not.toContain('stored title'); + }); + + it('filters the list by the tapped chip', async () => { + state.rows = [row(), row({ id: 'row-2', category: 'chatMessages', pushType: 'chat.message' })]; + await mount(); + expect(byType('Cell')).toHaveLength(2); + + const chatChip = withText(byType('Button'), 'Chat messages'); + press(chatChip); + + await waitFor(() => byType('Cell').length === 1); + expect(state.historyInputs.at(-1)).toMatchObject({ category: 'chatMessages' }); + }); + + it('opens a row through the pending deep link slot, not the router', async () => { + state.rows = [row()]; + await mount(); + + const pressable = byType('Pressable')[0]; + press(pressable); + + expect(mocks.setPendingDeepLink).toHaveBeenCalledWith( + '/(app)/(tabs)/(3_profile)/organization/credit-activity?org=org-acme', + 'notification', + { sessionBound: false } + ); + expect(mocks.push).not.toHaveBeenCalled(); + }); + + it('keeps the list and reports a row whose stored payload no longer resolves', async () => { + state.rows = [row({ payload: { type: 'retired_kind' } })]; + await mount(); + + const pressable = byType('Pressable')[0]; + press(pressable); + + expect(mocks.toastError).toHaveBeenCalledWith('This notification no longer opens anywhere.'); + expect(mocks.setPendingDeepLink).not.toHaveBeenCalled(); + expect(byType('Cell')).toHaveLength(1); + }); + + it('states the retention window when the account has no rows', async () => { + await mount(); + + const empty = byType('EmptyState')[0]; + expect(empty?.props).toMatchObject({ + title: 'No notifications yet', + description: 'Notifications this account receives appear here. They are kept for 90 days.', + }); + }); + + it('offers a retry when the first page fails', async () => { + state.historyError = new Error('offline'); + await mount(); + + const failure = byType('QueryError')[0]; + expect(failure?.props).toMatchObject({ message: 'Could not load notification history.' }); + expect(typeof failure?.props.onRetry).toBe('function'); + }); + + it('shows the settings banner while the push gate is closed and still loads the list', async () => { + state.gate = { loading: false, settled: true, permissionGranted: false, pushEnabled: false }; + state.rows = [row()]; + await mount(); + + const banner = withText(byType('Button'), 'Notification settings'); + expect(banner).toBeDefined(); + expect(byType('Cell')).toHaveLength(1); + + press(banner); + expect(mocks.push).toHaveBeenCalledWith('/(app)/notifications'); + }); + + it('shows no banner while push reaches the device', async () => { + state.rows = [row()]; + await mount(); + + const texts = byType('Text').map(node => node.props.children); + expect(texts).not.toContain('Notification settings'); + }); + + it('announces the chip row as a tab list with the selected chip marked', async () => { + state.rows = [row({ category: 'chatMessages', pushType: 'chat.message' })]; + await mount(); + + const chip = withText(byType('Button'), 'Chat messages'); + expect(chipProps(chip)).toMatchObject({ + role: 'tab', + accessibilityState: { selected: false }, + }); + + press(chip); + await waitFor( + () => + chipProps(withText(byType('Button'), 'Chat messages'))?.accessibilityState?.selected === + true + ); + }); + + it('says a category has nothing rather than that the account has nothing', async () => { + state.rows = [row({ category: 'balanceAlerts' })]; + await mount(); + press(withText(byType('Button'), 'Chat messages')); + await waitFor(() => byType('EmptyState').length === 1); + + const empty = byType('EmptyState')[0]; + expect(empty?.props).toMatchObject({ title: 'No notifications in this category' }); + // The retention window describes the whole list, not one empty category. + expect(empty?.props.description).toBeUndefined(); + }); + + it('collapses retained pages on a refresh instead of re-reading their cursors', async () => { + state.pageSize = 3; + state.rows = [1, 2, 3, 4, 5].map(index => + row({ id: `row-${index}`, title: `stored ${index}` }) + ); + await mount(); + expect(byType('Cell')).toHaveLength(3); + + act(() => { + state.listProps?.onEndReached?.(); + }); + await waitFor(() => byType('Cell').length === 5); + + act(() => { + state.listProps?.onRefresh?.(); + }); + await waitFor(() => byType('Cell').length === 3); + }); + + it('reports a refresh that fails instead of leaving the list silent', async () => { + state.rows = [row()]; + await mount(); + + state.historyError = new Error('offline'); + act(() => { + state.listProps?.onRefresh?.(); + }); + + await waitFor(() => mocks.toastError.mock.calls.length > 0); + expect(mocks.toastError).toHaveBeenCalledWith("Couldn't refresh. Pull down to try again."); + }); + + it('keeps the tab-bar clearance under the next-page retry', async () => { + state.pageSize = 1; + state.rows = [row({ id: 'row-1' }), row({ id: 'row-2' })]; + await mount(); + + state.pageError = new Error('paging failed'); + act(() => { + state.listProps?.onEndReached?.(); + }); + await waitFor(() => withText(byType('Button'), 'Retry loading more') !== undefined); + + // 24dp of tab bar plus the footer's own 32dp. + const footer = byType('View').find(node => footerPadding(node) === 56); + expect(footer).toBeDefined(); + }); + + it('settles the chip row offline instead of holding its skeletons', async () => { + // The online manager reports offline, so a read that keeps the default + // `networkMode: 'online'` pauses in `pending` and never renders a chip. + onlineManager.setOnline(false); + state.preferencesError = new Error('offline'); + await mount(); + + expect(withText(byType('Button'), 'All')).toBeDefined(); + expect(withText(byType('Button'), 'Chat messages')).toBeDefined(); + }); + + it('turns off the network gate so an offline first load can fail into its retry', async () => { + state.rows = [row()]; + await mount(); + + expect(state.historyOptions.at(-1)).toMatchObject({ networkMode: 'always' }); + }); +}); diff --git a/apps/mobile/src/components/notification-history-screen.tsx b/apps/mobile/src/components/notification-history-screen.tsx new file mode 100644 index 00000000000..8b4579be64e --- /dev/null +++ b/apps/mobile/src/components/notification-history-screen.tsx @@ -0,0 +1,348 @@ +import { FlashList } from '@shopify/flash-list'; +import { useInfiniteQuery, useQuery, useQueryClient } from '@tanstack/react-query'; +import { type Href, useRouter } from 'expo-router'; +import { useCallback, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { ScrollView, View, type ViewStyle } from 'react-native'; +import { toast } from 'sonner-native'; + +import { NOTIFICATION_HISTORY_RETENTION_DAYS } from '@kilocode/notifications'; + +import { EmptyState } from '@/components/empty-state'; +import { QueryError } from '@/components/query-error'; +import { ScreenHeader } from '@/components/screen-header'; +import { RefreshControl } from '@/components/ui/refresh-control'; +import { BellOff } from '@/components/ui/icons'; +import { Button } from '@/components/ui/button'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Text } from '@/components/ui/text'; +import { useAuth } from '@/lib/auth/auth-context'; +import { setPendingDeepLink } from '@/lib/deep-link-launch'; +import { pendingDeepLinkOptionsForData } from '@/lib/notification-actions'; +import { + NOTIFICATION_HISTORY_FILTERS, + type NotificationHistoryFilter, + notificationHistoryFilters, + notificationHistoryTarget, +} from '@/lib/notification-history'; +import { useNotificationMasterGate } from '@/lib/hooks/use-notification-master-gate'; +import { type HistoryRow, HistoryRowView } from '@/components/notification-history-row'; +import { useNowTicker } from '@/lib/hooks/use-now-ticker'; +import { useTabBarBottomPadding } from '@/components/tab-screen'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; +import { + hasRetentionBudget, + reconcileFirstPage, + withInfiniteRetention, +} from '@/lib/query/infinite-retention'; +import { useTRPC } from '@/lib/trpc'; + +/** Rows requested per page; the server defaults to the same 30. */ +const PAGE_SIZE = 30; +/** How often a row's relative time is re-read while the screen stays open. */ +const RELATIVE_TIME_TICK_MS = 60_000; + +/** Retained pages; the bound refuses the next page rather than dropping the newest. */ +const MAX_PAGES = 10; + +const listStyle = { flex: 1 } satisfies ViewStyle; +const listContentContainerStyle = { paddingHorizontal: 16 } satisfies ViewStyle; + +/** One filter chip. Single-select, so the active one carries `selected`. */ +function FilterChip({ + label, + active, + onPress, +}: Readonly<{ label: string; active: boolean; onPress: () => void }>) { + return ( + + ); +} + +/** + * The pushes this account received, newest first: one row per recorded + * notification, rendered on the device from the catalog key and params the push + * carried, filtered by the same categories the settings screen toggles, and + * opening the destination a push tap opens. + * + * The list is independent of the push gate: a user whose pushes are off sees + * what arrived before they were, with a banner that leads to the switch. + */ +export function NotificationHistoryScreen() { + const trpc = useTRPC(); + const router = useRouter(); + const colors = useThemeColors(); + const { t } = useTranslation(); + const { token: authToken } = useAuth(); + const gate = useNotificationMasterGate(); + const now = useNowTicker(RELATIVE_TIME_TICK_MS); + const queryClient = useQueryClient(); + const paddingBottom = useTabBarBottomPadding(); + const [category, setCategory] = useState(null); + const [refreshing, setRefreshing] = useState(false); + + const preferences = useQuery({ + ...trpc.user.getNotificationPreferences.queryOptions(), + enabled: authToken != null, + // Same reason as the list: a read started offline must settle into its + // answer or its error instead of pausing in `pending`, which would hold the + // chip skeletons on screen for as long as the device stays offline. A + // settled failure falls back to every chip. + networkMode: 'always', + // `networkMode: 'always'` also drops the derived reconnect refetch. + refetchOnReconnect: true, + }); + + const query = useInfiniteQuery( + withInfiniteRetention( + trpc.user.getNotificationHistory.infiniteQueryOptions( + { category: category ?? undefined, limit: PAGE_SIZE }, + { + enabled: authToken != null, + // A newest-first list reaches its retention bound by refusing the + // next page: `maxPages` alone would drop index 0, which is the newest + // page. + getNextPageParam: (lastPage, pages) => + hasRetentionBudget(pages.length, MAX_PAGES) + ? (lastPage.nextCursor ?? undefined) + : undefined, + // A first load while offline must settle into the error state and its + // retry rather than pausing in `pending`, which would leave the + // skeleton on screen for as long as the device stays offline. + networkMode: 'always', + // `networkMode: 'always'` also drops the derived reconnect refetch, + // so ask for it back. + refetchOnReconnect: true, + } + ), + MAX_PAGES + ) + ); + + const flatItems = useMemo( + () => query.data?.pages.flatMap(page => page.items) ?? [], + [query.data?.pages] + ); + // `reconcileFirstPage` empties the cached pages before page one refetches, so + // the rows a refresh started from stay on screen until it delivers. Without + // the hold the list would flash its empty state and lose its scroll. + const [refreshHold, setRefreshHold] = useState(null); + const items = flatItems.length > 0 ? flatItems : (refreshHold ?? []); + const hasListContent = items.length > 0 || query.isFetchNextPageError; + // A query that is disabled never leaves `pending`; only a signed-in read can. + const loading = authToken != null && query.isPending && !query.isError; + const failure = !loading && query.isError && !hasListContent; + + const openRow = useCallback( + (payload: Record) => { + const target = notificationHistoryTarget(payload); + if (target === null) { + toast.error(t('notifications.history.openFailed')); + return; + } + // The same slot a notification response fills: the root layout navigates. + setPendingDeepLink(target.href, 'notification', pendingDeepLinkOptionsForData(target.data)); + }, + [t] + ); + + const renderItem = useCallback( + ({ item }: { item: HistoryRow }) => , + [now, openRow] + ); + + const handleRefresh = () => { + void (async () => { + setRefreshing(true); + setRefreshHold(items); + try { + // A retained page is a keyset window that only stays contiguous with + // the page above it while page one's newest row is unchanged. Rows that + // arrived since would move page one's tail past the older pages' + // cursors, so a refresh collapses the list and reloads from the newest + // row instead of re-requesting every retained window. + // + // Three different keys: the path resets every variant of this read, the + // infinite key is the entry the list observes, and `getQueryState` only + // answers an exact key. + const path = trpc.user.getNotificationHistory.pathKey(); + const key = trpc.user.getNotificationHistory.infiniteQueryKey({ + category: category ?? undefined, + limit: PAGE_SIZE, + }); + reconcileFirstPage(queryClient, path); + await queryClient.refetchQueries( + { queryKey: key, type: 'active' }, + // `cancelRefetch: false` awaits the page-one fetch the invalidate + // started instead of cancelling it and issuing a second one. + { cancelRefetch: false } + ); + if (queryClient.getQueryState(key)?.status === 'error') { + toast.error(t('common.couldNotRefresh')); + } + } finally { + setRefreshing(false); + setRefreshHold(null); + } + })(); + }; + const refreshControl = ; + + const filters = preferences.data + ? notificationHistoryFilters(preferences.data.capabilities) + : NOTIFICATION_HISTORY_FILTERS; + + // The page bound, not the server, ends the list: say so. + const capped = !query.hasNextPage && (query.data?.pages.at(-1)?.nextCursor ?? null) !== null; + let footer = ( + + {capped ? ( + + {t('notifications.history.capped')} + + ) : null} + + ); + if (query.isFetchNextPageError) { + footer = ( + + + + ); + } else if (query.isFetchingNextPage) { + footer = ; + } + + return ( + + + + {gate.settled && !gate.pushEnabled ? ( + + + + {t('notifications.history.bannerBody')} + + + + ) : null} + + {preferences.isPending && authToken != null ? ( + + + + + + ) : ( + + {filters.map(filter => ( + { + setCategory(filter.category); + }} + /> + ))} + + )} + + + {loading ? ( + + + + + + ) : null} + + {failure ? ( + { + void query.refetch(); + }} + isRetrying={query.isFetching} + refreshControl={refreshControl} + /> + ) : null} + + {!loading && !failure && !hasListContent ? ( + + ) : null} + + {!loading && !failure && hasListContent ? ( + + style={listStyle} + data={items} + keyExtractor={item => item.id} + renderItem={renderItem} + contentContainerStyle={listContentContainerStyle} + // FlashList adds its own control from this pair; the element + // below is for the state scrollers that accept one. + refreshing={refreshing} + onRefresh={handleRefresh} + onEndReached={() => { + // Once the next page has failed, auto-loading re-fires on every + // content-size change, so recovery waits for the footer's tap. + if (query.hasNextPage && !query.isFetchingNextPage && !query.isFetchNextPageError) { + void query.fetchNextPage(); + } + }} + onEndReachedThreshold={0.5} + ListFooterComponent={footer} + /> + ) : null} + + + + ); +} diff --git a/apps/mobile/src/components/preferences-screen.mounted.test.tsx b/apps/mobile/src/components/preferences-screen.mounted.test.tsx index 1e384568e0f..87cf66a1ccd 100644 --- a/apps/mobile/src/components/preferences-screen.mounted.test.tsx +++ b/apps/mobile/src/components/preferences-screen.mounted.test.tsx @@ -18,6 +18,7 @@ vi.mock('expo-router', () => ({ vi.mock('@/components/ui/icons', () => ({ Bell: 'Bell', Globe: 'Globe', + History: 'History', Mic: 'Mic', SlidersHorizontal: 'SlidersHorizontal', Wallet: 'Wallet', @@ -68,7 +69,7 @@ describe('PreferencesScreen hub', () => { it('renders one navigation row per settings group with its title and subtitle', async () => { const renderer = await mountPreferences(); - expect(hubRows(renderer)).toHaveLength(6); + expect(hubRows(renderer)).toHaveLength(7); expect(row(renderer, 'General').props).toMatchObject({ icon: 'SlidersHorizontal', last: true }); expect(row(renderer, 'Voice input').props).toMatchObject({ icon: 'Mic', @@ -92,6 +93,11 @@ describe('PreferencesScreen hub', () => { subtitle: 'Push preferences', last: true, }); + expect(row(renderer, i18n.t('notifications.history.title')).props).toMatchObject({ + icon: 'History', + subtitle: 'Notifications this account received', + last: true, + }); expect(row(renderer, i18n.t('notifications.channel.spend')).props).toMatchObject({ icon: 'Wallet', last: true, @@ -104,6 +110,7 @@ describe('PreferencesScreen hub', () => { ['Translate tool summaries', '/(app)/(tabs)/(3_profile)/tool-summary-translation'], ['Account', '/(app)/(tabs)/(3_profile)/account'], ['Notifications', '/(app)/notifications'], + [i18n.t('notifications.history.title'), '/(app)/(tabs)/(3_profile)/notification-history'], [i18n.t('notifications.channel.spend'), '/(app)/(tabs)/(3_profile)/spend-alerts'], ])('pushes the %s subpage from its row', async (title, route) => { const renderer = await mountPreferences(); diff --git a/apps/mobile/src/components/preferences-screen.tsx b/apps/mobile/src/components/preferences-screen.tsx index 5a3dc5196f5..ce8316fde31 100644 --- a/apps/mobile/src/components/preferences-screen.tsx +++ b/apps/mobile/src/components/preferences-screen.tsx @@ -1,5 +1,13 @@ import { type Href, useRouter } from 'expo-router'; -import { Bell, Globe, Mic, SlidersHorizontal, Wallet, WandSparkles } from '@/components/ui/icons'; +import { + Bell, + Globe, + History, + Mic, + SlidersHorizontal, + Wallet, + WandSparkles, +} from '@/components/ui/icons'; import { View } from 'react-native'; import { useTranslation } from 'react-i18next'; @@ -99,6 +107,16 @@ export function PreferencesScreen() { router.push('/(app)/notifications' as Href); }} /> + { + router.push('/(app)/(tabs)/(3_profile)/notification-history' as Href); + }} + /> ({ vi.mock('@/components/ui/icons', () => ({ Bell: 'Bell', Globe: 'Globe', + History: 'History', KeyRound: 'KeyRound', Mic: 'Mic', Shield: 'Shield', diff --git a/apps/mobile/src/i18n/locales/af.json b/apps/mobile/src/i18n/locales/af.json index a6e49cd6635..d8458045bbb 100644 --- a/apps/mobile/src/i18n/locales/af.json +++ b/apps/mobile/src/i18n/locales/af.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "oorskryding van drempels en uurlikse pieke", "spendAlertsToggle": "Kategorie vir bestedingswaarskuwings" }, + "history": { + "title": "Kennisgewingsgeskiedenis", + "subtitle": "Kennisgewings wat hierdie rekening ontvang het", + "bannerBody": "Push-kennisgewings is af vir hierdie toestel, so nuwe kennisgewings sal nie hier aankom nie.", + "bannerAction": "Kennisgewingsinstellings", + "emptyTitle": "Nog geen kennisgewings nie", + "emptyCategoryTitle": "Geen kennisgewings in hierdie kategorie nie", + "emptyBody": "Kennisgewings wat hierdie rekening ontvang, verskyn hier. Hulle word vir {{days}} dae bewaar.", + "capped": "Die mees onlangse kennisgewings word gewys.", + "loadError": "Kon nie kennisgewingsgeskiedenis laai nie.", + "openFailed": "Hierdie kennisgewing open nêrens meer nie." + }, "action": { "openSession": "Maak sessie oop" }, diff --git a/apps/mobile/src/i18n/locales/am.json b/apps/mobile/src/i18n/locales/am.json index 6a0d9710472..62ba1c6ffdf 100644 --- a/apps/mobile/src/i18n/locales/am.json +++ b/apps/mobile/src/i18n/locales/am.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "የገደብ መሻገሪያዎች እና የሰዓት ጭማሪዎች", "spendAlertsToggle": "የወጪ ማስጠንቀቂያ ምድብ" }, + "history": { + "title": "የማሳወቂያ ታሪክ", + "subtitle": "ይህ መለያ የተቀበላቸው ማሳወቂያዎች", + "bannerBody": "ለዚህ መሣሪያ ግፋ ማሳወቂያዎች ጠፍተዋል፣ ስለዚህ አዲሶች እዚህ አይደርሱም።", + "bannerAction": "የማሳወቂያ ቅንብሮች", + "emptyTitle": "እስካሁን ማሳወቂያዎች የሉም", + "emptyCategoryTitle": "በዚህ ምድብ ውስጥ ማሳወቂያዎች የሉም", + "emptyBody": "ይህ መለያ የሚቀበላቸው ማሳወቂያዎች እዚህ ይታያሉ። ለ{{days}} ቀናት ይቀመጣሉ።", + "capped": "የቅርብ ጊዜ ማሳወቂያዎችን በማሳየት ላይ።", + "loadError": "የማሳወቂያ ታሪክን መጫን አልተቻለም።", + "openFailed": "ይህ ማሳወቂያ ከእንግዲህ በየትኛውም ቦታ አይከፈትም።" + }, "action": { "openSession": "ክፍለ ጊዜውን ክፈት" }, diff --git a/apps/mobile/src/i18n/locales/ar.json b/apps/mobile/src/i18n/locales/ar.json index d2c40d5addf..fb071d5c695 100644 --- a/apps/mobile/src/i18n/locales/ar.json +++ b/apps/mobile/src/i18n/locales/ar.json @@ -353,6 +353,18 @@ "spendAlertsSubtitle": "تجاوزات الحدود والارتفاعات الساعية", "spendAlertsToggle": "فئة تنبيهات الإنفاق" }, + "history": { + "title": "سجل الإشعارات", + "subtitle": "الإشعارات التي تلقّاها هذا الحساب", + "bannerBody": "الإشعارات الفورية معطّلة على هذا الجهاز، لذلك لن تصل الإشعارات الجديدة هنا.", + "bannerAction": "إعدادات الإشعارات", + "emptyTitle": "لا توجد إشعارات بعد", + "emptyCategoryTitle": "لا توجد إشعارات في هذا التصنيف", + "emptyBody": "تظهر هنا الإشعارات التي يتلقّاها هذا الحساب. يتم الاحتفاظ بها لمدة {{days}} يوم.", + "capped": "يتم عرض أحدث الإشعارات.", + "loadError": "تعذّر تحميل سجل الإشعارات.", + "openFailed": "لم يعد هذا الإشعار يفتح في أي مكان." + }, "action": { "openSession": "فتح الجلسة" }, diff --git a/apps/mobile/src/i18n/locales/az.json b/apps/mobile/src/i18n/locales/az.json index d34ec75024d..929ec6bc8fc 100644 --- a/apps/mobile/src/i18n/locales/az.json +++ b/apps/mobile/src/i18n/locales/az.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "hədd aşmaları və saatlıq sıçrayışlar", "spendAlertsToggle": "Xərc bildirişləri kateqoriyası" }, + "history": { + "title": "Bildiriş tarixçəsi", + "subtitle": "Bu hesabın aldığı bildirişlər", + "bannerBody": "Bu cihaz üçün push bildirişlər deaktivdir, ona görə də yeni bildirişlər bura gəlməyəcək.", + "bannerAction": "Bildiriş parametrləri", + "emptyTitle": "Hələ bildiriş yoxdur", + "emptyCategoryTitle": "Bu kateqoriyada bildiriş yoxdur", + "emptyBody": "Bu hesabın aldığı bildirişlər burada görünür. Onlar {{days}} gün saxlanılır.", + "capped": "Ən son bildirişlər göstərilir.", + "loadError": "Bildiriş tarixçəsini yükləmək mümkün olmadı.", + "openFailed": "Bu bildiriş artıq heç yerdə açılmır." + }, "action": { "openSession": "Sessiyanı aç" }, diff --git a/apps/mobile/src/i18n/locales/be.json b/apps/mobile/src/i18n/locales/be.json index d3fbe369cf1..e45eb05855c 100644 --- a/apps/mobile/src/i18n/locales/be.json +++ b/apps/mobile/src/i18n/locales/be.json @@ -915,6 +915,18 @@ "spendAlertsSubtitle": "перасячэнні парогаў і гадзінныя ўсплёскі", "spendAlertsToggle": "Катэгорыя апавяшчэнняў аб выдатаках" }, + "history": { + "title": "Гісторыя апавяшчэнняў", + "subtitle": "Апавяшчэнні, атрыманыя гэтым акаўнтам", + "bannerBody": "Push-апавяшчэнні адключаны для гэтай прылады, таму новыя сюды не паступаюць.", + "bannerAction": "Налады апавяшчэнняў", + "emptyTitle": "Пакуль няма апавяшчэнняў", + "emptyCategoryTitle": "У гэтай катэгорыі няма апавяшчэнняў", + "emptyBody": "Апавяшчэнні, якія атрымлівае гэты акаўнт, з'яўляюцца тут. Яны захоўваюцца {{days}} дзён.", + "capped": "Паказваюцца самыя апошнія апавяшчэнні.", + "loadError": "Не ўдалося загрузіць гісторыю апавяшчэнняў.", + "openFailed": "Гэтае апавяшчэнне больш нідзе не адкрываецца." + }, "action": { "openSession": "Адкрыць сеанс" }, diff --git a/apps/mobile/src/i18n/locales/bg.json b/apps/mobile/src/i18n/locales/bg.json index 2dcf7bc897c..53e68805a5c 100644 --- a/apps/mobile/src/i18n/locales/bg.json +++ b/apps/mobile/src/i18n/locales/bg.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "преминаване на прагове и часови скокове", "spendAlertsToggle": "Категория известия за разходи" }, + "history": { + "title": "История на известията", + "subtitle": "Известия, получени от този акаунт", + "bannerBody": "Push известията са изключени за това устройство, така че нови няма да пристигат тук.", + "bannerAction": "Настройки на известията", + "emptyTitle": "Все още няма известия", + "emptyCategoryTitle": "Няма известия в тази категория", + "emptyBody": "Известията, които този акаунт получава, се показват тук. Те се запазват за {{days}} дни.", + "capped": "Показват се най-новите известия.", + "loadError": "Не можахме да заредим историята на известията.", + "openFailed": "Това известие вече не се отваря никъде." + }, "action": { "openSession": "Отваряне на сесия" }, diff --git a/apps/mobile/src/i18n/locales/bn.json b/apps/mobile/src/i18n/locales/bn.json index 7a86f2c3f0f..450bbe523a1 100644 --- a/apps/mobile/src/i18n/locales/bn.json +++ b/apps/mobile/src/i18n/locales/bn.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "সীমা অতিক্রম ও ঘণ্টাভিত্তিক বৃদ্ধি", "spendAlertsToggle": "খরচের সতর্কতার বিভাগ" }, + "history": { + "title": "বিজ্ঞপ্তির ইতিহাস", + "subtitle": "এই অ্যাকাউন্টে প্রাপ্ত বিজ্ঞপ্তি", + "bannerBody": "এই ডিভাইসে পুশ বিজ্ঞপ্তি বন্ধ আছে, তাই নতুন বিজ্ঞপ্তি এখানে আসবে না।", + "bannerAction": "বিজ্ঞপ্তি সেটিংস", + "emptyTitle": "এখনো কোনো বিজ্ঞপ্তি নেই", + "emptyCategoryTitle": "এই বিভাগে কোনো বিজ্ঞপ্তি নেই", + "emptyBody": "এই অ্যাকাউন্টে প্রাপ্ত বিজ্ঞপ্তি এখানে দেখা যাবে। সেগুলো {{days}} দিন রাখা হয়।", + "capped": "সাম্প্রতিকতম বিজ্ঞপ্তিগুলো দেখানো হচ্ছে।", + "loadError": "বিজ্ঞপ্তির ইতিহাস লোড করা যায়নি।", + "openFailed": "এই বিজ্ঞপ্তিটি আর কোথাও খোলে না।" + }, "action": { "openSession": "সেশন খুলুন" }, diff --git a/apps/mobile/src/i18n/locales/bs.json b/apps/mobile/src/i18n/locales/bs.json index 20d3b2081b9..d03e7dee367 100644 --- a/apps/mobile/src/i18n/locales/bs.json +++ b/apps/mobile/src/i18n/locales/bs.json @@ -896,6 +896,18 @@ "spendAlertsSubtitle": "prekoračenja pragova i skokovi na sat", "spendAlertsToggle": "Kategorija upozorenja o potrošnji" }, + "history": { + "title": "Historija obavijesti", + "subtitle": "Obavijesti koje je ovaj račun primio", + "bannerBody": "Push obavijesti su isključene za ovaj uređaj, pa nove neće stizati ovdje.", + "bannerAction": "Postavke obavijesti", + "emptyTitle": "Još nema obavijesti", + "emptyCategoryTitle": "Nema obavijesti u ovoj kategoriji", + "emptyBody": "Obavijesti koje ovaj račun primi pojavljuju se ovdje. Čuvaju se {{days}} dana.", + "capped": "Prikazuju se najnovije obavijesti.", + "loadError": "Nije moguće učitati historiju obavijesti.", + "openFailed": "Ova obavijest se više ne otvara nigdje." + }, "action": { "openSession": "Otvori sesiju" }, diff --git a/apps/mobile/src/i18n/locales/ca.json b/apps/mobile/src/i18n/locales/ca.json index a7b33378cda..30c1ddbc168 100644 --- a/apps/mobile/src/i18n/locales/ca.json +++ b/apps/mobile/src/i18n/locales/ca.json @@ -896,6 +896,18 @@ "spendAlertsSubtitle": "superacions de llindars i pics horaris", "spendAlertsToggle": "Categoria d'alertes de despesa" }, + "history": { + "title": "Historial de notificacions", + "subtitle": "Notificacions que ha rebut aquest compte", + "bannerBody": "Les notificacions push estan desactivades en aquest dispositiu, així que les noves no arribaran aquí.", + "bannerAction": "Configuració de notificacions", + "emptyTitle": "Encara no hi ha notificacions", + "emptyCategoryTitle": "No hi ha notificacions en aquesta categoria", + "emptyBody": "Les notificacions que rep aquest compte apareixen aquí. Es conserven durant {{days}} dies.", + "capped": "Es mostren les notificacions més recents.", + "loadError": "No s'ha pogut carregar l'historial de notificacions.", + "openFailed": "Aquesta notificació ja no s'obre enlloc." + }, "action": { "openSession": "Obre la sessió" }, diff --git a/apps/mobile/src/i18n/locales/ckb.json b/apps/mobile/src/i18n/locales/ckb.json index 33a53e46f0b..d3ee772f668 100644 --- a/apps/mobile/src/i18n/locales/ckb.json +++ b/apps/mobile/src/i18n/locales/ckb.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "تێپەڕینی سنوورەکان و بەرزبوونەوەکانی کاتژمێری", "spendAlertsToggle": "پۆلی ئاگادارکردنەوەکانی خەرجی" }, + "history": { + "title": "مێژووی ئاگادارکردنەوەکان", + "subtitle": "ئاگادارکردنەوەکانی وەرگیراو لەم هەژمارە", + "bannerBody": "ئاگادارکردنەوەکانی پاڵنان بۆ ئەم ئامێرە ناچالاک کراون، بۆیە ئەوانەی نوێ لێرە ناگەن.", + "bannerAction": "ڕێکخستنەکانی ئاگادارکردنەوە", + "emptyTitle": "هێشتا هیچ ئاگادارکردنەوەیەک نییە", + "emptyCategoryTitle": "هیچ ئاگادارکردنەوەیەک لەم پۆلەدا نییە", + "emptyBody": "ئەو ئاگادارکردنەوانەی کە ئەم هەژمارە وەریان دەگرێت لێرە دەردەکەون. بۆ ماوەی {{days}} ڕۆژ پاشەکەوت دەکرێن.", + "capped": "نوێترین ئاگادارکردنەوەکان پیشان دەدرێن.", + "loadError": "نەتوانرا مێژووی ئاگادارکردنەوەکان بار بکرێت.", + "openFailed": "ئەم ئاگادارکردنەوەیە چیتر لە هیچ شوێنێک ناکرێتەوە." + }, "action": { "openSession": "کردنەوەی سێشن" }, diff --git a/apps/mobile/src/i18n/locales/cs.json b/apps/mobile/src/i18n/locales/cs.json index fbdc0c27d6f..57cca71c426 100644 --- a/apps/mobile/src/i18n/locales/cs.json +++ b/apps/mobile/src/i18n/locales/cs.json @@ -915,6 +915,18 @@ "spendAlertsSubtitle": "překročení prahů a hodinové špičky", "spendAlertsToggle": "Kategorie upozornění na útratu" }, + "history": { + "title": "Historie oznámení", + "subtitle": "Oznámení, která tento účet obdržel", + "bannerBody": "Push oznámení jsou pro toto zařízení vypnutá, takže nová sem nebudou přicházet.", + "bannerAction": "Nastavení oznámení", + "emptyTitle": "Zatím žádná oznámení", + "emptyCategoryTitle": "V této kategorii nejsou žádná oznámení", + "emptyBody": "Oznámení, která tento účet obdrží, se zobrazují zde. Uchovávají se {{days}} dní.", + "capped": "Zobrazují se nejnovější oznámení.", + "loadError": "Historii oznámení se nepodařilo načíst.", + "openFailed": "Toto oznámení se už nikde neotevře." + }, "action": { "openSession": "Otevřít relaci" }, diff --git a/apps/mobile/src/i18n/locales/cy.json b/apps/mobile/src/i18n/locales/cy.json index 58908b2f8bc..017667737ed 100644 --- a/apps/mobile/src/i18n/locales/cy.json +++ b/apps/mobile/src/i18n/locales/cy.json @@ -953,6 +953,18 @@ "spendAlertsSubtitle": "croesi trothwyon a sbigau bob awr", "spendAlertsToggle": "Categori rhybuddion gwariant" }, + "history": { + "title": "Hanes hysbysiadau", + "subtitle": "Hysbysiadau a dderbyniodd y cyfrif hwn", + "bannerBody": "Mae hysbysiadau gwthio wedi'u diffodd ar gyfer y ddyfais hon, felly ni fydd rhai newydd yn cyrraedd yma.", + "bannerAction": "Gosodiadau hysbysiadau", + "emptyTitle": "Dim hysbysiadau eto", + "emptyCategoryTitle": "Dim hysbysiadau yn y categori hwn", + "emptyBody": "Bydd hysbysiadau a ddaw i'r cyfrif hwn yn ymddangos yma. Cânt eu cadw am {{days}} diwrnod.", + "capped": "Yn dangos y hysbysiadau diweddaraf.", + "loadError": "Methu llwytho hanes hysbysiadau.", + "openFailed": "Nid yw'r hysbysiad hwn yn agor yn unman mwyach." + }, "action": { "openSession": "Agor y sesiwn" }, diff --git a/apps/mobile/src/i18n/locales/da.json b/apps/mobile/src/i18n/locales/da.json index 6a2beb51d96..ea9e745db78 100644 --- a/apps/mobile/src/i18n/locales/da.json +++ b/apps/mobile/src/i18n/locales/da.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "grænseoverskridelser og spikes pr. time", "spendAlertsToggle": "Kategorien forbrugsadvarsler" }, + "history": { + "title": "Notifikationshistorik", + "subtitle": "Notifikationer, som denne konto har modtaget", + "bannerBody": "Pushnotifikationer er slået fra på denne enhed, så nye ankommer ikke her.", + "bannerAction": "Notifikationsindstillinger", + "emptyTitle": "Ingen notifikationer endnu", + "emptyCategoryTitle": "Ingen notifikationer i denne kategori", + "emptyBody": "Notifikationer, som denne konto modtager, vises her. De gemmes i {{days}} dage.", + "capped": "Viser de seneste notifikationer.", + "loadError": "Kunne ikke indlæse notifikationshistorikken.", + "openFailed": "Denne notifikation kan ikke længere åbnes nogen steder." + }, "action": { "openSession": "Åbn session" }, diff --git a/apps/mobile/src/i18n/locales/de.json b/apps/mobile/src/i18n/locales/de.json index 2dad6938204..0288a2ce6e4 100644 --- a/apps/mobile/src/i18n/locales/de.json +++ b/apps/mobile/src/i18n/locales/de.json @@ -353,6 +353,18 @@ "spendAlertsSubtitle": "Schwellenwertüberschreitungen und stündliche Spitzen", "spendAlertsToggle": "Kategorie „Ausgaben-Warnungen“" }, + "history": { + "title": "Benachrichtigungsverlauf", + "subtitle": "Benachrichtigungen, die dieses Konto erhalten hat", + "bannerBody": "Push-Benachrichtigungen sind für dieses Gerät deaktiviert, daher werden hier keine neuen eintreffen.", + "bannerAction": "Benachrichtigungseinstellungen", + "emptyTitle": "Noch keine Benachrichtigungen", + "emptyCategoryTitle": "Noch keine Benachrichtigungen in dieser Kategorie", + "emptyBody": "Benachrichtigungen, die dieses Konto erhält, erscheinen hier. Sie werden {{days}} Tage lang aufbewahrt.", + "capped": "Es werden die neuesten Benachrichtigungen angezeigt.", + "loadError": "Der Benachrichtigungsverlauf konnte nicht geladen werden.", + "openFailed": "Diese Benachrichtigung lässt sich nirgendwo mehr öffnen." + }, "action": { "openSession": "Sitzung öffnen" }, diff --git a/apps/mobile/src/i18n/locales/el.json b/apps/mobile/src/i18n/locales/el.json index 6ef0405e565..cd6eafaced8 100644 --- a/apps/mobile/src/i18n/locales/el.json +++ b/apps/mobile/src/i18n/locales/el.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "υπερβάσεις ορίων και ωριαίες κορυφώσεις", "spendAlertsToggle": "Κατηγορία ειδοποιήσεων δαπανών" }, + "history": { + "title": "Ιστορικό ειδοποιήσεων", + "subtitle": "Ειδοποιήσεις που έλαβε αυτός ο λογαριασμός", + "bannerBody": "Οι ειδοποιήσεις push είναι απενεργοποιημένες για αυτή τη συσκευή, οπότε δεν θα φτάνουν νέες εδώ.", + "bannerAction": "Ρυθμίσεις ειδοποιήσεων", + "emptyTitle": "Δεν υπάρχουν ειδοποιήσεις ακόμη", + "emptyCategoryTitle": "Δεν υπάρχουν ειδοποιήσεις σε αυτή την κατηγορία", + "emptyBody": "Οι ειδοποιήσεις που λαμβάνει αυτός ο λογαριασμός εμφανίζονται εδώ. Διατηρούνται για {{days}} ημέρες.", + "capped": "Εμφανίζονται οι πιο πρόσφατες ειδοποιήσεις.", + "loadError": "Δεν ήταν δυνατή η φόρτωση του ιστορικού ειδοποιήσεων.", + "openFailed": "Αυτή η ειδοποίηση δεν ανοίγει πλέον πουθενά." + }, "action": { "openSession": "Άνοιγμα συνεδρίας" }, diff --git a/apps/mobile/src/i18n/locales/en.json b/apps/mobile/src/i18n/locales/en.json index 84e21ada242..44af12722ba 100644 --- a/apps/mobile/src/i18n/locales/en.json +++ b/apps/mobile/src/i18n/locales/en.json @@ -971,6 +971,18 @@ "spendAlertsSubtitle": "threshold crossings and hourly spikes", "spendAlertsToggle": "Spend alerts category" }, + "history": { + "title": "Notification history", + "subtitle": "Notifications this account received", + "bannerBody": "Push notifications are off for this device, so new ones will not arrive here.", + "bannerAction": "Notification settings", + "emptyTitle": "No notifications yet", + "emptyCategoryTitle": "No notifications in this category", + "emptyBody": "Notifications this account receives appear here. They are kept for {{days}} days.", + "capped": "Showing the most recent notifications.", + "loadError": "Could not load notification history.", + "openFailed": "This notification no longer opens anywhere." + }, "action": { "openSession": "Open session" }, diff --git a/apps/mobile/src/i18n/locales/es.json b/apps/mobile/src/i18n/locales/es.json index 0f8aa097dc0..c33aa5ec624 100644 --- a/apps/mobile/src/i18n/locales/es.json +++ b/apps/mobile/src/i18n/locales/es.json @@ -326,6 +326,18 @@ "spendAlertsSubtitle": "superaciones de umbrales y picos por hora", "spendAlertsToggle": "Categoría de alertas de gasto" }, + "history": { + "title": "Historial de notificaciones", + "subtitle": "Notificaciones que recibió esta cuenta", + "bannerBody": "Las notificaciones push están desactivadas en este dispositivo, por lo que las nuevas no llegarán aquí.", + "bannerAction": "Configuración de notificaciones", + "emptyTitle": "Aún no hay notificaciones", + "emptyCategoryTitle": "No hay notificaciones en esta categoría", + "emptyBody": "Las notificaciones que recibe esta cuenta aparecen aquí. Se conservan durante {{days}} días.", + "capped": "Mostrando las notificaciones más recientes.", + "loadError": "No se pudo cargar el historial de notificaciones.", + "openFailed": "Esta notificación ya no se puede abrir en ningún sitio." + }, "liveActivities": "Actividades en directo", "liveActivitySubtitle": "Mostrar los agentes activos en la pantalla de bloqueo", "liveUpdates": "Actualizaciones en tiempo real", diff --git a/apps/mobile/src/i18n/locales/et.json b/apps/mobile/src/i18n/locales/et.json index 682c5a63326..4cb4f167d65 100644 --- a/apps/mobile/src/i18n/locales/et.json +++ b/apps/mobile/src/i18n/locales/et.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "lävendi ületamised ja tunnipõhised tõusud", "spendAlertsToggle": "Kulutushoiatuste kategooria" }, + "history": { + "title": "Teavituste ajalugu", + "subtitle": "Teavitused, mille see konto sai", + "bannerBody": "Tõuketeavitused on selle seadme jaoks välja lülitatud, seega uued siia ei jõua.", + "bannerAction": "Teavituste seaded", + "emptyTitle": "Teavitusi veel pole", + "emptyCategoryTitle": "Selles kategoorias pole teavitusi", + "emptyBody": "Teavitused, mille see konto saab, kuvatakse siin. Neid säilitatakse {{days}} päeva.", + "capped": "Kuvatakse kõige hiljutisemad teavitused.", + "loadError": "Teavituste ajaloo laadimine ebaõnnestus.", + "openFailed": "See teavitus ei avane enam kuskil." + }, "action": { "openSession": "Ava seanss" }, diff --git a/apps/mobile/src/i18n/locales/eu.json b/apps/mobile/src/i18n/locales/eu.json index 6778ed8992d..eba26cf48c6 100644 --- a/apps/mobile/src/i18n/locales/eu.json +++ b/apps/mobile/src/i18n/locales/eu.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "atalaseen gainditzeak eta orduko gorakadak", "spendAlertsToggle": "Gastu-alerten kategoria" }, + "history": { + "title": "Jakinarazpenen historia", + "subtitle": "Kontu honek jasotako jakinarazpenak", + "bannerBody": "Push jakinarazpenak desgaituta daude gailu honetan, beraz, berriak ez dira hona iritsiko.", + "bannerAction": "Jakinarazpenen ezarpenak", + "emptyTitle": "Oraindik ez dago jakinarazpenik", + "emptyCategoryTitle": "Kategoria honetan ez dago jakinarazpenik", + "emptyBody": "Kontu honek jasotzen dituen jakinarazpenak hemen agertzen dira. {{days}} egunez gordetzen dira.", + "capped": "Azken jakinarazpenak erakusten dira.", + "loadError": "Ezin izan da jakinarazpenen historia kargatu.", + "openFailed": "Jakinarazpen hau jada ez da inon irekitzen." + }, "action": { "openSession": "Ireki saioa" }, diff --git a/apps/mobile/src/i18n/locales/fa.json b/apps/mobile/src/i18n/locales/fa.json index b031dec0335..362e7d92e8d 100644 --- a/apps/mobile/src/i18n/locales/fa.json +++ b/apps/mobile/src/i18n/locales/fa.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "عبور از آستانه‌ها و جهش‌های ساعتی", "spendAlertsToggle": "دستهٔ هشدارهای هزینه" }, + "history": { + "title": "تاریخچه اعلان‌ها", + "subtitle": "اعلان‌هایی که این حساب دریافت کرده است", + "bannerBody": "اعلان‌های فوری برای این دستگاه غیرفعال هستند، بنابراین اعلان‌های جدید به اینجا نمی‌رسند.", + "bannerAction": "تنظیمات اعلان‌ها", + "emptyTitle": "هنوز اعلانی وجود ندارد", + "emptyCategoryTitle": "هیچ اعلانی در این دسته وجود ندارد", + "emptyBody": "اعلان‌هایی که این حساب دریافت می‌کند در اینجا نمایش داده می‌شوند. آن‌ها به مدت {{days}} روز نگهداری می‌شوند.", + "capped": "جدیدترین اعلان‌ها نمایش داده می‌شوند.", + "loadError": "بارگذاری تاریخچه اعلان‌ها ممکن نشد.", + "openFailed": "این اعلان دیگر در هیچ‌جا باز نمی‌شود." + }, "action": { "openSession": "باز کردن نشست" }, diff --git a/apps/mobile/src/i18n/locales/fi.json b/apps/mobile/src/i18n/locales/fi.json index cf54a9d176c..371d5a414b0 100644 --- a/apps/mobile/src/i18n/locales/fi.json +++ b/apps/mobile/src/i18n/locales/fi.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "raja-arvojen ylitykset ja tunneittaiset piikit", "spendAlertsToggle": "Kulutusvaroitusten luokka" }, + "history": { + "title": "Ilmoitushistoria", + "subtitle": "Tämän tilin vastaanottamat ilmoitukset", + "bannerBody": "Push-ilmoitukset ovat pois päältä tällä laitteella, joten uudet ilmoitukset eivät saavu tänne.", + "bannerAction": "Ilmoitusasetukset", + "emptyTitle": "Ei vielä ilmoituksia", + "emptyCategoryTitle": "Ei ilmoituksia tässä kategoriassa", + "emptyBody": "Tämän tilin vastaanottamat ilmoitukset näkyvät täällä. Ne säilytetään {{days}} päivää.", + "capped": "Näytetään uusimmat ilmoitukset.", + "loadError": "Ilmoitushistoriaa ei voitu ladata.", + "openFailed": "Tämä ilmoitus ei enää avaudu missään." + }, "action": { "openSession": "Avaa istunto" }, diff --git a/apps/mobile/src/i18n/locales/fil.json b/apps/mobile/src/i18n/locales/fil.json index f4845168d33..f5efe8fd535 100644 --- a/apps/mobile/src/i18n/locales/fil.json +++ b/apps/mobile/src/i18n/locales/fil.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "mga paglampas sa hangganan at pagtaas bawat oras", "spendAlertsToggle": "Kategorya ng mga babala sa gastos" }, + "history": { + "title": "Kasaysayan ng notipikasyon", + "subtitle": "Mga notipikasyong natanggap ng account na ito", + "bannerBody": "Naka-off ang mga push notification para sa device na ito, kaya wala nang bagong darating dito.", + "bannerAction": "Mga setting ng notipikasyon", + "emptyTitle": "Wala pang mga notipikasyon", + "emptyCategoryTitle": "Wala pang mga notipikasyon sa kategoryang ito", + "emptyBody": "Lumalabas dito ang mga notipikasyong natatanggap ng account na ito. Itinatago ang mga ito sa loob ng {{days}} araw.", + "capped": "Ipinapakita ang mga pinakabagong notipikasyon.", + "loadError": "Hindi ma-load ang kasaysayan ng notipikasyon.", + "openFailed": "Hindi na mabubuksan kahit saan ang notipikasyong ito." + }, "action": { "openSession": "Buksan ang sesyon" }, diff --git a/apps/mobile/src/i18n/locales/fr.json b/apps/mobile/src/i18n/locales/fr.json index 9c2f0f0f4df..7fe09d31a1b 100644 --- a/apps/mobile/src/i18n/locales/fr.json +++ b/apps/mobile/src/i18n/locales/fr.json @@ -294,6 +294,18 @@ "spendAlertsSubtitle": "franchissements de seuils et pics horaires", "spendAlertsToggle": "Catégorie « Alertes de dépenses »" }, + "history": { + "title": "Historique des notifications", + "subtitle": "Notifications reçues par ce compte", + "bannerBody": "Les notifications push sont désactivées pour cet appareil, les nouvelles n'arriveront donc pas ici.", + "bannerAction": "Paramètres des notifications", + "emptyTitle": "Aucune notification pour l'instant", + "emptyCategoryTitle": "Aucune notification dans cette catégorie", + "emptyBody": "Les notifications reçues par ce compte apparaissent ici. Elles sont conservées pendant {{days}} jours.", + "capped": "Affichage des notifications les plus récentes.", + "loadError": "Impossible de charger l'historique des notifications.", + "openFailed": "Cette notification ne s'ouvre plus nulle part." + }, "liveActivities": "Activités en direct", "liveActivitySubtitle": "Afficher les agents actifs sur l’écran verrouillé", "liveUpdates": "Mises à jour en direct", diff --git a/apps/mobile/src/i18n/locales/ga.json b/apps/mobile/src/i18n/locales/ga.json index 32b42b7d374..57c39463e76 100644 --- a/apps/mobile/src/i18n/locales/ga.json +++ b/apps/mobile/src/i18n/locales/ga.json @@ -934,6 +934,18 @@ "spendAlertsSubtitle": "sáruithe tairsí agus spuaiceanna uaire", "spendAlertsToggle": "Catagóir foláirimh chaiteachais" }, + "history": { + "title": "Stair fógraí", + "subtitle": "Na fógraí a fuair an cuntas seo", + "bannerBody": "Tá fógraí push múchta don ghléas seo, mar sin ní thiocfaidh cinn nua anseo.", + "bannerAction": "Socruithe fógraí", + "emptyTitle": "Níl aon fhógraí fós", + "emptyCategoryTitle": "Níl aon fhógraí sa chatagóir seo", + "emptyBody": "Taispeántar na fógraí a fhaigheann an cuntas seo anseo. Coimeádtar iad ar feadh {{days}} lá.", + "capped": "Taispeántar na fógraí is déanaí.", + "loadError": "Níorbh fhéidir stair fógraí a lódáil.", + "openFailed": "Ní osclaíonn an fógra seo in áit ar bith a thuilleadh." + }, "action": { "openSession": "Oscail an seisiún" }, diff --git a/apps/mobile/src/i18n/locales/gl.json b/apps/mobile/src/i18n/locales/gl.json index 211b877c7f6..7ab66c2387e 100644 --- a/apps/mobile/src/i18n/locales/gl.json +++ b/apps/mobile/src/i18n/locales/gl.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "superacións de limiares e picos por hora", "spendAlertsToggle": "Categoría de alertas de gasto" }, + "history": { + "title": "Historial de notificacións", + "subtitle": "Notificacións recibidas por esta conta", + "bannerBody": "As notificacións push están desactivadas neste dispositivo, polo que non chegarán novas aquí.", + "bannerAction": "Configuración de notificacións", + "emptyTitle": "Aínda non hai notificacións", + "emptyCategoryTitle": "Non hai notificacións nesta categoría", + "emptyBody": "As notificacións que recibe esta conta aparecen aquí. Consérvanse durante {{days}} días.", + "capped": "Mostrando as notificacións máis recentes.", + "loadError": "Non se puido cargar o historial de notificacións.", + "openFailed": "Esta notificación xa non se abre desde ningún sitio." + }, "action": { "openSession": "Abrir a sesión" }, diff --git a/apps/mobile/src/i18n/locales/gu.json b/apps/mobile/src/i18n/locales/gu.json index d84650f0a71..f20963d8f3a 100644 --- a/apps/mobile/src/i18n/locales/gu.json +++ b/apps/mobile/src/i18n/locales/gu.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "મર્યાદા ઓળંગવી અને કલાકદીઠ ઉછાળા", "spendAlertsToggle": "ખર્ચની ચેતવણીની શ્રેણી" }, + "history": { + "title": "સૂચનાનો ઇતિહાસ", + "subtitle": "આ ખાતાને મળેલી સૂચનાઓ", + "bannerBody": "આ ઉપકરણ માટે પુશ સૂચનાઓ બંધ છે, તેથી નવી સૂચનાઓ અહીં આવશે નહીં.", + "bannerAction": "સૂચના સેટિંગ્સ", + "emptyTitle": "હજી સુધી કોઈ સૂચના નથી", + "emptyCategoryTitle": "આ શ્રેણીમાં કોઈ સૂચના નથી", + "emptyBody": "આ ખાતાને મળતી સૂચનાઓ અહીં દેખાય છે. તે {{days}} દિવસ સુધી રાખવામાં આવે છે.", + "capped": "સૌથી તાજેતરની સૂચનાઓ દર્શાવવામાં આવે છે.", + "loadError": "સૂચનાનો ઇતિહાસ લોડ કરી શકાયો નહીં.", + "openFailed": "આ સૂચના હવે ક્યાંય ખૂલતી નથી." + }, "action": { "openSession": "સેશન ખોલો" }, diff --git a/apps/mobile/src/i18n/locales/ha.json b/apps/mobile/src/i18n/locales/ha.json index 328932eb78e..9f9aa75b50b 100644 --- a/apps/mobile/src/i18n/locales/ha.json +++ b/apps/mobile/src/i18n/locales/ha.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "wucewar iyaka da hauhawar sa'a", "spendAlertsToggle": "Rukunin faɗakarwar kashe kuɗi" }, + "history": { + "title": "Tarihin sanarwa", + "subtitle": "Sanarwar da wannan asusu ya karɓa", + "bannerBody": "An kashe sanarwar turawa a wannan na'urar, don haka sabbin ba za su isa nan ba.", + "bannerAction": "Saitunan sanarwa", + "emptyTitle": "Babu sanarwa tukuna", + "emptyCategoryTitle": "Babu sanarwa a wannan rukunin", + "emptyBody": "Sanarwar da wannan asusu ke karɓa suna bayyana a nan. Ana ajiye su na kwanaki {{days}}.", + "capped": "Ana nuna sanarwar baya-bayan nan.", + "loadError": "An kasa loda tarihin sanarwa.", + "openFailed": "Wannan sanarwa ba ta ƙara buɗewa a ko'ina ba." + }, "action": { "openSession": "Buɗe zaman" }, diff --git a/apps/mobile/src/i18n/locales/he.json b/apps/mobile/src/i18n/locales/he.json index d787ff79ecc..470c50bf80f 100644 --- a/apps/mobile/src/i18n/locales/he.json +++ b/apps/mobile/src/i18n/locales/he.json @@ -353,6 +353,18 @@ "spendAlertsSubtitle": "חציית ספים וזינוקים שעתיים", "spendAlertsToggle": "קטגוריית התראות הוצאות" }, + "history": { + "title": "היסטוריית התראות", + "subtitle": "התראות שהחשבון הזה קיבל", + "bannerBody": "התראות דחיפה מושבתות במכשיר הזה, כך שלא יגיעו לכאן התראות חדשות.", + "bannerAction": "הגדרות התראות", + "emptyTitle": "אין עדיין התראות", + "emptyCategoryTitle": "אין התראות בקטגוריה הזו", + "emptyBody": "התראות שהחשבון הזה מקבל מופיעות כאן. הן נשמרות {{days}} ימים.", + "capped": "מוצגות ההתראות העדכניות ביותר.", + "loadError": "לא ניתן היה לטעון את היסטוריית ההתראות.", + "openFailed": "התראה זו כבר לא נפתחת בשום מקום." + }, "action": { "openSession": "פתיחת סשן" }, diff --git a/apps/mobile/src/i18n/locales/hi.json b/apps/mobile/src/i18n/locales/hi.json index a814affc4b7..8831dd2f619 100644 --- a/apps/mobile/src/i18n/locales/hi.json +++ b/apps/mobile/src/i18n/locales/hi.json @@ -353,6 +353,18 @@ "spendAlertsSubtitle": "सीमा पार होना और हर घंटे की बढ़त", "spendAlertsToggle": "खर्च की चेतावनी श्रेणी" }, + "history": { + "title": "सूचनाओं का इतिहास", + "subtitle": "इस खाते को मिली सूचनाएँ", + "bannerBody": "इस डिवाइस के लिए पुश सूचनाएँ बंद हैं, इसलिए नई सूचनाएँ यहाँ नहीं आएँगी।", + "bannerAction": "सूचना सेटिंग्स", + "emptyTitle": "अभी तक कोई सूचना नहीं", + "emptyCategoryTitle": "इस श्रेणी में कोई सूचना नहीं", + "emptyBody": "इस खाते को मिलने वाली सूचनाएँ यहाँ दिखाई देती हैं। इन्हें {{days}} दिनों तक रखा जाता है।", + "capped": "सबसे हाल की सूचनाएँ दिखाई जा रही हैं।", + "loadError": "सूचनाओं का इतिहास लोड नहीं हो सका।", + "openFailed": "यह सूचना अब कहीं नहीं खुलती।" + }, "action": { "openSession": "सत्र खोलें" }, diff --git a/apps/mobile/src/i18n/locales/hr.json b/apps/mobile/src/i18n/locales/hr.json index 3e03fefa0c0..3b5a05bbe71 100644 --- a/apps/mobile/src/i18n/locales/hr.json +++ b/apps/mobile/src/i18n/locales/hr.json @@ -896,6 +896,18 @@ "spendAlertsSubtitle": "prekoračenja pragova i skokovi po satu", "spendAlertsToggle": "Kategorija upozorenja o potrošnji" }, + "history": { + "title": "Povijest obavijesti", + "subtitle": "Obavijesti koje je ovaj račun primio", + "bannerBody": "Push obavijesti su isključene za ovaj uređaj pa nove neće stizati ovdje.", + "bannerAction": "Postavke obavijesti", + "emptyTitle": "Još nema obavijesti", + "emptyCategoryTitle": "Nema obavijesti u ovoj kategoriji", + "emptyBody": "Obavijesti koje ovaj račun prima pojavljuju se ovdje. Čuvaju se {{days}} dana.", + "capped": "Prikazuju se najnovije obavijesti.", + "loadError": "Nije moguće učitati povijest obavijesti.", + "openFailed": "Ova se obavijest više ne otvara nigdje." + }, "action": { "openSession": "Otvori sesiju" }, diff --git a/apps/mobile/src/i18n/locales/ht.json b/apps/mobile/src/i18n/locales/ht.json index 301ed8a8527..c97cbd975b6 100644 --- a/apps/mobile/src/i18n/locales/ht.json +++ b/apps/mobile/src/i18n/locales/ht.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "depasman papòt ak ogmantasyon chak èdtan", "spendAlertsToggle": "Kategori alèt depans" }, + "history": { + "title": "Istwa notifikasyon", + "subtitle": "Notifikasyon kont sa a te resevwa", + "bannerBody": "Notifikasyon push dezaktive pou aparèy sa a, donk nouvo yo p ap rive isit la.", + "bannerAction": "Paramèt notifikasyon", + "emptyTitle": "Poko gen notifikasyon", + "emptyCategoryTitle": "Poko gen notifikasyon nan kategori sa a", + "emptyBody": "Notifikasyon kont sa a resevwa yo parèt isit la. Yo kenbe yo pandan {{days}} jou.", + "capped": "Sa yo se notifikasyon ki pi resan yo.", + "loadError": "Nou pa t ka chaje istwa notifikasyon yo.", + "openFailed": "Notifikasyon sa a pa ouvri okenn kote ankò." + }, "action": { "openSession": "Louvri sesyon an" }, diff --git a/apps/mobile/src/i18n/locales/hu.json b/apps/mobile/src/i18n/locales/hu.json index 538fe36ccda..a6f7e5566aa 100644 --- a/apps/mobile/src/i18n/locales/hu.json +++ b/apps/mobile/src/i18n/locales/hu.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "küszöbátlépések és óránkénti kiugrások", "spendAlertsToggle": "Költésfigyelmeztetések kategóriája" }, + "history": { + "title": "Értesítési előzmények", + "subtitle": "Az ehhez a fiókhoz beérkezett értesítések", + "bannerBody": "A push értesítések ki vannak kapcsolva ezen az eszközön, ezért az új értesítések nem érkeznek meg ide.", + "bannerAction": "Értesítési beállítások", + "emptyTitle": "Még nincsenek értesítések", + "emptyCategoryTitle": "Ebben a kategóriában nincsenek értesítések", + "emptyBody": "Az ehhez a fiókhoz beérkező értesítések itt jelennek meg. {{days}} napig maradnak meg.", + "capped": "A legutóbbi értesítések láthatók.", + "loadError": "Nem sikerült betölteni az értesítési előzményeket.", + "openFailed": "Ez az értesítés már sehol nem nyitható meg." + }, "action": { "openSession": "Munkamenet megnyitása" }, diff --git a/apps/mobile/src/i18n/locales/hy.json b/apps/mobile/src/i18n/locales/hy.json index ab0290ea24f..b051a4d29bf 100644 --- a/apps/mobile/src/i18n/locales/hy.json +++ b/apps/mobile/src/i18n/locales/hy.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "շեմերի հատումներ և ժամային ցատկեր", "spendAlertsToggle": "Ծախսերի ծանուցումների կատեգորիա" }, + "history": { + "title": "Ծանուցումների պատմություն", + "subtitle": "Ծանուցումներ, որոնք ստացել է այս հաշիվը", + "bannerBody": "Փուշ ծանուցումներն անջատված են այս սարքի համար, ուստի նորերը այստեղ չեն հասնի։", + "bannerAction": "Ծանուցումների կարգավորումներ", + "emptyTitle": "Դեռ ծանուցումներ չկան", + "emptyCategoryTitle": "Այս կատեգորիայում ծանուցումներ չկան", + "emptyBody": "Ծանուցումները, որոնք ստանում է այս հաշիվը, հայտնվում են այստեղ։ Դրանք պահվում են {{days}} օր։", + "capped": "Ցուցադրվում են ամենավերջին ծանուցումները։", + "loadError": "Չհաջողվեց բեռնել ծանուցումների պատմությունը։", + "openFailed": "Այս ծանուցումն այլևս ոչ մի տեղ չի բացվում։" + }, "action": { "openSession": "Բացել նիստը" }, diff --git a/apps/mobile/src/i18n/locales/id.json b/apps/mobile/src/i18n/locales/id.json index 2d586ea3dcc..06e1b54ae89 100644 --- a/apps/mobile/src/i18n/locales/id.json +++ b/apps/mobile/src/i18n/locales/id.json @@ -353,6 +353,18 @@ "spendAlertsSubtitle": "melewati ambang batas dan lonjakan per jam", "spendAlertsToggle": "Kategori peringatan pengeluaran" }, + "history": { + "title": "Riwayat notifikasi", + "subtitle": "Notifikasi yang diterima akun ini", + "bannerBody": "Notifikasi push nonaktif untuk perangkat ini, jadi notifikasi baru tidak akan masuk di sini.", + "bannerAction": "Pengaturan notifikasi", + "emptyTitle": "Belum ada notifikasi", + "emptyCategoryTitle": "Tidak ada notifikasi di kategori ini", + "emptyBody": "Notifikasi yang diterima akun ini akan muncul di sini. Notifikasi disimpan selama {{days}} hari.", + "capped": "Menampilkan notifikasi terbaru.", + "loadError": "Tidak dapat memuat riwayat notifikasi.", + "openFailed": "Notifikasi ini tidak lagi dapat dibuka di mana pun." + }, "action": { "openSession": "Buka sesi" }, diff --git a/apps/mobile/src/i18n/locales/ig.json b/apps/mobile/src/i18n/locales/ig.json index 878f4f750a3..c394bdf6d66 100644 --- a/apps/mobile/src/i18n/locales/ig.json +++ b/apps/mobile/src/i18n/locales/ig.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "ịgafe oke na mmụba kwa awa", "spendAlertsToggle": "Otu ọkwa gbasara mmefu" }, + "history": { + "title": "Akụkọ ọkwa", + "subtitle": "Ọkwa akaụntụ a nwetara", + "bannerBody": "Agbanyụla ọkwa push maka ngwaọrụ a, yabụ ndị ọhụrụ agaghị abịa ebe a.", + "bannerAction": "Ntọala ọkwa", + "emptyTitle": "Ọnwebeghị ọkwa", + "emptyCategoryTitle": "Ọnwebeghị ọkwa n'ụdị a", + "emptyBody": "Ọkwa akaụntụ a na-anweta na-apụta ebe a. A na-edebe ha maka ụbọchị {{days}}.", + "capped": "Na-egosi ọkwa ndị kachasị ọhụrụ.", + "loadError": "Enweghị ike ịbute akụkọ ọkwa.", + "openFailed": "Ọkwa a anaghịzi emeghe ebe ọ bụla." + }, "action": { "openSession": "Mepee nnọkọ" }, diff --git a/apps/mobile/src/i18n/locales/is.json b/apps/mobile/src/i18n/locales/is.json index df65508082a..9a42e507ec8 100644 --- a/apps/mobile/src/i18n/locales/is.json +++ b/apps/mobile/src/i18n/locales/is.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "yfirferðir þröskulda og uppsveiflur á klukkustund", "spendAlertsToggle": "Flokkur útgjaldaviðvarana" }, + "history": { + "title": "Tilkynningasaga", + "subtitle": "Tilkynningar sem þessi reikningur fékk", + "bannerBody": "Slökkt er á push-tilkynningum fyrir þetta tæki, svo nýjar munu ekki berast hingað.", + "bannerAction": "Stillingar tilkynninga", + "emptyTitle": "Engar tilkynningar ennþá", + "emptyCategoryTitle": "Engar tilkynningar í þessum flokki", + "emptyBody": "Tilkynningar sem þessi reikningur fær birtast hér. Þær eru geymdar í {{days}} daga.", + "capped": "Nýjustu tilkynningarnar eru sýndar.", + "loadError": "Ekki tókst að hlaða tilkynningasögu.", + "openFailed": "Þessi tilkynning opnast hvergi lengur." + }, "action": { "openSession": "Opna lotu" }, diff --git a/apps/mobile/src/i18n/locales/it.json b/apps/mobile/src/i18n/locales/it.json index e9906e254ad..6d44c79bcc1 100644 --- a/apps/mobile/src/i18n/locales/it.json +++ b/apps/mobile/src/i18n/locales/it.json @@ -294,6 +294,18 @@ "spendAlertsSubtitle": "superamenti di soglia e picchi orari", "spendAlertsToggle": "Categoria avvisi di spesa" }, + "history": { + "title": "Cronologia notifiche", + "subtitle": "Notifiche ricevute da questo account", + "bannerBody": "Le notifiche push sono disattivate per questo dispositivo, quindi le nuove non arriveranno qui.", + "bannerAction": "Impostazioni notifiche", + "emptyTitle": "Nessuna notifica per ora", + "emptyCategoryTitle": "Nessuna notifica in questa categoria", + "emptyBody": "Le notifiche ricevute da questo account compaiono qui. Vengono conservate per {{days}} giorni.", + "capped": "Vengono mostrate le notifiche più recenti.", + "loadError": "Impossibile caricare la cronologia delle notifiche.", + "openFailed": "Questa notifica non si apre più da nessuna parte." + }, "liveActivities": "Attività in tempo reale", "liveActivitySubtitle": "Mostra gli agenti attivi nella schermata di blocco", "liveUpdates": "Aggiornamenti in tempo reale", diff --git a/apps/mobile/src/i18n/locales/ja.json b/apps/mobile/src/i18n/locales/ja.json index 606ba5cae54..60ab36bd646 100644 --- a/apps/mobile/src/i18n/locales/ja.json +++ b/apps/mobile/src/i18n/locales/ja.json @@ -353,6 +353,18 @@ "spendAlertsSubtitle": "しきい値の超過と毎時の急増", "spendAlertsToggle": "支出アラートのカテゴリ" }, + "history": { + "title": "通知履歴", + "subtitle": "このアカウントが受け取った通知", + "bannerBody": "このデバイスではプッシュ通知がオフになっているため、新しい通知はここに届きません。", + "bannerAction": "通知設定", + "emptyTitle": "通知はまだありません", + "emptyCategoryTitle": "このカテゴリの通知はありません", + "emptyBody": "このアカウントが受け取った通知はここに表示されます。{{days}}日間保存されます。", + "capped": "最新の通知を表示しています。", + "loadError": "通知履歴を読み込めませんでした。", + "openFailed": "この通知はもうどこからも開けません。" + }, "action": { "openSession": "セッションを開く" }, diff --git a/apps/mobile/src/i18n/locales/ka.json b/apps/mobile/src/i18n/locales/ka.json index 3f1d44d37c2..b59ee65814a 100644 --- a/apps/mobile/src/i18n/locales/ka.json +++ b/apps/mobile/src/i18n/locales/ka.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "ზღვრების გადაცილება და საათობრივი ნახტომები", "spendAlertsToggle": "ხარჯვის შეტყობინებების კატეგორია" }, + "history": { + "title": "შეტყობინებების ისტორია", + "subtitle": "შეტყობინებები, რომლებიც ამ ანგარიშმა მიიღო", + "bannerBody": "Push შეტყობინებები გამორთულია ამ მოწყობილობისთვის, ამიტომ ახლები აქ არ მოვა.", + "bannerAction": "შეტყობინებების პარამეტრები", + "emptyTitle": "შეტყობინებები ჯერ არ არის", + "emptyCategoryTitle": "ამ კატეგორიაში შეტყობინებები არ არის", + "emptyBody": "შეტყობინებები, რომლებსაც ეს ანგარიში იღებს, აქ ჩნდება. ისინი ინახება {{days}} დღის განმავლობაში.", + "capped": "ნაჩვენებია უახლესი შეტყობინებები.", + "loadError": "შეტყობინებების ისტორიის ჩატვირთვა ვერ მოხერხდა.", + "openFailed": "ეს შეტყობინება აღარ იხსნება არსად." + }, "action": { "openSession": "სესიის გახსნა" }, diff --git a/apps/mobile/src/i18n/locales/kk.json b/apps/mobile/src/i18n/locales/kk.json index 184934f2bd9..af8533e2e63 100644 --- a/apps/mobile/src/i18n/locales/kk.json +++ b/apps/mobile/src/i18n/locales/kk.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "шектерден асу және сағаттық секірістер", "spendAlertsToggle": "Шығыс ескертулері санаты" }, + "history": { + "title": "Хабарландырулар тарихы", + "subtitle": "Осы аккаунт алған хабарландырулар", + "bannerBody": "Бұл құрылғыда push-хабарландырулар өшірілген, сондықтан жаңалары осында келмейді.", + "bannerAction": "Хабарландыру баптаулары", + "emptyTitle": "Әзірге хабарландыру жоқ", + "emptyCategoryTitle": "Бұл санатта хабарландыру жоқ", + "emptyBody": "Осы аккаунт алған хабарландырулар осында көрсетіледі. Олар {{days}} күн бойы сақталады.", + "capped": "Ең соңғы хабарландырулар көрсетілуде.", + "loadError": "Хабарландырулар тарихын жүктеу мүмкін болмады.", + "openFailed": "Бұл хабарландыру енді еш жерде ашылмайды." + }, "action": { "openSession": "Сессияны ашу" }, diff --git a/apps/mobile/src/i18n/locales/km.json b/apps/mobile/src/i18n/locales/km.json index c49bd289c31..e5ddb51be78 100644 --- a/apps/mobile/src/i18n/locales/km.json +++ b/apps/mobile/src/i18n/locales/km.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "ការឆ្លងកាត់កម្រិត និងការកើនឡើងប្រចាំម៉ោង", "spendAlertsToggle": "ប្រភេទនៃការជូនដំណឹងអំពីការចំណាយ" }, + "history": { + "title": "ប្រវត្តិការជូនដំណឹង", + "subtitle": "ការជូនដំណឹងដែលគណនីនេះបានទទួល", + "bannerBody": "ការជូនដំណឹងពន្លើសត្រូវបានបិទសម្រាប់ឧបករណ៍នេះ ដូច្នេះការជូនដំណឹងថ្មីៗនឹងមិនមកដល់ទីនេះទេ។", + "bannerAction": "ការកំណត់ការជូនដំណឹង", + "emptyTitle": "មិនទាន់មានការជូនដំណឹងនៅឡើយទេ", + "emptyCategoryTitle": "មិនទាន់មានការជូនដំណឹងនៅក្នុងប្រភេទនេះនៅឡើយទេ", + "emptyBody": "ការជូនដំណឹងដែលគណនីនេះទទួលបាននឹងបង្ហាញនៅទីនេះ។ ពួកវាត្រូវបានរក្សាទុកសម្រាប់រយៈពេល {{days}} ថ្ងៃ។", + "capped": "បង្ហាញការជូនដំណឹងថ្មីៗបំផុត។", + "loadError": "មិនអាចផ្ទុកប្រវត្តិការជូនដំណឹងបានទេ។", + "openFailed": "ការជូនដំណឹងនេះលែងបើកនៅកន្លែងណាទៀតហើយ។" + }, "action": { "openSession": "បើកវគ្គ" }, diff --git a/apps/mobile/src/i18n/locales/kn.json b/apps/mobile/src/i18n/locales/kn.json index c605ea39f38..0a04613cbe2 100644 --- a/apps/mobile/src/i18n/locales/kn.json +++ b/apps/mobile/src/i18n/locales/kn.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "ಮಿತಿ ದಾಟುವಿಕೆ ಮತ್ತು ಪ್ರತಿ ಗಂಟೆಯ ಏರಿಕೆಗಳು", "spendAlertsToggle": "ಖರ್ಚಿನ ಎಚ್ಚರಿಕೆಗಳ ವರ್ಗ" }, + "history": { + "title": "ಅಧಿಸೂಚನೆ ಇತಿಹಾಸ", + "subtitle": "ಈ ಖಾತೆ ಸ್ವೀಕರಿಸಿದ ಅಧಿಸೂಚನೆಗಳು", + "bannerBody": "ಈ ಸಾಧನದಲ್ಲಿ ಪುಶ್ ಅಧಿಸೂಚನೆಗಳು ಆಫ್ ಆಗಿವೆ, ಆದ್ದರಿಂದ ಹೊಸ ಅಧಿಸೂಚನೆಗಳು ಇಲ್ಲಿ ಬರುವುದಿಲ್ಲ.", + "bannerAction": "ಅಧಿಸೂಚನೆ ಸೆಟ್ಟಿಂಗ್‌ಗಳು", + "emptyTitle": "ಇನ್ನೂ ಯಾವುದೇ ಅಧಿಸೂಚನೆಗಳಿಲ್ಲ", + "emptyCategoryTitle": "ಈ ವರ್ಗದಲ್ಲಿ ಯಾವುದೇ ಅಧಿಸೂಚನೆಗಳಿಲ್ಲ", + "emptyBody": "ಈ ಖಾತೆ ಸ್ವೀಕರಿಸುವ ಅಧಿಸೂಚನೆಗಳು ಇಲ್ಲಿ ಕಾಣಿಸಿಕೊಳ್ಳುತ್ತವೆ. ಅವುಗಳನ್ನು {{days}} ದಿನಗಳವರೆಗೆ ಇರಿಸಲಾಗುತ್ತದೆ.", + "capped": "ಅತ್ಯಂತ ಇತ್ತೀಚಿನ ಅಧಿಸೂಚನೆಗಳನ್ನು ತೋರಿಸಲಾಗುತ್ತಿದೆ.", + "loadError": "ಅಧಿಸೂಚನೆ ಇತಿಹಾಸವನ್ನು ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ.", + "openFailed": "ಈ ಅಧಿಸೂಚನೆ ಇನ್ನು ಮುಂದೆ ಎಲ್ಲಿಯೂ ತೆರೆಯುವುದಿಲ್ಲ." + }, "action": { "openSession": "ಸೆಷನ್ ತೆರೆಯಿರಿ" }, diff --git a/apps/mobile/src/i18n/locales/ko.json b/apps/mobile/src/i18n/locales/ko.json index e336148de62..0d765e46cf6 100644 --- a/apps/mobile/src/i18n/locales/ko.json +++ b/apps/mobile/src/i18n/locales/ko.json @@ -353,6 +353,18 @@ "spendAlertsSubtitle": "임계값 초과 및 시간당 급증", "spendAlertsToggle": "지출 알림 카테고리" }, + "history": { + "title": "알림 기록", + "subtitle": "이 계정이 받은 알림", + "bannerBody": "이 기기에서 푸시 알림이 꺼져 있어 새 알림이 여기에 도착하지 않습니다.", + "bannerAction": "알림 설정", + "emptyTitle": "아직 알림이 없습니다", + "emptyCategoryTitle": "이 카테고리에 알림이 없습니다", + "emptyBody": "이 계정이 받은 알림이 여기에 표시됩니다. {{days}}일 동안 보관됩니다.", + "capped": "최근 알림을 표시합니다.", + "loadError": "알림 기록을 불러오지 못했습니다.", + "openFailed": "이 알림은 더 이상 어디에서도 열 수 없습니다." + }, "action": { "openSession": "세션 열기" }, diff --git a/apps/mobile/src/i18n/locales/lo.json b/apps/mobile/src/i18n/locales/lo.json index ea1dc311989..127efda5213 100644 --- a/apps/mobile/src/i18n/locales/lo.json +++ b/apps/mobile/src/i18n/locales/lo.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "ການຂ້າມຂີດຈຳກັດ ແລະ ການເພີ່ມຂຶ້ນຕໍ່ຊົ່ວໂມງ", "spendAlertsToggle": "ໝວດການແຈ້ງເຕືອນການໃຊ້ຈ່າຍ" }, + "history": { + "title": "ປະຫວັດການແຈ້ງເຕືອນ", + "subtitle": "ການແຈ້ງເຕືອນທີ່ບັນຊີນີ້ໄດ້ຮັບ", + "bannerBody": "ການແຈ້ງເຕືອນແບບພຸດຖືກປິດຢູ່ໃນອຸປະກອນນີ້, ດັ່ງນັ້ນການແຈ້ງເຕືອນໃໝ່ຈະບໍ່ມາຮອດນີ້.", + "bannerAction": "ການຕັ້ງຄ່າການແຈ້ງເຕືອນ", + "emptyTitle": "ຍັງບໍ່ມີການແຈ້ງເຕືອນ", + "emptyCategoryTitle": "ຍັງບໍ່ມີການແຈ້ງເຕືອນໃນໝວດໝູ່ນີ້", + "emptyBody": "ການແຈ້ງເຕືອນທີ່ບັນຊີນີ້ໄດ້ຮັບຈະສະແດງຢູ່ນີ້. ພວກມັນຈະຖືກເກັບໄວ້ {{days}} ມື້.", + "capped": "ກຳລັງສະແດງການແຈ້ງເຕືອນຫຼ້າສຸດ.", + "loadError": "ບໍ່ສາມາດໂຫຼດປະຫວັດການແຈ້ງເຕືອນໄດ້.", + "openFailed": "ການແຈ້ງເຕືອນນີ້ບໍ່ສາມາດເປີດໄດ້ອີກຕໍ່ໄປ." + }, "action": { "openSession": "ເປີດເຊສຊັນ" }, diff --git a/apps/mobile/src/i18n/locales/lt.json b/apps/mobile/src/i18n/locales/lt.json index 5e39d338cf0..a2f3653e22a 100644 --- a/apps/mobile/src/i18n/locales/lt.json +++ b/apps/mobile/src/i18n/locales/lt.json @@ -915,6 +915,18 @@ "spendAlertsSubtitle": "slenksčių viršijimai ir valandiniai šuoliai", "spendAlertsToggle": "Išlaidų įspėjimų kategorija" }, + "history": { + "title": "Pranešimų istorija", + "subtitle": "Pranešimai, kuriuos gavo ši paskyra", + "bannerBody": "Šiame įrenginyje stumiamieji pranešimai išjungti, todėl nauji pranešimai čia neatkeliaus.", + "bannerAction": "Pranešimų nustatymai", + "emptyTitle": "Kol kas pranešimų nėra", + "emptyCategoryTitle": "Šioje kategorijoje pranešimų nėra", + "emptyBody": "Pranešimai, kuriuos gauna ši paskyra, rodomi čia. Jie saugomi {{days}} dienų.", + "capped": "Rodomi naujausi pranešimai.", + "loadError": "Nepavyko įkelti pranešimų istorijos.", + "openFailed": "Šio pranešimo nebegalima atidaryti niekur." + }, "action": { "openSession": "Atidaryti sesiją" }, diff --git a/apps/mobile/src/i18n/locales/lv.json b/apps/mobile/src/i18n/locales/lv.json index 2d96e261fe2..d4bff4839a8 100644 --- a/apps/mobile/src/i18n/locales/lv.json +++ b/apps/mobile/src/i18n/locales/lv.json @@ -896,6 +896,18 @@ "spendAlertsSubtitle": "sliekšņu pārsniegumi un stundas lēcieni", "spendAlertsToggle": "Izdevumu brīdinājumu kategorija" }, + "history": { + "title": "Paziņojumu vēsture", + "subtitle": "Paziņojumi, ko šis konts ir saņēmis", + "bannerBody": "Push paziņojumi šajā ierīcē ir izslēgti, tāpēc jaunie šeit netiks saņemti.", + "bannerAction": "Paziņojumu iestatījumi", + "emptyTitle": "Vēl nav paziņojumu", + "emptyCategoryTitle": "Šajā kategorijā nav paziņojumu", + "emptyBody": "Paziņojumi, ko šis konts saņem, parādās šeit. Tie tiek glabāti {{days}} dienas.", + "capped": "Tiek rādīti jaunākie paziņojumi.", + "loadError": "Neizdevās ielādēt paziņojumu vēsturi.", + "openFailed": "Šis paziņojums vairs nekur neatveras." + }, "action": { "openSession": "Atvērt sesiju" }, diff --git a/apps/mobile/src/i18n/locales/mg.json b/apps/mobile/src/i18n/locales/mg.json index 6cfd571a05b..9cd80711317 100644 --- a/apps/mobile/src/i18n/locales/mg.json +++ b/apps/mobile/src/i18n/locales/mg.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "fihoaram-bidy sy fitsambikinana isan'ora", "spendAlertsToggle": "Sokajin'ny fampitandremana momba ny fandaniana" }, + "history": { + "title": "Tantaran'ny fampandrenesana", + "subtitle": "Fampandrenesana voarain'ity kaonty ity", + "bannerBody": "Tsy mandeha ny fampandrenesana push amin'ity fitaovana ity, ka tsy hisy vaovao tonga eto.", + "bannerAction": "Fandrindran'ny fampandrenesana", + "emptyTitle": "Mbola tsy misy fampandrenesana", + "emptyCategoryTitle": "Tsy misy fampandrenesana ao amin'ity sokajy ity", + "emptyBody": "Miseho eto ny fampandrenesana azon'ity kaonty ity. Tehirizina mandritra ny {{days}} andro izy ireo.", + "capped": "Aseho ny fampandrenesana farany indrindra.", + "loadError": "Tsy nahomby ny fampakarana ny tantaran'ny fampandrenesana.", + "openFailed": "Tsy misokatra na aiza na aiza intsony io fampandrenesana io." + }, "action": { "openSession": "Sokafy ny fotoam-piasana" }, diff --git a/apps/mobile/src/i18n/locales/mi.json b/apps/mobile/src/i18n/locales/mi.json index e36d0d7a3b7..a6d405c2c5e 100644 --- a/apps/mobile/src/i18n/locales/mi.json +++ b/apps/mobile/src/i18n/locales/mi.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "ngā whitinga paepae me ngā pikinga ia hāora", "spendAlertsToggle": "Kāwai matohi whakapaunga" }, + "history": { + "title": "Hītori whakamōhiotanga", + "subtitle": "Ngā whakamōhiotanga i riro i tēnei pūkete", + "bannerBody": "Kua weto ngā pānui pana mō tēnei taputapu, nō reira kāore e tae mai he pānui hou ki konei.", + "bannerAction": "Ngā tautuhinga whakamōhiotanga", + "emptyTitle": "Kāore anō he whakamōhiotanga", + "emptyCategoryTitle": "Kāore he whakamōhiotanga i tēnei kāwai", + "emptyBody": "Ka puta mai ki konei ngā whakamōhiotanga ka riro i tēnei pūkete. Ka tiakina mō {{days}} rā.", + "capped": "E whakaatu ana i ngā whakamōhiotanga tino hou.", + "loadError": "Kāore i taea te uta i te hītori whakamōhiotanga.", + "openFailed": "Kāore tēnei whakamōhiotanga e taea te whakatuwhera i tētahi wāhi ināianei." + }, "action": { "openSession": "Whakatuwheratia te wātū" }, diff --git a/apps/mobile/src/i18n/locales/mk.json b/apps/mobile/src/i18n/locales/mk.json index 4c47a2c284e..cc21923778d 100644 --- a/apps/mobile/src/i18n/locales/mk.json +++ b/apps/mobile/src/i18n/locales/mk.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "преминувања на прагови и часовни скокови", "spendAlertsToggle": "Категорија предупредувања за трошење" }, + "history": { + "title": "Историја на известувања", + "subtitle": "Известувањата што ги прими оваа сметка", + "bannerBody": "Push известувањата се исклучени за овој уред, така што новите нема да пристигнат овде.", + "bannerAction": "Поставки за известувања", + "emptyTitle": "Сè уште нема известувања", + "emptyCategoryTitle": "Нема известувања во оваа категорија", + "emptyBody": "Известувањата што ги прима оваа сметка се појавуваат овде. Се чуваат {{days}} дена.", + "capped": "Се прикажуваат најновите известувања.", + "loadError": "Не успеа вчитувањето на историјата на известувања.", + "openFailed": "Ова известување веќе не се отвора никаде." + }, "action": { "openSession": "Отвори сесија" }, diff --git a/apps/mobile/src/i18n/locales/ml.json b/apps/mobile/src/i18n/locales/ml.json index 335bf73e4be..12107db74ac 100644 --- a/apps/mobile/src/i18n/locales/ml.json +++ b/apps/mobile/src/i18n/locales/ml.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "പരിധി ലംഘനങ്ങളും മണിക്കൂർ തോറുമുള്ള വർധനകളും", "spendAlertsToggle": "ചെലവ് മുന്നറിയിപ്പ് വിഭാഗം" }, + "history": { + "title": "അറിയിപ്പ് ചരിത്രം", + "subtitle": "ഈ അക്കൗണ്ടിന് ലഭിച്ച അറിയിപ്പുകൾ", + "bannerBody": "ഈ ഉപകരണത്തിൽ പുഷ് അറിയിപ്പുകൾ ഓഫാണ്, അതിനാൽ പുതിയവ ഇവിടെ എത്തില്ല.", + "bannerAction": "അറിയിപ്പ് ക്രമീകരണങ്ങൾ", + "emptyTitle": "ഇതുവരെ അറിയിപ്പുകൾ ഇല്ല", + "emptyCategoryTitle": "ഈ വിഭാഗത്തിൽ അറിയിപ്പുകൾ ഇല്ല", + "emptyBody": "ഈ അക്കൗണ്ടിന് ലഭിക്കുന്ന അറിയിപ്പുകൾ ഇവിടെ ദൃശ്യമാകും. അവ {{days}} ദിവസത്തേക്ക് സൂക്ഷിക്കുന്നു.", + "capped": "ഏറ്റവും പുതിയ അറിയിപ്പുകൾ കാണിക്കുന്നു.", + "loadError": "അറിയിപ്പ് ചരിത്രം ലോഡ് ചെയ്യാനായില്ല.", + "openFailed": "ഈ അറിയിപ്പ് ഇനി എവിടെയും തുറക്കില്ല." + }, "action": { "openSession": "സെഷൻ തുറക്കുക" }, diff --git a/apps/mobile/src/i18n/locales/mn.json b/apps/mobile/src/i18n/locales/mn.json index 0154a7fccc3..8e0b7118cf2 100644 --- a/apps/mobile/src/i18n/locales/mn.json +++ b/apps/mobile/src/i18n/locales/mn.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "босго давсан болон цагийн огцом өсөлт", "spendAlertsToggle": "Зарцуулалтын анхааруулгын ангилал" }, + "history": { + "title": "Мэдэгдлийн түүх", + "subtitle": "Энэ бүртгэлийн хүлээн авсан мэдэгдлүүд", + "bannerBody": "Энэ төхөөрөмж дээр push мэдэгдэл унтарсан тул шинэ мэдэгдэл энд ирэхгүй.", + "bannerAction": "Мэдэгдлийн тохиргоо", + "emptyTitle": "Одоогоор мэдэгдэл байхгүй", + "emptyCategoryTitle": "Энэ ангилалд мэдэгдэл байхгүй", + "emptyBody": "Энэ бүртгэлийн хүлээн авсан мэдэгдлүүд энд харагдана. Тэдгээрийг {{days}} хоног хадгална.", + "capped": "Хамгийн сүүлийн мэдэгдлүүдийг харуулж байна.", + "loadError": "Мэдэгдлийн түүхийг ачаалж чадсангүй.", + "openFailed": "Энэ мэдэгдэл хаана ч нээгдэхгүй болсон." + }, "action": { "openSession": "Сесс нээх" }, diff --git a/apps/mobile/src/i18n/locales/mr.json b/apps/mobile/src/i18n/locales/mr.json index 975e503e1e2..32e29c90df3 100644 --- a/apps/mobile/src/i18n/locales/mr.json +++ b/apps/mobile/src/i18n/locales/mr.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "मर्यादा ओलांडणे आणि प्रति तास वाढ", "spendAlertsToggle": "खर्चाच्या सूचना श्रेणी" }, + "history": { + "title": "सूचनांचा इतिहास", + "subtitle": "या खात्याला मिळालेल्या सूचना", + "bannerBody": "या डिव्हाइसवर पुश सूचना बंद आहेत, त्यामुळे नवीन सूचना येथे येणार नाहीत.", + "bannerAction": "सूचना सेटिंग्ज", + "emptyTitle": "अद्याप सूचना नाहीत", + "emptyCategoryTitle": "या श्रेणीत सूचना नाहीत", + "emptyBody": "या खात्याला मिळणाऱ्या सूचना येथे दिसतात. त्या {{days}} दिवसांसाठी ठेवल्या जातात.", + "capped": "सर्वात अलीकडच्या सूचना दाखवल्या जात आहेत.", + "loadError": "सूचनांचा इतिहास लोड करता आला नाही.", + "openFailed": "ही सूचना आता कुठेही उघडत नाही." + }, "action": { "openSession": "सत्र उघडा" }, diff --git a/apps/mobile/src/i18n/locales/ms.json b/apps/mobile/src/i18n/locales/ms.json index 1591c879719..bc7fe64a0e4 100644 --- a/apps/mobile/src/i18n/locales/ms.json +++ b/apps/mobile/src/i18n/locales/ms.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "melepasi ambang dan lonjakan setiap jam", "spendAlertsToggle": "Kategori amaran perbelanjaan" }, + "history": { + "title": "Sejarah pemberitahuan", + "subtitle": "Pemberitahuan yang diterima oleh akaun ini", + "bannerBody": "Pemberitahuan push dimatikan untuk peranti ini, jadi yang baharu tidak akan tiba di sini.", + "bannerAction": "Tetapan pemberitahuan", + "emptyTitle": "Belum ada pemberitahuan", + "emptyCategoryTitle": "Tiada pemberitahuan dalam kategori ini", + "emptyBody": "Pemberitahuan yang diterima oleh akaun ini muncul di sini. Ia disimpan selama {{days}} hari.", + "capped": "Memaparkan pemberitahuan terkini.", + "loadError": "Tidak dapat memuatkan sejarah pemberitahuan.", + "openFailed": "Pemberitahuan ini tidak lagi boleh dibuka di mana-mana." + }, "action": { "openSession": "Buka sesi" }, diff --git a/apps/mobile/src/i18n/locales/mt.json b/apps/mobile/src/i18n/locales/mt.json index 6f5293eddea..af4f8376a5a 100644 --- a/apps/mobile/src/i18n/locales/mt.json +++ b/apps/mobile/src/i18n/locales/mt.json @@ -934,6 +934,18 @@ "spendAlertsSubtitle": "qbiż ta' limiti u żidiet fis-siegħa", "spendAlertsToggle": "Kategorija tat-twissijiet tal-infiq" }, + "history": { + "title": "Kronoloġija tan-notifiki", + "subtitle": "In-notifiki li rċieva dan il-kont", + "bannerBody": "In-notifiki push huma mitfija għal dan l-apparat, għalhekk dawk ġodda mhux se jaslu hawn.", + "bannerAction": "Impostazzjonijiet tan-notifiki", + "emptyTitle": "Għad m'hemmx notifiki", + "emptyCategoryTitle": "Għad m'hemmx notifiki f'din il-kategorija", + "emptyBody": "In-notifiki li jirċievi dan il-kont jidhru hawn. Jinżammu għal {{days}} ġranet.", + "capped": "Qed jintwerew in-notifiki l-aktar reċenti.", + "loadError": "Ma setgħetx tiġi mgħobbija l-kronoloġija tan-notifiki.", + "openFailed": "Din in-notifika m'għadhiex tinfetaħ imkien." + }, "action": { "openSession": "Iftaħ is-sessjoni" }, diff --git a/apps/mobile/src/i18n/locales/my.json b/apps/mobile/src/i18n/locales/my.json index c47a4001acf..88a12556164 100644 --- a/apps/mobile/src/i18n/locales/my.json +++ b/apps/mobile/src/i18n/locales/my.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "သတ်မှတ်ချက် ကျော်လွန်မှုနှင့် နာရီအလိုက် မြင့်တက်မှု", "spendAlertsToggle": "အသုံးစရိတ် သတိပေးချက် အမျိုးအစား" }, + "history": { + "title": "အကြောင်းကြားချက် မှတ်တမ်း", + "subtitle": "ဤအကောင့် လက်ခံရရှိသည့် အကြောင်းကြားချက်များ", + "bannerBody": "ဤစက်ပစ္စည်းအတွက် အကြောင်းကြားချက်များ ပိတ်ထားသည်၊ ထို့ကြောင့် အသစ်များ ဤနေရာသို့ မရောက်ရှိပါ။", + "bannerAction": "အကြောင်းကြားချက် ဆက်တင်များ", + "emptyTitle": "အကြောင်းကြားချက်များ မရှိသေးပါ", + "emptyCategoryTitle": "ဤအမျိုးအစားတွင် အကြောင်းကြားချက်များ မရှိပါ", + "emptyBody": "ဤအကောင့် လက်ခံရရှိသော အကြောင်းကြားချက်များ ဤနေရာတွင် ပေါ်လာပါသည်။ ၎င်းတို့ကို {{days}} ရက် သိမ်းဆည်းထားပါသည်။", + "capped": "နောက်ဆုံးရ အကြောင်းကြားချက်များကို ပြသနေပါသည်။", + "loadError": "အကြောင်းကြားချက် မှတ်တမ်းကို တင်၍မရပါ။", + "openFailed": "ဤအကြောင်းကြားချက်ကို မည်သည့်နေရာတွင်မျှ ဖွင့်၍မရတော့ပါ။" + }, "action": { "openSession": "စက်ရှင်ကို ဖွင့်ပါ" }, diff --git a/apps/mobile/src/i18n/locales/nb.json b/apps/mobile/src/i18n/locales/nb.json index 94ff8271e08..db62049f307 100644 --- a/apps/mobile/src/i18n/locales/nb.json +++ b/apps/mobile/src/i18n/locales/nb.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "grenseoverskridelser og topper per time", "spendAlertsToggle": "Kategorien forbruksvarsler" }, + "history": { + "title": "Varslingshistorikk", + "subtitle": "Varsler denne kontoen har mottatt", + "bannerBody": "Pushvarsler er slått av for denne enheten, så nye varsler vil ikke komme hit.", + "bannerAction": "Varslingsinnstillinger", + "emptyTitle": "Ingen varsler ennå", + "emptyCategoryTitle": "Ingen varsler i denne kategorien", + "emptyBody": "Varsler denne kontoen mottar, vises her. De oppbevares i {{days}} dager.", + "capped": "Viser de nyeste varslene.", + "loadError": "Kunne ikke laste varslingshistorikk.", + "openFailed": "Dette varselet åpnes ikke lenger noe sted." + }, "action": { "openSession": "Åpne økt" }, diff --git a/apps/mobile/src/i18n/locales/ne.json b/apps/mobile/src/i18n/locales/ne.json index 8d3330c8550..30691574f9b 100644 --- a/apps/mobile/src/i18n/locales/ne.json +++ b/apps/mobile/src/i18n/locales/ne.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "सीमा नाघ्ने र प्रति घण्टा वृद्धि", "spendAlertsToggle": "खर्चका चेतावनी श्रेणी" }, + "history": { + "title": "सूचना इतिहास", + "subtitle": "यो खाताले प्राप्त गरेका सूचनाहरू", + "bannerBody": "यो यन्त्रका लागि पुश सूचनाहरू बन्द छन्, त्यसैले नयाँ सूचनाहरू यहाँ आउँदैनन्।", + "bannerAction": "सूचना सेटिङहरू", + "emptyTitle": "अहिलेसम्म कुनै सूचना छैन", + "emptyCategoryTitle": "यो श्रेणीमा कुनै सूचना छैन", + "emptyBody": "यो खाताले प्राप्त गर्ने सूचनाहरू यहाँ देखिन्छन्। तिनीहरू {{days}} दिनसम्म राखिन्छन्।", + "capped": "सबैभन्दा पछिल्ला सूचनाहरू देखाइँदै छ।", + "loadError": "सूचना इतिहास लोड गर्न सकिएन।", + "openFailed": "यो सूचना अब कतै खुल्दैन।" + }, "action": { "openSession": "सत्र खोल्नुहोस्" }, diff --git a/apps/mobile/src/i18n/locales/nl.json b/apps/mobile/src/i18n/locales/nl.json index 7068743475a..7893025a406 100644 --- a/apps/mobile/src/i18n/locales/nl.json +++ b/apps/mobile/src/i18n/locales/nl.json @@ -326,6 +326,18 @@ "spendAlertsSubtitle": "drempeloverschrijdingen en uurlijkse pieken", "spendAlertsToggle": "Categorie bestedingswaarschuwingen" }, + "history": { + "title": "Meldingsgeschiedenis", + "subtitle": "Meldingen die dit account heeft ontvangen", + "bannerBody": "Pushmeldingen staan uit voor dit apparaat, dus nieuwe meldingen komen hier niet binnen.", + "bannerAction": "Meldingsinstellingen", + "emptyTitle": "Nog geen meldingen", + "emptyCategoryTitle": "Nog geen meldingen in deze categorie", + "emptyBody": "Meldingen die dit account ontvangt, verschijnen hier. Ze worden {{days}} dagen bewaard.", + "capped": "De meest recente meldingen worden weergegeven.", + "loadError": "Meldingsgeschiedenis kon niet worden geladen.", + "openFailed": "Deze melding opent nergens meer." + }, "liveActivities": "Liveactiviteiten", "liveActivitySubtitle": "Actieve agents op het toegangsscherm tonen", "liveUpdates": "Live-updates", diff --git a/apps/mobile/src/i18n/locales/om.json b/apps/mobile/src/i18n/locales/om.json index 9938e15cf98..795f53b4816 100644 --- a/apps/mobile/src/i18n/locales/om.json +++ b/apps/mobile/src/i18n/locales/om.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "daarbii daangaa fi olka'iinsa sa'aatii", "spendAlertsToggle": "Ramaddii akeekkachiisa baasii" }, + "history": { + "title": "Seenaa beeksisaa", + "subtitle": "Beeksisoota herregni kun argate", + "bannerBody": "Beeksisoonni push meeshaa kanaaf cufamaniiru, kanaaf haaraan asitti hin dhufan.", + "bannerAction": "Qindaa'ina beeksisaa", + "emptyTitle": "Ammatti beeksisoonni hin jiran", + "emptyCategoryTitle": "Beeksisoonni rammii kana keessatti hin jiran", + "emptyBody": "Beeksisoonni herregni kun fudhatu asitti mul'atu. Guyyaa {{days}}f ni eegamu.", + "capped": "Beeksisoonni haaraan agarsiifamaa jiran.", + "loadError": "Seenaa beeksisaa fiduu hin dandeenye.", + "openFailed": "Beeksisni kun amma bakka tokkollee hin banamu." + }, "action": { "openSession": "Turtii bani" }, diff --git a/apps/mobile/src/i18n/locales/or.json b/apps/mobile/src/i18n/locales/or.json index 32664065bef..942fd4ed044 100644 --- a/apps/mobile/src/i18n/locales/or.json +++ b/apps/mobile/src/i18n/locales/or.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "ସୀମା ଅତିକ୍ରମ ଏବଂ ଘଣ୍ଟାପ୍ରତି ବୃଦ୍ଧି", "spendAlertsToggle": "ଖର୍ଚ୍ଚ ସତର୍କତା ବର୍ଗ" }, + "history": { + "title": "ବିଜ୍ଞପ୍ତି ଇତିହାସ", + "subtitle": "ଏହି ଖାତାରେ ଗ୍ରହଣ କରିଥିବା ବିଜ୍ଞପ୍ତି", + "bannerBody": "ଏହି ଡିଭାଇସ୍ ପାଇଁ ପୁଶ୍ ବିଜ୍ଞପ୍ତି ବନ୍ଦ ଅଛି, ତେଣୁ ନୂଆ ବିଜ୍ଞପ୍ତି ଏଠାରେ ଆସିବ ନାହିଁ।", + "bannerAction": "ବିଜ୍ଞପ୍ତି ସେଟିଂସ୍", + "emptyTitle": "ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ବିଜ୍ଞପ୍ତି ନାହିଁ", + "emptyCategoryTitle": "ଏହି ବର୍ଗରେ କୌଣସି ବିଜ୍ଞପ୍ତି ନାହିଁ", + "emptyBody": "ଏହି ଖାତାରେ ଗ୍ରହଣ କରୁଥିବା ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ଏଠାରେ ଦେଖାଯିବ। ସେଗୁଡ଼ିକ {{days}} ଦିନ ପାଇଁ ରଖାଯାଏ।", + "capped": "ସର୍ବାଧିକ ନୂଆ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ଦେଖାଯାଉଛି।", + "loadError": "ବିଜ୍ଞପ୍ତି ଇତିହାସ ଲୋଡ୍ ହୋଇପାରିଲା ନାହିଁ।", + "openFailed": "ଏହି ବିଜ୍ଞପ୍ତି ଆଉ କୌଣସି ସ୍ଥାନରେ ଖୁଲିବ ନାହିଁ।" + }, "action": { "openSession": "ସେସନ ଖୋଲନ୍ତୁ" }, diff --git a/apps/mobile/src/i18n/locales/pa.json b/apps/mobile/src/i18n/locales/pa.json index 884029537c6..7dac23383a0 100644 --- a/apps/mobile/src/i18n/locales/pa.json +++ b/apps/mobile/src/i18n/locales/pa.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "ਸੀਮਾ ਟੱਪਣਾ ਅਤੇ ਹਰ ਘੰਟੇ ਵਾਧਾ", "spendAlertsToggle": "ਖਰਚੇ ਦੀਆਂ ਚੇਤਾਵਨੀਆਂ ਦੀ ਸ਼੍ਰੇਣੀ" }, + "history": { + "title": "ਸੂਚਨਾਵਾਂ ਦਾ ਇਤਿਹਾਸ", + "subtitle": "ਇਸ ਖਾਤੇ ਨੂੰ ਮਿਲੀਆਂ ਸੂਚਨਾਵਾਂ", + "bannerBody": "ਇਸ ਡੀਵਾਈਸ ਲਈ ਪੁਸ਼ ਸੂਚਨਾਵਾਂ ਬੰਦ ਹਨ, ਇਸ ਲਈ ਨਵੀਆਂ ਸੂਚਨਾਵਾਂ ਇੱਥੇ ਨਹੀਂ ਆਉਣਗੀਆਂ।", + "bannerAction": "ਸੂਚਨਾ ਸੈਟਿੰਗਾਂ", + "emptyTitle": "ਹਾਲੇ ਕੋਈ ਸੂਚਨਾ ਨਹੀਂ", + "emptyCategoryTitle": "ਇਸ ਸ਼੍ਰੇਣੀ ਵਿੱਚ ਕੋਈ ਸੂਚਨਾ ਨਹੀਂ", + "emptyBody": "ਇਸ ਖਾਤੇ ਨੂੰ ਮਿਲਣ ਵਾਲੀਆਂ ਸੂਚਨਾਵਾਂ ਇੱਥੇ ਦਿਖਾਈ ਦਿੰਦੀਆਂ ਹਨ। ਇਹਨਾਂ ਨੂੰ {{days}} ਦਿਨਾਂ ਲਈ ਰੱਖਿਆ ਜਾਂਦਾ ਹੈ।", + "capped": "ਸਭ ਤੋਂ ਨਵੀਆਂ ਸੂਚਨਾਵਾਂ ਦਿਖਾਈਆਂ ਜਾ ਰਹੀਆਂ ਹਨ।", + "loadError": "ਸੂਚਨਾਵਾਂ ਦਾ ਇਤਿਹਾਸ ਲੋਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ।", + "openFailed": "ਇਹ ਸੂਚਨਾ ਹੁਣ ਕਿਤੇ ਵੀ ਨਹੀਂ ਖੁੱਲ੍ਹਦੀ।" + }, "action": { "openSession": "ਸੈਸ਼ਨ ਖੋਲ੍ਹੋ" }, diff --git a/apps/mobile/src/i18n/locales/pl.json b/apps/mobile/src/i18n/locales/pl.json index 4f14fa44168..78f0e4be5e1 100644 --- a/apps/mobile/src/i18n/locales/pl.json +++ b/apps/mobile/src/i18n/locales/pl.json @@ -353,6 +353,18 @@ "spendAlertsSubtitle": "przekroczenia progów i skoki godzinowe", "spendAlertsToggle": "Kategoria alertów o wydatkach" }, + "history": { + "title": "Historia powiadomień", + "subtitle": "Powiadomienia otrzymane przez to konto", + "bannerBody": "Powiadomienia push są wyłączone dla tego urządzenia, więc nowe nie będą tu przychodzić.", + "bannerAction": "Ustawienia powiadomień", + "emptyTitle": "Nie ma jeszcze powiadomień", + "emptyCategoryTitle": "Brak powiadomień w tej kategorii", + "emptyBody": "Powiadomienia otrzymywane przez to konto pojawiają się tutaj. Są przechowywane przez {{days}} dni.", + "capped": "Wyświetlane są najnowsze powiadomienia.", + "loadError": "Nie udało się wczytać historii powiadomień.", + "openFailed": "Tego powiadomienia nie można już nigdzie otworzyć." + }, "action": { "openSession": "Otwórz sesję" }, diff --git a/apps/mobile/src/i18n/locales/ps.json b/apps/mobile/src/i18n/locales/ps.json index 1098a9dae9c..2e5d60ab4dd 100644 --- a/apps/mobile/src/i18n/locales/ps.json +++ b/apps/mobile/src/i18n/locales/ps.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "د حدونو تېرېدل او ساعتي زیاتوالی", "spendAlertsToggle": "د لګښت خبرتیاوو کټګوري" }, + "history": { + "title": "د خبرتیاوو تاریخچه", + "subtitle": "هغه خبرتیاوې چې دا حساب ترلاسه کړې دي", + "bannerBody": "د دې وسیلې لپاره پش خبرتیاوې بندې دي، نو نوي به دلته نه رسیږي.", + "bannerAction": "د خبرتیاوو تنظیمات", + "emptyTitle": "تر اوسه هېڅ خبرتیاوې نشته", + "emptyCategoryTitle": "په دې کټګورۍ کې هېڅ خبرتیاوې نشته", + "emptyBody": "هغه خبرتیاوې چې دا حساب ترلاسه کوي دلته ښکاري. دوی د {{days}} ورځو لپاره ساتل کیږي.", + "capped": "تر ټولو وروستي خبرتیاوې ښودل کیږي.", + "loadError": "د خبرتیاوو تاریخچه بار نه شوه.", + "openFailed": "دا خبرتیا نوره هېچیرته نه خلاصیږي." + }, "action": { "openSession": "ناسته پرانیزئ" }, diff --git a/apps/mobile/src/i18n/locales/pt-BR.json b/apps/mobile/src/i18n/locales/pt-BR.json index 752e8463b49..4aee5bb3247 100644 --- a/apps/mobile/src/i18n/locales/pt-BR.json +++ b/apps/mobile/src/i18n/locales/pt-BR.json @@ -321,6 +321,18 @@ "spendAlertsSubtitle": "ultrapassagens de limites e picos por hora", "spendAlertsToggle": "Categoria de alertas de gastos" }, + "history": { + "title": "Histórico de notificações", + "subtitle": "Notificações recebidas por esta conta", + "bannerBody": "As notificações push estão desativadas neste dispositivo, então novas notificações não chegarão aqui.", + "bannerAction": "Configurações de notificações", + "emptyTitle": "Nenhuma notificação ainda", + "emptyCategoryTitle": "Nenhuma notificação nesta categoria", + "emptyBody": "As notificações que esta conta recebe aparecem aqui. Elas são mantidas por {{days}} dias.", + "capped": "Mostrando as notificações mais recentes.", + "loadError": "Não foi possível carregar o histórico de notificações.", + "openFailed": "Esta notificação não abre mais em lugar nenhum." + }, "action": { "openSession": "Abrir sessão" }, diff --git a/apps/mobile/src/i18n/locales/pt.json b/apps/mobile/src/i18n/locales/pt.json index 4f0f4a5abd0..226fc693f06 100644 --- a/apps/mobile/src/i18n/locales/pt.json +++ b/apps/mobile/src/i18n/locales/pt.json @@ -896,6 +896,18 @@ "spendAlertsSubtitle": "ultrapassagens de limites e picos horários", "spendAlertsToggle": "Categoria de alertas de gastos" }, + "history": { + "title": "Histórico de notificações", + "subtitle": "Notificações que esta conta recebeu", + "bannerBody": "As notificações push estão desativadas neste dispositivo, por isso as novas não chegarão aqui.", + "bannerAction": "Definições de notificações", + "emptyTitle": "Ainda não há notificações", + "emptyCategoryTitle": "Ainda não há notificações nesta categoria", + "emptyBody": "As notificações que esta conta recebe aparecem aqui. São guardadas durante {{days}} dias.", + "capped": "A mostrar as notificações mais recentes.", + "loadError": "Não foi possível carregar o histórico de notificações.", + "openFailed": "Esta notificação já não abre em lado nenhum." + }, "action": { "openSession": "Abrir sessão" }, diff --git a/apps/mobile/src/i18n/locales/ro.json b/apps/mobile/src/i18n/locales/ro.json index 0cd7e37d1ef..2f62a31a630 100644 --- a/apps/mobile/src/i18n/locales/ro.json +++ b/apps/mobile/src/i18n/locales/ro.json @@ -896,6 +896,18 @@ "spendAlertsSubtitle": "depășiri de praguri și vârfuri orare", "spendAlertsToggle": "Categoria alerte de cheltuieli" }, + "history": { + "title": "Istoric notificări", + "subtitle": "Notificările primite de acest cont", + "bannerBody": "Notificările push sunt dezactivate pentru acest dispozitiv, așa că cele noi nu vor ajunge aici.", + "bannerAction": "Setări notificări", + "emptyTitle": "Nicio notificare încă", + "emptyCategoryTitle": "Nicio notificare în această categorie", + "emptyBody": "Notificările primite de acest cont apar aici. Acestea se păstrează pentru {{days}} zile.", + "capped": "Se afișează cele mai recente notificări.", + "loadError": "Nu s-a putut încărca istoricul notificărilor.", + "openFailed": "Această notificare nu se mai deschide nicăieri." + }, "action": { "openSession": "Deschide sesiunea" }, diff --git a/apps/mobile/src/i18n/locales/ru.json b/apps/mobile/src/i18n/locales/ru.json index 36a99ba01a3..7499b2f8453 100644 --- a/apps/mobile/src/i18n/locales/ru.json +++ b/apps/mobile/src/i18n/locales/ru.json @@ -353,6 +353,18 @@ "spendAlertsSubtitle": "превышения порогов и почасовые всплески", "spendAlertsToggle": "Категория «Оповещения о расходах»" }, + "history": { + "title": "История уведомлений", + "subtitle": "Уведомления, полученные этим аккаунтом", + "bannerBody": "Push-уведомления отключены на этом устройстве, поэтому новые не будут приходить сюда.", + "bannerAction": "Настройки уведомлений", + "emptyTitle": "Уведомлений пока нет", + "emptyCategoryTitle": "В этой категории уведомлений нет", + "emptyBody": "Уведомления, которые получает этот аккаунт, появляются здесь. Они хранятся {{days}} дней.", + "capped": "Показаны последние уведомления.", + "loadError": "Не удалось загрузить историю уведомлений.", + "openFailed": "Это уведомление больше нигде не открывается." + }, "action": { "openSession": "Открыть сеанс" }, diff --git a/apps/mobile/src/i18n/locales/si.json b/apps/mobile/src/i18n/locales/si.json index 8d62fd2676c..2c031e219fa 100644 --- a/apps/mobile/src/i18n/locales/si.json +++ b/apps/mobile/src/i18n/locales/si.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "සීමා ඉක්මවීම් සහ පැයකට ඇති උච්චතා", "spendAlertsToggle": "වියදම් දැනුම්දීම් ප්‍රවර්ගය" }, + "history": { + "title": "දැනුම්දීම් ඉතිහාසය", + "subtitle": "මෙම ගිණුමට ලැබුණු දැනුම්දීම්", + "bannerBody": "මෙම උපාංගය සඳහා තල්ලු දැනුම්දීම් අක්‍රීය කර ඇත, එබැවින් නව ඒවා මෙහි නොපැමිණේ.", + "bannerAction": "දැනුම්දීම් සැකසීම්", + "emptyTitle": "තවම දැනුම්දීම් නැත", + "emptyCategoryTitle": "මෙම කාණ්ඩයේ දැනුම්දීම් නැත", + "emptyBody": "මෙම ගිණුමට ලැබෙන දැනුම්දීම් මෙහි දිස්වේ. ඒවා දින {{days}} ක් තබා ගනු ලැබේ.", + "capped": "නවතම දැනුම්දීම් පෙන්වයි.", + "loadError": "දැනුම්දීම් ඉතිහාසය පූරණය කිරීමට නොහැකි විය.", + "openFailed": "මෙම දැනුම්දීම තවදුරටත් කිසිදු තැනක විවෘත නොවේ." + }, "action": { "openSession": "සැසිය විවෘත කරන්න" }, diff --git a/apps/mobile/src/i18n/locales/sk.json b/apps/mobile/src/i18n/locales/sk.json index 30f8bf9b68a..08798139e1d 100644 --- a/apps/mobile/src/i18n/locales/sk.json +++ b/apps/mobile/src/i18n/locales/sk.json @@ -915,6 +915,18 @@ "spendAlertsSubtitle": "prekročenia prahov a hodinové špičky", "spendAlertsToggle": "Kategória upozornení na výdavky" }, + "history": { + "title": "História upozornení", + "subtitle": "Upozornenia, ktoré tento účet dostal", + "bannerBody": "Push upozornenia sú pre toto zariadenie vypnuté, takže sem nové nebudú prichádzať.", + "bannerAction": "Nastavenia upozornení", + "emptyTitle": "Zatiaľ žiadne upozornenia", + "emptyCategoryTitle": "Žiadne upozornenia v tejto kategórii", + "emptyBody": "Upozornenia, ktoré tento účet dostáva, sa zobrazujú tu. Uchovávajú sa {{days}} dní.", + "capped": "Zobrazujú sa najnovšie upozornenia.", + "loadError": "Históriu upozornení sa nepodarilo načítať.", + "openFailed": "Toto upozornenie sa už nikde neotvorí." + }, "action": { "openSession": "Otvoriť reláciu" }, diff --git a/apps/mobile/src/i18n/locales/sl.json b/apps/mobile/src/i18n/locales/sl.json index 9564703661e..6a101a3966a 100644 --- a/apps/mobile/src/i18n/locales/sl.json +++ b/apps/mobile/src/i18n/locales/sl.json @@ -915,6 +915,18 @@ "spendAlertsSubtitle": "preseganja pragov in urni skoki", "spendAlertsToggle": "Kategorija opozoril o porabi" }, + "history": { + "title": "Zgodovina obvestil", + "subtitle": "Obvestila, ki jih je prejel ta račun", + "bannerBody": "Potisna obvestila so za to napravo izklopljena, zato nova ne bodo prispela sem.", + "bannerAction": "Nastavitve obvestil", + "emptyTitle": "Ni še obvestil", + "emptyCategoryTitle": "Ni obvestil v tej kategoriji", + "emptyBody": "Obvestila, ki jih prejme ta račun, se prikažejo tukaj. Hranijo se {{days}} dni.", + "capped": "Prikazana so najnovejša obvestila.", + "loadError": "Zgodovine obvestil ni bilo mogoče naložiti.", + "openFailed": "To obvestilo se ne odpre več nikjer." + }, "action": { "openSession": "Odpri sejo" }, diff --git a/apps/mobile/src/i18n/locales/so.json b/apps/mobile/src/i18n/locales/so.json index a7ce347dc16..daca0631e87 100644 --- a/apps/mobile/src/i18n/locales/so.json +++ b/apps/mobile/src/i18n/locales/so.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "dhaafitaanka xadka iyo kor u kaca saacadlaha", "spendAlertsToggle": "Qaybta digniinaha kharashka" }, + "history": { + "title": "Taariikhda ogeysiisyada", + "subtitle": "Ogeysiisyada ay heshay akoonkan", + "bannerBody": "Ogeysiisyada push waa dansan yihiin qalabkan, sidaas darteed kuwa cusub halkan kuma soo gaari doonaan.", + "bannerAction": "Dejinta ogeysiisyada", + "emptyTitle": "Weli ogeysiisyo ma jiraan", + "emptyCategoryTitle": "Qaybtaan ogeysiisyo ma jiraan", + "emptyBody": "Ogeysiisyada ay hesho akoonkan ayaa halkan ka muuqda. Waxaa la hayaa {{days}} maalmood.", + "capped": "Waxaa la muujinayaa ogeysiisyada ugu dambeeyay.", + "loadError": "Taariikhda ogeysiisyada lama soo dejin karin.", + "openFailed": "Ogeysiiskan mar dambe meelna lagama furi karo." + }, "action": { "openSession": "Fur kalfadhi" }, diff --git a/apps/mobile/src/i18n/locales/sq.json b/apps/mobile/src/i18n/locales/sq.json index 90d9f675d6d..c09a3f66e75 100644 --- a/apps/mobile/src/i18n/locales/sq.json +++ b/apps/mobile/src/i18n/locales/sq.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "tejkalime pragjesh dhe rritje për orë", "spendAlertsToggle": "Kategoria e njoftimeve për shpenzimet" }, + "history": { + "title": "Historiku i njoftimeve", + "subtitle": "Njoftimet që ka marrë kjo llogari", + "bannerBody": "Njoftimet push janë të çaktivizuara për këtë pajisje, kështu që të rejat nuk do të mbërrijnë këtu.", + "bannerAction": "Cilësimet e njoftimeve", + "emptyTitle": "Nuk ka ende njoftime", + "emptyCategoryTitle": "Nuk ka njoftime në këtë kategori", + "emptyBody": "Njoftimet që merr kjo llogari shfaqen këtu. Ato ruhen për {{days}} ditë.", + "capped": "Po shfaqen njoftimet më të fundit.", + "loadError": "Nuk u arrit të ngarkohej historiku i njoftimeve.", + "openFailed": "Ky njoftim nuk hapet më askund." + }, "action": { "openSession": "Hap seancën" }, diff --git a/apps/mobile/src/i18n/locales/sr.json b/apps/mobile/src/i18n/locales/sr.json index 40796ffaf95..49861fe9cbd 100644 --- a/apps/mobile/src/i18n/locales/sr.json +++ b/apps/mobile/src/i18n/locales/sr.json @@ -896,6 +896,18 @@ "spendAlertsSubtitle": "prekoračenja pragova i skokovi po satu", "spendAlertsToggle": "Kategorija obaveštenja o potrošnji" }, + "history": { + "title": "Istorija obaveštenja", + "subtitle": "Obaveštenja koja je ovaj nalog primio", + "bannerBody": "Push obaveštenja su isključena za ovaj uređaj, pa nova neće stizati ovde.", + "bannerAction": "Podešavanja obaveštenja", + "emptyTitle": "Još nema obaveštenja", + "emptyCategoryTitle": "Nema obaveštenja u ovoj kategoriji", + "emptyBody": "Obaveštenja koja ovaj nalog prima prikazuju se ovde. Čuvaju se {{days}} dana.", + "capped": "Prikazuju se najnovija obaveštenja.", + "loadError": "Učitavanje istorije obaveštenja nije uspelo.", + "openFailed": "Ovo obaveštenje se više ne otvara nigde." + }, "action": { "openSession": "Otvori sesiju" }, diff --git a/apps/mobile/src/i18n/locales/sv.json b/apps/mobile/src/i18n/locales/sv.json index 0e05616193f..52fff9648df 100644 --- a/apps/mobile/src/i18n/locales/sv.json +++ b/apps/mobile/src/i18n/locales/sv.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "gränsöverskridanden och toppar per timme", "spendAlertsToggle": "Kategorin utgiftsvarningar" }, + "history": { + "title": "Aviseringshistorik", + "subtitle": "Aviseringar som det här kontot har tagit emot", + "bannerBody": "Push-aviseringar är avstängda för den här enheten, så nya aviseringar kommer inte fram hit.", + "bannerAction": "Aviseringsinställningar", + "emptyTitle": "Inga aviseringar än", + "emptyCategoryTitle": "Inga aviseringar i den här kategorin", + "emptyBody": "Aviseringar som det här kontot tar emot visas här. De sparas i {{days}} dagar.", + "capped": "Visar de senaste aviseringarna.", + "loadError": "Kunde inte läsa in aviseringshistoriken.", + "openFailed": "Den här aviseringen öppnas inte längre någonstans." + }, "action": { "openSession": "Öppna session" }, diff --git a/apps/mobile/src/i18n/locales/sw.json b/apps/mobile/src/i18n/locales/sw.json index 1c5b4c52d62..12d7d4c281f 100644 --- a/apps/mobile/src/i18n/locales/sw.json +++ b/apps/mobile/src/i18n/locales/sw.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "kuvuka vizingiti na kuongezeka kwa saa", "spendAlertsToggle": "Aina ya tahadhari za matumizi" }, + "history": { + "title": "Historia ya arifa", + "subtitle": "Arifa ambazo akaunti hii imepokea", + "bannerBody": "Arifa za push zimezimwa kwenye kifaa hiki, kwa hivyo mpya hazitafika hapa.", + "bannerAction": "Mipangilio ya arifa", + "emptyTitle": "Hakuna arifa bado", + "emptyCategoryTitle": "Hakuna arifa katika kundi hili", + "emptyBody": "Arifa ambazo akaunti hii inapokea zinaonekana hapa. Zinahifadhiwa kwa siku {{days}}.", + "capped": "Inaonyesha arifa za hivi karibuni.", + "loadError": "Imeshindikana kupakia historia ya arifa.", + "openFailed": "Arifa hii haifunguki tena popote." + }, "action": { "openSession": "Fungua kikao" }, diff --git a/apps/mobile/src/i18n/locales/ta.json b/apps/mobile/src/i18n/locales/ta.json index 86697cbe809..b511543727d 100644 --- a/apps/mobile/src/i18n/locales/ta.json +++ b/apps/mobile/src/i18n/locales/ta.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "வரம்பு மீறல்கள் மற்றும் மணிநேர உயர்வுகள்", "spendAlertsToggle": "செலவு எச்சரிக்கை வகை" }, + "history": { + "title": "அறிவிப்பு வரலாறு", + "subtitle": "இந்தக் கணக்கு பெற்ற அறிவிப்புகள்", + "bannerBody": "இந்தச் சாதனத்தில் புஷ் அறிவிப்புகள் முடக்கப்பட்டுள்ளன, எனவே புதியவை இங்கு வராது.", + "bannerAction": "அறிவிப்பு அமைப்புகள்", + "emptyTitle": "இன்னும் அறிவிப்புகள் இல்லை", + "emptyCategoryTitle": "இந்தப் பிரிவில் அறிவிப்புகள் இல்லை", + "emptyBody": "இந்தக் கணக்கு பெறும் அறிவிப்புகள் இங்கே தோன்றும். அவை {{days}} நாட்களுக்கு வைக்கப்படும்.", + "capped": "மிகச் சமீபத்திய அறிவிப்புகள் காட்டப்படுகின்றன.", + "loadError": "அறிவிப்பு வரலாற்றை ஏற்ற முடியவில்லை.", + "openFailed": "இந்த அறிவிப்பு இனி எங்கும் திறக்காது." + }, "action": { "openSession": "அமர்வைத் திறக்கவும்" }, diff --git a/apps/mobile/src/i18n/locales/te.json b/apps/mobile/src/i18n/locales/te.json index 567e3ad42c9..0974ddb2ae3 100644 --- a/apps/mobile/src/i18n/locales/te.json +++ b/apps/mobile/src/i18n/locales/te.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "పరిమితి దాటడం మరియు గంటకు పెరుగుదల", "spendAlertsToggle": "ఖర్చు హెచ్చరికల వర్గం" }, + "history": { + "title": "నోటిఫికేషన్ చరిత్ర", + "subtitle": "ఈ ఖాతా స్వీకరించిన నోటిఫికేషన్‌లు", + "bannerBody": "ఈ పరికరానికి పుష్ నోటిఫికేషన్‌లు ఆఫ్‌లో ఉన్నాయి, కాబట్టి కొత్తవి ఇక్కడికి రావు.", + "bannerAction": "నోటిఫికేషన్ సెట్టింగ్‌లు", + "emptyTitle": "ఇంకా నోటిఫికేషన్‌లు లేవు", + "emptyCategoryTitle": "ఈ వర్గంలో నోటిఫికేషన్‌లు లేవు", + "emptyBody": "ఈ ఖాతా స్వీకరించే నోటిఫికేషన్‌లు ఇక్కడ కనిపిస్తాయి. అవి {{days}} రోజులు ఉంచబడతాయి.", + "capped": "ఇటీవలి నోటిఫికేషన్‌లు చూపబడుతున్నాయి.", + "loadError": "నోటిఫికేషన్ చరిత్రను లోడ్ చేయలేకపోయాము.", + "openFailed": "ఈ నోటిఫికేషన్ ఇకపై ఎక్కడా తెరవదు." + }, "action": { "openSession": "సెషన్‌ను తెరవండి" }, diff --git a/apps/mobile/src/i18n/locales/th.json b/apps/mobile/src/i18n/locales/th.json index 1a0b2c7ade3..f4410717a93 100644 --- a/apps/mobile/src/i18n/locales/th.json +++ b/apps/mobile/src/i18n/locales/th.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "การเกินเกณฑ์และการพุ่งขึ้นรายชั่วโมง", "spendAlertsToggle": "หมวดหมู่การแจ้งเตือนการใช้จ่าย" }, + "history": { + "title": "ประวัติการแจ้งเตือน", + "subtitle": "การแจ้งเตือนที่บัญชีนี้ได้รับ", + "bannerBody": "การแจ้งเตือนแบบพุชปิดอยู่สำหรับอุปกรณ์นี้ ดังนั้นการแจ้งเตือนใหม่จะไม่มาที่นี่", + "bannerAction": "การตั้งค่าการแจ้งเตือน", + "emptyTitle": "ยังไม่มีการแจ้งเตือน", + "emptyCategoryTitle": "ยังไม่มีการแจ้งเตือนในหมวดหมู่นี้", + "emptyBody": "การแจ้งเตือนที่บัญชีนี้ได้รับจะแสดงที่นี่ โดยจะเก็บไว้เป็นเวลา {{days}} วัน", + "capped": "กำลังแสดงการแจ้งเตือนล่าสุด", + "loadError": "ไม่สามารถโหลดประวัติการแจ้งเตือนได้", + "openFailed": "การแจ้งเตือนนี้ไม่สามารถเปิดได้อีกต่อไป" + }, "action": { "openSession": "เปิดเซสชัน" }, diff --git a/apps/mobile/src/i18n/locales/tr.json b/apps/mobile/src/i18n/locales/tr.json index 6298a244f7a..f85c15f996f 100644 --- a/apps/mobile/src/i18n/locales/tr.json +++ b/apps/mobile/src/i18n/locales/tr.json @@ -353,6 +353,18 @@ "spendAlertsSubtitle": "eşik aşımları ve saatlik artışlar", "spendAlertsToggle": "Harcama uyarıları kategorisi" }, + "history": { + "title": "Bildirim geçmişi", + "subtitle": "Bu hesabın aldığı bildirimler", + "bannerBody": "Bu cihazda anlık bildirimler kapalı, bu nedenle yenileri buraya gelmez.", + "bannerAction": "Bildirim ayarları", + "emptyTitle": "Henüz bildirim yok", + "emptyCategoryTitle": "Bu kategoride bildirim yok", + "emptyBody": "Bu hesabın aldığı bildirimler burada görünür. {{days}} gün boyunca saklanır.", + "capped": "En son bildirimler gösteriliyor.", + "loadError": "Bildirim geçmişi yüklenemedi.", + "openFailed": "Bu bildirim artık hiçbir yerde açılmıyor." + }, "action": { "openSession": "Oturumu aç" }, diff --git a/apps/mobile/src/i18n/locales/uk.json b/apps/mobile/src/i18n/locales/uk.json index c70bebb29c6..4657d2488ac 100644 --- a/apps/mobile/src/i18n/locales/uk.json +++ b/apps/mobile/src/i18n/locales/uk.json @@ -353,6 +353,18 @@ "spendAlertsSubtitle": "перевищення порогів і погодинні сплески", "spendAlertsToggle": "Категорія «Сповіщення про витрати»" }, + "history": { + "title": "Історія сповіщень", + "subtitle": "Сповіщення, які отримав цей акаунт", + "bannerBody": "Push-сповіщення вимкнено для цього пристрою, тому нові тут не надходитимуть.", + "bannerAction": "Налаштування сповіщень", + "emptyTitle": "Поки що немає сповіщень", + "emptyCategoryTitle": "У цій категорії немає сповіщень", + "emptyBody": "Сповіщення, які отримує цей акаунт, з'являються тут. Вони зберігаються протягом {{days}} днів.", + "capped": "Показано найновіші сповіщення.", + "loadError": "Не вдалося завантажити історію сповіщень.", + "openFailed": "Це сповіщення більше ніде не відкривається." + }, "action": { "openSession": "Відкрити сеанс" }, diff --git a/apps/mobile/src/i18n/locales/ur.json b/apps/mobile/src/i18n/locales/ur.json index 786341c158e..388a913c521 100644 --- a/apps/mobile/src/i18n/locales/ur.json +++ b/apps/mobile/src/i18n/locales/ur.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "حدوں سے تجاوز اور فی گھنٹہ اضافہ", "spendAlertsToggle": "اخراجات کی تنبیہات کی قسم" }, + "history": { + "title": "اطلاعات کی سرگزشت", + "subtitle": "اس اکاؤنٹ کو موصول ہونے والی اطلاعات", + "bannerBody": "اس ڈیوائس کے لیے پش اطلاعات بند ہیں، اس لیے نئی اطلاعات یہاں موصول نہیں ہوں گی۔", + "bannerAction": "اطلاعات کی ترتیبات", + "emptyTitle": "ابھی تک کوئی اطلاعات نہیں", + "emptyCategoryTitle": "اس زمرے میں کوئی اطلاعات نہیں", + "emptyBody": "اس اکاؤنٹ کو موصول ہونے والی اطلاعات یہاں نظر آتی ہیں۔ یہ {{days}} دنوں تک محفوظ رکھی جاتی ہیں۔", + "capped": "حالیہ ترین اطلاعات دکھائی جا رہی ہیں۔", + "loadError": "اطلاعات کی سرگزشت لوڈ نہیں ہو سکی۔", + "openFailed": "یہ اطلاع اب کہیں نہیں کھلتی۔" + }, "action": { "openSession": "سیشن کھولیں" }, diff --git a/apps/mobile/src/i18n/locales/uz.json b/apps/mobile/src/i18n/locales/uz.json index 1825ad552c9..20604da3250 100644 --- a/apps/mobile/src/i18n/locales/uz.json +++ b/apps/mobile/src/i18n/locales/uz.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "chegaradan oshish va soatlik ko'tarilishlar", "spendAlertsToggle": "Xarajat bildirishnomalari toifasi" }, + "history": { + "title": "Bildirishnomalar tarixi", + "subtitle": "Ushbu hisob olgan bildirishnomalar", + "bannerBody": "Ushbu qurilmada push-bildirishnomalar o'chirilgan, shuning uchun yangilari bu yerga kelmaydi.", + "bannerAction": "Bildirishnoma sozlamalari", + "emptyTitle": "Hozircha bildirishnomalar yo'q", + "emptyCategoryTitle": "Bu toifada bildirishnomalar yo'q", + "emptyBody": "Ushbu hisob oladigan bildirishnomalar shu yerda ko'rinadi. Ular {{days}} kun saqlanadi.", + "capped": "Eng so'nggi bildirishnomalar ko'rsatilmoqda.", + "loadError": "Bildirishnomalar tarixini yuklab bo'lmadi.", + "openFailed": "Bu bildirishnoma endi hech qayerda ochilmaydi." + }, "action": { "openSession": "Sessiyani ochish" }, diff --git a/apps/mobile/src/i18n/locales/vi.json b/apps/mobile/src/i18n/locales/vi.json index 2dda5e179c1..6f13b8cd5d7 100644 --- a/apps/mobile/src/i18n/locales/vi.json +++ b/apps/mobile/src/i18n/locales/vi.json @@ -353,6 +353,18 @@ "spendAlertsSubtitle": "vượt ngưỡng và tăng vọt theo giờ", "spendAlertsToggle": "Danh mục cảnh báo chi tiêu" }, + "history": { + "title": "Lịch sử thông báo", + "subtitle": "Các thông báo mà tài khoản này đã nhận", + "bannerBody": "Thông báo đẩy đã tắt trên thiết bị này, vì vậy thông báo mới sẽ không đến đây.", + "bannerAction": "Cài đặt thông báo", + "emptyTitle": "Chưa có thông báo nào", + "emptyCategoryTitle": "Không có thông báo nào trong danh mục này", + "emptyBody": "Các thông báo mà tài khoản này nhận được sẽ xuất hiện ở đây. Chúng được lưu trong {{days}} ngày.", + "capped": "Đang hiển thị các thông báo gần đây nhất.", + "loadError": "Không thể tải lịch sử thông báo.", + "openFailed": "Thông báo này không còn mở được ở đâu nữa." + }, "action": { "openSession": "Mở phiên" }, diff --git a/apps/mobile/src/i18n/locales/yo.json b/apps/mobile/src/i18n/locales/yo.json index 5e8600f5acf..81117f60ed4 100644 --- a/apps/mobile/src/i18n/locales/yo.json +++ b/apps/mobile/src/i18n/locales/yo.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "àwọn ìkọjá ààlà àti ìgbésókè lójú wákàtí", "spendAlertsToggle": "Ẹ̀ka àwọn ìkìlọ̀ ìnáwó" }, + "history": { + "title": "Ìtàn àwọn ìfitónilétí", + "subtitle": "Àwọn ìfitónilétí tí àkáńtì yìí gbà", + "bannerBody": "Àwọn ìfitónilétí títì ti wà ní pípa fún ẹ̀rọ yìí, nítorí náà àwọn tuntun kò ní dé síbí.", + "bannerAction": "Àwọn ètò ìfitónilétí", + "emptyTitle": "Kò tíì sí ìfitónilétí", + "emptyCategoryTitle": "Kò sí ìfitónilétí kankan ní ẹ̀ka yìí", + "emptyBody": "Àwọn ìfitónilétí tí àkáńtì yìí ń gbà máa ń farahàn síbí. A máa ń tọ́jú wọn fún {{days}} ọjọ́.", + "capped": "Ń ṣàfihàn àwọn ìfitónilétí tuntun jùlọ.", + "loadError": "A kò lè gbé ìtàn àwọn ìfitónilétí wọ.", + "openFailed": "Ìfitónilétí yìí kò tún ṣí níbikíbi mọ́." + }, "action": { "openSession": "Ṣí ìgbà iṣẹ́" }, diff --git a/apps/mobile/src/i18n/locales/zh-Hans.json b/apps/mobile/src/i18n/locales/zh-Hans.json index 32e8d0a4206..b36ab5b317b 100644 --- a/apps/mobile/src/i18n/locales/zh-Hans.json +++ b/apps/mobile/src/i18n/locales/zh-Hans.json @@ -353,6 +353,18 @@ "spendAlertsSubtitle": "阈值突破与每小时激增", "spendAlertsToggle": "支出提醒类别" }, + "history": { + "title": "通知历史", + "subtitle": "此账户收到的通知", + "bannerBody": "此设备的推送通知已关闭,因此新通知不会在此处送达。", + "bannerAction": "通知设置", + "emptyTitle": "暂无通知", + "emptyCategoryTitle": "此类别中暂无通知", + "emptyBody": "此账户收到的通知会显示在这里。通知会保留 {{days}} 天。", + "capped": "仅显示最近的通知。", + "loadError": "无法加载通知历史。", + "openFailed": "此通知已无法在任何位置打开。" + }, "action": { "openSession": "打开会话" }, diff --git a/apps/mobile/src/i18n/locales/zh-Hant.json b/apps/mobile/src/i18n/locales/zh-Hant.json index 48b1ec6b85f..8cf691441e1 100644 --- a/apps/mobile/src/i18n/locales/zh-Hant.json +++ b/apps/mobile/src/i18n/locales/zh-Hant.json @@ -353,6 +353,18 @@ "spendAlertsSubtitle": "門檻突破與每小時激增", "spendAlertsToggle": "支出提醒類別" }, + "history": { + "title": "通知歷史", + "subtitle": "此帳戶收到的通知", + "bannerBody": "此裝置的推播通知已關閉,因此新通知不會送達這裡。", + "bannerAction": "通知設定", + "emptyTitle": "尚無通知", + "emptyCategoryTitle": "此類別尚無通知", + "emptyBody": "此帳戶收到的通知會顯示在這裡。通知會保留 {{days}} 天。", + "capped": "顯示最近的通知。", + "loadError": "無法載入通知歷史。", + "openFailed": "此通知已無法開啟。" + }, "action": { "openSession": "開啟工作階段" }, diff --git a/apps/mobile/src/i18n/locales/zu.json b/apps/mobile/src/i18n/locales/zu.json index a782a7978bb..89b20461c86 100644 --- a/apps/mobile/src/i18n/locales/zu.json +++ b/apps/mobile/src/i18n/locales/zu.json @@ -877,6 +877,18 @@ "spendAlertsSubtitle": "ukweqa imingcele nokukhuphuka ngehora", "spendAlertsToggle": "Isigaba sezexwayiso zokusebenzisa imali" }, + "history": { + "title": "Umlando wezaziso", + "subtitle": "Izaziso ezitholwe yile akhawunti", + "bannerBody": "Izaziso zokupush zivaliwe kule divayisi, ngakho ezintsha ngeke zifike lapha.", + "bannerAction": "Izilungiselelo zezaziso", + "emptyTitle": "Akukho izaziso okwamanje", + "emptyCategoryTitle": "Akukho izaziso kulesi sigaba", + "emptyBody": "Izaziso ezitholwa yile akhawunti zibonakala lapha. Zigcinwa izinsuku ezingama-{{days}}.", + "capped": "Kuboniswa izaziso zakamuva.", + "loadError": "Asikwazanga ukulayisha umlando wezaziso.", + "openFailed": "Lesi saziso asisavuleki ndawo." + }, "action": { "openSession": "Vula iseshini" }, diff --git a/apps/mobile/src/lib/hooks/use-notification-master-gate.ts b/apps/mobile/src/lib/hooks/use-notification-master-gate.ts new file mode 100644 index 00000000000..cc0147f03d9 --- /dev/null +++ b/apps/mobile/src/lib/hooks/use-notification-master-gate.ts @@ -0,0 +1,74 @@ +import { useQuery } from '@tanstack/react-query'; + +import { useAuth } from '@/lib/auth/auth-context'; +import { getDevicePushToken, getNotificationPermissionStatus } from '@/lib/notifications'; +import { useTRPC } from '@/lib/trpc'; + +import { deriveGateSettled } from './agent-push-preference'; + +/** + * The same cache keys the Notifications settings screen reads, so both surfaces + * share one permission read and one device-token read. + */ +const permissionQueryKey = ['notificationPermission'] as const; +const deviceTokenQueryKey = ['devicePushToken'] as const; + +export type NotificationMasterGate = Readonly<{ + /** No answer yet: do not render an enabled/disabled claim. */ + loading: boolean; + /** Every input answered, including by failing. */ + settled: boolean; + /** The OS allows this app to post notifications. */ + permissionGranted: boolean; + /** OS permission granted and this device's token is registered on the account. */ + pushEnabled: boolean; +}>; + +/** + * Whether pushes can reach this device: the OS permission plus a token the + * account holds. The settings screen's master switch is the same pair, so a + * screen that says "push is off" and the switch that turns it back on cannot + * disagree. + * + * A permission read that fails counts as not granted, which is what + * `deriveGateSettled` short-circuits on: the token queries are irrelevant to a + * closed gate, and the device-token query is disabled while it is closed. + */ +export function useNotificationMasterGate(): NotificationMasterGate { + const trpc = useTRPC(); + const { token: authToken } = useAuth(); + + const permission = useQuery({ + queryKey: permissionQueryKey, + queryFn: async () => (await getNotificationPermissionStatus()) === 'granted', + }); + const permissionGranted = permission.data === true; + + const deviceToken = useQuery({ + queryKey: deviceTokenQueryKey, + queryFn: getDevicePushToken, + enabled: permissionGranted, + }); + const pushTokens = useQuery({ + ...trpc.user.getMyPushTokens.queryOptions(), + enabled: authToken != null, + }); + + const settled = deriveGateSettled({ + permissionSettled: permission.isFetched || permission.isError, + permissionGranted, + pushTokensSettled: pushTokens.isFetched || pushTokens.isError, + deviceTokenSettled: deviceToken.isFetched || deviceToken.isError, + }); + const registeredToken = deviceToken.data ?? null; + const serverRegistered = + registeredToken !== null && + (pushTokens.data ?? []).some(entry => entry.token === registeredToken); + + return { + loading: !settled, + settled, + permissionGranted, + pushEnabled: permissionGranted && serverRegistered, + }; +} diff --git a/apps/mobile/src/lib/notification-history.test.ts b/apps/mobile/src/lib/notification-history.test.ts new file mode 100644 index 00000000000..b8955ffada5 --- /dev/null +++ b/apps/mobile/src/lib/notification-history.test.ts @@ -0,0 +1,154 @@ +import { describe, expect, it } from 'vitest'; + +import { + NOTIFICATION_HISTORY_FILTERS, + notificationHistoryCopy, + notificationHistoryFilters, + notificationHistoryTarget, +} from '@/lib/notification-history'; + +const LOW_BALANCE_PARAMS = { organizationName: 'Acme', minimumBalanceUsd: '5' }; + +describe('notificationHistoryCopy', () => { + it('renders the stored catalog key and its params in the requested language', () => { + const copy = notificationHistoryCopy( + { + i18nKey: 'internal.lowBalance', + i18nParams: LOW_BALANCE_PARAMS, + title: 'stored title', + body: 'stored body', + }, + 'de' + ); + + expect(copy.title).toBe('Warnung bei niedrigem Guthaben'); + expect(copy.body).toBe('Das Guthaben von Acme fiel unter $5'); + }); + + it('renders English copy for a locale the push catalog does not carry', () => { + const copy = notificationHistoryCopy( + { i18nKey: 'internal.lowBalance', i18nParams: LOW_BALANCE_PARAMS, title: '', body: '' }, + 'not-a-locale' + ); + + expect(copy).toEqual({ + title: 'Low balance alert', + body: 'Acme balance fell below $5', + }); + }); + + it('falls back to the stored copy when the catalog key no longer exists', () => { + const copy = notificationHistoryCopy( + { + i18nKey: 'internal.retiredKind', + i18nParams: { anything: 'x' }, + title: 'Stored title', + body: 'Stored body', + }, + 'de' + ); + + expect(copy).toEqual({ title: 'Stored title', body: 'Stored body' }); + }); + + it('uses the stored copy for a push that carried no catalog key', () => { + const copy = notificationHistoryCopy( + { i18nKey: null, i18nParams: null, title: 'Raw title', body: 'Raw body' }, + 'de' + ); + + expect(copy).toEqual({ title: 'Raw title', body: 'Raw body' }); + }); +}); + +describe('notificationHistoryTarget', () => { + it.each([ + [ + { type: 'cloud_agent_session', cliSessionId: 'ses_1', organizationId: 'org-2' }, + '/(app)/agent-chat/ses_1', + ], + [{ type: 'agent_briefing', url: '/agent-briefing' }, '/(app)/agent-briefing'], + ])('opens the push destination of a %o row without the push marker', (payload, href) => { + expect(notificationHistoryTarget(payload)).toMatchObject({ href }); + }); + + it('keeps the other query params and drops only the push marker', () => { + expect( + notificationHistoryTarget({ type: 'low_balance', organizationId: 'org-acme' }) + ).toMatchObject({ + href: '/(app)/(tabs)/(3_profile)/organization/credit-activity?org=org-acme', + }); + expect( + notificationHistoryTarget({ + type: 'spend_alert', + scope: 'organization', + organizationId: 'org-acme', + }) + ).toMatchObject({ + href: '/(app)/(tabs)/(3_profile)/spend-alerts?org=org-acme', + }); + }); + + it('routes a security finding row to its scope', () => { + expect( + notificationHistoryTarget({ + type: 'security_finding', + findingId: 'f-1', + scope: 'org-9', + }) + ).toMatchObject({ href: '/(app)/security-agent/org-9/findings/f-1' }); + }); + + it('carries the payload the row was recorded from', () => { + const target = notificationHistoryTarget({ + type: 'cloud_agent_session', + cliSessionId: 'ses_1', + organizationId: 'org-2', + }); + + expect(target?.data).toEqual({ + type: 'cloud_agent_session', + cliSessionId: 'ses_1', + organizationId: 'org-2', + }); + }); + + it('reports no destination for a payload this app cannot parse', () => { + expect(notificationHistoryTarget({ type: 'retired_kind' })).toBeNull(); + expect(notificationHistoryTarget({})).toBeNull(); + }); +}); + +describe('notificationHistoryFilters', () => { + it('offers every recorded category behind an "All" chip', () => { + expect(NOTIFICATION_HISTORY_FILTERS.map(filter => filter.category)).toEqual([ + null, + 'chatMessages', + 'agentAttention', + 'sessionStatus', + 'balanceAlerts', + 'spendAlerts', + 'securityFindings', + ]); + }); + + it('drops a category the account cannot receive', () => { + const filters = notificationHistoryFilters({ + balanceAlerts: { available: false }, + securityFindings: { available: false }, + }); + + expect(filters.map(filter => filter.category)).toEqual([ + null, + 'chatMessages', + 'agentAttention', + 'sessionStatus', + 'spendAlerts', + ]); + }); + + it('keeps every chip while availability is unknown or unread', () => { + expect(notificationHistoryFilters(undefined)).toEqual(NOTIFICATION_HISTORY_FILTERS); + expect(notificationHistoryFilters({})).toEqual(NOTIFICATION_HISTORY_FILTERS); + }); +}); diff --git a/apps/mobile/src/lib/notification-history.ts b/apps/mobile/src/lib/notification-history.ts new file mode 100644 index 00000000000..89f7c23012a --- /dev/null +++ b/apps/mobile/src/lib/notification-history.ts @@ -0,0 +1,143 @@ +import { + NOTIFICATION_HISTORY_CATEGORIES, + type NotificationHistoryCategory, + type PushData, + pushDataSchema, + translatePush, +} from '@kilocode/notifications'; + +import { notificationPathForData } from './notification-path'; + +/** + * Pure logic for the Notification history screen: how a recorded row is read + * back, where a row's tap goes, and which filter chips the list offers. + * + * The row is rendered on the device from the catalog key and params the push + * carried, in the app's current language, so a row reads like the notification + * did. The worker's own renderer does the same with its per-token locale + * (`NotificationChannelDO.dispatchPush`); the stored fallback copy is what a + * removed key falls back to. + */ + +/** The stored fields a row's copy is rendered from. */ +export type NotificationHistoryCopyFields = Readonly<{ + /** Catalog key without a `.title`/`.body` suffix, or null for a raw-form push. */ + i18nKey: string | null; + i18nParams: Record | null; + /** The copy the sender stored, used verbatim when no key resolves. */ + title: string; + body: string; +}>; + +/** The rendered title and body of one row. */ +export type NotificationHistoryCopy = Readonly<{ title: string; body: string }>; + +/** + * The row's title and body in `locale`. A row with no catalog key, or whose key + * is no longer in the push catalog, keeps the stored fallback copy — never the + * content-free preview copy, which is a per-device send-time setting and is + * never what was recorded. + */ +export function notificationHistoryCopy( + row: NotificationHistoryCopyFields, + locale: string +): NotificationHistoryCopy { + if (row.i18nKey === null) { + return { title: row.title, body: row.body }; + } + const params = row.i18nParams ?? undefined; + return { + title: translatePush(locale, `${row.i18nKey}.title`, params, row.title), + body: translatePush(locale, `${row.i18nKey}.body`, params, row.body), + }; +} + +export type NotificationHistoryTarget = Readonly<{ + data: PushData; + href: string; +}>; + +/** + * Drop the `via=push` marker from a destination. The marker tags the landing + * screen's analytics event as push-originated, and opening a row from the list + * is not a push tap. + */ +function withoutPushOrigin(href: string): string { + const queryStart = href.indexOf('?'); + if (queryStart === -1) { + return href; + } + const kept = href + .slice(queryStart + 1) + .split('&') + .filter(part => part !== 'via=push'); + if (kept.length === 0) { + return href.slice(0, queryStart); + } + return `${href.slice(0, queryStart)}?${kept.join('&')}`; +} + +/** + * Where a row's tap goes, or null when the stored payload no longer resolves to + * a destination (a producer shape this app cannot parse). A null target is the + * one dead destination the list can detect without a request: every recorded + * kind maps to a route. + */ +export function notificationHistoryTarget( + payload: Record +): NotificationHistoryTarget | null { + const parsed = pushDataSchema.safeParse(payload); + if (!parsed.success) { + return null; + } + return { data: parsed.data, href: withoutPushOrigin(notificationPathForData(parsed.data)) }; +} + +export type NotificationHistoryFilter = Readonly<{ + /** null is the unfiltered "All" chip. */ + category: NotificationHistoryCategory | null; + labelKey: string; +}>; + +/** + * Chip label per recorded category. The settings screen's own wording, so a + * chip and the switch that governs it read the same. Keys are literal so the + * catalog check can see them. + */ +const CATEGORY_LABEL_KEYS = { + chatMessages: 'notifications.channel.chat', + agentAttention: 'notifications.category.agentAttentionTitle', + sessionStatus: 'notifications.category.sessionStatusTitle', + balanceAlerts: 'notifications.channel.balance', + spendAlerts: 'notifications.channel.spend', + securityFindings: 'notifications.channel.security', +} satisfies Record; + +/** + * Every chip in the order the settings screen lists the categories: "All" + * first, then one per category the recorder can produce. A category the + * recorder never writes (there is no recorded push kind for it) has no chip, + * so no chip can only ever answer an empty page. + */ +export const NOTIFICATION_HISTORY_FILTERS: readonly NotificationHistoryFilter[] = [ + { category: null, labelKey: 'common.all' }, + ...NOTIFICATION_HISTORY_CATEGORIES.map(category => ({ + category, + labelKey: CATEGORY_LABEL_KEYS[category], + })), +]; + +/** + * The chips an account can use: a category the server marks unavailable (no + * organization, no security agent) is dropped rather than offered as a control + * that can only return an empty page. Availability the client has not read yet + * — or could not read — keeps the chip, so a failed preferences query never + * hides a filter the account does have. + */ +export function notificationHistoryFilters( + capabilities: Readonly> | undefined +): readonly NotificationHistoryFilter[] { + return NOTIFICATION_HISTORY_FILTERS.filter( + filter => filter.category === null || capabilities?.[filter.category]?.available !== false + ); +} diff --git a/packages/notifications/src/session-live-activity.ts b/packages/notifications/src/session-live-activity.ts index b668b8648c5..7dc40bcda6a 100644 --- a/packages/notifications/src/session-live-activity.ts +++ b/packages/notifications/src/session-live-activity.ts @@ -149,7 +149,6 @@ export function buildSessionLiveActivityContentState( */ export function sessionCardStaleAtSeconds( content: Pick, - nowSeconds: number, defaultStaleAtSeconds: number ): number { if (content.statusKind !== 'running') return defaultStaleAtSeconds; diff --git a/packages/trpc/package.json b/packages/trpc/package.json index 8ebdefb6380..31b882c3d81 100644 --- a/packages/trpc/package.json +++ b/packages/trpc/package.json @@ -20,6 +20,7 @@ "@kilocode/kilo-chat": "workspace:*", "@kilocode/kiloclaw-instance-tiers": "workspace:*", "@kilocode/kiloclaw-secret-catalog": "workspace:*", + "@kilocode/notifications": "workspace:*", "@kilocode/worker-utils": "workspace:*", "@trpc/server": "catalog:", "@workos-inc/node": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad870d29f74..ff6e930b1c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1910,6 +1910,9 @@ importers: '@kilocode/kiloclaw-secret-catalog': specifier: workspace:* version: link:../kiloclaw-secret-catalog + '@kilocode/notifications': + specifier: workspace:* + version: link:../notifications '@kilocode/worker-utils': specifier: workspace:* version: link:../worker-utils diff --git a/services/notifications/src/lib/session-card-delivery.ts b/services/notifications/src/lib/session-card-delivery.ts index 022ad19c09a..9242134ef9b 100644 --- a/services/notifications/src/lib/session-card-delivery.ts +++ b/services/notifications/src/lib/session-card-delivery.ts @@ -278,7 +278,6 @@ export function planSessionCardSend(params: { // The quiet window for a working card, the aggregate window otherwise. staleAtSeconds: sessionCardStaleAtSeconds( content, - nowSeconds, nowSeconds + SESSION_CARD_AGGREGATE_STALE_MS / 1000 ), },