From a16510aba853c193731d07097e858ee1b759c1a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Bene=C5=A1?= Date: Sat, 26 Sep 2026 14:46:33 +0000 Subject: [PATCH] The interface speaks Slovak MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 969 strings, an instance default and a per-person choice, and no half-done corner: the admin prose is translated too. Design and reasoning: LLeMbas.wiki/Translations. KEYED BY THE ENGLISH SENTENCE A missing entry renders the key, which is the English -- so an untranslated string looks as it always did, an English instance is byte-for-byte 1.6.0, and a half-finished catalogue is a half-translated page rather than a page of dotted key names. The cost is that editing an English sentence orphans its translation silently, which is what tests/test_translations.py asserts in both directions. No gettext: .po -> .mo is a build step and this project does not have one. A JINJA GLOBAL, AND THEREFORE A CONTEXTVAR `t()` is a global for the reason `brand` already documents -- render() is bypassed by 25 TemplateResponse calls and 8 get_template().render() calls, the latter being the SSE frames, which have no Request at all. A global is bound once at import and the language is per person, so the active language is a ContextVar set per request. 🚨 `get_current_user` had to become `async def`. FastAPI runs a sync dependency in a threadpool, and anyio copies the context in and discards it on the way out -- so the language was set where nothing could see it and every page rendered in the instance's language whatever anybody had chosen, with no error anywhere. NOT TRANSLATED, ON PURPOSE Everything a model reads: the 60 prompt fragments, and the dates in harness.py, schedule/runner.py and schedule/compile.py. Only `i18n.stamp` is localised, and only where a person reads it -- with the *format* translatable as well as the words, because "26. septembra 2026" is a different pattern rather than the same one with different words in it. A process locale is not an option: global, not thread-safe, two people's pages at once. A second fragment telling models to answer in the reader's language was written during this work and removed: `core.style` has said it since long before, and test_an_empty_override_turns_a_fragment_off caught the duplicate. THE BULK PASS 1213 sites wrapped by a one-off script that only touched patterns it could not misread. It got three wrong in a way that mattered -- `t('…')` inside `attr="…"` where the sentence held an apostrophe, closing the Jinja string and 500ing two pages whose partials no test renders. tests/test_translations.py now compiles all 110 templates. It also wrapped the product's own name, an SSH key header, a keystroke hint and an example URL, all taken back out: a string is not translatable just because it is a string. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 30 + pyproject.toml | 7 + scripts/i18n_extract.py | 156 ++ src/lembas/__init__.py | 2 +- src/lembas/api/admin.py | 9 + src/lembas/api/deps.py | 20 +- src/lembas/api/pages.py | 13 +- src/lembas/api/preferences.py | 24 + src/lembas/main.py | 10 + src/lembas/services/settings_store.py | 5 + src/lembas/web/i18n/__init__.py | 282 +++ src/lembas/web/i18n/sk.py | 1778 +++++++++++++++++ .../web/templates/admin/_connection_row.html | 28 +- src/lembas/web/templates/admin/_layout.html | 38 +- src/lembas/web/templates/admin/_mcp_row.html | 6 +- .../web/templates/admin/_prompt_field.html | 8 +- .../web/templates/admin/_prompt_preview.html | 13 +- .../web/templates/admin/_tool_test.html | 2 +- src/lembas/web/templates/admin/agents.html | 306 +-- src/lembas/web/templates/admin/audio.html | 74 +- .../web/templates/admin/connections.html | 23 +- .../web/templates/admin/customization.html | 79 +- .../web/templates/admin/extraction.html | 107 +- src/lembas/web/templates/admin/general.html | 81 +- .../web/templates/admin/group_detail.html | 45 +- src/lembas/web/templates/admin/groups.html | 21 +- src/lembas/web/templates/admin/images.html | 166 +- src/lembas/web/templates/admin/mcp.html | 13 +- .../web/templates/admin/mcp_detail.html | 66 +- .../web/templates/admin/model_detail.html | 146 +- src/lembas/web/templates/admin/models.html | 42 +- src/lembas/web/templates/admin/prompts.html | 59 +- src/lembas/web/templates/admin/schedules.html | 55 +- src/lembas/web/templates/admin/search.html | 70 +- .../web/templates/admin/suggestions.html | 53 +- .../web/templates/admin/tool_detail.html | 118 +- src/lembas/web/templates/admin/tools.html | 20 +- src/lembas/web/templates/admin/updates.html | 49 +- .../web/templates/admin/user_detail.html | 77 +- src/lembas/web/templates/admin/users.html | 24 +- .../web/templates/admin/workflow_detail.html | 41 +- src/lembas/web/templates/agents/_browse.html | 6 +- src/lembas/web/templates/agents/_check.html | 10 +- src/lembas/web/templates/agents/detail.html | 54 +- src/lembas/web/templates/agents/index.html | 24 +- src/lembas/web/templates/auth/login.html | 6 +- src/lembas/web/templates/auth/register.html | 14 +- src/lembas/web/templates/base.html | 2 +- .../web/templates/chat/_attachment_error.html | 2 +- src/lembas/web/templates/chat/_base_chip.html | 2 +- src/lembas/web/templates/chat/_canvas.html | 10 +- .../web/templates/chat/_canvas_conflict.html | 8 +- .../web/templates/chat/_canvas_doc.html | 6 +- .../web/templates/chat/_canvas_inner.html | 6 +- src/lembas/web/templates/chat/_composer.html | 64 +- src/lembas/web/templates/chat/_edit_form.html | 12 +- src/lembas/web/templates/chat/_inspector.html | 6 +- .../web/templates/chat/_inspector_body.html | 33 +- .../web/templates/chat/_interaction.html | 29 +- src/lembas/web/templates/chat/_jobs_chip.html | 2 +- .../web/templates/chat/_jobs_panel.html | 8 +- .../web/templates/chat/_mention_picker.html | 18 +- src/lembas/web/templates/chat/_message.html | 20 +- .../web/templates/chat/_model_picker.html | 8 +- src/lembas/web/templates/chat/_plan.html | 8 +- src/lembas/web/templates/chat/_terminal.html | 26 +- src/lembas/web/templates/chat/_thread.html | 5 +- .../web/templates/chat/_tool_activity.html | 6 +- src/lembas/web/templates/chat/_usage.html | 19 +- src/lembas/web/templates/chat/index.html | 67 +- src/lembas/web/templates/folders/edit.html | 81 +- src/lembas/web/templates/library/_layout.html | 2 +- src/lembas/web/templates/library/_share.html | 6 +- .../web/templates/library/_share_panel.html | 21 +- .../web/templates/library/base_detail.html | 32 +- .../web/templates/library/knowledge.html | 26 +- .../templates/library/knowledge_detail.html | 26 +- .../web/templates/library/note_detail.html | 16 +- src/lembas/web/templates/library/notes.html | 12 +- .../web/templates/library/skill_detail.html | 42 +- src/lembas/web/templates/library/skills.html | 15 +- .../web/templates/messages/_history.html | 2 +- src/lembas/web/templates/messages/index.html | 11 +- src/lembas/web/templates/offline.html | 4 +- .../web/templates/partials/_chat_link.html | 10 +- .../web/templates/partials/_folder.html | 14 +- .../templates/partials/_sidebar_actions.html | 4 +- .../templates/partials/_sidebar_close.html | 2 +- .../templates/partials/_sidebar_sections.html | 10 +- .../templates/partials/_sidebar_toggle.html | 2 +- .../web/templates/partials/_sidebar_tree.html | 2 +- .../templates/partials/_voice_options.html | 2 +- .../web/templates/partials/sidebar.html | 16 +- src/lembas/web/templates/reports/detail.html | 6 +- src/lembas/web/templates/reports/index.html | 14 +- src/lembas/web/templates/schedules/_form.html | 52 +- .../web/templates/schedules/_strip.html | 7 +- src/lembas/web/templates/schedules/edit.html | 13 +- src/lembas/web/templates/schedules/index.html | 18 +- src/lembas/web/templates/schedules/new.html | 8 +- src/lembas/web/templates/settings.html | 209 +- src/lembas/web/templating.py | 16 + tests/test_sidebar_drawer.py | 7 +- tests/test_translations.py | 266 +++ 104 files changed, 3833 insertions(+), 1788 deletions(-) create mode 100644 scripts/i18n_extract.py create mode 100644 src/lembas/web/i18n/__init__.py create mode 100644 src/lembas/web/i18n/sk.py create mode 100644 tests/test_translations.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 3df0c7d..6501bbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,36 @@ for 1.0.0 have something to be assembled from. ## Unreleased +## 1.7.0 + +- **The interface speaks Slovak.** Pick a language under **Appearance** in your + own settings, or set what everybody else gets under Admin → General. Your own + choice wins over the instance's, it is saved to your account rather than to one + browser, and `` finally says what the page is actually in. + + All 969 translatable strings are translated, including the long explanatory + paragraphs on the admin pages — there is no half-done corner where a Slovak + instance falls back to English. Dates follow too: a month name is a month name + in the language you are reading, which `strftime` cannot do without a process + locale that this application must not set. + + **What is deliberately still in English**: everything a *model* reads. The + prompt fragments under Admin → Prompts are instructions written for models, and + translating them would change what the models are told rather than what you + see. Models answer in whatever language you write to them in — they already + did, and that line is editable where all the others are. + + An English instance is byte-for-byte what shipped in 1.6.0. That is a property + of the design rather than a claim: a string with no translation renders the + English it was written in, so a language added later cannot leave holes in a + page. + +- Fixed: **every page was rendered in the instance's language, whatever anybody + had chosen.** Found while building the above and worth naming because it would + have been invisible: the language was resolved in a dependency that FastAPI runs + in a threadpool, and the context it was set in is discarded on the way out. Now + it is resolved in the request's own task. + ## 1.6.0 - **A chat can have a crowd.** Switch it on under Admin → Agents, and each chat's diff --git a/pyproject.toml b/pyproject.toml index ff53d20..2e5c3c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,6 +81,13 @@ src = ["src", "tests"] select = ["E", "F", "I", "UP", "B", "SIM", "C4"] ignore = ["B008"] # FastAPI Depends() in defaults is idiomatic +[tool.ruff.lint.per-file-ignores] +# A translation catalogue is keyed by the English sentence, and a sentence cannot +# be rewrapped without becoming a different key. Wrapping them would mean every +# key spelled as an implicit concatenation, which is both unreadable and one +# stray space away from a silent miss. +"src/lembas/web/i18n/*.py" = ["E501"] + [tool.pytest.ini_options] testpaths = ["tests"] # Registered so `-m "not slow"` works and an unknown-marker warning does not diff --git a/scripts/i18n_extract.py b/scripts/i18n_extract.py new file mode 100644 index 0000000..a974e80 --- /dev/null +++ b/scripts/i18n_extract.py @@ -0,0 +1,156 @@ +"""Find every translatable string, and say what the catalogues are missing. + +Run it: + + python scripts/i18n_extract.py # a report + python scripts/i18n_extract.py --write sk # fill sk.py with what is missing + +A development instrument, like `shoot.py` and `fetch_vendor.py`: nothing in `src/` +imports it. What it knows is the one thing a catalogue keyed on source text cannot +know for itself -- that an English sentence has been edited, leaving its +translation stranded under the old wording. `tests/test_translations.py` asserts +the same property from the other side, so a catalogue cannot rot quietly. + +The scan is deliberately simple: `t("…")` and `t('…')`, in templates and in +Python. A string built by concatenation or an f-string is not found, and that is +the point -- a sentence assembled from pieces cannot be translated, because the +order of the pieces is not the same in every language. Use `t("… %(name)s …", +name=…)`. +""" + +from __future__ import annotations + +import re +import sys +from pathlib import Path + +REPO = Path(__file__).resolve().parent.parent +SRC = REPO / "src/lembas" +TEMPLATES = SRC / "web/templates" +CATALOGUES = SRC / "web/i18n" + +# `t("…")` with either quote, allowing escaped quotes inside. Multi-line, because +# a paragraph in a template is wrapped for the width of the file. +CALL = re.compile(r"""\bt\(\s*(?P["'])(?P(?:\\.|(?!\1).)*?)\1""", re.S) + +# `i18n.stamp(value, "%d %B %Y")` -- the *format* is translated too, so a language +# that puts the day first, or wants a full stop after it, says so in the +# catalogue. A second pattern rather than a looser first one: widening `t(` to +# "any call with a string in it" would sweep up every `select("…")` in the +# codebase. +STAMP = re.compile( + # One level of nesting allowed in the first argument, because it is usually a + # call: `stamp(clock.now_for(user), "…")`. + r"""\bstamp\((?:[^()"']|\([^()]*\))*,\s*(?P["'])(?P(?:\\.|(?!\1).)*?)\1""", + re.S, +) + + +def normalise(text: str) -> str: + """The key: whitespace collapsed, escapes resolved. + + A template wraps one sentence across three lines, and the same sentence in a + Python file across two. Keying on the exact bytes would need an entry per + wrapping, so the key is the text with its runs of whitespace flattened -- which + is exactly what `i18n.translate` looks up. + """ + text = text.replace('\\"', '"').replace("\\'", "'").replace("\\n", " ") + return " ".join(text.split()) + + +def sources() -> list[Path]: + files = sorted(TEMPLATES.rglob("*.html")) + files += [ + path + for path in sorted(SRC.rglob("*.py")) + if "web/i18n" not in str(path) and "__pycache__" not in str(path) + ] + return files + + +JINJA_COMMENT = re.compile(r"\{#.*?#\}", re.S) +PY_COMMENT = re.compile(r"^[ \t]*#.*$", re.M) + + +def strip_comments(path: Path, text: str) -> str: + """Comments are not strings. This file's own docstrings quote `t("Save")`, and + so does `web/templating.py`'s comment explaining the global -- both would + otherwise arrive in the catalogue as things to translate.""" + if path.suffix == ".html": + return JINJA_COMMENT.sub("", text) + return PY_COMMENT.sub("", text) + + +def found() -> dict[str, list[str]]: + """Every string, with the files it appears in.""" + out: dict[str, list[str]] = {} + for path in sources(): + text = strip_comments(path, path.read_text(encoding="utf-8")) + for match in list(CALL.finditer(text)) + list(STAMP.finditer(text)): + key = normalise(match.group("text")) + if not key: + continue + out.setdefault(key, []) + where = str(path.relative_to(REPO)) + if where not in out[key]: + out[key].append(where) + return out + + +def catalogue(code: str) -> dict[str, str]: + path = CATALOGUES / f"{code}.py" + if not path.exists(): + return {} + namespace: dict[str, object] = {} + exec(compile(path.read_text(encoding="utf-8"), str(path), "exec"), namespace) + return dict(namespace.get("MESSAGES", {})) # type: ignore[arg-type] + + +def report() -> int: + strings = found() + print(f"{len(strings)} translatable strings in {len(sources())} files") + for code in ("sk",): + have = catalogue(code) + missing = [key for key in strings if key not in have] + orphans = [key for key in have if key not in strings] + done = len(strings) - len(missing) + print( + f" {code}: {done}/{len(strings)} translated" + f" ({len(missing)} missing, {len(orphans)} orphaned)" + ) + for key in orphans[:10]: + print(f" orphan: {key[:80]!r}") + return 0 + + +def write(code: str) -> int: + """Append the missing keys to a catalogue, each mapped to itself. + + Mapped to the English rather than to "" on purpose: an empty translation would + render as an empty paragraph, while the English renders as what it already + said. A catalogue half-filled is a page half-translated, never a page with + holes in it. + """ + strings = found() + have = catalogue(code) + missing = [key for key in strings if key not in have] + if not missing: + print(f"{code}: nothing missing") + return 0 + path = CATALOGUES / f"{code}.py" + with path.open("a", encoding="utf-8") as handle: + handle.write(f"\n# --- {len(missing)} added by scripts/i18n_extract.py ---\n") + for key in missing: + handle.write(f"MESSAGES[{key!r}] = {key!r}\n") + print(f"{code}: added {len(missing)} keys to {path.relative_to(REPO)}") + return 0 + + +def main() -> int: + if "--write" in sys.argv: + return write(sys.argv[sys.argv.index("--write") + 1]) + return report() + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/lembas/__init__.py b/src/lembas/__init__.py index a8f6de3..92e4d79 100644 --- a/src/lembas/__init__.py +++ b/src/lembas/__init__.py @@ -1,3 +1,3 @@ """LLeMbas - a Middle-earth themed web UI for OpenAI-compatible LLM endpoints.""" -__version__ = "1.6.0" +__version__ = "1.7.0" diff --git a/src/lembas/api/admin.py b/src/lembas/api/admin.py index 993a5cd..709e4e3 100644 --- a/src/lembas/api/admin.py +++ b/src/lembas/api/admin.py @@ -16,6 +16,7 @@ from lembas.db.models import Connection, Model, User from lembas.services import settings_store from lembas.services.crypto import UNCHANGED_SENTINEL, decrypt, encrypt, mask from lembas.services.llm.openai_client import Endpoint, LLMError, context_from, list_models +from lembas.web import i18n from lembas.web.templating import render log = logging.getLogger(__name__) @@ -46,6 +47,7 @@ async def general_page(request: Request, db: Db, user: AdminUser, saved: bool = "admin/general.html", { "values": settings_store.get_group(db), + "languages": i18n.LANGUAGES, "saved": saved, "user_count": db.scalar(select(func.count()).select_from(User)), }, @@ -57,6 +59,7 @@ async def save_general( db: Db, user: AdminUser, allow_signup: bool = Form(False), + language: str = Form(""), system_prompt: str = Form(""), compact_threshold: int = Form(95), max_chat_rounds: int = Form(5), @@ -70,6 +73,9 @@ async def save_general( db, { "allow_signup": allow_signup, + # Validated rather than trusted: a code this release does not have + # would leave every page in a language nobody chose. + "language": i18n.known(language), "system_prompt": system_prompt.strip()[:8000], # 0 is "never"; anything else is clamped into a band where it can # do some good. 100 is useless -- you cannot compact after @@ -82,6 +88,9 @@ async def save_general( "max_chat_rounds": min(max(max_chat_rounds, 0), 100), }, ) + # The instance default is cached at process level, exactly as branding is, so + # the one module that writes it is the one that drops the cache. + i18n.forget() log.info("registration %s by %s", "opened" if allow_signup else "closed", user.email) return RedirectResponse("/admin/general?saved=1", status_code=status.HTTP_303_SEE_OTHER) diff --git a/src/lembas/api/deps.py b/src/lembas/api/deps.py index 7e86b4b..28c6398 100644 --- a/src/lembas/api/deps.py +++ b/src/lembas/api/deps.py @@ -13,6 +13,7 @@ from starlette.requests import HTTPConnection from lembas.db.models import User from lembas.db.session import get_session_factory from lembas.security.sessions import COOKIE_NAME, resolve_session +from lembas.web import i18n def get_db() -> Iterator[DBSession]: @@ -27,9 +28,21 @@ def get_db() -> Iterator[DBSession]: Db = Annotated[DBSession, Depends(get_db)] -def get_current_user(conn: HTTPConnection, db: Db) -> User | None: +async def get_current_user(conn: HTTPConnection, db: Db) -> User | None: """Resolve the session cookie to a user, or None when signed out. + ⚠ `async def`, and that is load-bearing rather than tidy. FastAPI runs a + *sync* dependency in a threadpool, and `i18n.activate` below sets a + `ContextVar` -- which anyio copies **into** the thread and discards on the way + out, so the language was set in a context nothing else could see and every + page rendered in English however anybody's preference was stored. An async + dependency is awaited in the request's own task, where the value survives to + the render. + + What it costs is one indexed SELECT on the event loop rather than in a + thread, which is what every route in this application already does with its + session. + Cached on the connection's state so several dependencies in one request do not each hit the sessions table. @@ -44,6 +57,11 @@ def get_current_user(conn: HTTPConnection, db: Db) -> User | None: return cached user = resolve_session(db, conn.cookies.get(COOKIE_NAME)) conn.state.user = user + # The language this request renders in, set here because this is where the + # person is already known -- no second session and no second cookie read. A + # request that never resolves a user keeps whatever `LanguageMiddleware` set, + # which is the instance default. + i18n.activate(i18n.for_user(user)) return user diff --git a/src/lembas/api/pages.py b/src/lembas/api/pages.py index 80c1efa..18287d3 100644 --- a/src/lembas/api/pages.py +++ b/src/lembas/api/pages.py @@ -33,6 +33,7 @@ from lembas.services import settings_store from lembas.services import suggestions as suggestions_service from lembas.services.library import documents as documents_service from lembas.services.schedule import clock +from lembas.web import i18n from lembas.web.templating import STATIC_DIR, render router = APIRouter(tags=["pages"]) @@ -909,10 +910,20 @@ async def settings_page( "impressions": personas_service.impressions_for(db, user), # Sorted rather than left in set order, because a list of six # hundred zones that is not alphabetical is one nobody can use. + "languages": i18n.LANGUAGES, + # Their own choice, and what "follow the instance" currently means -- + # named rather than left blank, because "follow the instance" is only a + # useful option if you can see what you would be following. + "chosen_language": str((user.settings_json or {}).get("language") or ""), + "instance_language": dict(i18n.LANGUAGES).get( + i18n.instance_default(), i18n.instance_default() + ), "timezones": sorted(available_timezones()), "timezone": clock.name_for(user), "server_timezone": str(clock.server_zone()), - "local_now": clock.now_for(user).strftime("%H:%M on %A %-d %B"), + # Through `i18n.stamp`, not `strftime`: `%A` and `%B` are C-locale + # English whatever the page is in, and this one is read by a person. + "local_now": i18n.stamp(clock.now_for(user), "%H:%M on %A %-d %B"), **context, **sidebar_context(db, user), }, diff --git a/src/lembas/api/preferences.py b/src/lembas/api/preferences.py index d927473..0bf1e3b 100644 --- a/src/lembas/api/preferences.py +++ b/src/lembas/api/preferences.py @@ -13,6 +13,7 @@ from lembas.config import settings from lembas.security.passwords import hash_password, validate_password, verify_password from lembas.security.sessions import COOKIE_NAME, create_session, revoke_all_for_user from lembas.services.schedule import clock +from lembas.web import i18n log = logging.getLogger(__name__) @@ -66,6 +67,29 @@ async def set_timezone(db: Db, user: RequiredUser, timezone: str = Form("")) -> return RedirectResponse("/settings?saved=timezone", status_code=status.HTTP_303_SEE_OTHER) +@router.post("/language") +async def set_language(db: Db, user: RequiredUser, language: str = Form("")) -> Response: + """Which language this person sees the interface in. + + Empty is a real answer -- "whatever the instance is set to" -- rather than an + unset field, which is why it is stored as "" rather than removed. The same + shape the timezone above uses, and for the same reason: absent and "follow the + default" are different states, and a form cannot tell them apart otherwise. + + Unlike the theme and the layout this needs no `localStorage` tier. Those two + exist there because a paint that starts in the wrong theme flashes; text is + rendered on the server and cannot. + """ + chosen = (language or "").strip().lower() + if chosen and chosen not in i18n.LANGUAGE_IDS: + return RedirectResponse( + "/settings?error=language", status_code=status.HTTP_303_SEE_OTHER + ) + user.settings_json = {**(user.settings_json or {}), "language": chosen} + db.commit() + return RedirectResponse("/settings?saved=language", status_code=status.HTTP_303_SEE_OTHER) + + # Which CSS variables a browser is allowed to set from here, and how far. An # open dict would let a page store anything under somebody's account and have # it read back on every load; a width outside these bounds would hand them a diff --git a/src/lembas/main.py b/src/lembas/main.py index ab35ff2..ee79c6e 100644 --- a/src/lembas/main.py +++ b/src/lembas/main.py @@ -49,6 +49,7 @@ from lembas.api.deps import RedirectToLogin, is_htmx, login_redirect from lembas.config import settings from lembas.db.session import init_db from lembas.services.library import indexing +from lembas.web import i18n from lembas.web.templating import STATIC_DIR, render log = logging.getLogger("lembas") @@ -177,6 +178,15 @@ def create_app() -> FastAPI: app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static") + # The language every request starts in. A `ContextVar` is per task, and a task + # is reused between requests -- so without resetting it here, a signed-out page + # would inherit whichever person was served last on that worker. The user's own + # choice is applied later, by `get_current_user`, where they are already known. + @app.middleware("http") + async def _language(request, call_next): + i18n.activate(i18n.instance_default()) + return await call_next(request) + # One place that notices a library record changing, rather than a call in # each of the ten writers that touch those tables. Idempotent, because the # factory is called per test. See services/library/indexing.py:install. diff --git a/src/lembas/services/settings_store.py b/src/lembas/services/settings_store.py index 706d786..b9e8ec1 100644 --- a/src/lembas/services/settings_store.py +++ b/src/lembas/services/settings_store.py @@ -40,6 +40,11 @@ EXTRACTION = "extraction" def _general_defaults() -> dict[str, Any]: return { "allow_signup": env_settings.allow_signup, + # What the interface is rendered in when a person has not chosen. Empty + # and "en" mean the same thing; `web/i18n.known` is what decides, so a + # value from a release that offered more languages than this one cannot + # leave somebody with a page nobody can read. + "language": "", # When on, new accounts land in the `pending` role and cannot sign in # until an administrator approves them. Reserved for the users pass. "require_approval": False, diff --git a/src/lembas/web/i18n/__init__.py b/src/lembas/web/i18n/__init__.py new file mode 100644 index 0000000..7c62f1d --- /dev/null +++ b/src/lembas/web/i18n/__init__.py @@ -0,0 +1,282 @@ +"""Translating the interface, without a build step. + +## Why not gettext + +`.po` files compiled to `.mo` are a build step, and this project does not have +one (hard rule 1). So a catalogue is a committed Python module: a dict, keyed by +**the English source text**, loaded at import. + +Keying on the source has one large advantage and one cost, and the advantage is +what decides it: **a missing entry renders the key**, which is the English. An +untranslated string therefore looks exactly as it did before, an instance running +in English is byte-for-byte what shipped, and a half-finished catalogue degrades +into a half-translated page rather than into `settings.appearance.theme.label` +written across somebody's screen. The cost is that editing an English sentence +orphans its translation silently -- which is what +`tests/test_translations.py` exists to catch, in both directions. + +This is the shape `branding.FLAVOUR` and `services/prompts.py` already use: +defaults in code, overrides beside them, and a test that the two agree. + +## Why a ContextVar + +`t()` has to be reachable from a Jinja **global**, not from the template context. +`web/templating.render` is bypassed by 25 direct `TemplateResponse` calls and 8 +`get_template().render()` calls, and the second group is the SSE frame path, which +has no `Request` object at all -- so threading a language through the context +would leave a third of the application untranslated, and `brand`'s docstring +records that lesson already. + +But a global is bound once at import and the language is **per person**, so the +active language cannot live in the global. It lives in a `ContextVar` that +`LanguageMiddleware` sets per request. That is the one piece of genuinely new +machinery here; `brand` avoids needing it only because instance branding is the +same for everybody. + +⚠ A `ContextVar` is per *task*, and a background reply is a task of its own. It +therefore does not inherit a request's language, which is correct rather than +unfortunate: nothing a generation writes is interface text, and the one place it +matters -- a fragment telling a model which language to answer in -- is a prompt +variable, not a `t()` call. +""" + +from __future__ import annotations + +import logging +from collections.abc import Mapping +from contextvars import ContextVar + +log = logging.getLogger(__name__) + +# The language every string is written in, and the key every catalogue uses. +SOURCE = "en" + +# What an instance may be set to. Ordered, because it is also the order the +# settings screens offer. +LANGUAGES: tuple[tuple[str, str], ...] = ( + ("en", "English"), + ("sk", "Slovenčina"), +) + +LANGUAGE_IDS = tuple(code for code, _name in LANGUAGES) + +# Text direction, so `` is answered from one place when a +# right-to-left language is added rather than being forgotten. +DIRECTIONS: Mapping[str, str] = {"en": "ltr", "sk": "ltr"} + +_active: ContextVar[str] = ContextVar("lembas_language", default=SOURCE) + +# Loaded lazily and cached: a catalogue is a module, and importing every language +# at start would read files an instance in English never needs. +_catalogues: dict[str, Mapping[str, str]] = {} + + +# The instance default, cached at process level exactly as `branding.snapshot()` +# is and invalidated the same way -- by the one module that writes it calling +# `forget()`. Without the cache, every request would need a settings read before +# it could decide what language to render in, including the ones that never touch +# the database otherwise. +_DEFAULT: str | None = None + + +def instance_default() -> str: + """What this instance renders in when nobody has said otherwise. + + Never raises: a language that cannot be read is not a reason to fail a page, + and English is a usable answer. The same argument `branding.snapshot` makes. + """ + global _DEFAULT + if _DEFAULT is not None: + return _DEFAULT + try: + from lembas.db.session import session_scope + from lembas.services import settings_store + + with session_scope() as db: + _DEFAULT = known(settings_store.get(db, "language")) + except Exception: # noqa: BLE001 - defaults are a usable answer + log.debug("could not read the instance language; using %s", SOURCE, exc_info=True) + return SOURCE + return _DEFAULT + + +def forget() -> None: + """Drop the cached instance default. Called by whoever saves it.""" + global _DEFAULT + _DEFAULT = None + + +def for_user(user) -> str: + """The language one person sees: their own choice, else the instance's. + + A `User` or None, so a signed-out page -- the sign-in screen, an error page -- + is rendered in the instance's language rather than in English by accident. + """ + chosen = "" + if user is not None: + chosen = str((getattr(user, "settings_json", None) or {}).get("language") or "") + return known(chosen) if chosen else instance_default() + + +def known(code: str | None) -> str: + """A language this application has, from whatever was stored or requested.""" + value = (code or "").strip().lower() + if value in LANGUAGE_IDS: + return value + # A stored value from a release that offered more languages than this one, or + # a hand-edited row. English rather than an error: a preference nobody can + # satisfy is not a reason to refuse somebody their settings page. + return SOURCE + + +def catalogue(code: str) -> Mapping[str, str]: + """Every translation for one language, keyed by its English source.""" + code = known(code) + if code == SOURCE: + return {} + if code not in _catalogues: + try: + module = __import__(f"lembas.web.i18n.{code}", fromlist=["MESSAGES"]) + _catalogues[code] = dict(getattr(module, "MESSAGES", {})) + except Exception: # noqa: BLE001 - a broken catalogue must not break the page + log.exception("could not load the %s catalogue", code) + _catalogues[code] = {} + return _catalogues[code] + + +def active() -> str: + return _active.get() + + +def activate(code: str | None) -> str: + """Set the language for this request. Returns what was actually set.""" + code = known(code) + _active.set(code) + return code + + +def direction(code: str | None = None) -> str: + return DIRECTIONS.get(known(code) if code else active(), "ltr") + + +def translate(text: str, code: str | None = None) -> str: + """One string in the active language, or the English it was written in. + + Whitespace is collapsed for the *lookup* and not for the output. A template + wraps its prose across lines for the width of the file, so the same sentence + reaches here with different newlines in it depending on where it sits -- and a + catalogue keyed on the exact bytes would need an entry per wrapping. What is + returned is the translation as written in the catalogue, or the original text + untouched. + """ + if not text: + return text + entries = catalogue(code or active()) + if not entries: + return text + return entries.get(" ".join(text.split()), text) + + +def t(text: str, **fields: object) -> str: + """The function templates and routes call. + + `t("Saved %(name)s", name=x)` rather than an f-string, because a translator + needs the whole sentence and because the order of the parts is not the same in + every language. Percent-named rather than `str.format`, so a stray brace in a + translation cannot raise. + """ + out = translate(text) + if not fields: + return out + try: + return out % fields + except (KeyError, TypeError, ValueError): + # A catalogue whose placeholders do not match the source is a bug in the + # catalogue, and the English sentence is a better answer than a traceback + # in the middle of somebody's page. + log.warning("placeholder mismatch translating %r", text[:60]) + try: + return text % fields + except (KeyError, TypeError, ValueError): + return text + + +__all__ = [ + "DIRECTIONS", + "LANGUAGES", + "LANGUAGE_IDS", + "SOURCE", + "activate", + "active", + "catalogue", + "direction", + "forget", + "for_user", + "instance_default", + "known", + "stamp", + "t", + "translate", +] + + +# --- Dates --------------------------------------------------------------------- +# +# `strftime("%A")` and `%B` emit C-locale English whatever the page is in, which is +# invisible until the day a second language ships and then wrong on every screen +# showing a date. Setting a process locale is not an option: it is global, it is +# not thread-safe, and this application renders two people's pages at once. +# +# So the names are a table and the *format* is itself translatable -- Slovak wants +# "26. septembra 2026", not "26 September 2026", and that is a different pattern +# rather than different words in the same one. +# +# ⚠ Only for what a **person** reads. `harness.py`, `schedule/runner.py` and +# `schedule/compile.py` all put dates in front of a *model*, and those stay +# English: the prompts are written in English, a model is not the reader, and a +# background task has no request language anyway. +MONTHS: Mapping[str, Mapping[str, str]] = { + "sk": { + "January": "januára", + "February": "februára", + "March": "marca", + "April": "apríla", + "May": "mája", + "June": "júna", + "July": "júla", + "August": "augusta", + "September": "septembra", + "October": "októbra", + "November": "novembra", + "December": "decembra", + } +} + +DAYS: Mapping[str, Mapping[str, str]] = { + "sk": { + "Monday": "pondelok", + "Tuesday": "utorok", + "Wednesday": "streda", + "Thursday": "štvrtok", + "Friday": "piatok", + "Saturday": "sobota", + "Sunday": "nedeľa", + } +} + + +def stamp(value, fmt: str) -> str: + """One date, in the reader's language. + + `fmt` is an English `strftime` pattern and is translated like any other + string, so a language that puts the day before the month -- or wants a full + stop after it -- says so in the catalogue rather than here. + """ + if value is None: + return "" + code = active() + text = value.strftime(translate(fmt, code)) + for table in (MONTHS.get(code, {}), DAYS.get(code, {})): + for english, local in table.items(): + text = text.replace(english, local) + return text diff --git a/src/lembas/web/i18n/sk.py b/src/lembas/web/i18n/sk.py new file mode 100644 index 0000000..82fcf3b --- /dev/null +++ b/src/lembas/web/i18n/sk.py @@ -0,0 +1,1778 @@ +"""Slovenčina. + +Keyed by the English source text, as `web/i18n/__init__.py` explains: a missing +entry renders the English, so a half-finished catalogue is a half-translated page +rather than a page with `settings.theme.label` written across it. + +Two conventions, kept for consistency rather than because Slovak demands them: + +* **Buttons and menu items are infinitives** -- "Uložiť", "Zmazať", "Poslať" -- + because a button says what pressing it does. A label above a field is a noun. +* **Second person plural for the reader** ("vy"), which is what a tool somebody + else may also use should say. The models are spoken of in the third person. + +⚠ `%(name)s` placeholders must survive. `i18n.t` falls back to the English +sentence if a translation's placeholders do not match, so a mistake here shows up +as an untranslated line rather than as a traceback -- but it still shows up. +""" + +from __future__ import annotations + +MESSAGES: dict[str, str] = {} + +# --- Signing in, and the shell ------------------------------------------------ +MESSAGES.update( + { + "Email": "E-mail", + "Password": "Heslo", + "Sign in": "Prihlásiť sa", + "Sign out": "Odhlásiť sa", + "Create your account.": "Vytvorte si účet.", + "Nobody has claimed this instance yet. The first account becomes its administrator.": ( + "Túto instanciu si ešte nikto nepriradil. Prvý účet sa stáva jej správcom." + ), + "At least 8 characters.": "Najmenej 8 znakov.", + "Current password": "Súčasné heslo", + "Confirm new password": "Potvrdenie nového hesla", + "Change password": "Zmeniť heslo", + "Every other session is signed out when the password changes. You stay signed in here.": ( + "Pri zmene hesla sa odhlásia všetky ostatné prihlásenia. Tu zostanete prihlásení." + ), + "Admin settings": "Nastavenia správy", + "Your settings": "Vaše nastavenia", + "Settings sections": "Časti nastavení", + "People": "Ľudia", + "Toggle": "Prepnúť", + "Toggle sidebar": "Prepnúť bočný panel", + "Close sidebar": "Zavrieť bočný panel", + "More": "Viac", + "Cancel": "Zrušiť", + "Reset": "Obnoviť", + "Reset this": "Obnoviť toto", + "Revert": "Vrátiť zmenu", + "Try again": "Skúsiť znova", + "Dismiss": "Zavrieť", + "Accept": "Prijať", + "Discard": "Zahodiť", + "Stop": "Zastaviť", + "Check": "Overiť", + "Check it": "Overiť to", + "Upload": "Nahrať", + "Attach": "Priložiť", + "Delete this": "Zmazať toto", + "Forget": "Zabudnúť", + "Forget this": "Zabudnúť toto", + "Forget this?": "Zabudnúť toto?", + "Remember": "Zapamätať", + "Send now": "Poslať teraz", + "Send to chat": "Poslať do konverzácie", + "Save page": "Uložiť stránku", + "Empty": "Prázdne", + "Details": "Podrobnosti", + "Title": "Názov", + "Message": "Správa", + "Text": "Text", + "File": "Súbor", + "Image": "Obrázok", + "Kind": "Druh", + "When": "Kedy", + "At": "V", + "Once": "Raz", + "Inside": "Vnútri", + "there": "tam", + "nobody": "nikto", + "new": "nové", + "root": "root", + "password": "heslo", + "shared": "zdieľané", + "shared with you": "zdieľané s vami", + "not allowed": "nepovolené", + "not checked": "neoverené", + "paused": "pozastavené", + "scheduled": "naplánované", + "remembered": "zapamätané", + "set up for you": "nastavené pre vás", + "its own words": "jeho vlastné slová", + "written by a model": "napísal model", + "edited by you": "upravili ste vy", + "editing": "upravuje sa", + "writing": "píše", + "complete": "hotové", + "error": "chyba", + "failed": "zlyhalo", + "estimated": "odhad", + "no text": "bez textu", + "key confirmed": "kľúč overený", + "weekly-report": "weekly-report", + } +) + +# --- The conversation ---------------------------------------------------------- +MESSAGES.update( + { + "Chat": "Konverzácia", + "Chats": "Konverzácie", + "Chat settings": "Nastavenia konverzácie", + "Agent chat": "Agentská konverzácia", + "Kind of chat": "Druh konverzácie", + "New chat here": "Nová konverzácia tu", + "New chat in this folder": "Nová konverzácia v tejto zložke", + "New folder": "Nová zložka", + "Folder name": "Názov zložky", + "Folder settings": "Nastavenia zložky", + "Rename chat": "Premenovať konverzáciu", + "Rename folder": "Premenovať zložku", + "Delete chat": "Zmazať konverzáciu", + "Delete folder": "Zmazať zložku", + "Up a level": "O úroveň vyššie", + "Temporary": "Dočasná", + "Temporary chat": "Dočasná konverzácia", + "Temporary — removed 24 hours after the last message": ( + "Dočasná — zmizne 24 hodín po poslednej správe" + ), + "Not listed in the sidebar, and removed 24 hours after the last message.": ( + "Nie je v bočnom paneli a zmizne 24 hodín po poslednej správe." + ), + "Which chats to show": "Ktoré konverzácie zobraziť", + "Nothing said yet": "Zatiaľ nič nepovedané", + "Nothing here.": "Nič tu nie je.", + "Nothing was found.": "Nič sa nenašlo.", + "Nothing is running.": "Nič nebeží.", + "Nothing has been answered in this chat yet.": ( + "V tejto konverzácii ešte nič nebolo odpovedané." + ), + "Last reply": "Posledná odpoveď", + "What would you ask?": "Na čo sa chcete spýtať?", + "Say it in your own words": "Povedzte to vlastnými slovami", + "/ for commands": "/ pre príkazy", + "Dictate a message": "Nadiktovať správu", + "Drop to attach": "Pustite sem na priloženie", + "Images, PDFs and text. A PDF has its text read once, now.": ( + "Obrázky, PDF a text. Z PDF sa text prečíta raz, hneď teraz." + ), + "PDF, text, code": "PDF, text, kód", + "Sent only to vision models": "Posiela sa len modelom, ktoré vidia obrázky", + "The generated image": "Vygenerovaný obrázok", + "Copy message": "Skopírovať správu", + "Edit message": "Upraviť správu", + "Edit and retry from here": "Upraviť a pokračovať odtiaľto", + "Rewind and send": "Vrátiť sa a poslať", + "Rewind the conversation": "Vrátiť konverzáciu", + "Regenerate reply": "Vygenerovať odpoveď znova", + "Discard this message?": "Zahodiť túto správu?", + "Read this reply aloud": "Prečítať túto odpoveď nahlas", + "Thinking": "Premýšľa", + "Compact": "Zhrnúť", + "Compacted": "Zhrnuté", + "Summarise the earlier turns so they stop costing context": ( + "Zhrnúť starší priebeh, aby prestal zaberať kontext" + ), + "Earlier turns are summarised. They are still in the transcript; they just stop being sent.": ( + "Starší priebeh je zhrnutý. V zápise zostáva, len sa už neposiela." + ), + "These are no longer sent to the model; a summary of them goes instead. They are kept here so nothing is lost.": ( + "Tieto sa už modelu neposielajú, namiesto nich ide ich zhrnutie. " + "Zostávajú tu, aby sa nič nestratilo." + ), + "The reply could not be completed.": "Odpoveď sa nepodarilo dokončiť.", + "The round ended here": "Tu sa kolo skončilo", + "Crowd": "Skupina", + "The model is asking you": "Model sa vás pýta", + "Your own answer…": "Vaša vlastná odpoveď…", + "Something else": "Niečo iné", + "No opinion": "Bez názoru", + "Ask me about these again": "Znova sa ma na to spýtať", + "Always allow this": "Toto vždy povoliť", + "Always allowed here": "Tu vždy povolené", + "Change this before it runs": "Upraviť to pred spustením", + "Allow runs what is in the box. It is not checked against this chat's rules again — you typed it.": ( + "Povolením sa spustí to, čo je v poli. Znova sa to neporovnáva s pravidlami " + "tejto konverzácie — napísali ste to vy." + ), + "Carry it out": "Vykonať to", + "Switches to Edit — commands still ask.": ( + "Prepne do režimu Úpravy — príkazy sa stále pýtajú." + ), + "What should happen": "Čo sa má stať", + "What was found": "Čo sa našlo", + "What it said:": "Čo povedal:", + "What is there now": "Čo tam je teraz", + "Your version": "Vaša verzia", + "Discard mine and reload": "Zahodiť moje a načítať znova", + "A model wrote this version.": "Túto verziu napísal model.", + "Canvas": "Plocha", + "Close canvas": "Zavrieť plochu", + "Resize the canvas": "Zmeniť veľkosť plochy", + "Open a file beside the conversation": "Otvoriť súbor pri konverzácii", + "This file is empty.": "Tento súbor je prázdny.", + "No colour while you type. Tab inserts a tab.": ( + "Počas písania bez zvýraznenia. Tabulátor vloží tabulátor." + ), + "Markdown.": "Markdown.", + "Rendered": "Vykreslené", + "Inspect this chat": "Preskúmať túto konverzáciu", + "Request inspector": "Prehliadka požiadavky", + "Close inspector": "Zavrieť prehliadku", + "Request body": "Telo požiadavky", + "System message": "Systémová správa", + "None is being sent.": "Neposiela sa žiadna.", + "Rebuilt now against the current configuration. This is not a recording of the request that produced the last reply — if a prompt or a setting has changed since, this shows what would be sent today.": ( + "Zloží sa teraz podľa súčasného nastavenia. Nie je to záznam požiadavky, " + "ktorá vytvorila poslednú odpoveď — ak sa odvtedy zmenil pokyn alebo " + "nastavenie, vidíte, čo by sa poslalo dnes." + ), + "In the window now": "Teraz v okne", + "Of which sent": "Z toho poslané", + "Of which written": "Z toho napísané", + "Spent in total": "Spolu spotrebované", + "Max tokens": "Najviac tokenov", + "Temperature": "Teplota", + "Top-p": "Top-p", + "Prompt, knowledge, sampling": "Pokyn, znalosti, vzorkovanie", + "Leave a field empty to let the provider decide. Values outside the allowed range are ignored rather than clamped.": ( + "Prázdne pole znamená, že rozhodne poskytovateľ. Hodnoty mimo povoleného " + "rozsahu sa ignorujú, neupravujú sa na najbližšiu možnú." + ), + "Reasoning effort": "Náročnosť uvažovania", + "How hard this model should think": "Ako intenzívne má tento model premýšľať", + "Effort: off": "Náročnosť: vypnutá", + "Endpoint default": "Predvolené podľa endpointu", + "Choose a model": "Vyberte model", + "Filter models": "Filtrovať modely", + "Filter models…": "Filtrovať modely…", + "No model matches that.": "Tomu nezodpovedá žiadny model.", + "No models available": "Žiadne dostupné modely", + "No models are available to you yet.": "Zatiaľ pre vás nie sú dostupné žiadne modely.", + "In the order an administrator arranged them.": "V poradí, ako ich zoradil správca.", + "Available to you": "Dostupné pre vás", + "Default model": "Predvolený model", + "Overrides what the administrator chose, for you only.": ( + "Prebije voľbu správcu, ale len pre vás." + ), + "What a new chat starts as": "Ako začína nová konverzácia", + "What a new chat starts with. Existing chats keep their model.": ( + "S čím začína nová konverzácia. Existujúce konverzácie si svoj model nechávajú." + ), + "Where a chat starts by default. Each chat records its own when it is created, so changing this later does not move a conversation already under way.": ( + "Čím konverzácia predvolene začína. Každá si pri vytvorení zapíše svoj " + "vlastný, takže neskoršia zmena nepresunie už rozbehnutú konverzáciu." + ), + } +) + +# --- Knowledge, notes and skills ---------------------------------------------- +MESSAGES.update( + { + "Library": "Knižnica", + "Knowledge": "Znalosti", + "Knowledge base": "Znalostná báza", + "New knowledge base": "Nová znalostná báza", + "Delete knowledge base": "Zmazať znalostnú bázu", + "No knowledge bases yet": "Zatiaľ žiadne znalostné bázy", + "Knowledge bases are collections of documents, images and saved web pages. Keep them separate — one per subject, project or client — and a chat can be pointed at just the ones it should draw on. Sharing happens here too: share a base and everything in it comes with it.": ( + "Znalostné bázy sú zbierky dokumentov, obrázkov a uložených webových " + "stránok. Držte ich oddelene — jednu na tému, projekt alebo klienta — a " + "konverzáciu potom nasmerujete len na tie, z ktorých má čerpať. Zdieľanie " + "je tiež tu: zdieľaním bázy ide s ňou všetko, čo je v nej." + ), + "Make one below, then put documents in it. A chat with no base attached searches everything you have; a chat pointed at one searches only that.": ( + "Vytvorte jednu nižšie a vložte do nej dokumenty. Konverzácia bez " + "pripojenej bázy prehľadáva všetko, čo máte; konverzácia nasmerovaná na " + "bázu prehľadáva len ju." + ), + "Add to this base": "Pridať do tejto bázy", + "This base": "Táto báza", + "Search this base…": "Hľadať v tejto báze…", + "In your library": "Vo vašej knižnici", + "From your library": "Z vašej knižnice", + "Search only these": "Hľadať len v týchto", + "Scope this chat to this knowledge base": "Zúžiť túto konverzáciu na túto bázu", + "This chat now searches only the bases it is attached to": ( + "Táto konverzácia teraz prehľadáva len pripojené bázy" + ), + "Already in this chat": "Už v tejto konverzácii", + "Delete document": "Zmazať dokument", + "A file": "Súbor", + "A folder inside another inherits its system prompt where it has none of its own.": ( + "Zložka v inej zložke prevezme jej systémový pokyn, ak nemá vlastný." + ), + "A page to read": "Stránka na prečítanie", + "A web page": "Webová stránka", + "Fetch a page and attach its text": "Načítať stránku a priložiť jej text", + "Fetch this page": "Načítať túto stránku", + "Fetched now and kept as text, so it survives the page changing.": ( + "Načíta sa teraz a uloží ako text, takže prežije zmenu stránky." + ), + "Notes": "Poznámky", + "Delete note": "Zmazať poznámku", + "Search notes…": "Hľadať v poznámkach…", + "What this note is about": "O čom je táto poznámka", + "Longer things worth keeping between conversations. A model writes these itself when it works something out, and you can edit or delete any of them — they are yours, not its.": ( + "Dlhšie veci, ktoré sa vyplatí uchovať medzi konverzáciami. Model si ich " + "píše sám, keď na niečo príde, a vy ich môžete upraviť alebo zmazať — sú " + "vaše, nie jeho." + ), + "Skills": "Schopnosti", + "Delete skill": "Zmazať schopnosť", + "Search skills…": "Hľadať v schopnostiach…", + "Offer this skill to models": "Nabídnuť túto schopnosť modelom", + "Turned off, it stays here but disappears from the list the model sees.": ( + "Vypnutá tu zostáva, ale zmizne zo zoznamu, ktorý vidí model." + ), + "Instructions": "Pokyny", + "Markdown. Steps, conventions, things to avoid.": ( + "Markdown. Postup, dohody, čomu sa vyhnúť." + ), + "When to use it": "Kedy ju použiť", + "What this is, and when it is worth reading.": "Čo to je a kedy sa to vyplatí prečítať.", + "The load-bearing field.": "Toto pole je nosné.", + "Shown in search results, so it is what decides whether a model opens it.": ( + "Zobrazuje sa vo výsledkoch hľadania, takže rozhoduje o tom, či to model otvorí." + ), + "Searched along with the contents, so a line here helps a model find it.": ( + "Prehľadáva sa spolu s obsahom, takže jeden riadok tu modelu pomôže to nájsť." + ), + "Lowercase letters, numbers and hyphens. This is how a model asks for it, and it cannot be changed later.": ( + "Malé písmená, číslice a spojovníky. Takto si to model vyžiada a neskôr sa " + "to už nedá zmeniť." + ), + "Saved procedures. Every enabled skill's name and description are shown to the model on each turn; it reads the instructions only when it decides one applies. A model may write and revise its own — every version is kept, so any change can be read and undone.": ( + "Uložené postupy. Názov a opis každej zapnutej schopnosti vidí model v " + "každom kroku; samotné pokyny si prečíta až vtedy, keď usúdi, že sa " + "niektorá hodí. Model si môže písať a upravovať vlastné — každá verzia " + "sa uchová, takže každú zmenu si možno prečítať aj vrátiť." + ), + "What this skill said before each change. This is the whole safety story for a model editing its own instructions: not a gate, but a record and a way back.": ( + "Čo táto schopnosť hovorila pred každou zmenou. To je celá bezpečnosť " + "toho, že si model upravuje vlastné pokyny: nie zákaz, ale záznam a cesta " + "späť." + ), + "Put the skill back to this version? The current one is kept in the history.": ( + "Vrátiť schopnosť na túto verziu? Súčasná zostane v histórii." + ), + "Shared with": "Zdieľané s", + "Shared with you. You can read this but not change it.": ( + "Zdieľané s vami. Môžete to čítať, ale nie meniť." + ), + "Find somebody": "Nájsť niekoho", + "Name, email or group…": "Meno, e-mail alebo skupina…", + "Loading who this is shared with": "Načítava sa, s kým je toto zdieľané", + "Showing the first few. Type to narrow it — anything already shared stays listed whatever you search for.": ( + "Zobrazuje sa prvých niekoľko. Písaním to zúžite — čo je už zdieľané, " + "zostáva v zozname bez ohľadu na to, čo hľadáte." + ), + "They will be able to read this, and their models will find it. They cannot change it, delete it, or share it on — so “who can see this?” stays a question you can answer.": ( + "Budú to môcť čítať a ich modely to nájdu. Nemôžu to zmeniť, zmazať ani " + "zdieľať ďalej — takže „kto to vidí?“ zostáva otázkou, na ktorú viete " + "odpovedať." + ), + "Moving it changes who can see it.": "Presunutím sa zmení, kto to vidí.", + "What belongs in here.": "Čo tu patrí.", + "Nothing — a folder at the top": "Nič — zložka na najvyššej úrovni", + "Contracts": "Zmluvy", + } +) + +# --- Memory, personality and settings ----------------------------------------- +MESSAGES.update( + { + "Theme": "Vzhľad", + "Saved to this browser and to your account, so it follows you.": ( + "Uloží sa do tohto prehliadača aj k vášmu účtu, takže vás sleduje." + ), + "Timezone": "Časová zóna", + "Your timezone": "Vaša časová zóna", + "Use the instance default": "Použiť predvoľbu instancie", + "What time a model is told it is, and the zone anything you schedule runs in. Leave it unset to follow the server.": ( + "Aký čas sa modelu oznamuje a v akej zóne beží všetko, čo si naplánujete. " + "Ak to nenastavíte, riadi sa serverom." + ), + "That is not a timezone this server knows about.": ( + "Takúto časovú zónu tento server nepozná." + ), + "What you see the interface in. Saved to your account rather than to this browser, so it follows you — and it overrides whatever the instance is set to.": ( + "V akom jazyku vidíte rozhranie. Uloží sa k vášmu účtu, nie do tohto " + "prehliadača, takže vás sleduje — a prebíja to, čo je nastavené pre celú " + "instanciu." + ), + "The interface only. Models answer in the language you write to them in, whatever this says.": ( + "Len rozhranie. Modely odpovedajú v jazyku, v ktorom im píšete, bez ohľadu " + "na toto nastavenie." + ), + "Voice": "Hlas", + "Dictation": "Diktovanie", + "Reading replies aloud": "Čítanie odpovedí nahlas", + "Read each reply aloud as it finishes": "Prečítať každú odpoveď nahlas, keď dopíše", + "Only replies that arrive while you are looking at the chat.": ( + "Len odpovede, ktoré prídu, kým sa na konverzáciu pozeráte." + ), + "Save audio preferences": "Uložiť nastavenia zvuku", + "Whatever is default at the time": "Čokoľvek je vtedy predvolené", + "The microphone needs HTTPS or localhost — browsers do not grant it over plain HTTP.": ( + "Mikrofón potrebuje HTTPS alebo localhost — po obyčajnom HTTP ho " + "prehliadače nepovolia." + ), + "Notifications": "Upozornenia", + "Turn on notifications": "Zapnúť upozornenia", + "A reply, a filed report or a scheduled run arriving while you are looking at something else. In the page always; from the browser as well if you allow it here.": ( + "Odpoveď, podaná správa alebo naplánovaný beh, ktoré prídu, kým sa " + "pozeráte inam. V stránke vždy; z prehliadača tiež, ak to tu povolíte." + ), + "Kept per browser, because the permission is. Nothing is shown while you are looking at the page — the toast is there for that.": ( + "Uchováva sa pre každý prehliadač zvlášť, pretože povolenie je tiež " + "také. Kým sa pozeráte na stránku, nič sa nezobrazí — na to je bublina." + ), + "Checking what this browser allows…": "Zisťuje sa, čo tento prehliadač dovoľuje…", + "Install as an app": "Nainštalovať ako aplikáciu", + "Runs in its own window, without browser chrome. Everything still comes from your server — there is no offline mode beyond a page saying so.": ( + "Beží vo vlastnom okne, bez ovládacích prvkov prehliadača. Všetko aj tak " + "prichádza z vášho servera — offline režim je len stránka, ktorá to oznámi." + ), + "Installing needs a secure connection — HTTPS with a certificate this device trusts, or localhost — and some browsers never offer it. On iOS, use Share → Add to Home Screen.": ( + "Inštalácia potrebuje bezpečné spojenie — HTTPS s certifikátom, ktorému " + "toto zariadenie verí, alebo localhost — a niektoré prehliadače ju " + "neponúknu vôbec. Na iOS použite Zdieľať → Pridať na plochu." + ), + "Something worth remembering": "Niečo, čo sa vyplatí zapamätať", + "What this memory says": "Čo si táto pamäť pamätá", + "Prefers metric units and a 24-hour clock.": ( + "Preferuje metrické jednotky a 24-hodinový čas." + ), + "Nothing yet. A model with the memory tool adds to this when you tell it something worth keeping.": ( + "Zatiaľ nič. Model, ktorý má nástroj pamäti, tu pridá záznam, keď mu " + "povieš niečo, čo sa vyplatí uchovať." + ), + "For you, not for any model. It is never sent anywhere.": ( + "Pre vás, nie pre model. Nikam sa to neposiela." + ), + "A model's character is something it works out with a particular person, so this is yours — somebody else talking to the same model is talking to a different one, and neither of you can see the other's. Delete one and that model starts again from the default its administrator wrote.": ( + "Povaha modelu je niečo, na čom sa dohodne s konkrétnym človekom, takže " + "toto je vaše — kto sa rozpráva s tým istým modelom, rozpráva sa v " + "skutočnosti s iným, a ani jeden z vás nevidí to druhé. Keď jednu " + "zmažete, model začne znova od predvolenej, ktorú napísal správca." + ), + "Each model's own impression of how you work, kept by that model and read back to it in every conversation. Opinions rather than facts, and each one is that model's alone — the others cannot see it, and neither can anybody else. Delete any of them; it will form another if it has reason to.": ( + "Vlastný dojem každého modelu o tom, ako pracujete, ktorý si ten model " + "uchováva a v každej konverzácii sa mu znova predkladá. Sú to názory, nie " + "fakty, a každý patrí len tomu modelu — ostatné ho nevidia a nikto iný " + "tiež nie. Ktorýkoľvek zmažte; ak bude mať dôvod, utvorí si nový." + ), + "Delete what this model makes of you?": "Zmazať, čo si tento model o vás myslí?", + "Session": "Prihlásenie", + } +) + +# --- Agent connections, the terminal and background work ---------------------- +MESSAGES.update( + { + "The machine": "Stroj", + "Project container": "Kontejner projektu", + "What you will pick from when starting an agent chat.": ( + "Z tohto budete vyberať pri začatí agentskej konverzácie." + ), + "Host": "Hostiteľ", + "Port": "Port", + "Log in as": "Prihlásiť sa ako", + "How it logs in": "Ako sa prihlasuje", + "A private key": "Privátny kľúč", + "A password": "Heslo", + "Private key": "Privátny kľúč", + "Key passphrase": "Prístupová fráza kľúča", + "If the key has one": "Ak ju kľúč má", + "No password set": "Heslo nie je nastavené", + "Forget the old key": "Zabudnúť starý kľúč", + "Key forgotten. Check again to see the new one.": ( + "Kľúč zabudnutý. Overte znova, aby ste videli nový." + ), + "Enabled — can be picked when starting an agent chat": ( + "Zapnuté — dá sa vybrať pri začatí agentskej konverzácie" + ), + "No connections yet": "Zatiaľ žiadne spojenia", + "Agent chats are switched off for this instance. You can still add connections here, but nothing will use them until an administrator turns them on.": ( + "Agentské konverzácie sú pre túto instanciu vypnuté. Spojenia tu pridať " + "môžete, ale nič ich nepoužije, kým ich správca nezapne." + ), + "Project directory": "Adresár projektu", + "/project": "/project", + "Connect timeout (seconds)": "Časový limit spojenia (sekundy)", + "Choose the directory this chat works in": ( + "Vyberte adresár, v ktorom táto konverzácia pracuje" + ), + "Choose the project directory": "Vyberte adresár projektu", + "Clear the project directory": "Vymazať adresár projektu", + "What a relative path is measured from in chats started here.": ( + "Od čoho sa počíta relatívna cesta v konverzáciách začatých tu." + ), + "In the project": "V projekte", + "Approval mode": "Režim schvaľovania", + "Switched off here only. Everything is on unless you say otherwise.": ( + "Vypnuté len tu. Všetko je zapnuté, kým nepovieš inak." + ), + "Terminal": "Terminál", + "Close terminal": "Zavrieť terminál", + "Resize the terminal": "Zmeniť veľkosť terminálu", + "Connecting…": "Pripája sa…", + "Copy the last command and its output": "Skopírovať posledný príkaz a jeho výstup", + "Put the last command and its output into the message box": ( + "Vložiť posledný príkaz a jeho výstup do políčka správy" + ), + "What to do when a command finishes": "Čo robiť, keď príkaz dobehne", + "What to do with each command you run": "Čo robiť s každým príkazom, ktorý spustíte", + "Auto: off": "Automaticky: nič", + "Auto: copy": "Automaticky: skopírovať", + "Auto: send": "Automaticky: poslať", + "Background jobs": "Úlohy na pozadí", + "Stop this job? It and everything it started are killed.": ( + "Zastaviť túto úlohu? Ukončí sa aj všetko, čo spustila." + ), + "Opening…": "Otvára sa…", + "Put this in the message box as an attachment": "Vložiť do políčka správy ako prílohu", + "This chat": "Táto konverzácia", + "Keep this chat": "Nechať túto konverzáciu", + "Window size": "Veľkosť okna", + "Why not, and what to do instead": "Prečo nie a čo urobiť namiesto toho", + "Why not, and what to do instead…": "Prečo nie a čo urobiť namiesto toho…", + "None. A model has to be marked as supporting tools, the user needs the permission, and the tool itself has to be turned on.": ( + "Žiadne. Model musí byť označený, že podporuje nástroje, používateľ " + "potrebuje oprávnenie a samotný nástroj musí byť zapnutý." + ), + } +) + +# --- Folders, reports, schedules and messages --------------------------------- +MESSAGES.update( + { + "What this folder is for.": "Na čo je táto zložka.", + "Used by every chat in this folder, and by folders nested inside it, unless the chat has a prompt of its own. Read each time a reply is built rather than copied when a chat is made, so editing this reaches the chats already here.": ( + "Použije ho každá konverzácia v tejto zložke aj vo zložkách v nej, ak " + "konverzácia nemá vlastný pokyn. Číta sa pri každom zostavení odpovede, " + "nekopíruje sa pri vytvorení konverzácie — úprava teda zasiahne aj " + "konverzácie, ktoré tu už sú." + ), + "Leave empty to fall through to the model's prompt, then the instance's.": ( + "Prázdne znamená prepadnutie na pokyn modelu a potom instancie." + ), + "Leave empty to use the instance default.": "Prázdne znamená predvoľbu instancie.", + "Precedence, not concatenation: chat, then folder, then model, then instance. The most specific one wins outright.": ( + "Prednosť, nie skladanie: konverzácia, potom zložka, potom model, potom " + "instancia. Najkonkrétnejší vyhráva úplne." + ), + "Seeds, copied onto a chat when it is created and its own from then on. Anything chosen on the new-chat screen wins over these.": ( + "Základ, ktorý sa skopíruje na konverzáciu pri vytvorení a odvtedy je jej " + "vlastný. Čo vyberiete na obrazovke novej konverzácie, má prednosť." + ), + "A folder set to one kind shows on only that side of the sidebar's switch, and opens the new-chat screen already on that fork.": ( + "Zložka nastavená na jeden druh sa zobrazí len na tej strane prepínača v " + "bočnom paneli a obrazovku novej konverzácie otvorí už na tej vetve." + ), + "What a model is given when it reads this.": "Čo model dostane, keď si to prečíta.", + "When the user asks for the weekly report.": "Keď si používateľ vyžiada týždennú správu.", + "Reports": "Správy", + "New reports": "Nové správy", + "Messages": "Odkazy", + "New messages": "Nové odkazy", + "New reply": "Nová odpoveď", + "Loading earlier messages": "Načítavajú sa staršie správy", + "One conversation that keeps going. Anything scheduled to write here will arrive in it.": ( + "Jedna konverzácia, ktorá stále pokračuje. Všetko naplánované na písanie " + "sem dorazí do nej." + ), + "Search reports…": "Hľadať v správach…", + "This run did not finish cleanly.": "Tento beh sa nedokončil čisto.", + "Delete this report? It cannot be brought back.": ( + "Zmazať túto správu? Už sa nedá vrátiť." + ), + "Finished work, filed to be read later. A model writes one when you ask for it or when it finishes something worth keeping, and anything running on a schedule leaves its result here. Nothing on this page can be replied to.": ( + "Hotová práca, odložená na prečítanie neskôr. Model ju napíše, keď si ju " + "vyžiadate alebo keď dokončí niečo, čo sa vyplatí uchovať, a všetko, čo " + "beží podľa plánu, tu necháva svoj výsledok. Na nič na tejto stránke sa " + "nedá odpovedať." + ), + "Nothing scheduled": "Nič naplánované", + "Work that runs on its own, whether or not you are here. Each one has its own chat, and replies into it every time it comes round.": ( + "Práca, ktorá beží sama, či tu ste alebo nie. Každá má vlastnú " + "konverzáciu a pri každom opakovaní do nej odpovedá." + ), + "Set something to run later — a daily summary, a check every Monday morning, a reminder in an hour.": ( + "Nastavte niečo na neskôr — denné zhrnutie, kontrolu každé pondelkové " + "ráno, pripomienku za hodinu." + ), + "files a report": "podá správu", + "Monday build check": "Pondelková kontrola buildu", + "What this appears as in the list.": "Ako sa to zobrazí v zozname.", + "Check whether the build is passing and summarise anything that broke since last week.": ( + "Skontroluj, či build prechádza, a zhrň všetko, čo sa od minulého týždňa " + "pokazilo." + ), + "Every Monday morning, check whether the build is passing and write me a report.": ( + "Každé pondelkové ráno skontroluj, či build prechádza, a napíš mi správu." + ), + "Written for a model that will read it with no conversation around it, so say the whole thing. Nobody will be there to answer a question about it.": ( + "Píše sa pre model, ktorý to prečíta bez konverzácie okolo, takže povedzte " + "celú vec. Nikto tam nebude, aby na otázku odpovedal." + ), + "Where the result goes": "Kam ide výsledok", + "Every so often": "Každú chvíľu", + "On particular days": "V určité dni", + "Starting": "Začína", + "Repeat every": "Opakovať každých", + "On these days": "V tieto dni", + "Leave all of them unticked for every day.": "Nechajte všetky nezaškrtnuté pre každý deň.", + "One or more times, separated by commas. These are wall-clock times: 09:00 stays 09:00 when the clocks change.": ( + "Jeden alebo viac časov, oddelených čiarkami. Ide o časy na hodinách: " + "09:00 zostáva 09:00 aj po zmene času." + ), + "Only on these dates": "Len v tieto dni", + "Days of the month, if you want it narrower. Optional.": ( + "Dni v mesiaci, ak to chcete zúžiť. Nepovinné." + ), + "Stop after": "Zastaviť po", + "Number of runs. Leave it at 0 to keep going until you stop it.": ( + "Počet behov. Nechajte 0, aby to pokračovalo, kým to nezastavíte." + ), + "Or stop on": "Alebo zastaviť dňa", + "Optional. Nothing runs after this date.": "Nepovinné. Po tomto dni už nič nebeží.", + "Run it now without using up the next scheduled run": ( + "Spustiť teraz bez toho, aby sa spotreboval ďalší naplánovaný beh" + ), + "Remove this schedule? It will stop running.": "Odstrániť tento plán? Prestane bežať.", + "Keep its chat": "Nechať jeho konverzáciu", + "Stops it running. Its chat is kept by default and becomes an ordinary one, so the transcript of everything it has already done stays where it is.": ( + "Prestane bežať. Jeho konverzácia sa predvolene zachová a stane sa " + "obyčajnou, takže zápis všetkého, čo už urobil, zostáva na svojom mieste." + ), + "This chat belonged to a schedule that has been removed. It is kept as a record of what was done.": ( + "Táto konverzácia patrila plánu, ktorý bol odstránený. Zostáva ako záznam " + "toho, čo sa urobilo." + ), + "What you can do": "Čo môžete robiť", + "Add one": "Pridať", + "Reset this model's personality with you?": ( + "Obnoviť povahu tohto modelu voči vám?" + ), + } +) + +# --- Date formats ------------------------------------------------------------- +# The pattern, not the words: Slovak puts a full stop after the day and says "v" +# rather than "on". `i18n.stamp` translates the format and then substitutes the +# month and weekday names, which are in the genitive because that is what a date +# takes. +MESSAGES.update( + { + "%H:%M on %A %-d %B": "%H:%M v %A, %-d. %B", + "%d %B %Y at %H:%M": "%d. %B %Y v %H:%M", + } +) + +# --- Administration: the pages and their controls ------------------------------ +MESSAGES.update( + { + "Administration": "Správa", + "General": "Všeobecné", + "Language": "Jazyk", + "Interface language": "Jazyk rozhrania", + "Identity": "Identita", + "Customization": "Prispôsobenie", + "Connections": "Spojenia", + "All connections": "Všetky spojenia", + "Add a connection": "Pridať spojenie", + "Models": "Modely", + "Model": "Model", + "Groups": "Skupiny", + "Members": "Členovia", + "Account": "Účet", + "Agents": "Agenti", + "Agent": "Agent", + "Agent mode": "Režim agenta", + "Agent execution": "Vykonávanie agentom", + "Audio": "Zvuk", + "Images": "Obrázky", + "Image generation": "Generovanie obrázkov", + "Extraction": "Extrakcia", + "Memory": "Pamäť", + "Helpers": "Pomocníci", + "A crowd": "Skupina", + "Custom tools": "Vlastné nástroje", + "MCP servers": "Servery MCP", + "Guidance": "Vedenie", + "Capabilities": "Schopnosti", + "Availability": "Dostupnosť", + "Description": "Opis", + "Display name": "Zobrazované meno", + "Name": "Názov", + "Identifier": "Identifikátor", + "Id": "Id", + "Length": "Dĺžka", + "Format": "Formát", + "Method": "Metóda", + "Headers": "Hlavičky", + "Body": "Telo", + "Link": "Odkaz", + "Logo": "Logo", + "Favicon": "Favicon", + "CSS": "CSS", + "Add": "Pridať", + "Enable": "Zapnúť", + "Disable": "Vypnúť", + "Delete": "Zmazať", + "Copy": "Kopírovať", + "Next": "Ďalej", + "Move up": "Posunúť vyššie", + "Move down": "Posunúť nižšie", + "Make public": "Zverejniť", + "Active": "Aktívne", + "Auto": "Automaticky", + "Always allow": "Vždy povoliť", + "Always ask": "Vždy sa spýtať", + "Asking you things": "Otázky pre vás", + "Attached files": "Priložené súbory", + "Back to chats": "Späť na konverzácie", + "Baseline permissions": "Základné oprávnenia", + "Groups with access": "Skupiny s prístupom", + "Models they can use": "Modely, ktoré môžu používať", + "Models this group unlocks": "Modely, ktoré táto skupina odomkne", + "Limits in force": "Platné obmedzenia", + "New group name": "Názov novej skupiny", + "New password": "Nové heslo", + "Add an account": "Pridať účet", + "Add a suggestion": "Pridať návrh", + "Delete suggestion": "Zmazať návrh", + "No groups yet. Everybody gets the baseline above and nothing more.": ( + "Zatiaľ žiadne skupiny. Každý dostane základ uvedený vyššie a nič viac." + ), + "In no group. They get the baseline and nothing more.": ( + "V žiadnej skupine. Dostanú základ a nič viac." + ), + "An administrator bypasses every permission and every quota below.": ( + "Správca obchádza každé oprávnenie a každú kvótu nižšie." + ), + "Edited from the group's own page. One control per value, so a save here cannot undo a save there.": ( + "Upravuje sa na vlastnej stránke skupiny. Jeden ovládací prvok na hodnotu, " + "takže uloženie tu nemôže zrušiť uloženie tam." + ), + "Members keep their accounts and lose whatever this group granted them. Every share naming this group goes too — nothing cascades to those, so they are deleted explicitly.": ( + "Členovia si svoje účty nechajú a stratia to, čo im táto skupina " + "poskytovala. Zmizne aj každé zdieľanie, ktoré túto skupinu menuje — nič " + "sa na ne nekaskáduje, takže sa mažú výslovne." + ), + "Model access is separate from permissions: a permission says what somebody may do, this says what they may do it with.": ( + "Prístup k modelom je oddelený od oprávnení: oprávnenie hovorí, čo niekto " + "smie robiť, toto hovorí, s čím to smie robiť." + ), + "A model marked public is available to everyone; one that is not is available to the groups named here. Model access is separate from permissions — one says what somebody may do, the other what with.": ( + "Model označený ako verejný je dostupný všetkým; ten, ktorý nie je, je " + "dostupný skupinám uvedeným tu. Prístup k modelom je oddelený od " + "oprávnení — jedno hovorí, čo niekto smie, druhé s čím." + ), + "Ignored while the model is available to everyone.": ( + "Ignoruje sa, kým je model dostupný všetkým." + ), + "Ignored while the server is available to everyone.": ( + "Ignoruje sa, kým je server dostupný všetkým." + ), + "Ignored while the tool is available to everyone.": ( + "Ignoruje sa, kým je nástroj dostupný všetkým." + ), + "Available to everyone": "Dostupné všetkým", + "Instance-wide settings. These are stored in the database and take effect immediately — no restart, and they survive one.": ( + "Nastavenia pre celú instanciu. Ukladajú sa do databázy a platia okamžite " + "— bez restartu, a restart prežijú." + ), + "Anyone who can reach this instance may create an account": ( + "Účet si môže vytvoriť každý, kto sa dostane k tejto instancii" + ), + "Default system prompt": "Predvolený systémový pokyn", + "Applied to every chat that does not have a prompt of its own. A model's prompt overrides this, and a chat's prompt overrides both — most specific wins outright rather than the three being stacked together.": ( + "Použije sa na každú konverzáciu, ktorá nemá vlastný pokyn. Pokyn modelu " + "toto prebije a pokyn konverzácie prebije oba — najkonkrétnejší vyhráva " + "úplne, namiesto toho, aby sa všetky tri skladali." + ), + "Leave empty to send no system prompt at all.": ( + "Prázdne znamená neposielať žiadny systémový pokyn." + ), + "Compaction": "Zhrnutie", + "Compact at": "Zhrnúť pri", + "A long conversation eventually fills the model's context. When it gets close, the earlier turns are summarised and the summary is sent in their place. The messages themselves are kept and stay readable in the transcript — they simply stop being sent.": ( + "Dlhá konverzácia nakoniec zaplní kontext modelu. Keď sa priblíži, starší " + "priebeh sa zhrnie a namiesto neho sa posiela zhrnutie. Samotné správy " + "zostávajú a v zápise sú stále čitateľné — len sa prestanú posielať." + ), + "Most rounds of tool calls": "Najviac kôl volania nástrojov", + "A model ends its own turn the moment it stops asking for tools — that is it saying it has what it needs, and nothing here overrides it. This is a ceiling for the case where it never says so.": ( + "Model ukončí svoj krok vo chvíli, keď si prestane žiadať nástroje — tým " + "hovorí, že má, čo potrebuje, a nič tu to neprebíja. Toto je strop pre " + "prípad, že to nepovie nikdy." + ), + "A backstop, as it is above. The clock and the token ceiling are what normally end one.": ( + "Záchranná sieť, ako vyššie. Beh obyčajne ukončia hodiny a strop tokenov." + ), + "A backstop, not a working budget. An agent reply is meant to run until the task is done, so a number low enough to be what stops it is a number that stops it halfway. Use the token ceiling above for a real limit.": ( + "Záchranná sieť, nie pracovný rozpočet. Agentská odpoveď má bežať, kým " + "nie je úloha hotová, takže číslo dosť nízke na to, aby ju zastavilo, ju " + "zastaví v polovici. Na skutočné obmedzenie použite strop tokenov vyššie." + ), + "A ceiling — a model asking for more gets this.": ( + "Strop — model, ktorý si žiada viac, dostane toto." + ), + "Chat title request": "Požiadavka na názov konverzácie", + "Empty, so no model is asked to name a chat. Chats are named from the first thing said in them.": ( + "Prázdne, takže o názov konverzácie sa nežiada žiadny model. Konverzácie " + "sa pomenúvajú podľa prvej vety, ktorá v nich padne." + ), + } +) + +# --- Administration: connections, models and endpoints ------------------------- +MESSAGES.update( + { + "API URL": "URL rozhrania", + "API key": "Kľúč rozhrania", + "Base URL": "Základná URL", + "Endpoint URL": "URL endpointu", + "Instance URL": "URL instancie", + "Extra headers": "Ďalšie hlavičky", + "Header or parameter name": "Názov hlavičky alebo parametra", + "How it is sent": "Ako sa posiela", + "How to ask it to unload": "Ako ho požiadať o uvoľnenie", + "Credential": "Prihlasovací údaj", + "No key set": "Kľúč nie je nastavený", + "No secret set": "Tajný údaj nie je nastavený", + "No models configured": "Žiadne nastavené modely", + "Encrypted at rest. Clear the field to remove it.": ( + "Zašifrované pri uložení. Vymazaním poľa ho odstránite." + ), + "Encrypted before it is stored, and never sent back to the browser. Leave empty for endpoints that need no key.": ( + "Zašifruje sa pred uložením a nikdy sa neposiela späť do prehliadača. Pre " + "endpointy, ktoré kľúč nepotrebujú, nechajte prázdne." + ), + "Enabled — its models are offered in chats": ( + "Zapnuté — jeho modely sa ponúkajú v konverzáciách" + ), + "Enabled — offered in chats": "Zapnuté — ponúka sa v konverzáciách", + "Context length": "Dĺžka kontextu", + "How many tokens this model can hold, filled in from the endpoint where it says. Leave it empty if you do not know: the context percentage and automatic compaction both stay off rather than working from a guess.": ( + "Koľko tokenov tento model zvládne; vypĺňa sa z endpointu tam, kde to " + "uvádza. Ak to neviete, nechajte prázdne: percento kontextu aj " + "automatické zhrnutie zostanú vypnuté, namiesto toho, aby pracovali s " + "odhadom." + ), + "Default reasoning effort": "Predvolená náročnosť uvažovania", + "Default personality": "Predvolená povaha", + "A personality belongs to a person.": "Povaha patrí človeku.", + "Edit its own personality": "Upravovať si vlastnú povahu", + "Facts for other models": "Fakty pre ostatné modely", + "Never shown to a person. It goes into the list of the other models that a model sees when it is allowed to ask one of them a question, so write what would help it choose: size, what this one is good and bad at, a score you trust. Leave it empty and the description above carries that on its own.": ( + "Človeku sa nikdy nezobrazí. Ide do zoznamu ostatných modelov, ktorý model " + "vidí, keď sa smie niektorého na niečo spýtať — napíšte teda, čo mu pomôže " + "vybrať si: veľkosť, v čom je tento dobrý a v čom slabý, skóre, ktorému " + "veríte. Ak to necháte prázdne, ponesie to opis vyššie sám." + ), + "Answering model": "Odpovedajúci model", + "Model image": "Obrázok modelu", + "Model that reviews": "Model, ktorý posudzuje", + "Name the model uses": "Názov, ktorý model používa", + "Embedding model": "Model pre vnorenia", + "Export (API)": "Export (API)", + "GET": "GET", + "GitHub": "GitHub", + "Firecrawl": "Firecrawl", + "ComfyUI": "ComfyUI", + "ComfyUI API format": "Formát ComfyUI API", + "Local LM Studio": "Lokálne LM Studio", + "LLeMbas": "LLeMbas", + "Checkout": "Checkout", + "Commit": "Commit", + "DuckDuckGo region": "Región DuckDuckGo", + "Change only for a self-hosted Firecrawl.": ( + "Meňte len pri vlastnom Firecrawle." + ), + "Accept: application/json": "Accept: application/json", + "HTTP-Referer: https://example.org": "HTTP-Referer: https://example.org", + '"how do I get in"': '"ako sa tam dostanem"', + "/effort": "/effort", + "- Check the weather rather than guessing at it.": ( + "- Počasie si over, nehádaj ho." + ), + "- Use the GitHub tools for anything about our repositories.": ( + "- Na čokoľvek o našich repozitároch použi nástroje GitHubu." + ), + "Look up the current weather for a city.": "Zisti aktuálne počasie pre mesto.", + "List the project directory": "Vypíš adresár projektu", + "Arguments": "Argumenty", + "Extra instructions": "Ďalšie pokyny", + "Dotted; a number indexes a list. Leave empty for the whole document. A path that leads nowhere gives the whole document rather than nothing.": ( + "S bodkami; číslo indexuje zoznam. Prázdne znamená celý dokument. Cesta, " + "ktorá nikam nevedie, vráti celý dokument, nie nič." + ), + "Ignored for GET. Placeholders are escaped for JSON, so a value cannot end the string it sits in and add a field.": ( + "Pri GET sa ignoruje. Zástupné hodnoty sa pre JSON escapujú, takže hodnota " + "nemôže ukončiť reťazec, v ktorom je, a pridať ďalšie pole." + ), + "Leave the second unticked unless this tool points at something on your own network. It is what stops a tool being aimed at this server, a router, or a cloud metadata endpoint.": ( + "Druhé nechajte nezaškrtnuté, ak tento nástroj nemieri na niečo vo vašej " + "vlastnej sieti. Práve to zabraňuje tomu, aby nástroj mieril na tento " + "server, na router alebo na metadátový endpoint cloudu." + ), + "May reach private and loopback addresses": ( + "Môže dosiahnuť privátne a loopback adresy" + ), + "Allow fetching addresses on this machine and this network": ( + "Povoliť načítanie adries na tomto stroji a v tejto sieti" + ), + "Let a model fetch a page itself": "Nechať model načítať stránku sám", + "Lets a model look things up while it answers. It is offered as a tool the model chooses to call, so nothing changes for a question that does not need it — and it is only offered to models marked as supporting tools, because sending a tool list to one that does not fails the whole request.": ( + "Umožní modelu vyhľadávať, kým odpovedá. Ponúka sa ako nástroj, ktorý si " + "model sám zvolí, takže pri otázke, ktorá ho nepotrebuje, sa nič nemení — " + "a ponúka sa len modelom označeným, že podporujú nástroje, pretože " + "poslanie zoznamu nástrojov modelu, ktorý ich nepodporuje, zhodí celú " + "požiadavku." + ), + "Most characters to keep": "Najviac znakov na uchovanie", + "Most characters to keep per call": "Najviac znakov na uchovanie za volanie", + "Characters of it in the prompt": "Koľko jeho znakov ide do pokynu", + "Characters of it to use": "Koľko jeho znakov použiť", + "Characters. The rest is cut off and the model is told so.": ( + "Znaky. Zvyšok sa odreže a modelu sa to oznámi." + ), + "Characters. Roughly four to a token, so 120,000 is about 30,000 tokens — already most of a small context window. The rest is cut and the model is told so.": ( + "Znaky. Približne štyri na token, takže 120 000 je asi 30 000 tokenov — už " + "väčšina malého kontextového okna. Zvyšok sa odreže a modelu sa to oznámi." + ), + "Every number here is a trade, and a large one usually breaks a request rather than being slow — the text of a whole book does not fit in a context window, and a model handed it fails the request outright rather than reading the first half.": ( + "Každé číslo tu je kompromis a veľká hodnota obyčajne požiadavku zhodí, nie " + "spomalí — text celej knihy sa do kontextového okna nevojde a model, " + "ktorému ho podáte, požiadavku odmietne, namiesto toho, aby prečítal prvú " + "polovicu." + ), + } +) + +# --- Administration: agents, terminals and background work --------------------- +MESSAGES.update( + { + "Allow agent chats": "Povoliť agentské konverzácie", + "Allow commands to run in the background": "Povoliť príkazy bežiace na pozadí", + "Allow the terminal panel": "Povoliť panel terminálu", + "Allow messages to be dictated": "Povoliť diktovanie správ", + "Allow replies to be read out": "Povoliť čítanie odpovedí nahlas", + "Let a model delegate": "Nechať model delegovať", + "Let a chat have a crowd": "Povoliť konverzácii skupinu", + "Let people write their own answer": "Nechať ľudí napísať vlastnú odpoveď", + "Let scheduled work run": "Nechať bežať naplánovanú prácu", + "Delegate to a helper": "Delegovať pomocníkovi", + "Ask another model": "Spýtať sa iného modelu", + "Open a terminal": "Otvoriť terminál", + "Background commands": "Príkazy na pozadí", + "Connection": "Spojenie", + "Connections to this machine": "Spojenia na tento stroj", + "Only on one port": "Len na jednom porte", + "Close a shell after": "Zavrieť shell po", + "Most shells at once": "Najviac shellov naraz", + "Most shells per person": "Najviac shellov na osobu", + "Most jobs watched at once": "Najviac naraz sledovaných úloh", + "Most output to keep": "Najviac výstupu na uchovanie", + "Most output across a reply": "Najviac výstupu za celú odpoveď", + "Most a reply may write": "Najviac, čo smie odpoveď napísať", + "Most a helper may write": "Najviac, čo smie napísať pomocník", + "Most helpers one reply may send": "Najviac pomocníkov, ktorých smie odpoveď poslať", + "Most models besides the chat's own": "Najviac modelov okrem vlastného modelu konverzácie", + "Most rounds": "Najviac kôl", + "Longest a command may ask for": "Najdlhšie, čo si smie príkaz vyžiadať", + "Longest a reply may take": "Najdlhšie, čo smie odpoveď trvať", + "Longest a helper may take": "Najdlhšie, čo smie trvať pomocník", + "Longest a turn may take": "Najdlhšie, čo smie trvať jeden krok", + "How long a question waits": "Ako dlho otázka čaká", + "Keep a timed-out command running instead of killing it": ( + "Nechať príkaz po vypršaní času bežať namiesto jeho ukončenia" + ), + "Keep a helper's own chat afterwards": "Nechať potom vlastnú konverzáciu pomocníka", + "Mark where commands begin and end": "Označiť, kde príkazy začínajú a končia", + "Ask it to carry on when it stops with tasks outstanding": ( + "Požiadať ho, aby pokračoval, keď skončí s nesplnenými úlohami" + ), + "Off leaves the timeout a hard stop; the model can still choose to background a command up front.": ( + "Vypnuté necháva časový limit ako tvrdý koniec; model si aj tak môže " + "príkaz sám poslať na pozadie hneď na začiatku." + ), + "A command that would outlast its timeout can be left running instead of killed — detached on the far side, checked on later. It is how a long install, build or download becomes possible at all.": ( + "Príkaz, ktorý by prežil svoj časový limit, sa dá nechať bežať namiesto " + "ukončenia — odpojený na druhej strane, skontrolovaný neskôr. Práve tak sa " + "vôbec dá zvládnuť dlhá inštalácia, build alebo sťahovanie." + ), + "A reply can hand a self-contained piece of work to a second model that runs on its own and reports back — several at once, which is what makes research fan out instead of queueing. This applies to ordinary chats as much as agent ones.": ( + "Odpoveď môže samostatný kus práce podať druhému modelu, ktorý beží sám a " + "ohlási sa — aj viacerým naraz, a práve preto sa výskum rozvetví namiesto " + "toho, aby stál v rade. Platí to pre obyčajné konverzácie rovnako ako pre " + "agentské." + ), + "Asking another model a question uses the same switch and the same allowance below": ( + "Otázka inému modelu používa ten istý prepínač a ten istý limit nižšie" + ), + "Each is a whole generation against the same endpoint the reply that asked for it is waiting on. Past this a model is told to do the work itself rather than made to wait.": ( + "Každý je celá generácia proti tomu istému endpointu, na ktorý čaká " + "odpoveď, ktorá si ho vyžiadala. Nad týmto sa modelu povie, aby prácu " + "urobil sám, namiesto toho, aby čakal." + ), + "Fanning out across a handful of independent questions is what this is for. A reply that wants twenty has misread the tool. Questions put to other models count against this same number, so one reply cannot spend the allowance twice.": ( + "Je to na rozvetvenie do niekoľkých nezávislých otázok. Odpoveď, ktorá " + "chce dvadsať, nástroj nepochopila. Otázky položené iným modelom sa " + "počítajú do toho istého čísla, takže jedna odpoveď nemôže limit " + "spotrebovať dvakrát." + ), + "In tokens, across every round of one reply. This is the bound that normally ends a long piece of work. Zero means no ceiling.": ( + "V tokenoch, cez všetky kolá jednej odpovede. Toto je hranica, ktorá dlhú " + "prácu obyčajne ukončí. Nula znamená bez stropu." + ), + "In tokens, across every round. A helper answers one question, so this should run out well before the reply that asked does. Zero means no ceiling.": ( + "V tokenoch, cez všetky kolá. Pomocník odpovedá na jednu otázku, takže " + "toto by malo skončiť výrazne skôr než odpoveď, ktorá sa pýtala. Nula " + "znamená bez stropu." + ), + "Four separate bounds, because they fail differently: the clock stops one slow command eating an afternoon, tool output stops a model filling its own context with build logs and having no room to answer, written tokens stop one that keeps going, and the step count is a backstop against a runaway.": ( + "Štyri samostatné hranice, pretože zlyhávajú rozdielne: hodiny zastavia " + "jeden pomalý príkaz, ktorý by zjedol celé poobedie; výstup nástrojov " + "zabráni modelu zaplniť si vlastný kontext logmi z buildu a nemať už kde " + "odpovedať; napísané tokeny zastavia ten, ktorý nevie prestať; a počet " + "krokov je záchranná sieť proti utečencovi." + ), + "Each is a periodic reconnect to the machine. Jobs past this still run; they are simply not watched, and the model is not woken for them.": ( + "Každé je pravidelné znovupripojenie k stroju. Úlohy nad tento počet " + "stále bežia; len sa nesledujú a model sa pre ne nebudí." + ), + "Every restart ends every open terminal session — a command still running on the far side is cut off. A reply being written is saved with whatever it has.": ( + "Každý restart ukončí každé otvorené sedenie terminálu — príkaz, ktorý na " + "druhej strane stále beží, sa odstrihne. Odpoveď, ktorá sa práve píše, sa " + "uloží s tým, čo má." + ), + "On, a finished job starts (or joins) a reply carrying its result. Off, the model only sees it the next time it runs of its own accord.": ( + "Zapnuté: dokončená úloha spustí (alebo doplní) odpoveď, ktorá nesie jej " + "výsledok. Vypnuté: model ju uvidí až pri ďalšom vlastnom behu." + ), + "Only ever against a plan, and only while tasks on it are still open — that is the one thing there is to be objectively wrong about. A reply with no plan that says it has finished is believed. It is asked at most twice in a row, and if it stops a third time that is recorded in the transcript rather than argued with.": ( + "Vždy len oproti plánu a len kým sú na ňom otvorené úlohy — to je jediná " + "vec, v ktorej sa dá objektívne mýliť. Odpovedi bez plánu, ktorá tvrdí, že " + "skončila, sa verí. Spýta sa najviac dvakrát za sebou, a ak skončí aj " + "tretí raz, zapíše sa to do zápisu namiesto hádky." + ), + "Off means it is deleted once its answer has been handed over, which is what keeps this cheap to use. Turn it on to work out why one came back with something odd. Kept chats are temporary either way and are swept a day later, and neither appears in anybody's sidebar.": ( + "Vypnuté znamená, že sa po odovzdaní odpovede zmaže, a práve preto je toto " + "lacné na používanie. Zapnite, keď chcete zistiť, prečo sa niektorý vrátil " + "s niečím čudným. Ponechané konverzácie sú aj tak dočasné a o deň neskôr " + "sa zmetú, a ani jedna sa nikomu nezobrazí v bočnom paneli." + ), + "A round is out and back. Two gives the first model one chance to change its mind after hearing the objections, which is the point of the whole thing; three is where going in circles starts.": ( + "Kolo je tam a späť. Dve dávajú prvému modelu jednu možnosť zmeniť názor " + "po tom, čo si vypočul námietky, a to je celý zmysel; pri troch sa začína " + "chodenie dokola." + ), + "Four is already eight replies a turn at one round each. More voices past that tend to repeat each other rather than add anything.": ( + "Štyri už znamenajú osem odpovedí na krok pri jednom kole. Ďalšie hlasy " + "nad tento počet sa väčšinou opakujú, namiesto toho, aby niečo pridali." + ), + "Across every speaker, not each. A member whose endpoint has stalled cannot then hold the round open all afternoon.": ( + "Cez všetkých hovoriacich, nie pre každého. Člen, ktorého endpoint zamrzol, " + "potom nemôže držať kolo otvorené celé poobedie." + ), + "Fold away a short \"I agree\" on the way back": ( + "Zbaliť krátke „súhlasím“ na cestě späť" + ), + "Off by default. With it on, each chat's settings panel offers the other models; a chat with none ticked behaves exactly as it always has.": ( + "Predvolene vypnuté. Po zapnutí panel nastavení každej konverzácie ponúka " + "ostatné modely; konverzácia bez zaškrtnutého modelu sa chová presne ako " + "vždy." + ), + "Check for due work every": "Kontrolovať splatnú prácu každých", + "How often it looks": "Ako často sa pozerá", + "Nothing may repeat faster than": "Nič sa nesmie opakovať častejšie než", + "A schedule that comes round faster than its chat can answer would build a backlog for ever. Past this, a run is skipped and says so on the schedule rather than joining the queue.": ( + "Plán, ktorý sa vracia rýchlejšie, než jeho konverzácia dokáže odpovedať, " + "by donekonečna hromadil nevybavené. Nad týmto sa beh preskočí a v pláne " + "sa to uvedie, namiesto toho, aby sa pridal do radu." + ), + "Fifty schedules due at nine o'clock must not open fifty replies against one endpoint. The rest wait their turn rather than being dropped.": ( + "Päťdesiat plánov splatných o deviatej nesmie otvoriť päťdesiat odpovedí " + "proti jednému endpointu. Ostatné počkajú, kým na ne príde rad, " + "nezahodia sa." + ), + "Counted from the first of the month, UTC. Recorded for every reply including one that was stopped or failed — an endpoint charges for tokens it generated whether or not anybody wanted them.": ( + "Počíta sa od prvého dňa mesiaca v UTC. Zapisuje sa pri každej odpovedi " + "vrátane zastavenej alebo neúspešnej — endpoint si účtuje tokeny, ktoré " + "vygeneroval, či ich niekto chcel alebo nie." + ), + "Empty means a chat that can see everything.": ( + "Prázdne znamená konverzáciu, ktorá vidí všetko." + ), + "Knowledge bases in scope": "Znalostné bázy v rozsahu", + "Permissions this group adds": "Oprávnenia, ktoré táto skupina pridáva", + "Password and removal": "Heslo a odstránenie", + "None. They cannot start a chat at all.": "Žiadne. Nemôžu vôbec začať konverzáciu.", + "None": "Žiadne", + "Nothing matches that filter.": "Tomuto filtru nič nezodpovedá.", + "No suggestions": "Žiadne návrhy", + "Order on the screen, lowest first.": "Poradie na obrazovke, najnižšie prvé.", + "One line under the name, saying what it is for.": ( + "Jeden riadok pod názvom, ktorý hovorí, na čo to je." + ), + "One per chat. Each holds an SSH connection open on the far machine.": ( + "Jeden na konverzáciu. Každý drží otvorené SSH spojenie na vzdialenom stroji." + ), + } +) + +# --- Administration: prompts, themes, users and saving ------------------------- +MESSAGES.update( + { + "Save": "Uložiť", + "Save changes": "Uložiť zmeny", + "Save settings": "Uložiť nastavenia", + "Save group": "Uložiť skupinu", + "Save baseline": "Uložiť základ", + "Save identity": "Uložiť identitu", + "Save personality": "Uložiť povahu", + "Save search": "Uložiť hľadanie", + "Save extraction": "Uložiť extrakciu", + "Save stylesheet": "Uložiť stylopis", + "Save themes": "Uložiť vzhľady", + "Save wording": "Uložiť formulácie", + "Saved.": "Uložené.", + "Settings saved.": "Nastavenia uložené.", + "Prompts saved.": "Pokyny uložené.", + "Audio settings saved.": "Nastavenia zvuku uložené.", + "Search settings saved.": "Nastavenia hľadania uložené.", + "Restore": "Obnoviť", + "Restore defaults": "Obnoviť predvolené", + "Restore all defaults": "Obnoviť všetky predvolené", + "Reset password": "Obnoviť heslo", + "Remove": "Odstrániť", + "Remove it": "Odstrániť to", + "Remove image": "Odstrániť obrázok", + "Restrict": "Obmedziť", + "Select all": "Vybrať všetko", + "Select every model on this page": "Vybrať každý model na tejto stránke", + "Set default": "Nastaviť ako predvolené", + "Show this one": "Zobraziť tento", + "Switch": "Prepnúť", + "Switch theme": "Prepnúť vzhľad", + "Send": "Poslať", + "Try it": "Vyskúšať", + "Preview": "Náhľad", + "Previous": "Predchádzajúce", + "Position": "Pozícia", + "Presentation": "Zobrazenie", + "Pinning": "Pripnutie", + "Pinned — shortcut in the chat sidebar": "Pripnuté — skratka v bočnom paneli", + "Prompt": "Pokyn", + "Prompts": "Pokyny", + "Prompt groups": "Skupiny pokynov", + "System prompt": "Systémový pokyn", + "Preamble character cap": "Limit znakov preambuly", + "Tagline": "Slogan", + "Themes": "Vzhľady", + "Stylesheet": "Stylopis", + "Starts from": "Vychádza z", + "Dusk": "Súmrak", + "Suggestions": "Návrhy", + "Role": "Rola", + "Quotas": "Kvóty", + "Tokens": "Tokeny", + "Tokens this month": "Tokeny tento mesiac", + "This month": "Tento mesiac", + "Replies": "Odpovede", + "Running": "Beží", + "Runs at once": "Behov naraz", + "Running at once, instance-wide": "Beží naraz, v celej instancii", + "Schedule work": "Naplánovať prácu", + "Scheduling": "Plánovanie", + "Scheduler": "Plánovač", + "Scheduled": "Naplánované", + "Schedules per person": "Plánov na osobu", + "Run commands": "Spúšťať príkazy", + "Tool calls in an ordinary chat": "Volania nástrojov v obyčajnej konverzácii", + "Turns that may pile up in one chat": "Kroky, ktoré sa môžu nakopiť v jednej konverzácii", + "Tools": "Nástroje", + "Tools it offers": "Nástroje, ktoré ponúka", + "Tools offered": "Ponúkané nástroje", + "Tool descriptions": "Opisy nástrojov", + "Search tools": "Hľadať nástroje", + "Search tools…": "Hľadať nástroje…", + "Search models": "Hľadať modely", + "Search models…": "Hľadať modely…", + "Search users": "Hľadať používateľov", + "Search by name or email…": "Hľadať podľa mena alebo e-mailu…", + "Provider": "Poskytovateľ", + "Results per search": "Výsledkov na hľadanie", + "Safe search": "Bezpečné hľadanie", + "SearXNG": "SearXNG", + "Searching by meaning": "Hľadanie podľa významu", + "Secret": "Tajný údaj", + "Speed": "Rýchlosť", + "Steps": "Kroky", + "Sampler": "Vzorkovač", + "Reasoning": "Uvažovanie", + "Reasoning efforts this model accepts": "Náročnosti uvažovania, ktoré tento model prijíma", + "Required.": "Povinné.", + "Registration is open.": "Registrácia je otvorená.", + "Timeout (seconds)": "Časový limit (sekundy)", + "The answer": "Odpoveď", + "The call": "Volanie", + "The index": "Index", + "The modes": "Režimy", + "The server": "Server", + "The terminal": "Terminál", + "The workflow": "Pracovný postup", + "The placeholders": "Zástupné hodnoty", + "The allowed port": "Povolený port", + "The first one": "Prvý", + "The first in the list above": "Prvý v zozname vyššie", + "The project directory": "Adresár projektu", + "The project's own instructions": "Vlastné pokyny projektu", + "Read the project's own instructions": "Čítať vlastné pokyny projektu", + "This chat is": "Táto konverzácia je", + "This chat's own working document": "Vlastný pracovný dokument tejto konverzácie", + "This is what the model would read back:": "Toto by si model prečítal:", + "Text kept per file": "Text uchovaný na súbor", + "Time spent waiting for you to answer does not count.": ( + "Čas strávený čakaním na vašu odpoveď sa nepočíta." + ), + "The heading on the card.": "Nadpis na karte.", + "Shown to you, in the list.": "Zobrazuje sa vám v zozname.", + "Shown instead of the raw id. Empty uses the id.": ( + "Zobrazuje sa namiesto surového id. Prázdne použije id." + ), + "Shown in the transcript when the model uses it.": ( + "Zobrazí sa v zápise, keď to model použije." + ), + "Sent as the first message when the card is clicked.": ( + "Pošle sa ako prvá správa po kliknutí na kartu." + ), + "Sent as the first message. Nothing is added to it, so a prompt that needs material should ask for it.": ( + "Pošle sa ako prvá správa. Nič sa k nej nepridáva, takže pokyn, ktorý " + "potrebuje podklady, si ich má vyžiadať." + ), + "Sent on its own after the first reply, not as part of any conversation.": ( + "Pošle sa samostatne po prvej odpovedi, nie ako súčasť konverzácie." + ), + "Sent to the model verbatim. This is the whole basis on which it decides whether to call this tool, so say what it does and when it is the right thing to use.": ( + "Posiela sa modelu slovo za slovom. Je to celý základ, na ktorom sa " + "rozhoduje, či tento nástroj zavolá — napíšte teda, čo robí a kedy je to " + "správna voľba." + ), + "Set per chat and switchable at any time. This is what each one means; the two lists below adjust them.": ( + "Nastavuje sa pre každú konverzáciu a dá sa kedykoľvek prepnúť. Toto je " + "význam každého z nich; dva zoznamy nižšie ich upravujú." + ), + "Signs them out everywhere. An administrator resetting a password usually means the account is compromised or the person has gone.": ( + "Odhlási ich všade. Keď správca obnovuje heslo, obyčajne to znamená, že je " + "účet napadnutý alebo že daný človek odišiel." + ), + "Turning this off signs them out everywhere at once, rather than waiting for a cookie to expire.": ( + "Vypnutím ich odhlásite všade naraz, namiesto čakania na vypršanie cookie." + ), + "Turn this off once your users exist. Sign-in is unaffected — existing accounts keep working, and the \"Create one\" link disappears from the sign-in page.": ( + "Vypnite to, keď už máte používateľov. Prihlasovanie to neovplyvní — " + "existujúce účty fungujú ďalej a odkaz „Vytvorte si ho“ zmizne z " + "prihlasovacej stránky." + ), + "Their chats, folders and library go too, and every share naming them or naming anything of theirs.": ( + "Zmiznú aj ich konverzácie, zložky a knižnica a každé zdieľanie, ktoré " + "menuje ich alebo čokoľvek ich." + ), + "The one place membership is edited. A user's own page links here rather than offering a second control for the same value.": ( + "Jediné miesto, kde sa upravuje členstvo. Stránka používateľa sem odkazuje, " + "namiesto toho, aby pre tú istú hodnotu ponúkala druhý ovládací prvok." + ), + "The lines with a bit of character in them. They live in the empty states, the error pages and the sign-in screen — never in the functional interface, where a button says what it does. Replace them with your own, or leave them.": ( + "Vety, ktoré majú trochu charakteru. Bývajú v prázdnych stavoch, na " + "chybových stránkach a na prihlasovacej obrazovke — nikdy vo funkčnom " + "rozhraní, kde tlačidlo hovorí, čo robí. Nahraďte ich vlastnými alebo ich " + "nechajte." + ), + "The whole system message, assembled from what is in the boxes below — including changes you have not saved yet. Your own memories and skills are used, because a preview against invented ones cannot tell you whether it reads well against what is actually there.": ( + "Celá systémová správa, zložená z toho, čo je v poliach nižšie — vrátane " + "zmien, ktoré ste ešte neuložili. Použijú sa vaše vlastné pamäti a " + "schopnosti, pretože náhľad proti vymysleným vám nepovie, či to dobre " + "sedí oproti tomu, čo tam naozaj je." + ), + "Defined in code — part of the schema sent to the endpoint alongside the prompt, not guidance layered on top of it. They are statements of fact about what each tool does, so they change when the tool does; editing them here would let the text quietly become a lie. A custom tool's description will be editable, because a custom tool is a row rather than a function.": ( + "Definované v kóde — sú súčasťou schémy, ktorá ide endpointu spolu s " + "pokynom, nie vedenie navrstvené nad ňou. Sú to tvrdenia o tom, čo každý " + "nástroj robí, takže sa menia, keď sa mení nástroj; ich úprava tu by " + "nechala text potichu sa stať lžou. Opis vlastného nástroja sa upravovať " + "dá, pretože vlastný nástroj je záznam, nie funkcia." + ), + "Put every prompt back to its built-in wording? Everything you have edited here is lost.": ( + "Vrátiť každý pokyn na jeho zabudovanú formuláciu? Všetko, čo ste tu " + "upravili, sa stratí." + ), + "Put this version back? The current one is kept in the history.": ( + "Vrátiť túto verziu? Súčasná zostane v histórii." + ), + "A theme is a set of colours, not a stylesheet — nothing in this interface hard-codes one, so a third palette composes with everything. Pick which built-in it starts from and change only what you want; everything left empty is inherited. The soft variants behind focus rings and selected rows are worked out from the accent, so you do not have to.": ( + "Vzhľad je sada farieb, nie stylopis — nič v tomto rozhraní ich nemá " + "zadrátované, takže tretia paleta zapadne ku všetkému. Vyberte, z ktorého " + "zabudovaného vychádza, a zmeňte len to, čo chcete; všetko ponechané " + "prázdne sa zdedí. Jemné varianty za sústredenými rámikmi a vybranými " + "riadkami sa dopočítajú z akcentu, takže to nemusíte robiť vy." + ), + "The disagreements are what a crowd is for; a column of bubbles saying nothing is what makes somebody switch it off. The text is still there behind a disclosure.": ( + "Nesúhlas je to, na čo je skupina; stĺpec bubliniek, ktoré nič nehovoria, " + "je to, čo niekoho prinúti vypnúť ju. Text tam stále je, skrytý pod " + "rozbalením." + ), + "Refused at the point of creation, with the reason. Existing schedules over a lowered limit keep running; only new ones are refused.": ( + "Odmietne sa pri vytvorení, s dôvodom. Existujúce plány nad zníženým " + "limitom bežia ďalej; odmietajú sa len nové." + ), + "Seconds. A floor on how often one schedule may come round. Raise it if people are setting things to run more often than the work takes.": ( + "Sekundy. Spodná hranica toho, ako často sa jeden plán môže vrátiť. Zvýšte " + "ju, ak si ľudia nastavujú veci častejšie, než práca trvá." + ), + "Seconds. After this the reply carries on without an answer and says so. At least a minute, whatever is typed here.": ( + "Sekundy. Po tomto čase odpoveď pokračuje bez odpovede a povie to. Najmenej " + "minúta, čokoľvek sem napíšete." + ), + "Seconds. This is how late a run can be, not how often anything happens: the finest a schedule can be set to is one minute, so anything under that buys nothing. One indexed query per tick.": ( + "Sekundy. Je to o tom, ako neskoro beh môže byť, nie ako často sa niečo " + "deje: najmenšia jednotka, na akú sa plán dá nastaviť, je jedna minúta, " + "takže čokoľvek pod ňou nič neprinesie. Jedna indexovaná otázka na tik." + ), + "Spent out of the context window on every call. The rest is cut off, and the model is told so.": ( + "Spotrebuje sa z kontextového okna pri každom volaní. Zvyšok sa odreže a " + "modelu sa to oznámi." + ), + "Read new replies aloud as they finish, by default": ( + "Predvolene čítať nové odpovede nahlas, keď dopíšu" + ), + "Read the response as": "Prečítať odpoveď ako", + "Only the starting value for each account — anyone can turn it off in their own settings, and nobody is made to listen.": ( + "Len počiatočná hodnota pre každý účet — každý si to môže vo vlastných " + "nastaveniach vypnúť a nikoho nikto počúvať nenúti." + ), + "Only used when SearXNG is the chosen provider. Your own instance, so no third party sees the queries.": ( + "Používa sa len vtedy, keď je zvoleným poskytovateľom SearXNG. Vaša vlastná " + "instancia, takže dotazy nevidí tretia strana." + ), + "Adds a microphone to the composer. Recordings are sent to this endpoint and never written to disk.": ( + "Pridá do písania mikrofón. Nahrávky sa posielajú na tento endpoint a nikdy " + "sa nezapisujú na disk." + ), + "Adds a speaker button to every reply. Each reader can pick their own voice in their settings; what is chosen here is the default.": ( + "Pridá ku každej odpovedi tlačidlo prehrávania. Každý si vo svojich " + "nastaveniach môže vybrať vlastný hlas; čo je vybrané tu, je predvolené." + ), + "Default voice": "Predvolený hlas", + "Summarise a document": "Zhrnúť dokument", + "Saving links": "Ukladanie odkazov", + "Applying an update": "Aplikuje sa aktualizácia", + "An update has been requested and is waiting for the helper to pick it up. The service restarts when it does.": ( + "Aktualizácia bola vyžiadaná a čaká, kým si ju pomocník prevezme. Služba sa " + "potom restartuje." + ), + "This checkout has uncommitted changes, and updating discards them. Nothing here is meant to be edited in place, so this usually means somebody was debugging on the box.": ( + "Tento checkout má nezapísané zmeny a aktualizácia ich zahodí. Nič tu nie je " + "určené na úpravu na mieste, takže to obyčajne znamená, že niekto na " + "stroji ladil." + ), + "This was not installed from a git checkout — a container image, or a wheel — so there is nothing here to compare or update. Pull a new image instead.": ( + "Toto nebolo nainštalované z git checkoutu — je to obraz kontejnera alebo " + "wheel — takže tu niet čo porovnávať ani aktualizovať. Stiahnite namiesto " + "toho nový obraz." + ), + } +) + +# --- Administration: images, extraction, MCP and updates ----------------------- +MESSAGES.update( + { + "URL": "URL", + "Unload URL": "URL na uvoľnenie", + "No unload call": "Bez volania na uvoľnenie", + "POST": "POST", + "Parameters": "Parametre", + "Variables": "Premenné", + "Version": "Verzia", + "Updates": "Aktualizácie", + "Users": "Používatelia", + "Wording": "Formulácie", + "Workflows": "Pracovné postupy", + "Weather": "Počasie", + "Web search": "Vyhľadávanie na webe", + "Width": "Šírka", + "Height": "Výška", + "Overlap": "Presah", + "Piece size": "Veľkosť časti", + "Pieces per request": "Častí na požiadavku", + "Denoise": "Odšumenie", + "Guidance (cfg)": "Vedenie (cfg)", + "Negative prompt": "Negatívny pokyn", + "Checkpoint": "Checkpoint", + "Checkpoints": "Checkpointy", + "Available checkpoints": "Dostupné checkpointy", + "Default workflow": "Predvolený pracovný postup", + "Offer this workflow": "Ponúkať tento postup", + "Images per run": "Obrázkov na beh", + "Attempts per image": "Pokusov na obrázok", + "JPEG quality": "Kvalita JPEG", + "Longest image edge": "Najdlhšia hrana obrázka", + "Largest upload": "Najväčší súbor", + "Keep abandoned uploads for": "Uchovať opustené súbory", + "Pages read from a PDF": "Strán prečítaných z PDF", + "Path into the JSON": "Cesta do JSON", + "Also treat as text": "Považovať za text aj", + "Checking the result": "Kontrola výsledku", + "Leave a box empty": "Nechajte pole prázdne", + "Use default": "Použiť predvolené", + "With selected:": "S vybranými:", + "Withdraw the request": "Zrušiť požiadavku", + "SDXL, photographic": "SDXL, fotografický", + "SVG is deliberately not accepted": "SVG sa zámerne neprijíma", + "What it is": "Čo to je", + "What it is for": "Na čo to je", + "What it is for, in one line.": "Na čo to je, v jednom riadku.", + "What it may spend": "Čo smie spotrebovať", + "What this account can do": "Čo tento účet smie", + "What one reply may spend": "Čo smie spotrebovať jedna odpoveď", + "What one command may spend": "Čo smie spotrebovať jeden príkaz", + "What a file may cost": "Čo smie stáť jeden súbor", + "What a generation uses by default": "Čo generovanie predvolene používa", + "What always needs asking": "Na čo sa treba vždy spýtať", + "What never needs asking": "Na čo sa netreba pýtať nikdy", + "What is this model good at?": "V čom je tento model dobrý?", + "Parameters, quantisation, a benchmark figure, what it is bad at": ( + "Parametre, kvantizácia, číslo z benchmarku, v čom je slabý" + ), + "Wake the model when a background job finishes": ( + "Prebudiť model, keď dobehne úloha na pozadí" + ), + "Offer web search to models that support tools": ( + "Ponúkať vyhľadávanie modelom, ktoré podporujú nástroje" + ), + "Offer image generation to models that support tools": ( + "Ponúkať generovanie obrázkov modelom, ktoré podporujú nástroje" + ), + "Look at each image before showing it, and try again if it is wrong": ( + "Pozrieť sa na každý obrázok pred zobrazením a pri chybe to skúsiť znova" + ), + "Refuse a PDF whose text cannot be read": "Odmietnuť PDF, ktorého text sa nedá prečítať", + "Preserve VRAM: unload the language model while ComfyUI works": ( + "Šetriť VRAM: uvoľniť jazykový model, kým pracuje ComfyUI" + ), + "The chat's own model, when it has vision": ( + "Vlastný model konverzácie, ak vidí obrázky" + ), + "Only models marked as having vision are listed. If there is nothing to ask, the first image is kept and nothing fails.": ( + "Vypisujú sa len modely označené, že vidia obrázky. Ak nie je koho sa " + "spýtať, zachová sa prvý obrázok a nič nezlyhá." + ), + "A vision model is shown the picture and the request it came from, and says keep or retry. Only clearly wrong images are retried — a missing subject, a mangled picture — because taste is not a fault and the next attempt is not promised to be better. The reader sees only the image that was kept.": ( + "Modelu, ktorý vidí obrázky, sa ukáže obrázok aj požiadavka, z ktorej " + "vznikol, a on povie zachovať alebo skúsiť znova. Znova sa skúšajú len " + "jasne pokazené obrázky — chýbajúci motív, zdeformovaná kresba — pretože " + "vkus nie je chyba a ďalší pokus nemá zaručené, že bude lepší. Čitateľ " + "vidí len ten obrázok, ktorý sa zachoval." + ), + "Including the first. The last attempt is kept whatever the review says, so a request always produces a picture.": ( + "Vrátane prvého. Posledný pokus sa zachová bez ohľadu na posudok, takže " + "požiadavka vždy vytvorí obrázok." + ), + "Lets a model draw a picture and show it in the conversation, on a ComfyUI you are running. It is offered as a tool the model chooses to call, so nothing changes for a conversation that never asks for one — and it is only offered once there is a ComfyUI, a workflow and at least one checkpoint, because a tool that fails on its first call is worse than a tool nobody was given.": ( + "Umožní modelu nakresliť obrázok a zobraziť ho v konverzácii, na ComfyUI, " + "ktoré prevádzkujete. Ponúka sa ako nástroj, ktorý si model sám zvolí, " + "takže pri konverzácii, ktorá o obrázok nikdy nepožiada, sa nič nemení — " + "a ponúka sa až vtedy, keď existuje ComfyUI, pracovný postup a aspoň jeden " + "checkpoint, pretože nástroj, ktorý zlyhá pri prvom volaní, je horší než " + "nástroj, ktorý nikto nedostal." + ), + "For a machine that cannot hold both at once. Before generating, the chat's own connection is asked to unload — set an unload URL on it under Connections, or nothing happens. Afterwards ComfyUI is asked to free its own models, and the language model loads again by itself on the next request.": ( + "Pre stroj, ktorý neudrží oba naraz. Pred generovaním sa vlastné spojenie " + "konverzácie požiada o uvoľnenie — nastavte mu URL na uvoľnenie v " + "Spojeniach, inak sa nestane nič. Potom sa ComfyUI požiada, aby uvoľnilo " + "svoje modely, a jazykový model sa pri ďalšej požiadavke nahrá sám." + ), + "How long to wait for one image, queue included. Far longer than any other timeout here, because the thing being waited for genuinely takes that long.": ( + "Ako dlho čakať na jeden obrázok, vrátane radu. Oveľa dlhšie než " + "akýkoľvek iný limit tu, pretože to, na čo sa čaká, naozaj tak dlho trvá." + ), + "What every picture is drawn with unless the model names otherwise. Leave a box empty to use the built-in value shown beside it — the built-ins are SD1.5-era, and 512×512 on an SDXL checkpoint is what produces the duplicated limbs.": ( + "S čím sa kreslí každý obrázok, ak model neuvedie inak. Prázdne pole " + "použije zabudovanú hodnotu uvedenú vedľa — zabudované sú z éry SD1.5 a " + "512×512 na SDXL checkpointe je presne to, čo vytvára zdvojené končatiny." + ), + "One filename per line, exactly as ComfyUI spells it. This is the list the model chooses from, so leaving out a checkpoint is how you stop it being used. Press Test below to read them off ComfyUI — that fills this in the first time and never overwrites it afterwards.": ( + "Jeden názov súboru na riadok, presne ako ho píše ComfyUI. Toto je zoznam, " + "z ktorého model vyberá, takže vynechanie checkpointu je spôsob, ako " + "zabrániť jeho použitiu. Stlačením Overiť nižšie sa prečítajú z ComfyUI — " + "prvý raz to toto pole vyplní a potom ho už nikdy neprepíše." + ), + "Where ComfyUI is listening. An address on this machine or this network is fine here and is not checked against the request-forgery rules — you typed it, unlike an address a model asks for.": ( + "Kde ComfyUI počúva. Adresa na tomto stroji alebo v tejto sieti je tu v " + "poriadku a neporovnáva sa s pravidlami proti falšovaniu požiadaviek — " + "napísali ste ju vy, na rozdiel od adresy, ktorú si vyžiada model." + ), + "Lowercase letters, digits, hyphens and underscores. This is what the model writes when it picks this workflow.": ( + "Malé písmená, číslice, spojovníky a podčiarkovníky. Toto model napíše, keď " + "si tento postup vyberie." + ), + "Used when the model names no template and the chat has no preference.": ( + "Použije sa, keď model neuvedie šablónu a konverzácia nemá preferenciu." + ), + "Used when the model does not write one of its own. It writes one often, so this is a floor rather than something always applied — “always add these words” belongs in the instructions below, where the model is told to include them.": ( + "Použije sa, keď si model nenapíše vlastný. Píše si ho často, takže je to " + "skôr spodná hranica než niečo, čo sa použije vždy — „vždy pridaj tieto " + "slová“ patrí do pokynov nižšie, kde sa modelu povie, aby ich zahrnul." + ), + "Used by chats on this model that have no prompt of their own. A chat's own prompt overrides this; this overrides the instance prompt.": ( + "Použijú ho konverzácie na tomto modeli, ktoré nemajú vlastný pokyn. Vlastný " + "pokyn konverzácie toto prebíja; toto prebíja pokyn instancie." + ), + "Prefer the SDXL template for anything photographic.": ( + "Na čokoľvek fotografické použi šablónu SDXL." + ), + "Photographic and slow. Best for people, interiors and product shots at 1024px.": ( + "Fotografický a pomalý. Najlepší na ľudí, interiéry a produktové zábery " + "pri 1024 px." + ), + "Pixels. Images are re-encoded before they are sent, because a phone photo is several megabytes of base64.": ( + "Pixely. Obrázky sa pred poslaním prekódujú, pretože fotka z telefónu je " + "niekoľko megabajtov base64." + ), + "Megabytes, before anything is done to it.": "Megabajty, pred akoukoľvek úpravou.", + "Hours. A file chosen in the composer and never sent. Swept at startup.": ( + "Hodiny. Súbor vybraný pri písaní a nikdy neposlaný. Zmetá sa pri štarte." + ), + "PNG, JPEG, WEBP or GIF, under 2 MB. Without one, the model gets a generated badge whose colour is derived from its id.": ( + "PNG, JPEG, WEBP alebo GIF, do 2 MB. Bez neho model dostane vygenerovaný " + "odznak, ktorého farba sa odvodí z jeho id." + ), + "Optional. Without one, a logo you upload is used at 32px, and without that the shipped leaf.": ( + "Nepovinné. Bez neho sa použije nahrané logo v 32 px, a bez toho dodávaný " + "list." + ), + "Up to 40,000 characters. Nothing here is validated: a rule that does not parse is dropped by the browser, quietly, as it would be in any stylesheet.": ( + "Až 40 000 znakov. Nič sa tu neoveruje: pravidlo, ktoré sa nedá prečítať, " + "prehliadač potichu zahodí, ako by to urobil v každom stylopise." + ), + "Extraction is slow and happens once, at upload. Beyond this the file is still stored; only its text stops.": ( + "Extrakcia je pomalá a stane sa raz, pri nahraní. Nad týmto sa súbor " + "stále uloží; prestane len jeho text." + ), + "Off, a scanned PDF is stored with an explanation saying why it contributes nothing — there is no OCR here. That is usually what somebody wants: the file is still attached and still downloadable.": ( + "Vypnuté: naskenované PDF sa uloží s vysvetlením, prečo neprináša nič — " + "OCR tu nie je. Obyčajne je to presne to, čo niekto chce: súbor je aj tak " + "priložený a aj tak sa dá stiahnuť." + ), + "Characters. A record is split on paragraph boundaries into pieces of about this size, and each is embedded separately — a document is found by its best piece, not by its average.": ( + "Znaky. Záznam sa na hraniciach odstavcov rozdelí na časti približne tejto " + "veľkosti a každá sa vnorí samostatne — dokument sa nájde podľa svojej " + "najlepšej časti, nie podľa priemeru." + ), + "How much of each piece is repeated at the start of the next, so a sentence across a boundary is whole somewhere. Capped at half the piece size.": ( + "Koľko z každej časti sa zopakuje na začiatku ďalšej, aby veta na hranici " + "bola niekde celá. Najviac polovica veľkosti časti." + ), + "Lower this if the endpoint refuses large requests; raise it if a rebuild is slow and the far side has room.": ( + "Znížte, ak endpoint odmieta veľké požiadavky; zvýšte, ak je " + "prestavba pomalá a druhá strana má priestor." + ), + "Documents, notes, skills and reports are indexed as they are written. A rebuild is for everything that already existed — or for after changing the model or the piece size, both of which make what is stored stop meaning anything. It runs in the background and can be left.": ( + "Dokumenty, poznámky, schopnosti a správy sa indexujú pri zápise. Prestavba " + "je pre všetko, čo už existovalo — alebo po zmene modelu či veľkosti " + "častí, po ktorej to, čo je uložené, prestane čokoľvek znamenať. Beží na " + "pozadí a dá sa nechať." + ), + "Off means no listing is built at all, and the file picker offers only what is in the library.": ( + "Vypnuté znamená, že sa nezostaví žiadny výpis a výber súborov ponúkne len " + "to, čo je v knižnici." + ), + "Placeholders are filled from the arguments and escaped, so a value cannot add a path segment or a query of its own. The scheme and host must be written out — they cannot come from an argument.": ( + "Zástupné hodnoty sa vyplnia z argumentov a escapujú, takže hodnota nemôže " + "pridať vlastný segment cesty ani vlastný dotaz. Schéma a hostiteľ musia " + "byť napísané — nemôžu prísť z argumentu." + ), + "A server's tool names and descriptions are sent to the model as instructions, and what it returns is read back as fact. Add servers you trust, the way you would a dependency.": ( + "Názvy a opisy nástrojov servera idú modelu ako pokyny a to, čo vráti, sa " + "čita ako fakt. Pridávajte servery, ktorým veríte, tak ako by ste pridávali " + "závislosť." + ), + "The streamable-HTTP endpoint itself, the one that accepts a POST. A server that answers with a redirect to somewhere else will be refused.": ( + "Samotný streamable-HTTP endpoint, ten, ktorý prijíma POST. Server, ktorý " + "odpovie presmerovaním niekam inam, bude odmietnutý." + ), + "Discovered at the last refresh. Untick one to withhold it — a tool this server adds later is offered by default.": ( + "Zistené pri poslednom obnovení. Odškrtnutím ho zadržíte — nástroj, ktorý " + "tento server pridá neskôr, sa predvolene ponúka." + ), + "No servers yet. You will need the URL of an MCP endpoint that speaks streamable HTTP — local ones launched as a subprocess are not supported.": ( + "Zatiaľ žiadne servery. Budete potrebovať URL endpointu MCP, ktorý hovorí " + "streamable HTTP — lokálne, spúšťané ako podproces, nie sú podporované." + ), + "No workflows yet. One is needed before anything can be drawn; the default one is filled in for you when you add the first.": ( + "Zatiaľ žiadne postupy. Jeden je potrebný, aby sa dalo niečo nakresliť; " + "predvolený sa vyplní sám, keď pridáte prvý." + ), + "Nothing configured yet. Add a connection above and its models appear here.": ( + "Zatiaľ nič nastavené. Pridajte spojenie vyššie a jeho modely sa tu zobrazia." + ), + "Nothing known about the remote yet. Check it above.": ( + "O vzdialenej strane sa zatiaľ nič nevie. Overte ju vyššie." + ), + "Nothing yet. Write one, or let the model write its own.": ( + "Zatiaľ nič. Napíšte jednu alebo nechajte model napísať si vlastnú." + ), + "None — keyword search only": "Žiadny — len hľadanie podľa slov", + "None — send nothing": "Žiadne — neposielať nič", + "The new-chat screen shows its empty state instead.": ( + "Obrazovka novej konverzácie namiesto toho zobrazí svoj prázdny stav." + ), + "What this default said before each change. Each person's own personality keeps its own history, which they can see and restore in their own settings — this is the starting point's history, not theirs.": ( + "Čo táto predvoľba hovorila pred každou zmenou. Vlastná povaha každého " + "človeka má svoju vlastnú históriu, ktorú si vidí a vie obnoviť vo svojich " + "nastaveniach — toto je história východiskového bodu, nie ich." + ), + "When a model asks a question it can offer answers to pick from, and by default a box to write something else. Turn this off if you would rather nobody typed free text into a prompt a model composed.": ( + "Keď sa model na niečo pýta, môže ponúknuť odpovede na výber a predvolene " + "aj pole na napísanie niečoho iného. Vypnite, ak nechcete, aby niekto " + "písal voľný text do pokynu, ktorý zložil model." + ), + "What every signed-in user can do before any group is considered. Turn something off here and grant it through a group to make it opt-in.": ( + "Čo smie každý prihlásený používateľ predtým, než sa berie do úvahy " + "akákoľvek skupina. Vypnite tu niečo a udeľte to cez skupinu, aby to bolo " + "na prihlásenie." + ), + "What happens to a file between the upload and the model, and how anything is found again afterwards. The two are the same pipeline: what is extracted decides what there is to search.": ( + "Čo sa so súborom stane medzi nahraním a modelom a ako sa potom čokoľvek " + "znova nájde. Sú to tie isté koľaje: čo sa vytiahne, rozhoduje o tom, v " + "čom sa dá hľadať." + ), + "What the interface is in for anybody who has not chosen for themselves. Everyone can pick their own under Appearance in their settings, and that choice wins here.": ( + "V akom jazyku je rozhranie pre každého, kto si nevybral sám. Každý si môže " + "vybrať vlastný v nastaveniach pod Vzhľad a tá voľba tu vyhráva." + ), + "Two endpoints speaking the OpenAI audio API: one that turns speech into text so a message can be dictated, one that reads a reply out. They are configured separately because they usually are separate servers — whisper.cpp and Kokoro, say, or Speaches for both.": ( + "Dva endpointy hovoriace zvukovým API OpenAI: jeden premieňa reč na text, " + "aby sa dala správa nadiktovať, druhý odpoveď prečíta. Nastavujú sa " + "oddelene, pretože to obyčajne sú oddelené servery — napríklad whisper.cpp " + "a Kokoro, alebo Speaches na oboje." + ), + "Uncheck to restrict this model to chosen groups. Administrators always have access.": ( + "Odškrtnutím obmedzíte tento model na vybrané skupiny. Správcovia majú " + "prístup vždy." + ), + "Uncheck to restrict this tool to chosen groups. Administrators always have access.": ( + "Odškrtnutím obmedzíte tento nástroj na vybrané skupiny. Správcovia majú " + "prístup vždy." + ), + "Without going through registration — useful when sign-up is closed.": ( + "Bez prechodu registráciou — užitočné, keď je registrácia zatvorená." + ), + "Update and restart? Replies being written are saved; open terminals are cut off.": ( + "Aktualizovať a restartovať? Odpovede, ktoré sa práve píšu, sa uložia; " + "otvorené terminály sa odstrihnú." + ), + "You are a helpful assistant.": "Si užitočný asistent.", + } +) + +# --- Small words and status labels --------------------------------------------- +# The technical ones map to themselves on purpose: a model id, a parameter name, a +# URL or an effort level is sent to an endpoint or typed by a person, and +# translating it would either break the request or describe something that is not +# what the field contains. +MESSAGES.update( + { + "adds": "pridáva", + "admin": "správca", + "all inherited": "všetko zdedené", + "already in the baseline": "už v základe", + "and": "a", + "backwards": "dozadu", + "below the cut": "pod hranicou", + "can": "smie", + "closed": "zatvorené", + "default": "predvolené", + "detect": "zistiť", + "disabled": "vypnuté", + "dusk": "súmrak", + "edited": "upravené", + "empty": "prázdne", + "every": "každých", + "for": "pre", + "hidden": "skryté", + "ignores this": "toto ignoruje", + "inherited": "zdedené", + "last call failed": "posledné volanie zlyhalo", + "last written by the model": "naposledy napísal model", + "last written here": "naposledy napísané tu", + "maximum": "najviac", + "mallorn tree": "mallornový strom", + "needs tools": "potrebuje nástroje", + "no opinion": "bez názoru", + "no setup": "bez nastavenia", + "not": "nie", + "not granted": "neudelené", + "not installed on this host": "na tomto stroji nenainštalované", + "not the same for every model": "nie je to isté pre každý model", + "nothing released yet": "zatiaľ nič vydané", + "off": "vypnuté", + "on": "zapnuté", + "only in the title request": "len v požiadavke na názov", + "open": "otvorené", + "pending": "čaká", + "pinned": "pripnuté", + "private network": "privátna sieť", + "quotas": "kvóty", + "restricted": "obmedzené", + "saved": "uložené", + "schedule": "plán", + "search": "hľadanie", + "source": "zdroj", + "stopped": "zastavené", + "theirs": "ich", + "this": "toto", + "uncommitted changes": "nezapísané zmeny", + "union": "zjednotenie", + "unknown": "neznáme", + "up to date": "aktuálne", + "when": "kedy", + "whole": "celé", + "with tools": "s nástrojmi", + "without": "bez", + "you": "vy", + "model": "model", + "models × rounds × 2 − 1": "modely × kolá × 2 − 1", + # Sent to an endpoint or typed verbatim: not prose. + "chat_template_kwargs": "chat_template_kwargs", + "reasoning_effort": "reasoning_effort", + "reasoning": "reasoning", + "vision": "vision", + "tools": "tools", + "embeddings": "embeddings", + "minimal": "minimal", + "low/medium/high": "low/medium/high", + "low/medium/xhigh": "low/medium/xhigh", + "high": "high", + "max": "max", + "xhigh": "xhigh", + "seed": "seed", + "euler (built-in)": "euler (zabudovaný)", + "normal (built-in)": "normal (zabudovaný)", + "text, watermark": "text, watermark", + "sd_xl_base_1.0.safetensors": "sd_xl_base_1.0.safetensors", + "sdxl-photo": "sdxl-photo", + "data.items.0.title": "data.items.0.title", + "github": "github", + "weather": "weather", + "wt-wt": "wt-wt", + "sk-…": "sk-…", + "tts-1": "tts-1", + "whisper-1": "whisper-1", + "http://127.0.0.1:8081": "http://127.0.0.1:8081", + "http://127.0.0.1:8188": "http://127.0.0.1:8188", + "http://127.0.0.1:8880": "http://127.0.0.1:8880", + "http://127.0.0.1:8888": "http://127.0.0.1:8888", + "http://localhost:1234/v1": "http://localhost:1234/v1", + "https://api.firecrawl.dev": "https://api.firecrawl.dev", + "https://mcp.example.com/mcp": "https://mcp.example.com/mcp", + } +) diff --git a/src/lembas/web/templates/admin/_connection_row.html b/src/lembas/web/templates/admin/_connection_row.html index 0cf2658..83c98dd 100644 --- a/src/lembas/web/templates/admin/_connection_row.html +++ b/src/lembas/web/templates/admin/_connection_row.html @@ -17,7 +17,7 @@ {{ model_count }} model{{ '' if model_count == 1 else 's' }} {% endif %} {% if not connection.enabled %} - disabled + {{ t("disabled") }} {% endif %} @@ -28,7 +28,7 @@ formnovalidate> {{ icon("refresh", "icon--sm") }} Test & refresh - + @@ -45,23 +45,23 @@ {% endif %}
- +
- +
- + + placeholder="{{ t('No key set') }}">

{% if connection.api_key_encrypted %} Currently {{ masked }}. Leave the dots alone to keep it, @@ -73,14 +73,14 @@

- +
- + +

@@ -92,10 +92,10 @@

- +

One Name: value per line, sent with every request to this @@ -109,7 +109,7 @@

diff --git a/src/lembas/web/templates/admin/_layout.html b/src/lembas/web/templates/admin/_layout.html index f8827c4..8f3f977 100644 --- a/src/lembas/web/templates/admin/_layout.html +++ b/src/lembas/web/templates/admin/_layout.html @@ -29,76 +29,76 @@ {% include "partials/_sidebar_close.html" %} -