The daily reminder said one generic line to everyone, because the server
never sees a deck. Now the notification text is written in the browser
(lib/reminder.ts) and travels with the subscription:
- Personal: "3 cards are due in Sky deck", in the app's language, tapping
through to /review. Without notes (or with nothing due) it is the plain
nudge, translated in both languages — push.title / push.due.* / push.generic.
- POST /api/push accepts an optional message {title, body, url}; url must be
a path on this app, so a stolen subscription cannot make the notification
link elsewhere. The store keeps endpoint, keys, the message and createdAt
(a refresh no longer resets the timestamp).
- components/PushReminderSync.tsx refreshes the stored text when the app
opens and whenever the tab becomes visible (throttled to twice a day), so a
count is only ever sent while it is true.
- scripts/send-reminders.mjs sends each device its own text and falls back to
PUSH_REMINDER_TITLE/_BODY for a subscription that has none
(scripts/reminder-payload.mjs, unit-checked).
- /api/push/test previews the real reminder with a "Test:" prefix.
- deploy/freezmo-reminders.{service,timer}: the daily send as a systemd user
timer (18:00 local, Persistent, randomised), documented in the README.
- Settings copy, privacy page (en+cs) and .env.example say exactly what the
server now holds.
- phase2-test grew the whole path with a faked browser subscription: personal
text in en and cs, the generic fallback in both, the URL guard, and the
sender's preference order (81 checks).
Version 0.8.2, SW v104.
2387 lines
123 KiB
TypeScript
2387 lines
123 KiB
TypeScript
/**
|
||
* Pure dictionary + lookup helpers, shared by the browser and the server.
|
||
*
|
||
* No React and no DOM here on purpose: API routes import this module to answer
|
||
* in the same language the UI is currently set to (the browser sends
|
||
* `x-freezmo-locale` with every request).
|
||
*/
|
||
|
||
export type Locale = "en" | "cs";
|
||
|
||
export const LOCALES: Array<{ id: Locale; label: string; short: string }> = [
|
||
{ id: "en", label: "English", short: "EN" },
|
||
{ id: "cs", label: "Čeština", short: "CS" },
|
||
];
|
||
|
||
export const DEFAULT_LOCALE: Locale = "en";
|
||
|
||
/** Header the browser uses to ask for a language in API responses. */
|
||
export const LOCALE_HEADER = "x-freezmo-locale";
|
||
|
||
export type Vars = Record<string, string | number>;
|
||
|
||
function interpolate(template: string, vars?: Vars): string {
|
||
if (!vars) return template;
|
||
return template.replace(/\{(\w+)\}/g, (match, key: string) =>
|
||
Object.prototype.hasOwnProperty.call(vars, key) ? String(vars[key]) : match,
|
||
);
|
||
}
|
||
|
||
/** Translate a key for an explicit locale (falls back to English, then the key). */
|
||
export function translate(locale: Locale, key: string, vars?: Vars): string {
|
||
const table = locale === "cs" ? cs : en;
|
||
const template = table[key] ?? en[key] ?? key;
|
||
return interpolate(template, vars);
|
||
}
|
||
|
||
/**
|
||
* Plural-aware lookup. Keys live as `<base>.one` / `<base>.few` / `<base>.many`;
|
||
* Czech uses the 1 / 2-4 / 5+ rule, English only 1 / rest. `{n}` is provided.
|
||
*/
|
||
export function translatePlural(locale: Locale, n: number, base: string, vars?: Vars): string {
|
||
const form =
|
||
locale === "cs"
|
||
? n === 1
|
||
? "one"
|
||
: n >= 2 && n <= 4
|
||
? "few"
|
||
: "many"
|
||
: n === 1
|
||
? "one"
|
||
: "many";
|
||
const table = locale === "cs" ? cs : en;
|
||
const template = table[`${base}.${form}`] ?? table[`${base}.many`] ?? en[`${base}.many`] ?? base;
|
||
return interpolate(template, { n, ...vars });
|
||
}
|
||
|
||
/** Normalise anything a request sends ("cs-CZ", "CS", "en") to a supported locale. */
|
||
export function normaliseLocale(value: string | null | undefined): Locale {
|
||
if (!value) return DEFAULT_LOCALE;
|
||
const key = value.trim().toLowerCase().split(/[-_,;]/)[0];
|
||
return key === "cs" || key === "cz" ? "cs" : "en";
|
||
}
|
||
|
||
const en: Record<string, string> = {
|
||
/* common ---------------------------------------------------------- */
|
||
"common.cancel": "Cancel",
|
||
"common.save": "Save",
|
||
"common.add": "Add",
|
||
"common.delete": "Delete",
|
||
"common.rename": "Rename",
|
||
"common.close": "Close",
|
||
"common.retry": "Retry",
|
||
"common.settings": "Settings",
|
||
"common.library": "Library",
|
||
"common.newDeck": "New deck",
|
||
"common.folder": "Folder",
|
||
"common.folders": "Folders",
|
||
"common.newFolder": "New folder",
|
||
"common.foldersHint": "Create a folder to organize decks.",
|
||
"common.folderName": "Folder name",
|
||
"common.newFolderName": "New folder name",
|
||
"common.unorganized": "Unorganized",
|
||
"common.allDecks": "All decks",
|
||
"common.lightMode": "Light mode",
|
||
"common.darkMode": "Dark mode",
|
||
"common.switchToLight": "Switch to light mode",
|
||
"common.switchToDark": "Switch to dark mode",
|
||
"common.back": "Back",
|
||
"common.next": "Next",
|
||
"common.more": "More",
|
||
"common.less": "Less",
|
||
"common.edit": "Edit",
|
||
"common.stepOf": "{n} / {total}",
|
||
"common.aiDisclaimer": "Freezmo uses AI and can make mistakes. Double-check important info.",
|
||
|
||
/* header ---------------------------------------------------------- */
|
||
"header.home": "Freezmo home",
|
||
"header.tagline": "study feed",
|
||
"header.openSettings": "Open settings and Ollama status",
|
||
|
||
/* tab bar --------------------------------------------------------- */
|
||
"tabbar.main": "Main",
|
||
/* Short tab labels: they must stay on one line at 360px in both languages. */
|
||
"tab.library": "Library",
|
||
"tab.progress": "Progress",
|
||
"tab.new": "New deck",
|
||
"tab.exam": "Exam",
|
||
"tab.settings": "Settings",
|
||
|
||
/* sidebar --------------------------------------------------------- */
|
||
"sidebar.collapse": "Collapse sidebar",
|
||
"sidebar.expand": "Expand sidebar",
|
||
"sidebar.library": "Library",
|
||
"sidebar.label": "Sidebar",
|
||
|
||
/* language -------------------------------------------------------- */
|
||
"language.label": "Language",
|
||
"language.note": "Interface language. Your notes and the generated decks keep their own language.",
|
||
|
||
/* settings -------------------------------------------------------- */
|
||
"settings.title": "Settings",
|
||
"settings.subtitle.before": "Models come from",
|
||
"settings.subtitle.after": "on the server; your API key stays in this browser.",
|
||
"settings.close": "Close settings",
|
||
"settings.apiKey": "Ollama API key",
|
||
"settings.savedInBrowser": "saved in this browser",
|
||
"settings.required": "required",
|
||
"settings.notNeeded": "not needed",
|
||
"settings.pasteKey": "paste your key from ollama.com/settings/keys",
|
||
"settings.hide": "Hide",
|
||
"settings.show": "Show",
|
||
"settings.keyNote":
|
||
"Stored only in this browser's local storage and sent with each request so the server can talk to Ollama for you. It is never written to the server, never logged and never put in a cookie.",
|
||
"settings.localTipTitle": "Using the local daemon?",
|
||
"settings.localTipSet": "Set",
|
||
"settings.keyNoteAfter": "in",
|
||
"settings.keyNoteEnd": "and use",
|
||
"settings.keyNoteEnd2": " instead.",
|
||
"settings.removeKeyConfirm":
|
||
"Remove the key? The full-screen key setup comes back until you add one.",
|
||
"settings.removeKeyYes": "Yes, remove it",
|
||
"settings.testConnection": "Test connection",
|
||
"settings.removeKey": "Remove key from this browser",
|
||
"settings.savedTesting": "Saved — testing…",
|
||
"settings.installApp": "Install app",
|
||
"settings.addToHome": "Add Freezmo to your home screen",
|
||
"settings.checking": "Checking Ollama…",
|
||
"settings.connection": "Connection",
|
||
"settings.connected": "Connected",
|
||
"settings.modelsReady.one": "{n} model ready",
|
||
"settings.modelsReady.few": "{n} models ready",
|
||
"settings.modelsReady.many": "{n} models ready",
|
||
"settings.details": "Details",
|
||
"settings.privacyLine": "Your key stays in this browser.",
|
||
"settings.recheck": "Re-check connection",
|
||
"settings.haptics": "Haptics",
|
||
"settings.hapticsNote": "Vibration feedback on supported phones.",
|
||
|
||
/* data, backup & sync --------------------------------------------- */
|
||
"settings.data": "Data & backup",
|
||
"settings.dataNote": "Everything lives in this browser. Export a backup regularly.",
|
||
"settings.exportAll": "Export all data",
|
||
"settings.exportNote": "Decks, homework, progress and settings — never your API key.",
|
||
"settings.importBackup": "Import backup",
|
||
"settings.importPreview": "This backup has {decks} decks, {homework} homework results, {podcasts} podcasts, {folders} folders and {chats} chats.",
|
||
"settings.importMergeNote": "Merge keeps what's here and adds the backup; matching IDs are replaced.",
|
||
"settings.importReplaceNote": "Replace wipes this browser's data and uses the backup instead.",
|
||
"settings.importMerge": "Merge",
|
||
"settings.importReplace": "Replace",
|
||
"settings.importCancel": "Cancel",
|
||
"settings.importDone": "Imported {decks} decks and {homework} homework results.",
|
||
"settings.importFailed": "That backup could not be imported.",
|
||
"settings.storage": "Storage",
|
||
"settings.storagePersistent": "Persistent — the browser won't evict your data.",
|
||
"settings.storageBestEffort": "Best-effort — the browser may clear data under pressure.",
|
||
"settings.storageRequest": "Ask for persistence",
|
||
"settings.storageGranted": "Granted — your data is now persistent.",
|
||
"settings.storageDenied": "The browser declined. Installing the app usually helps.",
|
||
"settings.backupNudgeTitle": "Back up your decks?",
|
||
"settings.backupNudgeBody": "You've added decks since your last backup. A backup keeps them safe.",
|
||
"settings.backupNow": "Back up now",
|
||
"settings.backupLater": "Not now",
|
||
"settings.sync": "Sync",
|
||
"settings.syncNote":
|
||
"Off by default. Data is encrypted in this browser before upload — the server stores an opaque blob and never sees your data or key.",
|
||
"settings.syncEnable": "Turn on sync",
|
||
"settings.syncPassphrase": "Sync passphrase",
|
||
"settings.syncPassphraseConfirm": "Repeat the passphrase",
|
||
"settings.syncPassphraseMismatch": "The passphrases don't match.",
|
||
"settings.syncPassphraseShort": "Use at least 8 characters.",
|
||
"settings.syncStart": "Start syncing",
|
||
"settings.syncCancel": "Cancel",
|
||
"settings.syncId": "Sync ID",
|
||
"settings.syncCopy": "Copy",
|
||
"settings.syncCopied": "Copied.",
|
||
"settings.syncLast": "Last synced {when}",
|
||
"settings.syncNever": "Not synced yet.",
|
||
"settings.syncNow": "Sync now",
|
||
"settings.syncWorking": "Syncing…",
|
||
"settings.syncDone": "Up to date.",
|
||
"settings.syncPushed": "Uploaded from this device.",
|
||
"settings.syncPulled": "Downloaded from the synced copy.",
|
||
"settings.syncConflict": "This device and the synced copy both changed. Choose which one to keep.",
|
||
"settings.syncKeepLocal": "Keep this device",
|
||
"settings.syncKeepRemote": "Use synced copy",
|
||
"settings.syncJoin": "Join another sync",
|
||
"settings.syncJoinNote":
|
||
"Enter the sync ID and passphrase from your other device. Data in this browser is replaced by the synced copy.",
|
||
"settings.syncJoinId": "Sync ID",
|
||
"settings.syncJoinAction": "Join",
|
||
"settings.syncLocked": "Enter your sync passphrase to unlock syncing on this device.",
|
||
"settings.syncUnlock": "Unlock",
|
||
"settings.syncForget": "Forget passphrase on this device",
|
||
"settings.syncDelete": "Delete synced data",
|
||
"settings.syncDeleteConfirm": "Delete the synced copy from the server? This device keeps its data.",
|
||
"settings.syncDeleteYes": "Delete",
|
||
"settings.syncWrongPass": "That passphrase can't unlock the synced copy.",
|
||
"settings.syncError": "Sync failed. Check your connection and try again.",
|
||
"settings.syncOff": "Sync is off.",
|
||
"settings.syncOn": "Sync is on.",
|
||
"settings.syncNoteKey": "Your API key is never synced.",
|
||
|
||
/* offline / share / toasts ---------------------------------------- */
|
||
"offline.banner": "You're offline — saved decks still work.",
|
||
"offline.generate": "You're offline — connect to generate.",
|
||
"share.title": "Add to Freezmo",
|
||
"share.intro": "What should I do with this?",
|
||
"share.preview": "Shared content",
|
||
"share.loading": "Loading the shared content…",
|
||
"share.expired": "That share expired — try again.",
|
||
"share.tooLarge": "That's too large to share (max 30 MB).",
|
||
"share.nothing": "Nothing to add.",
|
||
"share.copied": "Copied to the clipboard.",
|
||
"share.failed": "Sharing isn't available here.",
|
||
"toast.deckDeleted": "Deck deleted",
|
||
"toast.fileRemoved": "File removed",
|
||
"toast.undo": "Undo",
|
||
"upload.pasteClipboard": "Paste from clipboard",
|
||
"upload.clipboardDenied": "Clipboard access was denied.",
|
||
|
||
/* health ---------------------------------------------------------- */
|
||
"health.ok": "Ollama is reachable and both models are available.",
|
||
"health.needsKey": "Waiting for your API key.",
|
||
"health.rejected": "Ollama rejected your API key.",
|
||
"health.modelMissing": "Ollama is reachable, but a configured model is missing.",
|
||
"health.unreachable": "Ollama isn't reachable.",
|
||
"health.auth": "Auth",
|
||
"health.authUserKey": "Your own key",
|
||
"health.authServer": "Server key (.env)",
|
||
"health.authLocal": "Local daemon sign-in",
|
||
"health.endpoint": "Endpoint",
|
||
"health.mode": "Mode",
|
||
"health.modeCloud": "Direct cloud API",
|
||
"health.modeLocal": "Local daemon",
|
||
"health.key": "Key",
|
||
"health.keyMissing": "missing",
|
||
"health.keyAccepted": "accepted",
|
||
"health.keyRejected": "rejected",
|
||
"health.keyNotNeeded": "not needed",
|
||
"health.keyUnknown": "unknown",
|
||
"health.visionModel": "Vision model (reads photos)",
|
||
"health.textModel": "Text model (builds & grades)",
|
||
"health.available": "available",
|
||
"health.notFound": "not found",
|
||
"health.unknown": "unknown",
|
||
"health.configuredAs": "configured as {model}",
|
||
"health.checkedNow": "Checked just now",
|
||
"health.checkedAt": "Checked at {time}",
|
||
"health.createKey.before": "Create a key at",
|
||
"health.createKey.after": "and paste it above. Tip: with the local daemon you can run",
|
||
"health.createKey.end": "instead and set",
|
||
|
||
/* make my homework ------------------------------------------------- */
|
||
"homework.nav": "New homework",
|
||
"wizard.mode.deck": "Study deck",
|
||
"wizard.mode.homework": "Make my homework",
|
||
"wizard.mode.podcast": "Podcast",
|
||
"wizard.mode.aria": "Study deck, homework or podcast",
|
||
|
||
/* Podcast mode (notes → a spoken episode with a highlighted transcript) */
|
||
"podcast.title": "New podcast",
|
||
"podcast.subtitle": "Freezmo reads your notes back to you as a short episode — and highlights every word as it is spoken, like lyrics.",
|
||
"podcast.addNotes": "Add your notes",
|
||
"podcast.addPhotos": "Photos",
|
||
"podcast.addDocs": "Documents",
|
||
"podcast.addFirst": "Add notes first",
|
||
"podcast.generate": "Create the episode",
|
||
"podcast.lengthAria": "Episode length",
|
||
"podcast.lengthHelp": "A rough target — the script stays as long as the notes need.",
|
||
"podcast.focusLabel": "What should it focus on? (optional)",
|
||
"podcast.minutes.one": "{n} minute",
|
||
"podcast.minutes.few": "{n} minutes",
|
||
"podcast.minutes.many": "{n} minutes",
|
||
"podcast.sectionCount.one": "{n} section",
|
||
"podcast.sectionCount.few": "{n} sections",
|
||
"podcast.sectionCount.many": "{n} sections",
|
||
"podcast.wordCount.one": "{n} word",
|
||
"podcast.wordCount.few": "{n} words",
|
||
"podcast.wordCount.many": "{n} words",
|
||
"podcast.sectionOf": "Part {n} of {total}",
|
||
"podcast.play": "Play the episode",
|
||
"podcast.pause": "Pause the episode",
|
||
"podcast.restart": "Start from the beginning",
|
||
"podcast.speedAria": "Playback speed",
|
||
"podcast.voiceAria": "Voice",
|
||
"podcast.voiceAuto": "Automatic (best available)",
|
||
"podcast.voiceLocal": "installed",
|
||
"podcast.voiceNote": "Voices come from your browser and system — pick the one that sounds best to you.",
|
||
"podcast.voiceIos": "On iPhone, Safari offers web pages a single voice per language — the system's preferred one. For more (and better) voices: Settings → Accessibility → Spoken Content → Voices → your language, download a voice in Enhanced or Premium quality and make it the preferred one. Freezmo then reads with it.",
|
||
"podcast.voiceSystem": "System voices",
|
||
"podcast.voiceNatural": "Natural voice",
|
||
"podcast.voiceMale": "male",
|
||
"podcast.voiceFemale": "female",
|
||
"podcast.voiceNaturalNote": "Generated by your own provider key — the words are timed to the audio, so the highlight still follows the voice.",
|
||
"podcast.voiceNeedsKey": "Add an OpenAI or Groq key in Settings → Providers to use natural voices.",
|
||
"podcast.voiceNoSystem": "This device reports no system voices. The natural voice works anywhere — or add a voice in your system settings.",
|
||
"podcast.voiceSilent": "The system voice did not start — this browser has no speech engine behind it. On Firefox/Linux install speech-dispatcher (sudo apt install speech-dispatcher) and reopen this page.",
|
||
"podcast.voiceUseNatural": "Use the natural voice",
|
||
"podcast.voiceOpenProviders": "Add a provider key",
|
||
"podcast.voiceGenerating": "Generating the voice…",
|
||
"podcast.voiceRobotic": "Only the basic system voices are installed. For a natural host voice, add one in your system settings (Windows: Speech; macOS: Spoken Content; Chrome ships the Google voices).",
|
||
"podcast.tapToJump": "Tap any word to jump there.",
|
||
"podcast.noVoice": "This browser has no speech voice installed, so the transcript cannot be read aloud here. You can still read along.",
|
||
"podcast.loadingEpisode": "Loading the episode",
|
||
"podcast.notFound": "Episode not found",
|
||
"podcast.notFoundBody": "It was probably deleted, or it lives in another browser's storage.",
|
||
"podcast.loading.read1": "Reading your notes…",
|
||
"podcast.loading.read2": "Making sense of the pages…",
|
||
"podcast.loading.write1": "Writing the script…",
|
||
"podcast.loading.write2": "Finding the through-line…",
|
||
"podcast.loading.write3": "Polishing the spoken bits…",
|
||
"podcast.tip1": "The transcript highlights every word as it is read — tap any word to jump there.",
|
||
"podcast.tip2": "A four-minute walk is a great length for one subject.",
|
||
|
||
/* Library: the podcasts tab */
|
||
"tabPodcasts": "Podcasts",
|
||
"podcast.library.empty": "No episodes yet",
|
||
"podcast.library.emptyBody": "Turn a page of notes into a spoken episode and it will wait here.",
|
||
"podcast.library.new": "New podcast",
|
||
"podcast.card.sections": "{n} parts",
|
||
"podcast.card.minutes": "~{n} min",
|
||
"podcast.card.actions": "Episode actions",
|
||
"podcast.card.open": "Listen",
|
||
"podcast.card.rename": "Rename",
|
||
"podcast.card.delete": "Delete episode",
|
||
"podcast.card.deleted": "Episode deleted",
|
||
"podcast.card.renamed": "Episode renamed",
|
||
"podcast.regenerate": "Write it again",
|
||
"homeworkHeroTitle": "Make my homework",
|
||
"homework.subtitle": "Paste it, photograph it or add a file — then pick how I should help.",
|
||
"homework.phone.assignment": "The assignment",
|
||
"homework.phone.mode": "How should I help?",
|
||
"homework.phone.modeDesc": "Pick how I should help with this assignment.",
|
||
"homework.phone.preview": "Assignment preview",
|
||
"homework.phone.startHint": "Tap a card to start.",
|
||
"homework.source.title": "1. The assignment",
|
||
"homework.source.help": "Paste its text, drop photos, or add a document.",
|
||
"homework.source.textLabel": "Assignment text",
|
||
"homework.source.placeholder": "Paste the assignment here…",
|
||
"homework.source.add": "Add photos or files",
|
||
"homework.source.drop": "Drop the assignment here",
|
||
"homework.source.formats": "Text, photos, PDF, DOCX, PPTX, ODT/ODP, TXT, MD",
|
||
"homework.source.ready": "Ready when you are.",
|
||
"homework.source.need": "Add the assignment first.",
|
||
"homework.source.tooManyPhotos": "Up to {n} photos per assignment.",
|
||
"homework.source.tooManyDocs": "Up to {n} documents per assignment.",
|
||
"homework.mode.title": "2. What should I do?",
|
||
"homework.mode.make.title": "Just make it",
|
||
"homework.mode.make.subtitle": "The finished homework, ready to copy.",
|
||
"homework.mode.guide.title": "Explain how to make this homework",
|
||
"homework.mode.guide.subtitle": "A step-by-step guide with hints and quick checks.",
|
||
"homework.start.make": "Make my homework",
|
||
"homework.start.guide": "Guide me through it",
|
||
"homework.loading.reading": "Reading the assignment…",
|
||
"homework.loading.readingSolution": "Reading your solution…",
|
||
"homework.loading.vision2": "Transcribing the questions…",
|
||
"homework.loading.writing": "Writing your homework…",
|
||
"homework.loading.planning": "Planning the steps…",
|
||
"homework.loading.gen2": "Checking every part…",
|
||
"homework.loading.gen3": "Almost done…",
|
||
"homework.error.title": "That didn't work",
|
||
"homework.error.body": "Something went wrong while working on the assignment.",
|
||
"homework.result.title": "Your homework",
|
||
"homework.result.copy": "Copy",
|
||
"homework.result.copied": "Copied!",
|
||
"homework.result.copyFailed": "Copy didn't work — select the text and copy it manually.",
|
||
"homework.result.again": "Make it again",
|
||
"homework.result.new": "New assignment",
|
||
"homework.guide.stepOf": "Step {n} of {total}",
|
||
"homework.guide.close": "Close the guide",
|
||
"homework.guide.tryIt": "Try it yourself",
|
||
"homework.report": "Report exercise",
|
||
"homework.guide.hint": "Show a hint",
|
||
"homework.guide.hintMore": "Another hint",
|
||
"homework.guide.hintAnswer": "Show the answer",
|
||
"homework.guide.hintN": "Hint {n}",
|
||
"homework.guide.next": "Next step",
|
||
"homework.guide.showSolution": "Show the solution",
|
||
"homework.guide.solution": "The full solution",
|
||
"homework.guide.solutionNote":
|
||
"Assembled from the steps above — copy it and hand it in.",
|
||
"homework.history.title": "Recent homework",
|
||
"homework.history.made": "Made",
|
||
"homework.history.guide": "Guide",
|
||
"homework.history.openTitle": "Open “{title}”",
|
||
"homework.history.deleteConfirm": "Delete this result?",
|
||
"homework.emptyTitle": "No homework yet",
|
||
"homework.emptyBody": "Solved assignments and step-by-step guides show up here.",
|
||
"homework.open": "Open",
|
||
"homework.steps.one": "{n} step",
|
||
"homework.steps.few": "{n} steps",
|
||
"homework.steps.many": "{n} steps",
|
||
|
||
/* library tabs ----------------------------------------------------- */
|
||
"tabDecks": "Study Decks",
|
||
"tabHomework": "Homework",
|
||
|
||
/* quick actions (library empty state + /create switcher) ----------- */
|
||
"emptyStateCreateDeck": "Create study deck",
|
||
"emptyStateHomework": "New homework",
|
||
|
||
/* library --------------------------------------------------------- */
|
||
"library.title": "Your freezer",
|
||
"library.subtitle": "Decks frozen from your notes, saved in this browser.",
|
||
"library.import": "Import JSON",
|
||
"library.moreActions": "More actions",
|
||
"library.moreTitle": "Library actions",
|
||
"library.folderCreated": "Folder created — decks you freeze while it's selected land here.",
|
||
"library.imported": "Imported “{title}”.",
|
||
"library.couldNotRead": "Could not read that file.",
|
||
"library.folderDeleted": "Folder deleted — its decks moved to Unorganized.",
|
||
"library.deckMoved": "Deck moved.",
|
||
"library.deckMovedUnorganized": "Deck moved to Unorganized.",
|
||
"library.noDecks": "No decks yet",
|
||
"library.emptyBody": "Freeze a page of notes into flashcards and quizzes.",
|
||
"library.renameFolderAria": "Folder name",
|
||
"library.nothingUnfiled": "Nothing unfiled right now.",
|
||
"library.emptyFolder": "Empty folder — freeze a deck into it or move one over from Unorganized.",
|
||
"library.stats.best": "best {n}%",
|
||
"library.deleteWarning.one": "The {n} deck inside will move to Unorganized — nothing is deleted.",
|
||
"library.deleteWarning.few": "The {n} decks inside will move to Unorganized — nothing is deleted.",
|
||
"library.deleteWarning.many": "The {n} decks inside will move to Unorganized — nothing is deleted.",
|
||
"stats.decks.one": "{n} deck",
|
||
"stats.decks.few": "{n} decks",
|
||
"stats.decks.many": "{n} decks",
|
||
"stats.folders.one": "{n} folder",
|
||
"stats.folders.few": "{n} folders",
|
||
"stats.folders.many": "{n} folders",
|
||
"stats.activities.one": "{n} activity",
|
||
"stats.activities.few": "{n} activities",
|
||
"stats.activities.many": "{n} activities",
|
||
|
||
/* deck card ------------------------------------------------------- */
|
||
"deck.activities.one": "{n} activity",
|
||
"deck.activities.few": "{n} activities",
|
||
"deck.activities.many": "{n} activities",
|
||
"deck.runs.one": "{n} run",
|
||
"deck.runs.few": "{n} runs",
|
||
"deck.runs.many": "{n} runs",
|
||
"deck.notPlayed": "Not played yet",
|
||
"deck.new": "New",
|
||
"deck.best": "Best {n}%",
|
||
"deck.bestLabel": "Best",
|
||
"deck.study": "Study",
|
||
"deck.actions": "Deck actions",
|
||
"deck.titleLabel": "Deck title",
|
||
"deck.rename": "Rename",
|
||
"deck.export": "Export JSON",
|
||
"deck.share": "Share",
|
||
"deck.regenerate": "Regenerate deck",
|
||
"deck.reallyDelete": "Really delete",
|
||
"deck.delete": "Delete deck",
|
||
"deck.newFolder": "+ New folder",
|
||
"deck.addMore": "Add more activities",
|
||
"deck.generate": "Generate",
|
||
"deck.noSource": "No stored source text for this deck (imported decks don't carry it).",
|
||
"deck.noSourceExtend": "This deck has no stored source notes, so it can't be extended.",
|
||
"deck.noSourceRegenerate": "This deck has no stored source notes, so it can't be regenerated.",
|
||
"deck.couldNotAdd": "Could not add more activities.",
|
||
"deck.couldNotRegenerate": "Could not regenerate the deck.",
|
||
"deck.regenerated": "Deck regenerated",
|
||
"deck.added.one": "Added {n} activity",
|
||
"deck.added.few": "Added {n} activities",
|
||
"deck.added.many": "Added {n} activities",
|
||
"deck.addingActivities": "Adding activities",
|
||
"deck.regeneratingDeck": "Regenerating deck",
|
||
"deck.addingNew": "Adding new activities…",
|
||
"deck.regeneratingWhole": "Regenerating the whole deck…",
|
||
|
||
/* upload ---------------------------------------------------------- */
|
||
"upload.title": "Turn your notes into a study feed",
|
||
"upload.introShort": "Add your notes — I'll build the deck.",
|
||
"upload.intro":
|
||
"Snap or drop photos of your notes. Freezmo reads them with your own Ollama vision model and builds a swipeable deck of mini study games.",
|
||
"upload.addPhotosFiles": "Add photos or files",
|
||
"upload.pasteInstead": "Paste text instead",
|
||
"upload.focusExtra": "Add a focus or extra notes (optional)",
|
||
"upload.phone.notes": "Your notes",
|
||
"upload.phone.notesDesc": "Photos, PDF, Word or text — up to {images} photos and {documents} files.",
|
||
"upload.phone.options": "Options",
|
||
"upload.phone.optionsDesc": "Everything has a sensible default — change what you like.",
|
||
"upload.phone.summary": "Ready to freeze",
|
||
"upload.phone.summaryDesc": "Check what goes in, then freeze.",
|
||
"upload.phone.difficultyHelp": "How deep the questions go.",
|
||
"upload.phone.activitiesHelp": "How many activities to build.",
|
||
"upload.phone.folderHelp": "Where the deck lands in your library.",
|
||
"upload.nothingAdded": "Nothing added yet",
|
||
"upload.summary.files": "Files",
|
||
"upload.summary.folder": "Folder",
|
||
"upload.summaryText": "Text",
|
||
"upload.photoCount.one": "{n} photo",
|
||
"upload.photoCount.few": "{n} photos",
|
||
"upload.photoCount.many": "{n} photos",
|
||
"upload.errNeedsKey": "API key required",
|
||
"upload.errRate": "Slow down a moment",
|
||
"upload.errAuth": "Ollama rejected your key",
|
||
"upload.errFailed": "Generation failed",
|
||
"upload.openSettings": "Open settings",
|
||
"upload.addPhotosAria": "Add photos of your notes",
|
||
"upload.step1": "1. Add your notes",
|
||
"upload.step1Help":
|
||
"Drag & drop, paste a screenshot (Ctrl/⌘+V), or use the camera. Photos, PDF, Word, PowerPoint, OpenDocument and text files. Up to {images} photos and {documents} documents.",
|
||
"upload.chooseFiles": "Choose files",
|
||
"upload.camera": "Camera",
|
||
"upload.dropHere": "Drop your notes here",
|
||
"upload.formats": "Photos, PDF, DOCX, PPTX, ODT/ODP, TXT, MD · images resized to 1600px before sending",
|
||
"upload.remove": "Remove {name}",
|
||
"upload.addMorePhotos": "Add more photos",
|
||
"upload.reading": "Reading…",
|
||
"upload.noReadable": "No readable text — is this a scan without a text layer?",
|
||
"upload.words.one": "{n} word",
|
||
"upload.words.few": "{n} words",
|
||
"upload.words.many": "{n} words",
|
||
"upload.pages.one": "{n} page",
|
||
"upload.pages.few": "{n} pages",
|
||
"upload.pages.many": "{n} pages",
|
||
"upload.pageImages.one": "{n} page image",
|
||
"upload.pageImages.few": "{n} page images",
|
||
"upload.pageImages.many": "{n} page images",
|
||
"upload.truncated": "truncated",
|
||
"upload.photoSummary.one": "{n} photo · {size} after compression",
|
||
"upload.photoSummary.few": "{n} photos · {size} after compression",
|
||
"upload.photoSummary.many": "{n} photos · {size} after compression",
|
||
"upload.docSummary.one": "{n} document",
|
||
"upload.docSummary.few": "{n} documents",
|
||
"upload.docSummary.many": "{n} documents",
|
||
"upload.docImages.one": "{n} page image",
|
||
"upload.docImages.few": "{n} page images",
|
||
"upload.docImages.many": "{n} page images",
|
||
"upload.skipImages.one": "{n} image will be skipped (max {max} per deck)",
|
||
"upload.skipImages.few": "{n} images will be skipped (max {max} per deck)",
|
||
"upload.skipImages.many": "{n} images will be skipped (max {max} per deck)",
|
||
"upload.compressing": "Compressing photos…",
|
||
"upload.step2": "2. Optional extras",
|
||
"upload.step2Help": "Paste extra notes or give the AI a hint about what to focus on.",
|
||
"upload.pastedNotes": "Pasted notes or text",
|
||
"upload.pastedPlaceholder": "Paste lecture text, a summary, or any extra facts here…",
|
||
"upload.topicHint": "Topic hint (optional)",
|
||
"upload.topicPlaceholder": "e.g. focus on the formulas, or on dates",
|
||
"upload.step3": "3. Difficulty & size",
|
||
"upload.freezeInto": "Freeze into folder",
|
||
"upload.folderPlaceholder": "e.g. History",
|
||
"upload.difficultyAria": "Difficulty",
|
||
"upload.easy": "Easy",
|
||
"upload.easyBlurb": "Recall & recognition",
|
||
"upload.medium": "Medium",
|
||
"upload.mediumBlurb": "Understanding & application",
|
||
"upload.hard": "Hard",
|
||
"upload.hardBlurb": "Analysis & tricky edges",
|
||
"upload.activities": "Activities",
|
||
"upload.readingDocs": "Reading your documents…",
|
||
"upload.ready": "Ready when you are — generation can take a minute.",
|
||
"upload.addToStart": "Add a photo or a document (or some text) to start.",
|
||
"upload.freezeDeck": "Freeze my deck",
|
||
"upload.generationProgress": "Generation progress",
|
||
"upload.cancel": "Cancel",
|
||
"upload.unsupported":
|
||
"“{name}” is not a supported format. Add photos or a PDF, DOCX, PPTX, ODT/ODP, TXT or MD file.",
|
||
"upload.tooManyDocs": "Up to {n} documents per deck.",
|
||
"upload.tooManyPhotos": "Up to {n} photos per deck.",
|
||
"upload.couldNotReadFile": "Could not read “{name}”.",
|
||
"upload.couldNotReadDoc": "Could not read this file.",
|
||
"upload.genericError":
|
||
"Something went wrong while generating your deck. Check your Ollama settings and try again.",
|
||
"upload.warming": "Warming up…",
|
||
"upload.vision1": "Reading your notes…",
|
||
"upload.vision2": "Transcribing handwriting…",
|
||
"upload.vision3": "Spotting definitions…",
|
||
"upload.gen1": "Finding the key ideas…",
|
||
"upload.gen2": "Writing good questions…",
|
||
"upload.gen3": "Building your activities…",
|
||
"upload.tip1": "Tip: swipe up and down through your deck like a feed.",
|
||
"upload.tip2": "Tip: matching questions only give full XP for a perfect match.",
|
||
"upload.tip3": "Tip: your decks are saved in this browser — no account needed.",
|
||
"upload.tip4": "Tip: export a deck as JSON to share it with a friend.",
|
||
|
||
/* study feed ------------------------------------------------------ */
|
||
"feed.back": "Back to your library",
|
||
"feed.activityOf": "Activity {n} of {total}",
|
||
"feed.streak": "Answer streak: {n}",
|
||
"feed.xp": "XP earned: {n}",
|
||
"feed.retry": "Retry · ",
|
||
"feed.swipeUp": "Swipe up for the next activity ↑",
|
||
"feed.prev": "Previous activity",
|
||
"feed.next": "Next activity",
|
||
"feed.results": "Results",
|
||
"feed.report": "Report question",
|
||
"feed.reported": "Question reported — it won't show up again.",
|
||
"feed.timed": "Timed run · 30s per card",
|
||
"feed.timedOn": "Timed run on — a card that runs out counts as a miss",
|
||
"feed.timedOut": "Time's up — the card counts as a miss.",
|
||
|
||
/* results --------------------------------------------------------- */
|
||
"results.perfect": "Perfect freeze! 🧊",
|
||
"results.nice": "Nicely done!",
|
||
"results.start": "Good start — keep going!",
|
||
"results.acedAll": "You aced all {n} activities in “{title}”.",
|
||
"results.gotOf": "You got {correct} of {total} right{retry}.",
|
||
"results.retrySuffix": " on the retry run",
|
||
"results.xpEarned": "{n} XP earned",
|
||
"results.bestStreak": "Best streak {n}",
|
||
"results.difficulty": "{level} difficulty",
|
||
"results.best": "🏆 Best {n}%",
|
||
"results.missed": "Missed items ({n})",
|
||
"results.unanswered": "Not answered ({n})",
|
||
"results.unansweredNote": "Skipped cards are not counted as mistakes — they wait for you here.",
|
||
"results.retryUnanswered": "Answer the rest ({n})",
|
||
"results.noneAnswered": "Nothing answered this time — pick up where you left off.",
|
||
"results.yourAnswer": "Your answer: {answer}",
|
||
"results.answer": "Answer:",
|
||
"results.retryMissed": "Retry missed ones ({n})",
|
||
"results.studyAgain": "Study this deck again",
|
||
"results.studyWhole": "Study the whole deck again",
|
||
"results.regenerate": "Regenerate deck",
|
||
"results.regenerating": "Regenerating…",
|
||
"results.export": "Export JSON",
|
||
"results.score": "score",
|
||
"results.true": "True",
|
||
"results.false": "False",
|
||
|
||
/* gate ------------------------------------------------------------ */
|
||
"gate.free": "Free",
|
||
"gate.ollamaLabel": "Ollama",
|
||
"gate.or": "or",
|
||
"gate.paidOption": "Use a paid provider instead",
|
||
"gate.paidNote": "OpenAI, Anthropic, Gemini, DeepSeek, Groq, OpenRouter… Billed by the provider directly — Freezmo never sees a card. Your keys stay in this browser, and picking one switches study generation to it.",
|
||
"gate.title": "Connect your Ollama key",
|
||
"gate.subtitle": "One step to get your grades up!",
|
||
"gate.apiKeyLabel": "Ollama API key",
|
||
"gate.placeholder": "Paste your Ollama API key",
|
||
"gate.hide": "Hide API key",
|
||
"gate.show": "Show API key",
|
||
"gate.checking": "Checking…",
|
||
"gate.continue": "Continue",
|
||
"gate.howGet": "How do I get a key?",
|
||
"gate.howGetAria": "How do I get an Ollama API key?",
|
||
"gate.stays": "Your key stays in this browser. Our server never stores it.",
|
||
"gate.helpTitle": "How to get your Ollama API key",
|
||
"gate.freeTier.before": "You only need the",
|
||
"gate.freeTier.strong": "free cloud tier",
|
||
"gate.freeTier.after": ". No payment and no credit card required.",
|
||
"gate.step1.before": "Go to",
|
||
"gate.step1.after": "and sign in, or create a free account.",
|
||
"gate.step2.before": "Open Settings → API keys at",
|
||
"gate.step2.after": ".",
|
||
"gate.step3.before": "Click",
|
||
"gate.step3.strong": "“Create new key”",
|
||
"gate.step3.after": ", give it any name, and copy it.",
|
||
"gate.step4": "Paste it into Freezmo and press Continue.",
|
||
"gate.footnote":
|
||
"The free tier is enough for generating decks. You can swap or remove your key anytime in Settings.",
|
||
"gate.gotIt": "Got it",
|
||
|
||
/* install sheet --------------------------------------------------- */
|
||
"install.titleIos": "Install Freezmo on iPhone",
|
||
"install.titleAndroid": "Install Freezmo on Android",
|
||
"install.install": "Install",
|
||
"install.installing": "Installing…",
|
||
"install.orManually": "Or install manually:",
|
||
"install.maybeLater": "Maybe later",
|
||
"install.openIn": "Open this page in",
|
||
"install.tap": "Tap",
|
||
"install.tapConfirm": "Tap",
|
||
"install.inBrowserBar": "in the browser bar",
|
||
"install.then": ", then",
|
||
"install.choose": "Choose",
|
||
"install.toConfirm": "to confirm",
|
||
"install.or": "or",
|
||
"install.inTopRight": "in the top right of the browser",
|
||
"install.moreOptions": "More options",
|
||
"install.share": "Share",
|
||
"install.more": "More",
|
||
"install.addToHome": "Add to Home Screen",
|
||
"install.add": "Add",
|
||
"install.installApp": "Install app",
|
||
"install.addToHomeScreen": "Add to Home screen",
|
||
|
||
/* deck page ------------------------------------------------------- */
|
||
"deckPage.loading": "Loading your deck…",
|
||
"deckPage.notFound": "Deck not found",
|
||
"deckPage.notFoundBody":
|
||
"This deck isn't in this browser's library. It may have been deleted, or it was created in a different browser profile.",
|
||
"deckPage.back": "Back to library",
|
||
"deckPage.create": "Create a deck",
|
||
|
||
/* offline --------------------------------------------------------- */
|
||
"offline.title": "You're offline",
|
||
"offline.body":
|
||
"Freezmo needs a connection for generating decks and grading answers. Your saved decks are still in this browser — reconnect and reload.",
|
||
|
||
/* not found ------------------------------------------------------- */
|
||
"notFound.title": "This page melted away",
|
||
"notFound.hint":
|
||
"The link may be old, or the address has a typo. Your decks are safe in this browser — head back home.",
|
||
"notFound.home": "Back to library",
|
||
"notFound.chat": "Ask the chat instead",
|
||
|
||
/* activity types -------------------------------------------------- */
|
||
"type.flashcard": "Flashcard",
|
||
"type.multipleChoice": "Multiple choice",
|
||
"type.trueFalse": "True or false",
|
||
"type.fillBlank": "Fill the blank",
|
||
"type.matching": "Match up",
|
||
"type.ordering": "Put in order",
|
||
"type.shortAnswer": "Short answer",
|
||
|
||
/* activities shared ----------------------------------------------- */
|
||
"activity.answerOptions": "Answer options",
|
||
"activity.wordBank": "Word bank",
|
||
"activity.niceOne": "Nice one!",
|
||
"activity.notQuite": "Not quite",
|
||
"activity.why": "Why:",
|
||
"activity.seeResults": "See results",
|
||
"activity.next": "Next",
|
||
"activity.correctAnswer": "Correct answer:",
|
||
"activity.unsupported": "Unsupported activity: {json}",
|
||
"activity.source": "From your notes",
|
||
"activity.speak": "Read aloud",
|
||
"activity.stopSpeaking": "Stop reading",
|
||
|
||
/* flashcard ------------------------------------------------------- */
|
||
"flashcard.tapToFlip": "Tap the card to flip it.",
|
||
"flashcard.didYouKnow": "Did you know it before flipping?",
|
||
"flashcard.question": "Question",
|
||
"flashcard.answer": "Answer",
|
||
"flashcard.tapFlip": "tap to flip ↻",
|
||
"flashcard.flipBack": "Flip back to the question",
|
||
"flashcard.reveal": "Reveal the answer",
|
||
"flashcard.notYet": "😅 Not yet",
|
||
"flashcard.knewIt": "🧊 I knew it",
|
||
"flashcard.frozenSolid": "Frozen solid!",
|
||
"flashcard.keepPile": "Keep it on the pile",
|
||
|
||
/* multiple choice ------------------------------------------------ */
|
||
"mc.tipKeys": "Tip: press 1–4 on a keyboard.",
|
||
|
||
/* true/false ------------------------------------------------------ */
|
||
"tf.hint": "Swipe the card right for true, left for false — or tap a button.",
|
||
"tf.niceSwipe": "Nice swipe!",
|
||
"tf.trueLabel": "✓ true",
|
||
"tf.falseLabel": "false ✕",
|
||
"tf.falseBtn": "✕ False",
|
||
"tf.trueBtn": "✓ True",
|
||
"tf.statementIs.before": "This statement is",
|
||
"tf.true": "true",
|
||
"tf.false": "false",
|
||
|
||
/* fill blank ------------------------------------------------------ */
|
||
"fill.typeMissing": "Type the missing word",
|
||
"fill.placeholder": "type it",
|
||
"fill.check": "Check answer",
|
||
"fill.hint": "Press Enter to check. Small typos are forgiven.",
|
||
"fill.expected": "Expected:",
|
||
"fill.youWrote": "— you wrote",
|
||
|
||
/* matching -------------------------------------------------------- */
|
||
"matching.matched": "{n}/{total} matched",
|
||
"matching.allMatched": "All matched!",
|
||
"matching.tapTerm": "Tap a term on the left, then its match on the right.",
|
||
"matching.tapDef": "Now tap the matching definition →",
|
||
"matching.perfect": "Perfect match!",
|
||
"matching.slips": "Matched — with a few slips",
|
||
"matching.firstTry": "You matched everything first try.",
|
||
"matching.wrongAttempts.one": "You made {n} wrong attempt.",
|
||
"matching.wrongAttempts.few": "You made {n} wrong attempts.",
|
||
"matching.wrongAttempts.many": "You made {n} wrong attempts.",
|
||
|
||
/* ordering -------------------------------------------------------- */
|
||
"ordering.hint": "Drag the cards, or use ▲▼, then check your order.",
|
||
"ordering.correctOrder": "Here is the correct order:",
|
||
"ordering.check": "Check order",
|
||
"ordering.moveUp": "Move “{item}” up",
|
||
"ordering.moveDown": "Move “{item}” down",
|
||
|
||
/* short answer ---------------------------------------------------- */
|
||
"short.placeholder": "Type your answer in your own words…",
|
||
"short.ctrlEnter": "Ctrl/⌘ + Enter to submit.",
|
||
"short.submit": "Submit answer",
|
||
"short.grading": "Grading your answer…",
|
||
"short.couldNotGrade": "Couldn't grade that answer",
|
||
"short.showAnswer": "Show answer & self-check",
|
||
"short.modelAnswer": "Model answer",
|
||
"short.keyPoints": "Key points:",
|
||
"short.missedIt": "😅 I missed it",
|
||
"short.hadRight": "🧊 I had it right",
|
||
"short.accepted": "Accepted",
|
||
"short.needsWork": "Needs work",
|
||
"short.selfChecked": "Self-checked — you judged this one yourself.",
|
||
"short.yourAnswer": "Your answer",
|
||
"short.gradeFailed": "Grading failed. Check that Ollama is reachable and try again.",
|
||
|
||
/* client API errors ----------------------------------------------- */
|
||
"api.cancelled": "Generation was cancelled.",
|
||
"api.unreachable": "Could not reach the Freezmo server. Is it still running?",
|
||
"api.unreachableShort": "Could not reach the Freezmo server.",
|
||
"api.httpError": "The server returned HTTP {status}.",
|
||
"api.empty": "The server sent an empty response.",
|
||
"api.stalled": "The model stopped responding. Check it and try again.",
|
||
"api.streamLost": "The connection dropped mid-run. Check your connection and try again.",
|
||
"api.almost": "Almost there…",
|
||
"api.noDeck": "No deck came back from the server. Check your Ollama settings and try again.",
|
||
"api.noPodcast": "No script came back from the server. Check the model setup and try again.",
|
||
"api.noAudio": "The voice came back empty. Try another voice or provider.",
|
||
"api.incomplete": "The server finished without sending a deck.",
|
||
"api.noActivities": "The generated deck had no usable activities. Try again.",
|
||
"api.gradingCancelled": "Grading was cancelled.",
|
||
"api.gradingFailed": "Grading failed with HTTP {status}.",
|
||
"api.healthFailed": "The health check failed with HTTP {status}.",
|
||
"api.healthUnexpected": "The Freezmo server sent an unexpected health response.",
|
||
"api.serverUnexpected": "The Freezmo server answered unexpectedly. Try again in a moment.",
|
||
"api.keyRejected": "Ollama rejected this key. Check for typos, or create a new one at ollama.com/settings/keys.",
|
||
"api.ollamaUnreachable": "Couldn't reach Ollama just now. Check your connection and try again.",
|
||
|
||
/* server-side messages (API routes) -------------------------------- */
|
||
"server.bodyNotJson": "Request body was not valid JSON.",
|
||
"server.invalidRequest": "Invalid request: {detail}",
|
||
"server.providerModelNeeded": "Pick the generation model in Settings → Providers.",
|
||
"server.ttsNeedsProvider": "Natural voices come from a provider — add a provider key in Settings → Providers first.",
|
||
"server.ttsUnsupported": "{provider} does not offer text-to-speech. OpenAI, Groq or a custom OpenAI-compatible server will.",
|
||
"server.ttsFailed": "{provider} could not generate the voice for this part.",
|
||
"server.ttsTimeout": "{provider} took too long to generate the voice.",
|
||
"server.needContent": "Add at least one photo of your notes or paste some text to build a deck.",
|
||
"server.needAssignment":
|
||
"Add the assignment first — paste its text, photograph it, or add a document.",
|
||
"server.invalidGrade": "Invalid grading request.",
|
||
"server.bodyTooLarge": "Request body is too large ({size}). Limit is {limit}.",
|
||
"server.invalidOrigin": "Invalid Origin header.",
|
||
"server.crossOrigin": "Cross-origin requests are not allowed.",
|
||
"server.rateLimited": "Too many requests — this app allows {limit} per window. Try again in {retry}s.",
|
||
"server.busy": "Freezmo is already working on something — try again in a moment.",
|
||
"server.pushTooMany": "This server already stores {max} reminder devices — remove one first.",
|
||
"server.pushEndpoint": "That is not a push service this server is allowed to send to.",
|
||
"server.authMissingKey":
|
||
"Add your Ollama API key first — open the settings dialog (⚙️) and paste a key from ollama.com/settings/keys.",
|
||
"server.authServerKeyMissing":
|
||
"This server is configured to use its own Ollama API key (OLLAMA_AUTH=server), but OLLAMA_API_KEY is empty in .env.local.",
|
||
"server.authLocalCloud":
|
||
"This server is set to OLLAMA_AUTH=local but points at https://ollama.com. A signed-in local daemon can't be used against the cloud URL — either set OLLAMA_AUTH=user-key, or point OLLAMA_BASE_URL at http://localhost:11434.",
|
||
"server.ollamaUnreachable":
|
||
"Ollama isn't reachable at {url}. Check your settings and make sure the daemon is running.",
|
||
"server.ollamaTimeout": "Ollama took too long to answer. Try fewer/smaller images, a faster model, or retry.",
|
||
"server.ollamaAuth":
|
||
"Ollama rejected the request. If you are using https://ollama.com, check OLLAMA_API_KEY (create one at ollama.com/settings/keys).",
|
||
"server.ollamaUnexpected": "Ollama returned an unexpected response.",
|
||
"server.ollamaGeneric": "Something went wrong while talking to Ollama.",
|
||
"server.ollamaTimeoutList": "Timed out while listing Ollama models.",
|
||
"server.ollamaUnauthorised": "Ollama refused the request (unauthorised).",
|
||
"server.ollamaNotJson": "Ollama sent a response that wasn't JSON.",
|
||
"server.ollamaNoContent": "Ollama sent a response with no message content.",
|
||
"server.healthNoKey":
|
||
"No Ollama API key yet. Add your key in the settings dialog — it is stored only in this browser.",
|
||
"server.healthKeyRejected": "Ollama rejected this API key. Create a new one at ollama.com/settings/keys and paste it again.",
|
||
"server.healthNoCredits":
|
||
"The key works, but {model} is not included in this account's plan. Add usage credits or switch to a free-tier model in .env.local.",
|
||
"server.healthModelNoteLocal": "Not in the daemon's model list. Run `ollama pull {model}` or check your sign-in.",
|
||
"server.healthModelNoteCloud": "Not returned by {url}/api/tags. Check the model name and your key's plan.",
|
||
"server.healthModelsMissing": "Ollama is reachable, but at least one configured model is not available.",
|
||
"server.usageLimit":
|
||
"Your free monthly Ollama usage has run out. Upgrade your Ollama account, or create a new one and use the new API key.",
|
||
"server.ollamaThrottled": "Ollama is temporarily rate limiting requests. Try again in a moment.",
|
||
"server.syncInvalidId": "That sync ID doesn't look right.",
|
||
"server.syncTooLarge": "This sync upload is too large.",
|
||
"server.syncStorageFull": "The sync storage is full. Delete some synced data and try again.",
|
||
"server.syncNotFound": "No synced data found for that ID.",
|
||
"server.pushDisabled": "Push reminders are not configured on this server (set VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY).",
|
||
"server.pushNotSubscribed": "This browser is not subscribed to reminders.",
|
||
"server.pushGone": "That subscription is no longer valid — reminders were turned off for it.",
|
||
"server.pushSendFailed": "The reminder could not be sent.",
|
||
"server.pushTestPrefix": "Test:",
|
||
|
||
/* folders / files -------------------------------------------------- */
|
||
"folder.nameRequired": "Give the folder a name.",
|
||
"folder.nameTooLong": "That name is a bit long (60 characters max).",
|
||
"folder.nameTaken": "Another folder already has that name.",
|
||
"file.notImage": "“{name}” is not an image.",
|
||
"file.imageTooLarge": "“{name}” is larger than 20 MB.",
|
||
"file.notDocument": "“{name}” is not a supported document (use PDF, DOCX, PPTX, ODT/ODP or text).",
|
||
"file.documentTooLarge": "“{name}” is larger than 40 MB.",
|
||
"import.notJson": "That file is not valid JSON.",
|
||
"import.notDeck": "That file doesn't look like a Freezmo deck.",
|
||
"import.notBackup": "That file doesn't look like a Freezmo backup.",
|
||
"error.usageTitle": "Free monthly usage used up",
|
||
"error.usageBody":
|
||
"You've run out of your free monthly Ollama usage. Upgrade your Ollama account, or create a new one and use the new API key.",
|
||
"error.openOllama": "Open Ollama",
|
||
"error.throttledTitle": "Ollama is busy",
|
||
"error.throttledBody": "Ollama is temporarily rate limiting requests. Try again in a moment.",
|
||
|
||
/* learning: review, mistakes, stats, exam -------------------------- */
|
||
"review.cardTitle": "Smart review",
|
||
"review.title": "Review due",
|
||
"review.mistakesTitle": "Mistakes",
|
||
"review.mistakesDeck": "Mistakes in this deck",
|
||
"review.nothingDue": "Nothing is due right now. Study a deck and the schedule fills up on its own.",
|
||
"review.noMistakes": "No open mistakes — everything you got wrong has since been answered correctly.",
|
||
"review.done": "Back to the Library",
|
||
"review.seeResults": "See results",
|
||
"review.finishFirst": "Finish the last activity to see your results.",
|
||
"review.results": "Results",
|
||
"review.preparing": "Preparing your review…",
|
||
"review.due.one": "{n} due",
|
||
"review.due.few": "{n} due",
|
||
"review.due.many": "{n} due",
|
||
"review.dueByDeck": "Due by deck: {list}",
|
||
"review.dueOfTotal": "{due} of {total}",
|
||
"review.mistakesByDeck": "Mistakes by deck: {list}",
|
||
"review.moreDecks.one": "+{n} more deck",
|
||
"review.moreDecks.few": "+{n} more decks",
|
||
"review.moreDecks.many": "+{n} more decks",
|
||
"review.mistakes.one": "{n} mistake",
|
||
"review.mistakes.few": "{n} mistakes",
|
||
"review.mistakes.many": "{n} mistakes",
|
||
"exam.title": "Exam mode",
|
||
"exam.preparing": "Preparing exam mode…",
|
||
"exam.subtitle": "Pick one or more decks, set a timer, no hints — see how you'd do.",
|
||
"exam.pickDecks": "Decks",
|
||
"exam.noDecks": "Create a deck first.",
|
||
"exam.questions": "Questions",
|
||
"exam.timer": "Timer",
|
||
"exam.timerOff": "Off",
|
||
"exam.start": "Start exam ({n} questions)",
|
||
"exam.results": "Exam results",
|
||
"exam.byDeck": "By deck",
|
||
"exam.byTopic": "By topic",
|
||
"exam.studyMistakes": "Study mistakes",
|
||
"exam.timeLeft": "{time} left",
|
||
"stats.title": "Your progress",
|
||
"stats.preparing": "Preparing your progress…",
|
||
"stats.subtitle": "Streaks, accuracy and the topics that deserve another pass.",
|
||
"stats.streak": "Day streak",
|
||
"stats.today": "Today",
|
||
"stats.todayOf": "{n} / {goal} answers",
|
||
"stats.goalDone": "Daily goal done 🎉",
|
||
"stats.accuracy": "Accuracy",
|
||
"stats.answers.one": "{n} answer",
|
||
"stats.answers.few": "{n} answers",
|
||
"stats.answers.many": "{n} answers",
|
||
"stats.calendar": "Last 5 weeks",
|
||
"stats.bySubject": "By subject",
|
||
"stats.byDeck": "By deck",
|
||
"stats.weakest": "Weakest topics",
|
||
"stats.noSubject": "No subject",
|
||
"stats.correctOf": "{correct} / {total} correct",
|
||
"stats.empty": "No answers yet",
|
||
"stats.emptyBody": "Study a deck and your progress shows up here.",
|
||
"stats.start": "Study a deck",
|
||
"stats.legendLess": "Less",
|
||
"stats.legendMore": "More",
|
||
"nav.stats": "Progress",
|
||
"nav.exam": "Exam mode",
|
||
"settings.learning": "Learning",
|
||
"settings.dailyGoal": "Daily goal",
|
||
"settings.dailyGoalNote": "Answers per day that count as done.",
|
||
"settings.reminders": "Daily reminder",
|
||
"settings.remindersNote":
|
||
"A gentle nudge when your review is due, naming the deck that is waiting. Needs the installed app — on iPhone that is required.",
|
||
"push.title": "Time to review",
|
||
"push.due.one": "{n} card is due in {deck}",
|
||
"push.due.few": "{n} cards are due in {deck}",
|
||
"push.due.many": "{n} cards are due in {deck}",
|
||
"push.generic": "Your decks are waiting — a few due cards keep the streak alive.",
|
||
"settings.reminderEnable": "Turn on reminders",
|
||
"settings.reminderDisable": "Turn off",
|
||
"settings.reminderTest": "Send a test",
|
||
"settings.reminderOn": "Reminders are on.",
|
||
"settings.reminderOff": "Reminders are off.",
|
||
"settings.reminderDenied": "Notifications are blocked in your browser settings.",
|
||
"settings.reminderError": "Could not set up reminders.",
|
||
"library.learningToday": "Today: {n} / {goal}",
|
||
|
||
/* homework: check my work + follow-ups ----------------------------- */
|
||
"homework.mode.check": "Check my work",
|
||
"homework.mode.checkDesc": "Mark your own attempt: what's right, what's wrong, and the corrected version.",
|
||
"homework.solution.label": "Your solution",
|
||
"homework.solution.placeholder": "Paste your own attempt here — the assignment is above.",
|
||
"homework.solution.note": "Type or paste your solution, or add photos and documents of your work — handwritten pages are fine.",
|
||
"homework.solution.add": "Add photos or files",
|
||
"homework.solution.tooManyPhotos": "Up to {n} photos per solution.",
|
||
"homework.solution.tooManyDocs": "Up to {n} documents per solution.",
|
||
"homework.solution.required": "Add your solution so there is something to check.",
|
||
"homework.check.title": "Checked",
|
||
"homework.check.verdict": "Verdict",
|
||
"homework.check.correct": "What's right",
|
||
"homework.check.wrong": "What needs fixing",
|
||
"homework.check.fix": "Fix",
|
||
"homework.check.corrected": "Corrected version",
|
||
"homework.history.checked": "Checked",
|
||
"homework.loading.checking": "Checking your attempt…",
|
||
"homework.followup.shorter": "Shorter",
|
||
"homework.followup.more": "More detail",
|
||
"homework.followup.explain": "Explain differently",
|
||
"homework.followup.redo": "Redo",
|
||
"homework.followup.step": "Explain step {n} differently",
|
||
"homework.followup.working": "Rewriting…",
|
||
"homework.followup.failed": "That didn't work. Try again.",
|
||
"homework.followup.restore": "Original",
|
||
"homework.toDeck": "Turn into a study deck",
|
||
"homework.toDeckNote": "Opens New deck with this content and the options pre-filled.",
|
||
"server.needSolution": "Add your own solution so it can be checked.",
|
||
"doc.readingPage": "Reading page {n} of {total}…",
|
||
"doc.readingWord": "Reading the Word document…",
|
||
"doc.readingSlides": "Reading the slides…",
|
||
"doc.readingOpen": "Reading the OpenDocument file…",
|
||
"doc.readingText": "Reading the text file…",
|
||
|
||
/* chat ------------------------------------------------------------ */
|
||
"chat.nav": "Chat",
|
||
"chat.title": "Chat",
|
||
"chat.ask": "Ask",
|
||
"chat.askAboutDeck": "Ask about this deck",
|
||
"chat.askQuestion": "Ask a question",
|
||
"chat.back": "Back",
|
||
"chat.new": "New chat",
|
||
"chat.placeholder": "Ask about your notes…",
|
||
"chat.send": "Send",
|
||
"chat.stop": "Stop",
|
||
"chat.copy": "Copy",
|
||
"chat.copied": "Copied",
|
||
"chat.copyFailed": "Copy failed",
|
||
"chat.edit": "Edit and resend",
|
||
"chat.regenerate": "Regenerate",
|
||
"chat.retry": "Retry",
|
||
"chat.thinking": "Thinking…",
|
||
"chat.thought": "Thinking",
|
||
"chat.sources": "Sources",
|
||
"chat.jumpToBottom": "Jump to the newest",
|
||
"chat.offlineNote": "Offline — sending is paused",
|
||
"chat.emptyTitle": "Ask your notes anything",
|
||
"chat.emptyHint": "Answers come from your own decks. Pick what the chat may read with the context chip below.",
|
||
"chat.oldDecksNote": "Older decks only have the generated study content. Re-add the notes for better answers.",
|
||
"chat.fromLabel": "From",
|
||
"chat.attach": "Attach photos or documents",
|
||
"chat.attachPhoto": "photo",
|
||
"chat.attachDoc": "document",
|
||
"chat.attachRemove": "Remove attachment",
|
||
"chat.attachFailed": "That attachment could not be read.",
|
||
"chat.attachNoText": "No text found in that file — try a photo instead.",
|
||
"chat.attachEmpty": "Nothing readable was found in that attachment.",
|
||
"chat.attachmentLabel": "Attachment",
|
||
"chat.modelTitle": "Chat model",
|
||
"chat.modelText": "Text",
|
||
"chat.modelVision": "Vision",
|
||
"chat.modelBoth": "Text + vision",
|
||
"chat.modelSearch": "Search models",
|
||
"chat.modelSearchEmpty": "No model matches.",
|
||
"chat.modelAvailable": "available",
|
||
"chat.modelUnavailable": "not found",
|
||
"chat.modelNote": "Photos and documents are transcribed first, so any of these works.",
|
||
"chat.scope.title": "What may chat read?",
|
||
"chat.scope.all": "All notes",
|
||
"chat.scope.allNote": "Every deck's notes can ground the answers.",
|
||
"chat.scope.pick": "Selected decks",
|
||
"chat.scope.pickNote": "Choose folders or single decks below.",
|
||
"chat.scope.none": "No notes",
|
||
"chat.scope.noneNote": "General chat — the model answers from general knowledge only.",
|
||
"chat.scope.decks.one": "{n} deck",
|
||
"chat.scope.decks.few": "{n} decks",
|
||
"chat.scope.decks.many": "{n} decks",
|
||
"chat.scope.deckCount.one": "{n} deck",
|
||
"chat.scope.deckCount.few": "{n} decks",
|
||
"chat.scope.deckCount.many": "{n} decks",
|
||
"chat.scope.hasNotes": "notes text kept",
|
||
"chat.scope.generatedOnly": "generated content only",
|
||
"chat.scope.note": "Only what you pick here is sent as context — the API key never leaves this browser.",
|
||
"chat.suggest.explain": "Explain the main idea of {deck}",
|
||
"chat.suggest.quiz": "Quiz me on {deck}",
|
||
"chat.suggest.summarize": "Summarize my notes",
|
||
"chat.suggest.quizAll": "Quiz me on my decks",
|
||
"chat.suggest.studyFirst": "What should I study first?",
|
||
"chat.suggest.general": "Help me understand a topic",
|
||
"chat.suggest.explainTopic": "Explain a topic step by step",
|
||
"chat.seedResults": "Study results",
|
||
"chat.seedAssignment": "Assignment",
|
||
"chat.seedAnswer": "Answer",
|
||
"chat.seedAttempt": "My attempt",
|
||
"chat.seedVerdict": "Verdict",
|
||
"chat.seedToFix": "To fix",
|
||
"chat.seedCorrected": "Corrected version",
|
||
"chat.seedSteps": "Steps",
|
||
"chat.notesDeleted": "Notes text deleted",
|
||
"chat.deleteNotes": "Delete notes text",
|
||
"chat.history": "Chats",
|
||
"chat.searchChats": "Search chats",
|
||
"chat.noResults": "No chats match that search.",
|
||
"chat.noHistory": "No saved chats yet — ask something and it will appear here.",
|
||
"chat.untitled": "Untitled chat",
|
||
"chat.deleteChat": "Delete chat",
|
||
"chat.deletedChat": "Chat deleted",
|
||
"chat.toFlashcards": "Turn into flashcards",
|
||
"chat.saveToNotes": "Save to notes",
|
||
"chat.savedToNotes": "Saved to {deck}'s notes",
|
||
"chat.savedToNotesTrimmed": "Saved to {deck}'s notes (trimmed to fit)",
|
||
"chat.savedFromChat": "Saved from chat",
|
||
"chat.voiceStart": "Dictate",
|
||
"chat.voiceStop": "Stop dictating",
|
||
"teach.nav": "Teach it back",
|
||
"teach.title": "Teach it back",
|
||
"teach.subtitle": "You explain. The AI plays your student.",
|
||
"teach.intro": "Explain the topic in your own words and the student asks short follow-up questions. Where it keeps digging, that is your gap.",
|
||
"teach.topicLabel": "What will you explain?",
|
||
"teach.topicPlaceholder": "e.g. Why the sky is blue",
|
||
"teach.studentLabel": "What may the student know?",
|
||
"teach.difficultyLabel": "Pick a student",
|
||
"teach.friendly": "Friendly student",
|
||
"teach.friendlyNote": "Patient questions, wants it to click.",
|
||
"teach.skeptical": "Skeptical student",
|
||
"teach.skepticalNote": "Asks why, looks for the catch and the edges.",
|
||
"teach.start": "Start teaching",
|
||
"teach.recent": "Recent sessions",
|
||
"teach.noSessions": "Nothing here yet — finished sessions and their recaps appear here.",
|
||
"teach.inProgress": "In progress",
|
||
"teach.recap": "Recap",
|
||
"teach.newSession": "New session",
|
||
"teach.open": "Open",
|
||
"teach.untitled": "Untitled session",
|
||
"teach.deleteSession": "Delete session",
|
||
"teach.deleted": "Session deleted",
|
||
"teach.back": "Back",
|
||
"teach.finish": "Finish",
|
||
"teach.finishHint": "End the session and get your recap.",
|
||
"teach.exchangeCount": "{n}/{max}",
|
||
"teach.youExplain": "You explain",
|
||
"teach.emptyTitle": "You are the teacher now",
|
||
"teach.emptyHint": "Explain the topic the way you would tell a classmate. The student asks one short follow-up at a time — no grading, no lectures.",
|
||
"teach.placeholder": "Explain it in your own words…",
|
||
"teach.voiceStart": "Dictate your explanation",
|
||
"teach.voiceStop": "Stop dictating",
|
||
"teach.listening": "Listening…",
|
||
"teach.limitReached": "Good place to wrap up — here is your recap.",
|
||
"teach.summaryTitle": "Recap",
|
||
"teach.summaryBusy": "Writing your recap…",
|
||
"teach.summaryFailed": "The recap could not be written. Try again.",
|
||
"teach.retry": "Write it again",
|
||
"teach.well": "Explained well",
|
||
"teach.wellEmpty": "Nothing stood out yet.",
|
||
"teach.gaps": "Missed or unclear",
|
||
"teach.gapsEmpty": "Nothing was missed.",
|
||
"teach.next": "Next steps",
|
||
"teach.cardLinkTitle": "Open in {deck}",
|
||
"teach.addToReview": "Add missed cards to review",
|
||
"teach.reviewAdded.one": "{n} card added to review",
|
||
"teach.reviewAdded.few": "{n} cards added to review",
|
||
"teach.reviewAdded.many": "{n} cards added to review",
|
||
"teach.gapsToCards": "Turn gaps into cards",
|
||
"teach.gapDeck": "Add the cards to",
|
||
"teach.generate": "Create the cards",
|
||
"teach.generating": "Creating the cards…",
|
||
"teach.reviewTitle": "Review the cards",
|
||
"teach.reviewHint": "Edit anything before saving; uncheck what you do not want.",
|
||
"teach.cardFront": "Front",
|
||
"teach.cardBack": "Back",
|
||
"teach.saveCards.one": "Save {n} card",
|
||
"teach.saveCards.few": "Save {n} cards",
|
||
"teach.saveCards.many": "Save {n} cards",
|
||
"teach.cardsSaved.one": "{n} card added to {deck}",
|
||
"teach.cardsSaved.few": "{n} cards added to {deck}",
|
||
"teach.cardsSaved.many": "{n} cards added to {deck}",
|
||
"teach.generateFailed": "The cards could not be created.",
|
||
"teach.noGapDeck": "Pick a deck with notes for the cards.",
|
||
"teach.noDecksHint": "No decks are selected — the student has nothing to check against.",
|
||
"legal.updated": "Last updated {when}",
|
||
"legal.privacy": "Privacy policy",
|
||
"legal.terms": "Terms of service",
|
||
"legal.cookies": "Cookie policy",
|
||
"settings.theme": "Theme",
|
||
"theme.aero": "Frutiger Aero",
|
||
"theme.aeroNote": "Aero has no light/dark switch — pick Light or Dark to get it back.",
|
||
"aero.music": "Frutiger Aero music",
|
||
"aero.musicPlay": "Play the music",
|
||
"aero.musicPause": "Pause the music",
|
||
"aero.musicClose": "Turn the music off",
|
||
"aero.musicCollapse": "Back to the music button",
|
||
"aero.musicLoading": "Loading the soundtrack…",
|
||
"settings.legal": "Legal",
|
||
"consent.title": "About cookies",
|
||
"consent.body": "Freezmo uses no cookies and no trackers — your decks, homework and settings stay in this browser.",
|
||
"consent.link": "Cookie policy",
|
||
"consent.ok": "Got it",
|
||
"settings.version": "Version",
|
||
"settings.chatNotes": "Keep notes text for chat",
|
||
"providers.title": "Cloud providers",
|
||
"providers.saveKey": "Save key",
|
||
"providers.note": "Add your own API keys to use more models. Keys stay in this browser — the server forwards them with your requests and never stores them.",
|
||
"providers.key": "API key",
|
||
"providers.keySaved": "Key saved — paste a new one to replace",
|
||
"providers.getKey": "Get a key",
|
||
"providers.test": "Test and list models",
|
||
"providers.remove": "Remove the key",
|
||
"providers.testFailed": "Could not reach the provider. Check the key (and the base URL for custom endpoints).",
|
||
"providers.modelCount": "{n} models",
|
||
"providers.generation": "Study generation",
|
||
"providers.generationNote": "Generate decks, homework and grading on a provider model instead of the server's Ollama.",
|
||
"providers.serverDefault": "Server default (Ollama)",
|
||
"providers.textModel": "Text model",
|
||
"providers.visionModel": "Vision model",
|
||
"providers.baseUrl": "OpenAI-compatible base URL",
|
||
"providers.baseUrlPlaceholder": "https://host/v1",
|
||
"settings.reduceMotion": "Reduce motion",
|
||
"settings.reduceMotionNote": "Calms the scene and UI animations. Useful when motion feels distracting.",
|
||
"settings.chatNotesNote": "Chat answers from the notes you added (stored with each deck). Turn off to use only the generated study content.",
|
||
"server.chatModelNotAllowed": "That chat model is not on the server's allowlist.",
|
||
"server.chatNeedsQuestion": "The conversation must end with a user message.",
|
||
};
|
||
|
||
const cs: Record<string, string> = {
|
||
/* common ---------------------------------------------------------- */
|
||
"common.cancel": "Zrušit",
|
||
"common.save": "Uložit",
|
||
"common.add": "Přidat",
|
||
"common.delete": "Smazat",
|
||
"common.rename": "Přejmenovat",
|
||
"common.close": "Zavřít",
|
||
"common.retry": "Zkusit znovu",
|
||
"common.settings": "Nastavení",
|
||
"common.library": "Knihovna",
|
||
"common.newDeck": "Nová sada",
|
||
"common.folder": "Složka",
|
||
"common.folders": "Složky",
|
||
"common.newFolder": "Nová složka",
|
||
"common.foldersHint": "Vytvořte složku pro uspořádání sad.",
|
||
"common.folderName": "Název složky",
|
||
"common.newFolderName": "Název nové složky",
|
||
"common.unorganized": "Nezařazené",
|
||
"common.allDecks": "Všechny sady",
|
||
"common.lightMode": "Světlý režim",
|
||
"common.darkMode": "Tmavý režim",
|
||
"common.switchToLight": "Přepnout na světlý režim",
|
||
"common.switchToDark": "Přepnout na tmavý režim",
|
||
"common.back": "Zpět",
|
||
"common.next": "Další",
|
||
"common.more": "Více",
|
||
"common.less": "Méně",
|
||
"common.edit": "Upravit",
|
||
"common.stepOf": "{n} / {total}",
|
||
"common.aiDisclaimer": "Freezmo používá AI a může se mýlit. Důležité informace si radši ověř.",
|
||
|
||
/* header ---------------------------------------------------------- */
|
||
"header.home": "Domů — Freezmo",
|
||
"header.tagline": "studijní feed",
|
||
"header.openSettings": "Otevřít nastavení a stav Ollamy",
|
||
|
||
/* tab bar --------------------------------------------------------- */
|
||
"tabbar.main": "Hlavní",
|
||
/* Krátké popisky záložek: musí zůstat na jednom řádku při 360px v obou jazycích. */
|
||
"tab.library": "Knihovna",
|
||
"tab.progress": "Pokrok",
|
||
"tab.new": "Nová sada",
|
||
"tab.exam": "Zkouška",
|
||
"tab.settings": "Nastavení",
|
||
|
||
/* sidebar --------------------------------------------------------- */
|
||
"sidebar.collapse": "Schovat panel",
|
||
"sidebar.expand": "Prodloužit panel",
|
||
"sidebar.library": "Knihovna",
|
||
"sidebar.label": "Postranní panel",
|
||
|
||
/* language -------------------------------------------------------- */
|
||
"language.label": "Jazyk",
|
||
"language.note": "Jazyk rozhraní. Tvoje poznámky a vytvořené sady si zachovávají vlastní jazyk.",
|
||
|
||
/* settings -------------------------------------------------------- */
|
||
"settings.title": "Nastavení",
|
||
"settings.subtitle.before": "Modely pocházejí z",
|
||
"settings.subtitle.after": "na serveru; tvůj API klíč zůstává v tomto prohlížeči.",
|
||
"settings.close": "Zavřít nastavení",
|
||
"settings.apiKey": "API klíč Ollama",
|
||
"settings.savedInBrowser": "uložen v tomto prohlížeči",
|
||
"settings.required": "vyžadován",
|
||
"settings.notNeeded": "není potřeba",
|
||
"settings.pasteKey": "vlož klíč z ollama.com/settings/keys",
|
||
"settings.hide": "Skrýt",
|
||
"settings.show": "Zobrazit",
|
||
"settings.keyNote":
|
||
"Ukládá se jen do místního úložiště tohoto prohlížeče a posílá se s každým požadavkem, aby server mohl komunikovat s Ollamou. Nikdy se neukládá na server, nezapisuje do logů ani do cookies.",
|
||
"settings.localTipTitle": "Používáš místní daemon?",
|
||
"settings.localTipSet": "Nastav",
|
||
"settings.keyNoteAfter": "v",
|
||
"settings.keyNoteEnd": "a použij",
|
||
"settings.keyNoteEnd2": ".",
|
||
"settings.removeKeyConfirm":
|
||
"Odstranit klíč? Dokud klíč znovu nepřidáš, vrátí se celoobrazovková obrazovka pro nastavení klíče.",
|
||
"settings.removeKeyYes": "Ano, odstranit",
|
||
"settings.testConnection": "Otestovat připojení",
|
||
"settings.removeKey": "Odstranit klíč z prohlížeče",
|
||
"settings.savedTesting": "Uloženo — testuji…",
|
||
"settings.installApp": "Nainstalovat aplikaci",
|
||
"settings.addToHome": "Přidat Freezmo na domovskou obrazovku",
|
||
"settings.checking": "Kontroluji Ollamu…",
|
||
"settings.connection": "Připojení",
|
||
"settings.connected": "Připojeno",
|
||
"settings.modelsReady.one": "{n} model připraven",
|
||
"settings.modelsReady.few": "{n} modely připraveny",
|
||
"settings.modelsReady.many": "{n} modelů připraveno",
|
||
"settings.details": "Podrobnosti",
|
||
"settings.privacyLine": "Tvůj klíč zůstává v tomto prohlížeči.",
|
||
"settings.recheck": "Zkontrolovat připojení",
|
||
"settings.haptics": "Haptika",
|
||
"settings.hapticsNote": "Vibrace na podporovaných zařízení.",
|
||
|
||
/* data, backup & sync --------------------------------------------- */
|
||
"settings.data": "Data a záloha",
|
||
"settings.dataNote": "Všechno je uloženo v tomto prohlížeči. Zálohuj pravidelně.",
|
||
"settings.exportAll": "Exportovat všechna data",
|
||
"settings.exportNote": "Sady, úkoly, pokrok a nastavení — nikdy tvůj API klíč.",
|
||
"settings.importBackup": "Importovat zálohu",
|
||
"settings.importPreview": "Záloha obsahuje {decks} sad, {homework} výsledků úkolů, {podcasts} podcastů, {folders} složek a {chats} chatů.",
|
||
"settings.importMergeNote": "Sloučení ponechá stávající data a přidá zálohu; stejná ID se nahradí.",
|
||
"settings.importReplaceNote": "Nahrazení smaže data v tomto prohlížeči a použije zálohu.",
|
||
"settings.importMerge": "Sloučit",
|
||
"settings.importReplace": "Nahradit",
|
||
"settings.importCancel": "Zrušit",
|
||
"settings.importDone": "Importováno {decks} sad a {homework} výsledků úkolů.",
|
||
"settings.importFailed": "Zálohu se nepodařilo importovat.",
|
||
"settings.storage": "Úložiště",
|
||
"settings.storagePersistent": "Trvalé — prohlížeč tvá data nesmaže.",
|
||
"settings.storageBestEffort": "Dočasné — prohlížeč může data při nedostatku místa smazat.",
|
||
"settings.storageRequest": "Požádat o trvalé úložiště",
|
||
"settings.storageGranted": "Povoleno — data jsou teď trvalá.",
|
||
"settings.storageDenied": "Prohlížeč odmítl. Pomůže nainstalovat aplikaci.",
|
||
"settings.backupNudgeTitle": "Zálohovat sady?",
|
||
"settings.backupNudgeBody": "Od poslední zálohy jsi přidal sady. Záloha je udrží v bezpečí.",
|
||
"settings.backupNow": "Zálohovat teď",
|
||
"settings.backupLater": "Teď ne",
|
||
"settings.sync": "Synchronizace",
|
||
"settings.syncNote":
|
||
"Ve výchozím stavu vypnuto. Data se šifrují v tomto prohlížeči před odesláním — server ukládá jen neprůhledný blob a nikdy nevidí tvá data ani klíč.",
|
||
"settings.syncEnable": "Zapnout synchronizaci",
|
||
"settings.syncPassphrase": "Přístupová fráze",
|
||
"settings.syncPassphraseConfirm": "Zopakuj přístupovou frázi",
|
||
"settings.syncPassphraseMismatch": "Fráze se neshodují.",
|
||
"settings.syncPassphraseShort": "Použij aspoň 8 znaků.",
|
||
"settings.syncStart": "Začít synchronizovat",
|
||
"settings.syncCancel": "Zrušit",
|
||
"settings.syncId": "Sync ID",
|
||
"settings.syncCopy": "Kopírovat",
|
||
"settings.syncCopied": "Zkopírováno.",
|
||
"settings.syncLast": "Naposledy synchronizováno {when}",
|
||
"settings.syncNever": "Zatím nesynchronizováno.",
|
||
"settings.syncNow": "Synchronizovat teď",
|
||
"settings.syncWorking": "Synchronizuji…",
|
||
"settings.syncDone": "Vše je aktuální.",
|
||
"settings.syncPushed": "Nahráno z tohoto zařízení.",
|
||
"settings.syncPulled": "Staženo ze synchronizované kopie.",
|
||
"settings.syncConflict": "Toto zařízení i synchronizovaná kopie se změnily. Vyber, kterou ponechat.",
|
||
"settings.syncKeepLocal": "Ponechat toto zařízení",
|
||
"settings.syncKeepRemote": "Použít synchronizovanou kopii",
|
||
"settings.syncJoin": "Připojit k jiné synchronizaci",
|
||
"settings.syncJoinNote":
|
||
"Zadej sync ID a přístupovou frázi z druhého zařízení. Data v tomto prohlížeči nahradí synchronizovaná kopie.",
|
||
"settings.syncJoinId": "Sync ID",
|
||
"settings.syncJoinAction": "Připojit",
|
||
"settings.syncLocked": "Zadej přístupovou frázi a odemkni synchronizaci na tomto zařízení.",
|
||
"settings.syncUnlock": "Odemknout",
|
||
"settings.syncForget": "Zapomenout frázi na tomto zařízení",
|
||
"settings.syncDelete": "Smazat synchronizovaná data",
|
||
"settings.syncDeleteConfirm": "Smazat synchronizovanou kopii ze serveru? Data v tomto zařízení zůstanou.",
|
||
"settings.syncDeleteYes": "Smazat",
|
||
"settings.syncWrongPass": "Touhle frází se synchronizovaná kopie odemknout nedá.",
|
||
"settings.syncError": "Synchronizace selhala. Zkontroluj připojení a zkus to znovu.",
|
||
"settings.syncOff": "Synchronizace je vypnutá.",
|
||
"settings.syncOn": "Synchronizace je zapnutá.",
|
||
"settings.syncNoteKey": "Tvůj API klíč se nikdy nesynchronizuje.",
|
||
|
||
/* offline / share / toasts ---------------------------------------- */
|
||
"offline.banner": "Jsi offline — uložené sady fungují dál.",
|
||
"offline.generate": "Jsi offline — pro vytvoření se připoj.",
|
||
"share.title": "Přidat do Freezma",
|
||
"share.intro": "Co s tím mám udělat?",
|
||
"share.preview": "Sdílený obsah",
|
||
"share.loading": "Načítám sdílený obsah…",
|
||
"share.expired": "Sdílení vypršelo — zkus to znovu.",
|
||
"share.tooLarge": "To je příliš velké (max 30 MB).",
|
||
"share.nothing": "Není co přidat.",
|
||
"share.copied": "Zkopírováno do schránky.",
|
||
"share.failed": "Sdílení tu není dostupné.",
|
||
"toast.deckDeleted": "Sada smazána",
|
||
"toast.fileRemoved": "Soubor odebrán",
|
||
"toast.undo": "Vrátit",
|
||
"upload.pasteClipboard": "Vložit ze schránky",
|
||
"upload.clipboardDenied": "Přístup ke schránce byl zamítnut.",
|
||
|
||
/* health ---------------------------------------------------------- */
|
||
"health.ok": "Ollama je dostupná a oba modely jsou k dispozici.",
|
||
"health.needsKey": "Čeká se na tvůj API klíč.",
|
||
"health.rejected": "Ollama odmítla tvůj API klíč.",
|
||
"health.modelMissing": "Ollama je dostupná, ale nakonfigurovaný model chybí.",
|
||
"health.unreachable": "Ollama není dostupná.",
|
||
"health.auth": "Autorizace",
|
||
"health.authUserKey": "Vlastní klíč",
|
||
"health.authServer": "Klíč serveru (.env)",
|
||
"health.authLocal": "Přihlášení místního daemonu",
|
||
"health.endpoint": "Endpoint",
|
||
"health.mode": "Režim",
|
||
"health.modeCloud": "Přímé cloudové API",
|
||
"health.modeLocal": "Místní daemon",
|
||
"health.key": "Klíč",
|
||
"health.keyMissing": "chybí",
|
||
"health.keyAccepted": "přijat",
|
||
"health.keyRejected": "odmítnut",
|
||
"health.keyNotNeeded": "není potřeba",
|
||
"health.keyUnknown": "neznámý",
|
||
"health.visionModel": "Vizuální model (překládá fotky na text)",
|
||
"health.textModel": "Textový model (tvoří a známkuje)",
|
||
"health.available": "dostupný",
|
||
"health.notFound": "nenalezen",
|
||
"health.unknown": "neznámý",
|
||
"health.configuredAs": "nakonfigurováno jako {model}",
|
||
"health.checkedNow": "Zkontrolováno právě teď",
|
||
"health.checkedAt": "Zkontrolováno v {time}",
|
||
"health.createKey.before": "Vytvoř klíč na",
|
||
"health.createKey.after": "a vlož ho výše. Tip: s místním daemonem můžeš spustit",
|
||
"health.createKey.end": "a nastavit",
|
||
|
||
/* make my homework ------------------------------------------------- */
|
||
"homework.nav": "Nový úkol",
|
||
"wizard.mode.deck": "Studijní sada",
|
||
"wizard.mode.homework": "Udělej mi úkol",
|
||
"wizard.mode.podcast": "Podcast",
|
||
"wizard.mode.aria": "Studijní sada, úkol nebo podcast",
|
||
|
||
/* Podcast mode (notes → a spoken episode with a highlighted transcript) */
|
||
"podcast.title": "Nový podcast",
|
||
"podcast.subtitle": "Freezmo ti tvoje poznámky přečte jako krátkou epizodu — a každé slovo při čtení zvýrazní, jako texty písní.",
|
||
"podcast.addNotes": "Přidej poznámky",
|
||
"podcast.addPhotos": "Fotky",
|
||
"podcast.addDocs": "Dokumenty",
|
||
"podcast.addFirst": "Nejdřív přidej poznámky",
|
||
"podcast.generate": "Vytvořit epizodu",
|
||
"podcast.lengthAria": "Délka epizody",
|
||
"podcast.lengthHelp": "Jen orientační cíl — scénář zůstane tak dlouhý, jak poznámky potřebují.",
|
||
"podcast.focusLabel": "Na co se má zaměřit? (nepovinné)",
|
||
"podcast.minutes.one": "{n} minuta",
|
||
"podcast.minutes.few": "{n} minuty",
|
||
"podcast.minutes.many": "{n} minut",
|
||
"podcast.sectionCount.one": "{n} část",
|
||
"podcast.sectionCount.few": "{n} části",
|
||
"podcast.sectionCount.many": "{n} částí",
|
||
"podcast.wordCount.one": "{n} slovo",
|
||
"podcast.wordCount.few": "{n} slova",
|
||
"podcast.wordCount.many": "{n} slov",
|
||
"podcast.sectionOf": "Část {n} z {total}",
|
||
"podcast.play": "Přehrát epizodu",
|
||
"podcast.pause": "Pozastavit epizodu",
|
||
"podcast.restart": "Začít od začátku",
|
||
"podcast.speedAria": "Rychlost přehrávání",
|
||
"podcast.voiceAria": "Hlas",
|
||
"podcast.voiceAuto": "Automaticky (nejlepší dostupný)",
|
||
"podcast.voiceLocal": "nainstalovaný",
|
||
"podcast.voiceNote": "Hlasy dodává prohlížeč a systém — vyber si ten, co zní nejlíp.",
|
||
"podcast.voiceIos": "Na iPhonu nabízí Safari webovým stránkám jen jeden hlas na jazyk — ten preferovaný v systému. Pro další (a lepší) hlasy: Nastavení → Zpřístupnění → Mluvený obsah → Hlasy → tvůj jazyk, stáhni si hlas ve verzi Vylepšený nebo Prémiový a nastav ho jako preferovaný. Freezmo pak čte s ním.",
|
||
"podcast.voiceSystem": "Systémové hlasy",
|
||
"podcast.voiceNatural": "Přirozený hlas",
|
||
"podcast.voiceMale": "mužský",
|
||
"podcast.voiceFemale": "ženský",
|
||
"podcast.voiceNaturalNote": "Generuje ho klíč tvého providera — slova jsou časovaná podle audia, takže zvýraznění drží s hlasem.",
|
||
"podcast.voiceNeedsKey": "Pro přirozené hlasy si v Nastavení → Provideři přidej klíč OpenAI nebo Groq.",
|
||
"podcast.voiceNoSystem": "Tohle zařízení nehlásí žádné systémové hlasy. Přirozený hlas funguje všude — nebo si hlas přidej v nastavení systému.",
|
||
"podcast.voiceSilent": "Systémový hlas se nespustil — prohlížeč za ním nemá žádný hlasový engine. Na Firefoxu/Linuxu doinstaluj speech-dispatcher (sudo apt install speech-dispatcher) a stránku otevři znovu.",
|
||
"podcast.voiceUseNatural": "Použít přirozený hlas",
|
||
"podcast.voiceOpenProviders": "Přidat klíč providera",
|
||
"podcast.voiceGenerating": "Generuji hlas…",
|
||
"podcast.voiceRobotic": "Jsou nainstalované jen základní systémové hlasy. Pro přirozený hlas moderátora si nějaký přidej v nastavení systému (Windows: Řeč; macOS: Mluvený obsah; Chrome má hlasy Google).",
|
||
"podcast.tapToJump": "Klikni na kterékoli slovo a přeskočíš tam.",
|
||
"podcast.noVoice": "Tento prohlížeč nemá nainstalovaný hlas, takže se přepis nedá přečíst nahlas. Číst ho ale můžeš dál.",
|
||
"podcast.loadingEpisode": "Načítání epizody",
|
||
"podcast.notFound": "Epizoda nenalezena",
|
||
"podcast.notFoundBody": "Nejspíš byla smazaná, nebo je uložená v jiném prohlížeči.",
|
||
"podcast.loading.read1": "Čtu tvoje poznámky…",
|
||
"podcast.loading.read2": "Dávám stránky dohromady…",
|
||
"podcast.loading.write1": "Píšu scénář…",
|
||
"podcast.loading.write2": "Hledám hlavní linii…",
|
||
"podcast.loading.write3": "Ladím mluvené pasáže…",
|
||
"podcast.tip1": "Přepis zvýrazňuje každé čtené slovo — kliknutím na slovo se tam přesuneš.",
|
||
"podcast.tip2": "Čtyřminutová procházka je pro jedno téma tak akorát.",
|
||
|
||
/* Library: the podcasts tab */
|
||
"tabPodcasts": "Podcasty",
|
||
"podcast.library.empty": "Zatím žádné epizody",
|
||
"podcast.library.emptyBody": "Proměň stránku poznámek v mluvenou epizodu a počká tady na tebe.",
|
||
"podcast.library.new": "Nový podcast",
|
||
"podcast.card.sections": "{n} částí",
|
||
"podcast.card.minutes": "~{n} min",
|
||
"podcast.card.actions": "Akce epizody",
|
||
"podcast.card.open": "Poslouchat",
|
||
"podcast.card.rename": "Přejmenovat",
|
||
"podcast.card.delete": "Smazat epizodu",
|
||
"podcast.card.deleted": "Epizoda smazána",
|
||
"podcast.card.renamed": "Epizoda přejmenována",
|
||
"podcast.regenerate": "Napsat znovu",
|
||
"homeworkHeroTitle": "Udělej mi úkol",
|
||
"homework.subtitle": "Vlož ho, vyfoť nebo přidej soubor — a vyber, jak ti mám pomoct.",
|
||
"homework.phone.assignment": "Zadání",
|
||
"homework.phone.mode": "Jak ti mám pomoct?",
|
||
"homework.phone.modeDesc": "Vyber, jak ti s tímto zadáním pomůžu.",
|
||
"homework.phone.preview": "Náhled zadání",
|
||
"homework.phone.startHint": "Klepni na kartu a začnu.",
|
||
"homework.source.title": "1. Zadání",
|
||
"homework.source.help": "Vlož jeho text, přetáhni fotky nebo přidej dokument.",
|
||
"homework.source.textLabel": "Text zadání",
|
||
"homework.source.placeholder": "Sem vlož zadání…",
|
||
"homework.source.add": "Přidat fotky nebo soubory",
|
||
"homework.source.drop": "Přetáhni zadání sem",
|
||
"homework.source.formats": "Text, fotky, PDF, DOCX, PPTX, ODT/ODP, TXT, MD",
|
||
"homework.source.ready": "Můžeme začít.",
|
||
"homework.source.need": "Nejdřív přidej zadání.",
|
||
"homework.source.tooManyPhotos": "Až {n} fotek na zadání.",
|
||
"homework.source.tooManyDocs": "Až {n} dokumentů na zadání.",
|
||
"homework.mode.title": "2. Co mám udělat?",
|
||
"homework.mode.make.title": "Jen to udělej",
|
||
"homework.mode.make.subtitle": "Hotový úkol, připravený ke zkopírování.",
|
||
"homework.mode.guide.title": "Vysvětli, jak to udělat",
|
||
"homework.mode.guide.subtitle": "Návod krok za krokem s nápovědami a rychlými kontrolami.",
|
||
"homework.start.make": "Udělej mi úkol",
|
||
"homework.start.guide": "Proveď mě tím",
|
||
"homework.loading.reading": "Čtu zadání…",
|
||
"homework.loading.readingSolution": "Čtu tvoje řešení…",
|
||
"homework.loading.vision2": "Přepisuji otázky…",
|
||
"homework.loading.writing": "Píšu tvůj úkol…",
|
||
"homework.loading.planning": "Plánuji postup…",
|
||
"homework.loading.gen2": "Kontroluji každou část…",
|
||
"homework.loading.gen3": "Už to bude…",
|
||
"homework.error.title": "To se nepovedlo",
|
||
"homework.error.body": "Při zpracování zadání se něco pokazilo.",
|
||
"homework.result.title": "Tvůj úkol",
|
||
"homework.result.copy": "Kopírovat",
|
||
"homework.result.copied": "Zkopírováno!",
|
||
"homework.result.copyFailed": "Kopírování se nepovedlo — vyber text a zkopíruj ho ručně.",
|
||
"homework.result.again": "Udělej to znovu",
|
||
"homework.result.new": "Nové zadání",
|
||
"homework.guide.stepOf": "Krok {n}/{total}",
|
||
"homework.guide.close": "Zavřít návod",
|
||
"homework.guide.tryIt": "Zkus si to",
|
||
"homework.report": "Nahlásit cvičení",
|
||
"homework.guide.hint": "Ukaž nápovědu",
|
||
"homework.guide.hintMore": "Další nápověda",
|
||
"homework.guide.hintAnswer": "Ukaž odpověď",
|
||
"homework.guide.hintN": "Nápověda {n}",
|
||
"homework.guide.next": "Další krok",
|
||
"homework.guide.showSolution": "Ukaž řešení",
|
||
"homework.guide.solution": "Celé řešení",
|
||
"homework.guide.solutionNote":
|
||
"Složeno ze všech kroků — zkopíruj a odevzdej.",
|
||
"homework.history.title": "Nedávné úkoly",
|
||
"homework.history.made": "Hotové",
|
||
"homework.history.guide": "Návod",
|
||
"homework.history.openTitle": "Otevřít „{title}“",
|
||
"homework.history.deleteConfirm": "Smazat tento výsledek?",
|
||
"homework.emptyTitle": "Zatím žádné úkoly",
|
||
"homework.emptyBody": "Vyřešená zadání a návody krok za krokem se objeví tady.",
|
||
"homework.open": "Otevřít",
|
||
"homework.steps.one": "{n} krok",
|
||
"homework.steps.few": "{n} kroky",
|
||
"homework.steps.many": "{n} kroků",
|
||
|
||
/* library tabs ----------------------------------------------------- */
|
||
"tabDecks": "Studijní balíčky",
|
||
"tabHomework": "Úkoly",
|
||
|
||
/* quick actions (library empty state + /create switcher) ----------- */
|
||
"emptyStateCreateDeck": "Vytvořit studijní balíček",
|
||
"emptyStateHomework": "Nový úkol",
|
||
|
||
/* library --------------------------------------------------------- */
|
||
"library.title": "Tvůj mrazák",
|
||
"library.subtitle": "Sady zmrazené z tvých poznámek, uložené v tomto prohlížeči.",
|
||
"library.import": "Importovat JSON",
|
||
"library.moreActions": "Další akce",
|
||
"library.moreTitle": "Akce knihovny",
|
||
"library.folderCreated": "Složka vytvořena — sady, které zmrazíš, když je vybraná, přistanou tady.",
|
||
"library.imported": "Importováno „{title}“.",
|
||
"library.couldNotRead": "Tento soubor se nepodařilo přečíst.",
|
||
"library.folderDeleted": "Složka smazána — její sady se přesunuly do Nezařazených.",
|
||
"library.deckMoved": "Sada přesunuta.",
|
||
"library.deckMovedUnorganized": "Sada přesunuta do Nezařazených.",
|
||
"library.noDecks": "Zatím žádné sady",
|
||
"library.emptyBody": "Zmraz stránku poznámek do kartiček a kvízů.",
|
||
"library.renameFolderAria": "Název složky",
|
||
"library.nothingUnfiled": "Teď tu nic nezařazeného není.",
|
||
"library.emptyFolder": "Prázdná složka — zmraz do ní sadu nebo ji sem přesuň z Nezařazených.",
|
||
"library.stats.best": "nejlepší {n} %",
|
||
"library.deleteWarning.one": "Sada uvnitř ({n}) se přesune do Nezařazených — nic se nesmaže.",
|
||
"library.deleteWarning.few": "Sady uvnitř ({n}) se přesunou do Nezařazených — nic se nesmaže.",
|
||
"library.deleteWarning.many": "Sad uvnitř ({n}) se přesune do Nezařazených — nic se nesmaže.",
|
||
"stats.decks.one": "{n} sada",
|
||
"stats.decks.few": "{n} sady",
|
||
"stats.decks.many": "{n} sad",
|
||
"stats.folders.one": "{n} složka",
|
||
"stats.folders.few": "{n} složky",
|
||
"stats.folders.many": "{n} složek",
|
||
"stats.activities.one": "{n} aktivita",
|
||
"stats.activities.few": "{n} aktivity",
|
||
"stats.activities.many": "{n} aktivit",
|
||
|
||
/* deck card ------------------------------------------------------- */
|
||
"deck.activities.one": "{n} aktivita",
|
||
"deck.activities.few": "{n} aktivity",
|
||
"deck.activities.many": "{n} aktivit",
|
||
"deck.runs.one": "{n} běh",
|
||
"deck.runs.few": "{n} běhy",
|
||
"deck.runs.many": "{n} běhů",
|
||
"deck.notPlayed": "Zatím nehráno",
|
||
"deck.new": "Nové",
|
||
"deck.best": "Nejlepší {n} %",
|
||
"deck.bestLabel": "Nejlepší",
|
||
"deck.study": "Studovat",
|
||
"deck.actions": "Akce sady",
|
||
"deck.titleLabel": "Název sady",
|
||
"deck.rename": "Přejmenovat",
|
||
"deck.export": "Exportovat JSON",
|
||
"deck.share": "Sdílet",
|
||
"deck.regenerate": "Znovu vytvořit sadu",
|
||
"deck.reallyDelete": "Opravdu smazat",
|
||
"deck.delete": "Smazat sadu",
|
||
"deck.newFolder": "+ Nová složka",
|
||
"deck.addMore": "Přidat další aktivity",
|
||
"deck.generate": "Vygenerovat",
|
||
"deck.noSource": "Tato sada nemá uložený zdrojový text (importované sady ho nenesou).",
|
||
"deck.noSourceExtend": "Tato sada nemá uložené zdrojové poznámky, takže ji nelze rozšířit.",
|
||
"deck.noSourceRegenerate": "Tato sada nemá uložené zdrojové poznámky, takže ji nelze znovu vytvořit.",
|
||
"deck.couldNotAdd": "Další aktivity se nepodařilo přidat.",
|
||
"deck.couldNotRegenerate": "Sadu se nepodařilo znovu vytvořit.",
|
||
"deck.regenerated": "Sada znovu vytvořena",
|
||
"deck.added.one": "Přidána {n} aktivita",
|
||
"deck.added.few": "Přidány {n} aktivity",
|
||
"deck.added.many": "Přidáno {n} aktivit",
|
||
"deck.addingActivities": "Přidávám aktivity",
|
||
"deck.regeneratingDeck": "Znovu vytvářím sadu",
|
||
"deck.addingNew": "Přidávám nové aktivity…",
|
||
"deck.regeneratingWhole": "Znovu vytvářím celou sadu…",
|
||
|
||
/* upload ---------------------------------------------------------- */
|
||
"upload.title": "Proměň poznámky ve studijní feed",
|
||
"upload.introShort": "Přidej poznámky — sadu postavím já.",
|
||
"upload.intro":
|
||
"Vyfoť nebo přetáhni fotky svých poznámek. Freezmo je přečte tvým vlastním vizuálním modelem Ollama a postaví prolistovatelnou sadu mini her.",
|
||
"upload.addPhotosFiles": "Přidat fotky nebo soubory",
|
||
"upload.pasteInstead": "Místo toho vložit text",
|
||
"upload.focusExtra": "Přidat zaměření nebo poznámky navíc (nepovinné)",
|
||
"upload.phone.notes": "Tvoje poznámky",
|
||
"upload.phone.notesDesc": "Fotky, PDF, Word nebo text — až {images} fotek a {documents} souborů.",
|
||
"upload.phone.options": "Možnosti",
|
||
"upload.phone.optionsDesc": "Vše má rozumné výchozí hodnoty — změň, co chceš.",
|
||
"upload.phone.summary": "Připraveno ke zmrazení",
|
||
"upload.phone.summaryDesc": "Zkontroluj, co se použije, a zmraz.",
|
||
"upload.phone.difficultyHelp": "Jak hluboko otázky jdou.",
|
||
"upload.phone.activitiesHelp": "Kolik aktivit se vytvoří.",
|
||
"upload.phone.folderHelp": "Kam se sada v knihovně zařadí.",
|
||
"upload.nothingAdded": "Zatím nic přidáno",
|
||
"upload.summary.files": "Soubory",
|
||
"upload.summary.folder": "Složka",
|
||
"upload.summaryText": "Text",
|
||
"upload.photoCount.one": "{n} fotka",
|
||
"upload.photoCount.few": "{n} fotky",
|
||
"upload.photoCount.many": "{n} fotek",
|
||
"upload.errNeedsKey": "Je vyžadován API klíč",
|
||
"upload.errRate": "Zpomal na chvíli",
|
||
"upload.errAuth": "Ollama odmítla tvůj klíč",
|
||
"upload.errFailed": "Vytváření selhalo",
|
||
"upload.openSettings": "Otevřít nastavení",
|
||
"upload.addPhotosAria": "Přidat fotky poznámek",
|
||
"upload.step1": "1. Přidej poznámky",
|
||
"upload.step1Help":
|
||
"Přetáhni soubory, vlož screenshot (Ctrl/⌘+V) nebo použij foťák. Fotky, PDF, Word, PowerPoint, OpenDocument a textové soubory. Až {images} fotek a {documents} dokumentů.",
|
||
"upload.chooseFiles": "Vybrat soubory",
|
||
"upload.camera": "Foťák",
|
||
"upload.dropHere": "Přetáhni poznámky sem",
|
||
"upload.formats": "Fotky, PDF, DOCX, PPTX, ODT/ODP, TXT, MD · obrázky se před odesláním zmenší na 1600 px",
|
||
"upload.remove": "Odebrat {name}",
|
||
"upload.addMorePhotos": "Přidat další fotky",
|
||
"upload.reading": "Čtu…",
|
||
"upload.noReadable": "Žádný čitelný text — není to sken bez textové vrstvy?",
|
||
"upload.words.one": "{n} slovo",
|
||
"upload.words.few": "{n} slova",
|
||
"upload.words.many": "{n} slov",
|
||
"upload.pages.one": "{n} strana",
|
||
"upload.pages.few": "{n} strany",
|
||
"upload.pages.many": "{n} stran",
|
||
"upload.pageImages.one": "{n} obrázek stránky",
|
||
"upload.pageImages.few": "{n} obrázky stránek",
|
||
"upload.pageImages.many": "{n} obrázků stránek",
|
||
"upload.truncated": "zkráceno",
|
||
"upload.photoSummary.one": "{n} fotka · {size} po komprimaci",
|
||
"upload.photoSummary.few": "{n} fotky · {size} po komprimaci",
|
||
"upload.photoSummary.many": "{n} fotek · {size} po komprimaci",
|
||
"upload.docSummary.one": "{n} dokument",
|
||
"upload.docSummary.few": "{n} dokumenty",
|
||
"upload.docSummary.many": "{n} dokumentů",
|
||
"upload.docImages.one": "{n} obrázek stránky",
|
||
"upload.docImages.few": "{n} obrázky stránek",
|
||
"upload.docImages.many": "{n} obrázků stránek",
|
||
"upload.skipImages.one": "{n} obrázek bude vynechán (max {max} na sadu)",
|
||
"upload.skipImages.few": "{n} obrázky budou vynechány (max {max} na sadu)",
|
||
"upload.skipImages.many": "{n} obrázků bude vynecháno (max {max} na sadu)",
|
||
"upload.compressing": "Komprimuji fotky…",
|
||
"upload.step2": "2. Nepovinné doplňky",
|
||
"upload.step2Help": "Vlož další poznámky nebo AI napověz, na co se zaměřit.",
|
||
"upload.pastedNotes": "Vložené poznámky nebo text",
|
||
"upload.pastedPlaceholder": "Vlož sem text z přednášky, shrnutí nebo další fakta…",
|
||
"upload.topicHint": "Nápověda k tématu (nepovinné)",
|
||
"upload.topicPlaceholder": "např. zaměř se na vzorce nebo na data",
|
||
"upload.step3": "3. Obtížnost a velikost",
|
||
"upload.freezeInto": "Zmrazit do složky",
|
||
"upload.folderPlaceholder": "např. Dějepis",
|
||
"upload.difficultyAria": "Obtížnost",
|
||
"upload.easy": "Snadná",
|
||
"upload.easyBlurb": "Vybavování a rozpoznávání",
|
||
"upload.medium": "Střední",
|
||
"upload.mediumBlurb": "Porozumění a použití",
|
||
"upload.hard": "Těžká",
|
||
"upload.hardBlurb": "Analýza a záludnosti",
|
||
"upload.activities": "Aktivity",
|
||
"upload.readingDocs": "Čtu dokumenty…",
|
||
"upload.ready": "Můžeme začít — vytváření může chvíli trvat.",
|
||
"upload.addToStart": "Začni přidáním fotky, dokumentu nebo textu.",
|
||
"upload.freezeDeck": "Zmrazit sadu",
|
||
"upload.generationProgress": "Průběh vytváření",
|
||
"upload.cancel": "Zrušit",
|
||
"upload.unsupported":
|
||
"„{name}“ není podporovaný formát. Přidej fotky nebo soubor PDF, DOCX, PPTX, ODT/ODP, TXT či MD.",
|
||
"upload.tooManyDocs": "Až {n} dokumentů na sadu.",
|
||
"upload.tooManyPhotos": "Až {n} fotek na sadu.",
|
||
"upload.couldNotReadFile": "Soubor „{name}“ se nepodařilo přečíst.",
|
||
"upload.couldNotReadDoc": "Tento soubor se nepodařilo přečíst.",
|
||
"upload.genericError":
|
||
"Při vytváření sady se něco pokazilo. Zkontroluj nastavení Ollamy a zkus to znovu.",
|
||
"upload.warming": "Zahřívám…",
|
||
"upload.vision1": "Čtu poznámky…",
|
||
"upload.vision2": "Přepisuji rukopis…",
|
||
"upload.vision3": "Hledám definice…",
|
||
"upload.gen1": "Hledám klíčové myšlenky…",
|
||
"upload.gen2": "Píšu dobré otázky…",
|
||
"upload.gen3": "Stavím aktivity…",
|
||
"upload.tip1": "Tip: prolistuj sadu nahoru a dolů jako feed.",
|
||
"upload.tip2": "Tip: přiřazovací otázky dají plné XP jen za perfektní shodu.",
|
||
"upload.tip3": "Tip: sady se ukládají v tomto prohlížeči — účet není potřeba.",
|
||
"upload.tip4": "Tip: exportuj sadu jako JSON a pošli ji kamarádovi.",
|
||
|
||
/* study feed ------------------------------------------------------ */
|
||
"feed.back": "Zpět do knihovny",
|
||
"feed.activityOf": "Aktivita {n} z {total}",
|
||
"feed.streak": "Série odpovědí: {n}",
|
||
"feed.xp": "Získané XP: {n}",
|
||
"feed.retry": "Opakování · ",
|
||
"feed.swipeUp": "Přejeď nahoru na další aktivitu ↑",
|
||
"feed.prev": "Předchozí aktivita",
|
||
"feed.next": "Další aktivita",
|
||
"feed.results": "Výsledky",
|
||
"feed.timed": "Kolo na čas · 30 s na kartu",
|
||
"feed.timedOn": "Na čas — kartě, které vyprší čas, se počítá jako chyba",
|
||
"feed.timedOut": "Čas vypršel — karta se počítá jako chyba.",
|
||
"feed.report": "Nahlásit otázku",
|
||
"feed.reported": "Otázka nahlášena — už se neukáže.",
|
||
|
||
/* results --------------------------------------------------------- */
|
||
"results.perfect": "Perfektní zmrazení! 🧊",
|
||
"results.nice": "Pěkně!",
|
||
"results.start": "Dobrý začátek — jen dál!",
|
||
"results.acedAll": "Zvládl jsi všech {n} aktivit v sadě „{title}“.",
|
||
"results.gotOf": "Máš {correct} z {total} správně{retry}.",
|
||
"results.retrySuffix": " v opakování",
|
||
"results.xpEarned": "Získáno {n} XP",
|
||
"results.bestStreak": "Nejlepší série {n}",
|
||
"results.difficulty": "Obtížnost: {level}",
|
||
"results.best": "🏆 Nejlepší {n} %",
|
||
"results.missed": "Chybné položky ({n})",
|
||
"results.unanswered": "Nezodpovězené ({n})",
|
||
"results.unansweredNote": "Přeskočené karty se nepočítají jako chyby — čekají tady na tebe.",
|
||
"results.retryUnanswered": "Zodpovědět zbytek ({n})",
|
||
"results.noneAnswered": "Tentokrát nic zodpovězeno — pokračuj tam, kde jsi skončil.",
|
||
"results.yourAnswer": "Tvoje odpověď: {answer}",
|
||
"results.answer": "Odpověď:",
|
||
"results.retryMissed": "Zopakovat chybné ({n})",
|
||
"results.studyAgain": "Studovat sadu znovu",
|
||
"results.studyWhole": "Studovat celou sadu znovu",
|
||
"results.regenerate": "Znovu vytvořit sadu",
|
||
"results.regenerating": "Znovu vytvářím…",
|
||
"results.export": "Exportovat JSON",
|
||
"results.score": "skóre",
|
||
"results.true": "Pravda",
|
||
"results.false": "Nepravda",
|
||
|
||
/* gate ------------------------------------------------------------ */
|
||
"gate.free": "Zdarma",
|
||
"gate.ollamaLabel": "Ollama",
|
||
"gate.or": "nebo",
|
||
"gate.paidOption": "Použít placeného poskytovatele",
|
||
"gate.paidNote": "OpenAI, Anthropic, Gemini, DeepSeek, Groq, OpenRouter… Účtuje je přímo poskytovatel — Freezmo nikdy nevidí kartu. Klíče zůstávají v tomto prohlížeči a výběrem se na něj přesune generování studia.",
|
||
"gate.title": "Připoj svůj klíč Ollama",
|
||
"gate.subtitle": "První krok k dobrým známkám",
|
||
"gate.apiKeyLabel": "API klíč Ollama",
|
||
"gate.placeholder": "Vlož svůj API klíč Ollama",
|
||
"gate.hide": "Skrýt API klíč",
|
||
"gate.show": "Zobrazit API klíč",
|
||
"gate.checking": "Kontroluji…",
|
||
"gate.continue": "Pokračovat",
|
||
"gate.howGet": "Jak získám klíč?",
|
||
"gate.howGetAria": "Jak získám API klíč Ollama?",
|
||
"gate.stays": "Tvůj klíč zůstává v tomto prohlížeči. Náš server ho nikdy neukládá.",
|
||
"gate.helpTitle": "Jak získat API klíč Ollama",
|
||
"gate.freeTier.before": "Potřebuješ jen",
|
||
"gate.freeTier.strong": "bezplatnou cloudovou úroveň",
|
||
"gate.freeTier.after": ". Žádná platba ani kreditní karta.",
|
||
"gate.step1.before": "Přejdi na",
|
||
"gate.step1.after": "a přihlas se, nebo si vytvoř zdarma účet.",
|
||
"gate.step2.before": "Otevři Settings → API keys na",
|
||
"gate.step2.after": ".",
|
||
"gate.step3.before": "Klikni na",
|
||
"gate.step3.strong": "„Create new key“",
|
||
"gate.step3.after": ", dej mu libovolný název a zkopíruj ho.",
|
||
"gate.step4": "Vlož ho do Freezma a stiskni Pokračovat.",
|
||
"gate.footnote":
|
||
"Bezplatná úroveň na vytváření sad stačí. Klíč můžeš kdykoli změnit nebo odebrat v Nastavení.",
|
||
"gate.gotIt": "Rozumím",
|
||
|
||
/* install sheet --------------------------------------------------- */
|
||
"install.titleIos": "Nainstalovat Freezmo na iPhone",
|
||
"install.titleAndroid": "Nainstalovat Freezmo na Android",
|
||
"install.install": "Nainstalovat",
|
||
"install.installing": "Instaluji…",
|
||
"install.orManually": "Nebo nainstaluj ručně:",
|
||
"install.maybeLater": "Možná později",
|
||
"install.openIn": "Otevři tuto stránku v",
|
||
"install.tap": "Klepni na",
|
||
"install.tapConfirm": "Klepnutím na",
|
||
"install.inBrowserBar": "v liště prohlížeče",
|
||
"install.then": "a pak na",
|
||
"install.choose": "Zvol",
|
||
"install.toConfirm": "potvrď",
|
||
"install.or": "nebo",
|
||
"install.inTopRight": "vpravo nahoře v prohlížeči",
|
||
"install.moreOptions": "Další možnosti",
|
||
"install.share": "Sdílet",
|
||
"install.more": "Další",
|
||
"install.addToHome": "Přidat na domovskou obrazovku",
|
||
"install.add": "Přidat",
|
||
"install.installApp": "Nainstalovat aplikaci",
|
||
"install.addToHomeScreen": "Přidat na domovskou obrazovku",
|
||
|
||
/* deck page ------------------------------------------------------- */
|
||
"deckPage.loading": "Načítám sadu…",
|
||
"deckPage.notFound": "Sada nenalezena",
|
||
"deckPage.notFoundBody":
|
||
"Tato sada není v knihovně tohoto prohlížeče. Možná byla smazána, nebo vznikla v jiném profilu prohlížeče.",
|
||
"deckPage.back": "Zpět do knihovny",
|
||
"deckPage.create": "Vytvořit sadu",
|
||
|
||
/* offline --------------------------------------------------------- */
|
||
"offline.title": "Jsi offline",
|
||
"offline.body":
|
||
"Freezmo potřebuje připojení k vytváření sad a známkování odpovědí. Uložené sady máš pořád v tomto prohlížeči — připoj se a načti stránku znovu.",
|
||
|
||
/* not found ------------------------------------------------------- */
|
||
"notFound.title": "Tahle stránka se rozpustila",
|
||
"notFound.hint":
|
||
"Odkaz může být zastaralý nebo je v adrese překlep. Tvoje sady jsou v tomto prohlížeči v pořádku — vrať se domů.",
|
||
"notFound.home": "Zpět do knihovny",
|
||
"notFound.chat": "Zeptat se chatu",
|
||
|
||
/* activity types -------------------------------------------------- */
|
||
"type.flashcard": "Kartička",
|
||
"type.multipleChoice": "Výběr z možností",
|
||
"type.trueFalse": "Pravda nebo nepravda",
|
||
"type.fillBlank": "Doplň slovo",
|
||
"type.matching": "Přiřaď k sobě",
|
||
"type.ordering": "Seřaď",
|
||
"type.shortAnswer": "Krátká odpověď",
|
||
|
||
/* activities shared ----------------------------------------------- */
|
||
"activity.answerOptions": "Možnosti odpovědi",
|
||
"activity.wordBank": "Nabídka slov",
|
||
"activity.niceOne": "Pěkně!",
|
||
"activity.notQuite": "Skoro",
|
||
"activity.why": "Proč:",
|
||
"activity.seeResults": "Zobrazit výsledky",
|
||
"activity.next": "Další",
|
||
"activity.correctAnswer": "Správná odpověď:",
|
||
"activity.unsupported": "Nepodporovaná aktivita: {json}",
|
||
"activity.source": "Z tvých poznámek",
|
||
"activity.speak": "Přečíst nahlas",
|
||
"activity.stopSpeaking": "Zastavit čtení",
|
||
|
||
/* flashcard ------------------------------------------------------- */
|
||
"flashcard.tapToFlip": "Klepnutím na kartu ji otočíš.",
|
||
"flashcard.didYouKnow": "Věděl jsi to ještě před otočením?",
|
||
"flashcard.question": "Otázka",
|
||
"flashcard.answer": "Odpověď",
|
||
"flashcard.tapFlip": "klepni pro otočení ↻",
|
||
"flashcard.flipBack": "Otočit zpět na otázku",
|
||
"flashcard.reveal": "Odhalit odpověď",
|
||
"flashcard.notYet": "😅 Ještě ne",
|
||
"flashcard.knewIt": "🧊 Věděl jsem to",
|
||
"flashcard.frozenSolid": "Zmrazeno na pevno!",
|
||
"flashcard.keepPile": "Nech to na hromádce",
|
||
|
||
/* multiple choice ------------------------------------------------ */
|
||
"mc.tipKeys": "Tip: stiskni 1–4 na klávesnici.",
|
||
|
||
/* true/false ------------------------------------------------------ */
|
||
"tf.hint": "Přejeď kartu doprava pro pravdu, doleva pro nepravdu — nebo klepni na tlačítko.",
|
||
"tf.niceSwipe": "Pěkný přejezd!",
|
||
"tf.trueLabel": "✓ pravda",
|
||
"tf.falseLabel": "nepravda ✕",
|
||
"tf.falseBtn": "✕ Nepravda",
|
||
"tf.trueBtn": "✓ Pravda",
|
||
"tf.statementIs.before": "Toto tvrzení je",
|
||
"tf.true": "pravda",
|
||
"tf.false": "nepravda",
|
||
|
||
/* fill blank ------------------------------------------------------ */
|
||
"fill.typeMissing": "Napiš chybějící slovo",
|
||
"fill.placeholder": "napiš to",
|
||
"fill.check": "Zkontrolovat odpověď",
|
||
"fill.hint": "Stiskni Enter pro kontrolu. Malé překlepy se prominou.",
|
||
"fill.expected": "Očekáváno:",
|
||
"fill.youWrote": "— napsal jsi",
|
||
|
||
/* matching -------------------------------------------------------- */
|
||
"matching.matched": "{n}/{total} přiřazeno",
|
||
"matching.allMatched": "Vše přiřazeno!",
|
||
"matching.tapTerm": "Klepni vlevo na pojem a pak vpravo na jeho definici.",
|
||
"matching.tapDef": "Teď klepni na odpovídající definici →",
|
||
"matching.perfect": "Perfektní přiřazení!",
|
||
"matching.slips": "Přiřazeno — s pár chybami",
|
||
"matching.firstTry": "Vše jsi přiřadil na první pokus.",
|
||
"matching.wrongAttempts.one": "Udělal jsi {n} chybný pokus.",
|
||
"matching.wrongAttempts.few": "Udělal jsi {n} chybné pokusy.",
|
||
"matching.wrongAttempts.many": "Udělal jsi {n} chybných pokusů.",
|
||
|
||
/* ordering -------------------------------------------------------- */
|
||
"ordering.hint": "Přetahuj karty nebo použij ▲▼ a pak zkontroluj pořadí.",
|
||
"ordering.correctOrder": "Toto je správné pořadí:",
|
||
"ordering.check": "Zkontrolovat pořadí",
|
||
"ordering.moveUp": "Posunout „{item}“ nahoru",
|
||
"ordering.moveDown": "Posunout „{item}“ dolů",
|
||
|
||
/* short answer ---------------------------------------------------- */
|
||
"short.placeholder": "Napiš odpověď vlastními slovy…",
|
||
"short.ctrlEnter": "Ctrl/⌘ + Enter pro odeslání.",
|
||
"short.submit": "Odeslat odpověď",
|
||
"short.grading": "Známkuji tvoji odpověď…",
|
||
"short.couldNotGrade": "Tuto odpověď nešlo oznámkovat",
|
||
"short.showAnswer": "Zobrazit odpověď a sebehodnocení",
|
||
"short.modelAnswer": "Vzorová odpověď",
|
||
"short.keyPoints": "Klíčové body:",
|
||
"short.missedIt": "😅 Netrefil jsem se",
|
||
"short.hadRight": "🧊 Měl jsem to správně",
|
||
"short.accepted": "Přijato",
|
||
"short.needsWork": "Ještě zapracovat",
|
||
"short.selfChecked": "Sebehodnocení — tuhle odpověď jsi posoudil sám.",
|
||
"short.yourAnswer": "Tvoje odpověď",
|
||
"short.gradeFailed": "Známkování selhalo. Zkontroluj, že je Ollama dostupná, a zkus to znovu.",
|
||
|
||
/* client API errors ----------------------------------------------- */
|
||
"api.cancelled": "Vytváření bylo zrušeno.",
|
||
"api.unreachable": "Nepodařilo se připojit k serveru Freezmo. Běží ještě?",
|
||
"api.unreachableShort": "Nepodařilo se připojit k serveru Freezmo.",
|
||
"api.httpError": "Server vrátil HTTP {status}.",
|
||
"api.empty": "Server poslal prázdnou odpověď.",
|
||
"api.stalled": "Model přestal odpovídat. Zkontrolujte ho a zkuste to znovu.",
|
||
"api.streamLost": "Připojení se během generování přerušilo. Zkontrolujte připojení a zkuste to znovu.",
|
||
"api.almost": "Už to bude…",
|
||
"api.noDeck": "Ze serveru nepřišla žádná sada. Zkontroluj nastavení Ollamy a zkus to znovu.",
|
||
"api.noPodcast": "Ze serveru nepřišel žádný scénář. Zkontroluj nastavení modelu a zkus to znovu.",
|
||
"api.noAudio": "Hlas přišel prázdný. Zkus jiný hlas nebo providera.",
|
||
"api.incomplete": "Server skončil, aniž by poslal sadu.",
|
||
"api.noActivities": "Vygenerovaná sada neměla žádné použitelné aktivity. Zkus to znovu.",
|
||
"api.gradingCancelled": "Známkování bylo zrušeno.",
|
||
"api.gradingFailed": "Známkování selhalo s HTTP {status}.",
|
||
"api.healthFailed": "Kontrola stavu selhala s HTTP {status}.",
|
||
"api.healthUnexpected": "Server Freezmo poslal neočekávanou odpověď o stavu.",
|
||
"api.serverUnexpected": "Server Freezmo odpověděl neočekávaně. Zkus to za chvíli znovu.",
|
||
"api.keyRejected": "Ollama tento klíč odmítla. Zkontroluj překlepy, nebo si vytvoř nový na ollama.com/settings/keys.",
|
||
"api.ollamaUnreachable": "Teď se nepodařilo připojit k Ollamě. Zkontroluj připojení a zkus to znovu.",
|
||
|
||
/* server-side messages (API routes) -------------------------------- */
|
||
"server.bodyNotJson": "Tělo požadavku nebylo platné JSON.",
|
||
"server.invalidRequest": "Neplatný požadavek: {detail}",
|
||
"server.providerModelNeeded": "Vyberte model pro generování v Nastavení → Poskytovatelé.",
|
||
"server.ttsNeedsProvider": "Přirozené hlasy dodává provider — nejdřív si v Nastavení → Provideři přidej klíč.",
|
||
"server.ttsUnsupported": "{provider} text-to-speech neumí. Umí ho OpenAI, Groq nebo vlastní OpenAI-compatible server.",
|
||
"server.ttsFailed": "{provider} nedokázal pro tuhle část vygenerovat hlas.",
|
||
"server.ttsTimeout": "{provider} generoval hlas příliš dlouho.",
|
||
"server.needContent": "Přidej aspoň jednu fotku poznámek nebo vlož text, ze kterého sada vznikne.",
|
||
"server.needAssignment":
|
||
"Nejdřív přidej zadání — vlož jeho text, vyfoť ho nebo přidej dokument.",
|
||
"server.invalidGrade": "Neplatný požadavek na známkování.",
|
||
"server.bodyTooLarge": "Tělo požadavku je příliš velké ({size}). Limit je {limit}.",
|
||
"server.invalidOrigin": "Neplatná hlavička Origin.",
|
||
"server.crossOrigin": "Požadavky z jiné domény nejsou povoleny.",
|
||
"server.rateLimited": "Příliš mnoho požadavků — aplikace povoluje {limit} za okno. Zkus to znovu za {retry} s.",
|
||
"server.busy": "Freezmo už na něčem pracuje — zkus to za chvilku znovu.",
|
||
"server.pushTooMany": "Server už má uložených {max} zařízení pro připomínky — nejdřív nějaké odeber.",
|
||
"server.pushEndpoint": "Na tuhle službu server připomínky posílat nesmí.",
|
||
"server.authMissingKey":
|
||
"Nejdřív přidej svůj API klíč Ollama — otevři nastavení (⚙️) a vlož klíč z ollama.com/settings/keys.",
|
||
"server.authServerKeyMissing":
|
||
"Tento server má používat vlastní API klíč Ollama (OLLAMA_AUTH=server), ale OLLAMA_API_KEY je v .env.local prázdný.",
|
||
"server.authLocalCloud":
|
||
"Tento server má OLLAMA_AUTH=local, ale míří na https://ollama.com. Přihlášený místní daemon se s cloudovou adresou použít nedá — nastav OLLAMA_AUTH=user-key, nebo přepiš OLLAMA_BASE_URL na http://localhost:11434.",
|
||
"server.ollamaUnreachable":
|
||
"Ollama není dostupná na {url}. Zkontroluj nastavení a jestli daemon běží.",
|
||
"server.ollamaTimeout": "Ollamě to trvalo příliš dlouho. Zkus méně nebo menší obrázky, rychlejší model, případně to zopakuj.",
|
||
"server.ollamaAuth":
|
||
"Ollama požadavek odmítla. Pokud používáš https://ollama.com, zkontroluj OLLAMA_API_KEY (vytvoř si ho na ollama.com/settings/keys).",
|
||
"server.ollamaUnexpected": "Ollama vrátila neočekávanou odpověď.",
|
||
"server.ollamaGeneric": "Při komunikaci s Ollamou se něco pokazilo.",
|
||
"server.ollamaTimeoutList": "Vypršel čas při načítání modelů Ollama.",
|
||
"server.ollamaUnauthorised": "Ollama požadavek odmítla (neautorizováno).",
|
||
"server.ollamaNotJson": "Ollama poslala odpověď, která nebyla JSON.",
|
||
"server.ollamaNoContent": "Ollama poslala odpověď bez obsahu zprávy.",
|
||
"server.healthNoKey":
|
||
"Zatím žádný API klíč Ollama. Přidej klíč v nastavení — ukládá se jen v tomto prohlížeči.",
|
||
"server.healthKeyRejected": "Ollama tento API klíč odmítla. Vytvoř si nový na ollama.com/settings/keys a vlož ho znovu.",
|
||
"server.healthNoCredits":
|
||
"Klíč funguje, ale {model} není součástí tarifu tohoto účtu. Přidej kredity, nebo v .env.local přepni na model zdarma.",
|
||
"server.healthModelNoteLocal": "Není v seznamu modelů daemonu. Spusť `ollama pull {model}` nebo zkontroluj přihlášení.",
|
||
"server.healthModelNoteCloud": "Není v odpovědi {url}/api/tags. Zkontroluj název modelu a tarif klíče.",
|
||
"server.healthModelsMissing": "Ollama je dostupná, ale aspoň jeden nakonfigurovaný model není k dispozici.",
|
||
"server.usageLimit":
|
||
"Vyčerpal se tvůj měsíční limit Ollama zdarma. Upgraduj svůj účet Ollama, nebo si vytvoř nový a použij jeho nový API klíč.",
|
||
"server.ollamaThrottled": "Ollama dočasně omezuje požadavky. Zkus to za chvíli znovu.",
|
||
"server.syncInvalidId": "Tenhle sync ID nevypadá správně.",
|
||
"server.syncTooLarge": "Tahle synchronizace je příliš velká.",
|
||
"server.syncStorageFull": "Úložiště synchronizace je plné. Smaž nějaká synchronizovaná data a zkus to znovu.",
|
||
"server.syncNotFound": "Pro tenhle ID nejsou žádná synchronizovaná data.",
|
||
"server.pushDisabled": "Push připomínky nejsou na tomto serveru nastavené (nastav VAPID_PUBLIC_KEY a VAPID_PRIVATE_KEY).",
|
||
"server.pushNotSubscribed": "Tento prohlížeč není k připomínkám přihlášený.",
|
||
"server.pushGone": "Ta přihláška už není platná — připomínky pro ni byly vypnuté.",
|
||
"server.pushSendFailed": "Připomínku se nepodařilo poslat.",
|
||
"server.pushTestPrefix": "Test:",
|
||
|
||
/* folders / files -------------------------------------------------- */
|
||
"folder.nameRequired": "Dej složce název.",
|
||
"folder.nameTooLong": "Ten název je trochu dlouhý (max 60 znaků).",
|
||
"folder.nameTaken": "Jiná složka už tenhle název má.",
|
||
"file.notImage": "„{name}“ není obrázek.",
|
||
"file.imageTooLarge": "„{name}“ je větší než 20 MB.",
|
||
"file.notDocument": "„{name}“ není podporovaný dokument (použij PDF, DOCX, PPTX, ODT/ODP nebo text).",
|
||
"file.documentTooLarge": "„{name}“ je větší než 40 MB.",
|
||
"import.notJson": "Tento soubor není platný JSON.",
|
||
"import.notDeck": "Tento soubor nevypadá jako sada Freezmo.",
|
||
"import.notBackup": "Tento soubor nevypadá jako záloha Freezmo.",
|
||
"error.usageTitle": "Měsíční limit zdarma vyčerpán",
|
||
"error.usageBody":
|
||
"Vyčerpal se tvůj měsíční limit Ollama zdarma. Upgraduj svůj účet Ollama, nebo si vytvoř nový a použij jeho nový API klíč.",
|
||
"error.openOllama": "Otevřít Ollamu",
|
||
"error.throttledTitle": "Ollama je zaneprázdněná",
|
||
"error.throttledBody": "Ollama dočasně omezuje požadavky. Zkus to za chvíli znovu.",
|
||
|
||
/* learning: review, mistakes, stats, exam -------------------------- */
|
||
"review.cardTitle": "Chytré opakování",
|
||
"review.title": "K opakování",
|
||
"review.mistakesTitle": "Chyby",
|
||
"review.mistakesDeck": "Chyby v této sadě",
|
||
"review.nothingDue": "Teď není co opakovat. Zauč se do sady a rozvrh se sám naplní.",
|
||
"review.noMistakes": "Žádné otevřené chyby — co jsi měl špatně, jsi od té doby zodpověděl správně.",
|
||
"review.done": "Zpět do knihovny",
|
||
"review.seeResults": "Zobrazit výsledky",
|
||
"review.finishFirst": "Dokonči poslední aktivitu a uvidíš výsledky.",
|
||
"review.results": "Výsledky",
|
||
"review.preparing": "Připravuji opakování…",
|
||
"review.due.one": "{n} k opakování",
|
||
"review.due.few": "{n} k opakování",
|
||
"review.due.many": "{n} k opakování",
|
||
"review.dueByDeck": "K opakování podle sady: {list}",
|
||
"review.dueOfTotal": "{due} z {total}",
|
||
"review.mistakesByDeck": "Chyby podle sady: {list}",
|
||
"review.moreDecks.one": "+{n} další sada",
|
||
"review.moreDecks.few": "+{n} další sady",
|
||
"review.moreDecks.many": "+{n} dalších sad",
|
||
"review.mistakes.one": "{n} chyba",
|
||
"review.mistakes.few": "{n} chyby",
|
||
"review.mistakes.many": "{n} chyb",
|
||
"exam.title": "Zkouškový režim",
|
||
"exam.preparing": "Připravuji zkouškový režim…",
|
||
"exam.subtitle": "Vyber sady, nastav časovač, žádné nápovědy — uvidíš, jak bys dopadl.",
|
||
"exam.pickDecks": "Sady",
|
||
"exam.noDecks": "Nejdřív si vytvoř sadu.",
|
||
"exam.questions": "Otázky",
|
||
"exam.timer": "Časovač",
|
||
"exam.timerOff": "Vypnuto",
|
||
"exam.start": "Spustit zkoušku ({n} otázek)",
|
||
"exam.results": "Výsledky zkoušky",
|
||
"exam.byDeck": "Podle sady",
|
||
"exam.byTopic": "Podle tématu",
|
||
"exam.studyMistakes": "Procvičit chyby",
|
||
"exam.timeLeft": "zbývá {time}",
|
||
"stats.title": "Tvůj pokrok",
|
||
"stats.preparing": "Připravuji pokrok…",
|
||
"stats.subtitle": "Série, úspěšnost a témata, která si zaslouží další kolo.",
|
||
"stats.streak": "Série dní",
|
||
"stats.today": "Dnes",
|
||
"stats.todayOf": "{n} / {goal} odpovědí",
|
||
"stats.goalDone": "Denní cíl splněn 🎉",
|
||
"stats.accuracy": "Úspěšnost",
|
||
"stats.answers.one": "{n} odpověď",
|
||
"stats.answers.few": "{n} odpovědi",
|
||
"stats.answers.many": "{n} odpovědí",
|
||
"stats.calendar": "Posledních 5 týdnů",
|
||
"stats.bySubject": "Podle předmětu",
|
||
"stats.byDeck": "Podle sady",
|
||
"stats.weakest": "Nejslabší témata",
|
||
"stats.noSubject": "Bez předmětu",
|
||
"stats.correctOf": "{correct} / {total} správně",
|
||
"stats.empty": "Zatím žádné odpovědi",
|
||
"stats.emptyBody": "Zauč se do sady a tvůj pokrok se objeví tady.",
|
||
"stats.start": "Jít studovat",
|
||
"stats.legendLess": "Méně",
|
||
"stats.legendMore": "Více",
|
||
"nav.stats": "Pokrok",
|
||
"nav.exam": "Zkouškový režim",
|
||
"settings.learning": "Učení",
|
||
"settings.dailyGoal": "Denní cíl",
|
||
"settings.dailyGoalNote": "Kolik odpovědí za den se počítá jako splněno.",
|
||
"settings.reminders": "Denní připomínka",
|
||
"push.title": "Čas na opakování",
|
||
"push.due.one": "{n} kartička čeká v sadě {deck}",
|
||
"push.due.few": "{n} kartičky čekají v sadě {deck}",
|
||
"push.due.many": "{n} kartiček čeká v sadě {deck}",
|
||
"push.generic": "Sady na tebe čekají — pár kartiček udrží sérii.",
|
||
"settings.remindersNote":
|
||
"Jemné připomenutí, když je čas na opakování — i s názvem sady, která čeká. Potřebuje nainstalovanou aplikaci (na iPhonu povinně).",
|
||
"settings.reminderEnable": "Zapnout připomínky",
|
||
"settings.reminderDisable": "Vypnout",
|
||
"settings.reminderTest": "Poslat zkušební",
|
||
"settings.reminderOn": "Připomínky jsou zapnuté.",
|
||
"settings.reminderOff": "Připomínky jsou vypnuté.",
|
||
"settings.reminderDenied": "Oznámení jsou v nastavení prohlížeče zablokovaná.",
|
||
"settings.reminderError": "Připomínky se nepodařilo nastavit.",
|
||
"library.learningToday": "Dnes: {n} / {goal}",
|
||
|
||
/* homework: check my work + follow-ups ----------------------------- */
|
||
"homework.mode.check": "Zkontrolovat moje řešení",
|
||
"homework.mode.checkDesc": "Oznámkuj svůj postup: co je správně, co špatně a opravená verze.",
|
||
"homework.solution.label": "Tvoje řešení",
|
||
"homework.solution.placeholder": "Sem vlož svůj postup — zadání je nahoře.",
|
||
"homework.solution.note": "Řešení napiš, vlož, nebo přidej fotky a dokumenty se svým postupem — ručně psané stránky jsou v pohodě.",
|
||
"homework.solution.add": "Přidat fotky nebo soubory",
|
||
"homework.solution.tooManyPhotos": "Až {n} fotek k řešení.",
|
||
"homework.solution.tooManyDocs": "Až {n} dokumentů k řešení.",
|
||
"homework.solution.required": "Přidej svoje řešení, ať je co zkontrolovat.",
|
||
"homework.check.title": "Zkontrolováno",
|
||
"homework.check.verdict": "Hodnocení",
|
||
"homework.check.correct": "Co je správně",
|
||
"homework.check.wrong": "Co opravit",
|
||
"homework.check.fix": "Oprava",
|
||
"homework.check.corrected": "Opravená verze",
|
||
"homework.history.checked": "Zkontrolováno",
|
||
"homework.loading.checking": "Kontroluji tvůj postup…",
|
||
"homework.followup.shorter": "Kratší",
|
||
"homework.followup.more": "Více detailů",
|
||
"homework.followup.explain": "Vysvětlit jinak",
|
||
"homework.followup.redo": "Znovu",
|
||
"homework.followup.step": "Vysvětlit krok {n} jinak",
|
||
"homework.followup.working": "Přepisuji…",
|
||
"homework.followup.failed": "Nepovedlo se. Zkus to znovu.",
|
||
"homework.followup.restore": "Původní",
|
||
"homework.toDeck": "Převést na studijní sadu",
|
||
"homework.toDeckNote": "Otevře Novou sadu s tímto obsahem a předvyplněnými možnostmi.",
|
||
"server.needSolution": "Přidej svoje řešení, ať se dá zkontrolovat.",
|
||
"doc.readingPage": "Čtu stranu {n} z {total}…",
|
||
"doc.readingWord": "Čtu dokument Word…",
|
||
"doc.readingSlides": "Čtu snímky…",
|
||
"doc.readingOpen": "Čtu soubor OpenDocument…",
|
||
"doc.readingText": "Čtu textový soubor…",
|
||
|
||
/* chat ------------------------------------------------------------ */
|
||
"chat.nav": "Chat",
|
||
"chat.title": "Chat",
|
||
"chat.ask": "Zeptat se",
|
||
"chat.askAboutDeck": "Zeptat se na tuto sadu",
|
||
"chat.askQuestion": "Zeptat se na něco",
|
||
"chat.back": "Zpět",
|
||
"chat.new": "Nový chat",
|
||
"chat.placeholder": "Zeptej se na svoje poznámky…",
|
||
"chat.send": "Odeslat",
|
||
"chat.stop": "Zastavit",
|
||
"chat.copy": "Kopírovat",
|
||
"chat.copied": "Zkopírováno",
|
||
"chat.copyFailed": "Kopírování se nepovedlo",
|
||
"chat.edit": "Upravit a poslat znovu",
|
||
"chat.regenerate": "Vygenerovat znovu",
|
||
"chat.retry": "Zkusit znovu",
|
||
"chat.thinking": "Přemýšlím…",
|
||
"chat.thought": "Myšlenkový postup",
|
||
"chat.sources": "Zdroje",
|
||
"chat.jumpToBottom": "Skočit na nejnovější",
|
||
"chat.offlineNote": "Offline — posílání je pozastavené",
|
||
"chat.emptyTitle": "Zeptej se svých poznámek na cokoli",
|
||
"chat.emptyHint": "Odpovědi vycházejí z tvých sad. Pomocí chipu s kontextem níže vyber, co smí chat číst.",
|
||
"chat.oldDecksNote": "Starší sady mají jen vygenerovaný obsah. Přidej znovu poznámky a odpovědi budou lepší.",
|
||
"chat.fromLabel": "Z",
|
||
"chat.attach": "Přiložit fotky nebo dokumenty",
|
||
"chat.attachPhoto": "fotka",
|
||
"chat.attachDoc": "dokument",
|
||
"chat.attachRemove": "Odebrat přílohu",
|
||
"chat.attachFailed": "Tuto přílohu se nepovedlo přečíst.",
|
||
"chat.attachNoText": "V souboru nebyl žádný text — zkus místo toho fotku.",
|
||
"chat.attachEmpty": "V příloze nebylo nic čitelného.",
|
||
"chat.attachmentLabel": "Příloha",
|
||
"chat.modelTitle": "Model chatu",
|
||
"chat.modelText": "Text",
|
||
"chat.modelVision": "Obrázky",
|
||
"chat.modelBoth": "Text + obrázky",
|
||
"chat.modelSearch": "Hledat modely",
|
||
"chat.modelSearchEmpty": "Žádný model neodpovídá.",
|
||
"chat.modelAvailable": "dostupný",
|
||
"chat.modelUnavailable": "nenalezen",
|
||
"chat.modelNote": "Fotky a dokumenty se nejdřív přepíšou na text, takže funguje kterýkoli z nich.",
|
||
"chat.scope.title": "Co smí chat číst?",
|
||
"chat.scope.all": "Všechny poznámky",
|
||
"chat.scope.allNote": "Odpovědi mohou vycházet z poznámek všech sad.",
|
||
"chat.scope.pick": "Vybrané sady",
|
||
"chat.scope.pickNote": "Níže vyber složky nebo jednotlivé sady.",
|
||
"chat.scope.none": "Bez poznámek",
|
||
"chat.scope.noneNote": "Obecný chat — model odpovídá jen z obecných znalostí.",
|
||
"chat.scope.decks.one": "{n} sada",
|
||
"chat.scope.decks.few": "{n} sady",
|
||
"chat.scope.decks.many": "{n} sad",
|
||
"chat.scope.deckCount.one": "{n} sada",
|
||
"chat.scope.deckCount.few": "{n} sady",
|
||
"chat.scope.deckCount.many": "{n} sad",
|
||
"chat.scope.hasNotes": "text poznámek uložen",
|
||
"chat.scope.generatedOnly": "jen vygenerovaný obsah",
|
||
"chat.scope.note": "Jako kontext se posílá jen to, co tady vybereš — API klíč nikdy neopustí tento prohlížeč.",
|
||
"chat.suggest.explain": "Vysvětli hlavní myšlenku sady {deck}",
|
||
"chat.suggest.quiz": "Vyzkoušej mě ze sady {deck}",
|
||
"chat.suggest.summarize": "Shrň moje poznámky",
|
||
"chat.suggest.quizAll": "Vyzkoušej mě ze všech sad",
|
||
"chat.suggest.studyFirst": "Co mám studovat jako první?",
|
||
"chat.suggest.general": "Pomoz mi pochopit nějaké téma",
|
||
"chat.suggest.explainTopic": "Vysvětli téma krok za krokem",
|
||
"chat.seedResults": "Výsledky studia",
|
||
"chat.seedAssignment": "Zadání",
|
||
"chat.seedAnswer": "Odpověď",
|
||
"chat.seedAttempt": "Můj postup",
|
||
"chat.seedVerdict": "Hodnocení",
|
||
"chat.seedToFix": "Co opravit",
|
||
"chat.seedCorrected": "Opravená verze",
|
||
"chat.seedSteps": "Postup",
|
||
"chat.notesDeleted": "Text poznámek smazán",
|
||
"chat.deleteNotes": "Smazat text poznámek",
|
||
"chat.history": "Chaty",
|
||
"chat.searchChats": "Hledat v chatech",
|
||
"chat.noResults": "Tomuto hledání nic neodpovídá.",
|
||
"chat.noHistory": "Zatím žádné uložené chaty — na něco se zeptej a objeví se tady.",
|
||
"chat.untitled": "Chat bez názvu",
|
||
"chat.deleteChat": "Smazat chat",
|
||
"chat.deletedChat": "Chat smazán",
|
||
"chat.toFlashcards": "Převést na kartičky",
|
||
"chat.saveToNotes": "Uložit do poznámek",
|
||
"chat.savedToNotes": "Uloženo do poznámek sady {deck}",
|
||
"chat.savedToNotesTrimmed": "Uloženo do poznámek sady {deck} (zkráceno, aby se vešlo)",
|
||
"chat.savedFromChat": "Uloženo z chatu",
|
||
"chat.voiceStart": "Diktovat",
|
||
"chat.voiceStop": "Zastavit diktování",
|
||
"teach.nav": "Vysvětli to",
|
||
"teach.title": "Vysvětli to",
|
||
"teach.subtitle": "Ty vysvětluješ. AI hraje studenta.",
|
||
"teach.intro": "Vysvětli téma vlastními slovy a student se bude krátce doptávat. Kde bude kopat hlouběji, tam máš mezeru.",
|
||
"teach.topicLabel": "Co budeš vysvětlovat?",
|
||
"teach.topicPlaceholder": "např. Proč je nebe modré",
|
||
"teach.studentLabel": "Co smí student znát?",
|
||
"teach.difficultyLabel": "Vyber si studenta",
|
||
"teach.friendly": "Přátelský student",
|
||
"teach.friendlyNote": "Trpělivé otázky, chce to pochopit.",
|
||
"teach.skeptical": "Nedůvěřivý student",
|
||
"teach.skepticalNote": "Ptá se proč, hledá háček a okrajové případy.",
|
||
"teach.start": "Začít učit",
|
||
"teach.recent": "Nedávné výuky",
|
||
"teach.noSessions": "Zatím tu nic není — dokončené výuky a jejich přehledy se objeví tady.",
|
||
"teach.inProgress": "Probíhá",
|
||
"teach.recap": "Přehled",
|
||
"teach.newSession": "Nová výuka",
|
||
"teach.open": "Otevřít",
|
||
"teach.untitled": "Výuka bez názvu",
|
||
"teach.deleteSession": "Smazat výuku",
|
||
"teach.deleted": "Výuka smazána",
|
||
"teach.back": "Zpět",
|
||
"teach.finish": "Dokončit",
|
||
"teach.finishHint": "Ukončit výuku a zobrazit přehled.",
|
||
"teach.exchangeCount": "{n}/{max}",
|
||
"teach.youExplain": "Ty vysvětluješ",
|
||
"teach.emptyTitle": "Teď učíš ty",
|
||
"teach.emptyHint": "Vysvětli téma, jako bys ho vyprávěl spolužákovi. Student se vždy zeptá na jednu krátkou věc — žádné známkování, žádné přednášky.",
|
||
"teach.placeholder": "Vysvětli to vlastními slovy…",
|
||
"teach.voiceStart": "Nadiktovat vysvětlení",
|
||
"teach.voiceStop": "Zastavit diktování",
|
||
"teach.listening": "Poslouchám…",
|
||
"teach.limitReached": "Tady je dobré místo skončit — tady je přehled.",
|
||
"teach.summaryTitle": "Přehled",
|
||
"teach.summaryBusy": "Píšu přehled…",
|
||
"teach.summaryFailed": "Přehled se nepovedlo napsat. Zkus to znovu.",
|
||
"teach.retry": "Napsat znovu",
|
||
"teach.well": "Dobře vysvětlené",
|
||
"teach.wellEmpty": "Zatím nic nevyniklo.",
|
||
"teach.gaps": "Chybělo nebo bylo nejasné",
|
||
"teach.gapsEmpty": "Nic nechybělo.",
|
||
"teach.next": "Další kroky",
|
||
"teach.cardLinkTitle": "Otevřít v sadě {deck}",
|
||
"teach.addToReview": "Přidat vynechané kartičky k opakování",
|
||
"teach.reviewAdded.one": "{n} kartička přidána k opakování",
|
||
"teach.reviewAdded.few": "{n} kartičky přidány k opakování",
|
||
"teach.reviewAdded.many": "{n} kartiček přidáno k opakování",
|
||
"teach.gapsToCards": "Udělat z mezer kartičky",
|
||
"teach.gapDeck": "Přidat kartičky do",
|
||
"teach.generate": "Vytvořit kartičky",
|
||
"teach.generating": "Vytvářím kartičky…",
|
||
"teach.reviewTitle": "Zkontroluj kartičky",
|
||
"teach.reviewHint": "Před uložením cokoli uprav; nechtěné odškrtni.",
|
||
"teach.cardFront": "Přední strana",
|
||
"teach.cardBack": "Zadní strana",
|
||
"teach.saveCards.one": "Uložit {n} kartičku",
|
||
"teach.saveCards.few": "Uložit {n} kartičky",
|
||
"teach.saveCards.many": "Uložit {n} kartiček",
|
||
"teach.cardsSaved.one": "{n} kartička přidána do sady {deck}",
|
||
"teach.cardsSaved.few": "{n} kartičky přidány do sady {deck}",
|
||
"teach.cardsSaved.many": "{n} kartiček přidáno do sady {deck}",
|
||
"teach.generateFailed": "Kartičky se nepovedlo vytvořit.",
|
||
"teach.noGapDeck": "Vyber sadu s poznámkami pro kartičky.",
|
||
"teach.noDecksHint": "Nejsou vybrané žádné sady — student nemá proti čemu ověřovat.",
|
||
"legal.updated": "Naposledy upraveno {when}",
|
||
"legal.privacy": "Zásady soukromí",
|
||
"legal.terms": "Podmínky použití",
|
||
"legal.cookies": "Zásady cookies",
|
||
"settings.theme": "Téma",
|
||
"theme.aero": "Frutiger Aero",
|
||
"theme.aeroNote": "Aero nemá přepínač světlý/tmavý — vyber Světlý nebo Tmavý, pokud ho chceš zpět.",
|
||
"aero.music": "Hudba Frutiger Aero",
|
||
"aero.musicPlay": "Přehrát hudbu",
|
||
"aero.musicPause": "Pozastavit hudbu",
|
||
"aero.musicClose": "Vypnout hudbu",
|
||
"aero.musicCollapse": "Zpět na tlačítko hudby",
|
||
"aero.musicLoading": "Načítání hudby…",
|
||
"settings.legal": "Právní informace",
|
||
"consent.title": "O cookies",
|
||
"consent.body": "Freezmo nepoužívá cookies ani sledování — sady, úkoly a nastavení zůstávají v tomto prohlížeči.",
|
||
"consent.link": "Zásady cookies",
|
||
"consent.ok": "Rozumím",
|
||
"settings.version": "Verze",
|
||
"settings.chatNotes": "Uchovávat text poznámek pro chat",
|
||
"providers.title": "Cloudoví poskytovatelé",
|
||
"providers.saveKey": "Uložit klíč",
|
||
"providers.note": "Přidejte si vlastní API klíče a používejte více modelů. Klíče zůstávají v tomto prohlížeči — server je posílá jen s vašimi požadavky a nikdy je neukládá.",
|
||
"providers.key": "API klíč",
|
||
"providers.keySaved": "Klíč uložen — vložte nový pro nahrazení",
|
||
"providers.getKey": "Získat klíč",
|
||
"providers.test": "Otestovat a vypsat modely",
|
||
"providers.remove": "Odstranit klíč",
|
||
"providers.testFailed": "Nepodařilo se připojit k poskytovateli. Zkontrolujte klíč (a u vlastního endpointu i URL).",
|
||
"providers.modelCount": "{n} modelů",
|
||
"providers.generation": "Generování studia",
|
||
"providers.generationNote": "Generovat sady, úkoly a hodnocení na modelu poskytovatele místo serverového Ollama.",
|
||
"providers.serverDefault": "Serverové výchozí (Ollama)",
|
||
"providers.textModel": "Textový model",
|
||
"providers.visionModel": "Model pro obrázky",
|
||
"providers.baseUrl": "OpenAI kompatibilní základní URL",
|
||
"providers.baseUrlPlaceholder": "https://host/v1",
|
||
"settings.reduceMotion": "Omezit pohyb",
|
||
"settings.reduceMotionNote": "Zklidní scénu i animace rozhraní. Hodí se, když je pohyb rušivý.",
|
||
"settings.chatNotesNote": "Chat odpovídá z poznámek, které jsi přidal (jsou uložené u každé sady). Vypnutím bude používat jen vygenerovaný obsah.",
|
||
"server.chatModelNotAllowed": "Tento model chatu není v povoleném seznamu serveru.",
|
||
"server.chatNeedsQuestion": "Konverzace musí končit zprávou uživatele.",
|
||
};
|
||
|
||
/** Used by tests/tools that want the raw tables. */
|
||
export const dictionaries = { en, cs };
|