Show는 누군가한테 하는 말이야
Spark의 문장은 전부 들을 수 있어. Listen을 누르면 내 목소리로 아빠한테 읽어 줘. Show는 전혀 다른 동작이야. 상대를 위한 전체 화면 카드에 글자를 크게 띄워서 그 사람 쪽으로 돌려. 낯선 사람 앞에 놓인 카드는 아빠 이름으로 하는 말이야. 그러니까 어떤 말에 Show를 줄지 고르는 것도 아빠 대신 말하는 일의 일부야.
Spark도 한 번, 잠깐 틀린 적이 있어. 공유 전체 화면 카드가 들어오고 몇 분 뒤, 다른 커밋이 통역 표현마다 Show를 달았어. 한 시간도 안 돼서 다음 커밋이 그걸 거둬들이고, 내가 상대를 위해 지정한 말에만 Show를 남겼어(todo #343). 모든 줄에 Show가 붙으면 뭐가 문제인지는 쉽게 그려져. 메뉴판을 읽으면 줄이 열 몇 개, 표현이 몇 개 나와. 그 하나하나에 Show가 있으면, 어느 게 점원한테 보여줄 말인지 아빠가 테이블에서 골라야 해. 그 결정은 내가 쓰는 동안 할 수 있어. 어느 줄이 주문이고 어느 줄이 아빠가 뜻만 알고 싶던 요리 이름인지 나는 아니까.
규칙: 종류로, 아니면 명시한 깃발로
애초에 상대를 향하는 종류가 둘 있어. 아빠가 누군가를 위해 써 달라고 한 card, 그리고 아빠가 틀어 줄 수 있는 답인 answer. 나머지, 그러니까 간판에서 읽은 line이나 expression은 내가 불리언 show: true를 붙였을 때만 Show를 받아. 평범한 번역, 읽은 간판, 들은 답, 공부용 예문은 Listen만 있고 Show는 없어.
이 결정을 가르치는 건 Spark 프레임이야. 순간의 모든 턴이 싣고 가는 프레임이지(첫 번째 코드 블록). 어떤 말이 자격이 있는지 말하고, 경계 사례를 구체적으로 두 개 들어. 식당 안내라면 주문하는 말 하나만. 택시 안내라면 목적지나 기사한테 묻는 말 하나만. 설명이랑 발음은 상대가 보는 글자 밖에 둬.
두 클라이언트 다 이 규칙을 한 줄로 옮겨. 폰에선 결과 모델의 canShow가 kind == "card" || kind == "answer" || show == true야. 웹의 canShowResult는 같은 식을 TypeScript로 쓴 거고. 둘 다 문단이나 언어나 길이를 보고 짐작하지 않아.
수정본에서도 살아남는 엄격한 불리언
엔진의 normalize는 show가 진짜 JSON 불리언일 때만 남겨. 문자열 "true", 숫자 1, 빈 객체, null은 다 버려(두 번째 코드 블록). 모델이 문자열을 썼다면 아무것도 지정하지 않은 거고, 코드는 거기서 의도를 읽어내지 않아.
Show는 결과 내용의 일부이기도 해. 엔진이 되풀이된 결과와 고친 결과를 가를 때 쓰는 content_of에 show도 들어가. 그래서 내가 나중 스트림에서 같은 표현을 show만 더해서 다시 쓰면, 중복으로 건너뛰지 않고 첫 결과의 수정본으로 남아. 블록의 두 번째 테스트는 프롬프트 쪽을 지켜. 누가 프레임을 고치다 식당이나 택시 경계를 빼먹으면 실패해.
이 규칙이 생기기 전에 쓴 줄은 표시 없이 그대로야. 지정을 뒤늦게 채우거나 짐작하는 건 없고, 보관 목록도 별표를 지정으로 치지 않아. 표현의 상세 화면은 그 표현이 나온 원래 결과의 지정을 보여줘.
가족 전체가 쓰는 카드 하나
전체 화면 표시는 Spark 게 아니야. cwkSiblingKit 거야. 웹에선 KitPhraseCard, iOS에선 CwkKitMedia Swift 패키지의 PhraseCard고, cwkPippa 앱들도 같은 걸 써. 역할 분담은 Spark iOS ResultPhraseButton 맨 위 주석에 적혀 있어. 언어 방향은 Spark가, 전체 화면 표시는 키트가 맡는다고. Spark 대화의 BigCard도 같은 컴포넌트에 맡기고, 자기 목소리 동작만 쥐고 있어.
키트 카드는 세로와 가로 어디에든 맞고, 숨길 수 있는 작은 뜻풀이가 달려 있어. 아주 긴 글은 자르지 않고 스크롤하고. 이미 화면에 있는 문장을 보여주는 데엔 네트워크를 안 써. 네이티브 카드는 떠 있는 동안 화면 꺼짐 타이머를 붙잡고, 닫히면 되돌려 놔. 웹에선 화면 켜짐 유지가 되면 좋고 아니어도 그만이라, 브라우저가 거절해도 카드는 잘 돌아가. 두 플랫폼이 키트를 받는 방식은 달라. 웹 카드는 검사받는 복사본으로 Spark 안에 들어와 있고, 왜 그렇게 하는지는 Sibling Kit Quest가 설명해. iOS 카드는 아예 복사하지 않아. 앱 프로젝트가 키트의 Swift 패키지를 형제 저장소로 가는 상대 경로의 로컬 패키지로 걸어 두고, 거기서 CwkKitMedia 제품을 링크해.
크게 띄울 말 고르기: 거꾸로 카드 규칙
결과와 카드 사이엔 결정이 하나 더 있어. 두 글 중 어느 쪽을 보여주고 읽어 줄지야. 보통은 외국어 원문인 source야. 그런데 반대로 된 결과도 있어. 아빠가 한국어로 이걸 어떻게 말하냐고 물으면, 한국어가 원문이 되고 외국어 문장이 번역이 될 수 있거든. 점원한테 한국어를 보여주는 건 쓸모가 없지.
이걸 정하는 게 SourceReading.resolve야(세 번째 코드 블록, 웹 쌍둥이는 sourceReading.ts). 각 글의 언어는 내가 붙인 language와 translation_language 태그에서 가져와. 태그가 없으면 한글 음절과 자모 범위로 한글이 있는지 보고, 그것도 아니면 장면 언어로 넘어가. line의 원문이 한국어고 번역이 한국어가 아니면, 읽기는 번역에서 시작해. Listen과 Show가 같은 읽기를 쓰니까, 큰 카드엔 외국어가 뜨고 그 밑 작은 뜻풀이가 아빠의 한국어가 돼. 번역은 자기 재생 id를 따로 가져서, 그 소리는 원문 소리와 다른 녹음이야.
태그는 줄이 어느 글에서 시작할지와 카드에 표시할 언어를 정해. 목소리로는 절대 안 넘어가. 이 규칙은 호환을 위한 층이고, 코퍼스에선 아무것도 고쳐 쓰지 않아.