feat(i18n): translate remaining components (HU/EN/DE)
Feed, VideoCard, Sidebar, PlayerModal, Channels, Stats, SettingsPanel, OnboardingWizard, NotificationCenter, Toaster, ErrorBoundary and the relativeTime helper are now fully translated in Hungarian, English and German, each with its own locale area file (auto-loaded). Key parity verified across all three languages.
This commit is contained in:
parent
b38ae92d8d
commit
9c61bd898d
45 changed files with 1522 additions and 355 deletions
17
frontend/src/i18n/locales/en/card.json
Normal file
17
frontend/src/i18n/locales/en/card.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"watchedUnmark": "Watched — click to unmark",
|
||||
"markWatched": "Mark watched",
|
||||
"savedRemove": "Saved — click to remove",
|
||||
"saveForLater": "Save for later",
|
||||
"unhide": "Unhide",
|
||||
"hide": "Hide",
|
||||
"onlyThisChannel": "Only this channel",
|
||||
"thisChannel": "This channel",
|
||||
"continueTitle": "Continue where you left off",
|
||||
"continue": "Continue",
|
||||
"restartTitle": "Play from the beginning",
|
||||
"restart": "Restart",
|
||||
"play": "Play",
|
||||
"stream": "stream",
|
||||
"views": "views"
|
||||
}
|
||||
69
frontend/src/i18n/locales/en/channels.json
Normal file
69
frontend/src/i18n/locales/en/channels.json
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"intro": "Set a channel's <0>priority</0> to push its videos up when you sort by “Channel priority”, attach your own <1>tags</1> to filter the feed, or <2>hide</2> a channel to drop it from the feed without unsubscribing.",
|
||||
"filterPlaceholder": "Filter channels…",
|
||||
"syncSubscriptions": "Sync subscriptions",
|
||||
"syncSubscriptionsHint": "Re-import your subscription list from YouTube — adds channels you've newly followed and drops ones you've unfollowed. The videos themselves keep syncing automatically in the background; this does not re-fetch them.",
|
||||
"backfillEverything": "Backfill everything",
|
||||
"backfillEverythingHint": "Request full back-catalog backfill for every channel you're subscribed to. Older videos and search become complete as the shared daily quota allows — this can take a while.",
|
||||
"filters": {
|
||||
"all": "All",
|
||||
"needsFull": "Needs full history",
|
||||
"fullySynced": "Fully synced",
|
||||
"hidden": "Hidden"
|
||||
},
|
||||
"tags": {
|
||||
"yourTags": "Your tags",
|
||||
"yourTagsHint": "Your personal labels. Attach them to channels below, then filter the feed by tag from the sidebar. (Separate from the automatic language/topic tags.)",
|
||||
"newTag": "new tag",
|
||||
"createTag": "Create tag"
|
||||
},
|
||||
"loading": "Loading channels…",
|
||||
"empty": "No channels.",
|
||||
"stats": {
|
||||
"channels": "Channels",
|
||||
"channelsHint": "Channels you're subscribed to.",
|
||||
"recentSynced": "Recent synced",
|
||||
"recentSyncedHint": "Channels whose recent uploads are in the local DB and show in your feed.",
|
||||
"fullHistory": "Full history",
|
||||
"fullHistoryHint": "Channels whose entire back-catalog is fetched, out of the ones you've requested full history for.",
|
||||
"left": "left",
|
||||
"leftHint": "Rough estimate to finish full-history backfill of {{count}} pending channel(s), based on the shared daily quota.",
|
||||
"myVideos": "My videos",
|
||||
"myVideosHint": "Total videos available across your channels.",
|
||||
"quotaLeft": "Quota left",
|
||||
"quotaLeftHint": "Shared YouTube API budget left today (resets midnight US Pacific)."
|
||||
},
|
||||
"row": {
|
||||
"stored": "{{formatted}} stored",
|
||||
"subs": "{{formatted}} subs",
|
||||
"priorityHint": "Your ranking for this channel. Sort the feed by “Channel priority” to bring higher-priority channels to the top.",
|
||||
"raisePriority": "Raise priority",
|
||||
"lowerPriority": "Lower priority",
|
||||
"recent": "recent",
|
||||
"recentSyncedHint": "Recent uploads synced.",
|
||||
"recentNotSyncedHint": "Recent uploads not fetched yet.",
|
||||
"full": "full",
|
||||
"fullHint": "Full history fetched.",
|
||||
"fullHistoryQueued": "full history queued",
|
||||
"queuedRequestedHint": "Full history requested — this channel's whole back-catalog will backfill as the shared quota allows. Click to cancel your request.",
|
||||
"queuedByOtherHint": "Another subscriber already requested this channel's full history, so its whole back-catalog is on its way to everyone — nothing to do here.",
|
||||
"getFullHistory": "get full history",
|
||||
"getFullHistoryHint": "Only recent uploads so far. Click to request this channel's full back-catalog (older videos + complete search).",
|
||||
"hiddenHint": "Hidden — this channel's videos are kept out of your feed. Click to show them again.",
|
||||
"hideHint": "Hide this channel's videos from your feed. It stays subscribed; this doesn't unsubscribe on YouTube.",
|
||||
"unhide": "Unhide",
|
||||
"hideFromFeed": "Hide from feed",
|
||||
"unsubscribeHint": "Unsubscribe from this channel on YouTube (changes your real account). Read-only mode hides this — use Hide instead.",
|
||||
"unsubscribeOnYoutube": "Unsubscribe on YouTube",
|
||||
"thisChannel": "This channel"
|
||||
},
|
||||
"notify": {
|
||||
"synced": "Synced {{count}} subscriptions",
|
||||
"syncFailed": "Subscription sync failed",
|
||||
"unsubscribed": "Unsubscribed on YouTube",
|
||||
"unsubscribeFailed": "Unsubscribe failed",
|
||||
"fullHistoryRequested": "Full history requested for {{count}} channels",
|
||||
"fullHistoryFailed": "Couldn't request full history"
|
||||
},
|
||||
"confirmUnsubscribe": "Unsubscribe from \"{{name}}\" on YouTube? This changes your real YouTube account. To just remove it from your feed, hide it instead."
|
||||
}
|
||||
9
frontend/src/i18n/locales/en/errors.json
Normal file
9
frontend/src/i18n/locales/en/errors.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"boundary": {
|
||||
"title": "Something went wrong.",
|
||||
"subtitle": "The app hit an unexpected error.",
|
||||
"reload": "Reload",
|
||||
"notifTitle": "Something broke",
|
||||
"notifMessage": "Unexpected error"
|
||||
}
|
||||
}
|
||||
17
frontend/src/i18n/locales/en/feed.json
Normal file
17
frontend/src/i18n/locales/en/feed.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"loading": "Loading feed…",
|
||||
"loadError": "Couldn't load the feed.",
|
||||
"emptyTitle": "Your feed is empty",
|
||||
"emptyBody": "Connect your YouTube account to import your subscriptions and build your feed.",
|
||||
"setUp": "Set up my feed",
|
||||
"noMatches": "No videos match these filters.",
|
||||
"videoCount_one": "{{formattedCount}} video",
|
||||
"videoCount_other": "{{formattedCount}} videos",
|
||||
"loadingMore": "Loading more…",
|
||||
"hiddenNamed": "Hidden “{{title}}”",
|
||||
"hidden": "Video hidden",
|
||||
"undo": "Undo",
|
||||
"markedWatchedNamed": "Marked watched “{{title}}”",
|
||||
"markedWatched": "Marked watched",
|
||||
"unwatch": "Unwatch"
|
||||
}
|
||||
19
frontend/src/i18n/locales/en/notifications.json
Normal file
19
frontend/src/i18n/locales/en/notifications.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"title": "Notifications",
|
||||
"clearAll": "Clear all",
|
||||
"clear": "Clear",
|
||||
"empty": "No notifications yet.",
|
||||
"actionNeeded": "Action needed",
|
||||
"dismiss": "Dismiss",
|
||||
"findInFeed": "Find in feed",
|
||||
"unhide": "Unhide",
|
||||
"unwatch": "Unwatch",
|
||||
"unhidden": "Unhidden “{{title}}”",
|
||||
"unwatched": "Unwatched “{{title}}”",
|
||||
"time": {
|
||||
"justNow": "just now",
|
||||
"minutes": "{{count}}m ago",
|
||||
"hours": "{{count}}h ago",
|
||||
"days": "{{count}}d ago"
|
||||
}
|
||||
}
|
||||
29
frontend/src/i18n/locales/en/onboarding.json
Normal file
29
frontend/src/i18n/locales/en/onboarding.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"close": "Close",
|
||||
"consent": "Google will show a <0>\"Google hasn't verified this app\"</0> screen — that's expected, because Siftlode hasn't gone through Google's full review. It's safe to continue: click <1>Advanced</1> → <2>Go to Siftlode</2>. You can revoke access anytime from your <3>Google Account</3>.",
|
||||
"read": {
|
||||
"title": "Connect your YouTube subscriptions",
|
||||
"body": "You're signed in. To build your feed, Siftlode needs <0>read-only</0> access to the channels you're subscribed to on YouTube. It never posts, deletes, or changes anything with this.",
|
||||
"connect": "Connect (read-only)",
|
||||
"skip": "Skip for now"
|
||||
},
|
||||
"importing": {
|
||||
"title": "Building your feed…",
|
||||
"body": "Importing your YouTube subscriptions. Channels already in Siftlode show up instantly; any new ones fill in automatically in the background."
|
||||
},
|
||||
"write": {
|
||||
"title": "You're connected",
|
||||
"importFailed": "Your YouTube account is connected, but importing subscriptions failed — you can retry from Settings → Sync. ",
|
||||
"feedReady": "Your feed is ready{{channels}}. Optionally, let Siftlode ",
|
||||
"feedReadyChannels": " ({{count}} channels)",
|
||||
"rationale": "<0>unsubscribe</0> from channels on YouTube for you — this needs an extra write permission. Skip it to stay read-only; you can always enable it later in Settings.",
|
||||
"retryImport": "Retry import",
|
||||
"enableUnsubscribe": "Enable unsubscribe",
|
||||
"keepReadOnly": "No thanks — keep it read-only"
|
||||
},
|
||||
"done": {
|
||||
"title": "All set",
|
||||
"body": "Read and unsubscribe access are both granted. You can review or revoke either one anytime in Settings → Account, or from your Google Account.",
|
||||
"done": "Done"
|
||||
}
|
||||
}
|
||||
17
frontend/src/i18n/locales/en/player.json
Normal file
17
frontend/src/i18n/locales/en/player.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"loading": "Loading…",
|
||||
"backToOriginal": "Back to the original video",
|
||||
"back": "Back",
|
||||
"close": "Close",
|
||||
"closeEsc": "Close (Esc)",
|
||||
"description": "Description",
|
||||
"noDescription": "No description.",
|
||||
"channel": "Channel",
|
||||
"views": "{{formattedCount}} views",
|
||||
"stream": "stream",
|
||||
"watched": "Watched",
|
||||
"markWatched": "Mark watched",
|
||||
"watchedUnmark": "Watched — click to unmark",
|
||||
"jumpToTime": "Jump to this time",
|
||||
"playInApp": "Play in the in-app player"
|
||||
}
|
||||
101
frontend/src/i18n/locales/en/settings.json
Normal file
101
frontend/src/i18n/locales/en/settings.json
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
{
|
||||
"title": "Settings",
|
||||
"tabs": {
|
||||
"appearance": "Appearance",
|
||||
"notifications": "Notifications",
|
||||
"sync": "Sync",
|
||||
"account": "Account"
|
||||
},
|
||||
"appearance": {
|
||||
"colorScheme": "Color scheme",
|
||||
"display": "Display",
|
||||
"darkMode": "Dark mode",
|
||||
"listView": "List view",
|
||||
"listViewHint": "Show the feed as a compact list instead of a grid of cards.",
|
||||
"performanceMode": "Performance mode",
|
||||
"performanceModeHint": "Turns off the translucent glass blur and soft shadows for snappier rendering on slower machines.",
|
||||
"showHints": "Show hints",
|
||||
"showHintsHint": "These little hover explanations. Turn them off once you know your way around.",
|
||||
"textSize": "Text size"
|
||||
},
|
||||
"notifications": {
|
||||
"title": "Notifications",
|
||||
"sound": "Sound on alerts",
|
||||
"soundHint": "Play a short tone when a notification needs your attention (e.g. errors, prompts).",
|
||||
"autoDismiss": "Auto-dismiss toasts",
|
||||
"autoDismissHint": "When off, pop-up toasts stay until you close them. When on, they fade after the set time.",
|
||||
"dismissAfter": "Dismiss after",
|
||||
"sendTest": "Send a test notification",
|
||||
"testTitle": "Test notification",
|
||||
"testMessage": "This is what a notification looks like."
|
||||
},
|
||||
"sync": {
|
||||
"myStatus": "My sync status",
|
||||
"channels": "Channels",
|
||||
"channelsHint": "How many channels you're subscribed to.",
|
||||
"recentSynced": "Recent synced",
|
||||
"recentSyncedHint": "Channels whose recent uploads (~last year) are already in the local database, so they show in your feed.",
|
||||
"fullHistory": "Full history",
|
||||
"fullHistoryHint": "Channels whose entire back-catalog has been fetched, out of the ones you've requested full history for (opt in per channel on the Channels page).",
|
||||
"fullHistoryEta": "Full history ETA",
|
||||
"fullHistoryEtaHint": "Rough estimate to finish full-history backfill of {{count}} pending channel(s), based on the shared daily quota.",
|
||||
"myVideos": "My videos",
|
||||
"myVideosHint": "Total videos available to you across your subscribed channels.",
|
||||
"quotaLeft": "Quota left today",
|
||||
"quotaLeftHint": "Shared YouTube API budget remaining today (resets midnight US Pacific). Backfill yields to it.",
|
||||
"loading": "Loading…",
|
||||
"apiUsage": "Your API usage",
|
||||
"today": "Today",
|
||||
"todayHint": "YouTube API units your own actions used today (resets midnight US Pacific). Background sync isn't counted here.",
|
||||
"last7d": "Last 7 days",
|
||||
"last30d": "Last 30 days",
|
||||
"allTime": "All time",
|
||||
"byAction": "By action (30d)",
|
||||
"noUsage": "No usage yet.",
|
||||
"actions": "Actions",
|
||||
"syncSubscriptions": "Sync subscriptions",
|
||||
"syncSubscriptionsHint": "Re-import your YouTube subscriptions and pull in any newly-followed channels.",
|
||||
"backfillEverything": "Backfill everything",
|
||||
"backfillEverythingHint": "Request full back-catalog backfill for every channel you're subscribed to. Older videos + complete search fill in as the shared daily quota allows.",
|
||||
"synced": "Synced {{count}} subscriptions",
|
||||
"syncFailed": "Sync failed",
|
||||
"fullHistoryRequested": "Full history requested for {{count}} channels",
|
||||
"fullHistoryFailed": "Couldn't request full history",
|
||||
"admin": "Admin",
|
||||
"adminPauseHint": "Pause or resume the background sync for the whole app (all users).",
|
||||
"resumeBackgroundSync": "Resume background sync",
|
||||
"pauseBackgroundSync": "Pause background sync"
|
||||
},
|
||||
"account": {
|
||||
"title": "Account",
|
||||
"youtubeAccess": "YouTube access",
|
||||
"youtubeAccessIntro": "Sign-in only shares your name and email. YouTube access is granted separately, below — each is optional and revocable anytime in your Google Account.",
|
||||
"readTitle": "Read subscriptions (your feed)",
|
||||
"readGrantedHint": "Granted. Siftlode reads the channels you follow to build your feed. It never changes your YouTube account with this.",
|
||||
"readEnableHint": "Read-only access to your subscriptions — required to build your feed. Siftlode can't modify anything with it.",
|
||||
"writeTitle": "Unsubscribe (write)",
|
||||
"writeGrantedHint": "Granted. You can unsubscribe from channels on YouTube from inside Siftlode. It only writes when you ask it to.",
|
||||
"writeEnableHint": "Lets Siftlode unsubscribe from channels on YouTube for you. Optional — skip it to stay read-only.",
|
||||
"granted": "Granted",
|
||||
"enable": "Enable",
|
||||
"enableHint": "Redirects to Google to grant the permission. Google shows an 'unverified app' notice — that's expected; click Advanced → Go to Siftlode.",
|
||||
"walkMeThrough": "Walk me through setup"
|
||||
},
|
||||
"invites": {
|
||||
"title": "Access requests",
|
||||
"noPending": "No pending requests.",
|
||||
"addPlaceholder": "Add an email directly…",
|
||||
"add": "Add",
|
||||
"decided": "{{count}} decided",
|
||||
"approved": "Approved — they can sign in now",
|
||||
"approveFailed": "Approve failed",
|
||||
"denyFailed": "Deny failed",
|
||||
"addedToWhitelist": "Added to the whitelist",
|
||||
"addFailed": "Couldn't add that email",
|
||||
"requested": "requested {{time}}",
|
||||
"approveHint": "Approve — whitelist this email and email them they're in.",
|
||||
"approve": "Approve",
|
||||
"denyHint": "Deny this request.",
|
||||
"deny": "Deny"
|
||||
}
|
||||
}
|
||||
65
frontend/src/i18n/locales/en/sidebar.json
Normal file
65
frontend/src/i18n/locales/en/sidebar.json
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
{
|
||||
"filters": "Filters",
|
||||
"activeCount": "{{count}} active",
|
||||
"clearAll": "Clear all",
|
||||
"resetDefaults": "Reset to defaults",
|
||||
"customize": "Customize sidebar",
|
||||
"done": "Done",
|
||||
"editHint": "Drag to reorder · eye to show/hide",
|
||||
"channel": "Channel",
|
||||
"loading": "Loading…",
|
||||
"thisChannel": "This channel",
|
||||
"channelCount": "{{count}} channel",
|
||||
"channelCount_other": "{{count}} channels",
|
||||
"dragToReorder": "Drag to reorder",
|
||||
"showWidget": "Show widget",
|
||||
"hideWidget": "Hide widget",
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse",
|
||||
"any": "Any",
|
||||
"all": "All",
|
||||
"tagModeTooltip": "Match any vs all selected tags",
|
||||
"custom": "Custom",
|
||||
"from": "From",
|
||||
"to": "To",
|
||||
"clearDates": "clear dates",
|
||||
"widget": {
|
||||
"show": "Show",
|
||||
"sort": "Sort",
|
||||
"date": "Upload date",
|
||||
"content": "Content type",
|
||||
"language": "Language",
|
||||
"topic": "Topic"
|
||||
},
|
||||
"show": {
|
||||
"unwatched": "Unwatched",
|
||||
"in_progress": "In progress",
|
||||
"all": "All",
|
||||
"watched": "Watched",
|
||||
"saved": "Saved",
|
||||
"hidden": "Hidden"
|
||||
},
|
||||
"sort": {
|
||||
"newest": "Newest",
|
||||
"oldest": "Oldest",
|
||||
"views": "Most viewed",
|
||||
"duration_desc": "Longest",
|
||||
"duration_asc": "Shortest",
|
||||
"title": "Name (A–Z)",
|
||||
"subscribers": "Channel subscribers",
|
||||
"priority": "Channel priority",
|
||||
"shuffle": "Surprise me"
|
||||
},
|
||||
"content": {
|
||||
"normal": "Normal",
|
||||
"shorts": "Shorts",
|
||||
"live": "Live / Upcoming"
|
||||
},
|
||||
"datePreset": {
|
||||
"24h": "24h",
|
||||
"1week": "1 week",
|
||||
"1month": "1 month",
|
||||
"6months": "6 months",
|
||||
"1year": "1 year"
|
||||
}
|
||||
}
|
||||
13
frontend/src/i18n/locales/en/stats.json
Normal file
13
frontend/src/i18n/locales/en/stats.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"title": "API quota usage",
|
||||
"rangeDays": "{{count}}d",
|
||||
"introBefore": "YouTube Data API units attributed by who triggered the spend. ",
|
||||
"introSystem": "System",
|
||||
"introAfter": " is shared background work (scheduled backfill, enrichment, resync) that isn't tied to one person.",
|
||||
"loading": "Loading…",
|
||||
"noData": "No data.",
|
||||
"dailyTotal": "Daily total ({{count}}d · {{units}} units)",
|
||||
"noUsageInRange": "No usage in this range.",
|
||||
"dailyTooltip": "{{day}}: {{units}} units",
|
||||
"system": "System (background)"
|
||||
}
|
||||
10
frontend/src/i18n/locales/en/time.json
Normal file
10
frontend/src/i18n/locales/en/time.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"justNow": "just now",
|
||||
"secondsAgo": "{{count}}s ago",
|
||||
"minutesAgo": "{{count}} min ago",
|
||||
"hoursAgo": "{{count}}h ago",
|
||||
"daysAgo": "{{count}}d ago",
|
||||
"weeksAgo": "{{count}} wk ago",
|
||||
"monthsAgo": "{{count}} mo ago",
|
||||
"yearsAgo": "{{count}} yr ago"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue