release: ship v1.6.0 with paged suggestions and a cleaner phrase memory
Next-sentence suggestions now arrive one at a time up to twelve, shown three per page with Ctrl+Alt+Up/Down to move, Left/Right to page, Enter to accept and Esc to close; old default bindings migrate and the panel guide follows the live bindings. The overlay is redesigned, stays put while candidates stream and sits outside the input box when no caret is reported. The personal phrase memory stops learning from terminals, code editors and the coding-agent hub, ignores symbol-heavy lines and empty-field placeholders, and prunes existing entries that break those rules. Fixes suggestion keys starting dictation, installs stuck on a pre-1.5.0 speech engine without the focus endpoint, Ollama runner windows flashing while typing, the speech engine starting twice, and cold-model timeouts. Live captions can be dragged to a remembered position and show a waiting notice until the first line arrives. Bumps the product version to 1.6.0 (Android/iOS build 1060000).
This commit is contained in:
parent
856e375f3e
commit
2fe20fa7b5
71 changed files with 2469 additions and 366 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@d3ro/api-client",
|
||||
"version": "1.5.0",
|
||||
"version": "1.6.0",
|
||||
"private": true,
|
||||
"description": "D3RO Voice API 클라이언트 — Supabase 래퍼 (web/desktop/mobile 공유)",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@d3ro/core",
|
||||
"version": "1.5.0",
|
||||
"version": "1.6.0",
|
||||
"private": true,
|
||||
"description": "D3RO Voice 공유 비즈니스 로직 — 타입, 에러, IPC 채널, 상수, 유틸",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,16 @@ export interface UiRect {
|
|||
height: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 앵커 rect 가 케어렛(한 줄 위치)인지 포커스 요소 전체인지.
|
||||
*
|
||||
* 케어렛을 못 주는 제공자가 많아(Chrome, Windows Terminal 등 caret=-1) 그때는
|
||||
* elementRect 로 폴백하는데, 두 경우는 배치 전략이 달라야 한다 — 케어렛은 "그
|
||||
* 줄 아래" 에 붙이면 되지만, elementRect(멀티라인/큰 입력창 전체)는 그 규칙을
|
||||
* 그대로 쓰면 텍스트와 멀리 떨어지거나 텍스트 위에 겹친다.
|
||||
*/
|
||||
export type AnchorKind = 'caret' | 'element' | null
|
||||
|
||||
/** 마우스 이동 누적 전에 무시할 최소 이동량(px). 미세抖动 노이즈 제거. */
|
||||
export const MOUSE_MOVE_NOISE_FLOOR_PX = 2
|
||||
|
||||
|
|
@ -37,16 +47,24 @@ export function manhattanDistance(from: { x: number; y: number }, to: { x: numbe
|
|||
/**
|
||||
* 오버레이 커 좌표 계산.
|
||||
*
|
||||
* 앵커(케어렛/포커스 요소) 아래에 붙이는 것이 기본 — IME 후보창과 같은 관례다.
|
||||
* 아래 공간이 없으면 위로 뒤집고, 마지막으로 작업영역 안으로 클램프한다.
|
||||
* 케어렛 앵커(또는 앵커 없음 → 커서)는 앵커 아래에 붙이는 것이 기본 — IME 후보창과
|
||||
* 같은 관례다. 아래 공간이 없으면 위로 뒤집는다.
|
||||
* 요소 앵커(케어렛을 못 얻어 elementRect 로 폴백한 경우)는 요소 "바깥" 에 붙인다 —
|
||||
* 아래/위/오른쪽/왼쪽 순으로 작업영역에 들어맞는 첫 방향을 쓴다.
|
||||
* 마지막으로 항상 작업영역 안으로 클램프한다.
|
||||
*/
|
||||
export function anchorFloatingPanel(
|
||||
anchor: UiRect | null,
|
||||
anchorKind: AnchorKind,
|
||||
cursor: { x: number; y: number },
|
||||
size: { width: number; height: number },
|
||||
workArea: UiRect,
|
||||
gap = 6
|
||||
): { x: number; y: number } {
|
||||
if (anchorKind === 'element' && anchor && anchor.width >= 0 && anchor.height >= 0) {
|
||||
return anchorOutsideElement(anchor, size, workArea, gap)
|
||||
}
|
||||
|
||||
const rect: UiRect =
|
||||
anchor && anchor.width >= 0 && anchor.height >= 0
|
||||
? anchor
|
||||
|
|
@ -64,6 +82,56 @@ export function anchorFloatingPanel(
|
|||
}
|
||||
}
|
||||
|
||||
function fitsInWorkArea(
|
||||
x: number,
|
||||
y: number,
|
||||
size: { width: number; height: number },
|
||||
workArea: UiRect
|
||||
): boolean {
|
||||
return (
|
||||
x >= workArea.x &&
|
||||
y >= workArea.y &&
|
||||
x + size.width <= workArea.x + workArea.width &&
|
||||
y + size.height <= workArea.y + workArea.height
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 요소 앵커를 요소 "바깥" 에 배치한다.
|
||||
*
|
||||
* 케어렛을 못 얻어 elementRect(입력창 전체)로 폴백했을 때, 기존 "앵커 아래/위" 규칙을
|
||||
* 그대로 쓰면 큰/여러 줄 요소에서는 텍스트와 멀리 떨어지거나 텍스트 위에 겹친다
|
||||
* (실측: 2·3번째 생성에서 패널이 튐). 아래→위→오른쪽→왼쪽 순으로 작업영역에
|
||||
* 맞는 첫 방향을 쓰고, 전부 안 맞으면 요소 안쪽 우하단 모서리로 물러난다.
|
||||
*/
|
||||
function anchorOutsideElement(
|
||||
element: UiRect,
|
||||
size: { width: number; height: number },
|
||||
workArea: UiRect,
|
||||
gap: number
|
||||
): { x: number; y: number } {
|
||||
const candidates: Array<{ x: number; y: number }> = [
|
||||
{ x: element.x, y: element.y + element.height + gap }, // 아래
|
||||
{ x: element.x, y: element.y - gap - size.height }, // 위
|
||||
{ x: element.x + element.width + gap, y: element.y }, // 오른쪽
|
||||
{ x: element.x - gap - size.width, y: element.y } // 왼쪽
|
||||
]
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (fitsInWorkArea(candidate.x, candidate.y, size, workArea)) return candidate
|
||||
}
|
||||
|
||||
const fallback = {
|
||||
x: element.x + element.width - size.width - gap,
|
||||
y: element.y + element.height - size.height - gap
|
||||
}
|
||||
|
||||
return {
|
||||
x: clamp(fallback.x, workArea.x, workArea.x + workArea.width - size.width),
|
||||
y: clamp(fallback.y, workArea.y, workArea.y + workArea.height - size.height)
|
||||
}
|
||||
}
|
||||
|
||||
function clamp(value: number, min: number, max: number): number {
|
||||
if (max < min) return min
|
||||
return Math.max(min, Math.min(value, max))
|
||||
|
|
@ -401,6 +469,8 @@ export type SuggestionSkipReason =
|
|||
| 'unchanged'
|
||||
/** 연속 실패 후 쿨다운 중 (모델이 다른 작업으로 바쁠 수 있다) */
|
||||
| 'cooldown'
|
||||
/** 포커스만 옮겨 왔을 뿐 이 필드에서 실제로 타이핑하지 않았다 (마우스 클릭 등) */
|
||||
| 'not-typing'
|
||||
|
||||
export type SuggestionDecision =
|
||||
| { action: 'request'; prefix: string }
|
||||
|
|
@ -419,6 +489,10 @@ export interface SuggestionPolicyInput {
|
|||
isEditable: boolean
|
||||
appName: string | null
|
||||
excludedApps: readonly string[]
|
||||
/** 포커스가 바뀐 뒤 이 필드에서 실제로 편집이 있었는가 (마우스로 필드에 들어오기만 한 경우 false) */
|
||||
editedSinceFocus: boolean
|
||||
/** 최근에 실제로 타이핑했는가 (recentTypingWindowMs 이내) */
|
||||
typedRecently: boolean
|
||||
/** 어렛 앞 스트 */
|
||||
prefix: string
|
||||
idleMs: number
|
||||
|
|
@ -446,6 +520,14 @@ export function decideSuggestion(input: SuggestionPolicyInput): SuggestionDecisi
|
|||
if (input.appName && isAppExcluded(input.appName, input.excludedApps)) {
|
||||
return { action: 'clear', reason: 'excluded-app' }
|
||||
}
|
||||
// 포커스만 옮겨 왔을 뿐(마우스 클릭 등) 이 필드에서 아무것도 치지 않았으면 제안하지 않는다.
|
||||
//
|
||||
// 유휴 판정이 키보드 기준이라, 필드에 이미 차 있던 텍스트로 클릭만 해도 (마지막
|
||||
// 키 입력이 오래전이라) idleMs 조건을 통과해 제안이 뜨던 문제(실측: YouTube 검색창
|
||||
// 클릭만 했는데 옛 검색어로 제안이 뜸).
|
||||
if (!input.editedSinceFocus || !input.typedRecently) {
|
||||
return { action: 'clear', reason: 'not-typing' }
|
||||
}
|
||||
|
||||
const prefix = input.prefix.replace(/\s+$/u, '')
|
||||
if (!prefix) return { action: 'clear', reason: 'empty-prefix' }
|
||||
|
|
@ -481,6 +563,34 @@ export function decideSuggestion(input: SuggestionPolicyInput): SuggestionDecisi
|
|||
return { action: 'request', prefix }
|
||||
}
|
||||
|
||||
/**
|
||||
* 생성 접두가 현재 접두의 연속 확장인지 판정한다.
|
||||
*
|
||||
* 마지막 글자는 아직 조합 중인 IME 음절일 수 있어, 그 한 글자를 뺀 접두까지도
|
||||
* 연속 확장으로 인정한다 (예: 생성 시점 "하" → 현재 "한").
|
||||
*/
|
||||
export function extendsPrefix(generatedPrefix: string, currentPrefix: string): boolean {
|
||||
const generated = generatedPrefix.replace(/\s+$/u, '')
|
||||
const current = currentPrefix.replace(/\s+$/u, '')
|
||||
if (current.startsWith(generated)) return true
|
||||
return generated.length > 0 && current.startsWith(generated.slice(0, -1))
|
||||
}
|
||||
|
||||
/**
|
||||
* 표시 중인 제안 세션(페이지 넘기며 보는 후보 목록)이 여전히 이 접두에 유효한지.
|
||||
*
|
||||
* `extendsPrefix` 와 달리 성장(이어 치기)은 허용하지 않는다 — "다음 문장이 시작됐다"
|
||||
* 는 곧 세션 종료다(설계). 마지막 글자만 IME 조합으로 바뀐 경우만 예외로 둔다
|
||||
* (생성 시점 "하" → 지금 "한": 길이는 같고 마지막 글자만 다르다).
|
||||
*/
|
||||
export function matchesSessionPrefix(generatedPrefix: string, currentPrefix: string): boolean {
|
||||
const generated = generatedPrefix.replace(/\s+$/u, '')
|
||||
const current = currentPrefix.replace(/\s+$/u, '')
|
||||
if (current === generated) return true
|
||||
if (generated.length === 0) return false
|
||||
return current.length === generated.length && current.slice(0, -1) === generated.slice(0, -1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 표시 중인 제안을 계속 둘지, 새로 만들지, 즉시 버릴지 결정한다.
|
||||
*
|
||||
|
|
@ -496,7 +606,7 @@ export function decideSuggestionRefresh(
|
|||
const generated = generatedPrefix.replace(/\s+$/u, '')
|
||||
const current = currentPrefix.replace(/\s+$/u, '')
|
||||
if (!generated) return 'regenerate'
|
||||
if (!current.startsWith(generated)) return 'stale'
|
||||
if (!extendsPrefix(generated, current)) return 'stale'
|
||||
return current.length - generated.length >= minimumGrowth ? 'regenerate' : 'keep'
|
||||
}
|
||||
|
||||
|
|
@ -513,6 +623,84 @@ export function isAppExcluded(appName: string, excludedApps: readonly string[]):
|
|||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 터미널 — 화면 버퍼가 곧 "입력창" 으로 읽혀 상태줄·명령·출력이 친 글로 잡힌다
|
||||
* (실측: Claude Code 상태줄 `◑ OPUS 5`, `5 medium │ CTX ▕░░▏` 가 학습됨).
|
||||
* 제안도 셸 프롬프트 위에 뜨므로 여기서는 제안과 학습을 모두 하지 않는다.
|
||||
*/
|
||||
export const TERMINAL_APPS: readonly string[] = Object.freeze([
|
||||
'WindowsTerminal',
|
||||
'wt',
|
||||
'OpenConsole',
|
||||
'conhost',
|
||||
'cmd',
|
||||
'powershell',
|
||||
'pwsh',
|
||||
'mintty',
|
||||
'alacritty',
|
||||
'wezterm-gui',
|
||||
'Hyper',
|
||||
'Tabby',
|
||||
'Warp'
|
||||
])
|
||||
|
||||
/**
|
||||
* 학습에서 빼는 앱 — 터미널 + 코드 에디터 + 코딩 에이전트 허브.
|
||||
*
|
||||
* 개인 문구 코퍼스는 사용자의 자연어 문체를 배우는 곳이다. 코드와 에이전트에게
|
||||
* 보낸 개발 지시가 섞이면 카카오톡에서도 개발 문장이 제안된다(실측: 코퍼스 138개 중
|
||||
* 대부분이 터미널·Agent Switchboard 발). 제안 자체는 에디터/에이전트에서도 허용한다.
|
||||
*/
|
||||
export const LEARNING_EXCLUDED_APPS: readonly string[] = Object.freeze([
|
||||
...TERMINAL_APPS,
|
||||
'Code',
|
||||
'Code - Insiders',
|
||||
'Cursor',
|
||||
'Windsurf',
|
||||
'Antigravity',
|
||||
'Zed',
|
||||
'devenv',
|
||||
'idea64',
|
||||
'pycharm64',
|
||||
'webstorm64',
|
||||
'rider64',
|
||||
'clion64',
|
||||
'goland64',
|
||||
'studio64',
|
||||
'sublime_text',
|
||||
'Agent Switchboard'
|
||||
])
|
||||
|
||||
/** 상자·블록·도형·기타 기호·딩뱃 — 터미널 UI/상태줄의 지문이다. */
|
||||
const NON_PROSE_GLYPH_PATTERN = /[←-⇿─-➿⬀-⯿]/u
|
||||
/** 공백을 뺀 글자 중 문자(모든 언어)가 이 비율 이상이어야 문장으로 본다. */
|
||||
const MIN_LETTER_RATIO = 0.6
|
||||
|
||||
/**
|
||||
* 개인 코퍼스에 넣어도 되는 문장인가.
|
||||
*
|
||||
* 문장이 아닌 것(타임스탬프 `5 분 5`, 상태줄 `00 ◷9`, 박스 선)을 거른다.
|
||||
* 앱 단위 제외(LEARNING_EXCLUDED_APPS)와 별개로 모든 출처에 적용한다.
|
||||
*/
|
||||
export function isLearnablePhrase(text: string): boolean {
|
||||
if (NON_PROSE_GLYPH_PATTERN.test(text)) return false
|
||||
const compact = text.replace(/\s+/gu, '')
|
||||
if (compact.length < 2) return false
|
||||
const letters = compact.match(/\p{L}/gu)?.length ?? 0
|
||||
return letters / compact.length >= MIN_LETTER_RATIO
|
||||
}
|
||||
|
||||
/**
|
||||
* 빈 입력창의 안내 문구(placeholder)를 텍스트로 돌려주는 제공자가 있다
|
||||
* (실측: KakaoTalk "메시지 입력", ChatGPT "ChatGPT에 메시지 보내기" 가 친 글로 학습됨).
|
||||
* 텍스트가 컨트롤 이름과 같으면 빈 칸으로 취급한다.
|
||||
*/
|
||||
export function withoutPlaceholderText(snapshot: FocusSnapshot): FocusSnapshot {
|
||||
const name = snapshot.controlName?.trim()
|
||||
if (!name || snapshot.text.trim() !== name) return snapshot
|
||||
return { ...snapshot, text: '', caretOffset: null }
|
||||
}
|
||||
|
||||
/**
|
||||
* 프롬프트에 넣을 컨텍스트 길이 상한.
|
||||
*
|
||||
|
|
@ -986,8 +1174,16 @@ export interface SuggestionState {
|
|||
partialText: string | null
|
||||
candidates: SuggestionCandidate[]
|
||||
activeIndex: number
|
||||
/**
|
||||
* 이 세션이 채우려는 후보 총량 — 모델 세션은 maxCandidatesTotal(12),
|
||||
* 로컬 기억 세션은 더 생성되지 않으므로 현재 candidates 수와 같다.
|
||||
* UI 가 "4–6 / 9" 같은 진행률을 보여주는 근거.
|
||||
*/
|
||||
targetTotal: number
|
||||
/** 앵커 rect (케어렛 → 요소 → 마우스 폴백은 메인이 계산) */
|
||||
anchor: UiRect | null
|
||||
/** 앵커가 케어렛인지 요소 전체인지 — 배치 전략(caret vs element)을 결정한다 */
|
||||
anchorKind: AnchorKind
|
||||
appName: string | null
|
||||
updatedAt: number
|
||||
lastSkipReason: SuggestionSkipReason | null
|
||||
|
|
@ -1031,7 +1227,16 @@ export const SUGGESTION_DEFAULTS = {
|
|||
minIntervalMs: 5000,
|
||||
maxRequestsPerMinute: 6,
|
||||
dailyBudget: 500,
|
||||
/** 로컬 기억 경로(폴백)에서 한 번에 만드는 후보 수 — 채우기 루프가 없다. */
|
||||
maxCandidates: 3,
|
||||
/**
|
||||
* 한 세션(모델 경로)이 채우기 루프로 쌓을 수 있는 후보 총량.
|
||||
*
|
||||
* 한 번에 요청하면 느리다(사용자 요청) — 1개씩 순차 요청해 채운다.
|
||||
*/
|
||||
maxCandidatesTotal: 12,
|
||||
/** 오버레이 한 페이지에 보여줄 후보 수. */
|
||||
pageSize: 3,
|
||||
maxOutputTokens: 64,
|
||||
/** 표시된 제안의 연속 접두가 이만큼 자랐을 때만 재생성한다. */
|
||||
regenerateAfterChars: 12,
|
||||
|
|
@ -1066,7 +1271,15 @@ export const SUGGESTION_DEFAULTS = {
|
|||
/** 연속 실패가 이 횟수에 도달하면 잠시 요청을 멈춘다 */
|
||||
failureCooldownThreshold: 2,
|
||||
/** 쿨다운 시간 (ms) */
|
||||
failureCooldownMs: 60000
|
||||
failureCooldownMs: 60000,
|
||||
/**
|
||||
* 이 시간 안에 실제 타이핑(letter/digit/symbol/space/backspace/delete/ime)이
|
||||
* 있어야 "지금 타이핑 중" 으로 본다.
|
||||
*
|
||||
* 마우스로 필드에 들어오기만 해도 (마지막 키 입력은 오래전이라) 유휴 조건을
|
||||
* 통과해 필드에 이미 있던 텍스트로 제안이 뜨던 문제를 막는다.
|
||||
*/
|
||||
recentTypingWindowMs: 8000
|
||||
} as const
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -472,6 +472,11 @@ export const IPC_CHANNELS = {
|
|||
// ── Popup Internal Channels (Caption) ──
|
||||
POPUP_CAPTION: {
|
||||
HIDE: 'caption:hide',
|
||||
/** 손잡이 위에 있는 동안만 마우스를 받는다 (그 외에는 클릭 통과) */
|
||||
SET_INTERACTIVE: 'captionPopup:setInteractive',
|
||||
DRAG_START: 'captionPopup:dragStart',
|
||||
DRAG_END: 'captionPopup:dragEnd',
|
||||
RESET_POSITION: 'captionPopup:resetPosition',
|
||||
},
|
||||
|
||||
// ── Voice Partial Transcript (RecordingTip) ──
|
||||
|
|
|
|||
|
|
@ -681,6 +681,8 @@ export type KeyBindingActionId =
|
|||
| 'suggestion-next'
|
||||
| 'suggestion-prev'
|
||||
| 'suggestion-dismiss'
|
||||
| 'suggestion-page-next'
|
||||
| 'suggestion-page-prev'
|
||||
|
||||
/** 액션 그룹 (설정 화면 섹션) */
|
||||
export type KeyBindingActionGroup = 'voice' | 'window' | 'input'
|
||||
|
|
@ -700,7 +702,8 @@ export interface KeyBindingActionSpec {
|
|||
defaultBindings: readonly KeyBinding[]
|
||||
}
|
||||
|
||||
function kb(
|
||||
/** 키보드 바인딩을 간결하게 만든다. 기본값(옛 기본값 등)을 구성할 때 이 모듈 밖에서도 쓴다. */
|
||||
export function kb(
|
||||
code: number,
|
||||
mods: Partial<Pick<KeyBinding, 'ctrl' | 'alt' | 'shift' | 'meta'>> = {}
|
||||
): KeyBinding {
|
||||
|
|
@ -783,9 +786,9 @@ export const KEYBINDING_ACTIONS: readonly KeyBindingActionSpec[] = Object.freeze
|
|||
descriptionKey: 'keybinding.action.suggestionAccept.desc',
|
||||
holdMode: false,
|
||||
doublePress: false,
|
||||
// 사용자 요청으로 Ctrl+Alt+화살표 계열로 통일했다:
|
||||
// 오른쪽 수락 / 아래 다음 후보 / 위 이전 후보 / 왼쪽 닫기.
|
||||
defaultBindings: [kb(VK.ArrowRight, { ctrl: true, alt: true })]
|
||||
// Ctrl+Alt+화살표 계열: 아래/위로 후보 순환, 좌우로 페이지 이동(12개까지
|
||||
// 순차 생성 — 사용자 요청). 화살표를 페이지 이동에 내주기 위해 수락은 Enter로 옮겼다.
|
||||
defaultBindings: [kb(VK.Enter, { ctrl: true, alt: true })]
|
||||
},
|
||||
{
|
||||
id: 'suggestion-next',
|
||||
|
|
@ -805,6 +808,24 @@ export const KEYBINDING_ACTIONS: readonly KeyBindingActionSpec[] = Object.freeze
|
|||
doublePress: false,
|
||||
defaultBindings: [kb(VK.ArrowUp, { ctrl: true, alt: true })]
|
||||
},
|
||||
{
|
||||
id: 'suggestion-page-next',
|
||||
group: 'input',
|
||||
labelKey: 'keybinding.action.suggestionPageNext',
|
||||
descriptionKey: 'keybinding.action.suggestionPageNext.desc',
|
||||
holdMode: false,
|
||||
doublePress: false,
|
||||
defaultBindings: [kb(VK.ArrowRight, { ctrl: true, alt: true })]
|
||||
},
|
||||
{
|
||||
id: 'suggestion-page-prev',
|
||||
group: 'input',
|
||||
labelKey: 'keybinding.action.suggestionPagePrev',
|
||||
descriptionKey: 'keybinding.action.suggestionPagePrev.desc',
|
||||
holdMode: false,
|
||||
doublePress: false,
|
||||
defaultBindings: [kb(VK.ArrowLeft, { ctrl: true, alt: true })]
|
||||
},
|
||||
{
|
||||
id: 'suggestion-dismiss',
|
||||
group: 'input',
|
||||
|
|
@ -812,7 +833,11 @@ export const KEYBINDING_ACTIONS: readonly KeyBindingActionSpec[] = Object.freeze
|
|||
descriptionKey: 'keybinding.action.suggestionDismiss.desc',
|
||||
holdMode: false,
|
||||
doublePress: false,
|
||||
defaultBindings: [kb(VK.ArrowLeft, { ctrl: true, alt: true })]
|
||||
// "모든 액션은 기본 바인딩을 하나 이상 갖는다" 가 카탈로그 불변식이라
|
||||
// (KEYBINDING_ACTIONS 불변식 테스트) 빈 배열을 기본값으로 두지 않는다.
|
||||
// 평범한 Esc(전역, 수정자 없음)가 오버레이가 떠 있을 때만 반응하는
|
||||
// 별도 경로로 항상 닫아 주므로, 이 바인딩은 보조 수단이다.
|
||||
defaultBindings: [kb(VK.Backspace, { ctrl: true, alt: true })]
|
||||
}
|
||||
])
|
||||
|
||||
|
|
|
|||
|
|
@ -495,6 +495,8 @@ export interface AppConfig {
|
|||
activeChainId: string | null
|
||||
/** Phase 10.1: Caption audio source (CaptionService, MeetingModeService) */
|
||||
captionAudioSource: import('@d3ro/core/types').CaptionAudioSource
|
||||
/** 사용자가 끌어다 놓은 자막 창 위치 (스크린 좌표). null 이면 화면 아래 가운데 */
|
||||
captionOverlayPosition: { x: number; y: number } | null
|
||||
/** Auto-update 채널 (latest=stable / beta / alpha). UpdateService */
|
||||
updateChannel: 'latest' | 'beta' | 'alpha'
|
||||
/** staged rollout용 영구 기기 식별자 (개인정보 아님, 최초 실행 시 생성) */
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@d3ro/i18n",
|
||||
"version": "1.5.0",
|
||||
"version": "1.6.0",
|
||||
"private": true,
|
||||
"description": "D3RO Voice 공유 i18n — 12개 locale, 타입 안전 키, Context, 포맷 유틸",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -374,6 +374,8 @@
|
|||
"popup.suggestion.hintAccept": "Übernehmen",
|
||||
"popup.suggestion.hintNext": "Weiter",
|
||||
"popup.suggestion.hintDismiss": "Schließen",
|
||||
"popup.suggestion.hintMove": "Bewegen",
|
||||
"popup.suggestion.hintPage": "Seite",
|
||||
"popup.suggestion.loading": "Wird erzeugt…",
|
||||
"keybinding.ui.sectionInput": "Eingabevorschläge",
|
||||
"keybinding.action.suggestionAccept": "Vorschlag übernehmen",
|
||||
|
|
@ -449,8 +451,13 @@
|
|||
"input.unit.percent": "%",
|
||||
"popup.suggestion.warming": "Modell wird vorbereitet…",
|
||||
"popup.suggestion.hintGenerating": "Wird erzeugt…",
|
||||
"popup.suggestion.hintGeneratingMore": "Mehr wird erzeugt… (bis zu {{max}})",
|
||||
"keybinding.action.suggestionPrev": "Vorheriger Vorschlag",
|
||||
"keybinding.action.suggestionPrev.desc": "Zum vorherigen Kandidaten wechseln.",
|
||||
"keybinding.action.suggestionPageNext": "Nächste Seite",
|
||||
"keybinding.action.suggestionPageNext.desc": "Zeigt die nächste Seite der Vorschläge (bis zu 12 insgesamt).",
|
||||
"keybinding.action.suggestionPagePrev": "Vorherige Seite",
|
||||
"keybinding.action.suggestionPagePrev.desc": "Zeigt die vorherige Seite der Vorschläge.",
|
||||
"input.insights.tabs.graph": "Graph",
|
||||
"input.graph.description": "Your sentences are stored as nodes and their relations as edges (what follows what, which share terms) so suggestions can pull personal context. Local only.",
|
||||
"input.graph.nodes": "Satz-Knoten",
|
||||
|
|
@ -512,5 +519,8 @@
|
|||
"input.privacy.unavailable": "Der lokale Datenbeleg ist derzeit nicht verfügbar. Mengen und Aufbewahrung werden nicht angezeigt.",
|
||||
"input.feedback.clearFailed": "Gesammelte Daten konnten nicht gelöscht werden.",
|
||||
"input.feedback.excludedFailed": "Ausschlüsse konnten nicht gespeichert werden.",
|
||||
"input.feedback.recommendationFailed": "{{app}} konnte nicht zu Ausschlüssen hinzugefügt werden."
|
||||
"input.feedback.recommendationFailed": "{{app}} konnte nicht zu Ausschlüssen hinzugefügt werden.",
|
||||
"popup.caption.loading": "Sprachmodell wird vorbereitet…",
|
||||
"popup.caption.waiting": "Hört zu… der erste Untertitel kann einige Sekunden dauern",
|
||||
"popup.caption.dragHint": "Ziehen zum Verschieben · Doppelklick setzt zurück"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1218,7 +1218,7 @@
|
|||
"popup.time.daysAgo": "{{d}}d ago",
|
||||
"popup.copy": "Copy",
|
||||
"popup.error.default": "An error occurred",
|
||||
"popup.caption.loading": "⏳ Loading STT model...",
|
||||
"popup.caption.loading": "Preparing the speech model…",
|
||||
"mobile.paywall.title": "D3RO PRO & Rewards",
|
||||
"mobile.paywall.close": "Close billing screen",
|
||||
"mobile.paywall.currentPlan": "Current plan",
|
||||
|
|
@ -1756,6 +1756,8 @@
|
|||
"popup.suggestion.hintAccept": "Accept",
|
||||
"popup.suggestion.hintNext": "Next",
|
||||
"popup.suggestion.hintDismiss": "Dismiss",
|
||||
"popup.suggestion.hintMove": "Move",
|
||||
"popup.suggestion.hintPage": "Page",
|
||||
"popup.suggestion.loading": "Generating…",
|
||||
"keybinding.ui.sectionInput": "Input suggestions",
|
||||
"keybinding.action.suggestionAccept": "Accept suggestion",
|
||||
|
|
@ -1831,8 +1833,13 @@
|
|||
"input.unit.percent": "%",
|
||||
"popup.suggestion.warming": "Preparing model…",
|
||||
"popup.suggestion.hintGenerating": "Generating…",
|
||||
"popup.suggestion.hintGeneratingMore": "More coming… (up to {{max}})",
|
||||
"keybinding.action.suggestionPrev": "Previous suggestion",
|
||||
"keybinding.action.suggestionPrev.desc": "Move to the previous suggestion candidate.",
|
||||
"keybinding.action.suggestionPageNext": "Next page",
|
||||
"keybinding.action.suggestionPageNext.desc": "Show the next page of suggestions (up to 12 total).",
|
||||
"keybinding.action.suggestionPagePrev": "Previous page",
|
||||
"keybinding.action.suggestionPagePrev.desc": "Show the previous page of suggestions.",
|
||||
"input.insights.tabs.graph": "Graph",
|
||||
"input.graph.description": "Your sentences are stored as nodes and their relations as edges (what follows what, which share terms) so suggestions can pull personal context. Local only.",
|
||||
"input.graph.nodes": "Sentence nodes",
|
||||
|
|
@ -1894,5 +1901,7 @@
|
|||
"input.privacy.unavailable": "The local data receipt is unavailable right now. Counts and retention are not shown.",
|
||||
"input.feedback.clearFailed": "Collected data could not be deleted.",
|
||||
"input.feedback.excludedFailed": "Exclusions could not be saved.",
|
||||
"input.feedback.recommendationFailed": "Could not add {{app}} to exclusions."
|
||||
"input.feedback.recommendationFailed": "Could not add {{app}} to exclusions.",
|
||||
"popup.caption.waiting": "Listening… the first caption can take a few seconds",
|
||||
"popup.caption.dragHint": "Drag to move · double-click to reset"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -374,6 +374,8 @@
|
|||
"popup.suggestion.hintAccept": "Aceptar",
|
||||
"popup.suggestion.hintNext": "Siguiente",
|
||||
"popup.suggestion.hintDismiss": "Descartar",
|
||||
"popup.suggestion.hintMove": "Mover",
|
||||
"popup.suggestion.hintPage": "Página",
|
||||
"popup.suggestion.loading": "Generando…",
|
||||
"keybinding.ui.sectionInput": "Sugerencias de entrada",
|
||||
"keybinding.action.suggestionAccept": "Aceptar sugerencia",
|
||||
|
|
@ -449,8 +451,13 @@
|
|||
"input.unit.percent": "%",
|
||||
"popup.suggestion.warming": "Preparando el modelo…",
|
||||
"popup.suggestion.hintGenerating": "Generando…",
|
||||
"popup.suggestion.hintGeneratingMore": "Generando más… (hasta {{max}})",
|
||||
"keybinding.action.suggestionPrev": "Sugerencia anterior",
|
||||
"keybinding.action.suggestionPrev.desc": "Pasa al candidato anterior.",
|
||||
"keybinding.action.suggestionPageNext": "Página siguiente",
|
||||
"keybinding.action.suggestionPageNext.desc": "Muestra la siguiente página de sugerencias (hasta 12 en total).",
|
||||
"keybinding.action.suggestionPagePrev": "Página anterior",
|
||||
"keybinding.action.suggestionPagePrev.desc": "Muestra la página anterior de sugerencias.",
|
||||
"input.insights.tabs.graph": "Grafo",
|
||||
"input.graph.description": "Your sentences are stored as nodes and their relations as edges (what follows what, which share terms) so suggestions can pull personal context. Local only.",
|
||||
"input.graph.nodes": "Nodos de frase",
|
||||
|
|
@ -512,5 +519,8 @@
|
|||
"input.privacy.unavailable": "El recibo de datos locales no está disponible ahora. No se muestran cantidades ni retención.",
|
||||
"input.feedback.clearFailed": "No se pudieron eliminar los datos recopilados.",
|
||||
"input.feedback.excludedFailed": "No se pudieron guardar las exclusiones.",
|
||||
"input.feedback.recommendationFailed": "No se pudo añadir {{app}} a las exclusiones."
|
||||
"input.feedback.recommendationFailed": "No se pudo añadir {{app}} a las exclusiones.",
|
||||
"popup.caption.loading": "Preparando el modelo de voz…",
|
||||
"popup.caption.waiting": "Escuchando… el primer subtítulo puede tardar unos segundos",
|
||||
"popup.caption.dragHint": "Arrastra para mover · doble clic para restablecer"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -374,6 +374,8 @@
|
|||
"popup.suggestion.hintAccept": "Accepter",
|
||||
"popup.suggestion.hintNext": "Suivant",
|
||||
"popup.suggestion.hintDismiss": "Fermer",
|
||||
"popup.suggestion.hintMove": "Déplacer",
|
||||
"popup.suggestion.hintPage": "Page",
|
||||
"popup.suggestion.loading": "Génération…",
|
||||
"keybinding.ui.sectionInput": "Suggestions de saisie",
|
||||
"keybinding.action.suggestionAccept": "Accepter la suggestion",
|
||||
|
|
@ -449,8 +451,13 @@
|
|||
"input.unit.percent": "%",
|
||||
"popup.suggestion.warming": "Préparation du modèle…",
|
||||
"popup.suggestion.hintGenerating": "Génération…",
|
||||
"popup.suggestion.hintGeneratingMore": "Génération en cours… (jusqu'à {{max}})",
|
||||
"keybinding.action.suggestionPrev": "Suggestion précédente",
|
||||
"keybinding.action.suggestionPrev.desc": "Passe au candidat précédent.",
|
||||
"keybinding.action.suggestionPageNext": "Page suivante",
|
||||
"keybinding.action.suggestionPageNext.desc": "Affiche la page suivante de suggestions (jusqu'à 12 au total).",
|
||||
"keybinding.action.suggestionPagePrev": "Page précédente",
|
||||
"keybinding.action.suggestionPagePrev.desc": "Affiche la page précédente de suggestions.",
|
||||
"input.insights.tabs.graph": "Graphe",
|
||||
"input.graph.description": "Your sentences are stored as nodes and their relations as edges (what follows what, which share terms) so suggestions can pull personal context. Local only.",
|
||||
"input.graph.nodes": "Nœuds de phrase",
|
||||
|
|
@ -512,5 +519,8 @@
|
|||
"input.privacy.unavailable": "Le reçu de données locales est indisponible. Les quantités et durées de conservation ne sont pas affichées.",
|
||||
"input.feedback.clearFailed": "Les données collectées n’ont pas pu être supprimées.",
|
||||
"input.feedback.excludedFailed": "Les exclusions n’ont pas pu être enregistrées.",
|
||||
"input.feedback.recommendationFailed": "Impossible d’ajouter {{app}} aux exclusions."
|
||||
"input.feedback.recommendationFailed": "Impossible d’ajouter {{app}} aux exclusions.",
|
||||
"popup.caption.loading": "Préparation du modèle vocal…",
|
||||
"popup.caption.waiting": "Écoute… le premier sous-titre peut prendre quelques secondes",
|
||||
"popup.caption.dragHint": "Glisser pour déplacer · double-clic pour réinitialiser"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -374,6 +374,8 @@
|
|||
"popup.suggestion.hintAccept": "承認",
|
||||
"popup.suggestion.hintNext": "次へ",
|
||||
"popup.suggestion.hintDismiss": "閉じる",
|
||||
"popup.suggestion.hintMove": "移動",
|
||||
"popup.suggestion.hintPage": "ページ",
|
||||
"popup.suggestion.loading": "生成中…",
|
||||
"keybinding.ui.sectionInput": "入力サジェスト",
|
||||
"keybinding.action.suggestionAccept": "サジェストを承認",
|
||||
|
|
@ -449,8 +451,13 @@
|
|||
"input.unit.percent": "%",
|
||||
"popup.suggestion.warming": "モデルを準備中…",
|
||||
"popup.suggestion.hintGenerating": "生成中…",
|
||||
"popup.suggestion.hintGeneratingMore": "さらに生成中…(最大{{max}}件)",
|
||||
"keybinding.action.suggestionPrev": "前の候補",
|
||||
"keybinding.action.suggestionPrev.desc": "前の候補に移動します。",
|
||||
"keybinding.action.suggestionPageNext": "次のページ",
|
||||
"keybinding.action.suggestionPageNext.desc": "次の提案ページを表示します(最大12件まで)。",
|
||||
"keybinding.action.suggestionPagePrev": "前のページ",
|
||||
"keybinding.action.suggestionPagePrev.desc": "前の提案ページに戻ります。",
|
||||
"input.insights.tabs.graph": "グラフ",
|
||||
"input.graph.description": "Your sentences are stored as nodes and their relations as edges (what follows what, which share terms) so suggestions can pull personal context. Local only.",
|
||||
"input.graph.nodes": "文ノード",
|
||||
|
|
@ -512,5 +519,8 @@
|
|||
"input.privacy.unavailable": "ローカルデータの明細は現在利用できません。件数と保持期間は表示されません。",
|
||||
"input.feedback.clearFailed": "収集したデータを削除できませんでした。",
|
||||
"input.feedback.excludedFailed": "除外設定を保存できませんでした。",
|
||||
"input.feedback.recommendationFailed": "{{app}} を除外に追加できませんでした。"
|
||||
"input.feedback.recommendationFailed": "{{app}} を除外に追加できませんでした。",
|
||||
"popup.caption.loading": "音声モデルを準備中…",
|
||||
"popup.caption.waiting": "聞き取り中… 最初の字幕まで数秒かかることがあります",
|
||||
"popup.caption.dragHint": "ドラッグで移動 · ダブルクリックで元の位置"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1225,7 +1225,7 @@
|
|||
"popup.time.daysAgo": "{{d}}일 전",
|
||||
"popup.copy": "복사",
|
||||
"popup.error.default": "오류가 발생했습니다",
|
||||
"popup.caption.loading": "⏳ STT 모델 로딩 중...",
|
||||
"popup.caption.loading": "음성 모델 준비 중…",
|
||||
"mobile.paywall.title": "D3RO PRO 및 리워드",
|
||||
"mobile.paywall.close": "결제 화면 닫기",
|
||||
"mobile.paywall.currentPlan": "현재 요금제",
|
||||
|
|
@ -1763,6 +1763,8 @@
|
|||
"popup.suggestion.hintAccept": "수락",
|
||||
"popup.suggestion.hintNext": "다음",
|
||||
"popup.suggestion.hintDismiss": "닫기",
|
||||
"popup.suggestion.hintMove": "이동",
|
||||
"popup.suggestion.hintPage": "페이지",
|
||||
"popup.suggestion.loading": "생성 중…",
|
||||
"keybinding.ui.sectionInput": "입력 제안",
|
||||
"keybinding.action.suggestionAccept": "제안 수락",
|
||||
|
|
@ -1838,8 +1840,13 @@
|
|||
"input.unit.percent": "%",
|
||||
"popup.suggestion.warming": "모델 준비 중…",
|
||||
"popup.suggestion.hintGenerating": "생성 중…",
|
||||
"popup.suggestion.hintGeneratingMore": "더 생성 중… (최대 {{max}}개)",
|
||||
"keybinding.action.suggestionPrev": "이전 제안",
|
||||
"keybinding.action.suggestionPrev.desc": "이전 제안 후보로 이동합니다.",
|
||||
"keybinding.action.suggestionPageNext": "다음 페이지",
|
||||
"keybinding.action.suggestionPageNext.desc": "다음 제안 페이지를 봅니다 (최대 12개까지).",
|
||||
"keybinding.action.suggestionPagePrev": "이전 페이지",
|
||||
"keybinding.action.suggestionPagePrev.desc": "이전 제안 페이지로 이동합니다.",
|
||||
"input.insights.tabs.graph": "그래프",
|
||||
"input.graph.description": "내 문장을 노드로, 문장 사이의 관계(무엇이 무엇 뒤에 오는지, 어떤 용어를 공유하는지)를 엣지로 저장해 제안에 개인 문맥을 끌어옵니다. 전부 로컬입니다.",
|
||||
"input.graph.nodes": "문장 노드",
|
||||
|
|
@ -1901,5 +1908,7 @@
|
|||
"input.privacy.unavailable": "지금은 로컬 데이터 영수증을 불러올 수 없습니다. 수량과 보존 기간은 표시하지 않습니다.",
|
||||
"input.feedback.clearFailed": "수집된 데이터를 삭제하지 못했습니다.",
|
||||
"input.feedback.excludedFailed": "제외 목록을 저장하지 못했습니다.",
|
||||
"input.feedback.recommendationFailed": "{{app}}을(를) 제외 목록에 추가하지 못했습니다."
|
||||
"input.feedback.recommendationFailed": "{{app}}을(를) 제외 목록에 추가하지 못했습니다.",
|
||||
"popup.caption.waiting": "듣는 중… 첫 자막까지 몇 초 걸릴 수 있어요",
|
||||
"popup.caption.dragHint": "끌어서 이동 · 더블클릭하면 원위치"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -374,6 +374,8 @@
|
|||
"popup.suggestion.hintAccept": "Aceitar",
|
||||
"popup.suggestion.hintNext": "Próxima",
|
||||
"popup.suggestion.hintDismiss": "Descartar",
|
||||
"popup.suggestion.hintMove": "Mover",
|
||||
"popup.suggestion.hintPage": "Página",
|
||||
"popup.suggestion.loading": "Gerando…",
|
||||
"keybinding.ui.sectionInput": "Sugestões de entrada",
|
||||
"keybinding.action.suggestionAccept": "Aceitar sugestão",
|
||||
|
|
@ -449,8 +451,13 @@
|
|||
"input.unit.percent": "%",
|
||||
"popup.suggestion.warming": "Preparando o modelo…",
|
||||
"popup.suggestion.hintGenerating": "Gerando…",
|
||||
"popup.suggestion.hintGeneratingMore": "Gerando mais… (até {{max}})",
|
||||
"keybinding.action.suggestionPrev": "Sugestão anterior",
|
||||
"keybinding.action.suggestionPrev.desc": "Vai para o candidato anterior.",
|
||||
"keybinding.action.suggestionPageNext": "Próxima página",
|
||||
"keybinding.action.suggestionPageNext.desc": "Mostra a próxima página de sugestões (até 12 no total).",
|
||||
"keybinding.action.suggestionPagePrev": "Página anterior",
|
||||
"keybinding.action.suggestionPagePrev.desc": "Mostra a página anterior de sugestões.",
|
||||
"input.insights.tabs.graph": "Grafo",
|
||||
"input.graph.description": "Your sentences are stored as nodes and their relations as edges (what follows what, which share terms) so suggestions can pull personal context. Local only.",
|
||||
"input.graph.nodes": "Nós de frase",
|
||||
|
|
@ -512,5 +519,8 @@
|
|||
"input.privacy.unavailable": "O recibo de dados locais está indisponível agora. Quantidades e retenção não são exibidas.",
|
||||
"input.feedback.clearFailed": "Não foi possível excluir os dados coletados.",
|
||||
"input.feedback.excludedFailed": "Não foi possível salvar as exclusões.",
|
||||
"input.feedback.recommendationFailed": "Não foi possível adicionar {{app}} às exclusões."
|
||||
"input.feedback.recommendationFailed": "Não foi possível adicionar {{app}} às exclusões.",
|
||||
"popup.caption.loading": "Preparando o modelo de voz…",
|
||||
"popup.caption.waiting": "Ouvindo… a primeira legenda pode levar alguns segundos",
|
||||
"popup.caption.dragHint": "Arraste para mover · clique duplo para restaurar"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -374,6 +374,8 @@
|
|||
"popup.suggestion.hintAccept": "Принять",
|
||||
"popup.suggestion.hintNext": "Далее",
|
||||
"popup.suggestion.hintDismiss": "Закрыть",
|
||||
"popup.suggestion.hintMove": "Перемещение",
|
||||
"popup.suggestion.hintPage": "Страница",
|
||||
"popup.suggestion.loading": "Генерация…",
|
||||
"keybinding.ui.sectionInput": "Подсказки ввода",
|
||||
"keybinding.action.suggestionAccept": "Принять подсказку",
|
||||
|
|
@ -449,8 +451,13 @@
|
|||
"input.unit.percent": "%",
|
||||
"popup.suggestion.warming": "Подготовка модели…",
|
||||
"popup.suggestion.hintGenerating": "Генерация…",
|
||||
"popup.suggestion.hintGeneratingMore": "Создаётся ещё… (до {{max}})",
|
||||
"keybinding.action.suggestionPrev": "Предыдущая подсказка",
|
||||
"keybinding.action.suggestionPrev.desc": "Перейти к предыдущему варианту.",
|
||||
"keybinding.action.suggestionPageNext": "Следующая страница",
|
||||
"keybinding.action.suggestionPageNext.desc": "Показывает следующую страницу подсказок (до 12 всего).",
|
||||
"keybinding.action.suggestionPagePrev": "Предыдущая страница",
|
||||
"keybinding.action.suggestionPagePrev.desc": "Показывает предыдущую страницу подсказок.",
|
||||
"input.insights.tabs.graph": "Граф",
|
||||
"input.graph.description": "Your sentences are stored as nodes and their relations as edges (what follows what, which share terms) so suggestions can pull personal context. Local only.",
|
||||
"input.graph.nodes": "Узлы-предложения",
|
||||
|
|
@ -512,5 +519,8 @@
|
|||
"input.privacy.unavailable": "Квитанция локальных данных сейчас недоступна. Количества и сроки хранения не показаны.",
|
||||
"input.feedback.clearFailed": "Не удалось удалить собранные данные.",
|
||||
"input.feedback.excludedFailed": "Не удалось сохранить исключения.",
|
||||
"input.feedback.recommendationFailed": "Не удалось добавить {{app}} в исключения."
|
||||
"input.feedback.recommendationFailed": "Не удалось добавить {{app}} в исключения.",
|
||||
"popup.caption.loading": "Подготовка речевой модели…",
|
||||
"popup.caption.waiting": "Слушаю… первый субтитр может появиться через несколько секунд",
|
||||
"popup.caption.dragHint": "Перетащите, чтобы переместить · двойной щелчок — сброс"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -374,6 +374,8 @@
|
|||
"popup.suggestion.hintAccept": "ยอมรับ",
|
||||
"popup.suggestion.hintNext": "ถัดไป",
|
||||
"popup.suggestion.hintDismiss": "ปิด",
|
||||
"popup.suggestion.hintMove": "ย้าย",
|
||||
"popup.suggestion.hintPage": "หน้า",
|
||||
"popup.suggestion.loading": "กำลังสร้าง…",
|
||||
"keybinding.ui.sectionInput": "คำแนะนำการป้อน",
|
||||
"keybinding.action.suggestionAccept": "ยอมรับคำแนะนำ",
|
||||
|
|
@ -449,8 +451,13 @@
|
|||
"input.unit.percent": "%",
|
||||
"popup.suggestion.warming": "กำลังเตรียมโมเดล…",
|
||||
"popup.suggestion.hintGenerating": "กำลังสร้าง…",
|
||||
"popup.suggestion.hintGeneratingMore": "กำลังสร้างเพิ่ม… (สูงสุด {{max}})",
|
||||
"keybinding.action.suggestionPrev": "คำแนะนำก่อนหน้า",
|
||||
"keybinding.action.suggestionPrev.desc": "ย้ายไปยังตัวเลือกก่อนหน้า",
|
||||
"keybinding.action.suggestionPageNext": "หน้าถัดไป",
|
||||
"keybinding.action.suggestionPageNext.desc": "แสดงคำแนะนำหน้าถัดไป (สูงสุด 12 รายการ)",
|
||||
"keybinding.action.suggestionPagePrev": "หน้าก่อนหน้า",
|
||||
"keybinding.action.suggestionPagePrev.desc": "แสดงคำแนะนำหน้าก่อนหน้า",
|
||||
"input.insights.tabs.graph": "กราฟ",
|
||||
"input.graph.description": "Your sentences are stored as nodes and their relations as edges (what follows what, which share terms) so suggestions can pull personal context. Local only.",
|
||||
"input.graph.nodes": "โหนดประโยค",
|
||||
|
|
@ -512,5 +519,8 @@
|
|||
"input.privacy.unavailable": "ใบรับข้อมูลในเครื่องไม่พร้อมใช้งานขณะนี้ จึงไม่แสดงจำนวนและระยะเวลาเก็บข้อมูล",
|
||||
"input.feedback.clearFailed": "ไม่สามารถลบข้อมูลที่เก็บรวบรวมได้",
|
||||
"input.feedback.excludedFailed": "ไม่สามารถบันทึกรายการยกเว้นได้",
|
||||
"input.feedback.recommendationFailed": "ไม่สามารถเพิ่ม {{app}} ในรายการยกเว้นได้"
|
||||
"input.feedback.recommendationFailed": "ไม่สามารถเพิ่ม {{app}} ในรายการยกเว้นได้",
|
||||
"popup.caption.loading": "กำลังเตรียมโมเดลเสียง…",
|
||||
"popup.caption.waiting": "กำลังฟัง… คำบรรยายแรกอาจใช้เวลาสักครู่",
|
||||
"popup.caption.dragHint": "ลากเพื่อย้าย · ดับเบิลคลิกเพื่อรีเซ็ต"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -374,6 +374,8 @@
|
|||
"popup.suggestion.hintAccept": "Chấp nhận",
|
||||
"popup.suggestion.hintNext": "Tiếp",
|
||||
"popup.suggestion.hintDismiss": "Đóng",
|
||||
"popup.suggestion.hintMove": "Di chuyển",
|
||||
"popup.suggestion.hintPage": "Trang",
|
||||
"popup.suggestion.loading": "Đang tạo…",
|
||||
"keybinding.ui.sectionInput": "Gợi ý nhập liệu",
|
||||
"keybinding.action.suggestionAccept": "Chấp nhận gợi ý",
|
||||
|
|
@ -449,8 +451,13 @@
|
|||
"input.unit.percent": "%",
|
||||
"popup.suggestion.warming": "Đang chuẩn bị mô hình…",
|
||||
"popup.suggestion.hintGenerating": "Đang tạo…",
|
||||
"popup.suggestion.hintGeneratingMore": "Đang tạo thêm… (tối đa {{max}})",
|
||||
"keybinding.action.suggestionPrev": "Gợi ý trước",
|
||||
"keybinding.action.suggestionPrev.desc": "Chuyển về ứng viên trước đó.",
|
||||
"keybinding.action.suggestionPageNext": "Trang tiếp theo",
|
||||
"keybinding.action.suggestionPageNext.desc": "Hiển thị trang gợi ý tiếp theo (tối đa 12 gợi ý).",
|
||||
"keybinding.action.suggestionPagePrev": "Trang trước",
|
||||
"keybinding.action.suggestionPagePrev.desc": "Hiển thị trang gợi ý trước đó.",
|
||||
"input.insights.tabs.graph": "Đồ thị",
|
||||
"input.graph.description": "Your sentences are stored as nodes and their relations as edges (what follows what, which share terms) so suggestions can pull personal context. Local only.",
|
||||
"input.graph.nodes": "Nút câu",
|
||||
|
|
@ -512,5 +519,8 @@
|
|||
"input.privacy.unavailable": "Biên nhận dữ liệu cục bộ hiện không khả dụng. Số lượng và thời hạn lưu giữ không được hiển thị.",
|
||||
"input.feedback.clearFailed": "Không thể xóa dữ liệu đã thu thập.",
|
||||
"input.feedback.excludedFailed": "Không thể lưu danh sách loại trừ.",
|
||||
"input.feedback.recommendationFailed": "Không thể thêm {{app}} vào danh sách loại trừ."
|
||||
"input.feedback.recommendationFailed": "Không thể thêm {{app}} vào danh sách loại trừ.",
|
||||
"popup.caption.loading": "Đang chuẩn bị mô hình giọng nói…",
|
||||
"popup.caption.waiting": "Đang nghe… phụ đề đầu tiên có thể mất vài giây",
|
||||
"popup.caption.dragHint": "Kéo để di chuyển · nhấp đúp để đặt lại"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -374,6 +374,8 @@
|
|||
"popup.suggestion.hintAccept": "接受",
|
||||
"popup.suggestion.hintNext": "下一個",
|
||||
"popup.suggestion.hintDismiss": "關閉",
|
||||
"popup.suggestion.hintMove": "移動",
|
||||
"popup.suggestion.hintPage": "翻頁",
|
||||
"popup.suggestion.loading": "產生中…",
|
||||
"keybinding.ui.sectionInput": "輸入建議",
|
||||
"keybinding.action.suggestionAccept": "接受建議",
|
||||
|
|
@ -449,8 +451,13 @@
|
|||
"input.unit.percent": "%",
|
||||
"popup.suggestion.warming": "正在準備模型…",
|
||||
"popup.suggestion.hintGenerating": "產生中…",
|
||||
"popup.suggestion.hintGeneratingMore": "正在產生更多…(最多 {{max}} 則)",
|
||||
"keybinding.action.suggestionPrev": "上一則建議",
|
||||
"keybinding.action.suggestionPrev.desc": "移動到上一則候選。",
|
||||
"keybinding.action.suggestionPageNext": "下一頁",
|
||||
"keybinding.action.suggestionPageNext.desc": "顯示下一頁建議(最多 12 則)。",
|
||||
"keybinding.action.suggestionPagePrev": "上一頁",
|
||||
"keybinding.action.suggestionPagePrev.desc": "顯示上一頁建議。",
|
||||
"input.insights.tabs.graph": "圖譜",
|
||||
"input.graph.description": "Your sentences are stored as nodes and their relations as edges (what follows what, which share terms) so suggestions can pull personal context. Local only.",
|
||||
"input.graph.nodes": "句子節點",
|
||||
|
|
@ -512,5 +519,8 @@
|
|||
"input.privacy.unavailable": "本機資料憑據目前無法使用,因此不顯示數量和保留期。",
|
||||
"input.feedback.clearFailed": "無法刪除已收集的資料。",
|
||||
"input.feedback.excludedFailed": "無法儲存排除清單。",
|
||||
"input.feedback.recommendationFailed": "無法將 {{app}} 加入排除清單。"
|
||||
"input.feedback.recommendationFailed": "無法將 {{app}} 加入排除清單。",
|
||||
"popup.caption.loading": "正在準備語音模型…",
|
||||
"popup.caption.waiting": "聆聽中… 第一則字幕可能需要幾秒鐘",
|
||||
"popup.caption.dragHint": "拖曳以移動 · 按兩下還原"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -374,6 +374,8 @@
|
|||
"popup.suggestion.hintAccept": "接受",
|
||||
"popup.suggestion.hintNext": "下一个",
|
||||
"popup.suggestion.hintDismiss": "关闭",
|
||||
"popup.suggestion.hintMove": "移动",
|
||||
"popup.suggestion.hintPage": "翻页",
|
||||
"popup.suggestion.loading": "生成中…",
|
||||
"keybinding.ui.sectionInput": "输入建议",
|
||||
"keybinding.action.suggestionAccept": "接受建议",
|
||||
|
|
@ -449,8 +451,13 @@
|
|||
"input.unit.percent": "%",
|
||||
"popup.suggestion.warming": "正在准备模型…",
|
||||
"popup.suggestion.hintGenerating": "生成中…",
|
||||
"popup.suggestion.hintGeneratingMore": "正在生成更多…(最多 {{max}} 条)",
|
||||
"keybinding.action.suggestionPrev": "上一条建议",
|
||||
"keybinding.action.suggestionPrev.desc": "移动到上一条候选。",
|
||||
"keybinding.action.suggestionPageNext": "下一页",
|
||||
"keybinding.action.suggestionPageNext.desc": "显示下一页建议(最多 12 条)。",
|
||||
"keybinding.action.suggestionPagePrev": "上一页",
|
||||
"keybinding.action.suggestionPagePrev.desc": "显示上一页建议。",
|
||||
"input.insights.tabs.graph": "图谱",
|
||||
"input.graph.description": "Your sentences are stored as nodes and their relations as edges (what follows what, which share terms) so suggestions can pull personal context. Local only.",
|
||||
"input.graph.nodes": "句子节点",
|
||||
|
|
@ -512,5 +519,8 @@
|
|||
"input.privacy.unavailable": "本地数据凭据暂不可用,因此不显示数量和保留期。",
|
||||
"input.feedback.clearFailed": "无法删除已收集的数据。",
|
||||
"input.feedback.excludedFailed": "无法保存排除列表。",
|
||||
"input.feedback.recommendationFailed": "无法将 {{app}} 添加到排除列表。"
|
||||
"input.feedback.recommendationFailed": "无法将 {{app}} 添加到排除列表。",
|
||||
"popup.caption.loading": "正在准备语音模型…",
|
||||
"popup.caption.waiting": "正在聆听… 第一条字幕可能需要几秒钟",
|
||||
"popup.caption.dragHint": "拖动以移动 · 双击复位"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@d3ro/ui-native",
|
||||
"version": "1.5.0",
|
||||
"version": "1.6.0",
|
||||
"private": true,
|
||||
"description": "D3RO Voice React Native DS — MetalCard/PhosphorText/Led/WaveBars etc.",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@d3ro/ui",
|
||||
"version": "1.5.0",
|
||||
"version": "1.6.0",
|
||||
"private": true,
|
||||
"description": "D3RO Voice 공유 UI — 디자인 시스템 컴포넌트 + 테마 토큰 + CSS 변수 맵",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue