mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-10-16 06:25:42 -04:00
feat: add i18n with LingUI, Crowdin integration, and typed error codes (#16)
* feat: add i18n with LingUI, Crowdin integration, and typed error codes - Add `@sofa/i18n` shared package with LingUI v5 (v6-ready config using `defineConfig` + `@lingui/format-po`), eager English + lazy-loaded fr/de/es/it/pt catalogs, `Intl`-based date/number format utilities, and test helpers - Wire `@lingui/vite-plugin` + babel macro plugin for web, and `@lingui/metro-transformer` + babel config for native - Add `I18nProvider` to both app roots with locale auto-detection (navigator.language / expo-localization) and persistence (localStorage / MMKV) - Wrap all ~512 user-facing strings across web and native with LingUI macros (`<Trans>`, `useLingui`/`t`, `i18n._(msg`...`)`, `plural`) - Add language switcher to Settings in both apps - Add `@sofa/api/errors` with 13 typed `AppErrorCode` values and `appErrorData()` helper for contract `.errors()` schemas - Update all oRPC contract error definitions with typed `data` fields; convert import procedure `throw new Error()` to `ORPCError` with codes - Add per-app `error-messages.ts` utilities that map error codes to localized strings; update global `QueryCache.onError` handlers to stop leaking raw `error.message` to users - Add `crowdin.yml` config and `.github/workflows/crowdin.yml` for automated source upload on merge and translation pull via dispatch - Add `lingui.config.ts` at repo root with `bun run i18n:extract` and `bun run i18n:compile` convenience scripts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: add Crowdin language mapping and ignore context file * fix(i18n): address 20 localization quality issues - Stop leaking raw error.message to users in change-password and account-section; use localized fallbacks instead - Add typed error data to discover contract's .errors() schema - Replace all manual English plural suffixes with LingUI plural() macro in episode counts, backup counts, star ratings, title/image counts - Replace date-fns formatDistanceToNow and hardcoded date patterns with locale-aware formatDate/formatRelativeTime from @sofa/i18n/format - Convert integration-configs from module-scope translations (frozen at import time) to lazy getIntegrationConfigs(i18n) function - Combine split Trans fragments into single translatable units in backup-schedule retention sentence and stats-display period labels - Localize TV media type badge and full episode accessibilityLabel - Replace Android-incompatible Alert.alert language picker (max 3 buttons) with zeego DropdownMenu - Await async locale initialization before hiding splash screen so non-English users don't see English flash on cold start - Add @lingui/core as direct native app dependency Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(i18n): add Intl polyfills for Hermes, replace DropdownMenu language picker, and remove date-fns - Add `@formatjs/intl-*` polyfills loaded at app startup in `intl-polyfills.ts` so Hermes on Android has full `Intl` support for locale-aware formatting - Alias `@formatjs/icu-messageformat-parser` to its `no-parser` variant in metro config to reduce bundle size - Replace the zeego `DropdownMenu` language picker in Settings with a new `SelectModal` component that works reliably on all platforms without the Android 3-button limit - Replace `date-fns` `format`/`parseISO` in `person/[id].tsx` with `formatDate` from `@sofa/i18n/format`; remove `date-fns` from native and web `package.json` - Refactor `StatusActionButton` to use a `StatusLabel` component with `<Trans>` rather than a `getStatusConfig(t)` factory so labels are always reactive to locale changes - Fix crowdin workflow to use root `bun run i18n:compile` script instead of `cd packages/i18n && bun run compile` - Wrap root layout `GestureHandlerRootView` in `SafeAreaProvider` and add `.catch` to locale-ready promise to prevent unhandled rejections blocking the splash screen * fix(i18n): localize integration status helpers, fix stats-display Trans wrapping, and clean up SelectModal - Localize `webhookStatus` and `listStatus` in `integration-card.tsx` using `i18n._(msg`...`)` so status strings are translated instead of always rendering in English - Wrap "Movies {select}" and "Episodes {select}" in a single `<Trans>` in `stats-display.tsx` so the period selector element is embedded inside the translatable unit rather than concatenated outside it - Fix locale activation order in Settings: close the modal first, then `activateLocale`, and only call `setPersistedLocale` on success to avoid persisting a locale that failed to load - Remove the redundant `SafeAreaProvider`/`SafeAreaView` wrapper from `SelectModal` — the root layout already provides `SafeAreaProvider` - Recompile all six `.po`/`.ts` catalogs to pick up new and updated message strings * chore(i18n): crowdin sync --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
e50ab44783
commit
ad0497ccd6
142 files changed
+18002
-1243
No files matched your search
@@ -6,6 +6,7 @@
|
||||
"exports": {
|
||||
"./contract": "./src/contract.ts",
|
||||
"./schemas": "./src/schemas.ts",
|
||||
"./errors": "./src/errors.ts",
|
||||
"./utils": "./src/utils.ts"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 = <T extends AppErrorCode>(code: T) =>
|
||||
z.object({ code: z.literal(code) });
|
||||
@@ -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:",
|
||||
|
||||
@@ -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<BackupInfo> {
|
||||
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`,
|
||||
|
||||
@@ -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:"
|
||||
}
|
||||
}
|
||||
@@ -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]}`;
|
||||
}
|
||||
@@ -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<string, () => 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<void> {
|
||||
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 };
|
||||
@@ -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" },
|
||||
];
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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\"],\"</0>. 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</0>\"],\"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</0>\"],\"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/></0>\"],\"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</0> 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>PlexLine truncated
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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\"],\"</0>. 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</0>\"],\"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</0>\"],\"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/></0>\"],\"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</0> 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>PlexLine truncated
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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\"],\"</0>. 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</0>\"],\"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</0>\"],\"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/></0>\"],\"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</0> 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>PlexLine truncated
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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\"],\"</0>. 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</0>\"],\"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</0>\"],\"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/></0>\"],\"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</0> 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>PlexLine truncated
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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\"],\"</0>. 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</0>\"],\"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</0>\"],\"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/></0>\"],\"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</0> 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>PlexLine truncated
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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\"],\"</0>. 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</0>\"],\"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</0>\"],\"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/></0>\"],\"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</0> 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>PlexLine truncated
|
||||
@@ -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 <I18nProvider i18n={i18n}>{children}</I18nProvider>;
|
||||
}
|
||||
|
||||
export { i18n as testI18n };
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user