From 9fd4e80563398247680256a1c8133911e1c26897 Mon Sep 17 00:00:00 2001 From: Richard Drake Date: Sat, 7 Nov 2026 18:27:27 +0000 Subject: [PATCH 01/16] Translation panel: a header popover like the mentions, opened from the channel menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mentions pane's look becomes one shared set of classes, so a pane under a header button is built from it instead of styled again: .header-popover-container (the v-backdrop over #viewport), .header-popover (the surface; the whole chat under the header on a phone), .header-popover-title with its .header-popover-aside and .header-popover-close, placed by helpers/headerPopover.ts placeUnder. The themes that styled .mentions-popup style .header-popover instead. TranslationPanel.vue is now one of these: under the channel menu's ⋮, the same title row as the mentions, the sections and hints in every layout, formality as the segmented control everywhere, controls 2.25rem with a pointer and 2.75rem under a finger. It closes through its own escape layer (so Android's Back closes it too), keeps Tab inside with trapTab, and a press outside closes it and does what the header button there says, as the mentions pane does. The segmented control no longer leaves an empty third cell under two choices, and takes the pane's own colours rather than the composer's (black on black on zelenzy). The channel header's translate button is gone: "Translation…" in the channel menu is the one way in, and the panel's switch is reading's on/off. Its nine tooltip strings leave the pot and the catalogs. Context menu icons get a 1.5rem box with the glyph centred, so the language glyph (a full 1.25em) no longer runs into its label. Docs: client-features.md § Header popovers, translation.md § The channel's panel. The translation scenarios open the panel through the menu (lib/translation.mjs openTranslationPanel, READING_ON). --- client/components/Chat.vue | 110 +---- client/components/Mentions.vue | 141 +----- client/components/TranslationPanel.vue | 573 +++++++++++------------ client/css/style.css | 469 +++++++++---------- client/js/helpers/headerPopover.ts | 24 + client/js/i18n/call-sites.ts | 9 - client/locales/ar.json | 9 - client/locales/ar.po | 54 --- client/locales/cs.json | 9 - client/locales/cs.po | 54 --- client/locales/da.json | 9 - client/locales/da.po | 54 --- client/locales/de.json | 9 - client/locales/de.po | 54 --- client/locales/en.json | 9 - client/locales/es.json | 9 - client/locales/es.po | 54 --- client/locales/fil.json | 9 - client/locales/fil.po | 54 --- client/locales/fr.json | 9 - client/locales/fr.po | 54 --- client/locales/hu.json | 9 - client/locales/hu.po | 54 --- client/locales/it.json | 9 - client/locales/it.po | 54 --- client/locales/ja.json | 9 - client/locales/ja.po | 54 --- client/locales/ko.json | 9 - client/locales/ko.po | 54 --- client/locales/messages.pot | 55 --- client/locales/nl.json | 9 - client/locales/nl.po | 54 --- client/locales/pl.json | 9 - client/locales/pl.po | 54 --- client/locales/pt.json | 9 - client/locales/pt.po | 54 --- client/locales/qqx.json | 9 - client/locales/ro.json | 9 - client/locales/ro.po | 54 --- client/locales/ru.json | 9 - client/locales/ru.po | 54 --- client/locales/sk.json | 9 - client/locales/sk.po | 54 --- client/locales/sv.json | 9 - client/locales/sv.po | 54 --- client/locales/th.json | 9 - client/locales/th.po | 54 --- client/locales/tr.json | 9 - client/locales/tr.po | 54 --- client/locales/uk.json | 9 - client/locales/uk.po | 54 --- client/locales/vi.json | 9 - client/locales/vi.po | 54 --- client/locales/zh.json | 9 - client/locales/zh.po | 54 --- client/themes/bourbaki.css | 2 +- client/themes/coffee.css | 2 +- client/themes/mardam.css | 2 +- client/themes/ps.css | 4 +- client/themes/zelenzy.css | 6 +- docs/resources/client-features.md | 8 +- docs/resources/translation.md | 71 ++- test/themes/ps.ts | 2 +- tools/scenarios/lib/translation.mjs | 60 +++ tools/scenarios/translate-capability.mjs | 74 +-- tools/scenarios/translate-composer.mjs | 88 ++-- tools/scenarios/translate-consent.mjs | 43 +- tools/scenarios/translate-off.mjs | 14 +- tools/scenarios/translate-reading.mjs | 224 +++------ tools/scenarios/translation-chips.mjs | 16 +- 70 files changed, 745 insertions(+), 2719 deletions(-) create mode 100644 client/js/helpers/headerPopover.ts diff --git a/client/components/Chat.vue b/client/components/Chat.vue index 8b0a40b0f..93f8d547e 100644 --- a/client/components/Chat.vue +++ b/client/components/Chat.vue @@ -71,21 +71,6 @@ :aria-label="connectingLabel" /> - - @@ -117,21 +92,10 @@ export default defineComponent({ /** The detector's runners-up, for the preselection when there is no source. */ candidates: {type: Array as PropType, default: () => []}, /** - * What the language is for: `source` retranslates a line from it - * (the chip menu's "Retranslate from…"), `target` translates the - * composer's draft into it, this once (the translate button). - */ - purpose: {type: String as PropType<"source" | "target">, default: "source"}, - /** `target` only: the source preselected -- the language the user reads. */ - selectedFrom: {type: String, default: ""}, - /** - * `source` only: the language the line is translated into, so the - * dialog can say when the picked source has no engine here. Empty: - * not checked. + * The language the line is translated into, so the dialog can say + * when the picked source has no engine here. Empty: not checked. */ to: {type: String, default: ""}, - /** `target` only: a draft is there, so "This message only" is on offer. */ - onceOffered: {type: Boolean, default: false}, }, emits: ["pick", "close"], setup(props, {emit}) { @@ -147,37 +111,14 @@ export default defineComponent({ const languages = computed(() => [...SUPPORTED_LANGUAGES].sort((a, b) => collator().compare(name(a), name(b))) ); - const dialogLabel = computed(() => - props.purpose === "target" - ? t("translate.picker.targetDialog") - : t("translate.picker.dialog") - ); - const fieldLabel = computed(() => - props.purpose === "target" - ? t("translate.picker.targetLabel") - : t("translate.picker.label") - ); const choice = ref(props.selected || props.candidates[0] || languages.value[0]); - // The source of a one-off: the language the user reads, or "" for - // detection. Picking the same language twice asks for a cleanup. - const from = ref(props.purpose === "target" ? props.selectedFrom : ""); - // "This message only": a one-off rather than the send target. - const once = ref(false); - const sameLanguage = computed( - () => props.purpose === "target" && from.value !== "" && from.value === choice.value - ); // The pair the confirm would ask for, through the router's own rule - // (reader.ts `pairVerdict`): a pair nothing here can serve -- the - // cleanup pass without the GPU model, both engines switched off -- - // is offered disabled with its reason, never left to fail later. - const verdict = computed(() => { - if (props.purpose === "target") { - return pairVerdict(from.value || null, choice.value); - } + // (reader.ts `pairVerdict`): a pair nothing here can serve -- both + // engines switched off, say -- is offered disabled with its reason, + // never left to fail later. + const verdict = computed(() => (props.to ? pairVerdict(choice.value, props.to) : null)); - return props.to ? pairVerdict(choice.value, props.to) : null; - }); const hintId = `source-language-picker-hint-${++pickers}`; const blockedLead = computed(() => { const v = verdict.value; @@ -190,12 +131,7 @@ export default defineComponent({ return t("translate.picker.sameLanguageNeedsGpu"); } - const pairFrom = props.purpose === "target" ? from.value : choice.value; - const pairTo = props.purpose === "target" ? choice.value : props.to; - - return pairFrom - ? t("translate.pair.unavailable", {from: name(pairFrom), to: name(pairTo)}) - : t("translate.pair.unavailableInto", {to: name(pairTo)}); + return t("translate.pair.unavailable", {from: name(choice.value), to: name(props.to)}); }); const blockedWhy = computed(() => { const v = verdict.value; @@ -203,18 +139,6 @@ export default defineComponent({ return v && !v.route ? translateWhyText(v.why, liveLlmName()) : ""; }); - // A target that is the source is a cleanup pass, not a translation: - // the button says what it will do (writer.ts, the strip's chip). - const confirmLabel = computed(() => { - if (props.purpose !== "target") { - return t("translate.picker.confirm"); - } - - return sameLanguage.value - ? t("translate.picker.targetConfirmCorrect") - : t("translate.picker.targetConfirm"); - }); - const narrowQuery = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia(NARROW) @@ -300,12 +224,7 @@ export default defineComponent({ ); const confirm = () => { - emit( - "pick", - choice.value, - props.purpose === "target" ? from.value || null : undefined, - props.purpose === "target" ? once.value && props.onceOffered : undefined - ); + emit("pick", choice.value); close(); }; @@ -366,12 +285,6 @@ export default defineComponent({ select, choice, languages, - dialogLabel, - fieldLabel, - confirmLabel, - from, - once, - sameLanguage, hintId, blockedLead, blockedWhy, diff --git a/client/css/style.css b/client/css/style.css index 43d689feb..0f6ddc772 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -2525,22 +2525,9 @@ html[data-contrast="more"] .translation-panel-segment[aria-checked="true"] { line-height: normal; } -/* "This message only": a checkbox row between the selects and the buttons. */ -.source-language-picker-once { - display: flex; - align-items: center; - gap: 0.4em; - font-size: 0.875rem; - cursor: pointer; -} - -.source-language-picker-once-box { - margin: 0; -} - -/* Prose under the selects: what a same-language pick does, or why the - pair cannot run here (a lead and its reason, two sentences, which the - disabled confirm button is described by). Empty, it takes no gap. */ +/* Prose under the select: why the pair cannot run here (a lead and its + reason, two sentences, which the disabled confirm button is described + by). Empty, it takes no gap. */ .source-language-picker-hint { margin: 0; color: var(--body-color-muted); @@ -2635,6 +2622,284 @@ html[data-shell="native"] .source-language-picker--sheet { padding: 0.75em 1em; } +/* The composer's language sheet (ComposerLanguageSheet.vue): the pair on + top, the channel's languages as chips, a search and the whole list. A + popover over the translate button on a desktop; on a phone a sheet + docked to the chat pane's bottom edge. Teleported to . */ +.language-sheet { + position: fixed; + z-index: 1000; + display: flex; + flex-direction: column; + gap: 0.625rem; + width: 21rem; + max-width: calc(100vw - 1rem); + max-height: min(32rem, 80vh); + box-sizing: border-box; + padding: 0.75rem; + border: 1px solid var(--window-border, rgb(128 128 128 / 30%)); + border-radius: 0.5rem; + background: var(--window-bg-color, var(--body-bg-color)); + box-shadow: 0 0.5rem 1.5rem rgb(0 0 0 / 25%); + color: var(--body-color); + font-size: 0.875rem; +} + +.language-sheet:focus { + outline: none; +} + +.language-sheet--phone { + inset: auto 0 0; + width: auto; + max-width: none; + max-height: calc(var(--viewport-height, 100vh) * 0.75); + padding: 1rem 1rem max(1rem, var(--inset-inner-bottom, 0px)); + border-width: 1px 0 0; + border-radius: 0.75rem 0.75rem 0 0; + font-size: 1rem; +} + +html[data-shell="native"] .language-sheet--phone { + bottom: var(--inset-outer-bottom); + + /* The insets are physical (the notch is a side of the screen). */ + padding-right: calc(1rem + var(--inset-right)); + padding-left: calc(1rem + var(--inset-left)); +} + +/* I write ⇄ They read: two halves, the one the list fills in lit. */ +.language-sheet-pair { + display: flex; + flex: 0 0 auto; + align-items: stretch; + gap: 0.375rem; +} + +.language-sheet-side { + display: flex; + flex: 1 1 0; + flex-direction: column; + gap: 0.125rem; + min-width: 0; + padding: 0.4rem 0.625rem; + border: 1px solid var(--window-border, rgb(128 128 128 / 30%)); + border-radius: 0.375rem; + background: transparent; + color: inherit; + font: inherit; + text-align: start; + cursor: pointer; +} + +.language-sheet-side.active { + border-color: var(--chat-accent, var(--link-color)); + background: var(--accent-tint-20, rgb(128 128 128 / 15%)); +} + +.language-sheet-side-label { + color: var(--body-color-muted); + font-size: 0.75em; +} + +.language-sheet-side-value { + overflow: hidden; + font-weight: 600; + text-overflow: ellipsis; + white-space: nowrap; +} + +.language-sheet-swap { + flex: 0 0 auto; + align-self: center; + width: 2rem; + height: 2rem; + padding: 0; + border: 0; + border-radius: 50%; + background: transparent; + color: var(--chat-accent, var(--link-color)); + font-size: 1.125rem; + cursor: pointer; +} + +.language-sheet-swap:disabled { + color: var(--body-color-muted); + cursor: default; + opacity: 0.5; +} + +.language-sheet-recent { + display: flex; + flex: 0 0 auto; + flex-direction: column; + gap: 0.375rem; +} + +.language-sheet-recent-label { + color: var(--body-color-muted); + font-size: 0.8125em; +} + +.language-sheet-chips { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; +} + +.language-sheet-chip { + padding: 0.25rem 0.75rem; + border: 1px solid var(--window-border, rgb(128 128 128 / 30%)); + border-radius: 1rem; + background: transparent; + color: inherit; + font: inherit; + font-size: 0.875em; + cursor: pointer; +} + +.language-sheet-chip.active { + border-color: var(--chat-accent, var(--link-color)); + color: var(--chat-accent, var(--link-color)); +} + +.language-sheet-off { + color: var(--body-color-muted); +} + +.language-sheet-search { + flex: 0 0 auto; + width: 100%; + box-sizing: border-box; + height: 2.25rem; + margin: 0; + padding: 0 0.625rem; + border-radius: 0.375rem; + font: inherit; +} + +.language-sheet--phone .language-sheet-search { + height: 2.75rem; +} + +/* The list takes what height is left and scrolls. */ +.language-sheet-list { + flex: 1 1 auto; + min-height: 6rem; + margin: 0; + padding: 0; + overflow-y: auto; + list-style: none; + overscroll-behavior: contain; +} + +.language-sheet-row { + display: flex; + align-items: baseline; + gap: 0.5rem; + width: 100%; + padding: 0.45rem 0.5rem; + border: 0; + border-radius: 0.25rem; + background: transparent; + color: inherit; + font: inherit; + text-align: start; + cursor: pointer; +} + +.language-sheet--phone .language-sheet-row { + padding-block: 0.7rem; +} + +.language-sheet-row:hover, +.language-sheet-row:focus-visible, +.language-sheet-row.pending { + background: var(--accent-tint-20, rgb(128 128 128 / 15%)); +} + +.language-sheet-row[aria-current="true"] { + color: var(--chat-accent, var(--link-color)); + font-weight: 600; +} + +.language-sheet-row.blocked .language-sheet-name, +.language-sheet-row.blocked .language-sheet-endonym { + opacity: 0.55; +} + +.language-sheet-name { + flex: 0 1 auto; + min-width: 0; + overflow-wrap: anywhere; +} + +.language-sheet-endonym { + flex: 1 1 auto; + min-width: 0; + color: var(--body-color-muted); + font-size: 0.875em; + font-weight: normal; + overflow-wrap: anywhere; +} + +.language-sheet-status { + flex: 0 0 auto; + margin-inline-start: auto; + color: var(--body-color-muted); + font-size: 0.8125em; + font-weight: normal; + white-space: nowrap; +} + +.language-sheet-check { + flex: 0 0 auto; + margin-inline-start: auto; +} + +.language-sheet-status + .language-sheet-check { + margin-inline-start: 0; +} + +.language-sheet-empty { + padding: 0.75rem 0.5rem; + color: var(--body-color-muted); +} + +.language-sheet-hint { + flex: 0 0 auto; + color: var(--body-color-muted); + font-size: 0.8125em; +} + +.language-sheet-hint:empty { + display: none; +} + +.language-sheet-hint p { + margin: 0; +} + +.language-sheet-hint p + p { + margin-block-start: 0.25rem; +} + +.language-sheet-blocked { + color: var(--body-color); +} + +.language-sheet-once { + display: flex; + flex: 0 0 auto; + align-items: center; + gap: 0.4em; + cursor: pointer; +} + +.language-sheet-once-box { + margin: 0; +} + /* Compose mode. A phone in landscape has ~430px; the keyboard and Safari's * form bar take ~250 of them, so the header and the composer leave a single * line of scrollback. While the composer has the caret in a band that short @@ -9403,6 +9668,52 @@ html[data-reduce-motion] #chat .msg.actions-open .msg-actions { font-style: italic; } +/* The suggestion card: "Most of #linux writes in Portuguese…", above the + strip, in the strip's accent rule. */ +#form .translate-suggest { + flex: 1 0 100%; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.25rem 0.5rem; + margin: 0 0.25rem 0.25rem; + padding: 0.375rem 0.5rem; + border-inline-start: 2px solid var(--chat-accent, var(--link-color)); + border-start-end-radius: 0.1875rem; + border-end-end-radius: 0.1875rem; + background: var(--accent-tint-20, rgb(128 128 128 / 15%)); + color: var(--body-color); + font-size: 0.875rem; + line-height: 1.4; +} + +#form .translate-suggest-lead { + flex: 1 1 100%; + margin: 0; +} + +#form .translate-suggest-note { + flex: 1 1 100%; + margin: 0; + color: var(--body-color-muted); + font-size: 0.75rem; +} + +#form .translate-suggest-actions { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; +} + +#form .translate-suggest-actions .btn { + margin: 0; +} + +#form .translate-suggest-accept { + background: var(--button-color); + color: var(--button-text-color-hover); +} + /* Both rows of the strip: the translation about to be sent, and the read-back under it. `body` is `user-select: none`, so without this the line the user is being asked to approve cannot be selected or copied. */ diff --git a/client/js/i18n/call-sites.ts b/client/js/i18n/call-sites.ts index 1971cc690..6599524ad 100644 --- a/client/js/i18n/call-sites.ts +++ b/client/js/i18n/call-sites.ts @@ -2437,16 +2437,8 @@ export const STATIC_CALL_SITES = new Set([ "translate.picker.cancel", "translate.picker.confirm", "translate.picker.dialog", - "translate.picker.fromAuto", - "translate.picker.fromLabel", "translate.picker.label", - "translate.picker.sameLanguage", "translate.picker.sameLanguageNeedsGpu", - "translate.picker.targetConfirm", - "translate.picker.targetConfirmCorrect", - "translate.picker.targetDialog", - "translate.picker.targetLabel", - "translate.picker.thisMessageOnly", "translate.reason.answered", "translate.reason.degenerate", "translate.reason.narration", @@ -2482,6 +2474,20 @@ export const STATIC_CALL_SITES = new Set([ "translate.settings.useGpu", "translate.settings.useNllb", "translate.settings.workerError", + "translate.sheet.choose", + "translate.sheet.corrects", + "translate.sheet.detect", + "translate.sheet.dialog", + "translate.sheet.download", + "translate.sheet.iWrite", + "translate.sheet.noMatch", + "translate.sheet.off", + "translate.sheet.recent", + "translate.sheet.search", + "translate.sheet.swap", + "translate.sheet.theyRead", + "translate.sheet.thisMessageOnly", + "translate.sheet.unavailable", "translate.strip.checkFailed", "translate.strip.copied", "translate.strip.copy", @@ -2499,6 +2505,10 @@ export const STATIC_CALL_SITES = new Set([ "translate.strip.sendsWhenReady", "translate.strip.sourceAuto", "translate.strip.willSendIn", + "translate.suggest.accept", + "translate.suggest.decline", + "translate.suggest.download", + "translate.suggest.lead", "translate.system.asking", "translate.system.download", "translate.system.heading", diff --git a/client/js/translate/channelStore.ts b/client/js/translate/channelStore.ts index 9030dc35b..85ff66898 100644 --- a/client/js/translate/channelStore.ts +++ b/client/js/translate/channelStore.ts @@ -61,6 +61,11 @@ export interface ChannelTranslation { * dialog next time; null until it has been used. */ once: string | null; + /** + * The composer's suggestion card ("Most of #linux writes in + * Portuguese") was turned down here: it is not offered again. + */ + noSuggest: boolean; } export interface StorageBackend { @@ -96,6 +101,7 @@ export function defaultChannelTranslation(): ChannelTranslation { languages: [], terms: [], once: null, + noSuggest: false, }; } @@ -180,6 +186,7 @@ function sanitize(value: unknown): ChannelTranslation | null { out.once = typeof raw.once === "string" && isSupported(raw.once) ? raw.once : null; out.writeFrom = typeof raw.writeFrom === "string" && isSupported(raw.writeFrom) ? raw.writeFrom : null; + out.noSuggest = raw.noSuggest === true; out.formality = isFormality(raw.formality) ? raw.formality : "auto"; out.variant = typeof raw.variant === "string" ? raw.variant : ""; out.languages = languagesOf(raw.languages); diff --git a/client/js/translate/reader.ts b/client/js/translate/reader.ts index 7e2f1ab70..0f8dbb90e 100644 --- a/client/js/translate/reader.ts +++ b/client/js/translate/reader.ts @@ -866,7 +866,7 @@ export function setChannelOptions( patch: Partial< Pick< ChannelTranslation, - "write" | "writeFrom" | "formality" | "variant" | "languages" | "once" + "write" | "writeFrom" | "formality" | "variant" | "languages" | "once" | "noSuggest" > > ): void { diff --git a/client/js/translate/recent.ts b/client/js/translate/recent.ts new file mode 100644 index 000000000..bff2bf641 --- /dev/null +++ b/client/js/translate/recent.ts @@ -0,0 +1,86 @@ +// The languages a channel has been written in lately, from the reading +// pipeline's own verdicts (store.state.translations), for the composer's +// language sheet ("Spoken in #linux lately") and its suggestion card +// ("Most of #linux writes in Portuguese"). Vue-free and store-free: the +// caller hands in the messages and the entries, so mocha pins the rules. + +/** How far back the channel is looked at: its newest lines, others' only. */ +export const RECENT_LINES = 60; + +/** The suggestion needs at least this many placed lines in the language… */ +export const SUGGEST_MIN_LINES = 5; + +/** …and at least this share of all the placed lines looked at. */ +export const SUGGEST_MIN_SHARE = 0.5; + +export interface RecentMessage { + id: number; + self?: boolean; + type?: string; +} + +export interface RecentEntry { + status: string; + from: string; +} + +export interface LanguageCount { + code: string; + count: number; +} + +/** + * Each language the channel's newest `RECENT_LINES` messages from others were + * placed in, most lines first (ties by code, so the order is stable). A line + * with no verdict yet, or one the detector could not place (`from` ""), is + * not counted; one skipped as the reading language is, under that language. + */ +export function recentLanguages( + messages: readonly RecentMessage[], + entries: Readonly> +): LanguageCount[] { + const counts = new Map(); + let looked = 0; + + for (let i = messages.length - 1; i >= 0 && looked < RECENT_LINES; i--) { + const message = messages[i]; + + if (message.self || (message.type !== "message" && message.type !== "action")) { + continue; + } + + looked++; + const entry = entries[message.id]; + + if (!entry || !entry.from || entry.status === "dropped") { + continue; + } + + counts.set(entry.from, (counts.get(entry.from) ?? 0) + 1); + } + + return [...counts.entries()] + .map(([code, count]) => ({code, count})) + .sort((a, b) => b.count - a.count || (a.code < b.code ? -1 : a.code > b.code ? 1 : 0)); +} + +/** + * The language to offer writing in: the channel's most written one, when it + * is not the reader's own, carries at least `SUGGEST_MIN_LINES` lines and at + * least `SUGGEST_MIN_SHARE` of every placed line. Null otherwise -- a mixed + * channel, a quiet one, or one that already writes the reader's language. + */ +export function suggestedLanguage( + counts: readonly LanguageCount[], + reading: string +): string | null { + const top = counts[0]; + + if (!top || top.code === reading || top.count < SUGGEST_MIN_LINES) { + return null; + } + + const total = counts.reduce((sum, c) => sum + c.count, 0); + + return top.count / total >= SUGGEST_MIN_SHARE ? top.code : null; +} diff --git a/client/js/translate/service.ts b/client/js/translate/service.ts index 4388705d0..3e76360c4 100644 --- a/client/js/translate/service.ts +++ b/client/js/translate/service.ts @@ -193,6 +193,21 @@ export function loadNote(view: ModelView): LoadNote { }; } +/** + * What a route still has to download before it can run: its copy's size, on + * the device it runs on, unless that copy's view says it is already in this + * device's cache. 0 when nothing is left to fetch or the size is unknown + * (a deploy's own model, Apple's system translation). + */ +export function downloadBytes( + route: Pick, + views: readonly Pick[] +): number { + const view = views.find((v) => v.ref.id === route.ref.id && v.device === route.device); + + return view?.cached ? 0 : route.ref.copies[route.device]?.sizeBytes ?? 0; +} + /** What a copy's view is kept under. */ function copyKey(ref: Pick, device: Device): string { return `${ref.id}@${device}`; diff --git a/client/locales/ar.json b/client/locales/ar.json index 334191291..d801c6226 100644 --- a/client/locales/ar.json +++ b/client/locales/ar.json @@ -2779,9 +2779,7 @@ "translate.chip.alreadyIn": "النص بـ {language} أصلًا، لم يُترجم. خيارات الترجمة", "translate.chip.options": "{languages}. خيارات الترجمة", "translate.chip.unsure": "لم تُحدَّد اللغة، لم يُترجم. خيارات الترجمة", - "translate.composer.sendCorrectedOn": "الإرسال مصحَّحًا بـ {language} · انقر للإيقاف", "translate.composer.sendOff": "ترجمة ما أرسله…", - "translate.composer.sendOn": "الإرسال بـ {language} · انقر للإيقاف", "translate.consent.metered": "اتصالك محدود البيانات أو مضبوط على توفير البيانات.", "translate.consent.notNow": "ليس الآن", "translate.consent.text": "تحتاج هذه الترجمة إلى {model}، وحجم تنزيله {size}. يبقى على هذا الجهاز، لذا لا يُنزَّل إلا مرة واحدة.", @@ -2860,16 +2858,8 @@ "translate.picker.cancel": "إلغاء", "translate.picker.confirm": "ترجمة", "translate.picker.dialog": "إعادة الترجمة من لغة أخرى", - "translate.picker.fromAuto": "الاكتشاف تلقائيًا", - "translate.picker.fromLabel": "الترجمة من", "translate.picker.label": "ترجمة هذا السطر من", - "translate.picker.sameLanguage": "اللغة نفسها: يُصحَّح الإملاء والقواعد بدلًا من ذلك.", "translate.picker.sameLanguageNeedsGpu": "يحتاج تصحيح الرسالة إلى النموذج اللغوي.", - "translate.picker.targetConfirm": "ترجمة", - "translate.picker.targetConfirmCorrect": "تصحيح", - "translate.picker.targetDialog": "ترجمة هذه الرسالة إلى لغة", - "translate.picker.targetLabel": "الترجمة إلى", - "translate.picker.thisMessageOnly": "هذه الرسالة فقط", "translate.reason.answered": "أجاب النموذج عن السؤال بدلًا من ترجمته", "translate.reason.degenerate": "كانت الإجابة رموزًا بلا معنى", "translate.reason.narration": "تحدث النموذج عن الطلب بدلًا من الترجمة", diff --git a/client/locales/ar.po b/client/locales/ar.po index d8b6d1153..1b52c8329 100644 --- a/client/locales/ar.po +++ b/client/locales/ar.po @@ -14956,22 +14956,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "لم تُحدَّد اللغة، لم يُترجم. خيارات الترجمة" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "الإرسال مصحَّحًا بـ {language} · انقر للإيقاف" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "ترجمة ما أرسله…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "الإرسال بـ {language} · انقر للإيقاف" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15333,15 +15335,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "لم يُترجم" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "لا شيء على هذا الجهاز يستطيع الترجمة من {from} إلى {to}." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "لا شيء على هذا الجهاز يستطيع الترجمة إلى {to}." @@ -15478,67 +15481,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "إعادة الترجمة من لغة أخرى" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "الاكتشاف تلقائيًا" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "الترجمة من" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "ترجمة هذا السطر من" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "اللغة نفسها: يُصحَّح الإملاء والقواعد بدلًا من ذلك." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "يحتاج تصحيح الرسالة إلى النموذج اللغوي." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "ترجمة" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "تصحيح" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "ترجمة هذه الرسالة إلى لغة" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "الترجمة إلى" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "هذه الرسالة فقط" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15762,6 +15718,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "تعذّر الوصول إلى عملية الترجمة في الخلفية: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15865,6 +15905,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/cs.json b/client/locales/cs.json index adf07fde4..1a20135ae 100644 --- a/client/locales/cs.json +++ b/client/locales/cs.json @@ -2535,9 +2535,7 @@ "translate.chip.alreadyIn": "Už je v jazyce {language}, nepřeloženo. Možnosti překladu", "translate.chip.options": "{languages}. Možnosti překladu", "translate.chip.unsure": "Jazyk se nepodařilo určit, nepřeloženo. Možnosti překladu", - "translate.composer.sendCorrectedOn": "Odesílání s opravami, jazyk: {language} · kliknutím vypnete", "translate.composer.sendOff": "Překládat, co odesílám…", - "translate.composer.sendOn": "Odesílání s překladem, jazyk: {language} · kliknutím vypnete", "translate.consent.metered": "Vaše připojení je měřené nebo nastavené na úsporu dat.", "translate.consent.notNow": "Teď ne", "translate.consent.text": "Tento překlad potřebuje {model}, stažení o velikosti {size}. Zůstane na tomto zařízení, takže se stahuje jen jednou.", @@ -2616,16 +2614,8 @@ "translate.picker.cancel": "Zrušit", "translate.picker.confirm": "Přeložit", "translate.picker.dialog": "Přeložit znovu z jiného jazyka", - "translate.picker.fromAuto": "Rozpoznat automaticky", - "translate.picker.fromLabel": "Přeložit z", "translate.picker.label": "Přeložit tento řádek z", - "translate.picker.sameLanguage": "Stejný jazyk: místo překladu se opraví pravopis a gramatika.", "translate.picker.sameLanguageNeedsGpu": "Oprava zprávy vyžaduje jazykový model.", - "translate.picker.targetConfirm": "Přeložit", - "translate.picker.targetConfirmCorrect": "Opravit", - "translate.picker.targetDialog": "Přeložit tuto zprávu do jazyka", - "translate.picker.targetLabel": "Přeložit do", - "translate.picker.thisMessageOnly": "Jen tuto zprávu", "translate.reason.answered": "model odpověděl na otázku, místo aby ji přeložil", "translate.reason.degenerate": "odpověď byla nesmyslná změť symbolů", "translate.reason.narration": "model mluvil o požadavku, místo aby překládal", diff --git a/client/locales/cs.po b/client/locales/cs.po index 3c9c98581..6498be175 100644 --- a/client/locales/cs.po +++ b/client/locales/cs.po @@ -14716,22 +14716,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "Jazyk se nepodařilo určit, nepřeloženo. Možnosti překladu" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "Odesílání s opravami, jazyk: {language} · kliknutím vypnete" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "Překládat, co odesílám…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "Odesílání s překladem, jazyk: {language} · kliknutím vypnete" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15093,15 +15095,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "Nepřeloženo" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "Nic na tomto zařízení nedokáže přeložit: {from} → {to}." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "Nic na tomto zařízení nedokáže přeložit do jazyka: {to}." @@ -15238,67 +15241,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "Přeložit znovu z jiného jazyka" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "Rozpoznat automaticky" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "Přeložit z" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "Přeložit tento řádek z" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "Stejný jazyk: místo překladu se opraví pravopis a gramatika." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "Oprava zprávy vyžaduje jazykový model." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "Přeložit" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "Opravit" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "Přeložit tuto zprávu do jazyka" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "Přeložit do" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "Jen tuto zprávu" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15516,6 +15472,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "Nelze se spojit s pracovním procesem překladu: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15619,6 +15659,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/da.json b/client/locales/da.json index 646762527..7d0ae819d 100644 --- a/client/locales/da.json +++ b/client/locales/da.json @@ -2415,9 +2415,7 @@ "translate.chip.alreadyIn": "Allerede på {language}, ikke oversat. Oversættelsesindstillinger", "translate.chip.options": "{languages}. Oversættelsesindstillinger", "translate.chip.unsure": "Sproget kunne ikke afgøres, ikke oversat. Oversættelsesindstillinger", - "translate.composer.sendCorrectedOn": "Sender rettet på {language} · klik for at stoppe", "translate.composer.sendOff": "Oversæt det, jeg sender…", - "translate.composer.sendOn": "Sender på {language} · klik for at stoppe", "translate.consent.metered": "Din forbindelse er takseret eller sat til at spare data.", "translate.consent.notNow": "Ikke nu", "translate.consent.text": "Denne oversættelse kræver {model}, en download på {size}. Den gemmes på denne enhed, så den hentes kun én gang.", @@ -2496,16 +2494,8 @@ "translate.picker.cancel": "Annuller", "translate.picker.confirm": "Oversæt", "translate.picker.dialog": "Oversæt igen fra et andet sprog", - "translate.picker.fromAuto": "Genkend automatisk", - "translate.picker.fromLabel": "Oversæt fra", "translate.picker.label": "Oversæt denne linje fra", - "translate.picker.sameLanguage": "Samme sprog: stavning og grammatik rettes i stedet.", "translate.picker.sameLanguageNeedsGpu": "At rette en besked kræver sprogmodellen.", - "translate.picker.targetConfirm": "Oversæt", - "translate.picker.targetConfirmCorrect": "Ret", - "translate.picker.targetDialog": "Oversæt denne besked til et sprog", - "translate.picker.targetLabel": "Oversæt til", - "translate.picker.thisMessageOnly": "Kun denne besked", "translate.reason.answered": "svarede på spørgsmålet i stedet for at oversætte det", "translate.reason.degenerate": "svaret var meningsløse symboler", "translate.reason.narration": "talte om anmodningen i stedet for at oversætte", diff --git a/client/locales/da.po b/client/locales/da.po index b6c18fa90..c02c7dfff 100644 --- a/client/locales/da.po +++ b/client/locales/da.po @@ -14636,22 +14636,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "Sproget kunne ikke afgøres, ikke oversat. Oversættelsesindstillinger" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "Sender rettet på {language} · klik for at stoppe" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "Oversæt det, jeg sender…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "Sender på {language} · klik for at stoppe" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15013,15 +15015,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "Ikke oversat" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "Intet på denne enhed kan oversætte fra {from} til {to}." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "Intet på denne enhed kan oversætte til {to}." @@ -15158,67 +15161,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "Oversæt igen fra et andet sprog" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "Genkend automatisk" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "Oversæt fra" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "Oversæt denne linje fra" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "Samme sprog: stavning og grammatik rettes i stedet." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "At rette en besked kræver sprogmodellen." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "Oversæt" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "Ret" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "Oversæt denne besked til et sprog" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "Oversæt til" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "Kun denne besked" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15434,6 +15390,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "Kunne ikke nå oversættelses-workeren: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15537,6 +15577,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/de.json b/client/locales/de.json index ac5aed13b..c5cd9f8f3 100644 --- a/client/locales/de.json +++ b/client/locales/de.json @@ -2391,9 +2391,7 @@ "translate.chip.alreadyIn": "Bereits auf {language}, nicht übersetzt. Übersetzungsoptionen", "translate.chip.options": "{languages}. Übersetzungsoptionen", "translate.chip.unsure": "Sprache nicht erkannt, nicht übersetzt. Übersetzungsoptionen", - "translate.composer.sendCorrectedOn": "Korrigiert auf {language} senden · zum Beenden klicken", "translate.composer.sendOff": "Gesendetes übersetzen…", - "translate.composer.sendOn": "Senden auf {language} · zum Beenden klicken", "translate.consent.metered": "Ihre Verbindung ist getaktet oder auf Datensparen eingestellt.", "translate.consent.notNow": "Nicht jetzt", "translate.consent.text": "Für diese Übersetzung wird {model} benötigt, ein Download von {size}. Das Modell bleibt auf diesem Gerät und wird nur einmal heruntergeladen.", @@ -2472,16 +2470,8 @@ "translate.picker.cancel": "Abbrechen", "translate.picker.confirm": "Übersetzen", "translate.picker.dialog": "Aus einer anderen Sprache neu übersetzen", - "translate.picker.fromAuto": "Automatisch erkennen", - "translate.picker.fromLabel": "Übersetzen aus", "translate.picker.label": "Diese Zeile übersetzen aus", - "translate.picker.sameLanguage": "Gleiche Sprache: Stattdessen werden Rechtschreibung und Grammatik korrigiert.", "translate.picker.sameLanguageNeedsGpu": "Zum Korrigieren einer Nachricht wird das Sprachmodell benötigt.", - "translate.picker.targetConfirm": "Übersetzen", - "translate.picker.targetConfirmCorrect": "Korrigieren", - "translate.picker.targetDialog": "Diese Nachricht in eine Sprache übersetzen", - "translate.picker.targetLabel": "Übersetzen in", - "translate.picker.thisMessageOnly": "Nur diese Nachricht", "translate.reason.answered": "beantwortete die Frage statt sie zu übersetzen", "translate.reason.degenerate": "die Antwort war Symbolmüll", "translate.reason.narration": "sprach über die Anfrage statt zu übersetzen", diff --git a/client/locales/de.po b/client/locales/de.po index bd90864f3..802bc4193 100644 --- a/client/locales/de.po +++ b/client/locales/de.po @@ -14636,22 +14636,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "Sprache nicht erkannt, nicht übersetzt. Übersetzungsoptionen" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "Korrigiert auf {language} senden · zum Beenden klicken" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "Gesendetes übersetzen…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "Senden auf {language} · zum Beenden klicken" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15013,15 +15015,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "Nicht übersetzt" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "Nichts auf diesem Gerät kann von {from} nach {to} übersetzen." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "Nichts auf diesem Gerät kann nach {to} übersetzen." @@ -15158,67 +15161,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "Aus einer anderen Sprache neu übersetzen" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "Automatisch erkennen" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "Übersetzen aus" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "Diese Zeile übersetzen aus" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "Gleiche Sprache: Stattdessen werden Rechtschreibung und Grammatik korrigiert." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "Zum Korrigieren einer Nachricht wird das Sprachmodell benötigt." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "Übersetzen" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "Korrigieren" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "Diese Nachricht in eine Sprache übersetzen" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "Übersetzen in" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "Nur diese Nachricht" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15434,6 +15390,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "Der Übersetzungs-Worker ist nicht erreichbar: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15537,6 +15577,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/en.json b/client/locales/en.json index debf745fb..5ff21b211 100644 --- a/client/locales/en.json +++ b/client/locales/en.json @@ -2592,9 +2592,9 @@ "translate.chip.options": "{languages}. Translation options", "translate.chip.sentIn": "sent in {language}", "translate.chip.unsure": "Language not told apart, not translated. Translation options", - "translate.composer.sendCorrectedOn": "Sending corrected in {language} · click to stop", + "translate.composer.sendCorrectedOn": "Sending corrected in {language} · change or turn off", "translate.composer.sendOff": "Translate what I send…", - "translate.composer.sendOn": "Sending in {language} · click to stop", + "translate.composer.sendOn": "Sending in {language} · change or turn off", "translate.composer.targetCorrected": "{language} ✓", "translate.composer.targetPair": "{from} → {to}", "translate.consent.metered": "Your connection is metered or set to save data.", @@ -2678,16 +2678,8 @@ "translate.picker.cancel": "Cancel", "translate.picker.confirm": "Translate", "translate.picker.dialog": "Retranslate from another language", - "translate.picker.fromAuto": "Detect automatically", - "translate.picker.fromLabel": "Translate from", "translate.picker.label": "Translate this line from", - "translate.picker.sameLanguage": "Same language: spelling and grammar are corrected instead.", "translate.picker.sameLanguageNeedsGpu": "Correcting a message needs the language model.", - "translate.picker.targetConfirm": "Translate", - "translate.picker.targetConfirmCorrect": "Correct", - "translate.picker.targetDialog": "Translate this message into a language", - "translate.picker.targetLabel": "Translate into", - "translate.picker.thisMessageOnly": "This message only", "translate.reason.answered": "answered the question instead of translating it", "translate.reason.degenerate": "the answer was symbol garbage", "translate.reason.narration": "talked about the request instead of translating", @@ -2729,6 +2721,20 @@ "translate.settings.useGpu": "Use {model} where it is the better choice", "translate.settings.useNllb": "Use NLLB-200 for languages the other models do not cover", "translate.settings.workerError": "Could not reach the translation worker: {error}", + "translate.sheet.choose": "Choose…", + "translate.sheet.corrects": "Corrects spelling", + "translate.sheet.detect": "Detect automatically", + "translate.sheet.dialog": "Choose the language your messages are sent in", + "translate.sheet.download": "{size} download", + "translate.sheet.iWrite": "I write", + "translate.sheet.noMatch": "No language matches “{query}”.", + "translate.sheet.off": "Off", + "translate.sheet.recent": "Spoken in {channel} lately", + "translate.sheet.search": "Search languages", + "translate.sheet.swap": "Swap languages", + "translate.sheet.theyRead": "They read", + "translate.sheet.thisMessageOnly": "This message only", + "translate.sheet.unavailable": "Not available here", "translate.strip.checkFailed": "couldn't check", "translate.strip.copied": "Copied", "translate.strip.copy": "Copy the translation", @@ -2746,6 +2752,10 @@ "translate.strip.sendsWhenReady": "Sends when the translation is ready", "translate.strip.sourceAuto": "auto", "translate.strip.willSendIn": "Will send in {language}", + "translate.suggest.accept": "Write in {language}", + "translate.suggest.decline": "Not in this channel", + "translate.suggest.download": "Uses {model}, a {size} download.", + "translate.suggest.lead": "Most of {channel} writes in {language}. Write your messages in {language} here?", "translate.system.asking": "{from} → {to}: waiting for the download…", "translate.system.download": "Download", "translate.system.heading": "Apple Translation", diff --git a/client/locales/es.json b/client/locales/es.json index 70b7535bb..bcbef2116 100644 --- a/client/locales/es.json +++ b/client/locales/es.json @@ -2390,9 +2390,7 @@ "translate.chip.alreadyIn": "Ya en {language}, no traducido. Opciones de traducción", "translate.chip.options": "{languages}. Opciones de traducción", "translate.chip.unsure": "Idioma no identificado, no traducido. Opciones de traducción", - "translate.composer.sendCorrectedOn": "Enviando corregido en {language} · haz clic para parar", "translate.composer.sendOff": "Traducir lo que envío…", - "translate.composer.sendOn": "Enviando en {language} · haz clic para parar", "translate.consent.metered": "Tu conexión es de uso medido o está en modo de ahorro de datos.", "translate.consent.notNow": "Ahora no", "translate.consent.text": "Esta traducción necesita {model}, una descarga de {size}. Se guarda en este dispositivo, así que solo se descarga una vez.", @@ -2471,16 +2469,8 @@ "translate.picker.cancel": "Cancelar", "translate.picker.confirm": "Traducir", "translate.picker.dialog": "Volver a traducir desde otro idioma", - "translate.picker.fromAuto": "Detectar automáticamente", - "translate.picker.fromLabel": "Traducir desde", "translate.picker.label": "Traducir esta línea desde", - "translate.picker.sameLanguage": "Mismo idioma: en su lugar se corrigen la ortografía y la gramática.", "translate.picker.sameLanguageNeedsGpu": "Corregir un mensaje necesita el modelo de lenguaje.", - "translate.picker.targetConfirm": "Traducir", - "translate.picker.targetConfirmCorrect": "Corregir", - "translate.picker.targetDialog": "Traducir este mensaje a un idioma", - "translate.picker.targetLabel": "Traducir a", - "translate.picker.thisMessageOnly": "Solo este mensaje", "translate.reason.answered": "respondió a la pregunta en lugar de traducirla", "translate.reason.degenerate": "la respuesta era basura de símbolos", "translate.reason.narration": "habló de la solicitud en lugar de traducirla", diff --git a/client/locales/es.po b/client/locales/es.po index 0e4a25ba6..f4187ff16 100644 --- a/client/locales/es.po +++ b/client/locales/es.po @@ -14636,22 +14636,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "Idioma no identificado, no traducido. Opciones de traducción" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "Enviando corregido en {language} · haz clic para parar" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "Traducir lo que envío…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "Enviando en {language} · haz clic para parar" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15013,15 +15015,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "No traducido" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "Nada en este dispositivo puede traducir del {from} al {to}." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "Nada en este dispositivo puede traducir al {to}." @@ -15158,67 +15161,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "Volver a traducir desde otro idioma" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "Detectar automáticamente" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "Traducir desde" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "Traducir esta línea desde" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "Mismo idioma: en su lugar se corrigen la ortografía y la gramática." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "Corregir un mensaje necesita el modelo de lenguaje." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "Traducir" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "Corregir" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "Traducir este mensaje a un idioma" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "Traducir a" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "Solo este mensaje" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15434,6 +15390,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "No se pudo contactar con el worker de traducción: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15537,6 +15577,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/fil.json b/client/locales/fil.json index 41f716832..ee3b5ce82 100644 --- a/client/locales/fil.json +++ b/client/locales/fil.json @@ -2387,9 +2387,7 @@ "translate.chip.alreadyIn": "Nasa {language} na, hindi isinalin. Mga opsyon sa pagsasalin", "translate.chip.options": "{languages}. Mga opsyon sa pagsasalin", "translate.chip.unsure": "Hindi matukoy ang wika, hindi isinalin. Mga opsyon sa pagsasalin", - "translate.composer.sendCorrectedOn": "Ipinapadalang naitama sa {language} · i-click para ihinto", "translate.composer.sendOff": "Isalin ang ipinapadala ko…", - "translate.composer.sendOn": "Ipinapadala sa {language} · i-click para ihinto", "translate.consent.metered": "May limitasyon sa data ang iyong koneksyon o nakatakda itong magtipid ng data.", "translate.consent.notNow": "Hindi muna", "translate.consent.text": "Kailangan ng pagsasaling ito ang {model}, isang download na {size}. Mananatili ito sa device na ito, kaya isang beses lang itong ida-download.", @@ -2468,16 +2466,8 @@ "translate.picker.cancel": "Kanselahin", "translate.picker.confirm": "Isalin", "translate.picker.dialog": "Isalin muli mula sa ibang wika", - "translate.picker.fromAuto": "Awtomatikong tukuyin", - "translate.picker.fromLabel": "Isalin mula sa", "translate.picker.label": "Isalin ang linyang ito mula sa", - "translate.picker.sameLanguage": "Parehong wika: itatama na lang ang spelling at grammar.", "translate.picker.sameLanguageNeedsGpu": "Kailangan ng modelo ng wika para iwasto ang isang mensahe.", - "translate.picker.targetConfirm": "Isalin", - "translate.picker.targetConfirmCorrect": "Iwasto", - "translate.picker.targetDialog": "Isalin ang mensaheng ito sa isang wika", - "translate.picker.targetLabel": "Isalin sa", - "translate.picker.thisMessageOnly": "Ang mensaheng ito lang", "translate.reason.answered": "sinagot ang tanong sa halip na isalin ito", "translate.reason.degenerate": "mga simbolong walang saysay ang sagot", "translate.reason.narration": "nagsalita tungkol sa kahilingan sa halip na magsalin", diff --git a/client/locales/fil.po b/client/locales/fil.po index 4f28caecb..8510e6c12 100644 --- a/client/locales/fil.po +++ b/client/locales/fil.po @@ -14636,22 +14636,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "Hindi matukoy ang wika, hindi isinalin. Mga opsyon sa pagsasalin" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "Ipinapadalang naitama sa {language} · i-click para ihinto" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "Isalin ang ipinapadala ko…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "Ipinapadala sa {language} · i-click para ihinto" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15013,15 +15015,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "Hindi isinalin" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "Walang makapagsasalin sa device na ito mula {from} papuntang {to}." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "Walang makapagsasalin sa device na ito papuntang {to}." @@ -15158,67 +15161,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "Isalin muli mula sa ibang wika" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "Awtomatikong tukuyin" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "Isalin mula sa" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "Isalin ang linyang ito mula sa" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "Parehong wika: itatama na lang ang spelling at grammar." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "Kailangan ng modelo ng wika para iwasto ang isang mensahe." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "Isalin" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "Iwasto" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "Isalin ang mensaheng ito sa isang wika" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "Isalin sa" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "Ang mensaheng ito lang" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15434,6 +15390,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "Hindi maabot ang proseso ng pagsasalin: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15537,6 +15577,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/fr.json b/client/locales/fr.json index 9cfae1e4a..d93ecf187 100644 --- a/client/locales/fr.json +++ b/client/locales/fr.json @@ -2367,9 +2367,7 @@ "translate.chip.alreadyIn": "Déjà dans {language}, non traduit. Options de traduction", "translate.chip.options": "{languages}. Options de traduction", "translate.chip.unsure": "Langue non identifiée, non traduit. Options de traduction", - "translate.composer.sendCorrectedOn": "Envoi corrigé en {language} · cliquez pour arrêter", "translate.composer.sendOff": "Traduire ce que j'envoie…", - "translate.composer.sendOn": "Envoi en {language} · cliquez pour arrêter", "translate.consent.metered": "Votre connexion est limitée ou en mode économie de données.", "translate.consent.notNow": "Pas maintenant", "translate.consent.text": "Cette traduction nécessite {model}, un téléchargement de {size}. Il reste sur cet appareil et n'est téléchargé qu'une fois.", @@ -2449,16 +2447,8 @@ "translate.picker.cancel": "Annuler", "translate.picker.confirm": "Traduire", "translate.picker.dialog": "Retraduire depuis une autre langue", - "translate.picker.fromAuto": "Détecter automatiquement", - "translate.picker.fromLabel": "Traduire depuis", "translate.picker.label": "Traduire cette ligne depuis", - "translate.picker.sameLanguage": "Même langue : l'orthographe et la grammaire sont corrigées à la place.", "translate.picker.sameLanguageNeedsGpu": "Corriger un message nécessite le modèle de langage.", - "translate.picker.targetConfirm": "Traduire", - "translate.picker.targetConfirmCorrect": "Corriger", - "translate.picker.targetDialog": "Traduire ce message vers une langue", - "translate.picker.targetLabel": "Traduire vers", - "translate.picker.thisMessageOnly": "Ce message uniquement", "translate.reason.answered": "a répondu à la question au lieu de la traduire", "translate.reason.degenerate": "la réponse n'était qu'une suite de symboles sans sens", "translate.reason.narration": "a parlé de la demande au lieu de la traduire", diff --git a/client/locales/fr.po b/client/locales/fr.po index a3d0cc00a..8ce2d3b43 100644 --- a/client/locales/fr.po +++ b/client/locales/fr.po @@ -14636,22 +14636,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "Langue non identifiée, non traduit. Options de traduction" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "Envoi corrigé en {language} · cliquez pour arrêter" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "Traduire ce que j'envoie…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "Envoi en {language} · cliquez pour arrêter" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15013,15 +15015,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "Non traduit" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "Rien sur cet appareil ne peut traduire : {from} → {to}." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "Rien sur cet appareil ne peut traduire vers cette langue : {to}." @@ -15158,67 +15161,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "Retraduire depuis une autre langue" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "Détecter automatiquement" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "Traduire depuis" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "Traduire cette ligne depuis" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "Même langue : l'orthographe et la grammaire sont corrigées à la place." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "Corriger un message nécessite le modèle de langage." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "Traduire" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "Corriger" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "Traduire ce message vers une langue" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "Traduire vers" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "Ce message uniquement" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15434,6 +15390,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "Impossible de joindre le worker de traduction : {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15537,6 +15577,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/hu.json b/client/locales/hu.json index bdc35d48a..a5de2027f 100644 --- a/client/locales/hu.json +++ b/client/locales/hu.json @@ -2473,9 +2473,7 @@ "translate.chip.alreadyIn": "Már a célnyelven van ({language}), nincs lefordítva. Fordítási beállítások", "translate.chip.options": "{languages}. Fordítási beállítások", "translate.chip.unsure": "A nyelv nem állapítható meg, nincs lefordítva. Fordítási beállítások", - "translate.composer.sendCorrectedOn": "Javított küldés, nyelv: {language} · kattints a leállításhoz", "translate.composer.sendOff": "Elküldött üzeneteim fordítása…", - "translate.composer.sendOn": "Küldés ezen a nyelven: {language} · kattints a leállításhoz", "translate.consent.metered": "A kapcsolatod forgalomkorlátos, vagy adattakarékos módban van.", "translate.consent.notNow": "Most nem", "translate.consent.text": "Ehhez a fordításhoz szükség van erre: {model}, ami {size} letöltés. Ezen az eszközön marad, így csak egyszer kell letölteni.", @@ -2554,16 +2552,8 @@ "translate.picker.cancel": "Mégse", "translate.picker.confirm": "Fordítás", "translate.picker.dialog": "Újrafordítás másik nyelvről", - "translate.picker.fromAuto": "Automatikus felismerés", - "translate.picker.fromLabel": "Forrásnyelv", "translate.picker.label": "A sor fordítása erről a nyelvről", - "translate.picker.sameLanguage": "Ugyanaz a nyelv: helyette a helyesírás és a nyelvtan javul.", "translate.picker.sameLanguageNeedsGpu": "Az üzenet javításához a nyelvi modell szükséges.", - "translate.picker.targetConfirm": "Fordítás", - "translate.picker.targetConfirmCorrect": "Javítás", - "translate.picker.targetDialog": "Üzenet fordítása egy nyelvre", - "translate.picker.targetLabel": "Célnyelv", - "translate.picker.thisMessageOnly": "Csak ez az üzenet", "translate.reason.answered": "válaszolt a kérdésre ahelyett, hogy lefordította volna", "translate.reason.degenerate": "a válasz értelmetlen szimbólumhalmaz volt", "translate.reason.narration": "a kérésről beszélt ahelyett, hogy fordított volna", diff --git a/client/locales/hu.po b/client/locales/hu.po index a334edb83..d68095e4e 100644 --- a/client/locales/hu.po +++ b/client/locales/hu.po @@ -14636,22 +14636,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "A nyelv nem állapítható meg, nincs lefordítva. Fordítási beállítások" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "Javított küldés, nyelv: {language} · kattints a leállításhoz" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "Elküldött üzeneteim fordítása…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "Küldés ezen a nyelven: {language} · kattints a leállításhoz" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15013,15 +15015,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "Nem fordítva" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "Ezen az eszközön semmi sem tud fordítani: {from} → {to}." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "Ezen az eszközön semmi sem tud fordítani erre a nyelvre: {to}." @@ -15158,67 +15161,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "Újrafordítás másik nyelvről" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "Automatikus felismerés" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "Forrásnyelv" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "A sor fordítása erről a nyelvről" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "Ugyanaz a nyelv: helyette a helyesírás és a nyelvtan javul." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "Az üzenet javításához a nyelvi modell szükséges." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "Fordítás" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "Javítás" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "Üzenet fordítása egy nyelvre" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "Célnyelv" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "Csak ez az üzenet" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15434,6 +15390,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "Nem sikerült elérni a fordítási workert: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15537,6 +15577,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/it.json b/client/locales/it.json index a58bfaf26..17ae9a154 100644 --- a/client/locales/it.json +++ b/client/locales/it.json @@ -2378,9 +2378,7 @@ "translate.chip.alreadyIn": "Già in {language}, non tradotto. Opzioni di traduzione", "translate.chip.options": "{languages}. Opzioni di traduzione", "translate.chip.unsure": "Lingua non identificata, non tradotta. Opzioni di traduzione", - "translate.composer.sendCorrectedOn": "Invio corretto in {language} · fai clic per interrompere", "translate.composer.sendOff": "Traduci ciò che invio…", - "translate.composer.sendOn": "Invio in {language} · fai clic per interrompere", "translate.consent.metered": "La tua connessione è a consumo o in modalità risparmio dati.", "translate.consent.notNow": "Non ora", "translate.consent.text": "Questa traduzione richiede {model}, un download di {size}. Resta su questo dispositivo, quindi si scarica una volta sola.", @@ -2459,16 +2457,8 @@ "translate.picker.cancel": "Annulla", "translate.picker.confirm": "Traduci", "translate.picker.dialog": "Ritraduci da un'altra lingua", - "translate.picker.fromAuto": "Rileva automaticamente", - "translate.picker.fromLabel": "Traduci da", "translate.picker.label": "Traduci questa riga da", - "translate.picker.sameLanguage": "Stessa lingua: vengono invece corrette ortografia e grammatica.", "translate.picker.sameLanguageNeedsGpu": "Per correggere un messaggio serve il modello linguistico.", - "translate.picker.targetConfirm": "Traduci", - "translate.picker.targetConfirmCorrect": "Correggi", - "translate.picker.targetDialog": "Traduci questo messaggio in una lingua", - "translate.picker.targetLabel": "Traduci in", - "translate.picker.thisMessageOnly": "Solo questo messaggio", "translate.reason.answered": "ha risposto alla domanda invece di tradurla", "translate.reason.degenerate": "la risposta era una sequenza di simboli senza senso", "translate.reason.narration": "ha parlato della richiesta invece di tradurre", diff --git a/client/locales/it.po b/client/locales/it.po index 187a3c027..fbb21f96f 100644 --- a/client/locales/it.po +++ b/client/locales/it.po @@ -14636,22 +14636,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "Lingua non identificata, non tradotta. Opzioni di traduzione" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "Invio corretto in {language} · fai clic per interrompere" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "Traduci ciò che invio…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "Invio in {language} · fai clic per interrompere" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15013,15 +15015,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "Non tradotto" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "Niente su questo dispositivo può tradurre da {from} a {to}." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "Niente su questo dispositivo può tradurre in {to}." @@ -15158,67 +15161,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "Ritraduci da un'altra lingua" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "Rileva automaticamente" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "Traduci da" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "Traduci questa riga da" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "Stessa lingua: vengono invece corrette ortografia e grammatica." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "Per correggere un messaggio serve il modello linguistico." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "Traduci" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "Correggi" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "Traduci questo messaggio in una lingua" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "Traduci in" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "Solo questo messaggio" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15434,6 +15390,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "Impossibile raggiungere il worker di traduzione: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15537,6 +15577,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/ja.json b/client/locales/ja.json index 05bdbfb69..c026cf779 100644 --- a/client/locales/ja.json +++ b/client/locales/ja.json @@ -2413,9 +2413,7 @@ "translate.chip.alreadyIn": "すでに{language}のため翻訳していません。翻訳オプション", "translate.chip.options": "{languages}。翻訳オプション", "translate.chip.unsure": "言語を判別できなかったため翻訳していません。翻訳オプション", - "translate.composer.sendCorrectedOn": "{language}で修正して送信中 · クリックで停止", "translate.composer.sendOff": "送信する内容を翻訳します…", - "translate.composer.sendOn": "{language}で送信中 · クリックで停止", "translate.consent.metered": "接続が従量制、またはデータセーバーに設定されています。", "translate.consent.notNow": "後で", "translate.consent.text": "この翻訳には {model}(ダウンロードサイズ {size})が必要です。このデバイスに保存されるため、ダウンロードは一度だけです。", @@ -2495,16 +2493,8 @@ "translate.picker.cancel": "キャンセル", "translate.picker.confirm": "翻訳", "translate.picker.dialog": "他の言語から再翻訳", - "translate.picker.fromAuto": "自動で検出", - "translate.picker.fromLabel": "翻訳元", "translate.picker.label": "この行の翻訳元", - "translate.picker.sameLanguage": "同じ言語: 代わりにスペルと文法を校正します。", "translate.picker.sameLanguageNeedsGpu": "メッセージの校正には言語モデルが必要です。", - "translate.picker.targetConfirm": "翻訳", - "translate.picker.targetConfirmCorrect": "校正する", - "translate.picker.targetDialog": "このメッセージを翻訳する言語", - "translate.picker.targetLabel": "翻訳先", - "translate.picker.thisMessageOnly": "このメッセージのみ", "translate.reason.answered": "翻訳せずに質問に答えてしまいました", "translate.reason.degenerate": "応答が意味のない記号の羅列でした", "translate.reason.narration": "翻訳せずに依頼について説明してしまいました", diff --git a/client/locales/ja.po b/client/locales/ja.po index 237512cfd..235ffc23a 100644 --- a/client/locales/ja.po +++ b/client/locales/ja.po @@ -14561,22 +14561,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "言語を判別できなかったため翻訳していません。翻訳オプション" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "{language}で修正して送信中 · クリックで停止" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "送信する内容を翻訳します…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "{language}で送信中 · クリックで停止" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -14938,15 +14940,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "翻訳されていません" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "このデバイスには{from}から{to}に翻訳できるものがありません。" -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "このデバイスには{to}に翻訳できるものがありません。" @@ -15083,67 +15086,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "他の言語から再翻訳" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "自動で検出" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "翻訳元" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "この行の翻訳元" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "同じ言語: 代わりにスペルと文法を校正します。" - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "メッセージの校正には言語モデルが必要です。" -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "翻訳" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "校正する" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "このメッセージを翻訳する言語" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "翻訳先" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "このメッセージのみ" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15357,6 +15313,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "翻訳ワーカーに接続できませんでした: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15460,6 +15500,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/ko.json b/client/locales/ko.json index 61ed01c44..c34a221c8 100644 --- a/client/locales/ko.json +++ b/client/locales/ko.json @@ -2409,9 +2409,7 @@ "translate.chip.alreadyIn": "이미 {language} 텍스트이므로 번역하지 않음. 번역 옵션", "translate.chip.options": "{languages}. 번역 옵션", "translate.chip.unsure": "언어가 구분되지 않아 번역되지 않았습니다. 번역 옵션", - "translate.composer.sendCorrectedOn": "{language}(으)로 교정해서 전송 중 · 클릭하여 중지", "translate.composer.sendOff": "보내는 메시지 번역…", - "translate.composer.sendOn": "{language}(으)로 전송 중 · 클릭하여 중지", "translate.consent.metered": "연결이 데이터 요금제 연결이거나 데이터 절약 모드로 설정되어 있습니다.", "translate.consent.notNow": "나중에", "translate.consent.text": "이 번역에는 {model}이(가) 필요하며 다운로드 크기는 {size}입니다. 이 기기에 보관되므로 한 번만 다운로드합니다.", @@ -2490,16 +2488,8 @@ "translate.picker.cancel": "취소", "translate.picker.confirm": "번역", "translate.picker.dialog": "다른 언어에서 다시 번역", - "translate.picker.fromAuto": "자동 감지", - "translate.picker.fromLabel": "원본 언어", "translate.picker.label": "이 줄의 원본 언어", - "translate.picker.sameLanguage": "같은 언어: 대신 맞춤법과 문법을 교정합니다.", "translate.picker.sameLanguageNeedsGpu": "메시지를 교정하려면 언어 모델이 필요합니다.", - "translate.picker.targetConfirm": "번역", - "translate.picker.targetConfirmCorrect": "교정", - "translate.picker.targetDialog": "이 메시지를 번역할 언어", - "translate.picker.targetLabel": "번역할 언어", - "translate.picker.thisMessageOnly": "이 메시지만", "translate.reason.answered": "번역하지 않고 질문에 답했습니다", "translate.reason.degenerate": "응답이 의미 없는 기호의 나열이었습니다", "translate.reason.narration": "번역하지 않고 요청에 대해 설명했습니다", diff --git a/client/locales/ko.po b/client/locales/ko.po index 9f219117e..5bd16becf 100644 --- a/client/locales/ko.po +++ b/client/locales/ko.po @@ -14556,22 +14556,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "언어가 구분되지 않아 번역되지 않았습니다. 번역 옵션" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "{language}(으)로 교정해서 전송 중 · 클릭하여 중지" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "보내는 메시지 번역…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "{language}(으)로 전송 중 · 클릭하여 중지" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -14933,15 +14935,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "번역 안 됨" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "이 기기에서는 {from}에서 {to}(으)로 번역할 수 있는 것이 없습니다." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "이 기기에서는 {to}(으)로 번역할 수 있는 것이 없습니다." @@ -15078,67 +15081,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "다른 언어에서 다시 번역" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "자동 감지" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "원본 언어" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "이 줄의 원본 언어" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "같은 언어: 대신 맞춤법과 문법을 교정합니다." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "메시지를 교정하려면 언어 모델이 필요합니다." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "번역" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "교정" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "이 메시지를 번역할 언어" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "번역할 언어" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "이 메시지만" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15352,6 +15308,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "번역 워커에 연결할 수 없음: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15455,6 +15495,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/messages.pot b/client/locales/messages.pot index f291c8c5d..9d5ac3c7f 100644 --- a/client/locales/messages.pot +++ b/client/locales/messages.pot @@ -14635,22 +14635,22 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15012,15 +15012,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "" -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "" @@ -15157,67 +15158,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "" - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "" -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15432,6 +15386,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15534,6 +15572,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/nl.json b/client/locales/nl.json index 0da70f364..c4ba9097d 100644 --- a/client/locales/nl.json +++ b/client/locales/nl.json @@ -2372,9 +2372,7 @@ "translate.chip.alreadyIn": "Al in {language}, niet vertaald. Vertalingsopties", "translate.chip.options": "{languages}. Vertalingsopties", "translate.chip.unsure": "Taal niet herkend, niet vertaald. Vertalingsopties", - "translate.composer.sendCorrectedOn": "Gecorrigeerd verzenden in het {language} · klik om te stoppen", "translate.composer.sendOff": "Vertalen wat ik verzend…", - "translate.composer.sendOn": "Verzenden in het {language} · klik om te stoppen", "translate.consent.metered": "Uw verbinding is een betaalde verbinding of staat op databesparing.", "translate.consent.notNow": "Niet nu", "translate.consent.text": "Voor deze vertaling is {model} nodig, een download van {size}. Het blijft op dit apparaat staan en wordt maar één keer gedownload.", @@ -2453,16 +2451,8 @@ "translate.picker.cancel": "Annuleren", "translate.picker.confirm": "Vertalen", "translate.picker.dialog": "Opnieuw vertalen uit een andere taal", - "translate.picker.fromAuto": "Automatisch herkennen", - "translate.picker.fromLabel": "Vertalen uit", "translate.picker.label": "Deze regel vertalen uit", - "translate.picker.sameLanguage": "Zelfde taal: in plaats daarvan worden spelling en grammatica gecorrigeerd.", "translate.picker.sameLanguageNeedsGpu": "Voor het corrigeren van een bericht is het taalmodel nodig.", - "translate.picker.targetConfirm": "Vertalen", - "translate.picker.targetConfirmCorrect": "Corrigeren", - "translate.picker.targetDialog": "Dit bericht naar een taal vertalen", - "translate.picker.targetLabel": "Vertalen naar", - "translate.picker.thisMessageOnly": "Alleen dit bericht", "translate.reason.answered": "beantwoordde de vraag in plaats van te vertalen", "translate.reason.degenerate": "het antwoord was symboolwartaal", "translate.reason.narration": "sprak over het verzoek in plaats van te vertalen", diff --git a/client/locales/nl.po b/client/locales/nl.po index 5aa835bd5..19bc5ba91 100644 --- a/client/locales/nl.po +++ b/client/locales/nl.po @@ -14636,22 +14636,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "Taal niet herkend, niet vertaald. Vertalingsopties" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "Gecorrigeerd verzenden in het {language} · klik om te stoppen" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "Vertalen wat ik verzend…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "Verzenden in het {language} · klik om te stoppen" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15013,15 +15015,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "Niet vertaald" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "Niets op dit apparaat kan van het {from} naar het {to} vertalen." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "Niets op dit apparaat kan naar het {to} vertalen." @@ -15158,67 +15161,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "Opnieuw vertalen uit een andere taal" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "Automatisch herkennen" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "Vertalen uit" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "Deze regel vertalen uit" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "Zelfde taal: in plaats daarvan worden spelling en grammatica gecorrigeerd." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "Voor het corrigeren van een bericht is het taalmodel nodig." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "Vertalen" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "Corrigeren" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "Dit bericht naar een taal vertalen" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "Vertalen naar" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "Alleen dit bericht" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15434,6 +15390,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "Kon de vertaalworker niet bereiken: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15537,6 +15577,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/pl.json b/client/locales/pl.json index 4f5b9e0c8..022349899 100644 --- a/client/locales/pl.json +++ b/client/locales/pl.json @@ -2604,9 +2604,7 @@ "translate.chip.alreadyIn": "Już w docelowym języku ({language}), nie przetłumaczono. Opcje tłumaczenia", "translate.chip.options": "{languages}. Opcje tłumaczenia", "translate.chip.unsure": "Nie rozpoznano języka, nie przetłumaczono. Opcje tłumaczenia", - "translate.composer.sendCorrectedOn": "Wysyłanie z poprawkami, język: {language} · kliknij, aby wyłączyć", "translate.composer.sendOff": "Przetłumacz to, co wysyłam…", - "translate.composer.sendOn": "Wysyłanie z tłumaczeniem, język: {language} · kliknij, aby wyłączyć", "translate.consent.metered": "Twoje połączenie jest taryfowe lub ma włączone oszczędzanie danych.", "translate.consent.notNow": "Nie teraz", "translate.consent.text": "To tłumaczenie wymaga modelu {model}, do pobrania {size}. Zostaje na tym urządzeniu, więc pobiera się go tylko raz.", @@ -2685,16 +2683,8 @@ "translate.picker.cancel": "Anuluj", "translate.picker.confirm": "Przetłumacz", "translate.picker.dialog": "Przetłumacz ponownie z innego języka", - "translate.picker.fromAuto": "Wykryj automatycznie", - "translate.picker.fromLabel": "Tłumacz z", "translate.picker.label": "Przetłumacz ten wiersz z", - "translate.picker.sameLanguage": "Ten sam język: zamiast tego poprawiana jest pisownia i gramatyka.", "translate.picker.sameLanguageNeedsGpu": "Poprawianie wiadomości wymaga modelu językowego.", - "translate.picker.targetConfirm": "Przetłumacz", - "translate.picker.targetConfirmCorrect": "Popraw", - "translate.picker.targetDialog": "Przetłumacz tę wiadomość na język", - "translate.picker.targetLabel": "Tłumacz na", - "translate.picker.thisMessageOnly": "Tylko ta wiadomość", "translate.reason.answered": "model odpowiedział na pytanie, zamiast je przetłumaczyć", "translate.reason.degenerate": "odpowiedź była ciągiem bezsensownych symboli", "translate.reason.narration": "model mówił o żądaniu, zamiast tłumaczyć", diff --git a/client/locales/pl.po b/client/locales/pl.po index 68c936d75..a116381d6 100644 --- a/client/locales/pl.po +++ b/client/locales/pl.po @@ -14716,22 +14716,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "Nie rozpoznano języka, nie przetłumaczono. Opcje tłumaczenia" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "Wysyłanie z poprawkami, język: {language} · kliknij, aby wyłączyć" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "Przetłumacz to, co wysyłam…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "Wysyłanie z tłumaczeniem, język: {language} · kliknij, aby wyłączyć" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15093,15 +15095,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "Nie przetłumaczono" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "Nic na tym urządzeniu nie przetłumaczy: {from} → {to}." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "Nic na tym urządzeniu nie przetłumaczy na język: {to}." @@ -15238,67 +15241,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "Przetłumacz ponownie z innego języka" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "Wykryj automatycznie" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "Tłumacz z" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "Przetłumacz ten wiersz z" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "Ten sam język: zamiast tego poprawiana jest pisownia i gramatyka." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "Poprawianie wiadomości wymaga modelu językowego." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "Przetłumacz" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "Popraw" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "Przetłumacz tę wiadomość na język" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "Tłumacz na" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "Tylko ta wiadomość" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15516,6 +15472,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "Nie udało się połączyć z procesem roboczym tłumaczenia: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15619,6 +15659,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/pt.json b/client/locales/pt.json index 53bb87892..4031c04a3 100644 --- a/client/locales/pt.json +++ b/client/locales/pt.json @@ -2434,9 +2434,7 @@ "translate.chip.alreadyIn": "Já está em {language}, não traduzido. Opções de tradução", "translate.chip.options": "{languages}. Opções de tradução", "translate.chip.unsure": "Idioma não identificado, não traduzido. Opções de tradução", - "translate.composer.sendCorrectedOn": "Enviando corrigido em {language} · clique para parar", "translate.composer.sendOff": "Traduzir o que eu envio…", - "translate.composer.sendOn": "Enviando em {language} · clique para parar", "translate.consent.metered": "Sua conexão é limitada ou está no modo de economia de dados.", "translate.consent.notNow": "Agora não", "translate.consent.text": "Esta tradução precisa do {model}, um download de {size}. Ele fica neste dispositivo, então é baixado só uma vez.", @@ -2515,16 +2513,8 @@ "translate.picker.cancel": "Cancelar", "translate.picker.confirm": "Traduzir", "translate.picker.dialog": "Traduzir de novo a partir de outro idioma", - "translate.picker.fromAuto": "Detectar automaticamente", - "translate.picker.fromLabel": "Traduzir de", "translate.picker.label": "Traduzir esta linha de", - "translate.picker.sameLanguage": "Idioma igual: a ortografia e a gramática são corrigidas em vez disso.", "translate.picker.sameLanguageNeedsGpu": "Corrigir uma mensagem requer o modelo de linguagem.", - "translate.picker.targetConfirm": "Traduzir", - "translate.picker.targetConfirmCorrect": "Corrigir", - "translate.picker.targetDialog": "Traduzir esta mensagem para um idioma", - "translate.picker.targetLabel": "Traduzir para", - "translate.picker.thisMessageOnly": "Somente esta mensagem", "translate.reason.answered": "respondeu à pergunta em vez de traduzi-la", "translate.reason.degenerate": "a resposta era lixo de símbolos", "translate.reason.narration": "falou sobre a solicitação em vez de traduzi-la", diff --git a/client/locales/pt.po b/client/locales/pt.po index 3041650cf..eee2c34cf 100644 --- a/client/locales/pt.po +++ b/client/locales/pt.po @@ -14636,22 +14636,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "Idioma não identificado, não traduzido. Opções de tradução" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "Enviando corrigido em {language} · clique para parar" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "Traduzir o que eu envio…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "Enviando em {language} · clique para parar" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15013,15 +15015,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "Não traduzido" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "Nada neste dispositivo consegue traduzir de {from} para {to}." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "Nada neste dispositivo consegue traduzir para {to}." @@ -15158,67 +15161,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "Traduzir de novo a partir de outro idioma" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "Detectar automaticamente" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "Traduzir de" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "Traduzir esta linha de" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "Idioma igual: a ortografia e a gramática são corrigidas em vez disso." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "Corrigir uma mensagem requer o modelo de linguagem." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "Traduzir" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "Corrigir" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "Traduzir esta mensagem para um idioma" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "Traduzir para" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "Somente esta mensagem" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15434,6 +15390,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "Não foi possível acessar o processo de tradução: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15537,6 +15577,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/qqx.json b/client/locales/qqx.json index f3c33d503..7a25035bd 100644 --- a/client/locales/qqx.json +++ b/client/locales/qqx.json @@ -2591,9 +2591,9 @@ "translate.chip.options": "‫{languages}. ţŕàñśľàţïöñ öṗţïöñś{languages}. ţŕàñśľàţïöñ öṗţïöñś‬", "translate.chip.sentIn": "‫śéñţ ïñ {language}śéñţ ïñ {language}‬", "translate.chip.unsure": "‫ľàñġüàġé ñöţ ţöľḋ àṗàŕţ, ñöţ ţŕàñśľàţéḋ. ţŕàñśľàţïöñ öṗţïöñśľàñġüàġé ñöţ ţöľḋ àṗàŕţ, ñöţ ţŕàñśľàţéḋ. ţŕàñśľàţïöñ öṗţïöñś‬", - "translate.composer.sendCorrectedOn": "‫śéñḋïñġ ċöŕŕéċţéḋ ïñ {language} · ċľïċķ ţö śţöṗśéñḋïñġ ċöŕŕéċţéḋ ïñ {language} · ċľïċķ ţö śţöṗ‬", + "translate.composer.sendCorrectedOn": "‫śéñḋïñġ ċöŕŕéċţéḋ ïñ {language} · ċḣàñġé öŕ ţüŕñ öḟḟśéñḋïñġ ċöŕŕéċţéḋ ïñ {language} · ċḣàñġé öŕ ţüŕñ öḟḟ‬", "translate.composer.sendOff": "‫ţŕàñśľàţé ẃḣàţ ï śéñḋ…ţŕàñśľàţé ẃḣàţ ï śéñḋ…‬", - "translate.composer.sendOn": "‫śéñḋïñġ ïñ {language} · ċľïċķ ţö śţöṗśéñḋïñġ ïñ {language} · ċľïċķ ţö śţöṗ‬", + "translate.composer.sendOn": "‫śéñḋïñġ ïñ {language} · ċḣàñġé öŕ ţüŕñ öḟḟśéñḋïñġ ïñ {language} · ċḣàñġé öŕ ţüŕñ öḟḟ‬", "translate.composer.targetCorrected": "‫{language} ✓{language} ✓‬", "translate.composer.targetPair": "‫{from} → {to}{from} → {to}‬", "translate.consent.metered": "‫ýöüŕ ċöññéċţïöñ ïś ṁéţéŕéḋ öŕ śéţ ţö śàṽé ḋàţà.ýöüŕ ċöññéċţïöñ ïś ṁéţéŕéḋ öŕ śéţ ţö śàṽé ḋàţà.‬", @@ -2677,16 +2677,8 @@ "translate.picker.cancel": "‫ċàñċéľċàñċéľ‬", "translate.picker.confirm": "‫ţŕàñśľàţéţŕàñśľàţé‬", "translate.picker.dialog": "‫ŕéţŕàñśľàţé ḟŕöṁ àñöţḣéŕ ľàñġüàġéŕéţŕàñśľàţé ḟŕöṁ àñöţḣéŕ ľàñġüàġé‬", - "translate.picker.fromAuto": "‫ḋéţéċţ àüţöṁàţïċàľľýḋéţéċţ àüţöṁàţïċàľľý‬", - "translate.picker.fromLabel": "‫ţŕàñśľàţé ḟŕöṁţŕàñśľàţé ḟŕöṁ‬", "translate.picker.label": "‫ţŕàñśľàţé ţḣïś ľïñé ḟŕöṁţŕàñśľàţé ţḣïś ľïñé ḟŕöṁ‬", - "translate.picker.sameLanguage": "‫śàṁé ľàñġüàġé: śṗéľľïñġ àñḋ ġŕàṁṁàŕ àŕé ċöŕŕéċţéḋ ïñśţéàḋ.śàṁé ľàñġüàġé: śṗéľľïñġ àñḋ ġŕàṁṁàŕ àŕé ċöŕŕéċţéḋ ïñśţéàḋ.‬", "translate.picker.sameLanguageNeedsGpu": "‫ċöŕŕéċţïñġ à ṁéśśàġé ñééḋś ţḣé ľàñġüàġé ṁöḋéľ.ċöŕŕéċţïñġ à ṁéśśàġé ñééḋś ţḣé ľàñġüàġé ṁöḋéľ.‬", - "translate.picker.targetConfirm": "‫ţŕàñśľàţéţŕàñśľàţé‬", - "translate.picker.targetConfirmCorrect": "‫ċöŕŕéċţċöŕŕéċţ‬", - "translate.picker.targetDialog": "‫ţŕàñśľàţé ţḣïś ṁéśśàġé ïñţö à ľàñġüàġéţŕàñśľàţé ţḣïś ṁéśśàġé ïñţö à ľàñġüàġé‬", - "translate.picker.targetLabel": "‫ţŕàñśľàţé ïñţöţŕàñśľàţé ïñţö‬", - "translate.picker.thisMessageOnly": "‫ţḣïś ṁéśśàġé öñľýţḣïś ṁéśśàġé öñľý‬", "translate.reason.answered": "‫àñśẃéŕéḋ ţḣé q́üéśţïöñ ïñśţéàḋ öḟ ţŕàñśľàţïñġ ïţàñśẃéŕéḋ ţḣé q́üéśţïöñ ïñśţéàḋ öḟ ţŕàñśľàţïñġ ïţ‬", "translate.reason.degenerate": "‫ţḣé àñśẃéŕ ẃàś śýṁḃöľ ġàŕḃàġéţḣé àñśẃéŕ ẃàś śýṁḃöľ ġàŕḃàġé‬", "translate.reason.narration": "‫ţàľķéḋ àḃöüţ ţḣé ŕéq́üéśţ ïñśţéàḋ öḟ ţŕàñśľàţïñġţàľķéḋ àḃöüţ ţḣé ŕéq́üéśţ ïñśţéàḋ öḟ ţŕàñśľàţïñġ‬", @@ -2728,6 +2720,20 @@ "translate.settings.useGpu": "‫üśé {model} ẃḣéŕé ïţ ïś ţḣé ḃéţţéŕ ċḣöïċéüśé {model} ẃḣéŕé ïţ ïś ţḣé ḃéţţéŕ ċḣöïċé‬", "translate.settings.useNllb": "‫üśé ñľľḃ-200 ḟöŕ ľàñġüàġéś ţḣé öţḣéŕ ṁöḋéľś ḋö ñöţ ċöṽéŕüśé ñľľḃ-200 ḟöŕ ľàñġüàġéś ţḣé öţḣéŕ ṁöḋéľś ḋö ñöţ ċöṽéŕ‬", "translate.settings.workerError": "‫ċöüľḋ ñöţ ŕéàċḣ ţḣé ţŕàñśľàţïöñ ẃöŕķéŕ: {error}ċöüľḋ ñöţ ŕéàċḣ ţḣé ţŕàñśľàţïöñ ẃöŕķéŕ: {error}‬", + "translate.sheet.choose": "‫ċḣööśé…ċḣööśé…‬", + "translate.sheet.corrects": "‫ċöŕŕéċţś śṗéľľïñġċöŕŕéċţś śṗéľľïñġ‬", + "translate.sheet.detect": "‫ḋéţéċţ àüţöṁàţïċàľľýḋéţéċţ àüţöṁàţïċàľľý‬", + "translate.sheet.dialog": "‫ċḣööśé ţḣé ľàñġüàġé ýöüŕ ṁéśśàġéś àŕé śéñţ ïñċḣööśé ţḣé ľàñġüàġé ýöüŕ ṁéśśàġéś àŕé śéñţ ïñ‬", + "translate.sheet.download": "‫{size} ḋöẃñľöàḋ{size} ḋöẃñľöàḋ‬", + "translate.sheet.iWrite": "‫ï ẃŕïţéï ẃŕïţé‬", + "translate.sheet.noMatch": "‫ñö ľàñġüàġé ṁàţċḣéś “{query}”.ñö ľàñġüàġé ṁàţċḣéś “{query}”.‬", + "translate.sheet.off": "‫öḟḟöḟḟ‬", + "translate.sheet.recent": "‫śṗöķéñ ïñ {channel} ľàţéľýśṗöķéñ ïñ {channel} ľàţéľý‬", + "translate.sheet.search": "‫śéàŕċḣ ľàñġüàġéśśéàŕċḣ ľàñġüàġéś‬", + "translate.sheet.swap": "‫śẃàṗ ľàñġüàġéśśẃàṗ ľàñġüàġéś‬", + "translate.sheet.theyRead": "‫ţḣéý ŕéàḋţḣéý ŕéàḋ‬", + "translate.sheet.thisMessageOnly": "‫ţḣïś ṁéśśàġé öñľýţḣïś ṁéśśàġé öñľý‬", + "translate.sheet.unavailable": "‫ñöţ àṽàïľàḃľé ḣéŕéñöţ àṽàïľàḃľé ḣéŕé‬", "translate.strip.checkFailed": "‫ċöüľḋñ'ţ ċḣéċķċöüľḋñ'ţ ċḣéċķ‬", "translate.strip.copied": "‫ċöṗïéḋċöṗïéḋ‬", "translate.strip.copy": "‫ċöṗý ţḣé ţŕàñśľàţïöñċöṗý ţḣé ţŕàñśľàţïöñ‬", @@ -2745,6 +2751,10 @@ "translate.strip.sendsWhenReady": "‫śéñḋś ẃḣéñ ţḣé ţŕàñśľàţïöñ ïś ŕéàḋýśéñḋś ẃḣéñ ţḣé ţŕàñśľàţïöñ ïś ŕéàḋý‬", "translate.strip.sourceAuto": "‫àüţöàüţö‬", "translate.strip.willSendIn": "‫ẃïľľ śéñḋ ïñ {language}ẃïľľ śéñḋ ïñ {language}‬", + "translate.suggest.accept": "‫ẃŕïţé ïñ {language}ẃŕïţé ïñ {language}‬", + "translate.suggest.decline": "‫ñöţ ïñ ţḣïś ċḣàññéľñöţ ïñ ţḣïś ċḣàññéľ‬", + "translate.suggest.download": "‫üśéś {model}, à {size} ḋöẃñľöàḋ.üśéś {model}, à {size} ḋöẃñľöàḋ.‬", + "translate.suggest.lead": "‫ṁöśţ öḟ {channel} ẃŕïţéś ïñ {language}. ẃŕïţé ýöüŕ ṁéśśàġéś ïñ {language} ḣéŕé?ṁöśţ öḟ {channel} ẃŕïţéś ïñ {language}. ẃŕïţé ýöüŕ ṁéśśàġéś ïñ {language} ḣéŕé?‬", "translate.system.asking": "‫{from} → {to}: ẃàïţïñġ ḟöŕ ţḣé ḋöẃñľöàḋ…{from} → {to}: ẃàïţïñġ ḟöŕ ţḣé ḋöẃñľöàḋ…‬", "translate.system.download": "‫ḋöẃñľöàḋḋöẃñľöàḋ‬", "translate.system.heading": "‫àṗṗľé ţŕàñśľàţïöñàṗṗľé ţŕàñśľàţïöñ‬", diff --git a/client/locales/ro.json b/client/locales/ro.json index 7fa2bf4d2..79cc084ce 100644 --- a/client/locales/ro.json +++ b/client/locales/ro.json @@ -2492,9 +2492,7 @@ "translate.chip.alreadyIn": "Deja în {language}, nu a fost tradus. Opțiuni de traducere", "translate.chip.options": "{languages}. Opțiuni de traducere", "translate.chip.unsure": "Limba nu a putut fi identificată, nu a fost tradus. Opțiuni de traducere", - "translate.composer.sendCorrectedOn": "Se trimite corectat în {language} · clic pentru a opri", "translate.composer.sendOff": "Tradu ce trimit…", - "translate.composer.sendOn": "Se trimite în {language} · clic pentru a opri", "translate.consent.metered": "Conexiunea ta este contorizată sau setată să economisească date.", "translate.consent.notNow": "Nu acum", "translate.consent.text": "Această traducere are nevoie de {model}, o descărcare de {size}. Rămâne pe acest dispozitiv, așa că se descarcă o singură dată.", @@ -2573,16 +2571,8 @@ "translate.picker.cancel": "Anulează", "translate.picker.confirm": "Tradu", "translate.picker.dialog": "Retradu din altă limbă", - "translate.picker.fromAuto": "Detectează automat", - "translate.picker.fromLabel": "Tradu din", "translate.picker.label": "Tradu acest rând din", - "translate.picker.sameLanguage": "Aceeași limbă: în schimb se corectează ortografia și gramatica.", "translate.picker.sameLanguageNeedsGpu": "Corectarea unui mesaj necesită modelul de limbaj.", - "translate.picker.targetConfirm": "Tradu", - "translate.picker.targetConfirmCorrect": "Corectează", - "translate.picker.targetDialog": "Tradu acest mesaj într-o limbă", - "translate.picker.targetLabel": "Tradu în", - "translate.picker.thisMessageOnly": "Doar acest mesaj", "translate.reason.answered": "a răspuns la întrebare în loc de a o traduce", "translate.reason.degenerate": "răspunsul a fost un șir de simboluri fără sens", "translate.reason.narration": "a vorbit despre cerere în loc s-o traducă", diff --git a/client/locales/ro.po b/client/locales/ro.po index 4af555080..b9270931f 100644 --- a/client/locales/ro.po +++ b/client/locales/ro.po @@ -14716,22 +14716,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "Limba nu a putut fi identificată, nu a fost tradus. Opțiuni de traducere" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "Se trimite corectat în {language} · clic pentru a opri" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "Tradu ce trimit…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "Se trimite în {language} · clic pentru a opri" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15093,15 +15095,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "Nu a fost tradus" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "Nimic de pe acest dispozitiv nu poate traduce din {from} în {to}." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "Nimic de pe acest dispozitiv nu poate traduce în {to}." @@ -15238,67 +15241,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "Retradu din altă limbă" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "Detectează automat" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "Tradu din" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "Tradu acest rând din" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "Aceeași limbă: în schimb se corectează ortografia și gramatica." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "Corectarea unui mesaj necesită modelul de limbaj." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "Tradu" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "Corectează" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "Tradu acest mesaj într-o limbă" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "Tradu în" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "Doar acest mesaj" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15516,6 +15472,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "Nu s-a putut contacta procesul de traducere: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15619,6 +15659,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/ru.json b/client/locales/ru.json index d58a142d1..6043aa997 100644 --- a/client/locales/ru.json +++ b/client/locales/ru.json @@ -2629,9 +2629,7 @@ "translate.chip.alreadyIn": "Уже на нужном языке ({language}), не переведено. Параметры перевода", "translate.chip.options": "{languages}. Параметры перевода", "translate.chip.unsure": "Язык не определён, не переведено. Параметры перевода", - "translate.composer.sendCorrectedOn": "Отправка с исправлением, язык: {language} · нажмите, чтобы отключить", "translate.composer.sendOff": "Переводить то, что я отправляю…", - "translate.composer.sendOn": "Отправка с переводом, язык: {language} · нажмите, чтобы отключить", "translate.consent.metered": "Ваше подключение лимитное или в режиме экономии трафика.", "translate.consent.notNow": "Не сейчас", "translate.consent.text": "Для этого перевода нужна модель {model}, скачивание {size}. Она останется на этом устройстве, поэтому скачивается только один раз.", @@ -2710,16 +2708,8 @@ "translate.picker.cancel": "Отмена", "translate.picker.confirm": "Перевести", "translate.picker.dialog": "Перевести заново с другого языка", - "translate.picker.fromAuto": "Определить автоматически", - "translate.picker.fromLabel": "Перевести с", "translate.picker.label": "Перевести эту строку с", - "translate.picker.sameLanguage": "Тот же язык: вместо перевода исправляются орфография и грамматика.", "translate.picker.sameLanguageNeedsGpu": "Для исправления сообщения нужна языковая модель.", - "translate.picker.targetConfirm": "Перевести", - "translate.picker.targetConfirmCorrect": "Исправить", - "translate.picker.targetDialog": "Перевести это сообщение на язык", - "translate.picker.targetLabel": "Перевести на", - "translate.picker.thisMessageOnly": "Только это сообщение", "translate.reason.answered": "модель ответила на вопрос вместо перевода", "translate.reason.degenerate": "ответ состоял из бессмысленных символов", "translate.reason.narration": "модель рассуждала о запросе вместо перевода", diff --git a/client/locales/ru.po b/client/locales/ru.po index 60eb04d07..8a8b83321 100644 --- a/client/locales/ru.po +++ b/client/locales/ru.po @@ -14716,22 +14716,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "Язык не определён, не переведено. Параметры перевода" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "Отправка с исправлением, язык: {language} · нажмите, чтобы отключить" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "Переводить то, что я отправляю…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "Отправка с переводом, язык: {language} · нажмите, чтобы отключить" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15093,15 +15095,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "Не переведено" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "На этом устройстве нечем перевести с языка «{from}» на «{to}»." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "На этом устройстве нечем перевести на язык «{to}»." @@ -15238,67 +15241,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "Перевести заново с другого языка" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "Определить автоматически" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "Перевести с" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "Перевести эту строку с" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "Тот же язык: вместо перевода исправляются орфография и грамматика." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "Для исправления сообщения нужна языковая модель." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "Перевести" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "Исправить" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "Перевести это сообщение на язык" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "Перевести на" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "Только это сообщение" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15516,6 +15472,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "Не удалось связаться с фоновым процессом перевода: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15619,6 +15659,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/sk.json b/client/locales/sk.json index d1c2eece7..6a96153cb 100644 --- a/client/locales/sk.json +++ b/client/locales/sk.json @@ -2536,9 +2536,7 @@ "translate.chip.alreadyIn": "Už je v jazyku {language}, nepreložené. Možnosti prekladu", "translate.chip.options": "{languages}. Možnosti prekladu", "translate.chip.unsure": "Jazyk sa nepodarilo určiť, nepreložené. Možnosti prekladu", - "translate.composer.sendCorrectedOn": "Odosielanie s opravami, jazyk: {language} · kliknutím vypnete", "translate.composer.sendOff": "Prekladať, čo odosielam…", - "translate.composer.sendOn": "Odosielanie s prekladom, jazyk: {language} · kliknutím vypnete", "translate.consent.metered": "Vaše pripojenie je merané alebo nastavené na šetrenie dát.", "translate.consent.notNow": "Teraz nie", "translate.consent.text": "Tento preklad potrebuje {model}, sťahovanie s veľkosťou {size}. Zostane na tomto zariadení, takže sa sťahuje iba raz.", @@ -2617,16 +2615,8 @@ "translate.picker.cancel": "Zrušiť", "translate.picker.confirm": "Preložiť", "translate.picker.dialog": "Preložiť znova z iného jazyka", - "translate.picker.fromAuto": "Rozpoznať automaticky", - "translate.picker.fromLabel": "Preložiť z", "translate.picker.label": "Preložiť tento riadok z", - "translate.picker.sameLanguage": "Rovnaký jazyk: namiesto prekladu sa opraví pravopis a gramatika.", "translate.picker.sameLanguageNeedsGpu": "Oprava správy vyžaduje jazykový model.", - "translate.picker.targetConfirm": "Preložiť", - "translate.picker.targetConfirmCorrect": "Opraviť", - "translate.picker.targetDialog": "Preložiť túto správu do jazyka", - "translate.picker.targetLabel": "Preložiť do", - "translate.picker.thisMessageOnly": "Len túto správu", "translate.reason.answered": "model odpovedal na otázku, namiesto aby ju preložil", "translate.reason.degenerate": "odpoveď bola nezmyselná zmes symbolov", "translate.reason.narration": "model hovoril o požiadavke, namiesto aby prekladal", diff --git a/client/locales/sk.po b/client/locales/sk.po index 0362caf83..e83ce5a93 100644 --- a/client/locales/sk.po +++ b/client/locales/sk.po @@ -14716,22 +14716,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "Jazyk sa nepodarilo určiť, nepreložené. Možnosti prekladu" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "Odosielanie s opravami, jazyk: {language} · kliknutím vypnete" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "Prekladať, čo odosielam…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "Odosielanie s prekladom, jazyk: {language} · kliknutím vypnete" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15093,15 +15095,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "Nepreložené" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "Nič na tomto zariadení nedokáže preložiť: {from} → {to}." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "Nič na tomto zariadení nedokáže preložiť do jazyka: {to}." @@ -15238,67 +15241,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "Preložiť znova z iného jazyka" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "Rozpoznať automaticky" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "Preložiť z" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "Preložiť tento riadok z" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "Rovnaký jazyk: namiesto prekladu sa opraví pravopis a gramatika." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "Oprava správy vyžaduje jazykový model." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "Preložiť" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "Opraviť" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "Preložiť túto správu do jazyka" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "Preložiť do" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "Len túto správu" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15516,6 +15472,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "Nedá sa spojiť s pracovným procesom prekladu: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15619,6 +15659,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/sv.json b/client/locales/sv.json index 5e2b8c125..2a5e3eae0 100644 --- a/client/locales/sv.json +++ b/client/locales/sv.json @@ -2421,9 +2421,7 @@ "translate.chip.alreadyIn": "Redan på {language}, inte översatt. Översättningsalternativ", "translate.chip.options": "{languages}. Översättningsalternativ", "translate.chip.unsure": "Språket kunde inte avgöras, inte översatt. Översättningsalternativ", - "translate.composer.sendCorrectedOn": "Skickar korrigerat på {language} · klicka för att stoppa", "translate.composer.sendOff": "Översätt vad jag skickar…", - "translate.composer.sendOn": "Skickar på {language} · klicka för att stoppa", "translate.consent.metered": "Din anslutning är taxerad eller inställd på att spara data.", "translate.consent.notNow": "Inte nu", "translate.consent.text": "Den här översättningen behöver {model}, en nedladdning på {size}. Den sparas på den här enheten och laddas bara ner en gång.", @@ -2502,16 +2500,8 @@ "translate.picker.cancel": "Avbryt", "translate.picker.confirm": "Översätt", "translate.picker.dialog": "Översätt på nytt från ett annat språk", - "translate.picker.fromAuto": "Identifiera automatiskt", - "translate.picker.fromLabel": "Översätt från", "translate.picker.label": "Översätt den här raden från", - "translate.picker.sameLanguage": "Samma språk: stavning och grammatik korrigeras istället.", "translate.picker.sameLanguageNeedsGpu": "Att rätta ett meddelande kräver språkmodellen.", - "translate.picker.targetConfirm": "Översätt", - "translate.picker.targetConfirmCorrect": "Rätta", - "translate.picker.targetDialog": "Översätt det här meddelandet till ett språk", - "translate.picker.targetLabel": "Översätt till", - "translate.picker.thisMessageOnly": "Bara det här meddelandet", "translate.reason.answered": "svarade på frågan istället för att översätta den", "translate.reason.degenerate": "svaret var teckensmörja", "translate.reason.narration": "talade om begäran istället för att översätta", diff --git a/client/locales/sv.po b/client/locales/sv.po index 6996da23b..05c5b072c 100644 --- a/client/locales/sv.po +++ b/client/locales/sv.po @@ -14636,22 +14636,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "Språket kunde inte avgöras, inte översatt. Översättningsalternativ" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "Skickar korrigerat på {language} · klicka för att stoppa" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "Översätt vad jag skickar…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "Skickar på {language} · klicka för att stoppa" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15013,15 +15015,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "Inte översatt" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "Inget på den här enheten kan översätta från {from} till {to}." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "Inget på den här enheten kan översätta till {to}." @@ -15158,67 +15161,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "Översätt på nytt från ett annat språk" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "Identifiera automatiskt" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "Översätt från" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "Översätt den här raden från" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "Samma språk: stavning och grammatik korrigeras istället." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "Att rätta ett meddelande kräver språkmodellen." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "Översätt" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "Rätta" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "Översätt det här meddelandet till ett språk" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "Översätt till" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "Bara det här meddelandet" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15434,6 +15390,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "Kunde inte nå översättnings-workern: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15537,6 +15577,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/th.json b/client/locales/th.json index b0bff1a8b..03da24eac 100644 --- a/client/locales/th.json +++ b/client/locales/th.json @@ -2411,9 +2411,7 @@ "translate.chip.alreadyIn": "เป็น{language}อยู่แล้ว จึงไม่ได้แปล ตัวเลือกการแปล", "translate.chip.options": "{languages}. ตัวเลือกการแปล", "translate.chip.unsure": "แยกภาษาไม่ได้ จึงไม่ได้แปล ตัวเลือกการแปล", - "translate.composer.sendCorrectedOn": "กำลังส่งแบบตรวจแก้เป็น {language} · คลิกเพื่อหยุด", "translate.composer.sendOff": "แปลสิ่งที่ฉันส่ง…", - "translate.composer.sendOn": "กำลังส่งเป็น {language} · คลิกเพื่อหยุด", "translate.consent.metered": "การเชื่อมต่อของคุณเป็นแบบคิดตามปริมาณข้อมูลหรือตั้งค่าประหยัดข้อมูลไว้", "translate.consent.notNow": "ไว้ทีหลัง", "translate.consent.text": "การแปลนี้ต้องใช้ {model} ซึ่งต้องดาวน์โหลด {size} โมเดลจะเก็บไว้บนอุปกรณ์นี้ จึงดาวน์โหลดเพียงครั้งเดียว", @@ -2492,16 +2490,8 @@ "translate.picker.cancel": "ยกเลิก", "translate.picker.confirm": "แปล", "translate.picker.dialog": "แปลใหม่จากภาษาอื่น", - "translate.picker.fromAuto": "ตรวจจับอัตโนมัติ", - "translate.picker.fromLabel": "แปลจาก", "translate.picker.label": "แปลบรรทัดนี้จาก", - "translate.picker.sameLanguage": "ภาษาเดียวกัน: จะตรวจแก้การสะกดและไวยากรณ์แทน", "translate.picker.sameLanguageNeedsGpu": "การตรวจแก้ข้อความต้องใช้โมเดลภาษา", - "translate.picker.targetConfirm": "แปล", - "translate.picker.targetConfirmCorrect": "ตรวจแก้", - "translate.picker.targetDialog": "แปลข้อความนี้เป็นภาษาอื่น", - "translate.picker.targetLabel": "แปลเป็น", - "translate.picker.thisMessageOnly": "เฉพาะข้อความนี้", "translate.reason.answered": "ตอบคำถามแทนที่จะแปล", "translate.reason.degenerate": "คำตอบเป็นสัญลักษณ์ที่ไม่มีความหมาย", "translate.reason.narration": "พูดถึงคำขอแทนที่จะแปล", diff --git a/client/locales/th.po b/client/locales/th.po index e9139ab0d..d1162957e 100644 --- a/client/locales/th.po +++ b/client/locales/th.po @@ -14556,22 +14556,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "แยกภาษาไม่ได้ จึงไม่ได้แปล ตัวเลือกการแปล" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "กำลังส่งแบบตรวจแก้เป็น {language} · คลิกเพื่อหยุด" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "แปลสิ่งที่ฉันส่ง…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "กำลังส่งเป็น {language} · คลิกเพื่อหยุด" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -14933,15 +14935,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "ไม่ได้แปล" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "ไม่มีสิ่งใดบนอุปกรณ์นี้ที่แปลจาก{from}เป็น{to}ได้" -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "ไม่มีสิ่งใดบนอุปกรณ์นี้ที่แปลเป็น{to}ได้" @@ -15078,67 +15081,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "แปลใหม่จากภาษาอื่น" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "ตรวจจับอัตโนมัติ" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "แปลจาก" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "แปลบรรทัดนี้จาก" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "ภาษาเดียวกัน: จะตรวจแก้การสะกดและไวยากรณ์แทน" - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "การตรวจแก้ข้อความต้องใช้โมเดลภาษา" -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "แปล" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "ตรวจแก้" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "แปลข้อความนี้เป็นภาษาอื่น" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "แปลเป็น" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "เฉพาะข้อความนี้" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15352,6 +15308,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "ไม่สามารถเชื่อมต่อกับเวิร์กเกอร์การแปล: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15455,6 +15495,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/tr.json b/client/locales/tr.json index 1b965a0d5..dd43a0e37 100644 --- a/client/locales/tr.json +++ b/client/locales/tr.json @@ -2475,9 +2475,7 @@ "translate.chip.alreadyIn": "Zaten bu dilde ({language}), çevrilmedi. Çeviri seçenekleri", "translate.chip.options": "{languages}. Çeviri seçenekleri", "translate.chip.unsure": "Dil ayırt edilemedi, çevrilmedi. Çeviri seçenekleri", - "translate.composer.sendCorrectedOn": "Düzeltilmiş gönderiliyor, dil: {language} · durdurmak için tıkla", "translate.composer.sendOff": "Gönderdiklerimi çevir…", - "translate.composer.sendOn": "Gönderim dili: {language} · durdurmak için tıkla", "translate.consent.metered": "Bağlantın kotalı ya da veri tasarrufuna ayarlı.", "translate.consent.notNow": "Şimdi değil", "translate.consent.text": "Bu çeviri için {model} gerekiyor; indirme boyutu {size}. Bu cihazda kalır, yani yalnızca bir kez indirilir.", @@ -2556,16 +2554,8 @@ "translate.picker.cancel": "İptal", "translate.picker.confirm": "Çevir", "translate.picker.dialog": "Başka bir dilden yeniden çevir", - "translate.picker.fromAuto": "Otomatik algıla", - "translate.picker.fromLabel": "Kaynak dil", "translate.picker.label": "Bu satırı şu dilden çevir", - "translate.picker.sameLanguage": "Aynı dil: bunun yerine yazım ve dilbilgisi düzeltilir.", "translate.picker.sameLanguageNeedsGpu": "Bir mesajı düzeltmek için dil modeli gerekir.", - "translate.picker.targetConfirm": "Çevir", - "translate.picker.targetConfirmCorrect": "Düzelt", - "translate.picker.targetDialog": "Bu mesajı bir dile çevir", - "translate.picker.targetLabel": "Hedef dil", - "translate.picker.thisMessageOnly": "Yalnızca bu mesaj", "translate.reason.answered": "çevirmek yerine soruyu yanıtladı", "translate.reason.degenerate": "yanıt anlamsız sembollerden oluşuyordu", "translate.reason.narration": "çevirmek yerine istek hakkında konuştu", diff --git a/client/locales/tr.po b/client/locales/tr.po index 1bb1e596d..bfbdb924c 100644 --- a/client/locales/tr.po +++ b/client/locales/tr.po @@ -14636,22 +14636,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "Dil ayırt edilemedi, çevrilmedi. Çeviri seçenekleri" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "Düzeltilmiş gönderiliyor, dil: {language} · durdurmak için tıkla" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "Gönderdiklerimi çevir…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "Gönderim dili: {language} · durdurmak için tıkla" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15013,15 +15015,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "Çevrilmedi" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "Bu cihazdaki hiçbir şey çeviri yapamıyor: {from} → {to}." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "Bu cihazdaki hiçbir şey şu dile çeviremiyor: {to}." @@ -15158,67 +15161,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "Başka bir dilden yeniden çevir" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "Otomatik algıla" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "Kaynak dil" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "Bu satırı şu dilden çevir" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "Aynı dil: bunun yerine yazım ve dilbilgisi düzeltilir." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "Bir mesajı düzeltmek için dil modeli gerekir." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "Çevir" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "Düzelt" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "Bu mesajı bir dile çevir" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "Hedef dil" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "Yalnızca bu mesaj" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15434,6 +15390,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "Çeviri çalışanına erişilemedi: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15537,6 +15577,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/uk.json b/client/locales/uk.json index a0a598594..4168b5e45 100644 --- a/client/locales/uk.json +++ b/client/locales/uk.json @@ -2629,9 +2629,7 @@ "translate.chip.alreadyIn": "Уже потрібною мовою ({language}), не перекладено. Параметри перекладу", "translate.chip.options": "{languages}. Параметри перекладу", "translate.chip.unsure": "Мову не визначено, не перекладено. Параметри перекладу", - "translate.composer.sendCorrectedOn": "Надсилання з виправленням, мова: {language} · натисніть, щоб вимкнути", "translate.composer.sendOff": "Перекладати те, що я надсилаю…", - "translate.composer.sendOn": "Надсилання з перекладом, мова: {language} · натисніть, щоб вимкнути", "translate.consent.metered": "Ваше з'єднання лімітне або в режимі заощадження трафіку.", "translate.consent.notNow": "Не зараз", "translate.consent.text": "Для цього перекладу потрібна модель {model}, завантаження {size}. Вона залишиться на цьому пристрої, тож завантажується лише один раз.", @@ -2710,16 +2708,8 @@ "translate.picker.cancel": "Скасувати", "translate.picker.confirm": "Перекласти", "translate.picker.dialog": "Перекласти знову з іншої мови", - "translate.picker.fromAuto": "Визначити автоматично", - "translate.picker.fromLabel": "Перекласти з", "translate.picker.label": "Перекласти цей рядок з", - "translate.picker.sameLanguage": "Та сама мова: замість перекладу виправляються правопис і граматика.", "translate.picker.sameLanguageNeedsGpu": "Для виправлення повідомлення потрібна мовна модель.", - "translate.picker.targetConfirm": "Перекласти", - "translate.picker.targetConfirmCorrect": "Виправити", - "translate.picker.targetDialog": "Перекласти це повідомлення на мову", - "translate.picker.targetLabel": "Перекласти на", - "translate.picker.thisMessageOnly": "Лише це повідомлення", "translate.reason.answered": "модель відповіла на питання замість перекладу", "translate.reason.degenerate": "відповідь складалася з безглуздих символів", "translate.reason.narration": "модель міркувала про запит замість перекладу", diff --git a/client/locales/uk.po b/client/locales/uk.po index 6eac5a933..4d077b564 100644 --- a/client/locales/uk.po +++ b/client/locales/uk.po @@ -14716,22 +14716,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "Мову не визначено, не перекладено. Параметри перекладу" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "Надсилання з виправленням, мова: {language} · натисніть, щоб вимкнути" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "Перекладати те, що я надсилаю…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "Надсилання з перекладом, мова: {language} · натисніть, щоб вимкнути" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -15093,15 +15095,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "Не перекладено" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "На цьому пристрої нічим перекласти з мови «{from}» на «{to}»." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "На цьому пристрої нічим перекласти на мову «{to}»." @@ -15238,67 +15241,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "Перекласти знову з іншої мови" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "Визначити автоматично" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "Перекласти з" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "Перекласти цей рядок з" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "Та сама мова: замість перекладу виправляються правопис і граматика." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "Для виправлення повідомлення потрібна мовна модель." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "Перекласти" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "Виправити" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "Перекласти це повідомлення на мову" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "Перекласти на" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "Лише це повідомлення" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15516,6 +15472,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "Не вдалося зв'язатися з фоновим процесом перекладу: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15619,6 +15659,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/vi.json b/client/locales/vi.json index 05ba8d6d3..38ae1591f 100644 --- a/client/locales/vi.json +++ b/client/locales/vi.json @@ -2410,9 +2410,7 @@ "translate.chip.alreadyIn": "Đã là {language}, không dịch. Tùy chọn dịch", "translate.chip.options": "{languages}. Tùy chọn dịch", "translate.chip.unsure": "Không phân biệt được ngôn ngữ, không dịch. Tùy chọn dịch", - "translate.composer.sendCorrectedOn": "Đang gửi bản đã sửa bằng {language} · nhấp để dừng", "translate.composer.sendOff": "Dịch những gì tôi gửi…", - "translate.composer.sendOn": "Đang gửi bằng {language} · nhấp để dừng", "translate.consent.metered": "Kết nối của bạn là kết nối có đo lượng dữ liệu hoặc đang bật tiết kiệm dữ liệu.", "translate.consent.notNow": "Để sau", "translate.consent.text": "Bản dịch này cần {model}, dung lượng tải xuống {size}. Mô hình được giữ trên thiết bị này nên chỉ tải xuống một lần.", @@ -2491,16 +2489,8 @@ "translate.picker.cancel": "Hủy", "translate.picker.confirm": "Dịch", "translate.picker.dialog": "Dịch lại từ ngôn ngữ khác", - "translate.picker.fromAuto": "Tự động nhận diện", - "translate.picker.fromLabel": "Dịch từ", "translate.picker.label": "Dịch dòng này từ", - "translate.picker.sameLanguage": "Cùng ngôn ngữ: thay vào đó sẽ sửa chính tả và ngữ pháp.", "translate.picker.sameLanguageNeedsGpu": "Sửa một tin nhắn cần mô hình ngôn ngữ.", - "translate.picker.targetConfirm": "Dịch", - "translate.picker.targetConfirmCorrect": "Sửa", - "translate.picker.targetDialog": "Dịch tin nhắn này sang một ngôn ngữ", - "translate.picker.targetLabel": "Dịch sang", - "translate.picker.thisMessageOnly": "Chỉ tin nhắn này", "translate.reason.answered": "đã trả lời thay vì dịch", "translate.reason.degenerate": "câu trả lời là những ký hiệu rác", "translate.reason.narration": "đã nói về yêu cầu thay vì dịch", diff --git a/client/locales/vi.po b/client/locales/vi.po index f3216c82a..5b4e0abb8 100644 --- a/client/locales/vi.po +++ b/client/locales/vi.po @@ -14558,22 +14558,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "Không phân biệt được ngôn ngữ, không dịch. Tùy chọn dịch" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "Đang gửi bản đã sửa bằng {language} · nhấp để dừng" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "Dịch những gì tôi gửi…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "Đang gửi bằng {language} · nhấp để dừng" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -14935,15 +14937,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "Không dịch" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "Không có gì trên thiết bị này dịch được từ {from} sang {to}." -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "Không có gì trên thiết bị này dịch được sang {to}." @@ -15080,67 +15083,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "Dịch lại từ ngôn ngữ khác" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "Tự động nhận diện" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "Dịch từ" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "Dịch dòng này từ" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "Cùng ngôn ngữ: thay vào đó sẽ sửa chính tả và ngữ pháp." - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "Sửa một tin nhắn cần mô hình ngôn ngữ." -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "Dịch" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "Sửa" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "Dịch tin nhắn này sang một ngôn ngữ" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "Dịch sang" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "Chỉ tin nhắn này" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15354,6 +15310,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "Không thể kết nối với worker dịch: {error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15457,6 +15497,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/client/locales/zh.json b/client/locales/zh.json index bef8dfe87..aad56a721 100644 --- a/client/locales/zh.json +++ b/client/locales/zh.json @@ -2413,9 +2413,7 @@ "translate.chip.alreadyIn": "已是{language},未翻译。翻译选项", "translate.chip.options": "{languages}。翻译选项", "translate.chip.unsure": "无法识别语言,未翻译。翻译选项", - "translate.composer.sendCorrectedOn": "正在以{language}校正后发送 · 点击停止", "translate.composer.sendOff": "翻译我发送的内容…", - "translate.composer.sendOn": "正在以{language}发送 · 点击停止", "translate.consent.metered": "你的连接为按流量计费或已开启省流量模式。", "translate.consent.notNow": "暂不", "translate.consent.text": "此翻译需要 {model},下载大小为 {size}。它会保存在此设备上,因此只需下载一次。", @@ -2495,16 +2493,8 @@ "translate.picker.cancel": "取消", "translate.picker.confirm": "翻译", "translate.picker.dialog": "从另一种语言重新翻译", - "translate.picker.fromAuto": "自动检测", - "translate.picker.fromLabel": "源语言", "translate.picker.label": "此行的源语言", - "translate.picker.sameLanguage": "同一语言:改为校正拼写和语法。", "translate.picker.sameLanguageNeedsGpu": "校正消息需要语言模型。", - "translate.picker.targetConfirm": "翻译", - "translate.picker.targetConfirmCorrect": "校正", - "translate.picker.targetDialog": "将这条消息翻译成某种语言", - "translate.picker.targetLabel": "目标语言", - "translate.picker.thisMessageOnly": "仅此消息", "translate.reason.answered": "回答了问题,而不是翻译它", "translate.reason.degenerate": "回答是一堆无意义的符号", "translate.reason.narration": "谈论了请求本身,而不是进行翻译", diff --git a/client/locales/zh.po b/client/locales/zh.po index 07a23a27d..84bcd2600 100644 --- a/client/locales/zh.po +++ b/client/locales/zh.po @@ -14557,22 +14557,24 @@ msgctxt "translate.chip.unsure" msgid "Language not told apart, not translated. Translation options" msgstr "无法识别语言,未翻译。翻译选项" -#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel corrects every message (spelling and grammar) in {language}, the user's own language, before sending; a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendCorrectedOn" -msgid "Sending corrected in {language} · click to stop" +msgid "Sending corrected in {language} · change or turn off" msgstr "正在以{language}校正后发送 · 点击停止" -#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens a dialog to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. +#. Tooltip and aria-label of the composer's translate button (the language mark beside the attachment and send buttons) while no send target is set; a click opens the language sheet to pick the language every message is sent in, or a correction pass. Ends with an ellipsis. #: client/components/ChatInput.vue msgctxt "translate.composer.sendOff" msgid "Translate what I send…" msgstr "翻译我发送的内容…" -#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click turns that off. The middle dot is part of the copy. +#. Tooltip and aria-label of the composer's translate button while the channel sends every message translated into {language} (a language name in the interface language); a click opens the language sheet, where it can be changed or turned off. The middle dot is part of the copy. #: client/components/ChatInput.vue +#, fuzzy msgctxt "translate.composer.sendOn" -msgid "Sending in {language} · click to stop" +msgid "Sending in {language} · change or turn off" msgstr "正在以{language}发送 · 点击停止" #. The composer's translate button while the channel's send target is the user's own language: lines are corrected, not translated. {language} is the short code, e.g. EN. @@ -14934,15 +14936,16 @@ msgctxt "translate.notTranslated" msgid "Not translated" msgstr "未翻译" -#. Lead sentence when a translation is unavailable for a language pair (the composer's translate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. +#. Lead sentence when a translation is unavailable for a language pair (the composer's language sheet, the retranslate dialog, the translation panel's 'Send my messages in'); the reason follows as a sentence of its own. {from} and {to} are language names, already in the interface language. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailable" msgid "Nothing on this device can translate {from} into {to}." msgstr "此设备上没有能将{from}翻译成{to}的引擎。" -#. Lead sentence in the composer's translate dialog when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. -#: client/components/SourceLanguagePicker.vue +#. Lead sentence in the composer's language sheet when the source is 'Detect automatically' and no engine here translates into the chosen language; the reason follows as a sentence of its own. {to} is a language name, already in the interface language. +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.pair.unavailableInto" msgid "Nothing on this device can translate into {to}." msgstr "此设备上没有能翻译成{to}的引擎。" @@ -15079,67 +15082,20 @@ msgctxt "translate.picker.dialog" msgid "Retranslate from another language" msgstr "从另一种语言重新翻译" -#. First option of the source-language picker in the composer's translate-into dialog: the draft's language is detected rather than named. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromAuto" -msgid "Detect automatically" -msgstr "自动检测" - -#. Label of the source-language picker in the dialog the composer's translate button opens; preselected to the language the user reads. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.fromLabel" -msgid "Translate from" -msgstr "源语言" - #. Label of the language picker in the dialog that translates one message again from a language the user picks. #: client/components/SourceLanguagePicker.vue msgctxt "translate.picker.label" msgid "Translate this line from" msgstr "此行的源语言" -#. Hint under the selects in the composer's translate-into dialog when the source and the target are the same language: the draft is corrected, not translated. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.sameLanguage" -msgid "Same language: spelling and grammar are corrected instead." -msgstr "同一语言:改为校正拼写和语法。" - -#. Lead sentence in the composer's translate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. +#. Lead sentence in the composer's language sheet, the retranslate dialog and the translation panel when the same language is picked as source and target (a spelling and grammar cleanup pass), which only the language model (a large AI model) can do and which cannot run now; the reason (no WebGPU, buffer too small, switched off, …) follows as a sentence of its own. #: client/components/SourceLanguagePicker.vue #: client/components/TranslationPanel.vue +#: client/components/ComposerLanguageSheet.vue msgctxt "translate.picker.sameLanguageNeedsGpu" msgid "Correcting a message needs the language model." msgstr "校正消息需要语言模型。" -#. Button that translates the draft into the language picked in the composer's translate-into dialog. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirm" -msgid "Translate" -msgstr "翻译" - -#. Button of the composer's translate dialog when the source and the target are the same language: the draft is corrected in that language rather than translated. Imperative verb ('correct this draft'), not the adjective. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetConfirmCorrect" -msgid "Correct" -msgstr "校正" - -#. aria-label of the small dialog the composer's translate button opens: pick the language the draft is translated into, this once. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetDialog" -msgid "Translate this message into a language" -msgstr "将这条消息翻译成某种语言" - -#. Label of the language picker in the dialog the composer's translate button opens; the draft is translated into the picked language. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.targetLabel" -msgid "Translate into" -msgstr "目标语言" - -#. Checkbox in the composer's translate dialog: ticked, the picked language applies to the draft alone (a one-off) rather than becoming the channel's send target. -#: client/components/SourceLanguagePicker.vue -msgctxt "translate.picker.thisMessageOnly" -msgid "This message only" -msgstr "仅此消息" - #. Failure reason after 'Couldn't translate': the model replied to a question it found in the message rather than translating the message. Lowercase; reads as the tail of that sentence. #: client/components/TranslationLine.vue msgctxt "translate.reason.answered" @@ -15353,6 +15309,90 @@ msgctxt "translate.settings.workerError" msgid "Could not reach the translation worker: {error}" msgstr "无法连接到翻译工作线程:{error}" +#. Value of the language sheet's 'They read' side before any language is picked. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.choose" +msgid "Choose…" +msgstr "" + +#. Status beside the language the user writes in, in the language sheet's 'They read' list: picking it corrects spelling and grammar rather than translating. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.corrects" +msgid "Corrects spelling" +msgstr "" + +#. Value of the language sheet's 'I write' side, and the first row of its list, when the language of each message is detected rather than named. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.detect" +msgid "Detect automatically" +msgstr "" + +#. aria-label of the language sheet the composer's translate button opens: pick the language you write in and the one your messages are translated into. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.dialog" +msgid "Choose the language your messages are sent in" +msgstr "" + +#. Status beside a language in the language sheet whose translation model is not on this device yet. {size} is a file size such as '312.5 MiB'. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.download" +msgid "{size} download" +msgstr "" + +#. Label of the language sheet's first side: the language the user writes their messages in. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.iWrite" +msgid "I write" +msgstr "" + +#. Shown in the language sheet's list when the search finds nothing. {query} is what the user typed. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.noMatch" +msgid "No language matches “{query}”." +msgstr "" + +#. Shortcut button in the language sheet: stop translating the user's messages in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.off" +msgid "Off" +msgstr "" + +#. Heading over shortcut buttons in the language sheet: the languages people wrote in most in this channel recently. {channel} is the channel's name, e.g. #linux. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.recent" +msgid "Spoken in {channel} lately" +msgstr "" + +#. Placeholder and aria-label of the search field in the language sheet. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.search" +msgid "Search languages" +msgstr "" + +#. Tooltip and aria-label of the button between the language sheet's two sides: exchanges the language the user writes and the one they send in. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.swap" +msgid "Swap languages" +msgstr "" + +#. Label of the language sheet's second side: the language the user's messages are sent in, the one the channel reads. Short, shown above the language's name. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.theyRead" +msgid "They read" +msgstr "" + +#. Checkbox in the language sheet: ticked, the picked language applies to the draft alone (a one-off) rather than to every message the user sends in this channel. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.thisMessageOnly" +msgid "This message only" +msgstr "" + +#. Status beside a language in the language sheet that nothing on this device can translate into from the language the user writes. Short. +#: client/components/ComposerLanguageSheet.vue +msgctxt "translate.sheet.unavailable" +msgid "Not available here" +msgstr "" + #. Text of the second row of the composer's translation strip when translating the translation back into the user's own language failed. #: client/components/ChatInput.vue msgctxt "translate.strip.checkFailed" @@ -15456,6 +15496,30 @@ msgctxt "translate.strip.willSendIn" msgid "Will send in {language}" msgstr "" +#. Button on the suggestion card above the message box: from now on, the user's messages in this channel are translated into {language} (a language name) before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.accept" +msgid "Write in {language}" +msgstr "" + +#. Button on the suggestion card above the message box: dismiss it and do not suggest it again in this channel. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.decline" +msgid "Not in this channel" +msgstr "" + +#. Note on the suggestion card above the message box. {model} is a translation model's name; {size} is a file size such as '312.5 MiB'. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.download" +msgid "Uses {model}, a {size} download." +msgstr "" + +#. Card above the message box. {channel} is a channel name such as #linux; {language} is a language name such as Portuguese. Saying yes translates the user's messages into that language before they are sent. +#: client/components/ChatInput.vue +msgctxt "translate.suggest.lead" +msgid "Most of {channel} writes in {language}. Write your messages in {language} here?" +msgstr "" + #. Status line in the settings → Translation, the section for the device's own translator (Apple's Translation framework, iPhone and iPad app only). "Apple Translation" is the feature's name; keep it recognisable. {from} and {to} are language names. Shown while the system's download sheet is open. #: client/components/Settings/TranslationOptions.vue msgctxt "translate.system.asking" diff --git a/docs/resources/client-features.md b/docs/resources/client-features.md index 8a9f8bbae..e96e47043 100644 --- a/docs/resources/client-features.md +++ b/docs/resources/client-features.md @@ -118,7 +118,7 @@ On-device translation of what a channel reads and of the composer's drafts. The **Switched off by default** (translation.md § Switched off by default): translation runs only where the deploy allows it (`config.json` `translation.enabled`) and the user switched on Settings → Language → "Translate messages on this device" (`settings.ts` `translationEnabled`, default false) — one rule, `translate/switch.ts` `translationAllowed`, asked through `index.ts` `translationOn()` by every surface and every entry of the pipeline. Off, it is absent: no panel, no composer button, strip or first-Enter translation (`outgoing.ts` `firstEnterTranslates`, `/me` included), no toolbar Translate, no channel-menu "Translation…", no `TranslationLine` or topic translation — and no service, so no probe, no worker and no question (`TranslationSwitch`: off hands out nothing, `translateService()` throws if asked; switching off cancels the reader's queues quietly and the composer's strips, clears `state.translations`/`state.outgoingTranslations`, then disposes the service). `thelounge.translate`, the downloaded models and the consents are kept; switching back on restarts every channel whose kept setting reads (`reader.ts` `resumeReading`), without a reload. Settings keeps the interface language picker either way; off, the tab and its heading read "Language" and the controls (`Settings/TranslationOptions.vue`) are not mounted. Tests `test/translate/switch.ts`, `test/translate/service.ts` "switched off by the user"; browser check `tools/scenarios/translate-off.mjs` (also `--mobile`); every other translation scenario seeds the switch on (`tools/scenarios/lib/translation.mjs`). -**Translation** — on-device, `client/js/translate/`: engines behind one `Engine` interface (`engines/webllm.ts`, WebLLM on WebGPU; `engines/seq2seq.ts`, transformers.js NLLB/OPUS-MT on WASM or WebGPU; `engines/cpuLlm.ts`, wllama running the language model's GGUF on the CPU) in `js/translate-worker.js` (its own Vite build, `vite.translate.config.mts`, run after every app build; `worker-entry.ts` is the only place the libraries are wired; the `*.real.ts` files are their only imports and never load under mocha), a per-pair route table of quality classes (`router.ts`: a downloaded model is preferred only inside its class, a seq2seq candidate takes the request's source hint when `from` is null; `routes.default.ts` placed from `tools/translate-eval/results/2026-09-12-languages.md` with Qwen re-scored on the web build's own 4-bit weights in `2026-09-13-web-weights.md`, one set of placement lists per GPU model — `routesFor(llmModelId)`, `limitedLanguagesFor` — each placed from its own model's web weights), NLLB sent sentence by sentence (`splitSentences`), **the device is a per-model choice, not the engine** (`docs/resources/translation.md` § Devices, `docs/projects/translation-devices.md`): every model lists a copy per device (`ModelRef.copies.gpu/cpu`; OPUS-MT and NLLB-200 have no GPU copy in this build, `CatalogOptions.seq2seqGpu` off, since the worker's ONNX Runtime is the wasm-only bundle — tests turn it on), Settings → Translation has a switch and a **Run on** radiogroup (Auto / GPU / CPU, `translateLlmDevice`/`translateOpusDevice`/`translateNllbDevice`, default Auto, unknown values read as Auto) per model and a Download/Remove line per copy, `modelBlock` answers per device (a pinned device that cannot run says why and never falls to the other), `models.ts` `deviceOrder` is Auto's order — a placeholder until measured on real GPUs; the language model's GPU copy marked down under Auto, after the GPU ran it (the copy on the device, or a GPU-loss reason), asks once a session, where its CPU copy can run, whether to carry on on the CPU (`ServiceDeps.askCpuFallback`, "The GPU stopped": this time, always — `translateLlmDevice` becomes `cpu` — or not now; a yes is the CPU copy's consent, `RouteInput.llmCpuAfterGpu` puts the CPU after the GPU for the session and the failed request carries on there) — and the router walks candidate × device (`DownKey` `@`), `engines/onDevice.ts` `OnDevice` holds one loaded seq2seq model on one device at a time, the language model on the CPU is wllama 3.8.1 with `unsloth/Qwen3-1.7B-GGUF` `Q4_K_M` (`CPU_LLM_COPY`; needs WASM Memory64; blocked on iPhone/iPad by memory; no 4B copy; a 1024-token window, batches of 2 and a 6 min deadline, `CPU_CONTEXT_WINDOW`/`CPU_BATCH_MAX_LINES`/`CPU_REQUEST_TIMEOUT_MS`, from `tools/translate-eval/results/2026-10-29-cpu-llm.md`) and consent is per copy (`consent.ts` `consentId`: the copy a model ran on before devices keeps its old id, the other is `@`); **the root service worker adds COOP `same-origin` + COEP `credentialless` to the app's navigations while translation is on, and to its worker scripts always** (`service-worker.js` `isolate`; the page's `isolate` flag in IndexedDB and an `isolation` message, `isolationFlag.ts`, written from `translationOn()` at boot and on every change; none on WebKit, no forced reload — Settings says "Reload the app to use N CPU threads" when the worker served this load with translation off) so the page is cross-origin isolated and the CPU models are multithreaded (`threads.ts` `cpuThreads`: phone min(4, cores−1), computer min(8, cores−1); `isolationState`: isolated / reload / unsupported), the device probe (`capability.ts`: `gpu` needs `shader-f16` and a storage binding (`min(maxBufferSize, maxStorageBufferBindingSize)`) that holds the smallest GPU model's largest tensor, `models.ts` `LLM_MIN_BUFFER_BYTES` — the 1.7B's small-buffer build's 74.2 MiB: a GPU binding WebGPU's 128 MiB floor (most phones) runs the 1.7B as the app's own library (`client/models/`, `tools/translate-split/`) that splits its 148 MiB vocabulary table in two, over the published weights through a rewritten manifest the worker seeds into WebLLM's cache (`engines/splitManifest.ts`), the build picked by one rule (`models.ts` `smallBufferBuildFor`), and `localStorage["thelounge.state.gpuTestLimitMiB"]` makes a desktop GPU bind what a phone does (translation.md § Phones and small GPUs); a phone's GPU model takes a prompt 64 tokens at a time (`models.ts` `prefillChunkFor`, set after each GPU reload through `WebLlmDeps.setPrefillChunk` on WebLLM's internal `prefillChunkSize`; switch `thelounge.state.gpuPrefillChunk`, `0` = the library's own; both switches also as `?gpuPrefillChunk=`/`?gpuTestLimitMiB=`, stored and stripped at boot by `translate/testSwitches.ts`); which model fits is `llmFits` against its smallest build, 185 MiB for the 4B, which has none — since `engines/gpuLimits.ts` raises the device WebLLM asks for, 128 MiB or 1 GiB and nothing between, to what the adapter offers, capped at 1 GiB; `cpu` needs WASM SIMD; `mobile` from `userAgentData`/the user agent, `isMobile`, an iPad asking for desktop sites by its touch points; a phone's page memory is the limit there — WebKit kills a page past ~2.1 GB with the GPU memory counted in — so on a mobile device the GPU model reloads with a 2048-token window (`MOBILE_CONTEXT_WINDOW`, `contextWindowFor`: the KV cache is that memory), the 4B is a `memory` block (`tooLargeForPhone`, `llmRunsOn`) and `service.ts` `makeRoom` keeps one engine in memory (the other unloaded first, a fresh worker for the GPU model once ORT's heap has grown); every LLM request, on every device, is fitted to the loaded window by `budget.ts` `fitToWindow` over a calibrated `tokenBound`, and WebLLM's own overflow is `CONTEXT_OVERFLOW`, retried bare and alone and never a pause — translation.md § Memory: the window, the 4B, one engine at a time), span protection (`spans.ts` — one canonical protection, and a marker pair is then rendered for the route: the marks themselves for the LLM (`LLM_MARKERS`, `renderMarkers`; measured, `⟦n⟧` pairs around words it must translate stop the model translating), numbered pairs for the seq2seq engines, and the reading queue chooses in `enqueueAt` because a message is protected before its route is known; a `⟦n⟧` the user typed is taken first, as its own `typed` span, so the engine never sees their number and `restoreAll` puts the brackets back as written), prompt assembly (`prompt.ts`, behind one prompt profile per GPU model — `prompts/` `promptProfileFor(modelId)`: 1.7B's is `prompt.ts` itself, 4B's a copy in `prompts/qwen3-4b.ts` to reword on its own; `WebLlmEngine` asks with the loaded model's, `tools/translate-llm.ts --profile` picks one offline), and `service.ts` (route, load on demand with the request deadline re-armed while the download progresses — `loadTicks`, `outgoing.ts` `armDeadline` — mark a failing candidate down, a GPU model unloaded after 3 idle minutes and a CPU model after 5 (`GPU_IDLE_UNLOAD_MS`, `CPU_IDLE_UNLOAD_MS`), a tier at once when switched off in Settings, and everything at once when no channel in the store reads or writes through translation, no line is queued and no composer strip is open — `reader.ts` `translationInUse`, `index.ts` `setTranslationUsage`). A model nobody downloaded by name is downloaded only after a yes (`consent.ts` `DownloadConsent`, asked once per model in `service.ts` `resolve` before any deadline is armed, through `ConfirmDialog.vue`; yes kept in `thelounge.state.translateConsent`, Settings' Download is one; Not now holds implicit downloads back for the session, the walk takes only what needs none, and a request left without fails with `needsDownloadError`, which pauses no queue; a Retry asks again; docs § A download asks first). `index.ts` is the singleton; `?fakeTranslate` on a dev build swaps in `fakePort.ts`; exactly three modules under `client/js/translate/` import the store — `index.ts`, `reader.ts` (the reading pipeline's glue) and `writer.ts` (the composer's). Settings → Translation (`Translation.vue`, the controls below the switch in `TranslationOptions.vue`) holds the target language (`LanguageSelect.vue`, the same control the dev build's sidebar globe uses, so there is one code list; it is the only language picker a production build has, so it stands outside the switch and the deploy's `translation.enabled` and stays, as "Interface language" under a tab named "Language", wherever translation is off; the reading language is therefore one of the 24 interface languages), the switch, formality and one switch per model (`translateLlm` the language model, `translateCpu` OPUS-MT, `translateNllb` NLLB-200, off by default and no longer gated by `translateCpu`: its licence is CC-BY-NC-4.0, so OPUS-MT and the language model serve without it and a pair only NLLB could take says so), the language model choice (a model this GPU cannot bind is a disabled option) (`translateLlmModel`: Qwen3-1.7B or Qwen3-4B, `models.ts` `LLM_CHOICES`, a deploy's `translation.llm.model` added as a third choice when it is neither; the setting stays `""` until the user picks and `llmChoice(catalog, setting, capability)` resolves an unset one **at read time, never persisting it** — the deploy's model, else `defaultLlmForAdapter` on a `gpu`-tier device (only a model that `llmFits`; the 4B only on a computer reporting `LARGE_LLM_MIN_MEMORY_GIB` = 6 GiB of `deviceMemory` — never on a phone, where Chrome reports 8 GiB, and never where the browser reports none; a chosen model the GPU cannot bind runs the default instead), else the catalog's default; `service.ts` `setLlmModel` switches without a reload, letting a running LLM request finish on the old model before unloading it) and the model manager. **What can run, and why not, is one Vue-free rule** (`docs/resources/translation.md` § What runs here, and why not): `models.ts` `modelBlock` per model (deploy off, probing, a device code, `buffer {needed, limit}`, `memory {needed}` on a phone, then the user's switch — never "switched off" for a model the device cannot run), `router.ts` `explainRoute` per pair (`resolveRoute` is its route, so the UI disables exactly what the router refuses; `routeInput` builds the input from the probe for the service and the UI, and skips the CPU models without WASM SIMD) and `translationBlock` for translation as a whole; `reader.ts` `pairVerdict`/`currentTranslationBlock` are the store glue and `helpers/translateWhy.ts` the sentences — Settings' model rows ("Can run on this device" / "Can't run…" with the reason, Download disabled), the composer dialog and the chips' Retranslate (confirm disabled, reason in a live region), the panel (notice, reading's On disabled, unwritable languages greyed) and the toolbar's Translate (disabled, reason as tooltip; under a finger aria-disabled, its tap opens the panel); `test/translate/availability.ts`, browser check `tools/scenarios/translate-capability.mjs` (five stubbed adapters, also `--mobile`). `config.json` `translation` block in `branding.ts`. The reading pipeline (`reader.ts`) runs detect → eligibility → context → queue on every message of a channel that is switched on: `detect.ts` (the function-word classifier `chatdetect.ts` first — generated tables in `stopwords.json`, `tools/generate-stopwords.py`; a decisive verdict wins over `franc`, lazy chunk, with a channel `LanguagePrior`; a thin verdict — one function-word hit, or a franc lead under `WEAK_CONFIDENCE` — is `Detection.weak`, a hint the line is never translated _from_ (`sourceFor`, the one place `from`/`unsure` is decided; a declared or prior placement is never weak; the router is told more than the prompt is — `sourceFor` also returns a `routeHint`, the channel's prior first and the weak verdict as the last resort, which rides on `QueueItem.routeHint` into `TranslateRequest.hint` and never into the prompt, so a weak line is still routable on a CPU-only device); the short-line lookup `wordlookup.ts` places a line of at most three words the classifier left unplaced from a frequency table — `wordlist.json`, `tools/generate-wordlist.py`, a lazy chunk of its own — so a lone "test" is English rather than nothing; `detectionSkip` skips a line detected as the reading language whatever its weight, or one it cannot place when the reading language is among its candidates — any other unplaced line is translated with no source named — and every line it skips gets a `status: "skipped"` entry, a chip in the shared accent style after the message with "Translate anyway" on its menu), `eligibility.ts` (no word floor — `MIN_WORDS` is 1, a line stripped to nothing still doesn't queue — pending copies — own lines are read too, and a posted translation keeps its read-back rather than being translated again, `sentReadBack.ts` `takesReadBack` — history newest first and capped per load, `ReplayBatches`), `context.ts` (`buildContext`, recent lines, reply target, topic, names, the newest `TERM_LINES` of the channel's terms plus the deploy glossary), `queue.ts` (`TranslateQueue`, one request per engine, LLM batching, drop-behind, a line judged untranslated is retried once bare, and a pause that ends by itself — `PAUSE_AFTER_FAILURES` (3) takes an engine out of the rotation and `PAUSE_RESUME_MS` (60 s) puts it back, failures zeroed and the pause the panel shows cleared through `QueueDeps.onResume`; `WORKER_DISPOSED` and an `ABORTED` the queue did not ask for count toward no pause at all — the batch goes back to the front of its engine's queue, nothing marked failed) and `channelStore.ts` (per-channel `read`/`write`/`formality`/`variant`/`languages`/`terms`/`once` — the translate button's last language — `thelounge.translate`, kept when a channel is left); results live in two store slices, `state.translations` (by message id; an entry follows its message out on an edit, a REDACT, the message-limit trim, a part or a quit) and `state.translateChannels` (by network/channel key), rendered by `TranslationLine.vue` under the original message (a judged `UNCHANGED` failure as the compact "Not translated" chip — icon alone in production, the reason appended in development, no Copy on its menu — with the original taking the translation's ink in place of the row it does not have, over `.self`'s dimming (`Message.vue` `unchanged`); other judged failures as the prose row, its frame and known reasons through t()), `TranslationPanel.vue` (the channel's reading switch and choices, opened from the channel menu's "Translation…": a header popover, § Header popovers) and the toolbar's Translate (`MessageActions.vue`, which also brings a hidden translation back). The channel's topic is read too (`reader.ts` `readTopic`, kept under `topic.ts` `topicEntryId` in the same slice): the header shows the translation in the topic's place, no row added, with a flipper before it that swaps it with the original. Reading covers what the channel shows, however old: live lines, replay batches, join fills and "load more" pages alike, history capped at `HISTORY_QUEUE_CAP` per load and newest first; switching reading on or to another language retranslates what is on screen (`requeueReading`, which a language change calls directly — `setReading` is already on). The channel menu's "Translation…" switches to the channel and asks through `state.translation.panelFor` rather than an event. The composer (`outgoing.ts`, Vue-free: `draftGate`, `writeSource`, `reverseTarget`, `termPair`, `translateDraft`) translates a draft as it is typed — a live preview Return then sends in one step — or into the language picked from the composer's translate button, which is the write target's toggle (independent of the reading switch; the picker's `purpose="target"` with a from-selector, its "This message only" a one-off through `writer.ts` `translateOnce`; the same language twice, or a draft detected in the picked language, is a cleanup pass, `purpose: "polish"`, LLM only, asked with the line alone -- no topic, names, terms or earlier lines, measured: any block above the line stops the model correcting -- and an echo meaning nothing to correct; a write target equal to the reading language gives every draft in it that pass) — `/me` is the one command that translates, `draftGate` answering `{kind: "action", text}` with what follows it so the strip holds the prose and Return sends `/me ` through `writer.ts` (the store glue beside `reader.ts`: `writeTarget`, `translateOutgoing` resolving `"strip" | "plain"`, `cancelOutgoing`, `checkOutgoing`, `noteOutgoingSent` for the voice and term memory), holding the reading queues (`holdReading`/`releaseReading`) for the run so a write is never starved behind reading traffic; the result lives in `store.state.outgoingTranslations` (by channel id) and renders as `ChatInput.vue`'s `.translate-bar` strip (`draftGate` gates the preview, `deliver` is the one path every send goes through, Return sends what the strip holds — waiting for it if it is not done yet — or, after a failure, the draft as written; the translate button names the target, "EN → DE", and the language picked with it is kept as the channel's `writeFrom`) with a round-trip row under it labelled "Reads back as", whose read-back is built with the channel's context the way the reader would build it for an incoming line (`checkOutgoing`), and which the posted line then shows as its translation (`recordSentReadBack` before the send, matched by channel and exact text in `sentReadBack.ts` — the pending copy, then the echo that replaces it). Tests `test/translate/`; browser checks `tools/scenarios/translate-off.mjs`, `tools/scenarios/translate-capability.mjs`, `tools/scenarios/translate-settings.mjs`, `tools/scenarios/translate-reading.mjs`, `tools/scenarios/translation-chips.mjs` and `tools/scenarios/translate-composer.mjs`. See `docs/resources/translation.md`, design `docs/projects/client-translation.md`. +**Translation** — on-device, `client/js/translate/`: engines behind one `Engine` interface (`engines/webllm.ts`, WebLLM on WebGPU; `engines/seq2seq.ts`, transformers.js NLLB/OPUS-MT on WASM or WebGPU; `engines/cpuLlm.ts`, wllama running the language model's GGUF on the CPU) in `js/translate-worker.js` (its own Vite build, `vite.translate.config.mts`, run after every app build; `worker-entry.ts` is the only place the libraries are wired; the `*.real.ts` files are their only imports and never load under mocha), a per-pair route table of quality classes (`router.ts`: a downloaded model is preferred only inside its class, a seq2seq candidate takes the request's source hint when `from` is null; `routes.default.ts` placed from `tools/translate-eval/results/2026-09-12-languages.md` with Qwen re-scored on the web build's own 4-bit weights in `2026-09-13-web-weights.md`, one set of placement lists per GPU model — `routesFor(llmModelId)`, `limitedLanguagesFor` — each placed from its own model's web weights), NLLB sent sentence by sentence (`splitSentences`), **the device is a per-model choice, not the engine** (`docs/resources/translation.md` § Devices, `docs/projects/translation-devices.md`): every model lists a copy per device (`ModelRef.copies.gpu/cpu`; OPUS-MT and NLLB-200 have no GPU copy in this build, `CatalogOptions.seq2seqGpu` off, since the worker's ONNX Runtime is the wasm-only bundle — tests turn it on), Settings → Translation has a switch and a **Run on** radiogroup (Auto / GPU / CPU, `translateLlmDevice`/`translateOpusDevice`/`translateNllbDevice`, default Auto, unknown values read as Auto) per model and a Download/Remove line per copy, `modelBlock` answers per device (a pinned device that cannot run says why and never falls to the other), `models.ts` `deviceOrder` is Auto's order — a placeholder until measured on real GPUs; the language model's GPU copy marked down under Auto, after the GPU ran it (the copy on the device, or a GPU-loss reason), asks once a session, where its CPU copy can run, whether to carry on on the CPU (`ServiceDeps.askCpuFallback`, "The GPU stopped": this time, always — `translateLlmDevice` becomes `cpu` — or not now; a yes is the CPU copy's consent, `RouteInput.llmCpuAfterGpu` puts the CPU after the GPU for the session and the failed request carries on there) — and the router walks candidate × device (`DownKey` `@`), `engines/onDevice.ts` `OnDevice` holds one loaded seq2seq model on one device at a time, the language model on the CPU is wllama 3.8.1 with `unsloth/Qwen3-1.7B-GGUF` `Q4_K_M` (`CPU_LLM_COPY`; needs WASM Memory64; blocked on iPhone/iPad by memory; no 4B copy; a 1024-token window, batches of 2 and a 6 min deadline, `CPU_CONTEXT_WINDOW`/`CPU_BATCH_MAX_LINES`/`CPU_REQUEST_TIMEOUT_MS`, from `tools/translate-eval/results/2026-10-29-cpu-llm.md`) and consent is per copy (`consent.ts` `consentId`: the copy a model ran on before devices keeps its old id, the other is `@`); **the root service worker adds COOP `same-origin` + COEP `credentialless` to the app's navigations while translation is on, and to its worker scripts always** (`service-worker.js` `isolate`; the page's `isolate` flag in IndexedDB and an `isolation` message, `isolationFlag.ts`, written from `translationOn()` at boot and on every change; none on WebKit, no forced reload — Settings says "Reload the app to use N CPU threads" when the worker served this load with translation off) so the page is cross-origin isolated and the CPU models are multithreaded (`threads.ts` `cpuThreads`: phone min(4, cores−1), computer min(8, cores−1); `isolationState`: isolated / reload / unsupported), the device probe (`capability.ts`: `gpu` needs `shader-f16` and a storage binding (`min(maxBufferSize, maxStorageBufferBindingSize)`) that holds the smallest GPU model's largest tensor, `models.ts` `LLM_MIN_BUFFER_BYTES` — the 1.7B's small-buffer build's 74.2 MiB: a GPU binding WebGPU's 128 MiB floor (most phones) runs the 1.7B as the app's own library (`client/models/`, `tools/translate-split/`) that splits its 148 MiB vocabulary table in two, over the published weights through a rewritten manifest the worker seeds into WebLLM's cache (`engines/splitManifest.ts`), the build picked by one rule (`models.ts` `smallBufferBuildFor`), and `localStorage["thelounge.state.gpuTestLimitMiB"]` makes a desktop GPU bind what a phone does (translation.md § Phones and small GPUs); a phone's GPU model takes a prompt 64 tokens at a time (`models.ts` `prefillChunkFor`, set after each GPU reload through `WebLlmDeps.setPrefillChunk` on WebLLM's internal `prefillChunkSize`; switch `thelounge.state.gpuPrefillChunk`, `0` = the library's own; both switches also as `?gpuPrefillChunk=`/`?gpuTestLimitMiB=`, stored and stripped at boot by `translate/testSwitches.ts`); which model fits is `llmFits` against its smallest build, 185 MiB for the 4B, which has none — since `engines/gpuLimits.ts` raises the device WebLLM asks for, 128 MiB or 1 GiB and nothing between, to what the adapter offers, capped at 1 GiB; `cpu` needs WASM SIMD; `mobile` from `userAgentData`/the user agent, `isMobile`, an iPad asking for desktop sites by its touch points; a phone's page memory is the limit there — WebKit kills a page past ~2.1 GB with the GPU memory counted in — so on a mobile device the GPU model reloads with a 2048-token window (`MOBILE_CONTEXT_WINDOW`, `contextWindowFor`: the KV cache is that memory), the 4B is a `memory` block (`tooLargeForPhone`, `llmRunsOn`) and `service.ts` `makeRoom` keeps one engine in memory (the other unloaded first, a fresh worker for the GPU model once ORT's heap has grown); every LLM request, on every device, is fitted to the loaded window by `budget.ts` `fitToWindow` over a calibrated `tokenBound`, and WebLLM's own overflow is `CONTEXT_OVERFLOW`, retried bare and alone and never a pause — translation.md § Memory: the window, the 4B, one engine at a time), span protection (`spans.ts` — one canonical protection, and a marker pair is then rendered for the route: the marks themselves for the LLM (`LLM_MARKERS`, `renderMarkers`; measured, `⟦n⟧` pairs around words it must translate stop the model translating), numbered pairs for the seq2seq engines, and the reading queue chooses in `enqueueAt` because a message is protected before its route is known; a `⟦n⟧` the user typed is taken first, as its own `typed` span, so the engine never sees their number and `restoreAll` puts the brackets back as written), prompt assembly (`prompt.ts`, behind one prompt profile per GPU model — `prompts/` `promptProfileFor(modelId)`: 1.7B's is `prompt.ts` itself, 4B's a copy in `prompts/qwen3-4b.ts` to reword on its own; `WebLlmEngine` asks with the loaded model's, `tools/translate-llm.ts --profile` picks one offline), and `service.ts` (route, load on demand with the request deadline re-armed while the download progresses — `loadTicks`, `outgoing.ts` `armDeadline` — mark a failing candidate down, a GPU model unloaded after 3 idle minutes and a CPU model after 5 (`GPU_IDLE_UNLOAD_MS`, `CPU_IDLE_UNLOAD_MS`), a tier at once when switched off in Settings, and everything at once when no channel in the store reads or writes through translation, no line is queued and no composer strip is open — `reader.ts` `translationInUse`, `index.ts` `setTranslationUsage`). A model nobody downloaded by name is downloaded only after a yes (`consent.ts` `DownloadConsent`, asked once per model in `service.ts` `resolve` before any deadline is armed, through `ConfirmDialog.vue`; yes kept in `thelounge.state.translateConsent`, Settings' Download is one; Not now holds implicit downloads back for the session, the walk takes only what needs none, and a request left without fails with `needsDownloadError`, which pauses no queue; a Retry asks again; docs § A download asks first). `index.ts` is the singleton; `?fakeTranslate` on a dev build swaps in `fakePort.ts`; exactly three modules under `client/js/translate/` import the store — `index.ts`, `reader.ts` (the reading pipeline's glue) and `writer.ts` (the composer's). Settings → Translation (`Translation.vue`, the controls below the switch in `TranslationOptions.vue`) holds the target language (`LanguageSelect.vue`, the same control the dev build's sidebar globe uses, so there is one code list; it is the only language picker a production build has, so it stands outside the switch and the deploy's `translation.enabled` and stays, as "Interface language" under a tab named "Language", wherever translation is off; the reading language is therefore one of the 24 interface languages), the switch, formality and one switch per model (`translateLlm` the language model, `translateCpu` OPUS-MT, `translateNllb` NLLB-200, off by default and no longer gated by `translateCpu`: its licence is CC-BY-NC-4.0, so OPUS-MT and the language model serve without it and a pair only NLLB could take says so), the language model choice (a model this GPU cannot bind is a disabled option) (`translateLlmModel`: Qwen3-1.7B or Qwen3-4B, `models.ts` `LLM_CHOICES`, a deploy's `translation.llm.model` added as a third choice when it is neither; the setting stays `""` until the user picks and `llmChoice(catalog, setting, capability)` resolves an unset one **at read time, never persisting it** — the deploy's model, else `defaultLlmForAdapter` on a `gpu`-tier device (only a model that `llmFits`; the 4B only on a computer reporting `LARGE_LLM_MIN_MEMORY_GIB` = 6 GiB of `deviceMemory` — never on a phone, where Chrome reports 8 GiB, and never where the browser reports none; a chosen model the GPU cannot bind runs the default instead), else the catalog's default; `service.ts` `setLlmModel` switches without a reload, letting a running LLM request finish on the old model before unloading it) and the model manager. **What can run, and why not, is one Vue-free rule** (`docs/resources/translation.md` § What runs here, and why not): `models.ts` `modelBlock` per model (deploy off, probing, a device code, `buffer {needed, limit}`, `memory {needed}` on a phone, then the user's switch — never "switched off" for a model the device cannot run), `router.ts` `explainRoute` per pair (`resolveRoute` is its route, so the UI disables exactly what the router refuses; `routeInput` builds the input from the probe for the service and the UI, and skips the CPU models without WASM SIMD) and `translationBlock` for translation as a whole; `reader.ts` `pairVerdict`/`currentTranslationBlock` are the store glue and `helpers/translateWhy.ts` the sentences — Settings' model rows ("Can run on this device" / "Can't run…" with the reason, Download disabled), the composer dialog and the chips' Retranslate (confirm disabled, reason in a live region), the panel (notice, reading's On disabled, unwritable languages greyed) and the toolbar's Translate (disabled, reason as tooltip; under a finger aria-disabled, its tap opens the panel); `test/translate/availability.ts`, browser check `tools/scenarios/translate-capability.mjs` (five stubbed adapters, also `--mobile`). `config.json` `translation` block in `branding.ts`. The reading pipeline (`reader.ts`) runs detect → eligibility → context → queue on every message of a channel that is switched on: `detect.ts` (the function-word classifier `chatdetect.ts` first — generated tables in `stopwords.json`, `tools/generate-stopwords.py`; a decisive verdict wins over `franc`, lazy chunk, with a channel `LanguagePrior`; a thin verdict — one function-word hit, or a franc lead under `WEAK_CONFIDENCE` — is `Detection.weak`, a hint the line is never translated _from_ (`sourceFor`, the one place `from`/`unsure` is decided; a declared or prior placement is never weak; the router is told more than the prompt is — `sourceFor` also returns a `routeHint`, the channel's prior first and the weak verdict as the last resort, which rides on `QueueItem.routeHint` into `TranslateRequest.hint` and never into the prompt, so a weak line is still routable on a CPU-only device); the short-line lookup `wordlookup.ts` places a line of at most three words the classifier left unplaced from a frequency table — `wordlist.json`, `tools/generate-wordlist.py`, a lazy chunk of its own — so a lone "test" is English rather than nothing; `detectionSkip` skips a line detected as the reading language whatever its weight, or one it cannot place when the reading language is among its candidates — any other unplaced line is translated with no source named — and every line it skips gets a `status: "skipped"` entry, a chip in the shared accent style after the message with "Translate anyway" on its menu), `eligibility.ts` (no word floor — `MIN_WORDS` is 1, a line stripped to nothing still doesn't queue — pending copies — own lines are read too, and a posted translation keeps its read-back rather than being translated again, `sentReadBack.ts` `takesReadBack` — history newest first and capped per load, `ReplayBatches`), `context.ts` (`buildContext`, recent lines, reply target, topic, names, the newest `TERM_LINES` of the channel's terms plus the deploy glossary), `queue.ts` (`TranslateQueue`, one request per engine, LLM batching, drop-behind, a line judged untranslated is retried once bare, and a pause that ends by itself — `PAUSE_AFTER_FAILURES` (3) takes an engine out of the rotation and `PAUSE_RESUME_MS` (60 s) puts it back, failures zeroed and the pause the panel shows cleared through `QueueDeps.onResume`; `WORKER_DISPOSED` and an `ABORTED` the queue did not ask for count toward no pause at all — the batch goes back to the front of its engine's queue, nothing marked failed) and `channelStore.ts` (per-channel `read`/`write`/`writeFrom`/`formality`/`variant`/`languages`/`terms`/`once` — the translate button's last language — `noSuggest` — `thelounge.translate`, kept when a channel is left); results live in two store slices, `state.translations` (by message id; an entry follows its message out on an edit, a REDACT, the message-limit trim, a part or a quit) and `state.translateChannels` (by network/channel key), rendered by `TranslationLine.vue` under the original message (a judged `UNCHANGED` failure as the compact "Not translated" chip — icon alone in production, the reason appended in development, no Copy on its menu — with the original taking the translation's ink in place of the row it does not have, over `.self`'s dimming (`Message.vue` `unchanged`); other judged failures as the prose row, its frame and known reasons through t()), `TranslationPanel.vue` (the channel's reading switch and choices, opened from the channel menu's "Translation…": a header popover, § Header popovers) and the toolbar's Translate (`MessageActions.vue`, which also brings a hidden translation back). The channel's topic is read too (`reader.ts` `readTopic`, kept under `topic.ts` `topicEntryId` in the same slice): the header shows the translation in the topic's place, no row added, with a flipper before it that swaps it with the original. Reading covers what the channel shows, however old: live lines, replay batches, join fills and "load more" pages alike, history capped at `HISTORY_QUEUE_CAP` per load and newest first; switching reading on or to another language retranslates what is on screen (`requeueReading`, which a language change calls directly — `setReading` is already on). The channel menu's "Translation…" switches to the channel and asks through `state.translation.panelFor` rather than an event. The composer (`outgoing.ts`, Vue-free: `draftGate`, `writeSource`, `reverseTarget`, `termPair`, `translateDraft`) translates a draft as it is typed — a live preview Return then sends in one step — or into the language picked from the composer's translate button, which opens the language sheet (`ComposerLanguageSheet.vue`, independent of the reading switch: I write ⇄ They read, the channel's recent languages as chips from `recent.ts`, a search, each row's download size or why it cannot run, Off; its "This message only" a one-off through `writer.ts` `translateOnce`; a channel mostly written in another language offers it on a suggestion card above the strip, declined per channel as `noSuggest`; the same language twice, or a draft detected in the picked language, is a cleanup pass, `purpose: "polish"`, LLM only, asked with the line alone -- no topic, names, terms or earlier lines, measured: any block above the line stops the model correcting -- and an echo meaning nothing to correct; a write target equal to the reading language gives every draft in it that pass) — `/me` is the one command that translates, `draftGate` answering `{kind: "action", text}` with what follows it so the strip holds the prose and Return sends `/me ` through `writer.ts` (the store glue beside `reader.ts`: `writeTarget`, `translateOutgoing` resolving `"strip" | "plain"`, `cancelOutgoing`, `checkOutgoing`, `noteOutgoingSent` for the voice and term memory), holding the reading queues (`holdReading`/`releaseReading`) for the run so a write is never starved behind reading traffic; the result lives in `store.state.outgoingTranslations` (by channel id) and renders as `ChatInput.vue`'s `.translate-bar` strip (`draftGate` gates the preview, `deliver` is the one path every send goes through, Return sends what the strip holds — waiting for it if it is not done yet — or, after a failure, the draft as written; the translate button names the target, "EN → DE", and the language picked with it is kept as the channel's `writeFrom`) with a round-trip row under it labelled "Reads back as", whose read-back is built with the channel's context the way the reader would build it for an incoming line (`checkOutgoing`), and which the posted line then shows as its translation (`recordSentReadBack` before the send, matched by channel and exact text in `sentReadBack.ts` — the pending copy, then the echo that replaces it). Tests `test/translate/`; browser checks `tools/scenarios/translate-off.mjs`, `tools/scenarios/translate-capability.mjs`, `tools/scenarios/translate-settings.mjs`, `tools/scenarios/translate-reading.mjs`, `tools/scenarios/translation-chips.mjs` and `tools/scenarios/translate-composer.mjs`. See `docs/resources/translation.md`, design `docs/projects/client-translation.md`. ## Interface languages (i18n) diff --git a/docs/resources/translation.md b/docs/resources/translation.md index e373b3bef..ab12d3575 100644 --- a/docs/resources/translation.md +++ b/docs/resources/translation.md @@ -1335,8 +1335,8 @@ where the user has gone meanwhile. There is no Send or Edit button on the strip any more -- the composer's own send button does what Return does -- only Copy. -**The language the user writes in is theirs to say.** The dialog the -translate button opens has a "Translate from" as well as a "Translate into"; +**The language the user writes in is theirs to say.** The sheet the +translate button opens has an "I write" as well as a "They read"; picked as the channel's target (not "This message only"), both are kept (`channelStore.ts` `writeFrom` beside `write`; turning the target off, or setting it from the panel, clears it), and every draft is then taken as that @@ -1349,11 +1349,25 @@ the request fails (`writer.ts` `translateOutgoing`): a draft misplaced by the detector is likelier than a language the user writes in with no model for it. -The dialog opens on a tap and nothing more: on a touch screen it focuses -itself rather than its select, since focusing a select there opens the -system's own wheel or list over the sheet before anyone asked -(`SourceLanguagePicker.vue`). The phone's sheet is docked to the chat pane, -not the screen, and opening the sidebar closes it. +**The suggestion card.** Where a channel's recent lines from others are +mostly in one language that is not the user's -- at least +`SUGGEST_MIN_LINES` (5) of them and `SUGGEST_MIN_SHARE` (half) of every +placed line among the newest `RECENT_LINES` (`recent.ts` +`suggestedLanguage`) -- and the channel has no write target, the composer +offers it above the strip: "Most of #linux writes in Portuguese. Write +your messages in Portuguese here?", with the model and its download size +when that is still to come. **Write in Portuguese** sets the target with +the reading language as `writeFrom`, as a pick in the sheet would; **Not +in this channel** sets `channelStore.ts` `noSuggest`, and the card is not +offered there again. It needs reading on (the counts are the reader's +verdicts), a channel rather than a query, and a route for the pair. + +The sheet opens on a tap and nothing more: on a touch screen it focuses +itself rather than its search, since focusing the search there brings the +keyboard up over the list before anyone asked (on a desktop the search has +the keyboard, so typing a few letters and Return picks). The phone's sheet +is docked to the chat pane, not the screen, and opening the sidebar closes +it. The preview (or a Return that comes first) for a non-empty, non-command, non-edit draft (`outgoing.ts` `draftGate`) calls `writer.ts` `translateOutgoing` instead @@ -1512,21 +1526,43 @@ channel switched off, a language change, drop-behind), never un-pauses an engine, and counts toward no pause; a user's Retry asks in the normal shape again and gets its own bare retry. -**The composer's translate button is the send target's toggle.** The +**The composer's translate button opens the language sheet.** The language mark beside the paperclip and the plane (`ChatInput.vue` -`translateOnce`) is independent of the reading switch: off, a -click opens the language picker in its "translate into" form -(`SourceLanguagePicker.vue` `purpose="target"`), and the pick becomes the -channel's write target -- `channelStore.ts` `write`, the very setting the -panel's "Send my messages in" shows, so the two controls never disagree -- -until the button, lit and `aria-pressed` while a target is set, is clicked -again (the panel's Off does the same). A draft already typed gets its -strip at once. The dialog's **"This message only"** (offered when there is +`translateOnce`) is independent of the reading switch: a click opens +`ComposerLanguageSheet.vue`, and a pick in it becomes the channel's write +target -- `channelStore.ts` `write`, the very setting the panel's "Send my +messages in" shows, so the two controls never disagree -- lit on the +button, and named in its label ("Sending in German · change or turn off"), +while it is set, until the sheet's **Off** chip (or the panel's Off). The +same pair picked again changes nothing; a new one replaces the strip. The +sheet closes on a channel switch. A draft already typed gets its strip at +once. The list is plain buttons (the current one `aria-current`, a row +that cannot run labelled so, not disabled, since its tap gives the +reason); "This message only" sits above it, as a pick applies at once, +and Return in an empty search applies the highlighted language. + +The sheet is the pair on top -- **I write** ⇄ **They read**, two halves +of which the lit one is what the list below fills in, and a swap between +them -- then the languages the channel has been written in lately as +chips ("Spoken in #linux lately": `recent.ts` `recentLanguages`, the +reading pipeline's own verdicts on the newest `RECENT_LINES` lines from +others, so only with reading on), a search (by the interface's name or the +language's own, accents folded), and the whole list, each row the name in +the interface's language with the endonym beside it. A row under "They +read" says what picking it costs: **Corrects spelling** for the language +the user writes, the model's download size where its copy is not on this +device yet (`service.ts` `downloadBytes`, the route's own copy), and +**Not available here** -- the row dimmed, its tap leaving the sheet up +with the reason under the list -- where `pairVerdict` has no route. +Picking under "They read" applies and closes; picking under "I write" +names the source (or "Detect automatically") and moves on to "They read". +The sheet's **"This message only"** (offered when there is a draft) makes the pick a one-off instead: `writer.ts` `translateOnce`, `translateOutgoing` with its target named rather than read off the channel, so it runs with no write target set and is not invalidated by the panel's target moving; the strip comes up as it does for a write -target and the next Enter sends what it holds. The dialog preselects the +target and the next Enter sends what it holds. The sheet's "They read" +starts on the target that is set; with none it is preselected to the channel's last one-off language, else its write target (`lastOnceTarget`; the last pick is kept with the channel's record, `channelStore.ts` `once`, so it survives a reload and travels in the settings backup), else a @@ -1536,9 +1572,9 @@ each only where this device serves it from the reading language (`outgoing.ts` `onceTargetDefault`, the router's `pairVerdict`), so a device without the GPU model never opens on a disabled cleanup pass. When nothing routes at all the last pick, else the interface's language, stands -and the dialog says why. The dialog also asks what to translate **from**, -preselected to the language the user reads, with "Detect automatically" -as the write target has it. **The same language twice is a cleanup**: a +and the sheet says why. Its "I write" starts on the language the user +reads, or on the `writeFrom` kept with the target that is set ("Detect +automatically" where that is none). **The same language twice is a cleanup**: a draft already in the language it is asked into is not sent as typed the way a write target would (the user asked for something) but corrected in place -- `purpose: "polish"`, the LLM as a copy editor with a wording @@ -1547,13 +1583,11 @@ misspellings, grammar, punctuation **and a word that is the wrong word though it is spelled correctly** fixed, every other word kept, abbreviations and symbols included, nothing rephrased), the router sending a same-language request to the LLM alone (`router.ts`; no GPU model, no route, and the -dialog says so), and an echo read as "nothing to correct" rather than a -failure. The dialog says what it will do rather than what it is: with the -source and the target the same, its confirm button reads **Correct**, not -Translate -- and it is disabled where the cleanup cannot run at all (no -GPU model, the hint under the selects saying why), which is the state a -freshly opened dialog is in on a CPU-only device, both languages having -defaulted to the interface's. **A polish carries none of the channel**: its prompt is the +sheet says so), and an echo read as "nothing to correct" rather than a +failure. The sheet says what it will do rather than what it is: the +language the user writes reads **Corrects spelling** under "They read" -- +and **Not available here** where the cleanup cannot run at all (no GPU +model, the reason under the list on a tap). **A polish carries none of the channel**: its prompt is the copy editor's system message and `Correct: `, nothing above it, and `writer.ts` gives the request an empty context but for the register. Measured on the web build's 4B weights (2026-09-19, diff --git a/test/tests/screenOverlays.ts b/test/tests/screenOverlays.ts index 41a84c955..46604998b 100644 --- a/test/tests/screenOverlays.ts +++ b/test/tests/screenOverlays.ts @@ -27,6 +27,9 @@ describe("overlays carry .screen-overlay", function () { // has its own inset rule (`html[data-shell="native"] // .source-language-picker--sheet`). "SourceLanguagePicker.vue": "anchored popover, own sheet rule", + // The composer's language sheet: the same, its phone sheet's rule + // is `html[data-shell="native"] .language-sheet--phone`. + "ComposerLanguageSheet.vue": "anchored popover, own sheet rule", // The connect form is a page, not an overlay: its link-approval // dialog is the window itself, laid out inside #viewport's padding. "Windows/Connect.vue": "a page inside #viewport", diff --git a/test/translate/channelStore.ts b/test/translate/channelStore.ts index 9f0eac9f3..ca5797c57 100644 --- a/test/translate/channelStore.ts +++ b/test/translate/channelStore.ts @@ -79,9 +79,15 @@ describe("translate/channelStore", () => { languages: [], terms: [], once: null, + noSuggest: false, }); }); + it("a declined suggestion is kept as a strict boolean", () => { + expect(setChannelTranslation("n1", "#seance", {noSuggest: true}).noSuggest).to.equal(true); + expect(getChannelTranslation("n1", "#seance").noSuggest).to.equal(true); + }); + it("the channel's languages are kept, deduplicated, unknown codes dropped", () => { expect( setChannelTranslation("n1", "#seance", {languages: ["de", "en", "de", "xx", "nb"]}) diff --git a/test/translate/recent.ts b/test/translate/recent.ts new file mode 100644 index 000000000..9ab42e518 --- /dev/null +++ b/test/translate/recent.ts @@ -0,0 +1,85 @@ +import {expect} from "chai"; +import { + RECENT_LINES, + recentLanguages, + suggestedLanguage, + type RecentEntry, + type RecentMessage, +} from "../../client/js/translate/recent"; + +function channel(froms: (string | null)[], extra: Partial = {}) { + const messages: RecentMessage[] = []; + const entries: Record = {}; + + froms.forEach((from, i) => { + messages.push({id: i + 1, type: "message", ...extra}); + + if (from !== null) { + entries[i + 1] = {status: "done", from}; + } + }); + + return {messages, entries}; +} + +describe("translate/recent", () => { + it("counts others' placed lines, most first", () => { + const {messages, entries} = channel(["pt", "pt", "es", "", null, "pt"]); + + expect(recentLanguages(messages, entries)).to.deep.equal([ + {code: "pt", count: 3}, + {code: "es", count: 1}, + ]); + }); + + it("leaves out the user's own lines, events and dropped entries", () => { + const {messages, entries} = channel(["pt", "pt"]); + + messages.push({id: 10, type: "message", self: true}); + entries[10] = {status: "done", from: "de"}; + messages.push({id: 11, type: "join"}); + entries[11] = {status: "done", from: "de"}; + messages.push({id: 12, type: "action"}); + entries[12] = {status: "dropped", from: "de"}; + + expect(recentLanguages(messages, entries)).to.deep.equal([{code: "pt", count: 2}]); + }); + + it("looks only at the newest lines", () => { + const old = Array.from({length: 10}, () => "de"); + const recent = Array.from({length: RECENT_LINES}, () => "pt"); + const {messages, entries} = channel([...old, ...recent]); + + expect(recentLanguages(messages, entries)).to.deep.equal([ + {code: "pt", count: RECENT_LINES}, + ]); + }); + + it("suggests the clear majority that is not the reader's language", () => { + expect( + suggestedLanguage( + [ + {code: "pt", count: 6}, + {code: "en", count: 3}, + ], + "en" + ) + ).to.equal("pt"); + }); + + it("suggests nothing for the reader's own language, a quiet or a mixed channel", () => { + expect(suggestedLanguage([{code: "en", count: 9}], "en")).to.equal(null); + expect(suggestedLanguage([{code: "pt", count: 4}], "en")).to.equal(null); + expect( + suggestedLanguage( + [ + {code: "pt", count: 5}, + {code: "es", count: 4}, + {code: "de", count: 3}, + ], + "en" + ) + ).to.equal(null); + expect(suggestedLanguage([], "en")).to.equal(null); + }); +}); diff --git a/test/translate/service.ts b/test/translate/service.ts index 4c58a950f..4e80ff848 100644 --- a/test/translate/service.ts +++ b/test/translate/service.ts @@ -28,6 +28,7 @@ import { GPU_IDLE_UNLOAD_MS, TRANSLATION_UNAVAILABLE, TranslateService, + downloadBytes, loadNote, type CpuFallbackAnswer, type CpuFallbackInfo, @@ -506,6 +507,24 @@ describe("translate/service", () => { }); }); + it("downloadBytes is the route's copy size until that copy is cached", () => { + const route = {ref: catalog.nllb, device: "cpu" as const}; + const size = catalog.nllb.copies.cpu?.sizeBytes ?? 0; + + expect(size).to.be.above(0); + expect(downloadBytes(route, [])).to.equal(size); + expect(downloadBytes(route, [{ref: catalog.nllb, device: "cpu", cached: false}])).to.equal( + size + ); + expect(downloadBytes(route, [{ref: catalog.nllb, device: "cpu", cached: true}])).to.equal( + 0 + ); + // Another device's copy being cached says nothing about this one. + expect(downloadBytes(route, [{ref: catalog.nllb, device: "gpu", cached: true}])).to.equal( + size + ); + }); + it("marks a candidate down when its model fails to load and takes the next", async () => { const r = rig("gpu"); clock = r.clock; diff --git a/tools/scenarios/translate-capability.mjs b/tools/scenarios/translate-capability.mjs index 9199c8930..d2a60f86a 100644 --- a/tools/scenarios/translate-capability.mjs +++ b/tools/scenarios/translate-capability.mjs @@ -212,8 +212,7 @@ const SETTING = { }; const text = (selector) => `((document.querySelector(${JSON.stringify(selector)}) || {}).textContent || "").trim()`; -const PICKER = ".source-language-picker"; -const CONFIRM = `${PICKER} .source-language-picker-confirm`; +const PICKER = ".language-sheet"; /** A second user who says one line in the channel, for the toolbar. */ function speaker(nick, line) { @@ -381,33 +380,54 @@ export default async function run(page) { }); }; - const pickTarget = async (code) => { - await page.evaluate( - `(() => { const s = document.querySelector('${PICKER} select[name="translateInto"]'); s.value = ${JSON.stringify( - code - )}; s.dispatchEvent(new Event("change", {bubbles: true})); })()` - ); + // A row of the sheet's "They read" list, read without picking it: a + // row that runs would apply at once. A blocked one is tapped, which + // leaves the sheet up with the reason under the list. + const rowState = async (code) => { + const row = `${PICKER} .language-sheet-row[data-code="${code}"]`; + const state = await page.evaluate(`(() => { + const row = document.querySelector('${row}'); + return { + disabled: row.classList.contains("blocked"), + status: (row.querySelector(".language-sheet-status") || {}).textContent?.trim() || "", + }; + })()`); + + if (!state.disabled) { + return { + ...state, + lead: "", + why: "", + live: await page.evaluate( + `document.querySelector("${PICKER} .language-sheet-hint")?.getAttribute("aria-live") ?? null` + ), + }; + } + + await page.evaluate(`document.querySelector('${row}').scrollIntoView({block: "nearest"})`); + await page.click(row); await page.sleep(100); - }; - const pickerState = () => - page.evaluate(`(() => { - const confirm = document.querySelector(${JSON.stringify(CONFIRM)}); - const hint = document.querySelector("${PICKER} .source-language-picker-hint"); - const described = confirm.getAttribute("aria-describedby"); + const hint = await page.evaluate(`(() => { + const hint = document.querySelector("${PICKER} .language-sheet-hint"); return { - disabled: confirm.disabled, - label: confirm.textContent.trim(), - lead: ((hint && hint.querySelector(".source-language-picker-blocked")) || {}).textContent?.trim() || "", - why: ((hint && hint.querySelector(".source-language-picker-why")) || {}).textContent?.trim() || "", - hint: hint ? hint.textContent.trim() : "", - describedOk: !described || document.getElementById(described) === hint, + lead: ((hint && hint.querySelector(".language-sheet-blocked")) || {}).textContent?.trim() || "", + why: ((hint && hint.querySelector(".language-sheet-why")) || {}).textContent?.trim() || "", live: hint ? hint.getAttribute("aria-live") : null, }; })()`); + return {...state, ...hint}; + }; + const openedOnCode = () => + page.evaluate( + `document.querySelector('${PICKER} .language-sheet-side[data-side="to"]').dataset.code` + ); + const closePicker = async () => { - await page.click(`${PICKER} .source-language-picker-cancel`); + await page.evaluate( + `document.querySelector(".language-sheet").dispatchEvent(new KeyboardEvent("keydown", {key: "Escape", bubbles: true}))` + ); await page.waitFor(`!document.querySelector(${JSON.stringify(PICKER)})`, { label: "dialog closed", }); @@ -703,10 +723,8 @@ export default async function run(page) { await openPicker(); const llm = device.runs[SMALL]; - const opened = await pickerState(); - const openedOn = await page.evaluate( - `document.querySelector('${PICKER} select[name="translateInto"]').value` - ); + const openedOn = await openedOnCode(); + const opened = await rowState(openedOn); // No earlier pick: the cleanup pass where the GPU model runs, else // the first language this device serves -- never a disabled dialog. @@ -717,33 +735,28 @@ export default async function run(page) { !opened.disabled && (llm ? openedOn === "en" : openedOn !== "en") ); await page.screenshot(`${name}-composer-opened`, {selector: PICKER, pad: 8}); - await pickTarget("en"); + const same = await rowState("en"); - const same = await pickerState(); - - page.check(`${name}: the dialog's hint is a polite live region`, same.live === "polite"); + page.check(`${name}: the sheet's hint is a polite live region`, same.live === "polite"); if (llm) { page.check( - `${name}: the cleanup pass is on offer ("${same.label}")`, - !same.disabled && - same.label === EN["translate.picker.targetConfirmCorrect"] && - same.hint === EN["translate.picker.sameLanguage"] + `${name}: the cleanup pass is on offer ("${same.status}")`, + !same.disabled && same.status === EN["translate.sheet.corrects"] ); } else { page.check( `${name}: the cleanup pass is disabled with its reason (${same.lead} / ${same.why})`, same.disabled && + same.status === EN["translate.sheet.unavailable"] && same.lead === EN["translate.picker.sameLanguageNeedsGpu"] && - same.why === device.why(SMALL) && - same.describedOk + same.why === device.why(SMALL) ); } await page.screenshot(`${name}-composer-same`, {selector: PICKER, pad: 8}); - await pickTarget("de"); - const german = await pickerState(); + const german = await rowState("de"); page.check( `${name}: German is on offer (CPU models serve it)`, @@ -856,9 +869,8 @@ export default async function run(page) { .replace("{to}", "Japanese"); await openPicker(); - await pickTarget("ja"); - const off = await pickerState(); + const off = await rowState("ja"); page.check( `noWebgpu, NLLB off: Japanese is disabled, NLLB named (${off.lead} / ${off.why})`, @@ -884,9 +896,8 @@ export default async function run(page) { ); await closeSettings(); await openPicker(); - await pickTarget("ja"); - const on = await pickerState(); + const on = await rowState("ja"); page.check( `noWebgpu, NLLB on: Japanese is on offer (${on.lead || "enabled"})`, @@ -954,18 +965,15 @@ export default async function run(page) { await closeSettings(); await openPicker(); - // The GPU model off: the dialog no longer opens on the cleanup pass. - const openedOff = await page.evaluate( - `document.querySelector('${PICKER} select[name="translateInto"]').value` - ); + // The GPU model off: the sheet no longer opens on the cleanup pass. + const openedOff = await openedOnCode(); page.check( - `desktop, GPU off: the dialog opens on a pair that runs (${openedOff})`, - openedOff !== "en" && !(await pickerState()).disabled + `desktop, GPU off: the sheet opens on a pair that runs (${openedOff})`, + openedOff !== "en" && !(await rowState(openedOff)).disabled ); - await pickTarget("en"); - const polish = await pickerState(); + const polish = await rowState("en"); page.check( `desktop, GPU off: the cleanup pass names the switch (${polish.why})`, @@ -1081,9 +1089,8 @@ export default async function run(page) { await closePanel(); await openPicker(); - await pickTarget("de"); - const blocked = await pickerState(); + const blocked = await rowState("de"); page.check( `desktop, both off: German in the dialog is disabled with the reason (${blocked.lead} / ${blocked.why})`, diff --git a/tools/scenarios/translate-composer.mjs b/tools/scenarios/translate-composer.mjs index dc65c7dc6..28780bafa 100644 --- a/tools/scenarios/translate-composer.mjs +++ b/tools/scenarios/translate-composer.mjs @@ -404,14 +404,29 @@ export default async function run(page) { // target and Enter sends the translation. The button is dead on an // empty draft, and the dialog remembers the language for the next pick. const ONCE = "#form #translate-once"; - const ONCE_SELECT = '.source-language-picker select[name="translateInto"]'; + const SHEET = ".language-sheet"; + const sideValue = (n) => + `((document.querySelectorAll("${SHEET} .language-sheet-side-value")[${n}] || {}).textContent || "").trim()`; + const row = (code) => `${SHEET} .language-sheet-row[data-code="${code}"]`; + // A row may sit under the list's own scroll: brought into it first, as + // a finger would scroll to it. + const pick = async (code) => { + await page.evaluate( + `document.querySelector('${row(code)}').scrollIntoView({block: "nearest"})` + ); + await page.click(row(code)); + }; + const openSheet = async (label) => { + await page.click(ONCE); + await page.waitFor(`!!document.querySelector(${JSON.stringify(SHEET)})`, {label}); + }; const onceDraft = `could someone translate just this one line ${RUN}`; await page.waitFor(`!!document.querySelector(${JSON.stringify(ONCE)})`, { label: "the composer's translate button", }); - // The button is the send target's toggle, live with or without a - // draft; with one, the dialog offers "This message only". + // The button opens the language sheet, live with or without a draft; + // with one, the sheet offers "This message only". await page.check( "the translate button is live on an empty draft", !(await page.evaluate(`document.querySelector(${JSON.stringify(ONCE)}).disabled`)) @@ -419,58 +434,68 @@ export default async function run(page) { await page.check( "the translate button is off with no send target", (await page.evaluate( - `document.querySelector(${JSON.stringify(ONCE)}).getAttribute("aria-pressed")` - )) === "false" + `document.querySelector(${JSON.stringify(ONCE)}).classList.contains("on")` + )) === false ); await page.fill(INPUT, onceDraft); - await page.click(ONCE); - await page.waitFor(`!!document.querySelector(${JSON.stringify(ONCE_SELECT)})`, { - label: "the translate-into dialog", - }); - // It opens on a tap and nothing more: on a touch screen no select is - // focused, which would put the system's own list over the sheet unasked. + await openSheet("the language sheet"); + // It opens on a tap and nothing more: on a touch screen the search is + // not focused, which would put the keyboard over the list unasked. if (MOBILE) { await page.check( - "the dialog opens without a language list over it", - (await page.evaluate(`document.activeElement?.tagName`)) !== "SELECT" + "the sheet opens without the keyboard", + (await page.evaluate(`document.activeElement?.tagName`)) !== "INPUT" + ); + } else { + await page.check( + "the sheet's search has the keyboard on a desktop", + await page.evaluate( + `document.activeElement?.classList.contains("language-sheet-search")` + ) ); } - // Nothing picked on this channel yet: the target starts as the language - // the interface is in, not as whatever sorts first in the list. + // Nothing picked on this channel yet: "They read" starts as the + // language the interface is in, "I write" as the same. await page.check( - "the dialog starts on the interface's own language", - (await page.evaluate(`document.querySelector(${JSON.stringify(ONCE_SELECT)}).value`)) === - "en" + "the sheet starts on the interface's own language", + (await page.evaluate(sideValue(1))) === "English" && + (await page.evaluate(sideValue(0))) === "English" ); await page.check( - "the dialog's languages are named in the interface language", + "the sheet's languages are named in the interface language, with their own name beside", await page.evaluate( `(() => { - const s = document.querySelector(${JSON.stringify(ONCE_SELECT)}); - const text = (v) => s.querySelector('option[value="' + v + '"]').textContent.trim(); - // Not the endonym: an English interface offers "French" - // (SourceLanguagePicker.vue name()). + const name = (v) => document.querySelector('${SHEET} .language-sheet-row[data-code="' + v + '"] .language-sheet-name').textContent.trim(); + const endonym = (v) => (document.querySelector('${SHEET} .language-sheet-row[data-code="' + v + '"] .language-sheet-endonym') || {}).textContent?.trim(); const names = new Intl.DisplayNames( [document.documentElement.lang || "en"], {type: "language"} ); - return text("de") === names.of("de") && text("fr") === names.of("fr"); + return name("de") === names.of("de") && name("fr") === names.of("fr") && endonym("de") === "Deutsch"; })()` ) ); - await page.evaluate( - `(() => { const s = document.querySelector(${JSON.stringify( - ONCE_SELECT - )}); s.value = "es"; s.dispatchEvent(new Event("change", {bubbles: true})); })()` + // The search narrows the list, by either name. + await page.fill(`${SHEET} .language-sheet-search`, "espa"); + await page.check( + "searching by a language's own name finds it", + await page.evaluate( + `(() => { const rows = [...document.querySelectorAll("${SHEET} .language-sheet-row[data-code]")]; return rows.length === 1 && rows[0].dataset.code === "es"; })()` + ) ); - await page.click(".source-language-picker-once-box"); - await page.click(".source-language-picker-confirm"); + await page.fill(`${SHEET} .language-sheet-search`, ""); + await page.click(".language-sheet-once-box"); + await pick("es"); await page.waitFor( `(document.querySelector(${JSON.stringify( BAR_TEXT )}) || {}).textContent === ${JSON.stringify(`[Spanish] ${onceDraft}`)}`, {timeout: 30000, label: "the one-off translation is in the strip"} ); + await page.check( + "picking a language applied it and put the sheet away", + (await page.count(SHEET)) === 0 + ); await page.check( "the strip's chip names the picked language", String( @@ -480,8 +505,8 @@ export default async function run(page) { await page.check( "the channel still has no write target", (await page.evaluate( - `document.querySelector("#form #input").getAttribute("placeholder")` - )) === `Write to ${CHANNEL}` + `document.querySelector(${JSON.stringify(ONCE)}).classList.contains("on")` + )) === false ); // The read-back under the strip runs first; a send waits for it. await page.waitFor( @@ -499,45 +524,28 @@ export default async function run(page) { label: "the strip is gone after the send", }); - // The same language twice is a cleanup pass: the fake "corrects" the - // line, the chip says so, and Enter sends the corrected line. - const ONCE_FROM = '.source-language-picker select[name="translateFrom"]'; + // The same language twice is a cleanup pass: the row says so, the fake + // "corrects" the line, the chip says so, and Enter sends it. const polishDraft = `pls fix my speling here ${RUN}`; await page.fill(INPUT, polishDraft); - await page.click(ONCE); - await page.waitFor(`!!document.querySelector(${JSON.stringify(ONCE_SELECT)})`, { - label: "the dialog for the cleanup", - }); + await openSheet("the sheet for the cleanup"); // The channel's last one-off, not the interface's language: a // remembered pick still outranks the default (`lastOnceTarget`). await page.check( - "the dialog remembers Spanish, the one-off picked before it", - (await page.evaluate(`document.querySelector(${JSON.stringify(ONCE_SELECT)}).value`)) === - "es" + "the sheet remembers Spanish, the one-off picked before it", + (await page.evaluate(sideValue(1))) === "Spanish" ); await page.check( - "the dialog's source starts as the language the page reads", - (await page.evaluate(`document.querySelector(${JSON.stringify(ONCE_FROM)}).value`)) === "en" - ); - await page.evaluate( - `(() => { const s = document.querySelector(${JSON.stringify( - ONCE_SELECT - )}); s.value = "en"; s.dispatchEvent(new Event("change", {bubbles: true})); })()` - ); - await page.waitFor(`!!document.querySelector(".source-language-picker-hint")`, { - label: "the same-language hint", - }); - // The same language twice corrects rather than translates, and the - // button says which of the two it is. - await page.check( - "the confirm button says Correct for a same-language pick", + "the language the user writes reads 'Corrects spelling'", (await page.evaluate( - `document.querySelector(".source-language-picker-confirm").textContent.trim()` - )) === "Correct" + `(document.querySelector('${row( + "en" + )} .language-sheet-status') || {}).textContent?.trim()` + )) === "Corrects spelling" ); - await page.click(".source-language-picker-once-box"); - await page.click(".source-language-picker-confirm"); + await page.click(".language-sheet-once-box"); + await pick("en"); await page.waitFor( `(document.querySelector(${JSON.stringify( BAR_TEXT @@ -563,28 +571,22 @@ export default async function run(page) { label: "the strip is gone after the corrected send", }); - // The button as a toggle: with no draft, a pick without "This message - // only" becomes the channel's send target -- the button lit, the - // placeholder saying so -- until the button is clicked again. + // With no draft, a pick becomes the channel's send target -- the + // button lit -- until the sheet's Off. await page.fill(INPUT, ""); - await page.click(ONCE); - await page.waitFor(`!!document.querySelector(${JSON.stringify(ONCE_SELECT)})`, { - label: "the dialog on an empty draft", - }); + await openSheet("the sheet on an empty draft"); await page.check( "no one-off box without a draft", - (await page.count(".source-language-picker-once-box")) === 0 + (await page.count(".language-sheet-once-box")) === 0 ); - await page.evaluate( - `(() => { const s = document.querySelector(${JSON.stringify( - ONCE_SELECT - )}); s.value = "de"; s.dispatchEvent(new Event("change", {bubbles: true})); })()` - ); - await page.click(".source-language-picker-confirm"); - await page.waitFor( - `document.querySelector(${JSON.stringify(ONCE)}).getAttribute("aria-pressed") === "true"`, - {label: "the button is on"} + await page.check( + "no Off chip with nothing to turn off", + (await page.count(".language-sheet-off")) === 0 ); + await pick("de"); + await page.waitFor(`document.querySelector(${JSON.stringify(ONCE)}).classList.contains("on")`, { + label: "the button is on", + }); await page.check( "the button names the send target, EN → DE", (await page.evaluate(buttonTarget)) === "EN → DE" @@ -634,47 +636,60 @@ export default async function run(page) { await page.waitFor(`!document.querySelector(${JSON.stringify(BAR)})`, { label: "the strip is gone after the one-step send", }); - await page.click(ONCE); - await page.waitFor( - `document.querySelector(${JSON.stringify(ONCE)}).getAttribute("aria-pressed") === "false"`, - {label: "the button is off again"} - ); + // With the target set, the sheet opens on it, checked, and offers Off. + await openSheet("the sheet with a target set"); await page.check( - "the placeholder is plain again", - (await page.evaluate( - `document.querySelector("#form #input").getAttribute("placeholder")` - )) === `Write to ${CHANNEL}` + "the sheet opens on the target that is set", + (await page.evaluate(sideValue(1))) === "German" && + (await page.evaluate( + `document.querySelector('${row("de")}').getAttribute("aria-current")` + )) === "true" + ); + await page.screenshot("translate-sheet", {selector: SHEET}); + await page.click(".language-sheet-off"); + await page.waitFor( + `!document.querySelector(${JSON.stringify(ONCE)}).classList.contains("on")`, + {label: "Off turned the button off"} ); // The next pick starts from the language picked last time. await page.fill(INPUT, `and this one too ${RUN}`); - await page.click(ONCE); - await page.waitFor(`!!document.querySelector(${JSON.stringify(ONCE_SELECT)})`, { - label: "the dialog again", - }); + await openSheet("the sheet again"); + await page.check( + "the sheet remembers English, the last one-off", + (await page.evaluate(sideValue(1))) === "English" + ); + // "I write" first: its pick names the source and moves on to "They read". + await page.click(`${SHEET} .language-sheet-side:first-child`); + await pick("fr"); await page.check( - "the dialog remembers English, the last pick", - (await page.evaluate(`document.querySelector(${JSON.stringify(ONCE_SELECT)}).value`)) === - "en" + "picking in 'I write' sets it and moves to 'They read'", + (await page.evaluate(sideValue(0))) === "French" && + (await page.evaluate( + `document.querySelectorAll("${SHEET} .language-sheet-side")[1].classList.contains("active")` + )) && + (await page.count(SHEET)) === 1 ); await page.screenshot("translate-once", {selector: "#form"}); // On a phone the sidebar opening (a swipe, the menu button) puts the - // dialog away rather than leave it over the channel list; on a desktop - // the sidebar is open beside it all along, so Cancel does. + // sheet away rather than leave it over the channel list; on a desktop + // the sidebar is open beside it all along, so Escape does. if (MOBILE) { await page.evaluate( `document.getElementById("app").__vue_app__.config.globalProperties.$store.commit("sidebarOpen", true)` ); - await page.waitFor(`!document.querySelector(${JSON.stringify(ONCE_SELECT)})`, { - label: "opening the sidebar closed the dialog", + await page.waitFor(`!document.querySelector(${JSON.stringify(SHEET)})`, { + label: "opening the sidebar closed the sheet", }); await page.evaluate( `document.getElementById("app").__vue_app__.config.globalProperties.$store.commit("sidebarOpen", false)` ); } else { - await page.click(".source-language-picker-cancel"); - await page.waitFor(`!document.querySelector(${JSON.stringify(ONCE_SELECT)})`, { - label: "the dialog closed", + await page.evaluate( + `document.querySelector(".language-sheet").dispatchEvent(new KeyboardEvent("keydown", {key: "Escape", bubbles: true}))` + ); + await page.waitFor(`!document.querySelector(${JSON.stringify(SHEET)})`, { + label: "Escape closed the sheet", }); } await page.fill(INPUT, ""); diff --git a/tools/scenarios/translate-off.mjs b/tools/scenarios/translate-off.mjs index f760c4727..fd069f16a 100644 --- a/tools/scenarios/translate-off.mjs +++ b/tools/scenarios/translate-off.mjs @@ -555,14 +555,8 @@ export default async function run(page) { label: "the composer's translate button is back", }); await page.fill("#form #input", "one more"); - // Lit for the kept write target, the button's first click turns it off; - // the next opens the picker. await page.click("#translate-once"); - await page.waitFor(`!document.querySelector("#translate-once.on")`, { - label: "the write target off", - }); - await page.click("#translate-once"); - await page.waitFor(`!!document.querySelector(".source-language-picker")`, { + await page.waitFor(`!!document.querySelector(".language-sheet")`, { label: "the composer's language picker", }); await openTab(page); @@ -574,6 +568,6 @@ export default async function run(page) { }); page.check( "on again: the picker left open at the switch-off stays closed", - !(await page.evaluate(`!!document.querySelector(".source-language-picker")`)) + !(await page.evaluate(`!!document.querySelector(".language-sheet")`)) ); } diff --git a/tools/scenarios/translate-reading.mjs b/tools/scenarios/translate-reading.mjs index adaeab1c9..898499cd5 100644 --- a/tools/scenarios/translate-reading.mjs +++ b/tools/scenarios/translate-reading.mjs @@ -976,6 +976,40 @@ async function scenario(page) { ); } + // The channel now mostly writes German: the composer offers to write in + // it (the suggestion card), and the language sheet lists it under + // "Spoken in … lately". Declined, the card stays gone for the channel. + await page.waitFor(`!!document.querySelector("#form .translate-suggest")`, { + label: "the suggestion card", + }); + await page.check( + "the suggestion card offers German", + /German/.test( + await page.evaluate( + `document.querySelector("#form .translate-suggest-accept").textContent` + ) + ) + ); + await page.screenshot("translate-suggest", {selector: "#form"}); + await page.click("#form #translate-once"); + await page.waitFor(`!!document.querySelector(".language-sheet")`, { + label: "the language sheet", + }); + await page.check( + "the sheet lists German as spoken here lately", + await page.evaluate( + `[...document.querySelectorAll(".language-sheet-chip")].some((c) => c.textContent.trim() === "German")` + ) + ); + await page.evaluate( + `document.querySelector(".language-sheet").dispatchEvent(new KeyboardEvent("keydown", {key: "Escape", bubbles: true}))` + ); + await page.waitFor(`!document.querySelector(".language-sheet")`, {label: "the sheet closed"}); + await page.click("#form .translate-suggest-decline"); + await page.waitFor(`!document.querySelector("#form .translate-suggest")`, { + label: "the declined card is gone", + }); + // The chip's menu: copy one translation, hide one translation. await page.evaluate( `document.querySelector(${JSON.stringify(CHIP)}).scrollIntoView({block: "center"})` From 88074b59f5c7440bcde8791ad0f00f4846cabe6c Mon Sep 17 00:00:00 2001 From: Richard Drake Date: Sat, 7 Nov 2026 22:36:44 +0000 Subject: [PATCH 06/16] Composer translation: a tidier language sheet A header with the title, Turn off and a close button. Languages nothing here can translate into go last under one heading instead of a label on every row, and the pair and rows are slimmer. --- client/components/ComposerLanguageSheet.vue | 63 ++++++++++++++---- client/css/style.css | 74 ++++++++++++++++++--- client/js/i18n/call-sites.ts | 4 +- client/locales/ar.po | 23 +++++-- client/locales/cs.po | 23 +++++-- client/locales/da.po | 23 +++++-- client/locales/de.po | 23 +++++-- client/locales/en.json | 6 +- client/locales/es.po | 23 +++++-- client/locales/fil.po | 23 +++++-- client/locales/fr.po | 23 +++++-- client/locales/hu.po | 23 +++++-- client/locales/it.po | 23 +++++-- client/locales/ja.po | 23 +++++-- client/locales/ko.po | 23 +++++-- client/locales/messages.pot | 22 ++++-- client/locales/nl.po | 23 +++++-- client/locales/pl.po | 23 +++++-- client/locales/pt.po | 23 +++++-- client/locales/qqx.json | 6 +- client/locales/ro.po | 23 +++++-- client/locales/ru.po | 23 +++++-- client/locales/sk.po | 23 +++++-- client/locales/sv.po | 23 +++++-- client/locales/th.po | 23 +++++-- client/locales/tr.po | 23 +++++-- client/locales/uk.po | 23 +++++-- client/locales/vi.po | 23 +++++-- client/locales/zh.po | 23 +++++-- docs/resources/translation.md | 13 ++-- tools/scenarios/translate-capability.mjs | 7 +- 31 files changed, 571 insertions(+), 153 deletions(-) diff --git a/client/components/ComposerLanguageSheet.vue b/client/components/ComposerLanguageSheet.vue index bfa634a86..b76d6caff 100644 --- a/client/components/ComposerLanguageSheet.vue +++ b/client/components/ComposerLanguageSheet.vue @@ -12,6 +12,21 @@ :aria-label="t('translate.sheet.dialog')" @keydown.esc.stop.prevent="close" > +
+ {{ t("translate.sheet.title") }} + + +
-
- {{ +
+ {{ t("translate.sheet.recent", {channel: channel.name}) }}
@@ -70,14 +85,6 @@ > {{ name(code) }} -
@@ -110,7 +117,34 @@ > -
  • +
  • + +
  • + +
  • + {{ t("translate.sheet.unavailableHeading") }} +
  • +