mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-02 16:07:12 +00:00
feat: add ICU interpolation, all 27 webmail locales and a key-parity test to i18n
This commit is contained in:
@@ -3,6 +3,7 @@ import { View, Text, StyleSheet } from 'react-native';
|
||||
import { CloudOff } from 'lucide-react-native';
|
||||
import { useNetworkStore } from '../stores/network-store';
|
||||
import { useOutboxStore } from '../stores/outbox-store';
|
||||
import { useLocaleStore } from '../stores/locale-store';
|
||||
import { spacing, typography, type ThemePalette } from '../theme/tokens';
|
||||
import { useColors } from '../theme/colors';
|
||||
|
||||
@@ -15,18 +16,24 @@ export function OfflineBanner({ hint }: OfflineBannerProps) {
|
||||
const c = useColors();
|
||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||
const online = useNetworkStore((s) => s.online);
|
||||
const t = useLocaleStore((s) => s.t);
|
||||
// Pending offline mutations that will replay once we're back online.
|
||||
const queued = useOutboxStore((s) => s.entries.length);
|
||||
if (online) return null;
|
||||
const queuedHint = queued > 0
|
||||
? `${queued} change${queued === 1 ? '' : 's'} will sync when you reconnect`
|
||||
? t(
|
||||
'offline.queued_changes',
|
||||
'{count, plural, one {# change} other {# changes}} will sync when you reconnect',
|
||||
{ count: queued },
|
||||
)
|
||||
: null;
|
||||
const suffix = [hint, queuedHint].filter(Boolean).join(' · ');
|
||||
const label = t('offline.banner', 'You are offline');
|
||||
return (
|
||||
<View style={styles.bar}>
|
||||
<CloudOff size={14} color={c.primaryForeground} />
|
||||
<View style={styles.bar} accessibilityRole="alert" accessibilityLiveRegion="polite">
|
||||
<CloudOff size={14} color={c.warningForeground} />
|
||||
<Text style={styles.text} numberOfLines={1}>
|
||||
You are offline{suffix ? ` — ${suffix}` : ''}
|
||||
{suffix ? `${label} — ${suffix}` : label}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
@@ -40,11 +47,11 @@ function makeStyles(c: ThemePalette) {
|
||||
gap: spacing.sm,
|
||||
paddingHorizontal: spacing.md,
|
||||
paddingVertical: 6,
|
||||
backgroundColor: '#a16207',
|
||||
backgroundColor: c.warning,
|
||||
},
|
||||
text: {
|
||||
...typography.caption,
|
||||
color: c.primaryForeground,
|
||||
color: c.warningForeground,
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -57,6 +57,7 @@ export function LanguageSettings() {
|
||||
|
||||
const deviceLabel =
|
||||
SUPPORTED_LOCALES.find((l) => l.code === deviceCode)?.label ?? 'English';
|
||||
const directionChangePending = useLocaleStore((s) => s.directionChangePending);
|
||||
|
||||
return (
|
||||
<View style={{ gap: spacing.xxxl }}>
|
||||
@@ -65,10 +66,21 @@ export function LanguageSettings() {
|
||||
description={t('settings.appearance.language.description', 'Choose your preferred language')}
|
||||
>
|
||||
<View style={styles.list}>
|
||||
{renderRow('system', `System default (${deviceLabel})`)}
|
||||
{renderRow(
|
||||
'system',
|
||||
`${t('settings.appearance.language.system_default', 'System default')} (${deviceLabel})`,
|
||||
)}
|
||||
<View style={styles.divider} />
|
||||
{SUPPORTED_LOCALES.map((l) => renderRow(l.code, l.label))}
|
||||
</View>
|
||||
{directionChangePending && (
|
||||
<Text style={styles.hint}>
|
||||
{t(
|
||||
'settings.appearance.language.restart_for_direction',
|
||||
'Restart the app to apply the new text direction.',
|
||||
)}
|
||||
</Text>
|
||||
)}
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection
|
||||
@@ -131,6 +143,7 @@ function makeStyles(c: ThemePalette) {
|
||||
divider: { height: 1, backgroundColor: c.border, marginHorizontal: spacing.md },
|
||||
label: { ...typography.body, color: c.text },
|
||||
sublabel: { ...typography.caption, color: c.textMuted, marginTop: 2 },
|
||||
hint: { ...typography.caption, color: c.warning, marginTop: spacing.sm },
|
||||
previewBox: {
|
||||
marginTop: spacing.md,
|
||||
padding: spacing.md,
|
||||
|
||||
Reference in New Issue
Block a user