- Add `privacyManifests` to `app.json` declaring crash/performance/diagnostic data collection (unlinked, non-tracking, app functionality only) and required API access reasons for UserDefaults, SystemBootTime, and FileTimestamp
- Switch metro config from `withSentryConfig` wrapper to `getSentryExpoConfig` (Sentry's recommended Expo integration) and remove redundant `withSentryConfig` wrapping
Adds @sentry/react-native for native crash reporting (segfaults, OOM,
ANRs, watchdog kills) that PostHog's JS-level tracking can't capture.
Privacy measures:
- sendDefaultPii: false, no tracing/replay/profiling/screenshots
- beforeSend strips user/request data from JS errors
- beforeBreadcrumb drops navigation/http breadcrumbs
- Screenshot, ViewHierarchy, UserInteraction integrations filtered out
Crash reporting has its own toggle in Settings, independent from the
existing analytics toggle. Enabled by default, takes effect on restart
since native crash handlers must be installed at init time.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Migrate the entire monorepo from Biome 2.4.7 to oxlint 1.56.0 (linter)
and oxfmt 0.41.0 (formatter) for faster lint/format and broader rule
coverage.
- Add `.oxlintrc.json` with React, TypeScript, unicorn, import plugins
and correctness/suspicious categories
- Add `.oxfmtrc.json` with 2-space indent, import sorting, and Tailwind
class sorting (all 30+ custom className attributes migrated)
- Add `docs/.oxlintrc.json` and `docs/.oxfmtrc.json` with Next.js plugin
- Update all 12 workspace package.json scripts: `oxlint`, `oxfmt`,
`oxfmt --check`
- Add `format:check` turbo task and CI step
- Update VS Code settings/extensions to use `oxc.oxc-vscode`
- Update CI path triggers from `biome.json` to new config files
- Remove all `biome-ignore` comments and fix shadowed variables
- Delete `biome.json` and `docs/biome.json`
- Reformat entire codebase with oxfmt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add i18n with LingUI, Crowdin integration, and typed error codes
- Add `@sofa/i18n` shared package with LingUI v5 (v6-ready config using
`defineConfig` + `@lingui/format-po`), eager English + lazy-loaded
fr/de/es/it/pt catalogs, `Intl`-based date/number format utilities,
and test helpers
- Wire `@lingui/vite-plugin` + babel macro plugin for web, and
`@lingui/metro-transformer` + babel config for native
- Add `I18nProvider` to both app roots with locale auto-detection
(navigator.language / expo-localization) and persistence
(localStorage / MMKV)
- Wrap all ~512 user-facing strings across web and native with LingUI
macros (`<Trans>`, `useLingui`/`t`, `i18n._(msg`...`)`, `plural`)
- Add language switcher to Settings in both apps
- Add `@sofa/api/errors` with 13 typed `AppErrorCode` values and
`appErrorData()` helper for contract `.errors()` schemas
- Update all oRPC contract error definitions with typed `data` fields;
convert import procedure `throw new Error()` to `ORPCError` with codes
- Add per-app `error-messages.ts` utilities that map error codes to
localized strings; update global `QueryCache.onError` handlers to stop
leaking raw `error.message` to users
- Add `crowdin.yml` config and `.github/workflows/crowdin.yml` for
automated source upload on merge and translation pull via dispatch
- Add `lingui.config.ts` at repo root with `bun run i18n:extract` and
`bun run i18n:compile` convenience scripts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: add Crowdin language mapping and ignore context file
* fix(i18n): address 20 localization quality issues
- Stop leaking raw error.message to users in change-password and
account-section; use localized fallbacks instead
- Add typed error data to discover contract's .errors() schema
- Replace all manual English plural suffixes with LingUI plural() macro
in episode counts, backup counts, star ratings, title/image counts
- Replace date-fns formatDistanceToNow and hardcoded date patterns with
locale-aware formatDate/formatRelativeTime from @sofa/i18n/format
- Convert integration-configs from module-scope translations (frozen at
import time) to lazy getIntegrationConfigs(i18n) function
- Combine split Trans fragments into single translatable units in
backup-schedule retention sentence and stats-display period labels
- Localize TV media type badge and full episode accessibilityLabel
- Replace Android-incompatible Alert.alert language picker (max 3
buttons) with zeego DropdownMenu
- Await async locale initialization before hiding splash screen so
non-English users don't see English flash on cold start
- Add @lingui/core as direct native app dependency
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(i18n): add Intl polyfills for Hermes, replace DropdownMenu language picker, and remove date-fns
- Add `@formatjs/intl-*` polyfills loaded at app startup in `intl-polyfills.ts` so Hermes on Android has full `Intl` support for locale-aware formatting
- Alias `@formatjs/icu-messageformat-parser` to its `no-parser` variant in metro config to reduce bundle size
- Replace the zeego `DropdownMenu` language picker in Settings with a new `SelectModal` component that works reliably on all platforms without the Android 3-button limit
- Replace `date-fns` `format`/`parseISO` in `person/[id].tsx` with `formatDate` from `@sofa/i18n/format`; remove `date-fns` from native and web `package.json`
- Refactor `StatusActionButton` to use a `StatusLabel` component with `<Trans>` rather than a `getStatusConfig(t)` factory so labels are always reactive to locale changes
- Fix crowdin workflow to use root `bun run i18n:compile` script instead of `cd packages/i18n && bun run compile`
- Wrap root layout `GestureHandlerRootView` in `SafeAreaProvider` and add `.catch` to locale-ready promise to prevent unhandled rejections blocking the splash screen
* fix(i18n): localize integration status helpers, fix stats-display Trans wrapping, and clean up SelectModal
- Localize `webhookStatus` and `listStatus` in `integration-card.tsx` using `i18n._(msg`...`)` so status strings are translated instead of always rendering in English
- Wrap "Movies {select}" and "Episodes {select}" in a single `<Trans>` in `stats-display.tsx` so the period selector element is embedded inside the translatable unit rather than concatenated outside it
- Fix locale activation order in Settings: close the modal first, then `activateLocale`, and only call `setPersistedLocale` on success to avoid persisting a locale that failed to load
- Remove the redundant `SafeAreaProvider`/`SafeAreaView` wrapper from `SelectModal` — the root layout already provides `SafeAreaProvider`
- Recompile all six `.po`/`.ts` catalogs to pick up new and updated message strings
* chore(i18n): crowdin sync
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>