From ad0497ccd60f4ba1139a119b895eb3c9e69617e2 Mon Sep 17 00:00:00 2001 From: Jake Jarvis Date: Wed, 18 Mar 2026 12:16:10 -0400 Subject: [PATCH] feat: add i18n with LingUI, Crowdin integration, and typed error codes (#16) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 (``, `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) * 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) * 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 `` 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 `` 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) --- .github/workflows/crowdin.yml | 68 + .github/workflows/eas-build.yml | 1 + .gitignore | 1 + apps/native/babel.config.js | 7 + apps/native/metro.config.js | 25 +- apps/native/package.json | 12 +- apps/native/src/app/(auth)/login.tsx | 37 +- apps/native/src/app/(auth)/register.tsx | 34 +- apps/native/src/app/(auth)/server-url.tsx | 37 +- .../native/src/app/(tabs)/(explore)/index.tsx | 12 +- apps/native/src/app/(tabs)/(home)/index.tsx | 30 +- apps/native/src/app/(tabs)/(search)/index.tsx | 6 +- .../src/app/(tabs)/(settings)/index.tsx | 135 +- apps/native/src/app/+not-found.tsx | 11 +- apps/native/src/app/_layout.tsx | 37 +- apps/native/src/app/change-password.tsx | 31 +- apps/native/src/app/person/[id].tsx | 47 +- apps/native/src/app/title/[id].tsx | 20 +- .../dashboard/continue-watching-card.tsx | 6 +- .../explore/filterable-title-row.tsx | 6 +- .../src/components/explore/genre-chip.tsx | 6 +- .../src/components/explore/hero-banner.tsx | 4 +- apps/native/src/components/header-avatar.tsx | 12 +- .../navigation/modal-stack-header.tsx | 4 +- .../search/recently-viewed-list.ios.tsx | 20 +- .../search/recently-viewed-list.tsx | 20 +- .../search/recently-viewed-row-content.tsx | 18 +- .../components/search/search-result-row.tsx | 14 +- .../components/settings/integration-card.tsx | 50 +- .../settings/integration-configs.ts | 201 +- .../settings/integrations-section.tsx | 13 +- .../titles/continue-watching-banner.tsx | 6 +- .../src/components/titles/episode-row.tsx | 8 +- .../components/titles/season-accordion.tsx | 11 +- .../titles/status-action-button.tsx | 49 +- .../src/components/ui/expandable-text.tsx | 6 +- .../src/components/ui/offline-banner.ios.tsx | 5 +- .../src/components/ui/offline-banner.tsx | 3 +- apps/native/src/components/ui/poster-card.tsx | 10 +- .../native/src/components/ui/select-modal.tsx | 89 + .../ui/server-unreachable-banner.ios.tsx | 7 +- .../ui/server-unreachable-banner.tsx | 5 +- apps/native/src/hooks/use-title-actions.ts | 63 +- apps/native/src/lib/error-messages.ts | 48 + apps/native/src/lib/i18n.ts | 36 + apps/native/src/lib/intl-polyfills.ts | 52 + apps/native/src/lib/query-client.ts | 6 +- apps/native/src/lib/title-actions.ts | 50 +- apps/server/src/orpc/procedures/admin.ts | 21 +- apps/server/src/orpc/procedures/discover.ts | 2 + apps/server/src/orpc/procedures/explore.ts | 2 + apps/server/src/orpc/procedures/imports.ts | 33 +- .../src/orpc/procedures/integrations.ts | 6 +- apps/server/src/orpc/procedures/people.ts | 6 +- apps/server/src/orpc/procedures/search.ts | 2 + apps/server/src/orpc/procedures/titles.ts | 11 +- apps/web/package.json | 5 +- apps/web/src/components/auth-form.tsx | 53 +- apps/web/src/components/command-palette.tsx | 30 +- .../dashboard/continue-watching-card.tsx | 7 +- .../dashboard/continue-watching-section.tsx | 5 +- .../components/dashboard/library-section.tsx | 5 +- .../dashboard/recommendations-section.tsx | 5 +- .../components/dashboard/stats-display.tsx | 101 +- .../components/dashboard/stats-section.tsx | 9 +- .../components/dashboard/welcome-header.tsx | 10 +- apps/web/src/components/expandable-text.tsx | 3 +- .../src/components/explore/explore-client.tsx | 8 +- .../explore/filterable-title-row.tsx | 3 +- .../src/components/explore/hero-banner.tsx | 9 +- apps/web/src/components/landing-page.tsx | 21 +- apps/web/src/components/nav-bar.tsx | 35 +- .../components/people/filmography-grid.tsx | 22 +- .../web/src/components/people/person-hero.tsx | 18 +- .../components/settings/account-section.tsx | 82 +- .../settings/backup-restore-section.tsx | 40 +- .../settings/backup-schedule-section.tsx | 163 +- .../components/settings/backup-section.tsx | 95 +- .../components/settings/danger-section.tsx | 107 +- .../components/settings/imports-section.tsx | 177 +- .../components/settings/integration-card.tsx | 48 +- .../settings/integration-configs.tsx | 270 +- .../settings/integrations-section.tsx | 3 +- .../components/settings/language-section.tsx | 91 + .../settings/registration-section.tsx | 16 +- .../components/settings/settings-shell.tsx | 5 +- .../settings/system-health-section.tsx | 119 +- .../settings/update-check-section.tsx | 14 +- apps/web/src/components/setup/copy-button.tsx | 5 +- .../src/components/setup/refresh-button.tsx | 7 +- apps/web/src/components/title-card.tsx | 46 +- .../src/components/titles/cast-carousel.tsx | 9 +- .../src/components/titles/status-button.tsx | 49 +- .../src/components/titles/title-actions.tsx | 3 +- .../components/titles/title-availability.tsx | 22 +- apps/web/src/components/titles/title-hero.tsx | 9 +- .../titles/title-recommendations.tsx | 5 +- .../src/components/titles/title-seasons.tsx | 37 +- .../src/components/titles/trailer-dialog.tsx | 8 +- .../components/titles/use-title-actions.ts | 70 +- apps/web/src/components/update-toast.tsx | 10 +- apps/web/src/hooks/use-time-ago.ts | 6 +- apps/web/src/lib/error-messages.ts | 49 + apps/web/src/lib/i18n.ts | 36 + apps/web/src/lib/orpc/client.ts | 9 +- apps/web/src/routes/__root.tsx | 51 +- apps/web/src/routes/_app/people.$id.tsx | 15 +- apps/web/src/routes/_app/settings.tsx | 35 +- apps/web/src/routes/_app/titles.$id.tsx | 23 +- apps/web/src/routes/_auth/register.tsx | 11 +- apps/web/src/routes/setup.tsx | 11 +- apps/web/vite.config.ts | 3 + biome.json | 1 + bun.lock | 273 +- crowdin.yml | 12 + docs/public/openapi.json | 449 ++- lingui.config.ts | 15 + package.json | 16 +- packages/api/package.json | 1 + packages/api/src/contract.ts | 89 +- packages/api/src/errors.ts | 23 + packages/core/package.json | 1 - packages/core/src/backup.ts | 10 +- packages/i18n/package.json | 28 + packages/i18n/src/format.ts | 70 + packages/i18n/src/index.ts | 34 + packages/i18n/src/locales.ts | 16 + packages/i18n/src/po/de.po | 2433 +++++++++++++++++ packages/i18n/src/po/de.ts | 1 + packages/i18n/src/po/en.po | 2433 +++++++++++++++++ packages/i18n/src/po/en.ts | 1 + packages/i18n/src/po/es.po | 2433 +++++++++++++++++ packages/i18n/src/po/es.ts | 1 + packages/i18n/src/po/fr.po | 2433 +++++++++++++++++ packages/i18n/src/po/fr.ts | 1 + packages/i18n/src/po/it.po | 2433 +++++++++++++++++ packages/i18n/src/po/it.ts | 1 + packages/i18n/src/po/pt.po | 2433 +++++++++++++++++ packages/i18n/src/po/pt.ts | 1 + packages/i18n/src/test-utils.tsx | 13 + packages/i18n/tsconfig.json | 15 + packages/logger/package.json | 5 + 142 files changed, 18002 insertions(+), 1243 deletions(-) create mode 100644 .github/workflows/crowdin.yml create mode 100644 apps/native/babel.config.js create mode 100644 apps/native/src/components/ui/select-modal.tsx create mode 100644 apps/native/src/lib/error-messages.ts create mode 100644 apps/native/src/lib/i18n.ts create mode 100644 apps/native/src/lib/intl-polyfills.ts create mode 100644 apps/web/src/components/settings/language-section.tsx create mode 100644 apps/web/src/lib/error-messages.ts create mode 100644 apps/web/src/lib/i18n.ts create mode 100644 crowdin.yml create mode 100644 lingui.config.ts create mode 100644 packages/api/src/errors.ts create mode 100644 packages/i18n/package.json create mode 100644 packages/i18n/src/format.ts create mode 100644 packages/i18n/src/index.ts create mode 100644 packages/i18n/src/locales.ts create mode 100644 packages/i18n/src/po/de.po create mode 100644 packages/i18n/src/po/de.ts create mode 100644 packages/i18n/src/po/en.po create mode 100644 packages/i18n/src/po/en.ts create mode 100644 packages/i18n/src/po/es.po create mode 100644 packages/i18n/src/po/es.ts create mode 100644 packages/i18n/src/po/fr.po create mode 100644 packages/i18n/src/po/fr.ts create mode 100644 packages/i18n/src/po/it.po create mode 100644 packages/i18n/src/po/it.ts create mode 100644 packages/i18n/src/po/pt.po create mode 100644 packages/i18n/src/po/pt.ts create mode 100644 packages/i18n/src/test-utils.tsx create mode 100644 packages/i18n/tsconfig.json diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml new file mode 100644 index 0000000..87693f8 --- /dev/null +++ b/.github/workflows/crowdin.yml @@ -0,0 +1,68 @@ +name: Crowdin Sync + +on: + push: + branches: [main] + paths: + - "packages/i18n/src/po/en.po" + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + upload: + name: Upload sources + runs-on: ubuntu-latest + if: github.event_name == 'push' + steps: + - uses: actions/checkout@v4 + + - name: Upload sources to Crowdin + uses: crowdin/github-action@v2 + with: + upload_sources: true + upload_translations: false + download_translations: false + config: crowdin.yml + env: + CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} + + download: + name: Download translations + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v2 + + - run: bun install --frozen-lockfile + + - name: Download translations from Crowdin + uses: crowdin/github-action@v2 + with: + upload_sources: false + upload_translations: false + download_translations: true + create_pull_request: true + pull_request_title: "chore(i18n): update translations from Crowdin" + pull_request_body: "Automated PR with latest translations from Crowdin." + config: crowdin.yml + env: + CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Compile catalogs + run: bun run i18n:compile + + - name: Commit compiled catalogs + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add packages/i18n/src/po/ + git diff --cached --quiet || git commit -m "chore(i18n): compile updated translation catalogs" + git push diff --git a/.github/workflows/eas-build.yml b/.github/workflows/eas-build.yml index 677eb00..463fd92 100644 --- a/.github/workflows/eas-build.yml +++ b/.github/workflows/eas-build.yml @@ -23,6 +23,7 @@ on: paths: - apps/native/** - packages/api/** + - packages/i18n/** jobs: build: diff --git a/.gitignore b/.gitignore index f246a0e..729650e 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ coverage .cache tmp temp +crowdin-context.jsonl # local data *.db diff --git a/apps/native/babel.config.js b/apps/native/babel.config.js new file mode 100644 index 0000000..9b9b6ae --- /dev/null +++ b/apps/native/babel.config.js @@ -0,0 +1,7 @@ +module.exports = (api) => { + api.cache(true); + return { + presets: ["babel-preset-expo"], + plugins: ["@lingui/babel-plugin-lingui-macro"], + }; +}; diff --git a/apps/native/metro.config.js b/apps/native/metro.config.js index 4f14d11..4cff2ac 100644 --- a/apps/native/metro.config.js +++ b/apps/native/metro.config.js @@ -7,9 +7,28 @@ const { /** @type {import('expo/metro-config').MetroConfig} */ const config = getDefaultConfig(__dirname); -const uniwindConfig = withUniwindConfig(wrapWithReanimatedMetroConfig(config), { +config.transformer = { + ...config.transformer, + babelTransformerPath: require.resolve("@lingui/metro-transformer/expo"), +}; +config.resolver = { + ...config.resolver, + sourceExts: [...config.resolver.sourceExts, "po", "pot"], + // Use pre-compiled ICU message parser for smaller bundles + // https://formatjs.github.io/docs/guides/react-native-hermes + resolveRequest(context, moduleName, platform) { + if (moduleName === "@formatjs/icu-messageformat-parser") { + return context.resolveRequest( + context, + "@formatjs/icu-messageformat-parser/no-parser", + platform, + ); + } + return context.resolveRequest(context, moduleName, platform); + }, +}; + +module.exports = withUniwindConfig(wrapWithReanimatedMetroConfig(config), { cssEntryFile: "./src/global.css", dtsFile: "./uniwind-types.d.ts", }); - -module.exports = uniwindConfig; diff --git a/apps/native/package.json b/apps/native/package.json index 810afc3..0c5aa15 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -17,6 +17,14 @@ "@better-auth/expo": "catalog:", "@expo/metro-runtime": "55.0.6", "@expo/ui": "55.0.3", + "@formatjs/intl-datetimeformat": "7.2.6", + "@formatjs/intl-getcanonicallocales": "3.2.2", + "@formatjs/intl-locale": "5.2.2", + "@formatjs/intl-numberformat": "9.2.4", + "@formatjs/intl-pluralrules": "6.2.4", + "@formatjs/intl-relativetimeformat": "12.2.4", + "@lingui/core": "catalog:", + "@lingui/react": "catalog:", "@orpc/client": "catalog:", "@orpc/contract": "catalog:", "@orpc/tanstack-query": "catalog:", @@ -25,6 +33,7 @@ "@react-navigation/native": "7.1.33", "@shopify/flash-list": "2.0.2", "@sofa/api": "workspace:*", + "@sofa/i18n": "workspace:*", "@tabler/icons-react-native": "3.40.0", "@tanstack/query-async-storage-persister": "5.90.24", "@tanstack/react-form": "1.28.5", @@ -32,7 +41,6 @@ "@tanstack/react-query-persist-client": "5.90.24", "better-auth": "catalog:", "burnt": "0.13.0", - "date-fns": "catalog:", "expo": "55.0.7", "expo-application": "55.0.10", "expo-clipboard": "55.0.9", @@ -77,6 +85,8 @@ "zod": "catalog:" }, "devDependencies": { + "@lingui/babel-plugin-lingui-macro": "catalog:", + "@lingui/metro-transformer": "catalog:", "@types/node": "catalog:", "@types/react": "catalog:", "typescript": "catalog:" diff --git a/apps/native/src/app/(auth)/login.tsx b/apps/native/src/app/(auth)/login.tsx index 1340a20..fa0b39f 100644 --- a/apps/native/src/app/(auth)/login.tsx +++ b/apps/native/src/app/(auth)/login.tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import { IconServer2 } from "@tabler/icons-react-native"; import { useForm, useStore } from "@tanstack/react-form"; import { useQuery } from "@tanstack/react-query"; @@ -30,6 +31,7 @@ const signInSchema = z.object({ }); export default function LoginScreen() { + const { t } = useLingui(); const passwordRef = useRef(null); const [errorFields, setErrorFields] = useState>(new Set()); const [isSignedIn, setIsSignedIn] = useState(false); @@ -51,7 +53,7 @@ export default function LoginScreen() { { email: result.data.email, password: result.data.password }, { onError(error) { - toast.error(error.error?.message || "Failed to sign in"); + toast.error(error.error?.message || t`Failed to sign in`); }, onSuccess() { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); @@ -86,7 +88,7 @@ export default function LoginScreen() { }; return ( - + {showOidc && ( - Sign in with {authConfig.data?.oidcProviderName ?? "SSO"} + + Sign in with {authConfig.data?.oidcProviderName ?? "SSO"} + {showPasswordLogin && ( - OR + + OR + )} @@ -123,7 +129,9 @@ export default function LoginScreen() { {(field) => ( - + {(field) => ( - + ) : ( - Sign In + + Sign In + )} @@ -200,7 +212,9 @@ export default function LoginScreen() { @@ -223,8 +237,11 @@ export default function LoginScreen() { color={statusCompletedColor} /> - Connected to {serverHost} - . Tap to change. + + Connected to{" "} + {serverHost}. Tap to + change. + diff --git a/apps/native/src/app/(auth)/register.tsx b/apps/native/src/app/(auth)/register.tsx index e41db38..97f74ae 100644 --- a/apps/native/src/app/(auth)/register.tsx +++ b/apps/native/src/app/(auth)/register.tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import { useForm, useStore } from "@tanstack/react-form"; import { useQuery } from "@tanstack/react-query"; import { Link } from "expo-router"; @@ -35,6 +36,7 @@ const signUpSchema = z.object({ }); export default function RegisterScreen() { + const { t } = useLingui(); const emailRef = useRef(null); const passwordRef = useRef(null); const [errorFields, setErrorFields] = useState>(new Set()); @@ -62,7 +64,7 @@ export default function RegisterScreen() { }, { onError(error) { - toast.error(error.error?.message || "Failed to create account"); + toast.error(error.error?.message || t`Failed to create account`); }, onSuccess() { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); @@ -92,14 +94,14 @@ export default function RegisterScreen() { if (!registrationOpen && !publicInfo.isPending) { return ( @@ -110,15 +112,17 @@ export default function RegisterScreen() { return ( {(field) => ( - + {(field) => ( - + {(field) => ( - + ) : ( - Create Account + + Create Account + )} @@ -226,7 +236,7 @@ export default function RegisterScreen() { - Already have an account? Sign in + Already have an account? Sign in diff --git a/apps/native/src/app/(auth)/server-url.tsx b/apps/native/src/app/(auth)/server-url.tsx index 9509997..9b822b1 100644 --- a/apps/native/src/app/(auth)/server-url.tsx +++ b/apps/native/src/app/(auth)/server-url.tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import { IconAlertCircle, IconCircleCheck, @@ -33,18 +34,20 @@ type ConnectionState = | { phase: "success" } | { phase: "error"; error: ValidationError }; -const ERROR_MESSAGES: Record = { - network_unreachable: - "Could not reach the server. Check your connection and the URL.", - timeout: "Connection timed out. The server might be down or unreachable.", - not_sofa_server: - "This doesn't appear to be a Sofa server. Double-check the URL.", - server_unhealthy: - "Server found but reporting an issue. Try again in a moment.", - invalid_url: "That URL doesn't look right. Include the full server address.", -}; +function getErrorMessages( + t: (strings: TemplateStringsArray, ...values: unknown[]) => string, +): Record { + return { + network_unreachable: t`Could not reach the server. Check your connection and the URL.`, + timeout: t`Connection timed out. The server might be down or unreachable.`, + not_sofa_server: t`This doesn't appear to be a Sofa server. Double-check the URL.`, + server_unhealthy: t`Server found but reporting an issue. Try again in a moment.`, + invalid_url: t`That URL doesn't look right. Include the full server address.`, + }; +} export default function ServerUrlScreen() { + const { t } = useLingui(); const { replace } = useRouter(); const inputRef = useRef(null); const successTimeout = useRef | null>(null); @@ -145,7 +148,7 @@ export default function ServerUrlScreen() { return ( @@ -180,7 +183,7 @@ export default function ServerUrlScreen() { style={dotAnimatedStyle} /> - Connecting to server... + Connecting to server... ) : isSuccess ? ( @@ -191,7 +194,7 @@ export default function ServerUrlScreen() { color={statusCompletedColor} /> - Connected + Connected ) : ( @@ -200,7 +203,9 @@ export default function ServerUrlScreen() { disabled={!isValidUrl} className="bg-primary" > - Connect + + Connect + )} @@ -218,7 +223,7 @@ export default function ServerUrlScreen() { style={{ marginTop: 1 }} /> - {ERROR_MESSAGES[connection.error]} + {getErrorMessages(t)[connection.error]} )} @@ -234,7 +239,7 @@ export default function ServerUrlScreen() { > - Don't have a server? + Don't have a server? diff --git a/apps/native/src/app/(tabs)/(explore)/index.tsx b/apps/native/src/app/(tabs)/(explore)/index.tsx index 0960e68..7eee8fc 100644 --- a/apps/native/src/app/(tabs)/(explore)/index.tsx +++ b/apps/native/src/app/(tabs)/(explore)/index.tsx @@ -1,3 +1,4 @@ +import { useLingui } from "@lingui/react/macro"; import { IconDeviceTv, IconFlame, IconMovie } from "@tabler/icons-react-native"; import { useInfiniteQuery, useQuery } from "@tanstack/react-query"; import { useCallback, useMemo } from "react"; @@ -15,6 +16,7 @@ const exploreContentContainerStyle = { }; export default function ExploreScreen() { + const { t } = useLingui(); const trending = useInfiniteQuery( orpc.explore.trending.infiniteOptions({ input: (pageParam: number) => ({ type: "all" as const, page: pageParam }), @@ -81,14 +83,16 @@ export default function ExploreScreen() { > {heroItem && ( - + )} [ - { label: "Movies this month", value: stats.data?.moviesThisMonth }, - { label: "Episodes this week", value: stats.data?.episodesThisWeek }, - { label: "In library", value: stats.data?.librarySize }, - { label: "Completed", value: stats.data?.completed }, + { label: t`Movies this month`, value: stats.data?.moviesThisMonth }, + { label: t`Episodes this week`, value: stats.data?.episodesThisWeek }, + { label: t`In library`, value: stats.data?.librarySize }, + { label: t`Completed`, value: stats.data?.completed }, ], - [stats.data], + [stats.data, t], ); const renderStatItem = useCallback( @@ -107,7 +109,10 @@ export default function DashboardScreen() { {hasContinueWatching && ( - + - + {library.isPending ? ( @@ -133,9 +138,9 @@ export default function DashboardScreen() { ) : ( push("/(tabs)/(explore)")} /> )} @@ -145,7 +150,10 @@ export default function DashboardScreen() { {hasRecommendations && ( - + Search setQuery(e.nativeEvent.text)} onCancelButtonPress={() => setQuery("")} onClose={() => setQuery("")} @@ -108,7 +110,7 @@ export default function SearchScreen() { className="flex-1 items-center justify-center" > - No results for "{debouncedQuery}" + No results for "{debouncedQuery}" ) : ( diff --git a/apps/native/src/app/(tabs)/(settings)/index.tsx b/apps/native/src/app/(tabs)/(settings)/index.tsx index 4a199f8..515a532 100644 --- a/apps/native/src/app/(tabs)/(settings)/index.tsx +++ b/apps/native/src/app/(tabs)/(settings)/index.tsx @@ -1,3 +1,7 @@ +import { plural } from "@lingui/core/macro"; +import { Trans, useLingui } from "@lingui/react/macro"; +import { activateLocale, type SupportedLocale } from "@sofa/i18n"; +import { LOCALE_INFO } from "@sofa/i18n/locales"; import { IconArrowUpRight, IconBrandGithub, @@ -7,6 +11,7 @@ import { IconDatabase, IconDeviceMobileCog, IconDots, + IconLanguage, IconLink, IconLock, IconLogout, @@ -40,9 +45,11 @@ import { SettingsSection } from "@/components/settings/settings-section"; import { TmdbLogo } from "@/components/tmdb-logo"; import { Image } from "@/components/ui/image"; import { ScaledIcon } from "@/components/ui/scaled-icon"; +import { SelectModal } from "@/components/ui/select-modal"; import { Spinner } from "@/components/ui/spinner"; import { Switch } from "@/components/ui/switch"; import { Text } from "@/components/ui/text"; +import { setPersistedLocale } from "@/lib/i18n"; import { orpc } from "@/lib/orpc"; import { isAnalyticsEnabled, setAnalyticsEnabled } from "@/lib/posthog"; import { queryClient } from "@/lib/query-client"; @@ -56,6 +63,7 @@ const settingsContentContainerStyle = { }; export default function SettingsScreen() { + const { t, i18n } = useLingui(); const { push } = useRouter(); const { data: session, refetch: refetchSession } = authClient.useSession(); const [isEditingName, setIsEditingName] = useState(false); @@ -65,7 +73,11 @@ export default function SettingsScreen() { if (!isEditingName && session?.user?.name) setNameInput(session.user.name); }, [session?.user?.name, isEditingName]); + const [languageModalOpen, setLanguageModalOpen] = useState(false); + const languageLabel = + LOCALE_INFO.find((o) => o.code === i18n.locale)?.nativeName ?? i18n.locale; const [analyticsEnabled, setAnalyticsToggle] = useState(isAnalyticsEnabled); + const isAdmin = session?.user?.role === "admin"; const serverUrl = getServerUrl(); @@ -92,34 +104,34 @@ export default function SettingsScreen() { const updateName = useMutation( orpc.account.updateName.mutationOptions({ onSuccess: () => { - toast.success("Name updated"); + toast.success(t`Name updated`); setIsEditingName(false); queryClient.invalidateQueries({ queryKey: orpc.account.key() }); refetchSession(); }, - onError: () => toast.error("Failed to update name"), + onError: () => toast.error(t`Failed to update name`), }), ); const uploadAvatar = useMutation( orpc.account.uploadAvatar.mutationOptions({ onSuccess: () => { - toast.success("Profile picture updated"); + toast.success(t`Profile picture updated`); queryClient.invalidateQueries({ queryKey: orpc.account.key() }); refetchSession(); }, - onError: () => toast.error("Failed to upload avatar"), + onError: () => toast.error(t`Failed to upload avatar`), }), ); const removeAvatar = useMutation( orpc.account.removeAvatar.mutationOptions({ onSuccess: () => { - toast.success("Profile picture removed"); + toast.success(t`Profile picture removed`); queryClient.invalidateQueries({ queryKey: orpc.account.key() }); refetchSession(); }, - onError: () => toast.error("Failed to remove profile picture"), + onError: () => toast.error(t`Failed to remove profile picture`), }), ); @@ -152,12 +164,12 @@ export default function SettingsScreen() { const toggleRegistration = useMutation( orpc.admin.toggleRegistration.mutationOptions({ onSuccess: (_data, { open }) => { - toast.success(open ? "Registration opened" : "Registration closed"); + toast.success(open ? t`Registration opened` : t`Registration closed`); queryClient.invalidateQueries({ queryKey: orpc.admin.registration.key(), }); }, - onError: () => toast.error("Failed to update registration setting"), + onError: () => toast.error(t`Failed to update registration setting`), }), ); @@ -170,13 +182,13 @@ export default function SettingsScreen() { orpc.admin.toggleUpdateCheck.mutationOptions({ onSuccess: (_data, { enabled }) => { toast.success( - enabled ? "Update checks enabled" : "Update checks disabled", + enabled ? t`Update checks enabled` : t`Update checks disabled`, ); queryClient.invalidateQueries({ queryKey: orpc.admin.updateCheck.key(), }); }, - onError: () => toast.error("Failed to update setting"), + onError: () => toast.error(t`Failed to update setting`), }), ); @@ -191,10 +203,10 @@ export default function SettingsScreen() { }, []); const handleSignOut = () => { - Alert.alert("Sign Out", "Are you sure you want to sign out?", [ - { text: "Cancel", style: "cancel" }, + Alert.alert(t`Sign Out`, t`Are you sure you want to sign out?`, [ + { text: t`Cancel`, style: "cancel" }, { - text: "Sign Out", + text: t`Sign Out`, style: "destructive", onPress: () => { authClient.signOut(); @@ -216,7 +228,7 @@ export default function SettingsScreen() { > {/* Account */} - + {hasAvatarImage ? ( @@ -252,7 +264,7 @@ export default function SettingsScreen() { ios={{ name: "photo.on.rectangle.angled" }} /> - Change Photo + Change Photo - Remove Photo + Remove Photo @@ -306,7 +318,9 @@ export default function SettingsScreen() { updateName.mutate({ name: nameInput })} > - Save + + Save + { @@ -315,7 +329,7 @@ export default function SettingsScreen() { }} > - Cancel + Cancel @@ -336,7 +350,7 @@ export default function SettingsScreen() { maxFontSizeMultiplier={1.0} className="font-medium font-sans text-primary text-xs" > - Admin + Admin )} @@ -344,14 +358,14 @@ export default function SettingsScreen() { {showPasswordOption && ( push("/change-password")} /> )} - + { Alert.alert( - "Change Server", - "You'll be signed out to change the server URL.", + t`Change Server`, + t`You'll be signed out to change the server URL.`, [ - { text: "Cancel", style: "cancel" }, + { text: t`Cancel`, style: "cancel" }, { - text: "Continue", + text: t`Continue`, style: "destructive", onPress: async () => { await authClient.signOut(); @@ -386,7 +400,31 @@ export default function SettingsScreen() { }} /> setLanguageModalOpen(true)} + /> + ({ + value: info.code, + label: info.nativeName, + }))} + onSelect={(locale) => { + setLanguageModalOpen(false); + activateLocale(locale as SupportedLocale).then( + () => setPersistedLocale(locale as SupportedLocale), + () => {}, + ); + }} + /> + {systemHealth.isPending ? ( @@ -422,24 +460,26 @@ export default function SettingsScreen() { ) : systemHealth.data ? ( <> - + - Update available: {updateCheck.data.updateCheck.latestVersion} + + Update available:{" "} + {updateCheck.data.updateCheck.latestVersion} + )} @@ -491,14 +538,14 @@ export default function SettingsScreen() { {/* More Settings */} - + Linking.openURL(`${serverUrl}/settings`)} className="flex-row items-center justify-center py-3.5 active:opacity-70" > - Open in browser… + Open in browser… - This product uses the TMDB API but is not endorsed or certified by - TMDB. + + This product uses the TMDB API but is not endorsed or certified by + TMDB. + diff --git a/apps/native/src/app/+not-found.tsx b/apps/native/src/app/+not-found.tsx index caa987b..0eb116e 100644 --- a/apps/native/src/app/+not-found.tsx +++ b/apps/native/src/app/+not-found.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { IconAlertTriangle } from "@tabler/icons-react-native"; import { Link, Stack } from "expo-router"; import { View } from "react-native"; @@ -12,7 +13,9 @@ export default function NotFoundScreen() { return ( <> - Not Found + + Not Found + - Page Not Found + Page Not Found - The page you're looking for doesn't exist. + The page you're looking for doesn't exist. diff --git a/apps/native/src/app/_layout.tsx b/apps/native/src/app/_layout.tsx index e51abe9..69e6fb8 100644 --- a/apps/native/src/app/_layout.tsx +++ b/apps/native/src/app/_layout.tsx @@ -1,5 +1,8 @@ +import "@/lib/intl-polyfills"; import "@/global.css"; +import { I18nProvider } from "@lingui/react"; import { ThemeProvider } from "@react-navigation/native"; +import { i18n } from "@sofa/i18n"; import { QueryClientProvider } from "@tanstack/react-query"; import { persistQueryClientRestore, @@ -17,11 +20,13 @@ import { PostHogErrorBoundary, PostHogProvider } from "posthog-react-native"; import { useEffect, useRef, useState } from "react"; import { GestureHandlerRootView } from "react-native-gesture-handler"; import { KeyboardProvider } from "react-native-keyboard-controller"; +import { SafeAreaProvider } from "react-native-safe-area-context"; import { enableFreeze } from "react-native-screens"; import { Uniwind, useResolveClassNames } from "uniwind"; import { OfflineBanner } from "@/components/ui/offline-banner"; import { ServerUnreachableBanner } from "@/components/ui/server-unreachable-banner"; import { useServerConnection } from "@/hooks/use-server-connection"; +import { initLocale } from "@/lib/i18n"; import { applyTrackingTransparency, posthog } from "@/lib/posthog"; import { queryClient } from "@/lib/query-client"; import { @@ -35,6 +40,7 @@ import { sofaTheme } from "@/lib/theme"; SplashScreen.preventAutoHideAsync(); enableFreeze(true); initialize(); +const localeReady = initLocale(); export const unstable_settings = { initialRouteName: "(tabs)", @@ -63,6 +69,15 @@ function AppContent() { const contentStyle = useResolveClassNames("bg-background"); const { session, isPending, hasServerUrl } = useServerConnection(); + // --- Locale readiness (wait for async catalog load before showing UI) --- + const [isLocaleReady, setLocaleReady] = useState(false); + + useEffect(() => { + localeReady + .then(() => setLocaleReady(true)) + .catch(() => setLocaleReady(true)); + }, []); + // --- App Tracking Transparency (must resolve before screen tracking) --- const [trackingReady, setTrackingReady] = useState(false); @@ -111,10 +126,10 @@ function AppContent() { }, []); useEffect(() => { - if (!isPending || !hasServerUrl) { + if (isLocaleReady && (!isPending || !hasServerUrl)) { SplashScreen.hideAsync(); } - }, [isPending, hasServerUrl]); + }, [isPending, hasServerUrl, isLocaleReady]); return ( @@ -215,13 +230,17 @@ function QueryProvider({ children }: { children: React.ReactNode }) { export default function RootLayout() { const inner = ( - - - - - - - + + + + + + + + + + + ); if (!posthog) return inner; diff --git a/apps/native/src/app/change-password.tsx b/apps/native/src/app/change-password.tsx index 27eae9e..35e5918 100644 --- a/apps/native/src/app/change-password.tsx +++ b/apps/native/src/app/change-password.tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import { useForm } from "@tanstack/react-form"; import { Stack, useRouter } from "expo-router"; import { useRef, useState } from "react"; @@ -43,6 +44,7 @@ function formatFormErrors(errors: unknown): string | null { } export default function ChangePasswordScreen() { + const { t } = useLingui(); const { back } = useRouter(); const newPasswordRef = useRef(null); const confirmPasswordRef = useRef(null); @@ -66,20 +68,17 @@ export default function ChangePasswordScreen() { }); if (result.error) { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); - Alert.alert( - "Error", - result.error.message ?? "Failed to change password", - ); + Alert.alert(t`Error`, t`Failed to change password`); return; } Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); - toast.success("Password updated"); + toast.success(t`Password updated`); formApi.reset(); back(); } catch { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); - Alert.alert("Error", "Something went wrong"); + Alert.alert(t`Error`, t`Something went wrong`); } }, }); @@ -96,7 +95,7 @@ export default function ChangePasswordScreen() { keyboardShouldPersistTaps="handled" > }> - Change Password + Change Password {(field) => ( - + {(field) => ( - + {(field) => ( - + - Sign out of other sessions + Sign out of other sessions ) : ( - Update Password + + Update Password + )} diff --git a/apps/native/src/app/person/[id].tsx b/apps/native/src/app/person/[id].tsx index 7ee6dde..d56f09e 100644 --- a/apps/native/src/app/person/[id].tsx +++ b/apps/native/src/app/person/[id].tsx @@ -1,5 +1,7 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import { useHeaderHeight } from "@react-navigation/elements"; import { FlashList } from "@shopify/flash-list"; +import { formatDate } from "@sofa/i18n/format"; import { IconAlertTriangle, IconCalendar, @@ -8,8 +10,6 @@ import { IconUser, } from "@tabler/icons-react-native"; import { useInfiniteQuery } from "@tanstack/react-query"; -import { format } from "date-fns/format"; -import { parseISO } from "date-fns/parseISO"; import { useLocalSearchParams, useRouter } from "expo-router"; import { useCallback, useEffect, useMemo } from "react"; import { @@ -48,15 +48,20 @@ function calculateAge(birthday: string, deathday?: string | null): number { return age; } -const departmentLabels: Record = { - Acting: "Actor", - Directing: "Director", - Writing: "Writer", - Production: "Producer", - Editing: "Editor", -}; +function getDepartmentLabels( + t: (strings: TemplateStringsArray, ...values: unknown[]) => string, +): Record { + return { + Acting: t`Actor`, + Directing: t`Director`, + Writing: t`Writer`, + Production: t`Producer`, + Editing: t`Editor`, + }; +} export default function PersonDetailScreen() { + const { t } = useLingui(); const { id } = useLocalSearchParams<{ id: string }>(); const insets = useSafeAreaInsets(); const headerHeight = useHeaderHeight(); @@ -205,13 +210,15 @@ export default function PersonDetailScreen() { > - Something went wrong + Something went wrong - Could not load person details + Could not load person details back()} className="mt-4"> - Go back + + Go back + @@ -233,10 +240,12 @@ export default function PersonDetailScreen() { > - Person not found + Person not found back()} className="mt-4"> - Go back + + Go back + @@ -275,7 +284,7 @@ export default function PersonDetailScreen() { {person.knownForDepartment ? ( - {departmentLabels[person.knownForDepartment] ?? + {getDepartmentLabels(t)[person.knownForDepartment] ?? person.knownForDepartment} @@ -291,12 +300,12 @@ export default function PersonDetailScreen() { color={primaryColor} /> - {format(parseISO(person.birthday), "MMMM d, yyyy")} + {formatDate(person.birthday)} {(() => { const age = calculateAge(person.birthday, person.deathday); return person.deathday - ? ` (died at ${age})` - : ` (age ${age})`; + ? ` (${t`died at ${age}`})` + : ` (${t`age ${age}`})`; })()} @@ -328,7 +337,7 @@ export default function PersonDetailScreen() { entering={FadeInDown.duration(300).delay(200)} className="px-4" > - + )} diff --git a/apps/native/src/app/title/[id].tsx b/apps/native/src/app/title/[id].tsx index eaa9209..da2000e 100644 --- a/apps/native/src/app/title/[id].tsx +++ b/apps/native/src/app/title/[id].tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import { useHeaderHeight } from "@react-navigation/elements"; import { FlashList } from "@shopify/flash-list"; import { @@ -87,6 +88,7 @@ const titleDetailStyles = StyleSheet.create({ }); export default function TitleDetailScreen() { + const { t } = useLingui(); const { id } = useLocalSearchParams<{ id: string }>(); const insets = useSafeAreaInsets(); const headerHeight = useHeaderHeight(); @@ -246,10 +248,12 @@ export default function TitleDetailScreen() { - Title not found + Title not found back()} className="mt-4"> - Go back + + Go back + @@ -355,7 +359,7 @@ export default function TitleDetailScreen() { maxFontSizeMultiplier={1.0} className="font-medium font-sans text-title-accent-foreground text-xs" > - {title.type === "movie" ? "Movie" : "TV"} + {title.type === "movie" ? t`Movie` : t`TV`} {year ? ( @@ -442,7 +446,7 @@ export default function TitleDetailScreen() { color={titleAccentForeground} /> - Mark Watched + Mark Watched )} @@ -476,7 +480,7 @@ export default function TitleDetailScreen() { > @@ -529,7 +533,7 @@ export default function TitleDetailScreen() { className="mt-6 px-4" > @@ -552,7 +556,7 @@ export default function TitleDetailScreen() { > @@ -578,7 +582,7 @@ export default function TitleDetailScreen() { > diff --git a/apps/native/src/components/dashboard/continue-watching-card.tsx b/apps/native/src/components/dashboard/continue-watching-card.tsx index 52700b0..d06aa31 100644 --- a/apps/native/src/components/dashboard/continue-watching-card.tsx +++ b/apps/native/src/components/dashboard/continue-watching-card.tsx @@ -1,3 +1,4 @@ +import { useLingui } from "@lingui/react/macro"; import { Link } from "expo-router"; import { Pressable, View } from "react-native"; import { GestureDetector } from "react-native-gesture-handler"; @@ -26,6 +27,7 @@ export interface ContinueWatchingItem { } export function ContinueWatchingCard({ item }: { item: ContinueWatchingItem }) { + const { t } = useLingui(); const { animatedStyle, gesture: tapGesture } = usePressAnimation(); const progressLabel = `${item.watchedEpisodes} of ${item.totalEpisodes} episodes`; @@ -117,14 +119,14 @@ export function ContinueWatchingCard({ item }: { item: ContinueWatchingItem }) { titleActions.markCompleted(item.title.id, item.title.title) } /> titleActions.removeFromLibrary(item.title.id)} diff --git a/apps/native/src/components/explore/filterable-title-row.tsx b/apps/native/src/components/explore/filterable-title-row.tsx index b571f87..3822a4c 100644 --- a/apps/native/src/components/explore/filterable-title-row.tsx +++ b/apps/native/src/components/explore/filterable-title-row.tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import type { Icon } from "@tabler/icons-react-native"; import { skipToken, useInfiniteQuery } from "@tanstack/react-query"; import { useMemo, useState } from "react"; @@ -42,6 +43,7 @@ export function FilterableTitleRow({ genres?: Array<{ id: number; name: string }>; isLoading?: boolean; }) { + const { t } = useLingui(); const [selectedGenre, setSelectedGenre] = useState(null); const discover = useInfiniteQuery({ @@ -114,7 +116,7 @@ export function FilterableTitleRow({ contentContainerStyle={genreChipsContentStyle} > setSelectedGenre(null)} /> @@ -134,7 +136,7 @@ export function FilterableTitleRow({ {!showLoading && items.length === 0 && selectedGenre !== null ? ( - No titles found for this genre. + No titles found for this genre. ) : ( diff --git a/apps/native/src/components/explore/genre-chip.tsx b/apps/native/src/components/explore/genre-chip.tsx index 97d7176..6fd9679 100644 --- a/apps/native/src/components/explore/genre-chip.tsx +++ b/apps/native/src/components/explore/genre-chip.tsx @@ -1,3 +1,4 @@ +import { useLingui } from "@lingui/react/macro"; import { Pressable } from "react-native"; import { Text } from "@/components/ui/text"; import * as Haptics from "@/utils/haptics"; @@ -11,6 +12,7 @@ export function GenreChip({ isSelected: boolean; onPress: () => void; }) { + const { t } = useLingui(); return ( { @@ -22,8 +24,8 @@ export function GenreChip({ accessibilityState={{ selected: isSelected }} accessibilityHint={ isSelected - ? "Double tap to clear this filter" - : `Double tap to filter by ${label}` + ? t`Double tap to clear this filter` + : t`Double tap to filter by ${label}` } className={`mr-2 rounded-full px-3 py-1.5 ${isSelected ? "bg-primary" : "bg-secondary"}`} > diff --git a/apps/native/src/components/explore/hero-banner.tsx b/apps/native/src/components/explore/hero-banner.tsx index fa2f317..a3bba85 100644 --- a/apps/native/src/components/explore/hero-banner.tsx +++ b/apps/native/src/components/explore/hero-banner.tsx @@ -1,3 +1,4 @@ +import { useLingui } from "@lingui/react/macro"; import { IconStarFilled } from "@tabler/icons-react-native"; import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect"; import { Link } from "expo-router"; @@ -22,6 +23,7 @@ export interface HeroBannerItem { } export function HeroBanner({ item }: { item: HeroBannerItem }) { + const { t } = useLingui(); const primary = useCSSVariable("--color-primary") as string; const { animatedStyle, gesture: tapGesture } = usePressAnimation(0.98); @@ -152,7 +154,7 @@ export function HeroBanner({ item }: { item: HeroBannerItem }) { titleActions.quickAdd(item.id, item.title)} /> diff --git a/apps/native/src/components/header-avatar.tsx b/apps/native/src/components/header-avatar.tsx index 503663a..c40107f 100644 --- a/apps/native/src/components/header-avatar.tsx +++ b/apps/native/src/components/header-avatar.tsx @@ -1,3 +1,4 @@ +import { useLingui } from "@lingui/react/macro"; import { useRouter } from "expo-router"; import { Alert, Pressable, View } from "react-native"; import * as DropdownMenu from "zeego/dropdown-menu"; @@ -8,6 +9,7 @@ import { authClient } from "@/lib/server"; import * as Haptics from "@/utils/haptics"; export function HeaderAvatar() { + const { t } = useLingui(); const { data: session } = authClient.useSession(); const { navigate } = useRouter(); @@ -58,17 +60,17 @@ export function HeaderAvatar() { ios={{ name: "gear" }} androidIconName="ic_menu_preferences" /> - Settings + {t`Settings`} { - Alert.alert("Sign Out", "Are you sure you want to sign out?", [ - { text: "Cancel", style: "cancel" }, + Alert.alert(t`Sign Out`, t`Are you sure you want to sign out?`, [ + { text: t`Cancel`, style: "cancel" }, { - text: "Sign Out", + text: t`Sign Out`, style: "destructive", onPress: () => { authClient.signOut(); @@ -82,7 +84,7 @@ export function HeaderAvatar() { ios={{ name: "rectangle.portrait.and.arrow.right" }} androidIconName="ic_menu_close_clear_cancel" /> - Sign out + {t`Sign out`} diff --git a/apps/native/src/components/navigation/modal-stack-header.tsx b/apps/native/src/components/navigation/modal-stack-header.tsx index 41e7953..119330e 100644 --- a/apps/native/src/components/navigation/modal-stack-header.tsx +++ b/apps/native/src/components/navigation/modal-stack-header.tsx @@ -1,15 +1,17 @@ +import { useLingui } from "@lingui/react/macro"; import { Stack, useRouter } from "expo-router"; import { View } from "react-native"; export function DetailStackHeader() { const { dismissAll } = useRouter(); + const { t } = useLingui(); return ( <> dismissAll()}> - Close + {t`Close`} diff --git a/apps/native/src/components/search/recently-viewed-list.ios.tsx b/apps/native/src/components/search/recently-viewed-list.ios.tsx index 20f3229..5901729 100644 --- a/apps/native/src/components/search/recently-viewed-list.ios.tsx +++ b/apps/native/src/components/search/recently-viewed-list.ios.tsx @@ -8,6 +8,7 @@ import { onTapGesture, scrollContentBackground, } from "@expo/ui/swift-ui/modifiers"; +import { Trans, useLingui } from "@lingui/react/macro"; import { IconHistory, IconSearch } from "@tabler/icons-react-native"; import { useRouter } from "expo-router"; import { useCallback } from "react"; @@ -23,6 +24,7 @@ import { import * as Haptics from "@/utils/haptics"; export function RecentlyViewedList() { + const { t } = useLingui(); const { navigate } = useRouter(); const { items, removeItem, clearAll } = useRecentlyViewed(); const [mutedForeground, primaryColor] = useCSSVariable([ @@ -55,18 +57,18 @@ export function RecentlyViewedList() { const handleClear = useCallback(() => { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); Alert.alert( - "Clear Recently Viewed?", - "This will remove all items from your history.", + t`Clear Recently Viewed?`, + t`This will remove all items from your history.`, [ - { text: "Cancel", style: "cancel" }, + { text: t`Cancel`, style: "cancel" }, { - text: "Clear", + text: t`Clear`, style: "destructive", onPress: () => clearAll(), }, ], ); - }, [clearAll]); + }, [clearAll, t]); if (items.length === 0) { return ( @@ -76,7 +78,7 @@ export function RecentlyViewedList() { > - Search for movies, shows, or people + Search for movies, shows, or people ); @@ -101,7 +103,7 @@ export function RecentlyViewedList() { - Recently Viewed + Recently Viewed ({ opacity: pressed ? 0.6 : 1 })} > - Clear + + Clear + diff --git a/apps/native/src/components/search/recently-viewed-list.tsx b/apps/native/src/components/search/recently-viewed-list.tsx index 3b0a8ab..e6bb3f4 100644 --- a/apps/native/src/components/search/recently-viewed-list.tsx +++ b/apps/native/src/components/search/recently-viewed-list.tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import { FlashList } from "@shopify/flash-list"; import { IconHistory, IconSearch } from "@tabler/icons-react-native"; import { useCallback } from "react"; @@ -13,6 +14,7 @@ import { import * as Haptics from "@/utils/haptics"; export function RecentlyViewedList() { + const { t } = useLingui(); const { items, removeItem, clearAll } = useRecentlyViewed(); const [mutedForeground, primaryColor] = useCSSVariable([ "--color-muted-foreground", @@ -29,18 +31,18 @@ export function RecentlyViewedList() { const handleClear = useCallback(() => { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); Alert.alert( - "Clear Recently Viewed?", - "This will remove all items from your history.", + t`Clear Recently Viewed?`, + t`This will remove all items from your history.`, [ - { text: "Cancel", style: "cancel" }, + { text: t`Cancel`, style: "cancel" }, { - text: "Clear", + text: t`Clear`, style: "destructive", onPress: () => clearAll(), }, ], ); - }, [clearAll]); + }, [clearAll, t]); const renderItem = useCallback( ({ item }: { item: RecentlyViewedItem }) => ( @@ -59,7 +61,7 @@ export function RecentlyViewedList() { > - Search for movies, shows, or people + Search for movies, shows, or people ); @@ -81,7 +83,7 @@ export function RecentlyViewedList() { - Recently Viewed + Recently Viewed ({ opacity: pressed ? 0.6 : 1 })} > - Clear + + Clear + } diff --git a/apps/native/src/components/search/recently-viewed-row-content.tsx b/apps/native/src/components/search/recently-viewed-row-content.tsx index 16ef25f..1c4d265 100644 --- a/apps/native/src/components/search/recently-viewed-row-content.tsx +++ b/apps/native/src/components/search/recently-viewed-row-content.tsx @@ -1,3 +1,4 @@ +import { useLingui } from "@lingui/react/macro"; import { IconDeviceTv, IconMovie, IconUser } from "@tabler/icons-react-native"; import { View } from "react-native"; import { useCSSVariable } from "uniwind"; @@ -11,17 +12,22 @@ const TypeIcon = { person: IconUser, } as const; -const TypeLabel = { - movie: "Movie", - tv: "TV", - person: "Person", -} as const; +function getTypeLabel( + t: (strings: TemplateStringsArray, ...values: unknown[]) => string, +) { + return { + movie: t`Movie`, + tv: t`TV`, + person: t`Person`, + } as const; +} export function RecentlyViewedRowContent({ item, }: { item: RecentlyViewedItem; }) { + const { t } = useLingui(); const mutedForeground = useCSSVariable("--color-muted-foreground") as string; const Icon = TypeIcon[item.type]; @@ -63,7 +69,7 @@ export function RecentlyViewedRowContent({ maxFontSizeMultiplier={1.0} className="text-muted-foreground text-xs" > - {TypeLabel[item.type]} + {getTypeLabel(t)[item.type]} {item.subtitle ? ( diff --git a/apps/native/src/components/search/search-result-row.tsx b/apps/native/src/components/search/search-result-row.tsx index 1d68757..b865a72 100644 --- a/apps/native/src/components/search/search-result-row.tsx +++ b/apps/native/src/components/search/search-result-row.tsx @@ -1,3 +1,4 @@ +import { useLingui } from "@lingui/react/macro"; import { IconLoader, IconPlus } from "@tabler/icons-react-native"; import { Link } from "expo-router"; import { memo, useMemo } from "react"; @@ -25,11 +26,16 @@ export const SearchResultRow = memo(function SearchResultRow({ onQuickAdd: (id: string) => void; isAdding: boolean; }) { + const { t } = useLingui(); const primary = useCSSVariable("--color-primary") as string; const imageSrc = item.posterPath ?? item.profilePath; const typeLabel = - item.type === "movie" ? "Movie" : item.type === "tv" ? "TV show" : "Person"; + item.type === "movie" + ? t`Movie` + : item.type === "tv" + ? t`TV show` + : t`Person`; const accessibilityLabel = [ item.title, typeLabel, @@ -87,10 +93,10 @@ export const SearchResultRow = memo(function SearchResultRow({ className="text-muted-foreground text-xs" > {item.type === "movie" - ? "Movie" + ? t`Movie` : item.type === "tv" - ? "TV" - : "Person"} + ? t`TV` + : t`Person`} {item.releaseDate ? ( diff --git a/apps/native/src/components/settings/integration-card.tsx b/apps/native/src/components/settings/integration-card.tsx index a0af492..c7564a8 100644 --- a/apps/native/src/components/settings/integration-card.tsx +++ b/apps/native/src/components/settings/integration-card.tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import { IconCheck, IconChevronDown, @@ -46,6 +47,7 @@ export function IntegrationCard({ config, connection }: IntegrationCardProps) { | "sonarr" | "radarr"; + const { t } = useLingui(); const mutedFgColor = useCSSVariable("--color-muted-foreground") as string; const primaryColor = useCSSVariable("--color-primary") as string; @@ -91,30 +93,30 @@ export function IntegrationCard({ config, connection }: IntegrationCardProps) { const connectMutation = useMutation( orpc.integrations.create.mutationOptions({ onSuccess: () => { - toast.success(`${label} connected`); + toast.success(t`${label} connected`); queryClient.invalidateQueries({ queryKey: orpc.integrations.key() }); }, - onError: () => toast.error(`Failed to connect ${label}`), + onError: () => toast.error(t`Failed to connect ${label}`), }), ); const deleteMutation = useMutation( orpc.integrations.delete.mutationOptions({ onSuccess: () => { - toast.success(`${label} disconnected`); + toast.success(t`${label} disconnected`); queryClient.invalidateQueries({ queryKey: orpc.integrations.key() }); }, - onError: () => toast.error(`Failed to disconnect ${label}`), + onError: () => toast.error(t`Failed to disconnect ${label}`), }), ); const regenerateMutation = useMutation( orpc.integrations.regenerateToken.mutationOptions({ onSuccess: () => { - toast.success(`${label} URL regenerated`); + toast.success(t`${label} URL regenerated`); queryClient.invalidateQueries({ queryKey: orpc.integrations.key() }); }, - onError: () => toast.error(`Failed to regenerate ${label} URL`), + onError: () => toast.error(t`Failed to regenerate ${label} URL`), }), ); @@ -124,39 +126,39 @@ export function IntegrationCard({ config, connection }: IntegrationCardProps) { if (!url) return; await Clipboard.setStringAsync(url); setCopied(true); - toast.success("URL copied to clipboard"); + toast.success(t`URL copied to clipboard`); setTimeout(() => setCopied(false), 2000); - }, [url]); + }, [url, t]); const handleRegenerate = useCallback(() => { Alert.alert( - "Regenerate URL", - `This will invalidate the current ${label} URL. You'll need to update it in ${label}.`, + t`Regenerate URL`, + t`This will invalidate the current ${label} URL. You'll need to update it in ${label}.`, [ - { text: "Cancel", style: "cancel" }, + { text: t`Cancel`, style: "cancel" }, { - text: "Regenerate", + text: t`Regenerate`, style: "destructive", onPress: () => regenerateMutation.mutate({ provider: providerInput }), }, ], ); - }, [label, providerInput, regenerateMutation]); + }, [label, providerInput, regenerateMutation, t]); const handleDisconnect = useCallback(() => { Alert.alert( - `Disconnect ${label}`, - `Are you sure you want to disconnect ${label}? The current URL will stop working.`, + t`Disconnect ${label}`, + t`Are you sure you want to disconnect ${label}? The current URL will stop working.`, [ - { text: "Cancel", style: "cancel" }, + { text: t`Cancel`, style: "cancel" }, { - text: "Disconnect", + text: t`Disconnect`, style: "destructive", onPress: () => deleteMutation.mutate({ provider: providerInput }), }, ], ); - }, [label, providerInput, deleteMutation]); + }, [label, providerInput, deleteMutation, t]); const Icon = config.icon; @@ -189,7 +191,7 @@ export function IntegrationCard({ config, connection }: IntegrationCardProps) { {connection ? config.connectedStatus(connection.lastEventAt) - : "Not configured"} + : t`Not configured`} @@ -231,7 +233,9 @@ export function IntegrationCard({ config, connection }: IntegrationCardProps) { className="items-center rounded-lg bg-primary py-2.5 active:opacity-80" > - {connectMutation.isPending ? "Connecting…" : `Connect ${label}`} + {connectMutation.isPending + ? t`Connecting…` + : t`Connect ${label}`} ) : ( @@ -282,7 +286,7 @@ export function IntegrationCard({ config, connection }: IntegrationCardProps) { color={mutedFgColor} /> - Regenerate + Regenerate - Disconnect + Disconnect @@ -312,7 +316,7 @@ export function IntegrationCard({ config, connection }: IntegrationCardProps) { /> - Setup instructions + Setup instructions diff --git a/apps/native/src/components/settings/integration-configs.ts b/apps/native/src/components/settings/integration-configs.ts index cfb1aed..bf2225d 100644 --- a/apps/native/src/components/settings/integration-configs.ts +++ b/apps/native/src/components/settings/integration-configs.ts @@ -1,4 +1,6 @@ -import { formatDistanceToNow } from "date-fns/formatDistanceToNow"; +import type { I18n } from "@lingui/core"; +import { msg } from "@lingui/core/macro"; +import { formatRelativeTime } from "@sofa/i18n/format"; import type { SvgProps } from "react-native-svg"; import { EmbyIcon, @@ -21,97 +23,114 @@ export interface IntegrationConfig { setupSteps: string[]; } -function webhookStatus(lastEventAt: string | null): string { - return lastEventAt - ? `Last event ${formatDistanceToNow(new Date(lastEventAt), { addSuffix: true })}` - : "Ready — nothing received yet"; +function webhookStatus(i18n: I18n, lastEventAt: string | null): string { + if (lastEventAt) { + const timeAgo = formatRelativeTime(new Date(lastEventAt)); + return i18n._(msg`Last event ${timeAgo}`); + } + return i18n._(msg`Ready — nothing received yet`); } -function listStatus(lastEventAt: string | null): string { - return lastEventAt - ? `Last polled ${formatDistanceToNow(new Date(lastEventAt), { addSuffix: true })}` - : "Ready — not polled yet"; +function listStatus(i18n: I18n, lastEventAt: string | null): string { + if (lastEventAt) { + const timeAgo = formatRelativeTime(new Date(lastEventAt)); + return i18n._(msg`Last polled ${timeAgo}`); + } + return i18n._(msg`Ready — not polled yet`); } -export const INTEGRATION_CONFIGS: IntegrationConfig[] = [ - { - provider: "plex", - label: "Plex", - icon: PlexIcon, - type: "webhook", - buildUrl: (token) => `${getServerUrl()}/api/webhooks/${token}`, - urlLabel: "Webhook URL", - connectedStatus: webhookStatus, - requirementNote: "Requires an active Plex Pass subscription.", - setupSteps: [ - "Open Plex, go to Settings > Webhooks", - 'Click "Add Webhook" and paste the URL above', - "Sofa will automatically log movies and episodes when you finish watching them", - ], - }, - { - provider: "jellyfin", - label: "Jellyfin", - icon: JellyfinIcon, - type: "webhook", - buildUrl: (token) => `${getServerUrl()}/api/webhooks/${token}`, - urlLabel: "Webhook URL", - connectedStatus: webhookStatus, - setupSteps: [ - "Install the Webhook plugin from Jellyfin's plugin catalog", - "Go to Dashboard > Plugins > Webhook", - 'Add a "Generic Destination" and paste the URL above', - 'Enable the "Playback Stop" notification type', - "Sofa will automatically log movies and episodes when you finish watching them", - ], - }, - { - provider: "emby", - label: "Emby", - icon: EmbyIcon, - type: "webhook", - buildUrl: (token) => `${getServerUrl()}/api/webhooks/${token}`, - urlLabel: "Webhook URL", - connectedStatus: webhookStatus, - requirementNote: - "Requires Emby Server 4.7.9+ and an active Emby Premiere license.", - setupSteps: [ - "Open Emby, go to Settings > Webhooks", - "Add a new webhook and paste the URL above", - 'Enable the "Playback" event category', - "Sofa will automatically log movies and episodes when you finish watching them", - ], - }, - { - provider: "sonarr", - label: "Sonarr", - icon: SonarrIcon, - type: "list", - buildUrl: (token) => `${getServerUrl()}/api/lists/${token}`, - urlLabel: "Sonarr List URL", - connectedStatus: listStatus, - setupSteps: [ - "Open Sonarr, go to Settings > Import Lists", - 'Click "+" and select "Custom Lists"', - "Paste the Sonarr URL above into the List URL field", - "Set your preferred quality profile and root folder", - "Titles on your Sofa watchlist will be automatically added for download when Sonarr polls this list (every 6 hours by default)", - ], - }, - { - provider: "radarr", - label: "Radarr", - icon: RadarrIcon, - type: "list", - buildUrl: (token) => `${getServerUrl()}/api/lists/${token}`, - urlLabel: "Radarr List URL", - connectedStatus: listStatus, - setupSteps: [ - "Open Radarr, go to Settings > Import Lists", - 'Click "+" and select "Custom Lists"', - "Paste the Radarr URL above into the List URL field", - "Set your preferred quality profile and root folder", - "Titles on your Sofa watchlist will be automatically added for download when Radarr polls this list (every 12 hours by default)", - ], - }, -]; +export function getIntegrationConfigs(i18n: I18n): IntegrationConfig[] { + return [ + { + provider: "plex", + label: "Plex", + icon: PlexIcon, + type: "webhook", + buildUrl: (token) => `${getServerUrl()}/api/webhooks/${token}`, + urlLabel: i18n._(msg`Webhook URL`), + connectedStatus: (lastEventAt) => webhookStatus(i18n, lastEventAt), + requirementNote: i18n._(msg`Requires an active Plex Pass subscription.`), + setupSteps: [ + i18n._(msg`Open Plex, go to Settings > Webhooks`), + i18n._(msg`Click "Add Webhook" and paste the URL above`), + i18n._( + msg`Sofa will automatically log movies and episodes when you finish watching them`, + ), + ], + }, + { + provider: "jellyfin", + label: "Jellyfin", + icon: JellyfinIcon, + type: "webhook", + buildUrl: (token) => `${getServerUrl()}/api/webhooks/${token}`, + urlLabel: i18n._(msg`Webhook URL`), + connectedStatus: (lastEventAt) => webhookStatus(i18n, lastEventAt), + setupSteps: [ + i18n._(msg`Install the Webhook plugin from Jellyfin's plugin catalog`), + i18n._(msg`Go to Dashboard > Plugins > Webhook`), + i18n._(msg`Add a "Generic Destination" and paste the URL above`), + i18n._(msg`Enable the "Playback Stop" notification type`), + i18n._( + msg`Sofa will automatically log movies and episodes when you finish watching them`, + ), + ], + }, + { + provider: "emby", + label: "Emby", + icon: EmbyIcon, + type: "webhook", + buildUrl: (token) => `${getServerUrl()}/api/webhooks/${token}`, + urlLabel: i18n._(msg`Webhook URL`), + connectedStatus: (lastEventAt) => webhookStatus(i18n, lastEventAt), + requirementNote: i18n._( + msg`Requires Emby Server 4.7.9+ and an active Emby Premiere license.`, + ), + setupSteps: [ + i18n._(msg`Open Emby, go to Settings > Webhooks`), + i18n._(msg`Add a new webhook and paste the URL above`), + i18n._(msg`Enable the "Playback" event category`), + i18n._( + msg`Sofa will automatically log movies and episodes when you finish watching them`, + ), + ], + }, + { + provider: "sonarr", + label: "Sonarr", + icon: SonarrIcon, + type: "list", + buildUrl: (token) => `${getServerUrl()}/api/lists/${token}`, + urlLabel: i18n._(msg`Sonarr List URL`), + connectedStatus: (lastEventAt) => listStatus(i18n, lastEventAt), + setupSteps: [ + i18n._(msg`Open Sonarr, go to Settings > Import Lists`), + i18n._(msg`Click "+" and select "Custom Lists"`), + i18n._(msg`Paste the Sonarr URL above into the List URL field`), + i18n._(msg`Set your preferred quality profile and root folder`), + i18n._( + msg`Titles on your Sofa watchlist will be automatically added for download when Sonarr polls this list (every 6 hours by default)`, + ), + ], + }, + { + provider: "radarr", + label: "Radarr", + icon: RadarrIcon, + type: "list", + buildUrl: (token) => `${getServerUrl()}/api/lists/${token}`, + urlLabel: i18n._(msg`Radarr List URL`), + connectedStatus: (lastEventAt) => listStatus(i18n, lastEventAt), + setupSteps: [ + i18n._(msg`Open Radarr, go to Settings > Import Lists`), + i18n._(msg`Click "+" and select "Custom Lists"`), + i18n._(msg`Paste the Radarr URL above into the List URL field`), + i18n._(msg`Set your preferred quality profile and root folder`), + i18n._( + msg`Titles on your Sofa watchlist will be automatically added for download when Radarr polls this list (every 12 hours by default)`, + ), + ], + }, + ]; +} diff --git a/apps/native/src/components/settings/integrations-section.tsx b/apps/native/src/components/settings/integrations-section.tsx index c5990e4..3eb5efe 100644 --- a/apps/native/src/components/settings/integrations-section.tsx +++ b/apps/native/src/components/settings/integrations-section.tsx @@ -1,8 +1,9 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import { IconRefresh, IconWebhook } from "@tabler/icons-react-native"; import { useQuery } from "@tanstack/react-query"; import { Pressable, View } from "react-native"; import { IntegrationCard } from "@/components/settings/integration-card"; -import { INTEGRATION_CONFIGS } from "@/components/settings/integration-configs"; +import { getIntegrationConfigs } from "@/components/settings/integration-configs"; import { ScaledIcon } from "@/components/ui/scaled-icon"; import { SectionHeader } from "@/components/ui/section-header"; import { Spinner } from "@/components/ui/spinner"; @@ -10,11 +11,13 @@ import { Text } from "@/components/ui/text"; import { orpc } from "@/lib/orpc"; export function IntegrationsSection() { + const { t, i18n } = useLingui(); + const configs = getIntegrationConfigs(i18n); const integrations = useQuery(orpc.integrations.list.queryOptions()); return ( - + {integrations.isPending ? ( @@ -23,7 +26,7 @@ export function IntegrationsSection() { ) : integrations.isError ? ( - Could not load integrations + Could not load integrations integrations.refetch()} @@ -36,12 +39,12 @@ export function IntegrationsSection() { className="accent-primary" /> - Retry + Retry ) : ( - INTEGRATION_CONFIGS.map((config) => { + configs.map((config) => { const connection = integrations.data?.integrations?.find( (i) => i.provider === config.provider, diff --git a/apps/native/src/components/titles/continue-watching-banner.tsx b/apps/native/src/components/titles/continue-watching-banner.tsx index de842d4..d494a72 100644 --- a/apps/native/src/components/titles/continue-watching-banner.tsx +++ b/apps/native/src/components/titles/continue-watching-banner.tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import type { Season } from "@sofa/api/schemas"; import { getNextEpisode } from "@sofa/api/utils"; import { IconPlayerPlay } from "@tabler/icons-react-native"; @@ -22,6 +23,7 @@ export function ContinueWatchingBanner({ backdropPath: string | null; backdropThumbHash?: string | null; }) { + const { t } = useLingui(); const titleAccentColor = useCSSVariable("--color-title-accent") as string; const { nextEpisode, totalEpisodes, watchedEpisodes } = useMemo( @@ -41,7 +43,7 @@ export function ContinueWatchingBanner({ className="mt-6 px-4" > @@ -74,7 +76,7 @@ export function ContinueWatchingBanner({ maxFontSizeMultiplier={1.0} className="font-medium font-sans text-title-accent text-xs uppercase tracking-wider" > - Up next + Up next @@ -50,7 +52,7 @@ export function EpisodeRow({ numberOfLines={1} > {episode.episodeNumber}.{" "} - {episode.name ?? `Episode ${episode.episodeNumber}`} + {episode.name ?? t`Episode ${episode.episodeNumber}`} {episode.airDate ? ( diff --git a/apps/native/src/components/titles/season-accordion.tsx b/apps/native/src/components/titles/season-accordion.tsx index e4b6561..06d5493 100644 --- a/apps/native/src/components/titles/season-accordion.tsx +++ b/apps/native/src/components/titles/season-accordion.tsx @@ -1,3 +1,5 @@ +import { plural } from "@lingui/core/macro"; +import { Trans, useLingui } from "@lingui/react/macro"; import { IconChevronDown } from "@tabler/icons-react-native"; import { useCallback, useEffect, useState } from "react"; import { InteractionManager, Pressable, View } from "react-native"; @@ -33,6 +35,7 @@ export function SeasonAccordion({ }>; watchedEpisodeIds: Set; }) { + const { t } = useLingui(); const titleAccentColor = useCSSVariable("--color-title-accent") as string; const mutedFgColor = useCSSVariable("--color-muted-foreground") as string; @@ -116,16 +119,16 @@ export function SeasonAccordion({ - {season.name ?? `Season ${season.seasonNumber}`} + {season.name ?? t`Season ${season.seasonNumber}`} - {watchedCount}/{episodes.length} episodes + {t`${watchedCount}/${episodes.length} ${plural(episodes.length, { one: "episode", other: "episodes" })}`} @@ -158,7 +161,7 @@ export function SeasonAccordion({ className="mx-4 mb-2 flex-row items-center justify-center rounded-lg bg-secondary py-2" > - Mark All Watched + Mark All Watched )} diff --git a/apps/native/src/components/titles/status-action-button.tsx b/apps/native/src/components/titles/status-action-button.tsx index de6d1bc..dac38bf 100644 --- a/apps/native/src/components/titles/status-action-button.tsx +++ b/apps/native/src/components/titles/status-action-button.tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import { IconBookmarkFilled, IconCheck, @@ -12,27 +13,38 @@ import * as Haptics from "@/utils/haptics"; type TitleStatus = "watchlist" | "in_progress" | "completed"; -const statusConfig = { +const STATUS_ICONS = { + watchlist: IconBookmarkFilled, + in_progress: IconPlayerPlayFilled, + completed: IconCheck, +} as const; + +const STATUS_STYLES = { watchlist: { - label: "Watchlisted", - Icon: IconBookmarkFilled, bgClass: "bg-title-accent/10 border-title-accent/20", textClass: "text-title-accent", }, in_progress: { - label: "Watching", - Icon: IconPlayerPlayFilled, bgClass: "bg-title-accent/10 border-title-accent/20", textClass: "text-title-accent", }, completed: { - label: "Completed", - Icon: IconCheck, bgClass: "bg-status-completed/10 border-status-completed/20", textClass: "text-status-completed", }, } as const; +function StatusLabel({ status }: { status: TitleStatus }) { + switch (status) { + case "watchlist": + return Watchlisted; + case "in_progress": + return Watching; + case "completed": + return Completed; + } +} + export function StatusActionButton({ currentStatus, onStatusChange, @@ -42,16 +54,13 @@ export function StatusActionButton({ onStatusChange: (status: TitleStatus | null) => void; isPending: boolean; }) { + const { t } = useLingui(); const [titleAccent, completedColor] = useCSSVariable([ "--color-title-accent", "--color-status-completed", ]) as [string, string]; - const config = currentStatus - ? statusConfig[currentStatus as keyof typeof statusConfig] - : null; - - if (!config) { + if (!currentStatus) { return ( { @@ -60,7 +69,7 @@ export function StatusActionButton({ }} disabled={isPending} accessibilityRole="button" - accessibilityLabel="Add to watchlist" + accessibilityLabel={t`Add to watchlist`} className="flex-row items-center gap-1.5 rounded-lg border border-title-accent/20 bg-title-accent/10 px-4 py-2" > - Watchlist + Watchlist ); } + const StatusIcon = STATUS_ICONS[currentStatus]; + const styles = STATUS_STYLES[currentStatus]; const iconColor = currentStatus === "completed" ? completedColor : titleAccent; @@ -87,12 +98,12 @@ export function StatusActionButton({ }} disabled={isPending} accessibilityRole="button" - accessibilityLabel={`${config.label}, double tap to remove`} - className={`flex-row items-center gap-1.5 rounded-lg border px-4 py-2 ${config.bgClass}`} + accessibilityLabel={t`Remove from library`} + className={`flex-row items-center gap-1.5 rounded-lg border px-4 py-2 ${styles.bgClass}`} > - - - {config.label} + + + ); diff --git a/apps/native/src/components/ui/expandable-text.tsx b/apps/native/src/components/ui/expandable-text.tsx index d9c7aef..5efc6bc 100644 --- a/apps/native/src/components/ui/expandable-text.tsx +++ b/apps/native/src/components/ui/expandable-text.tsx @@ -1,3 +1,4 @@ +import { useLingui } from "@lingui/react/macro"; import { useCallback, useRef, useState } from "react"; import type { NativeSyntheticEvent, TextLayoutEventData } from "react-native"; import { Platform, Pressable, View } from "react-native"; @@ -12,6 +13,7 @@ export function ExpandableText({ maxLines?: number; actionColor?: string; }) { + const { t } = useLingui(); const prevTextRef = useRef(text); let expanded: boolean; let needsTruncation: boolean; @@ -57,14 +59,14 @@ export function ExpandableText({ setExpanded(!expanded)} accessibilityRole="button" - accessibilityLabel={expanded ? "Show less" : "Show more"} + accessibilityLabel={expanded ? t`Show less` : t`Show more`} className="mt-1" > - {expanded ? "Show less" : "Show more"} + {expanded ? t`Show less` : t`Show more`} )} diff --git a/apps/native/src/components/ui/offline-banner.ios.tsx b/apps/native/src/components/ui/offline-banner.ios.tsx index b5c05e2..3b9abb0 100644 --- a/apps/native/src/components/ui/offline-banner.ios.tsx +++ b/apps/native/src/components/ui/offline-banner.ios.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { IconWifiOff } from "@tabler/icons-react-native"; import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect"; import * as Network from "expo-network"; @@ -70,14 +71,14 @@ export function OfflineBanner() { > - No internet connection + No internet connection ) : ( - No internet connection + No internet connection )} diff --git a/apps/native/src/components/ui/offline-banner.tsx b/apps/native/src/components/ui/offline-banner.tsx index e57f7e6..622d5ec 100644 --- a/apps/native/src/components/ui/offline-banner.tsx +++ b/apps/native/src/components/ui/offline-banner.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { IconWifiOff } from "@tabler/icons-react-native"; import * as Network from "expo-network"; import { useEffect, useRef, useState } from "react"; @@ -55,7 +56,7 @@ export function OfflineBanner() { - No internet connection + No internet connection diff --git a/apps/native/src/components/ui/poster-card.tsx b/apps/native/src/components/ui/poster-card.tsx index 238c5e8..c65d739 100644 --- a/apps/native/src/components/ui/poster-card.tsx +++ b/apps/native/src/components/ui/poster-card.tsx @@ -1,3 +1,4 @@ +import { useLingui } from "@lingui/react/macro"; import { IconBookmarkFilled, IconCheckbox, @@ -52,6 +53,7 @@ export function PosterCard({ onQuickAdd, isAdding, }: PosterCardProps) { + const { t } = useLingui(); const primaryColor = useCSSVariable("--color-primary") as string; const watchlistColor = useCSSVariable("--color-status-watchlist") as string; const watchingColor = useCSSVariable("--color-status-watching") as string; @@ -233,28 +235,28 @@ export function PosterCard({ {!userStatus && ( )} {userStatus !== "in_progress" && ( titleActions.markWatching(id)} /> )} {type === "movie" && ( titleActions.markMovieWatched(id, title)} /> )} {userStatus && ( titleActions.removeFromLibrary(id)} diff --git a/apps/native/src/components/ui/select-modal.tsx b/apps/native/src/components/ui/select-modal.tsx new file mode 100644 index 0000000..e1c27cd --- /dev/null +++ b/apps/native/src/components/ui/select-modal.tsx @@ -0,0 +1,89 @@ +import { type Icon, IconCheck } from "@tabler/icons-react-native"; +import { Modal, Pressable, View } from "react-native"; +import { useCSSVariable } from "uniwind"; + +import { Text } from "@/components/ui/text"; +import { ScaledIcon } from "./scaled-icon"; + +export interface SelectModalOption { + value: string; + label: string; +} + +export interface SelectModalProps { + /** Label displayed on the trigger row */ + label: string; + /** Icon displayed in the modal title */ + icon?: Icon; + /** Currently selected value */ + selection: string; + /** Available options */ + options: SelectModalOption[]; + /** Whether the modal is open */ + open: boolean; + /** Called when the modal is opened or closed */ + onOpenChange: (open: boolean) => void; + /** Called when an option is selected */ + onSelect: (value: string) => void; +} + +export function SelectModal({ + label, + icon: Icon, + selection, + options, + open, + onOpenChange, + onSelect, +}: SelectModalProps) { + const mutedFgColor = useCSSVariable("--color-muted-foreground") as string; + const primaryColor = useCSSVariable("--color-primary") as string; + + return ( + onOpenChange(false)} + > + onOpenChange(false)} + > + e.stopPropagation()} + > + + {Icon && } + + {label} + + + {options.map((option) => { + const isSelected = option.value === selection; + return ( + { + onOpenChange(false); + onSelect(option.value); + }} + > + + {option.label} + + {isSelected && ( + + )} + + ); + })} + + + + ); +} diff --git a/apps/native/src/components/ui/server-unreachable-banner.ios.tsx b/apps/native/src/components/ui/server-unreachable-banner.ios.tsx index cef659c..998574c 100644 --- a/apps/native/src/components/ui/server-unreachable-banner.ios.tsx +++ b/apps/native/src/components/ui/server-unreachable-banner.ios.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { IconCloudOff } from "@tabler/icons-react-native"; import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect"; import * as Network from "expo-network"; @@ -62,13 +63,15 @@ export function ServerUnreachableBanner() { <> - Can't reach server + Can't reach server - Retry + + Retry + ); diff --git a/apps/native/src/components/ui/server-unreachable-banner.tsx b/apps/native/src/components/ui/server-unreachable-banner.tsx index 831abb0..077b534 100644 --- a/apps/native/src/components/ui/server-unreachable-banner.tsx +++ b/apps/native/src/components/ui/server-unreachable-banner.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { IconCloudOff } from "@tabler/icons-react-native"; import * as Network from "expo-network"; import { useEffect, useRef, useState } from "react"; @@ -70,14 +71,14 @@ export function ServerUnreachableBanner() { - Can't reach server + Can't reach server - Retry + Retry diff --git a/apps/native/src/hooks/use-title-actions.ts b/apps/native/src/hooks/use-title-actions.ts index fa3cfce..e0635e2 100644 --- a/apps/native/src/hooks/use-title-actions.ts +++ b/apps/native/src/hooks/use-title-actions.ts @@ -1,3 +1,5 @@ +import { plural } from "@lingui/core/macro"; +import { useLingui } from "@lingui/react/macro"; import { useMutation } from "@tanstack/react-query"; import { orpc } from "@/lib/orpc"; import { queryClient } from "@/lib/query-client"; @@ -32,16 +34,19 @@ interface UseTitleActionsOptions { * Each returned property is a full UseMutationResult with isPending, mutate, etc. */ export function useTitleActions(options?: UseTitleActionsOptions) { - const t = options?.toasts; + const { t } = useLingui(); + const toastOverrides = options?.toasts; const quickAdd = useMutation( orpc.titles.quickAdd.mutationOptions({ onSuccess: (_data, input) => { - toast.success(resolveToast(t?.quickAdd, "Added to watchlist", input)); + toast.success( + resolveToast(toastOverrides?.quickAdd, t`Added to watchlist`, input), + ); invalidateTitleQueries(); }, onError: () => { - toast.error("Failed to add to watchlist"); + toast.error(t`Failed to add to watchlist`); // Refetch so optimistic local status reverts on failure queryClient.invalidateQueries({ queryKey: orpc.titles.key() }); }, @@ -52,27 +57,31 @@ export function useTitleActions(options?: UseTitleActionsOptions) { orpc.titles.updateStatus.mutationOptions({ onSuccess: (_data, input) => { const statusMessages: Record = { - watchlist: "Added to watchlist", - in_progress: "Marked as watching", - completed: "Marked as completed", + watchlist: t`Added to watchlist`, + in_progress: t`Marked as watching`, + completed: t`Marked as completed`, }; const defaultMsg = input.status - ? (statusMessages[input.status] ?? "Status updated") - : "Removed from library"; - toast.success(resolveToast(t?.updateStatus, defaultMsg, input)); + ? (statusMessages[input.status] ?? t`Status updated`) + : t`Removed from library`; + toast.success( + resolveToast(toastOverrides?.updateStatus, defaultMsg, input), + ); invalidateTitleQueries(); }, - onError: () => toast.error("Failed to update status"), + onError: () => toast.error(t`Failed to update status`), }), ); const watchMovie = useMutation( orpc.titles.watchMovie.mutationOptions({ onSuccess: (_data, input) => { - toast.success(resolveToast(t?.watchMovie, "Marked as watched", input)); + toast.success( + resolveToast(toastOverrides?.watchMovie, t`Marked as watched`, input), + ); invalidateTitleQueries(); }, - onError: () => toast.error("Failed to mark as watched"), + onError: () => toast.error(t`Failed to mark as watched`), }), ); @@ -81,23 +90,27 @@ export function useTitleActions(options?: UseTitleActionsOptions) { onSuccess: (_data, input) => { const defaultMsg = input.stars > 0 - ? `Rated ${input.stars} star${input.stars > 1 ? "s" : ""}` - : "Rating removed"; - toast.success(resolveToast(t?.updateRating, defaultMsg, input)); + ? t`Rated ${input.stars} ${plural(input.stars, { one: "star", other: "stars" })}` + : t`Rating removed`; + toast.success( + resolveToast(toastOverrides?.updateRating, defaultMsg, input), + ); // Rating only invalidates title queries, not dashboard queryClient.invalidateQueries({ queryKey: orpc.titles.key() }); }, - onError: () => toast.error("Failed to update rating"), + onError: () => toast.error(t`Failed to update rating`), }), ); const watchEpisode = useMutation( orpc.episodes.watch.mutationOptions({ onSuccess: (_data, input) => { - toast.success(resolveToast(t?.watchEpisode, "Episode watched", input)); + toast.success( + resolveToast(toastOverrides?.watchEpisode, t`Episode watched`, input), + ); invalidateTitleQueries(); }, - onError: () => toast.error("Failed to mark episode"), + onError: () => toast.error(t`Failed to mark episode`), }), ); @@ -105,21 +118,27 @@ export function useTitleActions(options?: UseTitleActionsOptions) { orpc.episodes.unwatch.mutationOptions({ onSuccess: (_data, input) => { toast.success( - resolveToast(t?.unwatchEpisode, "Episode unwatched", input), + resolveToast( + toastOverrides?.unwatchEpisode, + t`Episode unwatched`, + input, + ), ); invalidateTitleQueries(); }, - onError: () => toast.error("Failed to unmark episode"), + onError: () => toast.error(t`Failed to unmark episode`), }), ); const watchSeason = useMutation( orpc.seasons.watch.mutationOptions({ onSuccess: (_data, input) => { - toast.success(resolveToast(t?.watchSeason, "Season watched", input)); + toast.success( + resolveToast(toastOverrides?.watchSeason, t`Season watched`, input), + ); invalidateTitleQueries(); }, - onError: () => toast.error("Failed to mark some episodes"), + onError: () => toast.error(t`Failed to mark some episodes`), }), ); diff --git a/apps/native/src/lib/error-messages.ts b/apps/native/src/lib/error-messages.ts new file mode 100644 index 0000000..cb1fd5e --- /dev/null +++ b/apps/native/src/lib/error-messages.ts @@ -0,0 +1,48 @@ +import { ORPCError } from "@orpc/client"; +import type { AppErrorCode } from "@sofa/api/errors"; + +export function getAppErrorCode(error: unknown): AppErrorCode | null { + if ( + error instanceof ORPCError && + error.data && + typeof error.data === "object" && + "code" in error.data && + typeof error.data.code === "string" + ) { + return error.data.code as AppErrorCode; + } + return null; +} + +export function appErrorMessages( + t: (template: TemplateStringsArray, ...args: unknown[]) => string, +): Record { + return { + TITLE_NOT_FOUND: t`Title not found`, + PERSON_NOT_FOUND: t`Person not found`, + INTEGRATION_NOT_FOUND: t`Integration not found`, + BACKUP_NOT_FOUND: t`Backup not found`, + BACKUP_DELETE_FAILED: t`Failed to delete backup`, + BACKUP_RESTORE_FAILED: t`Restore failed`, + JOB_NOT_FOUND: t`Job not found`, + TMDB_NOT_CONFIGURED: t`TMDB not configured`, + IMPORT_INVALID_FILE: t`Invalid file`, + IMPORT_PAYLOAD_TOO_LARGE: t`Import too large`, + IMPORT_ALREADY_RUNNING: t`Import already running`, + IMPORT_CANNOT_CANCEL: t`Cannot cancel import`, + REGISTRATION_CLOSED: t`Registration closed`, + }; +} + +export function getErrorMessage( + error: unknown, + t: (template: TemplateStringsArray, ...args: unknown[]) => string, + fallback?: string, +): string { + const code = getAppErrorCode(error); + if (code) { + const messages = appErrorMessages(t); + if (code in messages) return messages[code as keyof typeof messages]; + } + return fallback ?? t`Something went wrong`; +} diff --git a/apps/native/src/lib/i18n.ts b/apps/native/src/lib/i18n.ts new file mode 100644 index 0000000..9fe32d4 --- /dev/null +++ b/apps/native/src/lib/i18n.ts @@ -0,0 +1,36 @@ +import { + activateLocale, + SUPPORTED_LOCALES, + type SupportedLocale, +} from "@sofa/i18n"; +import * as Localization from "expo-localization"; +import { globalStorage } from "./mmkv"; + +const LOCALE_STORAGE_KEY = "sofa:locale"; + +export function getPersistedLocale(): SupportedLocale { + const stored = globalStorage.getString(LOCALE_STORAGE_KEY); + if (stored && SUPPORTED_LOCALES.includes(stored as SupportedLocale)) { + return stored as SupportedLocale; + } + const deviceLocales = Localization.getLocales(); + for (const loc of deviceLocales) { + const lang = loc.languageCode; + if (lang && SUPPORTED_LOCALES.includes(lang as SupportedLocale)) { + return lang as SupportedLocale; + } + } + return "en"; +} + +export function setPersistedLocale(locale: SupportedLocale): void { + globalStorage.set(LOCALE_STORAGE_KEY, locale); +} + +export function initLocale(): Promise { + const locale = getPersistedLocale(); + if (locale !== "en") { + return activateLocale(locale); + } + return Promise.resolve(); +} diff --git a/apps/native/src/lib/intl-polyfills.ts b/apps/native/src/lib/intl-polyfills.ts new file mode 100644 index 0000000..bcd888a --- /dev/null +++ b/apps/native/src/lib/intl-polyfills.ts @@ -0,0 +1,52 @@ +/** + * Intl polyfills for Hermes (React Native). + * + * Load order matters — each polyfill depends on the ones before it. + * See: https://formatjs.github.io/docs/guides/react-native-hermes + * + * We use `polyfill-force` to always install the polyfill regardless of + * partial Hermes Intl support, ensuring consistent behavior. + */ + +// 1. getCanonicalLocales (no dependencies) +import "@formatjs/intl-getcanonicallocales/polyfill"; + +// 2. Locale (depends on getCanonicalLocales) +import "@formatjs/intl-locale/polyfill"; + +// 3. PluralRules (depends on Locale) +import "@formatjs/intl-pluralrules/polyfill-force"; +import "@formatjs/intl-pluralrules/locale-data/en"; +import "@formatjs/intl-pluralrules/locale-data/fr"; +import "@formatjs/intl-pluralrules/locale-data/de"; +import "@formatjs/intl-pluralrules/locale-data/es"; +import "@formatjs/intl-pluralrules/locale-data/it"; +import "@formatjs/intl-pluralrules/locale-data/pt"; + +// 4. NumberFormat (depends on PluralRules, Locale) +import "@formatjs/intl-numberformat/polyfill-force"; +import "@formatjs/intl-numberformat/locale-data/en"; +import "@formatjs/intl-numberformat/locale-data/fr"; +import "@formatjs/intl-numberformat/locale-data/de"; +import "@formatjs/intl-numberformat/locale-data/es"; +import "@formatjs/intl-numberformat/locale-data/it"; +import "@formatjs/intl-numberformat/locale-data/pt"; + +// 5. DateTimeFormat (depends on Locale) +import "@formatjs/intl-datetimeformat/polyfill-force"; +import "@formatjs/intl-datetimeformat/locale-data/en"; +import "@formatjs/intl-datetimeformat/locale-data/fr"; +import "@formatjs/intl-datetimeformat/locale-data/de"; +import "@formatjs/intl-datetimeformat/locale-data/es"; +import "@formatjs/intl-datetimeformat/locale-data/it"; +import "@formatjs/intl-datetimeformat/locale-data/pt"; +import "@formatjs/intl-datetimeformat/add-all-tz"; + +// 6. RelativeTimeFormat (depends on PluralRules, Locale) +import "@formatjs/intl-relativetimeformat/polyfill-force"; +import "@formatjs/intl-relativetimeformat/locale-data/en"; +import "@formatjs/intl-relativetimeformat/locale-data/fr"; +import "@formatjs/intl-relativetimeformat/locale-data/de"; +import "@formatjs/intl-relativetimeformat/locale-data/es"; +import "@formatjs/intl-relativetimeformat/locale-data/it"; +import "@formatjs/intl-relativetimeformat/locale-data/pt"; diff --git a/apps/native/src/lib/query-client.ts b/apps/native/src/lib/query-client.ts index a8cde46..eef2d53 100644 --- a/apps/native/src/lib/query-client.ts +++ b/apps/native/src/lib/query-client.ts @@ -1,3 +1,5 @@ +import { msg } from "@lingui/core/macro"; +import { i18n } from "@sofa/i18n"; import { QueryCache, QueryClient } from "@tanstack/react-query"; import { posthog } from "@/lib/posthog"; @@ -28,9 +30,7 @@ export const queryClient = new QueryClient({ // banner is already visible — avoids flooding native toasts. if (!getIsReachable() && isNetworkError(error)) return; - toast.error("Something went wrong\u2026", { - description: error.message, - }); + toast.error(i18n._(msg`Something went wrong\u2026`)); posthog?.captureException(error, { source: "react-query" }); }, }), diff --git a/apps/native/src/lib/title-actions.ts b/apps/native/src/lib/title-actions.ts index e7e3721..6d4c7f1 100644 --- a/apps/native/src/lib/title-actions.ts +++ b/apps/native/src/lib/title-actions.ts @@ -1,3 +1,5 @@ +import { msg, plural } from "@lingui/core/macro"; +import { i18n } from "@sofa/i18n"; import { client, orpc } from "@/lib/orpc"; import { queryClient } from "@/lib/query-client"; import { toast } from "@/lib/toast"; @@ -17,11 +19,13 @@ export const titleActions = { try { await client.titles.quickAdd({ id }); toast.success( - titleName ? `Added "${titleName}" to watchlist` : "Added to watchlist", + titleName + ? i18n._(msg`Added "${titleName}" to watchlist`) + : i18n._(msg`Added to watchlist`), ); invalidateTitleQueries(); } catch { - toast.error("Failed to add to watchlist"); + toast.error(i18n._(msg`Failed to add to watchlist`)); // Refetch so any optimistic local status reverts on failure queryClient.invalidateQueries({ queryKey: orpc.titles.key() }); } @@ -30,10 +34,10 @@ export const titleActions = { async markWatching(id: string) { try { await client.titles.updateStatus({ id, status: "in_progress" }); - toast.success("Marked as watching"); + toast.success(i18n._(msg`Marked as watching`)); invalidateTitleQueries(); } catch { - toast.error("Failed to update status"); + toast.error(i18n._(msg`Failed to update status`)); } }, @@ -42,12 +46,12 @@ export const titleActions = { await client.titles.updateStatus({ id, status: "completed" }); toast.success( titleName - ? `Marked "${titleName}" as completed` - : "Marked as completed", + ? i18n._(msg`Marked "${titleName}" as completed`) + : i18n._(msg`Marked as completed`), ); invalidateTitleQueries(); } catch { - toast.error("Failed to update status"); + toast.error(i18n._(msg`Failed to update status`)); } }, @@ -55,21 +59,23 @@ export const titleActions = { try { await client.titles.watchMovie({ id }); toast.success( - titleName ? `Marked "${titleName}" as watched` : "Marked as watched", + titleName + ? i18n._(msg`Marked "${titleName}" as watched`) + : i18n._(msg`Marked as watched`), ); invalidateTitleQueries(); } catch { - toast.error("Failed to mark as watched"); + toast.error(i18n._(msg`Failed to mark as watched`)); } }, async removeFromLibrary(id: string) { try { await client.titles.updateStatus({ id, status: null }); - toast.success("Removed from library"); + toast.success(i18n._(msg`Removed from library`)); invalidateTitleQueries(); } catch { - toast.error("Failed to remove from library"); + toast.error(i18n._(msg`Failed to remove from library`)); } }, @@ -78,32 +84,34 @@ export const titleActions = { await client.titles.updateRating({ id, stars }); toast.success( stars > 0 - ? `Rated ${stars} star${stars > 1 ? "s" : ""}` - : "Rating removed", + ? i18n._( + msg`Rated ${plural(stars, { one: "# star", other: "# stars" })}`, + ) + : i18n._(msg`Rating removed`), ); queryClient.invalidateQueries({ queryKey: orpc.titles.key() }); } catch { - toast.error("Failed to update rating"); + toast.error(i18n._(msg`Failed to update rating`)); } }, async watchEpisode(id: string) { try { await client.episodes.watch({ id }); - toast.success("Episode watched"); + toast.success(i18n._(msg`Episode watched`)); invalidateTitleQueries(); } catch { - toast.error("Failed to mark episode"); + toast.error(i18n._(msg`Failed to mark episode`)); } }, async unwatchEpisode(id: string) { try { await client.episodes.unwatch({ id }); - toast.success("Episode unwatched"); + toast.success(i18n._(msg`Episode unwatched`)); invalidateTitleQueries(); } catch { - toast.error("Failed to unmark episode"); + toast.error(i18n._(msg`Failed to unmark episode`)); } }, @@ -111,11 +119,13 @@ export const titleActions = { try { await client.seasons.watch({ id }); toast.success( - seasonName ? `Watched all of ${seasonName}` : "Season watched", + seasonName + ? i18n._(msg`Watched all of ${seasonName}`) + : i18n._(msg`Season watched`), ); invalidateTitleQueries(); } catch { - toast.error("Failed to mark some episodes"); + toast.error(i18n._(msg`Failed to mark some episodes`)); } }, }; diff --git a/apps/server/src/orpc/procedures/admin.ts b/apps/server/src/orpc/procedures/admin.ts index 01d188b..fada7c0 100644 --- a/apps/server/src/orpc/procedures/admin.ts +++ b/apps/server/src/orpc/procedures/admin.ts @@ -1,5 +1,6 @@ import path from "node:path"; import { ORPCError } from "@orpc/server"; +import { AppErrorCode } from "@sofa/api/errors"; import { BACKUP_DIR } from "@sofa/config"; import { createBackup, @@ -47,9 +48,15 @@ export const backupsDelete = os.admin.backups.delete if (err instanceof ORPCError) throw err; const msg = err instanceof Error ? err.message : String(err); if (msg.includes("not found")) { - throw new ORPCError("NOT_FOUND", { message: msg }); + throw new ORPCError("NOT_FOUND", { + message: msg, + data: { code: AppErrorCode.BACKUP_NOT_FOUND }, + }); } - throw new ORPCError("BAD_REQUEST", { message: msg }); + throw new ORPCError("BAD_REQUEST", { + message: msg, + data: { code: AppErrorCode.BACKUP_DELETE_FAILED }, + }); } }); @@ -71,7 +78,10 @@ export const backupsRestore = os.admin.backups.restore if (await f.exists()) await f.delete(); if (err instanceof ORPCError) throw err; const msg = err instanceof Error ? err.message : String(err); - throw new ORPCError("BAD_REQUEST", { message: msg }); + throw new ORPCError("BAD_REQUEST", { + message: msg, + data: { code: AppErrorCode.BACKUP_RESTORE_FAILED }, + }); } }); @@ -160,7 +170,10 @@ export const triggerJob = os.admin.triggerJob .handler(async ({ input }) => { const triggered = await triggerCronJob(input.name); if (!triggered) { - throw new ORPCError("NOT_FOUND", { message: "Job not found" }); + throw new ORPCError("NOT_FOUND", { + message: "Job not found", + data: { code: AppErrorCode.JOB_NOT_FOUND }, + }); } return { ok: true as const }; }); diff --git a/apps/server/src/orpc/procedures/discover.ts b/apps/server/src/orpc/procedures/discover.ts index 8a1ae37..37b04f1 100644 --- a/apps/server/src/orpc/procedures/discover.ts +++ b/apps/server/src/orpc/procedures/discover.ts @@ -1,4 +1,5 @@ import { ORPCError } from "@orpc/server"; +import { AppErrorCode } from "@sofa/api/errors"; import { ensureBrowseTitlesExist } from "@sofa/core/metadata"; import { getEpisodeProgressByTitleIds, @@ -16,6 +17,7 @@ export const discover = os.discover if (!isTmdbConfigured()) { throw new ORPCError("PRECONDITION_FAILED", { message: "TMDB API key is not configured", + data: { code: AppErrorCode.TMDB_NOT_CONFIGURED }, }); } diff --git a/apps/server/src/orpc/procedures/explore.ts b/apps/server/src/orpc/procedures/explore.ts index 21ed76a..ffca5d4 100644 --- a/apps/server/src/orpc/procedures/explore.ts +++ b/apps/server/src/orpc/procedures/explore.ts @@ -1,4 +1,5 @@ import { ORPCError } from "@orpc/server"; +import { AppErrorCode } from "@sofa/api/errors"; import { ensureBrowseTitlesExist } from "@sofa/core/metadata"; import { getEpisodeProgressByTitleIds, @@ -14,6 +15,7 @@ function requireTmdb() { if (!isTmdbConfigured()) { throw new ORPCError("PRECONDITION_FAILED", { message: "TMDB API key is not configured", + data: { code: AppErrorCode.TMDB_NOT_CONFIGURED }, }); } } diff --git a/apps/server/src/orpc/procedures/imports.ts b/apps/server/src/orpc/procedures/imports.ts index 756d428..545792a 100644 --- a/apps/server/src/orpc/procedures/imports.ts +++ b/apps/server/src/orpc/procedures/imports.ts @@ -1,3 +1,5 @@ +import { ORPCError } from "@orpc/server"; +import { AppErrorCode } from "@sofa/api/errors"; import type { ParseResult } from "@sofa/core/imports"; import { countUnresolved, @@ -31,9 +33,10 @@ export const parseFile = os.imports.parseFile try { json = await file.json(); } catch { - throw new Error( - "Invalid JSON file. Ensure it is a valid Trakt export.", - ); + throw new ORPCError("BAD_REQUEST", { + message: "Invalid JSON file", + data: { code: AppErrorCode.IMPORT_INVALID_FILE }, + }); } result = parseTraktPayload( json as Parameters[0], @@ -45,9 +48,10 @@ export const parseFile = os.imports.parseFile try { json = await file.json(); } catch { - throw new Error( - "Invalid JSON file. Ensure it is a valid Simkl export.", - ); + throw new ORPCError("BAD_REQUEST", { + message: "Invalid JSON file", + data: { code: AppErrorCode.IMPORT_INVALID_FILE }, + }); } result = parseSimklPayload( json as Parameters[0], @@ -99,9 +103,10 @@ export const createJob = os.imports.createJob data.watchlist.length + data.ratings.length; if (totalItems > 100_000) { - throw new Error( - `Import payload too large (${totalItems} items, max 100,000)`, - ); + throw new ORPCError("BAD_REQUEST", { + message: "Import payload too large", + data: { code: AppErrorCode.IMPORT_PAYLOAD_TOO_LARGE }, + }); } // Prevent concurrent imports per user. @@ -137,7 +142,10 @@ export const createJob = os.imports.createJob .run(); log.warn(`Auto-cancelled stale import job ${existing.id}`); } else { - throw new Error("An import is already in progress"); + throw new ORPCError("CONFLICT", { + message: "An import is already in progress", + data: { code: AppErrorCode.IMPORT_ALREADY_RUNNING }, + }); } } @@ -175,7 +183,10 @@ export const cancelJob = os.imports.cancelJob .handler(({ input, context }) => { const job = readImportJob(input.id, context.user.id); if (job.status !== "pending" && job.status !== "running") { - throw new Error("Can only cancel pending or running jobs"); + throw new ORPCError("BAD_REQUEST", { + message: "Can only cancel pending or running jobs", + data: { code: AppErrorCode.IMPORT_CANNOT_CANCEL }, + }); } db.update(importJobs) .set({ status: "cancelled" }) diff --git a/apps/server/src/orpc/procedures/integrations.ts b/apps/server/src/orpc/procedures/integrations.ts index a0de924..efd84b5 100644 --- a/apps/server/src/orpc/procedures/integrations.ts +++ b/apps/server/src/orpc/procedures/integrations.ts @@ -1,4 +1,5 @@ import { ORPCError } from "@orpc/server"; +import { AppErrorCode } from "@sofa/api/errors"; import { db } from "@sofa/db/client"; import { and, desc, eq } from "@sofa/db/helpers"; import { integrationEvents, integrations } from "@sofa/db/schema"; @@ -145,7 +146,10 @@ export const regenerateToken = os.integrations.regenerateToken .get(); if (!row) { - throw new ORPCError("NOT_FOUND", { message: "Integration not found" }); + throw new ORPCError("NOT_FOUND", { + message: "Integration not found", + data: { code: AppErrorCode.INTEGRATION_NOT_FOUND }, + }); } return serializeIntegration(row); diff --git a/apps/server/src/orpc/procedures/people.ts b/apps/server/src/orpc/procedures/people.ts index bdea1a0..6dd8bfe 100644 --- a/apps/server/src/orpc/procedures/people.ts +++ b/apps/server/src/orpc/procedures/people.ts @@ -1,4 +1,5 @@ import { ORPCError } from "@orpc/server"; +import { AppErrorCode } from "@sofa/api/errors"; import { fetchFullFilmography, getOrFetchPerson } from "@sofa/core/person"; import { getUserStatusesByTitleIds } from "@sofa/core/tracking"; import { os } from "../context"; @@ -9,7 +10,10 @@ export const detail = os.people.detail .handler(async ({ input, context }) => { const person = await getOrFetchPerson(input.id); if (!person) - throw new ORPCError("NOT_FOUND", { message: "Person not found" }); + throw new ORPCError("NOT_FOUND", { + message: "Person not found", + data: { code: AppErrorCode.PERSON_NOT_FOUND }, + }); const allCredits = await fetchFullFilmography(person.id); diff --git a/apps/server/src/orpc/procedures/search.ts b/apps/server/src/orpc/procedures/search.ts index d55b46b..9fc1eb9 100644 --- a/apps/server/src/orpc/procedures/search.ts +++ b/apps/server/src/orpc/procedures/search.ts @@ -1,4 +1,5 @@ import { ORPCError } from "@orpc/server"; +import { AppErrorCode } from "@sofa/api/errors"; import { ensureBrowseTitlesExist } from "@sofa/core/metadata"; import { ensureBrowsePersonsExist } from "@sofa/core/person"; import { @@ -16,6 +17,7 @@ export const search = os.search.use(authed).handler(async ({ input }) => { if (!isTmdbConfigured()) { throw new ORPCError("PRECONDITION_FAILED", { message: "TMDB API key is not configured", + data: { code: AppErrorCode.TMDB_NOT_CONFIGURED }, }); } diff --git a/apps/server/src/orpc/procedures/titles.ts b/apps/server/src/orpc/procedures/titles.ts index 87c93e8..e2bc891 100644 --- a/apps/server/src/orpc/procedures/titles.ts +++ b/apps/server/src/orpc/procedures/titles.ts @@ -1,4 +1,5 @@ import { ORPCError } from "@orpc/server"; +import { AppErrorCode } from "@sofa/api/errors"; import { getRecommendationsForTitle } from "@sofa/core/discovery"; import { getOrFetchTitle, getOrFetchTitleByTmdbId } from "@sofa/core/metadata"; import { @@ -21,7 +22,10 @@ export const detail = os.titles.detail .handler(async ({ input }) => { const result = await getOrFetchTitle(input.id); if (!result) - throw new ORPCError("NOT_FOUND", { message: "Title not found" }); + throw new ORPCError("NOT_FOUND", { + message: "Title not found", + data: { code: AppErrorCode.TITLE_NOT_FOUND }, + }); return result; }); @@ -80,7 +84,10 @@ export const quickAdd = os.titles.quickAdd .where(eq(titles.id, input.id)) .get(); if (!title) { - throw new ORPCError("NOT_FOUND", { message: "Title not found" }); + throw new ORPCError("NOT_FOUND", { + message: "Title not found", + data: { code: AppErrorCode.TITLE_NOT_FOUND }, + }); } // Trigger full TMDB import if still a shell diff --git a/apps/web/package.json b/apps/web/package.json index 7e977ee..48ed786 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -16,12 +16,14 @@ "@fontsource-variable/dm-sans": "5.2.8", "@fontsource-variable/geist-mono": "5.2.7", "@fontsource/dm-serif-display": "5.2.8", + "@lingui/react": "catalog:", "@orpc/client": "catalog:", "@orpc/contract": "catalog:", "@orpc/tanstack-query": "catalog:", "@player.style/sutro": "0.2.1", "@rolldown/plugin-babel": "0.2.1", "@sofa/api": "workspace:*", + "@sofa/i18n": "workspace:*", "@tabler/icons-react": "3.40.0", "@tanstack/react-hotkeys": "0.4.1", "@tanstack/react-query": "catalog:", @@ -30,7 +32,6 @@ "class-variance-authority": "0.7.1", "clsx": "2.1.1", "cmdk": "1.1.1", - "date-fns": "catalog:", "jotai": "2.18.1", "media-chrome": "4.18.1", "motion": "12.38.0", @@ -45,6 +46,8 @@ "youtube-video-element": "1.9.0" }, "devDependencies": { + "@lingui/babel-plugin-lingui-macro": "catalog:", + "@lingui/vite-plugin": "catalog:", "@tailwindcss/vite": "0.0.0-insiders.f302fce", "@tanstack/devtools-vite": "0.6.0", "@tanstack/react-devtools": "0.10.0", diff --git a/apps/web/src/components/auth-form.tsx b/apps/web/src/components/auth-form.tsx index 3a03c51..e4a3184 100644 --- a/apps/web/src/components/auth-form.tsx +++ b/apps/web/src/components/auth-form.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { IconKey } from "@tabler/icons-react"; import { Link, useNavigate } from "@tanstack/react-router"; import { AnimatePresence, motion } from "motion/react"; @@ -105,10 +106,18 @@ export function AuthForm({

- {isRegister ? "Create your account" : "Welcome back"} + {isRegister ? ( + Create your account + ) : ( + Welcome back + )}

- {isRegister ? "Start tracking your watches" : "Sign in to continue"} + {isRegister ? ( + Start tracking your watches + ) : ( + Sign in to continue + )}

@@ -130,9 +139,13 @@ export function AuthForm({ className="h-11 w-full gap-2 rounded-lg border-border/50 bg-background/50 text-sm hover:bg-accent hover:text-foreground" > - {oidcLoading - ? "Redirecting…" - : `Sign in with ${authConfig?.oidcProviderName || "SSO"}`} + {oidcLoading ? ( + Redirecting… + ) : ( + + Sign in with {authConfig?.oidcProviderName || "SSO"} + + )} @@ -141,7 +154,9 @@ export function AuthForm({ {showOidc && showPasswordForm && (
- or + + or +
)} @@ -163,7 +178,7 @@ export function AuthForm({ htmlFor="name" className="text-muted-foreground uppercase tracking-wider" > - Name + Name - Email + Email - Password + Password - {loading - ? "Loading…" - : isRegister - ? "Create account" - : "Sign in"} + {loading ? ( + Loading… + ) : isRegister ? ( + Create account + ) : ( + Sign in + )} @@ -258,22 +275,22 @@ export function AuthForm({

{isRegister ? ( <> - Already have an account?{" "} + Already have an account?{" "} - Sign in + Sign in ) : ( <> - Don't have an account?{" "} + Don't have an account?{" "} - Register + Register )} diff --git a/apps/web/src/components/command-palette.tsx b/apps/web/src/components/command-palette.tsx index 7104c86..0bb84cc 100644 --- a/apps/web/src/components/command-palette.tsx +++ b/apps/web/src/components/command-palette.tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import { IconDeviceTv, IconHome, @@ -81,6 +82,7 @@ interface SearchResult { } export function CommandPalette() { + const { t } = useLingui(); const navigate = useNavigate(); const progress = useProgress(); const [commandPaletteOpen, setCommandPaletteOpen] = useAtom( @@ -181,7 +183,7 @@ export function CommandPalette() { Command Palette - Search for movies, TV shows, or run commands + Search for movies, TV shows, or run commands @@ -211,11 +213,13 @@ export function CommandPalette() { )} {hasQuery && !loading && results.length === 0 && ( - No results found. + + No results found. + )} {hasQuery && !loading && results.length > 0 && ( - + {results.map((r) => ( - Recent Searches + + Recent Searches +

} @@ -350,7 +356,7 @@ export function CommandPalette() { )} {recentSearches.length > 0 && } - + { setCommandPaletteOpen(false); @@ -359,7 +365,7 @@ export function CommandPalette() { }} > - Go to Dashboard + Go to Dashboard G H - Go to Explore + Go to Explore G E - Keyboard Shortcuts + Keyboard Shortcuts ? @@ -394,7 +400,9 @@ export function CommandPalette() { - Keyboard Shortcuts + + Keyboard Shortcuts +
{Object.entries(groupedShortcuts).map(([scope, items]) => ( diff --git a/apps/web/src/components/dashboard/continue-watching-card.tsx b/apps/web/src/components/dashboard/continue-watching-card.tsx index c47dcc2..87dca4b 100644 --- a/apps/web/src/components/dashboard/continue-watching-card.tsx +++ b/apps/web/src/components/dashboard/continue-watching-card.tsx @@ -1,3 +1,5 @@ +import { plural } from "@lingui/core/macro"; +import { useLingui } from "@lingui/react/macro"; import { IconPlayerPlay } from "@tabler/icons-react"; import { Link } from "@tanstack/react-router"; @@ -26,6 +28,7 @@ export function ContinueWatchingCard({ }: { item: ContinueWatchingItemProps; }) { + const { t } = useLingui(); const stillUrl = item.nextEpisode?.stillPath ?? item.title.backdropPath ?? null; const progress = @@ -71,7 +74,7 @@ export function ContinueWatchingCard({

- Up next + {t`Up next`}

@@ -87,7 +90,7 @@ export function ContinueWatchingCard({

{item.title.title}

- {item.watchedEpisodes}/{item.totalEpisodes} episodes + {t`${item.watchedEpisodes}/${item.totalEpisodes} ${plural(item.totalEpisodes, { one: "episode", other: "episodes" })}`}

diff --git a/apps/web/src/components/dashboard/continue-watching-section.tsx b/apps/web/src/components/dashboard/continue-watching-section.tsx index f921fd1..87a43d5 100644 --- a/apps/web/src/components/dashboard/continue-watching-section.tsx +++ b/apps/web/src/components/dashboard/continue-watching-section.tsx @@ -1,3 +1,4 @@ +import { useLingui } from "@lingui/react/macro"; import { IconPlayerPlay } from "@tabler/icons-react"; import { useQuery } from "@tanstack/react-query"; import { orpc } from "@/lib/orpc/client"; @@ -12,6 +13,8 @@ export function ContinueWatchingSection() { orpc.dashboard.continueWatching.queryOptions(), ); + const { t } = useLingui(); + if (isPending) return ; const items = data?.items ?? []; @@ -19,7 +22,7 @@ export function ContinueWatchingSection() { return ( } > diff --git a/apps/web/src/components/dashboard/library-section.tsx b/apps/web/src/components/dashboard/library-section.tsx index 32fc75f..d6532a4 100644 --- a/apps/web/src/components/dashboard/library-section.tsx +++ b/apps/web/src/components/dashboard/library-section.tsx @@ -1,3 +1,4 @@ +import { useLingui } from "@lingui/react/macro"; import { IconBooks } from "@tabler/icons-react"; import { useInfiniteQuery } from "@tanstack/react-query"; import { useInfiniteScroll } from "@/hooks/use-infinite-scroll"; @@ -16,6 +17,8 @@ export function LibrarySection() { }), ); + const { t } = useLingui(); + const sentinelRef = useInfiniteScroll({ fetchNextPage, hasNextPage, @@ -29,7 +32,7 @@ export function LibrarySection() { return ( } > diff --git a/apps/web/src/components/dashboard/recommendations-section.tsx b/apps/web/src/components/dashboard/recommendations-section.tsx index b807da0..4effd3d 100644 --- a/apps/web/src/components/dashboard/recommendations-section.tsx +++ b/apps/web/src/components/dashboard/recommendations-section.tsx @@ -1,3 +1,4 @@ +import { useLingui } from "@lingui/react/macro"; import { IconThumbUp } from "@tabler/icons-react"; import { useQuery } from "@tanstack/react-query"; import { orpc } from "@/lib/orpc/client"; @@ -9,6 +10,8 @@ export function RecommendationsSection() { orpc.dashboard.recommendations.queryOptions(), ); + const { t } = useLingui(); + if (isPending) return ; const items = data?.items ?? []; @@ -16,7 +19,7 @@ export function RecommendationsSection() { return ( } > diff --git a/apps/web/src/components/dashboard/stats-display.tsx b/apps/web/src/components/dashboard/stats-display.tsx index 567775a..ff41264 100644 --- a/apps/web/src/components/dashboard/stats-display.tsx +++ b/apps/web/src/components/dashboard/stats-display.tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import type { DashboardStats, HistoryBucket, @@ -45,13 +46,6 @@ export function StatsSectionSkeleton() { ); } -const periodLabels: Record = { - today: "Today", - this_week: "This Week", - this_month: "This Month", - this_year: "This Year", -}; - const periods: TimePeriod[] = ["today", "this_week", "this_month", "this_year"]; interface StatCardProps { @@ -103,47 +97,78 @@ function StatCard({ const inlineTriggerClass = "h-auto w-auto gap-0.5 rounded-none border-0 bg-transparent py-1 px-0.5 -my-1 -mx-0.5 sm:p-0 sm:m-0 [font-size:inherit] [line-height:inherit] shadow-none underline decoration-dotted decoration-muted-foreground/50 underline-offset-4 hover:bg-transparent hover:text-foreground hover:decoration-foreground/50 focus-visible:ring-0 focus-visible:decoration-solid focus-visible:decoration-foreground dark:bg-transparent dark:hover:bg-transparent"; +function PeriodSelect({ + period, + onPeriodChange, + periodLabels, +}: { + period: TimePeriod; + onPeriodChange: (period: TimePeriod) => void; + periodLabels: Record; +}) { + return ( + + ); +} + function PeriodSelector({ - noun, + type, period, onPeriodChange, }: { - noun: string; + type: "movies" | "episodes"; period: TimePeriod; onPeriodChange: (period: TimePeriod) => void; }) { + const { t } = useLingui(); + const periodLabels: Record = { + today: t`Today`, + this_week: t`This Week`, + this_month: t`This Month`, + this_year: t`This Year`, + }; + + const select = ( + + ); + return ( - {noun}{" "} - + {type === "movies" ? ( + Movies {select} + ) : ( + Episodes {select} + )} ); } export function StatsDisplay({ stats }: { stats: DashboardStats }) { + const { t } = useLingui(); const [moviePeriod, setMoviePeriod] = useState("this_month"); const [episodePeriod, setEpisodePeriod] = useState("this_week"); @@ -175,7 +200,7 @@ export function StatsDisplay({ stats }: { stats: DashboardStats }) { sparklineData={movieHistory} label={ @@ -190,7 +215,7 @@ export function StatsDisplay({ stats }: { stats: DashboardStats }) { sparklineData={episodeHistory} label={ @@ -202,7 +227,7 @@ export function StatsDisplay({ stats }: { stats: DashboardStats }) { bgColor="bg-status-watchlist/10" value={stats.librarySize} index={2} - label="In Library" + label={t`In Library`} />
); diff --git a/apps/web/src/components/dashboard/stats-section.tsx b/apps/web/src/components/dashboard/stats-section.tsx index 919c542..9994883 100644 --- a/apps/web/src/components/dashboard/stats-section.tsx +++ b/apps/web/src/components/dashboard/stats-section.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { IconDeviceTv } from "@tabler/icons-react"; import { useQuery } from "@tanstack/react-query"; import { Link } from "@tanstack/react-router"; @@ -27,16 +28,18 @@ export function StatsSection() {
-

Your library is empty

+

+ Your library is empty +

- Search for movies and TV shows to start tracking + Search for movies and TV shows to start tracking

- Start exploring + Start exploring
)} diff --git a/apps/web/src/components/dashboard/welcome-header.tsx b/apps/web/src/components/dashboard/welcome-header.tsx index 0eea40b..e86df79 100644 --- a/apps/web/src/components/dashboard/welcome-header.tsx +++ b/apps/web/src/components/dashboard/welcome-header.tsx @@ -1,11 +1,17 @@ +import { Trans } from "@lingui/react/macro"; + export function WelcomeHeader({ name }: { name?: string | null }) { return (

- Welcome back{name ? `, ${name}` : ""} + {name ? ( + Welcome back, {name} + ) : ( + Welcome back + )}

- Here's what's happening with your library + Here's what's happening with your library

); diff --git a/apps/web/src/components/expandable-text.tsx b/apps/web/src/components/expandable-text.tsx index 55ce132..0376d31 100644 --- a/apps/web/src/components/expandable-text.tsx +++ b/apps/web/src/components/expandable-text.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { useEffect, useRef, useState } from "react"; import { cn } from "@/lib/utils"; @@ -47,7 +48,7 @@ export function ExpandableText({ onClick={() => setExpanded(!expanded)} className="mt-1 font-medium text-primary text-xs transition-colors hover:text-primary/80" > - {expanded ? "Show less" : "Read more"} + {expanded ? Show less : Read more} )}
diff --git a/apps/web/src/components/explore/explore-client.tsx b/apps/web/src/components/explore/explore-client.tsx index 6ff423a..1c6f34f 100644 --- a/apps/web/src/components/explore/explore-client.tsx +++ b/apps/web/src/components/explore/explore-client.tsx @@ -1,3 +1,4 @@ +import { useLingui } from "@lingui/react/macro"; import { IconDeviceTv, IconFlame, IconMovie } from "@tabler/icons-react"; import { useInfiniteQuery, useQuery } from "@tanstack/react-query"; import { useMemo } from "react"; @@ -41,6 +42,7 @@ function mergeMaps( } export function ExploreClient() { + const { t } = useLingui(); const { data: trendingData, isPending: trendingPending, @@ -107,7 +109,7 @@ export function ExploreClient() {
} @@ -121,7 +123,7 @@ export function ExploreClient() {
} mediaType="movie" defaultItems={(popularMoviesData?.items ?? []).slice(0, 20)} @@ -131,7 +133,7 @@ export function ExploreClient() { /> } diff --git a/apps/web/src/components/explore/filterable-title-row.tsx b/apps/web/src/components/explore/filterable-title-row.tsx index c75f24d..727d1c0 100644 --- a/apps/web/src/components/explore/filterable-title-row.tsx +++ b/apps/web/src/components/explore/filterable-title-row.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { skipToken, useInfiniteQuery } from "@tanstack/react-query"; import { useMemo, useRef, useState } from "react"; import { TitleCard, TitleCardSkeleton } from "@/components/title-card"; @@ -147,7 +148,7 @@ export function FilterableTitleRow({ {/* Empty state */} {!isLoading && selectedGenre !== null && items.length === 0 && (

- No titles found for this genre. + No titles found for this genre.

)} diff --git a/apps/web/src/components/explore/hero-banner.tsx b/apps/web/src/components/explore/hero-banner.tsx index 6405247..1d95ff5 100644 --- a/apps/web/src/components/explore/hero-banner.tsx +++ b/apps/web/src/components/explore/hero-banner.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { IconDeviceTv, IconMovie, @@ -55,12 +56,12 @@ export function HeroBanner({ {type === "movie" ? ( <> - Movie + Movie ) : ( <> - TV + TV )} @@ -74,7 +75,7 @@ export function HeroBanner({ )} - Trending today + Trending today - Add to Library + Add to Library diff --git a/apps/web/src/components/landing-page.tsx b/apps/web/src/components/landing-page.tsx index b10756c..104066c 100644 --- a/apps/web/src/components/landing-page.tsx +++ b/apps/web/src/components/landing-page.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { Link } from "@tanstack/react-router"; import { motion } from "motion/react"; import { SofaLogo } from "@/components/sofa-logo"; @@ -101,7 +102,7 @@ export function LandingPage({ damping: 20, }} > - Self-hosted movie & TV tracker + Self-hosted movie & TV tracker - Track what you watch. Know what's next. -
- Your library, your data, your rules. + + Track what you watch. Know what's next. +
+ Your library, your data, your rules. +
@@ -149,7 +152,9 @@ export function LandingPage({ to="/register" className="group relative inline-flex h-12 items-center justify-center overflow-hidden rounded-lg bg-primary px-8 font-medium text-primary-foreground transition-shadow hover:shadow-lg hover:shadow-primary/20" > - Get Started + + Get Started +
) : ( @@ -158,7 +163,9 @@ export function LandingPage({ to="/login" className="group relative inline-flex h-12 items-center justify-center overflow-hidden rounded-lg bg-primary px-8 font-medium text-primary-foreground transition-shadow hover:shadow-lg hover:shadow-primary/20" > - Sign In + + Sign In +
{registrationOpen && ( @@ -166,7 +173,7 @@ export function LandingPage({ to="/register" className="inline-flex h-12 items-center justify-center rounded-lg border border-border px-8 font-medium transition-colors hover:border-primary/40 hover:bg-primary/5" > - Register + Register )} diff --git a/apps/web/src/components/nav-bar.tsx b/apps/web/src/components/nav-bar.tsx index aca2859..b4f6f21 100644 --- a/apps/web/src/components/nav-bar.tsx +++ b/apps/web/src/components/nav-bar.tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import { IconCompass, IconHome, @@ -35,16 +36,7 @@ const springTransition = { damping: 30, } as const; -const navLinks = [ - { href: "/dashboard", label: "Home" }, - { href: "/explore", label: "Explore" }, -] as const; - -const mobileTabs = [ - { href: "/dashboard", label: "Home", icon: IconHome }, - { href: "/explore", label: "Explore", icon: IconCompass }, - { href: "/settings", label: "Settings", icon: IconSettings }, -] as const; +// navLinks and mobileTabs moved inside components for LingUI function isLinkActive(pathname: string, href: string) { return ( @@ -124,10 +116,16 @@ export function NavBar({ userImage?: string; userRole?: string; }) { + const { t } = useLingui(); const navigate = useNavigate(); const { pathname } = useLocation(); const setCommandPaletteOpen = useSetAtom(commandPaletteOpenAtom); + const navLinks = [ + { href: "/dashboard", label: t`Home` }, + { href: "/explore", label: t`Explore` }, + ] as const; + const initial = userName?.charAt(0).toUpperCase() ?? "?"; const activeIndex = navLinks.findIndex((link) => @@ -192,7 +190,7 @@ export function NavBar({ className="flex flex-1 items-center gap-2 rounded-lg border border-border/50 bg-card/50 px-3 py-1.5 text-[13px] text-muted-foreground transition-colors hover:border-primary/20 hover:bg-card sm:hidden" > - Search… + {t`Search…`} {/* Desktop search trigger pill */} - Admin + Admin )}

@@ -249,7 +247,7 @@ export function NavBar({ className="cursor-pointer text-[13px]" > - Settings + Settings - Sign out + Sign out @@ -285,8 +283,15 @@ export function NavBar({ } export function MobileTabBar() { + const { t } = useLingui(); const { pathname } = useLocation(); + const mobileTabs = [ + { href: "/dashboard", label: t`Home`, icon: IconHome }, + { href: "/explore", label: t`Explore`, icon: IconCompass }, + { href: "/settings", label: t`Settings`, icon: IconSettings }, + ] as const; + const activeIndex = mobileTabs.findIndex((tab) => isLinkActive(pathname, tab.href), ); diff --git a/apps/web/src/components/people/filmography-grid.tsx b/apps/web/src/components/people/filmography-grid.tsx index 6212179..3e4f453 100644 --- a/apps/web/src/components/people/filmography-grid.tsx +++ b/apps/web/src/components/people/filmography-grid.tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import type { PersonCredit } from "@sofa/api/schemas"; import { IconMovie } from "@tabler/icons-react"; import { useMemo, useState } from "react"; @@ -23,6 +24,7 @@ export function FilmographyGrid({ credits, userStatuses, }: FilmographyGridProps) { + const { t } = useLingui(); const [filter, setFilter] = useState("all"); const [sort, setSort] = useState("newest"); @@ -53,9 +55,9 @@ export function FilmographyGrid({ if (credits.length === 0) return null; const filters: { value: Filter; label: string }[] = [ - { value: "all", label: "All" }, - { value: "movie", label: "Movies" }, - { value: "tv", label: "TV" }, + { value: "all", label: t`All` }, + { value: "movie", label: t`Movies` }, + { value: "tv", label: t`TV` }, ]; return ( @@ -64,7 +66,7 @@ export function FilmographyGrid({

- Filmography + Filmography

({filtered.length}) @@ -81,9 +83,9 @@ export function FilmographyGrid({ {(value: string | null) => value === "newest" - ? "Newest" + ? t`Newest` : value === "rating" - ? "Rating" + ? t`Rating` : null } @@ -93,8 +95,12 @@ export function FilmographyGrid({ alignItemWithTrigger={false} className="p-1" > - Newest - Rating + + Newest + + + Rating +
diff --git a/apps/web/src/components/people/person-hero.tsx b/apps/web/src/components/people/person-hero.tsx index 027701a..f8afc65 100644 --- a/apps/web/src/components/people/person-hero.tsx +++ b/apps/web/src/components/people/person-hero.tsx @@ -1,6 +1,7 @@ +import { useLingui } from "@lingui/react/macro"; import type { ResolvedPerson } from "@sofa/api/schemas"; +import { formatDate } from "@sofa/i18n/format"; import { IconCalendar, IconMapPin } from "@tabler/icons-react"; -import { format, parseISO } from "date-fns"; import { ExpandableText } from "@/components/expandable-text"; import { Badge } from "@/components/ui/badge"; @@ -22,6 +23,7 @@ function calculateAge(birthday: string, deathday?: string | null): number { } export function PersonHero({ person }: PersonHeroProps) { + const { t } = useLingui(); const age = person.birthday ? calculateAge(person.birthday, person.deathday) : null; @@ -66,15 +68,15 @@ export function PersonHero({ person }: PersonHeroProps) { {person.knownForDepartment && ( {person.knownForDepartment === "Acting" - ? "Actor" + ? t`Actor` : person.knownForDepartment === "Directing" - ? "Director" + ? t`Director` : person.knownForDepartment === "Writing" - ? "Writer" + ? t`Writer` : person.knownForDepartment === "Production" - ? "Producer" + ? t`Producer` : person.knownForDepartment === "Editing" - ? "Editor" + ? t`Editor` : person.knownForDepartment} )} @@ -83,10 +85,10 @@ export function PersonHero({ person }: PersonHeroProps) { {person.birthday && ( - {format(parseISO(person.birthday), "MMMM d, yyyy")} + {formatDate(person.birthday)} {age !== null && ( - ({person.deathday ? `died at ${age}` : `age ${age}`}) + ({person.deathday ? t`died at ${age}` : t`age ${age}`}) )} diff --git a/apps/web/src/components/settings/account-section.tsx b/apps/web/src/components/settings/account-section.tsx index a09c957..2fa9ed8 100644 --- a/apps/web/src/components/settings/account-section.tsx +++ b/apps/web/src/components/settings/account-section.tsx @@ -1,3 +1,5 @@ +import { Trans, useLingui } from "@lingui/react/macro"; +import { formatDate } from "@sofa/i18n/format"; import { IconAlertTriangle, IconCamera, @@ -43,6 +45,7 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { authClient, signOut } from "@/lib/auth/client"; +import { getErrorMessage } from "@/lib/error-messages"; import { orpc } from "@/lib/orpc/client"; export function AccountSection({ @@ -56,6 +59,7 @@ export function AccountSection({ role?: string; }; }) { + const { t } = useLingui(); const navigate = useNavigate(); const router = useRouter(); const [avatarUrl, setAvatarUrl] = useState(user.image); @@ -73,12 +77,11 @@ export function AccountSection({ const trimmed = editValue.trim(); setDisplayName(trimmed); setIsEditingName(false); - toast.success("Name updated"); + toast.success(t`Name updated`); router.invalidate(); }, onError: (err) => { - const message = err instanceof Error ? err.message : "Update failed"; - toast.error(message); + toast.error(getErrorMessage(err, t, t`Update failed`)); }, }), ); @@ -91,9 +94,10 @@ export function AccountSection({ } }, [isEditingName]); - const memberSince = new Date(user.createdAt).toLocaleDateString(undefined, { + const memberSince = formatDate(user.createdAt, { year: "numeric", month: "long", + day: undefined, }); const initial = displayName?.charAt(0).toUpperCase() ?? "?"; @@ -101,12 +105,11 @@ export function AccountSection({ orpc.account.uploadAvatar.mutationOptions({ onSuccess: (data) => { setAvatarUrl(data.imageUrl); - toast.success("Profile picture updated"); + toast.success(t`Profile picture updated`); router.invalidate(); }, onError: (err) => { - const message = err instanceof Error ? err.message : "Upload failed"; - toast.error(message); + toast.error(getErrorMessage(err, t, t`Upload failed`)); }, onSettled: () => { if (fileInputRef.current) fileInputRef.current.value = ""; @@ -124,11 +127,11 @@ export function AccountSection({ orpc.account.removeAvatar.mutationOptions({ onSuccess: () => { setAvatarUrl(undefined); - toast.success("Profile picture removed"); + toast.success(t`Profile picture removed`); router.invalidate(); }, onError: () => { - toast.error("Failed to remove profile picture"); + toast.error(t`Failed to remove profile picture`); }, }), ); @@ -169,7 +172,7 @@ export function AccountSection({

- Account + Account

@@ -192,7 +195,9 @@ export function AccountSection({ } className="relative shrink-0 cursor-pointer rounded-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background" aria-label={ - avatarUrl ? "Remove profile picture" : "Upload profile picture" + avatarUrl + ? t`Remove profile picture` + : t`Upload profile picture` } > @@ -230,7 +235,11 @@ export function AccountSection({ - {avatarUrl ? "Remove picture" : "Upload picture"} + {avatarUrl ? ( + Remove picture + ) : ( + Upload picture + )} @@ -284,7 +293,7 @@ export function AccountSection({ handleNameSave(); }} className="shrink-0 rounded-md p-0.5 text-muted-foreground transition-colors hover:text-primary" - aria-label="Save name" + aria-label={t`Save name`} > @@ -295,7 +304,7 @@ export function AccountSection({ handleNameCancel(); }} className="shrink-0 rounded-md p-0.5 text-muted-foreground transition-colors hover:text-destructive" - aria-label="Cancel editing" + aria-label={t`Cancel editing`} > @@ -323,12 +332,12 @@ export function AccountSection({ {user.email} {user.role === "admin" && ( - Admin + Admin )}

- Member since {memberSince} + Member since {memberSince}

@@ -342,7 +351,7 @@ export function AccountSection({ }} > - Sign out + Sign out
@@ -352,6 +361,7 @@ export function AccountSection({ } function ChangePasswordDialog() { + const { t } = useLingui(); const [open, setOpen] = useState(false); const [currentPassword, setCurrentPassword] = useState(""); const [newPassword, setNewPassword] = useState(""); @@ -378,15 +388,15 @@ function ChangePasswordDialog() { setError(""); if (!currentPassword) { - setError("Current password is required"); + setError(t`Current password is required`); return; } if (newPassword.length < 8) { - setError("New password must be at least 8 characters"); + setError(t`New password must be at least 8 characters`); return; } if (newPassword !== confirmPassword) { - setError("Passwords do not match"); + setError(t`Passwords do not match`); return; } @@ -398,13 +408,13 @@ function ChangePasswordDialog() { revokeOtherSessions, }); if (result.error) { - setError(result.error.message ?? "Failed to change password"); + setError(t`Failed to change password`); return; } - toast.success("Password updated"); + toast.success(t`Password updated`); handleOpenChange(false); } catch { - setError("Something went wrong"); + setError(t`Something went wrong`); } finally { setIsSubmitting(false); } @@ -414,13 +424,15 @@ function ChangePasswordDialog() { - Change password + + Change password + - Enter your current password and choose a new one. + Enter your current password and choose a new one. @@ -433,7 +445,9 @@ function ChangePasswordDialog() { )}
- +
- +
- +
}> - Cancel + Cancel diff --git a/apps/web/src/components/settings/backup-restore-section.tsx b/apps/web/src/components/settings/backup-restore-section.tsx index 57a1cd4..97d9e58 100644 --- a/apps/web/src/components/settings/backup-restore-section.tsx +++ b/apps/web/src/components/settings/backup-restore-section.tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import { IconCloudUpload } from "@tabler/icons-react"; import { useMutation } from "@tanstack/react-query"; import { useRef, useState } from "react"; @@ -15,9 +16,11 @@ import { import { Button } from "@/components/ui/button"; import { CardContent, CardDescription, CardTitle } from "@/components/ui/card"; import { Spinner } from "@/components/ui/spinner"; +import { getErrorMessage } from "@/lib/error-messages"; import { orpc } from "@/lib/orpc/client"; export function BackupRestoreSection() { + const { t } = useLingui(); const [restoreDialogOpen, setRestoreDialogOpen] = useState(false); const [selectedFile, setSelectedFile] = useState(null); const fileInputRef = useRef(null); @@ -25,12 +28,11 @@ export function BackupRestoreSection() { const restoreMutation = useMutation( orpc.admin.backups.restore.mutationOptions({ onSuccess: () => { - toast.success("Database restored. Reloading..."); + toast.success(t`Database restored. Reloading...`); setTimeout(() => window.location.reload(), 1500); }, onError: (err) => { - const message = err instanceof Error ? err.message : "Restore failed"; - toast.error(message); + toast.error(getErrorMessage(err, t, t`Restore failed`)); }, onSettled: () => { if (fileInputRef.current) fileInputRef.current.value = ""; @@ -53,10 +55,14 @@ export function BackupRestoreSection() { />
- Restore + + Restore + - Upload a .db file to replace the current database. A safety backup - is created first. + + Upload a .db file to replace the current database. A safety + backup is created first. +
@@ -79,11 +85,15 @@ export function BackupRestoreSection() { > - Restore database? + + Restore database? + - This will replace your entire database with the uploaded file. A - safety backup of your current data will be created first. Active - sessions may need to refresh after restore. + + This will replace your entire database with the uploaded file. + A safety backup of your current data will be created first. + Active sessions may need to refresh after restore. + @@ -94,7 +104,7 @@ export function BackupRestoreSection() { if (fileInputRef.current) fileInputRef.current.value = ""; }} > - Cancel + Cancel { @@ -103,7 +113,7 @@ export function BackupRestoreSection() { setSelectedFile(null); }} > - Restore + Restore @@ -118,7 +128,11 @@ export function BackupRestoreSection() { ) : ( )} - {restoreMutation.isPending ? "Restoring…" : "Upload"} + {restoreMutation.isPending ? ( + Restoring… + ) : ( + Upload + )} diff --git a/apps/web/src/components/settings/backup-schedule-section.tsx b/apps/web/src/components/settings/backup-schedule-section.tsx index ae9580f..61bfdff 100644 --- a/apps/web/src/components/settings/backup-schedule-section.tsx +++ b/apps/web/src/components/settings/backup-schedule-section.tsx @@ -1,7 +1,8 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import type { BackupFrequency } from "@sofa/api/schemas"; +import { formatDate, formatRelativeTime } from "@sofa/i18n/format"; import { IconCalendarWeek } from "@tabler/icons-react"; import { useMutation, useQuery } from "@tanstack/react-query"; -import { format, formatDistanceToNow } from "date-fns"; import { AnimatePresence, motion } from "motion/react"; import { useCallback, useState } from "react"; import { toast } from "sonner"; @@ -28,16 +29,6 @@ const FREQUENCY_OPTIONS: { value: BackupFrequency; label: string }[] = [ const HOURS = Array.from({ length: 24 }, (_, i) => i); -const DAYS_OF_WEEK = [ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", -] as const; - interface BackupScheduleState { enabled: boolean; maxRetention: number; @@ -98,16 +89,19 @@ function getNextBackupDate( return next; } -function formatNextBackup( - frequency: BackupFrequency, - time: string, - dayOfWeek: number, -): string { - const next = getNextBackupDate(frequency, time, dayOfWeek); - return `Next backup ${formatDistanceToNow(next, { addSuffix: true })}`; -} - export function BackupScheduleSection() { + const { t } = useLingui(); + + const DAYS_OF_WEEK = [ + t`Sunday`, + t`Monday`, + t`Tuesday`, + t`Wednesday`, + t`Thursday`, + t`Friday`, + t`Saturday`, + ]; + const { data: scheduleData, isPending, @@ -127,6 +121,16 @@ export function BackupScheduleSection() { const { enabled, maxRetention, frequency, time, dow } = current; + function formatNextBackup( + frequency: BackupFrequency, + time: string, + dayOfWeek: number, + ): string { + const next = getNextBackupDate(frequency, time, dayOfWeek); + const distance = formatRelativeTime(next); + return t`Next backup ${distance}`; + } + const updateScheduleMutation = useMutation( orpc.admin.backups.updateSchedule.mutationOptions({ onMutate: (input) => { @@ -145,11 +149,11 @@ export function BackupScheduleSection() { onError: (_, input, ctx) => { if (ctx?.previous) setSchedule(ctx.previous); if (input.enabled !== undefined) { - toast.error("Failed to update scheduled backup setting"); + toast.error(t`Failed to update scheduled backup setting`); } else if (input.maxRetention !== undefined) { - toast.error("Failed to update retention setting"); + toast.error(t`Failed to update retention setting`); } else { - toast.error("Failed to update schedule"); + toast.error(t`Failed to update schedule`); } }, }), @@ -170,13 +174,13 @@ export function BackupScheduleSection() { onSuccess: () => toast.success( checked - ? "Scheduled backups enabled" - : "Scheduled backups disabled", + ? t`Scheduled backups enabled` + : t`Scheduled backups disabled`, ), }, ); }, - [updateScheduleMutation], + [updateScheduleMutation, t], ); const changeMaxRetention = useCallback( @@ -194,10 +198,10 @@ export function BackupScheduleSection() { time: newTime, dayOfWeek: newDow, }, - { onSuccess: () => toast.success("Schedule updated") }, + { onSuccess: () => toast.success(t`Schedule updated`) }, ); }, - [updateScheduleMutation, current.dow], + [updateScheduleMutation, current.dow, t], ); if (isPending) { @@ -208,7 +212,7 @@ export function BackupScheduleSection() { return (

- Failed to load backup schedule settings. + Failed to load backup schedule settings.

); @@ -226,46 +230,55 @@ export function BackupScheduleSection() { />
- Backup schedule + + Backup schedule + {enabled ? ( - {formatNextBackup(frequency, time, dow)}. Keeping{" "} - + v && changeMaxRetention(Number(v)) + } + modal={false} > - {[3, 5, 7, 14, 30, 0].map((n) => ( - - {n === 0 ? "unlimited" : `last ${n}`} - - ))} - - {" "} - backups. + + + {(value: string | null) => + value === "0" + ? t`unlimited` + : value + ? t`last ${value}` + : null + } + + + + {[3, 5, 7, 14, 30, 0].map((n) => ( + + {n === 0 ? t`unlimited` : t`last ${n}`} + + ))} + + {" "} + backups. + ) : ( - "Automatically back up your database on a schedule" + + Automatically back up your database on a schedule + )}
@@ -274,7 +287,7 @@ export function BackupScheduleSection() { checked={enabled} onCheckedChange={toggleScheduled} disabled={togglingSchedule} - aria-label="Toggle scheduled backups" + aria-label={t`Toggle scheduled backups`} /> @@ -293,7 +306,7 @@ export function BackupScheduleSection() { {/* Frequency selector */}
- Frequency + Frequency {FREQUENCY_OPTIONS.map((opt) => ( @@ -326,7 +339,7 @@ export function BackupScheduleSection() { className="overflow-hidden" > - Day:{" "} + Day:{" "} {(value: string | null) => value - ? format( + ? formatDate( new Date( 2000, 0, @@ -390,7 +407,13 @@ export function BackupScheduleSection() { Number(value.split(":")[0]), 0, ), - "h:mm a", + { + hour: "numeric", + minute: "2-digit", + year: undefined, + month: undefined, + day: undefined, + }, ) : null } @@ -405,7 +428,13 @@ export function BackupScheduleSection() { const val = `${String(h).padStart(2, "0")}:00`; return ( - {format(new Date(2000, 0, 1, h, 0), "h:mm a")} + {formatDate(new Date(2000, 0, 1, h, 0), { + hour: "numeric", + minute: "2-digit", + year: undefined, + month: undefined, + day: undefined, + })} ); })} diff --git a/apps/web/src/components/settings/backup-section.tsx b/apps/web/src/components/settings/backup-section.tsx index d6258c1..eb35db9 100644 --- a/apps/web/src/components/settings/backup-section.tsx +++ b/apps/web/src/components/settings/backup-section.tsx @@ -1,4 +1,11 @@ +import { plural } from "@lingui/core/macro"; +import { Trans, useLingui } from "@lingui/react/macro"; import type { BackupInfo } from "@sofa/api/schemas"; +import { + formatBytes as formatBytesI18n, + formatDate, + formatRelativeTime, +} from "@sofa/i18n/format"; import { IconClock, IconCloudDownload, @@ -9,7 +16,6 @@ import { IconTrash, } from "@tabler/icons-react"; import { useMutation, useQuery } from "@tanstack/react-query"; -import { format, formatDistanceToNow } from "date-fns"; import { AnimatePresence, motion } from "motion/react"; import { useState } from "react"; import { toast } from "sonner"; @@ -34,17 +40,17 @@ import { } from "@/components/ui/tooltip"; import { orpc } from "@/lib/orpc/client"; -function formatBytes(bytes: number): string { - if (bytes < 1024) return `${bytes} B`; - if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; - return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; -} - function formatBackupDate(dateStr: string): string { - return format(new Date(dateStr), "MMM d, h:mm a"); + return formatDate(dateStr, { + month: "short", + day: "numeric", + hour: "numeric", + minute: "2-digit", + }); } export function BackupSection() { + const { t } = useLingui(); const { data } = useQuery(orpc.admin.backups.list.queryOptions()); const [backups, setBackups] = useState(null); @@ -60,9 +66,9 @@ export function BackupSection() { (b: BackupInfo) => b.filename !== backup.filename, ), ]); - toast.success("Backup created", { + toast.success(t`Backup created`, { action: { - label: "Download", + label: t`Download`, onClick: () => { const a = document.createElement("a"); a.href = `/api/backup/${backup.filename}`; @@ -72,7 +78,7 @@ export function BackupSection() { }, }); }, - onError: () => toast.error("Failed to create backup"), + onError: () => toast.error(t`Failed to create backup`), }), ); @@ -85,10 +91,10 @@ export function BackupSection() { ); return { previous }; }, - onSuccess: () => toast.success("Backup deleted"), + onSuccess: () => toast.success(t`Backup deleted`), onError: (_, __, ctx) => { if (ctx?.previous) setBackups(ctx.previous); - toast.error("Failed to delete backup"); + toast.error(t`Failed to delete backup`); }, }), ); @@ -98,6 +104,11 @@ export function BackupSection() { ? (deleteMutation.variables?.filename ?? null) : null; + const backupCountLabel = + displayBackups.length > 0 + ? t`${displayBackups.length} ${plural(displayBackups.length, { one: "backup", other: "backups" })} stored` + : t`No backups yet`; + return ( <> {/* Header */} @@ -111,12 +122,10 @@ export function BackupSection() { />
- Database backups - - {displayBackups.length > 0 - ? `${displayBackups.length} backup${displayBackups.length !== 1 ? "s" : ""} stored` - : "No backups yet"} - + + Database backups + + {backupCountLabel}
@@ -167,10 +176,10 @@ export function BackupSection() { {backup.source === "scheduled" - ? "Scheduled backup" + ? t`Scheduled backup` : backup.source === "pre-restore" - ? "Pre-restore backup" - : "Manual backup"} + ? t`Pre-restore backup` + : t`Manual backup`}
@@ -179,15 +188,13 @@ export function BackupSection() { {formatBackupDate(backup.createdAt)} - {formatBytes(backup.sizeBytes)} + {formatBytesI18n(backup.sizeBytes)} - {formatDistanceToNow(new Date(backup.createdAt), { - addSuffix: true, - })} + {formatRelativeTime(backup.createdAt)}
@@ -204,7 +211,7 @@ export function BackupSection() { @@ -212,7 +219,9 @@ export function BackupSection() { /> } /> - Download + + Download + @@ -223,7 +232,7 @@ export function BackupSection() { )} {!config.supportsOAuth && ( @@ -507,7 +506,7 @@ function ImportSourceCard({ config }: { config: SourceConfig }) { disabled={isParsing} > {isParsing ? : } - {isParsing ? "Parsing…" : "Upload"} + {isParsing ? Parsing... : Upload} )} @@ -577,12 +576,15 @@ function ChooseStep({ isParsing: boolean; onCancel: () => void; }) { + const { t } = useLingui(); return ( <> - Import from {config.label} + + Import from {config.label} + - Choose how to import your {config.label} data. + Choose how to import your {config.label} data. @@ -602,10 +604,14 @@ function ChooseStep({
-

Connect with {config.label}

+

+ Connect with {config.label} +

- Authorize Sofa to read your {config.label} library. No password - shared. + + Authorize Sofa to read your {config.label} library. No password + shared. +

@@ -624,10 +630,11 @@ function ChooseStep({
-

Upload export file

+

+ Upload export file +

- Upload a {config.accept} export from your {config.label} account - settings. + {t`Upload a ${config.accept} export from your ${config.label} account settings.`}

@@ -635,7 +642,7 @@ function ChooseStep({ } onClick={onCancel}> - Cancel + Cancel @@ -654,16 +661,22 @@ function DeviceCodeStep({ return ( <> - Connect to {source} + + Connect to {source} + - Enter the code below on {source}'s website to authorize Sofa. + + Enter the code below on {source}'s website to authorize Sofa. + {deviceCode ? (
-

Your code:

+

+ Your code: +

{deviceCode.user_code}

@@ -677,13 +690,13 @@ function DeviceCodeStep({ className="inline-flex items-center gap-1.5 font-medium text-primary text-sm underline-offset-4 hover:underline" > - Open {source} to enter code + Open {source} to enter code
- Waiting for authorization... + Waiting for authorization...
) : ( @@ -694,7 +707,7 @@ function DeviceCodeStep({ @@ -705,15 +718,19 @@ function FetchingStep({ source }: { source: string }) { return ( <> - Connected to {source} + + Connected to {source} + - Fetching your library data from {source}... + Fetching your library data from {source}...

- Retrieving your watch history, watchlist, and ratings... + + Retrieving your watch history, watchlist, and ratings... +

@@ -739,6 +756,7 @@ function PreviewStep({ onImport: () => void; onCancel: () => void; }) { + const { t } = useLingui(); const { stats, warnings } = preview; const totalItems = (options.importWatches ? stats.movies + stats.episodes : 0) + @@ -748,46 +766,52 @@ function PreviewStep({ return ( <> - Import from {source} + + Import from {source} + - Review what was found and choose what to import. + Review what was found and choose what to import.
- - - - + + + +
{preview.diagnostics && preview.diagnostics.unresolved > 0 && (

- {preview.diagnostics.unresolved} items have no external IDs and - will be resolved by title search, which may be less accurate. + + {preview.diagnostics.unresolved} items have no external IDs and + will be resolved by title search, which may be less accurate. +

)}
-

Import options

+

+ Import options +

setOptions({ ...options, importWatches: v })} /> setOptions({ ...options, importWatchlist: v })} /> setOptions({ ...options, importRatings: v })} /> @@ -795,7 +819,9 @@ function PreviewStep({ {warnings.length > 0 && (
-

Warnings

+

+ Warnings +

    {warnings.map((w, i) => ( // biome-ignore lint/suspicious/noArrayIndexKey: static display list @@ -808,10 +834,10 @@ function PreviewStep({ } onClick={onCancel}> - Cancel + Cancel @@ -833,10 +859,14 @@ function ImportingStep({ return ( <> - Importing from {source} + + Importing from {source} + - This may take a few minutes for large libraries. Please don't close - this tab. + + This may take a few minutes for large libraries. Please don't close + this tab. +
    @@ -855,7 +885,7 @@ function ImportingStep({

    - Starting import... + Starting import...

    )} @@ -874,24 +904,29 @@ function DoneStep({ result: ImportResult; onClose: () => void; }) { + const { t } = useLingui(); return ( <> - Import complete - Finished importing from {source}. + + Import complete + + + Finished importing from {source}. +
    - - - + + +
    {result.errors.length > 0 && (

    - Errors ({result.errors.length}) + Errors ({result.errors.length})

      {result.errors.slice(0, 50).map((e, i) => ( @@ -899,7 +934,9 @@ function DoneStep({
    • {e}
    • ))} {result.errors.length > 50 && ( -
    • ...and {result.errors.length - 50} more
    • +
    • + ...and {result.errors.length - 50} more +
    • )}
    @@ -908,7 +945,7 @@ function DoneStep({ {result.warnings.length > 0 && (

    - Warnings ({result.warnings.length}) + Warnings ({result.warnings.length})

      {result.warnings.slice(0, 20).map((w, i) => ( @@ -921,7 +958,9 @@ function DoneStep({
    - + ); diff --git a/apps/web/src/components/settings/integration-card.tsx b/apps/web/src/components/settings/integration-card.tsx index c0c1b61..0b965a5 100644 --- a/apps/web/src/components/settings/integration-card.tsx +++ b/apps/web/src/components/settings/integration-card.tsx @@ -1,3 +1,7 @@ +import { msg } from "@lingui/core/macro"; +import { Trans, useLingui } from "@lingui/react/macro"; +import { i18n } from "@sofa/i18n"; +import { formatRelativeTime } from "@sofa/i18n/format"; import { IconBook2, IconCheck, @@ -8,7 +12,6 @@ import { IconTrash, } from "@tabler/icons-react"; import { useMutation } from "@tanstack/react-query"; -import { formatDistanceToNow } from "date-fns"; import { AnimatePresence, motion } from "motion/react"; import type { ComponentType, ReactNode } from "react"; import { useState } from "react"; @@ -87,6 +90,7 @@ export function IntegrationCard({ connection: IntegrationConnection | null; setConnections: React.Dispatch>; }) { + const { t } = useLingui(); const { provider, label } = config; const providerInput = provider as | "plex" @@ -99,9 +103,9 @@ export function IntegrationCard({ orpc.integrations.create.mutationOptions({ onSuccess: (result) => { setConnections((prev) => [...prev, { ...result, recentEvents: [] }]); - toast.success(`${label} connected`); + toast.success(t`${label} connected`); }, - onError: () => toast.error(`Failed to connect ${label}`), + onError: () => toast.error(t`Failed to connect ${label}`), }), ); @@ -115,10 +119,10 @@ export function IntegrationCard({ }); return { previous }; }, - onSuccess: () => toast.success(`${label} disconnected`), + onSuccess: () => toast.success(t`${label} disconnected`), onError: (_, __, ctx) => { if (ctx?.previous) setConnections(ctx.previous); - toast.error(`Failed to disconnect ${label}`); + toast.error(t`Failed to disconnect ${label}`); }, }), ); @@ -131,9 +135,9 @@ export function IntegrationCard({ c.provider === provider ? { ...c, token: result.token } : c, ), ); - toast.success(`${label} URL regenerated`); + toast.success(t`${label} URL regenerated`); }, - onError: () => toast.error(`Failed to regenerate ${label} URL`), + onError: () => toast.error(t`Failed to regenerate ${label} URL`), }), ); const [copied, setCopied] = useState(false); @@ -169,7 +173,7 @@ export function IntegrationCard({ {connection ? config.connectedStatus(connection.lastEventAt) - : "Not configured"} + : t`Not configured`}
    @@ -193,7 +197,11 @@ export function IntegrationCard({ size="lg" className="w-full" > - {connecting ? "Connecting\u2026" : `Connect ${config.label}`} + {connecting ? ( + Connecting... + ) : ( + Connect {config.label} + )} ) : ( @@ -234,7 +242,9 @@ export function IntegrationCard({ )} - Copy URL + + Copy URL + @@ -251,7 +261,7 @@ export function IntegrationCard({ } > - Regenerate URL + Regenerate URL
@@ -275,7 +285,7 @@ export function IntegrationCard({ aria-hidden={true} className={`size-3 transition-transform ${setupOpen ? "rotate-0" : "-rotate-90"}`} /> - Setup instructions + Setup instructions
@@ -288,14 +298,14 @@ export function IntegrationCard({ aria-hidden={true} className="mr-1 inline-block size-3 translate-y-[-1px]" /> - Need more help?{" "} + Need more help?{" "} - Open docs{" "} + Open docs{" "} - Requires an active{" "} - - Plex Pass - - {" "} - subscription. + + Requires an active{" "} + + Plex Pass + + {" "} + subscription. + ), setupSteps: ( <>
  • - Open Plex, go to{" "} - - Settings > Webhooks - - + + Open Plex, go to{" "} + + Settings > Webhooks + + +
  • - Click Add Webhook{" "} - and paste the URL above + + Click{" "} + Add Webhook and + paste the URL above +
  • - Sofa will automatically log movies and episodes when you finish - watching them + + Sofa will automatically log movies and episodes when you finish + watching them +
  • ), @@ -92,42 +102,52 @@ export const INTEGRATION_CONFIGS: IntegrationConfig[] = [ setupSteps: ( <>
  • - Install the{" "} - - Webhook plugin - - {" "} - from Jellyfin's plugin catalog + + Install the{" "} + + Webhook plugin + + {" "} + from Jellyfin's plugin catalog +
  • - Go to{" "} - - Dashboard > Plugins > Webhook - + + Go to{" "} + + Dashboard > Plugins > Webhook + +
  • - Add a{" "} - - Generic Destination - {" "} - and paste the URL above + + Add a{" "} + + Generic Destination + {" "} + and paste the URL above +
  • - Enable the{" "} - Playback Stop{" "} - notification type + + Enable the{" "} + Playback Stop{" "} + notification type +
  • - Sofa will automatically log movies and episodes when you finish - watching them + + Sofa will automatically log movies and episodes when you finish + watching them +
  • ), @@ -142,41 +162,53 @@ export const INTEGRATION_CONFIGS: IntegrationConfig[] = [ connectedStatus: webhookStatus, alert: ( - Requires{" "} - Emby Server 4.7.9+{" "} - and an active{" "} - - Emby Premiere - - {" "} - license. + + Requires{" "} + + Emby Server 4.7.9+ + {" "} + and an active{" "} + + Emby Premiere + + {" "} + license. + ), setupSteps: ( <>
  • - Open Emby, go to{" "} - - Settings > Webhooks - -
  • -
  • Add a new webhook and paste the URL above
  • -
  • - Enable the{" "} - Playback event - category + + Open Emby, go to{" "} + + Settings > Webhooks + +
  • - Sofa will automatically log movies and episodes when you finish - watching them + Add a new webhook and paste the URL above +
  • +
  • + + Enable the{" "} + Playback event + category + +
  • +
  • + + Sofa will automatically log movies and episodes when you finish + watching them +
  • ), @@ -194,21 +226,31 @@ export const INTEGRATION_CONFIGS: IntegrationConfig[] = [ setupSteps: ( <>
  • - Open Sonarr, go to{" "} - - Settings > Import Lists - + + Open Sonarr, go to{" "} + + Settings > Import Lists + +
  • - Click + and - select{" "} - Custom Lists + + Click + and + select{" "} + Custom Lists +
  • -
  • Paste the Sonarr URL above into the List URL field
  • -
  • Set your preferred quality profile and root folder
  • - Titles on your Sofa watchlist will be automatically added for download - when Sonarr polls this list (every 6 hours by default) + Paste the Sonarr URL above into the List URL field +
  • +
  • + Set your preferred quality profile and root folder +
  • +
  • + + Titles on your Sofa watchlist will be automatically added for + download when Sonarr polls this list (every 6 hours by default) +
  • ), @@ -224,21 +266,31 @@ export const INTEGRATION_CONFIGS: IntegrationConfig[] = [ setupSteps: ( <>
  • - Open Radarr, go to{" "} - - Settings > Import Lists - + + Open Radarr, go to{" "} + + Settings > Import Lists + +
  • - Click + and - select{" "} - Custom Lists + + Click + and + select{" "} + Custom Lists +
  • -
  • Paste the Radarr URL above into the List URL field
  • -
  • Set your preferred quality profile and root folder
  • - Titles on your Sofa watchlist will be automatically added for download - when Radarr polls this list (every 12 hours by default) + Paste the Radarr URL above into the List URL field +
  • +
  • + Set your preferred quality profile and root folder +
  • +
  • + + Titles on your Sofa watchlist will be automatically added for + download when Radarr polls this list (every 12 hours by default) +
  • ), diff --git a/apps/web/src/components/settings/integrations-section.tsx b/apps/web/src/components/settings/integrations-section.tsx index e341a0c..0e9c580 100644 --- a/apps/web/src/components/settings/integrations-section.tsx +++ b/apps/web/src/components/settings/integrations-section.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { IconWebhook } from "@tabler/icons-react"; import { useQuery } from "@tanstack/react-query"; import { useState } from "react"; @@ -37,7 +38,7 @@ export function IntegrationsSection() { className="size-4 text-muted-foreground" />

    - Integrations + Integrations

    {isPending ? ( diff --git a/apps/web/src/components/settings/language-section.tsx b/apps/web/src/components/settings/language-section.tsx new file mode 100644 index 0000000..8814433 --- /dev/null +++ b/apps/web/src/components/settings/language-section.tsx @@ -0,0 +1,91 @@ +import { Trans, useLingui } from "@lingui/react/macro"; +import { activateLocale, type SupportedLocale } from "@sofa/i18n"; +import { LOCALE_INFO } from "@sofa/i18n/locales"; +import { IconCheck, IconChevronDown, IconLanguage } from "@tabler/icons-react"; +import { useState } from "react"; +import { + Card, + CardContent, + CardDescription, + CardTitle, +} from "@/components/ui/card"; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible"; +import { setPersistedLocale } from "@/lib/i18n"; + +export function LanguageSection() { + const { t, i18n } = useLingui(); + const currentLocale = i18n.locale; + const [open, setOpen] = useState(false); + + const currentInfo = LOCALE_INFO.find((l) => l.code === currentLocale); + + async function handleLocaleChange(locale: SupportedLocale) { + await activateLocale(locale); + setPersistedLocale(locale); + } + + return ( + + + + +
    +
    + +
    +
    + + Language + + + {currentInfo?.nativeName ?? "English"} + +
    +
    + +
    +
    + + + +
    + {LOCALE_INFO.map((info) => ( + + ))} +
    +
    +
    +
    +
    + ); +} diff --git a/apps/web/src/components/settings/registration-section.tsx b/apps/web/src/components/settings/registration-section.tsx index 7314e56..df5f004 100644 --- a/apps/web/src/components/settings/registration-section.tsx +++ b/apps/web/src/components/settings/registration-section.tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import { IconDoorEnter } from "@tabler/icons-react"; import { useMutation, useQuery } from "@tanstack/react-query"; import { useOptimistic, useState, useTransition } from "react"; @@ -8,6 +9,7 @@ import { Switch } from "@/components/ui/switch"; import { orpc } from "@/lib/orpc/client"; export function RegistrationSection() { + const { t } = useLingui(); const { data, isPending: isLoading } = useQuery( orpc.admin.registration.queryOptions(), ); @@ -35,9 +37,11 @@ export function RegistrationSection() { try { await toggleMutation.mutateAsync({ open: checked }); setRegistrationOpen(checked); - toast.success(checked ? "Registration opened" : "Registration closed"); + toast.success( + checked ? t`Registration opened` : t`Registration closed`, + ); } catch { - toast.error("Failed to update registration setting"); + toast.error(t`Failed to update registration setting`); } }); } @@ -50,9 +54,11 @@ export function RegistrationSection() {
    - Open registration + + Open registration + - Allow new users to create accounts + Allow new users to create accounts
    @@ -60,7 +66,7 @@ export function RegistrationSection() { checked={optimisticOpen} onCheckedChange={handleToggle} disabled={isPending} - aria-label="Toggle open registration" + aria-label={t`Toggle open registration`} /> diff --git a/apps/web/src/components/settings/settings-shell.tsx b/apps/web/src/components/settings/settings-shell.tsx index 6b3de9b..e1b44b5 100644 --- a/apps/web/src/components/settings/settings-shell.tsx +++ b/apps/web/src/components/settings/settings-shell.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { IconSettings } from "@tabler/icons-react"; import { motion } from "motion/react"; import { Children, type ReactNode } from "react"; @@ -32,11 +33,11 @@ export function SettingsShell({

    - Settings + Settings

    - Manage your account and preferences + Manage your account and preferences

    diff --git a/apps/web/src/components/settings/system-health-section.tsx b/apps/web/src/components/settings/system-health-section.tsx index 0d02e2d..204f863 100644 --- a/apps/web/src/components/settings/system-health-section.tsx +++ b/apps/web/src/components/settings/system-health-section.tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import type { CronJobName, SystemHealthData } from "@sofa/api/schemas"; import { IconActivity, @@ -36,16 +37,6 @@ import { import { useTimeAgo } from "@/hooks/use-time-ago"; import { orpc } from "@/lib/orpc/client"; -const JOB_LABELS: Record = { - nightlyRefreshLibrary: "Library refresh", - refreshAvailability: "Availability", - refreshRecommendations: "Recommendations", - refreshTvChildren: "TV episodes", - cacheImages: "Image cache", - scheduledBackup: "Backup", - updateCheck: "Update check", -}; - /** Convert a cron pattern to a short human-readable string */ function cronToHuman(pattern: string): string { const parts = pattern.split(" "); @@ -170,6 +161,7 @@ function RefreshButton({ isRefreshing: boolean; onRefresh: () => void; }) { + const { t } = useLingui(); return ( {isRefreshing ? : } - Refresh + + Refresh + ); } @@ -214,9 +208,13 @@ function SystemStatusCard({ />
    - Health status + + Health status + - Checked + + Checked +
    @@ -228,7 +226,7 @@ function SystemStatusCard({
    - Database + Database {formatBytes(database.dbSizeBytes)} @@ -248,13 +246,15 @@ function SystemStatusCard({ <> - Not configured + Not configured ) : tmdb.connected && tmdb.tokenValid ? ( <> - Connected + + Connected + {tmdb.responseTimeMs}ms @@ -262,12 +262,16 @@ function SystemStatusCard({ ) : tmdb.connected && !tmdb.tokenValid ? ( <> - Invalid token + + Invalid token + ) : ( <> - Unreachable + + Unreachable + {tmdb.error && ( {tmdb.error} @@ -282,7 +286,7 @@ function SystemStatusCard({
    - Environment + Environment {environment.dataDirWritable ? ( ) : ( @@ -321,15 +325,27 @@ function BackgroundJobsCard({ isRefreshing: boolean; onRefresh: () => void; }) { + const { t } = useLingui(); + + const JOB_LABELS: Record = { + nightlyRefreshLibrary: t`Library refresh`, + refreshAvailability: t`Availability`, + refreshRecommendations: t`Recommendations`, + refreshTvChildren: t`TV episodes`, + cacheImages: t`Image cache`, + scheduledBackup: t`Backup`, + updateCheck: t`Update check`, + }; + const triggerJobMutation = useMutation( orpc.admin.triggerJob.mutationOptions({ onSuccess: (_, { name }) => { - toast.success(`${JOB_LABELS[name] ?? name} triggered`); + toast.success(t`${JOB_LABELS[name] ?? name} triggered`); setTimeout(onRefresh, 1500); }, onError: (err) => { toast.error( - err instanceof Error ? err.message : "Failed to trigger job", + err instanceof Error ? err.message : t`Failed to trigger job`, ); }, }), @@ -362,9 +378,13 @@ function BackgroundJobsCard({ />
    - Background jobs + + Background jobs + - {healthyCount} of {activeJobs.length} jobs healthy + + {healthyCount} of {activeJobs.length} jobs healthy +
    @@ -376,19 +396,21 @@ function BackgroundJobsCard({ - Job + Job - Schedule + Schedule - Last run + Last run - Next run + Next run - Actions + + Actions + @@ -406,17 +428,17 @@ function BackgroundJobsCard({
    {job.disabled ? ( - + ) : isRunning ? ( ) : job.lastStatus === null ? ( - + ) : job.lastStatus === "success" ? ( - + ) : ( )} @@ -476,7 +498,7 @@ function BackgroundJobsCard({ ) : ( - Never + Never )} @@ -512,7 +534,7 @@ function BackgroundJobsCard({
    - Storage + + Storage + - Image cache and backup disk usage + Image cache and backup disk usage
    @@ -580,7 +607,7 @@ function StorageCard({
    - Image cache + Image cache {imageCache.enabled ? ( @@ -591,7 +618,7 @@ function StorageCard({ {imageCache.enabled ? ( <>

    - {imageCache.imageCount.toLocaleString()} cached images + {t`${imageCache.imageCount.toLocaleString()} cached images`}

    {Object.entries(imageCache.categories) @@ -602,7 +629,7 @@ function StorageCard({ ) : (

    - Disabled + Disabled

    )} @@ -611,7 +638,7 @@ function StorageCard({
    - Backups + Backups {backups.backupCount > 0 && ( @@ -624,13 +651,15 @@ function StorageCard({ className="mt-1 text-muted-foreground text-xs" suppressHydrationWarning > - {backups.backupCount} backups · last{" "} - + + {backups.backupCount} backups · last{" "} + +

    ) : (

    - No backups yet + No backups yet

    )} diff --git a/apps/web/src/components/settings/update-check-section.tsx b/apps/web/src/components/settings/update-check-section.tsx index 3e2fc34..547eb37 100644 --- a/apps/web/src/components/settings/update-check-section.tsx +++ b/apps/web/src/components/settings/update-check-section.tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import { IconWorldUpload } from "@tabler/icons-react"; import { useMutation, useQuery } from "@tanstack/react-query"; import { useOptimistic, useState, useTransition } from "react"; @@ -8,6 +9,7 @@ import { Switch } from "@/components/ui/switch"; import { orpc } from "@/lib/orpc/client"; export function UpdateCheckSection() { + const { t } = useLingui(); const { data, isPending: isLoading } = useQuery( orpc.admin.updateCheck.queryOptions(), ); @@ -35,10 +37,10 @@ export function UpdateCheckSection() { await toggleMutation.mutateAsync({ enabled: checked }); setLocalEnabled(checked); toast.success( - checked ? "Update checks enabled" : "Update checks disabled", + checked ? t`Update checks enabled` : t`Update checks disabled`, ); } catch { - toast.error("Failed to update setting"); + toast.error(t`Failed to update setting`); } }); } @@ -54,9 +56,11 @@ export function UpdateCheckSection() { />
    - Automatic update checks + + Automatic update checks + - Periodically check GitHub for new Sofa releases + Periodically check GitHub for new Sofa releases
    @@ -64,7 +68,7 @@ export function UpdateCheckSection() { checked={optimisticEnabled} onCheckedChange={handleToggle} disabled={isPending} - aria-label="Toggle automatic update checks" + aria-label={t`Toggle automatic update checks`} />
    diff --git a/apps/web/src/components/setup/copy-button.tsx b/apps/web/src/components/setup/copy-button.tsx index e1173ff..cf632df 100644 --- a/apps/web/src/components/setup/copy-button.tsx +++ b/apps/web/src/components/setup/copy-button.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { IconCheck, IconCopy } from "@tabler/icons-react"; import { useState } from "react"; import { Button } from "@/components/ui/button"; @@ -21,12 +22,12 @@ export function CopyButton({ code }: { code: string }) { {copied ? ( <> - Copied + Copied ) : ( <> - Copy + Copy )} diff --git a/apps/web/src/components/setup/refresh-button.tsx b/apps/web/src/components/setup/refresh-button.tsx index ad6abac..fa52db6 100644 --- a/apps/web/src/components/setup/refresh-button.tsx +++ b/apps/web/src/components/setup/refresh-button.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { IconRefresh } from "@tabler/icons-react"; import { useState } from "react"; import { Button } from "@/components/ui/button"; @@ -23,7 +24,11 @@ export function RefreshButton() { ) : ( )} - {isRefreshing ? "Checking…" : "Check configuration"} + {isRefreshing ? ( + Checking… + ) : ( + Check configuration + )} ); } diff --git a/apps/web/src/components/title-card.tsx b/apps/web/src/components/title-card.tsx index 30bf2c9..b2f0e05 100644 --- a/apps/web/src/components/title-card.tsx +++ b/apps/web/src/components/title-card.tsx @@ -1,3 +1,4 @@ +import { useLingui } from "@lingui/react/macro"; import { IconBookmarkFilled, IconCircleCheckFilled, @@ -58,23 +59,26 @@ export interface TitleCardProps extends CardInnerProps { id: string; } -const statusConfig = { - watchlist: { - icon: IconBookmarkFilled, - label: "On Watchlist", - badgeClass: "bg-status-watching/90 text-white", - }, - in_progress: { - icon: IconPlayerPlayFilled, - label: "Watching", - badgeClass: "bg-status-watching/90 text-white", - }, - completed: { - icon: IconCircleCheckFilled, - label: "Completed", - badgeClass: "bg-status-completed/90 text-white", - }, -} as const; +function useStatusConfig() { + const { t } = useLingui(); + return { + watchlist: { + icon: IconBookmarkFilled, + label: t`On Watchlist`, + badgeClass: "bg-status-watching/90 text-white", + }, + in_progress: { + icon: IconPlayerPlayFilled, + label: t`Watching`, + badgeClass: "bg-status-watching/90 text-white", + }, + completed: { + icon: IconCircleCheckFilled, + label: t`Completed`, + badgeClass: "bg-status-completed/90 text-white", + }, + } as const; +} function QuickAddButton({ id, @@ -83,6 +87,8 @@ function QuickAddButton({ id: string; userStatus?: TitleStatus | null; }) { + const { t } = useLingui(); + const statusConfig = useStatusConfig(); const [addedStatus, setAddedStatus] = useState( userStatus ?? null, ); @@ -137,12 +143,13 @@ function QuickAddButton({ )} - Add to Watchlist + {t`Add to Watchlist`} ); } function ProgressBar({ watched, total }: { watched: number; total: number }) { + const { t } = useLingui(); const pct = total > 0 ? (watched / total) * 100 : 0; return ( @@ -156,7 +163,7 @@ function ProgressBar({ watched, total }: { watched: number; total: number }) { /> - {watched}/{total} episodes + {t`${watched}/${total} episodes`} ); @@ -173,6 +180,7 @@ function CardInner({ episodeProgress, tiltStyles, }: CardInnerProps) { + const statusConfig = useStatusConfig(); const year = releaseDate?.slice(0, 4); const TypeIcon = type === "movie" ? IconMovie : IconDeviceTv; const placeholderUrl = thumbHashToUrl(posterThumbHash); diff --git a/apps/web/src/components/titles/cast-carousel.tsx b/apps/web/src/components/titles/cast-carousel.tsx index d206c29..7571bc3 100644 --- a/apps/web/src/components/titles/cast-carousel.tsx +++ b/apps/web/src/components/titles/cast-carousel.tsx @@ -1,3 +1,4 @@ +import { Trans, useLingui } from "@lingui/react/macro"; import type { CastMember } from "@sofa/api/schemas"; import { IconUser, IconUsers } from "@tabler/icons-react"; import { Link } from "@tanstack/react-router"; @@ -11,11 +12,14 @@ interface CastCarouselProps { } export function CastCarousel({ actors, titleType }: CastCarouselProps) { + const { t } = useLingui(); return (
    -

    Cast

    +

    + Cast +

    {actors.length > 0 && ( @@ -73,8 +77,7 @@ export function CastCarousel({ actors, titleType }: CastCarouselProps) { )} {titleType === "tv" && member.episodeCount && (

    - {member.episodeCount} ep - {member.episodeCount !== 1 ? "s" : ""} + {t`${member.episodeCount} ep${member.episodeCount !== 1 ? "s" : ""}`}

    )} diff --git a/apps/web/src/components/titles/status-button.tsx b/apps/web/src/components/titles/status-button.tsx index 43674b5..21c4d07 100644 --- a/apps/web/src/components/titles/status-button.tsx +++ b/apps/web/src/components/titles/status-button.tsx @@ -1,3 +1,4 @@ +import { useLingui } from "@lingui/react/macro"; import { IconCheck, IconPlayerPlayFilled, @@ -6,32 +7,34 @@ import { } from "@tabler/icons-react"; import { AnimatePresence, motion } from "motion/react"; -const watchingStyle = { - label: "Watching", - icon: IconPlayerPlayFilled, - class: "text-status-watching", - bgClass: "bg-status-watching/10 hover:bg-status-watching/15", - borderClass: "ring-status-watching/20", -}; - -const statusConfig = { - watchlist: watchingStyle, - in_progress: watchingStyle, - completed: { - label: "Completed", - icon: IconCheck, - class: "text-status-completed", - bgClass: "bg-status-completed/10 hover:bg-status-completed/15", - borderClass: "ring-status-completed/20", - }, -} as const; - interface StatusButtonProps { currentStatus: string | null; onChange: (status: string | null) => void; } export function StatusButton({ currentStatus, onChange }: StatusButtonProps) { + const { t } = useLingui(); + + const watchingStyle = { + label: t`Watching`, + icon: IconPlayerPlayFilled, + class: "text-status-watching", + bgClass: "bg-status-watching/10 hover:bg-status-watching/15", + borderClass: "ring-status-watching/20", + }; + + const statusConfig = { + watchlist: watchingStyle, + in_progress: watchingStyle, + completed: { + label: t`Completed`, + icon: IconCheck, + class: "text-status-completed", + bgClass: "bg-status-completed/10 hover:bg-status-completed/15", + borderClass: "ring-status-completed/20", + }, + } as const; + const config = statusConfig[currentStatus as keyof typeof statusConfig] ?? null; @@ -49,7 +52,7 @@ export function StatusButton({ currentStatus, onChange }: StatusButtonProps) { className="inline-flex h-9 items-center gap-2 rounded-lg bg-primary/10 px-4 font-medium text-primary text-sm ring-1 ring-primary/20 transition-all hover:bg-primary/15 hover:ring-primary/30 active:scale-[0.97]" > - Watchlist + {t`Watchlist`} ) : ( @@ -78,7 +81,7 @@ export function StatusButton({ currentStatus, onChange }: StatusButtonProps) { {config.label} - Remove + {t`Remove`} diff --git a/apps/web/src/components/titles/title-actions.tsx b/apps/web/src/components/titles/title-actions.tsx index 023583d..9173a5c 100644 --- a/apps/web/src/components/titles/title-actions.tsx +++ b/apps/web/src/components/titles/title-actions.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { IconCheck } from "@tabler/icons-react"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; @@ -25,7 +26,7 @@ export function TitleActions() { className="h-9 rounded-lg px-4 text-sm hover:shadow-md hover:shadow-primary/20 active:scale-[0.97]" > - Mark Watched + Mark Watched )} = { - flatrate: "Stream", - rent: "Rent", - buy: "Buy", - free: "Free", - ads: "With Ads", -}; +// offerLabels moved into component for LingUI function ProviderBadge({ name, @@ -30,6 +25,7 @@ function ProviderBadge({ logoPath: string | null; watchUrl: string | null; }) { + const { t } = useLingui(); return ( - {watchUrl ? `Watch on ${name}` : name} + {watchUrl ? t`Watch on ${name}` : name} ); @@ -136,6 +132,14 @@ export function TitleAvailability({ }: { availability: AvailabilityOffer[]; }) { + const { t } = useLingui(); + const offerLabels: Record = { + flatrate: t`Stream`, + rent: t`Rent`, + buy: t`Buy`, + free: t`Free`, + ads: t`With Ads`, + }; const availByType: Record = {}; for (const offer of availability) { if (!availByType[offer.offerType]) availByType[offer.offerType] = []; @@ -147,7 +151,7 @@ export function TitleAvailability({ return (

    - Where to Watch + Where to Watch

    {Object.entries(availByType).map(([type, offers]) => { diff --git a/apps/web/src/components/titles/title-hero.tsx b/apps/web/src/components/titles/title-hero.tsx index d90a658..b6dec6d 100644 --- a/apps/web/src/components/titles/title-hero.tsx +++ b/apps/web/src/components/titles/title-hero.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import type { ColorPalette, ResolvedTitle } from "@sofa/api/schemas"; import { IconCalendarEvent, @@ -138,7 +139,9 @@ export function TitleHero({ aria-hidden className="size-3.5 translate-y-[-0.5px]" /> - Movie + + Movie + ) : ( <> @@ -146,7 +149,9 @@ export function TitleHero({ aria-hidden className="size-3.5 translate-y-[-0.5px]" /> - TV + + TV + )}
    diff --git a/apps/web/src/components/titles/title-recommendations.tsx b/apps/web/src/components/titles/title-recommendations.tsx index f588fa2..2b8881a 100644 --- a/apps/web/src/components/titles/title-recommendations.tsx +++ b/apps/web/src/components/titles/title-recommendations.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { IconThumbUp } from "@tabler/icons-react"; import { useQuery } from "@tanstack/react-query"; import { TitleCard, TitleCardSkeleton } from "@/components/title-card"; @@ -35,7 +36,9 @@ export function TitleRecommendations({ titleId }: { titleId: string }) {
    -

    Recommended

    +

    + Recommended +

    {data.recommendations.slice(0, 12).map((rec, i) => ( diff --git a/apps/web/src/components/titles/title-seasons.tsx b/apps/web/src/components/titles/title-seasons.tsx index 4b19408..488e348 100644 --- a/apps/web/src/components/titles/title-seasons.tsx +++ b/apps/web/src/components/titles/title-seasons.tsx @@ -1,4 +1,6 @@ +import { Trans } from "@lingui/react/macro"; import type { Season } from "@sofa/api/schemas"; +import { formatShortDate } from "@sofa/i18n/format"; import { IconCheck, IconChecks, @@ -6,7 +8,6 @@ import { IconChevronUp, IconDeviceTvOld, } from "@tabler/icons-react"; -import { format, parseISO } from "date-fns"; import { AnimatePresence, motion } from "motion/react"; import { useEffect, useMemo, useState } from "react"; @@ -97,7 +98,9 @@ export function TitleSeasons({
    -

    Episodes

    +

    + Episodes +

    {userStatus !== "completed" && ( @@ -109,29 +112,33 @@ export function TitleSeasons({ className="text-muted-foreground uppercase tracking-wider" > - Mark All Watched + Mark All Watched } /> - Mark all episodes as watched? + Mark all episodes as watched? - This will mark every episode of this show as watched. You can - undo this later by unmarking individual seasons. + + This will mark every episode of this show as watched. You + can undo this later by unmarking individual seasons. + - Cancel + + Cancel + { handleMarkAllWatched(); setMarkAllOpen(false); }} > - Mark All Watched + Mark All Watched @@ -191,7 +198,9 @@ export function TitleSeasons({ aria-hidden={true} className="size-3.5 sm:hidden" /> - Watch all + + Watch all + )} {totalCount > 0 && watchedCount === totalCount && ( @@ -208,7 +217,9 @@ export function TitleSeasons({ aria-hidden={true} className="size-3.5 sm:hidden" /> - Unwatch all + + Unwatch all + )} {totalCount > 0 && ( @@ -326,13 +337,11 @@ export function TitleSeasons({ - {ep.name ?? "Untitled"} + {ep.name ?? Untitled}

    - {ep.airDate - ? format(parseISO(ep.airDate), "MMM d, yyyy") - : ""} + {ep.airDate ? formatShortDate(ep.airDate) : ""} {ep.airDate && ep.runtimeMinutes ? " · " : ""} {ep.runtimeMinutes ? `${ep.runtimeMinutes}m` diff --git a/apps/web/src/components/titles/trailer-dialog.tsx b/apps/web/src/components/titles/trailer-dialog.tsx index 60a6e36..194ab04 100644 --- a/apps/web/src/components/titles/trailer-dialog.tsx +++ b/apps/web/src/components/titles/trailer-dialog.tsx @@ -1,3 +1,4 @@ +import { useLingui } from "@lingui/react/macro"; import { IconPlayerPlayFilled } from "@tabler/icons-react"; import { lazy, Suspense, useState } from "react"; import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; @@ -12,6 +13,7 @@ export function TrailerDialog({ videoKey: string; variant?: "badge" | "backdrop"; }) { + const { t } = useLingui(); const [open, setOpen] = useState(false); return ( @@ -19,7 +21,7 @@ export function TrailerDialog({ {variant === "backdrop" ? ( )} - Trailer + {t`Trailer`}

    1 ? "s" : ""} as watched`, + t`Caught up — marked ${episodeIds.length} ${plural(episodeIds.length, { one: "episode", other: "episodes" })} as watched`, ); } catch { setUserInfo((old) => ({ @@ -84,10 +87,10 @@ export function useTitleActions() { episodeWatches: prev.episodeWatches, status: prev.status, })); - toast.error("Failed to catch up"); + toast.error(t`Failed to catch up`); } }, - [getUserInfo, setUserInfo, seasons, batchWatchMutation], + [getUserInfo, setUserInfo, seasons, batchWatchMutation, t], ); const handleStatusChange = useCallback( @@ -105,13 +108,13 @@ export function useTitleActions() { id: titleId, status: status ? "in_progress" : null, }); - toast.success(status ? "Added to watchlist" : "Removed from library"); + toast.success(status ? t`Added to watchlist` : t`Removed from library`); } catch { setUserInfo((old) => ({ ...old, status: prevStatus })); - toast.error("Failed to update status"); + toast.error(t`Failed to update status`); } }, - [getUserInfo, setUserInfo, titleId, updateStatusMutation], + [getUserInfo, setUserInfo, titleId, updateStatusMutation, t], ); const handleRating = useCallback( @@ -125,15 +128,15 @@ export function useTitleActions() { }); toast.success( ratingStars > 0 - ? `Rated ${ratingStars} star${ratingStars > 1 ? "s" : ""}` - : "Rating removed", + ? t`Rated ${ratingStars} ${plural(ratingStars, { one: "star", other: "stars" })}` + : t`Rating removed`, ); } catch { setUserInfo((old) => ({ ...old, rating: prevRating })); - toast.error("Failed to update rating"); + toast.error(t`Failed to update rating`); } }, - [getUserInfo, setUserInfo, titleId, updateRatingMutation], + [getUserInfo, setUserInfo, titleId, updateRatingMutation, t], ); const handleWatchMovie = useCallback(async () => { @@ -141,12 +144,12 @@ export function useTitleActions() { setUserInfo((old) => ({ ...old, status: "completed" })); try { await watchMovieMutation.mutateAsync({ id: titleId }); - toast.success(`Marked "${titleName}" as watched`); + toast.success(t`Marked "${titleName}" as watched`); } catch { setUserInfo((old) => ({ ...old, status: prevStatus })); - toast.error("Failed to mark as watched"); + toast.error(t`Failed to mark as watched`); } - }, [getUserInfo, setUserInfo, titleId, titleName, watchMovieMutation]); + }, [getUserInfo, setUserInfo, titleId, titleName, watchMovieMutation, t]); const handleWatchEpisode = useCallback( async ( @@ -168,14 +171,14 @@ export function useTitleActions() { try { await unwatchEpMutation.mutateAsync({ id: episodeId }); - toast.success(`Unwatched S${seasonNum} E${epNum}`); + toast.success(t`Unwatched S${seasonNum} E${epNum}`); } catch { setUserInfo((old) => ({ ...old, episodeWatches: prevWatches, status: prevStatus, })); - toast.error("Failed to unmark episode"); + toast.error(t`Failed to unmark episode`); } } else { const prevWatches = getUserInfo().episodeWatches; @@ -214,16 +217,16 @@ export function useTitleActions() { if (previousUnwatched.length > 0) { const count = previousUnwatched.length; - toast.success(`Watched S${seasonNum} E${epNum}`, { - description: `${count} earlier episode${count > 1 ? "s" : ""} unwatched`, + toast.success(t`Watched S${seasonNum} E${epNum}`, { + description: t`${count} earlier ${plural(count, { one: "episode", other: "episodes" })} unwatched`, action: { - label: "Catch up", + label: t`Catch up`, onClick: () => catchUp(previousUnwatched), }, duration: 8000, }); } else { - toast.success(`Watched S${seasonNum} E${epNum}`); + toast.success(t`Watched S${seasonNum} E${epNum}`); } } catch { setUserInfo((old) => ({ @@ -231,7 +234,7 @@ export function useTitleActions() { episodeWatches: prevWatches, status: prevStatus, })); - toast.error("Failed to mark episode"); + toast.error(t`Failed to mark episode`); } } @@ -245,6 +248,7 @@ export function useTitleActions() { catchUp, unwatchEpMutation, watchEpMutation, + t, ], ); @@ -288,19 +292,19 @@ export function useTitleActions() { } } - const seasonLabel = season.name ?? `Season ${season.seasonNumber}`; + const seasonLabel = season.name ?? t`Season ${season.seasonNumber}`; if (previousUnwatched.length > 0) { const count = previousUnwatched.length; - toast.success(`Watched all of ${seasonLabel}`, { - description: `${count} earlier episode${count > 1 ? "s" : ""} unwatched`, + toast.success(t`Watched all of ${seasonLabel}`, { + description: t`${count} earlier ${plural(count, { one: "episode", other: "episodes" })} unwatched`, action: { - label: "Catch up", + label: t`Catch up`, onClick: () => catchUp(previousUnwatched), }, duration: 8000, }); } else { - toast.success(`Watched all of ${seasonLabel}`); + toast.success(t`Watched all of ${seasonLabel}`); } } catch { setUserInfo((old) => ({ @@ -308,10 +312,10 @@ export function useTitleActions() { episodeWatches: prevWatches, status: prevStatus, })); - toast.error("Failed to mark some episodes"); + toast.error(t`Failed to mark some episodes`); } }, - [getUserInfo, setUserInfo, seasons, catchUp, watchSeasonMutation], + [getUserInfo, setUserInfo, seasons, catchUp, watchSeasonMutation, t], ); const handleUnmarkSeason = useCallback( @@ -328,7 +332,7 @@ export function useTitleActions() { try { await unwatchSeasonMutation.mutateAsync({ id: season.id }); toast.success( - `Unwatched all of ${season.name ?? `Season ${season.seasonNumber}`}`, + t`Unwatched all of ${season.name ?? t`Season ${season.seasonNumber}`}`, ); } catch { setUserInfo((old) => ({ @@ -336,10 +340,10 @@ export function useTitleActions() { episodeWatches: prevWatches, status: prevStatus, })); - toast.error("Failed to unmark some episodes"); + toast.error(t`Failed to unmark some episodes`); } }, - [getUserInfo, setUserInfo, unwatchSeasonMutation], + [getUserInfo, setUserInfo, unwatchSeasonMutation, t], ); const handleMarkAllWatched = useCallback(async () => { @@ -353,16 +357,16 @@ export function useTitleActions() { })); try { await watchAllMutation.mutateAsync({ id: titleId }); - toast.success("Marked all episodes as watched"); + toast.success(t`Marked all episodes as watched`); } catch { setUserInfo((old) => ({ ...old, episodeWatches: prevWatches, status: prevStatus, })); - toast.error("Failed to mark all episodes as watched"); + toast.error(t`Failed to mark all episodes as watched`); } - }, [getUserInfo, setUserInfo, seasons, titleId, watchAllMutation]); + }, [getUserInfo, setUserInfo, seasons, titleId, watchAllMutation, t]); return { handleStatusChange, diff --git a/apps/web/src/components/update-toast.tsx b/apps/web/src/components/update-toast.tsx index 44dfaf5..145273d 100644 --- a/apps/web/src/components/update-toast.tsx +++ b/apps/web/src/components/update-toast.tsx @@ -1,3 +1,4 @@ +import { useLingui } from "@lingui/react/macro"; import type { UpdateCheckResult } from "@sofa/api/schemas"; import { useAtom } from "jotai"; import { useEffect } from "react"; @@ -5,6 +6,7 @@ import { toast } from "sonner"; import { updateToastDismissedVersionAtom } from "@/lib/atoms/update-check"; export function UpdateToast({ data }: { data: UpdateCheckResult | null }) { + const { t } = useLingui(); const [dismissedVersion, setDismissedVersion] = useAtom( updateToastDismissedVersionAtom, ); @@ -14,17 +16,17 @@ export function UpdateToast({ data }: { data: UpdateCheckResult | null }) { if (dismissedVersion === data.latestVersion) return; setDismissedVersion(data.latestVersion); - toast.info(`Sofa v${data.latestVersion} is available`, { - description: `You're running v${data.currentVersion}.`, + toast.info(t`Sofa v${data.latestVersion} is available`, { + description: t`You're running v${data.currentVersion}.`, duration: 15_000, action: data.releaseUrl ? { - label: "View release", + label: t`View release`, onClick: () => window.open(data.releaseUrl as string, "_blank"), } : undefined, }); - }, [data, dismissedVersion, setDismissedVersion]); + }, [data, dismissedVersion, setDismissedVersion, t]); return null; } diff --git a/apps/web/src/hooks/use-time-ago.ts b/apps/web/src/hooks/use-time-ago.ts index 3e85145..2a26793 100644 --- a/apps/web/src/hooks/use-time-ago.ts +++ b/apps/web/src/hooks/use-time-ago.ts @@ -1,4 +1,4 @@ -import { formatDistanceToNowStrict } from "date-fns"; +import { formatRelativeTime } from "@sofa/i18n/format"; import { useSyncExternalStore } from "react"; // Shared ticker — one interval regardless of how many components subscribe @@ -36,10 +36,10 @@ function toTimestamp(date: string | Date | null | undefined): number | null { export function useTimeAgo( date: string | Date | null | undefined, - { addSuffix = true, fallback = "" } = {}, + { fallback = "" } = {}, ): string { useSyncExternalStore(subscribe, getSnapshot, getSnapshot); const ts = toTimestamp(date); if (ts === null) return fallback; - return formatDistanceToNowStrict(ts, { addSuffix }); + return formatRelativeTime(new Date(ts)); } diff --git a/apps/web/src/lib/error-messages.ts b/apps/web/src/lib/error-messages.ts new file mode 100644 index 0000000..1ef3262 --- /dev/null +++ b/apps/web/src/lib/error-messages.ts @@ -0,0 +1,49 @@ +import { ORPCError } from "@orpc/client"; +import type { AppErrorCode } from "@sofa/api/errors"; + +export function getAppErrorCode(error: unknown): AppErrorCode | null { + if ( + error instanceof ORPCError && + error.data && + typeof error.data === "object" && + "code" in error.data && + typeof error.data.code === "string" + ) { + return error.data.code as AppErrorCode; + } + return null; +} + +// Must be called inside a render/hook body with the `t` from useLingui() +export function appErrorMessages( + t: (template: TemplateStringsArray, ...args: unknown[]) => string, +): Record { + return { + TITLE_NOT_FOUND: t`Title not found`, + PERSON_NOT_FOUND: t`Person not found`, + INTEGRATION_NOT_FOUND: t`Integration not found`, + BACKUP_NOT_FOUND: t`Backup not found`, + BACKUP_DELETE_FAILED: t`Failed to delete backup`, + BACKUP_RESTORE_FAILED: t`Backup restoration failed`, + JOB_NOT_FOUND: t`Job not found`, + TMDB_NOT_CONFIGURED: t`TMDB API key is not configured`, + IMPORT_INVALID_FILE: t`Invalid import file`, + IMPORT_PAYLOAD_TOO_LARGE: t`Import payload is too large`, + IMPORT_ALREADY_RUNNING: t`An import is already in progress`, + IMPORT_CANNOT_CANCEL: t`This import cannot be cancelled`, + REGISTRATION_CLOSED: t`Registration is currently closed`, + }; +} + +export function getErrorMessage( + error: unknown, + t: (template: TemplateStringsArray, ...args: unknown[]) => string, + fallback?: string, +): string { + const code = getAppErrorCode(error); + if (code) { + const messages = appErrorMessages(t); + if (code in messages) return messages[code as keyof typeof messages]; + } + return fallback ?? t`Something went wrong`; +} diff --git a/apps/web/src/lib/i18n.ts b/apps/web/src/lib/i18n.ts new file mode 100644 index 0000000..97717c9 --- /dev/null +++ b/apps/web/src/lib/i18n.ts @@ -0,0 +1,36 @@ +import { + activateLocale, + SUPPORTED_LOCALES, + type SupportedLocale, +} from "@sofa/i18n"; + +const LOCALE_STORAGE_KEY = "sofa:locale"; + +export function getPersistedLocale(): SupportedLocale { + const stored = localStorage.getItem(LOCALE_STORAGE_KEY); + if (stored && SUPPORTED_LOCALES.includes(stored as SupportedLocale)) { + return stored as SupportedLocale; + } + const browserLang = navigator.language.split("-")[0]; + if (SUPPORTED_LOCALES.includes(browserLang as SupportedLocale)) { + return browserLang as SupportedLocale; + } + return "en"; +} + +export function setPersistedLocale(locale: SupportedLocale): void { + localStorage.setItem(LOCALE_STORAGE_KEY, locale); + document.documentElement.lang = locale; +} + +let initialized = false; + +export async function initLocale(): Promise { + if (initialized) return; + initialized = true; + const locale = getPersistedLocale(); + document.documentElement.lang = locale; + if (locale !== "en") { + await activateLocale(locale); + } +} diff --git a/apps/web/src/lib/orpc/client.ts b/apps/web/src/lib/orpc/client.ts index 283ad19..e7b6ad9 100644 --- a/apps/web/src/lib/orpc/client.ts +++ b/apps/web/src/lib/orpc/client.ts @@ -1,18 +1,19 @@ +import { msg } from "@lingui/core/macro"; import { createORPCClient } from "@orpc/client"; import { RPCLink } from "@orpc/client/fetch"; import type { ContractRouterClient } from "@orpc/contract"; import { createTanstackQueryUtils } from "@orpc/tanstack-query"; import type { contract } from "@sofa/api/contract"; +import { i18n } from "@sofa/i18n"; import { QueryCache, QueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; export const queryClient = new QueryClient({ queryCache: new QueryCache({ - onError: (error, query) => { - toast.error("Something went wrong…", { - description: error.message, + onError: (_error, query) => { + toast.error(i18n._(msg`Something went wrong…`), { action: { - label: "Retry", + label: i18n._(msg`Retry`), onClick: () => query.invalidate(), }, }); diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx index 6f9ac7b..876bf40 100644 --- a/apps/web/src/routes/__root.tsx +++ b/apps/web/src/routes/__root.tsx @@ -1,3 +1,6 @@ +import { I18nProvider } from "@lingui/react"; +import { Trans } from "@lingui/react/macro"; +import { i18n } from "@sofa/i18n"; import { IconAlertTriangle } from "@tabler/icons-react"; import { TanStackDevtools } from "@tanstack/react-devtools"; import type { QueryClient } from "@tanstack/react-query"; @@ -14,6 +17,7 @@ import { ProgressProvider } from "@/components/navigation-progress"; import { SofaLogo } from "@/components/sofa-logo"; import { Toaster } from "@/components/ui/sonner"; import { TooltipProvider } from "@/components/ui/tooltip"; +import { initLocale } from "@/lib/i18n"; import type { orpc } from "@/lib/orpc/client"; import globalsCss from "@/styles/globals.css?url"; @@ -21,6 +25,9 @@ export const Route = createRootRouteWithContext<{ orpc: typeof orpc; queryClient: QueryClient; }>()({ + beforeLoad: async () => { + await initLocale(); + }, head: () => ({ meta: [ { @@ -48,14 +55,16 @@ function RootComponent() { <> - - - - - - - - + + + + + + + + + +

    - Scene not found + Scene not found

    - This page was left on the cutting room floor. It may have been - moved, removed, or never made it past the screenplay. + + This page was left on the cutting room floor. It may have been + moved, removed, or never made it past the screenplay. +

    - Return home + + Return home +
    @@ -127,11 +140,13 @@ function GlobalError({ reset }: { error: Error; reset: () => void }) {

    - Something went wrong + Something went wrong

    - An unexpected error occurred while loading this page. You can try - again or head back to the dashboard. + + An unexpected error occurred while loading this page. You can try + again or head back to the dashboard. +

    @@ -140,14 +155,16 @@ function GlobalError({ reset }: { error: Error; reset: () => void }) { onClick={reset} className="group relative inline-flex h-10 items-center justify-center gap-2 overflow-hidden rounded-lg bg-primary px-5 font-medium text-primary-foreground text-sm transition-shadow hover:shadow-lg hover:shadow-primary/20" > - Try again + + Try again +
    diff --git a/apps/web/src/routes/_app/people.$id.tsx b/apps/web/src/routes/_app/people.$id.tsx index c00ce97..3538634 100644 --- a/apps/web/src/routes/_app/people.$id.tsx +++ b/apps/web/src/routes/_app/people.$id.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { createFileRoute, Link } from "@tanstack/react-router"; import { PersonDetailClient, @@ -48,11 +49,13 @@ function PersonNotFound() { style={{ "--stagger-index": 1 } as React.CSSProperties} >

    - Person not found + Person not found

    - The person you're looking for doesn't exist or may have been - removed from the database. + + The person you're looking for doesn't exist or may have + been removed from the database. +

    - Explore titles + + Explore titles +
    - Dashboard + Dashboard
    diff --git a/apps/web/src/routes/_app/settings.tsx b/apps/web/src/routes/_app/settings.tsx index 66c23d3..fa1a305 100644 --- a/apps/web/src/routes/_app/settings.tsx +++ b/apps/web/src/routes/_app/settings.tsx @@ -1,6 +1,8 @@ +import { Trans } from "@lingui/react/macro"; import { IconAlertTriangle, IconDatabaseExport, + IconDeviceDesktopCog, IconServer2, IconShieldLock, } from "@tabler/icons-react"; @@ -12,6 +14,7 @@ import { BackupSection } from "@/components/settings/backup-section"; import { CacheSection } from "@/components/settings/danger-section"; import { ImportsSection } from "@/components/settings/imports-section"; import { IntegrationsSection } from "@/components/settings/integrations-section"; +import { LanguageSection } from "@/components/settings/language-section"; import { RegistrationSection } from "@/components/settings/registration-section"; import { SettingsShell } from "@/components/settings/settings-shell"; import { SystemHealthCards } from "@/components/settings/system-health-section"; @@ -35,7 +38,7 @@ function SettingsPage() {

    - This product uses the TMDB API but is not endorsed or certified by - TMDB. + + This product uses the TMDB API but is not endorsed or certified + by TMDB. +

    @@ -86,6 +91,20 @@ function SettingsPage() { }} /> + {/* App Settings */} +
    +
    + +

    + App Settings +

    +
    + +
    + @@ -99,10 +118,10 @@ function SettingsPage() { className="size-4 text-muted-foreground" />

    - Server + Server

    - Admin only + Admin only
    @@ -118,7 +137,7 @@ function SettingsPage() { className="size-4 text-muted-foreground" />

    - Security + Security

    Admin only @@ -144,7 +163,7 @@ function SettingsPage() { className="size-4 text-muted-foreground" />

    - Backups + Backups

    Admin only @@ -173,7 +192,7 @@ function SettingsPage() { className="size-4 text-destructive" />

    - Danger Zone + Danger Zone

    Admin only diff --git a/apps/web/src/routes/_app/titles.$id.tsx b/apps/web/src/routes/_app/titles.$id.tsx index a086078..5baca21 100644 --- a/apps/web/src/routes/_app/titles.$id.tsx +++ b/apps/web/src/routes/_app/titles.$id.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { createFileRoute, Link } from "@tanstack/react-router"; import { TitleActions } from "@/components/titles/title-actions"; import { TitleAvailability } from "@/components/titles/title-availability"; @@ -111,16 +112,18 @@ function TitleErrorComponent() { return (

    - Failed to load title + Failed to load title

    - Something went wrong while loading this title. Please try again. + + Something went wrong while loading this title. Please try again. +

    - Dashboard + Dashboard
    ); @@ -140,11 +143,13 @@ function TitleNotFound() { style={{ "--stagger-index": 1 } as React.CSSProperties} >

    - Title not found + Title not found

    - The title you're looking for doesn't exist or may have been - removed from the database. + + The title you're looking for doesn't exist or may have + been removed from the database. +

    - Explore titles + + Explore titles +
    - Dashboard + Dashboard
    diff --git a/apps/web/src/routes/_auth/register.tsx b/apps/web/src/routes/_auth/register.tsx index e8cca76..2cd0cac 100644 --- a/apps/web/src/routes/_auth/register.tsx +++ b/apps/web/src/routes/_auth/register.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { IconLock } from "@tabler/icons-react"; import { createFileRoute, Link, redirect } from "@tanstack/react-router"; import { AuthForm } from "@/components/auth-form"; @@ -28,18 +29,20 @@ function RegisterPage() {

    - Registration Closed + Registration Closed

    - New accounts are not being accepted right now. Contact the admin - if you need access. + + New accounts are not being accepted right now. Contact the admin + if you need access. +

    - Sign in instead + Sign in instead diff --git a/apps/web/src/routes/setup.tsx b/apps/web/src/routes/setup.tsx index e734259..53d5bd2 100644 --- a/apps/web/src/routes/setup.tsx +++ b/apps/web/src/routes/setup.tsx @@ -1,3 +1,4 @@ +import { Trans } from "@lingui/react/macro"; import { IconExternalLink, IconKey } from "@tabler/icons-react"; import { createFileRoute, redirect } from "@tanstack/react-router"; import { CopyButton } from "@/components/setup/copy-button"; @@ -94,10 +95,10 @@ function SetupPage() {
    - Setup required + Setup required

    - Connect to TMDB + Connect to TMDB

    Sofa uses{" "} @@ -183,8 +184,10 @@ function SetupPage() {

    - This product uses the TMDB API but is not endorsed or certified by - TMDB. + + This product uses the TMDB API but is not endorsed or certified by + TMDB. +

    diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index cb79604..c39e79d 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -1,3 +1,4 @@ +import { lingui } from "@lingui/vite-plugin"; import babel from "@rolldown/plugin-babel"; import tailwindcss from "@tailwindcss/vite"; import { devtools } from "@tanstack/devtools-vite"; @@ -10,7 +11,9 @@ export default defineConfig({ devtools(), tanstackRouter({ target: "react", autoCodeSplitting: true }), react(), + lingui(), babel({ + plugins: ["@lingui/babel-plugin-lingui-macro"], presets: [reactCompilerPreset()], }), tailwindcss(), diff --git a/biome.json b/biome.json index b7df309..beeeb9d 100644 --- a/biome.json +++ b/biome.json @@ -13,6 +13,7 @@ "!dist", "!build", "!packages/db/drizzle", + "!packages/i18n/src/po/*.ts", "!packages/tmdb/src/schema.d.ts", "!**/routeTree.gen.ts", "!apps/native/.expo/types/**/*.ts", diff --git a/bun.lock b/bun.lock index 8058436..8ec2a3b 100644 --- a/bun.lock +++ b/bun.lock @@ -6,6 +6,9 @@ "name": "sofa-monorepo", "devDependencies": { "@biomejs/biome": "2.4.7", + "@lingui/cli": "catalog:", + "@lingui/conf": "catalog:", + "@lingui/format-po": "catalog:", "turbo": "2.8.17", "typescript": "catalog:", }, @@ -17,6 +20,14 @@ "@better-auth/expo": "catalog:", "@expo/metro-runtime": "55.0.6", "@expo/ui": "55.0.3", + "@formatjs/intl-datetimeformat": "7.2.6", + "@formatjs/intl-getcanonicallocales": "3.2.2", + "@formatjs/intl-locale": "5.2.2", + "@formatjs/intl-numberformat": "9.2.4", + "@formatjs/intl-pluralrules": "6.2.4", + "@formatjs/intl-relativetimeformat": "12.2.4", + "@lingui/core": "catalog:", + "@lingui/react": "catalog:", "@orpc/client": "catalog:", "@orpc/contract": "catalog:", "@orpc/tanstack-query": "catalog:", @@ -25,6 +36,7 @@ "@react-navigation/native": "7.1.33", "@shopify/flash-list": "2.0.2", "@sofa/api": "workspace:*", + "@sofa/i18n": "workspace:*", "@tabler/icons-react-native": "3.40.0", "@tanstack/query-async-storage-persister": "5.90.24", "@tanstack/react-form": "1.28.5", @@ -32,7 +44,6 @@ "@tanstack/react-query-persist-client": "5.90.24", "better-auth": "catalog:", "burnt": "0.13.0", - "date-fns": "catalog:", "expo": "55.0.7", "expo-application": "55.0.10", "expo-clipboard": "55.0.9", @@ -77,6 +88,8 @@ "zod": "catalog:", }, "devDependencies": { + "@lingui/babel-plugin-lingui-macro": "catalog:", + "@lingui/metro-transformer": "catalog:", "@types/node": "catalog:", "@types/react": "catalog:", "typescript": "catalog:", @@ -131,12 +144,14 @@ "@fontsource-variable/dm-sans": "5.2.8", "@fontsource-variable/geist-mono": "5.2.7", "@fontsource/dm-serif-display": "5.2.8", + "@lingui/react": "catalog:", "@orpc/client": "catalog:", "@orpc/contract": "catalog:", "@orpc/tanstack-query": "catalog:", "@player.style/sutro": "0.2.1", "@rolldown/plugin-babel": "0.2.1", "@sofa/api": "workspace:*", + "@sofa/i18n": "workspace:*", "@tabler/icons-react": "3.40.0", "@tanstack/react-hotkeys": "0.4.1", "@tanstack/react-query": "catalog:", @@ -145,7 +160,6 @@ "class-variance-authority": "0.7.1", "clsx": "2.1.1", "cmdk": "1.1.1", - "date-fns": "catalog:", "jotai": "2.18.1", "media-chrome": "4.18.1", "motion": "12.38.0", @@ -160,6 +174,8 @@ "youtube-video-element": "1.9.0", }, "devDependencies": { + "@lingui/babel-plugin-lingui-macro": "catalog:", + "@lingui/vite-plugin": "catalog:", "@tailwindcss/vite": "0.0.0-insiders.f302fce", "@tanstack/devtools-vite": "0.6.0", "@tanstack/react-devtools": "0.10.0", @@ -222,7 +238,6 @@ "@sofa/logger": "workspace:*", "@sofa/tmdb": "workspace:*", "adm-zip": "0.5.16", - "date-fns": "catalog:", "node-vibrant": "4.0.4", "sharp": "0.34.5", "thumbhash": "catalog:", @@ -248,6 +263,21 @@ "typescript": "catalog:", }, }, + "packages/i18n": { + "name": "@sofa/i18n", + "version": "0.1.0", + "dependencies": { + "@lingui/core": "catalog:", + "@lingui/react": "catalog:", + }, + "devDependencies": { + "@lingui/cli": "catalog:", + "@lingui/conf": "catalog:", + "@lingui/format-po": "catalog:", + "@types/bun": "catalog:", + "typescript": "catalog:", + }, + }, "packages/logger": { "name": "@sofa/logger", "version": "0.1.0", @@ -275,6 +305,14 @@ }, "catalog": { "@better-auth/expo": "1.5.5", + "@lingui/babel-plugin-lingui-macro": "5.9.3", + "@lingui/cli": "5.9.3", + "@lingui/conf": "5.9.3", + "@lingui/core": "5.9.3", + "@lingui/format-po": "5.9.3", + "@lingui/metro-transformer": "5.9.3", + "@lingui/react": "5.9.3", + "@lingui/vite-plugin": "5.9.3", "@orpc/client": "1.13.7", "@orpc/contract": "1.13.7", "@orpc/tanstack-query": "1.13.7", @@ -283,7 +321,6 @@ "@types/node": "25.5.0", "@types/react": "19.2.14", "better-auth": "1.5.5", - "date-fns": "4.1.0", "react": "19.2.0", "react-dom": "19.2.0", "tailwind-merge": "3.5.0", @@ -701,6 +738,26 @@ "@fontsource/dm-serif-display": ["@fontsource/dm-serif-display@5.2.8", "", {}, "sha512-GYSDSlGU6vyhv9a5MwaiVNf9HCuSVpK8hEFRyG4NNDHCDeHiX7YHDAcWsaoLKKcfXLgWG9YkBkk9T3SxM4rAjQ=="], + "@formatjs/ecma402-abstract": ["@formatjs/ecma402-abstract@3.1.2", "", { "dependencies": { "@formatjs/fast-memoize": "3.1.1", "@formatjs/intl-localematcher": "0.8.2", "decimal.js": "^10.6.0" } }, "sha512-ApPcse35dtr+snmBbDtnBkc9bvAZCxr9DDzgUR3nXiHZgpwU+85g1Mz0KRqjh7kIgJ6rgIQLQVqYx56rf58XZg=="], + + "@formatjs/fast-memoize": ["@formatjs/fast-memoize@3.1.1", "", {}, "sha512-CbNbf+tlJn1baRnPkNePnBqTLxGliG6DDgNa/UtV66abwIjwsliPMOt0172tzxABYzSuxZBZfcp//qI8AvBWPg=="], + + "@formatjs/intl-datetimeformat": ["@formatjs/intl-datetimeformat@7.2.6", "", { "dependencies": { "@formatjs/ecma402-abstract": "3.1.2", "@formatjs/intl-localematcher": "0.8.2", "decimal.js": "^10.6.0" } }, "sha512-De9YgJj1Vsr+qbyXa2XC5OOb53tOMDcO0EkHvGPdrpF01MZb6dZ2XVPW0shqGHEDAqp+ZUlF0uNRdvkH4V7+Fw=="], + + "@formatjs/intl-getcanonicallocales": ["@formatjs/intl-getcanonicallocales@3.2.2", "", {}, "sha512-pjF2zC26yXXLv9FlPes6zzOme9MwgqyeWYu8r5bP7qEdoDcaGsuQO3mk6pQb57t9bxdbIwrAhZFy0hH760SeWw=="], + + "@formatjs/intl-locale": ["@formatjs/intl-locale@5.2.2", "", { "dependencies": { "@formatjs/ecma402-abstract": "3.1.2", "@formatjs/intl-getcanonicallocales": "3.2.2", "@formatjs/intl-supportedvaluesof": "2.2.2" } }, "sha512-WikljmmLCkmo6G2Ki4yCCVXZJb+Fhle5RrumxPdYAi52l9SdzssJdRzz1WEKc2oOsrHE0ZQKIEqbdH7oWENHPQ=="], + + "@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.8.2", "", { "dependencies": { "@formatjs/fast-memoize": "3.1.1" } }, "sha512-q05KMYGJLyqFNFtIb8NhWLF5X3aK/k0wYt7dnRFuy6aLQL+vUwQ1cg5cO4qawEiINybeCPXAWlprY2mSBjSXAQ=="], + + "@formatjs/intl-numberformat": ["@formatjs/intl-numberformat@9.2.4", "", { "dependencies": { "@formatjs/ecma402-abstract": "3.1.2", "@formatjs/intl-localematcher": "0.8.2", "decimal.js": "^10.6.0" } }, "sha512-ezoO/4gTS3Sz6vSZ/UgWgwD7RIYFNwL9RQ17nguWJN12wLeZ8TcWeCrMXlCenxh8TDaftDKwsREIJ5YTEMGi8g=="], + + "@formatjs/intl-pluralrules": ["@formatjs/intl-pluralrules@6.2.4", "", { "dependencies": { "@formatjs/ecma402-abstract": "3.1.2", "@formatjs/intl-localematcher": "0.8.2", "decimal.js": "^10.6.0" } }, "sha512-biG3Y/cd867OUZn6i0akstBJJ5S6n7/aYS3bnZAxjmQxPrThmFSDDKDT/TvklbqueRW8chWPLHyQUQ1t8yVEVQ=="], + + "@formatjs/intl-relativetimeformat": ["@formatjs/intl-relativetimeformat@12.2.4", "", { "dependencies": { "@formatjs/ecma402-abstract": "3.1.2", "@formatjs/intl-localematcher": "0.8.2" } }, "sha512-GRZAq7iAsHPQKneMaOf4I1QwvV7ERkyu436uUKt/2Jop2w0gfRCPGaRRP/238zRabV7ks/CXnd3+5jWDfv30hA=="], + + "@formatjs/intl-supportedvaluesof": ["@formatjs/intl-supportedvaluesof@2.2.2", "", { "dependencies": { "@formatjs/ecma402-abstract": "3.1.2", "@formatjs/fast-memoize": "3.1.1" } }, "sha512-hPB1NQj9dAqcxyICi9DMNS3oBn12m2u7bMF5Xjy11NwnehGCr1kU7ni4yDVBlGpr01N6HAx2LPwTw+/xOtHW4Q=="], + "@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="], "@hono/zod-validator": ["@hono/zod-validator@0.7.6", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Io1B6d011Gj1KknV4rXYz4le5+5EubcWEU/speUjuw9XMMIaP3n78yXLhjd2A3PXaXaUwEAluOiAyLqhBEJgsw=="], @@ -765,6 +822,8 @@ "@inquirer/type": ["@inquirer/type@3.0.10", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA=="], + "@isaacs/cliui": ["@isaacs/cliui@9.0.0", "", {}, "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg=="], + "@isaacs/ttlcache": ["@isaacs/ttlcache@1.4.1", "", {}, "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA=="], "@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="], @@ -819,6 +878,28 @@ "@js-temporal/polyfill": ["@js-temporal/polyfill@0.5.1", "", { "dependencies": { "jsbi": "^4.3.0" } }, "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ=="], + "@lingui/babel-plugin-extract-messages": ["@lingui/babel-plugin-extract-messages@5.9.3", "", {}, "sha512-zm6QHDILmhj8olgLL2zHQn18yFA5mf4hX7QzCr1OOI/e815I0IkecCYue1Ych+y+B+V0eLriiW8AcfpDRCQFFw=="], + + "@lingui/babel-plugin-lingui-macro": ["@lingui/babel-plugin-lingui-macro@5.9.3", "", { "dependencies": { "@babel/core": "^7.20.12", "@babel/runtime": "^7.20.13", "@babel/types": "^7.20.7", "@lingui/conf": "5.9.3", "@lingui/core": "5.9.3", "@lingui/message-utils": "5.9.3" }, "peerDependencies": { "babel-plugin-macros": "2 || 3" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-fLMhBarRsuqBGOq2YuEoqOjEAV2VNezz/+f/Dn0vLFHF/kAjnFwTHb8pL8DRSIMsWG16mPrGnLpdROZBmJlFtA=="], + + "@lingui/cli": ["@lingui/cli@5.9.3", "", { "dependencies": { "@babel/core": "^7.21.0", "@babel/generator": "^7.21.1", "@babel/parser": "^7.22.0", "@babel/runtime": "^7.21.0", "@babel/types": "^7.21.2", "@lingui/babel-plugin-extract-messages": "5.9.3", "@lingui/babel-plugin-lingui-macro": "5.9.3", "@lingui/conf": "5.9.3", "@lingui/core": "5.9.3", "@lingui/format-po": "5.9.3", "@lingui/message-utils": "5.9.3", "chokidar": "3.5.1", "cli-table": "^0.3.11", "commander": "^10.0.0", "convert-source-map": "^2.0.0", "date-fns": "^3.6.0", "esbuild": "^0.25.1", "glob": "^11.0.0", "micromatch": "^4.0.7", "ms": "^2.1.3", "normalize-path": "^3.0.0", "ora": "^5.1.0", "picocolors": "^1.1.1", "pofile": "^1.1.4", "pseudolocale": "^2.0.0", "source-map": "^0.7.6", "threads": "^1.7.0" }, "bin": { "lingui": "dist/lingui.js" } }, "sha512-KEE0J4eGlfpiLZ+l019qjraWfqfh5mUmQSJeTFw5PulO4v50zvxw5tDX8stpBzJ3QtgUQZlrMUh0OTGdURaAMg=="], + + "@lingui/conf": ["@lingui/conf@5.9.3", "", { "dependencies": { "@babel/runtime": "^7.20.13", "cosmiconfig": "^8.0.0", "jest-validate": "^29.4.3", "jiti": "^2.5.1", "picocolors": "^1.1.1" } }, "sha512-hVEoYHmO2A3XmFX4A5RuBgcoVBoM7Xgoqejeq25XELvesJj2s2T15F47TA5n3/S7iTqngd6n/8KxBli9TYwgqQ=="], + + "@lingui/core": ["@lingui/core@5.9.3", "", { "dependencies": { "@babel/runtime": "^7.20.13", "@lingui/message-utils": "5.9.3" }, "peerDependencies": { "@lingui/babel-plugin-lingui-macro": "5.9.3", "babel-plugin-macros": "2 || 3" }, "optionalPeers": ["@lingui/babel-plugin-lingui-macro", "babel-plugin-macros"] }, "sha512-3b8LnDjx8POdQ6q6UKBe2DHynyQFCO66vm8/UPQnvlQowUk4Xdu5bK6oet11D9/vrSznrDDS+Qb5JVcNBUImgg=="], + + "@lingui/format-po": ["@lingui/format-po@5.9.3", "", { "dependencies": { "@lingui/conf": "5.9.3", "@lingui/message-utils": "5.9.3", "date-fns": "^3.6.0", "pofile": "^1.1.4" } }, "sha512-+LMnhWl7EmXrdOv10gopE1g8w8vtPY5Fxk72OORrGQFVMGBIioz4BEnKrNdV1ek2M+GxoMZtnUs17KrJN5Jv9A=="], + + "@lingui/message-utils": ["@lingui/message-utils@5.9.3", "", { "dependencies": { "@messageformat/parser": "^5.0.0", "js-sha256": "^0.10.1" } }, "sha512-oAK7HA7lcQrzaEaM6G1T5RwwxJxaSKfG/IFIxpZIl49TSFQv+s9YPNgHnVi+d4DmterpXNxy9ZZ+NtckJx6u7g=="], + + "@lingui/metro-transformer": ["@lingui/metro-transformer@5.9.3", "", { "dependencies": { "@babel/runtime": "^7.20.13", "@lingui/cli": "5.9.3", "@lingui/conf": "5.9.3", "memoize-one": "^6.0.0" }, "peerDependencies": { "@expo/metro-config": "*", "@react-native/metro-babel-transformer": "*", "expo": ">=50.0.0", "react-native": ">=0.73.0" }, "optionalPeers": ["@expo/metro-config", "@react-native/metro-babel-transformer", "expo"] }, "sha512-1wPv9UQeRoogwZcA3RbxNfpbjU6fyvO34z8WtL84QNLieYv52C9anRwJ5NS8av2UlWTw63LQ6ADv6A9z+f3ZTg=="], + + "@lingui/react": ["@lingui/react@5.9.3", "", { "dependencies": { "@babel/runtime": "^7.20.13", "@lingui/core": "5.9.3" }, "peerDependencies": { "@lingui/babel-plugin-lingui-macro": "5.9.3", "babel-plugin-macros": "2 || 3", "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@lingui/babel-plugin-lingui-macro", "babel-plugin-macros"] }, "sha512-aje78l3zGGZ3C75fiGhDVKyVALHfiKlYFjcOlZpgXY/JAVfFuZX+6wUGG9x1A8k7BfxrDy/ofHIBahPvNAqoKw=="], + + "@lingui/vite-plugin": ["@lingui/vite-plugin@5.9.3", "", { "dependencies": { "@lingui/cli": "5.9.3", "@lingui/conf": "5.9.3" }, "peerDependencies": { "vite": "^3 || ^4 || ^5.0.9 || ^6 || ^7 || ^8" } }, "sha512-9L9GqWjGWU+WrJAb5MBiH9QxkfagrOOWOFucBSwQgEf9k9612o9TIw8eiFqLnnPhi64ktVUaBgDJhkAYo4XLjw=="], + + "@messageformat/parser": ["@messageformat/parser@5.1.1", "", { "dependencies": { "moo": "^0.5.1" } }, "sha512-3p0YRGCcTUCYvBKLIxtDDyrJ0YijGIwrTRu1DT8gIviIDZru8H23+FkY6MJBzM1n9n20CiM4VeDYuBsrrwnLjg=="], + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.27.1", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA=="], "@mongodb-js/saslprep": ["@mongodb-js/saslprep@1.4.6", "", { "dependencies": { "sparse-bitfield": "^3.0.3" } }, "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g=="], @@ -1041,6 +1122,8 @@ "@sofa/db": ["@sofa/db@workspace:packages/db"], + "@sofa/i18n": ["@sofa/i18n@workspace:packages/i18n"], + "@sofa/logger": ["@sofa/logger@workspace:packages/logger"], "@sofa/native": ["@sofa/native@workspace:apps/native"], @@ -1355,7 +1438,7 @@ "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], - "bl": ["bl@6.1.6", "", { "dependencies": { "@types/readable-stream": "^4.0.0", "buffer": "^6.0.3", "inherits": "^2.0.4", "readable-stream": "^4.2.0" } }, "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg=="], + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], "bmp-js": ["bmp-js@0.1.0", "", {}, "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw=="], @@ -1405,7 +1488,7 @@ "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], - "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], + "chokidar": ["chokidar@3.5.1", "", { "dependencies": { "anymatch": "~3.1.1", "braces": "~3.0.2", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.5.0" }, "optionalDependencies": { "fsevents": "~2.3.1" } }, "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw=="], "chrome-launcher": ["chrome-launcher@0.15.2", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0" }, "bin": { "print-chrome-path": "bin/print-chrome-path.js" } }, "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ=="], @@ -1415,10 +1498,12 @@ "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], - "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], + "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="], "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], + "cli-table": ["cli-table@0.3.11", "", { "dependencies": { "colors": "1.0.3" } }, "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ=="], + "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], @@ -1443,7 +1528,9 @@ "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], - "commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + "colors": ["colors@1.0.3", "", {}, "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw=="], + + "commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="], "compressible": ["compressible@2.0.18", "", { "dependencies": { "mime-db": ">= 1.43.0 < 2" } }, "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg=="], @@ -1469,7 +1556,7 @@ "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], - "cosmiconfig": ["cosmiconfig@9.0.1", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ=="], + "cosmiconfig": ["cosmiconfig@8.3.6", "", { "dependencies": { "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0", "path-type": "^4.0.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA=="], "croner": ["croner@10.0.2-dev.2", "", {}, "sha512-/xijJtHje2b9SeU1T6ysrs1IGASOH/5YQ54oTEO/48sveZp1ig+MU9bO0N4bo2uXjJETuaDBRN7bJs+4kEEN1A=="], @@ -1511,7 +1598,7 @@ "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], - "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="], + "date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="], "dateformat": ["dateformat@4.6.3", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="], @@ -1519,6 +1606,8 @@ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], + "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="], "decode-uri-component": ["decode-uri-component@0.2.2", "", {}, "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="], @@ -1605,6 +1694,8 @@ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + "esm": ["esm@3.2.25", "", {}, "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="], + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], @@ -1741,6 +1832,8 @@ "fontfaceobserver": ["fontfaceobserver@2.3.0", "", {}, "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg=="], + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], @@ -1783,7 +1876,7 @@ "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="], - "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], @@ -1853,7 +1946,7 @@ "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], - "is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="], + "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], @@ -1871,7 +1964,7 @@ "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], - "is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="], + "is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="], "is-node-process": ["is-node-process@1.2.0", "", {}, "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw=="], @@ -1879,6 +1972,8 @@ "is-obj": ["is-obj@3.0.0", "", {}, "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ=="], + "is-observable": ["is-observable@2.1.0", "", {}, "sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw=="], + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], @@ -1887,7 +1982,7 @@ "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="], - "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="], "is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="], @@ -1901,6 +1996,8 @@ "istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="], + "jackspeak": ["jackspeak@4.2.3", "", { "dependencies": { "@isaacs/cliui": "^9.0.0" } }, "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg=="], + "jest-environment-node": ["jest-environment-node@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw=="], "jest-get-type": ["jest-get-type@29.6.3", "", {}, "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="], @@ -1933,6 +2030,8 @@ "js-md4": ["js-md4@0.3.2", "", {}, "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA=="], + "js-sha256": ["js-sha256@0.10.1", "", {}, "sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], @@ -2017,7 +2116,7 @@ "lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="], - "log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], + "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="], "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], @@ -2039,7 +2138,7 @@ "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], - "memoize-one": ["memoize-one@5.2.1", "", {}, "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="], + "memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="], "memory-pager": ["memory-pager@1.5.0", "", {}, "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="], @@ -2101,6 +2200,8 @@ "mongodb-connection-string-url": ["mongodb-connection-string-url@7.0.1", "", { "dependencies": { "@types/whatwg-url": "^13.0.0", "whatwg-url": "^14.1.0" } }, "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ=="], + "moo": ["moo@0.5.3", "", {}, "sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA=="], + "motion": ["motion@12.38.0", "", { "dependencies": { "framer-motion": "^12.38.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w=="], "motion-dom": ["motion-dom@12.38.0", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="], @@ -2155,6 +2256,8 @@ "object-treeify": ["object-treeify@1.1.33", "", {}, "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A=="], + "observable-fns": ["observable-fns@0.6.1", "", {}, "sha512-9gRK4+sRWzeN6AOewNBTLXir7Zl/i3GB6Yl26gK4flxz8BXVpD3kt8amREmWNb0mxYOGDotvE5a4N+PtGGKdkg=="], + "omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="], "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], @@ -2175,7 +2278,7 @@ "openapi-typescript-helpers": ["openapi-typescript-helpers@0.1.0", "", {}, "sha512-OKTGPthhivLw/fHz6c3OPtg72vi86qaMlqbJuVJ23qOvQ+53uw1n7HdmkJFibloF7QEjDrDkzJiOJuockM/ljw=="], - "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], + "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="], "outvariant": ["outvariant@1.4.3", "", {}, "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA=="], @@ -2185,6 +2288,8 @@ "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="], "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], @@ -2211,6 +2316,8 @@ "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], + "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="], @@ -2237,6 +2344,8 @@ "pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="], + "pofile": ["pofile@1.1.4", "", {}, "sha512-r6Q21sKsY1AjTVVjOuU02VYKVNQGJNQHjTIvs4dEbeuuYfxgYk/DGD2mqqq4RDaVkwdSq0VEtmQUOPe/wH8X3g=="], + "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], "postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], @@ -2265,6 +2374,8 @@ "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + "pseudolocale": ["pseudolocale@2.2.0", "", { "dependencies": { "commander": "^10.0.0" }, "bin": { "pseudolocale": "dist/cli.mjs" } }, "sha512-O+D2eU7fO9wVLqrohvt9V/9fwMadnJQ4jxwiK+LeNEqhMx8JYx4xQHkArDCJFAdPPOp/pQq6z5L37eBvAoc8jw=="], + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], @@ -2333,11 +2444,11 @@ "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], - "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], "readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.4", "", { "dependencies": { "readable-stream": "^4.7.0" } }, "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw=="], - "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], + "readdirp": ["readdirp@3.5.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ=="], "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], @@ -2375,7 +2486,7 @@ "resolve-workspace-root": ["resolve-workspace-root@2.0.1", "", {}, "sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w=="], - "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], + "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="], "rettime": ["rettime@0.10.1", "", {}, "sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw=="], @@ -2467,7 +2578,7 @@ "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], - "source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="], + "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -2503,7 +2614,7 @@ "stringify-object": ["stringify-object@5.0.0", "", { "dependencies": { "get-own-enumerable-keys": "^1.0.0", "is-obj": "^3.0.0", "is-regexp": "^3.1.0" } }, "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg=="], - "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], @@ -2543,6 +2654,8 @@ "thread-stream": ["thread-stream@4.0.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA=="], + "threads": ["threads@1.7.0", "", { "dependencies": { "callsites": "^3.1.0", "debug": "^4.2.0", "is-observable": "^2.1.0", "observable-fns": "^0.6.1" }, "optionalDependencies": { "tiny-worker": ">= 2" } }, "sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ=="], + "throat": ["throat@5.0.0", "", {}, "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA=="], "thumbhash": ["thumbhash@0.1.1", "", {}, "sha512-kH5pKeIIBPQXAOni2AiY/Cu/NKdkFREdpH+TLdM0g6WA7RriCv0kPLgP731ady67MhTAqrVG/4mnEeibVuCJcg=="], @@ -2553,6 +2666,8 @@ "tiny-warning": ["tiny-warning@1.0.3", "", {}, "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="], + "tiny-worker": ["tiny-worker@2.3.0", "", { "dependencies": { "esm": "^3.2.25" } }, "sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g=="], + "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="], "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], @@ -2833,6 +2948,8 @@ "@radix-ui/react-tabs/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + "@react-native/codegen/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "@react-native/codegen/hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="], "@react-native/dev-middleware/open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="], @@ -2863,10 +2980,10 @@ "@tanstack/devtools-event-bus/ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], - "@tanstack/router-generator/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], - "@tanstack/router-generator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "@tanstack/router-plugin/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], + "@tanstack/router-plugin/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], @@ -2881,8 +2998,6 @@ "better-opn/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], - "bl/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], - "body-parser/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], "burnt/sf-symbols-typescript": ["sf-symbols-typescript@1.0.0", "", {}, "sha512-DkS7q3nN68dEMb4E18HFPDAvyrjDZK9YAQQF2QxeFu9gp2xRDXFMF8qLJ1EmQ/qeEGQmop4lmMM1WtYJTIcCMw=="], @@ -2891,8 +3006,6 @@ "chromium-edge-launcher/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], - "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "compression/negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], @@ -2907,8 +3020,6 @@ "eciesjs/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], - "error-ex/is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], - "expo-modules-autolinking/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], @@ -2929,6 +3040,8 @@ "express/serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], + "figures/is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "finalhandler/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="], @@ -2937,8 +3050,6 @@ "finalhandler/statuses": ["statuses@1.5.0", "", {}, "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA=="], - "glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], "hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], @@ -2963,7 +3074,7 @@ "lighthouse-logger/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], - "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], + "log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "metro/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -2975,14 +3086,20 @@ "metro/metro-source-map": ["metro-source-map@0.83.3", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.3", "nullthrows": "^1.1.1", "ob1": "0.83.3", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg=="], + "metro/source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="], + "metro-babel-transformer/hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="], "metro-config/metro-runtime": ["metro-runtime@0.83.3", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw=="], "metro-source-map/metro-symbolicate": ["metro-symbolicate@0.83.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.5", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-EMIkrjNRz/hF+p0RDdxoE60+dkaTLPN3vaaGkFmX5lvFdO6HPfHA/Ywznzkev+za0VhPQ5KSdz49/MALBRteHA=="], + "metro-source-map/source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="], + "metro-symbolicate/metro-source-map": ["metro-source-map@0.83.3", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.3", "nullthrows": "^1.1.1", "ob1": "0.83.3", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg=="], + "metro-symbolicate/source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="], + "metro-transform-worker/metro-source-map": ["metro-source-map@0.83.3", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.3", "nullthrows": "^1.1.1", "ob1": "0.83.3", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg=="], "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], @@ -2995,7 +3112,7 @@ "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], - "ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "ora/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "parse-png/pngjs": ["pngjs@3.4.0", "", {}, "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="], @@ -3009,19 +3126,27 @@ "raw-body/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + "react-native/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + + "react-native/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "react-native/memoize-one": ["memoize-one@5.2.1", "", {}, "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="], + "react-native-reanimated/react-native-is-edge-to-edge": ["react-native-is-edge-to-edge@1.2.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q=="], "react-native-reanimated/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "react-native-worklets/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + "readable-web-to-node-stream/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - "restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + "restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.9", "", {}, "sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw=="], @@ -3031,22 +3156,28 @@ "shadcn/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], + "shadcn/cosmiconfig": ["cosmiconfig@9.0.1", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ=="], + + "shadcn/ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], + "shadcn/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "simple-plist/bplist-parser": ["bplist-parser@0.3.1", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA=="], + "simple-swizzle/is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="], + "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], "stacktrace-parser/type-fest": ["type-fest@0.7.1", "", {}, "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg=="], - "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "tedious/bl": ["bl@6.1.6", "", { "dependencies": { "@types/readable-stream": "^4.0.0", "buffer": "^6.0.3", "inherits": "^2.0.4", "readable-stream": "^4.2.0" } }, "sha512-jLsPgN/YSvPUg9UX0Kd73CXpm2Psg9FxMeCSXnk3WBO3CMT10JMwijubhGfHCnFu6TPn1ei3b975dxv7K2pWVg=="], "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + "test-exclude/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "test-exclude/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "tsx/esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="], @@ -3057,8 +3188,6 @@ "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "write-file-atomic/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], "xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], @@ -3123,6 +3252,8 @@ "@expo/metro/metro-source-map/ob1": ["ob1@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA=="], + "@expo/metro/metro-source-map/source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="], + "@expo/package-manager/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "@expo/package-manager/ora/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="], @@ -3137,8 +3268,6 @@ "@inquirer/core/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "@inquirer/core/wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], "@jest/transform/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], @@ -3163,6 +3292,8 @@ "@radix-ui/react-tabs/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "@react-native/codegen/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "@react-native/codegen/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="], "@react-native/dev-middleware/open/is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], @@ -3217,6 +3348,8 @@ "@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@0.0.0-insiders.f302fce", "", { "os": "win32", "cpu": "x64" }, "sha512-adNYa9ocX3S18F5KD8FIEn+KD2pjVdlgHJbiPZmryhGPVwyu4pBrLeCnPxH8GX8IdbG3OWZVz393mNW5MdjrJQ=="], + "@tanstack/router-plugin/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], + "babel-jest/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "babel-plugin-syntax-hermes-parser/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="], @@ -3245,8 +3378,6 @@ "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], - "glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - "isomorphic-fetch/node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], "jest-message-util/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], @@ -3257,12 +3388,16 @@ "lighthouse-logger/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "log-symbols/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "metro-babel-transformer/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="], "metro-symbolicate/metro-source-map/ob1": ["ob1@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA=="], "metro-transform-worker/metro-source-map/ob1": ["ob1@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA=="], + "metro-transform-worker/metro-source-map/source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="], + "metro/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "metro/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="], @@ -3271,10 +3406,32 @@ "node-vibrant/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], - "ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "ora/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "react-native/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "readable-web-to-node-stream/readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + + "rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "shadcn/ora/cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], + + "shadcn/ora/is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="], + + "shadcn/ora/is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + + "shadcn/ora/log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], + + "shadcn/ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "shadcn/ora/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "tedious/bl/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + + "tedious/bl/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], + "test-exclude/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="], @@ -3373,6 +3530,8 @@ "@istanbuljs/load-nyc-config/js-yaml/argparse/sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], + "@react-native/codegen/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + "@tailwindcss/vite/@tailwindcss/node/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="], "@tailwindcss/vite/@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="], @@ -3407,12 +3566,24 @@ "@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "@tanstack/router-plugin/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "isomorphic-fetch/node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], "isomorphic-fetch/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + "react-native/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "shadcn/ora/cli-cursor/restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], + + "shadcn/ora/log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], + + "shadcn/ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "shadcn/ora/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "test-exclude/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "@expo/cli/ora/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], @@ -3431,6 +3602,14 @@ "@expo/package-manager/ora/cli-cursor/restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "@react-native/codegen/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "react-native/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "shadcn/ora/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + "@expo/cli/ora/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], "@expo/cli/ora/cli-cursor/restore-cursor/onetime/mimic-fn": ["mimic-fn@1.2.0", "", {}, "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="], diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 0000000..a782717 --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,12 @@ +project_id: "881052" +api_token_env: CROWDIN_PERSONAL_TOKEN + +preserve_hierarchy: true + +files: + - source: /packages/i18n/src/po/en.po + translation: /packages/i18n/src/po/%two_letters_code%.po + languages_mapping: + two_letters_code: + es-ES: es + pt-PT: pt diff --git a/docs/public/openapi.json b/docs/public/openapi.json index 9390882..e7a99a5 100644 --- a/docs/public/openapi.json +++ b/docs/public/openapi.json @@ -1691,13 +1691,24 @@ "type": "string", "default": "Title not found" }, - "data": {} + "data": { + "type": "object", + "properties": { + "code": { + "const": "TITLE_NOT_FOUND" + } + }, + "required": [ + "code" + ] + } }, "required": [ "defined", "code", "status", - "message" + "message", + "data" ] }, { @@ -2161,13 +2172,24 @@ "type": "string", "default": "Title not found" }, - "data": {} + "data": { + "type": "object", + "properties": { + "code": { + "const": "TITLE_NOT_FOUND" + } + }, + "required": [ + "code" + ] + } }, "required": [ "defined", "code", "status", - "message" + "message", + "data" ] }, { @@ -2547,13 +2569,24 @@ "type": "string", "default": "Person not found" }, - "data": {} + "data": { + "type": "object", + "properties": { + "code": { + "const": "PERSON_NOT_FOUND" + } + }, + "required": [ + "code" + ] + } }, "required": [ "defined", "code", "status", - "message" + "message", + "data" ] }, { @@ -3325,13 +3358,24 @@ "type": "string", "default": "TMDB API key is not configured" }, - "data": {} + "data": { + "type": "object", + "properties": { + "code": { + "const": "TMDB_NOT_CONFIGURED" + } + }, + "required": [ + "code" + ] + } }, "required": [ "defined", "code", "status", - "message" + "message", + "data" ] }, { @@ -3509,13 +3553,24 @@ "type": "string", "default": "TMDB API key is not configured" }, - "data": {} + "data": { + "type": "object", + "properties": { + "code": { + "const": "TMDB_NOT_CONFIGURED" + } + }, + "required": [ + "code" + ] + } }, "required": [ "defined", "code", "status", - "message" + "message", + "data" ] }, { @@ -3640,13 +3695,24 @@ "type": "string", "default": "TMDB API key is not configured" }, - "data": {} + "data": { + "type": "object", + "properties": { + "code": { + "const": "TMDB_NOT_CONFIGURED" + } + }, + "required": [ + "code" + ] + } }, "required": [ "defined", "code", "status", - "message" + "message", + "data" ] }, { @@ -4587,13 +4653,24 @@ "type": "string", "default": "Integration not found" }, - "data": {} + "data": { + "type": "object", + "properties": { + "code": { + "const": "INTEGRATION_NOT_FOUND" + } + }, + "required": [ + "code" + ] + } }, "required": [ "defined", "code", "status", - "message" + "message", + "data" ] }, { @@ -4753,13 +4830,24 @@ "type": "string", "default": "Failed to delete backup" }, - "data": {} + "data": { + "type": "object", + "properties": { + "code": { + "const": "BACKUP_DELETE_FAILED" + } + }, + "required": [ + "code" + ] + } }, "required": [ "defined", "code", "status", - "message" + "message", + "data" ] }, { @@ -4813,13 +4901,24 @@ "type": "string", "default": "Backup not found" }, - "data": {} + "data": { + "type": "object", + "properties": { + "code": { + "const": "BACKUP_NOT_FOUND" + } + }, + "required": [ + "code" + ] + } }, "required": [ "defined", "code", "status", - "message" + "message", + "data" ] }, { @@ -4912,13 +5011,24 @@ "type": "string", "default": "Backup restoration failed" }, - "data": {} + "data": { + "type": "object", + "properties": { + "code": { + "const": "BACKUP_RESTORE_FAILED" + } + }, + "required": [ + "code" + ] + } }, "required": [ "defined", "code", "status", - "message" + "message", + "data" ] }, { @@ -5458,13 +5568,24 @@ "type": "string", "default": "Job not found" }, - "data": {} + "data": { + "type": "object", + "properties": { + "code": { + "const": "JOB_NOT_FOUND" + } + }, + "required": [ + "code" + ] + } }, "required": [ "defined", "code", "status", - "message" + "message", + "data" ] }, { @@ -6037,6 +6158,77 @@ } } } + }, + "400": { + "description": "400", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "properties": { + "defined": { + "const": true + }, + "code": { + "const": "BAD_REQUEST" + }, + "status": { + "const": 400 + }, + "message": { + "type": "string", + "default": "Invalid import file" + }, + "data": { + "type": "object", + "properties": { + "code": { + "const": "IMPORT_INVALID_FILE" + } + }, + "required": [ + "code" + ] + } + }, + "required": [ + "defined", + "code", + "status", + "message", + "data" + ] + }, + { + "type": "object", + "properties": { + "defined": { + "const": false + }, + "code": { + "type": "string" + }, + "status": { + "type": "number" + }, + "message": { + "type": "string" + }, + "data": {} + }, + "required": [ + "defined", + "code", + "status", + "message" + ] + } + ] + } + } + } } }, "security": [ @@ -6848,6 +7040,148 @@ } } } + }, + "400": { + "description": "400", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "properties": { + "defined": { + "const": true + }, + "code": { + "const": "BAD_REQUEST" + }, + "status": { + "const": 400 + }, + "message": { + "type": "string", + "default": "Import payload too large" + }, + "data": { + "type": "object", + "properties": { + "code": { + "const": "IMPORT_PAYLOAD_TOO_LARGE" + } + }, + "required": [ + "code" + ] + } + }, + "required": [ + "defined", + "code", + "status", + "message", + "data" + ] + }, + { + "type": "object", + "properties": { + "defined": { + "const": false + }, + "code": { + "type": "string" + }, + "status": { + "type": "number" + }, + "message": { + "type": "string" + }, + "data": {} + }, + "required": [ + "defined", + "code", + "status", + "message" + ] + } + ] + } + } + } + }, + "409": { + "description": "409", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "properties": { + "defined": { + "const": true + }, + "code": { + "const": "CONFLICT" + }, + "status": { + "const": 409 + }, + "message": { + "type": "string", + "default": "An import is already in progress" + }, + "data": { + "type": "object", + "properties": { + "code": { + "const": "IMPORT_ALREADY_RUNNING" + } + }, + "required": [ + "code" + ] + } + }, + "required": [ + "defined", + "code", + "status", + "message", + "data" + ] + }, + { + "type": "object", + "properties": { + "defined": { + "const": false + }, + "code": { + "type": "string" + }, + "status": { + "type": "number" + }, + "message": { + "type": "string" + }, + "data": {} + }, + "required": [ + "defined", + "code", + "status", + "message" + ] + } + ] + } + } + } } }, "security": [ @@ -7132,6 +7466,77 @@ } } } + }, + "400": { + "description": "400", + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "properties": { + "defined": { + "const": true + }, + "code": { + "const": "BAD_REQUEST" + }, + "status": { + "const": 400 + }, + "message": { + "type": "string", + "default": "Import cannot be cancelled" + }, + "data": { + "type": "object", + "properties": { + "code": { + "const": "IMPORT_CANNOT_CANCEL" + } + }, + "required": [ + "code" + ] + } + }, + "required": [ + "defined", + "code", + "status", + "message", + "data" + ] + }, + { + "type": "object", + "properties": { + "defined": { + "const": false + }, + "code": { + "type": "string" + }, + "status": { + "type": "number" + }, + "message": { + "type": "string" + }, + "data": {} + }, + "required": [ + "defined", + "code", + "status", + "message" + ] + } + ] + } + } + } } }, "security": [ diff --git a/lingui.config.ts b/lingui.config.ts new file mode 100644 index 0000000..4db3d35 --- /dev/null +++ b/lingui.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from "@lingui/conf"; +import { formatter } from "@lingui/format-po"; + +export default defineConfig({ + locales: ["en", "fr", "de", "es", "it", "pt"], + sourceLocale: "en", + catalogs: [ + { + path: "packages/i18n/src/po/{locale}", + include: ["apps/web/src", "apps/native/src"], + }, + ], + format: formatter(), + compileNamespace: "ts", +}); diff --git a/package.json b/package.json index efe7065..1f78066 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,14 @@ "@types/node": "25.5.0", "@types/react": "19.2.14", "better-auth": "1.5.5", - "date-fns": "4.1.0", + "@lingui/babel-plugin-lingui-macro": "5.9.3", + "@lingui/cli": "5.9.3", + "@lingui/conf": "5.9.3", + "@lingui/core": "5.9.3", + "@lingui/format-po": "5.9.3", + "@lingui/metro-transformer": "5.9.3", + "@lingui/react": "5.9.3", + "@lingui/vite-plugin": "5.9.3", "react": "19.2.0", "react-dom": "19.2.0", "tailwind-merge": "3.5.0", @@ -37,10 +44,15 @@ "format": "turbo run format", "check-types": "turbo run check-types", "test": "turbo run test", - "generate:openapi": "bun scripts/generate-openapi-spec.ts && cd docs && bun run generate:api-docs" + "generate:openapi": "bun scripts/generate-openapi-spec.ts && cd docs && bun run generate:api-docs", + "i18n:extract": "lingui extract", + "i18n:compile": "lingui compile --typescript" }, "devDependencies": { "@biomejs/biome": "2.4.7", + "@lingui/cli": "catalog:", + "@lingui/conf": "catalog:", + "@lingui/format-po": "catalog:", "turbo": "2.8.17", "typescript": "catalog:" } diff --git a/packages/api/package.json b/packages/api/package.json index 9c19b4a..4cc1bbc 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -6,6 +6,7 @@ "exports": { "./contract": "./src/contract.ts", "./schemas": "./src/schemas.ts", + "./errors": "./src/errors.ts", "./utils": "./src/utils.ts" }, "scripts": { diff --git a/packages/api/src/contract.ts b/packages/api/src/contract.ts index 40316fa..4ecb789 100644 --- a/packages/api/src/contract.ts +++ b/packages/api/src/contract.ts @@ -1,5 +1,6 @@ import { eventIterator, oc } from "@orpc/contract"; import { z } from "zod"; +import { AppErrorCode, appErrorData } from "./errors"; import { AuthConfigOutput, BackupCreateOutput, @@ -78,7 +79,10 @@ export const contract = { .input(IdParam) .output(TitleDetailOutput) .errors({ - NOT_FOUND: { message: "Title not found" }, + NOT_FOUND: { + message: "Title not found", + data: appErrorData(AppErrorCode.TITLE_NOT_FOUND), + }, }), updateStatus: oc .route({ @@ -163,7 +167,10 @@ export const contract = { .input(IdParam) .output(QuickAddOutput) .errors({ - NOT_FOUND: { message: "Title not found" }, + NOT_FOUND: { + message: "Title not found", + data: appErrorData(AppErrorCode.TITLE_NOT_FOUND), + }, }), }, episodes: { @@ -238,7 +245,10 @@ export const contract = { .input(IdParam.merge(PaginatedInput)) .output(PersonDetailOutput) .errors({ - NOT_FOUND: { message: "Person not found" }, + NOT_FOUND: { + message: "Person not found", + data: appErrorData(AppErrorCode.PERSON_NOT_FOUND), + }, }), }, dashboard: { @@ -315,7 +325,10 @@ export const contract = { .input(TrendingTypeParam.merge(PageParam)) .output(TrendingOutput) .errors({ - PRECONDITION_FAILED: { message: "TMDB API key is not configured" }, + PRECONDITION_FAILED: { + message: "TMDB API key is not configured", + data: appErrorData(AppErrorCode.TMDB_NOT_CONFIGURED), + }, }), popular: oc .route({ @@ -330,7 +343,10 @@ export const contract = { .input(MediaTypeParam.merge(PageParam)) .output(PopularOutput) .errors({ - PRECONDITION_FAILED: { message: "TMDB API key is not configured" }, + PRECONDITION_FAILED: { + message: "TMDB API key is not configured", + data: appErrorData(AppErrorCode.TMDB_NOT_CONFIGURED), + }, }), genres: oc .route({ @@ -345,7 +361,10 @@ export const contract = { .input(MediaTypeParam) .output(GenresOutput) .errors({ - PRECONDITION_FAILED: { message: "TMDB API key is not configured" }, + PRECONDITION_FAILED: { + message: "TMDB API key is not configured", + data: appErrorData(AppErrorCode.TMDB_NOT_CONFIGURED), + }, }), }, search: oc @@ -376,7 +395,10 @@ export const contract = { .input(DiscoverInput) .output(DiscoverOutput) .errors({ - PRECONDITION_FAILED: { message: "TMDB API key is not configured" }, + PRECONDITION_FAILED: { + message: "TMDB API key is not configured", + data: appErrorData(AppErrorCode.TMDB_NOT_CONFIGURED), + }, }), system: { publicInfo: oc @@ -461,7 +483,10 @@ export const contract = { .input(ProviderParam) .output(IntegrationOutput) .errors({ - NOT_FOUND: { message: "Integration not found" }, + NOT_FOUND: { + message: "Integration not found", + data: appErrorData(AppErrorCode.INTEGRATION_NOT_FOUND), + }, }), }, admin: { @@ -500,8 +525,14 @@ export const contract = { .input(FilenameParam) .output(z.void()) .errors({ - NOT_FOUND: { message: "Backup not found" }, - BAD_REQUEST: { message: "Failed to delete backup" }, + NOT_FOUND: { + message: "Backup not found", + data: appErrorData(AppErrorCode.BACKUP_NOT_FOUND), + }, + BAD_REQUEST: { + message: "Failed to delete backup", + data: appErrorData(AppErrorCode.BACKUP_DELETE_FAILED), + }, }), restore: oc .route({ @@ -515,7 +546,10 @@ export const contract = { .input(RestoreBackupInput) .output(z.void()) .errors({ - BAD_REQUEST: { message: "Backup restoration failed" }, + BAD_REQUEST: { + message: "Backup restoration failed", + data: appErrorData(AppErrorCode.BACKUP_RESTORE_FAILED), + }, }), schedule: oc .route({ @@ -616,7 +650,10 @@ export const contract = { .input(TriggerJobInput) .output(TriggerJobOutput) .errors({ - NOT_FOUND: { message: "Job not found" }, + NOT_FOUND: { + message: "Job not found", + data: appErrorData(AppErrorCode.JOB_NOT_FOUND), + }, }), purgeMetadataCache: oc .route({ @@ -701,7 +738,13 @@ export const contract = { successDescription: "Preview of importable items with counts", }) .input(ParseFileInput) - .output(ImportPreviewSchema), + .output(ImportPreviewSchema) + .errors({ + BAD_REQUEST: { + message: "Invalid import file", + data: appErrorData(AppErrorCode.IMPORT_INVALID_FILE), + }, + }), parsePayload: oc .route({ method: "POST", @@ -725,7 +768,17 @@ export const contract = { successDescription: "Created import job", }) .input(CreateImportJobInput) - .output(ImportJobSchema), + .output(ImportJobSchema) + .errors({ + BAD_REQUEST: { + message: "Import payload too large", + data: appErrorData(AppErrorCode.IMPORT_PAYLOAD_TOO_LARGE), + }, + CONFLICT: { + message: "An import is already in progress", + data: appErrorData(AppErrorCode.IMPORT_ALREADY_RUNNING), + }, + }), getJob: oc .route({ method: "GET", @@ -748,7 +801,13 @@ export const contract = { successDescription: "Updated job state", }) .input(IdParam) - .output(ImportJobSchema), + .output(ImportJobSchema) + .errors({ + BAD_REQUEST: { + message: "Import cannot be cancelled", + data: appErrorData(AppErrorCode.IMPORT_CANNOT_CANCEL), + }, + }), jobEvents: oc .route({ method: "GET", diff --git a/packages/api/src/errors.ts b/packages/api/src/errors.ts new file mode 100644 index 0000000..fb6b6e7 --- /dev/null +++ b/packages/api/src/errors.ts @@ -0,0 +1,23 @@ +import { z } from "zod"; + +export const AppErrorCode = { + TITLE_NOT_FOUND: "TITLE_NOT_FOUND", + PERSON_NOT_FOUND: "PERSON_NOT_FOUND", + INTEGRATION_NOT_FOUND: "INTEGRATION_NOT_FOUND", + BACKUP_NOT_FOUND: "BACKUP_NOT_FOUND", + BACKUP_DELETE_FAILED: "BACKUP_DELETE_FAILED", + BACKUP_RESTORE_FAILED: "BACKUP_RESTORE_FAILED", + JOB_NOT_FOUND: "JOB_NOT_FOUND", + TMDB_NOT_CONFIGURED: "TMDB_NOT_CONFIGURED", + IMPORT_INVALID_FILE: "IMPORT_INVALID_FILE", + IMPORT_PAYLOAD_TOO_LARGE: "IMPORT_PAYLOAD_TOO_LARGE", + IMPORT_ALREADY_RUNNING: "IMPORT_ALREADY_RUNNING", + IMPORT_CANNOT_CANCEL: "IMPORT_CANNOT_CANCEL", + REGISTRATION_CLOSED: "REGISTRATION_CLOSED", +} as const; + +export type AppErrorCode = (typeof AppErrorCode)[keyof typeof AppErrorCode]; + +/** Typed error data schema for use in contract `.errors()` */ +export const appErrorData = (code: T) => + z.object({ code: z.literal(code) }); diff --git a/packages/core/package.json b/packages/core/package.json index baeac52..82555c1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -38,7 +38,6 @@ "@sofa/logger": "workspace:*", "@sofa/tmdb": "workspace:*", "adm-zip": "0.5.16", - "date-fns": "catalog:", "node-vibrant": "4.0.4", "sharp": "0.34.5", "thumbhash": "catalog:", diff --git a/packages/core/src/backup.ts b/packages/core/src/backup.ts index 8afa4ad..aedaef2 100644 --- a/packages/core/src/backup.ts +++ b/packages/core/src/backup.ts @@ -7,7 +7,11 @@ import { closeDatabase, db } from "@sofa/db/client"; import { sql } from "@sofa/db/helpers"; import { runMigrations } from "@sofa/db/migrate"; import { createLogger } from "@sofa/logger"; -import { format } from "date-fns"; + +function formatTimestamp(date: Date): string { + const p = (n: number, len = 2) => String(n).padStart(len, "0"); + return `${date.getFullYear()}-${p(date.getMonth() + 1)}-${p(date.getDate())}-${p(date.getHours())}${p(date.getMinutes())}${p(date.getSeconds())}${p(date.getMilliseconds(), 3)}`; +} const log = createLogger("backup"); @@ -138,7 +142,7 @@ export function isValidBackupFilename(filename: string): boolean { async function createBackupInternal(prefix: BackupPrefix): Promise { await ensureBackupDir(); - const timestamp = format(new Date(), "yyyy-MM-dd-HHmmssSSS"); + const timestamp = formatTimestamp(new Date()); const filename = `${prefix}-${timestamp}.db`; const dest = path.join(BACKUP_DIR, filename); @@ -223,7 +227,7 @@ export async function restoreFromBackup( const dbDir = path.dirname(DATABASE_URL); await mkdir(dbDir, { recursive: true }); - const timestamp = format(new Date(), "yyyy-MM-dd-HHmmssSSS"); + const timestamp = formatTimestamp(new Date()); const tempPath = path.join( dbDir, `.restore-temp-${timestamp}-${crypto.randomUUID()}.db`, diff --git a/packages/i18n/package.json b/packages/i18n/package.json new file mode 100644 index 0000000..e4428c6 --- /dev/null +++ b/packages/i18n/package.json @@ -0,0 +1,28 @@ +{ + "name": "@sofa/i18n", + "version": "0.1.0", + "private": true, + "type": "module", + "exports": { + ".": "./src/index.ts", + "./locales": "./src/locales.ts", + "./format": "./src/format.ts", + "./test-utils": "./src/test-utils.tsx" + }, + "scripts": { + "lint": "biome check", + "format": "biome format --write", + "check-types": "tsc --noEmit" + }, + "dependencies": { + "@lingui/core": "catalog:", + "@lingui/react": "catalog:" + }, + "devDependencies": { + "@lingui/cli": "catalog:", + "@lingui/conf": "catalog:", + "@lingui/format-po": "catalog:", + "@types/bun": "catalog:", + "typescript": "catalog:" + } +} diff --git a/packages/i18n/src/format.ts b/packages/i18n/src/format.ts new file mode 100644 index 0000000..102a38c --- /dev/null +++ b/packages/i18n/src/format.ts @@ -0,0 +1,70 @@ +import { i18n } from "./index"; + +/** Whether a string looks like a date-only value (no time component). */ +function isDateOnly(value: string): boolean { + return /^\d{4}-\d{2}-\d{2}$/.test(value); +} + +function toDate(date: Date | string): Date { + return typeof date === "string" ? new Date(date) : date; +} + +export function formatDate( + date: Date | string, + options?: Intl.DateTimeFormatOptions, +): string { + // Date-only ISO strings (e.g. "1990-05-15") are parsed as UTC midnight. + // Format in UTC so users west of UTC don't see the previous day. + const useUTC = + typeof date === "string" && isDateOnly(date) && !options?.timeZone; + return new Intl.DateTimeFormat(i18n.locale, { + year: "numeric", + month: "long", + day: "numeric", + ...(useUTC ? { timeZone: "UTC" } : {}), + ...options, + }).format(toDate(date)); +} + +export function formatShortDate(date: Date | string): string { + return new Intl.DateTimeFormat(i18n.locale, { + month: "short", + day: "numeric", + year: "numeric", + }).format(toDate(date)); +} + +export function formatRelativeTime(date: Date | string): string { + const d = toDate(date); + const now = Date.now(); + const diffMs = d.getTime() - now; + const absSec = Math.abs(Math.round(diffMs / 1000)); + const absMin = Math.abs(Math.round(diffMs / 60_000)); + const absHour = Math.abs(Math.round(diffMs / 3_600_000)); + const absDay = Math.abs(Math.round(diffMs / 86_400_000)); + const sign = diffMs < 0 ? -1 : 1; + + const rtf = new Intl.RelativeTimeFormat(i18n.locale, { numeric: "auto" }); + if (absSec < 60) return rtf.format(sign * absSec, "second"); + if (absMin < 60) return rtf.format(sign * absMin, "minute"); + if (absHour < 24) return rtf.format(sign * absHour, "hour"); + if (absDay < 30) return rtf.format(sign * absDay, "day"); + if (absDay < 365) return rtf.format(sign * Math.round(absDay / 30), "month"); + return rtf.format(sign * Math.round(absDay / 365), "year"); +} + +export function formatNumber( + n: number, + options?: Intl.NumberFormatOptions, +): string { + return new Intl.NumberFormat(i18n.locale, options).format(n); +} + +export function formatBytes(bytes: number): string { + if (bytes === 0) return "0 B"; + const k = 1024; + const sizes = ["B", "KB", "MB", "GB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + const value = bytes / k ** i; + return `${new Intl.NumberFormat(i18n.locale, { maximumFractionDigits: 1 }).format(value)} ${sizes[i]}`; +} diff --git a/packages/i18n/src/index.ts b/packages/i18n/src/index.ts new file mode 100644 index 0000000..fbd6447 --- /dev/null +++ b/packages/i18n/src/index.ts @@ -0,0 +1,34 @@ +import { i18n, type Messages } from "@lingui/core"; +import { messages as enMessages } from "./po/en"; + +export const SUPPORTED_LOCALES = ["en", "fr", "de", "es", "it", "pt"] as const; +export type SupportedLocale = (typeof SUPPORTED_LOCALES)[number]; + +// English always bundled — zero async on default locale +i18n.load("en", enMessages); +i18n.activate("en"); + +const loaders: Record Promise<{ messages: Messages }>> = { + fr: () => import("./po/fr"), + de: () => import("./po/de"), + es: () => import("./po/es"), + it: () => import("./po/it"), + pt: () => import("./po/pt"), +}; + +export async function activateLocale(locale: SupportedLocale): Promise { + if (locale === "en") { + i18n.activate("en"); + return; + } + const loader = loaders[locale]; + if (!loader) { + i18n.activate("en"); + return; + } + const { messages } = await loader(); + i18n.load(locale, messages); + i18n.activate(locale); +} + +export { i18n }; diff --git a/packages/i18n/src/locales.ts b/packages/i18n/src/locales.ts new file mode 100644 index 0000000..b3278e3 --- /dev/null +++ b/packages/i18n/src/locales.ts @@ -0,0 +1,16 @@ +import type { SupportedLocale } from "./index"; + +export interface LocaleInfo { + code: SupportedLocale; + name: string; + nativeName: string; +} + +export const LOCALE_INFO: LocaleInfo[] = [ + { code: "en", name: "English", nativeName: "English" }, + { code: "fr", name: "French", nativeName: "Fran\u00e7ais" }, + { code: "de", name: "German", nativeName: "Deutsch" }, + { code: "es", name: "Spanish", nativeName: "Espa\u00f1ol" }, + { code: "it", name: "Italian", nativeName: "Italiano" }, + { code: "pt", name: "Portuguese", nativeName: "Portugu\u00eas" }, +]; diff --git a/packages/i18n/src/po/de.po b/packages/i18n/src/po/de.po new file mode 100644 index 0000000..d0af23e --- /dev/null +++ b/packages/i18n/src/po/de.po @@ -0,0 +1,2433 @@ +msgid "" +msgstr "" +"POT-Creation-Date: 2026-03-17 21:04-0400\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: @lingui/cli\n" +"Language: de\n" +"Project-Id-Version: sofa\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2026-03-18 16:04\n" +"Last-Translator: \n" +"Language-Team: German\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: sofa\n" +"X-Crowdin-Project-ID: 881052\n" +"X-Crowdin-Language: de\n" +"X-Crowdin-File: /packages/i18n/src/po/en.po\n" +"X-Crowdin-File-ID: 12\n" + +#. placeholder {0}: result.errors.length - 50 +#: apps/web/src/components/settings/imports-section.tsx:938 +msgid "...and {0} more" +msgstr "" + +#. placeholder {0}: systemHealth.data.imageCache.imageCount +#: apps/native/src/app/(tabs)/(settings)/index.tsx:482 +msgid "{0, plural, one {# image} other {# images}}" +msgstr "" + +#. placeholder {0}: systemHealth.data.database.titleCount +#: apps/native/src/app/(tabs)/(settings)/index.tsx:466 +msgid "{0, plural, one {# title} other {# titles}}" +msgstr "" + +#. placeholder {0}: displayBackups.length +#. placeholder {1}: displayBackups.length +#: apps/web/src/components/settings/backup-section.tsx:109 +msgid "{0} {1, plural, one {backup} other {backups}} stored" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:105 +#~ msgid "{0} backup{1} stored" +#~ msgstr "{0} backup{1} stored" + +#. placeholder {0}: backups.backupCount +#: apps/web/src/components/settings/system-health-section.tsx:654 +msgid "{0} backups · last <0/>" +msgstr "" + +#. placeholder {0}: imageCache.imageCount.toLocaleString() +#: apps/web/src/components/settings/system-health-section.tsx:621 +msgid "{0} cached images" +msgstr "" + +#. placeholder {0}: member.episodeCount +#. placeholder {1}: member.episodeCount !== 1 ? "s" : "" +#: apps/web/src/components/titles/cast-carousel.tsx:80 +msgid "{0} ep{1}" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:472 +#~ msgid "{0} images" +#~ msgstr "{0} images" + +#. placeholder {0}: stats.watchlist +#: apps/web/src/components/settings/imports-section.tsx:808 +msgid "{0} items" +msgstr "" + +#. placeholder {0}: preview.diagnostics.unresolved +#: apps/web/src/components/settings/imports-section.tsx:788 +msgid "{0} items have no external IDs and will be resolved by title search, which may be less accurate." +msgstr "" + +#. placeholder {0}: stats.movies +#. placeholder {1}: stats.episodes +#: apps/web/src/components/settings/imports-section.tsx:802 +msgid "{0} movies, {1} episodes" +msgstr "" + +#. placeholder {0}: stats.ratings +#: apps/web/src/components/settings/imports-section.tsx:814 +msgid "{0} ratings" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:456 +#~ msgid "{0} titles" +#~ msgstr "{0} titles" + +#. placeholder {0}: JOB_LABELS[name] ?? name +#: apps/web/src/components/settings/system-health-section.tsx:343 +msgid "{0} triggered" +msgstr "" + +#. placeholder {0}: item.watchedEpisodes +#. placeholder {1}: item.totalEpisodes +#. placeholder {2}: item.totalEpisodes +#: apps/web/src/components/dashboard/continue-watching-card.tsx:93 +msgid "{0}/{1} {2, plural, one {episode} other {episodes}}" +msgstr "" + +#: apps/web/src/components/dashboard/continue-watching-card.tsx:92 +#~ msgid "{0}/{1} episodes" +#~ msgstr "{0}/{1} episodes" + +#: apps/web/src/components/titles/use-title-actions.ts:221 +#: apps/web/src/components/titles/use-title-actions.ts:299 +msgid "{count} earlier {count, plural, one {episode} other {episodes}} unwatched" +msgstr "" + +#. placeholder {0}: activeJobs.length +#: apps/web/src/components/settings/system-health-section.tsx:385 +msgid "{healthyCount} of {0} jobs healthy" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:96 +#: apps/web/src/components/settings/integration-card.tsx:106 +msgid "{label} connected" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:106 +#: apps/web/src/components/settings/integration-card.tsx:122 +msgid "{label} disconnected" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:116 +#: apps/web/src/components/settings/integration-card.tsx:138 +msgid "{label} URL regenerated" +msgstr "" + +#: apps/web/src/components/title-card.tsx:166 +msgid "{watched}/{total} episodes" +msgstr "" + +#. placeholder {0}: episodes.length +#: apps/native/src/components/titles/season-accordion.tsx:122 +msgid "{watchedCount} of {0} episodes watched" +msgstr "" + +#. placeholder {0}: episodes.length +#. placeholder {1}: episodes.length +#: apps/native/src/components/titles/season-accordion.tsx:131 +msgid "{watchedCount}/{0} {1, plural, one {episode} other {episodes}}" +msgstr "" + +#: apps/native/src/components/titles/season-accordion.tsx:130 +#~ msgid "{watchedCount}/{0} episodes" +#~ msgstr "{watchedCount}/{0} episodes" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:231 +#: apps/web/src/components/settings/account-section.tsx:175 +msgid "Account" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:412 +msgid "Actions" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:71 +msgid "Actor" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:72 +msgid "Add a \"Generic Destination\" and paste the URL above" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:131 +msgid "Add a <0>Generic Destination and paste the URL above" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:92 +#: apps/web/src/components/settings/integration-configs.tsx:198 +msgid "Add a new webhook and paste the URL above" +msgstr "" + +#: apps/web/src/components/explore/hero-banner.tsx:99 +msgid "Add to Library" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:72 +msgid "Add to watchlist" +msgstr "" + +#: apps/native/src/components/explore/hero-banner.tsx:157 +#: apps/native/src/components/ui/poster-card.tsx:238 +#: apps/web/src/components/title-card.tsx:146 +msgid "Add to Watchlist" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:23 +msgid "Added \"{titleName}\" to watchlist" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:44 +#: apps/native/src/hooks/use-title-actions.ts:60 +#: apps/native/src/lib/title-actions.ts:24 +#: apps/web/src/components/titles/use-title-actions.ts:111 +msgid "Added to watchlist" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:353 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:454 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:499 +#: apps/web/src/components/nav-bar.tsx:235 +#: apps/web/src/components/settings/account-section.tsx:335 +msgid "Admin" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:124 +msgid "Admin only" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:308 +#: apps/web/src/components/people/person-hero.tsx:91 +msgid "age {age}" +msgstr "" + +#: apps/native/src/components/explore/filterable-title-row.tsx:119 +#: apps/web/src/components/people/filmography-grid.tsx:58 +msgid "All" +msgstr "" + +#: apps/web/src/components/settings/registration-section.tsx:61 +msgid "Allow new users to create accounts" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:278 +msgid "Already have an account?" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:239 +msgid "Already have an account? Sign in" +msgstr "" + +#: apps/web/src/routes/__root.tsx:146 +msgid "An unexpected error occurred while loading this page. You can try again or head back to the dashboard." +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:427 +msgid "Anonymous usage reporting" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:102 +msgid "App Settings" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:378 +msgid "Application" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:151 +msgid "Are you sure you want to disconnect {label}? The current URL will stop working." +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:206 +#: apps/native/src/components/header-avatar.tsx:70 +msgid "Are you sure you want to sign out?" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:450 +msgid "Authorization succeeded but failed to fetch your library. Please try again." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:440 +msgid "Authorization was denied. Please try again." +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:611 +msgid "Authorize Sofa to read your {0} library. No password shared." +msgstr "" + +#: apps/web/src/components/settings/update-check-section.tsx:60 +msgid "Automatic update checks" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:279 +msgid "Automatically back up your database on a schedule" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:332 +msgid "Availability" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:104 +msgid "Back to Login" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:382 +msgid "Background jobs" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:336 +msgid "Backup" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:69 +msgid "Backup created" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:94 +msgid "Backup deleted" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:234 +msgid "Backup schedule" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:641 +#: apps/web/src/routes/_app/settings.tsx:166 +msgid "Backups" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:272 +#~ msgid "backups." +#~ msgstr "backups." + +#: apps/web/src/components/titles/title-availability.tsx:139 +msgid "Buy" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:89 +msgid "Cache management" +msgstr "" + +#: apps/native/src/components/ui/server-unreachable-banner.ios.tsx:66 +#: apps/native/src/components/ui/server-unreachable-banner.tsx:74 +msgid "Can't reach server" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:207 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:332 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:388 +#: apps/native/src/components/header-avatar.tsx:71 +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:63 +#: apps/native/src/components/search/recently-viewed-list.tsx:37 +#: apps/native/src/components/settings/integration-card.tsx:138 +#: apps/native/src/components/settings/integration-card.tsx:153 +#: apps/web/src/components/settings/account-section.tsx:505 +#: apps/web/src/components/settings/backup-restore-section.tsx:107 +#: apps/web/src/components/settings/backup-section.tsx:270 +#: apps/web/src/components/settings/danger-section.tsx:129 +#: apps/web/src/components/settings/danger-section.tsx:171 +#: apps/web/src/components/settings/danger-section.tsx:214 +#: apps/web/src/components/settings/imports-section.tsx:645 +#: apps/web/src/components/settings/imports-section.tsx:710 +#: apps/web/src/components/settings/imports-section.tsx:837 +#: apps/web/src/components/titles/title-seasons.tsx:133 +msgid "Cancel" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:307 +msgid "Cancel editing" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:559 +#: apps/web/src/components/titles/cast-carousel.tsx:21 +msgid "Cast" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:223 +#: apps/web/src/components/titles/use-title-actions.ts:301 +msgid "Catch up" +msgstr "" + +#. placeholder {0}: episodeIds.length +#. placeholder {1}: episodeIds.length +#: apps/web/src/components/titles/use-title-actions.ts:82 +msgid "Caught up — marked {0} {1, plural, one {episode} other {episodes}} as watched" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:361 +#: apps/web/src/components/settings/account-section.tsx:427 +#: apps/web/src/components/settings/account-section.tsx:432 +msgid "Change password" +msgstr "" + +#: apps/native/src/app/change-password.tsx:98 +msgid "Change Password" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:267 +msgid "Change Photo" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:385 +msgid "Change Server" +msgstr "" + +#: apps/web/src/components/setup/refresh-button.tsx:30 +msgid "Check configuration" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:513 +msgid "Check for updates" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:215 +msgid "Checked <0/>" +msgstr "" + +#: apps/web/src/components/setup/refresh-button.tsx:28 +msgid "Checking…" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:587 +msgid "Choose how to import your {0} data." +msgstr "" + +#: apps/web/src/components/settings/language-section.tsx:29 +#~ msgid "Choose your preferred display language" +#~ msgstr "Choose your preferred display language" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:65 +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:115 +#: apps/native/src/components/search/recently-viewed-list.tsx:39 +#: apps/native/src/components/search/recently-viewed-list.tsx:95 +msgid "Clear" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:322 +msgid "Clear all" +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:60 +#: apps/native/src/components/search/recently-viewed-list.tsx:34 +msgid "Clear Recently Viewed?" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:109 +#: apps/native/src/components/settings/integration-configs.ts:127 +msgid "Click \"+\" and select \"Custom Lists\"" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:55 +msgid "Click \"Add Webhook\" and paste the URL above" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:237 +#: apps/web/src/components/settings/integration-configs.tsx:277 +msgid "Click <0>+ and select <1>Custom Lists" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:79 +msgid "Click <0>Add Webhook and paste the URL above" +msgstr "" + +#: apps/native/src/components/navigation/modal-stack-header.tsx:14 +msgid "Close" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:63 +#: apps/native/src/components/titles/status-action-button.tsx:44 +#: apps/web/src/components/dashboard/stats-display.tsx:238 +#: apps/web/src/components/title-card.tsx:77 +#: apps/web/src/components/titles/status-button.tsx:30 +msgid "Completed" +msgstr "" + +#: apps/native/src/app/change-password.tsx:173 +#: apps/web/src/components/settings/account-section.tsx:477 +msgid "Confirm new password" +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:207 +#: apps/web/src/components/settings/imports-section.tsx:499 +msgid "Connect" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/integration-card.tsx:203 +msgid "Connect {0}" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:238 +msgid "Connect {label}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:665 +msgid "Connect to {source}" +msgstr "" + +#: apps/web/src/routes/setup.tsx:101 +msgid "Connect to TMDB" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:608 +msgid "Connect with {0}" +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:197 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:474 +#: apps/web/src/components/settings/system-health-section.tsx:256 +msgid "Connected" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:722 +msgid "Connected to {source}" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:240 +msgid "Connected to <0>{serverHost}. Tap to change." +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:186 +msgid "Connecting to server..." +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:201 +msgid "Connecting..." +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:237 +msgid "Connecting…" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:390 +msgid "Continue" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:113 +#: apps/web/src/components/dashboard/continue-watching-section.tsx:25 +msgid "Continue Watching" +msgstr "" + +#: apps/web/src/components/setup/copy-button.tsx:25 +msgid "Copied" +msgstr "" + +#: apps/web/src/components/setup/copy-button.tsx:30 +msgid "Copy" +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:246 +msgid "Copy URL" +msgstr "" + +#: apps/native/src/components/settings/integrations-section.tsx:29 +msgid "Could not load integrations" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:216 +msgid "Could not load person details" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:247 +msgid "Create account" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:115 +#: apps/native/src/app/(auth)/register.tsx:225 +msgid "Create Account" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:216 +msgid "Create an account" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:110 +msgid "Create your account" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:137 +msgid "Creating…" +msgstr "" + +#: apps/native/src/app/change-password.tsx:120 +#: apps/web/src/components/settings/account-section.tsx:449 +msgid "Current password" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:391 +msgid "Current password is required" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:195 +msgid "Danger Zone" +msgstr "" + +#: apps/web/src/routes/__root.tsx:167 +#: apps/web/src/routes/_app/people.$id.tsx:78 +#: apps/web/src/routes/_app/titles.$id.tsx:126 +#: apps/web/src/routes/_app/titles.$id.tsx:172 +msgid "Dashboard" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:463 +#: apps/web/src/components/settings/system-health-section.tsx:229 +msgid "Database" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:126 +msgid "Database backups" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:31 +msgid "Database restored. Reloading..." +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:342 +msgid "Day:" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:250 +#: apps/web/src/components/settings/backup-section.tsx:280 +msgid "Delete" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:235 +msgid "Delete backup" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:256 +msgid "Delete backup?" +msgstr "" + +#. placeholder {0}: data.deletedFiles +#: apps/web/src/components/settings/danger-section.tsx:54 +msgid "Deleted {0, plural, one {# file} other {# files}}, freed {freed}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:412 +#: apps/web/src/components/settings/imports-section.tsx:444 +msgid "Device code expired. Please try again." +msgstr "" + +#: apps/native/src/app/person/[id].tsx:307 +#: apps/web/src/components/people/person-hero.tsx:91 +msgid "died at {age}" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:73 +msgid "Director" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:431 +#: apps/web/src/components/settings/system-health-section.tsx:632 +msgid "Disabled" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:155 +#: apps/native/src/components/settings/integration-card.tsx:298 +#: apps/web/src/components/settings/integration-card.tsx:274 +msgid "Disconnect" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:150 +msgid "Disconnect {label}" +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:242 +msgid "Don't have a server?" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:288 +msgid "Don't have an account?" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:962 +msgid "Done" +msgstr "" + +#: apps/native/src/components/explore/genre-chip.tsx:27 +msgid "Double tap to clear this filter" +msgstr "" + +#: apps/native/src/components/explore/genre-chip.tsx:28 +msgid "Double tap to filter by {label}" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:71 +#: apps/web/src/components/settings/backup-section.tsx:223 +msgid "Download" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:214 +msgid "Download backup" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:79 +msgid "Editor" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:133 +#: apps/native/src/app/(auth)/register.tsx:154 +#: apps/web/src/components/auth-form.tsx:201 +msgid "Email" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:73 +msgid "Enable the \"Playback Stop\" notification type" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:93 +msgid "Enable the \"Playback\" event category" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:140 +msgid "Enable the <0>Playback Stop notification type" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:201 +msgid "Enable the <0>Playback event category" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:668 +msgid "Enter the code below on {source}'s website to authorize Sofa." +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:435 +msgid "Enter your current password and choose a new one." +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:151 +msgid "Enter your Sofa server URL to get started" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:289 +msgid "Environment" +msgstr "" + +#. placeholder {0}: episode.episodeNumber +#: apps/native/src/components/titles/episode-row.tsx:29 +#: apps/native/src/components/titles/episode-row.tsx:55 +msgid "Episode {0}" +msgstr "" + +#. placeholder {0}: episode.episodeNumber +#: apps/native/src/components/titles/episode-row.tsx:36 +msgid "Episode {0}, {episodeLabel}" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:123 +#: apps/native/src/lib/title-actions.ts:111 +msgid "Episode unwatched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:109 +#: apps/native/src/lib/title-actions.ts:101 +msgid "Episode watched" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:780 +#: apps/web/src/components/titles/title-seasons.tsx:102 +msgid "Episodes" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:162 +#~ msgid "Episodes {periodSelect}" +#~ msgstr "Episodes {periodSelect}" + +#: apps/web/src/components/dashboard/stats-display.tsx:164 +msgid "Episodes {select}" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:61 +msgid "Episodes this week" +msgstr "" + +#: apps/native/src/app/change-password.tsx:71 +#: apps/native/src/app/change-password.tsx:81 +msgid "Error" +msgstr "" + +#. placeholder {0}: result.errors.length +#: apps/web/src/components/settings/imports-section.tsx:929 +msgid "Errors ({0})" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:143 +#: apps/web/src/components/nav-bar.tsx:126 +#: apps/web/src/components/nav-bar.tsx:291 +msgid "Explore" +msgstr "" + +#: apps/web/src/routes/_app/people.$id.tsx:70 +#: apps/web/src/routes/_app/titles.$id.tsx:164 +msgid "Explore titles" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:923 +msgid "Failed" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:49 +#: apps/native/src/lib/title-actions.ts:28 +msgid "Failed to add to watchlist" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:90 +msgid "Failed to catch up" +msgstr "" + +#: apps/native/src/app/change-password.tsx:71 +#: apps/web/src/components/settings/account-section.tsx:411 +msgid "Failed to change password" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:398 +msgid "Failed to connect" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:99 +#: apps/web/src/components/settings/integration-card.tsx:108 +msgid "Failed to connect {label}" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:67 +msgid "Failed to create account" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:81 +msgid "Failed to create backup" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:97 +msgid "Failed to delete backup" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:109 +#: apps/web/src/components/settings/integration-card.tsx:125 +msgid "Failed to disconnect {label}" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:215 +msgid "Failed to load backup schedule settings." +msgstr "" + +#: apps/web/src/routes/_app/titles.$id.tsx:115 +msgid "Failed to load title" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:367 +msgid "Failed to mark all episodes as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:84 +#: apps/native/src/lib/title-actions.ts:68 +#: apps/web/src/components/titles/use-title-actions.ts:150 +msgid "Failed to mark as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:113 +#: apps/native/src/lib/title-actions.ts:104 +#: apps/web/src/components/titles/use-title-actions.ts:237 +msgid "Failed to mark episode" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:141 +#: apps/native/src/lib/title-actions.ts:128 +#: apps/web/src/components/titles/use-title-actions.ts:315 +msgid "Failed to mark some episodes" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:225 +msgid "Failed to parse file" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:240 +msgid "Failed to parse import data" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:75 +msgid "Failed to purge caches" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:58 +msgid "Failed to purge image cache" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:45 +msgid "Failed to purge metadata cache" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:119 +#: apps/web/src/components/settings/integration-card.tsx:140 +msgid "Failed to regenerate {label} URL" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:78 +msgid "Failed to remove from library" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:134 +#: apps/web/src/components/settings/account-section.tsx:134 +msgid "Failed to remove profile picture" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:56 +msgid "Failed to sign in" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:388 +msgid "Failed to start {0} connection" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:348 +msgid "Failed to trigger job" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:129 +#: apps/native/src/lib/title-actions.ts:114 +#: apps/web/src/components/titles/use-title-actions.ts:181 +msgid "Failed to unmark episode" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:343 +msgid "Failed to unmark some episodes" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:112 +msgid "Failed to update name" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:101 +#: apps/native/src/lib/title-actions.ts:94 +#: apps/web/src/components/titles/use-title-actions.ts:136 +msgid "Failed to update rating" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:172 +#: apps/web/src/components/settings/registration-section.tsx:44 +msgid "Failed to update registration setting" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:154 +msgid "Failed to update retention setting" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:156 +msgid "Failed to update schedule" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:152 +msgid "Failed to update scheduled backup setting" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:191 +#: apps/web/src/components/settings/update-check-section.tsx:43 +msgid "Failed to update setting" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:72 +#: apps/native/src/lib/title-actions.ts:40 +#: apps/native/src/lib/title-actions.ts:54 +#: apps/web/src/components/titles/use-title-actions.ts:114 +msgid "Failed to update status" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:123 +msgid "Failed to upload avatar" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:725 +msgid "Fetching your library data from {source}..." +msgstr "" + +#: apps/native/src/app/person/[id].tsx:340 +#: apps/web/src/components/people/filmography-grid.tsx:69 +msgid "Filmography" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:915 +msgid "Finished importing from {source}." +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:140 +msgid "Free" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:92 +msgid "Free up disk space by clearing cached metadata and images" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:309 +msgid "Frequency" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:101 +msgid "Friday" +msgstr "" + +#: apps/web/src/components/landing-page.tsx:156 +msgid "Get Started" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:220 +#: apps/native/src/app/person/[id].tsx:247 +#: apps/native/src/app/title/[id].tsx:255 +msgid "Go back" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:37 +msgid "Go Home" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:123 +msgid "Go to <0>Dashboard > Plugins > Webhook" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:368 +msgid "Go to Dashboard" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:71 +msgid "Go to Dashboard > Plugins > Webhook" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:379 +msgid "Go to Explore" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:212 +msgid "Health status" +msgstr "" + +#: apps/web/src/components/dashboard/welcome-header.tsx:14 +msgid "Here's what's happening with your library" +msgstr "" + +#: apps/web/src/components/nav-bar.tsx:125 +#: apps/web/src/components/nav-bar.tsx:290 +msgid "Home" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:335 +#: apps/web/src/components/settings/system-health-section.tsx:610 +msgid "Image cache" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:479 +msgid "Image Cache" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:598 +msgid "Image cache and backup disk usage" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:179 +msgid "Import" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:840 +msgid "Import {totalItems} items" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:912 +msgid "Import complete" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:340 +msgid "Import failed" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:584 +msgid "Import from {0}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:770 +msgid "Import from {source}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:298 +#: apps/web/src/components/settings/imports-section.tsx:329 +msgid "Import is still running in the background. Check back later." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:798 +msgid "Import options" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:921 +msgid "Imported" +msgstr "" + +#. placeholder {0}: event.job.importedCount +#. placeholder {1}: config.label +#: apps/web/src/components/settings/imports-section.tsx:292 +msgid "Imported {0} items from {1}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:863 +msgid "Importing from {source}" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:62 +msgid "In library" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:230 +msgid "In Library" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:133 +#: apps/web/src/components/dashboard/library-section.tsx:35 +msgid "In Your Library" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:105 +msgid "Install the <0>Webhook plugin<1/> from Jellyfin's plugin catalog" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:70 +msgid "Install the Webhook plugin from Jellyfin's plugin catalog" +msgstr "" + +#: apps/native/src/components/settings/integrations-section.tsx:20 +#: apps/web/src/components/settings/integrations-section.tsx:41 +msgid "Integrations" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:266 +msgid "Invalid token" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:399 +msgid "Job" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:243 +#~ msgid "Keeping" +#~ msgstr "Keeping" + +#. placeholder {0}: (value: string | null) => value === "0" ? t`unlimited` : value ? t`last ${value}` : null +#. placeholder {1}: [3, 5, 7, 14, 30, 0].map((n) => ( {n === 0 ? t`unlimited` : t`last ${n}`} )) +#: apps/web/src/components/settings/backup-schedule-section.tsx:243 +msgid "Keeping <0><1><2>{0}<3>{1} backups." +msgstr "" + +#: apps/web/src/components/command-palette.tsx:389 +#: apps/web/src/components/command-palette.tsx:404 +msgid "Keyboard Shortcuts" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:403 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:411 +#: apps/web/src/components/settings/language-section.tsx:42 +msgid "Language" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:270 +msgid "last {n}" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:258 +msgid "last {value}" +msgstr "" + +#. placeholder {0}: formatRelativeTime(lastEventAt) +#: apps/web/src/components/settings/integration-card.tsx:331 +msgid "Last event {0}" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:29 +msgid "Last event {timeAgo}" +msgstr "" + +#. placeholder {0}: formatRelativeTime(lastEventAt) +#: apps/web/src/components/settings/integration-card.tsx:338 +msgid "Last polled {0}" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:37 +msgid "Last polled {timeAgo}" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:405 +msgid "Last run" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:441 +msgid "Last run failed" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:437 +msgid "Last run succeeded" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:331 +msgid "Library refresh" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:245 +msgid "Loading…" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:334 +msgid "Lost connection to import. Check status in settings." +msgstr "" + +#: apps/web/src/components/settings/settings-shell.tsx:40 +msgid "Manage your account and preferences" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:182 +msgid "Manual backup" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:122 +msgid "Mark all episodes as watched?" +msgstr "" + +#: apps/native/src/components/titles/season-accordion.tsx:164 +#: apps/web/src/components/titles/title-seasons.tsx:115 +#: apps/web/src/components/titles/title-seasons.tsx:141 +msgid "Mark All Watched" +msgstr "" + +#: apps/native/src/components/dashboard/continue-watching-card.tsx:122 +msgid "Mark as Completed" +msgstr "" + +#: apps/native/src/components/ui/poster-card.tsx:252 +msgid "Mark as Watched" +msgstr "" + +#: apps/native/src/components/ui/poster-card.tsx:245 +msgid "Mark as Watching" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:449 +#: apps/web/src/components/titles/title-actions.tsx:29 +msgid "Mark Watched" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:49 +msgid "Marked \"{titleName}\" as completed" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:63 +#: apps/web/src/components/titles/use-title-actions.ts:147 +msgid "Marked \"{titleName}\" as watched" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:360 +msgid "Marked all episodes as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:62 +#: apps/native/src/lib/title-actions.ts:50 +msgid "Marked as completed" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:80 +#: apps/native/src/lib/title-actions.ts:64 +msgid "Marked as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:61 +#: apps/native/src/lib/title-actions.ts:37 +msgid "Marked as watching" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:340 +msgid "Member since {memberSince}" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:97 +msgid "Monday" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:585 +msgid "More Like This" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:541 +msgid "More Settings" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:362 +#: apps/native/src/components/search/search-result-row.tsx:35 +#: apps/native/src/components/search/search-result-row.tsx:96 +#: apps/web/src/components/explore/hero-banner.tsx:59 +#: apps/web/src/components/titles/title-hero.tsx:143 +msgid "Movie" +msgstr "" + +#: apps/web/src/components/people/filmography-grid.tsx:59 +#: apps/web/src/components/settings/imports-section.tsx:779 +msgid "Movies" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:161 +#~ msgid "Movies {periodSelect}" +#~ msgstr "Movies {periodSelect}" + +#: apps/web/src/components/dashboard/stats-display.tsx:162 +msgid "Movies {select}" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:60 +msgid "Movies this month" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:124 +#: apps/web/src/components/auth-form.tsx:181 +msgid "Name" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:107 +#: apps/web/src/components/settings/account-section.tsx:80 +msgid "Name updated" +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:301 +msgid "Need more help?" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:501 +msgid "Never" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:435 +msgid "Never run" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:98 +msgid "New account creation is currently disabled." +msgstr "" + +#: apps/web/src/routes/_auth/register.tsx:35 +msgid "New accounts are not being accepted right now. Contact the admin if you need access." +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:137 +msgid "New backup" +msgstr "" + +#: apps/native/src/app/change-password.tsx:145 +#: apps/web/src/components/settings/account-section.tsx:463 +msgid "New password" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:395 +msgid "New password must be at least 8 characters" +msgstr "" + +#: apps/web/src/components/people/filmography-grid.tsx:86 +#: apps/web/src/components/people/filmography-grid.tsx:99 +msgid "Newest" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:131 +msgid "Next backup {distance}" +msgstr "" + +#: apps/native/src/components/titles/continue-watching-banner.tsx:46 +msgid "Next Episode" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:408 +msgid "Next run" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:110 +#: apps/web/src/components/settings/system-health-section.tsx:662 +msgid "No backups yet" +msgstr "" + +#: apps/native/src/components/ui/offline-banner.ios.tsx:74 +#: apps/native/src/components/ui/offline-banner.ios.tsx:81 +#: apps/native/src/components/ui/offline-banner.tsx:59 +msgid "No internet connection" +msgstr "" + +#: apps/native/src/app/(tabs)/(search)/index.tsx:113 +msgid "No results for \"{debouncedQuery}\"" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:217 +msgid "No results found." +msgstr "" + +#: apps/native/src/components/explore/filterable-title-row.tsx:139 +#: apps/web/src/components/explore/filterable-title-row.tsx:151 +msgid "No titles found for this genre." +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:194 +#: apps/web/src/components/settings/integration-card.tsx:176 +#: apps/web/src/components/settings/system-health-section.tsx:249 +msgid "Not configured" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:17 +msgid "Not Found" +msgstr "" + +#: apps/web/src/components/title-card.tsx:67 +msgid "On Watchlist" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:693 +msgid "Open {source} to enter code" +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:308 +msgid "Open docs" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:190 +msgid "Open Emby, go to <0>Settings > Webhooks" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:91 +msgid "Open Emby, go to Settings > Webhooks" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:548 +msgid "Open in browser…" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:62 +msgid "Open Plex, go to <0>Settings > Webhooks<1/>" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:54 +msgid "Open Plex, go to Settings > Webhooks" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:269 +msgid "Open Radarr, go to <0>Settings > Import Lists" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:126 +msgid "Open Radarr, go to Settings > Import Lists" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:502 +#: apps/web/src/components/settings/registration-section.tsx:58 +msgid "Open registration" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:229 +msgid "Open Sonarr, go to <0>Settings > Import Lists" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:108 +msgid "Open Sonarr, go to Settings > Import Lists" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:158 +msgid "or" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:118 +msgid "OR" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:27 +msgid "Page Not Found" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:509 +msgid "Parsing..." +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:167 +#: apps/native/src/app/(auth)/register.tsx:187 +#: apps/web/src/components/auth-form.tsx:221 +msgid "Password" +msgstr "" + +#: apps/native/src/app/change-password.tsx:76 +#: apps/web/src/components/settings/account-section.tsx:414 +msgid "Password updated" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:399 +msgid "Passwords do not match" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:128 +#: apps/web/src/components/settings/integration-configs.tsx:284 +msgid "Paste the Radarr URL above into the List URL field" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:110 +#: apps/web/src/components/settings/integration-configs.tsx:244 +msgid "Paste the Sonarr URL above into the List URL field" +msgstr "" + +#: apps/web/src/components/settings/update-check-section.tsx:63 +msgid "Periodically check GitHub for new Sofa releases" +msgstr "" + +#: apps/native/src/components/search/search-result-row.tsx:38 +#: apps/native/src/components/search/search-result-row.tsx:99 +msgid "Person" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:243 +#: apps/web/src/routes/_app/people.$id.tsx:52 +msgid "Person not found" +msgstr "" + +#: apps/web/src/components/titles/trailer-dialog.tsx:24 +msgid "Play trailer" +msgstr "" + +#: apps/native/src/app/(tabs)/(explore)/index.tsx:107 +#: apps/web/src/components/explore/explore-client.tsx:126 +msgid "Popular Movies" +msgstr "" + +#: apps/native/src/app/(tabs)/(explore)/index.tsx:120 +#: apps/web/src/components/explore/explore-client.tsx:136 +msgid "Popular TV Shows" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:181 +msgid "Pre-restore backup" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:77 +msgid "Producer" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:130 +#: apps/web/src/components/settings/account-section.tsx:130 +msgid "Profile picture removed" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:119 +#: apps/web/src/components/settings/account-section.tsx:108 +msgid "Profile picture updated" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:196 +#: apps/web/src/components/settings/danger-section.tsx:220 +msgid "Purge all" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:202 +msgid "Purge all caches?" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:160 +msgid "Purge image cache?" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:154 +#: apps/web/src/components/settings/danger-section.tsx:177 +msgid "Purge images" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:111 +#: apps/web/src/components/settings/danger-section.tsx:135 +msgid "Purge metadata" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:117 +msgid "Purge metadata cache?" +msgstr "" + +#. placeholder {0}: data.deletedTitles +#. placeholder {1}: data.deletedPersons +#: apps/web/src/components/settings/danger-section.tsx:41 +msgid "Purged {0, plural, one {# stale title} other {# stale titles}} and {1, plural, one {# orphaned person} other {# orphaned persons}}" +msgstr "" + +#. placeholder {0}: metaResult.deletedTitles +#. placeholder {1}: metaResult.deletedPersons +#. placeholder {2}: imageResult.deletedFiles +#: apps/web/src/components/settings/danger-section.tsx:71 +msgid "Purged {0, plural, one {# title} other {# titles}}, {1, plural, one {# person} other {# persons}}, {2, plural, one {# file} other {# files}} ({freed} freed)" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:109 +#: apps/web/src/components/settings/danger-section.tsx:152 +#: apps/web/src/components/settings/danger-section.tsx:194 +msgid "Purging..." +msgstr "" + +#: apps/web/src/components/command-palette.tsx:359 +msgid "Quick Actions" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:123 +msgid "Radarr List URL" +msgstr "" + +#. placeholder {0}: input.stars +#. placeholder {1}: input.stars +#: apps/native/src/hooks/use-title-actions.ts:93 +msgid "Rated {0} {1, plural, one {star} other {stars}}" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:92 +#~ msgid "Rated {0} star{1}" +#~ msgstr "Rated {0} star{1}" + +#: apps/web/src/components/titles/use-title-actions.ts:131 +msgid "Rated {ratingStars} {ratingStars, plural, one {star} other {stars}}" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:88 +msgid "Rated {stars, plural, one {# star} other {# stars}}" +msgstr "" + +#: apps/web/src/components/people/filmography-grid.tsx:88 +#: apps/web/src/components/people/filmography-grid.tsx:102 +msgid "Rating" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:94 +#: apps/native/src/lib/title-actions.ts:90 +#: apps/web/src/components/titles/use-title-actions.ts:132 +msgid "Rating removed" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:782 +#: apps/web/src/components/settings/imports-section.tsx:813 +msgid "Ratings" +msgstr "" + +#: apps/web/src/components/expandable-text.tsx:51 +msgid "Read more" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:39 +#: apps/web/src/components/settings/integration-card.tsx:339 +msgid "Ready — not polled yet" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:31 +#: apps/web/src/components/settings/integration-card.tsx:332 +msgid "Ready — nothing received yet" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:315 +msgid "Recent Searches" +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:106 +#: apps/native/src/components/search/recently-viewed-list.tsx:86 +msgid "Recently Viewed" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:333 +msgid "Recommendations" +msgstr "" + +#: apps/web/src/components/titles/title-recommendations.tsx:40 +msgid "Recommended" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:154 +#: apps/web/src/components/dashboard/recommendations-section.tsx:22 +msgid "Recommended for You" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:143 +msgid "Redirecting…" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:182 +msgid "Refresh" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:172 +msgid "Refresh system health" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:140 +#: apps/native/src/components/settings/integration-card.tsx:289 +msgid "Regenerate" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:135 +#: apps/web/src/components/settings/integration-card.tsx:264 +msgid "Regenerate URL" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:293 +#: apps/web/src/components/landing-page.tsx:176 +msgid "Register" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:116 +msgid "Registering on {serverHost}" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:167 +#: apps/web/src/components/settings/registration-section.tsx:41 +msgid "Registration closed" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:97 +#: apps/web/src/routes/_auth/register.tsx:32 +msgid "Registration Closed" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:167 +#: apps/web/src/components/settings/registration-section.tsx:41 +msgid "Registration opened" +msgstr "" + +#: apps/web/src/components/titles/status-button.tsx:84 +msgid "Remove" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:101 +#: apps/web/src/components/titles/status-button.tsx:66 +msgid "Remove from library" +msgstr "" + +#: apps/native/src/components/dashboard/continue-watching-card.tsx:129 +#: apps/native/src/components/ui/poster-card.tsx:259 +msgid "Remove from Library" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:277 +msgid "Remove Photo" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:239 +msgid "Remove picture" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:199 +msgid "Remove profile picture" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:66 +#: apps/native/src/lib/title-actions.ts:75 +#: apps/web/src/components/titles/use-title-actions.ts:111 +msgid "Removed from library" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:138 +msgid "Rent" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:165 +msgid "Requires <0>Emby Server 4.7.9+ and an active <1>Emby Premiere<2/> license." +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:41 +msgid "Requires an active <0>Plex Pass<1/> subscription." +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:52 +msgid "Requires an active Plex Pass subscription." +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:88 +msgid "Requires Emby Server 4.7.9+ and an active Emby Premiere license." +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:59 +#: apps/web/src/components/settings/backup-restore-section.tsx:116 +msgid "Restore" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:89 +msgid "Restore database?" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:35 +msgid "Restore failed" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:132 +msgid "Restoring…" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:222 +msgid "Results" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:731 +msgid "Retrieving your watch history, watchlist, and ratings..." +msgstr "" + +#: apps/native/src/components/settings/integrations-section.tsx:42 +#: apps/native/src/components/ui/server-unreachable-banner.ios.tsx:73 +#: apps/native/src/components/ui/server-unreachable-banner.tsx:81 +#: apps/web/src/lib/orpc/client.ts:16 +msgid "Retry" +msgstr "" + +#: apps/web/src/routes/__root.tsx:119 +msgid "Return home" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:773 +msgid "Review what was found and choose what to import." +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:558 +msgid "Run now" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:102 +msgid "Saturday" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:322 +msgid "Save" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:296 +msgid "Save name" +msgstr "" + +#: apps/web/src/routes/__root.tsx:101 +msgid "Scene not found" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:402 +msgid "Schedule" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:201 +msgid "Schedule updated" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:179 +msgid "Scheduled backup" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:178 +msgid "Scheduled backups disabled" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:177 +msgid "Scheduled backups enabled" +msgstr "" + +#: apps/web/src/components/dashboard/stats-section.tsx:35 +msgid "Search for movies and TV shows to start tracking" +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:81 +#: apps/native/src/components/search/recently-viewed-list.tsx:64 +msgid "Search for movies, shows, or people" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:186 +msgid "Search for movies, TV shows, or run commands" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:195 +msgid "Search movies & TV shows…" +msgstr "" + +#: apps/native/src/app/(tabs)/(search)/index.tsx:90 +msgid "Search movies, shows, people..." +msgstr "" + +#: apps/web/src/components/nav-bar.tsx:193 +#: apps/web/src/components/nav-bar.tsx:202 +msgid "Search…" +msgstr "" + +#. placeholder {0}: season.seasonNumber +#: apps/native/src/components/titles/season-accordion.tsx:122 +#: apps/native/src/components/titles/season-accordion.tsx:128 +#: apps/web/src/components/titles/use-title-actions.ts:295 +#: apps/web/src/components/titles/use-title-actions.ts:335 +msgid "Season {0}" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:137 +#: apps/native/src/lib/title-actions.ts:124 +msgid "Season watched" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:536 +msgid "Seasons" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:497 +#: apps/web/src/routes/_app/settings.tsx:140 +msgid "Security" +msgstr "" + +#: apps/web/src/components/landing-page.tsx:105 +msgid "Self-hosted movie & TV tracker" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:121 +msgid "Server" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:452 +msgid "Server Health" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:380 +msgid "Server URL" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:361 +msgid "Set password" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:111 +#: apps/native/src/components/settings/integration-configs.ts:129 +#: apps/web/src/components/settings/integration-configs.tsx:247 +#: apps/web/src/components/settings/integration-configs.tsx:287 +msgid "Set your preferred quality profile and root folder" +msgstr "" + +#: apps/native/src/components/header-avatar.tsx:63 +#: apps/web/src/components/nav-bar.tsx:250 +#: apps/web/src/components/nav-bar.tsx:292 +#: apps/web/src/components/settings/settings-shell.tsx:36 +msgid "Settings" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:319 +#: apps/web/src/components/settings/integration-card.tsx:288 +msgid "Setup instructions" +msgstr "" + +#: apps/web/src/routes/setup.tsx:98 +msgid "Setup required" +msgstr "" + +#: apps/native/src/components/ui/expandable-text.tsx:62 +#: apps/native/src/components/ui/expandable-text.tsx:69 +#: apps/web/src/components/expandable-text.tsx:51 +msgid "Show less" +msgstr "" + +#: apps/native/src/components/ui/expandable-text.tsx:62 +#: apps/native/src/components/ui/expandable-text.tsx:69 +msgid "Show more" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:249 +#: apps/web/src/components/auth-form.tsx:283 +msgid "Sign in" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:205 +#: apps/web/src/components/landing-page.tsx:167 +msgid "Sign In" +msgstr "" + +#: apps/web/src/routes/_auth/register.tsx:45 +msgid "Sign in instead" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:91 +#: apps/web/src/components/auth-form.tsx:119 +msgid "Sign in to continue" +msgstr "" + +#. placeholder {0}: authConfig.data?.oidcProviderName ?? "SSO" +#. placeholder {0}: authConfig?.oidcProviderName || "SSO" +#: apps/native/src/app/(auth)/login.tsx:108 +#: apps/web/src/components/auth-form.tsx:145 +msgid "Sign in with {0}" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:368 +#: apps/native/src/components/header-avatar.tsx:87 +#: apps/web/src/components/nav-bar.tsx:262 +#: apps/web/src/components/settings/account-section.tsx:354 +msgid "Sign out" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:206 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:209 +#: apps/native/src/components/header-avatar.tsx:70 +#: apps/native/src/components/header-avatar.tsx:73 +msgid "Sign Out" +msgstr "" + +#: apps/native/src/app/change-password.tsx:199 +#: apps/web/src/components/settings/account-section.tsx:499 +msgid "Sign out of other sessions" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:922 +msgid "Skipped" +msgstr "" + +#. placeholder {0}: data.latestVersion +#: apps/web/src/components/update-toast.tsx:19 +msgid "Sofa v{0} is available" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:57 +#: apps/native/src/components/settings/integration-configs.ts:75 +#: apps/native/src/components/settings/integration-configs.ts:95 +#: apps/web/src/components/settings/integration-configs.tsx:86 +#: apps/web/src/components/settings/integration-configs.tsx:147 +#: apps/web/src/components/settings/integration-configs.tsx:208 +msgid "Sofa will automatically log movies and episodes when you finish watching them" +msgstr "" + +#: apps/native/src/app/change-password.tsx:81 +#: apps/native/src/app/person/[id].tsx:213 +#: apps/web/src/components/settings/account-section.tsx:417 +#: apps/web/src/routes/__root.tsx:143 +msgid "Something went wrong" +msgstr "" + +#: apps/web/src/routes/_app/titles.$id.tsx:118 +msgid "Something went wrong while loading this title. Please try again." +msgstr "" + +#: apps/native/src/lib/query-client.ts:33 +#: apps/web/src/lib/orpc/client.ts:14 +msgid "Something went wrong…" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:105 +msgid "Sonarr List URL" +msgstr "" + +#: apps/web/src/components/dashboard/stats-section.tsx:42 +msgid "Start exploring" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:142 +msgid "Start tracking movies and shows" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:117 +msgid "Start tracking your watches" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:388 +msgid "Starting at" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:888 +msgid "Starting import..." +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:65 +msgid "Status updated" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:595 +msgid "Storage" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:137 +msgid "Stream" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:96 +msgid "Sunday" +msgstr "" + +#. placeholder {0}: info.name +#: apps/web/src/components/settings/language-section.tsx:69 +msgid "Switch to {0}" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:30 +msgid "The page you're looking for doesn't exist." +msgstr "" + +#: apps/web/src/routes/_app/people.$id.tsx:55 +msgid "The person you're looking for doesn't exist or may have been removed from the database." +msgstr "" + +#: apps/web/src/routes/_app/titles.$id.tsx:149 +msgid "The title you're looking for doesn't exist or may have been removed from the database." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:866 +msgid "This may take a few minutes for large libraries. Please don't close this tab." +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:146 +msgid "This Month" +msgstr "" + +#: apps/web/src/routes/__root.tsx:104 +msgid "This page was left on the cutting room floor. It may have been moved, removed, or never made it past the screenplay." +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:606 +#: apps/web/src/routes/_app/settings.tsx:75 +#: apps/web/src/routes/setup.tsx:187 +msgid "This product uses the TMDB API but is not endorsed or certified by TMDB." +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:145 +msgid "This Week" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:163 +msgid "This will delete all cached TMDB images from disk. Images will be re-downloaded automatically as needed." +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:205 +msgid "This will delete all un-enriched stub titles and all cached images from disk. Everything will be re-imported and re-downloaded as needed." +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:120 +msgid "This will delete un-enriched stub titles that aren't in any user's library and clean up orphaned person records. Deleted titles will be re-imported if accessed again." +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:136 +msgid "This will invalidate the current {label} URL. You'll need to update it in {label}." +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:125 +msgid "This will mark every episode of this show as watched. You can undo this later by unmarking individual seasons." +msgstr "" + +#. placeholder {0}: formatBackupDate(backup.createdAt) +#: apps/web/src/components/settings/backup-section.tsx:259 +msgid "This will permanently delete the backup from <0>{0}. This cannot be undone." +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:61 +#: apps/native/src/components/search/recently-viewed-list.tsx:35 +msgid "This will remove all items from your history." +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:92 +msgid "This will replace your entire database with the uploaded file. A safety backup of your current data will be created first. Active sessions may need to refresh after restore." +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:147 +msgid "This Year" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:100 +msgid "Thursday" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:390 +msgid "Time:" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:251 +#: apps/web/src/routes/_app/titles.$id.tsx:146 +msgid "Title not found" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:131 +#: apps/web/src/components/settings/integration-configs.tsx:290 +msgid "Titles on your Sofa watchlist will be automatically added for download when Radarr polls this list (every 12 hours by default)" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:113 +#: apps/web/src/components/settings/integration-configs.tsx:250 +msgid "Titles on your Sofa watchlist will be automatically added for download when Sonarr polls this list (every 6 hours by default)" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:144 +msgid "Today" +msgstr "" + +#: apps/web/src/components/settings/update-check-section.tsx:71 +msgid "Toggle automatic update checks" +msgstr "" + +#: apps/web/src/components/settings/registration-section.tsx:69 +msgid "Toggle open registration" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:290 +msgid "Toggle scheduled backups" +msgstr "" + +#: apps/web/src/components/landing-page.tsx:131 +msgid "Track what you watch. Know what's next.<0/>Your library, your data, your rules." +msgstr "" + +#: apps/web/src/components/titles/trailer-dialog.tsx:37 +#: apps/web/src/components/titles/trailer-dialog.tsx:41 +msgid "Trailer" +msgstr "" + +#: apps/web/src/components/explore/hero-banner.tsx:78 +msgid "Trending today" +msgstr "" + +#: apps/native/src/app/(tabs)/(explore)/index.tsx:95 +#: apps/web/src/components/explore/explore-client.tsx:112 +msgid "Trending Today" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:537 +msgid "Trigger job" +msgstr "" + +#: apps/web/src/routes/__root.tsx:159 +msgid "Try again" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:98 +msgid "Tuesday" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:362 +#: apps/native/src/components/search/search-result-row.tsx:98 +#: apps/web/src/components/explore/hero-banner.tsx:64 +#: apps/web/src/components/people/filmography-grid.tsx:60 +#: apps/web/src/components/titles/title-hero.tsx:153 +msgid "TV" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:334 +msgid "TV episodes" +msgstr "" + +#: apps/native/src/components/search/search-result-row.tsx:37 +msgid "TV show" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:656 +msgid "unknown" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:256 +#: apps/web/src/components/settings/backup-schedule-section.tsx:270 +msgid "unlimited" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:273 +msgid "Unreachable" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:340 +msgid "Untitled" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:221 +msgid "Unwatch all" +msgstr "" + +#. placeholder {0}: season.name ?? t`Season ${season.seasonNumber}` +#: apps/web/src/components/titles/use-title-actions.ts:335 +msgid "Unwatched all of {0}" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:174 +msgid "Unwatched S{seasonNum} E{epNum}" +msgstr "" + +#: apps/native/src/components/titles/continue-watching-banner.tsx:79 +#: apps/web/src/components/dashboard/continue-watching-card.tsx:77 +msgid "Up next" +msgstr "" + +#. placeholder {0}: updateCheck.data.updateCheck.latestVersion +#: apps/native/src/app/(tabs)/(settings)/index.tsx:528 +msgid "Update available: {0}" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:337 +msgid "Update check" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:185 +#: apps/web/src/components/settings/update-check-section.tsx:40 +msgid "Update checks disabled" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:185 +#: apps/web/src/components/settings/update-check-section.tsx:40 +msgid "Update checks enabled" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:84 +msgid "Update failed" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:509 +msgid "Update password" +msgstr "" + +#: apps/native/src/app/change-password.tsx:218 +msgid "Update Password" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:134 +#: apps/web/src/components/settings/imports-section.tsx:509 +msgid "Upload" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:62 +msgid "Upload a .db file to replace the current database. A safety backup is created first." +msgstr "" + +#. placeholder {0}: config.accept +#. placeholder {1}: config.label +#: apps/web/src/components/settings/imports-section.tsx:637 +msgid "Upload a {0} export from your {1} account settings." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:634 +msgid "Upload export file" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:112 +msgid "Upload failed" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:241 +msgid "Upload picture" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:200 +msgid "Upload profile picture" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:129 +msgid "URL copied to clipboard" +msgstr "" + +#: apps/web/src/components/update-toast.tsx:24 +msgid "View release" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:699 +msgid "Waiting for authorization..." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:823 +msgid "Warnings" +msgstr "" + +#. placeholder {0}: result.warnings.length +#: apps/web/src/components/settings/imports-section.tsx:948 +msgid "Warnings ({0})" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:202 +msgid "Watch all" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:801 +msgid "Watch history" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:59 +msgid "Watch on {name}" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:298 +#: apps/web/src/components/titles/use-title-actions.ts:307 +msgid "Watched all of {seasonLabel}" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:123 +msgid "Watched all of {seasonName}" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:220 +#: apps/web/src/components/titles/use-title-actions.ts:229 +msgid "Watched S{seasonNum} E{epNum}" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:42 +#: apps/web/src/components/title-card.tsx:72 +#: apps/web/src/components/titles/status-button.tsx:19 +msgid "Watching" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:82 +#: apps/web/src/components/settings/imports-section.tsx:781 +#: apps/web/src/components/settings/imports-section.tsx:807 +#: apps/web/src/components/titles/status-button.tsx:55 +msgid "Watchlist" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:40 +msgid "Watchlisted" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:50 +#: apps/native/src/components/settings/integration-configs.ts:67 +#: apps/native/src/components/settings/integration-configs.ts:85 +msgid "Webhook URL" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:99 +msgid "Wednesday" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:112 +#: apps/web/src/components/dashboard/welcome-header.tsx:10 +msgid "Welcome back" +msgstr "" + +#: apps/web/src/components/dashboard/welcome-header.tsx:8 +msgid "Welcome back, {name}" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:483 +#: apps/web/src/components/titles/title-availability.tsx:154 +msgid "Where to Watch" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:141 +msgid "With Ads" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:75 +msgid "Writer" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:386 +msgid "You'll be signed out to change the server URL." +msgstr "" + +#. placeholder {0}: data.currentVersion +#: apps/web/src/components/update-toast.tsx:20 +msgid "You're running v{0}." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:678 +msgid "Your code:" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:141 +#: apps/web/src/components/dashboard/stats-section.tsx:32 +msgid "Your library is empty" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:134 +msgid "Your name" +msgstr "" + diff --git a/packages/i18n/src/po/de.ts b/packages/i18n/src/po/de.ts new file mode 100644 index 0000000..ef7e47e --- /dev/null +++ b/packages/i18n/src/po/de.ts @@ -0,0 +1 @@ +/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"+5kO8P\":[\"Saturday\"],\"+6i0lS\":[\"Retrieving your watch history, watchlist, and ratings...\"],\"+Cv+V9\":[\"Remove from library\"],\"+JkEpu\":[\"This page was left on the cutting room floor. It may have been moved, removed, or never made it past the screenplay.\"],\"+K0AvT\":[\"Disconnect\"],\"+N0l5/\":[\"Connected to <0>\",[\"serverHost\"],\". Tap to change.\"],\"+gLHYi\":[\"Failed to load title\"],\"+j1ex/\":[\"Failed to remove from library\"],\"+nF1ZO\":[\"Remove Photo\"],\"/+6dvC\":[\"Errors (\",[\"0\"],\")\"],\"/BBXeA\":[\"Connecting to server...\"],\"/DwR+n\":[\"Creating…\"],\"/XtbPO\":[[\"0\"],\" titles\"],\"/cg+QV\":[\"Watch all\"],\"/gQXGv\":[\"Failed to update status\"],\"/nT6AE\":[\"New password\"],\"/sHc1/\":[\"Update check\"],\"0+dyau\":[\"Failed to update retention setting\"],\"0BFJKK\":[\"Authorization was denied. Please try again.\"],\"0GHb20\":[\"Purge metadata cache?\"],\"0IBW21\":[\"Failed to purge caches\"],\"0gH/sc\":[\"Remove profile picture\"],\"1+P9RR\":[\"Switch to \",[\"0\"]],\"12cc1j\":[\"Watching\"],\"12lVOl\":[\"Self-hosted movie & TV tracker\"],\"1B4z0M\":[\"Filmography\"],\"1J4Ek0\":[\"Automatically back up your database on a schedule\"],\"1JhxXW\":[\"Episode \",[\"0\"],\", \",[\"episodeLabel\"]],\"1N6wNP\":[\"Import options\"],\"1Qz4uG\":[\"Enable the \\\"Playback\\\" event category\"],\"1hMWR6\":[\"Create account\"],\"1jHIjh\":[\"Watched all of \",[\"seasonName\"]],\"1vSYsG\":[\"Download backup\"],\"1wL1tj\":[\"TV show\"],\"2FletP\":[\"Purged \",[\"0\",\"plural\",{\"one\":[\"#\",\" title\"],\"other\":[\"#\",\" titles\"]}],\", \",[\"1\",\"plural\",{\"one\":[\"#\",\" person\"],\"other\":[\"#\",\" persons\"]}],\", \",[\"2\",\"plural\",{\"one\":[\"#\",\" file\"],\"other\":[\"#\",\" files\"]}],\" (\",[\"freed\"],\" freed)\"],\"2Fsd9r\":[\"This Month\"],\"2MPcep\":[\"Import complete\"],\"2Mu33Z\":[\"Cache management\"],\"2POOFK\":[\"Free\"],\"2Pt2NY\":[\"This will remove all items from your history.\"],\"2fCpt5\":[\"Return home\"],\"2pPBp6\":[\"Trending today\"],\"39y5bn\":[\"Friday\"],\"3Blefz\":[\"Ratings\"],\"3JTlG8\":[\"Recent Searches\"],\"3Jy8bM\":[\"Movies \",[\"select\"]],\"3L9OuQ\":[\"Episode \",[\"0\"]],\"3T8ziB\":[\"Create an account\"],\"3hELxX\":[\"Enter your Sofa server URL to get started\"],\"4fxLkp\":[\"Open Sonarr, go to <0>Settings > Import Lists\"],\"4kpBqM\":[\"Last event \",[\"0\"]],\"4uUjVO\":[\"Producer\"],\"4x+A56\":[\"Anonymous usage reporting\"],\"5IShvp\":[\"Import \",[\"totalItems\"],\" items\"],\"5IYJSv\":[\"Explore titles\"],\"5Y4mym\":[\"Import from \",[\"0\"]],\"5ZzgbQ\":[\"Connect \",[\"0\"]],\"5fEnbK\":[\"Failed to update scheduled backup setting\"],\"5lWFkC\":[\"Sign in\"],\"5v9C16\":[\"Connected to \",[\"source\"]],\"623bR4\":[\"Failed to trigger job\"],\"6HN0yh\":[\"Open Plex, go to Settings > Webhooks\"],\"6TNjOJ\":[\"Failed to update rating\"],\"6TfUy6\":[\"last \",[\"value\"]],\"6V3Ea3\":[\"Copied\"],\"6YtxFj\":[\"Name\"],\"6exX+8\":[\"Regenerate\"],\"6gRgw8\":[\"Retry\"],\"6lGV3K\":[\"Show less\"],\"76++pR\":[\"Warnings\"],\"77DIAu\":[\"Go to Dashboard > Plugins > Webhook\"],\"7Bj3x9\":[\"Failed\"],\"7D50KC\":[[\"label\"],\" disconnected\"],\"7GfM5w\":[\"Recently Viewed\"],\"7L01XJ\":[\"Actions\"],\"7eMo+U\":[\"Go Home\"],\"7p5kLi\":[\"Dashboard\"],\"85eoJ1\":[\"Schedule updated\"],\"8B9E2D\":[\"Day:\"],\"8YwF1J\":[\"Go to <0>Dashboard > Plugins > Webhook\"],\"8ZsakT\":[\"Password\"],\"8tjQCz\":[\"Explore\"],\"8vNtLy\":[\"Paste the Radarr URL above into the List URL field\"],\"8wYDMp\":[\"Already have an account?\"],\"9AE3vb\":[\"Open Plex, go to <0>Settings > Webhooks<1/>\"],\"9GW0ZB\":[\"Import is still running in the background. Check back later.\"],\"9NyAH9\":[\"Skipped\"],\"9Xhrps\":[\"Failed to connect\"],\"9ZLGbA\":[\"backups.\"],\"9eF5oV\":[\"Welcome back\"],\"9rG25a\":[\"Server URL\"],\"A+0rLe\":[\"Marked as completed\"],\"AOHgZp\":[\"Episodes\"],\"AOddWK\":[\"Connect \",[\"label\"]],\"ATfxL/\":[\"Trailer\"],\"AVlZoM\":[\"Environment\"],\"Acf6vF\":[\"Save name\"],\"AdoUfN\":[\"Failed to add to watchlist\"],\"AeXO77\":[\"Account\"],\"AjtYj0\":[\"On Watchlist\"],\"Avee+B\":[\"Failed to update name\"],\"B2R3xD\":[\"Toggle scheduled backups\"],\"BEVzjL\":[\"Import failed\"],\"BQnS5I\":[\"Open \",[\"source\"],\" to enter code\"],\"BUnoSB\":[[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"backup\"],\"other\":[\"backups\"]}],\" stored\"],\"BpttUI\":[\"Trending Today\"],\"BrrIs8\":[\"Storage\"],\"BskWMl\":[\"Unreachable\"],\"BzEFor\":[\"or\"],\"CGDHFb\":[\"Add a <0>Generic Destination and paste the URL above\"],\"CGExDN\":[\"Failed to purge image cache\"],\"CHeXFE\":[\"Status updated\"],\"CKyk7Q\":[\"Go back\"],\"CaB/+I\":[\"Welcome back, \",[\"name\"]],\"CodnUh\":[\"Removed from library\"],\"CpzGJY\":[\"This may take a few minutes for large libraries. Please don't close this tab.\"],\"CuPxpd\":[\"Requires an active <0>Plex Pass<1/> subscription.\"],\"CzBN6D\":[\"Start exploring\"],\"D+R2Xs\":[\"Starting import...\"],\"D3C4Yx\":[\"Current password is required\"],\"DBC3t5\":[\"Sunday\"],\"DI4lqs\":[\"Person not found\"],\"DKBbJf\":[\"Added \\\"\",[\"titleName\"],\"\\\" to watchlist\"],\"DPfwMq\":[\"Done\"],\"DZse/o\":[\"Add a \\\"Generic Destination\\\" and paste the URL above\"],\"E/QGRL\":[\"Disabled\"],\"E6nRW7\":[\"Copy URL\"],\"EWQlBH\":[\"Your library is empty\"],\"EWaCfj\":[\"Enter your current password and choose a new one.\"],\"Eeo/Gy\":[\"Failed to update setting\"],\"Efn6WU\":[\"This will delete all un-enriched stub titles and all cached images from disk. Everything will be re-imported and re-downloaded as needed.\"],\"Etp5if\":[\"Finished importing from \",[\"source\"],\".\"],\"F006BN\":[\"Import from \",[\"source\"]],\"FNvDMc\":[\"This Week\"],\"FWSp+7\":[\"Enter the code below on \",[\"source\"],\"'s website to authorize Sofa.\"],\"FXN0ro\":[\"Recommendations\"],\"FaU7Ag\":[\"Enable the \\\"Playback Stop\\\" notification type\"],\"FhvLDl\":[[\"watchedCount\"],\"/\",[\"0\"],\" episodes\"],\"Fo2bwm\":[\"Actor\"],\"FwRqjE\":[\"Image cache and backup disk usage\"],\"G00fgM\":[\"last \",[\"n\"]],\"G3myU+\":[\"Tuesday\"],\"GcCthe\":[\"In Library\"],\"Gf39AA\":[[\"0\"],\" triggered\"],\"GnhfWw\":[\"Toggle automatic update checks\"],\"GptGxg\":[\"Change password\"],\"GqTZ+S\":[\"Rated \",[\"ratingStars\"],\" \",[\"ratingStars\",\"plural\",{\"one\":[\"star\"],\"other\":[\"stars\"]}]],\"GrdN/F\":[\"Caught up — marked \",[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}],\" as watched\"],\"H4B5LG\":[\"This product uses the TMDB API but is not endorsed or certified by TMDB.\"],\"HBRd5n\":[\"Season \",[\"0\"]],\"HD+aQ7\":[\"Database backups\"],\"HG+31u\":[\"Purge all caches?\"],\"Haz+72\":[\"This will delete all cached TMDB images from disk. Images will be re-downloaded automatically as needed.\"],\"HbReP5\":[\"Marked \\\"\",[\"titleName\"],\"\\\" as completed\"],\"Hg6o8v\":[\"Refresh system health\"],\"HmEjnC\":[\"Last event \",[\"timeAgo\"]],\"HvDfH/\":[\"Imported\"],\"I89uD4\":[\"Restoring…\"],\"IRoxQm\":[\"Registration closed\"],\"IS0nrP\":[\"Create Account\"],\"IY9rQ0\":[\"Free up disk space by clearing cached metadata and images\"],\"IrC12v\":[\"Application\"],\"J28zul\":[\"Connecting...\"],\"J64cFL\":[\"Library refresh\"],\"JKmmmN\":[\"Added to watchlist\"],\"JN0f/Y\":[\"Connect to TMDB\"],\"JR6aH2\":[\"Movies \",[\"periodSelect\"]],\"JRQitQ\":[\"Confirm new password\"],\"JRadFJ\":[\"Start tracking your watches\"],\"JSwq8t\":[\"New account creation is currently disabled.\"],\"JY5Oyv\":[\"Database\"],\"JjsJnI\":[[\"0\"],\"/\",[\"1\"],\" episodes\"],\"JrFTcr\":[\"Connecting…\"],\"JwFbe8\":[\"TV\"],\"KDw4GX\":[\"Try again\"],\"KG6681\":[\"Episode watched\"],\"KIS/Sd\":[\"Sign out of other sessions\"],\"KK0ghs\":[\"Image cache\"],\"KVAoFR\":[\"unlimited\"],\"KcXJuc\":[\"Purging...\"],\"KhtG3o\":[\"Update Password\"],\"Khux7w\":[[\"0\",\"plural\",{\"one\":[\"#\",\" title\"],\"other\":[\"#\",\" titles\"]}]],\"KmWyx0\":[\"Job\"],\"KoF0x6\":[\"age \",[\"age\"]],\"Ksiej9\":[\"Requires an active Plex Pass subscription.\"],\"Kx9NEt\":[\"Invalid token\"],\"Lk6Jb/\":[\"Last polled \",[\"timeAgo\"]],\"LmEEic\":[\"Waiting for authorization...\"],\"LqKH42\":[\"Connect with \",[\"0\"]],\"Lrpjji\":[\"Disconnect \",[\"label\"]],\"Lu6Udx\":[\"Click \\\"+\\\" and select \\\"Custom Lists\\\"\"],\"MDoX++\":[\"Sonarr List URL\"],\"MRBlCJ\":[\"Failed to unmark some episodes\"],\"MUO7w9\":[\"Marked \\\"\",[\"titleName\"],\"\\\" as watched\"],\"MZbQHL\":[\"No results found.\"],\"MdxR1u\":[\"Choose your preferred display language\"],\"MkyzAV\":[\"Authorize Sofa to read your \",[\"0\"],\" library. No password shared.\"],\"N40H+G\":[\"All\"],\"N6SFhC\":[\"Sign in instead\"],\"N7h106\":[\"Sofa v\",[\"0\"],\" is available\"],\"N9RF2M\":[\"Click \\\"Add Webhook\\\" and paste the URL above\"],\"NBo4z0\":[\"Click <0>+ and select <1>Custom Lists\"],\"NICUmW\":[\"Director\"],\"NQzPoO\":[\"New backup\"],\"NSxl1l\":[\"More Settings\"],\"NVF43p\":[\"Time:\"],\"NdPMwS\":[\"In Your Library\"],\"NgaPSG\":[\"Failed to update schedule\"],\"O3oNi5\":[\"Email\"],\"OBcj5W\":[\"This will invalidate the current \",[\"label\"],\" URL. You'll need to update it in \",[\"label\"],\".\"],\"OL8hbM\":[\"New password must be at least 8 characters\"],\"ONWvwQ\":[\"Upload\"],\"OPFjyX\":[\"Install the <0>Webhook plugin<1/> from Jellyfin's plugin catalog\"],\"OW/+RD\":[\"Watch history\"],\"OZdaTZ\":[\"Person\"],\"OvO76u\":[\"Back to Login\"],\"OvdFIZ\":[\"Season watched\"],\"P2FLLe\":[\"View release\"],\"PBdLfg\":[\"No titles found for this genre.\"],\"PQ3qDa\":[\"Fetching your library data from \",[\"source\"],\"...\"],\"PjNoxI\":[\"Scheduled backup\"],\"Pn2B7/\":[\"Current password\"],\"PnEbL/\":[\"Rated \",[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"star\"],\"other\":[\"stars\"]}]],\"Pwqkdw\":[\"Loading…\"],\"Py87xY\":[\"Authorization succeeded but failed to fetch your library. Please try again.\"],\"Q3MPWA\":[\"Update password\"],\"QHcLEN\":[\"Connected\"],\"QK4UIx\":[\"Mark Watched\"],\"Qjlym2\":[\"Failed to create account\"],\"Qm1NmK\":[\"OR\"],\"Qoq+GP\":[\"Read more\"],\"QphVZW\":[\"Can't reach server\"],\"QqLJHH\":[\"This Year\"],\"R0yu2l\":[\"Catch up\"],\"R4YBui\":[\"Search for movies and TV shows to start tracking\"],\"RH46vw\":[[\"0\"],\"/\",[\"1\"],\" \",[\"2\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}]],\"RIR15/\":[\"Radarr List URL\"],\"RLe7Vk\":[\"Checking…\"],\"ROq8cl\":[\"Failed to parse file\"],\"RQq8Si\":[\"Registration opened\"],\"S0soqb\":[\"Failed to remove profile picture\"],\"S1McZh\":[\"Failed to upload avatar\"],\"S2ble5\":[[\"0\"],\" movies, \",[\"1\"],\" episodes\"],\"S2qPRR\":[\"Search movies & TV shows…\"],\"SDND4q\":[\"Not configured\"],\"SFdAk9\":[\"Unwatched S\",[\"seasonNum\"],\" E\",[\"epNum\"]],\"SUIUkB\":[\"Unwatch all\"],\"SbS+Bm\":[\"Regenerate URL\"],\"SlfejT\":[\"Error\"],\"Sp86ju\":[[\"0\"],\" ep\",[\"1\"]],\"SrfROI\":[\"Up next\"],\"SyPRjk\":[\"Sofa will automatically log movies and episodes when you finish watching them\"],\"T0/7WG\":[\"Next backup \",[\"distance\"]],\"TEaX6q\":[\"Backup deleted\"],\"TI7IKo\":[\"Rated \",[\"0\"],\" star\",[\"1\"]],\"TMb7iE\":[\"Profile picture removed\"],\"TqyQQS\":[\"Enable the <0>Playback event category\"],\"Tz0i8g\":[\"Settings\"],\"U+FxtW\":[\"Track what you watch. Know what's next.<0/>Your library, your data, your rules.\"],\"U3pytU\":[\"Admin\"],\"UC+OWB\":[\"Toggle open registration\"],\"UDMjsP\":[\"Quick Actions\"],\"UmQ6Fe\":[\"Purge image cache?\"],\"UtDm3q\":[\"URL copied to clipboard\"],\"V1Kh9Z\":[\"Episodes \",[\"select\"]],\"V6uzvC\":[\"More Like This\"],\"V9CuQ+\":[\"Connect to \",[\"source\"]],\"VAcXNz\":[\"Wednesday\"],\"VKyhZK\":[\"Title not found\"],\"VQvpro\":[\"Paste the Sonarr URL above into the List URL field\"],\"VhMDMg\":[\"Change Password\"],\"Vx0ayx\":[\"Failed to mark some episodes\"],\"WDC4PF\":[\"Episodes \",[\"periodSelect\"]],\"WEYdDv\":[\"Recommended\"],\"WMCwmR\":[\"Check for updates\"],\"WP48q2\":[\"Image Cache\"],\"WT1Ibn\":[\"Last run\"],\"Wb3E4g\":[\"Run now\"],\"WgF2UQ\":[\"You're running v\",[\"0\"],\".\"],\"WtWhSi\":[\"Rating removed\"],\"Wy/3II\":[\"Last polled \",[\"0\"]],\"XFCSYs\":[\"Cast\"],\"XN23JY\":[\"This will delete un-enriched stub titles that aren't in any user's library and clean up orphaned person records. Deleted titles will be re-imported if accessed again.\"],\"XZwihE\":[\"Ready — nothing received yet\"],\"XjTduw\":[\"Upload picture\"],\"YEGzVq\":[\"Failed to connect \",[\"label\"]],\"YErf89\":[\"Failed to purge metadata cache\"],\"YQ768h\":[\"Scene not found\"],\"YiRsXK\":[\"Clear Recently Viewed?\"],\"Yjp1zf\":[\"Don't have a server?\"],\"YqMfa9\":[\"Review what was found and choose what to import.\"],\"ZGUYm0\":[\"Ready — not polled yet\"],\"ZQKLI1\":[\"Danger Zone\"],\"ZVZUoU\":[[\"0\"],\" cached images\"],\"ZWTQ81\":[\"Watch on \",[\"name\"]],\"a3LDKx\":[\"Security\"],\"aLBUiR\":[\"Keeping <0><1><2>\",[\"0\"],\"<3>\",[\"1\"],\" backups.\"],\"aM0+kY\":[\"Purged \",[\"0\",\"plural\",{\"one\":[\"#\",\" stale title\"],\"other\":[\"#\",\" stale titles\"]}],\" and \",[\"1\",\"plural\",{\"one\":[\"#\",\" orphaned person\"],\"other\":[\"#\",\" orphaned persons\"]}]],\"aO1AxG\":[\"Episode unwatched\"],\"aV/hDI\":[\"Failed to disconnect \",[\"label\"]],\"acbSg0\":[\"Double tap to filter by \",[\"label\"]],\"agE7k4\":[\"Rated \",[\"stars\",\"plural\",{\"one\":[\"#\",\" star\"],\"other\":[\"#\",\" stars\"]}]],\"alPRaV\":[\"Titles on your Sofa watchlist will be automatically added for download when Radarr polls this list (every 12 hours by default)\"],\"aourBv\":[\"Scheduled backups enabled\"],\"apLLSU\":[\"Are you sure you want to sign out?\"],\"atc9MA\":[\"Open Emby, go to <0>Settings > Webhooks\"],\"auVUJO\":[\"Restore database?\"],\"ayqfr4\":[[\"label\"],\" URL regenerated\"],\"b/8KCH\":[\"This will mark every episode of this show as watched. You can undo this later by unmarking individual seasons.\"],\"b3Thhd\":[\"Upload failed\"],\"b5DFtH\":[\"Set password\"],\"bHYIks\":[\"Sign Out\"],\"bITrbE\":[\"Purge all\"],\"bNmdjU\":[\"Watchlist\"],\"bTRwag\":[\"Remove from Library\"],\"bXMotV\":[\"Marked as watched\"],\"bezGJ2\":[[\"0\"],\" images\"],\"boJlGf\":[\"Page Not Found\"],\"c1ssjI\":[\"Importing from \",[\"source\"]],\"c3b0B0\":[\"Get Started\"],\"c4b9Dm\":[\"No results for \\\"\",[\"debouncedQuery\"],\"\\\"\"],\"cQPKZt\":[\"Go to Dashboard\"],\"ccAJSB\":[\"Keeping\"],\"cgvva8\":[[\"0\"],\" ratings\"],\"cnGeoo\":[\"Delete\"],\"cpE88+\":[\"Create your account\"],\"d/6MoL\":[\"Manage your account and preferences\"],\"dA7BWh\":[\"Requires Emby Server 4.7.9+ and an active Emby Premiere license.\"],\"dEgA5A\":[\"Cancel\"],\"dTZwve\":[\"Marked all episodes as watched\"],\"dUCJry\":[\"Newest\"],\"ddwpAr\":[\"Warnings (\",[\"0\"],\")\"],\"e/ToF5\":[\"Sign in with \",[\"0\"]],\"e9sZMS\":[\"This will permanently delete the backup from <0>\",[\"0\"],\". This cannot be undone.\"],\"eFRooE\":[\"Last run failed\"],\"eM39Om\":[\"Watched all of \",[\"seasonLabel\"]],\"eZjYb8\":[\"Writer\"],\"ecUA8p\":[\"Today\"],\"evBxZy\":[\"Where to Watch\"],\"ezDa1h\":[\"Open docs\"],\"f+m696\":[\"An unexpected error occurred while loading this page. You can try again or head back to the dashboard.\"],\"f/AKdU\":[\"Are you sure you want to disconnect \",[\"label\"],\"? The current URL will stop working.\"],\"f7ax8J\":[\"Open in browser…\"],\"fMPkxb\":[\"Show more\"],\"fNMqNn\":[\"Popular TV Shows\"],\"fObVvy\":[\"Profile picture updated\"],\"fRettQ\":[[\"0\"],\" items\"],\"fUDRF9\":[\"Watchlisted\"],\"fcWrnU\":[\"Sign out\"],\"fgLNSM\":[\"Register\"],\"fsAEqk\":[\"Continue Watching\"],\"fzAeQI\":[\"Registering on \",[\"serverHost\"]],\"g+gBfk\":[\"Parsing...\"],\"g4JYff\":[\"Imported \",[\"0\"],\" items from \",[\"1\"]],\"gKtb5i\":[\"Titles on your Sofa watchlist will be automatically added for download when Sonarr polls this list (every 6 hours by default)\"],\"gaIAMq\":[\"Remove picture\"],\"gbEEMp\":[\"Delete backup\"],\"gcNLi0\":[\"Update checks enabled\"],\"gmB6oO\":[\"Schedule\"],\"h/T5Yb\":[\"Open registration\"],\"h4yKYk\":[\"Next run\"],\"h7MgpO\":[\"Keyboard Shortcuts\"],\"hTXYdY\":[\"Failed to create backup\"],\"hXYY5Q\":[\"Unwatched all of \",[\"0\"]],\"he3ygx\":[\"Copy\"],\"hjGupC\":[\"Lost connection to import. Check status in settings.\"],\"hlqjFc\":[\"In library\"],\"hou0tP\":[\"Stream\"],\"hty0d5\":[\"Monday\"],\"i0qMbr\":[\"Home\"],\"i9rcQ/\":[\"Movies\"],\"iGma9e\":[\"Update failed\"],\"iSLIjg\":[\"Connect\"],\"iWv3ck\":[\"Failed to catch up\"],\"iXZ09g\":[\"Mark as Completed\"],\"id08cd\":[\"Watched S\",[\"seasonNum\"],\" E\",[\"epNum\"]],\"ih87w/\":[\"Add to Library\"],\"ihn4zD\":[\"Search…\"],\"itDEco\":[\"Already have an account? Sign in\"],\"itheEn\":[\"With Ads\"],\"iwCRIF\":[\"You'll be signed out to change the server URL.\"],\"j5GBIy\":[\"Open Radarr, go to Settings > Import Lists\"],\"jAe8l4\":[[\"0\"],\" backups · last <0/>\"],\"jB3sfe\":[\"Manual backup\"],\"jFnMJ8\":[\"Double tap to clear this filter\"],\"jQsPwL\":[\"Scheduled backups disabled\"],\"jSjGeu\":[[\"0\"],\" items have no external IDs and will be resolved by title search, which may be less accurate.\"],\"jX6Gzg\":[\"This will replace your entire database with the uploaded file. A safety backup of your current data will be created first. Active sessions may need to refresh after restore.\"],\"jiHVUy\":[\"Pre-restore backup\"],\"k6c41p\":[\"Background jobs\"],\"kBDOjB\":[\"Backup schedule\"],\"kkDQ8m\":[\"Thursday\"],\"klOeIX\":[\"Failed to change password\"],\"ks3XeZ\":[\"Open Sonarr, go to Settings > Import Lists\"],\"kvuCtu\":[\"Something went wrong while loading this title. Please try again.\"],\"kx0s+n\":[\"Results\"],\"l2wcoS\":[\"Last run succeeded\"],\"l3s5ri\":[\"Import\"],\"lCF0wC\":[\"Refresh\"],\"lJ1yo4\":[\"Failed to sign in\"],\"lVqzRx\":[\"Click <0>Add Webhook and paste the URL above\"],\"lXkUEV\":[\"Availability\"],\"lcLe89\":[\"Search for movies, TV shows, or run commands\"],\"lfVyvz\":[\"Add to Watchlist\"],\"llDXYJ\":[\"Backups\"],\"llkZR0\":[\"Could not load integrations\"],\"ln9/n9\":[\"Don't have an account?\"],\"lpIMne\":[\"Passwords do not match\"],\"lqY3WY\":[\"Change Server\"],\"luoodD\":[\"Setup required\"],\"m5WhJy\":[\"Automatic update checks\"],\"mIx58S\":[\"Purge images\"],\"mYBORk\":[\"Movie\"],\"ml9cU0\":[\"Failed to regenerate \",[\"label\"],\" URL\"],\"mqwkjd\":[\"Health status\"],\"mqxHH7\":[\"Go to Explore\"],\"mzI/c+\":[\"Download\"],\"n1ekoW\":[\"Sign In\"],\"n3Pzd7\":[\"Backup created\"],\"nBHvPL\":[\"Cancel editing\"],\"nJw77c\":[[\"0\",\"plural\",{\"one\":[\"#\",\" image\"],\"other\":[\"#\",\" images\"]}]],\"nO0Qnj\":[\"Your code:\"],\"nRP1xx\":[\"Need more help?\"],\"nV1LjT\":[\"Enable the <0>Playback Stop notification type\"],\"nVsE67\":[\"Mark as Watching\"],\"nZ7lF1\":[\"Device code expired. Please try again.\"],\"nbfdhU\":[\"Integrations\"],\"nbmpf9\":[\"Purge metadata\"],\"nnKJTm\":[\"Failed to mark all episodes as watched\"],\"nszbQG\":[\"No backups yet\"],\"nuh/Wq\":[\"Webhook URL\"],\"nwtY4N\":[\"Something went wrong\"],\"oAIA3w\":[\"Search for movies, shows, or people\"],\"oC8IMh\":[\"Search movies, shows, people...\"],\"oNvZcA\":[\"Episodes this week\"],\"oXq+Wr\":[[\"label\"],\" connected\"],\"ogtYkT\":[\"Password updated\"],\"ojtedN\":[\"Open Radarr, go to <0>Settings > Import Lists\"],\"olMi35\":[\"Recommended for You\"],\"p+PZEl\":[\"Here's what's happening with your library\"],\"pAtylB\":[\"Not Found\"],\"pG9pq1\":[\"Deleted \",[\"0\",\"plural\",{\"one\":[\"#\",\" file\"],\"other\":[\"#\",\" files\"]}],\", freed \",[\"freed\"]],\"pWWFjv\":[\"Checked <0/>\"],\"ph76H6\":[\"Allow new users to create accounts\"],\"pjgw0N\":[\"Choose how to import your \",[\"0\"],\" data.\"],\"puo3W3\":[\"Redirecting…\"],\"q3GraM\":[\"...and \",[\"0\"],\" more\"],\"q6nrFE\":[\"died at \",[\"age\"]],\"q6pUQ9\":[\"Upload a .db file to replace the current database. A safety backup is created first.\"],\"q8yluz\":[\"Your name\"],\"qA6VR5\":[\"Requires <0>Emby Server 4.7.9+ and an active <1>Emby Premiere<2/> license.\"],\"qF2IBM\":[\"Movies this month\"],\"qHbApt\":[\"Failed to load backup schedule settings.\"],\"qLB1zX\":[\"Upload a \",[\"0\"],\" export from your \",[\"1\"],\" account settings.\"],\"qPNzfu\":[\"Update checks disabled\"],\"qWoML/\":[\"Add a new webhook and paste the URL above\"],\"qiOIiY\":[\"Buy\"],\"qn1X6N\":[\"Failed to update registration setting\"],\"qqWcBV\":[\"Completed\"],\"qqeAJM\":[\"Never\"],\"r9aDAY\":[[\"count\"],\" earlier \",[\"count\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}],\" unwatched\"],\"rLgPvm\":[\"Backup\"],\"rSTpb5\":[\"Server Health\"],\"rdymVD\":[\"Trigger job\"],\"rtir7c\":[\"unknown\"],\"s0U4ZZ\":[\"TV episodes\"],\"s4vVUm\":[\"Could not load person details\"],\"s9dVME\":[\"Failed to unmark episode\"],\"sGH11W\":[\"Server\"],\"sl/qWH\":[\"Upload profile picture\"],\"sstysK\":[\"Failed to mark episode\"],\"stZeiF\":[[\"watched\"],\"/\",[\"total\"],\" episodes\"],\"t/Ch8S\":[\"Marked as watching\"],\"t/YqKh\":[\"Remove\"],\"tfDRzk\":[\"Save\"],\"tiymc0\":[\"Something went wrong…\"],\"u9ugU7\":[[\"watchedCount\"],\"/\",[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}]],\"uBAxNB\":[\"Editor\"],\"uBrbSu\":[\"Failed to start \",[\"0\"],\" connection\"],\"uM6jnS\":[\"Restore failed\"],\"uMrJrX\":[\"Admin only\"],\"uswLvZ\":[\"The title you're looking for doesn't exist or may have been removed from the database.\"],\"uxOntd\":[[\"watchedCount\"],\" of \",[\"0\"],\" episodes watched\"],\"v2CA3w\":[\"Change Photo\"],\"v5ipVu\":[\"Mark all episodes as watched?\"],\"vKfeax\":[\"Open Emby, go to Settings > Webhooks\"],\"vXIe7J\":[\"Language\"],\"vuCCZ7\":[\"Failed to parse import data\"],\"vwKERN\":[\"Failed to delete backup\"],\"w8pqsh\":[\"Mark All Watched\"],\"wA7B2T\":[\"New accounts are not being accepted right now. Contact the admin if you need access.\"],\"wGFX13\":[\"Starting at\"],\"wR1UAy\":[\"Popular Movies\"],\"wRWcdL\":[\"Play trailer\"],\"wThGrS\":[\"App Settings\"],\"wZK4Xg\":[\"Never run\"],\"wdLxgL\":[\"Member since \",[\"memberSince\"]],\"wja8aL\":[\"Untitled\"],\"wtGebH\":[\"The person you're looking for doesn't exist or may have been removed from the database.\"],\"wtsbt5\":[\"Add to watchlist\"],\"wtuVU4\":[\"Frequency\"],\"wx7pwA\":[\"Mark as Watched\"],\"x4ZiTl\":[\"Setup instructions\"],\"xCJdfg\":[\"Clear\"],\"xGVfLh\":[\"Continue\"],\"xLoCm2\":[\"Check configuration\"],\"xSrU2g\":[[\"healthyCount\"],\" of \",[\"0\"],\" jobs healthy\"],\"xazqmy\":[\"Seasons\"],\"xbBXhy\":[\"Periodically check GitHub for new Sofa releases\"],\"xrh2/M\":[\"Failed to mark as watched\"],\"y3e9pF\":[\"Delete backup?\"],\"y6Urel\":[\"Next Episode\"],\"yGvjAo\":[\"Update available: \",[\"0\"]],\"yKu/3Y\":[\"Restore\"],\"yYxB17\":[\"Clear all\"],\"ybtG1U\":[[\"0\"],\" backup\",[\"1\"],\" stored\"],\"yey/zg\":[\"Database restored. Reloading...\"],\"ygo0l/\":[\"Start tracking movies and shows\"],\"yvwIbI\":[\"Upload export file\"],\"yz7wBu\":[\"Close\"],\"z1U/Fh\":[\"Rating\"],\"z5evln\":[\"No internet connection\"],\"zAvS8w\":[\"Sign in to continue\"],\"zEqK2w\":[\"The page you're looking for doesn't exist.\"],\"zFkiTv\":[\"Install the Webhook plugin from Jellyfin's plugin catalog\"],\"zNyR4f\":[\"Set your preferred quality profile and root folder\"],\"za8Le/\":[\"Registration Closed\"],\"zb77GC\":[\"Rent\"],\"ztAdhw\":[\"Name updated\"]}")as Messages; \ No newline at end of file diff --git a/packages/i18n/src/po/en.po b/packages/i18n/src/po/en.po new file mode 100644 index 0000000..21da7be --- /dev/null +++ b/packages/i18n/src/po/en.po @@ -0,0 +1,2433 @@ +msgid "" +msgstr "" +"POT-Creation-Date: 2026-03-17 21:04-0400\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: @lingui/cli\n" +"Language: en\n" +"Project-Id-Version: sofa\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2026-03-18 16:04\n" +"Last-Translator: \n" +"Language-Team: English\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: sofa\n" +"X-Crowdin-Project-ID: 881052\n" +"X-Crowdin-Language: en\n" +"X-Crowdin-File: /packages/i18n/src/po/en.po\n" +"X-Crowdin-File-ID: 12\n" + +#. placeholder {0}: result.errors.length - 50 +#: apps/web/src/components/settings/imports-section.tsx:938 +msgid "...and {0} more" +msgstr "" + +#. placeholder {0}: systemHealth.data.imageCache.imageCount +#: apps/native/src/app/(tabs)/(settings)/index.tsx:482 +msgid "{0, plural, one {# image} other {# images}}" +msgstr "" + +#. placeholder {0}: systemHealth.data.database.titleCount +#: apps/native/src/app/(tabs)/(settings)/index.tsx:466 +msgid "{0, plural, one {# title} other {# titles}}" +msgstr "" + +#. placeholder {0}: displayBackups.length +#. placeholder {1}: displayBackups.length +#: apps/web/src/components/settings/backup-section.tsx:109 +msgid "{0} {1, plural, one {backup} other {backups}} stored" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:105 +#~ msgid "{0} backup{1} stored" +#~ msgstr "{0} backup{1} stored" + +#. placeholder {0}: backups.backupCount +#: apps/web/src/components/settings/system-health-section.tsx:654 +msgid "{0} backups · last <0/>" +msgstr "" + +#. placeholder {0}: imageCache.imageCount.toLocaleString() +#: apps/web/src/components/settings/system-health-section.tsx:621 +msgid "{0} cached images" +msgstr "" + +#. placeholder {0}: member.episodeCount +#. placeholder {1}: member.episodeCount !== 1 ? "s" : "" +#: apps/web/src/components/titles/cast-carousel.tsx:80 +msgid "{0} ep{1}" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:472 +#~ msgid "{0} images" +#~ msgstr "{0} images" + +#. placeholder {0}: stats.watchlist +#: apps/web/src/components/settings/imports-section.tsx:808 +msgid "{0} items" +msgstr "" + +#. placeholder {0}: preview.diagnostics.unresolved +#: apps/web/src/components/settings/imports-section.tsx:788 +msgid "{0} items have no external IDs and will be resolved by title search, which may be less accurate." +msgstr "" + +#. placeholder {0}: stats.movies +#. placeholder {1}: stats.episodes +#: apps/web/src/components/settings/imports-section.tsx:802 +msgid "{0} movies, {1} episodes" +msgstr "" + +#. placeholder {0}: stats.ratings +#: apps/web/src/components/settings/imports-section.tsx:814 +msgid "{0} ratings" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:456 +#~ msgid "{0} titles" +#~ msgstr "{0} titles" + +#. placeholder {0}: JOB_LABELS[name] ?? name +#: apps/web/src/components/settings/system-health-section.tsx:343 +msgid "{0} triggered" +msgstr "" + +#. placeholder {0}: item.watchedEpisodes +#. placeholder {1}: item.totalEpisodes +#. placeholder {2}: item.totalEpisodes +#: apps/web/src/components/dashboard/continue-watching-card.tsx:93 +msgid "{0}/{1} {2, plural, one {episode} other {episodes}}" +msgstr "" + +#: apps/web/src/components/dashboard/continue-watching-card.tsx:92 +#~ msgid "{0}/{1} episodes" +#~ msgstr "{0}/{1} episodes" + +#: apps/web/src/components/titles/use-title-actions.ts:221 +#: apps/web/src/components/titles/use-title-actions.ts:299 +msgid "{count} earlier {count, plural, one {episode} other {episodes}} unwatched" +msgstr "" + +#. placeholder {0}: activeJobs.length +#: apps/web/src/components/settings/system-health-section.tsx:385 +msgid "{healthyCount} of {0} jobs healthy" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:96 +#: apps/web/src/components/settings/integration-card.tsx:106 +msgid "{label} connected" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:106 +#: apps/web/src/components/settings/integration-card.tsx:122 +msgid "{label} disconnected" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:116 +#: apps/web/src/components/settings/integration-card.tsx:138 +msgid "{label} URL regenerated" +msgstr "" + +#: apps/web/src/components/title-card.tsx:166 +msgid "{watched}/{total} episodes" +msgstr "" + +#. placeholder {0}: episodes.length +#: apps/native/src/components/titles/season-accordion.tsx:122 +msgid "{watchedCount} of {0} episodes watched" +msgstr "" + +#. placeholder {0}: episodes.length +#. placeholder {1}: episodes.length +#: apps/native/src/components/titles/season-accordion.tsx:131 +msgid "{watchedCount}/{0} {1, plural, one {episode} other {episodes}}" +msgstr "" + +#: apps/native/src/components/titles/season-accordion.tsx:130 +#~ msgid "{watchedCount}/{0} episodes" +#~ msgstr "{watchedCount}/{0} episodes" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:231 +#: apps/web/src/components/settings/account-section.tsx:175 +msgid "Account" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:412 +msgid "Actions" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:71 +msgid "Actor" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:72 +msgid "Add a \"Generic Destination\" and paste the URL above" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:131 +msgid "Add a <0>Generic Destination and paste the URL above" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:92 +#: apps/web/src/components/settings/integration-configs.tsx:198 +msgid "Add a new webhook and paste the URL above" +msgstr "" + +#: apps/web/src/components/explore/hero-banner.tsx:99 +msgid "Add to Library" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:72 +msgid "Add to watchlist" +msgstr "" + +#: apps/native/src/components/explore/hero-banner.tsx:157 +#: apps/native/src/components/ui/poster-card.tsx:238 +#: apps/web/src/components/title-card.tsx:146 +msgid "Add to Watchlist" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:23 +msgid "Added \"{titleName}\" to watchlist" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:44 +#: apps/native/src/hooks/use-title-actions.ts:60 +#: apps/native/src/lib/title-actions.ts:24 +#: apps/web/src/components/titles/use-title-actions.ts:111 +msgid "Added to watchlist" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:353 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:454 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:499 +#: apps/web/src/components/nav-bar.tsx:235 +#: apps/web/src/components/settings/account-section.tsx:335 +msgid "Admin" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:124 +msgid "Admin only" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:308 +#: apps/web/src/components/people/person-hero.tsx:91 +msgid "age {age}" +msgstr "" + +#: apps/native/src/components/explore/filterable-title-row.tsx:119 +#: apps/web/src/components/people/filmography-grid.tsx:58 +msgid "All" +msgstr "" + +#: apps/web/src/components/settings/registration-section.tsx:61 +msgid "Allow new users to create accounts" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:278 +msgid "Already have an account?" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:239 +msgid "Already have an account? Sign in" +msgstr "" + +#: apps/web/src/routes/__root.tsx:146 +msgid "An unexpected error occurred while loading this page. You can try again or head back to the dashboard." +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:427 +msgid "Anonymous usage reporting" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:102 +msgid "App Settings" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:378 +msgid "Application" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:151 +msgid "Are you sure you want to disconnect {label}? The current URL will stop working." +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:206 +#: apps/native/src/components/header-avatar.tsx:70 +msgid "Are you sure you want to sign out?" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:450 +msgid "Authorization succeeded but failed to fetch your library. Please try again." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:440 +msgid "Authorization was denied. Please try again." +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:611 +msgid "Authorize Sofa to read your {0} library. No password shared." +msgstr "" + +#: apps/web/src/components/settings/update-check-section.tsx:60 +msgid "Automatic update checks" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:279 +msgid "Automatically back up your database on a schedule" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:332 +msgid "Availability" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:104 +msgid "Back to Login" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:382 +msgid "Background jobs" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:336 +msgid "Backup" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:69 +msgid "Backup created" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:94 +msgid "Backup deleted" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:234 +msgid "Backup schedule" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:641 +#: apps/web/src/routes/_app/settings.tsx:166 +msgid "Backups" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:272 +#~ msgid "backups." +#~ msgstr "backups." + +#: apps/web/src/components/titles/title-availability.tsx:139 +msgid "Buy" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:89 +msgid "Cache management" +msgstr "" + +#: apps/native/src/components/ui/server-unreachable-banner.ios.tsx:66 +#: apps/native/src/components/ui/server-unreachable-banner.tsx:74 +msgid "Can't reach server" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:207 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:332 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:388 +#: apps/native/src/components/header-avatar.tsx:71 +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:63 +#: apps/native/src/components/search/recently-viewed-list.tsx:37 +#: apps/native/src/components/settings/integration-card.tsx:138 +#: apps/native/src/components/settings/integration-card.tsx:153 +#: apps/web/src/components/settings/account-section.tsx:505 +#: apps/web/src/components/settings/backup-restore-section.tsx:107 +#: apps/web/src/components/settings/backup-section.tsx:270 +#: apps/web/src/components/settings/danger-section.tsx:129 +#: apps/web/src/components/settings/danger-section.tsx:171 +#: apps/web/src/components/settings/danger-section.tsx:214 +#: apps/web/src/components/settings/imports-section.tsx:645 +#: apps/web/src/components/settings/imports-section.tsx:710 +#: apps/web/src/components/settings/imports-section.tsx:837 +#: apps/web/src/components/titles/title-seasons.tsx:133 +msgid "Cancel" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:307 +msgid "Cancel editing" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:559 +#: apps/web/src/components/titles/cast-carousel.tsx:21 +msgid "Cast" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:223 +#: apps/web/src/components/titles/use-title-actions.ts:301 +msgid "Catch up" +msgstr "" + +#. placeholder {0}: episodeIds.length +#. placeholder {1}: episodeIds.length +#: apps/web/src/components/titles/use-title-actions.ts:82 +msgid "Caught up — marked {0} {1, plural, one {episode} other {episodes}} as watched" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:361 +#: apps/web/src/components/settings/account-section.tsx:427 +#: apps/web/src/components/settings/account-section.tsx:432 +msgid "Change password" +msgstr "" + +#: apps/native/src/app/change-password.tsx:98 +msgid "Change Password" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:267 +msgid "Change Photo" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:385 +msgid "Change Server" +msgstr "" + +#: apps/web/src/components/setup/refresh-button.tsx:30 +msgid "Check configuration" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:513 +msgid "Check for updates" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:215 +msgid "Checked <0/>" +msgstr "" + +#: apps/web/src/components/setup/refresh-button.tsx:28 +msgid "Checking…" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:587 +msgid "Choose how to import your {0} data." +msgstr "" + +#: apps/web/src/components/settings/language-section.tsx:29 +#~ msgid "Choose your preferred display language" +#~ msgstr "Choose your preferred display language" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:65 +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:115 +#: apps/native/src/components/search/recently-viewed-list.tsx:39 +#: apps/native/src/components/search/recently-viewed-list.tsx:95 +msgid "Clear" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:322 +msgid "Clear all" +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:60 +#: apps/native/src/components/search/recently-viewed-list.tsx:34 +msgid "Clear Recently Viewed?" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:109 +#: apps/native/src/components/settings/integration-configs.ts:127 +msgid "Click \"+\" and select \"Custom Lists\"" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:55 +msgid "Click \"Add Webhook\" and paste the URL above" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:237 +#: apps/web/src/components/settings/integration-configs.tsx:277 +msgid "Click <0>+ and select <1>Custom Lists" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:79 +msgid "Click <0>Add Webhook and paste the URL above" +msgstr "" + +#: apps/native/src/components/navigation/modal-stack-header.tsx:14 +msgid "Close" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:63 +#: apps/native/src/components/titles/status-action-button.tsx:44 +#: apps/web/src/components/dashboard/stats-display.tsx:238 +#: apps/web/src/components/title-card.tsx:77 +#: apps/web/src/components/titles/status-button.tsx:30 +msgid "Completed" +msgstr "" + +#: apps/native/src/app/change-password.tsx:173 +#: apps/web/src/components/settings/account-section.tsx:477 +msgid "Confirm new password" +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:207 +#: apps/web/src/components/settings/imports-section.tsx:499 +msgid "Connect" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/integration-card.tsx:203 +msgid "Connect {0}" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:238 +msgid "Connect {label}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:665 +msgid "Connect to {source}" +msgstr "" + +#: apps/web/src/routes/setup.tsx:101 +msgid "Connect to TMDB" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:608 +msgid "Connect with {0}" +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:197 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:474 +#: apps/web/src/components/settings/system-health-section.tsx:256 +msgid "Connected" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:722 +msgid "Connected to {source}" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:240 +msgid "Connected to <0>{serverHost}. Tap to change." +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:186 +msgid "Connecting to server..." +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:201 +msgid "Connecting..." +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:237 +msgid "Connecting…" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:390 +msgid "Continue" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:113 +#: apps/web/src/components/dashboard/continue-watching-section.tsx:25 +msgid "Continue Watching" +msgstr "" + +#: apps/web/src/components/setup/copy-button.tsx:25 +msgid "Copied" +msgstr "" + +#: apps/web/src/components/setup/copy-button.tsx:30 +msgid "Copy" +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:246 +msgid "Copy URL" +msgstr "" + +#: apps/native/src/components/settings/integrations-section.tsx:29 +msgid "Could not load integrations" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:216 +msgid "Could not load person details" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:247 +msgid "Create account" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:115 +#: apps/native/src/app/(auth)/register.tsx:225 +msgid "Create Account" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:216 +msgid "Create an account" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:110 +msgid "Create your account" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:137 +msgid "Creating…" +msgstr "" + +#: apps/native/src/app/change-password.tsx:120 +#: apps/web/src/components/settings/account-section.tsx:449 +msgid "Current password" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:391 +msgid "Current password is required" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:195 +msgid "Danger Zone" +msgstr "" + +#: apps/web/src/routes/__root.tsx:167 +#: apps/web/src/routes/_app/people.$id.tsx:78 +#: apps/web/src/routes/_app/titles.$id.tsx:126 +#: apps/web/src/routes/_app/titles.$id.tsx:172 +msgid "Dashboard" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:463 +#: apps/web/src/components/settings/system-health-section.tsx:229 +msgid "Database" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:126 +msgid "Database backups" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:31 +msgid "Database restored. Reloading..." +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:342 +msgid "Day:" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:250 +#: apps/web/src/components/settings/backup-section.tsx:280 +msgid "Delete" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:235 +msgid "Delete backup" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:256 +msgid "Delete backup?" +msgstr "" + +#. placeholder {0}: data.deletedFiles +#: apps/web/src/components/settings/danger-section.tsx:54 +msgid "Deleted {0, plural, one {# file} other {# files}}, freed {freed}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:412 +#: apps/web/src/components/settings/imports-section.tsx:444 +msgid "Device code expired. Please try again." +msgstr "" + +#: apps/native/src/app/person/[id].tsx:307 +#: apps/web/src/components/people/person-hero.tsx:91 +msgid "died at {age}" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:73 +msgid "Director" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:431 +#: apps/web/src/components/settings/system-health-section.tsx:632 +msgid "Disabled" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:155 +#: apps/native/src/components/settings/integration-card.tsx:298 +#: apps/web/src/components/settings/integration-card.tsx:274 +msgid "Disconnect" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:150 +msgid "Disconnect {label}" +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:242 +msgid "Don't have a server?" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:288 +msgid "Don't have an account?" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:962 +msgid "Done" +msgstr "" + +#: apps/native/src/components/explore/genre-chip.tsx:27 +msgid "Double tap to clear this filter" +msgstr "" + +#: apps/native/src/components/explore/genre-chip.tsx:28 +msgid "Double tap to filter by {label}" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:71 +#: apps/web/src/components/settings/backup-section.tsx:223 +msgid "Download" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:214 +msgid "Download backup" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:79 +msgid "Editor" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:133 +#: apps/native/src/app/(auth)/register.tsx:154 +#: apps/web/src/components/auth-form.tsx:201 +msgid "Email" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:73 +msgid "Enable the \"Playback Stop\" notification type" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:93 +msgid "Enable the \"Playback\" event category" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:140 +msgid "Enable the <0>Playback Stop notification type" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:201 +msgid "Enable the <0>Playback event category" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:668 +msgid "Enter the code below on {source}'s website to authorize Sofa." +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:435 +msgid "Enter your current password and choose a new one." +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:151 +msgid "Enter your Sofa server URL to get started" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:289 +msgid "Environment" +msgstr "" + +#. placeholder {0}: episode.episodeNumber +#: apps/native/src/components/titles/episode-row.tsx:29 +#: apps/native/src/components/titles/episode-row.tsx:55 +msgid "Episode {0}" +msgstr "" + +#. placeholder {0}: episode.episodeNumber +#: apps/native/src/components/titles/episode-row.tsx:36 +msgid "Episode {0}, {episodeLabel}" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:123 +#: apps/native/src/lib/title-actions.ts:111 +msgid "Episode unwatched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:109 +#: apps/native/src/lib/title-actions.ts:101 +msgid "Episode watched" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:780 +#: apps/web/src/components/titles/title-seasons.tsx:102 +msgid "Episodes" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:162 +#~ msgid "Episodes {periodSelect}" +#~ msgstr "Episodes {periodSelect}" + +#: apps/web/src/components/dashboard/stats-display.tsx:164 +msgid "Episodes {select}" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:61 +msgid "Episodes this week" +msgstr "" + +#: apps/native/src/app/change-password.tsx:71 +#: apps/native/src/app/change-password.tsx:81 +msgid "Error" +msgstr "" + +#. placeholder {0}: result.errors.length +#: apps/web/src/components/settings/imports-section.tsx:929 +msgid "Errors ({0})" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:143 +#: apps/web/src/components/nav-bar.tsx:126 +#: apps/web/src/components/nav-bar.tsx:291 +msgid "Explore" +msgstr "" + +#: apps/web/src/routes/_app/people.$id.tsx:70 +#: apps/web/src/routes/_app/titles.$id.tsx:164 +msgid "Explore titles" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:923 +msgid "Failed" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:49 +#: apps/native/src/lib/title-actions.ts:28 +msgid "Failed to add to watchlist" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:90 +msgid "Failed to catch up" +msgstr "" + +#: apps/native/src/app/change-password.tsx:71 +#: apps/web/src/components/settings/account-section.tsx:411 +msgid "Failed to change password" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:398 +msgid "Failed to connect" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:99 +#: apps/web/src/components/settings/integration-card.tsx:108 +msgid "Failed to connect {label}" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:67 +msgid "Failed to create account" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:81 +msgid "Failed to create backup" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:97 +msgid "Failed to delete backup" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:109 +#: apps/web/src/components/settings/integration-card.tsx:125 +msgid "Failed to disconnect {label}" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:215 +msgid "Failed to load backup schedule settings." +msgstr "" + +#: apps/web/src/routes/_app/titles.$id.tsx:115 +msgid "Failed to load title" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:367 +msgid "Failed to mark all episodes as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:84 +#: apps/native/src/lib/title-actions.ts:68 +#: apps/web/src/components/titles/use-title-actions.ts:150 +msgid "Failed to mark as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:113 +#: apps/native/src/lib/title-actions.ts:104 +#: apps/web/src/components/titles/use-title-actions.ts:237 +msgid "Failed to mark episode" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:141 +#: apps/native/src/lib/title-actions.ts:128 +#: apps/web/src/components/titles/use-title-actions.ts:315 +msgid "Failed to mark some episodes" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:225 +msgid "Failed to parse file" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:240 +msgid "Failed to parse import data" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:75 +msgid "Failed to purge caches" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:58 +msgid "Failed to purge image cache" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:45 +msgid "Failed to purge metadata cache" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:119 +#: apps/web/src/components/settings/integration-card.tsx:140 +msgid "Failed to regenerate {label} URL" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:78 +msgid "Failed to remove from library" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:134 +#: apps/web/src/components/settings/account-section.tsx:134 +msgid "Failed to remove profile picture" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:56 +msgid "Failed to sign in" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:388 +msgid "Failed to start {0} connection" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:348 +msgid "Failed to trigger job" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:129 +#: apps/native/src/lib/title-actions.ts:114 +#: apps/web/src/components/titles/use-title-actions.ts:181 +msgid "Failed to unmark episode" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:343 +msgid "Failed to unmark some episodes" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:112 +msgid "Failed to update name" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:101 +#: apps/native/src/lib/title-actions.ts:94 +#: apps/web/src/components/titles/use-title-actions.ts:136 +msgid "Failed to update rating" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:172 +#: apps/web/src/components/settings/registration-section.tsx:44 +msgid "Failed to update registration setting" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:154 +msgid "Failed to update retention setting" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:156 +msgid "Failed to update schedule" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:152 +msgid "Failed to update scheduled backup setting" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:191 +#: apps/web/src/components/settings/update-check-section.tsx:43 +msgid "Failed to update setting" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:72 +#: apps/native/src/lib/title-actions.ts:40 +#: apps/native/src/lib/title-actions.ts:54 +#: apps/web/src/components/titles/use-title-actions.ts:114 +msgid "Failed to update status" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:123 +msgid "Failed to upload avatar" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:725 +msgid "Fetching your library data from {source}..." +msgstr "" + +#: apps/native/src/app/person/[id].tsx:340 +#: apps/web/src/components/people/filmography-grid.tsx:69 +msgid "Filmography" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:915 +msgid "Finished importing from {source}." +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:140 +msgid "Free" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:92 +msgid "Free up disk space by clearing cached metadata and images" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:309 +msgid "Frequency" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:101 +msgid "Friday" +msgstr "" + +#: apps/web/src/components/landing-page.tsx:156 +msgid "Get Started" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:220 +#: apps/native/src/app/person/[id].tsx:247 +#: apps/native/src/app/title/[id].tsx:255 +msgid "Go back" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:37 +msgid "Go Home" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:123 +msgid "Go to <0>Dashboard > Plugins > Webhook" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:368 +msgid "Go to Dashboard" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:71 +msgid "Go to Dashboard > Plugins > Webhook" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:379 +msgid "Go to Explore" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:212 +msgid "Health status" +msgstr "" + +#: apps/web/src/components/dashboard/welcome-header.tsx:14 +msgid "Here's what's happening with your library" +msgstr "" + +#: apps/web/src/components/nav-bar.tsx:125 +#: apps/web/src/components/nav-bar.tsx:290 +msgid "Home" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:335 +#: apps/web/src/components/settings/system-health-section.tsx:610 +msgid "Image cache" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:479 +msgid "Image Cache" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:598 +msgid "Image cache and backup disk usage" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:179 +msgid "Import" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:840 +msgid "Import {totalItems} items" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:912 +msgid "Import complete" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:340 +msgid "Import failed" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:584 +msgid "Import from {0}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:770 +msgid "Import from {source}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:298 +#: apps/web/src/components/settings/imports-section.tsx:329 +msgid "Import is still running in the background. Check back later." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:798 +msgid "Import options" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:921 +msgid "Imported" +msgstr "" + +#. placeholder {0}: event.job.importedCount +#. placeholder {1}: config.label +#: apps/web/src/components/settings/imports-section.tsx:292 +msgid "Imported {0} items from {1}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:863 +msgid "Importing from {source}" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:62 +msgid "In library" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:230 +msgid "In Library" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:133 +#: apps/web/src/components/dashboard/library-section.tsx:35 +msgid "In Your Library" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:105 +msgid "Install the <0>Webhook plugin<1/> from Jellyfin's plugin catalog" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:70 +msgid "Install the Webhook plugin from Jellyfin's plugin catalog" +msgstr "" + +#: apps/native/src/components/settings/integrations-section.tsx:20 +#: apps/web/src/components/settings/integrations-section.tsx:41 +msgid "Integrations" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:266 +msgid "Invalid token" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:399 +msgid "Job" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:243 +#~ msgid "Keeping" +#~ msgstr "Keeping" + +#. placeholder {0}: (value: string | null) => value === "0" ? t`unlimited` : value ? t`last ${value}` : null +#. placeholder {1}: [3, 5, 7, 14, 30, 0].map((n) => ( {n === 0 ? t`unlimited` : t`last ${n}`} )) +#: apps/web/src/components/settings/backup-schedule-section.tsx:243 +msgid "Keeping <0><1><2>{0}<3>{1} backups." +msgstr "" + +#: apps/web/src/components/command-palette.tsx:389 +#: apps/web/src/components/command-palette.tsx:404 +msgid "Keyboard Shortcuts" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:403 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:411 +#: apps/web/src/components/settings/language-section.tsx:42 +msgid "Language" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:270 +msgid "last {n}" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:258 +msgid "last {value}" +msgstr "" + +#. placeholder {0}: formatRelativeTime(lastEventAt) +#: apps/web/src/components/settings/integration-card.tsx:331 +msgid "Last event {0}" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:29 +msgid "Last event {timeAgo}" +msgstr "" + +#. placeholder {0}: formatRelativeTime(lastEventAt) +#: apps/web/src/components/settings/integration-card.tsx:338 +msgid "Last polled {0}" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:37 +msgid "Last polled {timeAgo}" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:405 +msgid "Last run" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:441 +msgid "Last run failed" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:437 +msgid "Last run succeeded" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:331 +msgid "Library refresh" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:245 +msgid "Loading…" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:334 +msgid "Lost connection to import. Check status in settings." +msgstr "" + +#: apps/web/src/components/settings/settings-shell.tsx:40 +msgid "Manage your account and preferences" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:182 +msgid "Manual backup" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:122 +msgid "Mark all episodes as watched?" +msgstr "" + +#: apps/native/src/components/titles/season-accordion.tsx:164 +#: apps/web/src/components/titles/title-seasons.tsx:115 +#: apps/web/src/components/titles/title-seasons.tsx:141 +msgid "Mark All Watched" +msgstr "" + +#: apps/native/src/components/dashboard/continue-watching-card.tsx:122 +msgid "Mark as Completed" +msgstr "" + +#: apps/native/src/components/ui/poster-card.tsx:252 +msgid "Mark as Watched" +msgstr "" + +#: apps/native/src/components/ui/poster-card.tsx:245 +msgid "Mark as Watching" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:449 +#: apps/web/src/components/titles/title-actions.tsx:29 +msgid "Mark Watched" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:49 +msgid "Marked \"{titleName}\" as completed" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:63 +#: apps/web/src/components/titles/use-title-actions.ts:147 +msgid "Marked \"{titleName}\" as watched" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:360 +msgid "Marked all episodes as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:62 +#: apps/native/src/lib/title-actions.ts:50 +msgid "Marked as completed" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:80 +#: apps/native/src/lib/title-actions.ts:64 +msgid "Marked as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:61 +#: apps/native/src/lib/title-actions.ts:37 +msgid "Marked as watching" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:340 +msgid "Member since {memberSince}" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:97 +msgid "Monday" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:585 +msgid "More Like This" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:541 +msgid "More Settings" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:362 +#: apps/native/src/components/search/search-result-row.tsx:35 +#: apps/native/src/components/search/search-result-row.tsx:96 +#: apps/web/src/components/explore/hero-banner.tsx:59 +#: apps/web/src/components/titles/title-hero.tsx:143 +msgid "Movie" +msgstr "" + +#: apps/web/src/components/people/filmography-grid.tsx:59 +#: apps/web/src/components/settings/imports-section.tsx:779 +msgid "Movies" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:161 +#~ msgid "Movies {periodSelect}" +#~ msgstr "Movies {periodSelect}" + +#: apps/web/src/components/dashboard/stats-display.tsx:162 +msgid "Movies {select}" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:60 +msgid "Movies this month" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:124 +#: apps/web/src/components/auth-form.tsx:181 +msgid "Name" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:107 +#: apps/web/src/components/settings/account-section.tsx:80 +msgid "Name updated" +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:301 +msgid "Need more help?" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:501 +msgid "Never" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:435 +msgid "Never run" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:98 +msgid "New account creation is currently disabled." +msgstr "" + +#: apps/web/src/routes/_auth/register.tsx:35 +msgid "New accounts are not being accepted right now. Contact the admin if you need access." +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:137 +msgid "New backup" +msgstr "" + +#: apps/native/src/app/change-password.tsx:145 +#: apps/web/src/components/settings/account-section.tsx:463 +msgid "New password" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:395 +msgid "New password must be at least 8 characters" +msgstr "" + +#: apps/web/src/components/people/filmography-grid.tsx:86 +#: apps/web/src/components/people/filmography-grid.tsx:99 +msgid "Newest" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:131 +msgid "Next backup {distance}" +msgstr "" + +#: apps/native/src/components/titles/continue-watching-banner.tsx:46 +msgid "Next Episode" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:408 +msgid "Next run" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:110 +#: apps/web/src/components/settings/system-health-section.tsx:662 +msgid "No backups yet" +msgstr "" + +#: apps/native/src/components/ui/offline-banner.ios.tsx:74 +#: apps/native/src/components/ui/offline-banner.ios.tsx:81 +#: apps/native/src/components/ui/offline-banner.tsx:59 +msgid "No internet connection" +msgstr "" + +#: apps/native/src/app/(tabs)/(search)/index.tsx:113 +msgid "No results for \"{debouncedQuery}\"" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:217 +msgid "No results found." +msgstr "" + +#: apps/native/src/components/explore/filterable-title-row.tsx:139 +#: apps/web/src/components/explore/filterable-title-row.tsx:151 +msgid "No titles found for this genre." +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:194 +#: apps/web/src/components/settings/integration-card.tsx:176 +#: apps/web/src/components/settings/system-health-section.tsx:249 +msgid "Not configured" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:17 +msgid "Not Found" +msgstr "" + +#: apps/web/src/components/title-card.tsx:67 +msgid "On Watchlist" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:693 +msgid "Open {source} to enter code" +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:308 +msgid "Open docs" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:190 +msgid "Open Emby, go to <0>Settings > Webhooks" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:91 +msgid "Open Emby, go to Settings > Webhooks" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:548 +msgid "Open in browser…" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:62 +msgid "Open Plex, go to <0>Settings > Webhooks<1/>" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:54 +msgid "Open Plex, go to Settings > Webhooks" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:269 +msgid "Open Radarr, go to <0>Settings > Import Lists" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:126 +msgid "Open Radarr, go to Settings > Import Lists" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:502 +#: apps/web/src/components/settings/registration-section.tsx:58 +msgid "Open registration" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:229 +msgid "Open Sonarr, go to <0>Settings > Import Lists" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:108 +msgid "Open Sonarr, go to Settings > Import Lists" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:158 +msgid "or" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:118 +msgid "OR" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:27 +msgid "Page Not Found" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:509 +msgid "Parsing..." +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:167 +#: apps/native/src/app/(auth)/register.tsx:187 +#: apps/web/src/components/auth-form.tsx:221 +msgid "Password" +msgstr "" + +#: apps/native/src/app/change-password.tsx:76 +#: apps/web/src/components/settings/account-section.tsx:414 +msgid "Password updated" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:399 +msgid "Passwords do not match" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:128 +#: apps/web/src/components/settings/integration-configs.tsx:284 +msgid "Paste the Radarr URL above into the List URL field" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:110 +#: apps/web/src/components/settings/integration-configs.tsx:244 +msgid "Paste the Sonarr URL above into the List URL field" +msgstr "" + +#: apps/web/src/components/settings/update-check-section.tsx:63 +msgid "Periodically check GitHub for new Sofa releases" +msgstr "" + +#: apps/native/src/components/search/search-result-row.tsx:38 +#: apps/native/src/components/search/search-result-row.tsx:99 +msgid "Person" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:243 +#: apps/web/src/routes/_app/people.$id.tsx:52 +msgid "Person not found" +msgstr "" + +#: apps/web/src/components/titles/trailer-dialog.tsx:24 +msgid "Play trailer" +msgstr "" + +#: apps/native/src/app/(tabs)/(explore)/index.tsx:107 +#: apps/web/src/components/explore/explore-client.tsx:126 +msgid "Popular Movies" +msgstr "" + +#: apps/native/src/app/(tabs)/(explore)/index.tsx:120 +#: apps/web/src/components/explore/explore-client.tsx:136 +msgid "Popular TV Shows" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:181 +msgid "Pre-restore backup" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:77 +msgid "Producer" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:130 +#: apps/web/src/components/settings/account-section.tsx:130 +msgid "Profile picture removed" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:119 +#: apps/web/src/components/settings/account-section.tsx:108 +msgid "Profile picture updated" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:196 +#: apps/web/src/components/settings/danger-section.tsx:220 +msgid "Purge all" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:202 +msgid "Purge all caches?" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:160 +msgid "Purge image cache?" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:154 +#: apps/web/src/components/settings/danger-section.tsx:177 +msgid "Purge images" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:111 +#: apps/web/src/components/settings/danger-section.tsx:135 +msgid "Purge metadata" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:117 +msgid "Purge metadata cache?" +msgstr "" + +#. placeholder {0}: data.deletedTitles +#. placeholder {1}: data.deletedPersons +#: apps/web/src/components/settings/danger-section.tsx:41 +msgid "Purged {0, plural, one {# stale title} other {# stale titles}} and {1, plural, one {# orphaned person} other {# orphaned persons}}" +msgstr "" + +#. placeholder {0}: metaResult.deletedTitles +#. placeholder {1}: metaResult.deletedPersons +#. placeholder {2}: imageResult.deletedFiles +#: apps/web/src/components/settings/danger-section.tsx:71 +msgid "Purged {0, plural, one {# title} other {# titles}}, {1, plural, one {# person} other {# persons}}, {2, plural, one {# file} other {# files}} ({freed} freed)" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:109 +#: apps/web/src/components/settings/danger-section.tsx:152 +#: apps/web/src/components/settings/danger-section.tsx:194 +msgid "Purging..." +msgstr "" + +#: apps/web/src/components/command-palette.tsx:359 +msgid "Quick Actions" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:123 +msgid "Radarr List URL" +msgstr "" + +#. placeholder {0}: input.stars +#. placeholder {1}: input.stars +#: apps/native/src/hooks/use-title-actions.ts:93 +msgid "Rated {0} {1, plural, one {star} other {stars}}" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:92 +#~ msgid "Rated {0} star{1}" +#~ msgstr "Rated {0} star{1}" + +#: apps/web/src/components/titles/use-title-actions.ts:131 +msgid "Rated {ratingStars} {ratingStars, plural, one {star} other {stars}}" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:88 +msgid "Rated {stars, plural, one {# star} other {# stars}}" +msgstr "" + +#: apps/web/src/components/people/filmography-grid.tsx:88 +#: apps/web/src/components/people/filmography-grid.tsx:102 +msgid "Rating" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:94 +#: apps/native/src/lib/title-actions.ts:90 +#: apps/web/src/components/titles/use-title-actions.ts:132 +msgid "Rating removed" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:782 +#: apps/web/src/components/settings/imports-section.tsx:813 +msgid "Ratings" +msgstr "" + +#: apps/web/src/components/expandable-text.tsx:51 +msgid "Read more" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:39 +#: apps/web/src/components/settings/integration-card.tsx:339 +msgid "Ready — not polled yet" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:31 +#: apps/web/src/components/settings/integration-card.tsx:332 +msgid "Ready — nothing received yet" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:315 +msgid "Recent Searches" +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:106 +#: apps/native/src/components/search/recently-viewed-list.tsx:86 +msgid "Recently Viewed" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:333 +msgid "Recommendations" +msgstr "" + +#: apps/web/src/components/titles/title-recommendations.tsx:40 +msgid "Recommended" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:154 +#: apps/web/src/components/dashboard/recommendations-section.tsx:22 +msgid "Recommended for You" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:143 +msgid "Redirecting…" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:182 +msgid "Refresh" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:172 +msgid "Refresh system health" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:140 +#: apps/native/src/components/settings/integration-card.tsx:289 +msgid "Regenerate" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:135 +#: apps/web/src/components/settings/integration-card.tsx:264 +msgid "Regenerate URL" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:293 +#: apps/web/src/components/landing-page.tsx:176 +msgid "Register" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:116 +msgid "Registering on {serverHost}" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:167 +#: apps/web/src/components/settings/registration-section.tsx:41 +msgid "Registration closed" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:97 +#: apps/web/src/routes/_auth/register.tsx:32 +msgid "Registration Closed" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:167 +#: apps/web/src/components/settings/registration-section.tsx:41 +msgid "Registration opened" +msgstr "" + +#: apps/web/src/components/titles/status-button.tsx:84 +msgid "Remove" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:101 +#: apps/web/src/components/titles/status-button.tsx:66 +msgid "Remove from library" +msgstr "" + +#: apps/native/src/components/dashboard/continue-watching-card.tsx:129 +#: apps/native/src/components/ui/poster-card.tsx:259 +msgid "Remove from Library" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:277 +msgid "Remove Photo" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:239 +msgid "Remove picture" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:199 +msgid "Remove profile picture" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:66 +#: apps/native/src/lib/title-actions.ts:75 +#: apps/web/src/components/titles/use-title-actions.ts:111 +msgid "Removed from library" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:138 +msgid "Rent" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:165 +msgid "Requires <0>Emby Server 4.7.9+ and an active <1>Emby Premiere<2/> license." +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:41 +msgid "Requires an active <0>Plex Pass<1/> subscription." +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:52 +msgid "Requires an active Plex Pass subscription." +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:88 +msgid "Requires Emby Server 4.7.9+ and an active Emby Premiere license." +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:59 +#: apps/web/src/components/settings/backup-restore-section.tsx:116 +msgid "Restore" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:89 +msgid "Restore database?" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:35 +msgid "Restore failed" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:132 +msgid "Restoring…" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:222 +msgid "Results" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:731 +msgid "Retrieving your watch history, watchlist, and ratings..." +msgstr "" + +#: apps/native/src/components/settings/integrations-section.tsx:42 +#: apps/native/src/components/ui/server-unreachable-banner.ios.tsx:73 +#: apps/native/src/components/ui/server-unreachable-banner.tsx:81 +#: apps/web/src/lib/orpc/client.ts:16 +msgid "Retry" +msgstr "" + +#: apps/web/src/routes/__root.tsx:119 +msgid "Return home" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:773 +msgid "Review what was found and choose what to import." +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:558 +msgid "Run now" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:102 +msgid "Saturday" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:322 +msgid "Save" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:296 +msgid "Save name" +msgstr "" + +#: apps/web/src/routes/__root.tsx:101 +msgid "Scene not found" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:402 +msgid "Schedule" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:201 +msgid "Schedule updated" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:179 +msgid "Scheduled backup" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:178 +msgid "Scheduled backups disabled" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:177 +msgid "Scheduled backups enabled" +msgstr "" + +#: apps/web/src/components/dashboard/stats-section.tsx:35 +msgid "Search for movies and TV shows to start tracking" +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:81 +#: apps/native/src/components/search/recently-viewed-list.tsx:64 +msgid "Search for movies, shows, or people" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:186 +msgid "Search for movies, TV shows, or run commands" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:195 +msgid "Search movies & TV shows…" +msgstr "" + +#: apps/native/src/app/(tabs)/(search)/index.tsx:90 +msgid "Search movies, shows, people..." +msgstr "" + +#: apps/web/src/components/nav-bar.tsx:193 +#: apps/web/src/components/nav-bar.tsx:202 +msgid "Search…" +msgstr "" + +#. placeholder {0}: season.seasonNumber +#: apps/native/src/components/titles/season-accordion.tsx:122 +#: apps/native/src/components/titles/season-accordion.tsx:128 +#: apps/web/src/components/titles/use-title-actions.ts:295 +#: apps/web/src/components/titles/use-title-actions.ts:335 +msgid "Season {0}" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:137 +#: apps/native/src/lib/title-actions.ts:124 +msgid "Season watched" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:536 +msgid "Seasons" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:497 +#: apps/web/src/routes/_app/settings.tsx:140 +msgid "Security" +msgstr "" + +#: apps/web/src/components/landing-page.tsx:105 +msgid "Self-hosted movie & TV tracker" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:121 +msgid "Server" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:452 +msgid "Server Health" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:380 +msgid "Server URL" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:361 +msgid "Set password" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:111 +#: apps/native/src/components/settings/integration-configs.ts:129 +#: apps/web/src/components/settings/integration-configs.tsx:247 +#: apps/web/src/components/settings/integration-configs.tsx:287 +msgid "Set your preferred quality profile and root folder" +msgstr "" + +#: apps/native/src/components/header-avatar.tsx:63 +#: apps/web/src/components/nav-bar.tsx:250 +#: apps/web/src/components/nav-bar.tsx:292 +#: apps/web/src/components/settings/settings-shell.tsx:36 +msgid "Settings" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:319 +#: apps/web/src/components/settings/integration-card.tsx:288 +msgid "Setup instructions" +msgstr "" + +#: apps/web/src/routes/setup.tsx:98 +msgid "Setup required" +msgstr "" + +#: apps/native/src/components/ui/expandable-text.tsx:62 +#: apps/native/src/components/ui/expandable-text.tsx:69 +#: apps/web/src/components/expandable-text.tsx:51 +msgid "Show less" +msgstr "" + +#: apps/native/src/components/ui/expandable-text.tsx:62 +#: apps/native/src/components/ui/expandable-text.tsx:69 +msgid "Show more" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:249 +#: apps/web/src/components/auth-form.tsx:283 +msgid "Sign in" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:205 +#: apps/web/src/components/landing-page.tsx:167 +msgid "Sign In" +msgstr "" + +#: apps/web/src/routes/_auth/register.tsx:45 +msgid "Sign in instead" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:91 +#: apps/web/src/components/auth-form.tsx:119 +msgid "Sign in to continue" +msgstr "" + +#. placeholder {0}: authConfig.data?.oidcProviderName ?? "SSO" +#. placeholder {0}: authConfig?.oidcProviderName || "SSO" +#: apps/native/src/app/(auth)/login.tsx:108 +#: apps/web/src/components/auth-form.tsx:145 +msgid "Sign in with {0}" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:368 +#: apps/native/src/components/header-avatar.tsx:87 +#: apps/web/src/components/nav-bar.tsx:262 +#: apps/web/src/components/settings/account-section.tsx:354 +msgid "Sign out" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:206 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:209 +#: apps/native/src/components/header-avatar.tsx:70 +#: apps/native/src/components/header-avatar.tsx:73 +msgid "Sign Out" +msgstr "" + +#: apps/native/src/app/change-password.tsx:199 +#: apps/web/src/components/settings/account-section.tsx:499 +msgid "Sign out of other sessions" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:922 +msgid "Skipped" +msgstr "" + +#. placeholder {0}: data.latestVersion +#: apps/web/src/components/update-toast.tsx:19 +msgid "Sofa v{0} is available" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:57 +#: apps/native/src/components/settings/integration-configs.ts:75 +#: apps/native/src/components/settings/integration-configs.ts:95 +#: apps/web/src/components/settings/integration-configs.tsx:86 +#: apps/web/src/components/settings/integration-configs.tsx:147 +#: apps/web/src/components/settings/integration-configs.tsx:208 +msgid "Sofa will automatically log movies and episodes when you finish watching them" +msgstr "" + +#: apps/native/src/app/change-password.tsx:81 +#: apps/native/src/app/person/[id].tsx:213 +#: apps/web/src/components/settings/account-section.tsx:417 +#: apps/web/src/routes/__root.tsx:143 +msgid "Something went wrong" +msgstr "" + +#: apps/web/src/routes/_app/titles.$id.tsx:118 +msgid "Something went wrong while loading this title. Please try again." +msgstr "" + +#: apps/native/src/lib/query-client.ts:33 +#: apps/web/src/lib/orpc/client.ts:14 +msgid "Something went wrong…" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:105 +msgid "Sonarr List URL" +msgstr "" + +#: apps/web/src/components/dashboard/stats-section.tsx:42 +msgid "Start exploring" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:142 +msgid "Start tracking movies and shows" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:117 +msgid "Start tracking your watches" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:388 +msgid "Starting at" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:888 +msgid "Starting import..." +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:65 +msgid "Status updated" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:595 +msgid "Storage" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:137 +msgid "Stream" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:96 +msgid "Sunday" +msgstr "" + +#. placeholder {0}: info.name +#: apps/web/src/components/settings/language-section.tsx:69 +msgid "Switch to {0}" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:30 +msgid "The page you're looking for doesn't exist." +msgstr "" + +#: apps/web/src/routes/_app/people.$id.tsx:55 +msgid "The person you're looking for doesn't exist or may have been removed from the database." +msgstr "" + +#: apps/web/src/routes/_app/titles.$id.tsx:149 +msgid "The title you're looking for doesn't exist or may have been removed from the database." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:866 +msgid "This may take a few minutes for large libraries. Please don't close this tab." +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:146 +msgid "This Month" +msgstr "" + +#: apps/web/src/routes/__root.tsx:104 +msgid "This page was left on the cutting room floor. It may have been moved, removed, or never made it past the screenplay." +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:606 +#: apps/web/src/routes/_app/settings.tsx:75 +#: apps/web/src/routes/setup.tsx:187 +msgid "This product uses the TMDB API but is not endorsed or certified by TMDB." +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:145 +msgid "This Week" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:163 +msgid "This will delete all cached TMDB images from disk. Images will be re-downloaded automatically as needed." +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:205 +msgid "This will delete all un-enriched stub titles and all cached images from disk. Everything will be re-imported and re-downloaded as needed." +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:120 +msgid "This will delete un-enriched stub titles that aren't in any user's library and clean up orphaned person records. Deleted titles will be re-imported if accessed again." +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:136 +msgid "This will invalidate the current {label} URL. You'll need to update it in {label}." +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:125 +msgid "This will mark every episode of this show as watched. You can undo this later by unmarking individual seasons." +msgstr "" + +#. placeholder {0}: formatBackupDate(backup.createdAt) +#: apps/web/src/components/settings/backup-section.tsx:259 +msgid "This will permanently delete the backup from <0>{0}. This cannot be undone." +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:61 +#: apps/native/src/components/search/recently-viewed-list.tsx:35 +msgid "This will remove all items from your history." +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:92 +msgid "This will replace your entire database with the uploaded file. A safety backup of your current data will be created first. Active sessions may need to refresh after restore." +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:147 +msgid "This Year" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:100 +msgid "Thursday" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:390 +msgid "Time:" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:251 +#: apps/web/src/routes/_app/titles.$id.tsx:146 +msgid "Title not found" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:131 +#: apps/web/src/components/settings/integration-configs.tsx:290 +msgid "Titles on your Sofa watchlist will be automatically added for download when Radarr polls this list (every 12 hours by default)" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:113 +#: apps/web/src/components/settings/integration-configs.tsx:250 +msgid "Titles on your Sofa watchlist will be automatically added for download when Sonarr polls this list (every 6 hours by default)" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:144 +msgid "Today" +msgstr "" + +#: apps/web/src/components/settings/update-check-section.tsx:71 +msgid "Toggle automatic update checks" +msgstr "" + +#: apps/web/src/components/settings/registration-section.tsx:69 +msgid "Toggle open registration" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:290 +msgid "Toggle scheduled backups" +msgstr "" + +#: apps/web/src/components/landing-page.tsx:131 +msgid "Track what you watch. Know what's next.<0/>Your library, your data, your rules." +msgstr "" + +#: apps/web/src/components/titles/trailer-dialog.tsx:37 +#: apps/web/src/components/titles/trailer-dialog.tsx:41 +msgid "Trailer" +msgstr "" + +#: apps/web/src/components/explore/hero-banner.tsx:78 +msgid "Trending today" +msgstr "" + +#: apps/native/src/app/(tabs)/(explore)/index.tsx:95 +#: apps/web/src/components/explore/explore-client.tsx:112 +msgid "Trending Today" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:537 +msgid "Trigger job" +msgstr "" + +#: apps/web/src/routes/__root.tsx:159 +msgid "Try again" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:98 +msgid "Tuesday" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:362 +#: apps/native/src/components/search/search-result-row.tsx:98 +#: apps/web/src/components/explore/hero-banner.tsx:64 +#: apps/web/src/components/people/filmography-grid.tsx:60 +#: apps/web/src/components/titles/title-hero.tsx:153 +msgid "TV" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:334 +msgid "TV episodes" +msgstr "" + +#: apps/native/src/components/search/search-result-row.tsx:37 +msgid "TV show" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:656 +msgid "unknown" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:256 +#: apps/web/src/components/settings/backup-schedule-section.tsx:270 +msgid "unlimited" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:273 +msgid "Unreachable" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:340 +msgid "Untitled" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:221 +msgid "Unwatch all" +msgstr "" + +#. placeholder {0}: season.name ?? t`Season ${season.seasonNumber}` +#: apps/web/src/components/titles/use-title-actions.ts:335 +msgid "Unwatched all of {0}" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:174 +msgid "Unwatched S{seasonNum} E{epNum}" +msgstr "" + +#: apps/native/src/components/titles/continue-watching-banner.tsx:79 +#: apps/web/src/components/dashboard/continue-watching-card.tsx:77 +msgid "Up next" +msgstr "" + +#. placeholder {0}: updateCheck.data.updateCheck.latestVersion +#: apps/native/src/app/(tabs)/(settings)/index.tsx:528 +msgid "Update available: {0}" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:337 +msgid "Update check" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:185 +#: apps/web/src/components/settings/update-check-section.tsx:40 +msgid "Update checks disabled" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:185 +#: apps/web/src/components/settings/update-check-section.tsx:40 +msgid "Update checks enabled" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:84 +msgid "Update failed" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:509 +msgid "Update password" +msgstr "" + +#: apps/native/src/app/change-password.tsx:218 +msgid "Update Password" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:134 +#: apps/web/src/components/settings/imports-section.tsx:509 +msgid "Upload" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:62 +msgid "Upload a .db file to replace the current database. A safety backup is created first." +msgstr "" + +#. placeholder {0}: config.accept +#. placeholder {1}: config.label +#: apps/web/src/components/settings/imports-section.tsx:637 +msgid "Upload a {0} export from your {1} account settings." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:634 +msgid "Upload export file" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:112 +msgid "Upload failed" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:241 +msgid "Upload picture" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:200 +msgid "Upload profile picture" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:129 +msgid "URL copied to clipboard" +msgstr "" + +#: apps/web/src/components/update-toast.tsx:24 +msgid "View release" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:699 +msgid "Waiting for authorization..." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:823 +msgid "Warnings" +msgstr "" + +#. placeholder {0}: result.warnings.length +#: apps/web/src/components/settings/imports-section.tsx:948 +msgid "Warnings ({0})" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:202 +msgid "Watch all" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:801 +msgid "Watch history" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:59 +msgid "Watch on {name}" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:298 +#: apps/web/src/components/titles/use-title-actions.ts:307 +msgid "Watched all of {seasonLabel}" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:123 +msgid "Watched all of {seasonName}" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:220 +#: apps/web/src/components/titles/use-title-actions.ts:229 +msgid "Watched S{seasonNum} E{epNum}" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:42 +#: apps/web/src/components/title-card.tsx:72 +#: apps/web/src/components/titles/status-button.tsx:19 +msgid "Watching" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:82 +#: apps/web/src/components/settings/imports-section.tsx:781 +#: apps/web/src/components/settings/imports-section.tsx:807 +#: apps/web/src/components/titles/status-button.tsx:55 +msgid "Watchlist" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:40 +msgid "Watchlisted" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:50 +#: apps/native/src/components/settings/integration-configs.ts:67 +#: apps/native/src/components/settings/integration-configs.ts:85 +msgid "Webhook URL" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:99 +msgid "Wednesday" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:112 +#: apps/web/src/components/dashboard/welcome-header.tsx:10 +msgid "Welcome back" +msgstr "" + +#: apps/web/src/components/dashboard/welcome-header.tsx:8 +msgid "Welcome back, {name}" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:483 +#: apps/web/src/components/titles/title-availability.tsx:154 +msgid "Where to Watch" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:141 +msgid "With Ads" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:75 +msgid "Writer" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:386 +msgid "You'll be signed out to change the server URL." +msgstr "" + +#. placeholder {0}: data.currentVersion +#: apps/web/src/components/update-toast.tsx:20 +msgid "You're running v{0}." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:678 +msgid "Your code:" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:141 +#: apps/web/src/components/dashboard/stats-section.tsx:32 +msgid "Your library is empty" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:134 +msgid "Your name" +msgstr "" + diff --git a/packages/i18n/src/po/en.ts b/packages/i18n/src/po/en.ts new file mode 100644 index 0000000..ef7e47e --- /dev/null +++ b/packages/i18n/src/po/en.ts @@ -0,0 +1 @@ +/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"+5kO8P\":[\"Saturday\"],\"+6i0lS\":[\"Retrieving your watch history, watchlist, and ratings...\"],\"+Cv+V9\":[\"Remove from library\"],\"+JkEpu\":[\"This page was left on the cutting room floor. It may have been moved, removed, or never made it past the screenplay.\"],\"+K0AvT\":[\"Disconnect\"],\"+N0l5/\":[\"Connected to <0>\",[\"serverHost\"],\". Tap to change.\"],\"+gLHYi\":[\"Failed to load title\"],\"+j1ex/\":[\"Failed to remove from library\"],\"+nF1ZO\":[\"Remove Photo\"],\"/+6dvC\":[\"Errors (\",[\"0\"],\")\"],\"/BBXeA\":[\"Connecting to server...\"],\"/DwR+n\":[\"Creating…\"],\"/XtbPO\":[[\"0\"],\" titles\"],\"/cg+QV\":[\"Watch all\"],\"/gQXGv\":[\"Failed to update status\"],\"/nT6AE\":[\"New password\"],\"/sHc1/\":[\"Update check\"],\"0+dyau\":[\"Failed to update retention setting\"],\"0BFJKK\":[\"Authorization was denied. Please try again.\"],\"0GHb20\":[\"Purge metadata cache?\"],\"0IBW21\":[\"Failed to purge caches\"],\"0gH/sc\":[\"Remove profile picture\"],\"1+P9RR\":[\"Switch to \",[\"0\"]],\"12cc1j\":[\"Watching\"],\"12lVOl\":[\"Self-hosted movie & TV tracker\"],\"1B4z0M\":[\"Filmography\"],\"1J4Ek0\":[\"Automatically back up your database on a schedule\"],\"1JhxXW\":[\"Episode \",[\"0\"],\", \",[\"episodeLabel\"]],\"1N6wNP\":[\"Import options\"],\"1Qz4uG\":[\"Enable the \\\"Playback\\\" event category\"],\"1hMWR6\":[\"Create account\"],\"1jHIjh\":[\"Watched all of \",[\"seasonName\"]],\"1vSYsG\":[\"Download backup\"],\"1wL1tj\":[\"TV show\"],\"2FletP\":[\"Purged \",[\"0\",\"plural\",{\"one\":[\"#\",\" title\"],\"other\":[\"#\",\" titles\"]}],\", \",[\"1\",\"plural\",{\"one\":[\"#\",\" person\"],\"other\":[\"#\",\" persons\"]}],\", \",[\"2\",\"plural\",{\"one\":[\"#\",\" file\"],\"other\":[\"#\",\" files\"]}],\" (\",[\"freed\"],\" freed)\"],\"2Fsd9r\":[\"This Month\"],\"2MPcep\":[\"Import complete\"],\"2Mu33Z\":[\"Cache management\"],\"2POOFK\":[\"Free\"],\"2Pt2NY\":[\"This will remove all items from your history.\"],\"2fCpt5\":[\"Return home\"],\"2pPBp6\":[\"Trending today\"],\"39y5bn\":[\"Friday\"],\"3Blefz\":[\"Ratings\"],\"3JTlG8\":[\"Recent Searches\"],\"3Jy8bM\":[\"Movies \",[\"select\"]],\"3L9OuQ\":[\"Episode \",[\"0\"]],\"3T8ziB\":[\"Create an account\"],\"3hELxX\":[\"Enter your Sofa server URL to get started\"],\"4fxLkp\":[\"Open Sonarr, go to <0>Settings > Import Lists\"],\"4kpBqM\":[\"Last event \",[\"0\"]],\"4uUjVO\":[\"Producer\"],\"4x+A56\":[\"Anonymous usage reporting\"],\"5IShvp\":[\"Import \",[\"totalItems\"],\" items\"],\"5IYJSv\":[\"Explore titles\"],\"5Y4mym\":[\"Import from \",[\"0\"]],\"5ZzgbQ\":[\"Connect \",[\"0\"]],\"5fEnbK\":[\"Failed to update scheduled backup setting\"],\"5lWFkC\":[\"Sign in\"],\"5v9C16\":[\"Connected to \",[\"source\"]],\"623bR4\":[\"Failed to trigger job\"],\"6HN0yh\":[\"Open Plex, go to Settings > Webhooks\"],\"6TNjOJ\":[\"Failed to update rating\"],\"6TfUy6\":[\"last \",[\"value\"]],\"6V3Ea3\":[\"Copied\"],\"6YtxFj\":[\"Name\"],\"6exX+8\":[\"Regenerate\"],\"6gRgw8\":[\"Retry\"],\"6lGV3K\":[\"Show less\"],\"76++pR\":[\"Warnings\"],\"77DIAu\":[\"Go to Dashboard > Plugins > Webhook\"],\"7Bj3x9\":[\"Failed\"],\"7D50KC\":[[\"label\"],\" disconnected\"],\"7GfM5w\":[\"Recently Viewed\"],\"7L01XJ\":[\"Actions\"],\"7eMo+U\":[\"Go Home\"],\"7p5kLi\":[\"Dashboard\"],\"85eoJ1\":[\"Schedule updated\"],\"8B9E2D\":[\"Day:\"],\"8YwF1J\":[\"Go to <0>Dashboard > Plugins > Webhook\"],\"8ZsakT\":[\"Password\"],\"8tjQCz\":[\"Explore\"],\"8vNtLy\":[\"Paste the Radarr URL above into the List URL field\"],\"8wYDMp\":[\"Already have an account?\"],\"9AE3vb\":[\"Open Plex, go to <0>Settings > Webhooks<1/>\"],\"9GW0ZB\":[\"Import is still running in the background. Check back later.\"],\"9NyAH9\":[\"Skipped\"],\"9Xhrps\":[\"Failed to connect\"],\"9ZLGbA\":[\"backups.\"],\"9eF5oV\":[\"Welcome back\"],\"9rG25a\":[\"Server URL\"],\"A+0rLe\":[\"Marked as completed\"],\"AOHgZp\":[\"Episodes\"],\"AOddWK\":[\"Connect \",[\"label\"]],\"ATfxL/\":[\"Trailer\"],\"AVlZoM\":[\"Environment\"],\"Acf6vF\":[\"Save name\"],\"AdoUfN\":[\"Failed to add to watchlist\"],\"AeXO77\":[\"Account\"],\"AjtYj0\":[\"On Watchlist\"],\"Avee+B\":[\"Failed to update name\"],\"B2R3xD\":[\"Toggle scheduled backups\"],\"BEVzjL\":[\"Import failed\"],\"BQnS5I\":[\"Open \",[\"source\"],\" to enter code\"],\"BUnoSB\":[[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"backup\"],\"other\":[\"backups\"]}],\" stored\"],\"BpttUI\":[\"Trending Today\"],\"BrrIs8\":[\"Storage\"],\"BskWMl\":[\"Unreachable\"],\"BzEFor\":[\"or\"],\"CGDHFb\":[\"Add a <0>Generic Destination and paste the URL above\"],\"CGExDN\":[\"Failed to purge image cache\"],\"CHeXFE\":[\"Status updated\"],\"CKyk7Q\":[\"Go back\"],\"CaB/+I\":[\"Welcome back, \",[\"name\"]],\"CodnUh\":[\"Removed from library\"],\"CpzGJY\":[\"This may take a few minutes for large libraries. Please don't close this tab.\"],\"CuPxpd\":[\"Requires an active <0>Plex Pass<1/> subscription.\"],\"CzBN6D\":[\"Start exploring\"],\"D+R2Xs\":[\"Starting import...\"],\"D3C4Yx\":[\"Current password is required\"],\"DBC3t5\":[\"Sunday\"],\"DI4lqs\":[\"Person not found\"],\"DKBbJf\":[\"Added \\\"\",[\"titleName\"],\"\\\" to watchlist\"],\"DPfwMq\":[\"Done\"],\"DZse/o\":[\"Add a \\\"Generic Destination\\\" and paste the URL above\"],\"E/QGRL\":[\"Disabled\"],\"E6nRW7\":[\"Copy URL\"],\"EWQlBH\":[\"Your library is empty\"],\"EWaCfj\":[\"Enter your current password and choose a new one.\"],\"Eeo/Gy\":[\"Failed to update setting\"],\"Efn6WU\":[\"This will delete all un-enriched stub titles and all cached images from disk. Everything will be re-imported and re-downloaded as needed.\"],\"Etp5if\":[\"Finished importing from \",[\"source\"],\".\"],\"F006BN\":[\"Import from \",[\"source\"]],\"FNvDMc\":[\"This Week\"],\"FWSp+7\":[\"Enter the code below on \",[\"source\"],\"'s website to authorize Sofa.\"],\"FXN0ro\":[\"Recommendations\"],\"FaU7Ag\":[\"Enable the \\\"Playback Stop\\\" notification type\"],\"FhvLDl\":[[\"watchedCount\"],\"/\",[\"0\"],\" episodes\"],\"Fo2bwm\":[\"Actor\"],\"FwRqjE\":[\"Image cache and backup disk usage\"],\"G00fgM\":[\"last \",[\"n\"]],\"G3myU+\":[\"Tuesday\"],\"GcCthe\":[\"In Library\"],\"Gf39AA\":[[\"0\"],\" triggered\"],\"GnhfWw\":[\"Toggle automatic update checks\"],\"GptGxg\":[\"Change password\"],\"GqTZ+S\":[\"Rated \",[\"ratingStars\"],\" \",[\"ratingStars\",\"plural\",{\"one\":[\"star\"],\"other\":[\"stars\"]}]],\"GrdN/F\":[\"Caught up — marked \",[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}],\" as watched\"],\"H4B5LG\":[\"This product uses the TMDB API but is not endorsed or certified by TMDB.\"],\"HBRd5n\":[\"Season \",[\"0\"]],\"HD+aQ7\":[\"Database backups\"],\"HG+31u\":[\"Purge all caches?\"],\"Haz+72\":[\"This will delete all cached TMDB images from disk. Images will be re-downloaded automatically as needed.\"],\"HbReP5\":[\"Marked \\\"\",[\"titleName\"],\"\\\" as completed\"],\"Hg6o8v\":[\"Refresh system health\"],\"HmEjnC\":[\"Last event \",[\"timeAgo\"]],\"HvDfH/\":[\"Imported\"],\"I89uD4\":[\"Restoring…\"],\"IRoxQm\":[\"Registration closed\"],\"IS0nrP\":[\"Create Account\"],\"IY9rQ0\":[\"Free up disk space by clearing cached metadata and images\"],\"IrC12v\":[\"Application\"],\"J28zul\":[\"Connecting...\"],\"J64cFL\":[\"Library refresh\"],\"JKmmmN\":[\"Added to watchlist\"],\"JN0f/Y\":[\"Connect to TMDB\"],\"JR6aH2\":[\"Movies \",[\"periodSelect\"]],\"JRQitQ\":[\"Confirm new password\"],\"JRadFJ\":[\"Start tracking your watches\"],\"JSwq8t\":[\"New account creation is currently disabled.\"],\"JY5Oyv\":[\"Database\"],\"JjsJnI\":[[\"0\"],\"/\",[\"1\"],\" episodes\"],\"JrFTcr\":[\"Connecting…\"],\"JwFbe8\":[\"TV\"],\"KDw4GX\":[\"Try again\"],\"KG6681\":[\"Episode watched\"],\"KIS/Sd\":[\"Sign out of other sessions\"],\"KK0ghs\":[\"Image cache\"],\"KVAoFR\":[\"unlimited\"],\"KcXJuc\":[\"Purging...\"],\"KhtG3o\":[\"Update Password\"],\"Khux7w\":[[\"0\",\"plural\",{\"one\":[\"#\",\" title\"],\"other\":[\"#\",\" titles\"]}]],\"KmWyx0\":[\"Job\"],\"KoF0x6\":[\"age \",[\"age\"]],\"Ksiej9\":[\"Requires an active Plex Pass subscription.\"],\"Kx9NEt\":[\"Invalid token\"],\"Lk6Jb/\":[\"Last polled \",[\"timeAgo\"]],\"LmEEic\":[\"Waiting for authorization...\"],\"LqKH42\":[\"Connect with \",[\"0\"]],\"Lrpjji\":[\"Disconnect \",[\"label\"]],\"Lu6Udx\":[\"Click \\\"+\\\" and select \\\"Custom Lists\\\"\"],\"MDoX++\":[\"Sonarr List URL\"],\"MRBlCJ\":[\"Failed to unmark some episodes\"],\"MUO7w9\":[\"Marked \\\"\",[\"titleName\"],\"\\\" as watched\"],\"MZbQHL\":[\"No results found.\"],\"MdxR1u\":[\"Choose your preferred display language\"],\"MkyzAV\":[\"Authorize Sofa to read your \",[\"0\"],\" library. No password shared.\"],\"N40H+G\":[\"All\"],\"N6SFhC\":[\"Sign in instead\"],\"N7h106\":[\"Sofa v\",[\"0\"],\" is available\"],\"N9RF2M\":[\"Click \\\"Add Webhook\\\" and paste the URL above\"],\"NBo4z0\":[\"Click <0>+ and select <1>Custom Lists\"],\"NICUmW\":[\"Director\"],\"NQzPoO\":[\"New backup\"],\"NSxl1l\":[\"More Settings\"],\"NVF43p\":[\"Time:\"],\"NdPMwS\":[\"In Your Library\"],\"NgaPSG\":[\"Failed to update schedule\"],\"O3oNi5\":[\"Email\"],\"OBcj5W\":[\"This will invalidate the current \",[\"label\"],\" URL. You'll need to update it in \",[\"label\"],\".\"],\"OL8hbM\":[\"New password must be at least 8 characters\"],\"ONWvwQ\":[\"Upload\"],\"OPFjyX\":[\"Install the <0>Webhook plugin<1/> from Jellyfin's plugin catalog\"],\"OW/+RD\":[\"Watch history\"],\"OZdaTZ\":[\"Person\"],\"OvO76u\":[\"Back to Login\"],\"OvdFIZ\":[\"Season watched\"],\"P2FLLe\":[\"View release\"],\"PBdLfg\":[\"No titles found for this genre.\"],\"PQ3qDa\":[\"Fetching your library data from \",[\"source\"],\"...\"],\"PjNoxI\":[\"Scheduled backup\"],\"Pn2B7/\":[\"Current password\"],\"PnEbL/\":[\"Rated \",[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"star\"],\"other\":[\"stars\"]}]],\"Pwqkdw\":[\"Loading…\"],\"Py87xY\":[\"Authorization succeeded but failed to fetch your library. Please try again.\"],\"Q3MPWA\":[\"Update password\"],\"QHcLEN\":[\"Connected\"],\"QK4UIx\":[\"Mark Watched\"],\"Qjlym2\":[\"Failed to create account\"],\"Qm1NmK\":[\"OR\"],\"Qoq+GP\":[\"Read more\"],\"QphVZW\":[\"Can't reach server\"],\"QqLJHH\":[\"This Year\"],\"R0yu2l\":[\"Catch up\"],\"R4YBui\":[\"Search for movies and TV shows to start tracking\"],\"RH46vw\":[[\"0\"],\"/\",[\"1\"],\" \",[\"2\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}]],\"RIR15/\":[\"Radarr List URL\"],\"RLe7Vk\":[\"Checking…\"],\"ROq8cl\":[\"Failed to parse file\"],\"RQq8Si\":[\"Registration opened\"],\"S0soqb\":[\"Failed to remove profile picture\"],\"S1McZh\":[\"Failed to upload avatar\"],\"S2ble5\":[[\"0\"],\" movies, \",[\"1\"],\" episodes\"],\"S2qPRR\":[\"Search movies & TV shows…\"],\"SDND4q\":[\"Not configured\"],\"SFdAk9\":[\"Unwatched S\",[\"seasonNum\"],\" E\",[\"epNum\"]],\"SUIUkB\":[\"Unwatch all\"],\"SbS+Bm\":[\"Regenerate URL\"],\"SlfejT\":[\"Error\"],\"Sp86ju\":[[\"0\"],\" ep\",[\"1\"]],\"SrfROI\":[\"Up next\"],\"SyPRjk\":[\"Sofa will automatically log movies and episodes when you finish watching them\"],\"T0/7WG\":[\"Next backup \",[\"distance\"]],\"TEaX6q\":[\"Backup deleted\"],\"TI7IKo\":[\"Rated \",[\"0\"],\" star\",[\"1\"]],\"TMb7iE\":[\"Profile picture removed\"],\"TqyQQS\":[\"Enable the <0>Playback event category\"],\"Tz0i8g\":[\"Settings\"],\"U+FxtW\":[\"Track what you watch. Know what's next.<0/>Your library, your data, your rules.\"],\"U3pytU\":[\"Admin\"],\"UC+OWB\":[\"Toggle open registration\"],\"UDMjsP\":[\"Quick Actions\"],\"UmQ6Fe\":[\"Purge image cache?\"],\"UtDm3q\":[\"URL copied to clipboard\"],\"V1Kh9Z\":[\"Episodes \",[\"select\"]],\"V6uzvC\":[\"More Like This\"],\"V9CuQ+\":[\"Connect to \",[\"source\"]],\"VAcXNz\":[\"Wednesday\"],\"VKyhZK\":[\"Title not found\"],\"VQvpro\":[\"Paste the Sonarr URL above into the List URL field\"],\"VhMDMg\":[\"Change Password\"],\"Vx0ayx\":[\"Failed to mark some episodes\"],\"WDC4PF\":[\"Episodes \",[\"periodSelect\"]],\"WEYdDv\":[\"Recommended\"],\"WMCwmR\":[\"Check for updates\"],\"WP48q2\":[\"Image Cache\"],\"WT1Ibn\":[\"Last run\"],\"Wb3E4g\":[\"Run now\"],\"WgF2UQ\":[\"You're running v\",[\"0\"],\".\"],\"WtWhSi\":[\"Rating removed\"],\"Wy/3II\":[\"Last polled \",[\"0\"]],\"XFCSYs\":[\"Cast\"],\"XN23JY\":[\"This will delete un-enriched stub titles that aren't in any user's library and clean up orphaned person records. Deleted titles will be re-imported if accessed again.\"],\"XZwihE\":[\"Ready — nothing received yet\"],\"XjTduw\":[\"Upload picture\"],\"YEGzVq\":[\"Failed to connect \",[\"label\"]],\"YErf89\":[\"Failed to purge metadata cache\"],\"YQ768h\":[\"Scene not found\"],\"YiRsXK\":[\"Clear Recently Viewed?\"],\"Yjp1zf\":[\"Don't have a server?\"],\"YqMfa9\":[\"Review what was found and choose what to import.\"],\"ZGUYm0\":[\"Ready — not polled yet\"],\"ZQKLI1\":[\"Danger Zone\"],\"ZVZUoU\":[[\"0\"],\" cached images\"],\"ZWTQ81\":[\"Watch on \",[\"name\"]],\"a3LDKx\":[\"Security\"],\"aLBUiR\":[\"Keeping <0><1><2>\",[\"0\"],\"<3>\",[\"1\"],\" backups.\"],\"aM0+kY\":[\"Purged \",[\"0\",\"plural\",{\"one\":[\"#\",\" stale title\"],\"other\":[\"#\",\" stale titles\"]}],\" and \",[\"1\",\"plural\",{\"one\":[\"#\",\" orphaned person\"],\"other\":[\"#\",\" orphaned persons\"]}]],\"aO1AxG\":[\"Episode unwatched\"],\"aV/hDI\":[\"Failed to disconnect \",[\"label\"]],\"acbSg0\":[\"Double tap to filter by \",[\"label\"]],\"agE7k4\":[\"Rated \",[\"stars\",\"plural\",{\"one\":[\"#\",\" star\"],\"other\":[\"#\",\" stars\"]}]],\"alPRaV\":[\"Titles on your Sofa watchlist will be automatically added for download when Radarr polls this list (every 12 hours by default)\"],\"aourBv\":[\"Scheduled backups enabled\"],\"apLLSU\":[\"Are you sure you want to sign out?\"],\"atc9MA\":[\"Open Emby, go to <0>Settings > Webhooks\"],\"auVUJO\":[\"Restore database?\"],\"ayqfr4\":[[\"label\"],\" URL regenerated\"],\"b/8KCH\":[\"This will mark every episode of this show as watched. You can undo this later by unmarking individual seasons.\"],\"b3Thhd\":[\"Upload failed\"],\"b5DFtH\":[\"Set password\"],\"bHYIks\":[\"Sign Out\"],\"bITrbE\":[\"Purge all\"],\"bNmdjU\":[\"Watchlist\"],\"bTRwag\":[\"Remove from Library\"],\"bXMotV\":[\"Marked as watched\"],\"bezGJ2\":[[\"0\"],\" images\"],\"boJlGf\":[\"Page Not Found\"],\"c1ssjI\":[\"Importing from \",[\"source\"]],\"c3b0B0\":[\"Get Started\"],\"c4b9Dm\":[\"No results for \\\"\",[\"debouncedQuery\"],\"\\\"\"],\"cQPKZt\":[\"Go to Dashboard\"],\"ccAJSB\":[\"Keeping\"],\"cgvva8\":[[\"0\"],\" ratings\"],\"cnGeoo\":[\"Delete\"],\"cpE88+\":[\"Create your account\"],\"d/6MoL\":[\"Manage your account and preferences\"],\"dA7BWh\":[\"Requires Emby Server 4.7.9+ and an active Emby Premiere license.\"],\"dEgA5A\":[\"Cancel\"],\"dTZwve\":[\"Marked all episodes as watched\"],\"dUCJry\":[\"Newest\"],\"ddwpAr\":[\"Warnings (\",[\"0\"],\")\"],\"e/ToF5\":[\"Sign in with \",[\"0\"]],\"e9sZMS\":[\"This will permanently delete the backup from <0>\",[\"0\"],\". This cannot be undone.\"],\"eFRooE\":[\"Last run failed\"],\"eM39Om\":[\"Watched all of \",[\"seasonLabel\"]],\"eZjYb8\":[\"Writer\"],\"ecUA8p\":[\"Today\"],\"evBxZy\":[\"Where to Watch\"],\"ezDa1h\":[\"Open docs\"],\"f+m696\":[\"An unexpected error occurred while loading this page. You can try again or head back to the dashboard.\"],\"f/AKdU\":[\"Are you sure you want to disconnect \",[\"label\"],\"? The current URL will stop working.\"],\"f7ax8J\":[\"Open in browser…\"],\"fMPkxb\":[\"Show more\"],\"fNMqNn\":[\"Popular TV Shows\"],\"fObVvy\":[\"Profile picture updated\"],\"fRettQ\":[[\"0\"],\" items\"],\"fUDRF9\":[\"Watchlisted\"],\"fcWrnU\":[\"Sign out\"],\"fgLNSM\":[\"Register\"],\"fsAEqk\":[\"Continue Watching\"],\"fzAeQI\":[\"Registering on \",[\"serverHost\"]],\"g+gBfk\":[\"Parsing...\"],\"g4JYff\":[\"Imported \",[\"0\"],\" items from \",[\"1\"]],\"gKtb5i\":[\"Titles on your Sofa watchlist will be automatically added for download when Sonarr polls this list (every 6 hours by default)\"],\"gaIAMq\":[\"Remove picture\"],\"gbEEMp\":[\"Delete backup\"],\"gcNLi0\":[\"Update checks enabled\"],\"gmB6oO\":[\"Schedule\"],\"h/T5Yb\":[\"Open registration\"],\"h4yKYk\":[\"Next run\"],\"h7MgpO\":[\"Keyboard Shortcuts\"],\"hTXYdY\":[\"Failed to create backup\"],\"hXYY5Q\":[\"Unwatched all of \",[\"0\"]],\"he3ygx\":[\"Copy\"],\"hjGupC\":[\"Lost connection to import. Check status in settings.\"],\"hlqjFc\":[\"In library\"],\"hou0tP\":[\"Stream\"],\"hty0d5\":[\"Monday\"],\"i0qMbr\":[\"Home\"],\"i9rcQ/\":[\"Movies\"],\"iGma9e\":[\"Update failed\"],\"iSLIjg\":[\"Connect\"],\"iWv3ck\":[\"Failed to catch up\"],\"iXZ09g\":[\"Mark as Completed\"],\"id08cd\":[\"Watched S\",[\"seasonNum\"],\" E\",[\"epNum\"]],\"ih87w/\":[\"Add to Library\"],\"ihn4zD\":[\"Search…\"],\"itDEco\":[\"Already have an account? Sign in\"],\"itheEn\":[\"With Ads\"],\"iwCRIF\":[\"You'll be signed out to change the server URL.\"],\"j5GBIy\":[\"Open Radarr, go to Settings > Import Lists\"],\"jAe8l4\":[[\"0\"],\" backups · last <0/>\"],\"jB3sfe\":[\"Manual backup\"],\"jFnMJ8\":[\"Double tap to clear this filter\"],\"jQsPwL\":[\"Scheduled backups disabled\"],\"jSjGeu\":[[\"0\"],\" items have no external IDs and will be resolved by title search, which may be less accurate.\"],\"jX6Gzg\":[\"This will replace your entire database with the uploaded file. A safety backup of your current data will be created first. Active sessions may need to refresh after restore.\"],\"jiHVUy\":[\"Pre-restore backup\"],\"k6c41p\":[\"Background jobs\"],\"kBDOjB\":[\"Backup schedule\"],\"kkDQ8m\":[\"Thursday\"],\"klOeIX\":[\"Failed to change password\"],\"ks3XeZ\":[\"Open Sonarr, go to Settings > Import Lists\"],\"kvuCtu\":[\"Something went wrong while loading this title. Please try again.\"],\"kx0s+n\":[\"Results\"],\"l2wcoS\":[\"Last run succeeded\"],\"l3s5ri\":[\"Import\"],\"lCF0wC\":[\"Refresh\"],\"lJ1yo4\":[\"Failed to sign in\"],\"lVqzRx\":[\"Click <0>Add Webhook and paste the URL above\"],\"lXkUEV\":[\"Availability\"],\"lcLe89\":[\"Search for movies, TV shows, or run commands\"],\"lfVyvz\":[\"Add to Watchlist\"],\"llDXYJ\":[\"Backups\"],\"llkZR0\":[\"Could not load integrations\"],\"ln9/n9\":[\"Don't have an account?\"],\"lpIMne\":[\"Passwords do not match\"],\"lqY3WY\":[\"Change Server\"],\"luoodD\":[\"Setup required\"],\"m5WhJy\":[\"Automatic update checks\"],\"mIx58S\":[\"Purge images\"],\"mYBORk\":[\"Movie\"],\"ml9cU0\":[\"Failed to regenerate \",[\"label\"],\" URL\"],\"mqwkjd\":[\"Health status\"],\"mqxHH7\":[\"Go to Explore\"],\"mzI/c+\":[\"Download\"],\"n1ekoW\":[\"Sign In\"],\"n3Pzd7\":[\"Backup created\"],\"nBHvPL\":[\"Cancel editing\"],\"nJw77c\":[[\"0\",\"plural\",{\"one\":[\"#\",\" image\"],\"other\":[\"#\",\" images\"]}]],\"nO0Qnj\":[\"Your code:\"],\"nRP1xx\":[\"Need more help?\"],\"nV1LjT\":[\"Enable the <0>Playback Stop notification type\"],\"nVsE67\":[\"Mark as Watching\"],\"nZ7lF1\":[\"Device code expired. Please try again.\"],\"nbfdhU\":[\"Integrations\"],\"nbmpf9\":[\"Purge metadata\"],\"nnKJTm\":[\"Failed to mark all episodes as watched\"],\"nszbQG\":[\"No backups yet\"],\"nuh/Wq\":[\"Webhook URL\"],\"nwtY4N\":[\"Something went wrong\"],\"oAIA3w\":[\"Search for movies, shows, or people\"],\"oC8IMh\":[\"Search movies, shows, people...\"],\"oNvZcA\":[\"Episodes this week\"],\"oXq+Wr\":[[\"label\"],\" connected\"],\"ogtYkT\":[\"Password updated\"],\"ojtedN\":[\"Open Radarr, go to <0>Settings > Import Lists\"],\"olMi35\":[\"Recommended for You\"],\"p+PZEl\":[\"Here's what's happening with your library\"],\"pAtylB\":[\"Not Found\"],\"pG9pq1\":[\"Deleted \",[\"0\",\"plural\",{\"one\":[\"#\",\" file\"],\"other\":[\"#\",\" files\"]}],\", freed \",[\"freed\"]],\"pWWFjv\":[\"Checked <0/>\"],\"ph76H6\":[\"Allow new users to create accounts\"],\"pjgw0N\":[\"Choose how to import your \",[\"0\"],\" data.\"],\"puo3W3\":[\"Redirecting…\"],\"q3GraM\":[\"...and \",[\"0\"],\" more\"],\"q6nrFE\":[\"died at \",[\"age\"]],\"q6pUQ9\":[\"Upload a .db file to replace the current database. A safety backup is created first.\"],\"q8yluz\":[\"Your name\"],\"qA6VR5\":[\"Requires <0>Emby Server 4.7.9+ and an active <1>Emby Premiere<2/> license.\"],\"qF2IBM\":[\"Movies this month\"],\"qHbApt\":[\"Failed to load backup schedule settings.\"],\"qLB1zX\":[\"Upload a \",[\"0\"],\" export from your \",[\"1\"],\" account settings.\"],\"qPNzfu\":[\"Update checks disabled\"],\"qWoML/\":[\"Add a new webhook and paste the URL above\"],\"qiOIiY\":[\"Buy\"],\"qn1X6N\":[\"Failed to update registration setting\"],\"qqWcBV\":[\"Completed\"],\"qqeAJM\":[\"Never\"],\"r9aDAY\":[[\"count\"],\" earlier \",[\"count\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}],\" unwatched\"],\"rLgPvm\":[\"Backup\"],\"rSTpb5\":[\"Server Health\"],\"rdymVD\":[\"Trigger job\"],\"rtir7c\":[\"unknown\"],\"s0U4ZZ\":[\"TV episodes\"],\"s4vVUm\":[\"Could not load person details\"],\"s9dVME\":[\"Failed to unmark episode\"],\"sGH11W\":[\"Server\"],\"sl/qWH\":[\"Upload profile picture\"],\"sstysK\":[\"Failed to mark episode\"],\"stZeiF\":[[\"watched\"],\"/\",[\"total\"],\" episodes\"],\"t/Ch8S\":[\"Marked as watching\"],\"t/YqKh\":[\"Remove\"],\"tfDRzk\":[\"Save\"],\"tiymc0\":[\"Something went wrong…\"],\"u9ugU7\":[[\"watchedCount\"],\"/\",[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}]],\"uBAxNB\":[\"Editor\"],\"uBrbSu\":[\"Failed to start \",[\"0\"],\" connection\"],\"uM6jnS\":[\"Restore failed\"],\"uMrJrX\":[\"Admin only\"],\"uswLvZ\":[\"The title you're looking for doesn't exist or may have been removed from the database.\"],\"uxOntd\":[[\"watchedCount\"],\" of \",[\"0\"],\" episodes watched\"],\"v2CA3w\":[\"Change Photo\"],\"v5ipVu\":[\"Mark all episodes as watched?\"],\"vKfeax\":[\"Open Emby, go to Settings > Webhooks\"],\"vXIe7J\":[\"Language\"],\"vuCCZ7\":[\"Failed to parse import data\"],\"vwKERN\":[\"Failed to delete backup\"],\"w8pqsh\":[\"Mark All Watched\"],\"wA7B2T\":[\"New accounts are not being accepted right now. Contact the admin if you need access.\"],\"wGFX13\":[\"Starting at\"],\"wR1UAy\":[\"Popular Movies\"],\"wRWcdL\":[\"Play trailer\"],\"wThGrS\":[\"App Settings\"],\"wZK4Xg\":[\"Never run\"],\"wdLxgL\":[\"Member since \",[\"memberSince\"]],\"wja8aL\":[\"Untitled\"],\"wtGebH\":[\"The person you're looking for doesn't exist or may have been removed from the database.\"],\"wtsbt5\":[\"Add to watchlist\"],\"wtuVU4\":[\"Frequency\"],\"wx7pwA\":[\"Mark as Watched\"],\"x4ZiTl\":[\"Setup instructions\"],\"xCJdfg\":[\"Clear\"],\"xGVfLh\":[\"Continue\"],\"xLoCm2\":[\"Check configuration\"],\"xSrU2g\":[[\"healthyCount\"],\" of \",[\"0\"],\" jobs healthy\"],\"xazqmy\":[\"Seasons\"],\"xbBXhy\":[\"Periodically check GitHub for new Sofa releases\"],\"xrh2/M\":[\"Failed to mark as watched\"],\"y3e9pF\":[\"Delete backup?\"],\"y6Urel\":[\"Next Episode\"],\"yGvjAo\":[\"Update available: \",[\"0\"]],\"yKu/3Y\":[\"Restore\"],\"yYxB17\":[\"Clear all\"],\"ybtG1U\":[[\"0\"],\" backup\",[\"1\"],\" stored\"],\"yey/zg\":[\"Database restored. Reloading...\"],\"ygo0l/\":[\"Start tracking movies and shows\"],\"yvwIbI\":[\"Upload export file\"],\"yz7wBu\":[\"Close\"],\"z1U/Fh\":[\"Rating\"],\"z5evln\":[\"No internet connection\"],\"zAvS8w\":[\"Sign in to continue\"],\"zEqK2w\":[\"The page you're looking for doesn't exist.\"],\"zFkiTv\":[\"Install the Webhook plugin from Jellyfin's plugin catalog\"],\"zNyR4f\":[\"Set your preferred quality profile and root folder\"],\"za8Le/\":[\"Registration Closed\"],\"zb77GC\":[\"Rent\"],\"ztAdhw\":[\"Name updated\"]}")as Messages; \ No newline at end of file diff --git a/packages/i18n/src/po/es.po b/packages/i18n/src/po/es.po new file mode 100644 index 0000000..bcd12d5 --- /dev/null +++ b/packages/i18n/src/po/es.po @@ -0,0 +1,2433 @@ +msgid "" +msgstr "" +"POT-Creation-Date: 2026-03-17 21:04-0400\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: @lingui/cli\n" +"Language: es\n" +"Project-Id-Version: sofa\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2026-03-18 16:04\n" +"Last-Translator: \n" +"Language-Team: Spanish\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: sofa\n" +"X-Crowdin-Project-ID: 881052\n" +"X-Crowdin-Language: es-ES\n" +"X-Crowdin-File: /packages/i18n/src/po/en.po\n" +"X-Crowdin-File-ID: 12\n" + +#. placeholder {0}: result.errors.length - 50 +#: apps/web/src/components/settings/imports-section.tsx:938 +msgid "...and {0} more" +msgstr "" + +#. placeholder {0}: systemHealth.data.imageCache.imageCount +#: apps/native/src/app/(tabs)/(settings)/index.tsx:482 +msgid "{0, plural, one {# image} other {# images}}" +msgstr "" + +#. placeholder {0}: systemHealth.data.database.titleCount +#: apps/native/src/app/(tabs)/(settings)/index.tsx:466 +msgid "{0, plural, one {# title} other {# titles}}" +msgstr "" + +#. placeholder {0}: displayBackups.length +#. placeholder {1}: displayBackups.length +#: apps/web/src/components/settings/backup-section.tsx:109 +msgid "{0} {1, plural, one {backup} other {backups}} stored" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:105 +#~ msgid "{0} backup{1} stored" +#~ msgstr "{0} backup{1} stored" + +#. placeholder {0}: backups.backupCount +#: apps/web/src/components/settings/system-health-section.tsx:654 +msgid "{0} backups · last <0/>" +msgstr "" + +#. placeholder {0}: imageCache.imageCount.toLocaleString() +#: apps/web/src/components/settings/system-health-section.tsx:621 +msgid "{0} cached images" +msgstr "" + +#. placeholder {0}: member.episodeCount +#. placeholder {1}: member.episodeCount !== 1 ? "s" : "" +#: apps/web/src/components/titles/cast-carousel.tsx:80 +msgid "{0} ep{1}" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:472 +#~ msgid "{0} images" +#~ msgstr "{0} images" + +#. placeholder {0}: stats.watchlist +#: apps/web/src/components/settings/imports-section.tsx:808 +msgid "{0} items" +msgstr "" + +#. placeholder {0}: preview.diagnostics.unresolved +#: apps/web/src/components/settings/imports-section.tsx:788 +msgid "{0} items have no external IDs and will be resolved by title search, which may be less accurate." +msgstr "" + +#. placeholder {0}: stats.movies +#. placeholder {1}: stats.episodes +#: apps/web/src/components/settings/imports-section.tsx:802 +msgid "{0} movies, {1} episodes" +msgstr "" + +#. placeholder {0}: stats.ratings +#: apps/web/src/components/settings/imports-section.tsx:814 +msgid "{0} ratings" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:456 +#~ msgid "{0} titles" +#~ msgstr "{0} titles" + +#. placeholder {0}: JOB_LABELS[name] ?? name +#: apps/web/src/components/settings/system-health-section.tsx:343 +msgid "{0} triggered" +msgstr "" + +#. placeholder {0}: item.watchedEpisodes +#. placeholder {1}: item.totalEpisodes +#. placeholder {2}: item.totalEpisodes +#: apps/web/src/components/dashboard/continue-watching-card.tsx:93 +msgid "{0}/{1} {2, plural, one {episode} other {episodes}}" +msgstr "" + +#: apps/web/src/components/dashboard/continue-watching-card.tsx:92 +#~ msgid "{0}/{1} episodes" +#~ msgstr "{0}/{1} episodes" + +#: apps/web/src/components/titles/use-title-actions.ts:221 +#: apps/web/src/components/titles/use-title-actions.ts:299 +msgid "{count} earlier {count, plural, one {episode} other {episodes}} unwatched" +msgstr "" + +#. placeholder {0}: activeJobs.length +#: apps/web/src/components/settings/system-health-section.tsx:385 +msgid "{healthyCount} of {0} jobs healthy" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:96 +#: apps/web/src/components/settings/integration-card.tsx:106 +msgid "{label} connected" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:106 +#: apps/web/src/components/settings/integration-card.tsx:122 +msgid "{label} disconnected" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:116 +#: apps/web/src/components/settings/integration-card.tsx:138 +msgid "{label} URL regenerated" +msgstr "" + +#: apps/web/src/components/title-card.tsx:166 +msgid "{watched}/{total} episodes" +msgstr "" + +#. placeholder {0}: episodes.length +#: apps/native/src/components/titles/season-accordion.tsx:122 +msgid "{watchedCount} of {0} episodes watched" +msgstr "" + +#. placeholder {0}: episodes.length +#. placeholder {1}: episodes.length +#: apps/native/src/components/titles/season-accordion.tsx:131 +msgid "{watchedCount}/{0} {1, plural, one {episode} other {episodes}}" +msgstr "" + +#: apps/native/src/components/titles/season-accordion.tsx:130 +#~ msgid "{watchedCount}/{0} episodes" +#~ msgstr "{watchedCount}/{0} episodes" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:231 +#: apps/web/src/components/settings/account-section.tsx:175 +msgid "Account" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:412 +msgid "Actions" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:71 +msgid "Actor" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:72 +msgid "Add a \"Generic Destination\" and paste the URL above" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:131 +msgid "Add a <0>Generic Destination and paste the URL above" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:92 +#: apps/web/src/components/settings/integration-configs.tsx:198 +msgid "Add a new webhook and paste the URL above" +msgstr "" + +#: apps/web/src/components/explore/hero-banner.tsx:99 +msgid "Add to Library" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:72 +msgid "Add to watchlist" +msgstr "" + +#: apps/native/src/components/explore/hero-banner.tsx:157 +#: apps/native/src/components/ui/poster-card.tsx:238 +#: apps/web/src/components/title-card.tsx:146 +msgid "Add to Watchlist" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:23 +msgid "Added \"{titleName}\" to watchlist" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:44 +#: apps/native/src/hooks/use-title-actions.ts:60 +#: apps/native/src/lib/title-actions.ts:24 +#: apps/web/src/components/titles/use-title-actions.ts:111 +msgid "Added to watchlist" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:353 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:454 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:499 +#: apps/web/src/components/nav-bar.tsx:235 +#: apps/web/src/components/settings/account-section.tsx:335 +msgid "Admin" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:124 +msgid "Admin only" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:308 +#: apps/web/src/components/people/person-hero.tsx:91 +msgid "age {age}" +msgstr "" + +#: apps/native/src/components/explore/filterable-title-row.tsx:119 +#: apps/web/src/components/people/filmography-grid.tsx:58 +msgid "All" +msgstr "" + +#: apps/web/src/components/settings/registration-section.tsx:61 +msgid "Allow new users to create accounts" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:278 +msgid "Already have an account?" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:239 +msgid "Already have an account? Sign in" +msgstr "" + +#: apps/web/src/routes/__root.tsx:146 +msgid "An unexpected error occurred while loading this page. You can try again or head back to the dashboard." +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:427 +msgid "Anonymous usage reporting" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:102 +msgid "App Settings" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:378 +msgid "Application" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:151 +msgid "Are you sure you want to disconnect {label}? The current URL will stop working." +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:206 +#: apps/native/src/components/header-avatar.tsx:70 +msgid "Are you sure you want to sign out?" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:450 +msgid "Authorization succeeded but failed to fetch your library. Please try again." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:440 +msgid "Authorization was denied. Please try again." +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:611 +msgid "Authorize Sofa to read your {0} library. No password shared." +msgstr "" + +#: apps/web/src/components/settings/update-check-section.tsx:60 +msgid "Automatic update checks" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:279 +msgid "Automatically back up your database on a schedule" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:332 +msgid "Availability" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:104 +msgid "Back to Login" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:382 +msgid "Background jobs" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:336 +msgid "Backup" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:69 +msgid "Backup created" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:94 +msgid "Backup deleted" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:234 +msgid "Backup schedule" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:641 +#: apps/web/src/routes/_app/settings.tsx:166 +msgid "Backups" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:272 +#~ msgid "backups." +#~ msgstr "backups." + +#: apps/web/src/components/titles/title-availability.tsx:139 +msgid "Buy" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:89 +msgid "Cache management" +msgstr "" + +#: apps/native/src/components/ui/server-unreachable-banner.ios.tsx:66 +#: apps/native/src/components/ui/server-unreachable-banner.tsx:74 +msgid "Can't reach server" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:207 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:332 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:388 +#: apps/native/src/components/header-avatar.tsx:71 +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:63 +#: apps/native/src/components/search/recently-viewed-list.tsx:37 +#: apps/native/src/components/settings/integration-card.tsx:138 +#: apps/native/src/components/settings/integration-card.tsx:153 +#: apps/web/src/components/settings/account-section.tsx:505 +#: apps/web/src/components/settings/backup-restore-section.tsx:107 +#: apps/web/src/components/settings/backup-section.tsx:270 +#: apps/web/src/components/settings/danger-section.tsx:129 +#: apps/web/src/components/settings/danger-section.tsx:171 +#: apps/web/src/components/settings/danger-section.tsx:214 +#: apps/web/src/components/settings/imports-section.tsx:645 +#: apps/web/src/components/settings/imports-section.tsx:710 +#: apps/web/src/components/settings/imports-section.tsx:837 +#: apps/web/src/components/titles/title-seasons.tsx:133 +msgid "Cancel" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:307 +msgid "Cancel editing" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:559 +#: apps/web/src/components/titles/cast-carousel.tsx:21 +msgid "Cast" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:223 +#: apps/web/src/components/titles/use-title-actions.ts:301 +msgid "Catch up" +msgstr "" + +#. placeholder {0}: episodeIds.length +#. placeholder {1}: episodeIds.length +#: apps/web/src/components/titles/use-title-actions.ts:82 +msgid "Caught up — marked {0} {1, plural, one {episode} other {episodes}} as watched" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:361 +#: apps/web/src/components/settings/account-section.tsx:427 +#: apps/web/src/components/settings/account-section.tsx:432 +msgid "Change password" +msgstr "" + +#: apps/native/src/app/change-password.tsx:98 +msgid "Change Password" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:267 +msgid "Change Photo" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:385 +msgid "Change Server" +msgstr "" + +#: apps/web/src/components/setup/refresh-button.tsx:30 +msgid "Check configuration" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:513 +msgid "Check for updates" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:215 +msgid "Checked <0/>" +msgstr "" + +#: apps/web/src/components/setup/refresh-button.tsx:28 +msgid "Checking…" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:587 +msgid "Choose how to import your {0} data." +msgstr "" + +#: apps/web/src/components/settings/language-section.tsx:29 +#~ msgid "Choose your preferred display language" +#~ msgstr "Choose your preferred display language" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:65 +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:115 +#: apps/native/src/components/search/recently-viewed-list.tsx:39 +#: apps/native/src/components/search/recently-viewed-list.tsx:95 +msgid "Clear" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:322 +msgid "Clear all" +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:60 +#: apps/native/src/components/search/recently-viewed-list.tsx:34 +msgid "Clear Recently Viewed?" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:109 +#: apps/native/src/components/settings/integration-configs.ts:127 +msgid "Click \"+\" and select \"Custom Lists\"" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:55 +msgid "Click \"Add Webhook\" and paste the URL above" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:237 +#: apps/web/src/components/settings/integration-configs.tsx:277 +msgid "Click <0>+ and select <1>Custom Lists" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:79 +msgid "Click <0>Add Webhook and paste the URL above" +msgstr "" + +#: apps/native/src/components/navigation/modal-stack-header.tsx:14 +msgid "Close" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:63 +#: apps/native/src/components/titles/status-action-button.tsx:44 +#: apps/web/src/components/dashboard/stats-display.tsx:238 +#: apps/web/src/components/title-card.tsx:77 +#: apps/web/src/components/titles/status-button.tsx:30 +msgid "Completed" +msgstr "" + +#: apps/native/src/app/change-password.tsx:173 +#: apps/web/src/components/settings/account-section.tsx:477 +msgid "Confirm new password" +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:207 +#: apps/web/src/components/settings/imports-section.tsx:499 +msgid "Connect" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/integration-card.tsx:203 +msgid "Connect {0}" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:238 +msgid "Connect {label}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:665 +msgid "Connect to {source}" +msgstr "" + +#: apps/web/src/routes/setup.tsx:101 +msgid "Connect to TMDB" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:608 +msgid "Connect with {0}" +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:197 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:474 +#: apps/web/src/components/settings/system-health-section.tsx:256 +msgid "Connected" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:722 +msgid "Connected to {source}" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:240 +msgid "Connected to <0>{serverHost}. Tap to change." +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:186 +msgid "Connecting to server..." +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:201 +msgid "Connecting..." +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:237 +msgid "Connecting…" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:390 +msgid "Continue" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:113 +#: apps/web/src/components/dashboard/continue-watching-section.tsx:25 +msgid "Continue Watching" +msgstr "" + +#: apps/web/src/components/setup/copy-button.tsx:25 +msgid "Copied" +msgstr "" + +#: apps/web/src/components/setup/copy-button.tsx:30 +msgid "Copy" +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:246 +msgid "Copy URL" +msgstr "" + +#: apps/native/src/components/settings/integrations-section.tsx:29 +msgid "Could not load integrations" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:216 +msgid "Could not load person details" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:247 +msgid "Create account" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:115 +#: apps/native/src/app/(auth)/register.tsx:225 +msgid "Create Account" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:216 +msgid "Create an account" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:110 +msgid "Create your account" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:137 +msgid "Creating…" +msgstr "" + +#: apps/native/src/app/change-password.tsx:120 +#: apps/web/src/components/settings/account-section.tsx:449 +msgid "Current password" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:391 +msgid "Current password is required" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:195 +msgid "Danger Zone" +msgstr "" + +#: apps/web/src/routes/__root.tsx:167 +#: apps/web/src/routes/_app/people.$id.tsx:78 +#: apps/web/src/routes/_app/titles.$id.tsx:126 +#: apps/web/src/routes/_app/titles.$id.tsx:172 +msgid "Dashboard" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:463 +#: apps/web/src/components/settings/system-health-section.tsx:229 +msgid "Database" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:126 +msgid "Database backups" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:31 +msgid "Database restored. Reloading..." +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:342 +msgid "Day:" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:250 +#: apps/web/src/components/settings/backup-section.tsx:280 +msgid "Delete" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:235 +msgid "Delete backup" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:256 +msgid "Delete backup?" +msgstr "" + +#. placeholder {0}: data.deletedFiles +#: apps/web/src/components/settings/danger-section.tsx:54 +msgid "Deleted {0, plural, one {# file} other {# files}}, freed {freed}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:412 +#: apps/web/src/components/settings/imports-section.tsx:444 +msgid "Device code expired. Please try again." +msgstr "" + +#: apps/native/src/app/person/[id].tsx:307 +#: apps/web/src/components/people/person-hero.tsx:91 +msgid "died at {age}" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:73 +msgid "Director" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:431 +#: apps/web/src/components/settings/system-health-section.tsx:632 +msgid "Disabled" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:155 +#: apps/native/src/components/settings/integration-card.tsx:298 +#: apps/web/src/components/settings/integration-card.tsx:274 +msgid "Disconnect" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:150 +msgid "Disconnect {label}" +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:242 +msgid "Don't have a server?" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:288 +msgid "Don't have an account?" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:962 +msgid "Done" +msgstr "" + +#: apps/native/src/components/explore/genre-chip.tsx:27 +msgid "Double tap to clear this filter" +msgstr "" + +#: apps/native/src/components/explore/genre-chip.tsx:28 +msgid "Double tap to filter by {label}" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:71 +#: apps/web/src/components/settings/backup-section.tsx:223 +msgid "Download" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:214 +msgid "Download backup" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:79 +msgid "Editor" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:133 +#: apps/native/src/app/(auth)/register.tsx:154 +#: apps/web/src/components/auth-form.tsx:201 +msgid "Email" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:73 +msgid "Enable the \"Playback Stop\" notification type" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:93 +msgid "Enable the \"Playback\" event category" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:140 +msgid "Enable the <0>Playback Stop notification type" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:201 +msgid "Enable the <0>Playback event category" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:668 +msgid "Enter the code below on {source}'s website to authorize Sofa." +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:435 +msgid "Enter your current password and choose a new one." +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:151 +msgid "Enter your Sofa server URL to get started" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:289 +msgid "Environment" +msgstr "" + +#. placeholder {0}: episode.episodeNumber +#: apps/native/src/components/titles/episode-row.tsx:29 +#: apps/native/src/components/titles/episode-row.tsx:55 +msgid "Episode {0}" +msgstr "" + +#. placeholder {0}: episode.episodeNumber +#: apps/native/src/components/titles/episode-row.tsx:36 +msgid "Episode {0}, {episodeLabel}" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:123 +#: apps/native/src/lib/title-actions.ts:111 +msgid "Episode unwatched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:109 +#: apps/native/src/lib/title-actions.ts:101 +msgid "Episode watched" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:780 +#: apps/web/src/components/titles/title-seasons.tsx:102 +msgid "Episodes" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:162 +#~ msgid "Episodes {periodSelect}" +#~ msgstr "Episodes {periodSelect}" + +#: apps/web/src/components/dashboard/stats-display.tsx:164 +msgid "Episodes {select}" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:61 +msgid "Episodes this week" +msgstr "" + +#: apps/native/src/app/change-password.tsx:71 +#: apps/native/src/app/change-password.tsx:81 +msgid "Error" +msgstr "" + +#. placeholder {0}: result.errors.length +#: apps/web/src/components/settings/imports-section.tsx:929 +msgid "Errors ({0})" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:143 +#: apps/web/src/components/nav-bar.tsx:126 +#: apps/web/src/components/nav-bar.tsx:291 +msgid "Explore" +msgstr "" + +#: apps/web/src/routes/_app/people.$id.tsx:70 +#: apps/web/src/routes/_app/titles.$id.tsx:164 +msgid "Explore titles" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:923 +msgid "Failed" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:49 +#: apps/native/src/lib/title-actions.ts:28 +msgid "Failed to add to watchlist" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:90 +msgid "Failed to catch up" +msgstr "" + +#: apps/native/src/app/change-password.tsx:71 +#: apps/web/src/components/settings/account-section.tsx:411 +msgid "Failed to change password" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:398 +msgid "Failed to connect" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:99 +#: apps/web/src/components/settings/integration-card.tsx:108 +msgid "Failed to connect {label}" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:67 +msgid "Failed to create account" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:81 +msgid "Failed to create backup" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:97 +msgid "Failed to delete backup" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:109 +#: apps/web/src/components/settings/integration-card.tsx:125 +msgid "Failed to disconnect {label}" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:215 +msgid "Failed to load backup schedule settings." +msgstr "" + +#: apps/web/src/routes/_app/titles.$id.tsx:115 +msgid "Failed to load title" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:367 +msgid "Failed to mark all episodes as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:84 +#: apps/native/src/lib/title-actions.ts:68 +#: apps/web/src/components/titles/use-title-actions.ts:150 +msgid "Failed to mark as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:113 +#: apps/native/src/lib/title-actions.ts:104 +#: apps/web/src/components/titles/use-title-actions.ts:237 +msgid "Failed to mark episode" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:141 +#: apps/native/src/lib/title-actions.ts:128 +#: apps/web/src/components/titles/use-title-actions.ts:315 +msgid "Failed to mark some episodes" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:225 +msgid "Failed to parse file" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:240 +msgid "Failed to parse import data" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:75 +msgid "Failed to purge caches" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:58 +msgid "Failed to purge image cache" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:45 +msgid "Failed to purge metadata cache" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:119 +#: apps/web/src/components/settings/integration-card.tsx:140 +msgid "Failed to regenerate {label} URL" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:78 +msgid "Failed to remove from library" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:134 +#: apps/web/src/components/settings/account-section.tsx:134 +msgid "Failed to remove profile picture" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:56 +msgid "Failed to sign in" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:388 +msgid "Failed to start {0} connection" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:348 +msgid "Failed to trigger job" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:129 +#: apps/native/src/lib/title-actions.ts:114 +#: apps/web/src/components/titles/use-title-actions.ts:181 +msgid "Failed to unmark episode" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:343 +msgid "Failed to unmark some episodes" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:112 +msgid "Failed to update name" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:101 +#: apps/native/src/lib/title-actions.ts:94 +#: apps/web/src/components/titles/use-title-actions.ts:136 +msgid "Failed to update rating" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:172 +#: apps/web/src/components/settings/registration-section.tsx:44 +msgid "Failed to update registration setting" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:154 +msgid "Failed to update retention setting" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:156 +msgid "Failed to update schedule" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:152 +msgid "Failed to update scheduled backup setting" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:191 +#: apps/web/src/components/settings/update-check-section.tsx:43 +msgid "Failed to update setting" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:72 +#: apps/native/src/lib/title-actions.ts:40 +#: apps/native/src/lib/title-actions.ts:54 +#: apps/web/src/components/titles/use-title-actions.ts:114 +msgid "Failed to update status" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:123 +msgid "Failed to upload avatar" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:725 +msgid "Fetching your library data from {source}..." +msgstr "" + +#: apps/native/src/app/person/[id].tsx:340 +#: apps/web/src/components/people/filmography-grid.tsx:69 +msgid "Filmography" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:915 +msgid "Finished importing from {source}." +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:140 +msgid "Free" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:92 +msgid "Free up disk space by clearing cached metadata and images" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:309 +msgid "Frequency" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:101 +msgid "Friday" +msgstr "" + +#: apps/web/src/components/landing-page.tsx:156 +msgid "Get Started" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:220 +#: apps/native/src/app/person/[id].tsx:247 +#: apps/native/src/app/title/[id].tsx:255 +msgid "Go back" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:37 +msgid "Go Home" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:123 +msgid "Go to <0>Dashboard > Plugins > Webhook" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:368 +msgid "Go to Dashboard" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:71 +msgid "Go to Dashboard > Plugins > Webhook" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:379 +msgid "Go to Explore" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:212 +msgid "Health status" +msgstr "" + +#: apps/web/src/components/dashboard/welcome-header.tsx:14 +msgid "Here's what's happening with your library" +msgstr "" + +#: apps/web/src/components/nav-bar.tsx:125 +#: apps/web/src/components/nav-bar.tsx:290 +msgid "Home" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:335 +#: apps/web/src/components/settings/system-health-section.tsx:610 +msgid "Image cache" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:479 +msgid "Image Cache" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:598 +msgid "Image cache and backup disk usage" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:179 +msgid "Import" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:840 +msgid "Import {totalItems} items" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:912 +msgid "Import complete" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:340 +msgid "Import failed" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:584 +msgid "Import from {0}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:770 +msgid "Import from {source}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:298 +#: apps/web/src/components/settings/imports-section.tsx:329 +msgid "Import is still running in the background. Check back later." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:798 +msgid "Import options" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:921 +msgid "Imported" +msgstr "" + +#. placeholder {0}: event.job.importedCount +#. placeholder {1}: config.label +#: apps/web/src/components/settings/imports-section.tsx:292 +msgid "Imported {0} items from {1}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:863 +msgid "Importing from {source}" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:62 +msgid "In library" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:230 +msgid "In Library" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:133 +#: apps/web/src/components/dashboard/library-section.tsx:35 +msgid "In Your Library" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:105 +msgid "Install the <0>Webhook plugin<1/> from Jellyfin's plugin catalog" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:70 +msgid "Install the Webhook plugin from Jellyfin's plugin catalog" +msgstr "" + +#: apps/native/src/components/settings/integrations-section.tsx:20 +#: apps/web/src/components/settings/integrations-section.tsx:41 +msgid "Integrations" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:266 +msgid "Invalid token" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:399 +msgid "Job" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:243 +#~ msgid "Keeping" +#~ msgstr "Keeping" + +#. placeholder {0}: (value: string | null) => value === "0" ? t`unlimited` : value ? t`last ${value}` : null +#. placeholder {1}: [3, 5, 7, 14, 30, 0].map((n) => ( {n === 0 ? t`unlimited` : t`last ${n}`} )) +#: apps/web/src/components/settings/backup-schedule-section.tsx:243 +msgid "Keeping <0><1><2>{0}<3>{1} backups." +msgstr "" + +#: apps/web/src/components/command-palette.tsx:389 +#: apps/web/src/components/command-palette.tsx:404 +msgid "Keyboard Shortcuts" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:403 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:411 +#: apps/web/src/components/settings/language-section.tsx:42 +msgid "Language" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:270 +msgid "last {n}" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:258 +msgid "last {value}" +msgstr "" + +#. placeholder {0}: formatRelativeTime(lastEventAt) +#: apps/web/src/components/settings/integration-card.tsx:331 +msgid "Last event {0}" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:29 +msgid "Last event {timeAgo}" +msgstr "" + +#. placeholder {0}: formatRelativeTime(lastEventAt) +#: apps/web/src/components/settings/integration-card.tsx:338 +msgid "Last polled {0}" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:37 +msgid "Last polled {timeAgo}" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:405 +msgid "Last run" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:441 +msgid "Last run failed" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:437 +msgid "Last run succeeded" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:331 +msgid "Library refresh" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:245 +msgid "Loading…" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:334 +msgid "Lost connection to import. Check status in settings." +msgstr "" + +#: apps/web/src/components/settings/settings-shell.tsx:40 +msgid "Manage your account and preferences" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:182 +msgid "Manual backup" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:122 +msgid "Mark all episodes as watched?" +msgstr "" + +#: apps/native/src/components/titles/season-accordion.tsx:164 +#: apps/web/src/components/titles/title-seasons.tsx:115 +#: apps/web/src/components/titles/title-seasons.tsx:141 +msgid "Mark All Watched" +msgstr "" + +#: apps/native/src/components/dashboard/continue-watching-card.tsx:122 +msgid "Mark as Completed" +msgstr "" + +#: apps/native/src/components/ui/poster-card.tsx:252 +msgid "Mark as Watched" +msgstr "" + +#: apps/native/src/components/ui/poster-card.tsx:245 +msgid "Mark as Watching" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:449 +#: apps/web/src/components/titles/title-actions.tsx:29 +msgid "Mark Watched" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:49 +msgid "Marked \"{titleName}\" as completed" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:63 +#: apps/web/src/components/titles/use-title-actions.ts:147 +msgid "Marked \"{titleName}\" as watched" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:360 +msgid "Marked all episodes as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:62 +#: apps/native/src/lib/title-actions.ts:50 +msgid "Marked as completed" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:80 +#: apps/native/src/lib/title-actions.ts:64 +msgid "Marked as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:61 +#: apps/native/src/lib/title-actions.ts:37 +msgid "Marked as watching" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:340 +msgid "Member since {memberSince}" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:97 +msgid "Monday" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:585 +msgid "More Like This" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:541 +msgid "More Settings" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:362 +#: apps/native/src/components/search/search-result-row.tsx:35 +#: apps/native/src/components/search/search-result-row.tsx:96 +#: apps/web/src/components/explore/hero-banner.tsx:59 +#: apps/web/src/components/titles/title-hero.tsx:143 +msgid "Movie" +msgstr "" + +#: apps/web/src/components/people/filmography-grid.tsx:59 +#: apps/web/src/components/settings/imports-section.tsx:779 +msgid "Movies" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:161 +#~ msgid "Movies {periodSelect}" +#~ msgstr "Movies {periodSelect}" + +#: apps/web/src/components/dashboard/stats-display.tsx:162 +msgid "Movies {select}" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:60 +msgid "Movies this month" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:124 +#: apps/web/src/components/auth-form.tsx:181 +msgid "Name" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:107 +#: apps/web/src/components/settings/account-section.tsx:80 +msgid "Name updated" +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:301 +msgid "Need more help?" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:501 +msgid "Never" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:435 +msgid "Never run" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:98 +msgid "New account creation is currently disabled." +msgstr "" + +#: apps/web/src/routes/_auth/register.tsx:35 +msgid "New accounts are not being accepted right now. Contact the admin if you need access." +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:137 +msgid "New backup" +msgstr "" + +#: apps/native/src/app/change-password.tsx:145 +#: apps/web/src/components/settings/account-section.tsx:463 +msgid "New password" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:395 +msgid "New password must be at least 8 characters" +msgstr "" + +#: apps/web/src/components/people/filmography-grid.tsx:86 +#: apps/web/src/components/people/filmography-grid.tsx:99 +msgid "Newest" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:131 +msgid "Next backup {distance}" +msgstr "" + +#: apps/native/src/components/titles/continue-watching-banner.tsx:46 +msgid "Next Episode" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:408 +msgid "Next run" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:110 +#: apps/web/src/components/settings/system-health-section.tsx:662 +msgid "No backups yet" +msgstr "" + +#: apps/native/src/components/ui/offline-banner.ios.tsx:74 +#: apps/native/src/components/ui/offline-banner.ios.tsx:81 +#: apps/native/src/components/ui/offline-banner.tsx:59 +msgid "No internet connection" +msgstr "" + +#: apps/native/src/app/(tabs)/(search)/index.tsx:113 +msgid "No results for \"{debouncedQuery}\"" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:217 +msgid "No results found." +msgstr "" + +#: apps/native/src/components/explore/filterable-title-row.tsx:139 +#: apps/web/src/components/explore/filterable-title-row.tsx:151 +msgid "No titles found for this genre." +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:194 +#: apps/web/src/components/settings/integration-card.tsx:176 +#: apps/web/src/components/settings/system-health-section.tsx:249 +msgid "Not configured" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:17 +msgid "Not Found" +msgstr "" + +#: apps/web/src/components/title-card.tsx:67 +msgid "On Watchlist" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:693 +msgid "Open {source} to enter code" +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:308 +msgid "Open docs" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:190 +msgid "Open Emby, go to <0>Settings > Webhooks" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:91 +msgid "Open Emby, go to Settings > Webhooks" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:548 +msgid "Open in browser…" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:62 +msgid "Open Plex, go to <0>Settings > Webhooks<1/>" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:54 +msgid "Open Plex, go to Settings > Webhooks" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:269 +msgid "Open Radarr, go to <0>Settings > Import Lists" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:126 +msgid "Open Radarr, go to Settings > Import Lists" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:502 +#: apps/web/src/components/settings/registration-section.tsx:58 +msgid "Open registration" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:229 +msgid "Open Sonarr, go to <0>Settings > Import Lists" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:108 +msgid "Open Sonarr, go to Settings > Import Lists" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:158 +msgid "or" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:118 +msgid "OR" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:27 +msgid "Page Not Found" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:509 +msgid "Parsing..." +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:167 +#: apps/native/src/app/(auth)/register.tsx:187 +#: apps/web/src/components/auth-form.tsx:221 +msgid "Password" +msgstr "" + +#: apps/native/src/app/change-password.tsx:76 +#: apps/web/src/components/settings/account-section.tsx:414 +msgid "Password updated" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:399 +msgid "Passwords do not match" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:128 +#: apps/web/src/components/settings/integration-configs.tsx:284 +msgid "Paste the Radarr URL above into the List URL field" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:110 +#: apps/web/src/components/settings/integration-configs.tsx:244 +msgid "Paste the Sonarr URL above into the List URL field" +msgstr "" + +#: apps/web/src/components/settings/update-check-section.tsx:63 +msgid "Periodically check GitHub for new Sofa releases" +msgstr "" + +#: apps/native/src/components/search/search-result-row.tsx:38 +#: apps/native/src/components/search/search-result-row.tsx:99 +msgid "Person" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:243 +#: apps/web/src/routes/_app/people.$id.tsx:52 +msgid "Person not found" +msgstr "" + +#: apps/web/src/components/titles/trailer-dialog.tsx:24 +msgid "Play trailer" +msgstr "" + +#: apps/native/src/app/(tabs)/(explore)/index.tsx:107 +#: apps/web/src/components/explore/explore-client.tsx:126 +msgid "Popular Movies" +msgstr "" + +#: apps/native/src/app/(tabs)/(explore)/index.tsx:120 +#: apps/web/src/components/explore/explore-client.tsx:136 +msgid "Popular TV Shows" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:181 +msgid "Pre-restore backup" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:77 +msgid "Producer" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:130 +#: apps/web/src/components/settings/account-section.tsx:130 +msgid "Profile picture removed" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:119 +#: apps/web/src/components/settings/account-section.tsx:108 +msgid "Profile picture updated" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:196 +#: apps/web/src/components/settings/danger-section.tsx:220 +msgid "Purge all" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:202 +msgid "Purge all caches?" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:160 +msgid "Purge image cache?" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:154 +#: apps/web/src/components/settings/danger-section.tsx:177 +msgid "Purge images" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:111 +#: apps/web/src/components/settings/danger-section.tsx:135 +msgid "Purge metadata" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:117 +msgid "Purge metadata cache?" +msgstr "" + +#. placeholder {0}: data.deletedTitles +#. placeholder {1}: data.deletedPersons +#: apps/web/src/components/settings/danger-section.tsx:41 +msgid "Purged {0, plural, one {# stale title} other {# stale titles}} and {1, plural, one {# orphaned person} other {# orphaned persons}}" +msgstr "" + +#. placeholder {0}: metaResult.deletedTitles +#. placeholder {1}: metaResult.deletedPersons +#. placeholder {2}: imageResult.deletedFiles +#: apps/web/src/components/settings/danger-section.tsx:71 +msgid "Purged {0, plural, one {# title} other {# titles}}, {1, plural, one {# person} other {# persons}}, {2, plural, one {# file} other {# files}} ({freed} freed)" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:109 +#: apps/web/src/components/settings/danger-section.tsx:152 +#: apps/web/src/components/settings/danger-section.tsx:194 +msgid "Purging..." +msgstr "" + +#: apps/web/src/components/command-palette.tsx:359 +msgid "Quick Actions" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:123 +msgid "Radarr List URL" +msgstr "" + +#. placeholder {0}: input.stars +#. placeholder {1}: input.stars +#: apps/native/src/hooks/use-title-actions.ts:93 +msgid "Rated {0} {1, plural, one {star} other {stars}}" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:92 +#~ msgid "Rated {0} star{1}" +#~ msgstr "Rated {0} star{1}" + +#: apps/web/src/components/titles/use-title-actions.ts:131 +msgid "Rated {ratingStars} {ratingStars, plural, one {star} other {stars}}" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:88 +msgid "Rated {stars, plural, one {# star} other {# stars}}" +msgstr "" + +#: apps/web/src/components/people/filmography-grid.tsx:88 +#: apps/web/src/components/people/filmography-grid.tsx:102 +msgid "Rating" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:94 +#: apps/native/src/lib/title-actions.ts:90 +#: apps/web/src/components/titles/use-title-actions.ts:132 +msgid "Rating removed" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:782 +#: apps/web/src/components/settings/imports-section.tsx:813 +msgid "Ratings" +msgstr "" + +#: apps/web/src/components/expandable-text.tsx:51 +msgid "Read more" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:39 +#: apps/web/src/components/settings/integration-card.tsx:339 +msgid "Ready — not polled yet" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:31 +#: apps/web/src/components/settings/integration-card.tsx:332 +msgid "Ready — nothing received yet" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:315 +msgid "Recent Searches" +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:106 +#: apps/native/src/components/search/recently-viewed-list.tsx:86 +msgid "Recently Viewed" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:333 +msgid "Recommendations" +msgstr "" + +#: apps/web/src/components/titles/title-recommendations.tsx:40 +msgid "Recommended" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:154 +#: apps/web/src/components/dashboard/recommendations-section.tsx:22 +msgid "Recommended for You" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:143 +msgid "Redirecting…" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:182 +msgid "Refresh" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:172 +msgid "Refresh system health" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:140 +#: apps/native/src/components/settings/integration-card.tsx:289 +msgid "Regenerate" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:135 +#: apps/web/src/components/settings/integration-card.tsx:264 +msgid "Regenerate URL" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:293 +#: apps/web/src/components/landing-page.tsx:176 +msgid "Register" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:116 +msgid "Registering on {serverHost}" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:167 +#: apps/web/src/components/settings/registration-section.tsx:41 +msgid "Registration closed" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:97 +#: apps/web/src/routes/_auth/register.tsx:32 +msgid "Registration Closed" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:167 +#: apps/web/src/components/settings/registration-section.tsx:41 +msgid "Registration opened" +msgstr "" + +#: apps/web/src/components/titles/status-button.tsx:84 +msgid "Remove" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:101 +#: apps/web/src/components/titles/status-button.tsx:66 +msgid "Remove from library" +msgstr "" + +#: apps/native/src/components/dashboard/continue-watching-card.tsx:129 +#: apps/native/src/components/ui/poster-card.tsx:259 +msgid "Remove from Library" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:277 +msgid "Remove Photo" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:239 +msgid "Remove picture" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:199 +msgid "Remove profile picture" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:66 +#: apps/native/src/lib/title-actions.ts:75 +#: apps/web/src/components/titles/use-title-actions.ts:111 +msgid "Removed from library" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:138 +msgid "Rent" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:165 +msgid "Requires <0>Emby Server 4.7.9+ and an active <1>Emby Premiere<2/> license." +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:41 +msgid "Requires an active <0>Plex Pass<1/> subscription." +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:52 +msgid "Requires an active Plex Pass subscription." +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:88 +msgid "Requires Emby Server 4.7.9+ and an active Emby Premiere license." +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:59 +#: apps/web/src/components/settings/backup-restore-section.tsx:116 +msgid "Restore" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:89 +msgid "Restore database?" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:35 +msgid "Restore failed" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:132 +msgid "Restoring…" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:222 +msgid "Results" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:731 +msgid "Retrieving your watch history, watchlist, and ratings..." +msgstr "" + +#: apps/native/src/components/settings/integrations-section.tsx:42 +#: apps/native/src/components/ui/server-unreachable-banner.ios.tsx:73 +#: apps/native/src/components/ui/server-unreachable-banner.tsx:81 +#: apps/web/src/lib/orpc/client.ts:16 +msgid "Retry" +msgstr "" + +#: apps/web/src/routes/__root.tsx:119 +msgid "Return home" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:773 +msgid "Review what was found and choose what to import." +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:558 +msgid "Run now" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:102 +msgid "Saturday" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:322 +msgid "Save" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:296 +msgid "Save name" +msgstr "" + +#: apps/web/src/routes/__root.tsx:101 +msgid "Scene not found" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:402 +msgid "Schedule" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:201 +msgid "Schedule updated" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:179 +msgid "Scheduled backup" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:178 +msgid "Scheduled backups disabled" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:177 +msgid "Scheduled backups enabled" +msgstr "" + +#: apps/web/src/components/dashboard/stats-section.tsx:35 +msgid "Search for movies and TV shows to start tracking" +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:81 +#: apps/native/src/components/search/recently-viewed-list.tsx:64 +msgid "Search for movies, shows, or people" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:186 +msgid "Search for movies, TV shows, or run commands" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:195 +msgid "Search movies & TV shows…" +msgstr "" + +#: apps/native/src/app/(tabs)/(search)/index.tsx:90 +msgid "Search movies, shows, people..." +msgstr "" + +#: apps/web/src/components/nav-bar.tsx:193 +#: apps/web/src/components/nav-bar.tsx:202 +msgid "Search…" +msgstr "" + +#. placeholder {0}: season.seasonNumber +#: apps/native/src/components/titles/season-accordion.tsx:122 +#: apps/native/src/components/titles/season-accordion.tsx:128 +#: apps/web/src/components/titles/use-title-actions.ts:295 +#: apps/web/src/components/titles/use-title-actions.ts:335 +msgid "Season {0}" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:137 +#: apps/native/src/lib/title-actions.ts:124 +msgid "Season watched" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:536 +msgid "Seasons" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:497 +#: apps/web/src/routes/_app/settings.tsx:140 +msgid "Security" +msgstr "" + +#: apps/web/src/components/landing-page.tsx:105 +msgid "Self-hosted movie & TV tracker" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:121 +msgid "Server" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:452 +msgid "Server Health" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:380 +msgid "Server URL" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:361 +msgid "Set password" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:111 +#: apps/native/src/components/settings/integration-configs.ts:129 +#: apps/web/src/components/settings/integration-configs.tsx:247 +#: apps/web/src/components/settings/integration-configs.tsx:287 +msgid "Set your preferred quality profile and root folder" +msgstr "" + +#: apps/native/src/components/header-avatar.tsx:63 +#: apps/web/src/components/nav-bar.tsx:250 +#: apps/web/src/components/nav-bar.tsx:292 +#: apps/web/src/components/settings/settings-shell.tsx:36 +msgid "Settings" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:319 +#: apps/web/src/components/settings/integration-card.tsx:288 +msgid "Setup instructions" +msgstr "" + +#: apps/web/src/routes/setup.tsx:98 +msgid "Setup required" +msgstr "" + +#: apps/native/src/components/ui/expandable-text.tsx:62 +#: apps/native/src/components/ui/expandable-text.tsx:69 +#: apps/web/src/components/expandable-text.tsx:51 +msgid "Show less" +msgstr "" + +#: apps/native/src/components/ui/expandable-text.tsx:62 +#: apps/native/src/components/ui/expandable-text.tsx:69 +msgid "Show more" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:249 +#: apps/web/src/components/auth-form.tsx:283 +msgid "Sign in" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:205 +#: apps/web/src/components/landing-page.tsx:167 +msgid "Sign In" +msgstr "" + +#: apps/web/src/routes/_auth/register.tsx:45 +msgid "Sign in instead" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:91 +#: apps/web/src/components/auth-form.tsx:119 +msgid "Sign in to continue" +msgstr "" + +#. placeholder {0}: authConfig.data?.oidcProviderName ?? "SSO" +#. placeholder {0}: authConfig?.oidcProviderName || "SSO" +#: apps/native/src/app/(auth)/login.tsx:108 +#: apps/web/src/components/auth-form.tsx:145 +msgid "Sign in with {0}" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:368 +#: apps/native/src/components/header-avatar.tsx:87 +#: apps/web/src/components/nav-bar.tsx:262 +#: apps/web/src/components/settings/account-section.tsx:354 +msgid "Sign out" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:206 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:209 +#: apps/native/src/components/header-avatar.tsx:70 +#: apps/native/src/components/header-avatar.tsx:73 +msgid "Sign Out" +msgstr "" + +#: apps/native/src/app/change-password.tsx:199 +#: apps/web/src/components/settings/account-section.tsx:499 +msgid "Sign out of other sessions" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:922 +msgid "Skipped" +msgstr "" + +#. placeholder {0}: data.latestVersion +#: apps/web/src/components/update-toast.tsx:19 +msgid "Sofa v{0} is available" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:57 +#: apps/native/src/components/settings/integration-configs.ts:75 +#: apps/native/src/components/settings/integration-configs.ts:95 +#: apps/web/src/components/settings/integration-configs.tsx:86 +#: apps/web/src/components/settings/integration-configs.tsx:147 +#: apps/web/src/components/settings/integration-configs.tsx:208 +msgid "Sofa will automatically log movies and episodes when you finish watching them" +msgstr "" + +#: apps/native/src/app/change-password.tsx:81 +#: apps/native/src/app/person/[id].tsx:213 +#: apps/web/src/components/settings/account-section.tsx:417 +#: apps/web/src/routes/__root.tsx:143 +msgid "Something went wrong" +msgstr "" + +#: apps/web/src/routes/_app/titles.$id.tsx:118 +msgid "Something went wrong while loading this title. Please try again." +msgstr "" + +#: apps/native/src/lib/query-client.ts:33 +#: apps/web/src/lib/orpc/client.ts:14 +msgid "Something went wrong…" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:105 +msgid "Sonarr List URL" +msgstr "" + +#: apps/web/src/components/dashboard/stats-section.tsx:42 +msgid "Start exploring" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:142 +msgid "Start tracking movies and shows" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:117 +msgid "Start tracking your watches" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:388 +msgid "Starting at" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:888 +msgid "Starting import..." +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:65 +msgid "Status updated" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:595 +msgid "Storage" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:137 +msgid "Stream" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:96 +msgid "Sunday" +msgstr "" + +#. placeholder {0}: info.name +#: apps/web/src/components/settings/language-section.tsx:69 +msgid "Switch to {0}" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:30 +msgid "The page you're looking for doesn't exist." +msgstr "" + +#: apps/web/src/routes/_app/people.$id.tsx:55 +msgid "The person you're looking for doesn't exist or may have been removed from the database." +msgstr "" + +#: apps/web/src/routes/_app/titles.$id.tsx:149 +msgid "The title you're looking for doesn't exist or may have been removed from the database." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:866 +msgid "This may take a few minutes for large libraries. Please don't close this tab." +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:146 +msgid "This Month" +msgstr "" + +#: apps/web/src/routes/__root.tsx:104 +msgid "This page was left on the cutting room floor. It may have been moved, removed, or never made it past the screenplay." +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:606 +#: apps/web/src/routes/_app/settings.tsx:75 +#: apps/web/src/routes/setup.tsx:187 +msgid "This product uses the TMDB API but is not endorsed or certified by TMDB." +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:145 +msgid "This Week" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:163 +msgid "This will delete all cached TMDB images from disk. Images will be re-downloaded automatically as needed." +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:205 +msgid "This will delete all un-enriched stub titles and all cached images from disk. Everything will be re-imported and re-downloaded as needed." +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:120 +msgid "This will delete un-enriched stub titles that aren't in any user's library and clean up orphaned person records. Deleted titles will be re-imported if accessed again." +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:136 +msgid "This will invalidate the current {label} URL. You'll need to update it in {label}." +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:125 +msgid "This will mark every episode of this show as watched. You can undo this later by unmarking individual seasons." +msgstr "" + +#. placeholder {0}: formatBackupDate(backup.createdAt) +#: apps/web/src/components/settings/backup-section.tsx:259 +msgid "This will permanently delete the backup from <0>{0}. This cannot be undone." +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:61 +#: apps/native/src/components/search/recently-viewed-list.tsx:35 +msgid "This will remove all items from your history." +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:92 +msgid "This will replace your entire database with the uploaded file. A safety backup of your current data will be created first. Active sessions may need to refresh after restore." +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:147 +msgid "This Year" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:100 +msgid "Thursday" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:390 +msgid "Time:" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:251 +#: apps/web/src/routes/_app/titles.$id.tsx:146 +msgid "Title not found" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:131 +#: apps/web/src/components/settings/integration-configs.tsx:290 +msgid "Titles on your Sofa watchlist will be automatically added for download when Radarr polls this list (every 12 hours by default)" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:113 +#: apps/web/src/components/settings/integration-configs.tsx:250 +msgid "Titles on your Sofa watchlist will be automatically added for download when Sonarr polls this list (every 6 hours by default)" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:144 +msgid "Today" +msgstr "" + +#: apps/web/src/components/settings/update-check-section.tsx:71 +msgid "Toggle automatic update checks" +msgstr "" + +#: apps/web/src/components/settings/registration-section.tsx:69 +msgid "Toggle open registration" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:290 +msgid "Toggle scheduled backups" +msgstr "" + +#: apps/web/src/components/landing-page.tsx:131 +msgid "Track what you watch. Know what's next.<0/>Your library, your data, your rules." +msgstr "" + +#: apps/web/src/components/titles/trailer-dialog.tsx:37 +#: apps/web/src/components/titles/trailer-dialog.tsx:41 +msgid "Trailer" +msgstr "" + +#: apps/web/src/components/explore/hero-banner.tsx:78 +msgid "Trending today" +msgstr "" + +#: apps/native/src/app/(tabs)/(explore)/index.tsx:95 +#: apps/web/src/components/explore/explore-client.tsx:112 +msgid "Trending Today" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:537 +msgid "Trigger job" +msgstr "" + +#: apps/web/src/routes/__root.tsx:159 +msgid "Try again" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:98 +msgid "Tuesday" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:362 +#: apps/native/src/components/search/search-result-row.tsx:98 +#: apps/web/src/components/explore/hero-banner.tsx:64 +#: apps/web/src/components/people/filmography-grid.tsx:60 +#: apps/web/src/components/titles/title-hero.tsx:153 +msgid "TV" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:334 +msgid "TV episodes" +msgstr "" + +#: apps/native/src/components/search/search-result-row.tsx:37 +msgid "TV show" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:656 +msgid "unknown" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:256 +#: apps/web/src/components/settings/backup-schedule-section.tsx:270 +msgid "unlimited" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:273 +msgid "Unreachable" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:340 +msgid "Untitled" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:221 +msgid "Unwatch all" +msgstr "" + +#. placeholder {0}: season.name ?? t`Season ${season.seasonNumber}` +#: apps/web/src/components/titles/use-title-actions.ts:335 +msgid "Unwatched all of {0}" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:174 +msgid "Unwatched S{seasonNum} E{epNum}" +msgstr "" + +#: apps/native/src/components/titles/continue-watching-banner.tsx:79 +#: apps/web/src/components/dashboard/continue-watching-card.tsx:77 +msgid "Up next" +msgstr "" + +#. placeholder {0}: updateCheck.data.updateCheck.latestVersion +#: apps/native/src/app/(tabs)/(settings)/index.tsx:528 +msgid "Update available: {0}" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:337 +msgid "Update check" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:185 +#: apps/web/src/components/settings/update-check-section.tsx:40 +msgid "Update checks disabled" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:185 +#: apps/web/src/components/settings/update-check-section.tsx:40 +msgid "Update checks enabled" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:84 +msgid "Update failed" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:509 +msgid "Update password" +msgstr "" + +#: apps/native/src/app/change-password.tsx:218 +msgid "Update Password" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:134 +#: apps/web/src/components/settings/imports-section.tsx:509 +msgid "Upload" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:62 +msgid "Upload a .db file to replace the current database. A safety backup is created first." +msgstr "" + +#. placeholder {0}: config.accept +#. placeholder {1}: config.label +#: apps/web/src/components/settings/imports-section.tsx:637 +msgid "Upload a {0} export from your {1} account settings." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:634 +msgid "Upload export file" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:112 +msgid "Upload failed" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:241 +msgid "Upload picture" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:200 +msgid "Upload profile picture" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:129 +msgid "URL copied to clipboard" +msgstr "" + +#: apps/web/src/components/update-toast.tsx:24 +msgid "View release" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:699 +msgid "Waiting for authorization..." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:823 +msgid "Warnings" +msgstr "" + +#. placeholder {0}: result.warnings.length +#: apps/web/src/components/settings/imports-section.tsx:948 +msgid "Warnings ({0})" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:202 +msgid "Watch all" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:801 +msgid "Watch history" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:59 +msgid "Watch on {name}" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:298 +#: apps/web/src/components/titles/use-title-actions.ts:307 +msgid "Watched all of {seasonLabel}" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:123 +msgid "Watched all of {seasonName}" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:220 +#: apps/web/src/components/titles/use-title-actions.ts:229 +msgid "Watched S{seasonNum} E{epNum}" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:42 +#: apps/web/src/components/title-card.tsx:72 +#: apps/web/src/components/titles/status-button.tsx:19 +msgid "Watching" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:82 +#: apps/web/src/components/settings/imports-section.tsx:781 +#: apps/web/src/components/settings/imports-section.tsx:807 +#: apps/web/src/components/titles/status-button.tsx:55 +msgid "Watchlist" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:40 +msgid "Watchlisted" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:50 +#: apps/native/src/components/settings/integration-configs.ts:67 +#: apps/native/src/components/settings/integration-configs.ts:85 +msgid "Webhook URL" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:99 +msgid "Wednesday" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:112 +#: apps/web/src/components/dashboard/welcome-header.tsx:10 +msgid "Welcome back" +msgstr "" + +#: apps/web/src/components/dashboard/welcome-header.tsx:8 +msgid "Welcome back, {name}" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:483 +#: apps/web/src/components/titles/title-availability.tsx:154 +msgid "Where to Watch" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:141 +msgid "With Ads" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:75 +msgid "Writer" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:386 +msgid "You'll be signed out to change the server URL." +msgstr "" + +#. placeholder {0}: data.currentVersion +#: apps/web/src/components/update-toast.tsx:20 +msgid "You're running v{0}." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:678 +msgid "Your code:" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:141 +#: apps/web/src/components/dashboard/stats-section.tsx:32 +msgid "Your library is empty" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:134 +msgid "Your name" +msgstr "" + diff --git a/packages/i18n/src/po/es.ts b/packages/i18n/src/po/es.ts new file mode 100644 index 0000000..ef7e47e --- /dev/null +++ b/packages/i18n/src/po/es.ts @@ -0,0 +1 @@ +/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"+5kO8P\":[\"Saturday\"],\"+6i0lS\":[\"Retrieving your watch history, watchlist, and ratings...\"],\"+Cv+V9\":[\"Remove from library\"],\"+JkEpu\":[\"This page was left on the cutting room floor. It may have been moved, removed, or never made it past the screenplay.\"],\"+K0AvT\":[\"Disconnect\"],\"+N0l5/\":[\"Connected to <0>\",[\"serverHost\"],\". Tap to change.\"],\"+gLHYi\":[\"Failed to load title\"],\"+j1ex/\":[\"Failed to remove from library\"],\"+nF1ZO\":[\"Remove Photo\"],\"/+6dvC\":[\"Errors (\",[\"0\"],\")\"],\"/BBXeA\":[\"Connecting to server...\"],\"/DwR+n\":[\"Creating…\"],\"/XtbPO\":[[\"0\"],\" titles\"],\"/cg+QV\":[\"Watch all\"],\"/gQXGv\":[\"Failed to update status\"],\"/nT6AE\":[\"New password\"],\"/sHc1/\":[\"Update check\"],\"0+dyau\":[\"Failed to update retention setting\"],\"0BFJKK\":[\"Authorization was denied. Please try again.\"],\"0GHb20\":[\"Purge metadata cache?\"],\"0IBW21\":[\"Failed to purge caches\"],\"0gH/sc\":[\"Remove profile picture\"],\"1+P9RR\":[\"Switch to \",[\"0\"]],\"12cc1j\":[\"Watching\"],\"12lVOl\":[\"Self-hosted movie & TV tracker\"],\"1B4z0M\":[\"Filmography\"],\"1J4Ek0\":[\"Automatically back up your database on a schedule\"],\"1JhxXW\":[\"Episode \",[\"0\"],\", \",[\"episodeLabel\"]],\"1N6wNP\":[\"Import options\"],\"1Qz4uG\":[\"Enable the \\\"Playback\\\" event category\"],\"1hMWR6\":[\"Create account\"],\"1jHIjh\":[\"Watched all of \",[\"seasonName\"]],\"1vSYsG\":[\"Download backup\"],\"1wL1tj\":[\"TV show\"],\"2FletP\":[\"Purged \",[\"0\",\"plural\",{\"one\":[\"#\",\" title\"],\"other\":[\"#\",\" titles\"]}],\", \",[\"1\",\"plural\",{\"one\":[\"#\",\" person\"],\"other\":[\"#\",\" persons\"]}],\", \",[\"2\",\"plural\",{\"one\":[\"#\",\" file\"],\"other\":[\"#\",\" files\"]}],\" (\",[\"freed\"],\" freed)\"],\"2Fsd9r\":[\"This Month\"],\"2MPcep\":[\"Import complete\"],\"2Mu33Z\":[\"Cache management\"],\"2POOFK\":[\"Free\"],\"2Pt2NY\":[\"This will remove all items from your history.\"],\"2fCpt5\":[\"Return home\"],\"2pPBp6\":[\"Trending today\"],\"39y5bn\":[\"Friday\"],\"3Blefz\":[\"Ratings\"],\"3JTlG8\":[\"Recent Searches\"],\"3Jy8bM\":[\"Movies \",[\"select\"]],\"3L9OuQ\":[\"Episode \",[\"0\"]],\"3T8ziB\":[\"Create an account\"],\"3hELxX\":[\"Enter your Sofa server URL to get started\"],\"4fxLkp\":[\"Open Sonarr, go to <0>Settings > Import Lists\"],\"4kpBqM\":[\"Last event \",[\"0\"]],\"4uUjVO\":[\"Producer\"],\"4x+A56\":[\"Anonymous usage reporting\"],\"5IShvp\":[\"Import \",[\"totalItems\"],\" items\"],\"5IYJSv\":[\"Explore titles\"],\"5Y4mym\":[\"Import from \",[\"0\"]],\"5ZzgbQ\":[\"Connect \",[\"0\"]],\"5fEnbK\":[\"Failed to update scheduled backup setting\"],\"5lWFkC\":[\"Sign in\"],\"5v9C16\":[\"Connected to \",[\"source\"]],\"623bR4\":[\"Failed to trigger job\"],\"6HN0yh\":[\"Open Plex, go to Settings > Webhooks\"],\"6TNjOJ\":[\"Failed to update rating\"],\"6TfUy6\":[\"last \",[\"value\"]],\"6V3Ea3\":[\"Copied\"],\"6YtxFj\":[\"Name\"],\"6exX+8\":[\"Regenerate\"],\"6gRgw8\":[\"Retry\"],\"6lGV3K\":[\"Show less\"],\"76++pR\":[\"Warnings\"],\"77DIAu\":[\"Go to Dashboard > Plugins > Webhook\"],\"7Bj3x9\":[\"Failed\"],\"7D50KC\":[[\"label\"],\" disconnected\"],\"7GfM5w\":[\"Recently Viewed\"],\"7L01XJ\":[\"Actions\"],\"7eMo+U\":[\"Go Home\"],\"7p5kLi\":[\"Dashboard\"],\"85eoJ1\":[\"Schedule updated\"],\"8B9E2D\":[\"Day:\"],\"8YwF1J\":[\"Go to <0>Dashboard > Plugins > Webhook\"],\"8ZsakT\":[\"Password\"],\"8tjQCz\":[\"Explore\"],\"8vNtLy\":[\"Paste the Radarr URL above into the List URL field\"],\"8wYDMp\":[\"Already have an account?\"],\"9AE3vb\":[\"Open Plex, go to <0>Settings > Webhooks<1/>\"],\"9GW0ZB\":[\"Import is still running in the background. Check back later.\"],\"9NyAH9\":[\"Skipped\"],\"9Xhrps\":[\"Failed to connect\"],\"9ZLGbA\":[\"backups.\"],\"9eF5oV\":[\"Welcome back\"],\"9rG25a\":[\"Server URL\"],\"A+0rLe\":[\"Marked as completed\"],\"AOHgZp\":[\"Episodes\"],\"AOddWK\":[\"Connect \",[\"label\"]],\"ATfxL/\":[\"Trailer\"],\"AVlZoM\":[\"Environment\"],\"Acf6vF\":[\"Save name\"],\"AdoUfN\":[\"Failed to add to watchlist\"],\"AeXO77\":[\"Account\"],\"AjtYj0\":[\"On Watchlist\"],\"Avee+B\":[\"Failed to update name\"],\"B2R3xD\":[\"Toggle scheduled backups\"],\"BEVzjL\":[\"Import failed\"],\"BQnS5I\":[\"Open \",[\"source\"],\" to enter code\"],\"BUnoSB\":[[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"backup\"],\"other\":[\"backups\"]}],\" stored\"],\"BpttUI\":[\"Trending Today\"],\"BrrIs8\":[\"Storage\"],\"BskWMl\":[\"Unreachable\"],\"BzEFor\":[\"or\"],\"CGDHFb\":[\"Add a <0>Generic Destination and paste the URL above\"],\"CGExDN\":[\"Failed to purge image cache\"],\"CHeXFE\":[\"Status updated\"],\"CKyk7Q\":[\"Go back\"],\"CaB/+I\":[\"Welcome back, \",[\"name\"]],\"CodnUh\":[\"Removed from library\"],\"CpzGJY\":[\"This may take a few minutes for large libraries. Please don't close this tab.\"],\"CuPxpd\":[\"Requires an active <0>Plex Pass<1/> subscription.\"],\"CzBN6D\":[\"Start exploring\"],\"D+R2Xs\":[\"Starting import...\"],\"D3C4Yx\":[\"Current password is required\"],\"DBC3t5\":[\"Sunday\"],\"DI4lqs\":[\"Person not found\"],\"DKBbJf\":[\"Added \\\"\",[\"titleName\"],\"\\\" to watchlist\"],\"DPfwMq\":[\"Done\"],\"DZse/o\":[\"Add a \\\"Generic Destination\\\" and paste the URL above\"],\"E/QGRL\":[\"Disabled\"],\"E6nRW7\":[\"Copy URL\"],\"EWQlBH\":[\"Your library is empty\"],\"EWaCfj\":[\"Enter your current password and choose a new one.\"],\"Eeo/Gy\":[\"Failed to update setting\"],\"Efn6WU\":[\"This will delete all un-enriched stub titles and all cached images from disk. Everything will be re-imported and re-downloaded as needed.\"],\"Etp5if\":[\"Finished importing from \",[\"source\"],\".\"],\"F006BN\":[\"Import from \",[\"source\"]],\"FNvDMc\":[\"This Week\"],\"FWSp+7\":[\"Enter the code below on \",[\"source\"],\"'s website to authorize Sofa.\"],\"FXN0ro\":[\"Recommendations\"],\"FaU7Ag\":[\"Enable the \\\"Playback Stop\\\" notification type\"],\"FhvLDl\":[[\"watchedCount\"],\"/\",[\"0\"],\" episodes\"],\"Fo2bwm\":[\"Actor\"],\"FwRqjE\":[\"Image cache and backup disk usage\"],\"G00fgM\":[\"last \",[\"n\"]],\"G3myU+\":[\"Tuesday\"],\"GcCthe\":[\"In Library\"],\"Gf39AA\":[[\"0\"],\" triggered\"],\"GnhfWw\":[\"Toggle automatic update checks\"],\"GptGxg\":[\"Change password\"],\"GqTZ+S\":[\"Rated \",[\"ratingStars\"],\" \",[\"ratingStars\",\"plural\",{\"one\":[\"star\"],\"other\":[\"stars\"]}]],\"GrdN/F\":[\"Caught up — marked \",[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}],\" as watched\"],\"H4B5LG\":[\"This product uses the TMDB API but is not endorsed or certified by TMDB.\"],\"HBRd5n\":[\"Season \",[\"0\"]],\"HD+aQ7\":[\"Database backups\"],\"HG+31u\":[\"Purge all caches?\"],\"Haz+72\":[\"This will delete all cached TMDB images from disk. Images will be re-downloaded automatically as needed.\"],\"HbReP5\":[\"Marked \\\"\",[\"titleName\"],\"\\\" as completed\"],\"Hg6o8v\":[\"Refresh system health\"],\"HmEjnC\":[\"Last event \",[\"timeAgo\"]],\"HvDfH/\":[\"Imported\"],\"I89uD4\":[\"Restoring…\"],\"IRoxQm\":[\"Registration closed\"],\"IS0nrP\":[\"Create Account\"],\"IY9rQ0\":[\"Free up disk space by clearing cached metadata and images\"],\"IrC12v\":[\"Application\"],\"J28zul\":[\"Connecting...\"],\"J64cFL\":[\"Library refresh\"],\"JKmmmN\":[\"Added to watchlist\"],\"JN0f/Y\":[\"Connect to TMDB\"],\"JR6aH2\":[\"Movies \",[\"periodSelect\"]],\"JRQitQ\":[\"Confirm new password\"],\"JRadFJ\":[\"Start tracking your watches\"],\"JSwq8t\":[\"New account creation is currently disabled.\"],\"JY5Oyv\":[\"Database\"],\"JjsJnI\":[[\"0\"],\"/\",[\"1\"],\" episodes\"],\"JrFTcr\":[\"Connecting…\"],\"JwFbe8\":[\"TV\"],\"KDw4GX\":[\"Try again\"],\"KG6681\":[\"Episode watched\"],\"KIS/Sd\":[\"Sign out of other sessions\"],\"KK0ghs\":[\"Image cache\"],\"KVAoFR\":[\"unlimited\"],\"KcXJuc\":[\"Purging...\"],\"KhtG3o\":[\"Update Password\"],\"Khux7w\":[[\"0\",\"plural\",{\"one\":[\"#\",\" title\"],\"other\":[\"#\",\" titles\"]}]],\"KmWyx0\":[\"Job\"],\"KoF0x6\":[\"age \",[\"age\"]],\"Ksiej9\":[\"Requires an active Plex Pass subscription.\"],\"Kx9NEt\":[\"Invalid token\"],\"Lk6Jb/\":[\"Last polled \",[\"timeAgo\"]],\"LmEEic\":[\"Waiting for authorization...\"],\"LqKH42\":[\"Connect with \",[\"0\"]],\"Lrpjji\":[\"Disconnect \",[\"label\"]],\"Lu6Udx\":[\"Click \\\"+\\\" and select \\\"Custom Lists\\\"\"],\"MDoX++\":[\"Sonarr List URL\"],\"MRBlCJ\":[\"Failed to unmark some episodes\"],\"MUO7w9\":[\"Marked \\\"\",[\"titleName\"],\"\\\" as watched\"],\"MZbQHL\":[\"No results found.\"],\"MdxR1u\":[\"Choose your preferred display language\"],\"MkyzAV\":[\"Authorize Sofa to read your \",[\"0\"],\" library. No password shared.\"],\"N40H+G\":[\"All\"],\"N6SFhC\":[\"Sign in instead\"],\"N7h106\":[\"Sofa v\",[\"0\"],\" is available\"],\"N9RF2M\":[\"Click \\\"Add Webhook\\\" and paste the URL above\"],\"NBo4z0\":[\"Click <0>+ and select <1>Custom Lists\"],\"NICUmW\":[\"Director\"],\"NQzPoO\":[\"New backup\"],\"NSxl1l\":[\"More Settings\"],\"NVF43p\":[\"Time:\"],\"NdPMwS\":[\"In Your Library\"],\"NgaPSG\":[\"Failed to update schedule\"],\"O3oNi5\":[\"Email\"],\"OBcj5W\":[\"This will invalidate the current \",[\"label\"],\" URL. You'll need to update it in \",[\"label\"],\".\"],\"OL8hbM\":[\"New password must be at least 8 characters\"],\"ONWvwQ\":[\"Upload\"],\"OPFjyX\":[\"Install the <0>Webhook plugin<1/> from Jellyfin's plugin catalog\"],\"OW/+RD\":[\"Watch history\"],\"OZdaTZ\":[\"Person\"],\"OvO76u\":[\"Back to Login\"],\"OvdFIZ\":[\"Season watched\"],\"P2FLLe\":[\"View release\"],\"PBdLfg\":[\"No titles found for this genre.\"],\"PQ3qDa\":[\"Fetching your library data from \",[\"source\"],\"...\"],\"PjNoxI\":[\"Scheduled backup\"],\"Pn2B7/\":[\"Current password\"],\"PnEbL/\":[\"Rated \",[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"star\"],\"other\":[\"stars\"]}]],\"Pwqkdw\":[\"Loading…\"],\"Py87xY\":[\"Authorization succeeded but failed to fetch your library. Please try again.\"],\"Q3MPWA\":[\"Update password\"],\"QHcLEN\":[\"Connected\"],\"QK4UIx\":[\"Mark Watched\"],\"Qjlym2\":[\"Failed to create account\"],\"Qm1NmK\":[\"OR\"],\"Qoq+GP\":[\"Read more\"],\"QphVZW\":[\"Can't reach server\"],\"QqLJHH\":[\"This Year\"],\"R0yu2l\":[\"Catch up\"],\"R4YBui\":[\"Search for movies and TV shows to start tracking\"],\"RH46vw\":[[\"0\"],\"/\",[\"1\"],\" \",[\"2\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}]],\"RIR15/\":[\"Radarr List URL\"],\"RLe7Vk\":[\"Checking…\"],\"ROq8cl\":[\"Failed to parse file\"],\"RQq8Si\":[\"Registration opened\"],\"S0soqb\":[\"Failed to remove profile picture\"],\"S1McZh\":[\"Failed to upload avatar\"],\"S2ble5\":[[\"0\"],\" movies, \",[\"1\"],\" episodes\"],\"S2qPRR\":[\"Search movies & TV shows…\"],\"SDND4q\":[\"Not configured\"],\"SFdAk9\":[\"Unwatched S\",[\"seasonNum\"],\" E\",[\"epNum\"]],\"SUIUkB\":[\"Unwatch all\"],\"SbS+Bm\":[\"Regenerate URL\"],\"SlfejT\":[\"Error\"],\"Sp86ju\":[[\"0\"],\" ep\",[\"1\"]],\"SrfROI\":[\"Up next\"],\"SyPRjk\":[\"Sofa will automatically log movies and episodes when you finish watching them\"],\"T0/7WG\":[\"Next backup \",[\"distance\"]],\"TEaX6q\":[\"Backup deleted\"],\"TI7IKo\":[\"Rated \",[\"0\"],\" star\",[\"1\"]],\"TMb7iE\":[\"Profile picture removed\"],\"TqyQQS\":[\"Enable the <0>Playback event category\"],\"Tz0i8g\":[\"Settings\"],\"U+FxtW\":[\"Track what you watch. Know what's next.<0/>Your library, your data, your rules.\"],\"U3pytU\":[\"Admin\"],\"UC+OWB\":[\"Toggle open registration\"],\"UDMjsP\":[\"Quick Actions\"],\"UmQ6Fe\":[\"Purge image cache?\"],\"UtDm3q\":[\"URL copied to clipboard\"],\"V1Kh9Z\":[\"Episodes \",[\"select\"]],\"V6uzvC\":[\"More Like This\"],\"V9CuQ+\":[\"Connect to \",[\"source\"]],\"VAcXNz\":[\"Wednesday\"],\"VKyhZK\":[\"Title not found\"],\"VQvpro\":[\"Paste the Sonarr URL above into the List URL field\"],\"VhMDMg\":[\"Change Password\"],\"Vx0ayx\":[\"Failed to mark some episodes\"],\"WDC4PF\":[\"Episodes \",[\"periodSelect\"]],\"WEYdDv\":[\"Recommended\"],\"WMCwmR\":[\"Check for updates\"],\"WP48q2\":[\"Image Cache\"],\"WT1Ibn\":[\"Last run\"],\"Wb3E4g\":[\"Run now\"],\"WgF2UQ\":[\"You're running v\",[\"0\"],\".\"],\"WtWhSi\":[\"Rating removed\"],\"Wy/3II\":[\"Last polled \",[\"0\"]],\"XFCSYs\":[\"Cast\"],\"XN23JY\":[\"This will delete un-enriched stub titles that aren't in any user's library and clean up orphaned person records. Deleted titles will be re-imported if accessed again.\"],\"XZwihE\":[\"Ready — nothing received yet\"],\"XjTduw\":[\"Upload picture\"],\"YEGzVq\":[\"Failed to connect \",[\"label\"]],\"YErf89\":[\"Failed to purge metadata cache\"],\"YQ768h\":[\"Scene not found\"],\"YiRsXK\":[\"Clear Recently Viewed?\"],\"Yjp1zf\":[\"Don't have a server?\"],\"YqMfa9\":[\"Review what was found and choose what to import.\"],\"ZGUYm0\":[\"Ready — not polled yet\"],\"ZQKLI1\":[\"Danger Zone\"],\"ZVZUoU\":[[\"0\"],\" cached images\"],\"ZWTQ81\":[\"Watch on \",[\"name\"]],\"a3LDKx\":[\"Security\"],\"aLBUiR\":[\"Keeping <0><1><2>\",[\"0\"],\"<3>\",[\"1\"],\" backups.\"],\"aM0+kY\":[\"Purged \",[\"0\",\"plural\",{\"one\":[\"#\",\" stale title\"],\"other\":[\"#\",\" stale titles\"]}],\" and \",[\"1\",\"plural\",{\"one\":[\"#\",\" orphaned person\"],\"other\":[\"#\",\" orphaned persons\"]}]],\"aO1AxG\":[\"Episode unwatched\"],\"aV/hDI\":[\"Failed to disconnect \",[\"label\"]],\"acbSg0\":[\"Double tap to filter by \",[\"label\"]],\"agE7k4\":[\"Rated \",[\"stars\",\"plural\",{\"one\":[\"#\",\" star\"],\"other\":[\"#\",\" stars\"]}]],\"alPRaV\":[\"Titles on your Sofa watchlist will be automatically added for download when Radarr polls this list (every 12 hours by default)\"],\"aourBv\":[\"Scheduled backups enabled\"],\"apLLSU\":[\"Are you sure you want to sign out?\"],\"atc9MA\":[\"Open Emby, go to <0>Settings > Webhooks\"],\"auVUJO\":[\"Restore database?\"],\"ayqfr4\":[[\"label\"],\" URL regenerated\"],\"b/8KCH\":[\"This will mark every episode of this show as watched. You can undo this later by unmarking individual seasons.\"],\"b3Thhd\":[\"Upload failed\"],\"b5DFtH\":[\"Set password\"],\"bHYIks\":[\"Sign Out\"],\"bITrbE\":[\"Purge all\"],\"bNmdjU\":[\"Watchlist\"],\"bTRwag\":[\"Remove from Library\"],\"bXMotV\":[\"Marked as watched\"],\"bezGJ2\":[[\"0\"],\" images\"],\"boJlGf\":[\"Page Not Found\"],\"c1ssjI\":[\"Importing from \",[\"source\"]],\"c3b0B0\":[\"Get Started\"],\"c4b9Dm\":[\"No results for \\\"\",[\"debouncedQuery\"],\"\\\"\"],\"cQPKZt\":[\"Go to Dashboard\"],\"ccAJSB\":[\"Keeping\"],\"cgvva8\":[[\"0\"],\" ratings\"],\"cnGeoo\":[\"Delete\"],\"cpE88+\":[\"Create your account\"],\"d/6MoL\":[\"Manage your account and preferences\"],\"dA7BWh\":[\"Requires Emby Server 4.7.9+ and an active Emby Premiere license.\"],\"dEgA5A\":[\"Cancel\"],\"dTZwve\":[\"Marked all episodes as watched\"],\"dUCJry\":[\"Newest\"],\"ddwpAr\":[\"Warnings (\",[\"0\"],\")\"],\"e/ToF5\":[\"Sign in with \",[\"0\"]],\"e9sZMS\":[\"This will permanently delete the backup from <0>\",[\"0\"],\". This cannot be undone.\"],\"eFRooE\":[\"Last run failed\"],\"eM39Om\":[\"Watched all of \",[\"seasonLabel\"]],\"eZjYb8\":[\"Writer\"],\"ecUA8p\":[\"Today\"],\"evBxZy\":[\"Where to Watch\"],\"ezDa1h\":[\"Open docs\"],\"f+m696\":[\"An unexpected error occurred while loading this page. You can try again or head back to the dashboard.\"],\"f/AKdU\":[\"Are you sure you want to disconnect \",[\"label\"],\"? The current URL will stop working.\"],\"f7ax8J\":[\"Open in browser…\"],\"fMPkxb\":[\"Show more\"],\"fNMqNn\":[\"Popular TV Shows\"],\"fObVvy\":[\"Profile picture updated\"],\"fRettQ\":[[\"0\"],\" items\"],\"fUDRF9\":[\"Watchlisted\"],\"fcWrnU\":[\"Sign out\"],\"fgLNSM\":[\"Register\"],\"fsAEqk\":[\"Continue Watching\"],\"fzAeQI\":[\"Registering on \",[\"serverHost\"]],\"g+gBfk\":[\"Parsing...\"],\"g4JYff\":[\"Imported \",[\"0\"],\" items from \",[\"1\"]],\"gKtb5i\":[\"Titles on your Sofa watchlist will be automatically added for download when Sonarr polls this list (every 6 hours by default)\"],\"gaIAMq\":[\"Remove picture\"],\"gbEEMp\":[\"Delete backup\"],\"gcNLi0\":[\"Update checks enabled\"],\"gmB6oO\":[\"Schedule\"],\"h/T5Yb\":[\"Open registration\"],\"h4yKYk\":[\"Next run\"],\"h7MgpO\":[\"Keyboard Shortcuts\"],\"hTXYdY\":[\"Failed to create backup\"],\"hXYY5Q\":[\"Unwatched all of \",[\"0\"]],\"he3ygx\":[\"Copy\"],\"hjGupC\":[\"Lost connection to import. Check status in settings.\"],\"hlqjFc\":[\"In library\"],\"hou0tP\":[\"Stream\"],\"hty0d5\":[\"Monday\"],\"i0qMbr\":[\"Home\"],\"i9rcQ/\":[\"Movies\"],\"iGma9e\":[\"Update failed\"],\"iSLIjg\":[\"Connect\"],\"iWv3ck\":[\"Failed to catch up\"],\"iXZ09g\":[\"Mark as Completed\"],\"id08cd\":[\"Watched S\",[\"seasonNum\"],\" E\",[\"epNum\"]],\"ih87w/\":[\"Add to Library\"],\"ihn4zD\":[\"Search…\"],\"itDEco\":[\"Already have an account? Sign in\"],\"itheEn\":[\"With Ads\"],\"iwCRIF\":[\"You'll be signed out to change the server URL.\"],\"j5GBIy\":[\"Open Radarr, go to Settings > Import Lists\"],\"jAe8l4\":[[\"0\"],\" backups · last <0/>\"],\"jB3sfe\":[\"Manual backup\"],\"jFnMJ8\":[\"Double tap to clear this filter\"],\"jQsPwL\":[\"Scheduled backups disabled\"],\"jSjGeu\":[[\"0\"],\" items have no external IDs and will be resolved by title search, which may be less accurate.\"],\"jX6Gzg\":[\"This will replace your entire database with the uploaded file. A safety backup of your current data will be created first. Active sessions may need to refresh after restore.\"],\"jiHVUy\":[\"Pre-restore backup\"],\"k6c41p\":[\"Background jobs\"],\"kBDOjB\":[\"Backup schedule\"],\"kkDQ8m\":[\"Thursday\"],\"klOeIX\":[\"Failed to change password\"],\"ks3XeZ\":[\"Open Sonarr, go to Settings > Import Lists\"],\"kvuCtu\":[\"Something went wrong while loading this title. Please try again.\"],\"kx0s+n\":[\"Results\"],\"l2wcoS\":[\"Last run succeeded\"],\"l3s5ri\":[\"Import\"],\"lCF0wC\":[\"Refresh\"],\"lJ1yo4\":[\"Failed to sign in\"],\"lVqzRx\":[\"Click <0>Add Webhook and paste the URL above\"],\"lXkUEV\":[\"Availability\"],\"lcLe89\":[\"Search for movies, TV shows, or run commands\"],\"lfVyvz\":[\"Add to Watchlist\"],\"llDXYJ\":[\"Backups\"],\"llkZR0\":[\"Could not load integrations\"],\"ln9/n9\":[\"Don't have an account?\"],\"lpIMne\":[\"Passwords do not match\"],\"lqY3WY\":[\"Change Server\"],\"luoodD\":[\"Setup required\"],\"m5WhJy\":[\"Automatic update checks\"],\"mIx58S\":[\"Purge images\"],\"mYBORk\":[\"Movie\"],\"ml9cU0\":[\"Failed to regenerate \",[\"label\"],\" URL\"],\"mqwkjd\":[\"Health status\"],\"mqxHH7\":[\"Go to Explore\"],\"mzI/c+\":[\"Download\"],\"n1ekoW\":[\"Sign In\"],\"n3Pzd7\":[\"Backup created\"],\"nBHvPL\":[\"Cancel editing\"],\"nJw77c\":[[\"0\",\"plural\",{\"one\":[\"#\",\" image\"],\"other\":[\"#\",\" images\"]}]],\"nO0Qnj\":[\"Your code:\"],\"nRP1xx\":[\"Need more help?\"],\"nV1LjT\":[\"Enable the <0>Playback Stop notification type\"],\"nVsE67\":[\"Mark as Watching\"],\"nZ7lF1\":[\"Device code expired. Please try again.\"],\"nbfdhU\":[\"Integrations\"],\"nbmpf9\":[\"Purge metadata\"],\"nnKJTm\":[\"Failed to mark all episodes as watched\"],\"nszbQG\":[\"No backups yet\"],\"nuh/Wq\":[\"Webhook URL\"],\"nwtY4N\":[\"Something went wrong\"],\"oAIA3w\":[\"Search for movies, shows, or people\"],\"oC8IMh\":[\"Search movies, shows, people...\"],\"oNvZcA\":[\"Episodes this week\"],\"oXq+Wr\":[[\"label\"],\" connected\"],\"ogtYkT\":[\"Password updated\"],\"ojtedN\":[\"Open Radarr, go to <0>Settings > Import Lists\"],\"olMi35\":[\"Recommended for You\"],\"p+PZEl\":[\"Here's what's happening with your library\"],\"pAtylB\":[\"Not Found\"],\"pG9pq1\":[\"Deleted \",[\"0\",\"plural\",{\"one\":[\"#\",\" file\"],\"other\":[\"#\",\" files\"]}],\", freed \",[\"freed\"]],\"pWWFjv\":[\"Checked <0/>\"],\"ph76H6\":[\"Allow new users to create accounts\"],\"pjgw0N\":[\"Choose how to import your \",[\"0\"],\" data.\"],\"puo3W3\":[\"Redirecting…\"],\"q3GraM\":[\"...and \",[\"0\"],\" more\"],\"q6nrFE\":[\"died at \",[\"age\"]],\"q6pUQ9\":[\"Upload a .db file to replace the current database. A safety backup is created first.\"],\"q8yluz\":[\"Your name\"],\"qA6VR5\":[\"Requires <0>Emby Server 4.7.9+ and an active <1>Emby Premiere<2/> license.\"],\"qF2IBM\":[\"Movies this month\"],\"qHbApt\":[\"Failed to load backup schedule settings.\"],\"qLB1zX\":[\"Upload a \",[\"0\"],\" export from your \",[\"1\"],\" account settings.\"],\"qPNzfu\":[\"Update checks disabled\"],\"qWoML/\":[\"Add a new webhook and paste the URL above\"],\"qiOIiY\":[\"Buy\"],\"qn1X6N\":[\"Failed to update registration setting\"],\"qqWcBV\":[\"Completed\"],\"qqeAJM\":[\"Never\"],\"r9aDAY\":[[\"count\"],\" earlier \",[\"count\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}],\" unwatched\"],\"rLgPvm\":[\"Backup\"],\"rSTpb5\":[\"Server Health\"],\"rdymVD\":[\"Trigger job\"],\"rtir7c\":[\"unknown\"],\"s0U4ZZ\":[\"TV episodes\"],\"s4vVUm\":[\"Could not load person details\"],\"s9dVME\":[\"Failed to unmark episode\"],\"sGH11W\":[\"Server\"],\"sl/qWH\":[\"Upload profile picture\"],\"sstysK\":[\"Failed to mark episode\"],\"stZeiF\":[[\"watched\"],\"/\",[\"total\"],\" episodes\"],\"t/Ch8S\":[\"Marked as watching\"],\"t/YqKh\":[\"Remove\"],\"tfDRzk\":[\"Save\"],\"tiymc0\":[\"Something went wrong…\"],\"u9ugU7\":[[\"watchedCount\"],\"/\",[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}]],\"uBAxNB\":[\"Editor\"],\"uBrbSu\":[\"Failed to start \",[\"0\"],\" connection\"],\"uM6jnS\":[\"Restore failed\"],\"uMrJrX\":[\"Admin only\"],\"uswLvZ\":[\"The title you're looking for doesn't exist or may have been removed from the database.\"],\"uxOntd\":[[\"watchedCount\"],\" of \",[\"0\"],\" episodes watched\"],\"v2CA3w\":[\"Change Photo\"],\"v5ipVu\":[\"Mark all episodes as watched?\"],\"vKfeax\":[\"Open Emby, go to Settings > Webhooks\"],\"vXIe7J\":[\"Language\"],\"vuCCZ7\":[\"Failed to parse import data\"],\"vwKERN\":[\"Failed to delete backup\"],\"w8pqsh\":[\"Mark All Watched\"],\"wA7B2T\":[\"New accounts are not being accepted right now. Contact the admin if you need access.\"],\"wGFX13\":[\"Starting at\"],\"wR1UAy\":[\"Popular Movies\"],\"wRWcdL\":[\"Play trailer\"],\"wThGrS\":[\"App Settings\"],\"wZK4Xg\":[\"Never run\"],\"wdLxgL\":[\"Member since \",[\"memberSince\"]],\"wja8aL\":[\"Untitled\"],\"wtGebH\":[\"The person you're looking for doesn't exist or may have been removed from the database.\"],\"wtsbt5\":[\"Add to watchlist\"],\"wtuVU4\":[\"Frequency\"],\"wx7pwA\":[\"Mark as Watched\"],\"x4ZiTl\":[\"Setup instructions\"],\"xCJdfg\":[\"Clear\"],\"xGVfLh\":[\"Continue\"],\"xLoCm2\":[\"Check configuration\"],\"xSrU2g\":[[\"healthyCount\"],\" of \",[\"0\"],\" jobs healthy\"],\"xazqmy\":[\"Seasons\"],\"xbBXhy\":[\"Periodically check GitHub for new Sofa releases\"],\"xrh2/M\":[\"Failed to mark as watched\"],\"y3e9pF\":[\"Delete backup?\"],\"y6Urel\":[\"Next Episode\"],\"yGvjAo\":[\"Update available: \",[\"0\"]],\"yKu/3Y\":[\"Restore\"],\"yYxB17\":[\"Clear all\"],\"ybtG1U\":[[\"0\"],\" backup\",[\"1\"],\" stored\"],\"yey/zg\":[\"Database restored. Reloading...\"],\"ygo0l/\":[\"Start tracking movies and shows\"],\"yvwIbI\":[\"Upload export file\"],\"yz7wBu\":[\"Close\"],\"z1U/Fh\":[\"Rating\"],\"z5evln\":[\"No internet connection\"],\"zAvS8w\":[\"Sign in to continue\"],\"zEqK2w\":[\"The page you're looking for doesn't exist.\"],\"zFkiTv\":[\"Install the Webhook plugin from Jellyfin's plugin catalog\"],\"zNyR4f\":[\"Set your preferred quality profile and root folder\"],\"za8Le/\":[\"Registration Closed\"],\"zb77GC\":[\"Rent\"],\"ztAdhw\":[\"Name updated\"]}")as Messages; \ No newline at end of file diff --git a/packages/i18n/src/po/fr.po b/packages/i18n/src/po/fr.po new file mode 100644 index 0000000..030879c --- /dev/null +++ b/packages/i18n/src/po/fr.po @@ -0,0 +1,2433 @@ +msgid "" +msgstr "" +"POT-Creation-Date: 2026-03-17 21:04-0400\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: @lingui/cli\n" +"Language: fr\n" +"Project-Id-Version: sofa\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2026-03-18 16:04\n" +"Last-Translator: \n" +"Language-Team: French\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Crowdin-Project: sofa\n" +"X-Crowdin-Project-ID: 881052\n" +"X-Crowdin-Language: fr\n" +"X-Crowdin-File: /packages/i18n/src/po/en.po\n" +"X-Crowdin-File-ID: 12\n" + +#. placeholder {0}: result.errors.length - 50 +#: apps/web/src/components/settings/imports-section.tsx:938 +msgid "...and {0} more" +msgstr "" + +#. placeholder {0}: systemHealth.data.imageCache.imageCount +#: apps/native/src/app/(tabs)/(settings)/index.tsx:482 +msgid "{0, plural, one {# image} other {# images}}" +msgstr "" + +#. placeholder {0}: systemHealth.data.database.titleCount +#: apps/native/src/app/(tabs)/(settings)/index.tsx:466 +msgid "{0, plural, one {# title} other {# titles}}" +msgstr "" + +#. placeholder {0}: displayBackups.length +#. placeholder {1}: displayBackups.length +#: apps/web/src/components/settings/backup-section.tsx:109 +msgid "{0} {1, plural, one {backup} other {backups}} stored" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:105 +#~ msgid "{0} backup{1} stored" +#~ msgstr "{0} backup{1} stored" + +#. placeholder {0}: backups.backupCount +#: apps/web/src/components/settings/system-health-section.tsx:654 +msgid "{0} backups · last <0/>" +msgstr "" + +#. placeholder {0}: imageCache.imageCount.toLocaleString() +#: apps/web/src/components/settings/system-health-section.tsx:621 +msgid "{0} cached images" +msgstr "" + +#. placeholder {0}: member.episodeCount +#. placeholder {1}: member.episodeCount !== 1 ? "s" : "" +#: apps/web/src/components/titles/cast-carousel.tsx:80 +msgid "{0} ep{1}" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:472 +#~ msgid "{0} images" +#~ msgstr "{0} images" + +#. placeholder {0}: stats.watchlist +#: apps/web/src/components/settings/imports-section.tsx:808 +msgid "{0} items" +msgstr "" + +#. placeholder {0}: preview.diagnostics.unresolved +#: apps/web/src/components/settings/imports-section.tsx:788 +msgid "{0} items have no external IDs and will be resolved by title search, which may be less accurate." +msgstr "" + +#. placeholder {0}: stats.movies +#. placeholder {1}: stats.episodes +#: apps/web/src/components/settings/imports-section.tsx:802 +msgid "{0} movies, {1} episodes" +msgstr "" + +#. placeholder {0}: stats.ratings +#: apps/web/src/components/settings/imports-section.tsx:814 +msgid "{0} ratings" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:456 +#~ msgid "{0} titles" +#~ msgstr "{0} titles" + +#. placeholder {0}: JOB_LABELS[name] ?? name +#: apps/web/src/components/settings/system-health-section.tsx:343 +msgid "{0} triggered" +msgstr "" + +#. placeholder {0}: item.watchedEpisodes +#. placeholder {1}: item.totalEpisodes +#. placeholder {2}: item.totalEpisodes +#: apps/web/src/components/dashboard/continue-watching-card.tsx:93 +msgid "{0}/{1} {2, plural, one {episode} other {episodes}}" +msgstr "" + +#: apps/web/src/components/dashboard/continue-watching-card.tsx:92 +#~ msgid "{0}/{1} episodes" +#~ msgstr "{0}/{1} episodes" + +#: apps/web/src/components/titles/use-title-actions.ts:221 +#: apps/web/src/components/titles/use-title-actions.ts:299 +msgid "{count} earlier {count, plural, one {episode} other {episodes}} unwatched" +msgstr "" + +#. placeholder {0}: activeJobs.length +#: apps/web/src/components/settings/system-health-section.tsx:385 +msgid "{healthyCount} of {0} jobs healthy" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:96 +#: apps/web/src/components/settings/integration-card.tsx:106 +msgid "{label} connected" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:106 +#: apps/web/src/components/settings/integration-card.tsx:122 +msgid "{label} disconnected" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:116 +#: apps/web/src/components/settings/integration-card.tsx:138 +msgid "{label} URL regenerated" +msgstr "" + +#: apps/web/src/components/title-card.tsx:166 +msgid "{watched}/{total} episodes" +msgstr "" + +#. placeholder {0}: episodes.length +#: apps/native/src/components/titles/season-accordion.tsx:122 +msgid "{watchedCount} of {0} episodes watched" +msgstr "" + +#. placeholder {0}: episodes.length +#. placeholder {1}: episodes.length +#: apps/native/src/components/titles/season-accordion.tsx:131 +msgid "{watchedCount}/{0} {1, plural, one {episode} other {episodes}}" +msgstr "" + +#: apps/native/src/components/titles/season-accordion.tsx:130 +#~ msgid "{watchedCount}/{0} episodes" +#~ msgstr "{watchedCount}/{0} episodes" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:231 +#: apps/web/src/components/settings/account-section.tsx:175 +msgid "Account" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:412 +msgid "Actions" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:71 +msgid "Actor" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:72 +msgid "Add a \"Generic Destination\" and paste the URL above" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:131 +msgid "Add a <0>Generic Destination and paste the URL above" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:92 +#: apps/web/src/components/settings/integration-configs.tsx:198 +msgid "Add a new webhook and paste the URL above" +msgstr "" + +#: apps/web/src/components/explore/hero-banner.tsx:99 +msgid "Add to Library" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:72 +msgid "Add to watchlist" +msgstr "" + +#: apps/native/src/components/explore/hero-banner.tsx:157 +#: apps/native/src/components/ui/poster-card.tsx:238 +#: apps/web/src/components/title-card.tsx:146 +msgid "Add to Watchlist" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:23 +msgid "Added \"{titleName}\" to watchlist" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:44 +#: apps/native/src/hooks/use-title-actions.ts:60 +#: apps/native/src/lib/title-actions.ts:24 +#: apps/web/src/components/titles/use-title-actions.ts:111 +msgid "Added to watchlist" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:353 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:454 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:499 +#: apps/web/src/components/nav-bar.tsx:235 +#: apps/web/src/components/settings/account-section.tsx:335 +msgid "Admin" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:124 +msgid "Admin only" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:308 +#: apps/web/src/components/people/person-hero.tsx:91 +msgid "age {age}" +msgstr "" + +#: apps/native/src/components/explore/filterable-title-row.tsx:119 +#: apps/web/src/components/people/filmography-grid.tsx:58 +msgid "All" +msgstr "" + +#: apps/web/src/components/settings/registration-section.tsx:61 +msgid "Allow new users to create accounts" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:278 +msgid "Already have an account?" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:239 +msgid "Already have an account? Sign in" +msgstr "" + +#: apps/web/src/routes/__root.tsx:146 +msgid "An unexpected error occurred while loading this page. You can try again or head back to the dashboard." +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:427 +msgid "Anonymous usage reporting" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:102 +msgid "App Settings" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:378 +msgid "Application" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:151 +msgid "Are you sure you want to disconnect {label}? The current URL will stop working." +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:206 +#: apps/native/src/components/header-avatar.tsx:70 +msgid "Are you sure you want to sign out?" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:450 +msgid "Authorization succeeded but failed to fetch your library. Please try again." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:440 +msgid "Authorization was denied. Please try again." +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:611 +msgid "Authorize Sofa to read your {0} library. No password shared." +msgstr "" + +#: apps/web/src/components/settings/update-check-section.tsx:60 +msgid "Automatic update checks" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:279 +msgid "Automatically back up your database on a schedule" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:332 +msgid "Availability" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:104 +msgid "Back to Login" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:382 +msgid "Background jobs" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:336 +msgid "Backup" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:69 +msgid "Backup created" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:94 +msgid "Backup deleted" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:234 +msgid "Backup schedule" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:641 +#: apps/web/src/routes/_app/settings.tsx:166 +msgid "Backups" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:272 +#~ msgid "backups." +#~ msgstr "backups." + +#: apps/web/src/components/titles/title-availability.tsx:139 +msgid "Buy" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:89 +msgid "Cache management" +msgstr "" + +#: apps/native/src/components/ui/server-unreachable-banner.ios.tsx:66 +#: apps/native/src/components/ui/server-unreachable-banner.tsx:74 +msgid "Can't reach server" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:207 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:332 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:388 +#: apps/native/src/components/header-avatar.tsx:71 +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:63 +#: apps/native/src/components/search/recently-viewed-list.tsx:37 +#: apps/native/src/components/settings/integration-card.tsx:138 +#: apps/native/src/components/settings/integration-card.tsx:153 +#: apps/web/src/components/settings/account-section.tsx:505 +#: apps/web/src/components/settings/backup-restore-section.tsx:107 +#: apps/web/src/components/settings/backup-section.tsx:270 +#: apps/web/src/components/settings/danger-section.tsx:129 +#: apps/web/src/components/settings/danger-section.tsx:171 +#: apps/web/src/components/settings/danger-section.tsx:214 +#: apps/web/src/components/settings/imports-section.tsx:645 +#: apps/web/src/components/settings/imports-section.tsx:710 +#: apps/web/src/components/settings/imports-section.tsx:837 +#: apps/web/src/components/titles/title-seasons.tsx:133 +msgid "Cancel" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:307 +msgid "Cancel editing" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:559 +#: apps/web/src/components/titles/cast-carousel.tsx:21 +msgid "Cast" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:223 +#: apps/web/src/components/titles/use-title-actions.ts:301 +msgid "Catch up" +msgstr "" + +#. placeholder {0}: episodeIds.length +#. placeholder {1}: episodeIds.length +#: apps/web/src/components/titles/use-title-actions.ts:82 +msgid "Caught up — marked {0} {1, plural, one {episode} other {episodes}} as watched" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:361 +#: apps/web/src/components/settings/account-section.tsx:427 +#: apps/web/src/components/settings/account-section.tsx:432 +msgid "Change password" +msgstr "" + +#: apps/native/src/app/change-password.tsx:98 +msgid "Change Password" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:267 +msgid "Change Photo" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:385 +msgid "Change Server" +msgstr "" + +#: apps/web/src/components/setup/refresh-button.tsx:30 +msgid "Check configuration" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:513 +msgid "Check for updates" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:215 +msgid "Checked <0/>" +msgstr "" + +#: apps/web/src/components/setup/refresh-button.tsx:28 +msgid "Checking…" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:587 +msgid "Choose how to import your {0} data." +msgstr "" + +#: apps/web/src/components/settings/language-section.tsx:29 +#~ msgid "Choose your preferred display language" +#~ msgstr "Choose your preferred display language" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:65 +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:115 +#: apps/native/src/components/search/recently-viewed-list.tsx:39 +#: apps/native/src/components/search/recently-viewed-list.tsx:95 +msgid "Clear" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:322 +msgid "Clear all" +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:60 +#: apps/native/src/components/search/recently-viewed-list.tsx:34 +msgid "Clear Recently Viewed?" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:109 +#: apps/native/src/components/settings/integration-configs.ts:127 +msgid "Click \"+\" and select \"Custom Lists\"" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:55 +msgid "Click \"Add Webhook\" and paste the URL above" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:237 +#: apps/web/src/components/settings/integration-configs.tsx:277 +msgid "Click <0>+ and select <1>Custom Lists" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:79 +msgid "Click <0>Add Webhook and paste the URL above" +msgstr "" + +#: apps/native/src/components/navigation/modal-stack-header.tsx:14 +msgid "Close" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:63 +#: apps/native/src/components/titles/status-action-button.tsx:44 +#: apps/web/src/components/dashboard/stats-display.tsx:238 +#: apps/web/src/components/title-card.tsx:77 +#: apps/web/src/components/titles/status-button.tsx:30 +msgid "Completed" +msgstr "" + +#: apps/native/src/app/change-password.tsx:173 +#: apps/web/src/components/settings/account-section.tsx:477 +msgid "Confirm new password" +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:207 +#: apps/web/src/components/settings/imports-section.tsx:499 +msgid "Connect" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/integration-card.tsx:203 +msgid "Connect {0}" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:238 +msgid "Connect {label}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:665 +msgid "Connect to {source}" +msgstr "" + +#: apps/web/src/routes/setup.tsx:101 +msgid "Connect to TMDB" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:608 +msgid "Connect with {0}" +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:197 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:474 +#: apps/web/src/components/settings/system-health-section.tsx:256 +msgid "Connected" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:722 +msgid "Connected to {source}" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:240 +msgid "Connected to <0>{serverHost}. Tap to change." +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:186 +msgid "Connecting to server..." +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:201 +msgid "Connecting..." +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:237 +msgid "Connecting…" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:390 +msgid "Continue" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:113 +#: apps/web/src/components/dashboard/continue-watching-section.tsx:25 +msgid "Continue Watching" +msgstr "" + +#: apps/web/src/components/setup/copy-button.tsx:25 +msgid "Copied" +msgstr "" + +#: apps/web/src/components/setup/copy-button.tsx:30 +msgid "Copy" +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:246 +msgid "Copy URL" +msgstr "" + +#: apps/native/src/components/settings/integrations-section.tsx:29 +msgid "Could not load integrations" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:216 +msgid "Could not load person details" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:247 +msgid "Create account" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:115 +#: apps/native/src/app/(auth)/register.tsx:225 +msgid "Create Account" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:216 +msgid "Create an account" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:110 +msgid "Create your account" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:137 +msgid "Creating…" +msgstr "" + +#: apps/native/src/app/change-password.tsx:120 +#: apps/web/src/components/settings/account-section.tsx:449 +msgid "Current password" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:391 +msgid "Current password is required" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:195 +msgid "Danger Zone" +msgstr "" + +#: apps/web/src/routes/__root.tsx:167 +#: apps/web/src/routes/_app/people.$id.tsx:78 +#: apps/web/src/routes/_app/titles.$id.tsx:126 +#: apps/web/src/routes/_app/titles.$id.tsx:172 +msgid "Dashboard" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:463 +#: apps/web/src/components/settings/system-health-section.tsx:229 +msgid "Database" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:126 +msgid "Database backups" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:31 +msgid "Database restored. Reloading..." +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:342 +msgid "Day:" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:250 +#: apps/web/src/components/settings/backup-section.tsx:280 +msgid "Delete" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:235 +msgid "Delete backup" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:256 +msgid "Delete backup?" +msgstr "" + +#. placeholder {0}: data.deletedFiles +#: apps/web/src/components/settings/danger-section.tsx:54 +msgid "Deleted {0, plural, one {# file} other {# files}}, freed {freed}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:412 +#: apps/web/src/components/settings/imports-section.tsx:444 +msgid "Device code expired. Please try again." +msgstr "" + +#: apps/native/src/app/person/[id].tsx:307 +#: apps/web/src/components/people/person-hero.tsx:91 +msgid "died at {age}" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:73 +msgid "Director" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:431 +#: apps/web/src/components/settings/system-health-section.tsx:632 +msgid "Disabled" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:155 +#: apps/native/src/components/settings/integration-card.tsx:298 +#: apps/web/src/components/settings/integration-card.tsx:274 +msgid "Disconnect" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:150 +msgid "Disconnect {label}" +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:242 +msgid "Don't have a server?" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:288 +msgid "Don't have an account?" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:962 +msgid "Done" +msgstr "" + +#: apps/native/src/components/explore/genre-chip.tsx:27 +msgid "Double tap to clear this filter" +msgstr "" + +#: apps/native/src/components/explore/genre-chip.tsx:28 +msgid "Double tap to filter by {label}" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:71 +#: apps/web/src/components/settings/backup-section.tsx:223 +msgid "Download" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:214 +msgid "Download backup" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:79 +msgid "Editor" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:133 +#: apps/native/src/app/(auth)/register.tsx:154 +#: apps/web/src/components/auth-form.tsx:201 +msgid "Email" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:73 +msgid "Enable the \"Playback Stop\" notification type" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:93 +msgid "Enable the \"Playback\" event category" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:140 +msgid "Enable the <0>Playback Stop notification type" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:201 +msgid "Enable the <0>Playback event category" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:668 +msgid "Enter the code below on {source}'s website to authorize Sofa." +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:435 +msgid "Enter your current password and choose a new one." +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:151 +msgid "Enter your Sofa server URL to get started" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:289 +msgid "Environment" +msgstr "" + +#. placeholder {0}: episode.episodeNumber +#: apps/native/src/components/titles/episode-row.tsx:29 +#: apps/native/src/components/titles/episode-row.tsx:55 +msgid "Episode {0}" +msgstr "" + +#. placeholder {0}: episode.episodeNumber +#: apps/native/src/components/titles/episode-row.tsx:36 +msgid "Episode {0}, {episodeLabel}" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:123 +#: apps/native/src/lib/title-actions.ts:111 +msgid "Episode unwatched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:109 +#: apps/native/src/lib/title-actions.ts:101 +msgid "Episode watched" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:780 +#: apps/web/src/components/titles/title-seasons.tsx:102 +msgid "Episodes" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:162 +#~ msgid "Episodes {periodSelect}" +#~ msgstr "Episodes {periodSelect}" + +#: apps/web/src/components/dashboard/stats-display.tsx:164 +msgid "Episodes {select}" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:61 +msgid "Episodes this week" +msgstr "" + +#: apps/native/src/app/change-password.tsx:71 +#: apps/native/src/app/change-password.tsx:81 +msgid "Error" +msgstr "" + +#. placeholder {0}: result.errors.length +#: apps/web/src/components/settings/imports-section.tsx:929 +msgid "Errors ({0})" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:143 +#: apps/web/src/components/nav-bar.tsx:126 +#: apps/web/src/components/nav-bar.tsx:291 +msgid "Explore" +msgstr "" + +#: apps/web/src/routes/_app/people.$id.tsx:70 +#: apps/web/src/routes/_app/titles.$id.tsx:164 +msgid "Explore titles" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:923 +msgid "Failed" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:49 +#: apps/native/src/lib/title-actions.ts:28 +msgid "Failed to add to watchlist" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:90 +msgid "Failed to catch up" +msgstr "" + +#: apps/native/src/app/change-password.tsx:71 +#: apps/web/src/components/settings/account-section.tsx:411 +msgid "Failed to change password" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:398 +msgid "Failed to connect" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:99 +#: apps/web/src/components/settings/integration-card.tsx:108 +msgid "Failed to connect {label}" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:67 +msgid "Failed to create account" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:81 +msgid "Failed to create backup" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:97 +msgid "Failed to delete backup" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:109 +#: apps/web/src/components/settings/integration-card.tsx:125 +msgid "Failed to disconnect {label}" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:215 +msgid "Failed to load backup schedule settings." +msgstr "" + +#: apps/web/src/routes/_app/titles.$id.tsx:115 +msgid "Failed to load title" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:367 +msgid "Failed to mark all episodes as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:84 +#: apps/native/src/lib/title-actions.ts:68 +#: apps/web/src/components/titles/use-title-actions.ts:150 +msgid "Failed to mark as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:113 +#: apps/native/src/lib/title-actions.ts:104 +#: apps/web/src/components/titles/use-title-actions.ts:237 +msgid "Failed to mark episode" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:141 +#: apps/native/src/lib/title-actions.ts:128 +#: apps/web/src/components/titles/use-title-actions.ts:315 +msgid "Failed to mark some episodes" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:225 +msgid "Failed to parse file" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:240 +msgid "Failed to parse import data" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:75 +msgid "Failed to purge caches" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:58 +msgid "Failed to purge image cache" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:45 +msgid "Failed to purge metadata cache" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:119 +#: apps/web/src/components/settings/integration-card.tsx:140 +msgid "Failed to regenerate {label} URL" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:78 +msgid "Failed to remove from library" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:134 +#: apps/web/src/components/settings/account-section.tsx:134 +msgid "Failed to remove profile picture" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:56 +msgid "Failed to sign in" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:388 +msgid "Failed to start {0} connection" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:348 +msgid "Failed to trigger job" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:129 +#: apps/native/src/lib/title-actions.ts:114 +#: apps/web/src/components/titles/use-title-actions.ts:181 +msgid "Failed to unmark episode" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:343 +msgid "Failed to unmark some episodes" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:112 +msgid "Failed to update name" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:101 +#: apps/native/src/lib/title-actions.ts:94 +#: apps/web/src/components/titles/use-title-actions.ts:136 +msgid "Failed to update rating" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:172 +#: apps/web/src/components/settings/registration-section.tsx:44 +msgid "Failed to update registration setting" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:154 +msgid "Failed to update retention setting" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:156 +msgid "Failed to update schedule" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:152 +msgid "Failed to update scheduled backup setting" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:191 +#: apps/web/src/components/settings/update-check-section.tsx:43 +msgid "Failed to update setting" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:72 +#: apps/native/src/lib/title-actions.ts:40 +#: apps/native/src/lib/title-actions.ts:54 +#: apps/web/src/components/titles/use-title-actions.ts:114 +msgid "Failed to update status" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:123 +msgid "Failed to upload avatar" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:725 +msgid "Fetching your library data from {source}..." +msgstr "" + +#: apps/native/src/app/person/[id].tsx:340 +#: apps/web/src/components/people/filmography-grid.tsx:69 +msgid "Filmography" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:915 +msgid "Finished importing from {source}." +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:140 +msgid "Free" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:92 +msgid "Free up disk space by clearing cached metadata and images" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:309 +msgid "Frequency" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:101 +msgid "Friday" +msgstr "" + +#: apps/web/src/components/landing-page.tsx:156 +msgid "Get Started" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:220 +#: apps/native/src/app/person/[id].tsx:247 +#: apps/native/src/app/title/[id].tsx:255 +msgid "Go back" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:37 +msgid "Go Home" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:123 +msgid "Go to <0>Dashboard > Plugins > Webhook" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:368 +msgid "Go to Dashboard" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:71 +msgid "Go to Dashboard > Plugins > Webhook" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:379 +msgid "Go to Explore" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:212 +msgid "Health status" +msgstr "" + +#: apps/web/src/components/dashboard/welcome-header.tsx:14 +msgid "Here's what's happening with your library" +msgstr "" + +#: apps/web/src/components/nav-bar.tsx:125 +#: apps/web/src/components/nav-bar.tsx:290 +msgid "Home" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:335 +#: apps/web/src/components/settings/system-health-section.tsx:610 +msgid "Image cache" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:479 +msgid "Image Cache" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:598 +msgid "Image cache and backup disk usage" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:179 +msgid "Import" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:840 +msgid "Import {totalItems} items" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:912 +msgid "Import complete" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:340 +msgid "Import failed" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:584 +msgid "Import from {0}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:770 +msgid "Import from {source}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:298 +#: apps/web/src/components/settings/imports-section.tsx:329 +msgid "Import is still running in the background. Check back later." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:798 +msgid "Import options" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:921 +msgid "Imported" +msgstr "" + +#. placeholder {0}: event.job.importedCount +#. placeholder {1}: config.label +#: apps/web/src/components/settings/imports-section.tsx:292 +msgid "Imported {0} items from {1}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:863 +msgid "Importing from {source}" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:62 +msgid "In library" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:230 +msgid "In Library" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:133 +#: apps/web/src/components/dashboard/library-section.tsx:35 +msgid "In Your Library" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:105 +msgid "Install the <0>Webhook plugin<1/> from Jellyfin's plugin catalog" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:70 +msgid "Install the Webhook plugin from Jellyfin's plugin catalog" +msgstr "" + +#: apps/native/src/components/settings/integrations-section.tsx:20 +#: apps/web/src/components/settings/integrations-section.tsx:41 +msgid "Integrations" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:266 +msgid "Invalid token" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:399 +msgid "Job" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:243 +#~ msgid "Keeping" +#~ msgstr "Keeping" + +#. placeholder {0}: (value: string | null) => value === "0" ? t`unlimited` : value ? t`last ${value}` : null +#. placeholder {1}: [3, 5, 7, 14, 30, 0].map((n) => ( {n === 0 ? t`unlimited` : t`last ${n}`} )) +#: apps/web/src/components/settings/backup-schedule-section.tsx:243 +msgid "Keeping <0><1><2>{0}<3>{1} backups." +msgstr "" + +#: apps/web/src/components/command-palette.tsx:389 +#: apps/web/src/components/command-palette.tsx:404 +msgid "Keyboard Shortcuts" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:403 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:411 +#: apps/web/src/components/settings/language-section.tsx:42 +msgid "Language" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:270 +msgid "last {n}" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:258 +msgid "last {value}" +msgstr "" + +#. placeholder {0}: formatRelativeTime(lastEventAt) +#: apps/web/src/components/settings/integration-card.tsx:331 +msgid "Last event {0}" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:29 +msgid "Last event {timeAgo}" +msgstr "" + +#. placeholder {0}: formatRelativeTime(lastEventAt) +#: apps/web/src/components/settings/integration-card.tsx:338 +msgid "Last polled {0}" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:37 +msgid "Last polled {timeAgo}" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:405 +msgid "Last run" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:441 +msgid "Last run failed" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:437 +msgid "Last run succeeded" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:331 +msgid "Library refresh" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:245 +msgid "Loading…" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:334 +msgid "Lost connection to import. Check status in settings." +msgstr "" + +#: apps/web/src/components/settings/settings-shell.tsx:40 +msgid "Manage your account and preferences" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:182 +msgid "Manual backup" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:122 +msgid "Mark all episodes as watched?" +msgstr "" + +#: apps/native/src/components/titles/season-accordion.tsx:164 +#: apps/web/src/components/titles/title-seasons.tsx:115 +#: apps/web/src/components/titles/title-seasons.tsx:141 +msgid "Mark All Watched" +msgstr "" + +#: apps/native/src/components/dashboard/continue-watching-card.tsx:122 +msgid "Mark as Completed" +msgstr "" + +#: apps/native/src/components/ui/poster-card.tsx:252 +msgid "Mark as Watched" +msgstr "" + +#: apps/native/src/components/ui/poster-card.tsx:245 +msgid "Mark as Watching" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:449 +#: apps/web/src/components/titles/title-actions.tsx:29 +msgid "Mark Watched" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:49 +msgid "Marked \"{titleName}\" as completed" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:63 +#: apps/web/src/components/titles/use-title-actions.ts:147 +msgid "Marked \"{titleName}\" as watched" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:360 +msgid "Marked all episodes as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:62 +#: apps/native/src/lib/title-actions.ts:50 +msgid "Marked as completed" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:80 +#: apps/native/src/lib/title-actions.ts:64 +msgid "Marked as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:61 +#: apps/native/src/lib/title-actions.ts:37 +msgid "Marked as watching" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:340 +msgid "Member since {memberSince}" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:97 +msgid "Monday" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:585 +msgid "More Like This" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:541 +msgid "More Settings" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:362 +#: apps/native/src/components/search/search-result-row.tsx:35 +#: apps/native/src/components/search/search-result-row.tsx:96 +#: apps/web/src/components/explore/hero-banner.tsx:59 +#: apps/web/src/components/titles/title-hero.tsx:143 +msgid "Movie" +msgstr "" + +#: apps/web/src/components/people/filmography-grid.tsx:59 +#: apps/web/src/components/settings/imports-section.tsx:779 +msgid "Movies" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:161 +#~ msgid "Movies {periodSelect}" +#~ msgstr "Movies {periodSelect}" + +#: apps/web/src/components/dashboard/stats-display.tsx:162 +msgid "Movies {select}" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:60 +msgid "Movies this month" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:124 +#: apps/web/src/components/auth-form.tsx:181 +msgid "Name" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:107 +#: apps/web/src/components/settings/account-section.tsx:80 +msgid "Name updated" +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:301 +msgid "Need more help?" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:501 +msgid "Never" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:435 +msgid "Never run" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:98 +msgid "New account creation is currently disabled." +msgstr "" + +#: apps/web/src/routes/_auth/register.tsx:35 +msgid "New accounts are not being accepted right now. Contact the admin if you need access." +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:137 +msgid "New backup" +msgstr "" + +#: apps/native/src/app/change-password.tsx:145 +#: apps/web/src/components/settings/account-section.tsx:463 +msgid "New password" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:395 +msgid "New password must be at least 8 characters" +msgstr "" + +#: apps/web/src/components/people/filmography-grid.tsx:86 +#: apps/web/src/components/people/filmography-grid.tsx:99 +msgid "Newest" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:131 +msgid "Next backup {distance}" +msgstr "" + +#: apps/native/src/components/titles/continue-watching-banner.tsx:46 +msgid "Next Episode" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:408 +msgid "Next run" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:110 +#: apps/web/src/components/settings/system-health-section.tsx:662 +msgid "No backups yet" +msgstr "" + +#: apps/native/src/components/ui/offline-banner.ios.tsx:74 +#: apps/native/src/components/ui/offline-banner.ios.tsx:81 +#: apps/native/src/components/ui/offline-banner.tsx:59 +msgid "No internet connection" +msgstr "" + +#: apps/native/src/app/(tabs)/(search)/index.tsx:113 +msgid "No results for \"{debouncedQuery}\"" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:217 +msgid "No results found." +msgstr "" + +#: apps/native/src/components/explore/filterable-title-row.tsx:139 +#: apps/web/src/components/explore/filterable-title-row.tsx:151 +msgid "No titles found for this genre." +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:194 +#: apps/web/src/components/settings/integration-card.tsx:176 +#: apps/web/src/components/settings/system-health-section.tsx:249 +msgid "Not configured" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:17 +msgid "Not Found" +msgstr "" + +#: apps/web/src/components/title-card.tsx:67 +msgid "On Watchlist" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:693 +msgid "Open {source} to enter code" +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:308 +msgid "Open docs" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:190 +msgid "Open Emby, go to <0>Settings > Webhooks" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:91 +msgid "Open Emby, go to Settings > Webhooks" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:548 +msgid "Open in browser…" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:62 +msgid "Open Plex, go to <0>Settings > Webhooks<1/>" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:54 +msgid "Open Plex, go to Settings > Webhooks" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:269 +msgid "Open Radarr, go to <0>Settings > Import Lists" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:126 +msgid "Open Radarr, go to Settings > Import Lists" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:502 +#: apps/web/src/components/settings/registration-section.tsx:58 +msgid "Open registration" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:229 +msgid "Open Sonarr, go to <0>Settings > Import Lists" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:108 +msgid "Open Sonarr, go to Settings > Import Lists" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:158 +msgid "or" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:118 +msgid "OR" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:27 +msgid "Page Not Found" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:509 +msgid "Parsing..." +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:167 +#: apps/native/src/app/(auth)/register.tsx:187 +#: apps/web/src/components/auth-form.tsx:221 +msgid "Password" +msgstr "" + +#: apps/native/src/app/change-password.tsx:76 +#: apps/web/src/components/settings/account-section.tsx:414 +msgid "Password updated" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:399 +msgid "Passwords do not match" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:128 +#: apps/web/src/components/settings/integration-configs.tsx:284 +msgid "Paste the Radarr URL above into the List URL field" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:110 +#: apps/web/src/components/settings/integration-configs.tsx:244 +msgid "Paste the Sonarr URL above into the List URL field" +msgstr "" + +#: apps/web/src/components/settings/update-check-section.tsx:63 +msgid "Periodically check GitHub for new Sofa releases" +msgstr "" + +#: apps/native/src/components/search/search-result-row.tsx:38 +#: apps/native/src/components/search/search-result-row.tsx:99 +msgid "Person" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:243 +#: apps/web/src/routes/_app/people.$id.tsx:52 +msgid "Person not found" +msgstr "" + +#: apps/web/src/components/titles/trailer-dialog.tsx:24 +msgid "Play trailer" +msgstr "" + +#: apps/native/src/app/(tabs)/(explore)/index.tsx:107 +#: apps/web/src/components/explore/explore-client.tsx:126 +msgid "Popular Movies" +msgstr "" + +#: apps/native/src/app/(tabs)/(explore)/index.tsx:120 +#: apps/web/src/components/explore/explore-client.tsx:136 +msgid "Popular TV Shows" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:181 +msgid "Pre-restore backup" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:77 +msgid "Producer" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:130 +#: apps/web/src/components/settings/account-section.tsx:130 +msgid "Profile picture removed" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:119 +#: apps/web/src/components/settings/account-section.tsx:108 +msgid "Profile picture updated" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:196 +#: apps/web/src/components/settings/danger-section.tsx:220 +msgid "Purge all" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:202 +msgid "Purge all caches?" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:160 +msgid "Purge image cache?" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:154 +#: apps/web/src/components/settings/danger-section.tsx:177 +msgid "Purge images" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:111 +#: apps/web/src/components/settings/danger-section.tsx:135 +msgid "Purge metadata" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:117 +msgid "Purge metadata cache?" +msgstr "" + +#. placeholder {0}: data.deletedTitles +#. placeholder {1}: data.deletedPersons +#: apps/web/src/components/settings/danger-section.tsx:41 +msgid "Purged {0, plural, one {# stale title} other {# stale titles}} and {1, plural, one {# orphaned person} other {# orphaned persons}}" +msgstr "" + +#. placeholder {0}: metaResult.deletedTitles +#. placeholder {1}: metaResult.deletedPersons +#. placeholder {2}: imageResult.deletedFiles +#: apps/web/src/components/settings/danger-section.tsx:71 +msgid "Purged {0, plural, one {# title} other {# titles}}, {1, plural, one {# person} other {# persons}}, {2, plural, one {# file} other {# files}} ({freed} freed)" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:109 +#: apps/web/src/components/settings/danger-section.tsx:152 +#: apps/web/src/components/settings/danger-section.tsx:194 +msgid "Purging..." +msgstr "" + +#: apps/web/src/components/command-palette.tsx:359 +msgid "Quick Actions" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:123 +msgid "Radarr List URL" +msgstr "" + +#. placeholder {0}: input.stars +#. placeholder {1}: input.stars +#: apps/native/src/hooks/use-title-actions.ts:93 +msgid "Rated {0} {1, plural, one {star} other {stars}}" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:92 +#~ msgid "Rated {0} star{1}" +#~ msgstr "Rated {0} star{1}" + +#: apps/web/src/components/titles/use-title-actions.ts:131 +msgid "Rated {ratingStars} {ratingStars, plural, one {star} other {stars}}" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:88 +msgid "Rated {stars, plural, one {# star} other {# stars}}" +msgstr "" + +#: apps/web/src/components/people/filmography-grid.tsx:88 +#: apps/web/src/components/people/filmography-grid.tsx:102 +msgid "Rating" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:94 +#: apps/native/src/lib/title-actions.ts:90 +#: apps/web/src/components/titles/use-title-actions.ts:132 +msgid "Rating removed" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:782 +#: apps/web/src/components/settings/imports-section.tsx:813 +msgid "Ratings" +msgstr "" + +#: apps/web/src/components/expandable-text.tsx:51 +msgid "Read more" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:39 +#: apps/web/src/components/settings/integration-card.tsx:339 +msgid "Ready — not polled yet" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:31 +#: apps/web/src/components/settings/integration-card.tsx:332 +msgid "Ready — nothing received yet" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:315 +msgid "Recent Searches" +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:106 +#: apps/native/src/components/search/recently-viewed-list.tsx:86 +msgid "Recently Viewed" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:333 +msgid "Recommendations" +msgstr "" + +#: apps/web/src/components/titles/title-recommendations.tsx:40 +msgid "Recommended" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:154 +#: apps/web/src/components/dashboard/recommendations-section.tsx:22 +msgid "Recommended for You" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:143 +msgid "Redirecting…" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:182 +msgid "Refresh" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:172 +msgid "Refresh system health" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:140 +#: apps/native/src/components/settings/integration-card.tsx:289 +msgid "Regenerate" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:135 +#: apps/web/src/components/settings/integration-card.tsx:264 +msgid "Regenerate URL" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:293 +#: apps/web/src/components/landing-page.tsx:176 +msgid "Register" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:116 +msgid "Registering on {serverHost}" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:167 +#: apps/web/src/components/settings/registration-section.tsx:41 +msgid "Registration closed" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:97 +#: apps/web/src/routes/_auth/register.tsx:32 +msgid "Registration Closed" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:167 +#: apps/web/src/components/settings/registration-section.tsx:41 +msgid "Registration opened" +msgstr "" + +#: apps/web/src/components/titles/status-button.tsx:84 +msgid "Remove" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:101 +#: apps/web/src/components/titles/status-button.tsx:66 +msgid "Remove from library" +msgstr "" + +#: apps/native/src/components/dashboard/continue-watching-card.tsx:129 +#: apps/native/src/components/ui/poster-card.tsx:259 +msgid "Remove from Library" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:277 +msgid "Remove Photo" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:239 +msgid "Remove picture" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:199 +msgid "Remove profile picture" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:66 +#: apps/native/src/lib/title-actions.ts:75 +#: apps/web/src/components/titles/use-title-actions.ts:111 +msgid "Removed from library" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:138 +msgid "Rent" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:165 +msgid "Requires <0>Emby Server 4.7.9+ and an active <1>Emby Premiere<2/> license." +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:41 +msgid "Requires an active <0>Plex Pass<1/> subscription." +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:52 +msgid "Requires an active Plex Pass subscription." +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:88 +msgid "Requires Emby Server 4.7.9+ and an active Emby Premiere license." +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:59 +#: apps/web/src/components/settings/backup-restore-section.tsx:116 +msgid "Restore" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:89 +msgid "Restore database?" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:35 +msgid "Restore failed" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:132 +msgid "Restoring…" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:222 +msgid "Results" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:731 +msgid "Retrieving your watch history, watchlist, and ratings..." +msgstr "" + +#: apps/native/src/components/settings/integrations-section.tsx:42 +#: apps/native/src/components/ui/server-unreachable-banner.ios.tsx:73 +#: apps/native/src/components/ui/server-unreachable-banner.tsx:81 +#: apps/web/src/lib/orpc/client.ts:16 +msgid "Retry" +msgstr "" + +#: apps/web/src/routes/__root.tsx:119 +msgid "Return home" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:773 +msgid "Review what was found and choose what to import." +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:558 +msgid "Run now" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:102 +msgid "Saturday" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:322 +msgid "Save" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:296 +msgid "Save name" +msgstr "" + +#: apps/web/src/routes/__root.tsx:101 +msgid "Scene not found" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:402 +msgid "Schedule" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:201 +msgid "Schedule updated" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:179 +msgid "Scheduled backup" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:178 +msgid "Scheduled backups disabled" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:177 +msgid "Scheduled backups enabled" +msgstr "" + +#: apps/web/src/components/dashboard/stats-section.tsx:35 +msgid "Search for movies and TV shows to start tracking" +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:81 +#: apps/native/src/components/search/recently-viewed-list.tsx:64 +msgid "Search for movies, shows, or people" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:186 +msgid "Search for movies, TV shows, or run commands" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:195 +msgid "Search movies & TV shows…" +msgstr "" + +#: apps/native/src/app/(tabs)/(search)/index.tsx:90 +msgid "Search movies, shows, people..." +msgstr "" + +#: apps/web/src/components/nav-bar.tsx:193 +#: apps/web/src/components/nav-bar.tsx:202 +msgid "Search…" +msgstr "" + +#. placeholder {0}: season.seasonNumber +#: apps/native/src/components/titles/season-accordion.tsx:122 +#: apps/native/src/components/titles/season-accordion.tsx:128 +#: apps/web/src/components/titles/use-title-actions.ts:295 +#: apps/web/src/components/titles/use-title-actions.ts:335 +msgid "Season {0}" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:137 +#: apps/native/src/lib/title-actions.ts:124 +msgid "Season watched" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:536 +msgid "Seasons" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:497 +#: apps/web/src/routes/_app/settings.tsx:140 +msgid "Security" +msgstr "" + +#: apps/web/src/components/landing-page.tsx:105 +msgid "Self-hosted movie & TV tracker" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:121 +msgid "Server" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:452 +msgid "Server Health" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:380 +msgid "Server URL" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:361 +msgid "Set password" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:111 +#: apps/native/src/components/settings/integration-configs.ts:129 +#: apps/web/src/components/settings/integration-configs.tsx:247 +#: apps/web/src/components/settings/integration-configs.tsx:287 +msgid "Set your preferred quality profile and root folder" +msgstr "" + +#: apps/native/src/components/header-avatar.tsx:63 +#: apps/web/src/components/nav-bar.tsx:250 +#: apps/web/src/components/nav-bar.tsx:292 +#: apps/web/src/components/settings/settings-shell.tsx:36 +msgid "Settings" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:319 +#: apps/web/src/components/settings/integration-card.tsx:288 +msgid "Setup instructions" +msgstr "" + +#: apps/web/src/routes/setup.tsx:98 +msgid "Setup required" +msgstr "" + +#: apps/native/src/components/ui/expandable-text.tsx:62 +#: apps/native/src/components/ui/expandable-text.tsx:69 +#: apps/web/src/components/expandable-text.tsx:51 +msgid "Show less" +msgstr "" + +#: apps/native/src/components/ui/expandable-text.tsx:62 +#: apps/native/src/components/ui/expandable-text.tsx:69 +msgid "Show more" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:249 +#: apps/web/src/components/auth-form.tsx:283 +msgid "Sign in" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:205 +#: apps/web/src/components/landing-page.tsx:167 +msgid "Sign In" +msgstr "" + +#: apps/web/src/routes/_auth/register.tsx:45 +msgid "Sign in instead" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:91 +#: apps/web/src/components/auth-form.tsx:119 +msgid "Sign in to continue" +msgstr "" + +#. placeholder {0}: authConfig.data?.oidcProviderName ?? "SSO" +#. placeholder {0}: authConfig?.oidcProviderName || "SSO" +#: apps/native/src/app/(auth)/login.tsx:108 +#: apps/web/src/components/auth-form.tsx:145 +msgid "Sign in with {0}" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:368 +#: apps/native/src/components/header-avatar.tsx:87 +#: apps/web/src/components/nav-bar.tsx:262 +#: apps/web/src/components/settings/account-section.tsx:354 +msgid "Sign out" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:206 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:209 +#: apps/native/src/components/header-avatar.tsx:70 +#: apps/native/src/components/header-avatar.tsx:73 +msgid "Sign Out" +msgstr "" + +#: apps/native/src/app/change-password.tsx:199 +#: apps/web/src/components/settings/account-section.tsx:499 +msgid "Sign out of other sessions" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:922 +msgid "Skipped" +msgstr "" + +#. placeholder {0}: data.latestVersion +#: apps/web/src/components/update-toast.tsx:19 +msgid "Sofa v{0} is available" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:57 +#: apps/native/src/components/settings/integration-configs.ts:75 +#: apps/native/src/components/settings/integration-configs.ts:95 +#: apps/web/src/components/settings/integration-configs.tsx:86 +#: apps/web/src/components/settings/integration-configs.tsx:147 +#: apps/web/src/components/settings/integration-configs.tsx:208 +msgid "Sofa will automatically log movies and episodes when you finish watching them" +msgstr "" + +#: apps/native/src/app/change-password.tsx:81 +#: apps/native/src/app/person/[id].tsx:213 +#: apps/web/src/components/settings/account-section.tsx:417 +#: apps/web/src/routes/__root.tsx:143 +msgid "Something went wrong" +msgstr "" + +#: apps/web/src/routes/_app/titles.$id.tsx:118 +msgid "Something went wrong while loading this title. Please try again." +msgstr "" + +#: apps/native/src/lib/query-client.ts:33 +#: apps/web/src/lib/orpc/client.ts:14 +msgid "Something went wrong…" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:105 +msgid "Sonarr List URL" +msgstr "" + +#: apps/web/src/components/dashboard/stats-section.tsx:42 +msgid "Start exploring" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:142 +msgid "Start tracking movies and shows" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:117 +msgid "Start tracking your watches" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:388 +msgid "Starting at" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:888 +msgid "Starting import..." +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:65 +msgid "Status updated" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:595 +msgid "Storage" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:137 +msgid "Stream" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:96 +msgid "Sunday" +msgstr "" + +#. placeholder {0}: info.name +#: apps/web/src/components/settings/language-section.tsx:69 +msgid "Switch to {0}" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:30 +msgid "The page you're looking for doesn't exist." +msgstr "" + +#: apps/web/src/routes/_app/people.$id.tsx:55 +msgid "The person you're looking for doesn't exist or may have been removed from the database." +msgstr "" + +#: apps/web/src/routes/_app/titles.$id.tsx:149 +msgid "The title you're looking for doesn't exist or may have been removed from the database." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:866 +msgid "This may take a few minutes for large libraries. Please don't close this tab." +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:146 +msgid "This Month" +msgstr "" + +#: apps/web/src/routes/__root.tsx:104 +msgid "This page was left on the cutting room floor. It may have been moved, removed, or never made it past the screenplay." +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:606 +#: apps/web/src/routes/_app/settings.tsx:75 +#: apps/web/src/routes/setup.tsx:187 +msgid "This product uses the TMDB API but is not endorsed or certified by TMDB." +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:145 +msgid "This Week" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:163 +msgid "This will delete all cached TMDB images from disk. Images will be re-downloaded automatically as needed." +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:205 +msgid "This will delete all un-enriched stub titles and all cached images from disk. Everything will be re-imported and re-downloaded as needed." +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:120 +msgid "This will delete un-enriched stub titles that aren't in any user's library and clean up orphaned person records. Deleted titles will be re-imported if accessed again." +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:136 +msgid "This will invalidate the current {label} URL. You'll need to update it in {label}." +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:125 +msgid "This will mark every episode of this show as watched. You can undo this later by unmarking individual seasons." +msgstr "" + +#. placeholder {0}: formatBackupDate(backup.createdAt) +#: apps/web/src/components/settings/backup-section.tsx:259 +msgid "This will permanently delete the backup from <0>{0}. This cannot be undone." +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:61 +#: apps/native/src/components/search/recently-viewed-list.tsx:35 +msgid "This will remove all items from your history." +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:92 +msgid "This will replace your entire database with the uploaded file. A safety backup of your current data will be created first. Active sessions may need to refresh after restore." +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:147 +msgid "This Year" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:100 +msgid "Thursday" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:390 +msgid "Time:" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:251 +#: apps/web/src/routes/_app/titles.$id.tsx:146 +msgid "Title not found" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:131 +#: apps/web/src/components/settings/integration-configs.tsx:290 +msgid "Titles on your Sofa watchlist will be automatically added for download when Radarr polls this list (every 12 hours by default)" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:113 +#: apps/web/src/components/settings/integration-configs.tsx:250 +msgid "Titles on your Sofa watchlist will be automatically added for download when Sonarr polls this list (every 6 hours by default)" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:144 +msgid "Today" +msgstr "" + +#: apps/web/src/components/settings/update-check-section.tsx:71 +msgid "Toggle automatic update checks" +msgstr "" + +#: apps/web/src/components/settings/registration-section.tsx:69 +msgid "Toggle open registration" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:290 +msgid "Toggle scheduled backups" +msgstr "" + +#: apps/web/src/components/landing-page.tsx:131 +msgid "Track what you watch. Know what's next.<0/>Your library, your data, your rules." +msgstr "" + +#: apps/web/src/components/titles/trailer-dialog.tsx:37 +#: apps/web/src/components/titles/trailer-dialog.tsx:41 +msgid "Trailer" +msgstr "" + +#: apps/web/src/components/explore/hero-banner.tsx:78 +msgid "Trending today" +msgstr "" + +#: apps/native/src/app/(tabs)/(explore)/index.tsx:95 +#: apps/web/src/components/explore/explore-client.tsx:112 +msgid "Trending Today" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:537 +msgid "Trigger job" +msgstr "" + +#: apps/web/src/routes/__root.tsx:159 +msgid "Try again" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:98 +msgid "Tuesday" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:362 +#: apps/native/src/components/search/search-result-row.tsx:98 +#: apps/web/src/components/explore/hero-banner.tsx:64 +#: apps/web/src/components/people/filmography-grid.tsx:60 +#: apps/web/src/components/titles/title-hero.tsx:153 +msgid "TV" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:334 +msgid "TV episodes" +msgstr "" + +#: apps/native/src/components/search/search-result-row.tsx:37 +msgid "TV show" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:656 +msgid "unknown" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:256 +#: apps/web/src/components/settings/backup-schedule-section.tsx:270 +msgid "unlimited" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:273 +msgid "Unreachable" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:340 +msgid "Untitled" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:221 +msgid "Unwatch all" +msgstr "" + +#. placeholder {0}: season.name ?? t`Season ${season.seasonNumber}` +#: apps/web/src/components/titles/use-title-actions.ts:335 +msgid "Unwatched all of {0}" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:174 +msgid "Unwatched S{seasonNum} E{epNum}" +msgstr "" + +#: apps/native/src/components/titles/continue-watching-banner.tsx:79 +#: apps/web/src/components/dashboard/continue-watching-card.tsx:77 +msgid "Up next" +msgstr "" + +#. placeholder {0}: updateCheck.data.updateCheck.latestVersion +#: apps/native/src/app/(tabs)/(settings)/index.tsx:528 +msgid "Update available: {0}" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:337 +msgid "Update check" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:185 +#: apps/web/src/components/settings/update-check-section.tsx:40 +msgid "Update checks disabled" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:185 +#: apps/web/src/components/settings/update-check-section.tsx:40 +msgid "Update checks enabled" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:84 +msgid "Update failed" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:509 +msgid "Update password" +msgstr "" + +#: apps/native/src/app/change-password.tsx:218 +msgid "Update Password" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:134 +#: apps/web/src/components/settings/imports-section.tsx:509 +msgid "Upload" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:62 +msgid "Upload a .db file to replace the current database. A safety backup is created first." +msgstr "" + +#. placeholder {0}: config.accept +#. placeholder {1}: config.label +#: apps/web/src/components/settings/imports-section.tsx:637 +msgid "Upload a {0} export from your {1} account settings." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:634 +msgid "Upload export file" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:112 +msgid "Upload failed" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:241 +msgid "Upload picture" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:200 +msgid "Upload profile picture" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:129 +msgid "URL copied to clipboard" +msgstr "" + +#: apps/web/src/components/update-toast.tsx:24 +msgid "View release" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:699 +msgid "Waiting for authorization..." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:823 +msgid "Warnings" +msgstr "" + +#. placeholder {0}: result.warnings.length +#: apps/web/src/components/settings/imports-section.tsx:948 +msgid "Warnings ({0})" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:202 +msgid "Watch all" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:801 +msgid "Watch history" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:59 +msgid "Watch on {name}" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:298 +#: apps/web/src/components/titles/use-title-actions.ts:307 +msgid "Watched all of {seasonLabel}" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:123 +msgid "Watched all of {seasonName}" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:220 +#: apps/web/src/components/titles/use-title-actions.ts:229 +msgid "Watched S{seasonNum} E{epNum}" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:42 +#: apps/web/src/components/title-card.tsx:72 +#: apps/web/src/components/titles/status-button.tsx:19 +msgid "Watching" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:82 +#: apps/web/src/components/settings/imports-section.tsx:781 +#: apps/web/src/components/settings/imports-section.tsx:807 +#: apps/web/src/components/titles/status-button.tsx:55 +msgid "Watchlist" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:40 +msgid "Watchlisted" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:50 +#: apps/native/src/components/settings/integration-configs.ts:67 +#: apps/native/src/components/settings/integration-configs.ts:85 +msgid "Webhook URL" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:99 +msgid "Wednesday" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:112 +#: apps/web/src/components/dashboard/welcome-header.tsx:10 +msgid "Welcome back" +msgstr "" + +#: apps/web/src/components/dashboard/welcome-header.tsx:8 +msgid "Welcome back, {name}" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:483 +#: apps/web/src/components/titles/title-availability.tsx:154 +msgid "Where to Watch" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:141 +msgid "With Ads" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:75 +msgid "Writer" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:386 +msgid "You'll be signed out to change the server URL." +msgstr "" + +#. placeholder {0}: data.currentVersion +#: apps/web/src/components/update-toast.tsx:20 +msgid "You're running v{0}." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:678 +msgid "Your code:" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:141 +#: apps/web/src/components/dashboard/stats-section.tsx:32 +msgid "Your library is empty" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:134 +msgid "Your name" +msgstr "" + diff --git a/packages/i18n/src/po/fr.ts b/packages/i18n/src/po/fr.ts new file mode 100644 index 0000000..ef7e47e --- /dev/null +++ b/packages/i18n/src/po/fr.ts @@ -0,0 +1 @@ +/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"+5kO8P\":[\"Saturday\"],\"+6i0lS\":[\"Retrieving your watch history, watchlist, and ratings...\"],\"+Cv+V9\":[\"Remove from library\"],\"+JkEpu\":[\"This page was left on the cutting room floor. It may have been moved, removed, or never made it past the screenplay.\"],\"+K0AvT\":[\"Disconnect\"],\"+N0l5/\":[\"Connected to <0>\",[\"serverHost\"],\". Tap to change.\"],\"+gLHYi\":[\"Failed to load title\"],\"+j1ex/\":[\"Failed to remove from library\"],\"+nF1ZO\":[\"Remove Photo\"],\"/+6dvC\":[\"Errors (\",[\"0\"],\")\"],\"/BBXeA\":[\"Connecting to server...\"],\"/DwR+n\":[\"Creating…\"],\"/XtbPO\":[[\"0\"],\" titles\"],\"/cg+QV\":[\"Watch all\"],\"/gQXGv\":[\"Failed to update status\"],\"/nT6AE\":[\"New password\"],\"/sHc1/\":[\"Update check\"],\"0+dyau\":[\"Failed to update retention setting\"],\"0BFJKK\":[\"Authorization was denied. Please try again.\"],\"0GHb20\":[\"Purge metadata cache?\"],\"0IBW21\":[\"Failed to purge caches\"],\"0gH/sc\":[\"Remove profile picture\"],\"1+P9RR\":[\"Switch to \",[\"0\"]],\"12cc1j\":[\"Watching\"],\"12lVOl\":[\"Self-hosted movie & TV tracker\"],\"1B4z0M\":[\"Filmography\"],\"1J4Ek0\":[\"Automatically back up your database on a schedule\"],\"1JhxXW\":[\"Episode \",[\"0\"],\", \",[\"episodeLabel\"]],\"1N6wNP\":[\"Import options\"],\"1Qz4uG\":[\"Enable the \\\"Playback\\\" event category\"],\"1hMWR6\":[\"Create account\"],\"1jHIjh\":[\"Watched all of \",[\"seasonName\"]],\"1vSYsG\":[\"Download backup\"],\"1wL1tj\":[\"TV show\"],\"2FletP\":[\"Purged \",[\"0\",\"plural\",{\"one\":[\"#\",\" title\"],\"other\":[\"#\",\" titles\"]}],\", \",[\"1\",\"plural\",{\"one\":[\"#\",\" person\"],\"other\":[\"#\",\" persons\"]}],\", \",[\"2\",\"plural\",{\"one\":[\"#\",\" file\"],\"other\":[\"#\",\" files\"]}],\" (\",[\"freed\"],\" freed)\"],\"2Fsd9r\":[\"This Month\"],\"2MPcep\":[\"Import complete\"],\"2Mu33Z\":[\"Cache management\"],\"2POOFK\":[\"Free\"],\"2Pt2NY\":[\"This will remove all items from your history.\"],\"2fCpt5\":[\"Return home\"],\"2pPBp6\":[\"Trending today\"],\"39y5bn\":[\"Friday\"],\"3Blefz\":[\"Ratings\"],\"3JTlG8\":[\"Recent Searches\"],\"3Jy8bM\":[\"Movies \",[\"select\"]],\"3L9OuQ\":[\"Episode \",[\"0\"]],\"3T8ziB\":[\"Create an account\"],\"3hELxX\":[\"Enter your Sofa server URL to get started\"],\"4fxLkp\":[\"Open Sonarr, go to <0>Settings > Import Lists\"],\"4kpBqM\":[\"Last event \",[\"0\"]],\"4uUjVO\":[\"Producer\"],\"4x+A56\":[\"Anonymous usage reporting\"],\"5IShvp\":[\"Import \",[\"totalItems\"],\" items\"],\"5IYJSv\":[\"Explore titles\"],\"5Y4mym\":[\"Import from \",[\"0\"]],\"5ZzgbQ\":[\"Connect \",[\"0\"]],\"5fEnbK\":[\"Failed to update scheduled backup setting\"],\"5lWFkC\":[\"Sign in\"],\"5v9C16\":[\"Connected to \",[\"source\"]],\"623bR4\":[\"Failed to trigger job\"],\"6HN0yh\":[\"Open Plex, go to Settings > Webhooks\"],\"6TNjOJ\":[\"Failed to update rating\"],\"6TfUy6\":[\"last \",[\"value\"]],\"6V3Ea3\":[\"Copied\"],\"6YtxFj\":[\"Name\"],\"6exX+8\":[\"Regenerate\"],\"6gRgw8\":[\"Retry\"],\"6lGV3K\":[\"Show less\"],\"76++pR\":[\"Warnings\"],\"77DIAu\":[\"Go to Dashboard > Plugins > Webhook\"],\"7Bj3x9\":[\"Failed\"],\"7D50KC\":[[\"label\"],\" disconnected\"],\"7GfM5w\":[\"Recently Viewed\"],\"7L01XJ\":[\"Actions\"],\"7eMo+U\":[\"Go Home\"],\"7p5kLi\":[\"Dashboard\"],\"85eoJ1\":[\"Schedule updated\"],\"8B9E2D\":[\"Day:\"],\"8YwF1J\":[\"Go to <0>Dashboard > Plugins > Webhook\"],\"8ZsakT\":[\"Password\"],\"8tjQCz\":[\"Explore\"],\"8vNtLy\":[\"Paste the Radarr URL above into the List URL field\"],\"8wYDMp\":[\"Already have an account?\"],\"9AE3vb\":[\"Open Plex, go to <0>Settings > Webhooks<1/>\"],\"9GW0ZB\":[\"Import is still running in the background. Check back later.\"],\"9NyAH9\":[\"Skipped\"],\"9Xhrps\":[\"Failed to connect\"],\"9ZLGbA\":[\"backups.\"],\"9eF5oV\":[\"Welcome back\"],\"9rG25a\":[\"Server URL\"],\"A+0rLe\":[\"Marked as completed\"],\"AOHgZp\":[\"Episodes\"],\"AOddWK\":[\"Connect \",[\"label\"]],\"ATfxL/\":[\"Trailer\"],\"AVlZoM\":[\"Environment\"],\"Acf6vF\":[\"Save name\"],\"AdoUfN\":[\"Failed to add to watchlist\"],\"AeXO77\":[\"Account\"],\"AjtYj0\":[\"On Watchlist\"],\"Avee+B\":[\"Failed to update name\"],\"B2R3xD\":[\"Toggle scheduled backups\"],\"BEVzjL\":[\"Import failed\"],\"BQnS5I\":[\"Open \",[\"source\"],\" to enter code\"],\"BUnoSB\":[[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"backup\"],\"other\":[\"backups\"]}],\" stored\"],\"BpttUI\":[\"Trending Today\"],\"BrrIs8\":[\"Storage\"],\"BskWMl\":[\"Unreachable\"],\"BzEFor\":[\"or\"],\"CGDHFb\":[\"Add a <0>Generic Destination and paste the URL above\"],\"CGExDN\":[\"Failed to purge image cache\"],\"CHeXFE\":[\"Status updated\"],\"CKyk7Q\":[\"Go back\"],\"CaB/+I\":[\"Welcome back, \",[\"name\"]],\"CodnUh\":[\"Removed from library\"],\"CpzGJY\":[\"This may take a few minutes for large libraries. Please don't close this tab.\"],\"CuPxpd\":[\"Requires an active <0>Plex Pass<1/> subscription.\"],\"CzBN6D\":[\"Start exploring\"],\"D+R2Xs\":[\"Starting import...\"],\"D3C4Yx\":[\"Current password is required\"],\"DBC3t5\":[\"Sunday\"],\"DI4lqs\":[\"Person not found\"],\"DKBbJf\":[\"Added \\\"\",[\"titleName\"],\"\\\" to watchlist\"],\"DPfwMq\":[\"Done\"],\"DZse/o\":[\"Add a \\\"Generic Destination\\\" and paste the URL above\"],\"E/QGRL\":[\"Disabled\"],\"E6nRW7\":[\"Copy URL\"],\"EWQlBH\":[\"Your library is empty\"],\"EWaCfj\":[\"Enter your current password and choose a new one.\"],\"Eeo/Gy\":[\"Failed to update setting\"],\"Efn6WU\":[\"This will delete all un-enriched stub titles and all cached images from disk. Everything will be re-imported and re-downloaded as needed.\"],\"Etp5if\":[\"Finished importing from \",[\"source\"],\".\"],\"F006BN\":[\"Import from \",[\"source\"]],\"FNvDMc\":[\"This Week\"],\"FWSp+7\":[\"Enter the code below on \",[\"source\"],\"'s website to authorize Sofa.\"],\"FXN0ro\":[\"Recommendations\"],\"FaU7Ag\":[\"Enable the \\\"Playback Stop\\\" notification type\"],\"FhvLDl\":[[\"watchedCount\"],\"/\",[\"0\"],\" episodes\"],\"Fo2bwm\":[\"Actor\"],\"FwRqjE\":[\"Image cache and backup disk usage\"],\"G00fgM\":[\"last \",[\"n\"]],\"G3myU+\":[\"Tuesday\"],\"GcCthe\":[\"In Library\"],\"Gf39AA\":[[\"0\"],\" triggered\"],\"GnhfWw\":[\"Toggle automatic update checks\"],\"GptGxg\":[\"Change password\"],\"GqTZ+S\":[\"Rated \",[\"ratingStars\"],\" \",[\"ratingStars\",\"plural\",{\"one\":[\"star\"],\"other\":[\"stars\"]}]],\"GrdN/F\":[\"Caught up — marked \",[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}],\" as watched\"],\"H4B5LG\":[\"This product uses the TMDB API but is not endorsed or certified by TMDB.\"],\"HBRd5n\":[\"Season \",[\"0\"]],\"HD+aQ7\":[\"Database backups\"],\"HG+31u\":[\"Purge all caches?\"],\"Haz+72\":[\"This will delete all cached TMDB images from disk. Images will be re-downloaded automatically as needed.\"],\"HbReP5\":[\"Marked \\\"\",[\"titleName\"],\"\\\" as completed\"],\"Hg6o8v\":[\"Refresh system health\"],\"HmEjnC\":[\"Last event \",[\"timeAgo\"]],\"HvDfH/\":[\"Imported\"],\"I89uD4\":[\"Restoring…\"],\"IRoxQm\":[\"Registration closed\"],\"IS0nrP\":[\"Create Account\"],\"IY9rQ0\":[\"Free up disk space by clearing cached metadata and images\"],\"IrC12v\":[\"Application\"],\"J28zul\":[\"Connecting...\"],\"J64cFL\":[\"Library refresh\"],\"JKmmmN\":[\"Added to watchlist\"],\"JN0f/Y\":[\"Connect to TMDB\"],\"JR6aH2\":[\"Movies \",[\"periodSelect\"]],\"JRQitQ\":[\"Confirm new password\"],\"JRadFJ\":[\"Start tracking your watches\"],\"JSwq8t\":[\"New account creation is currently disabled.\"],\"JY5Oyv\":[\"Database\"],\"JjsJnI\":[[\"0\"],\"/\",[\"1\"],\" episodes\"],\"JrFTcr\":[\"Connecting…\"],\"JwFbe8\":[\"TV\"],\"KDw4GX\":[\"Try again\"],\"KG6681\":[\"Episode watched\"],\"KIS/Sd\":[\"Sign out of other sessions\"],\"KK0ghs\":[\"Image cache\"],\"KVAoFR\":[\"unlimited\"],\"KcXJuc\":[\"Purging...\"],\"KhtG3o\":[\"Update Password\"],\"Khux7w\":[[\"0\",\"plural\",{\"one\":[\"#\",\" title\"],\"other\":[\"#\",\" titles\"]}]],\"KmWyx0\":[\"Job\"],\"KoF0x6\":[\"age \",[\"age\"]],\"Ksiej9\":[\"Requires an active Plex Pass subscription.\"],\"Kx9NEt\":[\"Invalid token\"],\"Lk6Jb/\":[\"Last polled \",[\"timeAgo\"]],\"LmEEic\":[\"Waiting for authorization...\"],\"LqKH42\":[\"Connect with \",[\"0\"]],\"Lrpjji\":[\"Disconnect \",[\"label\"]],\"Lu6Udx\":[\"Click \\\"+\\\" and select \\\"Custom Lists\\\"\"],\"MDoX++\":[\"Sonarr List URL\"],\"MRBlCJ\":[\"Failed to unmark some episodes\"],\"MUO7w9\":[\"Marked \\\"\",[\"titleName\"],\"\\\" as watched\"],\"MZbQHL\":[\"No results found.\"],\"MdxR1u\":[\"Choose your preferred display language\"],\"MkyzAV\":[\"Authorize Sofa to read your \",[\"0\"],\" library. No password shared.\"],\"N40H+G\":[\"All\"],\"N6SFhC\":[\"Sign in instead\"],\"N7h106\":[\"Sofa v\",[\"0\"],\" is available\"],\"N9RF2M\":[\"Click \\\"Add Webhook\\\" and paste the URL above\"],\"NBo4z0\":[\"Click <0>+ and select <1>Custom Lists\"],\"NICUmW\":[\"Director\"],\"NQzPoO\":[\"New backup\"],\"NSxl1l\":[\"More Settings\"],\"NVF43p\":[\"Time:\"],\"NdPMwS\":[\"In Your Library\"],\"NgaPSG\":[\"Failed to update schedule\"],\"O3oNi5\":[\"Email\"],\"OBcj5W\":[\"This will invalidate the current \",[\"label\"],\" URL. You'll need to update it in \",[\"label\"],\".\"],\"OL8hbM\":[\"New password must be at least 8 characters\"],\"ONWvwQ\":[\"Upload\"],\"OPFjyX\":[\"Install the <0>Webhook plugin<1/> from Jellyfin's plugin catalog\"],\"OW/+RD\":[\"Watch history\"],\"OZdaTZ\":[\"Person\"],\"OvO76u\":[\"Back to Login\"],\"OvdFIZ\":[\"Season watched\"],\"P2FLLe\":[\"View release\"],\"PBdLfg\":[\"No titles found for this genre.\"],\"PQ3qDa\":[\"Fetching your library data from \",[\"source\"],\"...\"],\"PjNoxI\":[\"Scheduled backup\"],\"Pn2B7/\":[\"Current password\"],\"PnEbL/\":[\"Rated \",[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"star\"],\"other\":[\"stars\"]}]],\"Pwqkdw\":[\"Loading…\"],\"Py87xY\":[\"Authorization succeeded but failed to fetch your library. Please try again.\"],\"Q3MPWA\":[\"Update password\"],\"QHcLEN\":[\"Connected\"],\"QK4UIx\":[\"Mark Watched\"],\"Qjlym2\":[\"Failed to create account\"],\"Qm1NmK\":[\"OR\"],\"Qoq+GP\":[\"Read more\"],\"QphVZW\":[\"Can't reach server\"],\"QqLJHH\":[\"This Year\"],\"R0yu2l\":[\"Catch up\"],\"R4YBui\":[\"Search for movies and TV shows to start tracking\"],\"RH46vw\":[[\"0\"],\"/\",[\"1\"],\" \",[\"2\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}]],\"RIR15/\":[\"Radarr List URL\"],\"RLe7Vk\":[\"Checking…\"],\"ROq8cl\":[\"Failed to parse file\"],\"RQq8Si\":[\"Registration opened\"],\"S0soqb\":[\"Failed to remove profile picture\"],\"S1McZh\":[\"Failed to upload avatar\"],\"S2ble5\":[[\"0\"],\" movies, \",[\"1\"],\" episodes\"],\"S2qPRR\":[\"Search movies & TV shows…\"],\"SDND4q\":[\"Not configured\"],\"SFdAk9\":[\"Unwatched S\",[\"seasonNum\"],\" E\",[\"epNum\"]],\"SUIUkB\":[\"Unwatch all\"],\"SbS+Bm\":[\"Regenerate URL\"],\"SlfejT\":[\"Error\"],\"Sp86ju\":[[\"0\"],\" ep\",[\"1\"]],\"SrfROI\":[\"Up next\"],\"SyPRjk\":[\"Sofa will automatically log movies and episodes when you finish watching them\"],\"T0/7WG\":[\"Next backup \",[\"distance\"]],\"TEaX6q\":[\"Backup deleted\"],\"TI7IKo\":[\"Rated \",[\"0\"],\" star\",[\"1\"]],\"TMb7iE\":[\"Profile picture removed\"],\"TqyQQS\":[\"Enable the <0>Playback event category\"],\"Tz0i8g\":[\"Settings\"],\"U+FxtW\":[\"Track what you watch. Know what's next.<0/>Your library, your data, your rules.\"],\"U3pytU\":[\"Admin\"],\"UC+OWB\":[\"Toggle open registration\"],\"UDMjsP\":[\"Quick Actions\"],\"UmQ6Fe\":[\"Purge image cache?\"],\"UtDm3q\":[\"URL copied to clipboard\"],\"V1Kh9Z\":[\"Episodes \",[\"select\"]],\"V6uzvC\":[\"More Like This\"],\"V9CuQ+\":[\"Connect to \",[\"source\"]],\"VAcXNz\":[\"Wednesday\"],\"VKyhZK\":[\"Title not found\"],\"VQvpro\":[\"Paste the Sonarr URL above into the List URL field\"],\"VhMDMg\":[\"Change Password\"],\"Vx0ayx\":[\"Failed to mark some episodes\"],\"WDC4PF\":[\"Episodes \",[\"periodSelect\"]],\"WEYdDv\":[\"Recommended\"],\"WMCwmR\":[\"Check for updates\"],\"WP48q2\":[\"Image Cache\"],\"WT1Ibn\":[\"Last run\"],\"Wb3E4g\":[\"Run now\"],\"WgF2UQ\":[\"You're running v\",[\"0\"],\".\"],\"WtWhSi\":[\"Rating removed\"],\"Wy/3II\":[\"Last polled \",[\"0\"]],\"XFCSYs\":[\"Cast\"],\"XN23JY\":[\"This will delete un-enriched stub titles that aren't in any user's library and clean up orphaned person records. Deleted titles will be re-imported if accessed again.\"],\"XZwihE\":[\"Ready — nothing received yet\"],\"XjTduw\":[\"Upload picture\"],\"YEGzVq\":[\"Failed to connect \",[\"label\"]],\"YErf89\":[\"Failed to purge metadata cache\"],\"YQ768h\":[\"Scene not found\"],\"YiRsXK\":[\"Clear Recently Viewed?\"],\"Yjp1zf\":[\"Don't have a server?\"],\"YqMfa9\":[\"Review what was found and choose what to import.\"],\"ZGUYm0\":[\"Ready — not polled yet\"],\"ZQKLI1\":[\"Danger Zone\"],\"ZVZUoU\":[[\"0\"],\" cached images\"],\"ZWTQ81\":[\"Watch on \",[\"name\"]],\"a3LDKx\":[\"Security\"],\"aLBUiR\":[\"Keeping <0><1><2>\",[\"0\"],\"<3>\",[\"1\"],\" backups.\"],\"aM0+kY\":[\"Purged \",[\"0\",\"plural\",{\"one\":[\"#\",\" stale title\"],\"other\":[\"#\",\" stale titles\"]}],\" and \",[\"1\",\"plural\",{\"one\":[\"#\",\" orphaned person\"],\"other\":[\"#\",\" orphaned persons\"]}]],\"aO1AxG\":[\"Episode unwatched\"],\"aV/hDI\":[\"Failed to disconnect \",[\"label\"]],\"acbSg0\":[\"Double tap to filter by \",[\"label\"]],\"agE7k4\":[\"Rated \",[\"stars\",\"plural\",{\"one\":[\"#\",\" star\"],\"other\":[\"#\",\" stars\"]}]],\"alPRaV\":[\"Titles on your Sofa watchlist will be automatically added for download when Radarr polls this list (every 12 hours by default)\"],\"aourBv\":[\"Scheduled backups enabled\"],\"apLLSU\":[\"Are you sure you want to sign out?\"],\"atc9MA\":[\"Open Emby, go to <0>Settings > Webhooks\"],\"auVUJO\":[\"Restore database?\"],\"ayqfr4\":[[\"label\"],\" URL regenerated\"],\"b/8KCH\":[\"This will mark every episode of this show as watched. You can undo this later by unmarking individual seasons.\"],\"b3Thhd\":[\"Upload failed\"],\"b5DFtH\":[\"Set password\"],\"bHYIks\":[\"Sign Out\"],\"bITrbE\":[\"Purge all\"],\"bNmdjU\":[\"Watchlist\"],\"bTRwag\":[\"Remove from Library\"],\"bXMotV\":[\"Marked as watched\"],\"bezGJ2\":[[\"0\"],\" images\"],\"boJlGf\":[\"Page Not Found\"],\"c1ssjI\":[\"Importing from \",[\"source\"]],\"c3b0B0\":[\"Get Started\"],\"c4b9Dm\":[\"No results for \\\"\",[\"debouncedQuery\"],\"\\\"\"],\"cQPKZt\":[\"Go to Dashboard\"],\"ccAJSB\":[\"Keeping\"],\"cgvva8\":[[\"0\"],\" ratings\"],\"cnGeoo\":[\"Delete\"],\"cpE88+\":[\"Create your account\"],\"d/6MoL\":[\"Manage your account and preferences\"],\"dA7BWh\":[\"Requires Emby Server 4.7.9+ and an active Emby Premiere license.\"],\"dEgA5A\":[\"Cancel\"],\"dTZwve\":[\"Marked all episodes as watched\"],\"dUCJry\":[\"Newest\"],\"ddwpAr\":[\"Warnings (\",[\"0\"],\")\"],\"e/ToF5\":[\"Sign in with \",[\"0\"]],\"e9sZMS\":[\"This will permanently delete the backup from <0>\",[\"0\"],\". This cannot be undone.\"],\"eFRooE\":[\"Last run failed\"],\"eM39Om\":[\"Watched all of \",[\"seasonLabel\"]],\"eZjYb8\":[\"Writer\"],\"ecUA8p\":[\"Today\"],\"evBxZy\":[\"Where to Watch\"],\"ezDa1h\":[\"Open docs\"],\"f+m696\":[\"An unexpected error occurred while loading this page. You can try again or head back to the dashboard.\"],\"f/AKdU\":[\"Are you sure you want to disconnect \",[\"label\"],\"? The current URL will stop working.\"],\"f7ax8J\":[\"Open in browser…\"],\"fMPkxb\":[\"Show more\"],\"fNMqNn\":[\"Popular TV Shows\"],\"fObVvy\":[\"Profile picture updated\"],\"fRettQ\":[[\"0\"],\" items\"],\"fUDRF9\":[\"Watchlisted\"],\"fcWrnU\":[\"Sign out\"],\"fgLNSM\":[\"Register\"],\"fsAEqk\":[\"Continue Watching\"],\"fzAeQI\":[\"Registering on \",[\"serverHost\"]],\"g+gBfk\":[\"Parsing...\"],\"g4JYff\":[\"Imported \",[\"0\"],\" items from \",[\"1\"]],\"gKtb5i\":[\"Titles on your Sofa watchlist will be automatically added for download when Sonarr polls this list (every 6 hours by default)\"],\"gaIAMq\":[\"Remove picture\"],\"gbEEMp\":[\"Delete backup\"],\"gcNLi0\":[\"Update checks enabled\"],\"gmB6oO\":[\"Schedule\"],\"h/T5Yb\":[\"Open registration\"],\"h4yKYk\":[\"Next run\"],\"h7MgpO\":[\"Keyboard Shortcuts\"],\"hTXYdY\":[\"Failed to create backup\"],\"hXYY5Q\":[\"Unwatched all of \",[\"0\"]],\"he3ygx\":[\"Copy\"],\"hjGupC\":[\"Lost connection to import. Check status in settings.\"],\"hlqjFc\":[\"In library\"],\"hou0tP\":[\"Stream\"],\"hty0d5\":[\"Monday\"],\"i0qMbr\":[\"Home\"],\"i9rcQ/\":[\"Movies\"],\"iGma9e\":[\"Update failed\"],\"iSLIjg\":[\"Connect\"],\"iWv3ck\":[\"Failed to catch up\"],\"iXZ09g\":[\"Mark as Completed\"],\"id08cd\":[\"Watched S\",[\"seasonNum\"],\" E\",[\"epNum\"]],\"ih87w/\":[\"Add to Library\"],\"ihn4zD\":[\"Search…\"],\"itDEco\":[\"Already have an account? Sign in\"],\"itheEn\":[\"With Ads\"],\"iwCRIF\":[\"You'll be signed out to change the server URL.\"],\"j5GBIy\":[\"Open Radarr, go to Settings > Import Lists\"],\"jAe8l4\":[[\"0\"],\" backups · last <0/>\"],\"jB3sfe\":[\"Manual backup\"],\"jFnMJ8\":[\"Double tap to clear this filter\"],\"jQsPwL\":[\"Scheduled backups disabled\"],\"jSjGeu\":[[\"0\"],\" items have no external IDs and will be resolved by title search, which may be less accurate.\"],\"jX6Gzg\":[\"This will replace your entire database with the uploaded file. A safety backup of your current data will be created first. Active sessions may need to refresh after restore.\"],\"jiHVUy\":[\"Pre-restore backup\"],\"k6c41p\":[\"Background jobs\"],\"kBDOjB\":[\"Backup schedule\"],\"kkDQ8m\":[\"Thursday\"],\"klOeIX\":[\"Failed to change password\"],\"ks3XeZ\":[\"Open Sonarr, go to Settings > Import Lists\"],\"kvuCtu\":[\"Something went wrong while loading this title. Please try again.\"],\"kx0s+n\":[\"Results\"],\"l2wcoS\":[\"Last run succeeded\"],\"l3s5ri\":[\"Import\"],\"lCF0wC\":[\"Refresh\"],\"lJ1yo4\":[\"Failed to sign in\"],\"lVqzRx\":[\"Click <0>Add Webhook and paste the URL above\"],\"lXkUEV\":[\"Availability\"],\"lcLe89\":[\"Search for movies, TV shows, or run commands\"],\"lfVyvz\":[\"Add to Watchlist\"],\"llDXYJ\":[\"Backups\"],\"llkZR0\":[\"Could not load integrations\"],\"ln9/n9\":[\"Don't have an account?\"],\"lpIMne\":[\"Passwords do not match\"],\"lqY3WY\":[\"Change Server\"],\"luoodD\":[\"Setup required\"],\"m5WhJy\":[\"Automatic update checks\"],\"mIx58S\":[\"Purge images\"],\"mYBORk\":[\"Movie\"],\"ml9cU0\":[\"Failed to regenerate \",[\"label\"],\" URL\"],\"mqwkjd\":[\"Health status\"],\"mqxHH7\":[\"Go to Explore\"],\"mzI/c+\":[\"Download\"],\"n1ekoW\":[\"Sign In\"],\"n3Pzd7\":[\"Backup created\"],\"nBHvPL\":[\"Cancel editing\"],\"nJw77c\":[[\"0\",\"plural\",{\"one\":[\"#\",\" image\"],\"other\":[\"#\",\" images\"]}]],\"nO0Qnj\":[\"Your code:\"],\"nRP1xx\":[\"Need more help?\"],\"nV1LjT\":[\"Enable the <0>Playback Stop notification type\"],\"nVsE67\":[\"Mark as Watching\"],\"nZ7lF1\":[\"Device code expired. Please try again.\"],\"nbfdhU\":[\"Integrations\"],\"nbmpf9\":[\"Purge metadata\"],\"nnKJTm\":[\"Failed to mark all episodes as watched\"],\"nszbQG\":[\"No backups yet\"],\"nuh/Wq\":[\"Webhook URL\"],\"nwtY4N\":[\"Something went wrong\"],\"oAIA3w\":[\"Search for movies, shows, or people\"],\"oC8IMh\":[\"Search movies, shows, people...\"],\"oNvZcA\":[\"Episodes this week\"],\"oXq+Wr\":[[\"label\"],\" connected\"],\"ogtYkT\":[\"Password updated\"],\"ojtedN\":[\"Open Radarr, go to <0>Settings > Import Lists\"],\"olMi35\":[\"Recommended for You\"],\"p+PZEl\":[\"Here's what's happening with your library\"],\"pAtylB\":[\"Not Found\"],\"pG9pq1\":[\"Deleted \",[\"0\",\"plural\",{\"one\":[\"#\",\" file\"],\"other\":[\"#\",\" files\"]}],\", freed \",[\"freed\"]],\"pWWFjv\":[\"Checked <0/>\"],\"ph76H6\":[\"Allow new users to create accounts\"],\"pjgw0N\":[\"Choose how to import your \",[\"0\"],\" data.\"],\"puo3W3\":[\"Redirecting…\"],\"q3GraM\":[\"...and \",[\"0\"],\" more\"],\"q6nrFE\":[\"died at \",[\"age\"]],\"q6pUQ9\":[\"Upload a .db file to replace the current database. A safety backup is created first.\"],\"q8yluz\":[\"Your name\"],\"qA6VR5\":[\"Requires <0>Emby Server 4.7.9+ and an active <1>Emby Premiere<2/> license.\"],\"qF2IBM\":[\"Movies this month\"],\"qHbApt\":[\"Failed to load backup schedule settings.\"],\"qLB1zX\":[\"Upload a \",[\"0\"],\" export from your \",[\"1\"],\" account settings.\"],\"qPNzfu\":[\"Update checks disabled\"],\"qWoML/\":[\"Add a new webhook and paste the URL above\"],\"qiOIiY\":[\"Buy\"],\"qn1X6N\":[\"Failed to update registration setting\"],\"qqWcBV\":[\"Completed\"],\"qqeAJM\":[\"Never\"],\"r9aDAY\":[[\"count\"],\" earlier \",[\"count\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}],\" unwatched\"],\"rLgPvm\":[\"Backup\"],\"rSTpb5\":[\"Server Health\"],\"rdymVD\":[\"Trigger job\"],\"rtir7c\":[\"unknown\"],\"s0U4ZZ\":[\"TV episodes\"],\"s4vVUm\":[\"Could not load person details\"],\"s9dVME\":[\"Failed to unmark episode\"],\"sGH11W\":[\"Server\"],\"sl/qWH\":[\"Upload profile picture\"],\"sstysK\":[\"Failed to mark episode\"],\"stZeiF\":[[\"watched\"],\"/\",[\"total\"],\" episodes\"],\"t/Ch8S\":[\"Marked as watching\"],\"t/YqKh\":[\"Remove\"],\"tfDRzk\":[\"Save\"],\"tiymc0\":[\"Something went wrong…\"],\"u9ugU7\":[[\"watchedCount\"],\"/\",[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}]],\"uBAxNB\":[\"Editor\"],\"uBrbSu\":[\"Failed to start \",[\"0\"],\" connection\"],\"uM6jnS\":[\"Restore failed\"],\"uMrJrX\":[\"Admin only\"],\"uswLvZ\":[\"The title you're looking for doesn't exist or may have been removed from the database.\"],\"uxOntd\":[[\"watchedCount\"],\" of \",[\"0\"],\" episodes watched\"],\"v2CA3w\":[\"Change Photo\"],\"v5ipVu\":[\"Mark all episodes as watched?\"],\"vKfeax\":[\"Open Emby, go to Settings > Webhooks\"],\"vXIe7J\":[\"Language\"],\"vuCCZ7\":[\"Failed to parse import data\"],\"vwKERN\":[\"Failed to delete backup\"],\"w8pqsh\":[\"Mark All Watched\"],\"wA7B2T\":[\"New accounts are not being accepted right now. Contact the admin if you need access.\"],\"wGFX13\":[\"Starting at\"],\"wR1UAy\":[\"Popular Movies\"],\"wRWcdL\":[\"Play trailer\"],\"wThGrS\":[\"App Settings\"],\"wZK4Xg\":[\"Never run\"],\"wdLxgL\":[\"Member since \",[\"memberSince\"]],\"wja8aL\":[\"Untitled\"],\"wtGebH\":[\"The person you're looking for doesn't exist or may have been removed from the database.\"],\"wtsbt5\":[\"Add to watchlist\"],\"wtuVU4\":[\"Frequency\"],\"wx7pwA\":[\"Mark as Watched\"],\"x4ZiTl\":[\"Setup instructions\"],\"xCJdfg\":[\"Clear\"],\"xGVfLh\":[\"Continue\"],\"xLoCm2\":[\"Check configuration\"],\"xSrU2g\":[[\"healthyCount\"],\" of \",[\"0\"],\" jobs healthy\"],\"xazqmy\":[\"Seasons\"],\"xbBXhy\":[\"Periodically check GitHub for new Sofa releases\"],\"xrh2/M\":[\"Failed to mark as watched\"],\"y3e9pF\":[\"Delete backup?\"],\"y6Urel\":[\"Next Episode\"],\"yGvjAo\":[\"Update available: \",[\"0\"]],\"yKu/3Y\":[\"Restore\"],\"yYxB17\":[\"Clear all\"],\"ybtG1U\":[[\"0\"],\" backup\",[\"1\"],\" stored\"],\"yey/zg\":[\"Database restored. Reloading...\"],\"ygo0l/\":[\"Start tracking movies and shows\"],\"yvwIbI\":[\"Upload export file\"],\"yz7wBu\":[\"Close\"],\"z1U/Fh\":[\"Rating\"],\"z5evln\":[\"No internet connection\"],\"zAvS8w\":[\"Sign in to continue\"],\"zEqK2w\":[\"The page you're looking for doesn't exist.\"],\"zFkiTv\":[\"Install the Webhook plugin from Jellyfin's plugin catalog\"],\"zNyR4f\":[\"Set your preferred quality profile and root folder\"],\"za8Le/\":[\"Registration Closed\"],\"zb77GC\":[\"Rent\"],\"ztAdhw\":[\"Name updated\"]}")as Messages; \ No newline at end of file diff --git a/packages/i18n/src/po/it.po b/packages/i18n/src/po/it.po new file mode 100644 index 0000000..2daef85 --- /dev/null +++ b/packages/i18n/src/po/it.po @@ -0,0 +1,2433 @@ +msgid "" +msgstr "" +"POT-Creation-Date: 2026-03-17 21:04-0400\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: @lingui/cli\n" +"Language: it\n" +"Project-Id-Version: sofa\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2026-03-18 16:04\n" +"Last-Translator: \n" +"Language-Team: Italian\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: sofa\n" +"X-Crowdin-Project-ID: 881052\n" +"X-Crowdin-Language: it\n" +"X-Crowdin-File: /packages/i18n/src/po/en.po\n" +"X-Crowdin-File-ID: 12\n" + +#. placeholder {0}: result.errors.length - 50 +#: apps/web/src/components/settings/imports-section.tsx:938 +msgid "...and {0} more" +msgstr "" + +#. placeholder {0}: systemHealth.data.imageCache.imageCount +#: apps/native/src/app/(tabs)/(settings)/index.tsx:482 +msgid "{0, plural, one {# image} other {# images}}" +msgstr "" + +#. placeholder {0}: systemHealth.data.database.titleCount +#: apps/native/src/app/(tabs)/(settings)/index.tsx:466 +msgid "{0, plural, one {# title} other {# titles}}" +msgstr "" + +#. placeholder {0}: displayBackups.length +#. placeholder {1}: displayBackups.length +#: apps/web/src/components/settings/backup-section.tsx:109 +msgid "{0} {1, plural, one {backup} other {backups}} stored" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:105 +#~ msgid "{0} backup{1} stored" +#~ msgstr "{0} backup{1} stored" + +#. placeholder {0}: backups.backupCount +#: apps/web/src/components/settings/system-health-section.tsx:654 +msgid "{0} backups · last <0/>" +msgstr "" + +#. placeholder {0}: imageCache.imageCount.toLocaleString() +#: apps/web/src/components/settings/system-health-section.tsx:621 +msgid "{0} cached images" +msgstr "" + +#. placeholder {0}: member.episodeCount +#. placeholder {1}: member.episodeCount !== 1 ? "s" : "" +#: apps/web/src/components/titles/cast-carousel.tsx:80 +msgid "{0} ep{1}" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:472 +#~ msgid "{0} images" +#~ msgstr "{0} images" + +#. placeholder {0}: stats.watchlist +#: apps/web/src/components/settings/imports-section.tsx:808 +msgid "{0} items" +msgstr "" + +#. placeholder {0}: preview.diagnostics.unresolved +#: apps/web/src/components/settings/imports-section.tsx:788 +msgid "{0} items have no external IDs and will be resolved by title search, which may be less accurate." +msgstr "" + +#. placeholder {0}: stats.movies +#. placeholder {1}: stats.episodes +#: apps/web/src/components/settings/imports-section.tsx:802 +msgid "{0} movies, {1} episodes" +msgstr "" + +#. placeholder {0}: stats.ratings +#: apps/web/src/components/settings/imports-section.tsx:814 +msgid "{0} ratings" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:456 +#~ msgid "{0} titles" +#~ msgstr "{0} titles" + +#. placeholder {0}: JOB_LABELS[name] ?? name +#: apps/web/src/components/settings/system-health-section.tsx:343 +msgid "{0} triggered" +msgstr "" + +#. placeholder {0}: item.watchedEpisodes +#. placeholder {1}: item.totalEpisodes +#. placeholder {2}: item.totalEpisodes +#: apps/web/src/components/dashboard/continue-watching-card.tsx:93 +msgid "{0}/{1} {2, plural, one {episode} other {episodes}}" +msgstr "" + +#: apps/web/src/components/dashboard/continue-watching-card.tsx:92 +#~ msgid "{0}/{1} episodes" +#~ msgstr "{0}/{1} episodes" + +#: apps/web/src/components/titles/use-title-actions.ts:221 +#: apps/web/src/components/titles/use-title-actions.ts:299 +msgid "{count} earlier {count, plural, one {episode} other {episodes}} unwatched" +msgstr "" + +#. placeholder {0}: activeJobs.length +#: apps/web/src/components/settings/system-health-section.tsx:385 +msgid "{healthyCount} of {0} jobs healthy" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:96 +#: apps/web/src/components/settings/integration-card.tsx:106 +msgid "{label} connected" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:106 +#: apps/web/src/components/settings/integration-card.tsx:122 +msgid "{label} disconnected" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:116 +#: apps/web/src/components/settings/integration-card.tsx:138 +msgid "{label} URL regenerated" +msgstr "" + +#: apps/web/src/components/title-card.tsx:166 +msgid "{watched}/{total} episodes" +msgstr "" + +#. placeholder {0}: episodes.length +#: apps/native/src/components/titles/season-accordion.tsx:122 +msgid "{watchedCount} of {0} episodes watched" +msgstr "" + +#. placeholder {0}: episodes.length +#. placeholder {1}: episodes.length +#: apps/native/src/components/titles/season-accordion.tsx:131 +msgid "{watchedCount}/{0} {1, plural, one {episode} other {episodes}}" +msgstr "" + +#: apps/native/src/components/titles/season-accordion.tsx:130 +#~ msgid "{watchedCount}/{0} episodes" +#~ msgstr "{watchedCount}/{0} episodes" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:231 +#: apps/web/src/components/settings/account-section.tsx:175 +msgid "Account" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:412 +msgid "Actions" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:71 +msgid "Actor" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:72 +msgid "Add a \"Generic Destination\" and paste the URL above" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:131 +msgid "Add a <0>Generic Destination and paste the URL above" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:92 +#: apps/web/src/components/settings/integration-configs.tsx:198 +msgid "Add a new webhook and paste the URL above" +msgstr "" + +#: apps/web/src/components/explore/hero-banner.tsx:99 +msgid "Add to Library" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:72 +msgid "Add to watchlist" +msgstr "" + +#: apps/native/src/components/explore/hero-banner.tsx:157 +#: apps/native/src/components/ui/poster-card.tsx:238 +#: apps/web/src/components/title-card.tsx:146 +msgid "Add to Watchlist" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:23 +msgid "Added \"{titleName}\" to watchlist" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:44 +#: apps/native/src/hooks/use-title-actions.ts:60 +#: apps/native/src/lib/title-actions.ts:24 +#: apps/web/src/components/titles/use-title-actions.ts:111 +msgid "Added to watchlist" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:353 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:454 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:499 +#: apps/web/src/components/nav-bar.tsx:235 +#: apps/web/src/components/settings/account-section.tsx:335 +msgid "Admin" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:124 +msgid "Admin only" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:308 +#: apps/web/src/components/people/person-hero.tsx:91 +msgid "age {age}" +msgstr "" + +#: apps/native/src/components/explore/filterable-title-row.tsx:119 +#: apps/web/src/components/people/filmography-grid.tsx:58 +msgid "All" +msgstr "" + +#: apps/web/src/components/settings/registration-section.tsx:61 +msgid "Allow new users to create accounts" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:278 +msgid "Already have an account?" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:239 +msgid "Already have an account? Sign in" +msgstr "" + +#: apps/web/src/routes/__root.tsx:146 +msgid "An unexpected error occurred while loading this page. You can try again or head back to the dashboard." +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:427 +msgid "Anonymous usage reporting" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:102 +msgid "App Settings" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:378 +msgid "Application" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:151 +msgid "Are you sure you want to disconnect {label}? The current URL will stop working." +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:206 +#: apps/native/src/components/header-avatar.tsx:70 +msgid "Are you sure you want to sign out?" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:450 +msgid "Authorization succeeded but failed to fetch your library. Please try again." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:440 +msgid "Authorization was denied. Please try again." +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:611 +msgid "Authorize Sofa to read your {0} library. No password shared." +msgstr "" + +#: apps/web/src/components/settings/update-check-section.tsx:60 +msgid "Automatic update checks" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:279 +msgid "Automatically back up your database on a schedule" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:332 +msgid "Availability" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:104 +msgid "Back to Login" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:382 +msgid "Background jobs" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:336 +msgid "Backup" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:69 +msgid "Backup created" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:94 +msgid "Backup deleted" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:234 +msgid "Backup schedule" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:641 +#: apps/web/src/routes/_app/settings.tsx:166 +msgid "Backups" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:272 +#~ msgid "backups." +#~ msgstr "backups." + +#: apps/web/src/components/titles/title-availability.tsx:139 +msgid "Buy" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:89 +msgid "Cache management" +msgstr "" + +#: apps/native/src/components/ui/server-unreachable-banner.ios.tsx:66 +#: apps/native/src/components/ui/server-unreachable-banner.tsx:74 +msgid "Can't reach server" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:207 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:332 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:388 +#: apps/native/src/components/header-avatar.tsx:71 +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:63 +#: apps/native/src/components/search/recently-viewed-list.tsx:37 +#: apps/native/src/components/settings/integration-card.tsx:138 +#: apps/native/src/components/settings/integration-card.tsx:153 +#: apps/web/src/components/settings/account-section.tsx:505 +#: apps/web/src/components/settings/backup-restore-section.tsx:107 +#: apps/web/src/components/settings/backup-section.tsx:270 +#: apps/web/src/components/settings/danger-section.tsx:129 +#: apps/web/src/components/settings/danger-section.tsx:171 +#: apps/web/src/components/settings/danger-section.tsx:214 +#: apps/web/src/components/settings/imports-section.tsx:645 +#: apps/web/src/components/settings/imports-section.tsx:710 +#: apps/web/src/components/settings/imports-section.tsx:837 +#: apps/web/src/components/titles/title-seasons.tsx:133 +msgid "Cancel" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:307 +msgid "Cancel editing" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:559 +#: apps/web/src/components/titles/cast-carousel.tsx:21 +msgid "Cast" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:223 +#: apps/web/src/components/titles/use-title-actions.ts:301 +msgid "Catch up" +msgstr "" + +#. placeholder {0}: episodeIds.length +#. placeholder {1}: episodeIds.length +#: apps/web/src/components/titles/use-title-actions.ts:82 +msgid "Caught up — marked {0} {1, plural, one {episode} other {episodes}} as watched" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:361 +#: apps/web/src/components/settings/account-section.tsx:427 +#: apps/web/src/components/settings/account-section.tsx:432 +msgid "Change password" +msgstr "" + +#: apps/native/src/app/change-password.tsx:98 +msgid "Change Password" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:267 +msgid "Change Photo" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:385 +msgid "Change Server" +msgstr "" + +#: apps/web/src/components/setup/refresh-button.tsx:30 +msgid "Check configuration" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:513 +msgid "Check for updates" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:215 +msgid "Checked <0/>" +msgstr "" + +#: apps/web/src/components/setup/refresh-button.tsx:28 +msgid "Checking…" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:587 +msgid "Choose how to import your {0} data." +msgstr "" + +#: apps/web/src/components/settings/language-section.tsx:29 +#~ msgid "Choose your preferred display language" +#~ msgstr "Choose your preferred display language" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:65 +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:115 +#: apps/native/src/components/search/recently-viewed-list.tsx:39 +#: apps/native/src/components/search/recently-viewed-list.tsx:95 +msgid "Clear" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:322 +msgid "Clear all" +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:60 +#: apps/native/src/components/search/recently-viewed-list.tsx:34 +msgid "Clear Recently Viewed?" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:109 +#: apps/native/src/components/settings/integration-configs.ts:127 +msgid "Click \"+\" and select \"Custom Lists\"" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:55 +msgid "Click \"Add Webhook\" and paste the URL above" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:237 +#: apps/web/src/components/settings/integration-configs.tsx:277 +msgid "Click <0>+ and select <1>Custom Lists" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:79 +msgid "Click <0>Add Webhook and paste the URL above" +msgstr "" + +#: apps/native/src/components/navigation/modal-stack-header.tsx:14 +msgid "Close" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:63 +#: apps/native/src/components/titles/status-action-button.tsx:44 +#: apps/web/src/components/dashboard/stats-display.tsx:238 +#: apps/web/src/components/title-card.tsx:77 +#: apps/web/src/components/titles/status-button.tsx:30 +msgid "Completed" +msgstr "" + +#: apps/native/src/app/change-password.tsx:173 +#: apps/web/src/components/settings/account-section.tsx:477 +msgid "Confirm new password" +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:207 +#: apps/web/src/components/settings/imports-section.tsx:499 +msgid "Connect" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/integration-card.tsx:203 +msgid "Connect {0}" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:238 +msgid "Connect {label}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:665 +msgid "Connect to {source}" +msgstr "" + +#: apps/web/src/routes/setup.tsx:101 +msgid "Connect to TMDB" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:608 +msgid "Connect with {0}" +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:197 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:474 +#: apps/web/src/components/settings/system-health-section.tsx:256 +msgid "Connected" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:722 +msgid "Connected to {source}" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:240 +msgid "Connected to <0>{serverHost}. Tap to change." +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:186 +msgid "Connecting to server..." +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:201 +msgid "Connecting..." +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:237 +msgid "Connecting…" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:390 +msgid "Continue" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:113 +#: apps/web/src/components/dashboard/continue-watching-section.tsx:25 +msgid "Continue Watching" +msgstr "" + +#: apps/web/src/components/setup/copy-button.tsx:25 +msgid "Copied" +msgstr "" + +#: apps/web/src/components/setup/copy-button.tsx:30 +msgid "Copy" +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:246 +msgid "Copy URL" +msgstr "" + +#: apps/native/src/components/settings/integrations-section.tsx:29 +msgid "Could not load integrations" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:216 +msgid "Could not load person details" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:247 +msgid "Create account" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:115 +#: apps/native/src/app/(auth)/register.tsx:225 +msgid "Create Account" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:216 +msgid "Create an account" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:110 +msgid "Create your account" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:137 +msgid "Creating…" +msgstr "" + +#: apps/native/src/app/change-password.tsx:120 +#: apps/web/src/components/settings/account-section.tsx:449 +msgid "Current password" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:391 +msgid "Current password is required" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:195 +msgid "Danger Zone" +msgstr "" + +#: apps/web/src/routes/__root.tsx:167 +#: apps/web/src/routes/_app/people.$id.tsx:78 +#: apps/web/src/routes/_app/titles.$id.tsx:126 +#: apps/web/src/routes/_app/titles.$id.tsx:172 +msgid "Dashboard" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:463 +#: apps/web/src/components/settings/system-health-section.tsx:229 +msgid "Database" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:126 +msgid "Database backups" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:31 +msgid "Database restored. Reloading..." +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:342 +msgid "Day:" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:250 +#: apps/web/src/components/settings/backup-section.tsx:280 +msgid "Delete" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:235 +msgid "Delete backup" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:256 +msgid "Delete backup?" +msgstr "" + +#. placeholder {0}: data.deletedFiles +#: apps/web/src/components/settings/danger-section.tsx:54 +msgid "Deleted {0, plural, one {# file} other {# files}}, freed {freed}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:412 +#: apps/web/src/components/settings/imports-section.tsx:444 +msgid "Device code expired. Please try again." +msgstr "" + +#: apps/native/src/app/person/[id].tsx:307 +#: apps/web/src/components/people/person-hero.tsx:91 +msgid "died at {age}" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:73 +msgid "Director" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:431 +#: apps/web/src/components/settings/system-health-section.tsx:632 +msgid "Disabled" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:155 +#: apps/native/src/components/settings/integration-card.tsx:298 +#: apps/web/src/components/settings/integration-card.tsx:274 +msgid "Disconnect" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:150 +msgid "Disconnect {label}" +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:242 +msgid "Don't have a server?" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:288 +msgid "Don't have an account?" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:962 +msgid "Done" +msgstr "" + +#: apps/native/src/components/explore/genre-chip.tsx:27 +msgid "Double tap to clear this filter" +msgstr "" + +#: apps/native/src/components/explore/genre-chip.tsx:28 +msgid "Double tap to filter by {label}" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:71 +#: apps/web/src/components/settings/backup-section.tsx:223 +msgid "Download" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:214 +msgid "Download backup" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:79 +msgid "Editor" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:133 +#: apps/native/src/app/(auth)/register.tsx:154 +#: apps/web/src/components/auth-form.tsx:201 +msgid "Email" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:73 +msgid "Enable the \"Playback Stop\" notification type" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:93 +msgid "Enable the \"Playback\" event category" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:140 +msgid "Enable the <0>Playback Stop notification type" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:201 +msgid "Enable the <0>Playback event category" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:668 +msgid "Enter the code below on {source}'s website to authorize Sofa." +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:435 +msgid "Enter your current password and choose a new one." +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:151 +msgid "Enter your Sofa server URL to get started" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:289 +msgid "Environment" +msgstr "" + +#. placeholder {0}: episode.episodeNumber +#: apps/native/src/components/titles/episode-row.tsx:29 +#: apps/native/src/components/titles/episode-row.tsx:55 +msgid "Episode {0}" +msgstr "" + +#. placeholder {0}: episode.episodeNumber +#: apps/native/src/components/titles/episode-row.tsx:36 +msgid "Episode {0}, {episodeLabel}" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:123 +#: apps/native/src/lib/title-actions.ts:111 +msgid "Episode unwatched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:109 +#: apps/native/src/lib/title-actions.ts:101 +msgid "Episode watched" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:780 +#: apps/web/src/components/titles/title-seasons.tsx:102 +msgid "Episodes" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:162 +#~ msgid "Episodes {periodSelect}" +#~ msgstr "Episodes {periodSelect}" + +#: apps/web/src/components/dashboard/stats-display.tsx:164 +msgid "Episodes {select}" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:61 +msgid "Episodes this week" +msgstr "" + +#: apps/native/src/app/change-password.tsx:71 +#: apps/native/src/app/change-password.tsx:81 +msgid "Error" +msgstr "" + +#. placeholder {0}: result.errors.length +#: apps/web/src/components/settings/imports-section.tsx:929 +msgid "Errors ({0})" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:143 +#: apps/web/src/components/nav-bar.tsx:126 +#: apps/web/src/components/nav-bar.tsx:291 +msgid "Explore" +msgstr "" + +#: apps/web/src/routes/_app/people.$id.tsx:70 +#: apps/web/src/routes/_app/titles.$id.tsx:164 +msgid "Explore titles" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:923 +msgid "Failed" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:49 +#: apps/native/src/lib/title-actions.ts:28 +msgid "Failed to add to watchlist" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:90 +msgid "Failed to catch up" +msgstr "" + +#: apps/native/src/app/change-password.tsx:71 +#: apps/web/src/components/settings/account-section.tsx:411 +msgid "Failed to change password" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:398 +msgid "Failed to connect" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:99 +#: apps/web/src/components/settings/integration-card.tsx:108 +msgid "Failed to connect {label}" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:67 +msgid "Failed to create account" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:81 +msgid "Failed to create backup" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:97 +msgid "Failed to delete backup" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:109 +#: apps/web/src/components/settings/integration-card.tsx:125 +msgid "Failed to disconnect {label}" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:215 +msgid "Failed to load backup schedule settings." +msgstr "" + +#: apps/web/src/routes/_app/titles.$id.tsx:115 +msgid "Failed to load title" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:367 +msgid "Failed to mark all episodes as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:84 +#: apps/native/src/lib/title-actions.ts:68 +#: apps/web/src/components/titles/use-title-actions.ts:150 +msgid "Failed to mark as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:113 +#: apps/native/src/lib/title-actions.ts:104 +#: apps/web/src/components/titles/use-title-actions.ts:237 +msgid "Failed to mark episode" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:141 +#: apps/native/src/lib/title-actions.ts:128 +#: apps/web/src/components/titles/use-title-actions.ts:315 +msgid "Failed to mark some episodes" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:225 +msgid "Failed to parse file" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:240 +msgid "Failed to parse import data" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:75 +msgid "Failed to purge caches" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:58 +msgid "Failed to purge image cache" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:45 +msgid "Failed to purge metadata cache" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:119 +#: apps/web/src/components/settings/integration-card.tsx:140 +msgid "Failed to regenerate {label} URL" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:78 +msgid "Failed to remove from library" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:134 +#: apps/web/src/components/settings/account-section.tsx:134 +msgid "Failed to remove profile picture" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:56 +msgid "Failed to sign in" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:388 +msgid "Failed to start {0} connection" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:348 +msgid "Failed to trigger job" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:129 +#: apps/native/src/lib/title-actions.ts:114 +#: apps/web/src/components/titles/use-title-actions.ts:181 +msgid "Failed to unmark episode" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:343 +msgid "Failed to unmark some episodes" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:112 +msgid "Failed to update name" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:101 +#: apps/native/src/lib/title-actions.ts:94 +#: apps/web/src/components/titles/use-title-actions.ts:136 +msgid "Failed to update rating" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:172 +#: apps/web/src/components/settings/registration-section.tsx:44 +msgid "Failed to update registration setting" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:154 +msgid "Failed to update retention setting" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:156 +msgid "Failed to update schedule" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:152 +msgid "Failed to update scheduled backup setting" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:191 +#: apps/web/src/components/settings/update-check-section.tsx:43 +msgid "Failed to update setting" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:72 +#: apps/native/src/lib/title-actions.ts:40 +#: apps/native/src/lib/title-actions.ts:54 +#: apps/web/src/components/titles/use-title-actions.ts:114 +msgid "Failed to update status" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:123 +msgid "Failed to upload avatar" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:725 +msgid "Fetching your library data from {source}..." +msgstr "" + +#: apps/native/src/app/person/[id].tsx:340 +#: apps/web/src/components/people/filmography-grid.tsx:69 +msgid "Filmography" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:915 +msgid "Finished importing from {source}." +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:140 +msgid "Free" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:92 +msgid "Free up disk space by clearing cached metadata and images" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:309 +msgid "Frequency" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:101 +msgid "Friday" +msgstr "" + +#: apps/web/src/components/landing-page.tsx:156 +msgid "Get Started" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:220 +#: apps/native/src/app/person/[id].tsx:247 +#: apps/native/src/app/title/[id].tsx:255 +msgid "Go back" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:37 +msgid "Go Home" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:123 +msgid "Go to <0>Dashboard > Plugins > Webhook" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:368 +msgid "Go to Dashboard" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:71 +msgid "Go to Dashboard > Plugins > Webhook" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:379 +msgid "Go to Explore" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:212 +msgid "Health status" +msgstr "" + +#: apps/web/src/components/dashboard/welcome-header.tsx:14 +msgid "Here's what's happening with your library" +msgstr "" + +#: apps/web/src/components/nav-bar.tsx:125 +#: apps/web/src/components/nav-bar.tsx:290 +msgid "Home" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:335 +#: apps/web/src/components/settings/system-health-section.tsx:610 +msgid "Image cache" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:479 +msgid "Image Cache" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:598 +msgid "Image cache and backup disk usage" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:179 +msgid "Import" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:840 +msgid "Import {totalItems} items" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:912 +msgid "Import complete" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:340 +msgid "Import failed" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:584 +msgid "Import from {0}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:770 +msgid "Import from {source}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:298 +#: apps/web/src/components/settings/imports-section.tsx:329 +msgid "Import is still running in the background. Check back later." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:798 +msgid "Import options" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:921 +msgid "Imported" +msgstr "" + +#. placeholder {0}: event.job.importedCount +#. placeholder {1}: config.label +#: apps/web/src/components/settings/imports-section.tsx:292 +msgid "Imported {0} items from {1}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:863 +msgid "Importing from {source}" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:62 +msgid "In library" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:230 +msgid "In Library" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:133 +#: apps/web/src/components/dashboard/library-section.tsx:35 +msgid "In Your Library" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:105 +msgid "Install the <0>Webhook plugin<1/> from Jellyfin's plugin catalog" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:70 +msgid "Install the Webhook plugin from Jellyfin's plugin catalog" +msgstr "" + +#: apps/native/src/components/settings/integrations-section.tsx:20 +#: apps/web/src/components/settings/integrations-section.tsx:41 +msgid "Integrations" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:266 +msgid "Invalid token" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:399 +msgid "Job" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:243 +#~ msgid "Keeping" +#~ msgstr "Keeping" + +#. placeholder {0}: (value: string | null) => value === "0" ? t`unlimited` : value ? t`last ${value}` : null +#. placeholder {1}: [3, 5, 7, 14, 30, 0].map((n) => ( {n === 0 ? t`unlimited` : t`last ${n}`} )) +#: apps/web/src/components/settings/backup-schedule-section.tsx:243 +msgid "Keeping <0><1><2>{0}<3>{1} backups." +msgstr "" + +#: apps/web/src/components/command-palette.tsx:389 +#: apps/web/src/components/command-palette.tsx:404 +msgid "Keyboard Shortcuts" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:403 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:411 +#: apps/web/src/components/settings/language-section.tsx:42 +msgid "Language" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:270 +msgid "last {n}" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:258 +msgid "last {value}" +msgstr "" + +#. placeholder {0}: formatRelativeTime(lastEventAt) +#: apps/web/src/components/settings/integration-card.tsx:331 +msgid "Last event {0}" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:29 +msgid "Last event {timeAgo}" +msgstr "" + +#. placeholder {0}: formatRelativeTime(lastEventAt) +#: apps/web/src/components/settings/integration-card.tsx:338 +msgid "Last polled {0}" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:37 +msgid "Last polled {timeAgo}" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:405 +msgid "Last run" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:441 +msgid "Last run failed" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:437 +msgid "Last run succeeded" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:331 +msgid "Library refresh" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:245 +msgid "Loading…" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:334 +msgid "Lost connection to import. Check status in settings." +msgstr "" + +#: apps/web/src/components/settings/settings-shell.tsx:40 +msgid "Manage your account and preferences" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:182 +msgid "Manual backup" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:122 +msgid "Mark all episodes as watched?" +msgstr "" + +#: apps/native/src/components/titles/season-accordion.tsx:164 +#: apps/web/src/components/titles/title-seasons.tsx:115 +#: apps/web/src/components/titles/title-seasons.tsx:141 +msgid "Mark All Watched" +msgstr "" + +#: apps/native/src/components/dashboard/continue-watching-card.tsx:122 +msgid "Mark as Completed" +msgstr "" + +#: apps/native/src/components/ui/poster-card.tsx:252 +msgid "Mark as Watched" +msgstr "" + +#: apps/native/src/components/ui/poster-card.tsx:245 +msgid "Mark as Watching" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:449 +#: apps/web/src/components/titles/title-actions.tsx:29 +msgid "Mark Watched" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:49 +msgid "Marked \"{titleName}\" as completed" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:63 +#: apps/web/src/components/titles/use-title-actions.ts:147 +msgid "Marked \"{titleName}\" as watched" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:360 +msgid "Marked all episodes as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:62 +#: apps/native/src/lib/title-actions.ts:50 +msgid "Marked as completed" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:80 +#: apps/native/src/lib/title-actions.ts:64 +msgid "Marked as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:61 +#: apps/native/src/lib/title-actions.ts:37 +msgid "Marked as watching" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:340 +msgid "Member since {memberSince}" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:97 +msgid "Monday" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:585 +msgid "More Like This" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:541 +msgid "More Settings" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:362 +#: apps/native/src/components/search/search-result-row.tsx:35 +#: apps/native/src/components/search/search-result-row.tsx:96 +#: apps/web/src/components/explore/hero-banner.tsx:59 +#: apps/web/src/components/titles/title-hero.tsx:143 +msgid "Movie" +msgstr "" + +#: apps/web/src/components/people/filmography-grid.tsx:59 +#: apps/web/src/components/settings/imports-section.tsx:779 +msgid "Movies" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:161 +#~ msgid "Movies {periodSelect}" +#~ msgstr "Movies {periodSelect}" + +#: apps/web/src/components/dashboard/stats-display.tsx:162 +msgid "Movies {select}" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:60 +msgid "Movies this month" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:124 +#: apps/web/src/components/auth-form.tsx:181 +msgid "Name" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:107 +#: apps/web/src/components/settings/account-section.tsx:80 +msgid "Name updated" +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:301 +msgid "Need more help?" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:501 +msgid "Never" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:435 +msgid "Never run" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:98 +msgid "New account creation is currently disabled." +msgstr "" + +#: apps/web/src/routes/_auth/register.tsx:35 +msgid "New accounts are not being accepted right now. Contact the admin if you need access." +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:137 +msgid "New backup" +msgstr "" + +#: apps/native/src/app/change-password.tsx:145 +#: apps/web/src/components/settings/account-section.tsx:463 +msgid "New password" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:395 +msgid "New password must be at least 8 characters" +msgstr "" + +#: apps/web/src/components/people/filmography-grid.tsx:86 +#: apps/web/src/components/people/filmography-grid.tsx:99 +msgid "Newest" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:131 +msgid "Next backup {distance}" +msgstr "" + +#: apps/native/src/components/titles/continue-watching-banner.tsx:46 +msgid "Next Episode" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:408 +msgid "Next run" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:110 +#: apps/web/src/components/settings/system-health-section.tsx:662 +msgid "No backups yet" +msgstr "" + +#: apps/native/src/components/ui/offline-banner.ios.tsx:74 +#: apps/native/src/components/ui/offline-banner.ios.tsx:81 +#: apps/native/src/components/ui/offline-banner.tsx:59 +msgid "No internet connection" +msgstr "" + +#: apps/native/src/app/(tabs)/(search)/index.tsx:113 +msgid "No results for \"{debouncedQuery}\"" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:217 +msgid "No results found." +msgstr "" + +#: apps/native/src/components/explore/filterable-title-row.tsx:139 +#: apps/web/src/components/explore/filterable-title-row.tsx:151 +msgid "No titles found for this genre." +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:194 +#: apps/web/src/components/settings/integration-card.tsx:176 +#: apps/web/src/components/settings/system-health-section.tsx:249 +msgid "Not configured" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:17 +msgid "Not Found" +msgstr "" + +#: apps/web/src/components/title-card.tsx:67 +msgid "On Watchlist" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:693 +msgid "Open {source} to enter code" +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:308 +msgid "Open docs" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:190 +msgid "Open Emby, go to <0>Settings > Webhooks" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:91 +msgid "Open Emby, go to Settings > Webhooks" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:548 +msgid "Open in browser…" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:62 +msgid "Open Plex, go to <0>Settings > Webhooks<1/>" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:54 +msgid "Open Plex, go to Settings > Webhooks" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:269 +msgid "Open Radarr, go to <0>Settings > Import Lists" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:126 +msgid "Open Radarr, go to Settings > Import Lists" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:502 +#: apps/web/src/components/settings/registration-section.tsx:58 +msgid "Open registration" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:229 +msgid "Open Sonarr, go to <0>Settings > Import Lists" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:108 +msgid "Open Sonarr, go to Settings > Import Lists" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:158 +msgid "or" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:118 +msgid "OR" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:27 +msgid "Page Not Found" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:509 +msgid "Parsing..." +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:167 +#: apps/native/src/app/(auth)/register.tsx:187 +#: apps/web/src/components/auth-form.tsx:221 +msgid "Password" +msgstr "" + +#: apps/native/src/app/change-password.tsx:76 +#: apps/web/src/components/settings/account-section.tsx:414 +msgid "Password updated" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:399 +msgid "Passwords do not match" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:128 +#: apps/web/src/components/settings/integration-configs.tsx:284 +msgid "Paste the Radarr URL above into the List URL field" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:110 +#: apps/web/src/components/settings/integration-configs.tsx:244 +msgid "Paste the Sonarr URL above into the List URL field" +msgstr "" + +#: apps/web/src/components/settings/update-check-section.tsx:63 +msgid "Periodically check GitHub for new Sofa releases" +msgstr "" + +#: apps/native/src/components/search/search-result-row.tsx:38 +#: apps/native/src/components/search/search-result-row.tsx:99 +msgid "Person" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:243 +#: apps/web/src/routes/_app/people.$id.tsx:52 +msgid "Person not found" +msgstr "" + +#: apps/web/src/components/titles/trailer-dialog.tsx:24 +msgid "Play trailer" +msgstr "" + +#: apps/native/src/app/(tabs)/(explore)/index.tsx:107 +#: apps/web/src/components/explore/explore-client.tsx:126 +msgid "Popular Movies" +msgstr "" + +#: apps/native/src/app/(tabs)/(explore)/index.tsx:120 +#: apps/web/src/components/explore/explore-client.tsx:136 +msgid "Popular TV Shows" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:181 +msgid "Pre-restore backup" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:77 +msgid "Producer" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:130 +#: apps/web/src/components/settings/account-section.tsx:130 +msgid "Profile picture removed" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:119 +#: apps/web/src/components/settings/account-section.tsx:108 +msgid "Profile picture updated" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:196 +#: apps/web/src/components/settings/danger-section.tsx:220 +msgid "Purge all" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:202 +msgid "Purge all caches?" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:160 +msgid "Purge image cache?" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:154 +#: apps/web/src/components/settings/danger-section.tsx:177 +msgid "Purge images" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:111 +#: apps/web/src/components/settings/danger-section.tsx:135 +msgid "Purge metadata" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:117 +msgid "Purge metadata cache?" +msgstr "" + +#. placeholder {0}: data.deletedTitles +#. placeholder {1}: data.deletedPersons +#: apps/web/src/components/settings/danger-section.tsx:41 +msgid "Purged {0, plural, one {# stale title} other {# stale titles}} and {1, plural, one {# orphaned person} other {# orphaned persons}}" +msgstr "" + +#. placeholder {0}: metaResult.deletedTitles +#. placeholder {1}: metaResult.deletedPersons +#. placeholder {2}: imageResult.deletedFiles +#: apps/web/src/components/settings/danger-section.tsx:71 +msgid "Purged {0, plural, one {# title} other {# titles}}, {1, plural, one {# person} other {# persons}}, {2, plural, one {# file} other {# files}} ({freed} freed)" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:109 +#: apps/web/src/components/settings/danger-section.tsx:152 +#: apps/web/src/components/settings/danger-section.tsx:194 +msgid "Purging..." +msgstr "" + +#: apps/web/src/components/command-palette.tsx:359 +msgid "Quick Actions" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:123 +msgid "Radarr List URL" +msgstr "" + +#. placeholder {0}: input.stars +#. placeholder {1}: input.stars +#: apps/native/src/hooks/use-title-actions.ts:93 +msgid "Rated {0} {1, plural, one {star} other {stars}}" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:92 +#~ msgid "Rated {0} star{1}" +#~ msgstr "Rated {0} star{1}" + +#: apps/web/src/components/titles/use-title-actions.ts:131 +msgid "Rated {ratingStars} {ratingStars, plural, one {star} other {stars}}" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:88 +msgid "Rated {stars, plural, one {# star} other {# stars}}" +msgstr "" + +#: apps/web/src/components/people/filmography-grid.tsx:88 +#: apps/web/src/components/people/filmography-grid.tsx:102 +msgid "Rating" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:94 +#: apps/native/src/lib/title-actions.ts:90 +#: apps/web/src/components/titles/use-title-actions.ts:132 +msgid "Rating removed" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:782 +#: apps/web/src/components/settings/imports-section.tsx:813 +msgid "Ratings" +msgstr "" + +#: apps/web/src/components/expandable-text.tsx:51 +msgid "Read more" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:39 +#: apps/web/src/components/settings/integration-card.tsx:339 +msgid "Ready — not polled yet" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:31 +#: apps/web/src/components/settings/integration-card.tsx:332 +msgid "Ready — nothing received yet" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:315 +msgid "Recent Searches" +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:106 +#: apps/native/src/components/search/recently-viewed-list.tsx:86 +msgid "Recently Viewed" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:333 +msgid "Recommendations" +msgstr "" + +#: apps/web/src/components/titles/title-recommendations.tsx:40 +msgid "Recommended" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:154 +#: apps/web/src/components/dashboard/recommendations-section.tsx:22 +msgid "Recommended for You" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:143 +msgid "Redirecting…" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:182 +msgid "Refresh" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:172 +msgid "Refresh system health" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:140 +#: apps/native/src/components/settings/integration-card.tsx:289 +msgid "Regenerate" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:135 +#: apps/web/src/components/settings/integration-card.tsx:264 +msgid "Regenerate URL" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:293 +#: apps/web/src/components/landing-page.tsx:176 +msgid "Register" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:116 +msgid "Registering on {serverHost}" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:167 +#: apps/web/src/components/settings/registration-section.tsx:41 +msgid "Registration closed" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:97 +#: apps/web/src/routes/_auth/register.tsx:32 +msgid "Registration Closed" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:167 +#: apps/web/src/components/settings/registration-section.tsx:41 +msgid "Registration opened" +msgstr "" + +#: apps/web/src/components/titles/status-button.tsx:84 +msgid "Remove" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:101 +#: apps/web/src/components/titles/status-button.tsx:66 +msgid "Remove from library" +msgstr "" + +#: apps/native/src/components/dashboard/continue-watching-card.tsx:129 +#: apps/native/src/components/ui/poster-card.tsx:259 +msgid "Remove from Library" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:277 +msgid "Remove Photo" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:239 +msgid "Remove picture" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:199 +msgid "Remove profile picture" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:66 +#: apps/native/src/lib/title-actions.ts:75 +#: apps/web/src/components/titles/use-title-actions.ts:111 +msgid "Removed from library" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:138 +msgid "Rent" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:165 +msgid "Requires <0>Emby Server 4.7.9+ and an active <1>Emby Premiere<2/> license." +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:41 +msgid "Requires an active <0>Plex Pass<1/> subscription." +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:52 +msgid "Requires an active Plex Pass subscription." +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:88 +msgid "Requires Emby Server 4.7.9+ and an active Emby Premiere license." +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:59 +#: apps/web/src/components/settings/backup-restore-section.tsx:116 +msgid "Restore" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:89 +msgid "Restore database?" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:35 +msgid "Restore failed" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:132 +msgid "Restoring…" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:222 +msgid "Results" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:731 +msgid "Retrieving your watch history, watchlist, and ratings..." +msgstr "" + +#: apps/native/src/components/settings/integrations-section.tsx:42 +#: apps/native/src/components/ui/server-unreachable-banner.ios.tsx:73 +#: apps/native/src/components/ui/server-unreachable-banner.tsx:81 +#: apps/web/src/lib/orpc/client.ts:16 +msgid "Retry" +msgstr "" + +#: apps/web/src/routes/__root.tsx:119 +msgid "Return home" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:773 +msgid "Review what was found and choose what to import." +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:558 +msgid "Run now" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:102 +msgid "Saturday" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:322 +msgid "Save" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:296 +msgid "Save name" +msgstr "" + +#: apps/web/src/routes/__root.tsx:101 +msgid "Scene not found" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:402 +msgid "Schedule" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:201 +msgid "Schedule updated" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:179 +msgid "Scheduled backup" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:178 +msgid "Scheduled backups disabled" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:177 +msgid "Scheduled backups enabled" +msgstr "" + +#: apps/web/src/components/dashboard/stats-section.tsx:35 +msgid "Search for movies and TV shows to start tracking" +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:81 +#: apps/native/src/components/search/recently-viewed-list.tsx:64 +msgid "Search for movies, shows, or people" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:186 +msgid "Search for movies, TV shows, or run commands" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:195 +msgid "Search movies & TV shows…" +msgstr "" + +#: apps/native/src/app/(tabs)/(search)/index.tsx:90 +msgid "Search movies, shows, people..." +msgstr "" + +#: apps/web/src/components/nav-bar.tsx:193 +#: apps/web/src/components/nav-bar.tsx:202 +msgid "Search…" +msgstr "" + +#. placeholder {0}: season.seasonNumber +#: apps/native/src/components/titles/season-accordion.tsx:122 +#: apps/native/src/components/titles/season-accordion.tsx:128 +#: apps/web/src/components/titles/use-title-actions.ts:295 +#: apps/web/src/components/titles/use-title-actions.ts:335 +msgid "Season {0}" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:137 +#: apps/native/src/lib/title-actions.ts:124 +msgid "Season watched" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:536 +msgid "Seasons" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:497 +#: apps/web/src/routes/_app/settings.tsx:140 +msgid "Security" +msgstr "" + +#: apps/web/src/components/landing-page.tsx:105 +msgid "Self-hosted movie & TV tracker" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:121 +msgid "Server" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:452 +msgid "Server Health" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:380 +msgid "Server URL" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:361 +msgid "Set password" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:111 +#: apps/native/src/components/settings/integration-configs.ts:129 +#: apps/web/src/components/settings/integration-configs.tsx:247 +#: apps/web/src/components/settings/integration-configs.tsx:287 +msgid "Set your preferred quality profile and root folder" +msgstr "" + +#: apps/native/src/components/header-avatar.tsx:63 +#: apps/web/src/components/nav-bar.tsx:250 +#: apps/web/src/components/nav-bar.tsx:292 +#: apps/web/src/components/settings/settings-shell.tsx:36 +msgid "Settings" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:319 +#: apps/web/src/components/settings/integration-card.tsx:288 +msgid "Setup instructions" +msgstr "" + +#: apps/web/src/routes/setup.tsx:98 +msgid "Setup required" +msgstr "" + +#: apps/native/src/components/ui/expandable-text.tsx:62 +#: apps/native/src/components/ui/expandable-text.tsx:69 +#: apps/web/src/components/expandable-text.tsx:51 +msgid "Show less" +msgstr "" + +#: apps/native/src/components/ui/expandable-text.tsx:62 +#: apps/native/src/components/ui/expandable-text.tsx:69 +msgid "Show more" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:249 +#: apps/web/src/components/auth-form.tsx:283 +msgid "Sign in" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:205 +#: apps/web/src/components/landing-page.tsx:167 +msgid "Sign In" +msgstr "" + +#: apps/web/src/routes/_auth/register.tsx:45 +msgid "Sign in instead" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:91 +#: apps/web/src/components/auth-form.tsx:119 +msgid "Sign in to continue" +msgstr "" + +#. placeholder {0}: authConfig.data?.oidcProviderName ?? "SSO" +#. placeholder {0}: authConfig?.oidcProviderName || "SSO" +#: apps/native/src/app/(auth)/login.tsx:108 +#: apps/web/src/components/auth-form.tsx:145 +msgid "Sign in with {0}" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:368 +#: apps/native/src/components/header-avatar.tsx:87 +#: apps/web/src/components/nav-bar.tsx:262 +#: apps/web/src/components/settings/account-section.tsx:354 +msgid "Sign out" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:206 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:209 +#: apps/native/src/components/header-avatar.tsx:70 +#: apps/native/src/components/header-avatar.tsx:73 +msgid "Sign Out" +msgstr "" + +#: apps/native/src/app/change-password.tsx:199 +#: apps/web/src/components/settings/account-section.tsx:499 +msgid "Sign out of other sessions" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:922 +msgid "Skipped" +msgstr "" + +#. placeholder {0}: data.latestVersion +#: apps/web/src/components/update-toast.tsx:19 +msgid "Sofa v{0} is available" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:57 +#: apps/native/src/components/settings/integration-configs.ts:75 +#: apps/native/src/components/settings/integration-configs.ts:95 +#: apps/web/src/components/settings/integration-configs.tsx:86 +#: apps/web/src/components/settings/integration-configs.tsx:147 +#: apps/web/src/components/settings/integration-configs.tsx:208 +msgid "Sofa will automatically log movies and episodes when you finish watching them" +msgstr "" + +#: apps/native/src/app/change-password.tsx:81 +#: apps/native/src/app/person/[id].tsx:213 +#: apps/web/src/components/settings/account-section.tsx:417 +#: apps/web/src/routes/__root.tsx:143 +msgid "Something went wrong" +msgstr "" + +#: apps/web/src/routes/_app/titles.$id.tsx:118 +msgid "Something went wrong while loading this title. Please try again." +msgstr "" + +#: apps/native/src/lib/query-client.ts:33 +#: apps/web/src/lib/orpc/client.ts:14 +msgid "Something went wrong…" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:105 +msgid "Sonarr List URL" +msgstr "" + +#: apps/web/src/components/dashboard/stats-section.tsx:42 +msgid "Start exploring" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:142 +msgid "Start tracking movies and shows" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:117 +msgid "Start tracking your watches" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:388 +msgid "Starting at" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:888 +msgid "Starting import..." +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:65 +msgid "Status updated" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:595 +msgid "Storage" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:137 +msgid "Stream" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:96 +msgid "Sunday" +msgstr "" + +#. placeholder {0}: info.name +#: apps/web/src/components/settings/language-section.tsx:69 +msgid "Switch to {0}" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:30 +msgid "The page you're looking for doesn't exist." +msgstr "" + +#: apps/web/src/routes/_app/people.$id.tsx:55 +msgid "The person you're looking for doesn't exist or may have been removed from the database." +msgstr "" + +#: apps/web/src/routes/_app/titles.$id.tsx:149 +msgid "The title you're looking for doesn't exist or may have been removed from the database." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:866 +msgid "This may take a few minutes for large libraries. Please don't close this tab." +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:146 +msgid "This Month" +msgstr "" + +#: apps/web/src/routes/__root.tsx:104 +msgid "This page was left on the cutting room floor. It may have been moved, removed, or never made it past the screenplay." +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:606 +#: apps/web/src/routes/_app/settings.tsx:75 +#: apps/web/src/routes/setup.tsx:187 +msgid "This product uses the TMDB API but is not endorsed or certified by TMDB." +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:145 +msgid "This Week" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:163 +msgid "This will delete all cached TMDB images from disk. Images will be re-downloaded automatically as needed." +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:205 +msgid "This will delete all un-enriched stub titles and all cached images from disk. Everything will be re-imported and re-downloaded as needed." +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:120 +msgid "This will delete un-enriched stub titles that aren't in any user's library and clean up orphaned person records. Deleted titles will be re-imported if accessed again." +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:136 +msgid "This will invalidate the current {label} URL. You'll need to update it in {label}." +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:125 +msgid "This will mark every episode of this show as watched. You can undo this later by unmarking individual seasons." +msgstr "" + +#. placeholder {0}: formatBackupDate(backup.createdAt) +#: apps/web/src/components/settings/backup-section.tsx:259 +msgid "This will permanently delete the backup from <0>{0}. This cannot be undone." +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:61 +#: apps/native/src/components/search/recently-viewed-list.tsx:35 +msgid "This will remove all items from your history." +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:92 +msgid "This will replace your entire database with the uploaded file. A safety backup of your current data will be created first. Active sessions may need to refresh after restore." +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:147 +msgid "This Year" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:100 +msgid "Thursday" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:390 +msgid "Time:" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:251 +#: apps/web/src/routes/_app/titles.$id.tsx:146 +msgid "Title not found" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:131 +#: apps/web/src/components/settings/integration-configs.tsx:290 +msgid "Titles on your Sofa watchlist will be automatically added for download when Radarr polls this list (every 12 hours by default)" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:113 +#: apps/web/src/components/settings/integration-configs.tsx:250 +msgid "Titles on your Sofa watchlist will be automatically added for download when Sonarr polls this list (every 6 hours by default)" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:144 +msgid "Today" +msgstr "" + +#: apps/web/src/components/settings/update-check-section.tsx:71 +msgid "Toggle automatic update checks" +msgstr "" + +#: apps/web/src/components/settings/registration-section.tsx:69 +msgid "Toggle open registration" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:290 +msgid "Toggle scheduled backups" +msgstr "" + +#: apps/web/src/components/landing-page.tsx:131 +msgid "Track what you watch. Know what's next.<0/>Your library, your data, your rules." +msgstr "" + +#: apps/web/src/components/titles/trailer-dialog.tsx:37 +#: apps/web/src/components/titles/trailer-dialog.tsx:41 +msgid "Trailer" +msgstr "" + +#: apps/web/src/components/explore/hero-banner.tsx:78 +msgid "Trending today" +msgstr "" + +#: apps/native/src/app/(tabs)/(explore)/index.tsx:95 +#: apps/web/src/components/explore/explore-client.tsx:112 +msgid "Trending Today" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:537 +msgid "Trigger job" +msgstr "" + +#: apps/web/src/routes/__root.tsx:159 +msgid "Try again" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:98 +msgid "Tuesday" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:362 +#: apps/native/src/components/search/search-result-row.tsx:98 +#: apps/web/src/components/explore/hero-banner.tsx:64 +#: apps/web/src/components/people/filmography-grid.tsx:60 +#: apps/web/src/components/titles/title-hero.tsx:153 +msgid "TV" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:334 +msgid "TV episodes" +msgstr "" + +#: apps/native/src/components/search/search-result-row.tsx:37 +msgid "TV show" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:656 +msgid "unknown" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:256 +#: apps/web/src/components/settings/backup-schedule-section.tsx:270 +msgid "unlimited" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:273 +msgid "Unreachable" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:340 +msgid "Untitled" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:221 +msgid "Unwatch all" +msgstr "" + +#. placeholder {0}: season.name ?? t`Season ${season.seasonNumber}` +#: apps/web/src/components/titles/use-title-actions.ts:335 +msgid "Unwatched all of {0}" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:174 +msgid "Unwatched S{seasonNum} E{epNum}" +msgstr "" + +#: apps/native/src/components/titles/continue-watching-banner.tsx:79 +#: apps/web/src/components/dashboard/continue-watching-card.tsx:77 +msgid "Up next" +msgstr "" + +#. placeholder {0}: updateCheck.data.updateCheck.latestVersion +#: apps/native/src/app/(tabs)/(settings)/index.tsx:528 +msgid "Update available: {0}" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:337 +msgid "Update check" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:185 +#: apps/web/src/components/settings/update-check-section.tsx:40 +msgid "Update checks disabled" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:185 +#: apps/web/src/components/settings/update-check-section.tsx:40 +msgid "Update checks enabled" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:84 +msgid "Update failed" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:509 +msgid "Update password" +msgstr "" + +#: apps/native/src/app/change-password.tsx:218 +msgid "Update Password" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:134 +#: apps/web/src/components/settings/imports-section.tsx:509 +msgid "Upload" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:62 +msgid "Upload a .db file to replace the current database. A safety backup is created first." +msgstr "" + +#. placeholder {0}: config.accept +#. placeholder {1}: config.label +#: apps/web/src/components/settings/imports-section.tsx:637 +msgid "Upload a {0} export from your {1} account settings." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:634 +msgid "Upload export file" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:112 +msgid "Upload failed" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:241 +msgid "Upload picture" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:200 +msgid "Upload profile picture" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:129 +msgid "URL copied to clipboard" +msgstr "" + +#: apps/web/src/components/update-toast.tsx:24 +msgid "View release" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:699 +msgid "Waiting for authorization..." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:823 +msgid "Warnings" +msgstr "" + +#. placeholder {0}: result.warnings.length +#: apps/web/src/components/settings/imports-section.tsx:948 +msgid "Warnings ({0})" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:202 +msgid "Watch all" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:801 +msgid "Watch history" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:59 +msgid "Watch on {name}" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:298 +#: apps/web/src/components/titles/use-title-actions.ts:307 +msgid "Watched all of {seasonLabel}" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:123 +msgid "Watched all of {seasonName}" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:220 +#: apps/web/src/components/titles/use-title-actions.ts:229 +msgid "Watched S{seasonNum} E{epNum}" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:42 +#: apps/web/src/components/title-card.tsx:72 +#: apps/web/src/components/titles/status-button.tsx:19 +msgid "Watching" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:82 +#: apps/web/src/components/settings/imports-section.tsx:781 +#: apps/web/src/components/settings/imports-section.tsx:807 +#: apps/web/src/components/titles/status-button.tsx:55 +msgid "Watchlist" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:40 +msgid "Watchlisted" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:50 +#: apps/native/src/components/settings/integration-configs.ts:67 +#: apps/native/src/components/settings/integration-configs.ts:85 +msgid "Webhook URL" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:99 +msgid "Wednesday" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:112 +#: apps/web/src/components/dashboard/welcome-header.tsx:10 +msgid "Welcome back" +msgstr "" + +#: apps/web/src/components/dashboard/welcome-header.tsx:8 +msgid "Welcome back, {name}" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:483 +#: apps/web/src/components/titles/title-availability.tsx:154 +msgid "Where to Watch" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:141 +msgid "With Ads" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:75 +msgid "Writer" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:386 +msgid "You'll be signed out to change the server URL." +msgstr "" + +#. placeholder {0}: data.currentVersion +#: apps/web/src/components/update-toast.tsx:20 +msgid "You're running v{0}." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:678 +msgid "Your code:" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:141 +#: apps/web/src/components/dashboard/stats-section.tsx:32 +msgid "Your library is empty" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:134 +msgid "Your name" +msgstr "" + diff --git a/packages/i18n/src/po/it.ts b/packages/i18n/src/po/it.ts new file mode 100644 index 0000000..ef7e47e --- /dev/null +++ b/packages/i18n/src/po/it.ts @@ -0,0 +1 @@ +/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"+5kO8P\":[\"Saturday\"],\"+6i0lS\":[\"Retrieving your watch history, watchlist, and ratings...\"],\"+Cv+V9\":[\"Remove from library\"],\"+JkEpu\":[\"This page was left on the cutting room floor. It may have been moved, removed, or never made it past the screenplay.\"],\"+K0AvT\":[\"Disconnect\"],\"+N0l5/\":[\"Connected to <0>\",[\"serverHost\"],\". Tap to change.\"],\"+gLHYi\":[\"Failed to load title\"],\"+j1ex/\":[\"Failed to remove from library\"],\"+nF1ZO\":[\"Remove Photo\"],\"/+6dvC\":[\"Errors (\",[\"0\"],\")\"],\"/BBXeA\":[\"Connecting to server...\"],\"/DwR+n\":[\"Creating…\"],\"/XtbPO\":[[\"0\"],\" titles\"],\"/cg+QV\":[\"Watch all\"],\"/gQXGv\":[\"Failed to update status\"],\"/nT6AE\":[\"New password\"],\"/sHc1/\":[\"Update check\"],\"0+dyau\":[\"Failed to update retention setting\"],\"0BFJKK\":[\"Authorization was denied. Please try again.\"],\"0GHb20\":[\"Purge metadata cache?\"],\"0IBW21\":[\"Failed to purge caches\"],\"0gH/sc\":[\"Remove profile picture\"],\"1+P9RR\":[\"Switch to \",[\"0\"]],\"12cc1j\":[\"Watching\"],\"12lVOl\":[\"Self-hosted movie & TV tracker\"],\"1B4z0M\":[\"Filmography\"],\"1J4Ek0\":[\"Automatically back up your database on a schedule\"],\"1JhxXW\":[\"Episode \",[\"0\"],\", \",[\"episodeLabel\"]],\"1N6wNP\":[\"Import options\"],\"1Qz4uG\":[\"Enable the \\\"Playback\\\" event category\"],\"1hMWR6\":[\"Create account\"],\"1jHIjh\":[\"Watched all of \",[\"seasonName\"]],\"1vSYsG\":[\"Download backup\"],\"1wL1tj\":[\"TV show\"],\"2FletP\":[\"Purged \",[\"0\",\"plural\",{\"one\":[\"#\",\" title\"],\"other\":[\"#\",\" titles\"]}],\", \",[\"1\",\"plural\",{\"one\":[\"#\",\" person\"],\"other\":[\"#\",\" persons\"]}],\", \",[\"2\",\"plural\",{\"one\":[\"#\",\" file\"],\"other\":[\"#\",\" files\"]}],\" (\",[\"freed\"],\" freed)\"],\"2Fsd9r\":[\"This Month\"],\"2MPcep\":[\"Import complete\"],\"2Mu33Z\":[\"Cache management\"],\"2POOFK\":[\"Free\"],\"2Pt2NY\":[\"This will remove all items from your history.\"],\"2fCpt5\":[\"Return home\"],\"2pPBp6\":[\"Trending today\"],\"39y5bn\":[\"Friday\"],\"3Blefz\":[\"Ratings\"],\"3JTlG8\":[\"Recent Searches\"],\"3Jy8bM\":[\"Movies \",[\"select\"]],\"3L9OuQ\":[\"Episode \",[\"0\"]],\"3T8ziB\":[\"Create an account\"],\"3hELxX\":[\"Enter your Sofa server URL to get started\"],\"4fxLkp\":[\"Open Sonarr, go to <0>Settings > Import Lists\"],\"4kpBqM\":[\"Last event \",[\"0\"]],\"4uUjVO\":[\"Producer\"],\"4x+A56\":[\"Anonymous usage reporting\"],\"5IShvp\":[\"Import \",[\"totalItems\"],\" items\"],\"5IYJSv\":[\"Explore titles\"],\"5Y4mym\":[\"Import from \",[\"0\"]],\"5ZzgbQ\":[\"Connect \",[\"0\"]],\"5fEnbK\":[\"Failed to update scheduled backup setting\"],\"5lWFkC\":[\"Sign in\"],\"5v9C16\":[\"Connected to \",[\"source\"]],\"623bR4\":[\"Failed to trigger job\"],\"6HN0yh\":[\"Open Plex, go to Settings > Webhooks\"],\"6TNjOJ\":[\"Failed to update rating\"],\"6TfUy6\":[\"last \",[\"value\"]],\"6V3Ea3\":[\"Copied\"],\"6YtxFj\":[\"Name\"],\"6exX+8\":[\"Regenerate\"],\"6gRgw8\":[\"Retry\"],\"6lGV3K\":[\"Show less\"],\"76++pR\":[\"Warnings\"],\"77DIAu\":[\"Go to Dashboard > Plugins > Webhook\"],\"7Bj3x9\":[\"Failed\"],\"7D50KC\":[[\"label\"],\" disconnected\"],\"7GfM5w\":[\"Recently Viewed\"],\"7L01XJ\":[\"Actions\"],\"7eMo+U\":[\"Go Home\"],\"7p5kLi\":[\"Dashboard\"],\"85eoJ1\":[\"Schedule updated\"],\"8B9E2D\":[\"Day:\"],\"8YwF1J\":[\"Go to <0>Dashboard > Plugins > Webhook\"],\"8ZsakT\":[\"Password\"],\"8tjQCz\":[\"Explore\"],\"8vNtLy\":[\"Paste the Radarr URL above into the List URL field\"],\"8wYDMp\":[\"Already have an account?\"],\"9AE3vb\":[\"Open Plex, go to <0>Settings > Webhooks<1/>\"],\"9GW0ZB\":[\"Import is still running in the background. Check back later.\"],\"9NyAH9\":[\"Skipped\"],\"9Xhrps\":[\"Failed to connect\"],\"9ZLGbA\":[\"backups.\"],\"9eF5oV\":[\"Welcome back\"],\"9rG25a\":[\"Server URL\"],\"A+0rLe\":[\"Marked as completed\"],\"AOHgZp\":[\"Episodes\"],\"AOddWK\":[\"Connect \",[\"label\"]],\"ATfxL/\":[\"Trailer\"],\"AVlZoM\":[\"Environment\"],\"Acf6vF\":[\"Save name\"],\"AdoUfN\":[\"Failed to add to watchlist\"],\"AeXO77\":[\"Account\"],\"AjtYj0\":[\"On Watchlist\"],\"Avee+B\":[\"Failed to update name\"],\"B2R3xD\":[\"Toggle scheduled backups\"],\"BEVzjL\":[\"Import failed\"],\"BQnS5I\":[\"Open \",[\"source\"],\" to enter code\"],\"BUnoSB\":[[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"backup\"],\"other\":[\"backups\"]}],\" stored\"],\"BpttUI\":[\"Trending Today\"],\"BrrIs8\":[\"Storage\"],\"BskWMl\":[\"Unreachable\"],\"BzEFor\":[\"or\"],\"CGDHFb\":[\"Add a <0>Generic Destination and paste the URL above\"],\"CGExDN\":[\"Failed to purge image cache\"],\"CHeXFE\":[\"Status updated\"],\"CKyk7Q\":[\"Go back\"],\"CaB/+I\":[\"Welcome back, \",[\"name\"]],\"CodnUh\":[\"Removed from library\"],\"CpzGJY\":[\"This may take a few minutes for large libraries. Please don't close this tab.\"],\"CuPxpd\":[\"Requires an active <0>Plex Pass<1/> subscription.\"],\"CzBN6D\":[\"Start exploring\"],\"D+R2Xs\":[\"Starting import...\"],\"D3C4Yx\":[\"Current password is required\"],\"DBC3t5\":[\"Sunday\"],\"DI4lqs\":[\"Person not found\"],\"DKBbJf\":[\"Added \\\"\",[\"titleName\"],\"\\\" to watchlist\"],\"DPfwMq\":[\"Done\"],\"DZse/o\":[\"Add a \\\"Generic Destination\\\" and paste the URL above\"],\"E/QGRL\":[\"Disabled\"],\"E6nRW7\":[\"Copy URL\"],\"EWQlBH\":[\"Your library is empty\"],\"EWaCfj\":[\"Enter your current password and choose a new one.\"],\"Eeo/Gy\":[\"Failed to update setting\"],\"Efn6WU\":[\"This will delete all un-enriched stub titles and all cached images from disk. Everything will be re-imported and re-downloaded as needed.\"],\"Etp5if\":[\"Finished importing from \",[\"source\"],\".\"],\"F006BN\":[\"Import from \",[\"source\"]],\"FNvDMc\":[\"This Week\"],\"FWSp+7\":[\"Enter the code below on \",[\"source\"],\"'s website to authorize Sofa.\"],\"FXN0ro\":[\"Recommendations\"],\"FaU7Ag\":[\"Enable the \\\"Playback Stop\\\" notification type\"],\"FhvLDl\":[[\"watchedCount\"],\"/\",[\"0\"],\" episodes\"],\"Fo2bwm\":[\"Actor\"],\"FwRqjE\":[\"Image cache and backup disk usage\"],\"G00fgM\":[\"last \",[\"n\"]],\"G3myU+\":[\"Tuesday\"],\"GcCthe\":[\"In Library\"],\"Gf39AA\":[[\"0\"],\" triggered\"],\"GnhfWw\":[\"Toggle automatic update checks\"],\"GptGxg\":[\"Change password\"],\"GqTZ+S\":[\"Rated \",[\"ratingStars\"],\" \",[\"ratingStars\",\"plural\",{\"one\":[\"star\"],\"other\":[\"stars\"]}]],\"GrdN/F\":[\"Caught up — marked \",[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}],\" as watched\"],\"H4B5LG\":[\"This product uses the TMDB API but is not endorsed or certified by TMDB.\"],\"HBRd5n\":[\"Season \",[\"0\"]],\"HD+aQ7\":[\"Database backups\"],\"HG+31u\":[\"Purge all caches?\"],\"Haz+72\":[\"This will delete all cached TMDB images from disk. Images will be re-downloaded automatically as needed.\"],\"HbReP5\":[\"Marked \\\"\",[\"titleName\"],\"\\\" as completed\"],\"Hg6o8v\":[\"Refresh system health\"],\"HmEjnC\":[\"Last event \",[\"timeAgo\"]],\"HvDfH/\":[\"Imported\"],\"I89uD4\":[\"Restoring…\"],\"IRoxQm\":[\"Registration closed\"],\"IS0nrP\":[\"Create Account\"],\"IY9rQ0\":[\"Free up disk space by clearing cached metadata and images\"],\"IrC12v\":[\"Application\"],\"J28zul\":[\"Connecting...\"],\"J64cFL\":[\"Library refresh\"],\"JKmmmN\":[\"Added to watchlist\"],\"JN0f/Y\":[\"Connect to TMDB\"],\"JR6aH2\":[\"Movies \",[\"periodSelect\"]],\"JRQitQ\":[\"Confirm new password\"],\"JRadFJ\":[\"Start tracking your watches\"],\"JSwq8t\":[\"New account creation is currently disabled.\"],\"JY5Oyv\":[\"Database\"],\"JjsJnI\":[[\"0\"],\"/\",[\"1\"],\" episodes\"],\"JrFTcr\":[\"Connecting…\"],\"JwFbe8\":[\"TV\"],\"KDw4GX\":[\"Try again\"],\"KG6681\":[\"Episode watched\"],\"KIS/Sd\":[\"Sign out of other sessions\"],\"KK0ghs\":[\"Image cache\"],\"KVAoFR\":[\"unlimited\"],\"KcXJuc\":[\"Purging...\"],\"KhtG3o\":[\"Update Password\"],\"Khux7w\":[[\"0\",\"plural\",{\"one\":[\"#\",\" title\"],\"other\":[\"#\",\" titles\"]}]],\"KmWyx0\":[\"Job\"],\"KoF0x6\":[\"age \",[\"age\"]],\"Ksiej9\":[\"Requires an active Plex Pass subscription.\"],\"Kx9NEt\":[\"Invalid token\"],\"Lk6Jb/\":[\"Last polled \",[\"timeAgo\"]],\"LmEEic\":[\"Waiting for authorization...\"],\"LqKH42\":[\"Connect with \",[\"0\"]],\"Lrpjji\":[\"Disconnect \",[\"label\"]],\"Lu6Udx\":[\"Click \\\"+\\\" and select \\\"Custom Lists\\\"\"],\"MDoX++\":[\"Sonarr List URL\"],\"MRBlCJ\":[\"Failed to unmark some episodes\"],\"MUO7w9\":[\"Marked \\\"\",[\"titleName\"],\"\\\" as watched\"],\"MZbQHL\":[\"No results found.\"],\"MdxR1u\":[\"Choose your preferred display language\"],\"MkyzAV\":[\"Authorize Sofa to read your \",[\"0\"],\" library. No password shared.\"],\"N40H+G\":[\"All\"],\"N6SFhC\":[\"Sign in instead\"],\"N7h106\":[\"Sofa v\",[\"0\"],\" is available\"],\"N9RF2M\":[\"Click \\\"Add Webhook\\\" and paste the URL above\"],\"NBo4z0\":[\"Click <0>+ and select <1>Custom Lists\"],\"NICUmW\":[\"Director\"],\"NQzPoO\":[\"New backup\"],\"NSxl1l\":[\"More Settings\"],\"NVF43p\":[\"Time:\"],\"NdPMwS\":[\"In Your Library\"],\"NgaPSG\":[\"Failed to update schedule\"],\"O3oNi5\":[\"Email\"],\"OBcj5W\":[\"This will invalidate the current \",[\"label\"],\" URL. You'll need to update it in \",[\"label\"],\".\"],\"OL8hbM\":[\"New password must be at least 8 characters\"],\"ONWvwQ\":[\"Upload\"],\"OPFjyX\":[\"Install the <0>Webhook plugin<1/> from Jellyfin's plugin catalog\"],\"OW/+RD\":[\"Watch history\"],\"OZdaTZ\":[\"Person\"],\"OvO76u\":[\"Back to Login\"],\"OvdFIZ\":[\"Season watched\"],\"P2FLLe\":[\"View release\"],\"PBdLfg\":[\"No titles found for this genre.\"],\"PQ3qDa\":[\"Fetching your library data from \",[\"source\"],\"...\"],\"PjNoxI\":[\"Scheduled backup\"],\"Pn2B7/\":[\"Current password\"],\"PnEbL/\":[\"Rated \",[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"star\"],\"other\":[\"stars\"]}]],\"Pwqkdw\":[\"Loading…\"],\"Py87xY\":[\"Authorization succeeded but failed to fetch your library. Please try again.\"],\"Q3MPWA\":[\"Update password\"],\"QHcLEN\":[\"Connected\"],\"QK4UIx\":[\"Mark Watched\"],\"Qjlym2\":[\"Failed to create account\"],\"Qm1NmK\":[\"OR\"],\"Qoq+GP\":[\"Read more\"],\"QphVZW\":[\"Can't reach server\"],\"QqLJHH\":[\"This Year\"],\"R0yu2l\":[\"Catch up\"],\"R4YBui\":[\"Search for movies and TV shows to start tracking\"],\"RH46vw\":[[\"0\"],\"/\",[\"1\"],\" \",[\"2\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}]],\"RIR15/\":[\"Radarr List URL\"],\"RLe7Vk\":[\"Checking…\"],\"ROq8cl\":[\"Failed to parse file\"],\"RQq8Si\":[\"Registration opened\"],\"S0soqb\":[\"Failed to remove profile picture\"],\"S1McZh\":[\"Failed to upload avatar\"],\"S2ble5\":[[\"0\"],\" movies, \",[\"1\"],\" episodes\"],\"S2qPRR\":[\"Search movies & TV shows…\"],\"SDND4q\":[\"Not configured\"],\"SFdAk9\":[\"Unwatched S\",[\"seasonNum\"],\" E\",[\"epNum\"]],\"SUIUkB\":[\"Unwatch all\"],\"SbS+Bm\":[\"Regenerate URL\"],\"SlfejT\":[\"Error\"],\"Sp86ju\":[[\"0\"],\" ep\",[\"1\"]],\"SrfROI\":[\"Up next\"],\"SyPRjk\":[\"Sofa will automatically log movies and episodes when you finish watching them\"],\"T0/7WG\":[\"Next backup \",[\"distance\"]],\"TEaX6q\":[\"Backup deleted\"],\"TI7IKo\":[\"Rated \",[\"0\"],\" star\",[\"1\"]],\"TMb7iE\":[\"Profile picture removed\"],\"TqyQQS\":[\"Enable the <0>Playback event category\"],\"Tz0i8g\":[\"Settings\"],\"U+FxtW\":[\"Track what you watch. Know what's next.<0/>Your library, your data, your rules.\"],\"U3pytU\":[\"Admin\"],\"UC+OWB\":[\"Toggle open registration\"],\"UDMjsP\":[\"Quick Actions\"],\"UmQ6Fe\":[\"Purge image cache?\"],\"UtDm3q\":[\"URL copied to clipboard\"],\"V1Kh9Z\":[\"Episodes \",[\"select\"]],\"V6uzvC\":[\"More Like This\"],\"V9CuQ+\":[\"Connect to \",[\"source\"]],\"VAcXNz\":[\"Wednesday\"],\"VKyhZK\":[\"Title not found\"],\"VQvpro\":[\"Paste the Sonarr URL above into the List URL field\"],\"VhMDMg\":[\"Change Password\"],\"Vx0ayx\":[\"Failed to mark some episodes\"],\"WDC4PF\":[\"Episodes \",[\"periodSelect\"]],\"WEYdDv\":[\"Recommended\"],\"WMCwmR\":[\"Check for updates\"],\"WP48q2\":[\"Image Cache\"],\"WT1Ibn\":[\"Last run\"],\"Wb3E4g\":[\"Run now\"],\"WgF2UQ\":[\"You're running v\",[\"0\"],\".\"],\"WtWhSi\":[\"Rating removed\"],\"Wy/3II\":[\"Last polled \",[\"0\"]],\"XFCSYs\":[\"Cast\"],\"XN23JY\":[\"This will delete un-enriched stub titles that aren't in any user's library and clean up orphaned person records. Deleted titles will be re-imported if accessed again.\"],\"XZwihE\":[\"Ready — nothing received yet\"],\"XjTduw\":[\"Upload picture\"],\"YEGzVq\":[\"Failed to connect \",[\"label\"]],\"YErf89\":[\"Failed to purge metadata cache\"],\"YQ768h\":[\"Scene not found\"],\"YiRsXK\":[\"Clear Recently Viewed?\"],\"Yjp1zf\":[\"Don't have a server?\"],\"YqMfa9\":[\"Review what was found and choose what to import.\"],\"ZGUYm0\":[\"Ready — not polled yet\"],\"ZQKLI1\":[\"Danger Zone\"],\"ZVZUoU\":[[\"0\"],\" cached images\"],\"ZWTQ81\":[\"Watch on \",[\"name\"]],\"a3LDKx\":[\"Security\"],\"aLBUiR\":[\"Keeping <0><1><2>\",[\"0\"],\"<3>\",[\"1\"],\" backups.\"],\"aM0+kY\":[\"Purged \",[\"0\",\"plural\",{\"one\":[\"#\",\" stale title\"],\"other\":[\"#\",\" stale titles\"]}],\" and \",[\"1\",\"plural\",{\"one\":[\"#\",\" orphaned person\"],\"other\":[\"#\",\" orphaned persons\"]}]],\"aO1AxG\":[\"Episode unwatched\"],\"aV/hDI\":[\"Failed to disconnect \",[\"label\"]],\"acbSg0\":[\"Double tap to filter by \",[\"label\"]],\"agE7k4\":[\"Rated \",[\"stars\",\"plural\",{\"one\":[\"#\",\" star\"],\"other\":[\"#\",\" stars\"]}]],\"alPRaV\":[\"Titles on your Sofa watchlist will be automatically added for download when Radarr polls this list (every 12 hours by default)\"],\"aourBv\":[\"Scheduled backups enabled\"],\"apLLSU\":[\"Are you sure you want to sign out?\"],\"atc9MA\":[\"Open Emby, go to <0>Settings > Webhooks\"],\"auVUJO\":[\"Restore database?\"],\"ayqfr4\":[[\"label\"],\" URL regenerated\"],\"b/8KCH\":[\"This will mark every episode of this show as watched. You can undo this later by unmarking individual seasons.\"],\"b3Thhd\":[\"Upload failed\"],\"b5DFtH\":[\"Set password\"],\"bHYIks\":[\"Sign Out\"],\"bITrbE\":[\"Purge all\"],\"bNmdjU\":[\"Watchlist\"],\"bTRwag\":[\"Remove from Library\"],\"bXMotV\":[\"Marked as watched\"],\"bezGJ2\":[[\"0\"],\" images\"],\"boJlGf\":[\"Page Not Found\"],\"c1ssjI\":[\"Importing from \",[\"source\"]],\"c3b0B0\":[\"Get Started\"],\"c4b9Dm\":[\"No results for \\\"\",[\"debouncedQuery\"],\"\\\"\"],\"cQPKZt\":[\"Go to Dashboard\"],\"ccAJSB\":[\"Keeping\"],\"cgvva8\":[[\"0\"],\" ratings\"],\"cnGeoo\":[\"Delete\"],\"cpE88+\":[\"Create your account\"],\"d/6MoL\":[\"Manage your account and preferences\"],\"dA7BWh\":[\"Requires Emby Server 4.7.9+ and an active Emby Premiere license.\"],\"dEgA5A\":[\"Cancel\"],\"dTZwve\":[\"Marked all episodes as watched\"],\"dUCJry\":[\"Newest\"],\"ddwpAr\":[\"Warnings (\",[\"0\"],\")\"],\"e/ToF5\":[\"Sign in with \",[\"0\"]],\"e9sZMS\":[\"This will permanently delete the backup from <0>\",[\"0\"],\". This cannot be undone.\"],\"eFRooE\":[\"Last run failed\"],\"eM39Om\":[\"Watched all of \",[\"seasonLabel\"]],\"eZjYb8\":[\"Writer\"],\"ecUA8p\":[\"Today\"],\"evBxZy\":[\"Where to Watch\"],\"ezDa1h\":[\"Open docs\"],\"f+m696\":[\"An unexpected error occurred while loading this page. You can try again or head back to the dashboard.\"],\"f/AKdU\":[\"Are you sure you want to disconnect \",[\"label\"],\"? The current URL will stop working.\"],\"f7ax8J\":[\"Open in browser…\"],\"fMPkxb\":[\"Show more\"],\"fNMqNn\":[\"Popular TV Shows\"],\"fObVvy\":[\"Profile picture updated\"],\"fRettQ\":[[\"0\"],\" items\"],\"fUDRF9\":[\"Watchlisted\"],\"fcWrnU\":[\"Sign out\"],\"fgLNSM\":[\"Register\"],\"fsAEqk\":[\"Continue Watching\"],\"fzAeQI\":[\"Registering on \",[\"serverHost\"]],\"g+gBfk\":[\"Parsing...\"],\"g4JYff\":[\"Imported \",[\"0\"],\" items from \",[\"1\"]],\"gKtb5i\":[\"Titles on your Sofa watchlist will be automatically added for download when Sonarr polls this list (every 6 hours by default)\"],\"gaIAMq\":[\"Remove picture\"],\"gbEEMp\":[\"Delete backup\"],\"gcNLi0\":[\"Update checks enabled\"],\"gmB6oO\":[\"Schedule\"],\"h/T5Yb\":[\"Open registration\"],\"h4yKYk\":[\"Next run\"],\"h7MgpO\":[\"Keyboard Shortcuts\"],\"hTXYdY\":[\"Failed to create backup\"],\"hXYY5Q\":[\"Unwatched all of \",[\"0\"]],\"he3ygx\":[\"Copy\"],\"hjGupC\":[\"Lost connection to import. Check status in settings.\"],\"hlqjFc\":[\"In library\"],\"hou0tP\":[\"Stream\"],\"hty0d5\":[\"Monday\"],\"i0qMbr\":[\"Home\"],\"i9rcQ/\":[\"Movies\"],\"iGma9e\":[\"Update failed\"],\"iSLIjg\":[\"Connect\"],\"iWv3ck\":[\"Failed to catch up\"],\"iXZ09g\":[\"Mark as Completed\"],\"id08cd\":[\"Watched S\",[\"seasonNum\"],\" E\",[\"epNum\"]],\"ih87w/\":[\"Add to Library\"],\"ihn4zD\":[\"Search…\"],\"itDEco\":[\"Already have an account? Sign in\"],\"itheEn\":[\"With Ads\"],\"iwCRIF\":[\"You'll be signed out to change the server URL.\"],\"j5GBIy\":[\"Open Radarr, go to Settings > Import Lists\"],\"jAe8l4\":[[\"0\"],\" backups · last <0/>\"],\"jB3sfe\":[\"Manual backup\"],\"jFnMJ8\":[\"Double tap to clear this filter\"],\"jQsPwL\":[\"Scheduled backups disabled\"],\"jSjGeu\":[[\"0\"],\" items have no external IDs and will be resolved by title search, which may be less accurate.\"],\"jX6Gzg\":[\"This will replace your entire database with the uploaded file. A safety backup of your current data will be created first. Active sessions may need to refresh after restore.\"],\"jiHVUy\":[\"Pre-restore backup\"],\"k6c41p\":[\"Background jobs\"],\"kBDOjB\":[\"Backup schedule\"],\"kkDQ8m\":[\"Thursday\"],\"klOeIX\":[\"Failed to change password\"],\"ks3XeZ\":[\"Open Sonarr, go to Settings > Import Lists\"],\"kvuCtu\":[\"Something went wrong while loading this title. Please try again.\"],\"kx0s+n\":[\"Results\"],\"l2wcoS\":[\"Last run succeeded\"],\"l3s5ri\":[\"Import\"],\"lCF0wC\":[\"Refresh\"],\"lJ1yo4\":[\"Failed to sign in\"],\"lVqzRx\":[\"Click <0>Add Webhook and paste the URL above\"],\"lXkUEV\":[\"Availability\"],\"lcLe89\":[\"Search for movies, TV shows, or run commands\"],\"lfVyvz\":[\"Add to Watchlist\"],\"llDXYJ\":[\"Backups\"],\"llkZR0\":[\"Could not load integrations\"],\"ln9/n9\":[\"Don't have an account?\"],\"lpIMne\":[\"Passwords do not match\"],\"lqY3WY\":[\"Change Server\"],\"luoodD\":[\"Setup required\"],\"m5WhJy\":[\"Automatic update checks\"],\"mIx58S\":[\"Purge images\"],\"mYBORk\":[\"Movie\"],\"ml9cU0\":[\"Failed to regenerate \",[\"label\"],\" URL\"],\"mqwkjd\":[\"Health status\"],\"mqxHH7\":[\"Go to Explore\"],\"mzI/c+\":[\"Download\"],\"n1ekoW\":[\"Sign In\"],\"n3Pzd7\":[\"Backup created\"],\"nBHvPL\":[\"Cancel editing\"],\"nJw77c\":[[\"0\",\"plural\",{\"one\":[\"#\",\" image\"],\"other\":[\"#\",\" images\"]}]],\"nO0Qnj\":[\"Your code:\"],\"nRP1xx\":[\"Need more help?\"],\"nV1LjT\":[\"Enable the <0>Playback Stop notification type\"],\"nVsE67\":[\"Mark as Watching\"],\"nZ7lF1\":[\"Device code expired. Please try again.\"],\"nbfdhU\":[\"Integrations\"],\"nbmpf9\":[\"Purge metadata\"],\"nnKJTm\":[\"Failed to mark all episodes as watched\"],\"nszbQG\":[\"No backups yet\"],\"nuh/Wq\":[\"Webhook URL\"],\"nwtY4N\":[\"Something went wrong\"],\"oAIA3w\":[\"Search for movies, shows, or people\"],\"oC8IMh\":[\"Search movies, shows, people...\"],\"oNvZcA\":[\"Episodes this week\"],\"oXq+Wr\":[[\"label\"],\" connected\"],\"ogtYkT\":[\"Password updated\"],\"ojtedN\":[\"Open Radarr, go to <0>Settings > Import Lists\"],\"olMi35\":[\"Recommended for You\"],\"p+PZEl\":[\"Here's what's happening with your library\"],\"pAtylB\":[\"Not Found\"],\"pG9pq1\":[\"Deleted \",[\"0\",\"plural\",{\"one\":[\"#\",\" file\"],\"other\":[\"#\",\" files\"]}],\", freed \",[\"freed\"]],\"pWWFjv\":[\"Checked <0/>\"],\"ph76H6\":[\"Allow new users to create accounts\"],\"pjgw0N\":[\"Choose how to import your \",[\"0\"],\" data.\"],\"puo3W3\":[\"Redirecting…\"],\"q3GraM\":[\"...and \",[\"0\"],\" more\"],\"q6nrFE\":[\"died at \",[\"age\"]],\"q6pUQ9\":[\"Upload a .db file to replace the current database. A safety backup is created first.\"],\"q8yluz\":[\"Your name\"],\"qA6VR5\":[\"Requires <0>Emby Server 4.7.9+ and an active <1>Emby Premiere<2/> license.\"],\"qF2IBM\":[\"Movies this month\"],\"qHbApt\":[\"Failed to load backup schedule settings.\"],\"qLB1zX\":[\"Upload a \",[\"0\"],\" export from your \",[\"1\"],\" account settings.\"],\"qPNzfu\":[\"Update checks disabled\"],\"qWoML/\":[\"Add a new webhook and paste the URL above\"],\"qiOIiY\":[\"Buy\"],\"qn1X6N\":[\"Failed to update registration setting\"],\"qqWcBV\":[\"Completed\"],\"qqeAJM\":[\"Never\"],\"r9aDAY\":[[\"count\"],\" earlier \",[\"count\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}],\" unwatched\"],\"rLgPvm\":[\"Backup\"],\"rSTpb5\":[\"Server Health\"],\"rdymVD\":[\"Trigger job\"],\"rtir7c\":[\"unknown\"],\"s0U4ZZ\":[\"TV episodes\"],\"s4vVUm\":[\"Could not load person details\"],\"s9dVME\":[\"Failed to unmark episode\"],\"sGH11W\":[\"Server\"],\"sl/qWH\":[\"Upload profile picture\"],\"sstysK\":[\"Failed to mark episode\"],\"stZeiF\":[[\"watched\"],\"/\",[\"total\"],\" episodes\"],\"t/Ch8S\":[\"Marked as watching\"],\"t/YqKh\":[\"Remove\"],\"tfDRzk\":[\"Save\"],\"tiymc0\":[\"Something went wrong…\"],\"u9ugU7\":[[\"watchedCount\"],\"/\",[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}]],\"uBAxNB\":[\"Editor\"],\"uBrbSu\":[\"Failed to start \",[\"0\"],\" connection\"],\"uM6jnS\":[\"Restore failed\"],\"uMrJrX\":[\"Admin only\"],\"uswLvZ\":[\"The title you're looking for doesn't exist or may have been removed from the database.\"],\"uxOntd\":[[\"watchedCount\"],\" of \",[\"0\"],\" episodes watched\"],\"v2CA3w\":[\"Change Photo\"],\"v5ipVu\":[\"Mark all episodes as watched?\"],\"vKfeax\":[\"Open Emby, go to Settings > Webhooks\"],\"vXIe7J\":[\"Language\"],\"vuCCZ7\":[\"Failed to parse import data\"],\"vwKERN\":[\"Failed to delete backup\"],\"w8pqsh\":[\"Mark All Watched\"],\"wA7B2T\":[\"New accounts are not being accepted right now. Contact the admin if you need access.\"],\"wGFX13\":[\"Starting at\"],\"wR1UAy\":[\"Popular Movies\"],\"wRWcdL\":[\"Play trailer\"],\"wThGrS\":[\"App Settings\"],\"wZK4Xg\":[\"Never run\"],\"wdLxgL\":[\"Member since \",[\"memberSince\"]],\"wja8aL\":[\"Untitled\"],\"wtGebH\":[\"The person you're looking for doesn't exist or may have been removed from the database.\"],\"wtsbt5\":[\"Add to watchlist\"],\"wtuVU4\":[\"Frequency\"],\"wx7pwA\":[\"Mark as Watched\"],\"x4ZiTl\":[\"Setup instructions\"],\"xCJdfg\":[\"Clear\"],\"xGVfLh\":[\"Continue\"],\"xLoCm2\":[\"Check configuration\"],\"xSrU2g\":[[\"healthyCount\"],\" of \",[\"0\"],\" jobs healthy\"],\"xazqmy\":[\"Seasons\"],\"xbBXhy\":[\"Periodically check GitHub for new Sofa releases\"],\"xrh2/M\":[\"Failed to mark as watched\"],\"y3e9pF\":[\"Delete backup?\"],\"y6Urel\":[\"Next Episode\"],\"yGvjAo\":[\"Update available: \",[\"0\"]],\"yKu/3Y\":[\"Restore\"],\"yYxB17\":[\"Clear all\"],\"ybtG1U\":[[\"0\"],\" backup\",[\"1\"],\" stored\"],\"yey/zg\":[\"Database restored. Reloading...\"],\"ygo0l/\":[\"Start tracking movies and shows\"],\"yvwIbI\":[\"Upload export file\"],\"yz7wBu\":[\"Close\"],\"z1U/Fh\":[\"Rating\"],\"z5evln\":[\"No internet connection\"],\"zAvS8w\":[\"Sign in to continue\"],\"zEqK2w\":[\"The page you're looking for doesn't exist.\"],\"zFkiTv\":[\"Install the Webhook plugin from Jellyfin's plugin catalog\"],\"zNyR4f\":[\"Set your preferred quality profile and root folder\"],\"za8Le/\":[\"Registration Closed\"],\"zb77GC\":[\"Rent\"],\"ztAdhw\":[\"Name updated\"]}")as Messages; \ No newline at end of file diff --git a/packages/i18n/src/po/pt.po b/packages/i18n/src/po/pt.po new file mode 100644 index 0000000..d586cfa --- /dev/null +++ b/packages/i18n/src/po/pt.po @@ -0,0 +1,2433 @@ +msgid "" +msgstr "" +"POT-Creation-Date: 2026-03-17 21:04-0400\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: @lingui/cli\n" +"Language: pt\n" +"Project-Id-Version: sofa\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2026-03-18 16:04\n" +"Last-Translator: \n" +"Language-Team: Portuguese\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Crowdin-Project: sofa\n" +"X-Crowdin-Project-ID: 881052\n" +"X-Crowdin-Language: pt-PT\n" +"X-Crowdin-File: /packages/i18n/src/po/en.po\n" +"X-Crowdin-File-ID: 12\n" + +#. placeholder {0}: result.errors.length - 50 +#: apps/web/src/components/settings/imports-section.tsx:938 +msgid "...and {0} more" +msgstr "" + +#. placeholder {0}: systemHealth.data.imageCache.imageCount +#: apps/native/src/app/(tabs)/(settings)/index.tsx:482 +msgid "{0, plural, one {# image} other {# images}}" +msgstr "" + +#. placeholder {0}: systemHealth.data.database.titleCount +#: apps/native/src/app/(tabs)/(settings)/index.tsx:466 +msgid "{0, plural, one {# title} other {# titles}}" +msgstr "" + +#. placeholder {0}: displayBackups.length +#. placeholder {1}: displayBackups.length +#: apps/web/src/components/settings/backup-section.tsx:109 +msgid "{0} {1, plural, one {backup} other {backups}} stored" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:105 +#~ msgid "{0} backup{1} stored" +#~ msgstr "{0} backup{1} stored" + +#. placeholder {0}: backups.backupCount +#: apps/web/src/components/settings/system-health-section.tsx:654 +msgid "{0} backups · last <0/>" +msgstr "" + +#. placeholder {0}: imageCache.imageCount.toLocaleString() +#: apps/web/src/components/settings/system-health-section.tsx:621 +msgid "{0} cached images" +msgstr "" + +#. placeholder {0}: member.episodeCount +#. placeholder {1}: member.episodeCount !== 1 ? "s" : "" +#: apps/web/src/components/titles/cast-carousel.tsx:80 +msgid "{0} ep{1}" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:472 +#~ msgid "{0} images" +#~ msgstr "{0} images" + +#. placeholder {0}: stats.watchlist +#: apps/web/src/components/settings/imports-section.tsx:808 +msgid "{0} items" +msgstr "" + +#. placeholder {0}: preview.diagnostics.unresolved +#: apps/web/src/components/settings/imports-section.tsx:788 +msgid "{0} items have no external IDs and will be resolved by title search, which may be less accurate." +msgstr "" + +#. placeholder {0}: stats.movies +#. placeholder {1}: stats.episodes +#: apps/web/src/components/settings/imports-section.tsx:802 +msgid "{0} movies, {1} episodes" +msgstr "" + +#. placeholder {0}: stats.ratings +#: apps/web/src/components/settings/imports-section.tsx:814 +msgid "{0} ratings" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:456 +#~ msgid "{0} titles" +#~ msgstr "{0} titles" + +#. placeholder {0}: JOB_LABELS[name] ?? name +#: apps/web/src/components/settings/system-health-section.tsx:343 +msgid "{0} triggered" +msgstr "" + +#. placeholder {0}: item.watchedEpisodes +#. placeholder {1}: item.totalEpisodes +#. placeholder {2}: item.totalEpisodes +#: apps/web/src/components/dashboard/continue-watching-card.tsx:93 +msgid "{0}/{1} {2, plural, one {episode} other {episodes}}" +msgstr "" + +#: apps/web/src/components/dashboard/continue-watching-card.tsx:92 +#~ msgid "{0}/{1} episodes" +#~ msgstr "{0}/{1} episodes" + +#: apps/web/src/components/titles/use-title-actions.ts:221 +#: apps/web/src/components/titles/use-title-actions.ts:299 +msgid "{count} earlier {count, plural, one {episode} other {episodes}} unwatched" +msgstr "" + +#. placeholder {0}: activeJobs.length +#: apps/web/src/components/settings/system-health-section.tsx:385 +msgid "{healthyCount} of {0} jobs healthy" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:96 +#: apps/web/src/components/settings/integration-card.tsx:106 +msgid "{label} connected" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:106 +#: apps/web/src/components/settings/integration-card.tsx:122 +msgid "{label} disconnected" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:116 +#: apps/web/src/components/settings/integration-card.tsx:138 +msgid "{label} URL regenerated" +msgstr "" + +#: apps/web/src/components/title-card.tsx:166 +msgid "{watched}/{total} episodes" +msgstr "" + +#. placeholder {0}: episodes.length +#: apps/native/src/components/titles/season-accordion.tsx:122 +msgid "{watchedCount} of {0} episodes watched" +msgstr "" + +#. placeholder {0}: episodes.length +#. placeholder {1}: episodes.length +#: apps/native/src/components/titles/season-accordion.tsx:131 +msgid "{watchedCount}/{0} {1, plural, one {episode} other {episodes}}" +msgstr "" + +#: apps/native/src/components/titles/season-accordion.tsx:130 +#~ msgid "{watchedCount}/{0} episodes" +#~ msgstr "{watchedCount}/{0} episodes" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:231 +#: apps/web/src/components/settings/account-section.tsx:175 +msgid "Account" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:412 +msgid "Actions" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:71 +msgid "Actor" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:72 +msgid "Add a \"Generic Destination\" and paste the URL above" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:131 +msgid "Add a <0>Generic Destination and paste the URL above" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:92 +#: apps/web/src/components/settings/integration-configs.tsx:198 +msgid "Add a new webhook and paste the URL above" +msgstr "" + +#: apps/web/src/components/explore/hero-banner.tsx:99 +msgid "Add to Library" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:72 +msgid "Add to watchlist" +msgstr "" + +#: apps/native/src/components/explore/hero-banner.tsx:157 +#: apps/native/src/components/ui/poster-card.tsx:238 +#: apps/web/src/components/title-card.tsx:146 +msgid "Add to Watchlist" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:23 +msgid "Added \"{titleName}\" to watchlist" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:44 +#: apps/native/src/hooks/use-title-actions.ts:60 +#: apps/native/src/lib/title-actions.ts:24 +#: apps/web/src/components/titles/use-title-actions.ts:111 +msgid "Added to watchlist" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:353 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:454 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:499 +#: apps/web/src/components/nav-bar.tsx:235 +#: apps/web/src/components/settings/account-section.tsx:335 +msgid "Admin" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:124 +msgid "Admin only" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:308 +#: apps/web/src/components/people/person-hero.tsx:91 +msgid "age {age}" +msgstr "" + +#: apps/native/src/components/explore/filterable-title-row.tsx:119 +#: apps/web/src/components/people/filmography-grid.tsx:58 +msgid "All" +msgstr "" + +#: apps/web/src/components/settings/registration-section.tsx:61 +msgid "Allow new users to create accounts" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:278 +msgid "Already have an account?" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:239 +msgid "Already have an account? Sign in" +msgstr "" + +#: apps/web/src/routes/__root.tsx:146 +msgid "An unexpected error occurred while loading this page. You can try again or head back to the dashboard." +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:427 +msgid "Anonymous usage reporting" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:102 +msgid "App Settings" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:378 +msgid "Application" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:151 +msgid "Are you sure you want to disconnect {label}? The current URL will stop working." +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:206 +#: apps/native/src/components/header-avatar.tsx:70 +msgid "Are you sure you want to sign out?" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:450 +msgid "Authorization succeeded but failed to fetch your library. Please try again." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:440 +msgid "Authorization was denied. Please try again." +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:611 +msgid "Authorize Sofa to read your {0} library. No password shared." +msgstr "" + +#: apps/web/src/components/settings/update-check-section.tsx:60 +msgid "Automatic update checks" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:279 +msgid "Automatically back up your database on a schedule" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:332 +msgid "Availability" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:104 +msgid "Back to Login" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:382 +msgid "Background jobs" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:336 +msgid "Backup" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:69 +msgid "Backup created" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:94 +msgid "Backup deleted" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:234 +msgid "Backup schedule" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:641 +#: apps/web/src/routes/_app/settings.tsx:166 +msgid "Backups" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:272 +#~ msgid "backups." +#~ msgstr "backups." + +#: apps/web/src/components/titles/title-availability.tsx:139 +msgid "Buy" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:89 +msgid "Cache management" +msgstr "" + +#: apps/native/src/components/ui/server-unreachable-banner.ios.tsx:66 +#: apps/native/src/components/ui/server-unreachable-banner.tsx:74 +msgid "Can't reach server" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:207 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:332 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:388 +#: apps/native/src/components/header-avatar.tsx:71 +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:63 +#: apps/native/src/components/search/recently-viewed-list.tsx:37 +#: apps/native/src/components/settings/integration-card.tsx:138 +#: apps/native/src/components/settings/integration-card.tsx:153 +#: apps/web/src/components/settings/account-section.tsx:505 +#: apps/web/src/components/settings/backup-restore-section.tsx:107 +#: apps/web/src/components/settings/backup-section.tsx:270 +#: apps/web/src/components/settings/danger-section.tsx:129 +#: apps/web/src/components/settings/danger-section.tsx:171 +#: apps/web/src/components/settings/danger-section.tsx:214 +#: apps/web/src/components/settings/imports-section.tsx:645 +#: apps/web/src/components/settings/imports-section.tsx:710 +#: apps/web/src/components/settings/imports-section.tsx:837 +#: apps/web/src/components/titles/title-seasons.tsx:133 +msgid "Cancel" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:307 +msgid "Cancel editing" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:559 +#: apps/web/src/components/titles/cast-carousel.tsx:21 +msgid "Cast" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:223 +#: apps/web/src/components/titles/use-title-actions.ts:301 +msgid "Catch up" +msgstr "" + +#. placeholder {0}: episodeIds.length +#. placeholder {1}: episodeIds.length +#: apps/web/src/components/titles/use-title-actions.ts:82 +msgid "Caught up — marked {0} {1, plural, one {episode} other {episodes}} as watched" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:361 +#: apps/web/src/components/settings/account-section.tsx:427 +#: apps/web/src/components/settings/account-section.tsx:432 +msgid "Change password" +msgstr "" + +#: apps/native/src/app/change-password.tsx:98 +msgid "Change Password" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:267 +msgid "Change Photo" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:385 +msgid "Change Server" +msgstr "" + +#: apps/web/src/components/setup/refresh-button.tsx:30 +msgid "Check configuration" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:513 +msgid "Check for updates" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:215 +msgid "Checked <0/>" +msgstr "" + +#: apps/web/src/components/setup/refresh-button.tsx:28 +msgid "Checking…" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:587 +msgid "Choose how to import your {0} data." +msgstr "" + +#: apps/web/src/components/settings/language-section.tsx:29 +#~ msgid "Choose your preferred display language" +#~ msgstr "Choose your preferred display language" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:65 +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:115 +#: apps/native/src/components/search/recently-viewed-list.tsx:39 +#: apps/native/src/components/search/recently-viewed-list.tsx:95 +msgid "Clear" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:322 +msgid "Clear all" +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:60 +#: apps/native/src/components/search/recently-viewed-list.tsx:34 +msgid "Clear Recently Viewed?" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:109 +#: apps/native/src/components/settings/integration-configs.ts:127 +msgid "Click \"+\" and select \"Custom Lists\"" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:55 +msgid "Click \"Add Webhook\" and paste the URL above" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:237 +#: apps/web/src/components/settings/integration-configs.tsx:277 +msgid "Click <0>+ and select <1>Custom Lists" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:79 +msgid "Click <0>Add Webhook and paste the URL above" +msgstr "" + +#: apps/native/src/components/navigation/modal-stack-header.tsx:14 +msgid "Close" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:63 +#: apps/native/src/components/titles/status-action-button.tsx:44 +#: apps/web/src/components/dashboard/stats-display.tsx:238 +#: apps/web/src/components/title-card.tsx:77 +#: apps/web/src/components/titles/status-button.tsx:30 +msgid "Completed" +msgstr "" + +#: apps/native/src/app/change-password.tsx:173 +#: apps/web/src/components/settings/account-section.tsx:477 +msgid "Confirm new password" +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:207 +#: apps/web/src/components/settings/imports-section.tsx:499 +msgid "Connect" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/integration-card.tsx:203 +msgid "Connect {0}" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:238 +msgid "Connect {label}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:665 +msgid "Connect to {source}" +msgstr "" + +#: apps/web/src/routes/setup.tsx:101 +msgid "Connect to TMDB" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:608 +msgid "Connect with {0}" +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:197 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:474 +#: apps/web/src/components/settings/system-health-section.tsx:256 +msgid "Connected" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:722 +msgid "Connected to {source}" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:240 +msgid "Connected to <0>{serverHost}. Tap to change." +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:186 +msgid "Connecting to server..." +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:201 +msgid "Connecting..." +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:237 +msgid "Connecting…" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:390 +msgid "Continue" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:113 +#: apps/web/src/components/dashboard/continue-watching-section.tsx:25 +msgid "Continue Watching" +msgstr "" + +#: apps/web/src/components/setup/copy-button.tsx:25 +msgid "Copied" +msgstr "" + +#: apps/web/src/components/setup/copy-button.tsx:30 +msgid "Copy" +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:246 +msgid "Copy URL" +msgstr "" + +#: apps/native/src/components/settings/integrations-section.tsx:29 +msgid "Could not load integrations" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:216 +msgid "Could not load person details" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:247 +msgid "Create account" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:115 +#: apps/native/src/app/(auth)/register.tsx:225 +msgid "Create Account" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:216 +msgid "Create an account" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:110 +msgid "Create your account" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:137 +msgid "Creating…" +msgstr "" + +#: apps/native/src/app/change-password.tsx:120 +#: apps/web/src/components/settings/account-section.tsx:449 +msgid "Current password" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:391 +msgid "Current password is required" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:195 +msgid "Danger Zone" +msgstr "" + +#: apps/web/src/routes/__root.tsx:167 +#: apps/web/src/routes/_app/people.$id.tsx:78 +#: apps/web/src/routes/_app/titles.$id.tsx:126 +#: apps/web/src/routes/_app/titles.$id.tsx:172 +msgid "Dashboard" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:463 +#: apps/web/src/components/settings/system-health-section.tsx:229 +msgid "Database" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:126 +msgid "Database backups" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:31 +msgid "Database restored. Reloading..." +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:342 +msgid "Day:" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:250 +#: apps/web/src/components/settings/backup-section.tsx:280 +msgid "Delete" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:235 +msgid "Delete backup" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:256 +msgid "Delete backup?" +msgstr "" + +#. placeholder {0}: data.deletedFiles +#: apps/web/src/components/settings/danger-section.tsx:54 +msgid "Deleted {0, plural, one {# file} other {# files}}, freed {freed}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:412 +#: apps/web/src/components/settings/imports-section.tsx:444 +msgid "Device code expired. Please try again." +msgstr "" + +#: apps/native/src/app/person/[id].tsx:307 +#: apps/web/src/components/people/person-hero.tsx:91 +msgid "died at {age}" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:73 +msgid "Director" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:431 +#: apps/web/src/components/settings/system-health-section.tsx:632 +msgid "Disabled" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:155 +#: apps/native/src/components/settings/integration-card.tsx:298 +#: apps/web/src/components/settings/integration-card.tsx:274 +msgid "Disconnect" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:150 +msgid "Disconnect {label}" +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:242 +msgid "Don't have a server?" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:288 +msgid "Don't have an account?" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:962 +msgid "Done" +msgstr "" + +#: apps/native/src/components/explore/genre-chip.tsx:27 +msgid "Double tap to clear this filter" +msgstr "" + +#: apps/native/src/components/explore/genre-chip.tsx:28 +msgid "Double tap to filter by {label}" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:71 +#: apps/web/src/components/settings/backup-section.tsx:223 +msgid "Download" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:214 +msgid "Download backup" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:79 +msgid "Editor" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:133 +#: apps/native/src/app/(auth)/register.tsx:154 +#: apps/web/src/components/auth-form.tsx:201 +msgid "Email" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:73 +msgid "Enable the \"Playback Stop\" notification type" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:93 +msgid "Enable the \"Playback\" event category" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:140 +msgid "Enable the <0>Playback Stop notification type" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:201 +msgid "Enable the <0>Playback event category" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:668 +msgid "Enter the code below on {source}'s website to authorize Sofa." +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:435 +msgid "Enter your current password and choose a new one." +msgstr "" + +#: apps/native/src/app/(auth)/server-url.tsx:151 +msgid "Enter your Sofa server URL to get started" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:289 +msgid "Environment" +msgstr "" + +#. placeholder {0}: episode.episodeNumber +#: apps/native/src/components/titles/episode-row.tsx:29 +#: apps/native/src/components/titles/episode-row.tsx:55 +msgid "Episode {0}" +msgstr "" + +#. placeholder {0}: episode.episodeNumber +#: apps/native/src/components/titles/episode-row.tsx:36 +msgid "Episode {0}, {episodeLabel}" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:123 +#: apps/native/src/lib/title-actions.ts:111 +msgid "Episode unwatched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:109 +#: apps/native/src/lib/title-actions.ts:101 +msgid "Episode watched" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:780 +#: apps/web/src/components/titles/title-seasons.tsx:102 +msgid "Episodes" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:162 +#~ msgid "Episodes {periodSelect}" +#~ msgstr "Episodes {periodSelect}" + +#: apps/web/src/components/dashboard/stats-display.tsx:164 +msgid "Episodes {select}" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:61 +msgid "Episodes this week" +msgstr "" + +#: apps/native/src/app/change-password.tsx:71 +#: apps/native/src/app/change-password.tsx:81 +msgid "Error" +msgstr "" + +#. placeholder {0}: result.errors.length +#: apps/web/src/components/settings/imports-section.tsx:929 +msgid "Errors ({0})" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:143 +#: apps/web/src/components/nav-bar.tsx:126 +#: apps/web/src/components/nav-bar.tsx:291 +msgid "Explore" +msgstr "" + +#: apps/web/src/routes/_app/people.$id.tsx:70 +#: apps/web/src/routes/_app/titles.$id.tsx:164 +msgid "Explore titles" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:923 +msgid "Failed" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:49 +#: apps/native/src/lib/title-actions.ts:28 +msgid "Failed to add to watchlist" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:90 +msgid "Failed to catch up" +msgstr "" + +#: apps/native/src/app/change-password.tsx:71 +#: apps/web/src/components/settings/account-section.tsx:411 +msgid "Failed to change password" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:398 +msgid "Failed to connect" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:99 +#: apps/web/src/components/settings/integration-card.tsx:108 +msgid "Failed to connect {label}" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:67 +msgid "Failed to create account" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:81 +msgid "Failed to create backup" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:97 +msgid "Failed to delete backup" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:109 +#: apps/web/src/components/settings/integration-card.tsx:125 +msgid "Failed to disconnect {label}" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:215 +msgid "Failed to load backup schedule settings." +msgstr "" + +#: apps/web/src/routes/_app/titles.$id.tsx:115 +msgid "Failed to load title" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:367 +msgid "Failed to mark all episodes as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:84 +#: apps/native/src/lib/title-actions.ts:68 +#: apps/web/src/components/titles/use-title-actions.ts:150 +msgid "Failed to mark as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:113 +#: apps/native/src/lib/title-actions.ts:104 +#: apps/web/src/components/titles/use-title-actions.ts:237 +msgid "Failed to mark episode" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:141 +#: apps/native/src/lib/title-actions.ts:128 +#: apps/web/src/components/titles/use-title-actions.ts:315 +msgid "Failed to mark some episodes" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:225 +msgid "Failed to parse file" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:240 +msgid "Failed to parse import data" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:75 +msgid "Failed to purge caches" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:58 +msgid "Failed to purge image cache" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:45 +msgid "Failed to purge metadata cache" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:119 +#: apps/web/src/components/settings/integration-card.tsx:140 +msgid "Failed to regenerate {label} URL" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:78 +msgid "Failed to remove from library" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:134 +#: apps/web/src/components/settings/account-section.tsx:134 +msgid "Failed to remove profile picture" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:56 +msgid "Failed to sign in" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:388 +msgid "Failed to start {0} connection" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:348 +msgid "Failed to trigger job" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:129 +#: apps/native/src/lib/title-actions.ts:114 +#: apps/web/src/components/titles/use-title-actions.ts:181 +msgid "Failed to unmark episode" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:343 +msgid "Failed to unmark some episodes" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:112 +msgid "Failed to update name" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:101 +#: apps/native/src/lib/title-actions.ts:94 +#: apps/web/src/components/titles/use-title-actions.ts:136 +msgid "Failed to update rating" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:172 +#: apps/web/src/components/settings/registration-section.tsx:44 +msgid "Failed to update registration setting" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:154 +msgid "Failed to update retention setting" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:156 +msgid "Failed to update schedule" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:152 +msgid "Failed to update scheduled backup setting" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:191 +#: apps/web/src/components/settings/update-check-section.tsx:43 +msgid "Failed to update setting" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:72 +#: apps/native/src/lib/title-actions.ts:40 +#: apps/native/src/lib/title-actions.ts:54 +#: apps/web/src/components/titles/use-title-actions.ts:114 +msgid "Failed to update status" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:123 +msgid "Failed to upload avatar" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:725 +msgid "Fetching your library data from {source}..." +msgstr "" + +#: apps/native/src/app/person/[id].tsx:340 +#: apps/web/src/components/people/filmography-grid.tsx:69 +msgid "Filmography" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:915 +msgid "Finished importing from {source}." +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:140 +msgid "Free" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:92 +msgid "Free up disk space by clearing cached metadata and images" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:309 +msgid "Frequency" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:101 +msgid "Friday" +msgstr "" + +#: apps/web/src/components/landing-page.tsx:156 +msgid "Get Started" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:220 +#: apps/native/src/app/person/[id].tsx:247 +#: apps/native/src/app/title/[id].tsx:255 +msgid "Go back" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:37 +msgid "Go Home" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:123 +msgid "Go to <0>Dashboard > Plugins > Webhook" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:368 +msgid "Go to Dashboard" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:71 +msgid "Go to Dashboard > Plugins > Webhook" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:379 +msgid "Go to Explore" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:212 +msgid "Health status" +msgstr "" + +#: apps/web/src/components/dashboard/welcome-header.tsx:14 +msgid "Here's what's happening with your library" +msgstr "" + +#: apps/web/src/components/nav-bar.tsx:125 +#: apps/web/src/components/nav-bar.tsx:290 +msgid "Home" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:335 +#: apps/web/src/components/settings/system-health-section.tsx:610 +msgid "Image cache" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:479 +msgid "Image Cache" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:598 +msgid "Image cache and backup disk usage" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:179 +msgid "Import" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:840 +msgid "Import {totalItems} items" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:912 +msgid "Import complete" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:340 +msgid "Import failed" +msgstr "" + +#. placeholder {0}: config.label +#: apps/web/src/components/settings/imports-section.tsx:584 +msgid "Import from {0}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:770 +msgid "Import from {source}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:298 +#: apps/web/src/components/settings/imports-section.tsx:329 +msgid "Import is still running in the background. Check back later." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:798 +msgid "Import options" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:921 +msgid "Imported" +msgstr "" + +#. placeholder {0}: event.job.importedCount +#. placeholder {1}: config.label +#: apps/web/src/components/settings/imports-section.tsx:292 +msgid "Imported {0} items from {1}" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:863 +msgid "Importing from {source}" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:62 +msgid "In library" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:230 +msgid "In Library" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:133 +#: apps/web/src/components/dashboard/library-section.tsx:35 +msgid "In Your Library" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:105 +msgid "Install the <0>Webhook plugin<1/> from Jellyfin's plugin catalog" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:70 +msgid "Install the Webhook plugin from Jellyfin's plugin catalog" +msgstr "" + +#: apps/native/src/components/settings/integrations-section.tsx:20 +#: apps/web/src/components/settings/integrations-section.tsx:41 +msgid "Integrations" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:266 +msgid "Invalid token" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:399 +msgid "Job" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:243 +#~ msgid "Keeping" +#~ msgstr "Keeping" + +#. placeholder {0}: (value: string | null) => value === "0" ? t`unlimited` : value ? t`last ${value}` : null +#. placeholder {1}: [3, 5, 7, 14, 30, 0].map((n) => ( {n === 0 ? t`unlimited` : t`last ${n}`} )) +#: apps/web/src/components/settings/backup-schedule-section.tsx:243 +msgid "Keeping <0><1><2>{0}<3>{1} backups." +msgstr "" + +#: apps/web/src/components/command-palette.tsx:389 +#: apps/web/src/components/command-palette.tsx:404 +msgid "Keyboard Shortcuts" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:403 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:411 +#: apps/web/src/components/settings/language-section.tsx:42 +msgid "Language" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:270 +msgid "last {n}" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:258 +msgid "last {value}" +msgstr "" + +#. placeholder {0}: formatRelativeTime(lastEventAt) +#: apps/web/src/components/settings/integration-card.tsx:331 +msgid "Last event {0}" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:29 +msgid "Last event {timeAgo}" +msgstr "" + +#. placeholder {0}: formatRelativeTime(lastEventAt) +#: apps/web/src/components/settings/integration-card.tsx:338 +msgid "Last polled {0}" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:37 +msgid "Last polled {timeAgo}" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:405 +msgid "Last run" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:441 +msgid "Last run failed" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:437 +msgid "Last run succeeded" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:331 +msgid "Library refresh" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:245 +msgid "Loading…" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:334 +msgid "Lost connection to import. Check status in settings." +msgstr "" + +#: apps/web/src/components/settings/settings-shell.tsx:40 +msgid "Manage your account and preferences" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:182 +msgid "Manual backup" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:122 +msgid "Mark all episodes as watched?" +msgstr "" + +#: apps/native/src/components/titles/season-accordion.tsx:164 +#: apps/web/src/components/titles/title-seasons.tsx:115 +#: apps/web/src/components/titles/title-seasons.tsx:141 +msgid "Mark All Watched" +msgstr "" + +#: apps/native/src/components/dashboard/continue-watching-card.tsx:122 +msgid "Mark as Completed" +msgstr "" + +#: apps/native/src/components/ui/poster-card.tsx:252 +msgid "Mark as Watched" +msgstr "" + +#: apps/native/src/components/ui/poster-card.tsx:245 +msgid "Mark as Watching" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:449 +#: apps/web/src/components/titles/title-actions.tsx:29 +msgid "Mark Watched" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:49 +msgid "Marked \"{titleName}\" as completed" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:63 +#: apps/web/src/components/titles/use-title-actions.ts:147 +msgid "Marked \"{titleName}\" as watched" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:360 +msgid "Marked all episodes as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:62 +#: apps/native/src/lib/title-actions.ts:50 +msgid "Marked as completed" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:80 +#: apps/native/src/lib/title-actions.ts:64 +msgid "Marked as watched" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:61 +#: apps/native/src/lib/title-actions.ts:37 +msgid "Marked as watching" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:340 +msgid "Member since {memberSince}" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:97 +msgid "Monday" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:585 +msgid "More Like This" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:541 +msgid "More Settings" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:362 +#: apps/native/src/components/search/search-result-row.tsx:35 +#: apps/native/src/components/search/search-result-row.tsx:96 +#: apps/web/src/components/explore/hero-banner.tsx:59 +#: apps/web/src/components/titles/title-hero.tsx:143 +msgid "Movie" +msgstr "" + +#: apps/web/src/components/people/filmography-grid.tsx:59 +#: apps/web/src/components/settings/imports-section.tsx:779 +msgid "Movies" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:161 +#~ msgid "Movies {periodSelect}" +#~ msgstr "Movies {periodSelect}" + +#: apps/web/src/components/dashboard/stats-display.tsx:162 +msgid "Movies {select}" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:60 +msgid "Movies this month" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:124 +#: apps/web/src/components/auth-form.tsx:181 +msgid "Name" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:107 +#: apps/web/src/components/settings/account-section.tsx:80 +msgid "Name updated" +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:301 +msgid "Need more help?" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:501 +msgid "Never" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:435 +msgid "Never run" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:98 +msgid "New account creation is currently disabled." +msgstr "" + +#: apps/web/src/routes/_auth/register.tsx:35 +msgid "New accounts are not being accepted right now. Contact the admin if you need access." +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:137 +msgid "New backup" +msgstr "" + +#: apps/native/src/app/change-password.tsx:145 +#: apps/web/src/components/settings/account-section.tsx:463 +msgid "New password" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:395 +msgid "New password must be at least 8 characters" +msgstr "" + +#: apps/web/src/components/people/filmography-grid.tsx:86 +#: apps/web/src/components/people/filmography-grid.tsx:99 +msgid "Newest" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:131 +msgid "Next backup {distance}" +msgstr "" + +#: apps/native/src/components/titles/continue-watching-banner.tsx:46 +msgid "Next Episode" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:408 +msgid "Next run" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:110 +#: apps/web/src/components/settings/system-health-section.tsx:662 +msgid "No backups yet" +msgstr "" + +#: apps/native/src/components/ui/offline-banner.ios.tsx:74 +#: apps/native/src/components/ui/offline-banner.ios.tsx:81 +#: apps/native/src/components/ui/offline-banner.tsx:59 +msgid "No internet connection" +msgstr "" + +#: apps/native/src/app/(tabs)/(search)/index.tsx:113 +msgid "No results for \"{debouncedQuery}\"" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:217 +msgid "No results found." +msgstr "" + +#: apps/native/src/components/explore/filterable-title-row.tsx:139 +#: apps/web/src/components/explore/filterable-title-row.tsx:151 +msgid "No titles found for this genre." +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:194 +#: apps/web/src/components/settings/integration-card.tsx:176 +#: apps/web/src/components/settings/system-health-section.tsx:249 +msgid "Not configured" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:17 +msgid "Not Found" +msgstr "" + +#: apps/web/src/components/title-card.tsx:67 +msgid "On Watchlist" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:693 +msgid "Open {source} to enter code" +msgstr "" + +#: apps/web/src/components/settings/integration-card.tsx:308 +msgid "Open docs" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:190 +msgid "Open Emby, go to <0>Settings > Webhooks" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:91 +msgid "Open Emby, go to Settings > Webhooks" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:548 +msgid "Open in browser…" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:62 +msgid "Open Plex, go to <0>Settings > Webhooks<1/>" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:54 +msgid "Open Plex, go to Settings > Webhooks" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:269 +msgid "Open Radarr, go to <0>Settings > Import Lists" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:126 +msgid "Open Radarr, go to Settings > Import Lists" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:502 +#: apps/web/src/components/settings/registration-section.tsx:58 +msgid "Open registration" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:229 +msgid "Open Sonarr, go to <0>Settings > Import Lists" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:108 +msgid "Open Sonarr, go to Settings > Import Lists" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:158 +msgid "or" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:118 +msgid "OR" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:27 +msgid "Page Not Found" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:509 +msgid "Parsing..." +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:167 +#: apps/native/src/app/(auth)/register.tsx:187 +#: apps/web/src/components/auth-form.tsx:221 +msgid "Password" +msgstr "" + +#: apps/native/src/app/change-password.tsx:76 +#: apps/web/src/components/settings/account-section.tsx:414 +msgid "Password updated" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:399 +msgid "Passwords do not match" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:128 +#: apps/web/src/components/settings/integration-configs.tsx:284 +msgid "Paste the Radarr URL above into the List URL field" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:110 +#: apps/web/src/components/settings/integration-configs.tsx:244 +msgid "Paste the Sonarr URL above into the List URL field" +msgstr "" + +#: apps/web/src/components/settings/update-check-section.tsx:63 +msgid "Periodically check GitHub for new Sofa releases" +msgstr "" + +#: apps/native/src/components/search/search-result-row.tsx:38 +#: apps/native/src/components/search/search-result-row.tsx:99 +msgid "Person" +msgstr "" + +#: apps/native/src/app/person/[id].tsx:243 +#: apps/web/src/routes/_app/people.$id.tsx:52 +msgid "Person not found" +msgstr "" + +#: apps/web/src/components/titles/trailer-dialog.tsx:24 +msgid "Play trailer" +msgstr "" + +#: apps/native/src/app/(tabs)/(explore)/index.tsx:107 +#: apps/web/src/components/explore/explore-client.tsx:126 +msgid "Popular Movies" +msgstr "" + +#: apps/native/src/app/(tabs)/(explore)/index.tsx:120 +#: apps/web/src/components/explore/explore-client.tsx:136 +msgid "Popular TV Shows" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:181 +msgid "Pre-restore backup" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:77 +msgid "Producer" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:130 +#: apps/web/src/components/settings/account-section.tsx:130 +msgid "Profile picture removed" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:119 +#: apps/web/src/components/settings/account-section.tsx:108 +msgid "Profile picture updated" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:196 +#: apps/web/src/components/settings/danger-section.tsx:220 +msgid "Purge all" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:202 +msgid "Purge all caches?" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:160 +msgid "Purge image cache?" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:154 +#: apps/web/src/components/settings/danger-section.tsx:177 +msgid "Purge images" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:111 +#: apps/web/src/components/settings/danger-section.tsx:135 +msgid "Purge metadata" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:117 +msgid "Purge metadata cache?" +msgstr "" + +#. placeholder {0}: data.deletedTitles +#. placeholder {1}: data.deletedPersons +#: apps/web/src/components/settings/danger-section.tsx:41 +msgid "Purged {0, plural, one {# stale title} other {# stale titles}} and {1, plural, one {# orphaned person} other {# orphaned persons}}" +msgstr "" + +#. placeholder {0}: metaResult.deletedTitles +#. placeholder {1}: metaResult.deletedPersons +#. placeholder {2}: imageResult.deletedFiles +#: apps/web/src/components/settings/danger-section.tsx:71 +msgid "Purged {0, plural, one {# title} other {# titles}}, {1, plural, one {# person} other {# persons}}, {2, plural, one {# file} other {# files}} ({freed} freed)" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:109 +#: apps/web/src/components/settings/danger-section.tsx:152 +#: apps/web/src/components/settings/danger-section.tsx:194 +msgid "Purging..." +msgstr "" + +#: apps/web/src/components/command-palette.tsx:359 +msgid "Quick Actions" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:123 +msgid "Radarr List URL" +msgstr "" + +#. placeholder {0}: input.stars +#. placeholder {1}: input.stars +#: apps/native/src/hooks/use-title-actions.ts:93 +msgid "Rated {0} {1, plural, one {star} other {stars}}" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:92 +#~ msgid "Rated {0} star{1}" +#~ msgstr "Rated {0} star{1}" + +#: apps/web/src/components/titles/use-title-actions.ts:131 +msgid "Rated {ratingStars} {ratingStars, plural, one {star} other {stars}}" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:88 +msgid "Rated {stars, plural, one {# star} other {# stars}}" +msgstr "" + +#: apps/web/src/components/people/filmography-grid.tsx:88 +#: apps/web/src/components/people/filmography-grid.tsx:102 +msgid "Rating" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:94 +#: apps/native/src/lib/title-actions.ts:90 +#: apps/web/src/components/titles/use-title-actions.ts:132 +msgid "Rating removed" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:782 +#: apps/web/src/components/settings/imports-section.tsx:813 +msgid "Ratings" +msgstr "" + +#: apps/web/src/components/expandable-text.tsx:51 +msgid "Read more" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:39 +#: apps/web/src/components/settings/integration-card.tsx:339 +msgid "Ready — not polled yet" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:31 +#: apps/web/src/components/settings/integration-card.tsx:332 +msgid "Ready — nothing received yet" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:315 +msgid "Recent Searches" +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:106 +#: apps/native/src/components/search/recently-viewed-list.tsx:86 +msgid "Recently Viewed" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:333 +msgid "Recommendations" +msgstr "" + +#: apps/web/src/components/titles/title-recommendations.tsx:40 +msgid "Recommended" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:154 +#: apps/web/src/components/dashboard/recommendations-section.tsx:22 +msgid "Recommended for You" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:143 +msgid "Redirecting…" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:182 +msgid "Refresh" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:172 +msgid "Refresh system health" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:140 +#: apps/native/src/components/settings/integration-card.tsx:289 +msgid "Regenerate" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:135 +#: apps/web/src/components/settings/integration-card.tsx:264 +msgid "Regenerate URL" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:293 +#: apps/web/src/components/landing-page.tsx:176 +msgid "Register" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:116 +msgid "Registering on {serverHost}" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:167 +#: apps/web/src/components/settings/registration-section.tsx:41 +msgid "Registration closed" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:97 +#: apps/web/src/routes/_auth/register.tsx:32 +msgid "Registration Closed" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:167 +#: apps/web/src/components/settings/registration-section.tsx:41 +msgid "Registration opened" +msgstr "" + +#: apps/web/src/components/titles/status-button.tsx:84 +msgid "Remove" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:101 +#: apps/web/src/components/titles/status-button.tsx:66 +msgid "Remove from library" +msgstr "" + +#: apps/native/src/components/dashboard/continue-watching-card.tsx:129 +#: apps/native/src/components/ui/poster-card.tsx:259 +msgid "Remove from Library" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:277 +msgid "Remove Photo" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:239 +msgid "Remove picture" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:199 +msgid "Remove profile picture" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:66 +#: apps/native/src/lib/title-actions.ts:75 +#: apps/web/src/components/titles/use-title-actions.ts:111 +msgid "Removed from library" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:138 +msgid "Rent" +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:165 +msgid "Requires <0>Emby Server 4.7.9+ and an active <1>Emby Premiere<2/> license." +msgstr "" + +#: apps/web/src/components/settings/integration-configs.tsx:41 +msgid "Requires an active <0>Plex Pass<1/> subscription." +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:52 +msgid "Requires an active Plex Pass subscription." +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:88 +msgid "Requires Emby Server 4.7.9+ and an active Emby Premiere license." +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:59 +#: apps/web/src/components/settings/backup-restore-section.tsx:116 +msgid "Restore" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:89 +msgid "Restore database?" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:35 +msgid "Restore failed" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:132 +msgid "Restoring…" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:222 +msgid "Results" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:731 +msgid "Retrieving your watch history, watchlist, and ratings..." +msgstr "" + +#: apps/native/src/components/settings/integrations-section.tsx:42 +#: apps/native/src/components/ui/server-unreachable-banner.ios.tsx:73 +#: apps/native/src/components/ui/server-unreachable-banner.tsx:81 +#: apps/web/src/lib/orpc/client.ts:16 +msgid "Retry" +msgstr "" + +#: apps/web/src/routes/__root.tsx:119 +msgid "Return home" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:773 +msgid "Review what was found and choose what to import." +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:558 +msgid "Run now" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:102 +msgid "Saturday" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:322 +msgid "Save" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:296 +msgid "Save name" +msgstr "" + +#: apps/web/src/routes/__root.tsx:101 +msgid "Scene not found" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:402 +msgid "Schedule" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:201 +msgid "Schedule updated" +msgstr "" + +#: apps/web/src/components/settings/backup-section.tsx:179 +msgid "Scheduled backup" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:178 +msgid "Scheduled backups disabled" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:177 +msgid "Scheduled backups enabled" +msgstr "" + +#: apps/web/src/components/dashboard/stats-section.tsx:35 +msgid "Search for movies and TV shows to start tracking" +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:81 +#: apps/native/src/components/search/recently-viewed-list.tsx:64 +msgid "Search for movies, shows, or people" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:186 +msgid "Search for movies, TV shows, or run commands" +msgstr "" + +#: apps/web/src/components/command-palette.tsx:195 +msgid "Search movies & TV shows…" +msgstr "" + +#: apps/native/src/app/(tabs)/(search)/index.tsx:90 +msgid "Search movies, shows, people..." +msgstr "" + +#: apps/web/src/components/nav-bar.tsx:193 +#: apps/web/src/components/nav-bar.tsx:202 +msgid "Search…" +msgstr "" + +#. placeholder {0}: season.seasonNumber +#: apps/native/src/components/titles/season-accordion.tsx:122 +#: apps/native/src/components/titles/season-accordion.tsx:128 +#: apps/web/src/components/titles/use-title-actions.ts:295 +#: apps/web/src/components/titles/use-title-actions.ts:335 +msgid "Season {0}" +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:137 +#: apps/native/src/lib/title-actions.ts:124 +msgid "Season watched" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:536 +msgid "Seasons" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:497 +#: apps/web/src/routes/_app/settings.tsx:140 +msgid "Security" +msgstr "" + +#: apps/web/src/components/landing-page.tsx:105 +msgid "Self-hosted movie & TV tracker" +msgstr "" + +#: apps/web/src/routes/_app/settings.tsx:121 +msgid "Server" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:452 +msgid "Server Health" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:380 +msgid "Server URL" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:361 +msgid "Set password" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:111 +#: apps/native/src/components/settings/integration-configs.ts:129 +#: apps/web/src/components/settings/integration-configs.tsx:247 +#: apps/web/src/components/settings/integration-configs.tsx:287 +msgid "Set your preferred quality profile and root folder" +msgstr "" + +#: apps/native/src/components/header-avatar.tsx:63 +#: apps/web/src/components/nav-bar.tsx:250 +#: apps/web/src/components/nav-bar.tsx:292 +#: apps/web/src/components/settings/settings-shell.tsx:36 +msgid "Settings" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:319 +#: apps/web/src/components/settings/integration-card.tsx:288 +msgid "Setup instructions" +msgstr "" + +#: apps/web/src/routes/setup.tsx:98 +msgid "Setup required" +msgstr "" + +#: apps/native/src/components/ui/expandable-text.tsx:62 +#: apps/native/src/components/ui/expandable-text.tsx:69 +#: apps/web/src/components/expandable-text.tsx:51 +msgid "Show less" +msgstr "" + +#: apps/native/src/components/ui/expandable-text.tsx:62 +#: apps/native/src/components/ui/expandable-text.tsx:69 +msgid "Show more" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:249 +#: apps/web/src/components/auth-form.tsx:283 +msgid "Sign in" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:205 +#: apps/web/src/components/landing-page.tsx:167 +msgid "Sign In" +msgstr "" + +#: apps/web/src/routes/_auth/register.tsx:45 +msgid "Sign in instead" +msgstr "" + +#: apps/native/src/app/(auth)/login.tsx:91 +#: apps/web/src/components/auth-form.tsx:119 +msgid "Sign in to continue" +msgstr "" + +#. placeholder {0}: authConfig.data?.oidcProviderName ?? "SSO" +#. placeholder {0}: authConfig?.oidcProviderName || "SSO" +#: apps/native/src/app/(auth)/login.tsx:108 +#: apps/web/src/components/auth-form.tsx:145 +msgid "Sign in with {0}" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:368 +#: apps/native/src/components/header-avatar.tsx:87 +#: apps/web/src/components/nav-bar.tsx:262 +#: apps/web/src/components/settings/account-section.tsx:354 +msgid "Sign out" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:206 +#: apps/native/src/app/(tabs)/(settings)/index.tsx:209 +#: apps/native/src/components/header-avatar.tsx:70 +#: apps/native/src/components/header-avatar.tsx:73 +msgid "Sign Out" +msgstr "" + +#: apps/native/src/app/change-password.tsx:199 +#: apps/web/src/components/settings/account-section.tsx:499 +msgid "Sign out of other sessions" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:922 +msgid "Skipped" +msgstr "" + +#. placeholder {0}: data.latestVersion +#: apps/web/src/components/update-toast.tsx:19 +msgid "Sofa v{0} is available" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:57 +#: apps/native/src/components/settings/integration-configs.ts:75 +#: apps/native/src/components/settings/integration-configs.ts:95 +#: apps/web/src/components/settings/integration-configs.tsx:86 +#: apps/web/src/components/settings/integration-configs.tsx:147 +#: apps/web/src/components/settings/integration-configs.tsx:208 +msgid "Sofa will automatically log movies and episodes when you finish watching them" +msgstr "" + +#: apps/native/src/app/change-password.tsx:81 +#: apps/native/src/app/person/[id].tsx:213 +#: apps/web/src/components/settings/account-section.tsx:417 +#: apps/web/src/routes/__root.tsx:143 +msgid "Something went wrong" +msgstr "" + +#: apps/web/src/routes/_app/titles.$id.tsx:118 +msgid "Something went wrong while loading this title. Please try again." +msgstr "" + +#: apps/native/src/lib/query-client.ts:33 +#: apps/web/src/lib/orpc/client.ts:14 +msgid "Something went wrong…" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:105 +msgid "Sonarr List URL" +msgstr "" + +#: apps/web/src/components/dashboard/stats-section.tsx:42 +msgid "Start exploring" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:142 +msgid "Start tracking movies and shows" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:117 +msgid "Start tracking your watches" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:388 +msgid "Starting at" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:888 +msgid "Starting import..." +msgstr "" + +#: apps/native/src/hooks/use-title-actions.ts:65 +msgid "Status updated" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:595 +msgid "Storage" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:137 +msgid "Stream" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:96 +msgid "Sunday" +msgstr "" + +#. placeholder {0}: info.name +#: apps/web/src/components/settings/language-section.tsx:69 +msgid "Switch to {0}" +msgstr "" + +#: apps/native/src/app/+not-found.tsx:30 +msgid "The page you're looking for doesn't exist." +msgstr "" + +#: apps/web/src/routes/_app/people.$id.tsx:55 +msgid "The person you're looking for doesn't exist or may have been removed from the database." +msgstr "" + +#: apps/web/src/routes/_app/titles.$id.tsx:149 +msgid "The title you're looking for doesn't exist or may have been removed from the database." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:866 +msgid "This may take a few minutes for large libraries. Please don't close this tab." +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:146 +msgid "This Month" +msgstr "" + +#: apps/web/src/routes/__root.tsx:104 +msgid "This page was left on the cutting room floor. It may have been moved, removed, or never made it past the screenplay." +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:606 +#: apps/web/src/routes/_app/settings.tsx:75 +#: apps/web/src/routes/setup.tsx:187 +msgid "This product uses the TMDB API but is not endorsed or certified by TMDB." +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:145 +msgid "This Week" +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:163 +msgid "This will delete all cached TMDB images from disk. Images will be re-downloaded automatically as needed." +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:205 +msgid "This will delete all un-enriched stub titles and all cached images from disk. Everything will be re-imported and re-downloaded as needed." +msgstr "" + +#: apps/web/src/components/settings/danger-section.tsx:120 +msgid "This will delete un-enriched stub titles that aren't in any user's library and clean up orphaned person records. Deleted titles will be re-imported if accessed again." +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:136 +msgid "This will invalidate the current {label} URL. You'll need to update it in {label}." +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:125 +msgid "This will mark every episode of this show as watched. You can undo this later by unmarking individual seasons." +msgstr "" + +#. placeholder {0}: formatBackupDate(backup.createdAt) +#: apps/web/src/components/settings/backup-section.tsx:259 +msgid "This will permanently delete the backup from <0>{0}. This cannot be undone." +msgstr "" + +#: apps/native/src/components/search/recently-viewed-list.ios.tsx:61 +#: apps/native/src/components/search/recently-viewed-list.tsx:35 +msgid "This will remove all items from your history." +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:92 +msgid "This will replace your entire database with the uploaded file. A safety backup of your current data will be created first. Active sessions may need to refresh after restore." +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:147 +msgid "This Year" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:100 +msgid "Thursday" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:390 +msgid "Time:" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:251 +#: apps/web/src/routes/_app/titles.$id.tsx:146 +msgid "Title not found" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:131 +#: apps/web/src/components/settings/integration-configs.tsx:290 +msgid "Titles on your Sofa watchlist will be automatically added for download when Radarr polls this list (every 12 hours by default)" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:113 +#: apps/web/src/components/settings/integration-configs.tsx:250 +msgid "Titles on your Sofa watchlist will be automatically added for download when Sonarr polls this list (every 6 hours by default)" +msgstr "" + +#: apps/web/src/components/dashboard/stats-display.tsx:144 +msgid "Today" +msgstr "" + +#: apps/web/src/components/settings/update-check-section.tsx:71 +msgid "Toggle automatic update checks" +msgstr "" + +#: apps/web/src/components/settings/registration-section.tsx:69 +msgid "Toggle open registration" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:290 +msgid "Toggle scheduled backups" +msgstr "" + +#: apps/web/src/components/landing-page.tsx:131 +msgid "Track what you watch. Know what's next.<0/>Your library, your data, your rules." +msgstr "" + +#: apps/web/src/components/titles/trailer-dialog.tsx:37 +#: apps/web/src/components/titles/trailer-dialog.tsx:41 +msgid "Trailer" +msgstr "" + +#: apps/web/src/components/explore/hero-banner.tsx:78 +msgid "Trending today" +msgstr "" + +#: apps/native/src/app/(tabs)/(explore)/index.tsx:95 +#: apps/web/src/components/explore/explore-client.tsx:112 +msgid "Trending Today" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:537 +msgid "Trigger job" +msgstr "" + +#: apps/web/src/routes/__root.tsx:159 +msgid "Try again" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:98 +msgid "Tuesday" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:362 +#: apps/native/src/components/search/search-result-row.tsx:98 +#: apps/web/src/components/explore/hero-banner.tsx:64 +#: apps/web/src/components/people/filmography-grid.tsx:60 +#: apps/web/src/components/titles/title-hero.tsx:153 +msgid "TV" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:334 +msgid "TV episodes" +msgstr "" + +#: apps/native/src/components/search/search-result-row.tsx:37 +msgid "TV show" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:656 +msgid "unknown" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:256 +#: apps/web/src/components/settings/backup-schedule-section.tsx:270 +msgid "unlimited" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:273 +msgid "Unreachable" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:340 +msgid "Untitled" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:221 +msgid "Unwatch all" +msgstr "" + +#. placeholder {0}: season.name ?? t`Season ${season.seasonNumber}` +#: apps/web/src/components/titles/use-title-actions.ts:335 +msgid "Unwatched all of {0}" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:174 +msgid "Unwatched S{seasonNum} E{epNum}" +msgstr "" + +#: apps/native/src/components/titles/continue-watching-banner.tsx:79 +#: apps/web/src/components/dashboard/continue-watching-card.tsx:77 +msgid "Up next" +msgstr "" + +#. placeholder {0}: updateCheck.data.updateCheck.latestVersion +#: apps/native/src/app/(tabs)/(settings)/index.tsx:528 +msgid "Update available: {0}" +msgstr "" + +#: apps/web/src/components/settings/system-health-section.tsx:337 +msgid "Update check" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:185 +#: apps/web/src/components/settings/update-check-section.tsx:40 +msgid "Update checks disabled" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:185 +#: apps/web/src/components/settings/update-check-section.tsx:40 +msgid "Update checks enabled" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:84 +msgid "Update failed" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:509 +msgid "Update password" +msgstr "" + +#: apps/native/src/app/change-password.tsx:218 +msgid "Update Password" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:134 +#: apps/web/src/components/settings/imports-section.tsx:509 +msgid "Upload" +msgstr "" + +#: apps/web/src/components/settings/backup-restore-section.tsx:62 +msgid "Upload a .db file to replace the current database. A safety backup is created first." +msgstr "" + +#. placeholder {0}: config.accept +#. placeholder {1}: config.label +#: apps/web/src/components/settings/imports-section.tsx:637 +msgid "Upload a {0} export from your {1} account settings." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:634 +msgid "Upload export file" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:112 +msgid "Upload failed" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:241 +msgid "Upload picture" +msgstr "" + +#: apps/web/src/components/settings/account-section.tsx:200 +msgid "Upload profile picture" +msgstr "" + +#: apps/native/src/components/settings/integration-card.tsx:129 +msgid "URL copied to clipboard" +msgstr "" + +#: apps/web/src/components/update-toast.tsx:24 +msgid "View release" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:699 +msgid "Waiting for authorization..." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:823 +msgid "Warnings" +msgstr "" + +#. placeholder {0}: result.warnings.length +#: apps/web/src/components/settings/imports-section.tsx:948 +msgid "Warnings ({0})" +msgstr "" + +#: apps/web/src/components/titles/title-seasons.tsx:202 +msgid "Watch all" +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:801 +msgid "Watch history" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:59 +msgid "Watch on {name}" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:298 +#: apps/web/src/components/titles/use-title-actions.ts:307 +msgid "Watched all of {seasonLabel}" +msgstr "" + +#: apps/native/src/lib/title-actions.ts:123 +msgid "Watched all of {seasonName}" +msgstr "" + +#: apps/web/src/components/titles/use-title-actions.ts:220 +#: apps/web/src/components/titles/use-title-actions.ts:229 +msgid "Watched S{seasonNum} E{epNum}" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:42 +#: apps/web/src/components/title-card.tsx:72 +#: apps/web/src/components/titles/status-button.tsx:19 +msgid "Watching" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:82 +#: apps/web/src/components/settings/imports-section.tsx:781 +#: apps/web/src/components/settings/imports-section.tsx:807 +#: apps/web/src/components/titles/status-button.tsx:55 +msgid "Watchlist" +msgstr "" + +#: apps/native/src/components/titles/status-action-button.tsx:40 +msgid "Watchlisted" +msgstr "" + +#: apps/native/src/components/settings/integration-configs.ts:50 +#: apps/native/src/components/settings/integration-configs.ts:67 +#: apps/native/src/components/settings/integration-configs.ts:85 +msgid "Webhook URL" +msgstr "" + +#: apps/web/src/components/settings/backup-schedule-section.tsx:99 +msgid "Wednesday" +msgstr "" + +#: apps/web/src/components/auth-form.tsx:112 +#: apps/web/src/components/dashboard/welcome-header.tsx:10 +msgid "Welcome back" +msgstr "" + +#: apps/web/src/components/dashboard/welcome-header.tsx:8 +msgid "Welcome back, {name}" +msgstr "" + +#: apps/native/src/app/title/[id].tsx:483 +#: apps/web/src/components/titles/title-availability.tsx:154 +msgid "Where to Watch" +msgstr "" + +#: apps/web/src/components/titles/title-availability.tsx:141 +msgid "With Ads" +msgstr "" + +#: apps/web/src/components/people/person-hero.tsx:75 +msgid "Writer" +msgstr "" + +#: apps/native/src/app/(tabs)/(settings)/index.tsx:386 +msgid "You'll be signed out to change the server URL." +msgstr "" + +#. placeholder {0}: data.currentVersion +#: apps/web/src/components/update-toast.tsx:20 +msgid "You're running v{0}." +msgstr "" + +#: apps/web/src/components/settings/imports-section.tsx:678 +msgid "Your code:" +msgstr "" + +#: apps/native/src/app/(tabs)/(home)/index.tsx:141 +#: apps/web/src/components/dashboard/stats-section.tsx:32 +msgid "Your library is empty" +msgstr "" + +#: apps/native/src/app/(auth)/register.tsx:134 +msgid "Your name" +msgstr "" + diff --git a/packages/i18n/src/po/pt.ts b/packages/i18n/src/po/pt.ts new file mode 100644 index 0000000..ef7e47e --- /dev/null +++ b/packages/i18n/src/po/pt.ts @@ -0,0 +1 @@ +/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"+5kO8P\":[\"Saturday\"],\"+6i0lS\":[\"Retrieving your watch history, watchlist, and ratings...\"],\"+Cv+V9\":[\"Remove from library\"],\"+JkEpu\":[\"This page was left on the cutting room floor. It may have been moved, removed, or never made it past the screenplay.\"],\"+K0AvT\":[\"Disconnect\"],\"+N0l5/\":[\"Connected to <0>\",[\"serverHost\"],\". Tap to change.\"],\"+gLHYi\":[\"Failed to load title\"],\"+j1ex/\":[\"Failed to remove from library\"],\"+nF1ZO\":[\"Remove Photo\"],\"/+6dvC\":[\"Errors (\",[\"0\"],\")\"],\"/BBXeA\":[\"Connecting to server...\"],\"/DwR+n\":[\"Creating…\"],\"/XtbPO\":[[\"0\"],\" titles\"],\"/cg+QV\":[\"Watch all\"],\"/gQXGv\":[\"Failed to update status\"],\"/nT6AE\":[\"New password\"],\"/sHc1/\":[\"Update check\"],\"0+dyau\":[\"Failed to update retention setting\"],\"0BFJKK\":[\"Authorization was denied. Please try again.\"],\"0GHb20\":[\"Purge metadata cache?\"],\"0IBW21\":[\"Failed to purge caches\"],\"0gH/sc\":[\"Remove profile picture\"],\"1+P9RR\":[\"Switch to \",[\"0\"]],\"12cc1j\":[\"Watching\"],\"12lVOl\":[\"Self-hosted movie & TV tracker\"],\"1B4z0M\":[\"Filmography\"],\"1J4Ek0\":[\"Automatically back up your database on a schedule\"],\"1JhxXW\":[\"Episode \",[\"0\"],\", \",[\"episodeLabel\"]],\"1N6wNP\":[\"Import options\"],\"1Qz4uG\":[\"Enable the \\\"Playback\\\" event category\"],\"1hMWR6\":[\"Create account\"],\"1jHIjh\":[\"Watched all of \",[\"seasonName\"]],\"1vSYsG\":[\"Download backup\"],\"1wL1tj\":[\"TV show\"],\"2FletP\":[\"Purged \",[\"0\",\"plural\",{\"one\":[\"#\",\" title\"],\"other\":[\"#\",\" titles\"]}],\", \",[\"1\",\"plural\",{\"one\":[\"#\",\" person\"],\"other\":[\"#\",\" persons\"]}],\", \",[\"2\",\"plural\",{\"one\":[\"#\",\" file\"],\"other\":[\"#\",\" files\"]}],\" (\",[\"freed\"],\" freed)\"],\"2Fsd9r\":[\"This Month\"],\"2MPcep\":[\"Import complete\"],\"2Mu33Z\":[\"Cache management\"],\"2POOFK\":[\"Free\"],\"2Pt2NY\":[\"This will remove all items from your history.\"],\"2fCpt5\":[\"Return home\"],\"2pPBp6\":[\"Trending today\"],\"39y5bn\":[\"Friday\"],\"3Blefz\":[\"Ratings\"],\"3JTlG8\":[\"Recent Searches\"],\"3Jy8bM\":[\"Movies \",[\"select\"]],\"3L9OuQ\":[\"Episode \",[\"0\"]],\"3T8ziB\":[\"Create an account\"],\"3hELxX\":[\"Enter your Sofa server URL to get started\"],\"4fxLkp\":[\"Open Sonarr, go to <0>Settings > Import Lists\"],\"4kpBqM\":[\"Last event \",[\"0\"]],\"4uUjVO\":[\"Producer\"],\"4x+A56\":[\"Anonymous usage reporting\"],\"5IShvp\":[\"Import \",[\"totalItems\"],\" items\"],\"5IYJSv\":[\"Explore titles\"],\"5Y4mym\":[\"Import from \",[\"0\"]],\"5ZzgbQ\":[\"Connect \",[\"0\"]],\"5fEnbK\":[\"Failed to update scheduled backup setting\"],\"5lWFkC\":[\"Sign in\"],\"5v9C16\":[\"Connected to \",[\"source\"]],\"623bR4\":[\"Failed to trigger job\"],\"6HN0yh\":[\"Open Plex, go to Settings > Webhooks\"],\"6TNjOJ\":[\"Failed to update rating\"],\"6TfUy6\":[\"last \",[\"value\"]],\"6V3Ea3\":[\"Copied\"],\"6YtxFj\":[\"Name\"],\"6exX+8\":[\"Regenerate\"],\"6gRgw8\":[\"Retry\"],\"6lGV3K\":[\"Show less\"],\"76++pR\":[\"Warnings\"],\"77DIAu\":[\"Go to Dashboard > Plugins > Webhook\"],\"7Bj3x9\":[\"Failed\"],\"7D50KC\":[[\"label\"],\" disconnected\"],\"7GfM5w\":[\"Recently Viewed\"],\"7L01XJ\":[\"Actions\"],\"7eMo+U\":[\"Go Home\"],\"7p5kLi\":[\"Dashboard\"],\"85eoJ1\":[\"Schedule updated\"],\"8B9E2D\":[\"Day:\"],\"8YwF1J\":[\"Go to <0>Dashboard > Plugins > Webhook\"],\"8ZsakT\":[\"Password\"],\"8tjQCz\":[\"Explore\"],\"8vNtLy\":[\"Paste the Radarr URL above into the List URL field\"],\"8wYDMp\":[\"Already have an account?\"],\"9AE3vb\":[\"Open Plex, go to <0>Settings > Webhooks<1/>\"],\"9GW0ZB\":[\"Import is still running in the background. Check back later.\"],\"9NyAH9\":[\"Skipped\"],\"9Xhrps\":[\"Failed to connect\"],\"9ZLGbA\":[\"backups.\"],\"9eF5oV\":[\"Welcome back\"],\"9rG25a\":[\"Server URL\"],\"A+0rLe\":[\"Marked as completed\"],\"AOHgZp\":[\"Episodes\"],\"AOddWK\":[\"Connect \",[\"label\"]],\"ATfxL/\":[\"Trailer\"],\"AVlZoM\":[\"Environment\"],\"Acf6vF\":[\"Save name\"],\"AdoUfN\":[\"Failed to add to watchlist\"],\"AeXO77\":[\"Account\"],\"AjtYj0\":[\"On Watchlist\"],\"Avee+B\":[\"Failed to update name\"],\"B2R3xD\":[\"Toggle scheduled backups\"],\"BEVzjL\":[\"Import failed\"],\"BQnS5I\":[\"Open \",[\"source\"],\" to enter code\"],\"BUnoSB\":[[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"backup\"],\"other\":[\"backups\"]}],\" stored\"],\"BpttUI\":[\"Trending Today\"],\"BrrIs8\":[\"Storage\"],\"BskWMl\":[\"Unreachable\"],\"BzEFor\":[\"or\"],\"CGDHFb\":[\"Add a <0>Generic Destination and paste the URL above\"],\"CGExDN\":[\"Failed to purge image cache\"],\"CHeXFE\":[\"Status updated\"],\"CKyk7Q\":[\"Go back\"],\"CaB/+I\":[\"Welcome back, \",[\"name\"]],\"CodnUh\":[\"Removed from library\"],\"CpzGJY\":[\"This may take a few minutes for large libraries. Please don't close this tab.\"],\"CuPxpd\":[\"Requires an active <0>Plex Pass<1/> subscription.\"],\"CzBN6D\":[\"Start exploring\"],\"D+R2Xs\":[\"Starting import...\"],\"D3C4Yx\":[\"Current password is required\"],\"DBC3t5\":[\"Sunday\"],\"DI4lqs\":[\"Person not found\"],\"DKBbJf\":[\"Added \\\"\",[\"titleName\"],\"\\\" to watchlist\"],\"DPfwMq\":[\"Done\"],\"DZse/o\":[\"Add a \\\"Generic Destination\\\" and paste the URL above\"],\"E/QGRL\":[\"Disabled\"],\"E6nRW7\":[\"Copy URL\"],\"EWQlBH\":[\"Your library is empty\"],\"EWaCfj\":[\"Enter your current password and choose a new one.\"],\"Eeo/Gy\":[\"Failed to update setting\"],\"Efn6WU\":[\"This will delete all un-enriched stub titles and all cached images from disk. Everything will be re-imported and re-downloaded as needed.\"],\"Etp5if\":[\"Finished importing from \",[\"source\"],\".\"],\"F006BN\":[\"Import from \",[\"source\"]],\"FNvDMc\":[\"This Week\"],\"FWSp+7\":[\"Enter the code below on \",[\"source\"],\"'s website to authorize Sofa.\"],\"FXN0ro\":[\"Recommendations\"],\"FaU7Ag\":[\"Enable the \\\"Playback Stop\\\" notification type\"],\"FhvLDl\":[[\"watchedCount\"],\"/\",[\"0\"],\" episodes\"],\"Fo2bwm\":[\"Actor\"],\"FwRqjE\":[\"Image cache and backup disk usage\"],\"G00fgM\":[\"last \",[\"n\"]],\"G3myU+\":[\"Tuesday\"],\"GcCthe\":[\"In Library\"],\"Gf39AA\":[[\"0\"],\" triggered\"],\"GnhfWw\":[\"Toggle automatic update checks\"],\"GptGxg\":[\"Change password\"],\"GqTZ+S\":[\"Rated \",[\"ratingStars\"],\" \",[\"ratingStars\",\"plural\",{\"one\":[\"star\"],\"other\":[\"stars\"]}]],\"GrdN/F\":[\"Caught up — marked \",[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}],\" as watched\"],\"H4B5LG\":[\"This product uses the TMDB API but is not endorsed or certified by TMDB.\"],\"HBRd5n\":[\"Season \",[\"0\"]],\"HD+aQ7\":[\"Database backups\"],\"HG+31u\":[\"Purge all caches?\"],\"Haz+72\":[\"This will delete all cached TMDB images from disk. Images will be re-downloaded automatically as needed.\"],\"HbReP5\":[\"Marked \\\"\",[\"titleName\"],\"\\\" as completed\"],\"Hg6o8v\":[\"Refresh system health\"],\"HmEjnC\":[\"Last event \",[\"timeAgo\"]],\"HvDfH/\":[\"Imported\"],\"I89uD4\":[\"Restoring…\"],\"IRoxQm\":[\"Registration closed\"],\"IS0nrP\":[\"Create Account\"],\"IY9rQ0\":[\"Free up disk space by clearing cached metadata and images\"],\"IrC12v\":[\"Application\"],\"J28zul\":[\"Connecting...\"],\"J64cFL\":[\"Library refresh\"],\"JKmmmN\":[\"Added to watchlist\"],\"JN0f/Y\":[\"Connect to TMDB\"],\"JR6aH2\":[\"Movies \",[\"periodSelect\"]],\"JRQitQ\":[\"Confirm new password\"],\"JRadFJ\":[\"Start tracking your watches\"],\"JSwq8t\":[\"New account creation is currently disabled.\"],\"JY5Oyv\":[\"Database\"],\"JjsJnI\":[[\"0\"],\"/\",[\"1\"],\" episodes\"],\"JrFTcr\":[\"Connecting…\"],\"JwFbe8\":[\"TV\"],\"KDw4GX\":[\"Try again\"],\"KG6681\":[\"Episode watched\"],\"KIS/Sd\":[\"Sign out of other sessions\"],\"KK0ghs\":[\"Image cache\"],\"KVAoFR\":[\"unlimited\"],\"KcXJuc\":[\"Purging...\"],\"KhtG3o\":[\"Update Password\"],\"Khux7w\":[[\"0\",\"plural\",{\"one\":[\"#\",\" title\"],\"other\":[\"#\",\" titles\"]}]],\"KmWyx0\":[\"Job\"],\"KoF0x6\":[\"age \",[\"age\"]],\"Ksiej9\":[\"Requires an active Plex Pass subscription.\"],\"Kx9NEt\":[\"Invalid token\"],\"Lk6Jb/\":[\"Last polled \",[\"timeAgo\"]],\"LmEEic\":[\"Waiting for authorization...\"],\"LqKH42\":[\"Connect with \",[\"0\"]],\"Lrpjji\":[\"Disconnect \",[\"label\"]],\"Lu6Udx\":[\"Click \\\"+\\\" and select \\\"Custom Lists\\\"\"],\"MDoX++\":[\"Sonarr List URL\"],\"MRBlCJ\":[\"Failed to unmark some episodes\"],\"MUO7w9\":[\"Marked \\\"\",[\"titleName\"],\"\\\" as watched\"],\"MZbQHL\":[\"No results found.\"],\"MdxR1u\":[\"Choose your preferred display language\"],\"MkyzAV\":[\"Authorize Sofa to read your \",[\"0\"],\" library. No password shared.\"],\"N40H+G\":[\"All\"],\"N6SFhC\":[\"Sign in instead\"],\"N7h106\":[\"Sofa v\",[\"0\"],\" is available\"],\"N9RF2M\":[\"Click \\\"Add Webhook\\\" and paste the URL above\"],\"NBo4z0\":[\"Click <0>+ and select <1>Custom Lists\"],\"NICUmW\":[\"Director\"],\"NQzPoO\":[\"New backup\"],\"NSxl1l\":[\"More Settings\"],\"NVF43p\":[\"Time:\"],\"NdPMwS\":[\"In Your Library\"],\"NgaPSG\":[\"Failed to update schedule\"],\"O3oNi5\":[\"Email\"],\"OBcj5W\":[\"This will invalidate the current \",[\"label\"],\" URL. You'll need to update it in \",[\"label\"],\".\"],\"OL8hbM\":[\"New password must be at least 8 characters\"],\"ONWvwQ\":[\"Upload\"],\"OPFjyX\":[\"Install the <0>Webhook plugin<1/> from Jellyfin's plugin catalog\"],\"OW/+RD\":[\"Watch history\"],\"OZdaTZ\":[\"Person\"],\"OvO76u\":[\"Back to Login\"],\"OvdFIZ\":[\"Season watched\"],\"P2FLLe\":[\"View release\"],\"PBdLfg\":[\"No titles found for this genre.\"],\"PQ3qDa\":[\"Fetching your library data from \",[\"source\"],\"...\"],\"PjNoxI\":[\"Scheduled backup\"],\"Pn2B7/\":[\"Current password\"],\"PnEbL/\":[\"Rated \",[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"star\"],\"other\":[\"stars\"]}]],\"Pwqkdw\":[\"Loading…\"],\"Py87xY\":[\"Authorization succeeded but failed to fetch your library. Please try again.\"],\"Q3MPWA\":[\"Update password\"],\"QHcLEN\":[\"Connected\"],\"QK4UIx\":[\"Mark Watched\"],\"Qjlym2\":[\"Failed to create account\"],\"Qm1NmK\":[\"OR\"],\"Qoq+GP\":[\"Read more\"],\"QphVZW\":[\"Can't reach server\"],\"QqLJHH\":[\"This Year\"],\"R0yu2l\":[\"Catch up\"],\"R4YBui\":[\"Search for movies and TV shows to start tracking\"],\"RH46vw\":[[\"0\"],\"/\",[\"1\"],\" \",[\"2\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}]],\"RIR15/\":[\"Radarr List URL\"],\"RLe7Vk\":[\"Checking…\"],\"ROq8cl\":[\"Failed to parse file\"],\"RQq8Si\":[\"Registration opened\"],\"S0soqb\":[\"Failed to remove profile picture\"],\"S1McZh\":[\"Failed to upload avatar\"],\"S2ble5\":[[\"0\"],\" movies, \",[\"1\"],\" episodes\"],\"S2qPRR\":[\"Search movies & TV shows…\"],\"SDND4q\":[\"Not configured\"],\"SFdAk9\":[\"Unwatched S\",[\"seasonNum\"],\" E\",[\"epNum\"]],\"SUIUkB\":[\"Unwatch all\"],\"SbS+Bm\":[\"Regenerate URL\"],\"SlfejT\":[\"Error\"],\"Sp86ju\":[[\"0\"],\" ep\",[\"1\"]],\"SrfROI\":[\"Up next\"],\"SyPRjk\":[\"Sofa will automatically log movies and episodes when you finish watching them\"],\"T0/7WG\":[\"Next backup \",[\"distance\"]],\"TEaX6q\":[\"Backup deleted\"],\"TI7IKo\":[\"Rated \",[\"0\"],\" star\",[\"1\"]],\"TMb7iE\":[\"Profile picture removed\"],\"TqyQQS\":[\"Enable the <0>Playback event category\"],\"Tz0i8g\":[\"Settings\"],\"U+FxtW\":[\"Track what you watch. Know what's next.<0/>Your library, your data, your rules.\"],\"U3pytU\":[\"Admin\"],\"UC+OWB\":[\"Toggle open registration\"],\"UDMjsP\":[\"Quick Actions\"],\"UmQ6Fe\":[\"Purge image cache?\"],\"UtDm3q\":[\"URL copied to clipboard\"],\"V1Kh9Z\":[\"Episodes \",[\"select\"]],\"V6uzvC\":[\"More Like This\"],\"V9CuQ+\":[\"Connect to \",[\"source\"]],\"VAcXNz\":[\"Wednesday\"],\"VKyhZK\":[\"Title not found\"],\"VQvpro\":[\"Paste the Sonarr URL above into the List URL field\"],\"VhMDMg\":[\"Change Password\"],\"Vx0ayx\":[\"Failed to mark some episodes\"],\"WDC4PF\":[\"Episodes \",[\"periodSelect\"]],\"WEYdDv\":[\"Recommended\"],\"WMCwmR\":[\"Check for updates\"],\"WP48q2\":[\"Image Cache\"],\"WT1Ibn\":[\"Last run\"],\"Wb3E4g\":[\"Run now\"],\"WgF2UQ\":[\"You're running v\",[\"0\"],\".\"],\"WtWhSi\":[\"Rating removed\"],\"Wy/3II\":[\"Last polled \",[\"0\"]],\"XFCSYs\":[\"Cast\"],\"XN23JY\":[\"This will delete un-enriched stub titles that aren't in any user's library and clean up orphaned person records. Deleted titles will be re-imported if accessed again.\"],\"XZwihE\":[\"Ready — nothing received yet\"],\"XjTduw\":[\"Upload picture\"],\"YEGzVq\":[\"Failed to connect \",[\"label\"]],\"YErf89\":[\"Failed to purge metadata cache\"],\"YQ768h\":[\"Scene not found\"],\"YiRsXK\":[\"Clear Recently Viewed?\"],\"Yjp1zf\":[\"Don't have a server?\"],\"YqMfa9\":[\"Review what was found and choose what to import.\"],\"ZGUYm0\":[\"Ready — not polled yet\"],\"ZQKLI1\":[\"Danger Zone\"],\"ZVZUoU\":[[\"0\"],\" cached images\"],\"ZWTQ81\":[\"Watch on \",[\"name\"]],\"a3LDKx\":[\"Security\"],\"aLBUiR\":[\"Keeping <0><1><2>\",[\"0\"],\"<3>\",[\"1\"],\" backups.\"],\"aM0+kY\":[\"Purged \",[\"0\",\"plural\",{\"one\":[\"#\",\" stale title\"],\"other\":[\"#\",\" stale titles\"]}],\" and \",[\"1\",\"plural\",{\"one\":[\"#\",\" orphaned person\"],\"other\":[\"#\",\" orphaned persons\"]}]],\"aO1AxG\":[\"Episode unwatched\"],\"aV/hDI\":[\"Failed to disconnect \",[\"label\"]],\"acbSg0\":[\"Double tap to filter by \",[\"label\"]],\"agE7k4\":[\"Rated \",[\"stars\",\"plural\",{\"one\":[\"#\",\" star\"],\"other\":[\"#\",\" stars\"]}]],\"alPRaV\":[\"Titles on your Sofa watchlist will be automatically added for download when Radarr polls this list (every 12 hours by default)\"],\"aourBv\":[\"Scheduled backups enabled\"],\"apLLSU\":[\"Are you sure you want to sign out?\"],\"atc9MA\":[\"Open Emby, go to <0>Settings > Webhooks\"],\"auVUJO\":[\"Restore database?\"],\"ayqfr4\":[[\"label\"],\" URL regenerated\"],\"b/8KCH\":[\"This will mark every episode of this show as watched. You can undo this later by unmarking individual seasons.\"],\"b3Thhd\":[\"Upload failed\"],\"b5DFtH\":[\"Set password\"],\"bHYIks\":[\"Sign Out\"],\"bITrbE\":[\"Purge all\"],\"bNmdjU\":[\"Watchlist\"],\"bTRwag\":[\"Remove from Library\"],\"bXMotV\":[\"Marked as watched\"],\"bezGJ2\":[[\"0\"],\" images\"],\"boJlGf\":[\"Page Not Found\"],\"c1ssjI\":[\"Importing from \",[\"source\"]],\"c3b0B0\":[\"Get Started\"],\"c4b9Dm\":[\"No results for \\\"\",[\"debouncedQuery\"],\"\\\"\"],\"cQPKZt\":[\"Go to Dashboard\"],\"ccAJSB\":[\"Keeping\"],\"cgvva8\":[[\"0\"],\" ratings\"],\"cnGeoo\":[\"Delete\"],\"cpE88+\":[\"Create your account\"],\"d/6MoL\":[\"Manage your account and preferences\"],\"dA7BWh\":[\"Requires Emby Server 4.7.9+ and an active Emby Premiere license.\"],\"dEgA5A\":[\"Cancel\"],\"dTZwve\":[\"Marked all episodes as watched\"],\"dUCJry\":[\"Newest\"],\"ddwpAr\":[\"Warnings (\",[\"0\"],\")\"],\"e/ToF5\":[\"Sign in with \",[\"0\"]],\"e9sZMS\":[\"This will permanently delete the backup from <0>\",[\"0\"],\". This cannot be undone.\"],\"eFRooE\":[\"Last run failed\"],\"eM39Om\":[\"Watched all of \",[\"seasonLabel\"]],\"eZjYb8\":[\"Writer\"],\"ecUA8p\":[\"Today\"],\"evBxZy\":[\"Where to Watch\"],\"ezDa1h\":[\"Open docs\"],\"f+m696\":[\"An unexpected error occurred while loading this page. You can try again or head back to the dashboard.\"],\"f/AKdU\":[\"Are you sure you want to disconnect \",[\"label\"],\"? The current URL will stop working.\"],\"f7ax8J\":[\"Open in browser…\"],\"fMPkxb\":[\"Show more\"],\"fNMqNn\":[\"Popular TV Shows\"],\"fObVvy\":[\"Profile picture updated\"],\"fRettQ\":[[\"0\"],\" items\"],\"fUDRF9\":[\"Watchlisted\"],\"fcWrnU\":[\"Sign out\"],\"fgLNSM\":[\"Register\"],\"fsAEqk\":[\"Continue Watching\"],\"fzAeQI\":[\"Registering on \",[\"serverHost\"]],\"g+gBfk\":[\"Parsing...\"],\"g4JYff\":[\"Imported \",[\"0\"],\" items from \",[\"1\"]],\"gKtb5i\":[\"Titles on your Sofa watchlist will be automatically added for download when Sonarr polls this list (every 6 hours by default)\"],\"gaIAMq\":[\"Remove picture\"],\"gbEEMp\":[\"Delete backup\"],\"gcNLi0\":[\"Update checks enabled\"],\"gmB6oO\":[\"Schedule\"],\"h/T5Yb\":[\"Open registration\"],\"h4yKYk\":[\"Next run\"],\"h7MgpO\":[\"Keyboard Shortcuts\"],\"hTXYdY\":[\"Failed to create backup\"],\"hXYY5Q\":[\"Unwatched all of \",[\"0\"]],\"he3ygx\":[\"Copy\"],\"hjGupC\":[\"Lost connection to import. Check status in settings.\"],\"hlqjFc\":[\"In library\"],\"hou0tP\":[\"Stream\"],\"hty0d5\":[\"Monday\"],\"i0qMbr\":[\"Home\"],\"i9rcQ/\":[\"Movies\"],\"iGma9e\":[\"Update failed\"],\"iSLIjg\":[\"Connect\"],\"iWv3ck\":[\"Failed to catch up\"],\"iXZ09g\":[\"Mark as Completed\"],\"id08cd\":[\"Watched S\",[\"seasonNum\"],\" E\",[\"epNum\"]],\"ih87w/\":[\"Add to Library\"],\"ihn4zD\":[\"Search…\"],\"itDEco\":[\"Already have an account? Sign in\"],\"itheEn\":[\"With Ads\"],\"iwCRIF\":[\"You'll be signed out to change the server URL.\"],\"j5GBIy\":[\"Open Radarr, go to Settings > Import Lists\"],\"jAe8l4\":[[\"0\"],\" backups · last <0/>\"],\"jB3sfe\":[\"Manual backup\"],\"jFnMJ8\":[\"Double tap to clear this filter\"],\"jQsPwL\":[\"Scheduled backups disabled\"],\"jSjGeu\":[[\"0\"],\" items have no external IDs and will be resolved by title search, which may be less accurate.\"],\"jX6Gzg\":[\"This will replace your entire database with the uploaded file. A safety backup of your current data will be created first. Active sessions may need to refresh after restore.\"],\"jiHVUy\":[\"Pre-restore backup\"],\"k6c41p\":[\"Background jobs\"],\"kBDOjB\":[\"Backup schedule\"],\"kkDQ8m\":[\"Thursday\"],\"klOeIX\":[\"Failed to change password\"],\"ks3XeZ\":[\"Open Sonarr, go to Settings > Import Lists\"],\"kvuCtu\":[\"Something went wrong while loading this title. Please try again.\"],\"kx0s+n\":[\"Results\"],\"l2wcoS\":[\"Last run succeeded\"],\"l3s5ri\":[\"Import\"],\"lCF0wC\":[\"Refresh\"],\"lJ1yo4\":[\"Failed to sign in\"],\"lVqzRx\":[\"Click <0>Add Webhook and paste the URL above\"],\"lXkUEV\":[\"Availability\"],\"lcLe89\":[\"Search for movies, TV shows, or run commands\"],\"lfVyvz\":[\"Add to Watchlist\"],\"llDXYJ\":[\"Backups\"],\"llkZR0\":[\"Could not load integrations\"],\"ln9/n9\":[\"Don't have an account?\"],\"lpIMne\":[\"Passwords do not match\"],\"lqY3WY\":[\"Change Server\"],\"luoodD\":[\"Setup required\"],\"m5WhJy\":[\"Automatic update checks\"],\"mIx58S\":[\"Purge images\"],\"mYBORk\":[\"Movie\"],\"ml9cU0\":[\"Failed to regenerate \",[\"label\"],\" URL\"],\"mqwkjd\":[\"Health status\"],\"mqxHH7\":[\"Go to Explore\"],\"mzI/c+\":[\"Download\"],\"n1ekoW\":[\"Sign In\"],\"n3Pzd7\":[\"Backup created\"],\"nBHvPL\":[\"Cancel editing\"],\"nJw77c\":[[\"0\",\"plural\",{\"one\":[\"#\",\" image\"],\"other\":[\"#\",\" images\"]}]],\"nO0Qnj\":[\"Your code:\"],\"nRP1xx\":[\"Need more help?\"],\"nV1LjT\":[\"Enable the <0>Playback Stop notification type\"],\"nVsE67\":[\"Mark as Watching\"],\"nZ7lF1\":[\"Device code expired. Please try again.\"],\"nbfdhU\":[\"Integrations\"],\"nbmpf9\":[\"Purge metadata\"],\"nnKJTm\":[\"Failed to mark all episodes as watched\"],\"nszbQG\":[\"No backups yet\"],\"nuh/Wq\":[\"Webhook URL\"],\"nwtY4N\":[\"Something went wrong\"],\"oAIA3w\":[\"Search for movies, shows, or people\"],\"oC8IMh\":[\"Search movies, shows, people...\"],\"oNvZcA\":[\"Episodes this week\"],\"oXq+Wr\":[[\"label\"],\" connected\"],\"ogtYkT\":[\"Password updated\"],\"ojtedN\":[\"Open Radarr, go to <0>Settings > Import Lists\"],\"olMi35\":[\"Recommended for You\"],\"p+PZEl\":[\"Here's what's happening with your library\"],\"pAtylB\":[\"Not Found\"],\"pG9pq1\":[\"Deleted \",[\"0\",\"plural\",{\"one\":[\"#\",\" file\"],\"other\":[\"#\",\" files\"]}],\", freed \",[\"freed\"]],\"pWWFjv\":[\"Checked <0/>\"],\"ph76H6\":[\"Allow new users to create accounts\"],\"pjgw0N\":[\"Choose how to import your \",[\"0\"],\" data.\"],\"puo3W3\":[\"Redirecting…\"],\"q3GraM\":[\"...and \",[\"0\"],\" more\"],\"q6nrFE\":[\"died at \",[\"age\"]],\"q6pUQ9\":[\"Upload a .db file to replace the current database. A safety backup is created first.\"],\"q8yluz\":[\"Your name\"],\"qA6VR5\":[\"Requires <0>Emby Server 4.7.9+ and an active <1>Emby Premiere<2/> license.\"],\"qF2IBM\":[\"Movies this month\"],\"qHbApt\":[\"Failed to load backup schedule settings.\"],\"qLB1zX\":[\"Upload a \",[\"0\"],\" export from your \",[\"1\"],\" account settings.\"],\"qPNzfu\":[\"Update checks disabled\"],\"qWoML/\":[\"Add a new webhook and paste the URL above\"],\"qiOIiY\":[\"Buy\"],\"qn1X6N\":[\"Failed to update registration setting\"],\"qqWcBV\":[\"Completed\"],\"qqeAJM\":[\"Never\"],\"r9aDAY\":[[\"count\"],\" earlier \",[\"count\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}],\" unwatched\"],\"rLgPvm\":[\"Backup\"],\"rSTpb5\":[\"Server Health\"],\"rdymVD\":[\"Trigger job\"],\"rtir7c\":[\"unknown\"],\"s0U4ZZ\":[\"TV episodes\"],\"s4vVUm\":[\"Could not load person details\"],\"s9dVME\":[\"Failed to unmark episode\"],\"sGH11W\":[\"Server\"],\"sl/qWH\":[\"Upload profile picture\"],\"sstysK\":[\"Failed to mark episode\"],\"stZeiF\":[[\"watched\"],\"/\",[\"total\"],\" episodes\"],\"t/Ch8S\":[\"Marked as watching\"],\"t/YqKh\":[\"Remove\"],\"tfDRzk\":[\"Save\"],\"tiymc0\":[\"Something went wrong…\"],\"u9ugU7\":[[\"watchedCount\"],\"/\",[\"0\"],\" \",[\"1\",\"plural\",{\"one\":[\"episode\"],\"other\":[\"episodes\"]}]],\"uBAxNB\":[\"Editor\"],\"uBrbSu\":[\"Failed to start \",[\"0\"],\" connection\"],\"uM6jnS\":[\"Restore failed\"],\"uMrJrX\":[\"Admin only\"],\"uswLvZ\":[\"The title you're looking for doesn't exist or may have been removed from the database.\"],\"uxOntd\":[[\"watchedCount\"],\" of \",[\"0\"],\" episodes watched\"],\"v2CA3w\":[\"Change Photo\"],\"v5ipVu\":[\"Mark all episodes as watched?\"],\"vKfeax\":[\"Open Emby, go to Settings > Webhooks\"],\"vXIe7J\":[\"Language\"],\"vuCCZ7\":[\"Failed to parse import data\"],\"vwKERN\":[\"Failed to delete backup\"],\"w8pqsh\":[\"Mark All Watched\"],\"wA7B2T\":[\"New accounts are not being accepted right now. Contact the admin if you need access.\"],\"wGFX13\":[\"Starting at\"],\"wR1UAy\":[\"Popular Movies\"],\"wRWcdL\":[\"Play trailer\"],\"wThGrS\":[\"App Settings\"],\"wZK4Xg\":[\"Never run\"],\"wdLxgL\":[\"Member since \",[\"memberSince\"]],\"wja8aL\":[\"Untitled\"],\"wtGebH\":[\"The person you're looking for doesn't exist or may have been removed from the database.\"],\"wtsbt5\":[\"Add to watchlist\"],\"wtuVU4\":[\"Frequency\"],\"wx7pwA\":[\"Mark as Watched\"],\"x4ZiTl\":[\"Setup instructions\"],\"xCJdfg\":[\"Clear\"],\"xGVfLh\":[\"Continue\"],\"xLoCm2\":[\"Check configuration\"],\"xSrU2g\":[[\"healthyCount\"],\" of \",[\"0\"],\" jobs healthy\"],\"xazqmy\":[\"Seasons\"],\"xbBXhy\":[\"Periodically check GitHub for new Sofa releases\"],\"xrh2/M\":[\"Failed to mark as watched\"],\"y3e9pF\":[\"Delete backup?\"],\"y6Urel\":[\"Next Episode\"],\"yGvjAo\":[\"Update available: \",[\"0\"]],\"yKu/3Y\":[\"Restore\"],\"yYxB17\":[\"Clear all\"],\"ybtG1U\":[[\"0\"],\" backup\",[\"1\"],\" stored\"],\"yey/zg\":[\"Database restored. Reloading...\"],\"ygo0l/\":[\"Start tracking movies and shows\"],\"yvwIbI\":[\"Upload export file\"],\"yz7wBu\":[\"Close\"],\"z1U/Fh\":[\"Rating\"],\"z5evln\":[\"No internet connection\"],\"zAvS8w\":[\"Sign in to continue\"],\"zEqK2w\":[\"The page you're looking for doesn't exist.\"],\"zFkiTv\":[\"Install the Webhook plugin from Jellyfin's plugin catalog\"],\"zNyR4f\":[\"Set your preferred quality profile and root folder\"],\"za8Le/\":[\"Registration Closed\"],\"zb77GC\":[\"Rent\"],\"ztAdhw\":[\"Name updated\"]}")as Messages; \ No newline at end of file diff --git a/packages/i18n/src/test-utils.tsx b/packages/i18n/src/test-utils.tsx new file mode 100644 index 0000000..37305a8 --- /dev/null +++ b/packages/i18n/src/test-utils.tsx @@ -0,0 +1,13 @@ +import { i18n } from "@lingui/core"; +import { I18nProvider } from "@lingui/react"; +import type { ReactNode } from "react"; +import { messages } from "./po/en"; + +i18n.load("en", messages); +i18n.activate("en"); + +export function TestI18nProvider({ children }: { children: ReactNode }) { + return {children}; +} + +export { i18n as testI18n }; diff --git a/packages/i18n/tsconfig.json b/packages/i18n/tsconfig.json new file mode 100644 index 0000000..57475a2 --- /dev/null +++ b/packages/i18n/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "isolatedModules": true, + "esModuleInterop": true, + "types": ["bun"] + }, + "include": ["src"] +} diff --git a/packages/logger/package.json b/packages/logger/package.json index 9a381db..4740230 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -6,6 +6,11 @@ "exports": { ".": "./src/index.ts" }, + "scripts": { + "lint": "biome check", + "format": "biome format --write", + "check-types": "tsc --noEmit" + }, "dependencies": { "pino": "10.3.1", "pino-pretty": "13.1.3"