From 56a7a7851b09cb30a5cd543c8cb4f926109b4290 Mon Sep 17 00:00:00 2001
From: Fuwn
Date: Sun, 24 May 2026 13:22:34 +0000
Subject: refactor(locale): move hardcoded UI strings into english locale
Adds optional namespaces (common, errors, commandPalette, headTitle,
notifications, schedule, events, home, reader, routes, badgePreview,
badgeWall) and extends existing ones (settings.*, lists.*, tools.*,
user.*, hololive.*) on the Locale interface. New fields are optional
so japanese.ts can omit them; svelte-i18n's fallbackLocale handles
the runtime miss.
HeadTitle gains an optional routeKey prop for type-safe lookup.
defaultActions becomes a factory so the command palette re-reads
locale on language toggle. The existing JP feedback translation
in routes/settings is preserved via japanese.ts.
Out of scope (kept hardcoded): service-worker.ts, app.html,
Landing*.svelte, tools.ts registry, Easter Event 2025 pages.
---
src/lib/Tools/ActivityHistory/Grid.svelte | 3 +-
src/lib/Tools/ActivityHistory/Tool.svelte | 5 +-
src/lib/Tools/EpisodeDiscussionCollector.svelte | 9 +-
src/lib/Tools/FollowFix.svelte | 3 +-
src/lib/Tools/InputTemplate.svelte | 3 +-
src/lib/Tools/Likes.svelte | 3 +-
src/lib/Tools/Picker.svelte | 5 +-
src/lib/Tools/SequelCatcher/List.svelte | 9 +-
src/lib/Tools/SequelCatcher/Tool.svelte | 11 +-
src/lib/Tools/SequelSpy/Tool.svelte | 12 +-
src/lib/Tools/Tracker/Tool.svelte | 34 ++++--
src/lib/Tools/Wrapped/Tool.svelte | 141 ++++++++++++++----------
12 files changed, 144 insertions(+), 94 deletions(-)
(limited to 'src/lib/Tools')
diff --git a/src/lib/Tools/ActivityHistory/Grid.svelte b/src/lib/Tools/ActivityHistory/Grid.svelte
index 6a931ab2..afa9cd8f 100644
--- a/src/lib/Tools/ActivityHistory/Grid.svelte
+++ b/src/lib/Tools/ActivityHistory/Grid.svelte
@@ -11,6 +11,7 @@ import { clearAllParameters } from "../../Utility/parameters";
import Skeleton from "$lib/Loading/Skeleton.svelte";
import tooltip from "$lib/Tooltip/tooltip";
import LogInRestricted from "$lib/Error/LogInRestricted.svelte";
+import locale from "$stores/locale";
export let user: AniListAuthorisation;
export let activityData: ActivityHistoryEntry[] | null = null;
@@ -52,7 +53,7 @@ const gradientColour = (amount: number, maxAmount: number, baseHue: number) => {
role="button"
tabindex="0"
use:tooltip
- title={`Date: ${new Date(activity.date * 1000).toLocaleDateString()}\nAmount: ${
+ title={`${$locale().tools.activityHistory?.dateLabel ?? 'Date:'} ${new Date(activity.date * 1000).toLocaleDateString()}\n${$locale().tools.activityHistory?.amountLabel ?? 'Amount:'} ${
activity.amount
}`}
>
diff --git a/src/lib/Tools/ActivityHistory/Tool.svelte b/src/lib/Tools/ActivityHistory/Tool.svelte
index 3cf7b09e..5e84db9e 100644
--- a/src/lib/Tools/ActivityHistory/Tool.svelte
+++ b/src/lib/Tools/ActivityHistory/Tool.svelte
@@ -14,6 +14,7 @@ import ActivityHistoryGrid from "./Grid.svelte";
import SettingHint from "$lib/Settings/SettingHint.svelte";
import Skeleton from "$lib/Loading/Skeleton.svelte";
import LogInRestricted from "$lib/Error/LogInRestricted.svelte";
+import locale from "$stores/locale";
export let user: AniListAuthorisation;
@@ -99,10 +100,10 @@ const screenshot = async () => {
- Days in risk of developing an activity history hole
+ {$locale().tools.activityHistory?.daysAtRisk}
- Days in which you did not log any activity or only have one activity logged.
+ {$locale().tools.activityHistory?.daysAtRiskHint}
diff --git a/src/lib/Tools/EpisodeDiscussionCollector.svelte b/src/lib/Tools/EpisodeDiscussionCollector.svelte
index 68addbdf..2bbefc0a 100644
--- a/src/lib/Tools/EpisodeDiscussionCollector.svelte
+++ b/src/lib/Tools/EpisodeDiscussionCollector.svelte
@@ -6,6 +6,7 @@ import { clearAllParameters } from "../Utility/parameters";
import Skeleton from "$lib/Loading/Skeleton.svelte";
import InputTemplate from "./InputTemplate.svelte";
import tooltip from "$lib/Tooltip/tooltip";
+import locale from "$stores/locale";
let submission = "";
@@ -46,17 +47,17 @@ onMount(clearAllParameters);
{/each}
{:catch}
-
Threads could not be loaded. You might have been rate-limited.
+
{$locale().tools.episodeDiscussion?.rateLimit}
- Try again in a few minutes. If the problem persists, please contact @fuwn on AniList.
+ >{$locale().tools.episodeDiscussion?.contactSupport?.split('@fuwn')[1]}
{/await}
{:else}
- Enter a username to search for to continue.
+ {$locale().tools.episodeDiscussion?.enterUsername}
{/if}
diff --git a/src/lib/Tools/FollowFix.svelte b/src/lib/Tools/FollowFix.svelte
index 6c599569..93d07739 100644
--- a/src/lib/Tools/FollowFix.svelte
+++ b/src/lib/Tools/FollowFix.svelte
@@ -2,6 +2,7 @@
import { toggleFollow } from "$lib/Data/AniList/follow";
import type { AniListAuthorisation } from "$lib/Data/AniList/identity";
import LogInRestricted from "$lib/Error/LogInRestricted.svelte";
+import locale from "$stores/locale";
export let user: AniListAuthorisation;
@@ -28,7 +29,7 @@ let submit = "";
/>
{#if input.length > 0}
(submit = input)}>
- Toggle follow for {input}
+ {$locale({ values: { input } }).tools.followFix?.toggleFor}
{/if}
diff --git a/src/lib/Tools/InputTemplate.svelte b/src/lib/Tools/InputTemplate.svelte
index c90d9b1c..c9d96dfb 100644
--- a/src/lib/Tools/InputTemplate.svelte
+++ b/src/lib/Tools/InputTemplate.svelte
@@ -3,6 +3,7 @@ import Spacer from "$lib/Layout/Spacer.svelte";
import { clearAllParameters } from "$lib/Utility/parameters";
import { onMount } from "svelte";
import SettingHint from "$lib/Settings/SettingHint.svelte";
+import locale from "$stores/locale";
export let field: string;
export let submission: string;
@@ -46,7 +47,7 @@ onMount(() => clearAllParameters(saveParameters));
onSubmit();
}}
- title="Or click your Enter key"
+ title={$locale().tools.input?.pressEnter}
data-umami-event={event}
>
{submitText}
diff --git a/src/lib/Tools/Likes.svelte b/src/lib/Tools/Likes.svelte
index dde5c755..70739ee6 100644
--- a/src/lib/Tools/Likes.svelte
+++ b/src/lib/Tools/Likes.svelte
@@ -5,6 +5,7 @@ import RateLimited from "$lib/Error/RateLimited.svelte";
import Skeleton from "$lib/Loading/Skeleton.svelte";
import tooltip from "$lib/Tooltip/tooltip";
import settings from "$stores/settings";
+import locale from "$stores/locale";
import InputTemplate from "./InputTemplate.svelte";
let submission = "";
@@ -56,6 +57,6 @@ $: likesPromise =
{/await}
{:else}
- Please enter a valid Activity or Thread URL.
+ {$locale().tools.likes?.invalidUrl}
{/if}
diff --git a/src/lib/Tools/Picker.svelte b/src/lib/Tools/Picker.svelte
index ffece7b6..ad8d3444 100644
--- a/src/lib/Tools/Picker.svelte
+++ b/src/lib/Tools/Picker.svelte
@@ -2,6 +2,7 @@
import { browser } from "$app/environment";
import { goto } from "$app/navigation";
import root from "$lib/Utility/root";
+import locale from "$stores/locale";
import { tools } from "./tools";
export let tool: string;
@@ -14,7 +15,9 @@ export let tool: string;
if (browser) goto(root(`/tools/${tool}`));
}}
>
-
+
{#each Object.keys(tools).filter((t) => t !== 'default' && !tools[t].hidden) as t}
diff --git a/src/lib/Tools/SequelCatcher/List.svelte b/src/lib/Tools/SequelCatcher/List.svelte
index b1512e22..4b1b8107 100644
--- a/src/lib/Tools/SequelCatcher/List.svelte
+++ b/src/lib/Tools/SequelCatcher/List.svelte
@@ -4,6 +4,7 @@ import { filterRelations, type Media } from "$lib/Data/AniList/media";
import MediaTitleDisplay from "$lib/List/MediaTitleDisplay.svelte";
import { outboundLink } from "$lib/Media/links";
import settings from "$stores/settings";
+import locale from "$stores/locale";
export let mediaListUnchecked: Media[];
@@ -25,11 +26,11 @@ const matchCheck = (media: Media | undefined, swap = false) =>
: undefined;
- Include current (watching, rewatching,
-paused)
+
+{$locale().tools.sequelCatcher?.includeCurrent}
- Include side stories (e.g., OVAs,
-specials, etc.)
+
+{$locale().tools.sequelCatcher?.includeSideStories}
diff --git a/src/lib/Tools/SequelCatcher/Tool.svelte b/src/lib/Tools/SequelCatcher/Tool.svelte
index 727a3a6c..f75b1f78 100644
--- a/src/lib/Tools/SequelCatcher/Tool.svelte
+++ b/src/lib/Tools/SequelCatcher/Tool.svelte
@@ -12,6 +12,7 @@ import lastPruneTimes from "$stores/lastPruneTimes";
import Message from "$lib/Loading/Message.svelte";
import Skeleton from "$lib/Loading/Skeleton.svelte";
import Username from "$lib/Layout/Username.svelte";
+import locale from "$stores/locale";
export let user: AniListAuthorisation;
@@ -69,13 +70,19 @@ onMount(async () => {
/>
{/if}
{:catch}
- Error fetching media.
+ {$locale().tools.wrapped?.errorFetchingMedia ?? 'Error fetching media.'}
{/await}
- Thanks to and for the idea!
+ {$locale().tools.sequelCatcher?.credit?.split('@sevengirl')[0]}{$locale().tools.sequelCatcher?.credit?.split('@sevengirl')[1]?.split('@esthereae')[0]}{$locale().tools.sequelCatcher?.credit?.split('@esthereae')[1]}
{/if}
diff --git a/src/lib/Tools/SequelSpy/Tool.svelte b/src/lib/Tools/SequelSpy/Tool.svelte
index 71056694..87931176 100644
--- a/src/lib/Tools/SequelSpy/Tool.svelte
+++ b/src/lib/Tools/SequelSpy/Tool.svelte
@@ -10,6 +10,7 @@ import { season as getSeason } from "$lib/Media/Anime/season";
import Skeleton from "$lib/Loading/Skeleton.svelte";
import identity from "$stores/identity";
import LogInRestricted from "$lib/Error/LogInRestricted.svelte";
+import locale from "$stores/locale";
import Prequels from "./Prequels.svelte";
export let user: AniListAuthorisation;
@@ -45,10 +46,10 @@ onMount(() => clearAllParameters(["year", "season"]));
@@ -61,7 +62,6 @@ onMount(() => clearAllParameters(["year", "season"]));
- The count ratio is the number of episodes you've seen of any direct prequels, and the total
- number of episodes of all direct prequels.
+ {$locale().tools.sequelSpy?.countRatio}
{/if}
diff --git a/src/lib/Tools/Tracker/Tool.svelte b/src/lib/Tools/Tracker/Tool.svelte
index 8f7a3197..b495522a 100644
--- a/src/lib/Tools/Tracker/Tool.svelte
+++ b/src/lib/Tools/Tracker/Tool.svelte
@@ -4,6 +4,8 @@ import { v6 as uuidv6 } from "uuid";
import { database, type TrackerEntry } from "$lib/Database/IDB/tracker";
import { onMount } from "svelte";
import Message from "$lib/Loading/Message.svelte";
+import locale from "$stores/locale";
+import { get } from "svelte/store";
let url = "";
let title = "";
@@ -34,15 +36,19 @@ const adjustEntry = (id: string, to: number) => {
const addEntry = async (url: string, title: string, progress: number) => {
if (!url || !title) {
- error = "URL and title are required fields";
+ error =
+ get(locale)().tools.tracker?.urlTitleRequired ??
+ "URL and title are required fields";
return;
}
if (listAccess.some((entry) => entry.url === url)) {
- error =
- "Entry with URL already exists: " +
- listAccess.find((entry) => entry.url === url)?.title;
+ const existing = listAccess.find((entry) => entry.url === url)?.title;
+
+ error = (
+ get(locale)().tools.tracker?.entryExists ?? "Entry with URL already exists: {url}"
+ ).replace("{url}", existing ?? "");
return;
}
@@ -55,7 +61,9 @@ const addEntry = async (url: string, title: string, progress: number) => {
const deleteEntry = async (id: string) => {
if (confirmDelete !== 1) {
confirmDelete = 1;
- error = "Click again to confirm deletion";
+ error =
+ get(locale)().tools.tracker?.confirmDelete ??
+ "Click again to confirm deletion";
return;
}
@@ -73,10 +81,16 @@ const deleteEntry = async (id: string) => {
- With many activities, it may take multiple attempts to obtain all of your activity
- history from AniList. If this occurs, wait one minute and try again to continue populating
- your local activity history database.
+ {$locale().tools.wrapped?.multiAttemptPrefix}many{$locale().tools.wrapped
+ ?.multiAttemptSuffix}