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) => {

    Error: {error}

    {/if} - - - - + + + + @@ -120,7 +134,7 @@ const deleteEntry = async (id: string) => { + | - + diff --git a/src/lib/Tools/Wrapped/Tool.svelte b/src/lib/Tools/Wrapped/Tool.svelte index b04bc5f6..b7d67cb0 100644 --- a/src/lib/Tools/Wrapped/Tool.svelte +++ b/src/lib/Tools/Wrapped/Tool.svelte @@ -2,6 +2,7 @@ import Spacer from "$lib/Layout/Spacer.svelte"; import "./wrapped.css"; import userIdentity from "$stores/identity"; +import locale from "$stores/locale"; import type { AniListAuthorisation } from "$lib/Data/AniList/identity"; import { onMount } from "svelte"; import { @@ -214,33 +215,36 @@ $: { updateWidth(); } $: genreTagTitle = (() => { + const w = $locale().tools.wrapped; switch (genreTagsSort) { case SortOptions.SCORE: - return "Highest Rated"; + return w?.highestRated ?? "Highest Rated"; case SortOptions.MINUTES_WATCHED: - return "Most Watched"; + return w?.mostWatched ?? "Most Watched"; case SortOptions.COUNT: - return "Most Common"; + return w?.mostCommon ?? "Most Common"; } })(); $: animeMostTitle = (() => { + const w = $locale().tools.wrapped; switch (mediaSort) { case SortOptions.SCORE: - return "Highest Rated"; + return w?.highestRated ?? "Highest Rated"; case SortOptions.MINUTES_WATCHED: - return "Most Watched"; + return w?.mostWatched ?? "Most Watched"; case SortOptions.COUNT: - return "Most Common"; + return w?.mostCommon ?? "Most Common"; } })(); $: mangaMostTitle = (() => { + const w = $locale().tools.wrapped; switch (mediaSort) { case SortOptions.SCORE: - return "Highest Rated"; + return w?.highestRated ?? "Highest Rated"; case SortOptions.MINUTES_WATCHED: - return "Most Read"; + return w?.mostRead ?? "Most Read"; case SortOptions.COUNT: - return "Most Common"; + return w?.mostCommon ?? "Most Common"; } })(); @@ -829,12 +833,12 @@ const pruneFullYear = async () => { {#if shouldFetchData} {#key fetchKey} {#await selectedYear !== currentYear || useFullActivityHistory || new Date().getMonth() <= 6 ? fullActivityHistory(user, $userIdentity, selectedYear, disableLoopingActivityCounter) : getActivityHistory($userIdentity)} - + {:then activities} {#await wrapped(user, $userIdentity, selectedYear, false, disableLoopingActivityCounter)} - + {:then wrapped} @@ -920,9 +924,8 @@ const pruneFullYear = async () => { > {#if useFullActivityHistory}

    - 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}

    {/if} @@ -970,7 +973,7 @@ const pruneFullYear = async () => { id="watermark" data-umami-event="Load Wrapped Data" > - Click load data! + {$locale().tools.wrapped?.clickLoadData} @@ -985,7 +988,7 @@ const pruneFullYear = async () => {
    - Click on the image to download, or right click and select "Save Image As...". + {$locale().tools.wrapped?.saveImageInstruction}
    {/if} @@ -995,66 +998,73 @@ const pruneFullYear = async () => { {/if}
    - + {#if !shouldFetchData} - + {:else if needsRefetch} {/if}
    - Display + {$locale().tools.wrapped?.display} - Show watermark
    - Enable background transparency
    + + {$locale().tools.wrapped?.showWatermark}
    + + {$locale().tools.wrapped?.bgTransparency}
    - Enable light mode
    + {$locale().tools.wrapped?.lightMode}
    - Show top genres and tags
    + {$locale().tools.wrapped?.showGenresTags}
    - Hide activity history
    - Show highest rated - media percentages
    - Show highest rated - genre and tag percentages
    - Show ongoing - media from previous years
    + {$locale().tools.wrapped?.hideActivityHistory}
    + + {$locale().tools.wrapped?.showRatedPercentages}
    + + {$locale().tools.wrapped?.showGenreTagPercentages}
    + + {$locale().tools.wrapped?.showOngoingPrevious}
    - Activity history position
    + {$locale().tools.wrapped?.activityHistoryPosition}
    - Highest rated media count
    + {$locale().tools.wrapped?.highestRatedCount}
    - Highest genre and tag count
    - + {$locale().tools.wrapped?.highestGenreTagCount}
    + - Width adjustment
    + {$locale().tools.wrapped?.widthAdjustment}
    - Calculation + {$locale().tools.wrapped?.calculation} - Enable full-year activity{$locale().tools.wrapped?.refreshData}
    - Calculate for year
    + {$locale().tools.wrapped?.calculateForYear}
    { update(); }} /> - Start date filter
    + {$locale().tools.wrapped?.startDateFilter}
    { update(); }} /> - End date filter
    + {$locale().tools.wrapped?.endDateFilter}
    - Anime and manga sort
    + {$locale().tools.wrapped?.animeMangaSort}
    - Genre and tag sort
    - Include music
    - Include rewatches & rereads
    - Include specials
    - Include OVAs
    - Include movies
    - Excluded unrated & - unwatched
    + {$locale().tools.wrapped?.genreTagSort}
    + + {$locale().tools.wrapped?.includeMusic}
    + + {$locale().tools.wrapped?.includeRewatches}
    + + {$locale().tools.wrapped?.includeSpecials}
    + + {$locale().tools.wrapped?.includeOvas}
    + + {$locale().tools.wrapped?.includeMovies}
    + + {$locale().tools.wrapped?.excludeUnrated}
    { e.key === 'Enter' && submitExcludedKeywords(); }} /> - Excluded keywords -
    - Comma separated list (e.g., "My Hero, Kaguya") + {$locale().tools.wrapped?.excludedHint}
    - Advanced + {$locale().tools.wrapped?.advanced} - Disable detailed activity information + {$locale().tools.wrapped?.disableDetailedActivity}
    {:else} - + {/if} -- cgit v1.2.3