2b45419ec4
The tree column now walks every folder under the one you picked instead of its first level, so a roll of dated folders inside dated folders is walked to the bottom. A folder being read turns a ring on its own row, the column is titled with the folder the tree belongs to, and a divider beside it drags the width, which is remembered along with the folder the screen was left on.
372 lines
16 KiB
TypeScript
372 lines
16 KiB
TypeScript
import type { Dict } from './vi';
|
||
|
||
export const en: Dict = {
|
||
'lang.name': 'English',
|
||
|
||
'nav.demo': 'TRY DEMO',
|
||
'nav.login': 'LOG IN',
|
||
'nav.signup': 'SIGN UP',
|
||
'nav.logout': 'Log out',
|
||
'nav.account': 'Account',
|
||
'nav.theme': 'Theme',
|
||
'nav.language': 'Language',
|
||
'nav.back': 'Back to home',
|
||
'nav.guest': 'Guest',
|
||
|
||
// The install offer (see vi.ts).
|
||
'install.title': 'Install RecipesCam',
|
||
'install.body': 'Add it to your home screen to open it faster — smoother, offline, full screen like an app.',
|
||
'install.yes': 'INSTALL',
|
||
'install.no': 'NO THANKS',
|
||
'install.ios': 'Tap the Share button in the toolbar, then choose “Add to Home Screen”.',
|
||
|
||
'act.undo': 'UNDO',
|
||
'act.redo': 'REDO',
|
||
'act.reset': 'RESET',
|
||
'act.export': 'EXPORT',
|
||
'act.exporting': 'Exporting…',
|
||
'act.upscaling': 'Upscaling the photo {done}/{total}…',
|
||
'act.cancel': 'Cancel',
|
||
|
||
'drop.title': 'Drag & drop a photo here',
|
||
'drop.hint': 'or click to pick one · paste from the clipboard with Ctrl+V',
|
||
'drop.replace': 'Open photo',
|
||
'drop.clear': 'Clear',
|
||
'drop.peeking': 'SHOWING THE ORIGINAL',
|
||
'drop.peekHint': 'Hold right-click or the \\ key to compare with the original',
|
||
'drop.compare': 'Compare with original picture',
|
||
'drop.keep': 'SAVE ORIGINAL',
|
||
'drop.keepHint': 'Save the untouched photo to the device photo library. A browser that cannot share files downloads it instead.',
|
||
'drop.demoLimit': 'Demo build: exports are capped at {px}px and carry a watermark.',
|
||
|
||
// The live viewfinder (see vi.ts for what the status line is for).
|
||
'cam.open': 'OPEN CAMERA',
|
||
'cam.shot': 'SHOOT',
|
||
'cam.flip': 'Switch front/back camera',
|
||
'cam.starting': 'Opening the camera…',
|
||
'cam.status': '{w}×{h} · {fps} fps',
|
||
'cam.denied': 'The browser was not allowed the camera. Grant it, then open again.',
|
||
'cam.unsupported': 'This address cannot use a camera — it needs HTTPS or localhost.',
|
||
'cam.fail': 'The camera did not open.',
|
||
'cam.shotFail': 'The shot failed — try again.',
|
||
|
||
'hist.title': 'HISTOGRAM',
|
||
'hist.drag': 'Drag to move',
|
||
'hist.hide': 'Hide the histogram',
|
||
|
||
'tab.presets': 'PRESETS',
|
||
'tab.favorited': 'FAVORITED',
|
||
'tab.photos': 'MY PHOTOS',
|
||
'tab.light': 'LIGHT',
|
||
'tab.wb': 'WB',
|
||
'tab.fx': 'FX',
|
||
'tab.hsl': 'HSL',
|
||
'hsl.hint': 'PRESS PICK, THEN SAMPLE A COLOUR ON THE PHOTO',
|
||
'tab.frame': 'FRAME',
|
||
'tab.save': 'SAVE RECENT',
|
||
'tab.create': 'CREATE RECIPES',
|
||
|
||
'create.title': 'CREATE RECIPES',
|
||
'create.save': 'SAVE RECIPE',
|
||
'create.export': 'EXPORT .RECIPE FILE',
|
||
'create.guestHint':
|
||
'Guest: the recipe stays in RECIPES until you close the page. Log in to save it to your account and export it.',
|
||
'create.userHint':
|
||
'Saved to your account. Export the .recipe file to share it — encrypted exactly like the app’s.',
|
||
|
||
'sec.filmSims': 'PHOTO STYLE',
|
||
'sec.recipes': 'RECIPES',
|
||
'sec.saved': 'SAVED',
|
||
'sec.savedEmpty': 'No saved recipes yet. Log in and hit SAVE to sync them.',
|
||
'sec.frame': 'FRAME',
|
||
'sec.crop': 'CROP',
|
||
'sec.rotate': 'ROTATE',
|
||
'sec.watermark': 'WATERMARK',
|
||
'sec.gps': 'GPS',
|
||
|
||
'wb.temp': 'COLOR TEMP',
|
||
|
||
'frame.none': 'NO FRAME',
|
||
'frame.polaroid': 'RETRO INSTANT',
|
||
'frame.classic': 'CLASSIC BORDER',
|
||
'frame.cinematic': 'CINEMATIC',
|
||
'frame.wall': 'WALL FRAME PORTRAIT',
|
||
'frame.wallLand': 'WALL FRAME LANDSCAPE',
|
||
|
||
'crop.none': 'NO CROP',
|
||
'crop.1:1': '1:1',
|
||
'crop.2:3': '2:3',
|
||
'crop.3:2': '3:2',
|
||
'crop.3:4': '3:4',
|
||
'crop.4:3': '4:3',
|
||
'crop.16:9': '16:9',
|
||
'crop.free': 'FREE',
|
||
'crop.apply': 'APPLY',
|
||
'crop.cancel': 'CANCEL',
|
||
|
||
'rot.0': '0°',
|
||
'rot.90': '90°',
|
||
'rot.180': '180°',
|
||
'rot.270': '270°',
|
||
|
||
'wm.text': 'TEXT',
|
||
'wm.color': 'COLOR',
|
||
'wm.size': 'SIZE',
|
||
'wm.gps': 'GPS STAMP',
|
||
'wm.place': 'PLACE NAME',
|
||
'wm.placePh': 'e.g. HANOI',
|
||
'wm.manual': 'ENTER COORDINATES',
|
||
'wm.lat': 'Latitude',
|
||
'wm.lng': 'Longitude',
|
||
'wm.hasExif': 'GPS read from the photo.',
|
||
'wm.noExif': 'This photo has no GPS — type it in, or use where you are now.',
|
||
'wm.useLocation': 'USE MY LOCATION',
|
||
'wm.locating': 'GETTING LOCATION…',
|
||
'wm.hasDevice': "GPS taken from the device's current position.",
|
||
'wm.hasManual': 'GPS typed in by you.',
|
||
'wm.locFail': 'Could not get a location — type the coordinates in.',
|
||
|
||
'auth.loginTitle': 'Log in',
|
||
'auth.signupTitle': 'Create an account',
|
||
'auth.email': 'Email',
|
||
'auth.password': 'Password',
|
||
'auth.passwordHint': 'At least 8 characters',
|
||
'auth.noAccount': 'No account yet? Sign up',
|
||
'auth.haveAccount': 'Already have an account? Log in',
|
||
'auth.continueDemo': 'Keep using the demo',
|
||
'auth.busy': 'Working…',
|
||
'auth.loggedInAs': 'Signed in as {email}',
|
||
'auth.verifyTitle': 'Verify your email',
|
||
'auth.verifyBody': 'We sent a six-digit code to {email}. Type it below — or open the link in the mail — to unlock the PRO features.',
|
||
'auth.verifyProHint': 'Until then the account works exactly like a guest: exports stay 2048px with a watermark, and nothing can be saved.',
|
||
'auth.verifySent': 'A new code has been sent.',
|
||
'auth.codePlaceholder': 'The six digits from the mail',
|
||
'auth.verifyBtn': 'VERIFY',
|
||
'auth.codeBad': 'That code is wrong or has expired (15 minutes).',
|
||
'auth.codeLocked': 'Too many wrong tries — ask for a new code by email.',
|
||
'auth.signupVerifyHint': 'After signing up we email you a six-digit code — type it in to unlock the PRO features.',
|
||
|
||
// The PRO gate. Signed in but unproven is served as a guest, so the studio
|
||
// needs one place that says why and one way to ask for the letter again.
|
||
'pro.verifyChip': 'VERIFY EMAIL TO USE PRO',
|
||
'pro.hslChip': 'HSL IS A PRO FEATURE',
|
||
'pro.unverifiedBar': 'Account {email} is not verified — it works like a guest.',
|
||
'pro.resend': 'RESEND VERIFICATION EMAIL',
|
||
'pro.resent': 'SENT',
|
||
|
||
'save.title': 'Save recipe',
|
||
'save.name': 'Recipe name',
|
||
'save.recent': 'SAVE RECENT TO RECIPES',
|
||
'save.recentHint': 'Files the look on the stage — sim, WB, light, FX and frame — under a name, in this account alone.',
|
||
'save.recentEmpty': 'No recipes of your own yet.',
|
||
'save.delete': 'Delete this recipe',
|
||
'name.photoTitle': 'NAME THIS PHOTO',
|
||
'name.photoLabel': 'What the folder keeps it as, and the title on its landing frame.',
|
||
'name.recipeTitle': 'SAVE TO RECIPES',
|
||
'name.recipeLabel': 'A name for this look, in this account alone.',
|
||
'name.required': 'Give it a name first.',
|
||
'name.duplicate': 'You already have a recipe with that name.',
|
||
'name.save': 'SAVE',
|
||
'name.cancel': 'CANCEL',
|
||
'save.saved': 'Saved "{name}"',
|
||
'save.needLogin': 'Log in to save a recipe.',
|
||
'save.photo': 'SAVE PHOTO {n}/{max}',
|
||
'save.photoNeedLogin': 'Log in to save a photo.',
|
||
'save.photoSaved': 'Photo saved {n}/{max}.',
|
||
'save.photoFull': 'That is all {max} — remove one in My photos.',
|
||
'save.photoNoMax': 'SAVE PHOTO {n}',
|
||
'save.photoNoSource': 'Load a photo first.',
|
||
'save.photoFailed': 'Could not save the photo: {msg}',
|
||
'myp.empty': 'NO SAVED PHOTOS YET.',
|
||
'myp.history': 'HISTORY',
|
||
'myp.historyEmpty': 'NO EARLIER SAVES YET.',
|
||
'myp.consent': 'Allow this photo on the landing page',
|
||
'myp.remove': 'REMOVE PHOTO',
|
||
'del.title': 'REMOVE THIS PHOTO',
|
||
'del.warning': 'Removing this photo also removes the {n} looks its history kept before the last saves — they cannot be recovered. Download the photo first if you want to keep it.',
|
||
'del.download': 'DOWNLOAD FIRST',
|
||
'del.confirm': 'DELETE PHOTO',
|
||
'del.cancel': 'CANCEL',
|
||
|
||
// CLEAR's confirmation: keep the work one of two ways, or let it go.
|
||
'clear.title': 'Clear this photo?',
|
||
'clear.hint':
|
||
'The photo leaves the workspace. Save it to MY PHOTOS to carry on editing later, export it as an image file, or clear it without keeping anything.',
|
||
'clear.save': 'SAVE PHOTO',
|
||
'clear.export': 'EXPORT IMAGE',
|
||
'clear.discard': 'CLEAR WITHOUT SAVING',
|
||
'clear.cancel': 'CANCEL',
|
||
|
||
// Export size. A target over the photo's own longest edge is upscaled on the
|
||
// device by the model (engine/superRes), so the file carries the pixels asked for.
|
||
'export.title': 'Export photo',
|
||
'export.hint':
|
||
"Longest edge of the photo on the stage: {px}px. Anything bigger is upscaled on this device.",
|
||
'export.same': 'EXPORT UNCHANGED',
|
||
'export.2k': 'EXPORT 2K (2048px)',
|
||
'export.4k': 'EXPORT 4K (4096px)',
|
||
'export.customLabel': 'Longest edge (px)',
|
||
'export.custom': 'EXPORT',
|
||
'export.cap': 'Guest exports are capped at {px}px.',
|
||
|
||
'photo.uploading': 'UPLOADING…',
|
||
|
||
'photos.title': 'My photos',
|
||
'photos.hint': 'Each account keeps up to {max} photos. What the studio saves lives here and shows up on the community reel.',
|
||
'photos.count': '{n}/{max} photos',
|
||
'photos.countNoMax': '{n} photos',
|
||
'photos.hintNoMax':
|
||
'Admin accounts keep no photo limit. What the studio saves lives here and shows up on the community reel.',
|
||
'photos.add': 'ADD A PHOTO',
|
||
'photos.empty': 'No photos yet. Open the studio and hit SAVE PHOTO, or add one right here.',
|
||
'photos.full': 'That is all {max}. Remove one before saving again.',
|
||
'photos.delete': 'DELETE',
|
||
'photos.deleted': 'Photo removed.',
|
||
'photos.failed': 'That did not work: {msg}',
|
||
'photos.noLabels': 'No strip labels yet',
|
||
|
||
'lib.title': 'LIBRARY',
|
||
'lib.hint':
|
||
'Pick a photo folder on this machine. Nothing is uploaded — the page only remembers where the folder is and keeps one thumbnail per frame.',
|
||
'lib.add': 'ADD FOLDER',
|
||
'lib.stop': 'STOP SCAN',
|
||
'lib.rename': 'RENAME FOLDER',
|
||
'lib.renameAsk': 'Name shown for this folder (the folder on the disk is not renamed):',
|
||
'lib.rescan': 'RESCAN',
|
||
'lib.remove': 'REMOVE',
|
||
'lib.reconnect': 'GRANT ACCESS AGAIN',
|
||
'lib.nodeHint': 'Right-click to rename, rescan or remove the folder.',
|
||
'lib.colHint': 'Right-click the empty part of this column to add a folder.',
|
||
'lib.colWidth': 'Drag to set the width of the folder column (arrow keys when focused)',
|
||
'lib.viewPreview': 'SINGLE',
|
||
'lib.viewGrid': 'ALL THUMBS',
|
||
'lib.open': 'Open in the studio',
|
||
'lib.edited': 'edited',
|
||
'lib.size': '{mb} MB',
|
||
'lib.count': '{n} photos',
|
||
'lib.scanning': 'Scanning {done}/{total} — {added} new…',
|
||
'lib.scanned': 'Scanned {folder}: {added}/{total} new frames.',
|
||
'lib.empty': 'Nothing in this folder yet.',
|
||
'lib.noFolders': 'No folder yet. Hit ADD FOLDER and pick a photo folder on this machine.',
|
||
'lib.missing': 'Could not open {name} — the file has been moved or deleted.',
|
||
'lib.failed': 'That folder could not be read.',
|
||
'lib.unsupported':
|
||
'This browser will not let a page read a folder from the disk (Chrome, Edge, Opera and Brave will). Photos still edit fine — drag and drop them into the studio.',
|
||
|
||
'nav.admin': 'Admin',
|
||
'nav.photos': 'My photos',
|
||
'nav.profile': 'Profile',
|
||
'nav.library': 'LIBRARY',
|
||
'nav.studio': 'Studio',
|
||
'adm.title': 'Strip moderation',
|
||
'adm.subtitle': 'Photos contributed by users. Delete one to pull it off the landing page.',
|
||
'adm.upload': 'ADD A PHOTO',
|
||
'adm.uploads': 'Uploaded photos',
|
||
'adm.sections': 'Landing sections',
|
||
'adm.albumAll': 'All photos',
|
||
'adm.filterName': 'Filter by user name',
|
||
'adm.sort': 'Sort',
|
||
'adm.sortNewest': 'Newest first',
|
||
'adm.sortRecipe': 'Recipe A→Z',
|
||
'adm.sortRating': 'Highest rated',
|
||
'adm.rating': 'Rating',
|
||
'adm.ratingAll': 'Any rating',
|
||
'adm.ratingAtLeast': '{n}★ and up',
|
||
'adm.recipe': 'Recipe',
|
||
'adm.bigEmpty': 'Pick a frame to view.',
|
||
'adm.pickStrip': 'Film strip',
|
||
'adm.pickTester': 'Live preset tester',
|
||
'adm.pickCreator': 'Custom recipe creator',
|
||
'adm.pickQr': 'QR card',
|
||
'adm.noPreset': 'no look to hand out',
|
||
'adm.rowEmpty': 'Nothing here yet.',
|
||
'adm.slotHint': 'Tick the sections a photo should appear in — a photo can sit in several at once, and each section draws one of its own at random per visit. Unticking every box takes the photo off the landing page without deleting it — its owner keeps it in MY PHOTOS. The QR section is the one that hands the look out: its card shows a code that downloads the photo’s own .recipe file for the phone app to scan in.',
|
||
'adm.signIn': 'Sign in with an admin account',
|
||
'adm.notAdmin': 'This account has no admin rights.',
|
||
'adm.empty': 'No contributions yet.',
|
||
'adm.total': '{n} photos',
|
||
'adm.owner': 'Uploaded by',
|
||
'adm.size': 'Size',
|
||
'adm.uploaded': 'Uploaded',
|
||
'adm.delete': 'DELETE',
|
||
'adm.clearAll': 'DELETE ALL',
|
||
'adm.clearConfirm': 'Delete all {n} contributions? This cannot be undone.',
|
||
'adm.removed': 'Deleted {n} photos.',
|
||
'adm.back': 'Back to home',
|
||
'adm.settings': 'SETTINGS',
|
||
'adm.tabPictures': 'Pictures',
|
||
'adm.tabUsers': 'User account',
|
||
'adm.tabProfile': 'Profile',
|
||
'adm.close': 'Close',
|
||
'adm.userPicture': 'Picture',
|
||
'adm.userName': 'Name',
|
||
'adm.userEmail': 'Email',
|
||
'adm.userRole': 'Role',
|
||
'adm.userPhotos': 'Photos',
|
||
'adm.userJoined': 'Joined',
|
||
'adm.roleAdmin': 'Admin',
|
||
'adm.roleMember': 'Member',
|
||
'adm.viewPhotos': 'VIEW PHOTOS',
|
||
'adm.noUsers': 'No accounts yet.',
|
||
'adm.profileHint': 'Picture, email and password of the signed-in account. Changing the email or password needs the current password.',
|
||
'adm.changeAvatar': 'CHANGE PICTURE',
|
||
'adm.currentPassword': 'Current password',
|
||
'adm.newEmail': 'New email',
|
||
'adm.newPassword': 'New password',
|
||
'adm.save': 'SAVE',
|
||
'adm.saved': 'Profile saved.',
|
||
'adm.userActions': 'Actions',
|
||
'adm.userBlock': 'BLOCK',
|
||
'adm.userUnblock': 'UNBLOCK',
|
||
'adm.userRemove': 'REMOVE',
|
||
'adm.userRestore': 'RESTORE',
|
||
'adm.userDelete': 'DELETE',
|
||
'adm.userBlocked': 'Blocked',
|
||
'adm.userRemoved': 'Removed',
|
||
'adm.userDeleteConfirm': 'Permanently delete the account {email} with all of its photos and recipes? This cannot be undone.',
|
||
'adm.selectAll': 'SELECT ALL',
|
||
'adm.selectNone': 'SELECT NONE',
|
||
'adm.deleteSelected': 'DELETE SELECTED ({n})',
|
||
'adm.userDeleteSelectedConfirm': 'Permanently delete the {n} selected accounts with all of their photos and recipes? This cannot be undone.',
|
||
'adm.tabStats': 'STATS',
|
||
'adm.tabBackup': 'BACKUP',
|
||
'adm.backupHint': 'Download the whole system state — the accounts/recipes database and both media folders — as one .tar.gz. That same file is the data package which moves this install to another machine: restore with it.',
|
||
'adm.backupSave': 'DOWNLOAD BACKUP',
|
||
'adm.restorePick': 'RESTORE FROM FILE…',
|
||
'adm.restoreHint': 'A restore replaces all current data with the file’s, then the API restarts itself. The data being replaced is archived aside in the data folder first.',
|
||
'adm.restoreConfirm': 'Replace all current data with {file}? This cannot be undone. A safety archive is still kept in the data folder.',
|
||
'adm.restoreRunning': 'Restoring — the API is restarting…',
|
||
'adm.restoreDone': 'Restore finished.',
|
||
'adm.restoreSlow': 'The API never answered. Check the container log, then reload.',
|
||
|
||
'stats.days': '{n} DAYS',
|
||
'stats.hint': 'Traffic over the selected range.',
|
||
'stats.views': 'PAGE VIEWS',
|
||
'stats.clicks': 'FEATURE CLICKS',
|
||
'stats.visitors': 'VISITORS',
|
||
'stats.timeline': 'TRAFFIC PER DAY',
|
||
'stats.peak': 'peak',
|
||
'stats.pages': 'PAGES VIEWED',
|
||
'stats.targets': 'FEATURES CLICKED',
|
||
'stats.countries': 'COUNTRIES',
|
||
'stats.regions': 'REGIONS',
|
||
'stats.cities': 'CITIES',
|
||
'stats.browsers': 'BROWSERS',
|
||
'stats.systems': 'OPERATING SYSTEMS',
|
||
'stats.devices': 'DEVICES',
|
||
'stats.empty': 'No data yet.',
|
||
|
||
'profile.title': 'My profile',
|
||
|
||
'err.noWebgl':
|
||
'The browser refused a WebGL context — this build needs WebGL to process photos (the CPU path is thousands of times slower).',
|
||
'err.decode': 'Could not read this image. Try another JPG or PNG.',
|
||
'err.raw': 'Could not develop this RAW file. Try again, or use its JPG.',
|
||
'err.generic': 'Something went wrong: {msg}',
|
||
'err.offline': 'Could not reach the server.',
|
||
|
||
'meta.exported': 'Downloaded {name} ({kb}KB, 300 ppi)',
|
||
};
|
||
|
||
export default en;
|