Files
immich-folio/lib/i18n/locales/en.ts
2026-10-02 18:14:23 +02:00

302 lines
11 KiB
TypeScript

/**
* English dictionary — the reference locale.
*
* Every other locale implements the `Dictionary` type derived from this file,
* so adding a key here is a type error in `de.ts` until it is translated.
* Values are plain strings, or functions when a count or a name is interpolated
* (a locale is free to pluralise differently — German "Alben" is not "Albums").
*/
const plural = (n: number, one: string, other: string) => `${n} ${n === 1 ? one : other}`;
export const en = {
/** Passed to `toLocaleDateString` for visitor-facing dates. */
dateLocale: 'en-US',
nav: {
home: 'Home',
about: 'About',
map: 'Map',
journal: 'Journal',
skipToContent: 'Skip to content',
mainNavAria: 'Main',
heroNavAria: 'Sections',
openMenu: 'Open menu',
closeMenu: 'Close menu',
},
common: {
backTo: (label: string) => `Back to ${label}`,
backToGallery: 'Back to Gallery',
backToJournal: 'Back to Journal',
/** Footer contact links, read out by screen readers. */
email: 'Email',
website: 'Website',
home: 'Home',
photos: (n: number) => plural(n, 'photo', 'photos'),
albums: (n: number) => plural(n, 'album', 'albums'),
collections: (n: number) => plural(n, 'collection', 'collections'),
prevAlbum: 'Previous album',
nextAlbum: 'Next album',
prevAlbumAria: (name: string) => `Previous album: ${name}`,
nextAlbumAria: (name: string) => `Next album: ${name}`,
albumNavAria: 'Album navigation',
prevEntry: 'Previous entry',
nextEntry: 'Next entry',
prevEntryAria: (title: string) => `Previous entry: ${title}`,
nextEntryAria: (title: string) => `Next entry: ${title}`,
entryNavAria: 'Journal entry navigation',
loadingGallery: 'Loading gallery',
gallery: 'Gallery',
loadingPhotos: 'Loading photos',
downloadAlbum: 'Download album',
},
home: {
enter: 'Enter',
pauseSlideshow: 'Pause slideshow',
playSlideshow: 'Play slideshow',
},
error: {
notFoundTitle: 'Not found',
notFoundText: 'This page does not exist, or the album is no longer published.',
errorTitle: 'Something went wrong',
errorText:
'This page could not be loaded right now. It is usually temporary — try again in a moment.',
tryAgain: 'Try again',
reference: (digest: string) => `Reference: ${digest}`,
},
/** The page the ZIP route answers a browser with when it refuses. */
download: {
unavailableTitle: 'Download unavailable',
notAvailable: 'This download is not available.',
rateLimited: 'Too many download requests. Please wait a moment and try again.',
immichUnavailable: 'The photo library is unavailable right now. Please try again shortly.',
back: 'Back to the gallery',
limitReached: 'The download limit for this link has been reached.',
},
theme: {
switchToLight: 'Switch to light mode',
switchToDark: 'Switch to dark mode',
scrollToTop: 'Scroll to top',
},
password: {
subtitle: 'This gallery is password-protected.',
siteSubtitle: 'This site is password-protected.',
pageSubtitle: 'This page is password-protected.',
journalSubtitle: 'This journal entry is password-protected.',
protectedPage: 'Protected page',
placeholder: 'Enter password',
submit: 'Enter',
verifying: 'Verifying…',
incorrect: 'Incorrect password. Please try again.',
failed: 'Unable to verify password. Please try again later.',
tooManyAttempts: (seconds: number | null) =>
seconds
? `Too many attempts. Please try again in ${seconds} ${seconds === 1 ? 'second' : 'seconds'}.`
: 'Too many attempts. Please try again later.',
legalNavAria: 'Legal information',
},
about: {
kicker: 'About',
title: 'About',
metaTitle: (name: string) => `About — ${name}`,
portraitAlt: 'Portrait',
gear: 'Gear',
},
map: {
title: 'Map',
kicker: 'Where',
subtitle: 'Where the photos were taken',
loading: 'Loading map…',
loadFailed: (status: number) => `Failed to load map data (${status})`,
initFailed: 'Failed to initialize map',
zoomIn: 'Zoom in',
zoomOut: 'Zoom out',
// The title on the Leaflet credit in the map's corner.
leafletTitle: 'A JavaScript library for interactive maps',
},
subpage: {
collectionKicker: (index: string) => `${index} — Collection`,
sectionsNav: 'Sections',
coverAria: (albumName: string, count: string) => `${albumName}, ${count}`,
nextSubpage: 'Next',
nextSubpageAria: (name: string) => `Next: ${name}`,
},
journal: {
title: 'Journal',
kicker: 'Stories & Essays',
subtitle: 'Photo essays, visual stories, and field notes.',
entryDescription: 'Journal entry',
empty: 'No journal entries published yet.',
readStory: 'Read Story →',
minRead: (n: number) => `${n} min read`,
draft: 'Draft',
by: (author: string) => `by ${author}`,
notFound: 'Not Found',
},
lightbox: {
viewer: 'Image viewer',
position: (n: number, total: number) => `Photo ${n} of ${total}`,
/** A grid tile, which opens the viewer on that photo. */
openPhoto: (n: number) => `View photo ${n}`,
close: 'Close',
closeTitle: 'Close (Esc)',
previous: 'Previous photo',
previousTitle: 'Previous photo (Left arrow)',
next: 'Next photo',
nextTitle: 'Next photo (Right arrow)',
toggleInfo: 'Toggle photo info',
toggleInfoTitle: 'Toggle photo info (i)',
hideInfo: 'Hide Info',
info: 'Info',
loading: 'Loading...',
camera: 'Camera',
lens: 'Lens',
focalLength: 'Focal Length',
aperture: 'Aperture',
shutter: 'Shutter',
iso: 'ISO',
location: 'Location',
noExif: 'No EXIF data available',
copyLink: 'Copy link to this photo',
copyLinkTitle: 'Copy link to this photo (c)',
copyLinkShort: 'Link',
copied: 'Copied',
copyManual: 'Copy this link',
shortcuts: 'Keyboard shortcuts',
shortcutNavigate: 'Previous / next photo',
shortcutInfo: 'Photo info',
shortcutFullscreen: 'Fullscreen',
shortcutExitFullscreen: 'Leave fullscreen',
download: 'Download the original file',
downloadTitle: 'Download the original file (d)',
downloadShort: 'Original',
shortcutDownload: 'Download the original',
shortcutSlideshow: 'Slideshow',
shortcutSlideshowRunning: (seconds: number) => `Slideshow — every ${seconds}s`,
slideshowStopped: 'Slideshow stopped',
slideshowRunning: (seconds: number) => `Slideshow running, advancing every ${seconds} seconds`,
shortcutCopyLink: 'Copy link to this photo',
shortcutList: 'This list',
shortcutClose: 'Close the viewer',
/** The zoom button (#467): to 1:1 from fit, and back. */
zoomIn: 'Zoom to full resolution',
zoomInTitle: 'Zoom to full resolution (+ or double-click)',
zoomOut: 'Fit the photo to the screen',
zoomOutTitle: 'Fit to screen (0 or Esc)',
zoomLoading: 'Loading full resolution…',
/** Immich has no full-resolution file the viewer may show for this photo. */
zoomUnavailable: 'Full resolution is not available for this photo.',
shortcutZoom: 'Zoom in / out',
shortcutZoomReset: 'Fit to screen',
},
proofing: {
addFavorite: 'Add favorite',
removeFavorite: 'Remove favorite',
addToFavorites: 'Add to favorites',
removeFromFavorites: 'Remove from favorites',
saved: 'Saved',
favorite: 'Favorite',
showAll: 'Show All',
/** Shown in place of the grid when the filter is on and nothing is selected. */
filterEmpty: 'No photos selected.',
selected: (n: number) => `❤️ ${n} Selected`,
shareExport: 'Share & Export',
modalTitle: (n: number) => `❤️ Selection (${n})`,
closeModal: 'Close modal',
intro: (n: number) =>
`You have selected ${plural(n, 'photo', 'photos')}. Choose an export option below to share your selection:`,
copyLink: 'Copy Shareable Link',
linkCopied: 'Link Copied!',
copyList: 'Copy Text List (#1, #2...)',
listCopied: 'List Copied!',
/** Shown when the browser offers no clipboard (plain http) — copy by hand. */
copyManualLink: 'Copy this link',
copyManualList: 'Copy this list',
sendEmail: '✉️ Send Email to Photographer',
downloadSelected: 'Download selected (.zip)',
clearSelection: 'Clear Selection',
confirmClear: 'Clear all selected favorites?',
listEmpty: (albumName: string) => `${albumName}: No photos selected.`,
listSummary: (albumName: string, count: number, indices: string) =>
`${albumName} — Selected Photos (${count}): ${indices}`,
mailSubject: (n: number) => `Photo Selection (${plural(n, 'item', 'items')})`,
mailBody: (list: string, url: string) =>
`Hello,\n\nHere is my photo selection:\n\n${list}\n\nShare Link: ${url}\n\nBest regards,`,
},
/** Client proofing links (/proof/<token>). */
proofSession: {
greeting: (name: string) => `Selection for ${name}`,
intro:
'Tap the heart on the photos you want. Your selection is saved as you go — you can come back with this link at any time.',
saving: 'Saving…',
saved: 'Selection saved',
saveFailed: 'Your selection could not be saved. Check your connection and try again.',
review: 'Review & submit',
modalTitle: (n: number) => `Your selection (${n})`,
empty: 'No photos selected yet.',
submit: 'Submit selection',
submitting: 'Submitting…',
confirmSubmit: (n: number) =>
`Submit ${plural(n, 'photo', 'photos')}? The selection cannot be changed afterwards.`,
submitted: 'Thank you — your selection has been submitted.',
locked: 'This selection has been submitted and can no longer be changed.',
validUntil: (date: string) => `This link is valid until ${date}.`,
expiredTitle: 'This link has expired',
expiredText: 'Please ask your photographer for a new link.',
downloadSelection: 'Download selection (.zip)',
downloadAll: 'Download all photos (.zip)',
downloadsLeft: (n: number) => `${plural(n, 'download', 'downloads')} left`,
},
legal: {
navLabel: 'Legal Notice',
title: 'Legal Notice',
subtitle: 'Information pursuant to § 5 DDG',
address: 'Address',
contact: 'Contact',
email: 'Email',
phone: 'Phone',
contactForm: 'Contact form',
taxSection: 'Tax Information',
taxId: 'Tax number',
vatId: 'VAT ID',
extraInfo: 'Additional Information',
},
contact: {
navLabel: 'Contact',
title: 'Contact',
subtitle: 'Write me a message',
name: 'Name',
email: 'Email',
message: 'Message',
submit: 'Send message',
sending: 'Sending…',
successTitle: 'Thank you',
successText: 'Your message has arrived. I will get back to you by email.',
invalid: 'Please fill in every field with a valid email address.',
tooMany: 'Too many messages in a short time. Please try again in a minute.',
failed: 'The message could not be sent. Please try again later.',
privacy: (days: number) =>
`Your message is stored on this site's own server, is only used to reply to you, and is deleted after ${days} ${days === 1 ? 'day' : 'days'}.`,
},
privacy: {
navLabel: 'Privacy',
title: 'Privacy Policy',
},
};