diff --git a/CHANGELOG.md b/CHANGELOG.md index 880236fd0..815835c61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## Unreleased + +Korean joins as a seventh interface language, and the three things that had to be fixed before it could actually work. + +### Added + +- Korean (`ko`) ships as a seventh interface language, AI-initial and community-maintained alongside French, Spanish, Italian and Polish. The whole surface speaks it: dashboard, settings, share views, the clinician report, notifications and the AI prompts. The Coach and the daily briefing take the reviewed English body with a Korean reply directive rather than a hand-curated Korean one, the same route the other community locales took before they had a safety-contract matrix of their own. + +### Fixed + +- A matcher that reads app text against text a person or a lab typed matched nothing at all in Korean. The shared fold decomposes with NFD and strips the combining marks, which turns Latin accents into plain letters — but Hangul decomposes into jamo, and jamo are not combining marks, so they survived and the folded text no longer resembled any table the app carries. Both readers of that fold were affected: the normative-claim check that grades whether a recommendation needs a citation, and the parser that reads a printed reference range. The fold recomposes now, and every Latin, Polish and German fold is byte-identical to before. +- No Korean analyte name could resolve to a LOINC code. The key normaliser kept `[a-z0-9]` and dropped everything else, so every Korean lab name in the catalogue normalised to the empty string and the derived index held none of them — a Korean account's cholesterol result reached a FHIR export uncoded. +- The reference-range parser now reads the tilde a Korean lab prints (`3.5~5.0`), in both the ASCII and fullwidth forms. It had the hyphen and the dashes and not the separator the reports actually use. + ## [1.37.28] — 2026-08-24 Eleven parts of a shared record were selectable and invisible. Ticking one and sending the link showed the recipient nothing, while the same link's PDF download carried it. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 24fb926e6..c00ba213f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -121,7 +121,7 @@ Contributors: branch off `main`; PRs target `main`. ## Translations -HealthLog ships with English (`en`) and German (`de`) maintained by the project, plus French (`fr`), Spanish (`es`), Italian (`it`), and Polish (`pl`) that are **AI-initial, community-maintained**. The maintained locales are reviewed line-by-line; the community-maintained ones surface a small banner inviting users to improve them. +HealthLog ships with English (`en`) and German (`de`) maintained by the project, plus French (`fr`), Spanish (`es`), Italian (`it`), Polish (`pl`), and Korean (`ko`) that are **AI-initial, community-maintained**. The maintained locales are reviewed line-by-line; the community-maintained ones surface a small banner inviting users to improve them. ### Improving an existing locale diff --git a/README.md b/README.md index cc627a54f..3576e1a60 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Try the [live demo](https://demo.healthlog.dev) to see a working install — sig - **AI insights you own.** Daily briefing, health scores, correlations, and a conversational coach grounded in your own measurements and interpreted against cited guideline reference bands — where a value sits and what that means in plain words, never a diagnosis. The coach proposes concrete if-then plans you accept or decline, and suppresses proactive check-ins when there's nothing genuine to say. Bring your own OpenAI or Anthropic key, or point at a local endpoint (Ollama, LM Studio, vLLM) so nothing leaves your network. - **Clinician-ready output.** A doctor-report PDF generated client-side, a read-only HL7 FHIR R4 API, and scoped, time-limited share links you revoke after the visit. - **Private by construction.** AES-256-GCM encryption at rest with zero-downtime key rotation, passkey login, server-side sessions, strict CSP — and no third-party tracking anywhere. -- **Built to be lived in.** Installable PWA with offline support, medication reminders over APNs, Telegram, ntfy, and Web Push, a sub-second dashboard on years of imported history — in English, German, Spanish, French, Italian, and Polish end to end. +- **Built to be lived in.** Installable PWA with offline support, medication reminders over APNs, Telegram, ntfy, and Web Push, a sub-second dashboard on years of imported history — in English, German, Spanish, French, Italian, Polish, and Korean end to end. The full feature tour, integration guides, and API reference live at [docs.healthlog.dev](https://docs.healthlog.dev). diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index ebba70075..fcc3c3e79 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -20248,6 +20248,7 @@ components: - es - it - pl + - ko practiceName: type: string maxLength: 120 @@ -22343,6 +22344,7 @@ components: - fr - it - pl + - ko today: description: "Optional override of the reference date used to resolve relative phrases (\"tomorrow\", \"next Monday\"). Format: `YYYY-MM-DD`. Defaults to the server's UTC day." @@ -23234,6 +23236,7 @@ components: - es - it - pl + - ko description: One of the interface locales this build ships. required: - locale @@ -27714,6 +27717,7 @@ components: - fr - it - pl + - ko timezone: type: string minLength: 1 @@ -27929,6 +27933,7 @@ components: - fr - it - pl + - ko - type: "null" timezone: type: string @@ -46021,6 +46026,7 @@ components: - es - it - pl + - ko required: - locale additionalProperties: false @@ -66553,6 +66559,7 @@ components: - fr - it - pl + - ko - type: "null" timezone: type: string diff --git a/messages/ko.json b/messages/ko.json new file mode 100644 index 000000000..8270f7797 --- /dev/null +++ b/messages/ko.json @@ -0,0 +1,10526 @@ +{ + "common": { + "save": "저장", + "conflictReloaded": "서버에서 다시 불러왔어요. 변경 내용을 다시 입력해 주세요.", + "cancel": "취소", + "delete": "삭제", + "undo": "실행 취소", + "edit": "편집", + "moreOptions": "옵션 더 보기", + "create": "만들기", + "close": "닫기", + "loading": "불러오는 중…", + "loadFailed": "불러오지 못했어요", + "error": "오류", + "saved": "저장됨", + "pullToRefresh": "새로고침 중…", + "no": "아니요", + "noData": "데이터가 없어요", + "optional": "선택", + "active": "사용 중", + "inactive": "사용 안 함", + "disabled": "비활성", + "networkError": "네트워크 오류", + "openDatePicker": "날짜 선택기 열기", + "openTimePicker": "시각 선택기 열기", + "hours": "시", + "minutes": "분", + "feet": "ft", + "inches": "in", + "heightFeet": "키(ft)", + "heightInches": "키(in)", + "period": "오전/오후", + "am": "오전", + "pm": "오후", + "unknownError": "알 수 없는 오류", + "copied": "복사했어요!", + "or": "또는", + "unknown": "알 수 없음", + "add": "추가", + "import": "불러오기", + "test": "테스트", + "reset": "초기화", + "copy": "복사", + "copyDetails": "상세 정보 복사", + "retry": "다시 시도", + "showPassword": "비밀번호 표시", + "hidePassword": "비밀번호 숨기기", + "learnMore": "자세히 보기", + "dismiss": "닫기", + "moreInfo": "추가 정보", + "durationHoursMinutes": "{hours}시간 {minutes}분", + "durationMinutes": "{minutes}분" + }, + "apiErrors": { + "measurement": { + "create": { + "invalid": "측정 내용을 확인하고 다시 시도해 주세요." + }, + "duplicate_timestamp": "이 시각에 이미 측정 기록이 있어요." + }, + "mood": { + "list": { + "invalid": "기분 목록 필터가 올바르지 않아요." + }, + "create": { + "invalid": "기분 기록을 확인하고 다시 시도해 주세요." + }, + "update": { + "invalid": "기분 기록을 확인하고 다시 시도해 주세요." + }, + "duplicate_timestamp": "이 시각에 이미 기분 기록이 있어요.", + "not_found": "이 기분 기록은 더 이상 없어요.", + "ratedFactor": { + "out_of_range": "요인 평가 값이 허용 범위를 벗어났어요." + } + }, + "labs": { + "create": { + "invalid": "검사 결과를 확인하고 다시 시도해 주세요." + }, + "result": { + "notFound": "이 검사 결과는 더 이상 없어요." + }, + "biomarker": { + "notFound": "이 바이오마커는 더 이상 없어요.", + "duplicate": "같은 이름의 바이오마커가 이미 있어요.", + "create": { + "invalid": "바이오마커 정보를 확인하고 다시 시도해 주세요." + }, + "update": { + "invalid": "바이오마커 정보를 확인하고 다시 시도해 주세요.", + "referenceRangeInvalid": "참고 범위의 하한이 상한보다 클 수 없어요." + } + }, + "update": { + "invalid": "검사 결과를 확인하고 다시 시도해 주세요.", + "linkedFieldsImmutable": "이름·단위·패널·참고 범위는 바이오마커에서 편집해 주세요.", + "qualitativeExpected": "이 정성 측정값은 텍스트로 입력해 주세요.", + "numericExpected": "이 수치 측정값은 숫자로 입력해 주세요.", + "referenceRangeInvalid": "참고 범위의 하한이 상한보다 클 수 없어요." + } + }, + "onboarding": { + "disclaimer": { + "invalid": "의료 고지 사항을 다시 확인해 주세요." + }, + "tour": { + "invalid": "둘러보기 진행 상황을 저장하지 못했어요." + }, + "complete": { + "invalid": "프로필 정보를 확인하고 다시 시도해 주세요." + }, + "user": { + "notFound": "계정을 찾지 못했어요." + }, + "step": { + "rateLimited": "시도가 너무 많아요. 잠시 후 다시 시도해 주세요.", + "invalid": "이 시작 설정 단계를 저장하지 못했어요.", + "completed": "시작 설정이 이미 끝났어요.", + "outOfOrder": "시작 설정 진행 상황이 바뀌었어요. 새로고침하고 계속해 주세요.", + "concurrent": "시작 설정 진행 상황이 바뀌었어요. 새로고침하고 계속해 주세요." + } + }, + "profile": { + "update": { + "nothingSaved": "저장된 내용이 없어요. 표시된 항목을 확인하고 다시 시도해 주세요.", + "invalidBody": "프로필 변경 내용을 읽지 못했어요.", + "emailInUse": "이미 사용 중인 이메일 주소예요." + } + } + }, + "doctorReport": { + "title": "건강 리포트", + "anamnesisTitle": "건강 프로필", + "anamnesisNotRecorded": "기록 없음", + "anamnesisUnreadable": "저장된 값을 읽지 못했어요", + "anamnesisConditions": "만성 질환", + "anamnesisSmoking": "흡연 상태", + "anamnesisAlcohol": "음주 패턴", + "anamnesisShiftSchedule": "근무 일정", + "anamnesisColFact": "프로필 항목", + "anamnesisColValue": "기록된 값", + "subtitle": "HealthLog — 개인 건강 리포트", + "patient": "환자", + "dateOfBirth": "생년월일", + "gender": "성별", + "genderMale": "남성", + "genderFemale": "여성", + "genderOther": "논바이너리", + "height": "키", + "period": "리포트 기간", + "createdOn": "작성일", + "emergency": { + "title": "응급 정보", + "subtitle": "응급 상황에서 참고하세요. 본인이 기록한 정보라 가능하면 확인이 필요해요.", + "none": "기록 없음", + "unreadable": "저장된 값을 읽지 못했어요", + "bloodType": "혈액형", + "bloodTypeUnknown": "모름", + "bloodTypeValues": { + "A_POS": "A형 Rh+", + "A_NEG": "A형 Rh-", + "B_POS": "B형 Rh+", + "B_NEG": "B형 Rh-", + "AB_POS": "AB형 Rh+", + "AB_NEG": "AB형 Rh-", + "O_POS": "O형 Rh+", + "O_NEG": "O형 Rh-" + }, + "severeAllergies": "중증 알레르기", + "activeMedications": "현재 복용 약", + "chronicConditions": "만성 질환", + "implants": "이식물 및 의료기기", + "advanceDirective": "사전연명의료의향서", + "advanceDirectiveValues": { + "EXISTS": "있음", + "NONE": "없음", + "UNKNOWN": "미기재" + }, + "organDonor": "장기기증 의사", + "organDonorValues": { + "YES": "예", + "NO": "아니요", + "UNKNOWN": "미기재" + }, + "contacts": "비상 연락처", + "notes": "추가 메모" + }, + "vitalsTitle": "활력징후 — 요약", + "colParameter": "항목", + "colCurrent": "현재", + "colAvgPeriod": "기간 평균", + "colMin": "최소", + "colMax": "최대", + "colN": "건수", + "bpClassificationTitle": "혈압 — ESH 분류 (2023)", + "avgBp": "평균 혈압", + "classification": "분류", + "bmiTitle": "체질량지수 (BMI)", + "bmiRow": "BMI: {bmi} kg/m² — 분류: {class} (WHO)", + "complianceTitle": "복약 순응도", + "glp1Title": "GLP-1 치료", + "glp1WeightSummary": "리포트 기간 체중: {start} kg → {end} kg ({delta} kg)", + "glp1CurrentDose": "현재 용량: {value} {unit} ({since}부터)", + "glp1Compliance": "순응도: {taken} / {total} ({rate}%)", + "glp1SideEffectsTitle": "기록된 부작용", + "colGlp1Date": "날짜", + "colGlp1Dose": "용량", + "colGlp1Note": "메모", + "colGlp1SideEffect": "부작용", + "colMedication": "약", + "colTaken": "복용", + "colSkipped": "건너뜀", + "colMissed": "누락", + "colExpected": "예정", + "colComplianceRate": "순응률", + "moodTitle": "기분 추이", + "moodSummary": "평균: {avg} / 5.0 · 기록: {count}건 · 범위: {min}–{max}", + "colMood": "기분", + "colCount": "횟수", + "colShare": "비율", + "footerDisclaimer1": "이 리포트는 직접 기록한 데이터에서 자동으로 생성되었으며,", + "footerDisclaimer2": "의사와 환자의 대화를 돕기 위한 것이에요. 의학적 진단을 대신하지 않으며, 상관관계가 인과관계를 뜻하지는 않아요.", + "footerSource": "출처: HealthLog | 시간대: {timezone} | 작성: {timestamp}", + "unitSteps": "걸음", + "bmiUnderweight": "저체중", + "bmiNormal": "정상 체중", + "bmiOverweight": "과체중 (비만 전 단계)", + "bmiObeseGrade1": "비만 1단계", + "bmiObeseGrade2": "비만 2단계", + "bmiObeseGrade3": "비만 3단계", + "bpOptimal": "최적", + "bpNormal": "정상", + "bpHighNormal": "정상 상위", + "bpHypertensionGrade1": "고혈압 1기", + "bpHypertensionGrade2": "고혈압 2기", + "bpHypertensionGrade3": "고혈압 3기", + "moodAwful": "매우 나쁨", + "moodBad": "나쁨", + "moodNeutral": "보통", + "moodGood": "좋음", + "moodGreat": "매우 좋음", + "typeGlucoseFasting": "혈당 (공복)", + "typeGlucosePostprandial": "혈당 (식후)", + "typeGlucoseRandom": "혈당 (임의)", + "typeGlucoseBedtime": "혈당 (취침 전)", + "glucoseClassificationTitle": "혈당 — 참고 범위 (ADA 2024 / DDG)", + "glucoseRow": "{label}: 평균 {avg} {unit} (목표 {rangeMin}–{rangeMax}) — {class}", + "glucoseInTarget": "목표 범위 내", + "glucoseBelowTarget": "목표 미만", + "glucoseAboveTarget": "목표 초과", + "practiceLabel": "진료기관", + "insurer": "건강보험사", + "insuranceNumber": "보험 번호", + "summaryTitle": "임상 요약", + "summaryReadings": "{days}일간 기록: {params}개 항목에서 측정 {count}건.", + "summaryTrend": "{label}: 최근 {latest} {arrow}", + "summaryAdherence": "복약 순응도: {rate}% (가중 평균).", + "chartsTitle": "추이 차트", + "packageReadme": "HealthLog 건강 기록\n\n이 패키지에 들어 있는 파일:\n- report.pdf: 의사에게 보여줄 건강 리포트.\n- bundle.json: 같은 데이터를 HL7 FHIR R4 document Bundle로 담은 파일, ePA 호환.\n\nHealthLog로 생성했어요.", + "wellnessTitle": "웰니스 요약", + "wellnessDisclaimer": "기록된 지표로 계산한 서술적 웰니스 점수예요 (0–100). 임상적 평가나 진단이 아니에요.", + "wellnessRecovery": "회복", + "wellnessStress": "스트레스", + "wellnessStrain": "부하", + "cycleTitle": "생리 주기", + "cycleLmp": "최근 생리", + "cycleAvgLength": "평균 주기 길이", + "cycleAvgPeriod": "평균 생리 기간", + "cyclePhase": "현재 단계", + "cycleDays": "일", + "cycleColStart": "생리 시작", + "cycleColLength": "주기 길이 (일)", + "cycleColPeriod": "생리 기간 (일)", + "cyclePhases": { + "MENSTRUAL": "생리기", + "FOLLICULAR": "난포기", + "OVULATORY": "배란기", + "LUTEAL": "황체기" + }, + "labsTitle": "검사 결과", + "labsColAnalyte": "검사 항목", + "labsColValue": "값", + "labsColReference": "참고 범위", + "labsColStatus": "상태", + "labsColDate": "날짜", + "glucoseClinical": { + "title": "혈당 개요 (임상)", + "colMetric": "지표", + "colValue": "값", + "tirInRange": "목표 범위 내 시간 (70–180 mg/dL)", + "tirLow": "낮음 (< 70 mg/dL)", + "tirVeryLow": "매우 낮음 (< 54 mg/dL)", + "tirHigh": "높음 (> 180 mg/dL)", + "tirVeryHigh": "매우 높음 (> 250 mg/dL)", + "mean": "평균 혈당", + "gmi": "혈당 관리 지표 (GMI)", + "eA1c": "추정 당화혈색소 (A1C)", + "cv": "변동성 (CV%)", + "spotCaveat": "개별 측정값에서 얻은 추정치이고 연속혈당측정 프로필이 아니에요. 임상적 판정이 아니라 대략적인 방향으로 봐 주세요." + }, + "illnessTitle": "질환 및 아픈 날", + "illnessOngoing": "진행 중", + "illnessColCondition": "질환", + "illnessColType": "유형", + "illnessColLifecycle": "경과", + "illnessColOnset": "발병", + "illnessColResolved": "회복", + "allergiesTitle": "알레르기 및 불내성", + "reactionUnreadable": "반응이 기록됐지만 복호화하지 못했어요", + "allergiesColSubstance": "물질", + "allergiesColCategory": "분류", + "allergiesColKind": "종류", + "allergiesColSeverity": "심각도", + "allergiesColReaction": "반응", + "allergiesColStatus": "상태", + "familyHistoryTitle": "가족력", + "familyHistoryColRelationship": "관계", + "familyHistoryColCondition": "질환", + "familyHistoryColAgeAtOnset": "발병 나이", + "labsSourceRangeNote": "* 참고 범위는 검사 결과지 자체에서 가져온 값이라 저장된 범위와 달라요:", + "labsCatalogRangeLabel": "저장된 범위", + "visitsTitle": "진료", + "visitsColDate": "날짜", + "visitsColPractice": "진료기관", + "visitsColKind": "종류", + "visitsColReason": "사유", + "visitsColOutcome": "결과", + "visitsColConditions": "질환", + "immunizationsTitle": "예방접종", + "immunizationsColDate": "날짜", + "immunizationsColVaccine": "백신", + "immunizationsColDose": "차수", + "immunizationsColLot": "로트번호" + }, + "errorBoundary": { + "title": "문제가 생겼어요", + "description": "예기치 못한 오류가 생겼어요. 다시 시도하거나, 기술 정보를 복사하거나, 버그를 신고할 수 있어요." + }, + "notFound": { + "title": "존재하지 않는 페이지예요.", + "backToDashboard": "대시보드로 돌아가기" + }, + "offlineBanner": { + "message": "연결 없음 — 온라인으로 돌아올 때까지 저장할 수 없어요.", + "saveFailed": "저장하지 못했어요 — 오프라인 상태예요. 온라인으로 돌아온 뒤 다시 시도해 주세요.", + "showingCached": "마지막으로 동기화한 데이터를 보여주고 있어요." + }, + "demoBanner": { + "message": "데모 모드 — 변경 내용은 저장되지 않아요." + }, + "nav": { + "dashboard": "대시보드", + "measurements": "측정", + "medications": "복약", + "insights": "인사이트", + "achievements": "업적", + "settings": "설정", + "admin": "관리자 콘솔", + "logout": "로그아웃", + "login": "로그인", + "notifications": "알림", + "theme": "테마", + "themeSystem": "시스템", + "themeDark": "다크", + "themeLight": "라이트", + "collapseSidebar": "접기", + "userMenu": "사용자 메뉴", + "accountSettings": "계정 설정", + "mood": "기분", + "coach": "코치", + "skipToContent": "본문으로 건너뛰기", + "sidebar": "사이드바", + "mainNavigation": "주 메뉴", + "mobileNavigation": "모바일 메뉴", + "loadingScreen": "불러오는 중…", + "more": "더 보기", + "moreSheetTitle": "더 보기", + "moreSheetDescription": "HealthLog의 나머지 기능으로 이동해요.", + "capture": { + "title": "기록", + "description": "무엇을 기록할까요?", + "measurement": "측정", + "measurementDescription": "체중, 혈압, 맥박 등", + "medication": "약", + "medicationDescription": "복용으로 표시하기", + "mood": "기분", + "moodDescription": "기분과 태그 기록하기" + }, + "cycle": "생리 주기", + "labs": "검사", + "profile": "프로필", + "vorsorge": "건강검진", + "illness": "아픈 날", + "documents": "문서", + "vaccinations": "예방접종", + "mentalWellbeing": "정신 건강" + }, + "auth": { + "login": "로그인", + "register": "회원가입", + "username": "사용자명", + "password": "비밀번호", + "email": "이메일 주소", + "emailPlaceholder": "name@example.com", + "emailOrUsername": "이메일 주소 또는 사용자명", + "emailOrUsernamePlaceholder": "name@example.com 또는 사용자명", + "loginWithPasskey": "패스키로 로그인", + "loginWithPassword": "비밀번호로 로그인", + "noAccount": "계정이 없나요?", + "hasAccount": "이미 계정이 있나요?", + "loginFailed": "로그인 실패", + "registerFailed": "회원가입 실패", + "invitedBanner": "초대를 받았어요 — 회원가입이 닫혀 있어도 계정을 만들 수 있어요.", + "createAccount": "계정 만들기", + "newAccount": "새 계정 만들기", + "backToPasskey": "패스키로 돌아가기", + "passkeySetupLater": "패스키는 설정에서 언제든 등록할 수 있어요.", + "passwordMinLength": "최소 12자", + "passwordTooShort": "비밀번호는 최소 {minLength}자여야 해요.", + "privacyPolicy": "개인정보 처리방침", + "passwordBreached": "이 비밀번호는 유출 사례에 등장한 적이 있어요. 다른 비밀번호를 골라 주세요.", + "oidc": { + "signInDefault": "SSO로 로그인", + "errorDenied": "로그인이 취소됐어요.", + "errorNoEmail": "제공자가 인증된 이메일 주소를 전달하지 않았어요.", + "errorEmailUnverified": "제공자가 이메일 주소를 인증됨으로 표시하지 않았어요.", + "errorIdentityConflict": "그 이메일 주소는 다른 SSO 계정에 이미 연결돼 있어요.", + "errorRegistrationDisabled": "일치하는 계정이 없고, 지금은 회원가입이 닫혀 있어요.", + "errorFailed": "싱글 사인온에 실패했어요. 다시 시도해 주세요." + }, + "native": { + "signingInToApp": "HealthLog 앱에 로그인하는 중" + }, + "mfa": { + "title": "2단계 인증", + "totpHint": "인증 앱에 표시된 6자리 코드를 입력해 주세요.", + "recoveryHint": "저장해 둔 복구 코드 중 하나를 입력해 주세요.", + "securityKeyHint": "등록한 보안 키로 계속 진행해 주세요.", + "codeLabel": "인증 코드", + "recoveryLabel": "복구 코드", + "verify": "확인", + "verifyFailed": "확인에 실패했어요. 다시 시도해 주세요.", + "useSecurityKey": "보안 키 사용", + "useRecoveryCode": "복구 코드 사용", + "useAuthenticator": "인증 앱 사용", + "backToLogin": "로그인으로 돌아가기", + "rememberDevice": "이 기기를 30일간 신뢰" + }, + "enrollMfa": { + "title": "2차 인증 수단 설정", + "body": "관리자가 이 계정에 2차 인증 수단을 요구해요. 인증 앱이나 보안 키를 추가하고 계속해 주세요.", + "signOut": "로그아웃" + } + }, + "dashboard": { + "title": "대시보드", + "greeting": { + "morning": "좋은 아침이에요", + "day": "안녕하세요", + "evening": "좋은 저녁이에요" + }, + "welcomeBack": "{greeting}, 다시 오셨네요.", + "welcomeBackWithName": "{greeting}, {name}님, 다시 오셨네요.", + "greetingSalutation": "안녕하세요, {name}님", + "streakLabel": "일 연속", + "weight": "체중", + "bloodPressure": "혈압", + "pulse": "맥박", + "bodyFat": "체지방", + "sleep": "수면", + "steps": "걸음 수", + "vo2Max": "VO₂ max", + "vo2MaxUnit": "mL/(kg·min)", + "bpInTarget": "목표 내 혈압", + "weightShort": "체중", + "bloodPressureSysShort": "혈압(수축기)", + "bloodPressureDiaShort": "혈압(이완기)", + "pulseShort": "맥박", + "bodyFatShort": "체지방", + "moodShort": "기분", + "sleepShort": "수면", + "stepsShort": "걸음 수", + "vo2MaxShort": "VO₂ max", + "bpInTargetWindow90": "목표 내 혈압 · 90일", + "bpInTargetWindowDynamic": "목표 내 혈압 · {days}일", + "bpInTargetCollecting": "데이터 모으는 중", + "medications": "복약", + "quickAddMeasurement": "측정 기록", + "quickAddMood": "기분 기록", + "quickAddMedicationIntake": "복용 기록", + "customizeDashboard": "대시보드 맞춤 설정", + "medicationIntakeQuickAdd": { + "sheetTitle": "복용 기록", + "sheetDescription": "몇 번의 탭으로 약과 용량, 시각을 기록해요.", + "medicationLabel": "약", + "medicationPlaceholder": "약 선택", + "doseLabel": "용량", + "dosePlaceholder": "예: 5 mg", + "doseHint": "기본 용량이 미리 채워져 있어요. 이번 복용만 다르게 기록하려면 수정하세요.", + "timeLabel": "복용 시각", + "saveError": "복용 기록을 저장하지 못했어요.", + "emptyTitle": "등록된 약이 없어요", + "emptyDescription": "먼저 일정이 있는 약을 만든 뒤, 여기로 돌아와 복용을 기록하세요.", + "emptyCta": "약 추가" + }, + "quickEntryDiscard": { + "title": "이 기록을 버릴까요?", + "description": "저장하지 않은 입력이 있어요. 폼을 닫고 입력한 내용을 버릴까요?", + "confirm": "버리기", + "cancel": "계속 작성" + }, + "compliance7d": "복약 순응도 (7일)", + "mood": "기분", + "customizeTitle": "대시보드 레이아웃", + "heroItemsTitle": "오늘의 하이라이트", + "heroItemsDescription": "오늘의 하이라이트에 표시할 항목 유형을 고르세요.", + "heroItemsNotificationNote": "알림 설정은 따로 관리해요.", + "heroContentTitle": "상단 카드 표시 내용", + "heroContentScore": "건강 점수와 하루 요약", + "heroContentReminders": "오늘의 하이라이트", + "heroItems": { + "coach_checkin": "코치 체크인", + "dose_window": "복용 일정", + "preventive_care": "예방 관리", + "sync_issue": "동기화 문제", + "milestone": "마일스톤", + "ecg_new_recording": "새 심전도 기록", + "tension_window": "긴장 구간", + "same_time_baseline": "같은 시간대 활동", + "upcoming_visit": "진료 예약" + }, + "layoutReset": "기본값으로 초기화", + "layoutResetTitle": "대시보드 레이아웃을 초기화할까요?", + "layoutResetBody": "타일 배치와 오늘의 하이라이트 선택이 기본값으로 돌아가요.", + "layoutResetConfirm": "레이아웃 초기화", + "layoutSaveSuccess": "대시보드 레이아웃을 저장했어요", + "layoutSaveError": "대시보드 레이아웃을 저장하지 못했어요", + "layoutConflictReloaded": "서버에서 레이아웃을 다시 불러왔어요. 변경을 다시 적용해 주세요.", + "layoutResetSuccess": "기본 레이아웃으로 되돌렸어요", + "layoutUsingDefaults": "기본 레이아웃 사용 중", + "layoutCustomized": "맞춤 레이아웃 적용 중", + "moveUp": "위로 이동", + "moveDown": "아래로 이동", + "layoutTileColumn": "타일", + "layoutChartColumn": "차트", + "layoutNativeOnlyTitle": "모바일 앱에 표시", + "layoutNativeOnlyHint": "이 위젯들은 모바일 앱에서만 그려지고 이 페이지에는 나오지 않아요. 같은 레이아웃에 저장되니 표시 여부는 여기서 설정해요.", + "dragHandle": "끌어서 순서 변경", + "dragHandleHint": "핸들을 끌어 이 위젯의 순서를 바꿔요. 위·아래 화살표 버튼을 쓰거나, 핸들에 포커스를 두고 스페이스와 화살표 키를 눌러도 돼요.", + "emptyTitle": "대시보드가 비어 있어요", + "emptyDescription": "첫 측정이나 기분 기록을 남겨 보세요. 데이터가 쌓이면 타일과 차트가 자동으로 나타나요.", + "emptyAddMeasurement": "첫 측정 추가", + "staleHint": "마지막 측정값 {count}일 전", + "staleHintWeeksOne": "마지막 측정값 1주 전", + "staleHintWeeksOther": "마지막 측정값 {count}주 전", + "staleHintWeeksFew": "마지막 측정값 {count}주 전", + "staleHintMonthsOne": "마지막 측정값 1개월 전", + "staleHintMonthsOther": "마지막 측정값 {count}개월 전", + "staleHintMonthsFew": "마지막 측정값 {count}개월 전", + "recentWorkouts": { + "title": "최근 운동", + "viewAll": "전체 보기", + "empty": { + "title": "운동 기록이 없어요", + "cta": "설정에서 데이터 소스를 연결하면 운동이 여기에 보여요." + } + }, + "metric": { + "title": { + "weight": "체중", + "bloodPressure": "혈압", + "pulse": "맥박", + "bodyFat": "체지방", + "glucose": "혈당", + "sleep": "수면", + "steps": "걸음 수", + "totalBodyWater": "체수분", + "boneMass": "골량", + "oxygenSaturation": "산소포화도", + "mood": "기분", + "bmi": "BMI" + }, + "unit": { + "weight": "kg", + "bloodPressure": "mmHg", + "pulse": "bpm", + "bodyFat": "%", + "glucose": "mg/dL", + "sleep": "h", + "steps": "걸음", + "totalBodyWater": "kg", + "boneMass": "kg", + "oxygenSaturation": "%", + "mood": "/5", + "bmi": "kg/m²" + } + } + }, + "measurements": { + "title": "측정", + "subtitle": "측정값을 기록하고 전체 기록을 살펴봐요.", + "addMeasurement": "추가", + "editMeasurement": "측정 편집", + "type": "유형", + "value": "값", + "date": "날짜", + "source": "출처", + "notes": "메모", + "typeWeight": "체중", + "typeBloodPressure": "혈압", + "typeBpSys": "수축기", + "typeBpDia": "이완기", + "typePulse": "맥박", + "typeBodyFat": "체지방", + "typeSleep": "수면", + "typeSteps": "걸음 수", + "typeBloodGlucose": "혈당", + "typeTotalBodyWater": "체수분", + "typeBoneMass": "골량", + "typeOxygenSaturation": "산소포화도", + "typeHeartRateVariability": "심박변이도", + "typeRestingHeartRate": "안정 시 심박수", + "typeActiveEnergyBurned": "활동 에너지", + "typeFlightsClimbed": "오른 층수", + "typeWalkingRunningDistance": "걷기 + 달리기 거리", + "typeVo2Max": "VO₂ max", + "typeBodyTemperature": "체온", + "typeFatFreeMass": "무지방량", + "typeFatMass": "체지방량", + "typeMuscleMass": "근육량", + "typeSkinTemperature": "피부 온도", + "typePulseWaveVelocity": "맥파 속도", + "typeVascularAge": "혈관 나이", + "typeVisceralFat": "내장지방", + "typeAudioExposureEnv": "소음 노출 (주변)", + "typeAudioExposureHeadphone": "소음 노출 (헤드폰)", + "typeTimeInDaylight": "햇빛 노출 시간", + "typeWalkingSteadiness": "보행 안정성", + "typeAudioExposureEvent": "큰 소리 청취 이벤트", + "typeRespiratoryRate": "호흡수", + "typeBodyMassIndex": "체질량지수", + "typeLeanBodyMass": "제지방량", + "typeWalkingHeartRateAverage": "보행 심박수 (평균)", + "typeWalkingAsymmetry": "보행 비대칭", + "typeWalkingDoubleSupport": "보행 이중 지지", + "typeWalkingStepLength": "보폭", + "typeWalkingSpeed": "보행 속도", + "glucoseContext": "상황", + "glucoseContextFasting": "공복 (8h 이상 금식)", + "glucoseContextPostprandial": "식후 (식사 2h 후)", + "glucoseContextRandom": "임의 / 수시", + "glucoseContextBedtime": "취침 전", + "unitHours": "시간", + "unitSteps": "걸음", + "sourceManual": "직접 입력", + "sourceWithings": "Withings", + "sourceImport": "불러오기", + "sourceAppleHealth": "Apple Health", + "systolicLabel": "수축기 (mmHg)", + "diastolicLabel": "이완기 (mmHg)", + "pulseLabel": "맥박 (bpm)", + "valueWithUnit": "값 ({unit})", + "timestamp": "시간", + "timestampBackdateHint": "날짜와 시간을 바꾸면 지난 측정값도 기록할 수 있어요.", + "notesPlaceholder": "예: 식사 후", + "formReset": "양식 초기화", + "saveError": "저장하지 못했어요", + "duplicateTimestamp": "선택한 시각에 같은 유형의 측정이 이미 있어요.", + "serverOwnedSource": "연동된 출처의 값은 수정할 수 없어요 — 시간과 메모는 수정할 수 있어요.", + "deleteError": "삭제하지 못했어요", + "deletedToast": "측정을 삭제했어요", + "restoredToast": "측정을 복원했어요", + "restoreError": "복원하지 못했어요", + "invalidValue": "올바른 값을 입력해 주세요.", + "invalidTimestamp": "올바른 시간을 선택해 주세요.", + "loginRequired": "측정을 보려면 로그인해 주세요.", + "allTypes": "모든 유형", + "measurementCount": "{count}개 측정", + "emptyTitle": "아직 측정이 없어요", + "emptyDescription": "체중, 혈압, 맥박 같은 수치를 기록하면 추이를 볼 수 있어요.", + "emptyFilteredTitle": "이 필터에 맞는 측정이 없어요", + "emptyFilteredDescription": "다른 유형을 골라 보거나 필터를 지우면 전체 측정을 볼 수 있어요.", + "emptyAddFirst": "첫 측정 추가", + "emptyResetFilter": "모든 유형 보기", + "comment": "코멘트", + "actions": "작업", + "deleteConfirmTitle": "측정을 삭제할까요?", + "deleteConfirmDescription": "이 측정을 삭제해요. 잠시 동안은 되돌릴 수 있어요.", + "pageInfo": "{total}쪽 중 {page}쪽", + "previousPage": "이전 쪽", + "nextPage": "다음 쪽", + "dailyTotalCaption": "({count}개 샘플)", + "openMetricInsights": "{type} 인사이트 열기", + "sleepNapCaption": "+낮잠 {count}회", + "sleepAwakeningsCaption": "깬 횟수 {count}회", + "expandDay": "이 날의 샘플 보기", + "collapseDay": "이 날의 샘플 숨기기", + "loadError": "측정을 불러오지 못했어요.", + "typeCardioRecovery": "심박 회복", + "typeWristTemperature": "손목 온도", + "typeFallCount": "낙상", + "typeSixMinuteWalkDistance": "6분 보행 거리", + "typeStairAscentSpeed": "계단 오르기 속도", + "typeStairDescentSpeed": "계단 내려가기 속도", + "typeBreathingDisturbances": "수면 중 호흡 장애", + "typeIrregularRhythmNotification": "불규칙 심장 리듬 알림", + "typeHighHeartRateEvent": "높은 심박수 알림", + "typeLowHeartRateEvent": "낮은 심박수 알림", + "typeWalkingSteadinessEvent": "보행 안정성 경고", + "typeBreathingDisturbanceEvent": "호흡 장애", + "typeRecoveryScore": "회복 점수", + "typeStressScore": "스트레스 점수", + "typeStrainScore": "부하 점수", + "typeHrvRmssd": "심박변이도 (RMSSD)", + "typeDayStrain": "하루 부하", + "typeWorkoutStrain": "운동 부하", + "typeSleepPerformance": "수면 성과", + "typeSleepEfficiency": "수면 효율", + "typeSleepConsistency": "수면 규칙성", + "typeSleepNeed": "필요 수면 시간", + "typeEnergyExpenditureKj": "에너지 소비량", + "typeAverageHeartRate": "평균 심박수", + "typeMaxHeartRate": "최대 심박수", + "typeSleepDisturbanceCount": "수면 방해", + "typeAnsCharge": "ANS 충전도", + "typeCardioLoad": "심폐 부하", + "typeSleepScore": "수면 점수", + "typeBodyTemperatureDeviation": "체온 편차", + "typeResilience": "회복 탄력성", + "sourceComputed": "계산값", + "sourceWhoop": "WHOOP", + "sourceFitbit": "Fitbit", + "bulkDeleteSuccess": "{count}개 측정을 삭제했어요", + "bulkDeleteSuccessOne": "측정을 삭제했어요", + "bulkDeleteError": "선택한 측정을 삭제하지 못했어요", + "bulkDeleteConfirmTitle": "측정 {count}개를 삭제할까요?", + "bulkDeleteConfirmTitleOne": "이 측정을 삭제할까요?", + "bulkDeleteConfirmBody": "선택한 측정 {count}개를 삭제해요. 잠시 동안은 되돌릴 수 있어요.", + "bulkDeleteConfirmBodyOne": "선택한 측정을 삭제해요. 잠시 동안은 되돌릴 수 있어요.", + "typePhq9Score": "PHQ-9 점수", + "typeGad7Score": "GAD-7 점수", + "typeGripStrength": "악력", + "typePainNrs": "통증 (0–10)", + "typeWaistCircumference": "허리둘레", + "typeWaistToHeight": "허리-키 비율", + "unitScore": "점", + "unitRatio": "비율", + "typeWho5Score": "WHO-5 점수", + "typeSciScore": "SCI 점수" + }, + "metricCatalog": { + "title": "HealthLog가 기록할 수 있는 것", + "description": "아직 데이터가 없더라도, HealthLog가 지원하는 모든 지표 영역과 그 데이터를 채워줄 방법이에요.", + "statusPresent": "기록 중", + "statusAbsent": "아직 기록 없음", + "statusModuleOff": "꺼짐", + "loadError": "기록 상태를 불러오지 못했어요.", + "connectCta": "데이터 원본 연결", + "viewCta": "보기", + "ecg": { + "source": "호환되는 워치(현재 Withings ScanWatch)의 단일 유도 ECG 기록을 자동으로 동기화해요." + }, + "watchdog": { + "title": "자동 감시", + "description": "HealthLog는 백그라운드에서도 조용히 지켜봐요 — 연결된 워치의 불규칙 리듬·호흡 장애 알림, 기준값 이탈 건강 상태 경고, 최근 두 번의 검사 패널 사이의 눈에 띄는 변화를 보고, 특별한 게 있을 때만 카드를 보여줘요. 이런 원본이 하나라도 연결돼 있다면 카드가 없는 건 기능 누락이 아니라 좋은 신호예요. 아직 아무것도 연결하지 않았다면 감시할 대상 자체가 없어요." + } + }, + "mood": { + "title": "기분", + "subtitle": "기분 추적과 기록", + "mentalWellbeingLink": "마음 건강 체크인 하기", + "addEntry": "추가", + "editEntry": "기록 편집", + "moodLevel": "기분", + "heroQuestion": "기분이 어때요?", + "factorYes": "예", + "factorNo": "아니요", + "factorRatingOption": "{label}: {max} 중 {value}", + "tags": "태그", + "tagsPlaceholder": "예: 운동, 일, 가족", + "tagsHelp": "쉼표로 구분", + "timestamp": "시각", + "date": "날짜", + "source": "출처", + "levelSuperGut": "최고", + "levelGut": "좋음", + "levelOkay": "보통", + "levelSchlecht": "나쁨", + "levelLausig": "최악", + "sourceMoodlog": "moodLog", + "allMoods": "모든 기분", + "entryCount": "{count}개 기록", + "emptyTitle": "아직 기분 기록이 없어요", + "emptyDescription": "오늘 기분을 기록하면 시간이 지나며 패턴이 보여요.", + "emptyFilteredTitle": "이 필터에 맞는 기분 기록이 없어요", + "emptyFilteredDescription": "다른 기분 단계를 고르거나 필터를 지워서 모든 기록을 보세요.", + "emptyAddFirst": "첫 기분 기록하기", + "emptyResetFilter": "모든 기분 보기", + "actions": "작업", + "formReset": "양식 초기화", + "noteCharCount": "{count}/{max}", + "formResetConfirmTitle": "양식을 초기화할까요?", + "formResetConfirmBody": "입력한 기분, 태그, 메모가 모두 지워져요. 되돌릴 수 없어요.", + "formResetConfirmAction": "초기화", + "saveError": "저장하지 못했어요", + "deleteError": "삭제하지 못했어요", + "deletedToast": "기록을 삭제했어요", + "restoredToast": "기록을 되살렸어요", + "restoreError": "되살리지 못했어요", + "invalidTimestamp": "올바른 시각을 선택해 주세요.", + "deleteConfirmTitle": "기록을 삭제할까요?", + "deleteConfirmDescription": "이 기분 기록을 삭제해요. 잠깐 동안은 되돌릴 수 있어요.", + "pageInfo": "{total}쪽 중 {page}쪽", + "previousPage": "이전 쪽", + "nextPage": "다음 쪽", + "loginRequired": "기분 기록을 보려면 로그인해 주세요.", + "note": "메모", + "noteCollapse": "메모 접기", + "noteExpand": "메모 전체 보기", + "notePlaceholder": "지금 기분에 대한 메모를 남겨 보세요…", + "tagPicker": "태그", + "tagCategory": { + "feelings": "감정", + "sleep": "수면", + "health": "건강", + "social": "사교", + "work": "일", + "hobbies": "취미", + "factors": "요인", + "custom": "직접 추가" + }, + "tag": { + "happy": "행복함", + "excited": "설렘", + "grateful": "고마움", + "relaxed": "느긋함", + "content": "만족스러움", + "tired": "피곤함", + "unsure": "혼란스러움", + "bored": "지루함", + "tense": "긴장됨", + "angry": "화남", + "stressed": "스트레스", + "sad": "슬픔", + "sleptWell": "숙면", + "sleptOk": "보통 수면", + "sleptPoorly": "잠 설침", + "earlyNight": "일찍 취침", + "workedOut": "운동", + "ateWell": "건강한 식사", + "hydrated": "물 충분히", + "walked": "산책", + "alcohol": "술", + "caffeine": "카페인", + "nicotine": "니코틴", + "family": "가족", + "friends": "친구", + "party": "파티", + "alone": "혼자", + "productive": "생산적", + "overtime": "야근", + "dayOff": "쉬는 날", + "travel": "여행", + "sickDay": "아픈 날", + "movies": "영화", + "reading": "독서", + "gaming": "게임", + "music": "음악", + "outdoors": "야외 활동", + "fastFood": "패스트푸드", + "noSweets": "단 것 안 먹음", + "bigMeal": "푸짐한 식사", + "factorWork": "일", + "factorSocial": "사교", + "factorSleepQuality": "수면의 질", + "factorStress": "스트레스", + "factorConflict": "갈등", + "factorFamily": "가족", + "factorSadness": "슬픔", + "praise": "칭찬" + }, + "sourceManual": "직접 입력", + "sourceWeb": "웹", + "sourceTelegram": "Telegram", + "sourceDaylio": "Daylio", + "bulkDeleteSuccess": "{count}개 기록을 삭제했어요", + "bulkDeleteSuccessOne": "기록을 삭제했어요", + "bulkDeleteError": "선택한 기록을 삭제하지 못했어요", + "bulkDeleteConfirmTitle": "{count}개 기록을 삭제할까요?", + "bulkDeleteConfirmTitleOne": "이 기록을 삭제할까요?", + "bulkDeleteConfirmBody": "선택한 기분 기록 {count}개를 삭제해요. 잠깐 동안은 되돌릴 수 있어요.", + "bulkDeleteConfirmBodyOne": "선택한 기분 기록을 삭제해요. 잠깐 동안은 되돌릴 수 있어요.", + "customize": "기분 태그 관리", + "addTagInline": "태그 추가", + "manage": { + "groupsTitle": "그룹", + "groupsDescription": "직접 그룹을 만들고 선택기에 보이는 순서를 정하세요.", + "newGroup": "새 그룹", + "groupLabel": "그룹 이름", + "groupIcon": "아이콘", + "groupCreated": "그룹을 만들었어요", + "groupRenamed": "그룹을 저장했어요", + "groupDeleteTitle": "그룹을 삭제할까요?", + "groupDeleteBody": "이 그룹의 태그는 원래 그룹으로 돌아가요. 기록은 삭제되지 않아요.", + "groupDeleted": "그룹을 삭제했어요", + "groupEmpty": "직접 만든 그룹이 아직 없어요.", + "groupLimitReached": "그룹 개수 한도에 도달했어요", + "tagsTitle": "태그", + "tagsDescription": "선택기에 보이는 태그를 숨기거나 순서를 바꾸고, 옮기거나 편집하세요.", + "newTag": "새 태그", + "tagLabel": "태그 이름", + "tagIcon": "아이콘", + "tagGroup": "그룹", + "usageCount": "{count}개 기록", + "hideTag": "태그 숨기기", + "showTag": "태그 표시", + "moveToGroup": "그룹으로 이동", + "archive": "보관", + "restore": "복원", + "archivedTitle": "보관한 태그", + "archivedDescription": "보관한 태그는 지난 기록에 그대로 남고, 선택기에서만 사라져요.", + "archivedEmpty": "보관한 태그가 없어요.", + "purgeTitle": "태그를 완전히 삭제할까요?", + "purgeBody": "지난 기록 {count}개에서 이 태그가 지워져요. 되돌릴 수 없어요.", + "purgeAction": "완전 삭제", + "purged": "태그를 삭제했어요", + "tagLimitReached": "태그 개수 한도에 도달했어요", + "iconPickerTitle": "아이콘 선택", + "iconSearchPlaceholder": "아이콘 검색…", + "iconSearchEmpty": "맞는 아이콘이 없어요.", + "iconSearchClear": "검색어 지우기", + "orderSave": "순서 저장", + "orderSaved": "순서를 저장했어요", + "moveUp": "위로 이동", + "moveDown": "아래로 이동", + "iconGroupEmotions": "감정", + "iconGroupActivities": "활동", + "iconGroupHealth": "건강", + "iconGroupFood": "음식·음료", + "iconGroupWeather": "날씨", + "iconGroupPlaces": "장소", + "iconGroupMisc": "기타" + }, + "sectionMoreTags": "태그 더 보기", + "sectionNoteDetails": "메모·상세", + "quickTagsHelp": "자주 쓰는 태그 — 한 번만 누르면 돼요", + "quickTagsExpand": "더 보기", + "loadError": "기분 기록을 불러오지 못했어요.", + "sectionLevelA": "마음 상태", + "sectionLevelAHelp": "선택 사항이에요. 건드리지 않은 항목은 비어 있어요.", + "dimensionNotAnswered": "기록 안 함", + "dimensionClear": "지우기", + "dimensionValueText": "{max} 중 {value}: {low} ~ {high}", + "dimension": { + "a1": { + "label": "기분", + "low": "매우 불쾌함", + "high": "매우 유쾌함" + }, + "a2": { + "label": "스트레스", + "low": "완전히 편안함", + "high": "극도로 긴장됨" + }, + "a3": { + "label": "활력", + "low": "완전히 지침", + "high": "활력이 넘침" + }, + "a4": { + "label": "유대감", + "low": "고립됨", + "high": "이해받고 지지받음" + }, + "a5": { + "label": "안정감", + "low": "완전히 벅참", + "high": "충분히 대처할 수 있음" + } + }, + "context": { + "section": { + "work": "일", + "contacts": "교류", + "leisure": "여가", + "events": "사건" + }, + "field": { + "workStatus": "일한 날", + "workMinutes": "일한 시간", + "overtimeMinutes": "초과 근무", + "workLoad": "업무량", + "workSatisfaction": "일 만족도", + "contactCircles": "누구와 만났나요", + "contactForm": "교류 방식", + "contactExtent": "얼마나 이어졌나요", + "contactQuality": "교류의 질", + "contactSupport": "받은 지지", + "leisureCategories": "여가에 무엇을 했나요", + "leisureMinutes": "나를 위한 시간", + "leisureJoy": "즐거움", + "leisureRecovery": "회복", + "eventType": "무슨 일이 있었나요", + "eventValence": "어떻게 다가왔나요", + "eventAt": "언제", + "notes": "메모" + }, + "workStatus": { + "off": "일 안 함", + "partial": "하루 중 일부", + "regular": "평소와 같은 날", + "overtime": "평소보다 길게" + }, + "contactCircles": { + "partner": "파트너", + "child": "아이", + "family": "가족", + "friends": "친구", + "colleagues": "동료", + "acquaintances": "지인", + "professional": "전문가 지원", + "other": "그 외" + }, + "contactForm": { + "inPerson": "직접 만남", + "phone": "전화", + "video": "영상 통화", + "written": "문자·메시지" + }, + "contactExtent": { + "brief": "짧게", + "medium": "보통", + "extended": "길게" + }, + "leisureCategories": { + "film": "영화·시리즈", + "reading": "독서", + "gaming": "게임", + "music": "음악", + "creative": "창작 활동", + "outing": "외출", + "garden": "정원", + "cooking": "요리", + "timeWithChild": "아이와 보낸 시간", + "relaxation": "휴식", + "other": "그 외" + }, + "eventType": { + "goodNews": "좋은 소식", + "badNews": "안 좋은 소식", + "success": "잘 풀린 일", + "disappointment": "실망스러운 일", + "conflict": "갈등", + "appointment": "약속", + "familyEvent": "가족 행사", + "change": "변화", + "unexpected": "예상 못 한 일" + }, + "rating": { + "workLoad": { + "low": "부담 없음", + "high": "부담이 아주 큼" + }, + "workSatisfaction": { + "low": "전혀 만족 못 함", + "high": "매우 만족함" + }, + "contactQuality": { + "low": "도움이 안 됐음", + "high": "많이 도움이 됐음" + }, + "contactSupport": { + "low": "지지받지 못함", + "high": "충분히 지지받음" + }, + "leisureJoy": { + "low": "즐겁지 않음", + "high": "아주 즐거움" + }, + "leisureRecovery": { + "low": "회복이 안 됨", + "high": "많이 회복됨" + }, + "eventValence": { + "low": "확실히 나쁨", + "high": "확실히 좋음" + } + } + }, + "dayContext": { + "minutesPlaceholder": "분", + "contactsHelp": "몇 명을 만났는지는 하루를 말해 주지 않아요. 중요한 건 그 교류가 어떻게 느껴졌는지예요.", + "notePlaceholder": "이 하루에 덧붙이고 싶은 것", + "notRecorded": "기록 안 함", + "linkedTitle": "다른 모듈에서 가져옴", + "linkedHelp": "원래 저장된 곳에서 읽어와요. 거기서 바꾸면 여기도 바뀌어요.", + "linkedSleep": "잠든 시간", + "linkedInBed": "침대에 있던 시간", + "linkedSteps": "걸음 수", + "linkedActiveEnergy": "활동 에너지", + "linkedRestingHeartRate": "안정 시 심박수", + "linkedHrv": "심박변이도", + "bodyLogged": "아픈 날 일지에 증상 {count}개를 기록했어요", + "bodyNotLogged": "이 날은 아픈 날 일지에 기록된 게 없어요", + "openIllness": "열기" + } + }, + "medications": { + "title": "복약", + "subtitle": "약·일정·복용 기록", + "loginRequired": "복약을 관리하려면 로그인해 주세요.", + "addMedication": "추가", + "firstMedication": "첫 약 추가", + "addChoice": { + "logIntake": "복용 기록", + "newMedication": "새 약" + }, + "takeAllDue": { + "button": "예정된 약 모두 복용", + "dialogTitle": "예정된 약 복용", + "dialogDescription": "지금 복용할 약이에요. 확인하면 모두 복용으로 기록해요.", + "confirm": "모두 복용 ({count})", + "successToast": "{count}건 복용을 기록했어요", + "partialToast": "{taken}건 기록, {failed}건 실패 – 다시 시도해 주세요", + "failedToast": "복용을 기록하지 못했어요 – 다시 시도해 주세요" + }, + "logIntake": { + "dialogTitle": "복용 기록", + "dialogDescription": "등록된 약에 복용량을 기록해요. 지난 날짜와 시각으로도 기록할 수 있어요.", + "medicationLabel": "약", + "slotLabel": "일정 슬롯", + "slotNone": "슬롯 지정 안 함", + "doseLabel": "용량", + "doseHint": "약 또는 슬롯의 용량이 미리 채워져요. 이번 복용만 다른 용량으로 기록하려면 수정하세요.", + "takenAtLabel": "날짜와 시각", + "takenAtHint": "기본값은 지금이에요. 지난 복용을 기록하려면 이전 날짜와 시각을 고르세요.", + "skippedLabel": "건너뜀으로 기록", + "noMedications": "약을 먼저 추가하면 복용을 기록할 수 있어요.", + "cancel": "취소", + "submit": "복용 기록", + "slotError": "선택한 슬롯의 시각을 찾지 못했어요. 다른 슬롯이나 '슬롯 지정 안 함'을 고르세요." + }, + "emptyTitle": "아직 등록한 약이 없어요", + "emptyDescription": "약과 복용량, 알림 시간을 추가하면 복약 순응도를 확인할 수 있어요.", + "loadFailed": "약을 불러오지 못했어요", + "retryLoad": "다시 시도", + "inactive": "일시중지", + "intakeHistory": "복용 기록", + "openDetailPage": "약 상세 페이지 열기", + "compliance": "복약 순응도", + "importIntakes": "복용 기록 불러오기", + "importDescription": "복용 데이터가 담긴 JSON 배열을 붙여넣으세요. 예상 형식:", + "importUploadFile": "JSON 파일 업로드", + "importPaste": "여기에 JSON을 붙여넣으세요…", + "importSelected": "선택함: {name}", + "importFileLoaded": "'{name}' 파일을 불러왔어요", + "importInvalidJson": "파일에 올바른 JSON이 없어요", + "importNoArray": "배열을 찾지 못했어요", + "importFailed": "불러오지 못했어요", + "importInvalidFormat": "JSON 형식이 올바르지 않아요", + "requestExample": "요청 예시", + "categoryBloodPressure": "혈압", + "categoryVitamin": "비타민", + "categoryThyroid": "갑상선", + "categoryHormone": "호르몬", + "categorySupplement": "보충제", + "categoryPainRelief": "진통", + "categoryAllergy": "알레르기", + "categoryDigestive": "소화기", + "categorySkin": "피부", + "categorySleepAid": "수면 보조", + "categoryDiabetes": "당뇨", + "categoryAntibiotic": "항생제", + "categoryOther": "기타", + "daysSun": "일", + "daysMon": "월", + "daysTue": "화", + "daysWed": "수", + "daysThu": "목", + "daysFri": "금", + "daysSat": "토", + "back": "뒤로", + "today": "오늘", + "yesterday": "어제", + "tomorrow": "내일", + "asNeededMarker": "필요시", + "withoutNotification": "알림 없음", + "pausedSince": "일시중지 시작", + "takeNow": "지금 복용", + "lastDoseYesterday": "마지막 복용 어제", + "lastDoseDaysAgo": "마지막 복용 {count}일 전", + "overdue": "시간 지남", + "veryOverdue": "많이 지남", + "complianceWindow": "복약 순응도 ({days}일)", + "complianceUnavailable": "복약 순응도를 불러오지 못했어요", + "complianceInfo": "예정된 복용 중 기록한 비율이에요. 주간 일정은 주 단위로 계산해요.", + "complianceInfoLabel": "복약 순응도란?", + "lastIntake": "마지막 복용:", + "nextIntake": "다음 복용:", + "nextIntakeOverdue": "시간 지남 · {time} — 지금도 복용할 수 있어요", + "dayStreak": "일 연속 기록", + "dayStreakOne": "일 연속 기록", + "cycleDueToday": "오늘 예정", + "cycleNoClosedCycles": "아직 완료된 복용 주기가 없어요", + "taken": "복용함", + "skipped": "건너뜀", + "intakeToastTaken": "복용으로 기록함: {name}", + "intakeToastSkipped": "건너뜀: {name}", + "intakeToastFailed": "기록하지 못했어요: {name} — 다시 시도해 주세요", + "intakeUndo": "실행 취소", + "intakeUndone": "기록을 되돌렸어요", + "intakeUndoFailed": "되돌리지 못했어요 — 기록에서 수정해 주세요", + "glp1LastInjection": "{label}", + "glp1NextInjectionDays": "{label} ({days}일 후)", + "glp1NextInjectionToday": "오늘", + "glp1NextInjectionTomorrow": "내일", + "glp1RotationSuggested": "다음 추천 부위: {site}", + "glp1LogSideEffect": "부작용 기록", + "sideEffects": { + "section": "부작용", + "addCta": "기록", + "sheetTitle": "부작용 기록", + "categoryLabel": "분류", + "entryLabel": "증상", + "severityLabel": "심각도", + "severity": { + "mild": "가벼움", + "moderate": "보통", + "significant": "뚜렷함", + "severe": "심함", + "verySevere": "매우 심함" + }, + "notesLabel": "메모 (선택)", + "notesPlaceholder": "기억해 둘 내용, 최대 280자", + "submitCta": "저장", + "cancelCta": "취소", + "emptyState": "아직 기록한 부작용이 없어요. 기록해 두면 진료에 도움이 되고 패턴도 드러나요.", + "recentTitle": "최근 30일", + "loading": "불러오는 중…", + "deleteCta": "삭제", + "deleteError": "기록을 삭제하지 못했어요", + "errorSeverity": "심각도는 1에서 5 사이여야 해요", + "errorEntry": "증상을 골라 주세요", + "categories": { + "gi": "소화기", + "metabolic": "대사", + "injectionSite": "주사 부위", + "cognitive": "인지", + "glp1Specific": "GLP-1 특이" + }, + "entries": { + "nausea": "메스꺼움", + "vomiting": "구토", + "diarrhea": "설사", + "constipation": "변비", + "abdominalPain": "복통", + "hypoglycemiaSymptoms": "저혈당 증상", + "dehydration": "탈수", + "anorexia": "식욕 저하", + "electrolyteFatigue": "전해질 불균형 피로", + "injectionRedness": "주사 부위 발적", + "injectionSwelling": "주사 부위 부종", + "injectionBruising": "주사 부위 멍", + "injectionInduration": "주사 부위 경결", + "brainFog": "브레인 포그", + "dizziness": "어지럼", + "lowMood": "기분 저하", + "lowEnergy": "활력 저하", + "earlySatiety": "조기 포만감", + "gastroparesisLike": "위 배출 지연", + "dysgeusia": "미각 변화", + "gallbladderDiscomfort": "담낭 불편감" + }, + "deleteConfirmTitle": "이 기록을 삭제할까요?", + "deleteConfirmBody": "이 부작용 기록이 영구히 삭제돼요." + }, + "scheduling": { + "naturalLanguage": { + "title": "약을 설명해 주세요", + "description": "약을 자유롭게 적으면 양식을 미리 채워 드려요.", + "placeholder": "예: Mounjaro 5mg 매주 수요일 아침, 다음 주 월요일부터", + "submit": "추출", + "submitBusy": "추출 중…", + "cancel": "취소", + "examples": { + "label": "이런 예시를 써 보세요", + "weekly": "Mounjaro 5mg 매주 수요일 아침", + "daily": "이부프로펜 200mg 매일 하루 3번", + "rolling": "메토트렉세이트 7.5mg 마지막 주사 후 7일마다" + }, + "error": { + "empty": "설명을 먼저 입력해 주세요.", + "tooLong": "설명은 2000자 이내로 적어 주세요.", + "rateLimit": "요청이 너무 많아요 — 잠시 후 다시 시도해 주세요.", + "noProvider": "AI 제공자가 설정되지 않았어요 — 설정에서 등록해 주세요.", + "budget": "오늘 AI 사용 한도를 다 썼어요 — 내일 다시 시도해 주세요.", + "network": "AI 제공자에 연결하지 못했어요 — 다시 시도해 주세요." + } + }, + "cadence": { + "section": "주기", + "label": "빈도", + "kind": { + "daily": "매일", + "weekdays": "특정 요일", + "everyNWeeks": "N주마다 특정 요일", + "monthly": "매월 특정 날짜", + "everyNMonths": "N개월마다 특정 날짜", + "yearly": "1년에 한 번", + "rolling": "마지막 복용일부터 N일마다 (유연)", + "rollingExplainer": "마지막으로 기록한 복용부터 세요. 복용을 건너뛰면 멈춰요.", + "oneShot": "1회 복용" + }, + "weekdays": { + "label": "요일", + "short": { + "mo": "월", + "tu": "화", + "we": "수", + "th": "목", + "fr": "금", + "sa": "토", + "su": "일" + }, + "long": { + "mo": "월요일", + "tu": "화요일", + "we": "수요일", + "th": "목요일", + "fr": "금요일", + "sa": "토요일", + "su": "일요일" + } + }, + "intervalWeeks": { + "suffix": "주" + }, + "dayOfMonth": { + "label": "일자" + }, + "intervalMonths": { + "suffix": "개월", + "dayOnLabel": "일자" + }, + "yearly": { + "date": { + "label": "날짜" + } + }, + "rollingDays": { + "suffix": "일" + } + }, + "timesOfDay": { + "section": "복용 시각", + "label": "복용 시각", + "empty": { + "cta": "첫 시각을 추가하세요." + }, + "add": "시각 추가", + "remove": "삭제", + "presets": { + "morning": "아침", + "noon": "점심", + "evening": "저녁", + "night": "밤" + }, + "max": { + "reached": "최대 개수에 도달했어요 — 다른 시각을 삭제한 뒤 추가하세요." + } + }, + "courseWindow": { + "section": "복용 기간", + "startsOn": { + "label": "시작일" + }, + "endsOn": { + "label": "종료일" + }, + "noEndDate": "종료일 없음", + "oneShotCaption": "(1회 복용)", + "invalidRange": "종료일은 시작일 이후여야 해요." + } + }, + "wizard": { + "header": { + "stepOf": "{current}/{total}단계", + "createTitle": "새 약", + "editTitle": "편집: {name}" + }, + "nav": { + "back": "뒤로", + "next": "다음", + "save": "저장", + "saveEdit": "변경 사항 저장", + "nextHint": "다음: {step}", + "reviewHint": "다음: 저장", + "jumpFirst": "첫 단계로 이동", + "jumpLast": "검토 및 저장으로 이동" + }, + "nl": { + "button": "설명" + }, + "steps": { + "step1": { + "title": "약 이름이 뭔가요?", + "subline": "상자에 적힌 그대로 적어 주세요.", + "label": "이름", + "placeholder": "예: Ramipril", + "short": "이름" + }, + "step2": { + "title": "어떤 종류의 약인가요?", + "subline": "알맞은 템플릿과 분석에 사용해요.", + "short": "종류" + }, + "step3": { + "title": "용량이 얼마인가요?", + "subline": "제형에 따라 한 정, 한 번 흡입, 한 방울 등이에요.", + "amountLabel": "양", + "amountPlaceholder": "예: 5", + "unitLabel": "단위", + "unit": { + "mg": "mg", + "ml": "ml", + "iu": "IU", + "mcg": "µg", + "g": "g", + "tablets": "정", + "capsules": "캡슐", + "drops": "방울", + "puffs": "흡입", + "sprays": "분무", + "pieces": "개", + "other": "기타" + }, + "deliveryFormLabel": "투여 경로", + "deliveryForm": { + "ORAL": "경구", + "INJECTION": "주사", + "OTHER": "기타" + }, + "dosesPerUnitLabel": "단위당 투여 횟수", + "dosesPerUnitPlaceholder": "예: 4", + "dosesPerUnitHint": "펜/바이알당 투여 횟수 — 재고 추적이 필요 없으면 비워 두세요.", + "short": "용량", + "unitsPerDoseLabel": "1회 복용 단위 수", + "unitsPerDoseHint": "예: 4mg 용량이면 2mg 2정, 반으로 나눠 먹으면 ½정" + }, + "step4": { + "title": "언제부터 언제까지 복용하나요?", + "subline": "지금은 시작일만 있어도 충분해요. 종료일은 나중에 추가할 수 있어요.", + "short": "기간" + }, + "step5": { + "title": "얼마나 자주 복용하나요?", + "subline": "주기를 고르세요 — 세부 설정은 다음 단계에서 해요.", + "short": "주기" + }, + "step6": { + "title": "자세히 어떻게 되나요?", + "subline": "요일, 간격, 매월 날짜를 설정하세요.", + "intervalWeeks": { + "label": "매", + "suffix": "주마다" + }, + "dayOfMonth": { + "label": "매월", + "suffix": "일에" + }, + "rollingDays": { + "label": "마지막 복용 후", + "suffix": "일마다" + }, + "short": "세부" + }, + "step7": { + "title": "하루 중 언제 복용하나요?", + "subline": "하루 한 번 이상 — 알림은 이 목록을 따라요.", + "presetsLabel": "시간대 프리셋", + "presets": { + "morning": "아침", + "noon": "점심", + "evening": "저녁", + "night": "밤" + }, + "overrideHint": "선택 사항 — 이 시간대에만 다른 용량이나 단위 수를 지정해요. 비워 두면 약의 기본값을 써요.", + "doseOverrideLabel": "이 시간대 용량 (선택)", + "doseOverridePlaceholder": "예: 반 알", + "unitsOverrideLabel": "이 시간대에 복용하는 단위 수", + "unitsOverrideInherit": "기본값", + "short": "시간" + }, + "step8": { + "title": "다 됐어요 — HealthLog가 알림을 보낼까요?", + "subline": "저장하기 전에 아래 내용을 확인해 주세요.", + "remindersLabel": "알림 켜기", + "remindersDescription": "푸시, Telegram, ntfy — 설정에 따라 보내요.", + "noChannelHint": "알림을 보내려면 알림 채널이 필요한데, 지금은 켜진 채널이 없어요.", + "noChannelHintLink": "알림 설정하기", + "short": "완료" + } + }, + "classRow": { + "bloodPressure": "혈압", + "diabetes": "당뇨", + "hormone": "호르몬", + "glp1": "GLP-1 주사", + "painRelief": "통증", + "allergy": "알레르기", + "vitamin": "비타민", + "supplement": "보충제", + "antibiotic": "항생제", + "other": "기타" + }, + "cadence": { + "daily": { + "label": "매일", + "description": "매일 같은 시간에 복용해요." + }, + "weekdays": { + "label": "특정 요일", + "description": "예: 월요일, 수요일, 금요일." + }, + "everyNWeeks": { + "label": "몇 주마다", + "description": "예: 2주마다 목요일 (GLP-1, 생물학적 제제)." + }, + "monthly": { + "label": "매월", + "description": "매월 정해진 날짜에 복용해요 (예: 항상 1일)." + }, + "rolling": { + "label": "마지막 복용 기준 유동", + "description": "다음 복용 시점을 열어 둬요. '복용함'을 누르면 그 순간부터 다시 세요." + }, + "oneShot": { + "label": "1회 복용", + "description": "한 번만 투여해요. 예: 예방접종이나 항생제 한 코스." + }, + "asNeeded": { + "label": "필요시", + "description": "정해진 일정 없이 필요할 때마다 복용을 기록해요 (예: 진통제)." + } + }, + "summary": { + "title": "요약", + "cadence": { + "daily": "매일", + "weekdays": "선택한 요일에", + "biweekly": "2주마다", + "monthly": "매월", + "quarterly": "3개월마다", + "yearly": "1년에 한 번", + "rolling": "마지막 복용 후 {n}일마다", + "oneShot": "1회 복용", + "asNeeded": "필요시", + "everyNWeeks": "{n}주마다", + "everyNMonths": "{n}개월마다" + }, + "weekdaysDetail": ", {days}에", + "dayOfMonthDetail": ", 매월 {day}일에.", + "times": "{times}에", + "startsOn": "시작: {date}", + "endsOn": "종료: {date}", + "noEndDate": "종료일 없음" + }, + "compose": { + "scheduleIndex": "일정 {n}/{total}", + "list": { + "add": "일정 추가", + "edit": "편집", + "remove": "삭제", + "removeDisabled": "일정은 최소 하나가 필요해요." + } + }, + "errors": { + "submitFailed": "약을 저장하지 못했어요 — 다시 시도해 주세요." + } + }, + "doseStrength": { + "title": "시간에 따른 용량 변화", + "axisLabel": "기록된 용량 ({unit})", + "tooltipLabel": "용량", + "currentDose": "현재 용량: {dose} {unit}", + "noCurrentDose": "아직 기록된 용량이 없어요.", + "empty": "아직 용량 조절 이력이 없어요. 용량 변경을 기록하면 변화 곡선을 볼 수 있어요." + }, + "titration": { + "title": "용량 조절 계획", + "empty": "아직 증량 단계가 기록되지 않았어요. 날짜가 있는 용량 변경을 추가하면 계획을 그려 줘요.", + "youAreHere": "현재 위치", + "doseStep": "{dose} {unit}", + "plannedFor": "예정 · {date}" + }, + "injectionSiteBodyOutlineAriaLabel": "신체 윤곽", + "injectionSitePickerAriaLabel": "주사 부위 선택기", + "injectionSiteRecommendedAriaLabel": "{site} — 다음 권장 부위", + "injectionSiteLegendAriaLabel": "마커 범례", + "injectionSiteLegendRecommended": "다음 권장 부위", + "injectionSiteLegendLastUsed": "마지막 사용 부위", + "siteAbdomenLeft": "복부 왼쪽 아래", + "siteAbdomenRight": "복부 오른쪽 아래", + "siteAbdomenUpperLeft": "복부 왼쪽 위", + "siteAbdomenUpperRight": "복부 오른쪽 위", + "siteThighLeft": "왼쪽 허벅지", + "siteThighRight": "오른쪽 허벅지", + "siteUpperArmLeft": "왼쪽 위팔", + "siteUpperArmRight": "오른쪽 위팔", + "sideEffectTagsHelp": "칩을 누르면 기분 기록 태그에 추가돼요.", + "sideEffectTagNausea": "메스꺼움", + "sideEffectTagConstipation": "변비", + "sideEffectTagDiarrhea": "설사", + "sideEffectTagFatigue": "피로", + "sideEffectTagAppetiteLoss": "식욕저하", + "sideEffectTagHeartburn": "속쓰림", + "sideEffectTagHeadache": "두통", + "phaseConfig": "알림 단계", + "phaseConfigDescription": "알림을 보낼 시점을 설정해요.", + "phaseGreen": "초록", + "phaseYellow": "노랑", + "phaseOrange": "주황", + "phaseRed": "빨강", + "phaseMinutes": "분", + "phasePercent": "%", + "phaseResetDefaults": "기본값으로 되돌리기", + "phaseSaved": "알림 단계를 저장했어요", + "sourceWeb": "웹", + "sourceApi": "iOS 앱", + "sourceReminder": "알림", + "sourceImport": "불러오기", + "intakeSourceVia": "{origin} 경유", + "intakeHistoryEmptyTitle": "아직 복용 기록이 없어요", + "intakeHistoryEmptyDescription": "일일 복용 페이지에서 용량을 기록하면 이력이 쌓여요.", + "intakeHistoryEmptyAction": "일일 복용 열기", + "intakeHistoryColTaken": "복용", + "intakeHistoryColScheduled": "예정", + "intakeHistoryColStatus": "상태", + "intakeHistoryColSource": "출처", + "intakeHistoryStatusTaken": "복용함", + "intakeHistoryStatusSkipped": "건너뜀", + "intakeHistorySortByTaken": "복용 시각순 정렬", + "intakeHistorySortByScheduled": "예정 시각순 정렬", + "intakeHistoryPageInfo": "{page}/{total}페이지 · {count}개 기록", + "intakeHistoryPrev": "이전", + "intakeHistoryNext": "다음", + "glp1": { + "drug": { + "tirzepatide": { + "name": "티르제파타이드" + }, + "semaglutide": { + "name": "세마글루타이드" + }, + "liraglutide": { + "name": "리라글루타이드" + }, + "dulaglutide": { + "name": "둘라글루타이드" + }, + "exenatide": { + "name": "엑세나타이드" + } + } + }, + "researchMode": { + "chart": { + "title": "예상 약물 농도", + "axisLabel": "예상 농도 (상대값)", + "emptyState": "아직 복용 기록이 없어요.", + "emptyStateCta": "용량을 기록하면 예상 농도 곡선이 그려져요.", + "estimateNote": "EMA가 공개한 집단 약동학 자료를 바탕으로 한 참고용 추정치예요. 실제 측정값이 아니에요.", + "unknownDrug": "{name} — EMA 승인 GLP-1 목록에 없어서 HealthLog가 집단 약동학 곡선을 그릴 수 없어요." + } + }, + "detail": { + "status": { + "active": "복용 중", + "paused": "일시중지", + "ended": "종료" + }, + "cadence": { + "oneShotOn": "{date}에 1회 복용해요.", + "oneShotPending": "1회 복용 예정이에요.", + "asNeeded": "필요시 복용 — 정해진 일정 없어요." + }, + "intake": { + "title": "복용 기록", + "importButton": "불러오기", + "rowActions": { + "openMenu": "행 작업 열기", + "edit": "편집", + "delete": "삭제" + }, + "selection": { + "rowToggleLabel": "행 선택" + }, + "bulkDelete": { + "selectionCount": "{count}개 항목 선택됨", + "deleteButton": "선택 항목 삭제", + "cancelButton": "취소", + "confirmTitle": "선택한 항목을 삭제할까요?", + "confirmBody": "{count}개 항목이 영구히 삭제돼요. 약 자체는 그대로 남아요.", + "confirmAction": "삭제", + "toast": "선택 항목을 삭제했어요", + "failed": "선택 항목을 삭제하지 못했어요." + }, + "edit": { + "dialogTitle": "복용 기록 편집", + "takenAtLabel": "복용 시각", + "scheduledForHint": "예정: {dateTime}", + "farFromScheduledWarning": "예정 시각과 많이 떨어져 있어요 — 날짜를 확인해 주세요.", + "skippedLabel": "건너뜀", + "save": "저장", + "cancel": "취소", + "savedToast": "복용 기록을 수정했어요", + "failed": "복용 기록을 수정하지 못했어요." + }, + "deleteRow": { + "confirmTitle": "이 복용 기록을 삭제할까요?", + "confirmBody": "기록이 영구히 삭제돼요.", + "confirmAction": "삭제", + "toast": "복용 기록을 삭제했어요", + "failed": "복용 기록을 삭제하지 못했어요." + }, + "viewRecent90": "최근 90일", + "viewAll": "전체 기록", + "viewToggleLabel": "기록 범위" + }, + "verlauf": { + "loading": "기록을 불러오는 중…", + "loadError": "기록을 불러오지 못했어요.", + "empty": "이 기간에는 복용 기록이 아직 없어요.", + "summaryEmpty": "아직 계산할 복용이 없어요.", + "adherenceLabel": "복약 순응도", + "adherenceBarLabel": "정시 {onTime}, 늦음 {late}, 놓침 {missed}", + "add": "기록", + "markTaken": "복용함", + "markSkipped": "건너뜀", + "rowActionsLabel": "{time} 작업", + "markAsTaken": "복용함으로 표시", + "markAsSkipped": "건너뜀으로 표시", + "adHocTag": "일정 외", + "takenDetail": "예정 {planned} · 복용 {taken}", + "doseTaken": "용량: {dose}", + "status": { + "taken_on_time": "정시", + "taken_late": "늦음", + "skipped": "건너뜀", + "missed": "놓침", + "upcoming": "예정", + "ad_hoc": "일정 외" + }, + "addDialog": { + "title": "복용 기록 추가", + "description": "복용을 기록해요 — 지난 날짜나 일정 외 복용도 괜찮아요.", + "takenAtLabel": "복용 시각", + "skippedLabel": "건너뜀", + "nudgeHint": "{slot} 시간대와 가까워요 — 저장할 때 그 시간대에 배정할지 물어볼게요.", + "cancel": "취소", + "submit": "저장" + }, + "nudge": { + "title": "이 시간대에 배정할까요?", + "body": "이 복용은 {slot} 시간대 범위를 살짝 벗어났어요. 그 시간대에 배정할까요, 아니면 일정 외 기록으로 둘까요?", + "keepAdHoc": "일정 외", + "pin": "배정" + }, + "pin": { + "action": "{time} 시간대에 배정", + "release": "배정 해제", + "pinnedTag": "배정됨", + "pinnedToast": "복용 기록을 시간대에 배정했어요", + "unpinnedToast": "배정을 해제했어요", + "dueContext": "예정 시각: {time} ({delta})", + "confirmTitle": "{time} 시간대에 배정할까요?", + "confirmBody": "이 복용은 해당 시간대에 늦게 복용한 것으로 계산돼요 – 순응도가 부풀려지지 않아요. 배정은 언제든 해제할 수 있어요.", + "confirmCancel": "취소", + "confirmAction": "배정" + } + }, + "notifications": { + "title": "알림", + "switchLabel": "복용 시각에 알림 보내기", + "helperOn": "알림이 켜져 있어요.", + "helperOff": "알림이 꺼져 있어요.", + "enabledToast": "알림 켜짐", + "disabledToast": "알림 꺼짐", + "toggleFailed": "알림 설정을 바꾸지 못했어요.", + "clientManagedChip": "iPhone에서 관리", + "clientManagedHint": "이 약의 알림은 iPhone에서 관리해요." + }, + "settings": { + "title": "설정", + "phases": { + "openButton": "단계 설정", + "requiresCourseWindow": "복용 기간을 설정하면 단계를 쓸 수 있어요." + }, + "grace": { + "label": "알림 유예 시간", + "appliesToAllNote": "이 약의 모든 일정에 적용돼요.", + "unit": "분", + "saved": "알림 유예 시간을 저장했어요", + "failed": "알림 유예 시간을 저장하지 못했어요." + }, + "codes": { + "label": "임상 코드 (ATC / RxNorm)", + "note": "선택 사항이에요. FHIR 건강기록 내보내기에 쓰여요. 입력한 코드를 그대로 쓰고, 추측하지 않아요.", + "atcLabel": "ATC", + "rxNormLabel": "RxNorm", + "saved": "임상 코드를 저장했어요", + "failed": "임상 코드를 저장하지 못했어요." + } + }, + "zone": { + "title": "관리 및 위험 영역", + "pause": { + "title": "일시중지", + "helper": "다시 켤 때까지 알림을 멈춰요. 기록은 그대로 남아요.", + "pausedToast": "알림을 멈췄어요", + "resumedToast": "알림을 다시 켰어요", + "failed": "일시중지 상태를 바꾸지 못했어요." + }, + "end": { + "title": "복약 종료", + "helper": "알림을 끄고 이 약을 종료 상태로 표시해요. 기록은 계속 볼 수 있어요.", + "button": "종료", + "dialogTitle": "종료: {name} — 알림을 끄고, 기록은 계속 볼 수 있어요", + "dialogBody": "알림이 더 이상 오지 않아요. 이전 기록은 타임라인에 남아요.", + "toast": "복약을 종료했어요", + "failed": "복약을 종료하지 못했어요." + }, + "purge": { + "title": "기록 삭제", + "helper": "이 약에 복용 기록 {count}건이 저장돼 있어요.", + "button": "기록 삭제", + "dialogTitle": "기록을 정말 삭제할까요?", + "dialogBody": "복용 기록 {count}건이 영구히 삭제돼요. 약 자체는 그대로 남아요.", + "toast": "기록을 삭제했어요", + "failed": "기록을 삭제하지 못했어요." + }, + "delete": { + "title": "약 삭제", + "helper": "약과 관련된 모든 기록을 삭제해요.", + "button": "삭제", + "dialogTitle": "{name} — 삭제할까요?", + "dialogBody": "약과 일정, 복용 기록, API 토큰, 단계 설정, 알림이 모두 영구히 삭제돼요.", + "toast": "약을 삭제했어요", + "failed": "약을 삭제하지 못했어요." + } + }, + "phases": { + "modeLabel": "{phase} 모드", + "saveFailed": "알림 단계를 저장하지 못했어요." + }, + "api": { + "caption": "'{name}' 엔드포인트", + "copyUrl": "URL 복사", + "urlCopied": "URL을 복사했어요", + "mintToken": "토큰 발급", + "mintAnotherToken": "토큰 추가 발급", + "mintFailed": "토큰을 발급하지 못했어요.", + "copyFailed": "복사하지 못했어요.", + "tokenCopied": "토큰을 복사했어요", + "copyToken": "토큰 복사", + "mintedHint": "지금 토큰을 복사해 주세요 — 한 번만 보여줘요." + }, + "advanced": { + "dataPortability": { + "import": { + "title": "복용 기록 불러오기", + "helper": "JSON 파일이나 붙여넣은 JSON에서 지난 복용 기록을 불러와요.", + "button": "JSON 불러오기" + }, + "export": { + "title": "약 내보내기", + "helper": "이 약을 CSV 파일로 내려받아요.", + "includeIntake": "복용 기록 포함", + "button": "CSV 내보내기" + } + } + }, + "header": { + "historyLabel": "복용 기록 보기" + }, + "history": { + "plannedSuffix": "예정" + }, + "tabs": { + "uebersicht": "개요", + "zeitplan": "일정", + "bestand": "재고", + "verlauf": "기록", + "injektion": "주사", + "api": "API", + "erweitert": "고급", + "wirkung": "효과" + }, + "shell": { + "loadFailed": "이 약을 불러오지 못했어요.", + "tablistLabel": "약 상세 섹션" + }, + "uebersicht": { + "statusTitle": "상태", + "nextDoseLabel": "다음 복용", + "nextDoseNone": "예정된 복용이 없어요.", + "nextDoseAsNeeded": "필요시 복용 — 예정된 복용이 없어요.", + "pausedHint": "일시중지 — 알림이 오지 않아요.", + "reminderLabel": "알림", + "reminderOn": "켜짐", + "reminderOff": "꺼짐", + "supplyLabel": "재고", + "supplyRunway": "약 {days}일 더 쓸 수 있어요", + "jumpToZeitplan": "일정 열기", + "jumpToVerlauf": "기록 열기", + "jumpToBestand": "재고 열기", + "complianceTitle": "복약 순응도", + "complianceEmpty": "아직 복약 순응도 데이터가 없어요." + }, + "bestand": { + "title": "재고", + "summary": "총 {total}회분 중 {remaining}회분 남음", + "itemsTitle": "포장 및 용기", + "doses": "{remaining} / {total}회분", + "unknown": "알 수 없음", + "state": { + "ACTIVE": "미개봉", + "IN_USE": "사용 중", + "EXPIRED": "유효기간 지남", + "USED_UP": "다 씀" + }, + "empty": "등록된 재고가 없어요.", + "emptyHelper": "포장이나 용기를 등록하면 남은 재고와 소진 예상일을 볼 수 있어요.", + "emptyCta": "포장 등록", + "addButton": "등록", + "lowStockLinkOn": "재고 부족 알림: {days}일 미만", + "lowStockLinkOff": "재고 부족 알림: 꺼짐", + "addTitle": "포장 또는 용기 등록", + "addDescription": "새 재고를 개수와 함께 기록해요. 유효기간은 선택 사항이에요.", + "addQuantityLabel": "수량 (개)", + "addQuantityHelper": "포장·용기당 1–1000개.", + "addExpiryLabel": "유효기간 (선택)", + "addSubmit": "등록", + "addSuccess": "재고를 등록했어요", + "addFailed": "재고를 등록하지 못했어요.", + "adjustButton": "조정", + "adjustTitle": "용기 정보 수정", + "adjustDescription": "남은 개수를 설정하고, 이 용기에 적혔거나 기록해 둔 날짜를 바로잡아요. 0개로 두면 다 쓴 것으로 표시돼요.", + "adjustQuantityLabel": "남은 개수", + "adjustHelper": "총 용량: {total}개.", + "adjustSubmit": "저장", + "adjustSuccess": "재고를 수정했어요", + "adjustFailed": "재고를 수정하지 못했어요.", + "deleteTitle": "용기를 삭제할까요?", + "deleteDescription": "이 포장·용기를 재고에서 영구히 삭제해요. 기록된 복용은 그대로예요.", + "deleteSuccess": "재고 항목을 삭제했어요", + "deleteFailed": "재고 항목을 삭제하지 못했어요.", + "containerTypeLabel": "용기 종류", + "containerType": { + "PEN": "펜", + "AMPOULE": "앰플", + "BLISTER": "정제 포장", + "INHALER": "흡입기", + "BOTTLE": "병", + "OTHER": "기타" + }, + "unitsDetail": "{remaining} / {total}개", + "quantityModeDoses": "회분", + "quantityModeUnits": "개수", + "quantityInUnits": "= {units}개", + "quantityInDoses": "≈ {doses}회분", + "quantityUnderOneDose": "< 1회분", + "expiredSuffix": "+ 유효기간 지난 {units}개", + "packagingTitle": "포장 및 용량", + "packagingUnitsPerDose": "1회분 = {units}개", + "packagingDefaultPack": "기본 포장 크기: {units}개", + "packagingNoDefaultPack": "기본 포장 크기가 설정되지 않았어요", + "packagingEdit": "편집", + "packagingDescription": "1회분에 몇 개를 쓰는지, 새 포장이 보통 몇 개인지 설정해요 — 예를 들어 제조사가 바뀌어 포장 크기가 달라졌을 때요.", + "packagingUnitsPerDoseLabel": "1회분당 개수", + "packagingUnitsPerDoseHelper": "예: 4mg 복용에 2mg 정제 2개 (1–100). 이미 기록된 복용은 그대로예요.", + "packagingDefaultPackLabel": "기본 포장 크기 (개)", + "packagingDefaultPackHelper": "새 포장을 등록할 때 수량을 미리 채워요 (1–1000). 해당 없으면 비워 두세요.", + "packagingSuccess": "포장 정보를 저장했어요", + "packagingFailed": "저장하지 못했어요", + "addManufacturerLabel": "제조사", + "addDoseStrengthLabel": "함량", + "addDoseStrengthHint": "용기에 적힌 그대로 적어 주세요. 예: 5mg/0.5ml.", + "openedOn": "{date} 개봉", + "expiresOn": "{date} 만료", + "summaryAllExpired": "쓸 수 있는 재고 없음: {units}개가 유효기간 지남", + "editExpiryLabel": "표기 유효기간", + "editExpiryHelper": "포장에 인쇄된 날짜예요. 기록된 게 없으면 비워 두세요.", + "editOpenedLabel": "개봉일", + "editOpenedHelper": "아직 개봉하지 않았으면 비워 두세요.", + "editOpenedHelperClock": "펜이나 앰플은 개봉 후 30일이 지나면 만료돼요. 아직 개봉하지 않았으면 비워 두세요." + }, + "erweitert": { + "group": { + "lifecycle": "수명 주기", + "externalApi": "외부 API", + "data": "데이터", + "danger": "위험 영역" + }, + "editor": { + "title": "편집기에서 약 수정", + "helper": "단계별 편집기에서 이름, 치료 분류, 주기, 일정을 바꿀 수 있어요.", + "button": "편집기 열기" + } + }, + "zeitplan": { + "title": "일정", + "timesTitle": "복용 시각", + "timesNote": "복용마다 시각과 정시 범위를 조정해요. 주기(매일, 매주…)는 고급 탭의 편집기에서 바꿔요.", + "scheduleLabel": "일정 {index}", + "fullEditNote": "주기, 치료 분류, 복용 기간은 고급 → 수명 주기의 편집기에서 바꿔요.", + "graceEcho": "알림 유예 시간: {minutes}분", + "reminderTitle": "알림", + "manageChannels": "알림 채널 관리", + "history": { + "title": "일정 변경 이력", + "currentSince": "{date}부터 현재 일정", + "noTimes": "정해진 시각 없음", + "manualChip": "직접 추가함", + "showErasOne": "이전 구간 1개 보기", + "showErasOther": "이전 구간 {count}개 보기", + "hideEras": "이전 구간 숨기기", + "addEra": "이전 구간 추가", + "addEraTitle": "이전 구간 추가", + "addEraDescription": "예전에 이 약을 복용하던 시각을 기록해요 — 예를 들어 여기서 일정을 바꾸기 전 시각이요.", + "editAria": "구간 편집", + "editEraTitle": "구간 편집", + "editEraDescription": "이 구간의 복용 시각과 기간을 조정해요.", + "archivedEditHint": "이 구간은 자동으로 기록됐어요 — 수정하면 기록된 이력이 바로잡히고, 원본은 내부에 기록으로 남아요.", + "updatedToast": "구간을 수정했어요", + "updateError": "구간을 수정하지 못했어요. 다른 구간과 겹치지 않고, 현재 일정보다 먼저 끝나야 해요.", + "timesLabel": "복용 시각", + "fromLabel": "시작", + "untilLabel": "종료", + "untilHint": "'종료'는 다음 일정이 시작된 날이에요(그날은 제외).", + "validationTimes": "시각을 하나 이상 추가해 주세요.", + "validationRangeMissing": "시작일과 종료일을 모두 설정해 주세요.", + "validationRangeOrder": "시작일이 종료일보다 앞서야 해요.", + "cancel": "취소", + "submit": "구간 저장", + "createdToast": "이전 구간을 저장했어요", + "createError": "구간을 저장하지 못했어요. 기존 구간과 겹치지 않고, 현재 일정보다 먼저 끝나야 해요.", + "deletedToast": "구간을 삭제했어요", + "deleteError": "구간을 삭제하지 못했어요", + "deleteAria": "구간 삭제", + "deleteConfirmTitle": "구간을 삭제할까요?", + "deleteConfirmBody": "이 기간의 기록과 복약 순응도는 다시 현재 일정 기준으로 계산돼요.", + "deleteConfirmAction": "삭제", + "loadError": "일정 변경 이력을 불러오지 못했어요" + }, + "save": "일정 저장", + "saved": "일정을 저장했어요", + "failed": "일정을 저장하지 못했어요", + "window": { + "intro": "복용마다 정시로 인정되는 범위를 정해요 — 정확한 시각(±1h)이나 07:00–09:00 같은 직접 지정 범위로요.", + "explicitToggle": "직접 범위 지정", + "startLabel": "범위 시작", + "endLabel": "범위 종료", + "onTimeBand": "정시: {start}–{end}", + "lateUntil": "{time}까지 늦음", + "lateDays": "{days}일 뒤까지 늦음", + "orderError": "시작이 종료보다 앞서야 해요.", + "noTimes": "아직 설정된 복용 시각이 없어요." + } + } + }, + "logInjectionSiteTitle": "주사 부위 기록", + "logInjectionSiteDescription": "{name} — 어디에 주사했나요? 선택 사항이라 건너뛰어도 돼요.", + "logInjectionSiteSkip": "건너뛰기", + "logInjectionSiteConfirm": "부위 저장", + "logInjectionSiteNoneAvailable": "쓸 수 있는 주사 부위가 없어요 — 설정에서 모든 부위를 제외했어요.", + "logInjectionSiteSaveFailed": "주사 부위를 저장하지 못했어요. 다시 시도해 주세요.", + "perSlotUnits": "1회분당 {units}", + "trackInjectionSitesToggle": "주사 부위 기록하기", + "trackInjectionSitesHint": "복용 후 어느 부위를 썼는지 묻고, 다음 순환 부위를 알려줘요.", + "allowedSitesLabel": "허용 부위", + "allowedSitesHint": "전부 선택해 두면 제한이 없어요. 전체 설정에서 제외한 부위는 제안하지 않아요.", + "viewToggleLabel": "보기", + "viewToggleHint": "약을 카드로 볼지 표로 볼지 선택하세요.", + "viewCards": "카드 보기", + "viewTable": "표 보기", + "viewSaveFailed": "보기를 저장하지 못했어요", + "tableCaption": "상태, 다음 복용, 복약 순응도, 재고를 담은 복약 표", + "tableColName": "이름", + "tableColStatus": "상태", + "tableColNextDose": "다음 복용", + "tableColCompliance": "복약 순응도", + "tableColStock": "재고", + "tableColActions": "작업", + "tableSortBy": "정렬 기준: {column}", + "tableComplianceDays": "{days}일", + "tableStockDoseOne": "1회분", + "tableStockDoses": "{count}회분", + "tableStockRunway": "약 {days}일 남음", + "cardLowStockRunway": "재고: 약 {days}일", + "takeActionFor": "복용 – {name}", + "skipActionFor": "건너뛰기 – {name}", + "reorderTitle": "순서 조정", + "reorderDescription": "약의 순서를 정하세요 – 카드 보기와 표 보기 모두에 적용돼요.", + "reorderSaved": "순서를 저장했어요", + "reorderSaveFailed": "순서를 저장하지 못했어요", + "customize": "복약 맞춤 설정", + "efficacy": { + "title": "효과", + "intro": "이 약이 영향을 주려는 측정값이 복용을 시작한 무렵을 전후로 어떻게 움직였는지 보여줘요. 시간상 함께 움직인 연관성일 뿐 원인은 아니고, 치료나 용량에 대한 조언도 아니에요.", + "notEligible": "이 약과 연결할 결과 지표가 없어요.", + "startFallback": "시작일이 설정되지 않아서 처음 기록한 측정값을 기준점으로 비교해요.", + "noStart": "이 약의 시작일을 설정하면 복용 전후 측정값을 비교할 수 있어요.", + "disclaimer": "연관성일 뿐이에요: 이 수치는 같은 기간에 무엇이 변했는지를 보여줄 뿐, 약이 그 변화를 일으켰다는 뜻은 아니에요. 치료에 관한 결정은 의사와 상의하세요.", + "primaryBadge": "주요 목표", + "noSeries": "이 목표로 기록된 측정값이 아직 없어요.", + "startMarker": "시작", + "adherenceLane": "복약 순응도", + "typicalRange": "일반적인 범위", + "levelShift": "{date} 무렵에 수준 변화가 감지됐어요.", + "plateau": { + "title": "현재 용량에서 체중 정체", + "evidence": "최근 {window}일 동안 체중이 {delta}{unit} 움직였어요({readings}개 측정값) — {dose}로 {days}일." + }, + "retarget": { + "label": "맞는 측정값이 아닌가요?", + "placeholder": "비교할 측정값…", + "apply": "적용", + "reset": "기본값으로 되돌리기", + "error": "변경 내용을 저장하지 못했어요. 다시 시도해 주세요." + }, + "beforeAfter": { + "summary": "시작 전 평균 {before} → 이후 평균 {after} — {delta} 변화.", + "counts": "시작 전 {beforeCount}개, 이후 {afterCount}개 측정값을 기준으로 했어요.", + "needBefore": "시작 전 측정값이 아직 부족해요 — 비교하려면 전후로 약 {weeks}주씩 필요해요.", + "needAfter": "시작 이후 측정값이 아직 부족해요 — 비교하려면 전후로 약 {weeks}주씩 필요해요.", + "needStart": "시작일을 설정하면 복용 전후 측정값을 비교할 수 있어요.", + "needData": "측정값이 아직 부족해요 — 비교하려면 시작 전후로 약 {weeks}주씩 필요해요." + }, + "insights": { + "title": "약 효과", + "subtitle": "약이 목표로 하는 측정값과 무엇이 함께 움직였는지 보여줘요 — 연관성일 뿐 판단이 아니에요.", + "link": "효과 보기 열기", + "empty": "약과 목표 측정값을 연결하기에는 데이터가 아직 부족해요." + }, + "scaleAbsolute": "절대값", + "scalePercentSinceStart": "시작 이후 %", + "scaleToggleLabel": "차트 눈금" + }, + "import": { + "resultSuccess": "복용 {imported}건을 불러왔어요", + "resultPartial": "{imported}건 불러오고 {skipped}건 건너뛰었어요.", + "resultNothing": "불러온 게 없어요: {skipped}개 기록을 모두 건너뛰었어요.", + "resultAlreadyRecorded": "복용 {recorded}건은 이미 기록돼 있어요. 새로 추가된 건 없어요.", + "resultEmpty": "그 파일에는 복용 기록이 없었어요.", + "showSkipDetails": "건너뛴 행 보기({count})", + "skipDetailLine": "{line}번째 줄: {reason}", + "skipDetailsOmitted": "건너뛴 행 {count}개는 표시하지 않았어요.", + "skipGroupOne": "{count}개 기록을 건너뛰었어요: {reason}", + "skipGroupFew": "{count}개 기록을 건너뛰었어요: {reason}", + "skipGroupOther": "{count}개 기록을 건너뛰었어요: {reason}", + "reasonDuplicateInFile": "같은 파일의 다른 기록과 날짜·시간이 같아요", + "reasonAlreadyRecorded": "그 날짜·시간에 복용 기록이 이미 있어요", + "reasonUnknown": "사유가 기록되지 않았어요", + "reasonMedicationNotFound": "파일의 이름과 일치하는 약이 기록에 없어요", + "reasonMedicationAmbiguous": "파일의 이름과 일치하는 약이 기록에 여러 개예요", + "reasonMedicationIsMirrored": "이 약은 다른 앱 목록에서 가져온 읽기 전용 사본이에요", + "reasonStatusUnknown": "파일의 상태값이 형식에 정의된 값이 아니에요", + "reasonMissingTimestamp": "행에 시간이 없어요", + "reasonMissingOffset": "시간에 UTC 오프셋이 없어서 정확한 시점을 알 수 없어요", + "reasonUnreadableTimestamp": "시간이 내보내기 문서에 정의된 형식이 아니에요", + "reasonImplausibleTimestamp": "시간이 실제로 존재하지 않거나 미래예요", + "reasonMissingMedication": "행에 약 이름이 없어요", + "reasonUnreadableDosage": "복용량이 숫자가 아니에요", + "reasonUnreadableRow": "행의 열 개수가 머리글보다 적어요", + "reasonStatusNoDoseInformation": "파일에 그 복용에 대한 기록이 없어요 — 응답도 없었고 기록된 것도 없어요", + "reasonStatusReminderEvent": "행이 미뤄 둔 알림에 관한 내용이라 복용에 대해 알려주는 게 없어요", + "reasonStatusNotificationNotSent": "행은 알림이 발송되지 않았다는 기록이라 복용이 아니라 앱에 관한 내용이에요" + } + }, + "charts": { + "loadingLabel": "차트 불러오는 중…", + "loadingSlowHint": "분석을 계산하는 중이에요 — 잠시 걸릴 수 있어요.", + "weight": "체중", + "bloodPressure": "혈압", + "pulse": "맥박", + "bodyFat": "체지방", + "sleep": "수면", + "steps": "걸음 수", + "hrv": "심박변이도", + "restingHeartRate": "안정 시 심박수", + "activeEnergy": "활동 에너지", + "flights": "오른 층수", + "distance": "걷기+달리기 거리", + "vo2Max": "최대산소섭취량", + "bodyTemperature": "체온", + "noData": "선택한 기간에 데이터가 없어요", + "bucketWeekly": "주 평균", + "bucketMonthly": "월 평균", + "points7Label": "7개", + "points7Title": "최근 데이터 7개", + "points30Label": "30개", + "points30Title": "최근 데이터 30개", + "points90Label": "90개", + "points90Title": "최근 데이터 90개", + "pointsAllLabel": "전체", + "pointsAllTitle": "사용 가능한 데이터 전체", + "systolic": "수축기", + "diastolic": "이완기", + "bodyWater": "체수분", + "boneMass": "골량", + "spo2": "산소포화도", + "movingAverage7d": "7일 추이", + "avg7dShort": "7일", + "avg30dShort": "30일", + "trend7dShort": "7일 추이", + "trend": "추이", + "perWeek": "/주", + "totalDelta": "전체", + "colorGreen": "초록:", + "colorOrange": "주황:", + "colorRed": "빨강:", + "mood": "기분", + "moodScore": "기분 점수", + "weekdays": { + "mon": "월", + "wed": "수", + "fri": "금", + "sun": "일" + }, + "months": { + "jan": "1월", + "feb": "2월", + "mar": "3월", + "apr": "4월", + "may": "5월", + "jun": "6월", + "jul": "7월", + "aug": "8월", + "sep": "9월", + "oct": "10월", + "nov": "11월", + "dec": "12월" + }, + "heatmapOnTime": "정시", + "heatmapLate": "늦음", + "heatmapVeryLate": "많이 늦음", + "legendOnTime": "정시", + "legendLate": "늦음", + "legendVeryLate": "많이 늦음", + "legendMissed": "놓침", + "emptyStateTitle": "측정을 더 추가하면 추이가 보여요", + "emptyStateDescription": "기록을 3개 이상 남기면 추세선이 열려요.", + "needMoreDistinctDaysTitle": "서로 다른 날짜가 더 필요해요", + "needMoreDistinctDaysDescription": "여러 날에 걸쳐 기록하면 추세선이 열려요.", + "noDataInRangeTitle": "이 기간에 데이터가 없어요", + "noDataInRangeDescription": "선택한 기간에 들어가는 측정이 없어요. 기간을 바꾸거나 새로 측정을 기록해 주세요.", + "errorTitle": "데이터를 불러오지 못했어요", + "loadErrorTitle": "차트를 불러오지 못했어요", + "loadErrorReload": "다시 불러오기", + "sparseDataCaption": "날이 쌓이면 추이가 채워져요.", + "dataTable": { + "toggle": "데이터 포인트 ({count})", + "columnDate": "날짜", + "noValue": "값 없음", + "captionDay": "{metric} 하루당 한 행, 차트가 그리는 값과 같아요.", + "captionWeek": "{metric} 한 주당 한 행, 그 주 일별 평균 — 차트가 그리는 값과 같아요.", + "captionMonth": "{metric} 한 달당 한 행, 그달 일별 평균 — 차트가 그리는 값과 같아요." + }, + "personalBaseline": "내 평소값", + "deltaVsBaseline": "평소값 대비 {delta}", + "deltaUnchanged": "평소값과 같아요", + "deltaVsTarget": "목표 대비 {delta}", + "weekdaysFull": { + "mon": "월요일", + "tue": "화요일", + "wed": "수요일", + "thu": "목요일", + "fri": "금요일", + "sat": "토요일", + "sun": "일요일" + }, + "rangeBand": "범위", + "a11y": { + "summary": "{metric} 차트. 범위 {min}~{max}, 최신 {latest}, 추이 {trend}.", + "noData": "{metric} 차트. 데이터가 없어요.", + "trendUp": "상승", + "trendDown": "하락", + "trendFlat": "유지", + "scatterSummary": "상관관계 차트. {xLabel} 대비 {yLabel}, 점 {count}개.", + "scatterNoData": "{xLabel} 대비 {yLabel} 상관관계 차트. 데이터가 없어요.", + "moodHeatmap": "기분 히트맵, {start}부터 {end}까지, {days}일 기록.", + "moodHeatmapEmpty": "기분 히트맵, 아직 기록한 날이 없어요.", + "complianceHeatmap": "복약 순응도 히트맵, {start}부터 {end}까지, 활동일 {days}일.", + "complianceHeatmapEmpty": "복약 순응도 히트맵, 아직 활동일이 없어요." + } + }, + "chart": { + "overlay": { + "controls": { + "title": "차트 오버레이", + "trendIndicator": "7일 추이", + "trendArrow": "추이 화살표", + "targetRange": "목표 범위", + "comparisonBaseline": "비교", + "comparisonUnavailable": "이전 기간 데이터가 아직 없어요", + "tooltip": { + "openSettings": "차트 오버레이 설정" + } + } + } + }, + "comparison": { + "baseline": { + "none": "없음", + "lastMonth": "지난달", + "lastYear": "작년" + }, + "captionLastMonth": "지난달 대비", + "captionLastYear": "작년 대비", + "deltaVs": { + "lastMonth": "지난달 대비 {delta}", + "lastYear": "작년 대비 {delta}" + }, + "tooltipPrior": "이전 기간", + "unavailable": { + "lastMonth": "비교 불가 — 지난달 데이터가 아직 없어요", + "lastYear": "비교 불가 — 작년 데이터가 아직 없어요" + }, + "notAvailableForCompliance": "복약 순응도는 비교 불가" + }, + "insights": { + "pattern": { + "dismiss": "숨기기", + "dismissed": "근거가 바뀔 때까지 이 패턴을 숨겨요.", + "restore": "다시 보기", + "dismissError": "이 패턴을 숨기지 못했어요.", + "restoreError": "이 패턴을 다시 표시하지 못했어요.", + "loadError": "패턴을 불러오지 못했어요.", + "empty": "근거 기준선을 넘은 패턴이 아직 없어요." + }, + "correlation": { + "direction": { + "higher": "더 높은", + "lower": "더 낮은" + }, + "weekdayDirection": { + "above": "높은", + "below": "낮은" + }, + "channelFactor": "{factor} 평가", + "channel": { + "WEIGHT": "체중", + "BLOOD_GLUCOSE": "혈당", + "BLOOD_PRESSURE_SYS": "수축기 혈압", + "BLOOD_PRESSURE_DIA": "이완기 혈압", + "ACTIVITY_STEPS": "하루 걸음 수", + "SLEEP_DURATION": "수면 시간", + "HEART_RATE_VARIABILITY": "심박변이도", + "RESTING_HEART_RATE": "안정 시 심박수", + "TIME_IN_DAYLIGHT": "햇빛 노출 시간", + "MOOD": "기분", + "PULSE": "안정 시 맥박", + "MEDICATION_COMPLIANCE": "복약 순응도", + "SYMPTOM_SEVERITY": "증상 심각도", + "ENV_TEMP_MEAN": "일평균 기온", + "ENV_TEMP_MIN": "밤 최저 기온", + "ENV_SUNSHINE": "일조량", + "ENV_DAYLIGHT": "낮 길이", + "ENV_PRECIP": "강수량", + "ENV_PRESSURE_MEAN": "기압", + "ENV_PRESSURE_DELTA": "하루 중 기압 변화" + }, + "daily": { + "high": "{behaviour} 수치가 높은 날에는 다음 날 {outcome} 수치가 {direction} 경향이 있어요 — 원인이 아니라 지켜볼 만한 패턴이에요.", + "moderate": "지금까지의 근거로는 {behaviour} 수치가 높은 날에 다음 날 {outcome} 수치가 {direction} 것처럼 보여요 — 원인이 아니라 지켜볼 만한 패턴이에요.", + "faint": "{behaviour} 수치가 높은 날에 다음 날 {outcome} 수치가 {direction} 아주 희미한 기미만 보여요 — 기대기엔 너무 작고, 원인은 더더욱 아니에요." + }, + "lab": { + "default": "{lab} 측정값이 높은 시기에는 같은 기간의 {outcome} 수치도 {direction} 편이에요 — 원인이 아니라 임상의와 함께 지켜볼 만한 연관이에요.", + "faint": "{lab} 측정값이 높은 시기에 같은 기간의 {outcome} 수치가 {direction} 아주 희미한 기미만 보여요 — 기대기엔 너무 작고, 원인은 아니지만 임상의에게 이야기해 볼 만한 연관이에요." + }, + "hypothesis": { + "bpCompliance": { + "support": "복약 순응도가 높은 날에 수축기 측정값이 낮은 편이에요 — 지켜볼 만한 패턴이에요.", + "surprising": "기록에서는 순응도와 수축기 혈압이 같은 방향으로 움직여요 — 뜻밖이에요. 무언가 조정하기 전에 의사와 상의해 주세요.", + "none": "이 기간에는 순응도와 수축기 혈압이 뚜렷하게 함께 움직이지 않아요." + }, + "moodPulse": { + "support": "기분이 낮은 날에 안정 시 맥박이 높은 편이에요 — 지켜볼 만한 패턴이에요.", + "surprising": "기록에서는 기분이 좋은 날에 안정 시 맥박도 높은 편이에요 — 뜻밖이에요.", + "none": "이 기간에는 기분과 안정 시 맥박이 뚜렷하게 함께 움직이지 않아요." + }, + "weightWeekday": "{weekday} 체중이 다른 요일 평균보다 {delta}kg {direction} 편이에요 — 지켜볼 만한 패턴이에요." + } + }, + "noAnalysisYet": "아직 평가가 없어요.", + "assessmentPreparing": "평가를 준비하고 있어요…", + "emptyTitle": "아직 데이터가 부족해요", + "emptyDescription": "측정을 몇 번 기록하면 인사이트가 켜져요. 체중이나 혈압부터 시작해 보세요.", + "emptyAddMeasurement": "측정 추가", + "editMode": { + "done": "완료", + "reset": "초기화", + "resetTitle": "개요를 초기화할까요?", + "resetBody": "섹션 순서와 숨긴 섹션이 기본값으로 돌아가요. 배치는 다른 곳에 저장되지 않아요.", + "resetConfirm": "개요 초기화", + "title": "개요 맞춤 설정", + "description": "끌어서 순서를 바꾸고, 눈 아이콘을 눌러 표시하거나 숨겨 주세요.", + "dragHandle": "끌어서 순서 바꾸기", + "show": "표시", + "hide": "숨기기", + "manageInSettings": "칩과 상세 페이지는 설정에서 관리", + "groupSleep": "수면", + "groupMood": "기분", + "groupEvents": "이벤트", + "gatedHint": "데이터나 기능이 준비되면 나타나요", + "allHiddenHint": "모든 섹션이 숨겨져 있어요. 개요를 보려면 하나 이상 표시해 주세요.", + "saveSuccess": "개요 배치를 저장했어요", + "saveError": "개요 배치를 저장하지 못했어요", + "resetSuccess": "개요를 기본값으로 되돌렸어요", + "emptyTitle": "모든 카드가 숨겨져 있어요", + "emptyDescription": "모든 섹션을 숨겼어요. 맞춤 설정을 열어 카드를 다시 꺼내 보세요." + }, + "bmiEmptyTitle": "BMI를 보려면 키를 입력해 주세요", + "bmiEmptyDescription": "BMI에는 키가 필요해요. 설정 → 계정에서 추가해 주세요.", + "bmiEmptyAction": "계정 설정 열기", + "noProviderConfigured": "AI 제공자를 연결하면 여기에서 측정값 평가를 볼 수 있어요.", + "noProviderAction": "AI 설정 열기", + "personalRecord": { + "badge": "신기록", + "tooltip": "개인 최고 기록: {date} {value}{unit}" + }, + "loadError": "인사이트를 불러오지 못했어요", + "heroPersonalBaseline": "최근 90일 기준", + "heroRegenerating": "다시 생성 중", + "regenerateAnalysis": "평가 다시 실행", + "regenerateSuccess": "평가를 새로 고쳤어요", + "narrativeTitle": "돌아보기", + "narrativeWeek": "이번 주", + "narrativeMonth": "이번 달", + "narrativeUpdating": "업데이트 중…", + "heroFallbackSubtitle": "기록한 숫자에서 바로 뽑아낸, 매일의 추이 읽기예요.", + "heroGreetingMorning": "좋은 아침이에요", + "heroGreetingAfternoon": "좋은 오후예요", + "heroGreetingEvening": "좋은 저녁이에요", + "heroActionAskCoach": "코치에게 묻기", + "healthScore": { + "label": "건강 점수", + "band": { + "green": "참고 범위 안이에요", + "yellow": "일부 항목은 주의가 필요해요", + "red": "참고 범위를 벗어난 항목이 하나 이상 있어요" + }, + "insufficient": "건강 영역 {required}개 중 {count}개에 최근 데이터가 충분해요. 점수에는 {required}개가 필요하고, 그중 하나는 신체 측정이어야 해요.", + "anatomyToggle": "이 점수가 만들어지는 방식", + "notScored": "아직 반영되지 않음 ({count})", + "method": "점수는 반영 대상 항목을 같은 비중으로 평균 낸 값이고, 0에서 100까지예요. 각 항목은 공개된 참고 구간에 맞춰 채점되고, 가장 약한 항목이 전체 구간을 정할 수 있어요. 최근 데이터가 부족한 항목은 0점으로 세지 않고 빼 둬요. 참고 구간은 공개된 출처에서 가져왔고, 어떤 항목을 반영할지는 직접 고르는 값이라 합쳐진 숫자 자체가 임상 기준은 아니에요.", + "bandSetter": "전체 구간을 정한 항목: {pillar}", + "versionAndNoise": "산출 방식 버전 {version}. 주간 비교 최소 변화폭: {noise}점.", + "methodVersion": "산출 방식 버전 {version}.", + "configured": "이 점수에 반영할 항목을 직접 골랐어요.", + "none": "없음", + "delta": "이전 비교 구간 대비 {delta}", + "deltaReason": { + "algorithm_changed": "점수 산출 방식이 바뀌어서 비교하지 않아요.", + "config_changed": "점수에 반영되는 항목을 바꿔서 비교하지 않아요.", + "composition_changed": "포함된 항목이 달라져서 비교하지 않아요.", + "first_eligibility_window": "첫 유효 구간이라 비교하지 않아요.", + "below_noise_floor": "변화가 측정 오차 범위 안이에요.", + "no_previous_window": "비교할 만한 이전 구간이 없어요.", + "no_current_score": "지금 구간이 유효하지 않아 비교하지 않아요." + }, + "restMode": "{since}부터 휴식 모드예요. 점수는 참고용으로만 보여 주고 감점하지 않아요.", + "pillar": { + "bloodPressure": "혈압", + "glycaemia": "혈당", + "activity": "활동", + "sleep": "수면", + "adiposity": "체지방", + "wellbeing": "마음 건강", + "lipids": "지질" + }, + "observed": "측정: {value}", + "reference": "참고: {value}", + "fastingObserved": "공복 혈당: {value}{unit}", + "fastingReference": "공복 {low}~{high}{unit}", + "personalReference": "내 목표: {value}", + "bpBasisAbove": "{axis} 기준으로 정해진 점수예요. 상한 {boundary}mmHg보다 약 {offset}mmHg 높아요.", + "bpBasisBelowFloor": "{axis} 기준으로 정해진 점수예요. 저혈압 하한 {boundary}mmHg보다 약 {offset}mmHg 낮아요.", + "bpBasisInBand": "두 값 모두 참고 상한 이하예요.", + "bpScale": "상한에서는 85점이고, 100점은 그보다 약 12mmHg 낮다는 뜻이에요.", + "bpRepresentative": "최근 {days}일에 가중치를 둔 평균이라 개별 측정값과 다를 수 있어요.", + "source": "출처: {source}, {date} 기준", + "observedActivity": "하루 {value}걸음", + "observedSleep": "하룻밤 {value}분", + "observedAdiposity": "허리-키 비율 {value}", + "referenceActivity": "하루 {high}걸음쯤에서 이득이 평평해져요", + "referenceSleep": "하룻밤 최소 {low}분, 더 자는 건 감점하지 않아요", + "referenceAdiposity": "허리-키 비율 {high} 미만", + "referenceHba1c": "HbA1c {low}~{high}%", + "referenceDetail": "{label} · {source} · {kind} · {low}~{high}", + "openBound": "제한 없음", + "laboratoryReferenceSource": "검사 기관 참고 범위", + "personalTargetSource": "개인 목표", + "referenceKind": { + "clinical": "임상 기준선", + "population": "인구 백분위", + "guideline": "지침 구간" + }, + "reason": { + "readFailed": "데이터를 불러오지 못했어요", + "notTracked": "기록하지 않음", + "missingProfile": "나이나 참고 프로필이 없어요", + "missingHeight": "키가 없어요", + "incompletePanel": "참고 범위가 있는 최근 전체 검사 결과가 필요해요", + "crisis": "점수 대신 안전 안내를 보여 줘요", + "insufficientHistory": "최근 유효 데이터가 부족해요", + "unavailable": "이 항목은 지금 반영 대상이 아니에요" + }, + "tension": "컨디션 신호가 엇갈려요. 좋은 쪽: {positive}. 부담이 큰 쪽: {negative}.", + "returnToBand": "{metric} — {days}일째 평소 범위 안으로 돌아와 있어요.", + "weightGoal": { + "title": "개인 체중 목표", + "value": "지금 {current}{unit}. 목표 {min}~{max}{unit}. 목표까지 {distance}{unit}.", + "delta": "직전 측정값 이후 목표까지 거리가 {delta}{unit} 바뀌었어요.", + "noDelta": "비교할 이전 체중 측정값이 없어요.", + "no_personal_goal": "개인 체중 목표를 정하면 여기에서 진행 상황을 볼 수 있어요.", + "weight_not_tracked": "체중을 기록하면 목표까지 진행 상황을 볼 수 있어요.", + "read_failed": "체중 데이터를 불러오지 못했어요. 다시 시도해 주세요.", + "notScored": "체중 목표 진행 상황은 개인 참고용이라 건강 점수에는 영향을 주지 않아요." + }, + "configureLink": "점수에 반영할 항목 고르기", + "coverage": { + "minimumMet": "점수에 필요한 세 영역을 채웠어요", + "areas": "건강 영역 {required}개 중 {present}개" + } + }, + "dailyBriefing": { + "title": "데일리 브리핑", + "keyFindingsTitle": "핵심 발견", + "signalsTitle": "오늘의 신호", + "emptyTitle": "아직 브리핑이 없어요", + "emptyDescription": "오늘 브리핑을 만들면 추이를 이야기로 읽어 줘요.", + "emptyAction": "브리핑 생성", + "loadingLabel": "오늘 브리핑을 정리하는 중", + "noProviderTitle": "연결된 AI 제공자가 없어요", + "noProviderDescription": "HealthLog를 로컬 모델(Ollama, LM Studio — 네트워크 밖으로 나가지 않아요)에 연결하거나 직접 쓰는 OpenAI·Anthropic 키를 넣으면, 데일리 브리핑이 내 측정값을 쉬운 말 요약으로 바꿔 줘요. 설정은 1분쯤 걸려요.", + "noProviderAction": "AI 설정 열기", + "staleNoProviderHint": "연결된 AI 제공자가 없어서 이 브리핑을 새로 고칠 수 없어요.", + "refreshFailedHint": "브리핑을 새로 고치지 못해서 마지막 브리핑을 보여 줘요.", + "retryAction": "다시 시도", + "failedTitle": "브리핑을 만들지 못했어요", + "failedDescription": "AI 제공자가 응답 시간을 넘겼거나 오류를 돌려줬어요. 다시 시도하거나, 느린 로컬 모델이라면 AI 설정에서 응답 제한 시간을 늘려 주세요.", + "failedDescriptionTimeout": "브리핑이 준비되기 전에 AI 요청 시간이 다 됐어요. 느린 로컬 모델이라면 설정에서 AI 응답 제한 시간을 늘린 뒤 다시 시도해 주세요.", + "failedDescriptionProvider": "AI 제공자가 요청을 거절했어요. AI 설정에서 제공자와 API 키를 다시 확인한 뒤 시도해 주세요.", + "failedDescriptionRateLimit": "AI 제공자가 지금 요청 수를 제한하고 있어요. 잠시 뒤에 다시 시도해 주세요.", + "omittedTitle": "브리핑을 보류했어요", + "omittedUngroundedDescription": "만들어진 브리핑에 내 데이터로 확인할 수 없는 수치가 있어서 보여 주지 않았어요. 다시 시도해 주세요." + }, + "trendsRow": { + "title": "추이", + "subtitle": "최근 30일 한눈에 보기", + "caption": { + "bp": "최근 30일 동안의 수축기·이완기 측정값이에요.", + "weight": "최근 30일 동안의 체중 추이예요.", + "pulse": "최근 30일 동안의 안정 시 맥박이에요.", + "mood": "최근 30일 동안의 하루 기분 점수예요.", + "sleep": "최근 30일 동안의 밤별 수면 시간이에요.", + "steps": "최근 30일 동안의 하루 걸음 수예요.", + "hrv": "최근 30일 동안의 심박변이도예요.", + "resting_hr": "최근 30일 동안의 안정 시 심박수예요.", + "active_energy": "최근 30일 동안의 활동 소모 열량이에요.", + "flights": "최근 30일 동안 오른 계단 층수예요.", + "distance": "최근 30일 동안의 걷기·달리기 거리예요.", + "vo2_max": "최근 30일 동안의 VO₂ max예요.", + "body_temp": "최근 30일 동안의 체온이에요." + }, + "viewDetail": "{metric} 자세히 보기" + }, + "vo2Max": { + "cardioLinkTitle": "심폐 체력 (VO₂ max)", + "cardioLinkBody": "VO₂ max는 유산소 체력을 대표하는 지표예요. 전체 차트와 평가는 별도 페이지에 있어요.", + "cardioLinkCta": "심폐 체력 열기" + }, + "trendAnnotation": { + "emptyBp": "이 추이에 설명을 붙이려면 데이터가 더 필요해요.", + "emptyWeight": "이 추이에 설명을 붙이려면 데이터가 더 필요해요.", + "emptyMood": "이 추이에 설명을 붙이려면 데이터가 더 필요해요.", + "confidenceHigh": "신뢰도 높음", + "confidenceModerate": "신뢰도 보통", + "confidenceLow": "신뢰도 낮음", + "pendingLabel": "추이 해설을 만드는 중" + }, + "correlationRow": { + "title": "관계", + "experimentCta": "7일 실험 해보기", + "experimentTooltip": "곧 제공돼요", + "emptyTitle": "이 관계를 보려면 데이터가 더 필요해요", + "sourceChip": "짝지은 측정값 {n}개 기준 · {window}", + "sourceWindowLast30": "최근 30일", + "confidenceHigh": "신뢰도 높음", + "confidenceModerate": "신뢰도 보통", + "confidenceLow": "신뢰도 낮음", + "card": { + "bpComplianceTitle": "혈압 × 복약 순응도", + "bpComplianceSubtitle": "하루 수축기 혈압과 순응도(%) 비교", + "moodPulseTitle": "기분 × 안정 시 맥박", + "moodPulseSubtitle": "같은 날의 기분과 안정 시 맥박", + "weightWeekdayTitle": "체중 × 요일", + "weightWeekdaySubtitle": "요일별로 묶은 체중" + } + }, + "coach": { + "refusalOutOfScope": "기록에 있는 건강 지표 — 측정값, 약, 기분, 추이 — 만 도와드릴 수 있어요. 그 밖의 질문에는 답할 수 없어요.", + "refusalInjection": "제 지시를 덮어쓰려는 표현이 보였어요. 저는 HealthLog 코치이고 회원님의 건강 데이터만 정리해 드려요 — 그 범위로 질문을 다시 적어 주세요.", + "errorBudget": "오늘 코치 사용 한도에 도달했어요. 사용량은 UTC 자정에 초기화돼요.", + "errorProvider": "지금은 코치가 인사이트 제공자에 연결하지 못했어요. 잠시 후 다시 시도해 주세요.", + "errorNoProvider": "코치는 기록된 건강 데이터를 읽고 그에 대한 질문에 답해요(의학적 조언은 아니에요). 그러려면 HealthLog에 AI 제공자가 필요해요 — 직접 발급한 키나 로컬 모델을 연결해 주세요.", + "errorNoProviderAction": "AI 제공자 연결", + "errorNetwork": "인터넷에 연결되어 있지 않아요 — 다시 연결되면 시도해 주세요.", + "prefsSaveError": "코치 설정을 저장하지 못했어요. 다시 시도해 주세요.", + "errorCredentialExpired": "AI 제공자 연결이 만료됐어요. 코치를 계속 쓰려면 설정에서 다시 연결해 주세요.", + "tagline": "내 데이터에 기반한 개인 건강 코치.", + "newChat": "새 대화", + "docScope": { + "badge": "문서", + "chattingAbout": "대화 중인 문서: {title}", + "chattingAboutMore": "대화 중인 문서: {titles} 외 {count}개", + "notIndexedHint": "코치가 답할 수 있도록 보관함에서 이 문서를 AI로 먼저 읽어 주세요." + }, + "actionsMenu": "작업", + "maximizeAriaLabel": "전체 화면으로 열기", + "minimizeAriaLabel": "서랍으로 돌아가기", + "send": "보내기", + "stop": "중지", + "thinking": "생각 중…", + "unverifiedFiguresNotice": "일부 수치는 데이터와 대조하지 못해 제외했어요.", + "composerPlaceholder": "무엇이든 물어보세요…", + "nudgeBubbleLabel": "코치의 새 메시지 — 대화 열기", + "fabLabel": "코치 열기", + "readAloud": "소리내어 읽기", + "readAloudStop": "읽기 중지", + "messageTimeLabel": "보낸 시각 {time}", + "copyMessage": "복사", + "copyMessageError": "복사하지 못했어요", + "regenerate": "다시 시도", + "selfContextAdopt": { + "offer": "이 답변을 셀프 컨텍스트에 추가할까요?", + "confirm": "추가", + "dismiss": "닫기", + "done": "셀프 컨텍스트에 추가했어요", + "duplicate": "이미 셀프 컨텍스트에 있어요", + "failed": "추가하지 못했어요. 다시 시도해 주세요." + }, + "rememberMessage": { + "action": "기억하기", + "done": "셀프 컨텍스트에 저장했어요", + "duplicate": "이미 셀프 컨텍스트에 있어요", + "failed": "저장하지 못했어요. 다시 시도해 주세요." + }, + "guided": { + "offerTitle": "코치가 물어볼 후속 질문이 {count}개 있어요", + "offerTitleOne": "코치가 물어볼 후속 질문이 있어요", + "offerBody": "짧은 답변이 셀프 컨텍스트를 더 정확하게 만들어요 — 여기 대화에서 한 번에 하나씩 물어볼게요.", + "offerStart": "지금 답하기", + "offerLater": "나중에", + "offerDismiss": "다시 묻지 않기", + "dismissError": "질문을 닫지 못했어요. 다시 시도해 주세요.", + "progress": "질문 {current}/{total}", + "skip": "건너뛰기", + "later": "나중에", + "dismissRemaining": "다시 묻지 않기", + "answerPlaceholder": "답변을 입력하세요…", + "summaryTitle": "다 됐어요 — 답변 고마워요!", + "summaryAnswered": "{total}개 중 {answered}개 답변", + "summaryAdopted": "셀프 컨텍스트에 {adopted}개 추가", + "summaryAdoptedNone": "셀프 컨텍스트에 새로 추가된 건 없어요", + "summaryLink": "셀프 컨텍스트 살펴보기" + }, + "threadEmpty": "추이, 약, 측정값에 대해 무엇이든 물어보면 대화가 시작돼요.", + "historyTitle": "대화", + "historySearchPlaceholder": "대화 검색…", + "historyEmpty": "대화가 여기에 표시돼요. 무엇이든 물어보며 시작하세요.", + "historyDeleteAria": "대화 삭제", + "historyDeleted": "대화를 삭제했어요", + "rename": { + "action": "대화 이름 바꾸기", + "input": "대화 제목", + "save": "제목 저장", + "cancel": "이름 바꾸기 취소", + "error": "대화 제목을 저장하지 못했어요." + }, + "historySearchEmpty": "검색과 일치하는 대화가 없어요.", + "historyLoadingMore": "더 불러오는 중…", + "sourcesTitle": "내가 볼 수 있는 것", + "sourcesFresh": "최신", + "sourcesStale": "오래됨", + "sourcesFooter": "코치는 연결한 데이터만 읽어요.", + "evidenceLabel": "내가 보고 있는 것", + "metric": { + "bp": "혈압", + "weight": "체중", + "pulse": "맥박", + "mood": "기분", + "compliance": "복약 순응도", + "general": "일반", + "hrv": "심박변이도", + "sleep": "수면", + "resting_hr": "안정 시 심박수", + "steps": "걸음 수", + "active_energy": "활동 에너지", + "flights": "오른 층수", + "distance": "거리", + "vo2_max": "최대산소섭취량", + "body_temp": "체온", + "medications": "복약", + "anthropometrics": "프로필(키, 나이, 성별)", + "walking_hr": "보행 심박수", + "respiratory_rate": "호흡수", + "spo2": "산소포화도", + "pulse_wave_velocity": "맥파 전달 속도", + "vascular_age": "혈관 나이", + "body_fat": "체지방", + "fat_mass": "체지방량", + "fat_free_mass": "제지방량", + "muscle_mass": "근육량", + "lean_body_mass": "제지방 체중", + "bone_mass": "골량", + "total_body_water": "체수분", + "bmi": "BMI", + "visceral_fat": "내장지방", + "glucose": "혈당", + "walking_steadiness": "보행 안정성", + "walking_asymmetry": "보행 비대칭", + "walking_double_support": "양발 지지 시간", + "walking_step_length": "보폭", + "walking_speed": "보행 속도", + "audio_env": "환경 소음", + "audio_headphone": "헤드폰 음량", + "audio_event": "큰 소리 노출", + "daylight": "햇빛 노출 시간", + "skin_temp": "피부 온도", + "workouts": "운동" + }, + "window": { + "last7days": "최근 7일", + "last30days": "최근 30일", + "last90days": "최근 90일", + "lastYear": "최근 12개월", + "allTime": "전체 기간(상세 12개월)" + }, + "windowLabel": "기간", + "settingsAriaLabel": "코치 설정", + "settingsTitle": "코치 설정", + "settingsDescription": "코치가 대화하는 방식을 조절해요.", + "settingsToneLabel": "말투", + "settingsToneWarm": "다정함", + "settingsToneNeutral": "중립", + "settingsToneConcise": "간결", + "settingsVerbosityLabel": "답변 길이", + "settingsVerbosityBrief": "짧게", + "settingsVerbosityDefault": "기본", + "settingsVerbosityDetailed": "자세히", + "settingsVerbosityConciseHint": "간결한 말투는 이미 답변을 짧게 유지해요 — 켜 두는 동안 답변 길이는 '짧게'로 고정돼요.", + "settingsSourcesPointer": "코치가 참고할 데이터 묶음과 기본 분석 기간은 코치 대화의 '내가 참고하는 것' 레일에서 바로 관리해요.", + "settingsSourcesPointerLink": "코치 열기", + "settingsExcludeLabel": "제외할 지표", + "settingsExcludeHint": "선택한 지표의 데이터는 코치가 보지 않아요.", + "settingsContextLabel": "포함할 맥락", + "settingsContextHint": "코치가 참고할 수 있는 선택 블록이에요. 끄면 모든 대화에서 그 블록이 빠져요.", + "settingsEvidenceLabel": "근거 기본으로 보기", + "settingsEvidenceHint": "모든 답변 아래의 근거를 자동으로 펼쳐요.", + "settingsDefaultWindowLabel": "기본 분석 기간", + "settingsDefaultWindowHint": "서랍 헤더에서 바꾸지 않으면 코치가 대화마다 이만큼의 기록을 읽어요.", + "settingsSave": "저장", + "settingsSaved": "저장됨", + "settingsCancel": "취소", + "feedbackHelpful": "도움됨", + "feedbackUnhelpful": "아쉬움", + "feedbackThanks": "알려줘서 고마워요.", + "feedbackError": "피드백을 저장하지 못했어요", + "dailyLimitTitle": "하루 한도 도달", + "dailyLimitBody": "하루 한도에 도달했어요. 사용량은 UTC 자정에 초기화돼요.", + "providerRateLimitTitle": "제공자 요청 제한", + "providerRateLimitBody": "제공자가 일시적으로 요청을 제한하고 있어요. 5분쯤 뒤에 다시 시도해 주세요.", + "cluster": { + "cardio": { + "label": "심혈관", + "hint": "혈압 · 맥박 · 심박변이도 · 안정 시와 보행 심박수" + }, + "body": { + "label": "체성분", + "hint": "체중 · 체지방 · 근육 · BMI" + }, + "activity": { + "label": "활동", + "hint": "걸음 수 · 에너지 · 거리 · 최대산소섭취량" + }, + "workouts": { + "label": "운동", + "hint": "러닝 · 라이딩 · 세션" + }, + "sleep": { + "label": "수면", + "hint": "수면 시간 · 단계" + }, + "mood": { + "label": "기분", + "hint": "하루 기분 기록" + }, + "glucose": { + "label": "혈당", + "hint": "공복 · 식후 측정값" + }, + "medication": { + "label": "복약", + "hint": "복약 순응도 · GLP-1 맥락" + }, + "mobility": { + "label": "이동성과 보행", + "hint": "안정성 · 비대칭 · 보행" + }, + "environment": { + "label": "환경", + "hint": "소음 · 햇빛 · 피부 온도" + } + }, + "settingsClustersLabel": "코치가 보는 데이터", + "settingsClustersHint": "코치가 읽을 수 있는 데이터 묶음을 고르세요. 데이터가 많을수록 답변이 풍부해지고 프롬프트도 커져요.", + "settingsClusterNoData": "아직 데이터 없음", + "sourcesMemberCount": "지표 {count}개", + "showConversations": "대화 보기", + "hideConversations": "대화 숨기기", + "heroGreeting": "내 데이터에 대해 무엇이든 물어보세요", + "heroSubline": "추이, 약, 측정값에 대해 물어보세요.", + "thinkingElapsed": "{seconds}초", + "thinkingDuration": "{seconds}초 동안 생각했어요", + "thinkingExpandAria": "코치가 참고한 내용 보기", + "thinkingDetail": "건강 스냅샷을 바탕으로 답했어요.", + "tokensUsed": "{count} 토큰", + "tokensUsedWithModel": "{count} 토큰 · {model}", + "askAboutThis": "코치에게 물어보기", + "readStrip": { + "label": "평소 범위", + "baselineWithin": "평소 범위: {low}–{high} {unit}. 오늘 값 {value} — 범위 안이에요.", + "baselineAbove": "평소 범위: {low}–{high} {unit}. 오늘 값 {value} — 범위 위예요.", + "baselineBelow": "평소 범위: {low}–{high} {unit}. 오늘 값 {value} — 범위 아래예요.", + "insufficient": "아직 평소 범위를 배우는 중이에요 — 며칠 더 기록해 주세요.", + "driver": "{note}" + }, + "scope": { + "prefix": "코치가 이미 보고 있어요: {metric}", + "tap": "탭해서 물어보기", + "question": "이 지표를 설명해 주세요 — 눈여겨볼 게 있나요?" + }, + "seeded": { + "prefix": "오늘 살펴볼 만한 것: {signal}", + "signal": { + "readiness": "준비도", + "recovery": "회복" + }, + "question": { + "readiness": "오늘 준비도가 평소와 달라요 — 무엇 때문일까요?", + "recovery": "회복 상태가 달라졌어요 — 이유가 뭘까요?" + }, + "dismiss": "닫기" + }, + "settings": "설정", + "history": { + "groupToday": "오늘", + "groupYesterday": "어제", + "groupThisWeek": "이번 주", + "groupEarlier": "이전" + }, + "seed": { + "bloodPressure": "최근 30일 혈압 추이를 설명해 주세요 — 눈여겨볼 게 있나요?", + "weight": "요즘 체중 추이가 어떤가요? 그 방향이 신경 쓸 만한가요?", + "bmi": "제 BMI 추이는 무엇을 말해 주나요? 어떻게 읽어야 하나요?", + "pulse": "제 맥박 측정값을 설명해 주세요 — 평소와 다른 점이 있나요?", + "restingHr": "안정 시 심박수 추이는 어떤가요? 제 체력에 대해 뭘 알려 주나요?", + "hrv": "요즘 제 심박변이도는 회복과 스트레스에 대해 뭘 말해 주나요?", + "sleep": "최근 수면을 설명해 주세요 — 수면 시간, 규칙성, 바꿀 만한 점까지.", + "mood": "최근 몇 주 동안 제 기분에서 어떤 패턴이 보이나요?", + "medications": "제 복약 순응도는 어떤가요? 요즘 컨디션과 맞물리나요?", + "steps": "걸음 수로 볼 때 요즘 얼마나 활동적이었나요? 건강한 기준값과 비교하면 어떤가요?", + "activeEnergy": "활동 에너지 추이는 요즘 제 활동량에 대해 뭘 말해 주나요?", + "cardioFitness": "심폐 체력(VO₂ max) 추이는 무슨 뜻인가요? 어떻게 하면 좋은 방향으로 올릴 수 있나요?", + "bloodGlucose": "최근 혈당 측정값을 설명해 주세요 — 건강한 범위 안인가요?", + "oxygenSaturation": "제 산소포화도(SpO₂) 추이는 무엇을 말해 주나요?", + "respiratoryRate": "제 호흡수 추이는 건강에 대해 뭘 말해 주나요?", + "workouts": "최근 운동을 설명해 주세요 — 운동량, 빈도, 사이사이 회복까지.", + "recoveryPage": "지금 제 회복 상태가 이런 이유는 뭔가요? 무엇이 영향을 주고 있나요?", + "briefing": "오늘 브리핑을 설명해 주세요 — 무엇에 집중해야 하고, 지금 할 만한 일이 있나요?", + "healthScore": "제 건강 점수가 100점 중 {score}점인 이유는 뭔가요? 무엇을 바꾸면 가장 크게 오를까요?", + "periodWeek": "지난주 건강 지표들이 어땠는지 설명해 주세요 — 눈에 띄는 게 있나요?", + "periodMonth": "지난달 건강 지표들이 어땠는지 설명해 주세요 — 눈에 띄는 게 있나요?", + "correlationBpCompliance": "제 복약 순응도가 혈압과 어떤 관계인가요? 이 상관관계가 저에게 어떤 의미인지 설명해 주세요.", + "correlationMoodPulse": "제 기분과 맥박 사이에 실제로 연관이 있나요? 이 상관관계를 읽는 법을 알려 주세요.", + "correlationWeightWeekday": "제 체중이 주간 패턴을 따르나요? 이 요일별 상관관계는 무엇을 말해 주나요?", + "recommendation": "이 추천에 대해 더 알려 주세요: '{text}' — 왜 저에게 해당하고, 무엇을 하면 되나요?" + }, + "assessmentPrompt": "제 {metric} 평가를 설명해 주세요 — 무슨 뜻이고 무엇을 하면 되나요?", + "attach": { + "menuChooseFromDocuments": "문서에서 선택", + "menuUploadNew": "새 문서 업로드", + "trigger": "문서 첨부", + "pickerTitle": "문서 첨부", + "pickerDescription": "코치는 첨부한 문서만 읽어요 — 건강 데이터는 읽지 않아요.", + "pickerSearchPlaceholder": "문서 검색…", + "pickerEmpty": "보관함에 문서가 아직 없어요.", + "pickerNotIndexed": "아직 색인되지 않음", + "pickerCap": "{max}개까지 더 첨부할 수 있어요.", + "pickerConfirm": "첨부 ({count})", + "pillRemove": "제거: {title}", + "indexing": "색인 중…", + "indexingHint": "아직 색인 중인 문서가 있어요 — 준비되면 보낼 수 있어요.", + "fencedHint": "이 대화는 건강 데이터에 접근하지 않아요 — 코치는 첨부한 문서만 읽어요.", + "documentsAttachedOne": "문서 1개 첨부됨", + "documentsAttachedOther": "문서 {count}개 첨부됨", + "railBadge": "문서 전용 대화", + "uploadError": "업로드하지 못했어요. 다시 시도해 주세요.", + "notFoundError": "그 문서 링크는 더 이상 유효하지 않아요 — 일반 대화로 시작할게요." + }, + "interrupted": { + "note": "이 답변은 끝나기 전에 중단됐어요.", + "retry": "다시 물어보기" + } + }, + "relativeJustNow": "방금", + "relativeMinutesAgoOne": "{count}분 전", + "relativeMinutesAgoOther": "{count}분 전", + "relativeMinutesAgoFew": "{count}분 전", + "relativeHoursAgoOne": "{count}시간 전", + "relativeHoursAgoOther": "{count}시간 전", + "relativeHoursAgoFew": "{count}시간 전", + "relativeDaysAgoOne": "{count}일 전", + "relativeDaysAgoOther": "{count}일 전", + "relativeDaysAgoFew": "{count}일 전", + "navOverview": "개요", + "navCatalog": "전체 지표", + "navBloodPressure": "혈압", + "navWeight": "체중", + "navPulse": "맥박", + "navMood": "기분", + "navMedication": "복약", + "navNutrients": "영양소", + "navBmi": "BMI", + "navSleep": "수면", + "navHrv": "심박변이도", + "navRestingHr": "안정 시 심박수", + "navOxygenSaturation": "산소", + "navBodyTemperature": "체온", + "navSteps": "걸음 수", + "navActiveEnergy": "활동 에너지", + "navAriaLabel": "섹션으로 이동", + "pillScrollLeft": "탭 왼쪽으로 스크롤", + "pillScrollRight": "탭 오른쪽으로 스크롤", + "tabStrip": { + "vitalsParent": { + "label": "바이탈", + "header": "활력 징후" + }, + "bodyParent": { + "label": "신체", + "header": "체성분" + }, + "activityParent": { + "label": "활동", + "header": "활동과 이동성" + }, + "heartParent": { + "label": "심장", + "header": "심장 건강" + }, + "hearingParent": { + "label": "청력", + "header": "청력과 소음 노출" + }, + "environmentParent": { + "label": "환경", + "header": "환경" + }, + "metabolicParent": { + "label": "대사", + "header": "대사" + } + }, + "hrv": { + "title": "심박변이도", + "description": "시간에 따른 심박변이도 — 신경계 회복 신호로, Apple Watch 등 호환 웨어러블이 수면 중에 측정해요.", + "chartTitle": "심박변이도", + "emptyState": { + "title": "아직 심박변이도 기록이 없어요", + "description": "Apple Watch(또는 호환 Withings 기기)가 밤마다 측정값을 동기화하기 시작하면 심박변이도 기록이 여기에 쌓여요." + } + }, + "restingHr": { + "title": "안정 시 심박수", + "description": "심장이 하루 중 내려앉는 최저치예요 — 맥박 페이지의 단발 측정값과는 달라요. Apple Health는 하루에 한 값을 제공해요.", + "chartTitle": "안정 시 심박수", + "emptyState": { + "title": "아직 안정 시 심박수가 없어요", + "description": "Apple Watch나 호환 Withings 기기를 조용한 하루 동안 착용하면 안정 시 심박수가 들어와요." + }, + "coachPrefill": "아직 안정 시 심박수를 기록한 적이 없어요 — 하루 안정 시 값이 단발 맥박과 왜 다른가요? 어떻게 개선하나요?" + }, + "oxygenSaturation": { + "title": "산소포화도", + "description": "혈중 산소(SpO₂) — 임상 정상 범위는 95–100 %이고, 목표 구간을 차트에 음영으로 표시해요.", + "chartTitle": "산소포화도", + "emptyState": { + "title": "아직 SpO₂ 기록이 없어요", + "description": "호환 웨어러블(Apple Watch, Withings ScanWatch)이 측정하면 SpO₂ 기록이 여기에 쌓여요." + } + }, + "bodyTemperature": { + "title": "체온", + "description": "심부 체온 추이 — 섭씨. Withings의 피부 온도 측정값은 별도 지표로 따로 있어요.", + "chartTitle": "체온", + "emptyState": { + "title": "아직 체온 기록이 없어요", + "description": "체온을 기록하거나 웨어러블이 동기화하면 여기에 추이가 나타나요.", + "cta": "체온 기록" + } + }, + "steps": { + "title": "걸음 수", + "description": "하루 걸음 수 — 하루에 한 줄씩, Apple Health나 연결된 활동 기기에서 가져와요.", + "chartTitle": "하루 걸음 수", + "emptyState": { + "title": "아직 걸음 수 데이터가 없어요", + "description": "Apple Health나 활동 기기가 하루치 움직임을 동기화하면 여기에 하루 걸음 수가 나타나요." + } + }, + "activeEnergy": { + "title": "활동 에너지", + "description": "하루 활동 에너지 합계(kcal) — 하루에 한 줄씩, Apple Health나 Withings 활동에서 가져와요.", + "chartTitle": "활동 에너지", + "emptyState": { + "title": "아직 활동 에너지 데이터가 없어요", + "description": "Apple Health나 Withings 활동 기기가 하루치 움직임을 동기화하면 여기에 활동 에너지 합계가 나타나요." + } + }, + "subPage": { + "loadError": "지금은 이 지표를 불러오지 못했어요.", + "blutdruckDescription": "나이에 맞춘 목표 구간과 함께 보는 수축기/이완기 추이, 그리고 서술형 평가예요.", + "gewichtDescription": "키에서 계산한 건강 체중 구간에 대비한 체중 추이와 서술형 평가예요.", + "pulsDescription": "CDC/NCHS 인구 백분위 기반의 연령·성별 참고 구간에 대비한 안정 시 맥박과 서술형 평가예요.", + "stimmungDescription": "시간에 따른 하루 기분 점수와 서술형 평가예요.", + "medikamenteDescription": "약별 복약 순응도 달력과 서술형 평가예요.", + "bmiDescription": "체중과 키로 계산한 BMI와 서술형 평가예요.", + "target": { + "adjustLink": "목표 범위 조정" + }, + "explainer": { + "bloodPressureBody": "혈압은 혈액이 동맥 벽에 가하는 힘이고, 수축기 분의 이완기로 적어요(심박 중 최고치와 박동 사이의 안정값). 높은 측정값이 계속되면 시간이 지나며 심장과 혈관에 부담이 쌓여요.", + "pulseBody": "맥박은 심장이 1분에 몇 번 뛰는지예요. 한 번의 측정값은 그 순간 무엇을 하고 있었는지를 반영해요 — 안정 시에는 보통 분당 60~100회이고, 움직임·스트레스·카페인에 따라 자연스럽게 올라가요.", + "weightBody": "체중은 장기적인 에너지 균형과 전반적인 건강을 보여주는 가장 단순한 신호 중 하나예요. 수분·음식·시간대에 따라 흔들리는 한 번의 측정값보다 몇 주에 걸친 추이가 훨씬 중요해요.", + "bmiBody": "BMI는 체중을 키의 제곱으로 나눈 값으로, 넓은 체중 범주를 알려주는 하나의 숫자예요. 진단이 아니라 인구 집단 수준의 선별 지표이고, 근육과 지방을 구분하지 못해요.", + "sleepBody": "수면 시간은 밤새 실제로 잠든 시간이고, 얕은 수면·깊은 수면·REM 단계로 나뉘어요. 대부분의 성인은 대략 7~9시간이 필요하고, 꾸준한 양과 질 모두가 회복·기분·대사를 뒷받침해요.", + "moodBody": "기분 기록은 간단한 척도로 시간에 따른 기분을 남겨요. 꾸준히 기록하면 수면·활동·약이 기분과 어떻게 맞물리는지, 하루만 봐서는 절대 보이지 않는 패턴이 드러나요.", + "medicationsBody": "복약 순응도는 처방대로 약을 얼마나 꾸준히 복용하는지이고, 여기서는 예정된 복용 중 실제로 기록한 비율로 보여줘요. 치료가 의도대로 효과를 내려면 꾸준한 복용이 관건인 경우가 많아요.", + "restingHrBody": "안정 시 심박수는 완전히 쉬고 있을 때의 심박수로, 보통 밤사이에 측정해요 — 활동 중 맥박과는 달라요. 안정 시 심박수가 낮을수록 대체로 심폐 체력이 좋다는 뜻이고, 올라가는 추이는 스트레스나 질병의 초기 신호일 수 있어요. 일반적인 기준으로 성인의 정상 안정 시 심박수는 분당 60~100회예요(AHA 2024). 단련된 심장은 40~60회에 머물러도 문제없어요.", + "hrvBody": "심박변이도는 연속된 심장 박동 사이 시간의 미세한 변동이에요. 변동이 클수록 대체로 잘 회복되고 적응력 있는 신경계를 뜻하고, 지속적으로 떨어지면 피로·스트레스·회복 부족의 신호일 수 있어요.", + "oxygenSaturationBody": "SpO₂는 산소를 실어 나르는 적혈구의 비율이에요. 안정 시 95~100%는 정상이고, 90% 아래에 계속 머무르면 살펴볼 필요가 있어요. 다만 밤중에 잠깐 떨어지는 건 흔해요.", + "bodyTemperatureBody": "체온은 몸의 심부 열 상태를 반영해요. 일반적인 기준으로 인구 평균은 37.0°C가 아니라 약 36.6°C이고, 구강 정상 범위는 대략 35.7~37.4°C, 발열은 38.0°C부터예요(J Gen Intern Med 2019). 하루 사이 약 0.5°C 오르내리고 측정 부위에 따라 최대 1°C가량 차이 나니 어디서 쟀는지 태그해 두세요. 밤사이 손목 측정값은 임상적 발열 수치가 아니라 상대적인 변화를 따라가요.", + "activeEnergyBody": "활동 에너지는 몸이 쉴 때 쓰는 기본량 위에, 움직임으로 태운 칼로리예요. 하루를 얼마나 몸을 움직였는지 직접 보여주는 지표예요.", + "workoutsBody": "운동은 기기가 기록한 구조화된 운동 세션으로, 종류·시간·에너지가 함께 남아요. 기록해 두면 꾸준한 훈련이 몇 주에 걸쳐 어떻게 쌓이는지, 다른 건강 신호와 어떻게 연결되는지 보여요.", + "respiratoryRateBody": "호흡수는 1분에 숨을 몇 번 쉬는지예요. 일반적인 기준으로 안정 시 대부분의 성인은 분당 12~20회 호흡해요(American Lung Association). 정말 쉬고 있거나 잠든 상태에서만 의미가 있어요. 본인의 밤사이 기준값보다 +3 정도 지속해서 올라가면 질병의 초기 신호일 수 있어요 — 임상 경보 기준치는 병원용 도구이지 자가 경보용이 아니에요.", + "bodyWaterBody": "체수분 총량은 세포 안팎의 수분이 체중에서 차지하는 비율이에요. 수분 섭취·음식·활동에 따라 움직이니 한 번의 측정값보다 몇 주 추이가 더 많은 걸 알려줘요.", + "boneMassBody": "골량은 뼈에 들어 있는 무기질의 추정 무게예요. 체중계 기반 추정은 대략적이지만, 유지되거나 올라가는 추이는 장기적인 뼈 건강에 도움이 돼요.", + "fatFreeMassBody": "제지방량은 몸에서 지방이 아닌 전부 — 근육·뼈·장기·수분이에요. 체중을 줄이면서도 이걸 유지하거나 늘리면 대체로 더 건강한 체성분을 뜻해요.", + "fatMassBody": "체지방량은 몸속 지방의 총 무게예요. 지방은 호르몬과 에너지 저장에 어느 정도 꼭 필요하지만, 특히 복부에 너무 많으면 장기적인 건강 위험이 올라가요(WHO 2024).", + "muscleMassBody": "근육량은 몸속 근육의 추정 무게예요. 근육이 많을수록 근력·대사·활동성에 도움이 되고, 나이가 들수록 유지하려면 적극적인 노력이 필요해요.", + "visceralFatBody": "내장지방은 복부 깊숙이 장기 주변에 쌓이는 지방이에요. 피부 밑 지방과 달리 수치가 높으면 심장병과 2형 당뇨병에 밀접하게 연결되니 대체로 낮을수록 좋아요.", + "leanBodyMassBody": "제지방 체중은 전체 체중에서 체지방을 뺀 값 — 근육·뼈·장기·수분을 합친 거예요. 체중과 함께 보면 변화가 근육에서 왔는지 지방에서 왔는지 알 수 있어요.", + "flightsClimbedBody": "오른 층수는 하루에 올라간 층을 세고, 한 층은 대략 3m의 고도 상승이에요. 일상 속 활동량과 하체 사용을 보여주는 단순한 지표예요.", + "walkingDistanceBody": "걷기·달리기 거리는 하루에 두 발로 이동한 거리예요. 얼마나 자주 걸었는지뿐 아니라 얼마나 멀리 움직였는지를 보여줘서 걸음 수를 보완해요.", + "walkingSteadinessBody": "보행 안정성은 걸음걸이가 얼마나 안정적이고 균형 잡혔는지를 양호부터 낮음까지로 추정해요. 등급이 낮을수록 낙상 가능성이 높다는 뜻이니 임상의와 상의해 볼 만해요.", + "walkingHeartRateBody": "보행 심박수는 꾸준히 걸을 때의 평균 맥박이에요. 같은 노력에서 보행 심박수가 시간이 지나며 낮아지면 대체로 심폐 체력이 좋아지고 있다는 뜻이에요.", + "walkingAsymmetryBody": "보행 비대칭성은 왼발과 오른발의 걸음이 고르지 않은 시간의 비율이에요. 비율이 높으면 통증·부상·보행 문제를 뜻할 수 있으니 살펴볼 만해요.", + "doubleSupportTimeBody": "이중 지지 시간은 걷는 주기 중 두 발이 동시에 땅에 닿아 있는 시간의 비율이에요. 비율이 높을수록 대체로 더 조심스럽고 덜 안정적인 걸음걸이를 뜻해요.", + "stepLengthBody": "보폭은 한 걸음에서 다음 걸음까지의 평균 거리예요. 시간이 지나며 보폭이 짧아지면 피로·조심스러움·활동성 변화를 반영할 수 있어요.", + "walkingSpeedBody": "보행 속도는 평지를 걸을 때의 속도예요. 전반적인 건강과 활동성을 보여주는 잘 연구된 지표이고, 속도가 유지되거나 빨라지면 좋은 신호예요.", + "pulseWaveVelocityBody": "맥파 전달 속도는 심장 박동마다 생기는 압력파가 동맥을 따라 얼마나 빨리 전달되는지 측정해요. 빠를수록 동맥이 뻣뻣하다는 뜻이고 심혈관 위험이 높아지는 것과 연결돼요. 일반적인 기준으로 유럽 임상 기준은 경동맥-대퇴동맥 속도 10m/s 초과를 주의 신호로 봐요(ESC/ESH 2018). 다만 소비자 기기의 추정값은 대리 지표일 뿐, 그 임상 측정과 같지 않아요.", + "vascularAgeBody": "혈관 나이는 동맥 경직도를 바탕으로 동맥이 실제 나이에 비해 몇 살처럼 움직이는지 추정해요. 혈관 나이가 실제 나이보다 낮으면 혈관이 더 건강하다는 뜻이에요.", + "environmentalAudioBody": "환경 소음 수준은 주변 세상의 소리 크기를 dB로 기록해요. 약 70dB이 넘는 소리에 오래 노출되면 청력이 손상될 수 있으니 하루 평균이 낮을수록 안전해요.", + "headphoneAudioBody": "헤드폰 음량은 이어폰이나 헤드폰으로 얼마나 크게 듣는지를 dB로 기록해요. 볼륨을 낮추고 중간중간 쉬어 주면 오래도록 청력을 지킬 수 있어요.", + "audioEventsBody": "큰 소리 이벤트는 주변 소음이 충분히 노출될 경우 청력을 해칠 수 있는 수준에 도달한 순간을 표시해요. 자주 발생한다면 더 조용한 환경을 찾거나 청력 보호구를 쓰라는 신호예요.", + "daylightBody": "햇빛 노출 시간은 하루에 야외 빛 속에서 보낸 시간이에요. 규칙적인 햇빛은 건강한 수면-각성 리듬과 기분에 도움이 되고(CDC 2024), 야외에서 보내는 시간은 눈 건강과도 연결돼요.", + "bloodGlucoseBody": "혈당은 몸의 주된 에너지원인 혈액 속 당의 양이에요. 식후에 올라가고 식사 사이에 가라앉아요. 당뇨가 없는 성인의 일반적인 기준으로 공복 혈당 100mg/dL 미만이 정상, 100~125는 당뇨 전 단계, 126 이상은 당뇨 범위예요(ADA 2024). 한 번 높게 나왔다고 진단되지는 않아요 — 임상의가 재검으로 확인하고, 당뇨를 관리하는 사람은 임상의가 정한 다른 목표를 써요.", + "skinTemperatureBody": "여기서 피부 온도는 절대적인 발열 수치가 아니라 본인 기준값 대비 밤사이 손목 온도의 작은 변화예요. 이 변화는 수면·환경·질병·생리 주기 단계를 반영할 수 있어요.", + "cardioFitnessBody": "VO₂ max는 최대 노력에서 몸이 쓸 수 있는 산소량으로, 유산소 체력의 가장 좋은 실험실 지표이고 웨어러블은 이를 간접 추정해요. 값이 높거나 올라가는 추이는 심혈관 건강의 강력한 신호예요.", + "cardioRecoveryBody": "심박 회복은 격한 운동을 멈춘 뒤 1분 동안 심박수가 분당 몇 회 떨어지는지예요. 많이 떨어질수록 심장이 더 빨리 안정된다는 뜻이고, 체력이 좋다는 신호예요.", + "wristTemperatureBody": "손목 온도는 자는 동안 측정하고 절대값이 아니라 본인 기준값과의 차이로 보여줘요. 값이 일정하면 정상이고, 지속적으로 올라가면 질병·고강도 훈련·생리 주기 변화를 암시할 수 있어요.", + "fallsBody": "웨어러블이 감지한 심한 낙상 횟수를 세요. 0이 목표이고, 횟수가 늘어나면 활동성 위험 신호이니 특히 고령자라면 임상의와 이야기해 볼 만해요(CDC STEADI 2024).", + "sixMinuteWalkBody": "6분 동안 걸을 수 있는 거리의 추정치예요 — 활동성과 심폐 지구력을 보는 오래된 임상 검사죠. 웨어러블 수치는 감독하에 진행하는 임상 검사가 아니라 대리 지표이니, 유지되거나 올라가는 추이를 보는 게 쓸모 있어요.", + "stairAscentSpeedBody": "계단 한 층을 얼마나 빨리 올라가는지예요. 다리 근력과 활동성을 민감하게 보여주는 지표라, 속도가 유지되거나 빨라지면 좋은 신호이고 느려지면 지켜볼 만해요.", + "stairDescentSpeedBody": "계단 한 층을 얼마나 빨리 내려가는지예요. 오르는 속도와 함께 보면 다리 조절력과 균형을 알 수 있어요 — 속도가 일정하면 좋은 신호이고 느려지면 지켜볼 만해요.", + "breathingDisturbancesBody": "웨어러블은 자는 동안 호흡이 얼마나 자주 끊겼는지 추정할 수 있어요. 지수가 낮을수록 좋고, 여러 밤에 걸쳐 높게 표시되면 임상의와 이야기해 볼 만해요. 수면무호흡 진단이 아니라 선별용 대리 지표예요.", + "stepsBody": "걸음 수는 하루에 걷는 총 걸음으로, 일상 활동량을 보는 가장 단순한 지표예요. 하루 크게 걷는 것보다 꾸준한 습관이 더 중요하고, 딱 떨어지는 1만 걸음 목표는 연구가 아니라 마케팅 문구예요.", + "readinessBody": "준비도는 안정 시 심박수·심박변이도·수면을 묶어 하루를 감당할 몸 상태가 얼마나 준비됐는지 하나로 보여줘요. 임상 지표가 아니라 본인 기준값에 맞춘 기기 대리 지표예요 — 점수가 높으면 회복됐다는 뜻이고, 낮으면 조금 쉬어 가라는 부드러운 신호예요.", + "sleepScoreBody": "수면 점수는 하룻밤 전체를 하나의 숫자로 압축해요 — 시간·효율·규칙성, 그리고 얕은 수면·깊은 수면·REM의 균형이에요. 하룻밤의 완벽한 점수를 쫓기보다 몇 주에 걸친 패턴을 보세요.", + "recoveryBody": "회복도는 심박변이도와 안정 시 심박수 같은 밤사이 신호를 바탕으로 몸이 얼마나 잘 회복했는지 보여줘요. 임상 지표가 아니라 본인 기준값에 견줘 읽는 기기 유래 대리 지표이고, 기기끼리 비교할 수 없어요. 점수가 높으면 밀어붙여도 좋다는 신호이고, 낮으면 몸이 아직 회복 중이라는 뜻이에요.", + "strainBody": "부하는 운동부터 종일 서서 활동한 하루까지, 몸에 실린 심혈관·신체 부담이 얼마나 되는지 담아요. 임상 지표가 아니라 본인의 평소 노력에 견줘 판단하는 기기 유래 지수이고, 대표 숫자는 기기끼리 비교할 수 없어요.", + "stressBody": "여기서 스트레스는 코르티솔 측정이나 스스로 느끼는 감정이 아니라 심박변이도 패턴에서 끌어낸 기기 추정값이고, 임상 스트레스 점수도 아니에요. 본인 기준값에 견줘 읽으세요. 기기끼리 비교할 수 없어요. 점수가 낮으면 차분하고 잘 조절된 상태이고, 높으면 회복 시간을 넣으라는 신호예요.", + "recoveryPageBody": "회복도는 웨어러블이 기록한 밤사이 신호(재충전, 부하, 누적 부담, 그날의 심박·에너지 합계)를 모아 몸이 얼마나 잘 회복했는지 보여줘요. 하나의 숫자보다 며칠에 걸친 패턴을 보세요. 낮은 값은 경보가 아니라 조금 쉬어 가라는 신호예요.", + "pulsePressureBody": "맥압은 수축기와 이완기 측정값의 차이예요(수축기 빼기 이완기). 일반적인 기준으로 안정 시 40mmHg 정도가 보통이고, 중년에서 약 60mmHg가 넘게 벌어지면 동맥이 뻣뻣해지고 있음을 반영할 수 있어요. 반대로 약 25mmHg 아래로 좁으면 심박출량이 적은 상태와 함께 나타날 수 있어요(StatPearls/NIH 2023). 진단이 아니라 이야기를 시작해 볼 거리예요.", + "meanArterialPressureBody": "평균 동맥압은 심장 박동 한 주기 동안 장기가 받는 평균 압력으로, 대략 이완기에 수축기까지 차이의 3분의 1을 더한 값이에요. 일반적인 기준으로 안정 시 값은 70~100mmHg 정도예요(StatPearls/NIH 2023). 흔히 인용되는 60mmHg 하한선은 급성기 진료 개념이지 소비자용 자가 경보가 아니에요.", + "cycleEducationBody": "생리 주기는 호르몬 변화가 이끄는 네 단계로 흘러가고, 주기 길이와 규칙성은 전반적인 건강을 들여다보는 손쉬운 창이에요. 일반적인 기준으로 성인의 주기는 보통 21~35일(임상 자료는 24~38일)이고 출혈은 5일 안팎이에요(ACOG 2015). 황체기가 일정한 기준점이라 배란은 다음 예정 생리에서 거꾸로 세어 추정하는 게 가장 좋아요 — 고정된 '14일째'는 절대 아니에요. 예측된 가임기는 여러 주기가 기록돼야 나오는 추정치이고 여전히 대략적이에요. 기초 체온은 배란을 예측하는 게 아니라 지나간 뒤에 확인해 줘요. 이건 피임 방법이 아니에요.", + "sleepHygieneBody": "수면에 도움이 되는 근거 기반 습관 몇 가지예요. 주말을 포함해 잠자리에 드는 시각과 일어나는 시각을 일정하게 유지하고, 아침에는 밝은 햇빛을 쬐고 저녁에는 조명을 낮추고, 방은 시원하고 어둡고 조용하게 하고, 오후 카페인·늦은 술·늦은 과식을 피하세요(CDC 2024). 규칙성은 수면 시간만큼이나 건강을 잘 예측하고, 웨어러블의 총 수면 시간은 깊은 수면·얕은 수면·REM 구분보다 훨씬 믿을 만해요." + }, + "bpDerived": { + "pulsePressure": "맥압(측정값 사이의 차이)은 약 {value}mmHg예요 — 안정 시에는 40 정도가 보통이에요.", + "meanArterialPressure": "평균 동맥압(장기가 받는 평균)은 약 {value}mmHg로, 보통 70~100 정도예요.", + "caveat": "이 값들은 가장 최근 측정값에서 계산한 일반적인 참고 정보이고, 진단이 아니에요." + }, + "stats": { + "label": "요약 통계", + "min": "최소", + "max": "최대", + "median": "중앙값", + "mean": "평균" + }, + "showAllValues": "모든 측정값 보기", + "valuesPageTitle": "{metric} — 전체 측정값", + "valuesPageDescription": "이 지표를 이루는 모든 측정값이에요. 기록을 바로 편집하거나 삭제할 수 있어요.", + "diversity": { + "trigger": "측정 팁", + "weekday": "{metric} 측정값 대부분이 같은 요일에 몰려 있어요. 다른 요일에도 기록하면 더 온전한 그림이 나와요.", + "timeOfDay": "{metric} 측정값 대부분이 비슷한 시간대에 몰려 있어요. 시간을 바꿔 가며 재면 더 온전한 그림이 나와요." + }, + "valuesBack": "인사이트로 돌아가기", + "valuesBackToMetric": "돌아가기: {metric}", + "scoresBack": "인사이트로 돌아가기", + "ecgDescription": "기기에서 기록한 단일 유도 ECG로, 각각 기기가 낸 결과가 함께 있어요." + }, + "units": { + "steps": "걸음", + "flights": "층", + "falls": "회", + "years": "세", + "breathingEvents": "회", + "respiratoryRate": "회/분" + }, + "sleep": { + "title": "수면", + "description": "Apple Health에서 가져온 수면 단계 구성과 밤별 수면 시간이에요.", + "mainSleep": "주 수면", + "totalSleep": "총 수면", + "compositionTitle": "단계 구성", + "compositionSubtitle": "최근 {nights}일 밤", + "compositionAriaLabel": "{nights}일 밤 동안의 수면 단계 구성", + "sourceDiscrepancy": { + "tooltipTitle": "소스가 서로 달라요" + }, + "stages": { + "deep": "깊은 수면", + "rem": "REM", + "core": "코어", + "asleep": "수면", + "awake": "깨어 있음", + "inBed": "침대에 있음", + "nap": "낮잠", + "napCount": "낮잠 {count}회", + "unavailable": "아직 단계 데이터가 없어요 — 연결된 소스가 보내주는 대로 수면 단계가 여기에 나타나요." + }, + "empty": { + "title": "아직 수면 데이터가 없어요", + "description": "Apple Health나 웨어러블 같은 소스를 연결하거나 수면을 직접 기록하면 이 화면이 자동으로 채워져요.", + "action": "기기 관리" + }, + "debt": { + "title": "수면 부채", + "value": "{hours}시간 {minutes}분 부족", + "caption": "최근 {nights}일 밤 동안 부족한 수면", + "clearValue": "모두 채웠어요", + "clearCaption": "최근 {nights}일 밤 동안 부족한 수면이 없어요", + "learning": "아직 수면을 익히는 중이에요 — 지금까지 {counted}일 밤, 믿을 만한 부족분을 읽으려면 {remaining}일 밤이 더 필요해요.", + "computedInfo": "이건 HealthLog가 자체적으로 낸 추정치예요. 최근 밤들의 필요 수면에서 실제 수면을 뺀 누적 잔액이고, 보충 수면이 이를 갚아 나가요. 자체 필요 수면 모델을 쓰는 웨어러블의 수면 부채 수치와는 다를 수 있어요." + }, + "average": { + "title": "밤 평균", + "value": "{hours}시간 {minutes}분", + "caption": "최근 {nights}일 밤의 평균 수면", + "learning": "아직 수면을 익히는 중이에요 — 지금까지 {counted}일 밤, 믿을 만한 평균을 내려면 {remaining}일 밤이 더 필요해요." + }, + "chronotype": { + "title": "크로노타입", + "learning": "아직 리듬을 익히는 중이에요 — 자유로운 밤 {total}일 중 {counted}일이에요. 크로노타입은 알람 없이 맞은 아침이 몇 번은 쌓여야 의미가 생겨요.", + "advancedToggle": "고급", + "socialJetlagValue": "사회적 시차 {hours}시간 {minutes}분", + "socialJetlagCaption": "일하는 날과 쉬는 날의 수면 시각 차이예요 — 매주 작은 시차를 겪는 셈이죠.", + "msfScValue": "보정 수면 중간 시각 {clock}", + "msfScCaption": "주말에 보충한 수면을 반영해 보정한 값이에요(MSFsc).", + "dayTypeNote": "날짜 유형 기록이 추가되기 전까지는 쉬는 날을 주말로 가정해요.", + "band": { + "extreme_early": "완전한 아침형", + "early": "아침형", + "intermediate": "중간형", + "late": "저녁형", + "extreme_late": "완전한 저녁형" + }, + "midpointHeadline": "자연스러운 수면 중간 시각은 {clock} 무렵이에요." + }, + "rhythm": { + "loadError": "지금은 수면 리듬을 불러오지 못했어요. 연결이 안정되면 다시 나타나요." + } + }, + "medicationCompliance": "복약 순응도", + "dayStreak": "{count}일 연속 기록", + "dayStreakOne": "{count}일 연속 기록", + "compliance7d": "복약 순응도(7일)", + "compliance30d": "복약 순응도(30일)", + "taken": "복용함", + "skipped": "건너뜀", + "missed": "놓침", + "assessmentTitle": "평가", + "restMode": { + "title": "휴식 모드", + "body": "아픈 날이나 컨디션 이슈가 진행 중이라 회복 기간으로 봐요. 이 때문에 점수가 낮아지지는 않아요.", + "bodySince": "{since}부터 아픈 날이나 컨디션 이슈가 진행 중이라 회복 기간으로 봐요. 이 때문에 점수가 낮아지지는 않아요." + }, + "bloodPressureSectionTitle": "혈압", + "weightSectionTitle": "체중", + "pulseSectionTitle": "맥박", + "bmiSectionTitle": "BMI", + "complianceNoData": "복약 순응도 데이터가 없어요.", + "generate": "새 분석 생성", + "moodSectionTitle": "기분", + "emptyState": { + "bloodPressure": { + "title": "아직 혈압 기록이 없어요", + "description": "혈압을 기록하면 추이 차트, 정상 범위 비율, WHO 분류가 여기에 나타나요.", + "cta": "혈압 기록" + }, + "weight": { + "title": "아직 체중 기록이 없어요", + "description": "체중을 기록하면 추이, 이동 평균, 목표 구간이 자동으로 나타나요.", + "cta": "체중 기록" + }, + "pulse": { + "title": "아직 맥박 기록이 없어요", + "description": "맥박을 기록하면 안정 시 맥박, 심박변이도, VO₂ max가 이 화면에 나타나요.", + "cta": "맥박 기록" + }, + "bmi": { + "title": "아직 BMI를 계산하지 못했어요", + "description": "키와 체중 기록이 하나 이상 있으면 WHO 구간과 함께 BMI 추이가 여기에 나타나요.", + "cta": "체중 기록" + }, + "mood": { + "title": "아직 기분 기록이 없어요", + "description": "기분을 기록하면 추이, 안정성 구간, 상관관계가 나타나요.", + "cta": "기분 기록" + }, + "medication": { + "title": "아직 등록한 약이 없어요", + "description": "약을 등록하면 복약 순응도, 약물 농도, 용량 조절 타임라인이 여기에 나타나요.", + "cta": "약 추가" + }, + "sleep": { + "title": "아직 수면 기록이 없어요", + "description": "수면 데이터가 들어오면(직접 기록하거나 Withings / Apple Health를 통해) 밤별 단계 구성이 여기에 나타나요.", + "cta": "소스 연결" + } + }, + "navWorkouts": "운동", + "navRecovery": "회복", + "workouts": { + "title": "운동", + "description": "최근 달리기, 자전거, 걷기, 근력 운동 세션이에요. Apple Watch와 Withings 소스 사이 중복은 정리했어요.", + "emptyState": { + "title": "아직 운동 기록이 없어요", + "description": "iPhone에서 Apple Health 앱을 열면 운동이 여기로 동기화돼요. Withings 활동 불러오기로 걷기와 자전거 세션도 계속 들어와요." + }, + "detail": { + "backToList": "운동으로 돌아가기", + "statsTitle": "통계", + "statsDuration": "시간", + "statsDistance": "거리", + "statsActiveEnergy": "활동 에너지", + "statsAvgHr": "평균 심박수", + "statsMaxHr": "최대 심박수", + "statsMinHr": "최소 심박수", + "statsStepCount": "걸음 수", + "statsElevation": "고도", + "statsPace": "페이스", + "routeTitle": "경로", + "hrChartTitle": "심박수", + "notFound": "운동을 찾을 수 없어요.", + "hrFromPulse": "이 세션 전후의 심박수 데이터에서 가져왔어요", + "hrAxisMinutes": "min", + "hrAvgMarker": "평균 {bpm}", + "hrMaxMarker": "최고 {bpm}", + "hrRangeValue": "{low}~{high}bpm", + "hrRangeLabel": "범위", + "hrNoProfile": "이 세션을 포함하는 개별 심박수 데이터가 없어요.", + "sportAverageLabel": "최근 평균:", + "zonesTitle": "운동 강도 구간", + "zonesCaption": "심박수 구간별로 운동 강도가 어떻게 나뉘었는지예요.", + "zonesCaptionDevice": "기기가 보고한 구간별 시간이에요.", + "exportGpx": "GPX 내보내기", + "elevationTitle": "고도", + "splitsTitle": "구간 기록", + "splitsKm": "km", + "splitsTime": "시간", + "splitsPace": "페이스", + "thatDayTitle": "그날", + "daySleepTitle": "전날 밤", + "daySleepNone": "그날 밤 수면 기록이 없어요", + "daySleepAwakeningsOne": "{count}회 깸", + "daySleepAwakeningsMany": "{count}회 깸", + "dayMoodTitle": "그날 기분", + "dayMoodNone": "그날 기분 기록이 없어요", + "sleepTrendsLink": "수면 추이", + "moodHistoryLink": "기분 기록", + "previousSessionLink": "이전 세션", + "insightTitle": "활동 인사이트" + }, + "sport": { + "walking": "걷기", + "running": "달리기", + "cycling": "자전거", + "hiking": "등산", + "swimming": "수영", + "rowing": "로잉", + "elliptical": "일립티컬", + "stairClimber": "스테어 클라이머", + "yoga": "요가", + "mindAndBody": "심신 운동", + "strength": "근력 운동", + "hiit": "HIIT", + "dance": "댄스", + "golf": "골프", + "badminton": "배드민턴", + "tennis": "테니스", + "basketball": "농구", + "soccer": "축구", + "crossTraining": "크로스 트레이닝", + "mixedCardio": "복합 유산소", + "other": "기타" + }, + "loadError": "운동을 불러오지 못했어요", + "loadMore": "운동 더 불러오기", + "loadingMore": "운동을 더 불러오는 중", + "loadMoreError": "운동을 더 불러오지 못했어요", + "countOne": "운동 {count}개", + "countOther": "운동 {count}개", + "countFew": "운동 {count}개", + "filterLabel": "종목별 필터", + "filterAll": "전체 종목", + "filterEmpty": "기록에 이 종목의 세션이 없어요." + }, + "bloodGlucose": { + "title": "혈당", + "description": "시간에 따른 혈당 측정값이에요(mg/dL).", + "chartTitle": "혈당", + "medianLabel": "혈당 중앙값", + "emptyState": { + "title": "아직 혈당 데이터가 없어요", + "description": "혈당을 기록하거나 연결된 기기가 동기화하면 여기에 측정값이 나타나요." + }, + "clinical": { + "title": "혈당 개요", + "subtitle": "최근 {days}일, 측정값 {count}개를 바탕으로 읽은 결과예요.", + "spotCaveat": "연속 혈당 측정 프로필이 아니라 개별 측정에서 낸 단발 추정치예요 — 임상적 판정이 아니라 흐름의 방향으로 읽어 주세요.", + "labsLink": "검사 결과가 있나요? 채혈 HbA1c나 공복 혈당은 검사에 기록해 주세요.", + "learningTitle": "아직 혈당을 익히는 중이에요", + "learningBody": "지금까지 {days} 동안 {count} — 기록이 조금만 더 쌓이면 더 온전한 그림이 잡혀요. 계속 기록하거나 연결된 기기가 채우게 두세요.", + "learningBodyEmpty": "직접 기록하든 기기에서 동기화되든 측정값이 몇 개 들어오면, 목표 범위 내 시간·평균·변동성이 여기에 자리를 잡아요.", + "readingsCount": "측정값 {count}개", + "readingsCountOne": "측정값 1개", + "daysCount": "{days}일", + "daysCountOne": "1일", + "tir": { + "title": "목표 범위 내 시간", + "caption": "구간별 측정값 비율이에요(목표는 70~180mg/dL).", + "veryLow": "매우 낮음", + "low": "낮음", + "inRange": "목표 범위", + "high": "높음", + "veryHigh": "매우 높음", + "ariaLabel": "목표 범위 내 시간: 목표 범위 {tir}%, 아래 {below}%, 위 {above}%.", + "empty": "아직 차트로 그릴 측정값이 없어요." + }, + "gmi": { + "label": "혈당 관리 지표(GMI)", + "help": "평균 혈당에서 계산한 A1C 유사 추정치예요." + }, + "eA1c": { + "label": "추정 A1C", + "help": "평균 혈당으로 낸 A1C 추정치예요(ADAG)." + }, + "mean": { + "label": "평균 혈당" + }, + "cv": { + "label": "변동성", + "steady": "안정적", + "unstable": "불안정", + "help": "변동계수예요. 36% 이상이면 혈당이 불안정한 것으로 보고, 낮을수록 안정적이에요." + }, + "advanced": { + "toggle": "고급 지표", + "intro": "더 깊이 들여다보는 연구 수준의 복합 지표예요. 각각 측정값을 위험 가중치를 매긴 하나의 숫자로 압축해요.", + "jIndex": { + "label": "J 지수", + "help": "평균과 그 퍼짐 정도를 합친 하나의 점수예요 — 낮을수록 더 촘촘한 관리로 읽혀요." + }, + "lbgi": { + "label": "저혈당 위험(LBGI)", + "help": "측정값이 얼마나 자주, 얼마나 깊이 낮아지는지에 가중치를 주는 Kovatchev 지수예요." + }, + "hbgi": { + "label": "고혈당 위험(HBGI)", + "help": "측정값이 얼마나 자주, 얼마나 높이 올라가는지에 가중치를 주는 Kovatchev 지수예요." + }, + "byContext": { + "title": "상황별", + "fasting": "공복", + "postprandial": "식후", + "random": "임의", + "bedtime": "취침 전", + "readingsShort": "측정값 {count}개", + "readingsShortOne": "측정값 1개" + } + } + } + }, + "navBloodGlucose": "혈당", + "totalBodyWater": { + "title": "총 체수분", + "description": "시간에 따른 총 체수분이에요(kg).", + "chartTitle": "총 체수분", + "emptyState": { + "title": "아직 체수분 데이터가 없어요", + "description": "연결된 체성분 체중계가 동기화되면 체수분 측정값이 여기에 나타나요." + } + }, + "navTotalBodyWater": "체수분", + "boneMass": { + "title": "골량", + "description": "시간에 따른 골량이에요(kg).", + "chartTitle": "골량", + "emptyState": { + "title": "아직 골량 데이터가 없어요", + "description": "연결된 체성분 체중계가 동기화되면 골량 측정값이 여기에 나타나요." + } + }, + "navBoneMass": "골량", + "flightsClimbed": { + "title": "오른 층수", + "description": "날마다 오른 계단 층수예요.", + "chartTitle": "하루 오른 층수", + "emptyState": { + "title": "아직 층수 데이터가 없어요", + "description": "Apple Health가 하루 활동을 동기화하면 오른 층수 합계가 여기에 나타나요." + } + }, + "navFlightsClimbed": "층수", + "walkingRunningDistance": { + "title": "걷기 + 달리기 거리", + "description": "날마다 걷고 달린 거리예요(km).", + "chartTitle": "하루 이동 거리", + "emptyState": { + "title": "아직 거리 데이터가 없어요", + "description": "Apple Health가 하루 활동을 동기화하면 하루 이동 거리가 여기에 나타나요." + } + }, + "navWalkingRunningDistance": "거리", + "fatFreeMass": { + "title": "제지방량", + "description": "시간에 따른 제지방량이에요(kg).", + "chartTitle": "제지방량", + "emptyState": { + "title": "아직 제지방량 데이터가 없어요", + "description": "연결된 체성분 체중계가 동기화되면 제지방량 측정값이 여기에 나타나요." + } + }, + "navFatFreeMass": "제지방량", + "fatMass": { + "title": "체지방량", + "description": "시간에 따른 체지방량이에요(kg).", + "chartTitle": "체지방량", + "emptyState": { + "title": "아직 체지방량 데이터가 없어요", + "description": "연결된 체성분 체중계가 동기화되면 체지방량 측정값이 여기에 나타나요." + } + }, + "navFatMass": "체지방량", + "muscleMass": { + "title": "근육량", + "description": "시간에 따른 근육량이에요(kg).", + "chartTitle": "근육량", + "emptyState": { + "title": "아직 근육량 데이터가 없어요", + "description": "연결된 체성분 체중계가 동기화되면 근육량 측정값이 여기에 나타나요." + } + }, + "navMuscleMass": "근육량", + "skinTemperature": { + "title": "피부 온도", + "description": "시간에 따른 손목 피부 온도예요(°C).", + "chartTitle": "피부 온도", + "emptyState": { + "title": "아직 피부 온도 데이터가 없어요", + "description": "웨어러블이 밤사이 손목 온도를 동기화하면 피부 온도 측정값이 여기에 나타나요." + } + }, + "navSkinTemperature": "피부 온도", + "pulseWaveVelocity": { + "title": "맥파 전파 속도", + "description": "시간에 따른 동맥 맥파 전파 속도예요(m/s) — 값이 높을수록 동맥이 뻣뻣하다는 뜻이에요.", + "chartTitle": "맥파 전파 속도", + "emptyState": { + "title": "아직 맥파 전파 속도 데이터가 없어요", + "description": "호환되는 Withings 기기가 동기화되면 맥파 전파 속도 측정값이 여기에 나타나요." + } + }, + "navPulseWaveVelocity": "맥파 전파 속도", + "vascularAge": { + "title": "혈관 나이", + "description": "시간에 따른 추정 혈관 나이예요(세).", + "chartTitle": "혈관 나이", + "emptyState": { + "title": "아직 혈관 나이 데이터가 없어요", + "description": "호환되는 Withings 기기가 동기화되면 혈관 나이 추정값이 여기에 나타나요." + } + }, + "navVascularAge": "혈관 나이", + "visceralFat": { + "title": "내장지방", + "description": "시간에 따른 내장지방 지수예요.", + "chartTitle": "내장지방", + "emptyState": { + "title": "아직 내장지방 데이터가 없어요", + "description": "호환되는 체성분 체중계가 동기화되면 내장지방 지수가 여기에 나타나요." + } + }, + "navVisceralFat": "내장지방", + "audioExposureEnv": { + "title": "주변 소음", + "description": "하루 평균 주변 소음 노출량이에요(dBA).", + "chartTitle": "주변 소음 노출", + "emptyState": { + "title": "아직 주변 소음 데이터가 없어요", + "description": "Apple Health가 동기화되면 주변 소음 측정값이 여기에 나타나요." + } + }, + "navAudioExposureEnv": "주변 소음", + "audioExposureHeadphone": { + "title": "헤드폰 오디오", + "description": "하루 평균 헤드폰 오디오 노출량이에요(dBA).", + "chartTitle": "헤드폰 오디오 노출", + "emptyState": { + "title": "아직 헤드폰 오디오 데이터가 없어요", + "description": "Apple Health가 동기화되면 헤드폰 오디오 측정값이 여기에 나타나요." + } + }, + "navAudioExposureHeadphone": "헤드폰 오디오", + "timeInDaylight": { + "title": "햇빛 시간", + "description": "날마다 햇빛 아래에서 보낸 시간이에요(분).", + "chartTitle": "하루 햇빛 시간", + "emptyState": { + "title": "아직 햇빛 데이터가 없어요", + "description": "Apple Health가 하루 노출량을 동기화하면 햇빛 시간 합계가 여기에 나타나요." + } + }, + "navTimeInDaylight": "햇빛", + "walkingSteadiness": { + "title": "보행 안정성", + "description": "시간에 따른 보행 안정성이에요(%).", + "chartTitle": "보행 안정성", + "emptyState": { + "title": "아직 보행 안정성 데이터가 없어요", + "description": "Apple Health가 동기화되면 보행 안정성 측정값이 여기에 나타나요." + } + }, + "navWalkingSteadiness": "보행 안정성", + "audioExposureEvent": { + "title": "큰 소음 이벤트", + "description": "큰 소음에 노출된 이벤트가 있었던 날이에요.", + "chartTitle": "하루 큰 소음 이벤트", + "emptyState": { + "title": "아직 큰 소음 이벤트 데이터가 없어요", + "description": "Apple Health가 동기화되면 큰 소음 이벤트가 여기에 나타나요." + } + }, + "navAudioExposureEvent": "큰 소음", + "respiratoryRate": { + "title": "호흡수", + "description": "시간에 따른 호흡수예요(분당 호흡 횟수).", + "chartTitle": "호흡수", + "emptyState": { + "title": "아직 호흡수 데이터가 없어요", + "description": "Apple Health가 수면이나 운동 중 호흡을 동기화하면 호흡수 측정값이 여기에 나타나요." + } + }, + "navRespiratoryRate": "호흡수", + "leanBodyMass": { + "title": "제지방 체중", + "description": "시간에 따른 제지방 체중이에요(kg).", + "chartTitle": "제지방 체중", + "emptyState": { + "title": "아직 제지방 체중 데이터가 없어요", + "description": "연결된 체성분 체중계가 동기화되면 제지방 체중 측정값이 여기에 나타나요." + } + }, + "navLeanBodyMass": "제지방 체중", + "walkingHeartRateAverage": { + "title": "보행 중 심박수", + "description": "걷는 동안의 평균 심박수예요(bpm).", + "chartTitle": "보행 중 평균 심박수", + "emptyState": { + "title": "아직 보행 중 심박수 데이터가 없어요", + "description": "Apple Health가 동기화되면 보행 중 심박수 측정값이 여기에 나타나요." + } + }, + "navWalkingHeartRateAverage": "보행 중 심박수", + "walkingAsymmetry": { + "title": "보행 비대칭성", + "description": "시간에 따른 보행 비대칭성이에요(%).", + "chartTitle": "보행 비대칭성", + "emptyState": { + "title": "아직 보행 비대칭성 데이터가 없어요", + "description": "Apple Health가 동기화되면 보행 비대칭성 측정값이 여기에 나타나요." + } + }, + "navWalkingAsymmetry": "보행 비대칭성", + "walkingDoubleSupport": { + "title": "이중 지지 시간", + "description": "한 걸음에서 두 발이 모두 땅에 닿아 있는 시간의 비율이에요.", + "chartTitle": "이중 지지 시간", + "emptyState": { + "title": "아직 이중 지지 데이터가 없어요", + "description": "Apple Health가 동기화되면 이중 지지 측정값이 여기에 나타나요." + } + }, + "navWalkingDoubleSupport": "이중 지지", + "walkingStepLength": { + "title": "보행 보폭", + "description": "시간에 따른 평균 보행 보폭이에요(m).", + "chartTitle": "보폭", + "emptyState": { + "title": "아직 보폭 데이터가 없어요", + "description": "Apple Health가 동기화되면 보폭 측정값이 여기에 나타나요." + } + }, + "navWalkingStepLength": "보폭", + "walkingSpeed": { + "title": "보행 속도", + "description": "시간에 따른 평균 보행 속도예요(km/h).", + "chartTitle": "보행 속도(km/h)", + "emptyState": { + "title": "아직 보행 속도 데이터가 없어요", + "description": "Apple Health가 동기화되면 보행 속도 측정값이 여기에 나타나요." + } + }, + "navWalkingSpeed": "보행 속도", + "mood": { + "logLink": "기분 기록하기", + "mentalWellbeingLink": "정신 건강 체크인 하기", + "heatmapTitle": "기분 캘린더", + "heatmapNoEntry": "기록 없음", + "legendGreat": "아주 좋음", + "legendGood": "좋음", + "legendOkay": "보통", + "legendLow": "낮음", + "distributionTitle": "기분 분포", + "distributionDaysUnit": "일", + "weekdayTitle": "요일별 평균", + "weekdayNoData": "데이터 없음", + "weekdayBestWorst": "가장 좋은 요일: {best} · 가장 힘든 요일: {worst}", + "tagsTitle": "태그와 유발 요인", + "tagAvgMood": "태그가 있는 날의 평균 기분", + "correlationsTitle": "기분과 다른 지표", + "correlationsDescription": "같은 날의 수면, 활동, 맥박과 기분이 어떻게 함께 움직이는지 보여줘요.", + "correlation": { + "sleepTitle": "기분 대 수면", + "stepsTitle": "기분 대 활동", + "pulseTitle": "기분 대 맥박", + "sleepAxis": "수면(분)", + "stepsAxis": "걸음 수", + "pulseAxis": "맥박(bpm)", + "strengthStrong": "강함", + "strengthModerate": "보통", + "strengthWeak": "약함", + "strengthNone": "없음", + "source": "짝지은 {n}일 · r = {r}", + "sourceLabel": "계산 방법", + "emptyTitle": "아직 짝지을 데이터가 부족해요", + "emptyDescription": "같은 날에 기분과 이 지표를 함께 기록하면 상관관계를 볼 수 있어요.", + "weightTitle": "기분 대 체중", + "bloodPressureTitle": "기분 대 혈압", + "weightAxis": "체중", + "bloodPressureAxis": "수축기(mmHg)" + }, + "structuredTagsTitle": "태그별 상세", + "inTargetValue": "{pct}%", + "inTargetCaption": "최근 날들 중 기분 좋음 범위에 든 비율", + "narrative": { + "title": "눈에 띄는 점", + "weekdayDip": "{weekday}에 기분이 가장 많이 떨어져요 — 한 주 평균보다 {delta} 낮아요.", + "weekdayPeak": "꾸준히 가장 좋은 요일은 {weekday}, 평균 {value}.", + "trendUp": "최근 {days}일 동안 기분이 올라가는 추세예요(이전 기간 대비 +{delta}).", + "trendDown": "최근 {days}일 동안 기분이 내려가는 추세예요(이전 기간 대비 -{delta}).", + "weekendUp": "주말에 기분이 더 좋아요(평일 대비 +{delta}).", + "weekendDown": "주말에 기분이 더 낮아요(평일 대비 -{delta}).", + "tagLift": "'{tag}' 태그가 붙은 날은 전체 평균보다 {delta} 높아요.", + "tagDrop": "'{tag}' 태그가 붙은 날은 평균보다 {delta} 낮아요.", + "inTarget": "최근 30일 중 {pct}%는 기분 좋음 범위에 있었어요.", + "streak": "{days}일 연속으로 기분을 기록했어요 — 계속 이어가 보세요.", + "timeOfDay": "기분이 가장 좋은 시간대는 {bucket} — 평균 {value}." + }, + "timeOfDay": { + "title": "시간대별 기분", + "morning": "아침", + "afternoon": "오후", + "evening": "저녁", + "night": "밤", + "bestCaption": "보통 {bucket}에 기분이 가장 좋아요." + }, + "stability": { + "value": "{score}/100", + "bandVerySteady": "기분이 아주 안정적이었어요", + "bandSteady": "기분이 비교적 안정적이었어요", + "bandVariable": "기분 변화가 눈에 띄게 컸어요", + "bandVeryVariable": "최근 기분 기복이 아주 컸어요", + "title": "안정성" + }, + "influence": { + "title": "기분에 미친 영향", + "detail": "이 태그를 붙인 {withDays}일의 평균은 {withAvg}, 붙이지 않은 날의 평균은 {withoutAvg}.", + "confidenceLow": "신뢰도 낮음", + "confidenceMedium": "신뢰도 보통", + "confidenceHigh": "신뢰도 높음" + }, + "betterDays": { + "title": "기분 좋은 날과 함께 나타나는 것", + "description": "기분이 높은 날과 낮은 날에 맞물리는 태그와 지표를 함께 움직이는 정도순으로 보여줘요." + }, + "crosstab": { + "title": "태그와 건강 지표", + "description": "각 활동을 태그한 날과 그렇지 않은 날에 건강 지표가 어떻게 다른지 보여줘요.", + "pairLabel": "{tag} · {metric}", + "metricActiveEnergy": "활동 에너지", + "metricSleepDuration": "수면", + "metricNextDayRecovery": "다음 날 회복도", + "metricNextDayRestingHr": "다음 날 안정 시 심박수", + "metricNextDayHrv": "다음 날 심박변이도", + "unitHours": "h", + "unitKcal": "kcal", + "unitScore": "점", + "unitBpm": "bpm", + "unitMs": "ms", + "detailSameDay": "이 태그를 붙인 {withDays}일의 평균은 {withAvg}{unit}, 붙이지 않은 날의 평균은 {withoutAvg}{unit}.", + "detailNextDay": "이 태그를 붙인 {withDays}일의 다음 날 평균은 {withAvg}{unit}, 그 외의 날은 {withoutAvg}{unit}." + }, + "factorCrosstab": { + "title": "직접 평가한 요인과 생체 지표", + "description": "각 요인을 낮게 평가한 날과 높게 평가한 날에 생체 지표가 어떻게 다른지 보여줘요.", + "pairLabel": "{factor} · {metric}", + "metricSleepDuration": "수면", + "metricSteps": "걸음 수", + "metricRestingHeartRate": "안정 시 심박수", + "metricHrv": "심박변이도", + "metricWeight": "체중", + "metricBloodPressureSys": "수축기", + "unitHours": "h", + "unitScore": "점", + "unitSteps": "걸음", + "unitBpm": "bpm", + "unitMs": "ms", + "unitKg": "kg", + "unitMmHg": "mmHg", + "detail": "{factor} 평가가 낮았던 {lowDays}일의 평균은 {lowAvg}{unit}, 높게 평가한 날의 평균은 {highAvg}{unit}.", + "detailInverse": "{factor} 상태가 나빴던 {lowDays}일의 평균은 {lowAvg}{unit}, 좋았던 날의 평균은 {highAvg}{unit}." + }, + "discovery": { + "title": "눈에 띄는 점", + "subheading": "발견된 관계", + "footer": "모든 지표에서 검정한 일자별 조합 {tested}개 중 허위 발견 보정을 거쳐 추려낸 결과예요.", + "disclaimer": "통계로 걸러냈지만 여전히 관찰 결과예요 — 원인이 아니라 지켜볼 만한 패턴이에요.", + "moodUp": "{factor} 값이 높으면 다음 날 기분도 높은 편이에요.", + "moodDown": "{factor} 값이 높으면 다음 날 기분은 낮은 편이에요.", + "outcomeUp": "기분이 좋으면 다음 날 {factor} 값도 높은 편이에요.", + "outcomeDown": "기분이 좋으면 다음 날 {factor} 값은 낮은 편이에요.", + "stat": "짝지은 {n}일 · r = {r} · q = {q}", + "statLabel": "계산 방법", + "explainerLabel": "계산 방법" + }, + "inTargetTitle": "내 범위 안", + "dimensions": { + "title": "마음 상태", + "none": "아직 마음 상태로 기록된 게 없어요.", + "window": "{days}일", + "noneInWindow": "이 기간에는 없음", + "higherIsMore": "높을수록 {anchor}", + "notRecorded": "기록 없음: {list}." + }, + "contextComparison": { + "title": "하루 맥락과 기분", + "description": "이 항목이 함께 기록된 날의 기분 값이 다른 날과 비교해 어땠는지 보여줘요.", + "lower": "낮게", + "higher": "높게", + "statement": "'{label}' 항목이 있던 날의 기분은 평균적으로 더 {direction} 나왔어요: {withAvg} 대 {withoutAvg}.", + "counts": "있던 날 {withDays}일, 없던 날 {withoutDays}일", + "caveat": "이건 연관성일 뿐 원인은 아니에요." + }, + "prognosis": { + "title": "내 자기 평가", + "selfLabel": "내 평가", + "expectedLabel": "내 패턴상 예상값", + "band": "범위 {low}~{high}", + "statement": "지금까지의 내 패턴으로 보면 {date}에는 {value} 정도가 예상됐어요. 비교할 만한 {n}일을 바탕으로 했어요.", + "provisional": "아직 잠정적이에요: 며칠 안 되는 기록에 기댄 패턴이에요.", + "notToday": "오늘이 아니라 {date}에 대한 설명이에요.", + "deviationWithin": "내 평가는 그 범위 안에 있어요.", + "deviationAbove": "내 평가는 그 범위보다 {amount}점 높아요.", + "deviationBelow": "내 평가는 그 범위보다 {amount}점 낮아요.", + "weightedTitle": "이날 비교에서 가장 크게 작용한 것", + "weightedUp": "{label} — 예상값을 높이는 쪽으로 작용했어요", + "weightedDown": "{label} — 예상값을 낮추는 쪽으로 작용했어요", + "caveat": "이건 내 지난 날들과의 비교일 뿐, 그날이 왜 그렇게 흘러갔는지에 대한 설명은 아니에요.", + "learning": "학습 중이에요: {threshold}일 중 {entries}일 기록했어요. 충분히 모이면 비교가 나타나요.", + "noPattern": "지금까지 기록한 {entries}일 중에는 하루를 견줄 만큼 일정한 패턴이 없어요. 하루의 맥락까지 함께 남기면 비교할 수 있는 게 늘어나요.", + "featureValue": "{field}: {value}" + } + }, + "derived": { + "coverage": { + "summary": "입력 {required}개 중 {present}개 · {percent}%", + "ratioLabel": "{present}/{required}", + "historyDays": "최근 기록 {count}일 기준", + "missing": "더 정확해지려면 기록해 보세요: {list}", + "summaryLabelled": "{label} · {percent}%" + }, + "scoreRing": { + "aria": "점수 100점 만점에 {score}점, {band}", + "ariaProvisional": "아직 점수를 낼 수 없어요 — 데이터가 부족해요", + "provisionalCaption": "아직 데이터가 부족해요", + "band": { + "green": "좋음", + "yellow": "보통", + "red": "낮음" + } + }, + "provenance": { + "title": "계산 방식", + "trigger": "계산 방식", + "inputsLabel": "입력값", + "noInputs": "아직 이 값을 뒷받침한 입력값이 없어요.", + "methodLabel": "방법", + "window": "최근 {days}일 기준 · {source} 데이터", + "asOf": "{date} 기준", + "standardLink": "기준: {name}" + }, + "vitals": { + "sectionTitle": "내 생체 지표", + "sectionSubtitle": "내 기준값과 비교한 안정 시 심혈관·호흡 신호", + "loadingLabel": "생체 지표 불러오는 중", + "loadError": "지금은 생체 지표를 불러오지 못했어요.", + "infoLabel": "어떻게 계산하나요?", + "building": "내 일반 범위를 만드는 중 — {target}일 중 {count}일", + "typicalRange": "내 일반 범위: {low}–{high}", + "fitnessYounger": "약 {years}세 젊은 사람 수준의 심폐 체력", + "fitnessOlder": "실제 나이보다 약 {years}세 높은 심폐 체력", + "fitnessTypical": "나이에 걸맞은 심폐 체력", + "fitnessNoNorm": "나이와 성별을 입력하면 기준을 잡을 수 있어요", + "vascularBelow": "실제 나이보다 {years}세 아래", + "vascularAbove": "실제 나이보다 {years}세 위", + "vascularMatch": "실제 나이와 비슷해요", + "vascularNoAge": "생년월일을 입력하면 기준을 잡을 수 있어요", + "hrvBand": { + "balanced": "내 기준에서 균형적이에요", + "unbalanced": "평소 범위보다 높아요", + "low": "평소 범위보다 낮아요" + }, + "bmiCategory": { + "underweight": "저체중 (WHO)", + "normal": "정상 범위 (WHO)", + "overweight": "과체중 (WHO)", + "obese": "비만 (WHO)" + }, + "sixMinuteBand": { + "green": "내 프로필 예측치의 {percent}%", + "yellow": "내 프로필 예측치의 {percent}%", + "red": "내 프로필 예측치의 {percent}%" + }, + "sixMinuteNoBand": "나이, 키, 체중, 성별을 입력하면 기준을 잡을 수 있어요", + "mobilitySectionTitle": "이동성·신체" + }, + "anatomy": { + "outOf": "/100", + "contributorsLabel": "구성 요소", + "contributorValue": "{value} · 가중치 {weight}%", + "contributorMissing": "데이터 없음", + "disclaimer": "참고용이에요 — 임상 평가가 아니에요.", + "insufficient": "아직 점수를 낼 데이터가 부족해요. 계속 기록하면 여기에 표시돼요." + }, + "composite": { + "SLEEP_SCORE": { + "title": "수면 점수", + "description": "세부 점수까지 모두 보여주는 투명한 수면 품질 점수예요.", + "method": "충분도(나이 기준 필요 수면 대비 실제 수면), 효율(잔 시간 ÷ 누워 있던 시간), 일관성(수면 중간 시각이 얼마나 일정한지), 타이밍(평소 중간 시각 대비), 구성(REM + 깊은 수면 비중)을 섞은 0–100 점수예요. 데이터로 뒷받침되지 않는 항목은 빼고 나머지에 가중치를 다시 나눠요. 없는 값을 지어내지는 않아요.", + "sub": { + "sufficiency": "충분도", + "efficiency": "효율", + "consistency": "일관성", + "timing": "타이밍", + "composition": "구성" + } + }, + "READINESS": { + "title": "준비도", + "description": "내 기준값과 비교한 신호로 그날의 컨디션을 가늠하는 지표예요.", + "method": "안정 시 심박수 편차, 심박변이도(SDNN) 편차, 수면 충분도, 호흡수 안정성, 기분 안정성을 섞은 0–100 점수예요. 각 항목은 내 개인 기준값과 비교해요. 데이터가 있는 항목끼리 가중치를 다시 나눠요. 회복도나 임상 점수가 아니라 컨디션을 가늠하는 지표예요.", + "component": { + "rhr": "안정 시 심박수", + "hrv": "심박변이도 (SDNN)", + "sleep": "수면", + "respiratory": "호흡수", + "mood": "기분 안정성" + }, + "caveat": "회복도나 임상 점수가 아니라 컨디션을 가늠하는 지표예요." + }, + "VITALS_BASELINE": { + "method": "개인 일반 범위는 해당 기간 일별 값의 중앙값에 견고한 산포(중앙절대편차를 보정한 값)를 더하고 뺀 구간이에요. 인구 평균이 아니라 내 측정값을 따라가요 — 그래서 이 범위 안이면 나에게는 평소 수준이에요." + }, + "HRV_BALANCE": { + "method": "최근 심박변이도(SDNN) 평균을 내 이동 기준값(중앙값 ± 견고한 산포)과 비교해요. 균형은 개인 범위 안에 있다는 뜻이고, 이 범위는 인구 기준선이 아니라 내 데이터로 만들어요." + }, + "BMI": { + "method": "체중 ÷ 키²를 WHO 성인 분류(저체중 / 정상 / 과체중 / 비만)에 맞춰 나눠요. 선별용 비율일 뿐이라 근육과 지방을 구분하지는 못해요." + }, + "FITNESS_AGE": { + "method": "내 VO₂max를 나이·성별 기준 심폐 체력 백분위와 비교해 체력 나이 차이로 보여줘요. VO₂max는 기기가 추정한 값이고 여기서 다시 계산하지 않아요." + }, + "VASCULAR_AGE_DELTA": { + "method": "기기가 추정한 혈관 나이 / 맥파 속도를 실제 나이와 비교해 연 단위 차이로 보여줘요. 추정은 기기가 하고, HealthLog는 다시 보여줄 뿐이에요.", + "caveat": "기기가 추정한 값을 다시 보여주는 것뿐이에요 — 설명용이고 임상 혈관 평가가 아니에요.", + "description": "기기가 추정한 혈관 나이가 실제 나이와 어떻게 비교되는지 보여줘요." + }, + "COINCIDENT_DEVIATION": { + "method": "같은 날 생체 지표 두 개 이상이 개인 일반 범위를 벗어나면 표시해요. 각 범위는 내 기록의 중앙값 ± k·MAD로 만들어서 인구 기준선이 아니라 나에게 맞춰져요.", + "caveat": "내 기준값에서 나온 알아차림 신호예요 — 설명용이고 진단이나 임상 경고가 아니에요." + }, + "RECOVERY_SCORE": { + "method": "심박변이도, 안정 시 심박수, 수면을 내 기준값과 비교해 섞은 밤마다의 0–100 회복 지표예요. 높을수록 더 잘 회복된 거예요. 매일 밤 한 번 계산해 두고 여기서 그대로 읽어와요.", + "caveat": "설명용 컨디션 지표예요. 훈련용이나 임상용 회복 측정값이 아니에요.", + "description": "심박변이도, 안정 시 심박수, 수면으로 매일 밤 계산하는 회복 지표예요." + }, + "STRESS_SCORE": { + "method": "하루 중 심박변이도의 흐름을 기준값과 비교해 매일 밤 내는 0–100 스트레스 지표예요. 높을수록 생리적 부담이 큰 거예요.", + "caveat": "심박변이도에서 뽑은 지표이고 EDA / 피부 전기 반응 측정이 아니에요 — 시계에 EDA 센서가 없어요. 설명용이고 임상용이 아니에요.", + "description": "하루 중 심박변이도의 흐름으로 매일 밤 계산하는 스트레스 지표예요." + }, + "STRAIN_SCORE": { + "method": "심박수로 가중한 활동 부하(TRIMP 방식 훈련 자극 모델)로 매일 밤 내는 0–100 부하 지표예요. 높을수록 힘든 하루였다는 뜻이에요.", + "anchorPersonal": "최근 운동한 날의 부하를 기준으로 삼아요 — 평소 강도와 비교해 점수를 매기니 나에게 정말 힘든 날이 높게 나와요.", + "anchorPopulation": "운동 기록이 쌓이는 동안에는 일반 기준으로 점수를 매기고, 충분히 모이면 내 평소 강도 기준으로 바뀌어요.", + "caveat": "설명용 활동 부하 지표예요. 임상용이나 훈련용 부하 측정값이 아니에요.", + "description": "심박수로 가중한 운동 강도를 평소 강도와 비교해 매일 밤 계산하는 활동 부하 지표예요." + }, + "WRIST_TEMPERATURE_BASELINE": { + "method": "밤사이 손목 온도의 일반 범위는 해당 기간 야간 측정값의 중앙값에 견고한 산포(중앙절대편차를 보정한 값)를 더하고 뺀 구간이에요. 내 측정값을 따라가기 때문에, 지난밤은 인구 평균이 아니라 내 기준값에서 얼마나 벗어났는지로 보여줘요.", + "caveat": "개인 편차 범위일 뿐이에요 — 설명용이고 질병, 발열, 생리 주기 신호가 아니에요. 해석은 직접 해 주세요." + }, + "STAIR_ASCENT_SPEED_BASELINE": { + "method": "계단 오르는 속도의 일반 범위는 해당 기간 일별 값의 중앙값에 견고한 산포(중앙절대편차를 보정한 값)를 더하고 뺀 구간이에요. 계단 모양과 다리 길이에 따라 속도가 달라져 인구 기준선이 없기 때문에, 범위는 내 데이터로만 만들어요.", + "caveat": "개인 이동성 추이 범위예요 — 설명용이고 노쇠나 낙상 위험 평가가 아니에요." + }, + "STAIR_DESCENT_SPEED_BASELINE": { + "method": "계단 내려가는 속도의 일반 범위는 해당 기간 일별 값의 중앙값에 견고한 산포(중앙절대편차를 보정한 값)를 더하고 뺀 구간이에요. 계단 모양과 다리 길이에 따라 속도가 달라져 인구 기준선이 없기 때문에, 범위는 내 데이터로만 만들어요.", + "caveat": "개인 이동성 추이 범위예요 — 설명용이고 노쇠나 낙상 위험 평가가 아니에요." + }, + "SIX_MINUTE_WALK_BAND": { + "method": "기기가 추정한 6분 보행 거리를 나이, 키, 체중, 성별에 맞춘 Enright & Sherrill 기준과 비교해 예측치 대비 백분율로 보여줘요. 거리는 기기의 추정값이고 여기서 다시 계산하지 않아요. 나이, 키, 체중, 성별이 없으면 백분율은 숨기고 거리와 추이만 보여줘요.", + "caveat": "발표된 기준과 비교해 다시 보여준 추정값이에요 — 신체 기능을 알아차리기 위한 신호이고 임상 6분 보행 검사나 진단이 아니에요. 이 기준은 40–80세를 대상으로 만들어졌고, 그 범위를 벗어나면 예측치는 외삽한 값이에요." + }, + "TRAJECTORY": { + "method": "최근 일별 값에 최소제곱 직선을 맞춰 짧은 기간 앞으로 이어 그리고, 멀어질수록 넓어지는 표준 예측 구간을 함께 보여줘요. 최근 추이가 충분히 뚜렷하고 기록이 길고 최신일 때만 계산하고, 그렇지 않으면 선을 그리지 않아요.", + "caveat": "최근 추이를 그대로 이어 그린 것이에요 — 예측이나 목표, 임상 전망이 아니에요. 최근 패턴이 이어질 때만 맞고, 음영 구간은 그 확신이 얼마나 빨리 흐려지는지 보여줘요." + }, + "SAME_TIME_BASELINE": { + "method": "오늘 누적값을 최근 4주 동안 같은 시각의 중앙값과 비교하고, 그 주위에 중앙값 ± MAD 범위를 함께 보여줘요. 기록이 충분한 날만 계산에 넣고, 그런 날이 14일 이상 모이기 전에는 비교를 보여주지 않아요.", + "caveat": "목표가 아니라 내 최근 기록과의 비교예요. 이 시각의 평소 패턴에서 오늘이 어디쯤인지 알려줄 뿐, 그 패턴이 나에게 맞는지는 말해 주지 않아요." + } + }, + "scores": { + "sectionTitle": "내 건강 점수", + "sectionSubtitle": "연결된 기기에서 읽어온 종합 회복·스트레스·부하 점수", + "loadError": "지금은 컨디션 점수를 불러오지 못했어요.", + "loadingLabel": "건강 점수 불러오는 중", + "recovery": "회복 점수", + "recoveryCrossLink": "웨어러블의 회복·부하 데이터 보기", + "stress": "스트레스", + "strain": "부하" + }, + "coincident": { + "label": "여러 지표가 기준값 밖", + "summary": "오늘 생체 지표 {count}개가 개인 범위를 벗어났어요.", + "vitals": "범위 밖: {list}", + "cardTitle": "오늘의 신호", + "allClear": "오늘은 생체 지표가 모두 개인 범위 안에 있어요.", + "allClearMeta": "생체 지표 {count}개를 내 일반 범위와 비교했어요.", + "watch": "오늘 지표 하나가 평소 범위를 벗어났어요.", + "watchVital": "범위 밖: {vital}. 대개는 한 번뿐이지만 며칠 지켜볼 만해요.", + "firedHeadline": "오늘 생체 지표 {count}개가 개인 범위를 벗어났어요.", + "factors": "가능한 요인 — 원인은 아니에요: 힘든 운동, 부족한 수면, 음주, 고도, 스트레스, 아픈 날. 내 기준값에서 나온 알아차림 신호이고 진단이 아니에요.", + "building": "개인 기준값을 만드는 중이에요 — 며칠 더 기록하면 여기에 표시돼요.", + "illnessExplained": "이 변화는 기록해 둔 상태 때문일 가능성이 커요." + }, + "trajectory": { + "cardTitle": "단기 전망", + "headline": "이 패턴이 이어진다면 — 앞으로 {days}일", + "range": "{days}일 뒤 예상 범위: 약 {low}–{high} {unit}.", + "caveat": "최근 추이를 그대로 이어 그린 것이고 예측이 아니에요. 음영 구간은 불확실성이 얼마나 빨리 커지는지 보여줘요.", + "insufficient": "아직 전망을 그릴 만큼 추이가 뚜렷하지 않아요 — 계속 기록해서 최근 패턴이 뚜렷해지면 전망이 표시돼요.", + "daysNeeded": "전망이 나오려면 {count}일쯤 더 기록해야 해요." + }, + "sameTime": { + "sectionTitle": "오늘 지금까지", + "sectionSubtitle": "이 시각의 평소 수준과 비교한 오늘 누적값", + "learning": "평소 하루를 익히는 중 — {target}일 중 {count}일", + "framing": "{time}까지 평소: {typical} {unit}", + "comparisonLabel": "평소 하루" + } + }, + "cardioFitness": { + "title": "심폐 체력", + "description": "시간에 따른 VO₂ max — 유산소 체력을 대표하는 지표예요.", + "chartTitle": "VO₂ max (mL/(kg·min))", + "emptyState": { + "title": "아직 심폐 체력 데이터가 없어요", + "description": "Apple Health가 운동 기록을 동기화하면 VO₂ max 측정값이 여기에 표시돼요." + } + }, + "navCardioFitness": "심폐 체력", + "cardioRecovery": { + "title": "운동 후 회복", + "description": "최고 강도 운동 1분 뒤 심박수가 얼마나 떨어지는지 보여줘요.", + "chartTitle": "운동 후 회복 (bpm)", + "emptyState": { + "title": "아직 운동 후 회복 데이터가 없어요", + "description": "Apple Health가 운동 기록을 동기화하면 운동 후 회복 측정값이 여기에 표시돼요." + } + }, + "navCardioRecovery": "운동 후 회복", + "wristTemperature": { + "title": "손목 온도", + "description": "내 기준값과 비교한 밤사이 손목 온도예요.", + "chartTitle": "손목 온도 (°C)", + "emptyState": { + "title": "아직 손목 온도 데이터가 없어요", + "description": "Apple Health가 밤사이 착용 기록을 동기화하면 손목 온도 측정값이 여기에 표시돼요." + } + }, + "navWristTemperature": "손목 온도", + "falls": { + "title": "낙상", + "description": "Apple Watch가 감지한 심한 낙상을 하루 단위로 센 값이에요.", + "chartTitle": "일별 낙상", + "emptyState": { + "title": "아직 낙상 데이터가 없어요", + "description": "Apple Health가 동기화하면 감지된 낙상이 여기에 표시돼요. 낙상이 없는 게 가장 좋아요." + } + }, + "navFalls": "낙상", + "sixMinuteWalk": { + "title": "6분 보행", + "description": "6분 동안 걸을 수 있는 거리를 Apple이 추정한 값이에요.", + "chartTitle": "6분 보행 거리 (m)", + "emptyState": { + "title": "아직 6분 보행 데이터가 없어요", + "description": "Apple Health가 동기화하면 6분 보행 추정값이 여기에 표시돼요." + } + }, + "navSixMinuteWalk": "6분 보행", + "stairAscentSpeed": { + "title": "계단 오르기 속도", + "description": "iPhone이나 Watch가 측정한 계단 오르는 속도예요.", + "chartTitle": "계단 오르기 속도 (m/s)", + "emptyState": { + "title": "아직 계단 오르기 속도 데이터가 없어요", + "description": "Apple Health가 동기화하면 계단 오르기 속도 측정값이 여기에 표시돼요." + } + }, + "navStairAscentSpeed": "계단 오르기", + "stairDescentSpeed": { + "title": "계단 내려가기 속도", + "description": "iPhone이나 Watch가 측정한 계단 내려가는 속도예요.", + "chartTitle": "계단 내려가기 속도 (m/s)", + "emptyState": { + "title": "아직 계단 내려가기 속도 데이터가 없어요", + "description": "Apple Health가 동기화하면 계단 내려가기 속도 측정값이 여기에 표시돼요." + } + }, + "navStairDescentSpeed": "계단 내려가기", + "breathingDisturbances": { + "title": "호흡 장애", + "description": "자는 동안 호흡이 얼마나 자주 흐트러졌는지 보여줘요.", + "chartTitle": "수면 중 호흡 장애", + "emptyState": { + "title": "아직 호흡 장애 데이터가 없어요", + "description": "지원되는 Apple Watch에서 Apple Health가 동기화하면 수면 호흡 측정값이 여기에 표시돼요." + } + }, + "navBreathingDisturbances": "호흡", + "rhythmEvents": { + "sectionTitle": "기기 건강 알림", + "sectionIntro": "시계가 스스로 보낸 알림이에요. HealthLog는 보여 주기만 할 뿐, 감지하거나 확인하거나 해석하지 않아요.", + "loadError": "기기 알림을 불러오지 못했어요.", + "disclaimer": "기기가 자체 감지 기능으로 만든 알림이에요. 참고용으로만 보여 줘요. HealthLog의 의학적 판단이 아니고, HealthLog는 어떤 질환도 진단하지 않아요. 알림이 걱정된다면 의사와 상의해 주세요.", + "event": { + "irregularRhythm": "불규칙한 리듬 알림", + "highHeartRate": "높은 심박수 알림", + "lowHeartRate": "낮은 심박수 알림", + "walkingSteadiness": "보행 안정성 알림", + "breathingDisturbance": "수면 중 호흡 장애" + }, + "verdict": { + "irregular": "기기가 불규칙한 리듬 가능성을 표시했어요.", + "notDetected": "기기가 불규칙한 리듬을 표시하지 않았어요.", + "inconclusive": "기기가 측정하지 못했어요.", + "low": "기기가 낮은 보행 안정성을 표시했어요.", + "veryLow": "기기가 매우 낮은 보행 안정성을 표시했어요.", + "fired": "기기가 이 이벤트를 표시했어요." + } + }, + "trendDescriptor": { + "rising": "30일간 상승했어요 ({delta}{unit}).", + "falling": "30일간 하락했어요 ({delta}{unit}).", + "stable": "최근 30일간 안정적이에요.", + "moodImproved": "30일간 기분이 조금 좋아지는 흐름이에요.", + "moodDeclined": "30일간 기분이 조금 나빠지는 흐름이에요.", + "moodStable": "최근 30일간 기분이 꾸준했어요." + }, + "target": { + "heading": "목표" + }, + "settings": { + "overviewTitle": "개요 구성", + "overviewDescription": "인사이트 개요에서 섹션을 표시하거나 숨기고 순서를 정하세요." + }, + "pillOrder": { + "title": "탐색 칩 정렬", + "description": "칩을 끌어 원하는 순서로 놓고, 눈 아이콘으로 표시하거나 숨기세요.", + "dragHandle": "순서 변경", + "saveSuccess": "칩 순서를 저장했어요", + "saveError": "칩 순서를 저장하지 못했어요.", + "save": "저장", + "empty": "아직 사용할 수 있는 탐색 칩이 없어요.", + "detail": "숨긴 상세 페이지는 상단 탐색에서도 사라져요." + }, + "customize": "인사이트 맞춤 설정", + "regenerateRateLimited": "새로고침 한도에 도달했어요 — 잠시 후 다시 시도해 주세요.", + "regenerateUnavailable": "새 분석이 도착하지 않았어요 — 잠시 후 다시 시도해 주세요.", + "regenerateError": "아직 새로고침 중이에요 — 잠시 후 다시 시도해 주세요.", + "regenerateSettling": "평가를 아직 준비하고 있어요", + "regenerateSettleFailed": "새로고침을 마치지 못했어요. 잠시 후 다시 시도해 주세요.", + "deviceScore": { + "learning": "아직 기준값을 익히는 중이에요 — 며칠 밤 더 지나면 추이가 채워져요.", + "average": "30일 평균 {value}", + "unitScore": "점", + "unitMinutes": "min", + "unitBpm": "bpm", + "unitKj": "kJ" + }, + "sleepQuality": { + "title": "수면 품질", + "subtitle": "웨어러블이 매일 밤 매긴 점수" + }, + "recovery": { + "title": "회복", + "description": "몸이 얼마나 회복했는지 — 웨어러블이 밤사이 기록한 회복·부하 신호예요.", + "scoreCrossLink": "종합 회복 점수 보기", + "emptyTitle": "아직 회복 데이터가 없어요", + "empty": "아직 회복 데이터가 없어요. WHOOP, Polar, Oura 계정을 연결하면 기기가 동기화될 때 점수가 채워져요.", + "block": { + "ansChargeExplainer": "오늘 하루를 맞을 자율신경계의 충전 상태예요 — 밤사이 심장·호흡 신호에서 읽어낸 재충전 지표예요.", + "dayStrainExplainer": "기기의 부하 척도로 본 하루 전체의 심혈관 부하예요. 그 자체를 목표로 삼지 말고 회복과 견줘 보세요.", + "workoutStrainExplainer": "같은 부하 척도에서 운동 하나가 얼마나 힘들었는지 — 그 세션의 심혈관 비용이에요.", + "cardioLoadExplainer": "쌓인 급성 심혈관 부하를 따라가는 훈련 부하 지표예요. 내 최근 며칠과 비교해 보세요.", + "averageHeartRateExplainer": "웨어러블이 보고한 하루 또는 주기 전체의 평균 심박수예요.", + "maxHeartRateExplainer": "웨어러블이 보고한 하루 또는 주기 전체의 최고 심박수예요.", + "energyExpenditureKjExplainer": "기기가 자체 단위로 보고한, 하루 동안 몸이 쓴 에너지예요(킬로줄)." + } + }, + "bodyTempDeviation": { + "title": "체온 편차", + "subtitle": "기준값 대비 밤사이 변화" + }, + "resilience": { + "explainer": "최근 며칠간의 밤사이 신호로 읽은, 몸이 하루의 부하와 회복을 얼마나 잘 맞추고 있는지예요.", + "learning": "아직 회복탄력성을 익히는 중이에요 — 며칠 더 지나면 범위가 자리 잡아요.", + "band": { + "limited": "제한적", + "adequate": "충분", + "solid": "탄탄", + "strong": "강함", + "exceptional": "매우 강함" + }, + "trend": { + "above": "최근 평균보다 높아요.", + "steady": "최근 평균과 비슷해요.", + "below": "최근 평균보다 낮아요." + } + }, + "briefing": { + "memory": { + "forwardWatch": "지켜볼 만한 항목: {metric}.", + "forwardHolding": "지금은 모두 안정적이에요." + } + }, + "updatedTodayAt": "오늘 {time}에 업데이트", + "updatedYesterday": "어제 업데이트", + "updatedOn": "{date} 업데이트", + "healthStatus": { + "sectionTitle": "달라진 점", + "subtitle": "평소 범위에서 벗어나고 있는 신호예요.", + "deviationAbove": "{vital} — 일반 범위보다 높아요.", + "deviationBelow": "{vital} — 일반 범위보다 낮아요.", + "shiftUp": "{vital} — 최근 평소 수준보다 올라갔어요.", + "shiftDown": "{vital} — 최근 평소 수준보다 내려갔어요.", + "rangeContext": "지금 {value}, 평소 {low}–{high}.", + "shiftContext": "이전엔 약 {before}, 이후엔 {after}." + }, + "breathingScreening": { + "sectionTitle": "수면 중 호흡", + "subtitle": "수면 데이터에서 나온 선별 신호예요.", + "classificationElevated": "기기가 호흡 장애가 높다고 표시했어요.", + "classificationNotElevated": "기기 분류는 '높지 않음'이에요.", + "trendUp": "이전 밤들보다 높아지는 흐름이에요.", + "trendDown": "이전 밤들보다 낮아지는 흐름이에요.", + "trendStable": "이전 밤들과 비슷하게 유지되고 있어요.", + "nights": "{count}일 밤 기록", + "indexLine": "최근 평균 지수 {value}", + "eventsFlagged": "기기가 표시한 이벤트 {count}건", + "disclaimer": "진단이 아니라 선별 신호예요 — 임상의와 상의해 주세요." + }, + "labsChanges": { + "sectionTitle": "지난 검사 이후", + "subtitle": "가장 최근 검사 결과를 그 전 검사와 비교했어요.", + "statusInRange": "범위 내", + "statusBelow": "범위 아래", + "statusAbove": "범위 위", + "directionFlat": "변화 없음" + }, + "navGripStrength": "악력", + "navPainNrs": "통증", + "navWaistCircumference": "허리둘레", + "navWaistToHeight": "허리-키 비율", + "gripStrength": { + "title": "악력", + "description": "최대 악력 — 전신 근력을 잘 대변하는 지표예요. 성별 기준선(남성 27kg, 여성 16kg) 아래면 근감소증 가능성을 뜻해요. 해석은 내 추이가 우선이에요.", + "chartTitle": "악력", + "emptyState": { + "title": "아직 악력 기록이 없어요", + "description": "악력계로 잰 값을 기록해 보세요. 몇 번 쥐어 본 값 중 가장 좋은 값을 남기면 추이가 여기에 표시돼요.", + "cta": "악력 기록" + }, + "coachPrefill": "아직 악력을 기록한 적이 없어요 — 악력은 내 근육 건강에 대해 무엇을 알려주고, 어떻게 재고 어떻게 키우나요?" + }, + "pain": { + "title": "통증 (0–10)", + "description": "환자가 직접 매기는 0–10 숫자 평가 척도의 통증 강도예요. 0은 통증 없음, 10은 상상할 수 있는 최악이에요. 2점 이상 지속되는 변화가 지켜볼 신호예요.", + "chartTitle": "통증 (0–10)", + "emptyState": { + "title": "아직 통증 점수가 없어요", + "description": "지금 느끼는 통증을 0–10 척도로 기록하면 추이가 여기에 표시돼요.", + "cta": "통증 점수 기록" + }, + "coachPrefill": "아직 통증 점수를 기록한 적이 없어요 — 0–10 척도로 통증을 어떻게 기록해야 하고, 어느 정도가 의미 있는 변화인가요?" + }, + "waist": { + "title": "허리둘레", + "description": "센티미터로 잰 허리둘레 — 복부 지방의 지표예요. WHO 위험 증가 기준선은 성별에 따라 달라요(남성 94cm, 여성 80cm). 해석은 내 추이가 우선이에요.", + "chartTitle": "허리둘레", + "emptyState": { + "title": "아직 허리둘레 측정값이 없어요", + "description": "가장 아래 갈비뼈와 골반뼈의 중간 지점에서, 숨을 편히 내쉰 끝에 허리를 재서 기록해 주세요.", + "cta": "허리둘레 기록" + }, + "coachPrefill": "아직 허리둘레를 기록한 적이 없어요 — 허리둘레는 어떻게 정확히 재고, 그 숫자가 건강에 어떤 의미인가요?" + }, + "waistToHeight": { + "title": "허리-키 비율", + "description": "허리둘레를 키로 나눈 값 — 인종에 관계없이 통하는 단일 지표예요. 0.5 미만(허리둘레가 키의 절반 아래)으로 유지하는 게 간단한 목표예요.", + "chartTitle": "허리-키 비율", + "emptyState": { + "title": "아직 허리-키 비율이 없어요", + "description": "허리-키 비율을 기록하면 추이가 여기에 표시돼요. 요령은 간단해요. 허리둘레를 키의 절반 아래로 유지하세요.", + "cta": "허리-키 비율 기록" + }, + "coachPrefill": "아직 허리-키 비율을 기록한 적이 없어요 — 이 숫자가 왜 유용하고, 어떻게 0.5 미만으로 유지하나요?" + }, + "ecg": { + "sectionTitle": "심전도 기록", + "loadError": "심전도 기록을 불러오지 못했어요.", + "backToList": "기록 목록으로", + "clinicianNote": "결과가 걱정된다면 이 기록을 임상의와 상의해 보세요.", + "resultAttribution": "기록된 결과: {result} — 측정 기기가 보고한 값이에요.", + "result": { + "irregular": "심방세동 감지", + "notDetected": "심방세동 징후 없음", + "inconclusive": "판정 불가 기록", + "none": "보고된 결과 없음" + }, + "meta": { + "recorded": "기록 시각", + "duration": "지속 시간", + "lead": "리드", + "averageHeartRate": "평균 심박수", + "samplingRate": "샘플링 레이트", + "leadSingle": "단일 리드 (리드 I)", + "durationValue": "{seconds}s", + "bpmValue": "{bpm}bpm", + "hzValue": "{hz}Hz", + "unknown": "—" + }, + "waveform": { + "ariaLabel": "{date}에 기록한 심전도 스트립, 지속 시간 {duration}, 평균 심박수 {heartRate}, 기록된 결과: {result}." + }, + "crossLink": { + "title": "심전도 기록", + "recordingsOne": "기록 1개가 있어요.", + "recordingsMany": "기록 {count}개가 있어요.", + "latestResult": "최근 기기 결과: {result}.", + "cta": "기록 보기" + }, + "emptyTitle": "아직 심전도 기록이 없어요", + "emptyDescription": "심전도 기록은 호환되는 단일 리드 기기에서 동기화돼요. 기기가 기록을 올리면 여기에 표시돼요.", + "emptyCta": "기기 연결" + }, + "intradayPulse": { + "title": "하루의 흐름", + "caption": "10분 단위로 평균 낸 하루 동안의 심박수예요.", + "hourlyNote": "이 날짜는 시간별 평균으로 보여줘요 — 최근 며칠은 10분 단위까지 자세히 볼 수 있어요.", + "empty": "이 날짜는 심박수 측정값이 적어서 아직 흐름을 보여줄 수 없어요.", + "coverageOne": "{hours}h 동안 모은 측정값 1개를 기준으로 했어요.", + "coverageMany": "{hours}h 동안 모은 측정값 {count}개를 기준으로 했어요.", + "baseline": "안정 시", + "today": "오늘", + "previousDay": "이전 날짜", + "nextDay": "다음 날짜", + "tension": { + "morning": "오늘 아침에 안정 시 심박수가 높은 구간이 이어졌어요 — 긴장이 있었을 수 있어요.", + "afternoon": "오늘 오후에 안정 시 심박수가 높은 구간이 이어졌어요 — 긴장이 있었을 수 있어요.", + "evening": "오늘 저녁에 안정 시 심박수가 높은 구간이 이어졌어요 — 긴장이 있었을 수 있어요.", + "night": "밤사이 안정 시 심박수가 높은 구간이 이어졌어요 — 긴장이 있었을 수 있어요." + } + }, + "navEcg": "심전도" + }, + "thresholds": { + "defaultLabel": "기본값", + "overrideToggleLabel": "사용자 범위", + "resetAction": "기본값으로 초기화", + "resetAllAction": "전체 기본값으로 초기화", + "resetAllTitle": "모든 사용자 범위를 초기화할까요?", + "resetAllBody": "모든 지표가 기본 범위로 돌아가요. 설정한 값은 다른 곳에 저장되지 않아 되돌릴 수 없어요.", + "resetAllConfirm": "전체 초기화", + "resetTitle": "이 범위를 초기화할까요?", + "resetBody": "이 지표가 기본 범위로 돌아가요. 설정한 값은 다른 곳에 저장되지 않아 되돌릴 수 없어요.", + "resetConfirm": "범위 초기화", + "minLabel": "최소", + "maxLabel": "최대", + "unitSuffix": "({unit})", + "outOfBoundsHint": "허용 범위: {min}–{max} {unit}", + "overrideWarning": "가이드라인 밖의 값으로 덮어쓰면 건강에 해로운 목표가 될 수 있어요. 의사 권고가 있을 때만 바꿔 주세요.", + "saveSuccess": "기준선을 저장했어요", + "saveError": "기준선을 저장하지 못했어요", + "resetSuccess": "기본값으로 초기화했어요", + "sourceOverride": "사용자 설정", + "metricWeight": "체중", + "metricBpSys": "혈압(수축기)", + "metricBpDia": "혈압(이완기)", + "metricPulse": "안정 시 맥박", + "metricBodyFat": "체지방", + "metricSleep": "수면 시간", + "metricSteps": "하루 걸음 수", + "metricGlucoseFasting": "혈당 — 공복", + "metricGlucosePostprandial": "혈당 — 식후", + "metricGlucoseRandom": "혈당 — 임의", + "metricGlucoseBedtime": "혈당 — 취침 전", + "metricBodyWater": "체수분", + "metricBoneMass": "골량", + "metricOxygenSaturation": "산소포화도(SpO₂)", + "unsetExplanation": "사용자 설정 없음 — 계산된 기본값을 써요." + }, + "onboarding": { + "errorGeneric": "설정을 마치는 중 문제가 생겼어요. 다시 시도해 주세요.", + "tour": { + "stepOf": "{current}/{total}단계", + "next": "다음", + "back": "이전", + "skip": "둘러보기 건너뛰기", + "done": "확인", + "steps": { + "dashboardOverview": { + "title": "내 대시보드", + "body": "대시보드는 모든 모듈을 한눈에 모아 보여줘요. 타일을 길게 누르면 순서를 바꾸거나 숨길 수 있어요(설정 → 대시보드 레이아웃). 타일은 그 뒤에 쌓인 데이터만큼만 말해 줘요 — 앞으로 나올 모듈들이 그 데이터를 채워요." + }, + "quickAdd": { + "title": "몇 초면 기록 끝", + "body": "'+ 추가'로 체중, 혈압, 맥박, 혈당 등 어떤 측정값이든 몇 초 만에 기록해요. 숨은 강점: 깜빡한 측정값을 지난 날짜로 넣을 수 있어요. 여기서 기록한 건 모두 추이, 건강 점수, AI 인사이트로 이어져요." + }, + "measurements": { + "title": "측정값과 목표 범위", + "body": "측정에서는 지표별 기록과 목표 범위를 볼 수 있어요. 숨은 보석: 값마다 목표 범위를 정해 두면 벗어난 값이 색으로 눈에 띄어요. 이 기록들이 코치의 상관관계 분석과 진료용 리포트의 바탕이 돼요." + }, + "medications": { + "title": "복약과 복용 기록", + "body": "복약에서 일정, 복용, 재고, 용량 조절을 관리해요. 숨은 기능: 반복 일정(RRULE/롤링)과 재고 부족 경고가 있어요. 복용률은 대시보드의 복약 순응도 항목과 AI의 맥락으로 쓰여요." + }, + "labs": { + "title": "검사 수치", + "body": "검사에서는 혈액검사, 콜레스테롤, HbA1c 같은 지표를 참고 범위와 함께 기록해요. 강점: 불러오기와 FHIR 내보내기. 참고 범위를 벗어난 결과는 인사이트와 진료용 리포트에 근거로 다시 등장해요." + }, + "illness": { + "title": "아픈 날 다이어리", + "body": "아픈 날 다이어리에는 급성 증상과 만성 진단, 악화 시기를 기록해요. 숨은 기능: 증상을 내 측정값과 견줘 보고, 알림을 멈추는 휴식 모드도 있어요. 값이 한동안 흔들리는 이유를 코치가 이렇게 이해해요." + }, + "vorsorge": { + "title": "건강검진", + "body": "건강검진은 검사와 예방접종을 각자의 주기에 맞춰 알려 줘요. 팁: '완료'를 누르면 측정값이나 검사 결과를 바로 만들 수 있어요. 받을 때가 된 항목은 알림과 진료용 리포트에 나타나요." + }, + "cycle": { + "title": "생리 주기", + "body": "주기 기록으로 시기와 증상을 남겨요. 숨은 기능: 증상을 기분·수면과 비교해 볼 수 있어요. 주기를 알면 설명되지 않던 변화의 맥락이 보여요." + }, + "mood": { + "title": "기분", + "body": "기분은 지금 느낌과 태그를 2초 만에 남겨요. 강점: 나만의 태그. 기분 추이는 수면, 약, 주기와 맞물리고 — 코치가 그 패턴을 찾아 줘요." + }, + "insights": { + "title": "인사이트", + "body": "인사이트가 핵심이에요. 뻔한 조언 대신 근거 기반의 동적 분석이라, 모든 문장이 실제 내 데이터를 가리켜요. 숨은 기능: 레이아웃을 원하는 대로 바꿀 수 있어요. 앞의 모듈들이 모은 것을 인사이트가 한데 엮어요." + }, + "coach": { + "title": "코치", + "body": "코치는 내 기록, 약, 검사 수치를 맥락 삼아 대화로 질문에 답해요. AI 제공자는 직접 고르면 돼요(내 키 또는 로컬). 새로 계산하지 않고 대시보드와 같은 숫자를 읽어요." + }, + "integrations": { + "title": "연동", + "body": "연동하면 Withings, WHOOP, Polar, Oura, Apple Health가 자동으로 동기화돼요. 체중, 수면, 회복이 직접 입력 없이 채워지고 — 그게 다시 인사이트와 코치로 이어져요." + }, + "export": { + "title": "진료용 리포트", + "body": "진료용 리포트는 측정값, 약, 검사 수치, 건강검진을 PDF나 FHIR로 묶어 진료에 가져갈 수 있게 해 줘요. 모든 모듈이 여기서 만나요 — 잘 쌓인 데이터라야 온전한 리포트가 나와요." + }, + "achievements": { + "title": "업적", + "body": "업적은 꾸준함에 보상을 줘요 — 연속 기록과 이정표. 계속하기 쉽게 만드는 작은 동기 장치예요." + }, + "wrapUp": { + "title": "둘러보기 끝", + "body": "이 안내는 한 번만 나오고 저절로 다시 열리지 않아요. 살펴본 내용은 그대로 있고, 설정 → 모듈에서 언제든 기능을 켜고 끌 수 있어요." + } + } + }, + "shell": { + "back": "이전", + "skip": "건너뛰기", + "next": "계속", + "step1of4": "1/4단계", + "step2of4": "2/4단계", + "step3of4": "3/4단계", + "step4of4": "4/4단계" + }, + "goals": { + "title": "무엇을 기록하고 싶으세요?", + "body": "관심 있는 영역을 골라 주세요. 대시보드에 먼저 보여 드리고, 다음에 무엇을 설정할지 정하는 데도 도움이 돼요.", + "helpHint": "우선순위는 1~3개가 가장 잘 맞아요 — 나중에 언제든 더 추가할 수 있어요.", + "options": { + "weightManagement": "체중 관리", + "bpTracking": "혈압", + "glucoseTracking": "혈당", + "sleepImprovement": "더 나은 수면", + "medicationCompliance": "복약", + "generalWellness": "전반적인 건강" + } + }, + "source": { + "title": "소스 연결", + "body": "측정값을 자동으로 가져오거나, 직접 입력만 써도 돼요. 여기서 대신 설정해 주는 건 없고, 카드마다 어디서 연결하는지만 알려 줘요. 기기 소스는 본인 계정에 무료 개발자 앱을 등록해야 하고(약 5분), 카드의 설정 가이드가 순서를 알려 줘요.", + "recommended": "추천", + "manual": { + "title": "직접 입력", + "body": "편할 때 직접 기록해요. 어떤 기기에서도 되고, 외부 계정도 필요 없어요." + }, + "withings": { + "title": "Withings", + "body": "Withings 체중계, 혈압계, 트래커는 연결해 두면 자동으로 동기화돼요.", + "cta": "Withings 연결", + "setupCta": "설정", + "setupHint": "먼저 본인의 무료 Withings 개발자 앱이 필요해요 — 처음 한 번, 약 5분." + }, + "more": { + "title": "다른 소스", + "body": "준비되면 언제든 설정에서 연결할 수 있어요 — 대부분 본인 계정에 무료 개발자 앱을 먼저 등록해야 해요.", + "appleHealth": "Apple Health", + "whoop": "WHOOP", + "oura": "Oura", + "polar": "Polar", + "nightscout": "Nightscout", + "fitbit": "Fitbit", + "appleHealthHint": "Apple Health는 웹으로 연결할 수 없어요 — 여기서 시작 설정을 마친 뒤 iPhone에서 HealthLog 앱을 열어 Apple Health 설정을 끝내 주세요." + }, + "import": "기존 기록 불러오기" + }, + "baseline": { + "title": "나에 대해 조금만", + "body": "선택 항목 네 가지가 목표 범위, BMI, 알림을 더 정확하게 만들어요. 나중에 설정에서 언제든 바꿀 수 있어요.", + "displayNameLabel": "표시 이름", + "displayNamePlaceholder": "HealthLog가 어떻게 부르면 좋을까요?", + "displayNameHint": "대시보드 인사말과 리포트에 표시돼요.", + "heightLabel": "키(cm)", + "heightLabelFtIn": "키(ft, in)", + "genderLabel": "성별", + "genderNone": "밝히지 않음", + "genderMale": "남성", + "genderFemale": "여성", + "genderOther": "기타", + "dateOfBirthLabel": "생년월일", + "dateOfBirthHint": "나이를 반영한 분석에만 써요.", + "saveCta": "저장하고 계속" + }, + "done": { + "title": "준비 끝났어요", + "body": "환영해요. 대시보드가 준비됐어요.", + "returnCta": "대시보드 열기", + "learning": "혈당, 수면 리듬, 혈압 추이, 주기 같은 인사이트는 데이터가 쌓이는 첫 한두 주 동안 점점 또렷해져요. 지금 비어 있는 타일은 아직 기준값을 익히는 중이라는 뜻이에요.", + "connectCta": "기기 연결", + "logCta": "첫 측정값 기록하기", + "importCta": "기존 기록 불러오기", + "modulesCta": "모듈 고르기" + }, + "welcomeBack": { + "title": "다시 오셨네요", + "body": "시작 설정은 이미 마쳤어요. 바꾸고 싶은 게 있으면 설정으로 가 보세요 — 설정한 건 모두 거기서 수정할 수 있어요.", + "cta": "대시보드 열기" + }, + "disclaimer": { + "acknowledge": "이해했어요. HealthLog는 개인 기록 도구일 뿐, 임상 평가나 진단이 아니에요. 걱정되는 일이 있으면 이 앱이 아니라 의사와 상의할게요.", + "learnMore": "자세히 보기" + }, + "welcome": { + "title": "HealthLog에 오신 걸 환영해요", + "carouselLabel": "환영 캐러셀", + "slideOf": "{current}/{total}번째 슬라이드", + "prevSlide": "이전 슬라이드", + "nextSlide": "다음 슬라이드", + "gotoSlide": "{index}번 슬라이드로 이동", + "cta": "시작하기", + "slide1": { + "title": "모든 신호를 기록해요", + "body": "체중, 혈압, 혈당, 수면, 기분 — 내가 가진 하나의 기록에 전체 그림을 담아요." + }, + "slide2": { + "title": "소스를 연결해요", + "body": "Apple Health, Withings와 동기화하거나 직접 입력해요. 데이터는 내 서버에 남아요." + }, + "slide3": { + "title": "패턴이 보여요", + "body": "하루 요약, 추이 차트, 무엇이 달라졌는지 짚어 주는 코치 — 모두 내 기기 안에서." + } + }, + "anamnesis": { + "title": "건강에 대해 (선택)", + "subtitle": "HealthLog가 내 숫자를 더 공정하게 읽도록 약간의 맥락을 알려 주세요.", + "intro": "원하는 만큼만 알려 주세요 — 모든 항목은 선택이고, 설정에서 언제든 바꿀 수 있어요.", + "conditionsLabel": "앓고 있는 질환", + "conditionsPlaceholder": "예: 고혈압, 2형 당뇨", + "conditionsHint": "당연한 값을 경고로 띄우는 대신, 맥락 속에서 측정값을 보게 해 줘요.", + "allergiesLabel": "알레르기나 불내성", + "allergiesPlaceholder": "예: 페니실린, 유당", + "privacyNote": "암호화해 저장하고, 더 나은 맥락을 주는 데만 써요. 여기서 약은 묻지 않아요 — 복약 페이지에서 언제든 추가하면 돼요." + }, + "ai": { + "panelTitle": "인사이트가 어떤 모습인지 보기", + "panelIntro": "코치, 데일리 브리핑, 지표별 해설이 내 숫자를 쉬운 말로 풀어 줘요. 무언가 연결하기 전에, 대략 이런 모습이에요.", + "showSample": "샘플 브리핑 보기", + "sampleTitle": "데일리 브리핑", + "sampleTag": "예시", + "sampleAriaLabel": "데일리 브리핑 예시", + "sampleBody": "2주 동안 체중 1.2kg 감소. 안정 시 맥박은 58bpm으로 꾸준해요. 혈압은 7일 중 6일 범위 안 — 높았던 하루는 늦은 시간의 짠 저녁 식사 다음이었어요.", + "sampleCaption": "예시일 뿐 내 데이터가 아니에요. 인사이트를 켜면 브리핑이 이런 식으로 읽혀요.", + "ladderTitle": "켜는 방법 세 가지", + "ladderLocalTitle": "로컬 모델 — 내 네트워크 밖으로 나가지 않아요", + "ladderLocalBody": "내 컴퓨터의 Ollama나 LM Studio를 HealthLog에 연결해요. 무료이고 완전히 비공개 — 가장 마음 편한 기본값이에요.", + "ladderByokTitle": "내 API 키 사용", + "ladderByokBody": "본인의 OpenAI나 Anthropic 키를 쓰면 돼요. 내 계정과 결제로 돌아가고, API 등급은 기본적으로 데이터를 학습에 쓰지 않아요.", + "ladderOauthTitle": "구독 계정으로 로그인", + "ladderOauthBody": "ChatGPT 계정을 연결해요. 토큰당 비용은 없지만 소비자 등급 약관은 내용을 학습에 쓸 수 있어서, 건강 데이터에는 기본값이 아니라 신중히 고를 선택이에요.", + "keylessLine": "AI 없이도 HealthLog는 충분히 쓸모 있어요. 타일, 차트, 추이, 내보내기 모두 그대로 작동하고 — AI는 그 위에 쉬운 말 해설을 더할 뿐이에요.", + "setupCta": "설정하기 (1분 정도)", + "sharedKeyNote": "이 인스턴스에는 이미 공용 AI 제공자가 있어서 지금 바로 인사이트를 쓸 수 있어요 — 따로 설정할 게 없어요." + } + }, + "gettingStarted": { + "title": "시작하기", + "subtitle": "HealthLog를 200% 활용하는 빠른 단계예요.", + "progress": "{total}개 중 {done}개 완료", + "dismissAll": "체크리스트 숨기기", + "dismissTooltip": "이 항목 숨기기", + "items": { + "profileTitle": "프로필 설정하기", + "profileDescription": "키, 생년월일, 성별을 넣으면 맞춤 목표가 열려요.", + "profileCta": "프로필 열기", + "measurementTitle": "첫 측정 기록하기", + "measurementDescription": "한 번만 기록해도 타일이 열리고, 추이는 5번째 측정값부터 그려져요.", + "measurementCta": "측정 추가", + "medicationTitle": "약 추가하기", + "medicationDescription": "복약 순응도를 기록하고 필요한 때에 알림을 받아요.", + "medicationCta": "약 추가", + "dataSourceTitle": "데이터 원본 연결하기(선택)", + "dataSourceDescription": "Withings, WHOOP, Oura, Polar, Nightscout, Fitbit, Apple Health에서 체중·혈압·수면 등을 자동으로 동기화해요.", + "dataSourceCta": "연결", + "notificationsTitle": "알림 설정하기", + "notificationsDescription": "HealthLog가 어떻게 알려줄지 고르세요 — Telegram, ntfy, 웹 푸시.", + "notificationsCta": "알림 열기", + "insightsTitle": "인사이트 켜기(선택)", + "insightsDescription": "로컬 모델이나 내 AI 키를 넣으면 일일 브리핑과 코치가 열려요.", + "insightsCta": "인사이트 켜기" + } + }, + "trendHints": { + "title": "측정값 5개부터 첫 추이", + "remainingOne": "1개만 더 기록하면 돼요.", + "remainingMany": "{count}개만 더 기록하면 돼요." + }, + "settings": { + "shell": { + "sectionsNav": "설정 섹션", + "groups": { + "account": "계정", + "yourData": "내 데이터", + "display": "화면", + "healthProfile": "건강 프로필", + "ai": "AI", + "connectivity": "연결", + "system": "시스템" + } + }, + "sharedRecord": { + "managedSettings": { + "saveError": "저장하지 못했어요. 다시 시도해 주세요.", + "validationError": "입력한 내용을 확인해 주세요.", + "profile": { + "title": "프로필", + "displayName": "표시 이름", + "height": "키 (cm)", + "dateOfBirth": "생년월일", + "gender": "성별", + "notSpecified": "지정 안 함", + "male": "남성", + "female": "여성", + "other": "기타", + "locale": "언어 및 지역", + "systemDefault": "시스템 기본값", + "timezone": "시간대", + "units": "단위", + "metric": "미터법", + "imperial": "야드파운드법", + "timeFormat": "시간 표시 형식", + "automatic": "자동", + "hour12": "12시간제", + "hour24": "24시간제", + "dateFormat": "날짜 형식", + "dateDmy": "일 / 월 / 년", + "dateMdy": "월 / 일 / 년", + "dateYmd": "년 / 월 / 일" + }, + "modules": { + "title": "모듈" + }, + "notifications": { + "title": "알림", + "moodReminder": "기분 알림 사용", + "reminderHour": "기분 알림 시각", + "lowStockRunwayDays": "재고 부족 기준 일수", + "reorderLeadDays": "재주문 준비 일수" + }, + "thresholds": { + "title": "목표", + "metric": "지표", + "minimum": "최솟값", + "maximum": "최댓값" + }, + "coach": { + "title": "코치", + "disable": "코치 끄기", + "tone": "말투", + "warm": "다정함", + "neutral": "중립", + "concise": "간결함", + "verbosity": "상세도", + "brief": "간략", + "default": "기본", + "detailed": "상세", + "defaultWindow": "기본 기간", + "last7days": "최근 7일", + "last30days": "최근 30일", + "last90days": "최근 90일", + "allTime": "전체 기간", + "excludedMetrics": "제외할 지표", + "dataClusters": "데이터 그룹", + "customClusters": "그룹 직접 선택하기", + "metrics": { + "bp": "혈압", + "weight": "체중", + "pulse": "맥박", + "mood": "기분", + "compliance": "복약 순응도", + "hrv": "심박변이도", + "sleep": "수면", + "restingHr": "안정 시 심박수", + "steps": "걸음 수", + "medications": "복약", + "anthropometrics": "신체 계측" + }, + "clusters": { + "cardio": "심혈관 건강", + "body": "체성분", + "activity": "활동", + "workouts": "운동", + "sleep": "수면", + "mood": "기분", + "glucose": "혈당", + "medication": "복약", + "mobility": "이동성", + "environment": "환경" + } + }, + "insights": { + "title": "인사이트", + "sections": "개요 섹션", + "tiles": "탐색 타일", + "order": "순서", + "unknownItem": "사용할 수 없는 인사이트", + "sectionLabels": { + "wellnessScores": "웰니스 점수", + "dailyBriefing": "오늘의 브리핑", + "vitals": "생체 신호", + "trends": "추이", + "periodReview": "생리 리뷰", + "cycleSummary": "생리 주기 요약", + "signals": "신호", + "rhythmEvents": "리듬 이벤트", + "healthStatus": "건강 상태", + "breathing": "호흡", + "labsChanges": "검사 결과 변화", + "ecg": "심전도" + } + } + }, + "unavailableTitle": "이 공유 기록에서는 사용할 수 없어요", + "unavailableDescription": "다른 사람의 기록을 보는 동안에는 이 설정 영역을 사용할 수 없어요.", + "integrationStatusDescription": "이 관리 기록의 연결 상태예요.", + "integrationStatusDetail": "연결 설정은 사용할 수 없어요." + }, + "sections": { + "account": { + "title": "계정", + "description": "프로필, 비밀번호, 패스키, 시작 설정 둘러보기.", + "subtitle": "프로필과 신원, 로그인 정보예요." + }, + "integrations": { + "title": "연동", + "description": "연결된 서비스, 전송 채널, 소스 우선순위를 한곳에서.", + "subtitle": "데이터 소스와 기기를 연결하고 관리해요.", + "connectionsHeading": "연결", + "channelsHeading": "전송 채널", + "channelsDescription": "알림과 경고가 전달되는 곳이에요.", + "matrixLink": "채널별 알림" + }, + "notifications": { + "title": "알림", + "description": "모든 알림을 한곳에서 — 무엇을 언제 알릴지, 어디로 보낼지.", + "subtitle": "받을 알림과 전달될 곳을 골라요.", + "remindersHeading": "알림", + "matrixLink": "채널별 알림", + "channelsLink": "전송 채널" + }, + "layout": { + "title": "화면 구성", + "description": "대시보드, 인사이트, 기록 모듈의 모습과 배치를 정해요.", + "backToHub": "화면 구성으로 돌아가기", + "dashboard": { + "title": "대시보드", + "description": "타일 배치와 순서." + }, + "insights": { + "title": "인사이트", + "description": "개요 섹션과 내비게이션 칩 순서." + }, + "subtitle": "대시보드, 인사이트, 기록 모듈의 모습을 원하는 대로 바꿔요.", + "medications": { + "title": "복약", + "description": "목록 보기와 수동 순서." + }, + "labs": { + "title": "검사", + "description": "보기, 정렬, 수동 순서." + }, + "illness": { + "title": "아픈 날 일지", + "description": "목록 보기와 에피소드 순서." + }, + "vorsorge": { + "title": "건강검진", + "description": "목록 보기와 알림 순서." + }, + "mood": { + "title": "기분 태그", + "description": "기분 선택기의 그룹, 태그, 보관된 태그." + } + }, + "dashboard": { + "subtitle": "대시보드의 타일을 배치해요." + }, + "thresholds": { + "title": "목표", + "description": "지표별로 건강한 값의 목표 범위를 정해요.", + "subtitle": "지표별 목표 범위를 직접 정해요." + }, + "sources": { + "title": "소스 우선순위", + "description": "같은 날 같은 지표를 여러 소스가 기록했을 때 어느 소스를 쓸지 골라요.", + "integrationsHint": "데이터 소스 연결과 해제는 연동 페이지에서 해요.", + "integrationsHintLink": "연동 열기", + "cardTitle": "지표별 소스 우선순위", + "help": "같은 날 여러 소스가 같은 값을 보내면 맨 위 소스를 대표값으로 써요.", + "resetDefaults": "기본값으로 되돌리기", + "resetDefaultsTitle": "소스 순서를 되돌릴까요?", + "resetDefaultsBody": "직접 정한 순서가 모든 지표와 기기 유형에서 한 번에 기본 순서로 돌아가요. 바로 저장되고 다시 되돌릴 수 없어요.", + "resetDefaultsConfirm": "순서 되돌리기", + "moveUp": "위로", + "moveDown": "아래로", + "saveSuccess": "소스 우선순위를 저장했어요", + "saveError": "소스 우선순위를 저장하지 못했어요.", + "resetSuccess": "소스 우선순위를 기본값으로 되돌렸어요", + "perMetricToggle": "지표별 재정의 ({count})", + "perMetricHelp": "기본 순서에서 직접 바꾼 지표예요. 다른 지표는 그대로 두고 하나만 기본값으로 되돌릴 때 이 목록을 쓰세요.", + "perMetricEmpty": "지표별 재정의가 없어요 — 모든 지표가 위의 전체 순서를 써요.", + "resetMetric": "되돌리기", + "deviceTypeToggle": "기기 유형 우선순위 ({count})", + "deviceTypeHelp": "한 소스 안에서 어느 기기를 쓸지예요. Apple Watch와 iPhone이 모두 걸음 수를 기록하면 순위가 높은 기기 유형의 측정값을 대표값으로 써요.", + "deviceTypeDefault": "기본 기기 유형 순서", + "resetDeviceTypes": "기기 유형 되돌리기", + "metrics": { + "steps": "걸음 수", + "activeEnergy": "활동 에너지", + "walkingRunningDistance": "걷기 + 달리기 거리", + "flightsClimbed": "오른 층수", + "sleep": "수면", + "weight": "체중", + "bloodPressure": "혈압", + "pulse": "맥박", + "bodyFat": "체지방", + "bodyTemperature": "체온", + "spo2": "산소포화도", + "hrv": "심박변이도", + "restingHeartRate": "안정 시 심박수", + "vo2Max": "최대산소섭취량", + "skinTemperature": "피부 온도", + "respiratoryRate": "호흡수", + "recovery": "회복", + "stress": "스트레스", + "sleepDebt": "수면 부채" + }, + "sourceLabels": { + "WITHINGS": "Withings", + "APPLE_HEALTH": "Apple Health", + "MANUAL": "직접 입력", + "IMPORT": "불러오기", + "WHOOP": "WHOOP", + "FITBIT": "Fitbit", + "COMPUTED": "계산값" + }, + "deviceLabels": { + "watch": "워치", + "band": "밴드", + "ring": "링", + "phone": "휴대폰", + "scale": "체중계", + "other": "기타", + "unknown": "알 수 없음" + }, + "subtitle": "같은 지표를 두 소스가 보낼 때 어느 쪽을 쓸지 정해요.", + "nutrients": { + "title": "동기화된 영양 섭취", + "description": "최근 {days}일간 Apple Health에서 동기화한 하루 합계예요.", + "empty": "아직 동기화된 영양 데이터가 없어요.", + "latest": "{date} · {amount} {unit}", + "daysWithData": "{days}일 중 {count}일", + "moduleHint": "동기화는 영양 섭취 모듈에서 조절해요.", + "moduleHintLink": "모듈 관리", + "detail": "읽기 전용 — 숫자는 원본 앱이 관리해요." + }, + "helpDetail": "나머지는 감사 기록용으로 그대로 보관해요." + }, + "ai": { + "title": "AI 제공자", + "description": "인사이트에 쓸 제공자, 모델, API 키 — 그리고 '나에 대해' 컨텍스트.", + "subtitle": "AI 제공자를 고르고 직접 키를 등록해요." + }, + "api": { + "title": "API와 토큰", + "description": "내 스크립트와 앱에서 쓸 베어러 토큰 — 어디서든 측정값이나 약 복용을 기록해요.", + "subtitle": "API 엔드포인트와 액세스 토큰을 관리해요." + }, + "advanced": { + "title": "고급", + "description": "마지막 출구: 모든 건강 기록을 영구히 지우거나 계정을 완전히 닫아요.", + "subtitle": "데이터 초기화와 계정 삭제." + }, + "export": { + "title": "내보내기와 불러오기", + "description": "데이터를 CSV나 전체 JSON 백업으로 내려받고, Apple Health·JSON·CSV에서 불러와요. 진료용 리포트는 이제 건강 기록에 있어요.", + "otherOptionsHeading": "다른 내보내기 옵션", + "downloadFailed": "내보내기 파일을 내려받지 못했어요. 다시 시도해 주세요.", + "actions": { + "download": "내려받기" + }, + "filters": { + "since": "시작", + "until": "끝" + }, + "cards": { + "measurementsCsv": { + "title": "측정", + "description": "체중, 혈압, 맥박, 혈당을 비롯한 모든 측정값을 쉼표로 구분한 값으로." + }, + "medicationsCsv": { + "title": "복약", + "description": "복용량, 일정, 그리고 원하면 전체 복용 기록까지 담은 약 목록.", + "includeIntake": "복용 기록 포함" + }, + "moodCsv": { + "title": "기분", + "description": "점수, 태그, 시각이 담긴 하루 기분 기록." + }, + "fullBackup": { + "title": "전체 백업", + "description": "모든 기록을 JSON 파일 하나에.", + "scopeNote": "문서 메타데이터와 AI가 만든 요약이 있으면 일반 텍스트로 들어가요. 원본 문서 파일과 운동 GPS·샘플 데이터는 빠지니 보관함이나 운동 내보내기에서 따로 내려받아 주세요.", + "encryptToggle": "암호문구로 암호화", + "passphrasePlaceholder": "암호문구 (12자 이상)", + "passphraseHelp": "Argon2id로 보관 파일 키를 만들고 AES-256-GCM으로 봉인해요. 파일은 .hlx 확장자로 저장해 주세요.", + "noRecoveryWarning": "복구 수단이 없어요 — 이 암호문구를 잃어버리면 보관 파일을 열 수 없어요. 안전한 곳에 보관해 주세요.", + "stepUpNeeded": "2단계 인증을 확인한 뒤 다시 시도해 주세요.", + "detail": "주간 자동 백업과 같은 형식이에요. 아직 담기지 않는 기록 유형이 있고, 파일의 매니페스트에 무엇이 들었는지 적혀 있어요.", + "contents": "측정, 복약, 기분, 생리 주기, 검사, 아픈 날 에피소드, 알레르기, 가족력, 영양, 운동, 건강 프로필, 사용자 지표를 JSON 파일 하나에." + } + }, + "import": { + "heading": "불러오기", + "description": "Apple Health 내보내기 파일, JSON, CSV에서 HealthLog로 데이터를 가져와요. 이미 있는 행은 건너뛰니 다시 실행해도 안전해요.", + "charCount": "{used} / {max}자", + "appleHealth": { + "title": "Apple Health", + "description": "iPhone 건강 앱에서 만든 export.zip을 올려 주세요 (프로필 → 모든 건강 데이터 내보내기).", + "dropLabel": "여기에 export.zip을 놓으세요", + "dropHint": "또는 눌러서 파일 선택", + "fileInputLabel": "Apple Health export.zip 파일 선택", + "choose": "파일 선택", + "idempotencyNote": "같은 보관 파일을 다시 올리면 병합돼요 — 중복이 생기지 않아요. 큰 파일은 백그라운드에서 처리해요.", + "uploading": "올리는 중…", + "uploadFailed": "올리지 못했어요. 다시 시도해 주세요.", + "rateLimited": "업로드가 너무 잦아요. 잠시 뒤에 다시 시도해 주세요.", + "tooLarge": "파일이 너무 커요 (최대 1.5 GB).", + "failed": "불러오지 못했어요.", + "failureInterrupted": "불러오기가 끝나기 전에 중단됐어요. 보통 서버 재시작이나 메모리 부족 때문이에요. 데이터는 안전하니 내보내기 파일을 다시 올려 주세요.", + "failureInsufficientMemory": "이 크기의 보관 파일을 처리하기엔 서버 메모리 한도가 낮아요. 서버 운영자에게 Node.js 힙 한도(NODE_OPTIONS=--max-old-space-size)를 올려 달라고 한 뒤 내보내기 파일을 다시 올려 주세요.", + "rowsImported": "지금까지 {count}개 행을 불러왔어요", + "doneSummary": "완료 — {read}개 중 {imported}개 기록을 불러왔고, 진료 기록 {skipped}개는 건너뛰었어요.", + "doneNothing": "그 보관 파일에서 불러온 게 없어요: {read}개 중 0개만 들어왔어요.", + "ecgResult": "심전도: 발견 {discovered}개, 불러옴 {imported}개, 갱신 {updated}개, 건너뜀 {skipped}개, 실패 {failed}개. 심전도가 실패해도 export.xml에서 불러온 지원 기록은 그대로 남아요.", + "estimateWarning": "{count}일치 누적 합계는 가장 큰 내보내기 소스에서 추정했어요. Apple Health 자체 통계가 여전히 기준이에요.", + "phase": { + "queued": "대기 중…", + "unpacking": "보관 파일 푸는 중…", + "parsing": "내보내기 파일 읽는 중…", + "upserting": "기록 불러오는 중…" + }, + "skipUnsupported": "{count}개 샘플을 건너뛰었어요 — HealthLog가 불러오지 않는 데이터 유형이에요.", + "skipRefused": "{count}개 샘플을 쓰지 못했어요 (읽을 수 없거나, 범위를 벗어났거나, 저장에 실패했어요).", + "skipBreakdown": "자세히 보기 ({count}개 유형)", + "skipBreakdownMore": "+ {count}개 유형 더", + "cycleSummary": "생리 주기 샘플 {samples}개를 {days}일치 기록으로 합쳤어요.", + "cycleModuleOff": "생리 주기 샘플 {count}개를 건너뛰었어요 — 이 계정은 주기 기록이 꺼져 있어요.", + "cycleDaysFailed": "생리 주기 {count}일치를 쓰지 못했어요: {reason}" + }, + "json": { + "title": "JSON 파일", + "description": "측정과 기분 기록이 담긴 JSON을 붙여 넣거나 .json 파일을 올려 주세요.", + "pasteLabel": "JSON 붙여 넣기", + "fileInputLabel": "불러올 JSON 파일 선택", + "schemaHint": "배열 두 개: measurements와 moodEntries.", + "docsLink": "스키마와 예시 보기.", + "uploadFile": "파일 올리기", + "downloadExample": "예시 내려받기", + "import": "불러오기", + "invalidJson": "올바른 JSON이 아니에요. 문법을 확인하고 다시 시도해 주세요.", + "invalidPayload": "데이터가 불러오기 스키마와 맞지 않아요.", + "rateLimited": "이번 시간에 너무 많이 불러왔어요 (최대 5회). 나중에 다시 시도해 주세요.", + "failed": "불러오지 못했어요. 다시 시도해 주세요.", + "readFailed": "파일을 읽지 못했어요.", + "resultSummary": "측정 {measurements}개와 기분 기록 {moods}개를 불러왔고, {skipped}개는 건너뛰었어요.", + "resultSuccess": "측정 {measurements}개와 기분 기록 {moods}개를 불러왔어요", + "resultNothing": "불러온 게 없어요. {skipped}개 기록을 모두 건너뛰었어요.", + "resultEmpty": "그 파일에는 측정도 기분 기록도 없었어요." + }, + "csv": { + "title": "CSV 파일", + "description": "스프레드시트나 측정기 내보내기 파일에서 측정값을 불러와요.", + "pasteLabel": "CSV 붙여 넣기", + "fileInputLabel": "불러올 CSV 파일 선택", + "schemaHint": "열: type, value, unit, measuredAt (시간대 오프셋 포함), 그리고 선택 항목인 glucoseContext, notes, externalId.", + "docsLink": "열 스키마와 예시 보기.", + "uploadFile": "파일 올리기", + "downloadExample": "예시 내려받기", + "preview": "미리 보기", + "import": "불러오기", + "empty": "불러오기 전에 CSV 행을 하나 이상 넣어 주세요.", + "rateLimited": "이번 시간에 너무 많이 불러왔어요 (최대 5회). 나중에 다시 시도해 주세요.", + "tooLarge": "파일이 너무 커요 (최대 16 MB).", + "invalidPayload": "CSV를 해석하지 못했어요. 머리글 행을 확인하고 다시 시도해 주세요.", + "failed": "불러오지 못했어요. 다시 시도해 주세요.", + "readFailed": "파일을 읽지 못했어요.", + "previewSummary": "미리 보기: {inserted}개를 불러오고 {skipped}개는 건너뛰어요.", + "resultSummary": "새로 {inserted}개를 불러오고 {updated}개를 갱신했으며, {skipped}개는 건너뛰었어요.", + "rowError": "{line}번째 줄: {reason}", + "resultSuccess": "새로 {inserted}개를 불러오고 {updated}개를 갱신했어요", + "resultNothing": "불러온 게 없어요. {skipped}개 행을 모두 건너뛰었어요.", + "resultEmpty": "그 파일에는 머리글만 있고 데이터 행이 없어요.", + "previewSuccess": "미리 보기: {inserted}개를 불러와요", + "previewNothing": "미리 보기: 불러올 게 없어요. {skipped}개 행을 모두 건너뛰어요.", + "skipGroupOne": "{count}개 행을 건너뛰었어요: {reason}", + "skipGroupFew": "{count}개 행을 건너뛰었어요: {reason}", + "skipGroupOther": "{count}개 행을 건너뛰었어요: {reason}", + "skipDetails": "건너뛴 행 모두 보기", + "reasonUnknownType": "알 수 없는 측정 유형", + "reasonInvalidValue": "값이 숫자가 아님", + "reasonValueOutOfRange": "값이 타당한 범위를 벗어남", + "reasonUnknownUnit": "이 유형에 맞지 않는 단위", + "reasonMissingTimezoneOffset": "시각에 시간대 오프셋이 없음", + "reasonInvalidTimestamp": "시각을 읽을 수 없음", + "reasonImplausibleTimestamp": "시각이 미래이거나 1900년 이전", + "reasonUnexpectedGlucoseContext": "혈당이 아닌 행에 혈당 상황이 설정됨", + "reasonInvalidGlucoseContext": "허용되지 않은 혈당 상황 값", + "reasonNotesTooLong": "메모가 너무 긺", + "reasonExternalIdTooLong": "외부 ID가 너무 긺", + "reasonUnstableExternalId": "외부 ID가 내보내기마다 달라짐", + "reasonDuplicate": "이미 불러온 항목", + "reasonUnknown": "건너뜀", + "detail": "측정값 하나당 한 행." + }, + "doseHistory": { + "title": "약 복용 이력", + "description": "다른 복약 앱에서 내보낸 복용 이력을 그대로 읽어요.", + "pasteLabel": "내보낸 내용 붙여 넣기", + "fileInputLabel": "복용 이력 내보내기 파일 선택", + "uploadFile": "파일 선택", + "preview": "미리 보기", + "import": "불러오기", + "empty": "먼저 내보낸 내용을 붙여 넣거나 파일을 선택해 주세요.", + "readFailed": "그 파일을 읽지 못했어요.", + "rateLimited": "한 시간에 최대 5회까지 불러올 수 있어요. 나중에 다시 시도해 주세요.", + "tooLarge": "파일이 16 MB 한도를 넘었어요.", + "inProgress": "복용 이력 불러오기가 이미 진행 중이에요. 끝날 때까지 기다려 주세요.", + "workerUnavailable": "백그라운드 작업자를 쓸 수 없어서 아무것도 불러오지 못했어요.", + "failed": "불러오지 못했어요. 아무것도 저장되지 않았어요.", + "columnsHeading": "각 열의 역할", + "jsonOnly": "JSON 전용", + "previewLine": "읽은 행: {rows} · 불러올 행: {queued} · 거부된 행: {refused}", + "submittedLine": "읽은 행: {rows} · 불러오기 대기: {queued}", + "unmatched": "기록에서 일치하는 약이 없어요: {names}", + "ambiguous": "약이 둘 이상 일치해요: {names}", + "mirrored": "다른 앱에서 미러링한 약이라 복용을 추가할 수 없어요: {names}", + "unknownColumns": "규칙이 없어 읽지 않은 열: {names}", + "codingsNotRead": "읽지 않은 Codings 값이 있는 행: {count}", + "archivedRowsPresent": "보관된 약에 속한다고 표시된 행: {count}. 이 표시는 행을 건너뛰지도, 약을 다시 활성화하지도 않아요. 무엇이 추가됐는지는 최종 결과에 나와요.", + "verdict": { + "honoured": "반영됨", + "reported": "보고됨", + "ignored": "읽지 않음" + }, + "column": { + "date": "실제로 복용한 시각이에요. UTC 오프셋은 적힌 그대로 읽어요.", + "scheduledDate": "그 복용이 속한 일정 칸이며, 실제로 복용한 시각과는 따로 둬요. 비어 있으면 어느 칸에도 속하지 않아요.", + "medication": "이미 기록에 있는 약과 대조해요. 파일에서 새로 만들지는 않아요.", + "nickname": "첫 이름이 아무것도 맞지 않을 때 두 번째 이름으로 시도해요.", + "dosage": "예정된 양과 다를 때만 그 복용에 기록해요.", + "scheduledDosage": "실제 양과 비교해 그 복용이 어긋났는지 판단해요. 따로 저장하지는 않아요.", + "unit": "양과 함께, 파일에 적힌 철자 그대로 저장해요. 'count'는 설정된 용량의 배수를 뜻해요.", + "status": "'Taken'은 복용으로, 'Skipped'는 일부러 거른 것으로 기록해요. 다른 값은 아무 결정도 남기지 않고 그 행은 거부돼요.", + "archived": "세어서 알려 줘요. 이미 있었던 복용은 나중에 약을 보관해도 사라지지 않아요.", + "codings": "CSV에 이 열의 인코딩이 문서로 정해져 있지 않아서, 추측하지 않고 그대로 둬요. 값이 있는 칸은 세어 둬요.", + "form": "복용이 아니라 약을 설명하는 열이에요.", + "start": "이 복용 시각이 아니라 약을 시작한 날짜예요.", + "end": "이 복용 시각이 아니라 약을 그만둔 날짜예요." + }, + "fatal": { + "emptyFile": "그 파일에는 행이 없어요.", + "missingColumns": "불러오기에 필요한 열이 빠졌어요: Date, Medication, Status.", + "unreadableJson": "올바른 JSON 파일이 아니에요.", + "jsonNotAnArray": "그 JSON에는 복용 목록이 없어요.", + "jsonNoIntakeTime": "JSON 내보내기에는 복용 예정 시각만 있고 실제로 복용한 시각이 없어요. 예정 시각을 실제 복용처럼 저장하지는 않으니, 둘 다 담긴 CSV로 내보내 주세요.", + "tooManyRows": "한 번에 불러올 수 있는 행 수를 넘었어요. 파일을 나눠서 불러와 주세요." + }, + "detail": "파일 하나에 담긴 모든 약을 함께 처리해요." + } + }, + "subtitle": "데이터를 내보내거나 백업에서 불러와요." + }, + "about": { + "title": "앱 정보", + "description": "버전, 라이선스, 링크.", + "subtitle": "버전, 라이선스, 프로젝트 링크." + }, + "sharing": { + "title": "공유", + "description": "임상의와 건강 기록을 나눌 읽기 전용 링크를 만들어요.", + "subtitle": "데이터를 읽기 전용으로 공유해요." + }, + "insights": { + "subtitle": "개요 섹션과 칩 순서를 배치해요." + }, + "medications": { + "subtitle": "약 목록 보기와 순서를 관리해요." + }, + "mood": { + "subtitle": "기분 태그와 그룹, 선택기 순서를 정리해요." + }, + "labs": { + "subtitle": "보기, 정렬 순서, 바이오마커를 관리해요." + }, + "illness": { + "subtitle": "에피소드의 보기와 순서를 관리해요." + }, + "vorsorge": { + "subtitle": "보기, 순서, 개별 알림을 관리해요." + }, + "modules": { + "title": "모듈", + "saved": "저장됨", + "error": "저장하지 못했어요. 다시 시도해 주세요.", + "toggleable": { + "title": "기록할 항목", + "description": "끈 모듈은 내비게이션, 타일, 인사이트에서 빠져요 — 데이터는 그대로 남아요." + }, + "manageIn": "{section}에서 관리", + "operatorDisabled": "서버 전체에서 꺼짐", + "subtitle": "HealthLog가 앱 전체에서 보여 줄 기록 영역을 골라요." + }, + "gesundheitsakte": { + "title": "건강 기록", + "description": "전체 건강 기록을 PDF, FHIR R4, 또는 하나로 묶은 ZIP으로 내보내요.", + "subtitle": "임상의에게 바로 건넬 수 있는 완전한 건강 기록을 내보내요." + }, + "coach": { + "title": "코치", + "description": "코치 켜기, 말투와 범위, 저장된 기억.", + "subtitle": "코치의 말투, 먼저 건네는 알림, 기억을 조정해요." + }, + "security": { + "title": "보안", + "subtitle": "2단계 인증, 복구 코드, 패스키." + }, + "privacy": { + "title": "데이터와 개인정보", + "subtitle": "데이터, 보관 기간, 암호화, 계정 보안을 한곳에서.", + "description": "내보내기, 삭제, 보관 기간, 암호화 상태, 세션, 보안 활동." + }, + "mcp": { + "title": "MCP 커넥터", + "subtitle": "외부 어시스턴트를 내 건강 데이터에 연결해요." + }, + "anamnesis": { + "title": "병력", + "subtitle": "알레르기와 가족력을 관리해요.", + "description": "구조화된 알레르기·가족력 기록." + }, + "environment": { + "title": "위치 및 날씨", + "subtitle": "날씨, 일조 시간, 위치 정보예요.", + "disabledHint": "환경 모듈을 켜면 기본 위치를 설정하고 매일 날씨와 일조 시간을 가져올 수 있어요.", + "searchPlaceholder": "도시 검색", + "search": "검색", + "searchError": "그 장소를 검색하지 못했어요.", + "use": "사용", + "select": "선택", + "saveError": "변경 내용을 저장하지 못했어요.", + "homeSaved": "기본 위치를 저장했어요", + "travelAdded": "위치 기간을 추가했어요.", + "travelRemoved": "위치 기간을 삭제했어요.", + "backfillQueued": "과거 데이터 채우기를 예약했어요.", + "backfillError": "과거 데이터 채우기를 시작하지 못했어요.", + "startDate": "시작일", + "endDate": "종료일", + "storedDays": "날씨 {count}일치를 저장했어요.", + "home": { + "title": "기본 위치", + "description": "도시 단위의 대략적인 위치예요 — 정밀한 이동 경로는 저장하지 않아요. 설정한 날짜부터, 위치 기간이 적용되지 않는 날에는 이곳의 날씨와 일조 시간을 가져와요.", + "current": "현재: {label}", + "none": "아직 설정한 기본 위치가 없어요.", + "since": "{date}부터 날씨를 기록해요. 그 이전 날짜는 위치 기간을 추가하기 전까지 비어 있어요." + }, + "travel": { + "title": "위치 기간", + "description": "장소와 기간을 추가하세요 — 여행일 수도, 지난 기록의 한 구간일 수도 있어요. 해당 기간에는 기본 위치 대신 이 위치가 쓰여서 실제로 있던 곳의 날씨가 반영돼요. 지난 기록을 바로잡는 방법이에요.", + "none": "아직 위치 기간이 없어요.", + "add": "{label} 추가", + "remove": "위치 기간 삭제", + "removeConfirmTitle": "이 여행을 삭제할까요?", + "removeConfirmBody": "그 기간에 측정한 값은 기록에 그대로 남고, 여행 항목만 사라져요." + }, + "backfill": { + "title": "지난 날짜 채우기", + "description": "지난 기간의 날씨를 가져와 저장하면 예전 날짜도 상관관계 분석에 들어가요. 시작일은 기본 위치의 적용 시작일로 잡히고, 더 이전까지 가려면 그 기간의 위치 기간을 먼저 추가하세요.", + "run": "채우기" + }, + "noResults": "일치하는 장소가 없어요.", + "fetchFailedOne": "마지막 백그라운드 날씨 업데이트가 실패했어요 ({when}).", + "fetchFailedFew": "최근 백그라운드 날씨 업데이트 {count}번이 실패했어요 — 마지막은 {when}.", + "fetchFailedOther": "최근 백그라운드 날씨 업데이트 {count}번이 실패했어요 — 마지막은 {when}." + }, + "score": { + "title": "건강 점수", + "subtitle": "어떤 항목을 점수에 반영할지 고르세요.", + "card": { + "title": "점수에 반영되는 항목", + "description": "여기서 따로 끄지 않으면, 기록하는 모든 항목이 점수에 반영돼요." + }, + "threeAxes": "기록하는 것, 보여주는 것, 점수에 반영하는 것은 서로 다른 세 가지예요. 이 페이지는 마지막 하나만 바꿔요. 여기서 끈 항목도 계속 기록되고 HealthLog의 다른 곳에도 그대로 나오며, 점수만 움직이지 않아요.", + "axis": { + "countsFor": "{pillar} 점수에 반영" + }, + "state": { + "counting": "점수에 반영 중", + "waiting": "선택됨, 데이터 기다리는 중", + "crisis": "점수 대신 안전 안내가 표시돼요. 선택은 그대로 괜찮아요.", + "readFailed": "지난번 계산에서 이 항목을 읽지 못했어요." + }, + "presets": { + "label": "시작 기준", + "all": "모든 항목", + "current": "저장된 선택" + }, + "save": "저장", + "saved": "저장됨", + "error": "저장하지 못했어요. 다시 시도해 주세요.", + "loadFailed": "선택을 불러오지 못했어요.", + "eligibilityFailed": "어떤 항목에 지금 데이터가 있는지 확인하지 못했어요. 아래 스위치는 그대로 쓸 수 있어요.", + "omitted": "{count}개 항목은 기록이 꺼져 있어서 목록에 없어요. 모듈을 다시 켜면 해당 항목도 돌아와요.", + "omittedLink": "모듈 열기", + "refusal": { + "physiological": "건강 점수에는 신체 측정값이 최소 하나 필요해요. 다른 항목을 어떻게 고르든 혈압, 혈당, 수면, 체형, 콜레스테롤 중 하나는 선택에 남겨 두세요.", + "domains": "건강 점수에는 서로 다른 건강 영역이 최소 세 개 필요해요. 혈압, 혈당, 콜레스테롤은 모두 같은 영역이라, 이 셋만 고르면 하나로 쳐요." + }, + "notice": { + "upgrade": { + "title": "무엇을 반영할지 직접 고를 수 있어요", + "body": "예전에는 켜 둔 모듈에 따라 건강 점수 반영 항목이 정해졌어요. 이제는 별도로 고르는 선택이고, 이 페이지에서 정해요. 이 기능이 생겼다고 점수가 바뀌지는 않았어요. 전과 똑같은 항목을 반영하고 있어요." + }, + "changed": { + "title": "반영 항목을 바꿨어요", + "body": "{date}부터는 다른 항목 조합으로 점수를 계산해요. 그 전 날짜는 예전 조합으로 계산해서 서로 비교하지 않고, 비교 구간에서 변경 시점이 빠질 때까지 주간 변화는 멈춰 있어요. 각 항목의 자체 기록은 끊기지 않고 이어져요.", + "bodyUndated": "예전과 다른 항목 조합으로 점수를 계산해요. 변경 전 날짜는 예전 조합으로 계산해서 서로 비교하지 않고, 비교 구간에서 변경 시점이 빠질 때까지 주간 변화는 멈춰 있어요. 각 항목의 자체 기록은 끊기지 않고 이어져요." + }, + "dismiss": "확인", + "dismissError": "저장하지 못했어요. 다시 시도해 주세요." + } + }, + "access": { + "title": "공유 접근", + "subtitle": "내 건강 기록을 누가 열 수 있고, 내가 누구의 기록을 열 수 있는지예요." + } + }, + "profile": "프로필", + "language": "언어", + "languageDescription": "앱에 표시할 언어를 선택하세요.", + "timeFormat": { + "title": "시간 형식", + "description": "시간을 24시간제로 표시할지 12시간제(AM/PM)로 표시할지 정해요.", + "auto": "자동 (언어 기준)", + "h24": "24시간제", + "h12": "12시간제 (AM/PM)", + "saved": "시간 형식을 저장했어요", + "saveError": "시간 형식을 저장하지 못했어요." + }, + "dateFormat": { + "title": "날짜 형식", + "description": "날짜를 표시할 때 일, 월, 연도의 순서를 정해요.", + "auto": "자동 (언어 기준)", + "dmy": "DD.MM.YYYY", + "mdy": "MM/DD/YYYY", + "ymd": "YYYY-MM-DD", + "saved": "날짜 형식을 저장했어요", + "saveError": "날짜 형식을 저장하지 못했어요." + }, + "username": "사용자 이름", + "height": "키 (cm)", + "heightFtIn": "키 (ft, in)", + "dateOfBirth": "생년월일", + "dateOfBirthHint": "혈압 목표를 자동으로 계산할 때 쓰여요.", + "gender": "성별", + "genderNone": "지정 안 함", + "genderMale": "남성", + "genderFemale": "여성", + "genderHint": "성별에 따른 목표값에 쓰여요.", + "timezone": "시간대", + "timezoneHint": "차트 축 라벨, 알림 시각, 내보내기 시각, 코치 맥락에 쓰여요. 저장된 데이터는 바뀌지 않아요.", + "timezoneInvalid": "올바른 IANA 시간대가 아니에요.", + "profileSaved": "프로필을 저장했어요", + "profilePartiallySaved": "프로필을 저장했지만 {field} 항목은 업데이트하지 못했어요.", + "profileNothingSaved": "아무것도 저장하지 못했어요. {field} 항목을 고친 뒤 다시 시도해 주세요.", + "avatar": { + "title": "프로필 사진", + "description": "직접 운영하는 서버에 저장되는 사진을 올려요. 외부 아바타 서비스 대신 앱 전체에 표시돼요.", + "upload": "사진 올리기", + "replace": "사진 바꾸기", + "remove": "삭제", + "removeTitle": "프로필 사진을 삭제할까요?", + "removeBody": "이미지가 서버에서 삭제되고 그 자리에 이니셜이 표시돼요. 다시 올리려면 원본 파일이 필요해요.", + "removeConfirm": "사진 삭제", + "hint": "JPEG, PNG, WebP. 최대 2MB, 2048×2048픽셀.", + "uploaded": "프로필 사진을 업데이트했어요", + "removed": "프로필 사진을 삭제했어요", + "invalidType": "지원하지 않는 이미지 형식이에요. JPEG, PNG, WebP를 써 주세요.", + "tooLarge": "이미지가 2MB 제한을 넘었어요.", + "error": "프로필 사진을 업데이트하지 못했어요. 다시 시도해 주세요." + }, + "changePassword": "비밀번호 변경", + "changePasswordDescription": "지금 쓰는 비밀번호를 새 비밀번호로 바꿔요.", + "passwordReset": "비밀번호 재설정", + "currentPassword": "현재 비밀번호", + "newPassword": "새 비밀번호", + "confirmNewPassword": "새 비밀번호 확인", + "passwordMismatch": "새 비밀번호가 서로 달라요", + "passwordUpdated": "비밀번호를 변경했어요", + "passkeys": "패스키", + "passkeysDescription": "패스키로 비밀번호 없이 안전하게 로그인할 수 있어요.", + "passkeyReauth": { + "title": "본인 확인", + "description": "새 패스키를 추가하기 전에 기존 로그인 수단으로 확인해 주세요.", + "methodLabel": "확인 방법", + "methods": { + "password": "비밀번호", + "totp": "인증 앱 코드", + "passkey": "패스키", + "webauthn": "보안 키" + }, + "currentPassword": "현재 비밀번호", + "authenticatorCode": "인증 앱 코드", + "authenticatorPlaceholder": "6자리 인증 앱 코드", + "verifyAndAdd": "확인 후 추가" + }, + "passkeyName": "이름", + "noPasskeys": "등록된 패스키가 없어요.", + "addPasskey": "패스키 추가", + "passkeyRegistrationFailed": "패스키 등록에 실패했어요", + "passkeyRegistrationCancelled": "패스키 등록을 취소했어요", + "passkeyNotSupported": "이 브라우저나 기기는 아직 패스키를 지원하지 않아요. 다른 브라우저나 기기로 시도해 주세요.", + "passkeyAlreadyRegistered": "이 기기는 이미 계정의 패스키로 등록돼 있어요.", + "passkeySecurityBlocked": "브라우저가 보안상의 이유로 패스키 요청을 막았어요. 신뢰할 수 있는 HTTPS 주소인지 확인하고 다시 시도해 주세요.", + "passkeyTimeout": "패스키 확인 시간이 지났어요. 다시 시도해 주세요.", + "passkeyUnknownError": "패스키 등록에 실패했어요: {message}", + "deletePasskey": "패스키를 삭제할까요?", + "deletePasskeyDescription": "패스키가 완전히 삭제돼요.", + "singleDevice": "단일 기기", + "multiDevice": "다중 기기", + "backedUp": "백업됨", + "telegram": "Telegram", + "telegramDescription": "봇으로 보내는 복약 알림이에요.", + "botToken": "봇 토큰", + "chatId": "채팅 ID", + "enableNotifications": "알림 켜기", + "telegramStep1": "1. Telegram에서 @BotFather로 봇을 만들고 토큰을 복사하세요.", + "telegramStep2": "2. 봇에게 /start를 보내 채팅을 활성화하세요.", + "telegramStep3": "3. @userinfobot이나 Bot API로 채팅 ID를 확인하세요.", + "ntfy": "ntfy", + "ntfyDescription": "ntfy(셀프 호스팅 또는 ntfy.sh)로 보내는 알림이에요.", + "ntfyEnable": "ntfy 켜기", + "ntfyServer": "서버 URL", + "ntfyTopic": "토픽", + "ntfyAuthToken": "인증 토큰 (선택)", + "ntfyAuthTokenHint": "접근 제어가 걸린 비공개 토픽에만 필요해요.", + "webhook": "웹훅", + "webhookDescription": "Gotify, Discord, Slack, Matrix, Home Assistant 등 어떤 URL로든 알림을 POST해요.", + "webhookEnable": "웹훅 켜기", + "webhookUrl": "웹훅 URL", + "webhookUrlHint": "POST를 보낼 전체 URL이에요. 외부에서 접근할 수 있어야 해요.", + "webhookHeaderName": "헤더 이름 (선택)", + "webhookHeaderValue": "헤더 값 (선택)", + "email": "이메일", + "emailDescription": "경고와 알림을 이메일로 받아요.", + "emailEnable": "이메일 켜기", + "emailRecipient": "받는 주소", + "webPush": "브라우저 푸시", + "webPushDescription": "HealthLog를 열어 두지 않아도 브라우저로 바로 알림을 받아요.", + "webPushNotSupported": "이 브라우저는 푸시 알림을 지원하지 않아요.", + "webPushDenied": "푸시 알림이 차단돼 있어요. 브라우저 설정에서 허용해 주세요.", + "webPushNotConfigured": "서버에 Web Push가 설정돼 있지 않아요.", + "webPushSubscribe": "푸시 켜기", + "webPushUnsubscribe": "푸시 끄기", + "webPushSubscribed": "푸시 알림을 켰어요!", + "webPushUnsubscribed": "푸시 알림을 껐어요.", + "webPushSubscribeFailed": "켜지 못했어요", + "kiInsightsDescription": "선택 사항: OpenAI 키를 저장하면 인사이트에서 매일 자동으로 분석해요.", + "codexConnected": "ChatGPT를 연결했어요! 이제 인사이트를 쓸 수 있어요.", + "codexDisconnected": "ChatGPT 연결을 해제했어요.", + "codexConnectionFailed": "ChatGPT 연결에 실패했어요. 다시 시도해 주세요.", + "rawData": "원본 데이터 보내기", + "rawDataOnDescription": "집계 지표와 함께 최근 30일의 익명화된 개별 값이 시간 맥락과 함께 전송돼요 — 이름, 이메일, 계정 식별자는 절대 보내지 않아요.", + "rawDataOffDescription": "요약 지표만 전송돼요(예: 평균, 추이, 최소/최대) — 개별 값이나 정확한 시간 맥락은 보내지 않아요.", + "rawDataWarning": "원본 모드가 켜져 있어요. 분석 정확도를 높이려고 제공자에게 최근 30일의 익명화된 값이 추가로 전달돼요.", + "regenerateInsights": "리포트 다시 생성", + "regenerateSuccess": "리포트를 다시 생성했어요", + "regenerateRateLimit": "잠시만 기다려 주세요 — 시간당 분석 한도에 도달했어요.", + "lastGeneratedAt": "마지막 생성", + "ai": { + "chatgptConnectedBadge": "ChatGPT 연결됨", + "adminAiActiveBadge": "관리자 제공자 사용 중", + "connectionExpiredBadge": "연결 만료됨", + "connectedSince": "{when}부터 연결되어 있어요.", + "deviceCodeHeading": "chatgpt.com에서 연결을 마무리하세요", + "deviceCodeStep1": "아무 기기에서나 이 링크를 여세요:", + "deviceCodeStep2": "이 일회용 코드를 입력하세요:", + "deviceCodeStep3": "연결을 승인하세요 — 이 페이지는 자동으로 갱신돼요.", + "deviceCodeCopy": "복사", + "deviceCodeWaiting": "승인을 기다리는 중…", + "deviceCodeCancel": "취소", + "oauthNotConfigured": "이 인스턴스에는 ChatGPT OAuth가 설정돼 있지 않아요 — 아래에서 직접 발급한 API 키를 쓰세요.", + "modelLabel": "모델", + "modelOptionDefault": "— 기본값 —", + "modelOptionCustom": "직접 입력…", + "customModelLabel": "직접 입력한 모델 이름", + "anthropicKeyLabel": "Anthropic API 키", + "baseUrlLabel": "기본 URL", + "localKeyLabel": "API 키 (선택)", + "localDescription": "OpenAI 호환 엔드포인트라면 무엇이든 쓸 수 있어요 — 로컬 모델(Ollama, LM Studio)은 물론 LiteLLM, OpenRouter, vLLM 같은 게이트웨이도요. 기본 URL은 /v1로 끝나고, API 키는 선택이에요.", + "savedPreview": "(저장됨: {preview})", + "savedShort": "(저장됨)", + "saveCta": "저장", + "saved": "저장됨", + "errorGeneric": "오류", + "providerChain": { + "types": { + "codex": "ChatGPT (Codex)", + "openai": "OpenAI (내 키)", + "anthropic": "Anthropic (Claude)", + "local": "로컬 모델", + "openai-compatible": "OpenAI 호환 게이트웨이", + "admin-openai": "관리자 OpenAI" + }, + "title": "대체 순서", + "description": "기본 제공자가 실패하면 HealthLog가 순서대로 다음 제공자를 시도해요. 행을 끌어 순서를 바꾸고, 스위치를 끄면 제공자를 지우지 않고 비활성화할 수 있어요.", + "moveUp": "위로", + "moveDown": "아래로", + "removeFromChain": "순서에서 제거", + "addProvider": "제공자 추가", + "addNoneAvailable": "모든 제공자가 이미 순서에 있어요", + "saveOrder": "순서 저장", + "saved": "순서를 저장했어요", + "saveFailed": "순서를 저장하지 못했어요", + "resetDefaults": "기본값으로 되돌리기", + "resetConfirmTitle": "순서를 기본값으로 되돌릴까요?", + "resetConfirmBody": "순서가 Codex → OpenAI → Anthropic → 로컬 → 관리자 OpenAI로 돌아가요. 저장한 인증 정보는 그대로예요." + }, + "providerCardTitle": "AI 제공자", + "activeProviderHeading": "사용 중인 제공자", + "activeProviderBody": "가장 먼저 쓸 제공자를 고르세요. 아래 양식은 선택한 제공자만 설정하고, 실패했을 때 어떻게 할지는 맨 아래 대체 순서가 정해요.", + "activeProviderLabel": "기본 제공자", + "providerConfigTitle": "제공자 설정", + "providerSelect": { + "codex": "ChatGPT 계정 (Codex)", + "openai": "OpenAI (내 API 키)", + "anthropic": "Anthropic (Claude)", + "local": "로컬 모델 (OpenAI 호환)", + "openai-compatible": "OpenAI 호환 게이트웨이", + "admin-openai": "관리자가 제공하는 OpenAI" + }, + "compat": { + "description": "OpenAI /v1/chat/completions API를 쓰는 엔드포인트라면 무엇이든 정식 제공자로 쓸 수 있어요: LiteLLM, OpenRouter, vLLM 같은 게이트웨이요. 기본 URL은 /v1로 끝나고, API 키는 선택이며, 모델은 게이트웨이가 연결해 주는 것을 쓰면 돼요. OpenAI 키는 이 엔드포인트에 절대 전달되지 않아요 — 이 제공자는 자체 키를 써요.", + "keyLabel": "API 키 (선택)", + "modelHint": "게이트웨이가 기대하는 모델 이름이에요. 비워 두면 다른 제공자에 저장한 모델을 쓰고, 둘 다 없으면 이 제공자는 건너뛰어요." + }, + "openai": { + "modelSelect": "모델", + "modelOptionCustom": "직접 입력한 슬러그…", + "modelCustomLabel": "직접 입력한 모델 슬러그", + "modelCustomPlaceholder": "gpt-5", + "baseUrlLabel": "기본 URL (고급)", + "baseUrlPlaceholder": "https://api.openai.com/v1", + "baseUrlHelp": "OpenAI 호환 게이트웨이를 쓸 때만 바꾸세요. OpenAI를 그대로 쓴다면 비워 두세요.", + "showAdvanced": "고급 설정 보기", + "hideAdvanced": "고급 설정 숨기기", + "apiKey": "API 키", + "apiKeyPlaceholder": "sk-…" + }, + "codex": { + "statusConnected": "연결됨", + "statusDisconnected": "연결 안 됨", + "statusExpired": "연결 만료됨", + "connectButton": "ChatGPT로 연결", + "disconnectButton": "연결 해제", + "disconnectTitle": "ChatGPT 연결을 해제할까요?", + "disconnectBody": "저장된 로그인 정보가 캐시된 인사이트 텍스트와 함께 삭제돼요. 다시 연결하려면 기기 코드 절차로 로그인해야 해요.", + "disconnectConfirm": "연결 해제", + "modelSlugLabel": "모델 슬러그", + "modelSlugBody": "Codex는 ChatGPT 구독이 연결해 주는 모델을 써요. 운영자는 CODEX_MODEL 환경 변수로 인스턴스에서 이 값을 덮어쓸 수 있어요.", + "lastInsight": "마지막 인사이트: {when}" + }, + "adminOpenai": { + "title": "관리자 OpenAI", + "body": "운영자가 제공한 OpenAI 키로, 개인 제공자를 설정하지 않았을 때 마지막 대체 수단으로 쓰여요. 여기서 설정할 건 없어요.", + "notConfigured": "운영자가 이 인스턴스에 공용 OpenAI 키를 설정하지 않았어요." + }, + "testProvider": "사용 중인 제공자 테스트", + "testSuccess": "정상 — {provider} ({model})", + "testFailedShort": "테스트 실패: {message}", + "testUnexpectedResponse": "AI 제공자 연결에 실패했어요 — 서버에서 예상 밖의 응답이 왔어요.", + "testReasonCredentials": "제공자가 인증 정보를 거부했어요 — AI 설정에서 다시 인증해 주세요.", + "testReasonRateLimited": "제공자가 요청 빈도를 제한했어요 — 잠시 뒤 다시 시도해 주세요.", + "testReasonServerError": "AI 제공자가 서버 오류를 반환했어요.", + "testReasonBadRequest": "제공자가 요청을 거부했어요(HTTP {status}) — 엔드포인트는 응답했으니 연결 문제가 아니라 요청 형식이나 모델 이름 문제예요.", + "testReasonUnreachable": "AI 제공자에 연결하지 못했어요.", + "coachMemory": { + "title": "코치가 기억하는 것", + "description": "코치에게 알려준, 오래 유지되는 정보예요.", + "empty": "코치가 아직 기억하는 정보가 없어요. 대화하면서 알려준 목표, 질환, 선호처럼 오래 유지되는 내용을 기억하고, 여기서 언제든 지울 수 있어요.", + "learnedPrefix": "학습: {when}", + "categoryPreference": "선호", + "categoryCondition": "질환", + "categoryGoal": "목표", + "categoryConstraint": "제약", + "categoryContext": "맥락", + "forget": "잊기", + "forgetAria": "이 정보 잊기", + "forgetTitle": "이 정보를 잊을까요?", + "forgetBody": "다음 메시지부터 코치가 이 정보를 쓰지 않아요. 한번 잊은 정보는 되돌릴 수 없어요.", + "forgetConfirm": "잊기", + "forgetAll": "전부 잊기", + "forgetAllConfirmTitle": "코치가 기억하는 것을 전부 잊을까요?", + "forgetAllConfirmBody": "저장된 정보가 모두 지워져요. 다음 대화부터 코치는 처음부터 시작해요. 되돌릴 수 없어요.", + "forgetAllConfirmAction": "전부 잊기", + "cancel": "취소", + "summaryNote": "코치는 대화마다 짧은 요약을 이어서 유지하기도 해요. 요약은 항목별로 조절할 수 없고, 대화를 삭제하면 요약도 함께 지워져요.", + "loadError": "코치가 기억하는 것을 불러오지 못했어요.", + "forgotToast": "정보를 잊었어요.", + "forgotError": "그 정보를 잊지 못했어요. 다시 시도해 주세요.", + "clearedToast": "저장된 정보 {count}개를 잊었어요.", + "clearError": "저장된 정보를 지우지 못했어요. 다시 시도해 주세요.", + "detail": "코치는 이 정보로 답변을 상황에 맞게 유지해요. 잊었으면 하는 건 지우세요." + }, + "coachReminders": { + "title": "내 알림", + "description": "나중에 다시 알려 달라고 코치에게 부탁한 것들이에요.", + "empty": "아직 알림이 없어요. 코치에게 무언가를 알려 달라고 하면 여기에 나타나요.", + "loadError": "알림을 불러오지 못했어요.", + "dueHeading": "지금 알릴 것", + "allHeading": "저장됨", + "duePrefix": "{when} 예정", + "confirm": "유지", + "markDone": "완료", + "dismiss": "닫기", + "deleteAria": "이 알림 삭제", + "note": "알림은 암호화해서 저장해요. 언제든 삭제할 수 있고, 삭제하면 코치도 볼 수 없어요.", + "status": { + "proposed": "제안됨", + "active": "저장됨", + "due": "예정", + "surfaced": "표시됨", + "done": "완료", + "dismissed": "닫힘" + }, + "deleteConfirmTitle": "이 알림을 삭제할까요?", + "deleteConfirmBody": "더 이상 알려 주지 않아요. 이미 기록한 내용은 그대로예요.", + "deleteConfirmAction": "삭제", + "detail": "예정된 알림은 여기와 코치에 나타나요." + }, + "aboutMe": { + "title": "나에 대해", + "description": "코치와 매일 브리핑이 내 데이터와 함께 참고하는 개인 맥락이에요.", + "placeholder": "예: 교대 근무, 채식 식단, 무릎 부상, 하프 마라톤 준비 중…", + "charCount": "{used} / {max}자", + "clear": "지우기", + "clearTitle": "나에 대해 메모를 지울까요?", + "clearBody": "자유 입력 메모만 지워져요. 알레르기와 불내성 정보는 그대로 남아요.", + "clearConfirm": "메모 지우기", + "save": "저장", + "savedToast": "나에 대해 메모를 저장했어요", + "clearedToast": "나에 대해 메모를 지웠어요.", + "saveError": "저장하지 못했어요. 다시 시도해 주세요.", + "loadError": "메모를 불러오지 못했어요.", + "hint": "코치와 매일 브리핑에 추가 맥락으로 전달돼요. 비워 두면 아무것도 보내지 않아요.", + "profileHint": "나이와 성별은 프로필에서 가져와 자동으로 포함돼요.", + "freeTextLabel": "나에 대해 (자유 입력)", + "conditionsPlaceholder": "예: 고혈압, 2형 당뇨…", + "allergiesPlaceholder": "예: 유당 불내증, 페니실린 알레르기…", + "focusPlaceholder": "예: 혈압을 잘 지켜봐 주세요, 더 움직이도록 격려해 주세요…", + "questionsTitle": "코치가 추가로 묻고 싶은 게 있어요", + "questionsOpenCoach": "코치에서 답하기" + }, + "responseTimeoutHeading": "응답 제한 시간", + "responseTimeoutBody": "AI 응답을 얼마나 기다린 뒤 포기할지 정해요. 첫 요청에서 모델을 불러오는 데 시간이 걸리는 로컬·셀프 호스팅 모델이라면 늘리세요.", + "responseTimeoutLabel": "응답 제한 시간 (초)", + "responseTimeoutPlaceholder": "기본값 (약 120)", + "responseTimeoutInvalid": "10에서 600초 사이의 숫자를 입력해 주세요.", + "autoRead": { + "title": "AI로 문서 자동 읽기", + "subLabel": "문서마다 따로 누르지 않아도 설정한 AI 제공자가 문서를 읽고 색인해요.", + "honesty": "켜 두면 올리는 문서마다 설정한 AI 제공자에게 전송되어 읽히고, 설명이 붙고, 검색용으로 색인돼요 — 문서별 확인 절차는 없어요. 켜는 순간 과거 문서에도 적용돼요. 이미 올렸지만 아직 요약이 없는 문서도 켤 때마다 최대 200개까지 함께 전송돼요. 이 작업은 서버에서 이뤄지고 문서 내용은 이 기기를 떠나 그 제공자에게 전달돼요. 설정한 제공자가 구독 연결(API 키가 아니라 로그인한 AI 계정)이라면 그 제공자의 데이터 정책이 적용돼요 — 구독 제공자는 내용을 모델 개선에 쓸 수 있고, 이를 보호하는 데이터 처리 계약도 없어요. 셀프 호스팅 로컬 모델이 읽은 문서는 서버 밖으로 나가지 않아요. 기본값은 꺼짐이고, 이 조건을 받아들일 때만 켜세요.", + "confirm": "이해했어요 — 켜기", + "cancel": "취소" + }, + "centralCodex": { + "title": "서버의 공용 AI 연결 사용", + "subLabel": "운영자가 이 서버에 설정한 연결로 AI 요청을 보내고, 내 제공자는 대체 수단으로 함께 써요.", + "honesty": "공용 연결은 운영자가 이 서버의 모든 사용자를 위해 연결해 둔 로그인 AI 계정 하나예요 — 내 키가 아니에요. 이 연결로 보낸 요청은 기본적으로 제공자의 학습에 쓰이고 이를 보호하는 데이터 처리 계약도 없으며, 계정의 시간당·주당 한도를 사용을 선택한 다른 모든 사용자와 나눠 써요. 내가 설정한 제공자는 여전히 먼저 시도돼요. 이 조건을 받아들일 때만 켜세요.", + "confirm": "이해했어요 — 연결 사용", + "cancel": "취소" + }, + "consent": { + "title": "AI 동의", + "activeSince": "{date}에 동의했어요. AI 기능을 위해 건강 데이터가 설정한 제공자에게 전송될 수 있어요.", + "withdrawn": "동의를 철회했어요. 건강 데이터를 AI 제공자에게 보내지 않고, AI 기능도 꺼진 상태로 유지돼요.", + "withdraw": "동의 철회", + "confirmBody": "AI 기능이 바로 멈춰요. 이미 기록한 내용은 지워지지 않아요 — 내보내거나 삭제하려면 데이터 및 개인정보를 이용하세요.", + "confirmWithdraw": "철회", + "regrantHint": "다시 동의하면 AI 기능이 건강 데이터를 설정한 제공자에게 보낼 수 있어요.", + "grant": "동의하기", + "grantError": "동의를 저장하지 못했어요. 다시 시도해 주세요.", + "error": "철회를 저장하지 못했어요. 다시 시도해 주세요." + } + }, + "dashboard": { + "units": { + "title": "단위", + "description": "차트와 타일에서 거리와 속도를 어떻게 보여줄지 고르세요.", + "metric": "미터법", + "imperial": "야드파운드법", + "saved": "단위 설정을 저장했어요", + "saveError": "단위 설정을 저장하지 못했어요." + } + }, + "withings": "Withings", + "withingsDescription": "Withings 체중계와 혈압계를 연결하면 체중, 체성분, 혈압을 자동으로 동기화해요.", + "withingsCredentials": "API 자격 증명", + "withingsClientId": "클라이언트 ID", + "withingsClientSecret": "클라이언트 시크릿", + "withingsCredentialsSaved": "자격 증명을 저장했어요", + "withingsCredentialsSavedPlaceholder": "저장됨 — 바꾸려면 새로 입력", + "withingsCredentialsSavedPlaceholderSecret": "저장됨 — 바꾸려면 새로 입력", + "withingsSaveCredentials": "저장", + "configured": "설정됨", + "withingsSync": "지금 동기화", + "withingsFullSync": "전체 데이터 동기화", + "withingsFullSyncTitle": "전체 동기화할까요?", + "withingsFullSyncDescription": "Withings에 있는 모든 데이터를 전부 동기화해요. 기록이 많으면 시간이 걸릴 수 있어요.", + "withingsSyncResult": "측정값 {count}개를 동기화했어요", + "syncOutcome": { + "empty": "새로 가져올 데이터가 없어요. 모두 최신이에요.", + "failed": "저장된 데이터가 없어요. 다음 실행에서 다시 시도해요.", + "partial": "측정값 {count}개를 동기화했어요. 일부는 저장하지 못했고, 다음 실행에서 다시 시도해요." + }, + "withingsFullSyncResult": "측정값 {count}개를 전체 동기화했어요", + "withingsSyncFailed": "동기화에 실패했어요", + "withingsOauthConnected": "Withings를 연결했어요 — 첫 동기화가 백그라운드에서 시작돼요.", + "withingsOauthFailed": "Withings 연결에 실패했어요", + "withingsOauthError": { + "csrf1": "브라우저 쿠키가 없어서 보안 확인에 실패했어요. 같은 브라우저에서 연결을 다시 시작해 주세요.", + "replay": "이미 사용한 확인 링크예요. 연결을 다시 시작해 주세요.", + "state": "Withings의 응답이 진행 중인 연결 요청과 맞지 않아요. 연결을 다시 시작해 주세요.", + "expired": "연결 요청이 만료됐어요. 연결을 다시 시작하고 몇 분 안에 승인을 마쳐 주세요.", + "cross_user": "다른 계정의 요청에 대한 응답이에요. 본인 계정에서 연결을 다시 시작해 주세요.", + "nocode": "Withings가 인증 코드를 보내지 않았어요. 다시 시도하고 Withings 창에서 접근을 허용해 주세요.", + "nocreds": "저장된 Withings API 자격 증명이 없어요. 클라이언트 ID와 클라이언트 시크릿을 먼저 저장한 뒤 다시 연결해 주세요.", + "token": "Withings와의 토큰 교환에 실패했어요. 클라이언트 ID와 클라이언트 시크릿을 확인하고 다시 시도해 주세요.", + "generic": "연결 중 알 수 없는 오류가 생겼어요. 연결을 다시 시작해 주세요." + }, + "withingsSynchronize": "동기화", + "withingsDisconnect": "연결 해제", + "withingsDisconnectTitle": "Withings 연결을 해제할까요?", + "withingsDisconnectDescription": "Withings 연결이 해제돼요. 이미 동기화한 데이터는 그대로 남아요.", + "withingsConnect": "Withings로 연결", + "withingsNoCredentials": "Withings를 연결하려면 위에 API 자격 증명을 입력해 주세요.", + "whoop": "WHOOP", + "whoopDescription": "WHOOP 스트랩을 연결하면 회복도, 수면, 부하, 운동을 HealthLog로 바로 동기화해요.", + "whoopCredentials": "API 자격 증명", + "whoopClientId": "클라이언트 ID", + "whoopClientSecret": "클라이언트 시크릿", + "whoopCredentialsSaved": "자격 증명을 저장했어요", + "whoopCredentialsSavedPlaceholder": "저장됨 — 바꾸려면 새로 입력", + "whoopCredentialsSavedPlaceholderSecret": "저장됨 — 바꾸려면 새로 입력", + "whoopSaveCredentials": "저장", + "whoopSync": "지금 동기화", + "whoopFullSync": "전체 데이터 동기화", + "whoopFullSyncTitle": "전체 동기화할까요?", + "whoopFullSyncDescription": "WHOOP에 있는 모든 기록을 전부 동기화해요. 기록이 많으면 시간이 걸릴 수 있어요.", + "whoopSyncResult": "측정값 {count}개를 동기화했어요", + "whoopSyncFailed": "동기화에 실패했어요", + "whoopSynchronize": "동기화", + "whoopDisconnect": "연결 해제", + "whoopDisconnectTitle": "WHOOP 연결을 해제할까요?", + "whoopDisconnectDescription": "WHOOP 연결이 해제돼요. 이미 동기화한 데이터는 그대로 남아요.", + "whoopConnect": "WHOOP으로 연결", + "whoopNoCredentials": "WHOOP을 연결하려면 위에 API 자격 증명을 입력해 주세요.", + "whoopBackfillInProgress": "WHOOP 기록을 백그라운드에서 가져오는 중이에요…", + "whoopOauthConnected": "WHOOP을 연결했어요 — 첫 동기화가 백그라운드에서 시작돼요.", + "whoopOauthFailed": "WHOOP 연결에 실패했어요", + "whoopOauthError": { + "csrf1": "브라우저 쿠키가 없어서 보안 확인에 실패했어요. 같은 브라우저에서 연결을 다시 시작해 주세요.", + "state": "WHOOP의 응답이 진행 중인 연결 요청과 맞지 않아요. 연결을 다시 시작해 주세요.", + "expired": "연결 요청이 완료되기 전에 만료됐어요. 연결을 다시 시작해 주세요.", + "cross_user": "다른 계정의 요청에 대한 응답이에요. 본인 계정에서 연결을 다시 시작해 주세요.", + "nocode": "WHOOP이 인증 코드를 보내지 않았어요. 다시 시도하고 WHOOP 창에서 접근을 허용해 주세요.", + "nocreds": "WHOOP이 설정되지 않았어요. 위에 WHOOP OAuth 자격 증명을 추가하고 다시 시도해 주세요.", + "token": "WHOOP과의 토큰 교환에 실패했어요. 연결을 다시 시작해 주세요.", + "generic": "연결 중 알 수 없는 오류가 생겼어요. 연결을 다시 시작해 주세요." + }, + "fitbit": "Fitbit", + "fitbitTag": "Fitbit & Pixel", + "fitbitDescription": "Fitbit을 연결하면 Fitbit과 Pixel Watch의 건강 지표를 동기화해요 — 체중, 심박수 등.", + "fitbitExperimentalBadge": "실험 기능", + "fitbitCredentials": "API 자격 증명", + "fitbitClientId": "클라이언트 ID", + "fitbitClientSecret": "클라이언트 시크릿", + "fitbitCredentialsSaved": "자격 증명을 저장했어요", + "fitbitCredentialsSavedPlaceholder": "저장됨 — 바꾸려면 새로 입력", + "fitbitCredentialsSavedPlaceholderSecret": "저장됨 — 바꾸려면 새로 입력", + "fitbitSaveCredentials": "저장", + "fitbitSync": "지금 동기화", + "fitbitFullSync": "전체 데이터 동기화", + "fitbitFullSyncTitle": "전체 동기화할까요?", + "fitbitFullSyncDescription": "Fitbit에 있는 모든 기록을 전부 동기화해요. 기록이 많으면 시간이 걸릴 수 있어요.", + "fitbitSyncResult": "측정값 {count}개를 동기화했어요", + "fitbitSyncFailed": "동기화에 실패했어요", + "fitbitSynchronize": "동기화", + "fitbitDisconnect": "연결 해제", + "fitbitDisconnectTitle": "Fitbit 연결을 해제할까요?", + "fitbitDisconnectDescription": "Fitbit 연결이 해제돼요. 이미 동기화한 데이터는 그대로 남아요.", + "fitbitConnect": "Fitbit으로 연결", + "fitbitNoCredentials": "Fitbit을 연결하려면 위에 API 자격 증명을 입력해 주세요.", + "fitbitBackfillInProgress": "Fitbit 기록을 백그라운드에서 가져오는 중이에요…", + "fitbitOauthConnected": "Fitbit을 연결했어요 — 첫 동기화가 백그라운드에서 시작돼요.", + "fitbitOauthFailed": "Fitbit 연결에 실패했어요", + "googleHealth": "Google Health", + "googleHealthTag": "Fitbit & Pixel", + "googleHealthBetaBadge": "베타", + "googleHealthDescription": "Google 계정으로 Fitbit, Pixel Watch, Fitbit Air를 연결해 체중, 심박수, 수면, 활동을 가져와요. Google이 2026년 9월에 종료하는 기존 Fitbit 연결을 대체해요.", + "googleHealthCredentials": "API 자격 증명", + "googleHealthClientId": "클라이언트 ID", + "googleHealthClientSecret": "클라이언트 시크릿", + "googleHealthCredentialsSaved": "자격 증명을 저장했어요", + "googleHealthCredentialsSavedPlaceholder": "저장됨 — 바꾸려면 새로 입력", + "googleHealthCredentialsSavedPlaceholderSecret": "저장됨 — 바꾸려면 새로 입력", + "googleHealthSaveCredentials": "저장", + "googleHealthSync": "지금 동기화", + "googleHealthSyncCheckStatus": "상태 새로고침", + "googleHealthFullSync": "전체 데이터 동기화", + "googleHealthFullSyncTitle": "전체 동기화할까요?", + "googleHealthFullSyncDescription": "Google Health에 있는 모든 기록을 전부 동기화해요. 기록이 많으면 시간이 걸릴 수 있어요.", + "googleHealthSyncResult": "측정값 {count}개를 동기화했어요", + "googleHealthSyncFailed": "동기화에 실패했어요", + "googleHealthSyncInProgress": "동기화 진행 중: 측정값 {count}개 저장, 영역 {total}개 중 {completed}개 처리했어요.", + "googleHealthResourceFailuresTitle": "아직 확인이 필요한 영역이에요:", + "googleHealthResource": { + "workout": "운동", + "sleep": "수면", + "boundedMetrics": "건강 측정값", + "activity": "활동", + "heartRate": "심박수", + "other": "기타 데이터" + }, + "googleHealthReason": { + "collectionFailed": "Google이 이 데이터를 주지 못했어요. 나중에 다시 동기화해 주세요.", + "tokenFailed": "연결이 만료됐어요. Google Health를 다시 연결해 주세요.", + "upsertFailed": "데이터를 저장하지 못했어요. 다시 동기화해 주세요.", + "rollupFailed": "데이터는 가져왔지만 요약이 아직 갱신되지 않았어요. 나중에 다시 시도해 주세요.", + "pageLimit": "전체 기록 중 일부만 처리했어요. 다음 동기화에서 이어서 처리해요.", + "generic": "이 영역은 완전히 동기화되지 않았어요. 다시 시도해 주세요." + }, + "googleHealthSynchronize": "동기화", + "googleHealthDisconnect": "연결 해제", + "googleHealthDisconnectTitle": "Google Health 연결을 해제할까요?", + "googleHealthDisconnectDescription": "Google Health 연결이 해제돼요. 이미 동기화한 데이터는 그대로 남아요.", + "googleHealthConnect": "Google로 연결", + "googleHealthNoCredentials": "Google Health를 연결하려면 위에 API 자격 증명을 입력해 주세요.", + "googleHealthBackfillInProgress": "Google Health 기록을 백그라운드에서 가져오는 중이에요…", + "googleHealthReauthBanner": "Google이 연결을 만료시켰어요 — OAuth 앱이 테스트 모드로 있는 동안 주기적으로 생기는 일이에요. 다시 연결하면 동기화가 이어져요.", + "googleHealthReconnect": "다시 연결", + "googleHealthOauthConnected": "Google Health를 연결했어요 — 첫 동기화가 백그라운드에서 시작돼요.", + "googleHealthOauthFailed": "Google Health 연결에 실패했어요", + "googleHealthOauthError": { + "csrf1": "브라우저 쿠키가 없어서 보안 확인에 실패했어요. 같은 브라우저에서 연결을 다시 시작해 주세요.", + "state": "Google의 응답이 진행 중인 연결 요청과 맞지 않아요. 연결을 다시 시작해 주세요.", + "expired": "연결 요청이 완료되기 전에 만료됐어요. 연결을 다시 시작해 주세요.", + "cross_user": "다른 계정의 요청에 대한 응답이에요. 본인 계정에서 연결을 다시 시작해 주세요.", + "nocode": "Google이 인증 코드를 보내지 않았어요. 다시 시도하고 Google 창에서 접근을 허용해 주세요.", + "nocreds": "Google Health가 설정되지 않았어요. 위에 Google OAuth 자격 증명을 추가하고 다시 시도해 주세요.", + "token": "Google과의 토큰 교환에 실패했어요. 연결을 다시 시작해 주세요.", + "generic": "연결 중 알 수 없는 오류가 생겼어요. 연결을 다시 시작해 주세요." + }, + "fitbitOauthError": { + "csrf1": "브라우저 쿠키가 없어서 보안 확인에 실패했어요. 같은 브라우저에서 연결을 다시 시작해 주세요.", + "state": "Fitbit의 응답이 진행 중인 연결 요청과 맞지 않아요. 연결을 다시 시작해 주세요.", + "expired": "연결 요청이 완료되기 전에 만료됐어요. 연결을 다시 시작해 주세요.", + "cross_user": "다른 계정의 요청에 대한 응답이에요. 본인 계정에서 연결을 다시 시작해 주세요.", + "nocode": "Fitbit이 인증 코드를 보내지 않았어요. 다시 시도하고 Fitbit 창에서 접근을 허용해 주세요.", + "nocreds": "Fitbit이 설정되지 않았어요. 위에 Fitbit OAuth 자격 증명을 추가하고 다시 시도해 주세요.", + "token": "Fitbit과의 토큰 교환에 실패했어요. 연결을 다시 시작해 주세요.", + "generic": "연결 중 알 수 없는 오류가 생겼어요. 연결을 다시 시작해 주세요." + }, + "integrations": { + "withings": { + "reconnect": { + "banner": { + "title": "활동 동기화를 쓸 수 있어요", + "body": "활동 동기화가 추가되기 전에 Withings 연결을 승인했어요. 한 번만 다시 연결하면 Withings 기기에서 걸음 수, 활동 에너지, 걷기·달리기 거리, 오른 층수를 가져올 수 있어요.", + "action": "Withings 다시 연결" + } + } + } + }, + "integrationPill": { + "connected": "연결됨", + "errorReconnect": "오류 — 다시 연결", + "warningServerError": "동기화 실패 중", + "failureCount": "{count}/{threshold}", + "parkedReconnect": "일시중지 — 다시 연결", + "notConnected": "연결 안 됨", + "justNow": "방금", + "minutesAgo": "{count}분 전", + "hoursAgo": "{count}시간 전", + "daysAgo": "{count}일 전", + "resumeCta": "다시 연결", + "resumeSuccess": "연동을 다시 시작했어요", + "resumeError": "다시 연결하지 못했어요", + "stale": "연결됨, 최근 데이터 없음", + "stalled": "동기화 멈춤", + "pendingSetup": "첫 데이터 기다리는 중" + }, + "integrationFreshness": { + "title": "받은 데이터", + "summary": "데이터 유형 {count}개 · 최신 {relative}", + "quiet": "{count}개 잠잠", + "workouts": "운동" + }, + "integrationFallback": { + "description": "이 계정에 설정돼 있지만 전용 설정 카드는 없어요.", + "lastAttempt": "마지막 시도 {relative}", + "noAttempt": "동기화 시도 기록이 없어요" + }, + "withingsViewData": "측정값 보기", + "whoopViewData": "회복도 보기", + "fitbitViewData": "수면 보기", + "googleHealthViewData": "걸음 수 보기", + "apiTokens": "API 토큰", + "apiTokensDescription": "외부에서 복용을 기록할 때 쓰는 API 토큰을 확인하고 폐기해요.", + "tokenMintMovedDescription": "토큰은 약별로 발급돼요. 약을 열고 API 엔드포인트를 켜면 그 약에만 적용되는 토큰을 받을 수 있어요.", + "tokenNamePlaceholder": "토큰 이름 (예: iPhone 단축어)", + "tokenCreated": "토큰을 만들었어요 — 지금 복사하세요! 다시 보여주지 않아요.", + "tokenCopy": "토큰 복사", + "tokenCopied": "토큰을 클립보드에 복사했어요", + "tokenCopyFailed": "토큰을 복사하지 못했어요", + "tokenRevokeFailed": "토큰을 폐기하지 못했어요. 다시 시도해 주세요.", + "tokenRevoke": "토큰을 폐기할까요?", + "tokenRevokeAction": "토큰 폐기", + "tokenRevokeDescription": "토큰이 영구히 비활성화돼요. 이 토큰을 쓰던 앱은 접근할 수 없게 돼요.", + "tokenRevoked": "폐기됨", + "tokenExpired": "만료됨", + "tokenActive": "사용 중", + "activeTokensTitle": "사용 중인 토큰", + "revokedTokensTitle": "폐기된 토큰 ({count})", + "tokenTableName": "이름", + "tokenTablePermissions": "권한", + "tokenTableStatus": "상태", + "tokenTableCreated": "생성일", + "tokenTableLastUsed": "마지막 사용", + "tokenTableActions": "작업", + "noActiveTokens": "사용 중인 토큰이 없어요.", + "tokenNeverUsed": "없음", + "apiEndpointsTitle": "API 엔드포인트", + "apiEndpointsDescription": "토큰으로 데이터를 보낼 때 쓸 수 있는 외부 엔드포인트 목록이에요.", + "apiEndpointMethod": "메서드", + "apiEndpointPath": "경로", + "apiEndpointAuth": "인증", + "apiEndpointExample": "본문 예시", + "dangerZone": "모든 데이터 삭제", + "dangerZoneDescription": "건강 기록 전체와 모든 연동 연결을 지워요.", + "dangerZoneConfirm": "모든 데이터를 삭제할까요?", + "dangerZoneConfirmDescription": "기록에 있는 모든 것이 영구히 삭제돼요: 측정값, 약, 기분 기록, 검사 결과, 주기 기록, 문서, AI 대화, 모든 연동 연결과 여기서 파생된 데이터, 앞서 삭제한 데이터까지 전부요. 의사에게 준 공유 링크도 바로 작동을 멈춰요. 계정은 그대로 남고, 로그인에 쓰는 자격 증명과 언어·시간대·단위 같은 표시 설정도 유지돼요. 이 작업은 되돌릴 수 없어요.", + "dangerZoneSuccess": "건강 기록을 삭제했어요. 계정과 로그인 정보는 그대로예요.", + "dangerZoneDeleteFailed": "삭제하지 못했어요", + "finalDelete": "영구 삭제", + "deleteAccountCardTitle": "계정 전체 삭제", + "deleteAccountCardDescription": "패스키, 감사 로그, 세션까지 함께 계정을 삭제해요.", + "deleteAccountCta": "계정 삭제", + "deleteAccountConfirmTitle": "계정을 영구히 삭제할까요?", + "deleteAccountConfirmDescription": "계정과 모든 건강 데이터, 패스키, 세션, 감사 기록이 영구히 삭제돼요. 삭제 직후 로그아웃돼요.", + "deleteAccountSuccess": "계정을 삭제했어요 — 로그아웃할게요.", + "deleteAccountFailed": "계정을 삭제하지 못했어요", + "deleteAccountFinal": "영구 삭제", + "saved": "저장됨", + "savingError": "저장 중 오류", + "about": { + "version": "앱 버전", + "gitSha": "빌드", + "builtAtLabel": "빌드 시각", + "license": "라이선스", + "repository": "소스 코드", + "changelog": "변경 이력", + "docs": "문서", + "credits": "크레딧 및 데이터 출처", + "linksHeading": "출처 및 문서", + "newerAvailable": "새 버전: {tag}" + }, + "testConnection": { + "test": "연결 테스트", + "testing": "테스트 중…", + "ok": "연결됨 (지연 {latency}ms)", + "errors": { + "credentials_rejected": "자격 증명이 거부됐어요 — 토큰을 확인해 주세요", + "rate_limited": "상대 서버에서 요청 수를 제한했어요", + "timeout": "요청 시간이 초과됐어요", + "upstream_error": "상대 서버가 오류를 반환했어요", + "connection_failed": "연결에 실패했어요", + "not_configured": "설정되지 않았어요", + "url_not_public": "URL이 공개 엔드포인트가 아니에요", + "url_invalid": "URL이 올바르지 않아요", + "redirected": "엔드포인트가 리디렉션돼요 — URL을 확인해 주세요", + "endpoint_not_found": "해당 URL에 엔드포인트가 없어요", + "credentials_unreadable": "자격 증명을 복호화할 수 없어요", + "upstream_invalid_json": "상대 서버가 잘못된 JSON을 보냈어요", + "vapid_not_configured": "웹 푸시가 설정되지 않았어요 (VAPID 키 없음)", + "rate_limited_self": "테스트 요청이 너무 많아요", + "generic": "테스트에 실패했어요" + } + }, + "notificationStatus": { + "title": "발송 상태", + "description": "채널별 상태예요 — '자동 비활성화'는 영구 오류가 이어져 재시도를 멈췄다는 뜻이에요.", + "emptyDescription": "설정 › 연동 › 발송 채널에서 채널을 설정해 주세요.", + "stateActive": "사용 중", + "stateAutoDisabled": "자동 비활성화", + "stateSendingPaused": "발송 일시중지", + "stateManuallyDisabled": "사용 안 함", + "lastSuccess": "마지막 성공 발송", + "lastFailure": "마지막 실패", + "consecutiveFailures": "연속 실패", + "disabledReason": "사유", + "nextRetry": "다음 재시도", + "reEnable": "다시 사용", + "sendTest": "테스트 발송", + "emptyTitle": "아직 설정한 채널이 없어요" + }, + "identity": { + "description": "건강 기록 내보내기(PDF 표지 + FHIR 내보내기)에 쓰이는 선택 정보예요. 모든 항목은 선택이에요.", + "fullName": "이름", + "fullNamePlaceholder": "예: 홍길동", + "insurer": "건강보험", + "insurerPlaceholder": "예: AOK Bayern", + "insuranceNumber": "보험번호 (KVNR)", + "insuranceNumberHint": "10자: 영문 1자 + 숫자 9자 (예: A123456780)." + }, + "healthRecord": { + "title": "건강 기록 내보내기", + "description": "의사에게 보여줄 전체 기록 — PDF, FHIR, 또는 둘 다.", + "format": "형식", + "format_pdf": "PDF", + "format_fhir": "구조화 (FHIR)", + "format_package": "묶음: PDF + FHIR", + "range": "기간", + "range90": "최근 90일", + "range180": "최근 6개월", + "range365": "최근 12개월", + "practiceName": "병원 (선택)", + "practiceNamePlaceholder": "예: 서울내과의원", + "includedData": "포함할 데이터", + "includeCharts": "추이 차트 넣기 (PDF만)", + "fhirNote": "구조화 출력은 FHIR R4 표준을 따라요 (ePA 호환).", + "generate": "만들기", + "documentsLink": "이 기간의 문서", + "range30": "최근 30일", + "rangeCustom": "직접 지정", + "rangeCustomStart": "시작", + "rangeCustomEnd": "끝", + "errorGeneric": "내보내기를 만들지 못했어요. 다시 시도해 주세요.", + "errorRateLimit": "이번 시간의 내보내기 한도에 도달했어요.", + "errorModuleDisabled": "이 계정에서는 건강 기록 내보내기가 꺼져 있어요.", + "visitOffer": "{when}에 예정된 진료가 있어요: {what}.", + "visitOfferApply": "그날까지로 리포트 만들기" + }, + "globalExcludedInjectionSitesLabel": "전체 제외 주사 부위", + "globalExcludedInjectionSitesHint": "약이 선호하더라도 용량을 기록할 때 추천하지 않고 선택도 막아요.", + "sharing": { + "createTitle": "새 공유 링크", + "createDescription": "링크는 읽기 전용이고 유효 기간이 있어요.", + "label": "라벨", + "labelPlaceholder": "예: 김 선생님 — 건강검진", + "range": "공유할 기록 기간(일)", + "rangeHint": "공유 범위가 과거 며칠까지 포함될지 정해요.", + "expiry": "만료까지(일)", + "expiryHint": "최대 {max}일이에요.", + "expiryInvalid": "만료 기간은 1일에서 {max}일 사이여야 해요.", + "create": "링크 만들기", + "tokenCreated": "공유 링크를 만들었어요", + "copied": "클립보드에 복사했어요", + "activeTitle": "활성 링크", + "noActive": "활성 공유 링크가 없어요.", + "created": "생성", + "expires": "만료", + "accessCount": "조회수", + "revoke": "해지", + "revokeDescription": "이 링크를 즉시 해지해요. 링크를 가진 사람은 바로 접근할 수 없어요. 되돌릴 수 없어요.", + "statusActive": "활성", + "statusRevoked": "해지됨", + "statusExpired": "만료됨", + "inactiveTitle": "해지·만료됨 ({count})", + "shownOnce": "한 번만 보여줘요. 지금 QR 코드와 암호를 저장해 두세요 — 나중에는 둘 다 복구할 수 없어요.", + "qrAlt": "공유 링크와 암호가 담긴 QR 코드", + "linkLabel": "링크", + "passphraseLabel": "암호", + "passphraseHint": "받는 사람에게 링크와 이 암호가 모두 필요해요. QR 코드에는 둘 다 담겨 있어요.", + "copyLink": "링크 복사", + "copyPassphrase": "암호 복사", + "protected": "암호", + "attachTitle": "문서 첨부", + "attachButton": "문서 선택", + "attachCount": "{max}개 중 {count}개 선택됨", + "attachFrozen": "링크를 만들면 문서 구성이 고정돼요 — 나중에 문서를 추가하거나 뺄 수 없어요.", + "attachRemove": "제거: {title}", + "exifNote": "공유할 때 JPEG, PNG, WebP 이미지에서는 카메라 메타데이터(EXIF/GPS)를 제거해요. PDF, TIFF, HEIC, Office 파일은 그대로 전달돼요.", + "documentCount": "첨부한 문서: {count}개", + "qrScanTitle": "다른 기기에서 열려면 스캔하세요", + "qrScanHint": "휴대폰 카메라로 코드를 비추면 공유된 기록이 열려요 — 암호는 이미 포함돼 있어요.", + "pickerTitle": "문서 첨부", + "pickerDescription": "보관함에서 검색해 공유할 문서를 고르세요. 최대 {max}개예요.", + "pickerSearch": "문서 검색", + "pickerEmpty": "문서를 찾지 못했어요.", + "pickerDone": "완료", + "pickerSelectedCount": "{max}개 중 {count}개 선택됨", + "pickerMaxReached": "최대 {max}개까지만 고를 수 있어요.", + "scopeTitle": "이 링크에 담기는 내용", + "scopeInsuranceExcluded": "보험 정보는 링크로 공유되지 않아요.", + "statusNeedsReselect": "내용 다시 선택", + "reselectReason": "이 링크는 선택한 적 없는 범위를 제공하고 있어서 닫았어요. 새로 만들고 담을 내용을 골라 주세요.", + "reselectAction": "새 링크 만들기", + "createDetail": "토큰은 만들 때 한 번만 보여줘요 — 그때 저장해 두세요. 복구할 수 없어요." + }, + "genderOther": "기타", + "nightscout": "Nightscout", + "nightscoutTag": "CGM", + "nightscoutDescription": "직접 운영하는 Nightscout 사이트를 연결해 연속 혈당 측정값을 혈당 패널로 동기화해요.", + "nightscoutViewData": "혈당 보기", + "nightscoutUrl": "Nightscout URL", + "nightscoutUrlSavedPlaceholder": "저장됨 — 바꾸려면 새 URL을 입력하세요", + "nightscoutToken": "API 토큰", + "nightscoutTokenOptional": "공개 사이트라면 선택 사항이에요", + "nightscoutTokenSavedPlaceholder": "저장됨 — 바꾸려면 새 토큰을 입력하세요", + "nightscoutTokenHelp": "Nightscout 관리자 도구에서 발급한 액세스 토큰을 붙여 넣으세요. 사이트를 누구나 읽을 수 있다면 비워 두세요.", + "nightscoutPrivateOperatorTitle": "비공개 Nightscout에 접근하려면 서버 운영자의 승인이 필요해요.", + "nightscoutPrivateOperatorDescription": "서버 운영자에게 이 Nightscout 출처를 그대로 승인해 달라고 요청하세요.", + "nightscoutConnect": "연결", + "nightscoutUpdate": "변경 사항 저장", + "nightscoutConnected": "Nightscout를 연결했어요.", + "nightscoutConnectFailed": "Nightscout에 연결하지 못했어요.", + "nightscoutSync": "지금 동기화", + "nightscoutSyncResult": "측정값 {count}개를 동기화했어요", + "nightscoutSyncFailed": "동기화하지 못했어요", + "nightscoutDisconnect": "연결 해제", + "nightscoutDisconnectTitle": "Nightscout 연결을 해제할까요?", + "nightscoutDisconnectDescription": "앞으로 동기화가 멈춰요. 이미 가져온 혈당 측정값은 기록에 그대로 남아요.", + "polar": "Polar", + "polarDescription": "Polar 계정을 연결해 Nightly Recharge 회복도, 수면 단계, 일일 활동, 운동 세션을 동기화해요.", + "polarViewData": "수면·회복 보기", + "polarCredentials": "API 자격 증명", + "polarClientId": "클라이언트 ID", + "polarClientSecret": "클라이언트 시크릿", + "polarCredentialsSaved": "자격 증명을 저장했어요", + "polarCredentialsSavedPlaceholder": "저장됨 — 바꾸려면 새로 입력하세요", + "polarSaveCredentials": "저장", + "polarUnavailable": "이 서버에는 Polar가 설정돼 있지 않아요. 위에 직접 Polar OAuth 자격 증명을 넣거나, 관리자에게 공용 앱 설정을 요청하세요.", + "polarConnect": "Polar 연결", + "polarSync": "지금 동기화", + "polarSyncResult": "측정값 {count}개를 동기화했어요", + "polarSyncFailed": "동기화하지 못했어요", + "polarDisconnect": "연결 해제", + "polarDisconnectTitle": "Polar 연결을 해제할까요?", + "polarDisconnectDescription": "앞으로 동기화가 멈춰요. 이미 가져온 측정값은 기록에 그대로 남아요.", + "polarOauthConnected": "Polar를 연결했어요 — 첫 동기화가 백그라운드에서 시작돼요.", + "polarOauthFailed": "Polar 연결에 실패했어요", + "polarOauthError": { + "csrf1": "브라우저 쿠키가 없어서 보안 확인에 실패했어요. 같은 브라우저에서 연결을 다시 시작해 주세요.", + "state": "Polar의 응답이 진행 중인 연결 요청과 일치하지 않아요. 연결을 다시 시작해 주세요.", + "cross_user": "다른 계정의 요청에 대한 응답이에요. 본인 계정에서 연결을 다시 시작해 주세요.", + "nocode": "Polar가 인증 코드를 보내지 않았어요. 다시 시도해서 Polar 창에서 접근을 허용해 주세요.", + "nocreds": "이 서버에는 Polar가 설정돼 있지 않아요. 관리자에게 Polar OAuth 자격 증명을 추가해 달라고 요청하세요.", + "token": "Polar와의 토큰 교환에 실패했어요. 연결을 다시 시작해 주세요.", + "rate_limited": "연결 시도가 너무 많아요. 잠시 후 다시 시도해 주세요.", + "generic": "연결 중 알 수 없는 오류가 났어요. 연결을 다시 시작해 주세요." + }, + "oura": "Oura", + "ouraDescription": "Oura 계정을 연결해 준비도, 수면 단계, 심박변이도, 일일 활동을 동기화해요.", + "ouraViewData": "수면·회복 보기", + "ouraCredentials": "API 자격 증명", + "ouraClientId": "클라이언트 ID", + "ouraClientSecret": "클라이언트 시크릿", + "ouraCredentialsSaved": "자격 증명을 저장했어요", + "ouraCredentialsSavedPlaceholder": "저장됨 — 바꾸려면 새로 입력하세요", + "ouraSaveCredentials": "저장", + "ouraUnavailable": "이 서버에는 Oura가 설정돼 있지 않아요. 위에 직접 Oura OAuth 자격 증명을 넣거나, 관리자에게 공용 앱 설정을 요청하세요.", + "ouraConnect": "Oura 연결", + "ouraSync": "지금 동기화", + "ouraSyncResult": "측정값 {count}개를 동기화했어요", + "ouraSyncFailed": "동기화하지 못했어요", + "ouraDisconnect": "연결 해제", + "ouraDisconnectTitle": "Oura 연결을 해제할까요?", + "ouraDisconnectDescription": "앞으로 동기화가 멈춰요. 이미 가져온 측정값은 기록에 그대로 남아요.", + "ouraOauthConnected": "Oura를 연결했어요 — 첫 동기화가 백그라운드에서 시작돼요.", + "ouraOauthFailed": "Oura 연결에 실패했어요", + "ouraOauthError": { + "csrf1": "브라우저 쿠키가 없어서 보안 확인에 실패했어요. 같은 브라우저에서 연결을 다시 시작해 주세요.", + "state": "Oura의 응답이 진행 중인 연결 요청과 일치하지 않아요. 연결을 다시 시작해 주세요.", + "cross_user": "다른 계정의 요청에 대한 응답이에요. 본인 계정에서 연결을 다시 시작해 주세요.", + "nocode": "Oura가 인증 코드를 보내지 않았어요. 다시 시도해서 Oura 창에서 접근을 허용해 주세요.", + "nocreds": "이 서버에는 Oura가 설정돼 있지 않아요. 관리자에게 Oura OAuth 자격 증명을 추가해 달라고 요청하세요.", + "token": "Oura와의 토큰 교환에 실패했어요. 연결을 다시 시작해 주세요.", + "rate_limited": "연결 시도가 너무 많아요. 잠시 후 다시 시도해 주세요.", + "generic": "연결 중 알 수 없는 오류가 났어요. 연결을 다시 시작해 주세요." + }, + "integrationSetupGuide": "설정 가이드", + "coach": { + "activate": { + "title": "코치 활성화", + "description": "코치 버튼과 패널을 표시해요.", + "toggleAria": "코치 버튼과 패널 활성화", + "savedOn": "코치를 켰어요.", + "savedOff": "코치를 숨겼어요.", + "saveError": "변경 사항을 저장하지 못했어요. 다시 시도해 주세요." + } + }, + "integrationRedirectGuide": { + "intro": "직접 만든 {provider} 개발자 앱으로 연결해요 — 설정에 5분쯤 걸려요:", + "step1": "{provider} 개발자 포털에서 앱을 만드세요.", + "step2": "리디렉션/콜백 URL을 다음으로 설정하세요:", + "step3": "발급받은 클라이언트 ID와 클라이언트 시크릿을 위에 붙여 넣으세요." + }, + "security": { + "sessionsTitle": "활성 세션", + "sessionsDescription": "지금 계정에 로그인돼 있는 기기와 브라우저예요.", + "sessionsLoadError": "활성 세션을 불러오지 못했어요.", + "sessionsEmpty": "활성 세션이 없어요.", + "currentSession": "이 기기", + "lastActive": "마지막 활동 {time}", + "unknownLocation": "위치 알 수 없음", + "revokeSession": "로그아웃", + "revokeError": "요청을 처리하지 못했어요. 다시 시도해 주세요.", + "signOutEverywhere": "다른 기기 모두 로그아웃", + "signOutEverywhereDone": "다른 세션 {count}개를 로그아웃했어요", + "activityTitle": "보안 활동", + "activityDescription": "계정의 최근 로그인, 보안 변경, 데이터 내보내기예요.", + "activityEmpty": "아직 보안 활동이 없어요.", + "activityLoadError": "보안 활동을 불러오지 못했어요.", + "action": { + "newDevice": "새 기기 로그인", + "loginFailed": "로그인 실패", + "login": "로그인", + "passwordChange": "비밀번호 변경", + "mfa": "2단계 인증 변경", + "sessionRevoke": "세션 로그아웃", + "token": "API 토큰 변경", + "accountDelete": "계정 삭제", + "dataClear": "데이터 초기화", + "export": "데이터 내보내기", + "other": "보안 이벤트" + }, + "active": "활성", + "inactive": "비활성", + "stepUpRequired": "보안을 위해 2단계 인증을 바꾸려면 다시 로그인해 주세요.", + "useRecoveryCode": "복구 코드로 대신 인증하기", + "useAuthenticator": "인증 앱으로 대신 인증하기", + "totp": { + "title": "인증 앱", + "description": "인증 앱의 시간 기반 코드를 2단계 인증으로 써요.", + "setUp": "설정", + "step1": "인증 앱으로 QR 코드를 스캔하거나 비밀 키를 직접 입력하세요.", + "step2": "앱에 표시된 6자리 코드를 입력해 확인하세요.", + "qrAlt": "인증 앱 설정용 QR 코드", + "qrUnavailable": "QR 코드를 표시할 수 없어요", + "manualEntry": "또는 이 비밀 키를 직접 입력하세요", + "enterCode": "6자리 코드를 입력하세요", + "verify": "확인", + "setupFailed": "설정을 시작하지 못했어요. 다시 시도해 주세요.", + "invalidCode": "코드가 올바르지 않아요. 다시 시도해 주세요.", + "disable": "해제", + "disableTitle": "2단계 인증 해제", + "disableConfirm": "2단계 인증을 끄려면 현재 코드를 입력하세요. 복구 코드도 함께 삭제돼요.", + "currentCode": "현재 코드", + "disableFailed": "2단계 인증을 해제하지 못했어요." + }, + "recovery": { + "saveNow": "지금 복구 코드를 저장하세요", + "saveNowHint": "코드는 각각 한 번만 쓸 수 있어요. 이번에만 보여주니 안전한 곳에 보관하세요. 인증 앱을 잃어버리면 복구 코드만이 다시 들어올 유일한 방법이에요.", + "download": "다운로드", + "remainingOne": "복구 코드가 {count}개 남았어요.", + "remainingOther": "복구 코드가 {count}개 남았어요.", + "regenerate": "코드 재발급", + "regenFailed": "복구 코드를 재발급하지 못했어요.", + "savedIt": "저장했어요", + "regenerateTitle": "복구 코드를 재발급할까요?", + "regenerateConfirm": "현재 복구 코드를 새 코드로 바꿔요. 기존 코드는 즉시 쓸 수 없게 되고, 새 코드는 한 번만 보여줘요." + }, + "keys": { + "title": "보안 키", + "description": "하드웨어 또는 플랫폼 보안 키를 2단계 인증으로 등록하세요.", + "empty": "아직 등록한 보안 키가 없어요.", + "add": "보안 키 추가", + "addFailed": "보안 키를 등록하지 못했어요.", + "rename": "이름 변경", + "renameFailed": "보안 키 이름을 변경하지 못했어요.", + "remove": "보안 키 삭제", + "removeFailed": "보안 키를 삭제하지 못했어요.", + "removeConfirm": "이 보안 키는 더 이상 2단계 인증으로 쓸 수 없게 돼요.", + "lastUsed": "마지막 사용 {date}", + "neverUsed": "사용한 적 없음", + "added": "{date} 추가됨", + "nameLabel": "보안 키 이름" + }, + "passkeyNudge": { + "title": "패스키를 추가하면 더 빠르게 로그인해요", + "body": "패스키로 지문, 얼굴, 기기 PIN을 써서 로그인할 수 있어요 — 비밀번호를 외울 필요가 없어요." + }, + "trustedDevices": { + "title": "신뢰하는 기기", + "description": "신뢰하기로 한 브라우저는 30일 동안 2단계 인증을 건너뛰어요. 비밀번호는 여전히 필요해요. 모르는 기기는 해지하세요.", + "loadError": "신뢰하는 기기를 불러오지 못했어요.", + "revokeError": "기기를 해지하지 못했어요. 다시 시도해 주세요.", + "revoke": "해지", + "revokeAll": "모든 기기 잊기", + "revokeAllDone": "신뢰하는 기기 {count}개를 잊었어요", + "unnamed": "알 수 없는 기기", + "current": "이 기기", + "expires": "{date}까지 신뢰", + "revokeConfirmTitle": "이 기기를 더 이상 신뢰하지 않을까요?", + "revokeConfirmBody": "다음에 이 기기에서 로그인할 때 2단계 인증을 다시 물어봐요.", + "revokeAllConfirmTitle": "모든 기기를 더 이상 신뢰하지 않을까요?", + "revokeAllConfirmBody": "이 기기를 포함해 모든 기기에서 다음에 로그인할 때 2단계 인증을 다시 물어봐요." + }, + "revokeSessionConfirmTitle": "이 기기를 로그아웃할까요?", + "revokeSessionConfirmBody": "{device} — 다시 로그인해야 해요. 기기에 기록된 내용은 영향받지 않아요.", + "signOutEverywhereConfirmTitle": "다른 기기를 모두 로그아웃할까요?", + "signOutEverywhereConfirmBody": "이 기기를 뺀 모든 기기가 다시 로그인해야 해요. 이 기기는 로그인 상태로 남아요. 기록한 내용은 영향받지 않아요." + }, + "privacy": { + "encryption": { + "title": "저장 시 암호화", + "summary": "민감한 데이터는 기록 유형 {models}종의 필드 {columns}개에 걸쳐 {algorithm} 방식으로 저장 시 암호화돼요.", + "statement": "민감한 데이터는 AES-256-GCM으로 저장 시 암호화돼요." + }, + "stored": { + "title": "저장되는 항목", + "measurements": "측정값 (체중, 혈압, 맥박, 혈당, 수면, 체성분)", + "medications": "약과 복용 기록", + "moodLabs": "기분 기록과 검사 결과", + "coach": "코치 대화", + "integrations": "연동 토큰 (암호화됨)", + "security": "계정·보안 활동 (세션, 로그인 기록)" + }, + "retention": { + "title": "보관 기간", + "description": "자동 정리 전까지 기간이 정해진 기록을 얼마나 보관하는지예요.", + "coach": "코치 대화: {days}일 보관", + "audit": "보안 활동 기록: {days}일 보관", + "delivery": "알림 발송 기록: {days}일 보관", + "unavailable": "지금은 보관 기간 정보를 볼 수 없어요." + }, + "export": { + "title": "내 데이터 내려받기", + "description": "기록을 CSV나 JSON으로, 또는 진료용 전체 건강 기록으로 받을 수 있어요.", + "openExport": "내보내기·백업", + "openHealthRecord": "건강 기록" + }, + "delete": { + "title": "삭제 또는 초기화", + "description": "계정을 완전히 없애거나, 계정은 두고 건강 기록만 지워요.", + "backupLag": "삭제하면 데이터는 즉시 지워져요. 외부에 보관된 암호화 백업에는 운영자의 백업 보관 기간이 끝날 때까지 남아 있어요.", + "openAdvanced": "삭제 또는 초기화" + }, + "posture": { + "title": "개인정보 보호 수준", + "description": "AI 기능에 데이터를 어디까지 공유할지 정해요.", + "openAi": "AI 개인정보 모드" + } + }, + "mcp": { + "enableTitle": "원격 MCP 엔드포인트", + "enableDescription": "MCP를 지원하는 어시스턴트(Claude.ai, ChatGPT)가 범위가 제한된 토큰으로 건강 데이터를 읽게 해요.", + "enabled": "켜짐", + "disabled": "꺼짐", + "enableToggleLabel": "MCP 엔드포인트 켜기", + "endpointLabel": "커넥터 URL", + "connectDescription": "어시스턴트의 커스텀 커넥터 설정에 이 URL을 붙여 넣고, 아래 토큰으로 인증하거나 안내가 나오면 로그인하세요.", + "tokensTitle": "커넥터 토큰", + "tokensDescription": "어시스턴트를 연결할 토큰을 발급하세요 — 아래에서 기록을 허용하지 않으면 읽기 전용이에요.", + "scopeNote": "새 토큰은 본인 기록만 읽을 수 있어요. 쓰기 권한(health:write)은 아래 토글로 토큰마다 따로 부여해요. 토큰은 관리자 기능에 절대 접근할 수 없어요.", + "writeScopeToggleLabel": "기록 허용 (health:write)", + "writeScopeNote": "health:write 범위를 추가해 어시스턴트가 새 측정값과 기분 기록을 계정에 남길 수 있게 해요. 모든 쓰기는 먼저 확인을 거쳐요. 그래도 토큰은 기존 기록을 삭제하거나 수정할 수 없고, 약을 바꾸거나 REST·관리자 기능에 접근할 수도 없어요. 읽기 전용으로 쓰려면 꺼 두세요.", + "connectionsTitle": "연결된 어시스턴트", + "connectionsDescription": "로그인 절차로 승인한 어시스턴트예요.", + "noConnections": "연결된 어시스턴트가 없어요.", + "connectionConnected": "연결됨", + "connectionLastUsed": "마지막 사용", + "connectionNeverUsed": "사용한 적 없음", + "connectionRevoke": "연결 해제", + "connectionRevokeDescription": "어시스턴트의 접근을 즉시 끊고 갱신도 막아요. 다시 연결하려면 승인을 새로 받아야 해요.", + "connectionRevokeFailed": "어시스턴트 연결을 해제하지 못했어요.", + "connectionsDetail": "연결을 해지하면 갱신 권한까지 포함해 접근이 즉시 끊겨요.", + "tokensDetail": "토큰은 한 번만 보여줘요." + }, + "strava": "Strava", + "stravaDescription": "Strava 계정을 연결해 활동을 운동 기록으로 동기화해요.", + "stravaViewData": "운동 기록 보기", + "stravaCredentials": "API 자격 증명", + "stravaClientId": "클라이언트 ID", + "stravaClientSecret": "클라이언트 시크릿", + "stravaCredentialsSaved": "자격 증명을 저장했어요", + "stravaCredentialsSavedPlaceholder": "저장됨 — 바꾸려면 새로 입력하세요", + "stravaSaveCredentials": "저장", + "stravaUnavailable": "이 서버에는 Strava가 설정돼 있지 않아요. 위에 직접 Strava API 자격 증명을 넣거나, 관리자에게 공용 앱 설정을 요청하세요.", + "stravaConnect": "Strava 연결", + "stravaSync": "지금 동기화", + "stravaSyncResult": "측정값 {count}개를 동기화했어요", + "stravaSyncFailed": "동기화하지 못했어요", + "stravaDisconnect": "연결 해제", + "stravaDisconnectTitle": "Strava 연결을 해제할까요?", + "stravaDisconnectDescription": "앞으로 동기화가 멈춰요. 이미 가져온 활동은 기록에 그대로 남아요.", + "stravaOauthConnected": "Strava를 연결했어요 — 첫 동기화가 백그라운드에서 시작돼요.", + "stravaOauthFailed": "Strava 연결에 실패했어요", + "stravaOauthError": { + "csrf1": "브라우저 쿠키가 없어서 보안 확인에 실패했어요. 같은 브라우저에서 연결을 다시 시작해 주세요.", + "state": "Strava의 응답이 진행 중인 연결 요청과 일치하지 않아요. 연결을 다시 시작해 주세요.", + "cross_user": "다른 계정의 요청에 대한 응답이에요. 본인 계정에서 연결을 다시 시작해 주세요.", + "nocode": "Strava가 인증 코드를 보내지 않았어요. 다시 시도해서 Strava 창에서 접근을 허용해 주세요.", + "nocreds": "이 서버에는 Strava가 설정돼 있지 않아요. 관리자에게 Strava API 자격 증명을 추가해 달라고 요청하세요.", + "token": "Strava와의 토큰 교환에 실패했어요. 연결을 다시 시작해 주세요.", + "rate_limited": "연결 시도가 너무 많아요. 잠시 후 다시 시도해 주세요.", + "generic": "연결 중 알 수 없는 오류가 났어요. 연결을 다시 시작해 주세요." + }, + "appleHealth": { + "title": "Apple Health", + "description": "실시간 동기화는 HealthLog iOS 앱에서 이뤄져요. 웹이나 OAuth 연결로는 안 돼요.", + "status": { + "unavailable": "Apple Health 상태를 알 수 없어요" + }, + "setupTitle": "실시간 동기화 켜기", + "permissionStep": "HealthLog iOS 앱에서 설정 → Apple Health를 열고 공유할 건강 카테고리를 허용해 주세요.", + "backgroundStep": "백그라운드 앱 새로고침을 켠 채로 두세요. 앱은 화면에 떠 있을 때와 iOS가 백그라운드 시간을 줄 때 새 데이터를 자동으로 동기화해요.", + "importNote": "예전 기록이 필요하거나 iOS 앱을 쓸 수 없나요? Apple Health XML/ZIP 업로드는 한 번만 쓰는 대안이라 실시간 동기화는 켜지지 않아요.", + "importAction": "일회성 불러오기 열기", + "staleNote": "일주일 넘게 Apple Health 데이터가 들어오지 않았어요. iPhone에서 HealthLog 앱을 열고 Apple Health 권한을 확인해 주세요.", + "progress": { + "title": "동기화 진행 상황", + "receivedLabel": "받은 기록 수", + "oldestLabel": "받아온 가장 오래된 측정값", + "flowing": "최근 1시간 안에 데이터가 들어왔어요 — iPhone 앱이 계속 보내는 중이에요.", + "waiting": "iPhone 앱이 데이터를 더 보내기를 기다리는 중이에요. 앱이 열려 있거나 iOS가 백그라운드 시간을 줄 때 전송이 이어져요 — 처음 대량으로 채워 넣는 동안 잠잠한 구간이 생기는 건 정상이고 오류가 아니에요." + }, + "delivery": { + "title": "전송", + "lastTriggerLabel": "마지막 동기화 도착", + "lastBackgroundLabel": "마지막 백그라운드 동기화", + "backgroundNever": "없음 — 지금까지 모든 동기화가 앱이 열린 상태에서 들어왔어요", + "noSyncYet": "아직 도착한 동기화가 없어서 여기 보여줄 내용이 없어요.", + "trigger": { + "foreground": "앱이 열린 상태에서", + "background": "백그라운드에서", + "push": "푸시 깨우기로", + "unknown": "앱이 트리거를 알려주지 않았어요" + } + } + }, + "garminInfo": { + "title": "Garmin", + "body": "여기서는 Garmin에 직접 연결할 수 없어요. Apple Health(iOS)나 Google Health Connect(Android)를 거쳐 연결하세요. Body Battery, Training Load, HRV Status, VO₂ max 같은 Garmin 고유 점수는 Garmin이 두 플랫폼 어디에도 공유하지 않아요.", + "docsLink": "가이드 보기" + }, + "glucoseReference": { + "title": "혈당 참고 구간", + "description": "혈당 측정값을 어떤 구간에 견줘 판단할지 정해요.", + "enable": "당뇨 목표 구간 사용", + "explainer": "꺼두면 측정값을 일반 비당뇨 참고 구간에 견줘 판단해요. 켜면 ADA Standards of Care의 더 좁은 혈당 목표 구간에 견줘 판단하고, 코치도 그 구간을 근거로 혈당 이야기를 해요. 아래에서 직접 정한 목표가 있으면 그게 우선해요.", + "disclaimer": "이건 참고 구간을 고르는 설정이에요. 진단이 아니고, 측정값으로 추정되지도 않으며, 앱이 이걸로 임상 조치를 하지도 않아요. 어떤 목표가 나에게 맞는지는 임상의와 상의하세요.", + "error": "설정을 저장하지 못했어요." + }, + "dangerZoneDetail": "계정과 로그인에 쓰는 인증 정보는 그대로 남아요.", + "deleteAccountCardDetail": "되돌릴 수 없어요.", + "passkeyStepUpRequired": "보안을 위해 패스키를 삭제하기 전에 다시 로그인해 주세요." + }, + "admin": { + "title": "관리자", + "subtitle": "시스템 상태, 사용자, 설정", + "settingsSaveError": "설정을 저장하지 못했어요", + "systemStatus": "시스템 상태", + "systemStatusDescription": "이 인스턴스의 실행 상태: 데이터베이스, 워커, 기록 수.", + "database": "데이터베이스", + "databaseConnected": "연결됨", + "databaseError": "오류", + "startedAt": "시작 시각", + "users": "사용자", + "measurementsCount": "측정", + "activeTokens": "활성 API 토큰", + "activeSessions": "활성 세션", + "loadingStatus": "상태 불러오는 중…", + "systemStatusLoadError": "시스템 상태를 불러오지 못했어요.", + "systemStatusRetry": "다시 시도", + "adminSettingsLoadError": "관리자 설정을 불러오지 못했어요. 아래 토글이 실제 상태와 다를 수 있으니 페이지를 새로고침해 주세요.", + "hostMetrics": { + "title": "호스트 부하", + "load1": "부하 (1m)", + "memUsedPercent": "메모리", + "diskBusyPercent": "디스크 I/O", + "last2hours": "최근 2시간", + "empty": "샘플링을 시작했어요 — 1–2분 뒤에 데이터가 나타나요.", + "loadError": "호스트 지표를 불러오지 못했어요. 페이지를 새로고침해 보세요." + }, + "aiServerKey": { + "title": "AI 서버 키", + "description": "자기 키를 설정하지 않은 계정을 위한 운영자 키예요.", + "byokHint": "사용자 본인 키(BYOK, ChatGPT 로그인, 로컬 제공자)가 항상 우선해요 — 서버 키는 제공자 체인의 마지막 단계예요.", + "consentHint": "서버 키로 나가는 요청에는 사용자의 유효한 AI 동의가 필요해요. 동의 기록이 없으면 아무것도 전송하지 않아요.", + "statusConfigured": "설정됨", + "statusNotConfigured": "설정 안 됨", + "keyLabel": "API 키", + "keyPlaceholder": "sk-...", + "keyPlaceholderSet": "저장됨 ({preview}) — 바꾸려면 새 키를 입력하세요", + "modelLabel": "모델", + "baseUrlLabel": "Base URL", + "save": "저장", + "remove": "키 삭제", + "removeTitle": "서버 키를 삭제할까요?", + "removeBody": "자기 키가 없는 계정은 바로 AI 기능을 쓸 수 없게 돼요. 저장한 뒤에는 키를 어디에도 다시 보여주지 않으니, 되돌리려면 원본 키가 필요해요.", + "removeConfirm": "키 삭제", + "saved": "AI 서버 키 설정을 저장했어요", + "error": "AI 서버 키를 저장하지 못했어요" + }, + "aiQuality": { + "title": "인사이트 품질", + "loading": "피드백 요약 불러오는 중…", + "loadError": "인사이트 품질 요약을 불러오지 못했어요. 페이지를 새로고침해 보세요.", + "empty": "아직 피드백이 없어요. 집계는 매일 04:00에 돌아요 — 사용자가 추천을 평가하기 시작하면 도움됨 비율 구간이 여기에 나타나요.", + "windowLabel": "최근 {days}일", + "generatedAtLabel": "생성 시각", + "colSeverity": "심각도", + "colMetric": "지표", + "colProvider": "제공자", + "colPromptVersion": "프롬프트", + "colHelpful": "도움됨", + "colNotHelpful": "도움 안 됨", + "colHelpfulRate": "도움됨 비율" + }, + "coachFeedback": { + "title": "코치 피드백", + "loading": "코치 피드백 요약 불러오는 중…", + "loadError": "코치 피드백 요약을 불러오지 못했어요. 페이지를 새로고침해 보세요.", + "empty": "아직 코치 피드백이 없어요. 집계는 매일 04:00에 돌아요 — 사용자가 코치 답변을 평가하면 도움됨 비율 구간이 여기에 나타나요.", + "windowLabel": "최근 {days}일", + "generatedAtLabel": "생성 시각", + "colPromptVersion": "프롬프트 버전", + "colTone": "말투", + "colVerbosity": "상세도", + "colHelpful": "도움됨", + "colNotHelpful": "도움 안 됨", + "colN": "건수", + "colHelpfulRate": "도움됨 비율" + }, + "workerStatus": "작업 워커", + "workerRunning": "실행 중", + "workerStopped": "중지됨", + "lastReminderCheck": "마지막 알림 점검", + "appSettings": "애플리케이션 설정", + "appSettingsDescription": "회원가입, 언어, 새 계정 시간대의 전역 기본값이에요.", + "registrationEnabled": "회원가입 허용", + "registrationEnabledDescription": "끄면 새 사용자가 가입할 수 없어요.", + "registrationOpenHint": "회원가입이 열려 있어요 — 이 인스턴스에 접근할 수 있는 누구나 계정을 만들 수 있어요. 필요한 사람이 다 가입했으면 여기서 닫으세요.", + "defaultLanguage": "기본 언어", + "defaultLanguageDescription": "새 사용자와 대체값으로 쓰여요.", + "defaultUserTimezone": "새 계정의 기본 시간대", + "defaultUserTimezoneDescription": "브라우저에서 시간대를 감지하지 못한 새 계정에 쓰여요. 기존 사용자에게는 영향이 없어요.", + "defaultUserTimezoneFallback": "Europe/Berlin (기본값)", + "servicesGlobal": "서비스 (전역)", + "servicesGlobalDescription": "끈 서비스는 어떤 사용자도 쓸 수 없어요.", + "telegramGlobal": "Telegram 알림을 허용해요.", + "ntfyGlobal": "ntfy 알림을 허용해요.", + "webPushGlobal": "브라우저 푸시 알림을 허용해요.", + "integrationWebPush": "웹 푸시", + "offlineGeoLabel": "위치 데이터", + "offlineGeoEnabled": "오프라인 (GeoLite2)", + "offlineGeoFallback": "온라인 ({host}) — GeoLite2 데이터베이스를 마운트하고 GEOLITE2_DIR을 설정하세요", + "apiGlobal": "API 엔드포인트와 API 토큰을 허용해요.", + "umamiTitle": "Umami", + "umamiDescription": "Umami 웹 분석", + "glitchtipTitle": "GlitchTip", + "glitchtipDescription": "GlitchTip 오류 추적", + "webPushVapidTitle": "웹 푸시 VAPID", + "umamiEnabled": "Umami 추적을 켜요.", + "umamiScriptUrl": "Umami 스크립트 URL", + "umamiScriptUrlPlaceholder": "https://analytics.example.com/script.js", + "umamiWebsiteId": "Umami 웹사이트 ID", + "umamiWebsiteIdPlaceholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "glitchtipEnabled": "GlitchTip 오류 추적을 켜요.", + "glitchtipDsn": "GlitchTip DSN", + "glitchtipDsnPlaceholder": "https://@glitchtip.example.com/", + "glitchtipEnvironment": "GlitchTip 환경", + "glitchtipEnvironmentPlaceholder": "production", + "webPushVapidDescription": "환경 변수 없이 브라우저 푸시가 동작하도록 VAPID 키와 subject를 여기서 설정하세요.", + "webPushVapidPublicKey": "VAPID 공개 키", + "webPushVapidPublicKeyPlaceholder": "BElx... (Base64URL)", + "webPushVapidPrivateKey": "VAPID 비밀 키", + "webPushVapidPrivateKeyPlaceholder": "선택: 새 비밀 키 입력", + "webPushVapidSubject": "VAPID subject", + "webPushVapidSubjectPlaceholder": "mailto:you@example.com", + "webPushVapidGenerate": "키 생성", + "webPushVapidGenerating": "생성 중…", + "webPushVapidGenerated": "VAPID 키를 생성했어요. subject를 저장하면 끝나요.", + "webPushVapidGenerateFailed": "VAPID 키를 생성하지 못했어요.", + "webPushVapidGenerateConfirm": "VAPID 키가 이미 설정돼 있어요. 새로 만들면 모든 브라우저 푸시 구독이 무효가 되고 기기마다 다시 구독해야 해요. 계속할까요?", + "webPushVapidGenerateConfirmTitle": "VAPID 키를 다시 생성할까요?", + "webPushVapidRegenerate": "다시 생성", + "configured": "설정됨", + "monitoringTestSuccess": "테스트 이벤트를 보냈어요", + "monitoringTestFailed": "모니터링 테스트에 실패했어요", + "assistant": { + "title": "어시스턴트 화면", + "description": "각 토글은 제공자 설정을 지우지 않고 모델 기반 화면만 앱 전체에서 숨겨요.", + "master": { + "title": "어시스턴트 사용", + "description": "모든 모델 기반 화면의 마스터 스위치예요. 끄면 아래 하위 항목이 전부 강제로 꺼져요." + }, + "coach": { + "title": "코치", + "description": "모든 인사이트 페이지의 채팅 서랍, 실행 버튼, 모바일 FAB." + }, + "briefing": { + "title": "데일리 브리핑", + "description": "브리핑 카드, 히어로 스트립 설명, 추천 그리드, 다시 생성 동작." + }, + "insightStatus": { + "title": "상태 카드", + "description": "모든 /insights 하위 페이지의 지표별 상태 카드." + }, + "correlations": { + "title": "상관관계", + "description": "인사이트 개요의 상관관계 타일 설명." + } + }, + "modules": { + "title": "모듈 사용 여부", + "description": "서버 전체에서 모듈을 끌 수 있어요.", + "saveError": "모듈 사용 여부를 바꾸지 못했어요.", + "detail": "여기서 끈 모듈은 개인 설정과 관계없이 모든 계정에서 꺼져요. 핵심 영역(체중, 혈압, 맥박, 복약)은 항상 켜져 있고 끌 수 없어요." + }, + "invites": { + "title": "초대", + "description": "가입 초대 링크 — 회원가입이 닫혀 있어도 동작해요.", + "create": "초대 만들기", + "createTitle": "초대 만들기", + "createDescription": "회원가입이 닫혀 있어도 이 링크로는 가입할 수 있어요.", + "ttlLegend": "유효 기간", + "createDays": "{days}일", + "maxUsesLabel": "최대 사용 횟수", + "maxUsesHint": "이 링크로 가입할 수 있는 인원이에요 (1–50).", + "createConfirm": "초대 만들기", + "createdToast": "초대를 만들었어요", + "createError": "초대를 만들지 못했어요.", + "tokenTitle": "초대 링크", + "tokenOnceHint": "지금만 복사할 수 있어요 — 링크를 복사하거나 QR 코드를 스캔하게 하세요.", + "hashExplainer": "링크의 해시만 저장하기 때문에 나중에 다시 보여줄 수 없어요. 잃어버렸다면 초대를 취소하고 새로 만드세요.", + "copyLink": "링크 복사", + "copiedToast": "링크를 복사했어요", + "copyError": "복사하지 못했어요.", + "qrAlt": "초대 링크 QR 코드", + "loadError": "초대 목록을 불러오지 못했어요.", + "emptyTitle": "아직 초대가 없어요", + "emptyDescription": "회원가입이 닫혀 있어도 가입시키려면 초대 링크를 만드세요.", + "colStatus": "상태", + "colCreated": "생성", + "colExpires": "만료", + "colUses": "사용", + "colRedeemedBy": "사용자", + "colActions": "동작", + "byCreator": "생성: {by}", + "expiresInDaysOne": "1일 후", + "expiresInDaysOther": "{count}일 후", + "statusActive": "활성", + "statusExpired": "만료됨", + "statusExhausted": "모두 사용", + "statusRevoked": "취소됨", + "redeemerDeleted": "삭제된 계정", + "redeemedByMore": "{username} 외 {count}명", + "redemptionsTitle": "사용 {count}회", + "revoke": "취소", + "revokeAria": "초대 취소", + "revokeConfirmTitle": "이 초대를 취소할까요?", + "revokeConfirmBody": "초대 링크가 바로 무효가 돼요. 이미 이 링크로 가입한 계정은 그대로 남고, 사용 이력도 계속 보여요.", + "revokeConfirmAction": "취소", + "cancel": "닫기", + "revokedToast": "초대를 취소했어요.", + "revokeError": "초대를 취소하지 못했어요." + }, + "userManagement": "사용자 관리", + "userManagementDescription": "계정 관리: 역할, 비밀번호 재설정, 강제 로그아웃.", + "userName": "이름", + "userEmail": "이메일", + "userRole": "역할", + "userPasskeys": "패스키", + "userCreated": "생성", + "userActions": "동작", + "editUser": "사용자 편집", + "editUserTitle": "사용자 편집: {name}", + "demoteToUser": "일반 사용자로 변경", + "promoteToAdmin": "관리자로 승격", + "toUser": "사용자로", + "toAdmin": "관리자로", + "ownRoleUnchangeable": "자기 역할은 바꿀 수 없어요", + "resetPassword": "비밀번호 재설정", + "resetPasswordTitle": "비밀번호 재설정: {name}", + "newPassword": "새 비밀번호", + "newPasswordPlaceholder": "12자 이상", + "passwordReset": "비밀번호를 재설정했어요", + "reset": "재설정", + "loadingUsers": "사용자 불러오는 중…", + "apiTokens": "API 토큰", + "apiTokensDescription": "모든 계정의 활성·취소된 API 토큰이에요.", + "tokenUser": "사용자", + "tokenName": "이름", + "tokenPermissions": "권한", + "tokenPermissionAll": "전체", + "tokenPermissionAllTooltip": "와일드카드 권한 — 이 토큰은 모든 API 경로를 호출할 수 있어요.", + "tokenStatus": "상태", + "tokenLastUsed": "마지막 사용", + "tokenCreated": "생성", + "tokenNeverUsed": "없음", + "tokensEmptyTitle": "아직 API 토큰이 없어요", + "tokensEmptyDescription": "네이티브 클라이언트가 로그인할 때나 설정 → 계정에서 토큰이 발급돼요.", + "loginOverview": "로그인 개요", + "loginOverviewDescription": "모든 계정의 로그인·인증 이벤트예요. 필터링하고 CSV로 내보낼 수 있어요.", + "allAuthEvents": "전체 인증 이벤트", + "failedOnly": "실패만", + "loginEmptyDescription": "로그인, 로그아웃, 패스키 변경이 여기에 표시돼요.", + "loginEmptyFailedTitle": "실패한 로그인 시도가 없어요", + "loginEmptyFailedDescription": "안전한 상태예요. 성공한 로그인을 보려면 전체 이벤트로 바꾸세요.", + "loginEmptyResetFilter": "전체 이벤트 보기", + "showingEntries": "인증 기록 {total}개 중 {count}개 표시 중", + "status": "상태", + "action": "동작", + "ip": "IP", + "location": "위치", + "timestamp": "시각", + "provider": "제공자", + "providerPassword": "비밀번호", + "providerPasskey": "패스키", + "providerApiToken": "API 토큰", + "providerWithings": "Withings", + "providerUnknown": "알 수 없음", + "outcome": "결과", + "outcomeSuccess": "성공", + "outcomeFailed": "실패", + "auditDetails": "상세", + "deleteAllData": "전체 데이터 삭제", + "deleteAllDescription": "모든 계정이 가진 기록을 전부 삭제해요. 계정과 로그인 자격 증명은 남아요.", + "deleteAllConfirm": "정말 전체 데이터를 삭제할까요?", + "deleteAllConfirmDescription": "일부 테이블이 아니라 데이터베이스 전체에서 모든 계정의 기록을 영구히 삭제해요. 남는 것: 사용자 계정과 로그인에 쓰이는 자격 증명, 인스턴스 설정, 관리 프로필 접근을 유지하는 위임 권한. 삭제 후 감사 로그에 이번 삭제 기록이 남고, 그 이전의 감사 기록은 나머지와 함께 사라져요.", + "deleteButton": "전체 데이터 삭제", + "deleteAllPromptLabel": "계속하려면 {token} 입력", + "finalDelete": "영구 삭제", + "deletedResult": "테이블 {tables}개에서 행 {rows}개를 삭제했어요. 사용자 계정과 로그인 자격 증명은 남겼어요.", + "authRegister": "회원가입", + "authLogin": "로그인", + "authLoginPasskey": "패스키 로그인", + "authLoginPassword": "비밀번호 로그인", + "authLoginFailed": "실패", + "authLogout": "로그아웃", + "authPasskeyRegister": "패스키 추가됨", + "authPasskeyDelete": "패스키 삭제됨", + "authTokenAutoissueNative": "네이티브 클라이언트 토큰 발급됨", + "authTokenRefresh": "토큰 갱신됨", + "authTokenRevoke": "토큰 취소됨", + "medicationReminders": "복약 알림", + "medicationRemindersDescription": "약 카드의 알림 배지 색이 바뀌는 기준선이에요.", + "notificationHealth": { + "title": "알림 상태", + "description": "최근 24시간 동안 모든 사용자의 발송 결과예요.", + "empty": "이 기간에 보낸 알림이 없어요.", + "ok": "정상", + "error": "실패", + "skipped": "건너뜀", + "autoDisabled": "자동으로 꺼진 채널" + }, + "reminderLateMinutes": "지연 기준선 (분)", + "reminderLateMinutesDescription": "복용 시간대가 끝난 뒤 배지가 노랑에서 주황으로 바뀌기까지의 분이에요.", + "reminderMissedMinutes": "놓침 기준선 (분)", + "reminderMissedMinutesDescription": "복용 시간대가 끝난 뒤 복용을 놓친 것으로 보기까지의 분이에요.", + "notificationTestSend": "테스트 발송", + "notificationTestSuccess": "테스트 알림을 보냈어요", + "notificationTestFailed": "테스트 알림을 보내지 못했어요", + "reminderCheckRun": "알림 점검 실행", + "reminderCheckSuccess": "알림 점검을 마쳤어요", + "reminderCheckFailed": "알림 점검을 실행하지 못했어요", + "reminderCheckResults": "결과", + "reminderCheckNotifOn": "알림 켜짐", + "reminderCheckNotifOff": "알림 꺼짐", + "reminderCheckEventsToday": "오늘 이벤트", + "reminderCheckNotifSent": "발송", + "shell": { + "overview": "개요", + "sectionsNav": "관리자 섹션" + }, + "overview": { + "snapshotTitle": "시스템 스냅샷", + "snapshotDatabase": "데이터베이스", + "snapshotWorker": "워커", + "snapshotStarted": "시작 시각", + "snapshotOfflineGeo": "위치 데이터", + "snapshotOfflineGeoOn": "오프라인 (GeoLite2)", + "snapshotOfflineGeoOff": "온라인 ({host})", + "versionTileTitle": "버전", + "versionTileVersion": "실행 중인 버전", + "versionTileBuildSha": "빌드 SHA", + "versionTileBuiltAt": "빌드 시각", + "versionTileUpdateAvailable": "업데이트 있음: {tag}", + "versionTileUpToDate": "최신 상태", + "snapshotLoadError": "시스템 스냅샷을 불러오지 못했어요.", + "snapshotLoading": "스냅샷 불러오는 중…", + "auditTitle": "최근 활동", + "auditEmpty": "최근 감사 기록이 없어요.", + "auditLoadError": "최근 활동을 불러오지 못했어요.", + "auditLoading": "활동 불러오는 중…", + "auditViewAll": "전체 보기" + }, + "section": { + "system-status": { + "title": "시스템 상태", + "subtitle": "워커, 데이터베이스, 연동 상태를 한눈에 봐요." + }, + "general": { + "title": "일반", + "subtitle": "회원가입, 기본 언어 등 앱 전체 설정이에요." + }, + "services": { + "title": "서비스", + "subtitle": "알림 채널과 연동을 시스템 전체에서 켜거나 꺼요." + }, + "integrations": { + "title": "연동", + "subtitle": "Umami, GlitchTip, 웹 푸시, 버그 신고를 설정해요." + }, + "coach": { + "title": "코치", + "subtitle": "어시스턴트 노출 위치, 서버 키, 피드백 품질을 한곳에서 관리해요." + }, + "reminders": { + "title": "알림", + "subtitle": "복약 알림 기준선을 조정하고 수동으로 점검해요." + }, + "users": { + "title": "사용자", + "subtitle": "사용자 계정, 역할, 활성 세션을 관리해요.", + "filter": { + "all": "전체", + "admin": "관리자", + "user": "사용자" + }, + "forceLogout": "강제 로그아웃", + "forceLogoutConfirmTitle": "{name} 강제 로그아웃할까요?", + "forceLogoutConfirmBody": "이 사용자의 활성 세션이 모두 해제돼요. 모든 기기에서 다시 로그인해야 해요.", + "forceLogoutSuccess": "{name} — 세션 {count}개에서 로그아웃했어요", + "forceLogoutFailed": "강제 로그아웃하지 못했어요", + "cannotLogoutSelf": "자기 자신은 강제 로그아웃할 수 없어요", + "emptyTitle": "이 화면에 사용자가 없어요", + "emptyDescription": "현재 필터에 맞는 사용자가 없어요.", + "emptyResetFilter": "전체 사용자 보기" + }, + "invites": { + "title": "초대", + "subtitle": "회원가입 초대 링크를 발급하고 추적하고 폐기해요." + }, + "api-tokens": { + "title": "API 토큰", + "subtitle": "네이티브 클라이언트와 연동에 발급된 활성 bearer 토큰이에요." + }, + "login-overview": { + "title": "로그인 개요", + "subtitle": "인증과 관리자 이벤트의 감사 기록이에요." + }, + "auditLog": { + "filterActor": "행위자 (사용자 id 또는 아이디)", + "filterAction": "동작", + "filterActionAll": "모든 동작", + "filterTarget": "대상 (id 또는 경로)", + "filterDate": "기간", + "range24h": "최근 24시간", + "range7d": "최근 7일", + "range30d": "최근 30일", + "rangeAll": "전체 기간", + "perPage": "쪽당 개수", + "prev": "이전", + "next": "다음", + "pageOf": "{total}쪽 중 {page}쪽", + "export": "CSV 내보내기", + "empty": "현재 필터에 맞는 감사 기록이 없어요." + }, + "app-logs": { + "title": "앱 로그", + "subtitle": "이 프로세스가 남긴 최근 구조화 wide-event를 실시간으로 봐요.", + "processNote": "앱 프로세스 로그만 보여줘요 — 워커 프로세스 로그는 Loki 연동을 설정하면 그쪽으로 가요.", + "filterTraceId": "추적 ID", + "filterAction": "동작 포함…", + "filterLevel": "레벨", + "levelAll": "모든 레벨", + "filterRange": "시간 범위", + "range15m": "최근 15분", + "range1h": "최근 1시간", + "range6h": "최근 6시간", + "rangeAll": "버퍼 전체", + "colLevel": "레벨", + "colTimestamp": "시각", + "colAction": "동작", + "colDuration": "소요 시간", + "colTraceId": "추적", + "viewDetails": "{timestamp}의 {action} 상세 보기", + "showing": "버퍼에 담긴 최대 {bufferMax}개 중 {count}개를 보여줘요", + "empty": "버퍼가 비었어요", + "emptyDescription": "아직 현재 필터에 맞는 이벤트가 없어요. 트래픽을 발생시킨 뒤 새로고침해 주세요.", + "refresh": "새로고침", + "eventDetails": "이벤트 상세", + "closeDetails": "상세 닫기", + "processDetail": "버퍼는 휘발성이고 최대 500개까지만 담아요." + }, + "backups": { + "title": "백업", + "subtitle": "PostgreSQL에 저장되는 주간 사용자별 데이터 스냅샷이에요.", + "description": "사용자마다 백업 유형별로 최신 스냅샷 하나를 굴려가며 보관해요. 크기는 암호화된 blob 기준이고, 이 페이지에서 내용을 내려받을 수는 없어요.", + "runNow": "지금 백업", + "runEnqueued": "백업 작업을 큐에 넣었어요", + "runFailed": "백업을 큐에 넣지 못했어요", + "loading": "백업을 불러오는 중…", + "loadError": "백업을 불러오지 못했어요.", + "emptyTitle": "아직 백업이 없어요", + "emptyDescription": "백업은 매주 일요일 03:00에 자동으로 돌아요 — 지금 실행해서 첫 스냅샷을 만들 수도 있어요.", + "colUser": "사용자", + "colType": "유형", + "typeWeeklyAuto": "주간 (자동)", + "typeManual": "수동", + "colSize": "크기", + "colCreatedAt": "최근 스냅샷", + "colActions": "동작", + "retentionLabel": "보관 기간: {days}일", + "download": "다운로드", + "downloadAria": "{username}의 백업 다운로드", + "downloadFailed": "다운로드하지 못했어요", + "downloadStarted": "다운로드를 시작했어요", + "uploadTitle": "백업 업로드", + "uploadDescription": "백업 파일을 검사한 뒤 수동 스냅샷으로 저장해요. 복원은 별도 단계예요.", + "uploadButton": "파일 업로드", + "uploadHelp": "현재 백업 스키마에 맞는 JSON 파일이에요. 최대 10 MB.", + "uploadSuccess": "백업을 업로드했어요 (기록 {count}개)", + "uploadFailed": "업로드하지 못했어요", + "restore": "복원", + "restoreAria": "{username}의 백업에서 복원", + "restoreTitle": "백업에서 복원", + "restoreDescription": "{username}의 현재 데이터 전체를 {when} 스냅샷으로 바꿔요. 기존 측정, 약, 복용 기록, 기분 기록, 알림 채널, 푸시 구독, 대기 중인 Telegram 삭제 항목이 지워져요. 스냅샷에 인스턴스 전체 설정이 들어 있으면 설치본 전체 설정을 덮어써요.", + "restorePromptLabel": "계속하려면 RESTORE를 입력해 주세요", + "previewTitle": "이 백업에 담긴 내용", + "previewLoading": "내용을 읽는 중…", + "previewUnavailable": "내용을 읽지 못했어요 — 복원은 그래도 할 수 있어요.", + "previewEmpty": "파일에 기록이 없어요.", + "previewOther": "기타 기록", + "previewMeasurements": "측정", + "previewMedications": "약과 복용 기록", + "previewMood": "기분 기록", + "previewLabs": "검사 결과", + "previewDocuments": "문서", + "previewWorkouts": "운동", + "previewCycles": "생리 주기 데이터", + "previewVisits": "진료 기록", + "previewVaccinations": "예방접종", + "previewReminders": "예방 관리 알림", + "previewCustomMetrics": "사용자 지표", + "previewHealthScores": "점수 기록", + "restoreConfirm": "지금 복원", + "restoreInProgress": "복원 중…", + "restoreSuccess": "복원을 마쳤어요", + "restoreFailed": "복원하지 못했어요", + "restoreSkippedToast": "복원을 마쳤어요 — 되돌리지 못한 증상 연결: {links}", + "restoreSkippedTitle": "복원하지 못한 연결: {links}", + "restoreSkippedDescription": "파일이 이 설치본에 없는 기록을 가리키고 있어요. 나머지는 모두 복원했어요. 같은 목록이 감사 로그에도 남아 있어요.", + "restoreSkippedLinks": "연결 {count}개", + "restoreSkippedCycleSymptom": "생리 주기 증상", + "restoreSkippedIllnessSymptom": "아픈 날 증상", + "restoreSkippedMoodFactor": "기분 요인", + "restoreSkippedMoodTag": "기분 태그", + "restoreSkippedVisitReference": "진료 참조", + "restoreSkippedVaccinationReference": "예방접종 참조", + "restoreSkippedCheckupClosure": "건강검진 종료 처리", + "restoreSkippedReminderReference": "알림 참조", + "restoreSkippedCoachAttachment": "코치 문서 연결", + "restoreSkippedCoachReference": "코치 참조", + "restoreSkippedDocumentConditionLink": "문서 분류", + "restoreSkippedExtractedFact": "문서 추출 정보", + "restoreSkippedFactCommitment": "추출 정보 확정", + "docsLink": "문서", + "restoreSkippedPersonalRecordReference": "개인 최고 기록 출처", + "restoreSkippedEcgReference": "ECG 파형 출처", + "restoreSkippedMedicationTarget": "약 목표 분석물", + "restoreSkippedScheduleRevisionLink": "일정 개정 연결", + "restoreMissingTitle": "복원을 중단했어요 — 파일이 불완전해요", + "restoreMissingDescription": "파일의 매니페스트에는 아래 항목이 들어 있다고 적혀 있는데 실제로는 없어요. 아무것도 바꾸지 않았어요. 복원은 해당 기록 전체를 갈아엎기 때문에, 파일에 없는 항목을 되돌리면 그 항목이 통째로 비워졌을 거예요.", + "restoreMissingRemedy": "재해 복구용 스냅샷을 쓰거나, 계정을 다시 내보내서 새 파일을 업로드해 주세요. 매니페스트가 처음부터 '빠졌다'고 표시한 항목은 다른 경우라 정상적으로 복원돼요.", + "restoreMissingDocuments": "문서", + "restoreMissingWorkouts": "운동", + "restoreMissingMentalHealth": "정신 건강 선별 검사", + "restoreMissingConsent": "동의 기록" + }, + "danger-zone": { + "title": "위험 구역", + "subtitle": "시스템 전체에 영향을 주는 파괴적 작업이에요." + }, + "about": { + "title": "정보", + "subtitle": "버전, 라이선스, 업데이트 상태예요." + }, + "module-availability": { + "title": "모듈", + "subtitle": "이 서버의 모든 계정에 대해 모듈을 켜거나 꺼요." + }, + "encryption": { + "title": "암호화", + "subtitle": "저장 데이터의 암호화 적용 범위와 키 교체 상태예요.", + "loading": "암호화 상태를 불러오는 중…", + "loadError": "암호화 상태를 불러오지 못했어요.", + "coverageTitle": "적용 범위", + "coverageDescription": "키 id별로 암호화된 행이 얼마나 있는지 보여줘요.", + "activeKeyId": "활성 키 id", + "configuredKeys": "설정된 키", + "totalRows": "암호화된 행", + "staleRows": "활성 키가 아닌 행", + "safeToDropLegacy": "이전 키를 삭제해도 안전해요", + "rotationIncomplete": "{count}개 행이 아직 교체되지 않았어요", + "rotationTitle": "키 교체", + "rotationDescription": "암호화된 모든 열을 활성 키로 다시 암호화해요.", + "rotationState": "상태", + "lastRun": "마지막 실행: 검사 {scanned}개, 교체 {rotated}개, 오류 {errors}개.", + "rotateNow": "지금 교체", + "rotateConfirmTitle": "기록 전체를 다시 암호화할까요?", + "rotateConfirmDescription": "암호화된 모든 열을 활성 키로 다시 암호화해요. 키를 추가하거나 삭제하지 않으니 여러 번 실행해도 안전해요. 데이터가 많은 계정은 시간이 걸릴 수 있어요.", + "cancel": "취소", + "rotateConfirm": "교체 시작", + "rotateEnqueued": "교체를 시작했어요.", + "rotateFailed": "교체를 시작하지 못했어요.", + "stepUpNeeded": "2단계 인증을 다시 확인해야 해요. 2단계 인증을 확인하거나, 등록한 수단이 없으면 명령줄 도구를 써 주세요.", + "cliNote": "이 버튼을 쓰려면 2단계 인증이 필요해요. 등록하지 않은 운영자는 scripts/rotate-encryption-key.ts로 교체해 주세요.", + "columnsTitle": "열별 적용 범위", + "columnsDescription": "암호화된 열마다 활성 키와 이전 키의 행 수를 보여줘요.", + "colColumn": "열", + "colTotal": "전체", + "colActive": "활성 키", + "colStale": "이전 키", + "state": { + "idle": "대기", + "running": "진행 중", + "completed": "완료", + "failed": "실패" + }, + "coverageDetail": "키 id와 개수만 보여줘요 — 키 자체는 절대 표시하지 않아요." + } + }, + "carrier": "통신사", + "mfaRequired": "2단계 인증 필수", + "mfaRequiredDescription": "2단계 인증이 없는 계정은 로그인 후 등록 화면으로 보내요. 웹 사용자에게 등록을 요구할 뿐, 아직 등록하지 않은 계정의 API나 네이티브(Bearer) 접근을 막지는 않아요.", + "mfaEnforcedOn": "MFA 켬", + "mfaEnforcedOff": "MFA 끔", + "mfaEnforcedOnHint": "이 사용자에게 2단계 인증이 필수예요. 누르면 해제돼요.", + "mfaEnforcedOffHint": "이 사용자에게 2단계 인증을 필수로 설정해요.", + "copyIp": "IP 주소 복사", + "ipCopied": "IP 주소를 복사했어요", + "copyFailed": "복사하지 못했어요.", + "documentMaxFile": "문서 업로드 제한", + "documentMaxFileDescription": "문서 보관함의 파일당 크기 제한이에요 (1–100 MB).", + "documentQuota": "문서 저장 용량", + "documentQuotaDescription": "계정당 기본 문서 저장 용량이에요 (0.1–1024 GB).", + "userDocumentQuota": "문서 용량 개별 설정", + "userDocumentQuotaDefault": "기본값", + "userDocumentQuotaHint": "비워 두면 인스턴스 기본값을 써요.", + "centralCodex": { + "title": "공용 AI 연결", + "description": "로그인한 ChatGPT 계정 하나를 연결하면, 이 서버의 사용자가 원할 때 AI 기능에 쓸 수 있어요.", + "honesty": "API 키가 아니라 로그인된 AI 계정 하나를 함께 쓰는 방식이에요. 사용을 선택한 모든 사용자가 이 계정으로 요청을 보내므로 시간·주간 한도를 다 같이 나눠 쓰고, 같은 한도를 코치도 이미 쓰고 있어요. 구독형 제공자는 내용을 모델 개선에 쓸 수 있고 데이터 처리 계약도 적용되지 않아서, 기본값으로 쓰지 않고 문서 읽기에서는 학습하지 않는 제공자보다 뒤에 둬요. 이렇게 공유해도 괜찮은 계정만 연결해 주세요.", + "statusConnected": "연결됨", + "statusDisconnected": "연결 안 됨", + "statusExpired": "만료됨", + "connectButton": "ChatGPT 계정 연결", + "disconnectButton": "연결 해제", + "disconnectTitle": "공용 AI 계정 연결을 해제할까요?", + "disconnectBody": "이 서버에서 공용 연결을 쓰던 사용자는 누군가 계정을 다시 연결할 때까지 AI 기능을 쓸 수 없어요. 저장된 로그인 정보는 되살릴 수 없어요.", + "disconnectConfirm": "연결 해제", + "connectedSince": "연결 시작: {when}", + "connected": "공용 AI 연결을 등록했어요", + "disconnected": "공용 AI 연결을 해제했어요", + "error": "문제가 생겼어요 — 다시 시도해 주세요" + }, + "failingJobs": { + "title": "백그라운드 작업", + "description": "재시도를 다 쓰고 포기한 작업이 있는 큐예요.", + "none": "최근 {hours}시간 동안 실패한 작업이 없어요.", + "unavailable": "읽을 작업 큐가 없어요 — 이 배포본은 백그라운드 워커 없이 돌고 있어요.", + "countOne": "실패 {count}건", + "countFew": "실패 {count}건", + "countOther": "실패 {count}건", + "lastFailed": "마지막 실패 {when}" + }, + "deleteAllDetail": "사용자 계정은 그대로 남아요." + }, + "achievements": { + "title": "업적", + "subtitle": "건강을 기록하고 약을 제때 복용하면 업적을 얻어요.", + "loginRequired": "업적을 보려면 로그인해 주세요.", + "points": "포인트", + "unlocked": "달성", + "nextGoal": "다음 목표", + "allCompleted": "모든 업적을 달성했어요!", + "completed": "완료", + "goalReached": "목표 달성", + "remainingUnlocks": "{count}개 더 달성 가능", + "noneUnlockedYet": "아직 달성한 업적이 없어요.", + "pointsValue": "{points}포인트", + "metricCount": "{count}", + "metricDays": "{count}일", + "metricPercent": "{count}%", + "badges": { + "intakeTotal1": { + "title": "첫 복용", + "description": "첫 복용 기록을 남겨요." + }, + "intakeTotal10": { + "title": "복용 입문", + "description": "복용 기록 10회를 남겨요." + }, + "intakeTotal50": { + "title": "복용 습관", + "description": "복용 기록 50회를 남겨요." + }, + "intakeTotal150": { + "title": "복용 달인", + "description": "복용 기록 150회를 남겨요." + }, + "intakeTotal300": { + "title": "복용 전설", + "description": "복용 기록 300회를 남겨요." + }, + "overIntake1": { + "title": "한 번 더", + "description": "계획보다 한 번 이상 더 복용해요." + }, + "skippedIntake1": { + "title": "한 걸음 물러서기", + "description": "계획된 복용을 한 번 이상 건너뛰어요." + }, + "passkeyCreated1": { + "title": "패스키 등록", + "description": "첫 패스키를 만들어요." + }, + "passkeyLogin1": { + "title": "패스키 로그인", + "description": "패스키로 한 번 이상 로그인해요." + }, + "passwordLogin1": { + "title": "옛날 방식", + "description": "아이디와 비밀번호로 한 번 이상 로그인해요." + }, + "loginStreak7": { + "title": "로그인 연속 7일", + "description": "7일 연속으로 로그인해요." + }, + "loginStreak30": { + "title": "로그인 연속 30일", + "description": "30일 연속으로 로그인해요." + }, + "onTimePerfect1": { + "title": "정시 복용 1일", + "description": "하루 동안 모든 약을 제때 복용해요." + }, + "compliance801": { + "title": "복약 순응도 80% · 1일", + "description": "30일 복약 순응도 80% 이상을 1일 유지해요." + }, + "bmiGreen1": { + "title": "BMI 정상 · 1일", + "description": "BMI를 1일 동안 정상 범위로 유지해요." + }, + "bpGreen1": { + "title": "혈압 정상 · 1일", + "description": "혈압을 1일 동안 정상 범위로 유지해요." + }, + "pulseGreen1": { + "title": "맥박 정상 · 1일", + "description": "안정 시 맥박을 1일 동안 정상 범위로 유지해요." + }, + "onTimePerfect7": { + "title": "정시 복용 7일", + "description": "7일 연속으로 모든 약을 제때 복용해요." + }, + "compliance807": { + "title": "복약 순응도 80% · 7일", + "description": "30일 복약 순응도 80% 이상을 7일 연속 유지해요." + }, + "bmiGreen7": { + "title": "BMI 정상 · 7일", + "description": "BMI를 7일 연속 정상 범위로 유지해요." + }, + "bpGreen7": { + "title": "혈압 정상 · 7일", + "description": "혈압을 7일 연속 정상 범위로 유지해요." + }, + "pulseGreen7": { + "title": "맥박 정상 · 7일", + "description": "안정 시 맥박을 7일 연속 정상 범위로 유지해요." + }, + "onTimePerfect30": { + "title": "정시 복용 30일", + "description": "30일 연속으로 모든 약을 제때 복용해요." + }, + "compliance8030": { + "title": "복약 순응도 80% · 30일", + "description": "30일 복약 순응도 80% 이상을 30일 연속 유지해요." + }, + "bmiGreen30": { + "title": "BMI 정상 · 30일", + "description": "BMI를 30일 연속 정상 범위로 유지해요." + }, + "bpGreen30": { + "title": "혈압 정상 · 30일", + "description": "혈압을 30일 연속 정상 범위로 유지해요." + }, + "pulseGreen30": { + "title": "맥박 정상 · 30일", + "description": "안정 시 맥박을 30일 연속 정상 범위로 유지해요." + }, + "onTimePerfect180": { + "title": "정시 복용 180일", + "description": "180일 연속으로 모든 약을 제때 복용해요." + }, + "compliance80180": { + "title": "복약 순응도 80% · 180일", + "description": "30일 복약 순응도 80% 이상을 180일 연속 유지해요." + }, + "bmiGreen180": { + "title": "BMI 정상 · 180일", + "description": "BMI를 180일 연속 정상 범위로 유지해요." + }, + "bpGreen180": { + "title": "혈압 정상 · 180일", + "description": "혈압을 180일 연속 정상 범위로 유지해요." + }, + "pulseGreen180": { + "title": "맥박 정상 · 180일", + "description": "안정 시 맥박을 180일 연속 정상 범위로 유지해요." + }, + "onTimePerfect360": { + "title": "정시 복용 360일", + "description": "360일 연속으로 모든 약을 제때 복용해요." + }, + "compliance80360": { + "title": "복약 순응도 80% · 360일", + "description": "30일 복약 순응도 80% 이상을 360일 연속 유지해요." + }, + "bmiGreen360": { + "title": "BMI 정상 · 360일", + "description": "BMI를 360일 연속 정상 범위로 유지해요." + }, + "bpGreen360": { + "title": "혈압 정상 · 360일", + "description": "혈압을 360일 연속 정상 범위로 유지해요." + }, + "pulseGreen360": { + "title": "맥박 정상 · 360일", + "description": "안정 시 맥박을 360일 연속 정상 범위로 유지해요." + }, + "moodFirst": { + "title": "첫 기분 기록", + "description": "첫 기분 기록을 남겨요." + }, + "moodStreak7": { + "title": "기분 다이어리 7일", + "description": "7일 연속으로 기분을 기록해요." + }, + "moodStreak30": { + "title": "기분 다이어리 30일", + "description": "30일 연속으로 기분을 기록해요." + }, + "moodUp7": { + "title": "밝아진 한 주", + "description": "7일 기분 평균이 지난주보다 1.0점 이상 좋아졌어요." + }, + "weightFirst": { + "title": "첫 체중 측정", + "description": "첫 체중 측정을 기록해요." + }, + "weight50": { + "title": "체중 50회", + "description": "체중을 50회 측정해 기록해요." + }, + "weight200": { + "title": "체중 200회", + "description": "체중을 200회 측정해 기록해요." + }, + "bpFirst": { + "title": "첫 측정값", + "description": "첫 혈압 측정을 기록해요." + }, + "bp50": { + "title": "혈압 50회", + "description": "혈압을 50회 측정해 기록해요." + }, + "bp200": { + "title": "혈압 200회", + "description": "혈압을 200회 측정해 기록해요." + }, + "pulseFirst": { + "title": "첫 맥박", + "description": "첫 맥박 측정을 기록해요." + }, + "consistentMonth": { + "title": "꾸준한 한 달", + "description": "한 달 안에 서로 다른 25일 이상 기록을 남겨요." + }, + "entryStreak7": { + "title": "기록 연속 7일", + "description": "7일 연속으로 기록을 하나 이상 남겨요." + }, + "entryStreak30": { + "title": "기록 연속 30일", + "description": "30일 연속으로 기록을 하나 이상 남겨요." + }, + "weekendWarrior": { + "title": "주말 기록러", + "description": "토·일 주말 4회 연속으로 기록을 남겨요." + }, + "hiddenNightOwl": { + "title": "올빼미", + "description": "새벽 02:00에서 04:00 사이에 기록을 남겼어요." + }, + "hiddenEarlyBird": { + "title": "아침형 인간", + "description": "새벽 04:00에서 06:00 사이에 기록을 남겼어요." + }, + "hiddenLeapDay": { + "title": "윤일의 전설", + "description": "2월 29일에 기록을 남겼어요." + }, + "hiddenDoctorPdf": { + "title": "왕진", + "description": "첫 진료용 리포트 PDF를 내보냈어요." + }, + "hiddenLocaleFlip": { + "title": "다국어 사용자", + "description": "앱 언어를 한 번 이상 바꿨어요." + }, + "missFree7": { + "title": "복용 누락 없음 · 7일", + "description": "7일 연속으로 복용을 한 번도 놓치지 않아요." + }, + "missFree30": { + "title": "복용 누락 없음 · 30일", + "description": "30일 연속으로 복용을 한 번도 놓치지 않아요." + }, + "missFree90": { + "title": "복용 누락 없음 · 90일", + "description": "90일 연속으로 복용을 한 번도 놓치지 않아요." + }, + "measurementWeeks4": { + "title": "측정 습관", + "description": "4주 연속으로 매주 5일 이상 측정을 기록해요." + }, + "selfContextComplete": { + "title": "자기 보고 완성", + "description": "자기 보고 항목 네 가지를 모두 채웠어요 — 코치가 상황을 알아요." + }, + "sleepLog7": { + "title": "수면 기록 · 7일", + "description": "7일 연속으로 수면 데이터를 기록해요." + } + }, + "nextProgressLabel": "달성까지 진행률", + "completedOn": "{date} 완료", + "locked": "잠김", + "criterionHint": "{current} / {target}", + "progressPercent": "{percent}%", + "categories": { + "medication": "복약", + "vitals": "활력징후", + "mood": "기분", + "security": "계정·보안", + "engagement": "참여", + "hidden": "히든" + }, + "hiddenCard": { + "title": "히든 업적", + "description": "계속 기록하다 보면 우연히 만날 수도 있어요.", + "ariaLabel": "잠긴 히든 업적" + }, + "hiddenUnlockToast": { + "title": "히든 업적을 달성했어요!" + }, + "dashboardCard": { + "title": "최근 달성", + "viewAll": "전체 보기", + "empty": "아직 업적이 없어요 — 기록을 이어가 첫 업적을 달성해 보세요." + } + }, + "notifications": { + "title": "알림", + "eventColumn": "이벤트", + "subtitle": "어떤 알림을 어떤 채널로 받을지 선택하세요.", + "loadError": "알림 설정을 불러오지 못했어요.", + "noChannels": "설정 → 연동에서 알림 채널을 먼저 설정해 주세요.", + "goToSettings": "채널 설정으로 이동", + "channelDisabled": "사용 안 함", + "admin": { + "deployFailedTitle": "배포 실패: {application}", + "deployFailedBody": "{application} (#{deployment}) 배포 중 오류가 발생했어요: {error}. 로그 보기: {logsUrl}", + "testNotificationTitle": "HealthLog 테스트 알림", + "testNotificationBody": "관리자 패널에서 보낸 테스트 알림이에요. 이 알림이 보이면 채널이 정상 작동해요.", + "reminderCheckMissedTitle": "알림 놓침: {medication}", + "reminderCheckMissedBody": "{window} 구간에 {medication} ({dose}) 복용 기록이 없어요.", + "reminderCheckOverdueTitle": "복용 지연: {medication}", + "reminderCheckOverdueBody": "{window} 구간의 {medication} ({dose}) 복용이 {minutes}분 지났어요.", + "offlineGeoUnavailableTitle": "오프라인 위치 데이터베이스가 로드되지 않았어요", + "offlineGeoUnavailableBody": "HealthLog가 {host}의 온라인 조회로 로그인 위치를 확인하고 있어서, 모든 로그인 IP 주소가 이 서버를 벗어나요. 로컬에서 확인하려면 무료 MaxMind 계정으로 GeoLite2-City와 GeoLite2-ASN 데이터베이스를 내려받아 그 디렉터리를 컨테이너에 마운트하고 GEOLITE2_DIR가 이를 가리키게 하세요. 절차: {docsUrl}", + "tlsPinChangedTitle": "TLS 리프 인증서 변경: {host}", + "tlsPinChangedBody": "{host}가 제공하는 TLS 리프의 SPKI 핀이 이제 {servedPin}인데, 알려진 핀 목록에 없어요. 기존 핀이 사라지면 핀을 검증하는 클라이언트(네이티브 앱)는 연결에 실패해요. 새 리프 핀을 다시 추출해 TLS_LEAF_SPKI_PINS와 네이티브 클라이언트 핀 목록에 추가하고, 이 인증서가 만료되는 {validTo} 전에 빌드를 배포하세요. docs/ops/tls-cert-pin.md를 참고하세요." + }, + "user": { + "telegramTestBody": "HealthLog: 연결에 성공했어요. Telegram 알림이 켜져 있어요." + }, + "moodReminder": { + "title": "매일 기분 알림", + "description": "그날 기분을 기록하지 않았을 때 정해둔 시각에 가볍게 알려줘요.", + "toggleAria": "매일 기분 알림 켜고 끄기", + "statusOn": "정해둔 시각에 켜짐", + "statusOff": "꺼짐", + "enabledToast": "기분 알림을 켰어요.", + "disabledToast": "기분 알림을 껐어요.", + "saveError": "저장하지 못했어요. 다시 시도해 주세요.", + "hourLabel": "알림 시각", + "hourAria": "기분 알림 시각", + "hourSavedToast": "알림 시각을 변경했어요", + "detail": "이미 설정한 채널(Telegram, ntfy, Web Push, Apple Push)로만 보내고, 오늘 기록을 남기면 알리지 않아요." + }, + "coachNudge": { + "title": "능동 코치", + "description": "데이터에 코치를 볼 만한 신호가 보이면 먼저 알려줘요.", + "toggleAria": "능동 코치 알림 켜고 끄기", + "statusOn": "켜짐", + "statusOff": "꺼짐", + "groupsLabel": "주제", + "groupMedication": "복약", + "groupMedicationDesc": "지난 한 주 복용이 들쭉날쭉할 때.", + "groupVitals": "바이탈", + "groupVitalsDesc": "혈압, 회복, 체중 추이, 수면.", + "groupRoutine": "루틴", + "groupRoutineDesc": "기록이 갑자기 끊겼을 때와 상황 점검.", + "frequencyLabel": "빈도", + "frequencyWeekly": "주 1회까지", + "frequencyBiweekly": "2주에 1회까지", + "frequencyAria": "알림 빈도 선택", + "enabledToast": "능동 코치 알림을 켰어요.", + "disabledToast": "능동 코치 알림을 껐어요.", + "saveError": "저장하지 못했어요. 나중에 다시 시도해 주세요.", + "suggestionsLabel": "예시 질문", + "suggestionsDesc": "코치 화면에 미리 준비된 예시 질문을 가끔 보여줘요.", + "suggestionsAria": "코치 예시 질문 켜고 끄기", + "aiComposedLabel": "코치가 알림 문구 작성", + "aiComposedDesc": "제공자를 쓸 수 있으면 고정 템플릿 대신 모델이 알림 문구를 써요. 기본값은 꺼짐이에요.", + "aiComposedAria": "AI가 쓴 능동 알림 켜고 끄기" + }, + "lowStock": { + "title": "재고 부족 알림", + "description": "남은 양이 이 기준보다 적으면 매일 알려줘요.", + "toggleAria": "재고 부족 알림 켜고 끄기", + "statusOn": "켜짐", + "statusOff": "꺼짐", + "daysLabel": "알림 기준 (일)", + "daysAria": "재고 부족 기준일 선택", + "leadLabel": "재주문 소요일 (일)", + "leadAria": "재주문 소요일", + "leadHelp": "설정한 일수에 복용 간격 하나를 더한 만큼 일찍 알려줘서 마지막 복용 전에 재처방이 도착하도록 해요. 개별 약은 재고 탭에서 다르게 설정할 수 있어요.", + "leadSavedToast": "재주문 소요일을 저장했어요", + "savedToast": "재고 부족 기준을 저장했어요", + "enabledToast": "재고 부족 알림을 켰어요.", + "disabledToast": "재고 부족 알림을 껐어요.", + "saveError": "저장하지 못했어요. 다시 시도해 주세요.", + "medicationsLink": "복약으로 이동", + "detail": "부족 건마다 한 번만 알리고, 재처방 후 다시 켜져요." + }, + "eventMedicationReminder": "복약 알림", + "eventMedicationReminderDesc": "예정된 복용 시각마다 보내요.", + "eventMeasurementAnomaly": "이상 측정값", + "eventMeasurementAnomalyDesc": "측정값이 평소 범위를 크게 벗어나면 보내요.", + "eventComplianceLow": "낮은 복약 순응도", + "eventComplianceLowDesc": "최근 며칠 복약 순응도가 80% 아래로 떨어지면 보내요.", + "eventWithingsSyncFailed": "Withings 동기화 실패", + "eventWithingsSyncFailedDesc": "Withings 동기화가 여러 번 연달아 실패하면 보내요.", + "eventSystemAlert": "시스템 공지", + "eventSystemAlertDesc": "HealthLog 서버에서 오는 일반 메시지예요. 예를 들어 점검 시간이나 계정 안내.", + "eventPersonalRecord": "개인 기록", + "eventPersonalRecordDesc": "측정값이 이전 최고 기록을 넘으면 보내요.", + "eventMoodReminder": "기분 알림", + "eventMoodReminderDesc": "지금 기분을 기록하도록 매일 가볍게 알려줘요.", + "eventCyclePeriodSoon": "생리 예정", + "eventCyclePeriodSoonDesc": "예측된 다음 생리 며칠 전에 보내요.", + "eventCyclePeriodConfirm": "생리가 시작됐나요?", + "eventCyclePeriodConfirmDesc": "예측된 시작일이 되도록 생리 기록이 없으면 가볍게 확인해요.", + "eventCycleFertileSoon": "가임기 예정", + "eventCycleFertileSoonDesc": "임신 준비가 목표일 때 예상 가임기 며칠 전에 보내요.", + "eventMedicationLowStock": "약 재고 부족", + "eventMedicationLowStockDesc": "추적 중인 약의 남은 양이 재고 부족 기준보다 적을 때", + "eventCoachNudge": "코치 알림", + "eventCoachNudgeDesc": "최근 추이가 점검할 만할 때 코치가 가끔 말을 걸어요.", + "eventMeasurementReminder": "측정 알림", + "eventMeasurementReminderDesc": "추적 중인 측정 시점이 되면 보내요.", + "eventMedicationIntakeSync": "복용 기록 동기화", + "eventMedicationIntakeSyncDesc": "복용을 기록하거나 복용·건너뜀으로 표시하면 기기 간에 업데이트해요.", + "eventDailyBriefing": "데일리 브리핑", + "eventDailyBriefingDesc": "그날의 요약을 아침에 한 번 차분하게 알려줘요. 켜기 전에는 보내지 않아요.", + "eventSecurityAlert": "새 로그인", + "eventSecurityAlertDesc": "처음 보는 기기나 장소에서 계정이 사용되면 보내요.", + "eventSharedRecordIntake": "기록을 공유한 사람이 복용을 표시할 때", + "eventSharedRecordIntakeDesc": "다른 사람이 표시했을 때만 보내고, 내 기록에는 보내지 않아요.", + "security": { + "newDeviceTitle": "계정에 새 로그인", + "newDeviceBody": "{device} ({location})에서 새로 로그인했어요. 본인이 아니라면 비밀번호를 바로 변경하세요.", + "unknownLocation": "알 수 없는 장소" + }, + "sharedRecordIntake": { + "title": "{name} 님이 복용을 표시했어요", + "bodyTaken": "{medication}: {name} 님이 복용으로 표시했어요.", + "bodySkipped": "{medication}: {name} 님이 건너뜀으로 표시했어요." + }, + "loginRequired": "알림을 관리하려면 로그인해 주세요.", + "someChannelsGloballyDisabled": "설정에서 꺼둔 채널이 있어서 그 채널로는 알림을 받지 못해요.", + "reminderSettingsLink": "알림 설정", + "channelsLink": "채널" + }, + "i18n": { + "maintainershipBanner": { + "notice": "이 언어는 코치의 안전 관련 지시문까지 기계 번역이에요. CI에서 자동 드리프트 검사를 돌리고 있어요. 코치 동작에 문제가 보이면 GitHub에 알려 주세요.", + "cta": "GitHub에 신고", + "dismiss": "닫기" + } + }, + "targets": { + "bmi": "BMI", + "average30d": "30일 평균:", + "consistency": { + "ariaLabel": "최근 7일, 하루별 목표 범위 상태", + "daysInRange": "{total}일 중 {count}일 범위 안", + "daysLogged": "{total}일 중 {count}일 기록", + "dotInRange": "범위 안", + "dotNearRange": "범위 근처", + "dotOutOfRange": "범위 밖", + "dotNotLogged": "기록 없음", + "relativeDay": { + "today": "오늘", + "yesterday": "어제", + "twoDaysAgo": "2일 전", + "threeDaysAgo": "3일 전", + "fourDaysAgo": "4일 전", + "fiveDaysAgo": "5일 전", + "sixDaysAgo": "6일 전" + } + }, + "edit": { + "title": "목표 범위 편집 — {metric}", + "description": "차트·인사이트·진료용 리포트에 쓰이는 목표 범위를 조정해요. 값 단위는 {unit}.", + "derivedHint": "이 카드는 다른 지표에서 계산돼요. 기준이 되는 목표(예: 혈압이나 체중)를 편집해 주세요.", + "minLabel": "최소", + "maxLabel": "최대", + "systolicMin": "수축기 최소", + "systolicMax": "수축기 최대", + "diastolicMin": "이완기 최소", + "diastolicMax": "이완기 최대", + "boundsHint": "허용 범위: {min}–{max} {unit}", + "resetToDefault": "기본값으로 되돌리기", + "resetTitle": "이 목표를 되돌릴까요?", + "resetBody": "범위가 기본값으로 돌아가요. 직접 설정한 값은 다른 곳에 저장되지 않아 되돌릴 수 없어요.", + "resetBodyBp": "수축기와 이완기 범위가 모두 기본값으로 돌아가요. 직접 설정한 값은 다른 곳에 저장되지 않아 되돌릴 수 없어요.", + "resetConfirm": "목표 되돌리기", + "saveSuccess": "목표 범위를 수정했어요", + "saveError": "목표 범위를 저장하지 못했어요", + "resetSuccess": "목표 범위를 기본값으로 되돌렸어요", + "validationOrder": "최소값은 최대값보다 작아야 해요.", + "validationBounds": "아래에 표시된 허용 범위 안의 값을 입력해 주세요." + }, + "currentValue": "현재: {value} {unit}", + "targetRangeValue": "목표: {min}–{max} {unit}", + "sourceLabel": "출처: {source}", + "inTarget": "목표 범위 안", + "aboveTarget": "목표보다 {delta} {unit} 높음", + "belowTarget": "목표보다 {delta} {unit} 낮음", + "diastolic": "이완기", + "glucoseFasting": "혈당 — 공복", + "glucosePostprandial": "혈당 — 식후", + "glucoseRandom": "혈당 — 무작위", + "glucoseBedtime": "혈당 — 취침 전", + "status": { + "underweight": "저체중", + "normal": "정상", + "overweight": "과체중", + "obesityGrade1": "비만 1단계", + "obesityGrade2": "비만 2단계", + "obesityGrade3": "비만 3단계", + "optimal": "최적", + "highNormal": "정상 높음", + "hypertensionGrade1": "고혈압 1기", + "hypertensionGrade2": "고혈압 2기", + "hypertensionGrade3": "고혈압 3기", + "belowTypicalRange": "일반 범위 미만", + "elevated": "높음", + "aboveTypicalRange": "일반 범위 초과", + "significantlyLow": "많이 낮음", + "slightlyLow": "약간 낮음", + "onTarget": "목표 범위", + "slightlyElevated": "약간 높음", + "significantlyElevated": "많이 높음", + "farTooShort": "많이 부족", + "tooShort": "부족", + "slightlyLong": "약간 과다", + "farTooLong": "많이 과다", + "belowEssential": "필수 지방 미만", + "essential": "필수 지방", + "athletic": "운동선수", + "fitness": "운동 수준", + "acceptable": "적정", + "obese": "비만", + "veryLow": "매우 낮음", + "lowActive": "저활동", + "moderatelyActive": "보통 활동", + "active": "활동적", + "veryActive": "매우 활동적", + "good": "좋음", + "moderate": "보통", + "low": "낮음", + "high": "높음", + "veryGood": "매우 좋음", + "veryStable": "매우 안정적", + "stable": "안정적", + "fluctuating": "변동 큼" + } + }, + "passwordStrength": { + "veryWeak": "매우 약함", + "weak": "약함", + "acceptable": "보통", + "strong": "강함", + "veryStrong": "매우 강함", + "tooShort": "너무 짧음", + "characters": "자", + "minLength": "최소 {count}자가 필요해요." + }, + "telegram": { + "errorInvalidAction": "올바르지 않은 동작이에요.", + "errorMedicationNotFound": "약을 찾지 못했어요.", + "errorMedicationInactive": "약을 찾지 못했거나 사용 중이 아니에요.", + "errorUnknownAction": "알 수 없는 동작이에요.", + "snoozeOneHour": "1시간", + "snoozeThreeHours": "3시간", + "snoozedFor": "{name} — {duration} 동안 미뤘어요.", + "skipped": "{name} — 건너뛰었어요.", + "alreadyRecorded": "{name} — 이미 기록돼 있어요.", + "recordedAsTaken": "{name} — 복용으로 기록했어요.", + "confirmed": "{name} — 확인했어요.", + "genericConfirmed": "확인했어요.", + "cancelled": "취소했어요.", + "moodLogged": "기록함: {score}/5", + "moodNotePrompt": "짧은 메모를 남겨 보세요(선택).", + "moodNoteSaved": "메모를 저장했어요.", + "moodNoteExpired": "그 요청은 만료됐어요.", + "remindLater": "{duration} 뒤로 알림을 미뤘어요.", + "measurementDone": "완료로 표시했어요.", + "measurementNotFound": "알림을 찾지 못했어요.", + "buttonMoodNote": "메모…", + "buttonLater": "나중에", + "buttonDone": "완료", + "noActiveMedications": "사용 중인 약이 없어요.", + "whichMedication": "어떤 약을 복용했나요?", + "cancelButton": "취소", + "askMedicationName": "약을 지정해 주세요. 예: taken 라미프릴.", + "medicationNotFoundExact": "약을 찾지 못했어요. HealthLog에 있는 이름 그대로 보내 주세요.", + "helpHeader": "사용할 수 있는 명령:", + "helpBody": "/help — 이 도움말 보기\n/add — 복용 기록하기\n/start — 봇 시작하기\n\n텍스트 명령:\ntaken <이름> — 복용 확인\n\n알림 속 버튼:\n• 복용 — 복용 확인\n• 🕐 1h / 🕐 3h — 알림 미루기\n• ⏭ 건너뛰기 — 복용 건너뛰기\n• ✓ 확인 — 놓친 복용 확인", + "measureValuePrompt": "값을 답장으로 보내면 기록해요(선택).", + "buttonMeasureValue": "값…", + "measureValueSaved": "측정값을 저장했어요.", + "measureValueInvalid": "측정값을 기록하려면 숫자로 답장해 주세요.", + "measureValueOutOfRange": "그 값은 가능한 범위를 벗어났어요.", + "measureValueExpired": "그 요청은 만료됐어요." + }, + "medicationReminders": { + "phaseGreenTitle": "🟢 알림: {medName}", + "phaseGreenMessage": "🟢 알림:\n{medName} ({doseInfo}, {timeWindow})\n{minutes}분 뒤에 시간대가 끝나요.", + "phaseYellowTitle": "🟡 곧 복용: {medName}", + "phaseYellowMessage": "🟡 곧 복용:\n{medName} ({doseInfo}, {timeWindow})\n{minutes}분 남았어요.", + "phaseOrangeTitle": "🟠 복용 시간 지남: {medName}", + "phaseOrangeMessage": "🟠 복용 시간 지남:\n{medName} ({doseInfo}, {timeWindow})\n{minutes}분 지났어요.", + "phaseRedTitle": "🔴 놓침: {medName}", + "phaseRedMessage": "🔴 놓침:\n{medName} ({doseInfo}, {timeWindow})\n놓친 것으로 기록했어요.", + "buttonTaken": "복용함", + "buttonConfirm": "✓ 확인", + "buttonSnoozeOneHour": "🕐 1시간", + "buttonSnoozeThreeHours": "🕐 3시간", + "buttonSkip": "⏭ 건너뛰기" + }, + "lowStockReminders": { + "title": "재고 부족: {medName}", + "bodyToday": "{medName}: {units}개 남았어요 — 하루치도 안 돼요.", + "bodyReorder": "{medName}: {units}개 남았어요. {runsOutOn}쯤 소진돼요 — {reorderBy}까지 재주문하세요.", + "bodyLastDose": "{medName}: {units}개 남았어요 — 마지막 복용분이에요. {runsOutOn}쯤 소진돼요.", + "bodyExpired": "{medName}: 쓸 수 있는 재고가 없어요. {units}개가 유효기간 만료로 표시돼 있어요." + }, + "moodReminders": { + "dailyTitle": "기분 기록하기", + "dailyBody": "오늘 기분은 어때요?" + }, + "coachNudges": { + "greetingMorning": "좋은 아침이에요", + "greetingMorningNamed": "좋은 아침이에요, {name}님", + "greetingHey": "안녕하세요", + "greetingHeyNamed": "안녕하세요, {name}님", + "complianceBody": "이번 주에는 복약 루틴이 조금 흐트러졌어요. 원하시면 어떻게 해야 더 쉽게 이어갈 수 있을지 같이 살펴볼게요.", + "bpBody": "이번 주 혈압이 평소 범위보다 조금 높게 나오고 있어요. 준비되시면 언제든 같이 살펴볼게요.", + "scoreBody": "최근 며칠 회복이 조금씩 떨어지고 있어요. 이유가 궁금하시면 언제든 같이 이야기해요.", + "selfContextBody": "자기소개를 업데이트한 지 좀 됐어요. 시간 나실 때 새로 적어 주시면 제가 더 잘 이해할 수 있어요.", + "weightBody": "최근 체중이 목표 범위에서 조금 벗어났어요. 원하시면 차분하게 같이 점검해 봐요.", + "sleepDebtBody": "이번 주에 잠이 부족했던 날이 여러 번 있었어요. 원하시면 같이 패턴을 찾아봐요.", + "measurementGapBody": "일주일쯤 기록이 비어 있어요. 편하실 때 가볍게 한 번 남기면 다시 이어가기 좋아요.", + "focusNote": "직접 챙겨보고 싶다고 하신 부분과도 연결돼요.", + "reminderSurfacedTitle": "부탁하신 알림이에요", + "reminderSurfacedLeadOne": "오늘 다시 알려 달라고 하신 내용이에요:", + "reminderSurfacedLeadMany": "오늘 다시 알려 달라고 하신 내용 {count}가지예요:", + "questionConditions": "코치가 알아야 할 만성 질환이 있나요?", + "questionFocus": "코치가 특별히 신경 써야 할 부분이 있나요?", + "questionAllergies": "알레르기나 불내증이 있나요?", + "questionAboutMe": "코치에게 자신에 대해 조금 알려줄래요 — 생활 루틴, 목표, 상황 같은 것?" + }, + "clinicianView": { + "title": "공유된 건강 기록", + "period": "보고 기간: {start} – {end}", + "expires": "이 링크는 {date}에 만료돼요.", + "provenance": "본인이 직접 기록한 HealthLog 데이터에서 내보낸 읽기 전용 요약이에요. 값은 본인이 기록한 것이고 기기에서 동기화된 측정값이 섞여 있을 수 있어요. 임상 검사가 아니라 환자가 보고한 자료로 봐 주세요.", + "labs": "검사 수치", + "medications": "복약 및 복약 순응도", + "bmi": "체질량지수(BMI)", + "statSummary": "최근 {latest} (평균 {avg}, 범위 {min}–{max})", + "adherence": "복약 순응도 {rate}", + "noAdherence": "—", + "glucose": { + "FASTING": "혈당 (공복)", + "POSTPRANDIAL": "혈당 (식후)", + "RANDOM": "혈당 (임의)", + "BEDTIME": "혈당 (취침 전)" + }, + "wellness": { + "title": "웰니스 점수", + "disclaimer": "기록된 신호로 계산한 서술적 점수(0–100)예요. 임상 평가나 진단이 아니에요.", + "recovery": "회복 점수", + "stress": "스트레스 점수", + "strain": "부하 점수", + "score": "웰니스 점수" + }, + "footer": "HealthLog에서 공유했어요 — 셀프 호스팅 개인 건강 기록 앱.", + "unlock": { + "title": "암호 입력", + "description": "이 공유 건강 기록은 보호돼 있어요. 전달받은 암호를 입력해 주세요.", + "label": "암호", + "error": "암호가 맞지 않아요.", + "submit": "잠금 해제" + }, + "loadError": { + "title": "공유 화면을 불러오지 못했어요", + "description": "공유된 건강 기록을 여는 중 문제가 생겼어요. 링크가 만료됐거나 일시적인 문제일 수 있어요. 다시 시도하거나 공유한 사람에게 새 링크를 요청해 주세요.", + "retry": "다시 시도" + }, + "documents": { + "title": "문서", + "untitled": "제목 없는 문서", + "download": "다운로드", + "attachmentHint": "이 파일은 기기로 다운로드돼요 (페이지 안에서는 미리보기가 없어요).", + "exifNote": "공유된 JPEG, PNG, WebP 이미지에서 카메라 메타데이터(EXIF/GPS)를 제거했어요." + }, + "downloadPdf": "PDF로 다운로드", + "downloadFhir": "FHIR로 다운로드", + "bmiSection": "체질량지수", + "sectionEmpty": "이 링크에 포함돼 있지만 여기에 기록된 내용이 없어요.", + "sectionUnavailable": "이 링크에 포함돼 있지만, 원본 계정에서 이 부분이 꺼져 있어 담긴 데이터가 없어요.", + "lastInjection": "마지막 주사", + "identity": { + "name": "이름" + }, + "doses": { + "title": "기록된 복용", + "showing": "기록된 복용 {total}건 중 최근 {shown}건을 보여줘요." + } + }, + "cycle": { + "symptomCategory": { + "physical": "신체", + "emotional": "감정", + "digestive": "소화", + "custom": "사용자 지정" + }, + "symptom": { + "cramps": "생리통", + "headache": "두통", + "bloating": "복부 팽만", + "acne": "여드름", + "breastTenderness": "가슴 통증", + "fatigue": "피로", + "backPain": "허리 통증", + "insomnia": "불면", + "libidoHigh": "성욕 증가", + "libidoLow": "성욕 감소", + "moodSwings": "감정 기복", + "foodCravings": "식욕 증가", + "nausea": "메스꺼움", + "diarrhea": "설사", + "constipation": "변비", + "custom": { + "add": "증상 추가", + "label": "이름", + "labelPlaceholder": "예: 어지러움", + "icon": "아이콘", + "limitReached": "직접 추가한 증상이 한도에 찼어요.", + "remove": "{label} 삭제", + "removeTitle": "이 증상을 삭제할까요?", + "removeBody": "'{label}' 항목이 증상 목록에서 사라져요. 이미 표시해 둔 날에는 그대로 남지만, 다시 고를 수는 없어요.", + "removeConfirm": "삭제", + "fallbackLabel": "직접 추가한 증상" + } + }, + "prediction": { + "disclaimer": "예측은 기록한 데이터로 추정한 값일 뿐, 의학적 조언이나 피임 수단이 아니에요. 주기를 더 많이 기록할수록 정확해져요." + }, + "insights": { + "headline": { + "title": "내 주기와 내 몸", + "restingHeartRate": "황체기의 안정 시 심박수가 난포기보다 약 {delta}{unit} {dir}.", + "heartRateVariability": "황체기의 심박변이도가 난포기보다 약 {delta}{unit} {dir}.", + "generic": "황체기의 {metric} 값이 난포기보다 약 {delta}{unit} {dir}.", + "dirHigher": "높아요", + "dirLower": "낮아요", + "empty": "아직 주기와 측정값이 부족해서 단계별 패턴을 보여줄 수 없어요. 계속 기록하면 기록이 쌓이면서 채워져요." + }, + "crosstab": { + "description": "내 데이터를 기준으로, 각 측정값이 황체기와 난포기에서 어떻게 다른지 비교했어요.", + "pairLabel": "{metric} · 황체기와 난포기 비교", + "detail": "황체기 {lutealAvg}{unit} ({lutealDays}일), 난포기 {follicularAvg}{unit} ({follicularDays}일).", + "metricRestingHeartRate": "안정 시 심박수", + "metricHrv": "심박변이도", + "metricSleepDuration": "수면 시간", + "metricSteps": "걸음 수", + "metricWeight": "체중", + "metricBasalBodyTemp": "기초 체온", + "metricWristTemperature": "야간 손목 온도", + "metricSkinTemperature": "피부 온도", + "metricBloodGlucose": "혈당", + "metricMood": "기분", + "unitHours": "h", + "unitSteps": "걸음", + "unitBpm": "bpm", + "unitMs": "ms", + "unitKg": "kg", + "unitCelsius": "°C", + "unitGlucose": "mg/dL", + "unitMood": "점", + "confidenceLow": "신뢰도 낮음", + "confidenceMedium": "신뢰도 보통", + "confidenceHigh": "신뢰도 높음" + }, + "title": "주기 인사이트", + "symptomPatternsTitle": "단계별 증상 패턴", + "symptomPatternsEmpty": "한 주기 동안 증상을 기록하면 어느 단계에 몰리는지 볼 수 있어요.", + "symptomMostlyIn": "주로 {phase}에 ({count}/{total}일)" + }, + "phaseEducation": { + "title": "지금 일어나는 일", + "whatsHappening": { + "MENSTRUAL": "생리는 자궁내막이 떨어져 나가면서 새 주기가 시작되는 시기예요. 첫 며칠은 활력과 기분이 가라앉기 쉬워요.", + "FOLLICULAR": "생리가 끝나면 몸이 난자를 준비하면서 에스트로겐이 서서히 올라가요. 이 구간에는 활력이 더 안정적이라고 느끼는 사람이 많아요.", + "OVULATORY": "주기 중반쯤 난자가 배출되고 짧은 기간 동안 가임력이 가장 높아져요. 네 단계 중 가장 짧은 시기예요.", + "LUTEAL": "배란 후에는 프로게스테론이 올라가면서 몸이 안정돼요. 다음 생리 전 며칠 동안 월경전 변화를 느끼는 사람도 있어요." + }, + "context": { + "MENSTRUAL": "호르몬 수치가 가장 낮아서 이 며칠은 조용한 재정비처럼 느껴질 수 있어요. 생리통이나 얕은 잠, 속도를 늦추고 싶은 마음을 느끼는 사람도 있고, 별 변화 없이 지나가는 사람도 있어요. 모두 정상 범위이고, 느낌은 주기마다 달라질 수 있어요.", + "FOLLICULAR": "에스트로겐이 올라가면서 이 며칠을 밝고 열린 느낌이라고 말하는 사람이 많아요. 잠도 더 쉽게 들고 머리도 맑아지죠. 기초 체온은 보통 배란 직전까지 낮은 구간에 머물러요. 이 단계의 길이는 사람마다, 주기마다 가장 많이 달라져요.", + "OVULATORY": "난자가 배출되기 직전에 에스트로겐이 정점을 찍고, 하루 이틀 뒤 기분이 잠깐 좋아지거나 기초 체온이 조금 오르기도 해요. 이 무렵 자궁경부 점액은 더 맑고 잘 늘어나는 형태가 돼요. 한쪽에 짧게 찌릿한 느낌을 느끼는 사람도 있고 전혀 못 느끼는 사람도 많은데, 둘 다 정상이에요.", + "LUTEAL": "후반부는 프로게스테론이 이끌고, 기초 체온은 보통 주기 초반보다 조금 높은 자리에 머물러요. 날이 갈수록 잠·식욕·기분·활력이 달라지는 걸 느끼는 사람도 있고, 내내 안정적인 사람도 있어요. 양상은 사람마다 다르고, 달마다 달라 보이기도 해요." + }, + "commonHere": "이 시기에 자주 느끼는 것", + "stillLearning": "아직 주기를 배우는 중이에요. 계속 기록하면 내 기록에서 나온 패턴으로 채워져요." + }, + "insightsSummary": { + "title": "내 주기", + "currentDay": "주기 {day}일째", + "noActiveCycle": "아직 진행 중인 주기가 없어요", + "viewDetails": "주기 인사이트 보기" + }, + "wellnessRing": { + "label": "주기" + }, + "disclaimer": "이 예측은 기록한 데이터로 추정한 설명일 뿐, 피임 방법도 의학적 조언도 아니에요. 피임 없는 성관계가 안전하다는 뜻은 절대 아니에요. 피임이나 의학적 결정은 의료 전문가와 상의해 주세요.", + "title": "내 주기", + "subtitle": "생리와 증상을 기록하고, 다음에 무엇이 올지 확인해 보세요.", + "tabs": { + "calendar": "캘린더", + "predictions": "예측", + "insights": "인사이트", + "settings": "설정" + }, + "logToday": "오늘 기록", + "startedPeriod": "오늘 생리를 시작했어요", + "endedPeriod": "오늘 생리가 끝났어요", + "phase": { + "MENSTRUAL": "생리기", + "FOLLICULAR": "난포기", + "OVULATORY": "배란기", + "LUTEAL": "황체기", + "none": "단계 없음", + "label": "단계" + }, + "flow": { + "NONE": "없음", + "SPOTTING": "점상 출혈", + "LIGHT": "적음", + "MEDIUM": "보통", + "HEAVY": "많음", + "label": "생리량" + }, + "ovulationTest": { + "NEGATIVE": "음성", + "POSITIVE_LH_SURGE": "LH 급상승", + "ESTROGEN_SURGE": "에스트로겐 급상승", + "INDETERMINATE": "판정 불가", + "label": "배란 테스트" + }, + "mucus": { + "DRY": "건조함", + "STICKY": "끈적함", + "CREAMY": "크림 같음", + "WATERY": "물 같음", + "EGG_WHITE": "달걀흰자 같음", + "label": "자궁경부 점액" + }, + "cervixPosition": { + "LOW": "낮음", + "HIGH": "높음" + }, + "cervixFirmness": { + "FIRM": "단단함", + "SOFT": "부드러움" + }, + "cervixOpening": { + "CLOSED": "닫힘", + "OPEN": "열림" + }, + "goal": { + "GENERAL_HEALTH": "전반적인 건강", + "AVOID_PREGNANCY": "임신 피하기", + "TRYING_TO_CONCEIVE": "임신 준비", + "PERIMENOPAUSE": "폐경 이행기", + "OFF": "추적 끄기", + "label": "무엇을 위해 기록하나요?", + "description": "이 설정에 따라 문구와 보이는 예측이 달라져요. 가임기 안내는 임신을 준비할 때만 보여요." + }, + "ring": { + "dayOfCycle": "{day}일째", + "dayUnknown": "—", + "ariaPhase": "주기 {day}일째, {phase} 단계", + "ariaUnknown": "아직 진행 중인 주기가 없어요", + "caption": "주기 중", + "firstPeriodCta": "첫 생리 기록하기", + "periodOverdue": "생리가 평소보다 늦어지고 있어요 — 다음 생리를 기록할 때까지 날짜 세기를 멈춰요.", + "periodOverdueDays": "생리가 평소보다 {days}일 늦어지고 있어요 — 다음 생리를 기록할 때까지 날짜 세기를 멈춰요.", + "logPeriodCta": "생리 기록" + }, + "calendar": { + "legendPeriod": "기록한 생리", + "legendPredicted": "예상 생리", + "legendFertile": "가임기", + "legendOvulation": "배란", + "legendSymptoms": "기록한 증상", + "prevMonth": "이전 달", + "nextMonth": "다음 달", + "loading": "캘린더를 불러오는 중…", + "learning": "주기를 배우는 중이에요 — 전체 주기를 세 번 이상 기록하면 가임기, 배란, 단계가 채워져요. 그때까지 캘린더에는 기록한 것만 보여요.", + "legendSymptom": "증상", + "legendOvulationConfirmed": "확인된 배란", + "flowSpotting": "점상 출혈", + "flowLight": "적음", + "flowMedium": "보통", + "flowHeavy": "많음" + }, + "predictions": { + "title": "다음에 올 일", + "nextPeriod": "다음 생리", + "nextPeriodWindow": "{low} – {high}", + "fertileWindow": "가임기", + "ovulation": "예상 배란일", + "confidenceHigh": "높음", + "confidenceMedium": "보통", + "confidenceLow": "낮음", + "stillLearning": "아직 배우는 중", + "stillLearningBody": "전체 주기를 세 번 이상 기록하면 예측이 더 정확해져요. 그때까지는 대략적인 추정이에요.", + "rawMode": "예측 꺼짐", + "rawModeBody": "예측을 꺼 두셨어요 — 예보 없이 기록한 데이터만 보여요.", + "none": "아직 예측이 없어요. 생리를 기록하고 시작해 보세요.", + "ovulationConfirmed": "배란 (확인됨)" + }, + "history": { + "title": "내 주기 기록", + "avgLength": "평균 길이", + "variability": "변동성", + "avgPeriodLength": "평균 생리 기간", + "regularity": "규칙성", + "regularityREGULAR": "규칙적", + "regularityIRREGULAR": "불규칙", + "regularityLEARNING": "학습 중", + "days": "{count}일", + "daysPlusMinus": "± {count}일", + "none": "아직 기록된 주기가 없어요.", + "recentCycles": "최근 주기", + "ongoing": "진행 중", + "periodDays": "생리 {count}일", + "ovulationChip": "배란", + "chartTitle": "주기 길이 변화", + "chartCaption": "막대 하나가 주기 하나예요. 아래쪽 분홍색은 생리 기간, 나머지는 주기 길이예요.", + "chartAria": "최근 주기 {count}개의 주기 길이 차트예요. 평균 {avg}일, 규칙성은 {regularity}.", + "legendPeriodSegment": "생리 일수", + "legendRestSegment": "나머지 기간" + }, + "sheet": { + "title": "하루 기록하기", + "description": "이 날의 생리량, 증상, 메모예요.", + "flow": "생리량", + "symptoms": "증상", + "temperature": "기초 체온", + "temperaturePlaceholder": "°C", + "temperatureDisturbed": "측정값을 방해받음으로 표시", + "temperatureDisturbedHint": "발열, 아픈 날, 음주, 늦은 측정 — 체온 평가에서 제외돼요.", + "ovulationTest": "배란 테스트", + "mucus": "자궁경부 점액", + "cervix": "자궁경부", + "cervixPosition": "위치", + "cervixFirmness": "굳기", + "cervixOpening": "열림 정도", + "intercourse": "성관계", + "protected": "피임함", + "intermenstrualBleeding": "생리 사이 출혈", + "pregnancyTest": "임신 테스트", + "progesteroneTest": "프로게스테론 검사", + "contraceptive": "피임", + "severity": "심각도", + "severityLevel": "심각도 {level}/4", + "delete": "삭제", + "deleteTitle": "이 날 기록을 삭제할까요?", + "deleteBody": "이 날 기록한 생리량, 체온, 증상, 메모가 모두 사라져요. 되돌릴 방법은 없어요.", + "deleteConfirm": "이 날 삭제", + "note": "메모", + "notePlaceholder": "오늘 기억해 두고 싶은 것…", + "save": "저장", + "saving": "저장 중…", + "cancel": "취소", + "saveError": "저장하지 못했어요. 다시 시도해 주세요.", + "temperatureSection": "체온 및 배란", + "intimacySection": "성관계 및 피임", + "testsSection": "검사", + "phaseDayHeader": "{day}일째 · {phase}", + "bbtHint": "아침에 일어나기 전, 가장 먼저 재 주세요.", + "titleForDate": "{date} 기록", + "descriptionToday": "오늘. 생리량, 증상, 메모예요.", + "deleteBodyCycleStart": "이 날은 주기가 시작되는 날이에요. 삭제하면 생리 시작도 함께 사라져서 이전 주기가 그대로 이어져요. 여기 기록한 생리량, 체온, 증상, 메모도 모두 사라지고요. 되돌릴 방법은 없어요." + }, + "testResult": { + "POSITIVE": "양성", + "NEGATIVE": "음성", + "INDETERMINATE": "불명확" + }, + "settings": { + "title": "주기 설정", + "typicalCycleLength": "평소 주기 길이 (일)", + "typicalPeriodLength": "평소 생리 기간 (일)", + "lutealPhaseLength": "황체기 길이 (일)", + "priorsDescription": "이 값은 주기를 많이 기록하기 전까지 예측을 돕는 사전 정보예요. 비워 두면 기본값을 써요.", + "rawChartMode": "원본 차트 모드", + "rawChartModeDescription": "예측이나 해석 없이 기록한 데이터만 보여줘요.", + "secondarySymptomLabel": "보조 가임 징후", + "secondarySymptomDescription": "증상체온법 이중 확인에서 체온과 짝을 이루는 두 번째 징후예요. 대부분 자궁경부 점액을 기록하지만, 원하면 자궁경부 관찰로 바꿀 수 있어요.", + "secondarySymptomCitation": "이 방법에 대해 읽어보기 (연구)", + "secondarySymptom": { + "MUCUS": "자궁경부 점액", + "CERVIX": "자궁경부 관찰" + }, + "prediction": "예측", + "predictionDescription": "다음 생리를, 필요하면 가임기까지 예측해요.", + "encryption": "민감한 기록 암호화", + "encryptionDescription": "항상 암호화되는 메모에 더해, 의도가 드러나는 항목(임신 테스트, 피임, 성관계)도 저장할 때 암호화해요.", + "discreet": "내용 감춘 알림", + "discreetDescription": "잠금 화면 알림 문구에서 주기 내용을 숨겨요.", + "saved": "설정을 저장했어요", + "saveError": "저장하지 못했어요. 다시 시도해 주세요.", + "save": "저장", + "reminders": "알림", + "remindersDescription": "생리 알림은 알림 설정에서 채널별로 켤 수 있어요.", + "remindersLink": "알림 설정", + "fertileReminder": "가임기 알림", + "fertileReminderDescription": "예상 가임기 며칠 전에 미리 알려줘요. 기본은 꺼짐이고, 알림 설정에서 채널별로 켤 수 있어요." + }, + "contraceptive": { + "NONE": "없음", + "UNSPECIFIED": "기타", + "IMPLANT": "피하 이식", + "INJECTION": "주사", + "IUD": "자궁내장치", + "INTRAVAGINAL_RING": "링", + "ORAL": "경구 피임약", + "PATCH": "패치", + "EMERGENCY": "응급 피임약" + }, + "loadError": "주기 데이터를 불러오지 못했어요.", + "saveError": "주기 기록을 저장하지 못했어요.", + "deleteError": "기록을 삭제하지 못했어요.", + "bbt": { + "title": "기초 체온", + "caption": "이번 주기의 아침 체온이에요. 배란 후에는 약 0.2°C 상승이 이어져요.", + "empty": "아직 체온 기록이 없어요", + "emptyHint": "매일 아침 기초 체온을 기록하면 주기의 체온 변화를 볼 수 있어요.", + "ovulationEstimate": "예상 배란", + "tooltipTemp": "{temp}°C", + "ovulationConfirmed": "확인된 배란" + }, + "fieldInfo": { + "bbt": "아침에 일어나기 전 가장 먼저 재는 안정 시 체온이에요. 배란 후에는 약 0.2~0.3°C 올라가요.", + "bbtLabel": "기초 체온이 뭔가요?", + "mucus": "질 분비물은 주기에 따라 달라져요 — 건조하거나 끈적한 상태에서 크림 같은 상태로, 가장 가임력이 높은 날 무렵에는 물 같고 달걀흰자 같은 상태로 바뀌어요.", + "mucusLabel": "자궁경부 점액이 뭔가요?", + "ovulationTest": "LH 급상승을 잡아내는 가정용 소변 검사예요. LH는 배란 약 24~36h 전에 치솟는 호르몬이고, 양성이면 가장 가임력이 높은 시기라는 뜻이에요.", + "ovulationTestLabel": "배란 테스트가 뭔가요?", + "spotting": "생리 기간이 아닐 때 나오는 소량 출혈이에요.", + "spottingLabel": "생리 사이 출혈이 뭔가요?", + "pregnancyTest": "가정용 임신(hCG) 테스트 결과예요.", + "pregnancyTestLabel": "임신 테스트가 뭔가요?", + "progesteroneTest": "배란이 일어났는지 확인하는 검사예요 — 배란 후에 프로게스테론이 올라가요.", + "progesteroneTestLabel": "프로게스테론 검사가 뭔가요?", + "intercourse": "이 날 성관계가 있었는지 기록해요.", + "intercourseLabel": "성관계 항목은 무엇을 기록하나요?", + "protected": "성관계 때 피임을 했는지 여부예요.", + "protectedLabel": "'피임함'은 무슨 뜻인가요?" + }, + "saveSuccess": "기록을 저장했어요", + "deleteSuccess": "기록을 삭제했어요", + "periodStartSaved": "생리 시작을 저장했어요", + "periodEndSaved": "생리 종료를 저장했어요", + "startedPeriodOnDate": "이 날 생리를 시작했어요", + "endedPeriodOnDate": "이 날 생리가 끝났어요" + }, + "cycleReminders": { + "periodSoonTitle": "생리가 곧 시작될 것 같아요", + "periodSoonBody": "다음 생리가 이틀쯤 뒤로 예측돼요.", + "periodConfirmTitle": "생리가 시작됐나요?", + "periodConfirmBody": "지금쯤 생리가 시작될 것으로 예측됐어요. 기록하면 예측이 더 정확해져요.", + "fertileSoonTitle": "가임기가 곧 시작돼요", + "fertileSoonBody": "예상 가임기가 이틀쯤 뒤에 시작될 것으로 예측돼요. 이건 추정이고 의학적·피임 보장이 아니에요.", + "discreetTitle": "HealthLog 알림", + "discreetBody": "HealthLog에 알림이 있어요." + }, + "dataList": { + "allSources": "모든 출처", + "dateFrom": "시작", + "dateTo": "종료", + "dateRange": "기간", + "valueRange": "값", + "valueMin": "최소", + "valueMax": "최대", + "typeLabel": "종류", + "sourceLabel": "출처", + "resetFilters": "초기화", + "clearFilter": "필터 해제: {label}", + "selectAll": "이 페이지 전체 선택", + "selectRow": "행 선택", + "selected": "{count}개 선택됨", + "deleteN": "삭제 ({count})", + "clearSelection": "선택 해제", + "selectionBarLabel": "선택 동작" + }, + "measurementReminders": { + "sectionTitle": "건강검진", + "sectionDescription": "무엇을 언제 어디서 측정하거나 확인할지 알려 주는 알림이에요.", + "markDone": "완료", + "delete": "삭제", + "deleteConfirmTitle": "알림을 삭제할까요?", + "deleteConfirmDescription": "이 건강검진 알림이 완전히 삭제돼요.", + "postpone": { + "menuItem": "미루기…", + "title": "예정일 미루기", + "description": "이번 예정일만 옮겨요 — 반복 주기는 그대로예요.", + "chip": "+{days}일", + "dateLabel": "새 날짜", + "confirm": "미루기" + }, + "skip": { + "heading": "이번 예정일 건너뛰기", + "explainer": "완료가 아니라 건너뜀으로 기록해요. 주기는 오늘부터 다시 시작해요.", + "action": "건너뛰기", + "confirmTitle": "이번 예정일을 건너뛸까요?" + }, + "snoozedUntil": "{date}까지 미룸", + "skippedOn": "{date}에 건너뜀", + "skipCount": "{count}회 건너뜀", + "disabledBadge": "꺼짐", + "nextDue": { + "none": "예정 없음", + "today": "오늘 예정", + "tomorrow": "내일 예정", + "inDays": "{days}일 후" + }, + "overdueByDays": "{days}일 지남", + "cadence": { + "everyNDays": "{days}일마다", + "custom": "사용자 지정 일정", + "customInterval": "사용자 지정(N일마다)", + "rruleOption": "사용자 지정 규칙(RRULE)" + }, + "location": { + "prefix": "장소: {location}" + }, + "types": { + "WEIGHT": "체중", + "BLOOD_PRESSURE_SYS": "혈압", + "PULSE": "맥박", + "BLOOD_GLUCOSE": "혈당", + "OXYGEN_SATURATION": "산소포화도", + "BODY_FAT": "체지방", + "BODY_TEMPERATURE": "체온", + "FAT_MASS": "체지방량", + "FAT_FREE_MASS": "제지방량", + "MUSCLE_MASS": "근육량", + "LEAN_BODY_MASS": "제지방 체중", + "BONE_MASS": "골량", + "TOTAL_BODY_WATER": "체수분", + "VISCERAL_FAT": "내장지방", + "BODY_MASS_INDEX": "체질량지수(BMI)", + "WAIST_CIRCUMFERENCE": "허리둘레", + "PHQ9_SCORE": "PHQ-9 · 기분", + "GAD7_SCORE": "GAD-7 · 걱정", + "WHO5_SCORE": "WHO-5 · 웰빙", + "SCI_SCORE": "SCI · 수면" + }, + "typeGroups": { + "vitals": "활력징후", + "bodyComposition": "체성분", + "mentalWellbeing": "정신 건강" + }, + "empty": { + "title": "아직 알림이 없어요", + "description": "혈압을 주기적으로 재거나 매년 혈액 검사를 받도록 알림을 추가해 보세요." + }, + "form": { + "label": "이름", + "labelPlaceholder": "예: 혈압 측정", + "kind": "종류", + "kindType": "측정과 연결", + "kindFreeText": "직접 입력 점검", + "measurementType": "측정", + "cadence": "반복", + "notifyHour": "알림 시각", + "location": "장소(선택)", + "locationPlaceholder": "예: 동네 병원, 검사실", + "save": "저장", + "createTitle": "새 알림", + "editTitle": "알림 편집", + "customInterval": "반복 간격(일)", + "rrule": "반복 규칙(RRULE)", + "rruleHint": "RFC-5545 형식이에요. 매년 점검은 FREQ=YEARLY.", + "firstDue": "첫 예정일(선택)" + }, + "pushTitle": "건강검진 알림", + "pushBody": "이제 할 시간이에요: {label}", + "pushLocation": "(장소: {location})", + "edit": "편집", + "originCoach": "코치", + "until": "{date}까지", + "selfPlanned": "직접 설정", + "captureValue": "값 기록", + "measureNow": "지금 측정", + "startCheckIn": "체크인 시작", + "nextDueLabel": "다음 예정:", + "lastDoneLabel": "마지막 완료:", + "customize": "건강검진 설정", + "adherence": { + "label": "주기 내", + "onTrack": "정상 진행", + "due": "지금 예정" + }, + "capture": { + "title": "측정값 기록", + "description": "측정값을 입력하면 알림이 완료로 표시돼요." + }, + "manage": { + "title": "알림 관리", + "description": "알림을 하나씩 켜거나 끌 수 있어요.", + "empty": "아직 설정할 알림이 없어요." + }, + "dashboard": { + "empty": "예정된 건강검진 알림이 없어요.", + "openLink": "건강검진 열기" + }, + "showMeasurements": "측정값 보기", + "loadError": "알림을 불러오지 못했어요.", + "fileVisit": "진료 기록" + }, + "moduleList": { + "viewToggleLabel": "보기", + "viewCards": "카드 보기", + "viewList": "목록 보기", + "viewHeading": "보기", + "viewDescription": "기록을 카드로 볼지 간결한 목록으로 볼지 선택하세요.", + "reorder": { + "heading": "순서", + "description": "기록을 끌어서 표시 순서를 정하세요.", + "empty": "아직 순서를 바꿀 기록이 없어요." + } + }, + "labs": { + "title": "검사 결과", + "subtitle": "혈액검사와 바이오마커 측정값", + "addResult": "값 추가", + "addDescription": "검사 결과지의 바이오마커 측정값을 기록해요.", + "addFirst": "첫 결과 추가", + "emptyTitle": "아직 검사 결과가 없어요", + "emptyDescription": "HbA1c, LDL, 페리틴 같은 혈액검사 수치를 기록하면 추이를 보고 의사와 공유할 수 있어요.", + "referenceLabel": "참고 범위", + "readingsCount": "측정값 {count}개", + "deleteConfirmTitle": "검사 결과를 삭제할까요?", + "deleteConfirmDescription": "이 검사 결과를 삭제해요. 삭제 직후 잠깐은 되돌릴 수 있어요.", + "deletedToast": "결과를 삭제했어요", + "deleteError": "결과를 삭제하지 못했어요", + "loadError": "검사 결과를 불러오지 못했어요", + "customize": "검사 화면 설정", + "localOcr": { + "heading": "이 기기에서 스캔 읽기", + "description": "사진을 이 기기에서 읽고, 추출한 텍스트만 제공자에게 보내요.", + "detail": "AI 제공자가 이미지를 읽지 못할 때 유용해요. 정확도가 낮으니 저장 전에 모든 값을 확인하세요." + }, + "sort": { + "heading": "정렬 순서", + "label": "값 정렬 기준", + "recentDesc": "최근 측정값 먼저", + "recentAsc": "오래된 측정값 먼저", + "alphaAsc": "이름 (A–Z)", + "alphaDesc": "이름 (Z–A)", + "manual": "직접 정렬", + "hint": "직접 정렬을 고르면 아래 바이오마커를 끌어서 원하는 순서로 놓을 수 있어요." + }, + "range": { + "inRange": "범위 내", + "below": "범위 아래", + "above": "범위 위" + }, + "form": { + "value": "값", + "takenAt": "검체 채취일", + "note": "메모", + "notePlaceholder": "이 결과에 대한 메모 (선택)", + "requiredError": "값을 입력해 주세요.", + "save": "저장", + "saveAndAddAnother": "저장하고 계속 추가", + "savedToast": "결과를 저장했어요", + "saveError": "결과를 저장하지 못했어요", + "biomarker": "바이오마커", + "biomarkerPlaceholder": "바이오마커 선택", + "defineNew": "+ 새 바이오마커 정의", + "noBiomarkersHint": "값을 기록하려면 바이오마커를 먼저 정의해 주세요.", + "pickBiomarkerError": "바이오마커를 먼저 선택해 주세요.", + "resultType": "결과 유형", + "numeric": "수치", + "qualitative": "정성", + "qualitativeResult": "결과", + "qualitativePlaceholder": "예: 음성", + "qualNegative": "음성", + "qualPositive": "양성", + "qualBorderline": "경계", + "sourceRange": "결과지에 적힌 범위", + "sourceRangePlaceholder": "예: 3.5 - 5.0 또는 < 5", + "sourceRangeAdd": "+ 결과지의 범위 추가", + "sourceRangeHint": "선택 사항이에요. 이 측정값에만 적용되고, 저장된 범위는 그대로예요.", + "sourceRangeUnparsed": "적은 그대로 뒀어요 — 수치 범위를 읽지 못했어요." + }, + "backToLabs": "검사 결과로 돌아가기", + "hasNote": "메모", + "editTitle": "측정값 편집", + "editDescription": "이 측정값의 값, 날짜, 메모를 고쳐요.", + "editedToast": "측정값을 수정했어요", + "editError": "측정값을 수정하지 못했어요", + "editTimestampError": "올바른 날짜를 입력해 주세요.", + "editReading": "측정값 편집", + "deleteReading": "측정값 삭제", + "restoredToast": "측정값을 되돌렸어요", + "restoreError": "측정값을 되돌리지 못했어요", + "detail": { + "emptyTitle": "아직 측정값이 없어요", + "emptyDescription": "값을 추가하면 이 바이오마커의 추이를 볼 수 있어요.", + "title": "바이오마커", + "genericDescription": "기록 중인 검사 바이오마커예요. 측정값을 추가하면 참고 범위와 비교한 추이를 볼 수 있어요.", + "valuesTitle": "{marker} — 전체 측정값", + "valuesDescription": "이 바이오마커의 모든 측정값이에요. 항목을 바로 편집하거나 삭제할 수 있어요.", + "backToMarker": "{marker} 화면으로 돌아가기", + "latestResult": "최근 결과" + }, + "chart": { + "empty": "이 기간에는 측정값이 없어요.", + "valueLabel": "값", + "range": { + "30": "30일", + "90": "90일", + "365": "1년", + "all": "전체" + }, + "sourceRangeLabel": "결과지 범위", + "sourceBandLegend": "두 번째 띠: {date} 결과지에 적힌 범위예요.", + "sourceBandMixed": "두 번째 띠: 가장 최근 결과지에 적힌 범위예요. 이 구간의 이전 결과지에는 다른 범위가 적혀 있어요." + }, + "biomarker": { + "manageTitle": "바이오마커", + "managerDescription": "마커를 한 번 정의하면 그 단위와 범위가 모든 측정값에 쓰여요.", + "define": "정의", + "defineFirst": "첫 바이오마커 정의", + "defineTitle": "바이오마커 정의", + "defineDescription": "이름, 단위, 참고 범위를 한 번만 설정해요. 값을 추가할 때 마커를 고르면 돼요.", + "editTitle": "바이오마커 편집", + "edit": "편집", + "delete": "삭제", + "deletedToast": "바이오마커를 삭제했어요", + "deleteError": "바이오마커를 삭제하지 못했어요", + "deleteConfirmTitle": "바이오마커를 삭제할까요?", + "deleteConfirmDescription": "바이오마커와 기록된 값이 모두 영구 삭제돼요. 되돌릴 수 없어요.", + "emptyTitle": "아직 바이오마커가 없어요", + "emptyDescription": "LDL이나 HbA1c 같은 마커를 정의하면 값을 기록할 수 있어요.", + "loadError": "바이오마커를 불러오지 못했어요", + "noRange": "참고 범위 없음", + "form": { + "seedLabel": "자주 쓰는 마커에서 시작", + "seedPlaceholder": "마커 선택 (선택 사항)", + "seedHint": "하나를 고르면 아래 항목이 채워져요 — 범위는 검사실에 맞게 조정하세요.", + "name": "이름", + "namePlaceholder": "예: LDL 콜레스테롤", + "unit": "단위", + "unitPlaceholder": "예: mg/dL", + "panel": "패널", + "panelPlaceholder": "예: 지질", + "lowerBound": "참고 하한", + "upperBound": "참고 상한", + "lowerPlaceholder": "하한값", + "upperPlaceholder": "상한값", + "context": "설명", + "contextPlaceholder": "이 마커에 대한 메모 (선택)", + "rangeHint": "하한과 상한 중 하나는 비워도 돼요 — 한쪽만 있는 범위가 돼요 (예: LDL < 116).", + "requiredError": "이름과 단위는 필수예요.", + "rangeOrderError": "하한은 상한보다 클 수 없어요.", + "save": "바이오마커 저장", + "savedToast": "바이오마커를 저장했어요", + "updatedToast": "바이오마커를 수정했어요", + "saveError": "바이오마커를 저장하지 못했어요" + }, + "hide": "숨기기", + "unhide": "복원", + "hiddenToast": "바이오마커를 숨겼어요", + "unhiddenToast": "바이오마커를 복원했어요", + "hideError": "바이오마커를 수정하지 못했어요", + "allHidden": "모든 바이오마커가 숨겨져 있어요.", + "hiddenSection": "숨김" + }, + "catalog": { + "total-cholesterol": "총콜레스테롤", + "ldl": "LDL 콜레스테롤", + "hdl": "HDL 콜레스테롤", + "triglycerides": "중성지방", + "fasting-glucose": "공복혈당", + "hba1c": "HbA1c", + "tsh": "TSH", + "ft3": "유리 T3", + "ft4": "유리 T4", + "ferritin": "페리틴", + "transferrin-saturation": "트랜스페린 포화도", + "vitamin-d": "비타민 D (25-OH)", + "vitamin-b12": "비타민 B12", + "folate": "엽산", + "hs-crp": "hs-CRP", + "creatinine": "크레아티닌", + "egfr": "eGFR", + "alt": "ALT", + "ast": "AST", + "ggt": "GGT", + "sodium": "나트륨", + "potassium": "칼륨", + "hemoglobin": "헤모글로빈", + "hematocrit": "헤마토크릿", + "wbc": "백혈구", + "platelets": "혈소판", + "panel": { + "lipids": "지질", + "glucose": "당대사", + "thyroid": "갑상선", + "iron": "철", + "vitamins": "비타민", + "inflammation": "염증", + "renal": "신장", + "liver": "간", + "electrolytes": "전해질", + "bloodCount": "혈구 수치" + }, + "desc": { + "total-cholesterol": "총콜레스테롤은 혈액 속 모든 지단백 입자가 나르는 콜레스테롤의 합으로, LDL과 HDL, 잔여 분획을 모두 포함해요. 콜레스테롤 자체는 세포막과 호르몬, 담즙산을 만드는 필수 재료예요. 다만 하나의 숫자로는 거친 요약이에요. 건강한 HDL 비중도, 높은 LDL 비중도 총량을 함께 올리기 때문에 보통 개별 분획과 함께 읽어요. 주로 장기적인 심혈관 위험 평가의 일부로 관리해요.", + "ldl": "LDL 콜레스테롤은 저밀도 지단백 입자가 나르는 콜레스테롤로, 조직에 콜레스테롤을 전달하지만 동맥 벽에 쌓이게도 하는 분획이에요. 이 침착이 수년에 걸쳐 죽상경화 플라크로 이어지기 때문에 LDL은 가장 주의 깊게 보는 지질 지표 중 하나예요. 장기 노출이 낮을수록 대체로 심혈관 위험도 낮아요. 고정된 정상값 하나가 아니라 위험군에 따라 해석해요.", + "hdl": "HDL 콜레스테롤은 고밀도 지단백 입자가 나르는 콜레스테롤로, 조직에서 남는 콜레스테롤을 모아 간으로 되돌려 재활용하거나 배출하게 해요. 이런 운반 역할 때문에 보호 분획이라고 불리는 경우가 많아요. 아주 낮으면 심혈관 위험이 높은 편이지만, 인위적으로 올린다고 위험이 확실히 줄지는 않아 목표라기보다 지표로 읽어요. 보통 LDL, 중성지방과 함께 해석해요.", + "triglycerides": "중성지방은 혈액을 도는 지방의 주된 형태로, 음식에서 오기도 하고 간에서 만들어지기도 하며 중요한 에너지 저장고 역할을 해요. 식후에 올라가기 때문에 비교를 위해 보통 공복 상태에서 채혈해요. 계속 높으면 대사·심혈관 위험과 연관되고, 인슐린 저항성이나 낮은 HDL과 함께 나타나는 경우가 많아요. 전체 지질 그림을 이루는 한 축으로 읽어요.", + "fasting-glucose": "공복혈당은 밤새 금식한 뒤 잰 혈액 속 당 농도로, 최근 음식 없이 몸이 기본 혈당을 어느 수준으로 유지하는지 보여줘요. 포도당은 몸이 바로 쓰는 주된 연료이고, 인슐린이 보통 그 농도를 좁은 범위로 지켜요. 공복 측정은 최근 식사의 영향을 걷어내기 때문에 혈당 조절 상태를 보는 표준 선별 검사예요. 대사 건강을 오래 지켜보는 데 쓰는 지표 중 하나예요.", + "hba1c": "HbA1c는 적혈구 속 헤모글로빈 가운데 포도당과 결합한 비율로, 평균 혈당 노출에 비례해 쌓여요. 적혈구 수명이 약 3개월이라 한 순간이 아니라 지난 2~3개월의 평균 혈당을 반영해요. 그래서 식사에 좌우되지 않는 안정적인 혈당 조절 지표예요. 적혈구 수명을 바꾸는 질환이 있으면 정확도가 떨어질 수 있어요.", + "tsh": "TSH(갑상선자극호르몬)는 뇌하수체가 갑상선에 호르몬을 만들라고 보내는 신호로, 균형을 맞추려고 오르내려요. 이 되먹임 구조 덕분에 TSH는 갑상선 기능을 보는 가장 민감한 1차 검사예요. 갑상선호르몬 자체보다 먼저 움직이는 편이거든요. TSH가 오르거나 내리면 갑상선기능저하나 항진을 가리키고, 보통 갑상선호르몬을 직접 재보게 돼요. 임신과 나이에 따라 기대 범위가 달라져요.", + "ft3": "유리 T3(트리요오드티로닌)는 더 강력한 갑상선호르몬 중 결합하지 않은 생물학적 활성형으로, 조직에 실제로 작용할 수 있는 분획이에요. 혈액 속 T3 대부분은 운반 단백질에 붙어 비활성 상태라, 유리 분획이 실제 호르몬 가용량을 보여줘요. 결과가 애매할 때 TSH, 유리 T4와 함께 갑상선 기능을 파악하려고 재요. 측정법에 따라 값 차이가 커서 임상 맥락에서 읽어요.", + "ft4": "유리 T4(티록신)는 갑상선이 혈액으로 내보내는 주 호르몬 중 결합하지 않은 형태로, 조직이 필요에 따라 더 활성인 T3로 바꿔 쓰는 저장고 역할을 해요. 유리 분획은 운반 단백질 농도와 무관하게 몸이 실제로 쓸 수 있는 양을 반영해요. 보통 TSH와 함께 재서 갑상선 자체의 문제인지 뇌하수체 신호의 문제인지 가려요. 임신이나 일부 약이 값을 움직일 수 있어요.", + "ferritin": "페리틴은 몸의 주된 철 저장 단백질로, 혈중 농도가 전체 철 저장량을 대체로 반영해요. 값이 낮으면 저장 철이 고갈됐다는 뜻이고, 높으면 철 과잉일 수도 있지만 단순히 염증 때문일 수도 있어요. 페리틴은 급성기 반응 단백질이라 염증에도 올라가거든요. 그래서 다른 철 지표와 염증 소견을 함께 보고 해석해요. 철 상태를 보는 핵심 지표 중 하나예요.", + "transferrin-saturation": "트랜스페린 포화도는 혈액에서 철을 실어 나르는 단백질인 트랜스페린 가운데 지금 철이 실려 있는 비율이에요. 운반 중인 철의 양을 몸의 철 운반 능력과 연결해, 저장량을 보는 페리틴과 달리 철 가용량을 한눈에 보여줘요. 포화도가 낮으면 철이 부족하다는 뜻이고, 높으면 과잉일 수 있어요. 페리틴과 철 검사 전체와 함께 읽어요.", + "vitamin-d": "비타민 D는 25-하이드록시비타민 D로 재는데, 혈액을 도는 저장형이자 전체 비타민 D 상태를 보는 표준 지표예요. 햇빛을 받은 피부에서 만들어지고 음식으로도 일부 흡수되며, 칼슘 균형과 뼈 건강, 면역 기능을 도와요. 계절과 위도, 피부색, 보충제에 따라 수치가 달라져요. 저장량이 충분한지 오래 지켜보려고 기록해요.", + "vitamin-b12": "비타민 B12(코발라민)는 주로 동물성 식품에서 얻고 대부분 간에 저장되는 필수 비타민이에요. 적혈구 생성과 DNA 합성, 신경계 건강에 꼭 필요해요. 섭취가 적거나 흡수가 나빠 오래 부족하면 빈혈과 신경 증상으로 이어질 수 있어요. 특히 섭취나 흡수가 제한될 수 있는 경우에 영양 상태를 보려고 재요.", + "folate": "엽산(비타민 B9)은 잎채소와 콩류, 강화식품에 들어 있는 B군 비타민으로 적혈구 생성과 DNA 합성의 핵심이에요. 비타민 B12와 밀접하게 함께 작용해서, 둘 중 하나만 부족해도 비슷한 형태의 빈혈이 생길 수 있어요. 임신 초기 무렵에는 태아의 건강한 발달을 위해 특히 중요해요. 영양 상태와 일부 빈혈의 원인을 보려고 재요.", + "hs-crp": "고민감도 CRP는 간이 염증에 반응해 내보내는 C반응단백을 재되, 심혈관 위험과 관련된 낮은 농도까지 잡아내도록 정밀하게 만든 검사예요. 급성 감염에 쓰는 일반 CRP와 달리, 혈관 질환과 연결되는 미세한 만성 염증을 포착해요. 최근 감염이나 부상만으로도 올라가기 때문에 급성 질환을 피해서 재고 재검으로 확인하는 경우가 많아요. 전체 심혈관 위험을 이루는 요소 중 하나로 읽어요.", + "creatinine": "크레아티닌은 정상적인 근육 대사에서 나오는 노폐물로, 신장이 비교적 일정한 속도로 혈액에서 걸러내요. 생성량이 비교적 일정하기 때문에 혈중 농도가 신장이 노폐물을 얼마나 잘 제거하는지 가늠하는 실용적인 척도가 돼요. 근육량과 수분 상태, 식사에도 영향을 받아서 보통 추정 여과율과 함께 해석해요. 신장 기능의 핵심 지표예요.", + "egfr": "eGFR(추정 사구체여과율)은 신장이 1분에 얼마만큼의 혈액을 거르는지 추정한 값으로, 크레아티닌에 나이와 성별을 함께 넣어 계산해요. 크레아티닌 원값을 신장 기능으로 더 바로 읽히는 수치로 바꿔주죠. eGFR이 시간이 지나며 떨어지는지가 만성 신장 변화를 단계로 나누고 추적하는 주된 방법이에요. 어디까지나 추정치라 정확한 한 값보다 추이로 읽어요.", + "alt": "ALT(알라닌아미노전이효소)는 간세포에 몰려 있는 효소로, 그 세포가 스트레스를 받거나 손상되면 혈액으로 흘러나와요. 비교적 간에 특이적이라 간 손상이나 염증을 잡아내는 주요 지표예요. 지방간, 바이러스 간염, 술, 일부 약으로 올라갈 수 있어요. 보통 AST와 다른 간 검사 항목과 함께 읽어요.", + "ast": "AST(아스파르트산아미노전이효소)는 간뿐 아니라 근육과 심장, 적혈구에도 있는 효소로, 이런 조직이 손상되면 혈액으로 나와요. ALT보다 간 특이도가 낮아서 둘을 함께 해석하고, 두 값의 비율이 문제의 원인과 성격을 짐작하게 해줘요. 격한 운동이나 근육 손상만으로도 간과 무관하게 올라갈 수 있어요. 표준 간 검사 효소 중 하나예요.", + "ggt": "GGT(감마글루타밀전이효소)는 간과 담관에 있는 효소로, 담즙 흐름이 막히거나 음주가 있을 때 특히 올라가요. 다른 간 지표가 왜 높은지 가릴 때, 예를 들어 간에서 온 것인지 담관에서 온 것인지 구분할 때 자주 써요. 민감하지만 특이도는 낮아서 나머지 간 검사와 함께 맥락으로 읽어요. 간과 담도 건강을 보는 여러 지표 중 하나예요.", + "sodium": "나트륨은 세포 바깥 체액의 주된 전해질이자 체액 균형과 혈액량을 좌우하는 핵심이에요. 몸이 갈증과 호르몬, 신장을 통해 촘촘히 조절하기 때문에 혈중 농도는 식사로 먹은 양보다 물과 소금의 균형을 더 잘 보여줘요. 값이 비정상이면 보통 나트륨 자체보다 수분 균형이 흐트러졌다는 신호예요. 기본 대사·전해질 검사의 핵심 항목이에요.", + "potassium": "칼륨은 세포 안의 주된 전해질로, 신경 신호 전달과 심장 근육의 규칙적인 박동에 꼭 필요해요. 높아도 낮아도 심장의 전기 활동을 흐트러뜨릴 수 있어서 신장과 호르몬 신호가 좁은 범위로 유지해요. 신장 기능과 수분 상태, 흔히 쓰는 여러 약에 따라 값이 달라질 수 있어요. 전해질 검사의 핵심 항목이에요.", + "hemoglobin": "헤모글로빈은 적혈구 속 철을 품은 단백질로, 폐에서 산소를 붙잡아 조직으로 실어 날라요. 그 농도가 혈액의 산소 운반 능력을 보는 주된 척도이자 빈혈을 찾아내는 핵심 지표예요. 낮으면 출혈이나 철·비타민 부족, 생성 저하를 반영할 수 있고, 높으면 탈수나 저산소 환경 뒤에 나타날 수 있어요. 일반혈액검사의 기둥이 되는 항목이에요.", + "hematocrit": "헤마토크릿은 전체 혈액 부피에서 적혈구가 차지하는 비율을 백분율로 나타낸 값이에요. 헤모글로빈과 비슷하게 움직이며 적혈구량과 혈액의 산소 운반 능력을 보완해서 보여줘요. 적혈구 수가 그대로여도 혈장 부피가 바뀌면 비율이 달라지므로 수분 상태의 영향을 받아요. 헤모글로빈, 나머지 혈구 수치와 함께 읽어요.", + "wbc": "백혈구 수는 혈액을 도는 면역세포의 총수로, 감염에 맞서는 몸의 기동 방어력이에요. 감염과 염증, 스트레스에 반응해 올라가고 골수가 억제되거나 일부 면역질환에서는 떨어질 수 있어요. 여러 종류의 세포를 합한 값이라 눈에 띄게 변하면 보통 세부 분획을 나누는 감별검사로 이어져요. 일반혈액검사의 핵심 항목이에요.", + "platelets": "혈소판은 혈액 속 작은 세포 조각으로, 서로 뭉쳐 손상된 혈관의 출혈을 막는 첫 마개를 만들어요. 그 수는 골수에서의 생성과 소모·파괴 사이의 균형을 반영해요. 적으면 출혈 경향이 커질 수 있고, 많으면 염증이나 다른 자극 뒤에 나타날 수 있어요. 일반혈액검사의 일부이자 지혈 능력을 보는 기본 지표예요.", + "apob": "아포지단백 B는 죽상경화를 일으키는 지단백 입자마다 하나씩 붙어 있는 구조 단백질이라, 이 값을 재면 입자가 나르는 콜레스테롤이 아니라 입자 수를 직접 세는 셈이에요. LDL과 VLDL, 관련 입자 하나에 ApoB 분자가 하나씩 있기 때문에, 입자 수와 콜레스테롤 양이 어긋날 때 LDL 콜레스테롤이 놓치는 심혈관 위험을 잡아낼 수 있어요. 그래서 지질 기반 위험 평가를 다듬는 데 점점 많이 쓰여요. 기본 지질 검사와 함께 해석해요.", + "lp-a": "지단백(a)는 LDL과 비슷한 입자에 단백질이 하나 더 붙은 형태로, 수치는 대체로 유전으로 정해지고 평생 꽤 일정하게 유지돼요. Lp(a)가 높으면 심혈관 질환과 대동맥판막 협착의 독립적인 유전 위험 요인이에요. 거의 변하지 않기 때문에 반복해서 추적하기보다 평생 위험을 파악하려고 보통 한 번만 재요. 몰 단위로 보고되고 전체 심혈관 위험의 일부로 읽어요.", + "fasting-insulin": "공복 인슐린은 혈액 속 포도당을 세포로 옮기는 호르몬인 인슐린을 밤새 금식한 뒤 잰 값이에요. 공복혈당과 함께 보면 혈당을 유지하려고 몸이 더 많은 인슐린을 필요로 하는 상태, 즉 인슐린 저항성을 가늠할 수 있어요. 혈당은 정상인데 공복 인슐린이 높다면 이런 보상이 이미 진행 중이라는 이른 신호일 수 있어요. 단독으로 보기보다 대사 건강 전체 그림의 일부로 읽어요.", + "omega-3-index": "오메가3 지수는 적혈구 막의 지방산 가운데 긴사슬 해양 오메가3 지방산인 EPA와 DHA가 차지하는 비율이에요. 적혈구는 천천히 교체되기 때문에 최근 식사가 아니라 장기적인 오메가3 섭취를 반영해요. 지수가 높을수록 관찰연구에서 심혈관 위험이 낮은 것과 연관됐어요. 몇 달에 걸쳐 식사에 반응하는 안정적인 오메가3 상태 지표로 기록해요." + }, + "apob": "아포지단백 B (ApoB)", + "lp-a": "지단백(a)", + "fasting-insulin": "공복 인슐린", + "omega-3-index": "오메가3 지수" + }, + "noteLoadFailed": "메모를 불러오지 못했어요. 저장해도 메모는 그대로 유지돼요.", + "showingLatestOf": "측정값 {total}개 중 최근 {shown}개를 보여주고 있어요", + "ocr": { + "scanButton": "결과지 스캔", + "addScan": "문서 스캔", + "addManual": "직접 값 추가", + "uploadHint": "검사 결과지 사진이나 PDF를 올려 주세요", + "acceptWithPdf": "JPEG, PNG, WebP 또는 PDF", + "acceptImageOnly": "JPEG, PNG 또는 WebP", + "extracting": "결과지를 읽고 있어요…", + "readingOnDevice": "기기에서 읽고 있어요…", + "localModeHint": "기기에서 읽은 뒤 텍스트로 보내요. 정확도가 낮으니 모든 값을 확인하세요.", + "rangeFromScan": "스캔에서 가져온 참고 범위", + "rangeVerify": "참고 범위를 확인해 주세요", + "reviewTitle": "저장 전에 확인하기", + "reviewHint": "측정값을 하나씩 확인하고, 남길 항목만 저장하세요.", + "foundCount": "측정값 {count}개를 찾았어요", + "confirmRow": "이 측정값 포함", + "analyteLabel": "검사 항목", + "resultLabel": "결과", + "valueLabel": "값", + "unitLabel": "단위", + "dateLabel": "날짜", + "refLowLabel": "참고 하한", + "refHighLabel": "참고 상한", + "newBiomarker": "새 바이오마커 — 새로 만들어요", + "linksExisting": "기존 {name} 항목에 연결돼요", + "duplicateWarning": "같은 측정값이 이미 있어요", + "lowConfidence": "이 값은 다시 확인해 주세요", + "lowConfidenceBadge": "확신 낮음", + "valueUnreadable": "값을 읽지 못했어요 — 직접 입력해 주세요", + "saveSelected": "선택한 {count}개 저장", + "discardAll": "모두 버리기", + "savedToast": "검사 결과 {count}개를 저장했어요", + "savedPartialToast": "검사 결과 {count}개를 저장하고, 중복 {skipped}개는 건너뛰었어요", + "savedNothingToast": "저장한 항목이 없어요: 측정값 {skipped}개는 이미 기록돼 있어요", + "savedEmptyToast": "저장할 항목이 없어요", + "noRows": "이 결과지에서 측정값을 찾지 못했어요.", + "nothingToSave": "저장할 측정값을 하나 이상 선택해 주세요.", + "saveFailed": "측정값을 저장하지 못했어요. 다시 시도해 주세요.", + "providerUnsupported": "AI 제공자가 이미지를 읽지 못해요. 이미지를 읽는 모델로 설정해 주세요.", + "consentRequired": "결과지를 스캔하려면 AI 처리 동의가 필요해요.", + "rateLimited": "스캔이 너무 많아요. 잠시 후 다시 시도해 주세요.", + "budgetExceeded": "오늘의 AI 사용 예산을 다 썼어요.", + "fileTooLarge": "파일이 너무 커요 (최대 12MB).", + "fileType": "JPEG, PNG, WebP 또는 PDF를 올려 주세요.", + "extractFailed": "결과지를 읽지 못했어요. 더 선명한 사진으로 시도해 주세요.", + "pdfNeedsAnthropic": "PDF 스캔에는 Claude 비전 제공자가 필요해요. 대신 사진을 올려 주세요.", + "refTextLabel": "적힌 그대로의 범위", + "refTextPlaceholder": "예: 3.5 - 5.0" + }, + "loadMoreReadings": "측정값 더 보기", + "documentsLink": "검사 결과지 문서", + "notLinkedYet": "아직 바이오마커에 연결되지 않았어요", + "sourceRange": { + "fromReport": "결과지 범위 {range}", + "catalogDiffers": "내 범위 {range}" + } + }, + "modules": { + "cycle": { + "label": "생리 주기", + "description": "생리, 주기 단계, 예측을 기록해요." + }, + "mood": { + "label": "기분", + "description": "매일 기분과 태그를 기록해요." + }, + "sleep": { + "label": "수면", + "description": "수면 시간, 수면 단계, 타임라인." + }, + "glucose": { + "label": "혈당", + "description": "혈당 측정값과 추이." + }, + "workouts": { + "label": "운동", + "description": "운동 세션과 활동." + }, + "recovery": { + "label": "회복", + "description": "기기에서 가져온 회복·부하 지표." + }, + "labs": { + "label": "검사 결과", + "description": "검사 결과를 기록하고 이력을 관리해요." + }, + "achievements": { + "label": "업적", + "description": "루틴에 따라 받는 배지와 연속 기록." + }, + "coach": { + "label": "코치", + "description": "내 데이터를 근거로 하는 AI 대화." + }, + "insights": { + "label": "인사이트", + "description": "AI 분석, 브리핑, 상관관계." + }, + "doctorReport": { + "label": "진료용 리포트", + "description": "병원에 낼 PDF·FHIR 내보내기." + }, + "medications": { + "label": "복약", + "description": "일정, 복용, 복약 순응도." + }, + "illness": { + "label": "아픈 날 일지", + "description": "아픈 날과 질환 경과를 매일의 증상 타임라인으로 기록해요." + }, + "mcp": { + "label": "외부 어시스턴트 접근 (MCP)", + "description": "범위가 제한된 토큰으로 MCP를 지원하는 어시스턴트에 읽기 권한을 줘요." + }, + "environment": { + "label": "환경", + "description": "내 위치의 매일 날씨와 일조를 기분·수면·활력 징후와 나란히 보여줘요." + }, + "inboundDocuments": { + "label": "문서", + "description": "진료 의뢰서, 소견서, 스캔본을 암호화된 한 곳에서 검색해요." + }, + "mentalHealth": { + "label": "정신 건강", + "description": "기분 기록과 함께 쓰는 선택형 선별검사 4종." + }, + "nutrients": { + "label": "영양소 섭취", + "description": "Apple Health에서 동기화한 하루 미량영양소 합계." + }, + "vaccinations": { + "label": "예방접종", + "description": "예방접종 기록을 구조화해서: 용량, 차수, 다음 추가 접종." + } + }, + "vaccinations": { + "title": "예방접종", + "subtitle": "접종 한 건씩 남기는 예방접종 기록이에요.", + "listLoadError": "예방접종 기록을 불러오지 못했어요.", + "series": { + "booster": "추가 접종", + "ofTotal": "{total}회 중 {position}회차", + "doseN": "{position}회차" + }, + "row": { + "lot": "로트 {lot}" + }, + "form": { + "date": "날짜", + "catalogLabel": "백신 (목록)", + "identityHint": "목록에서 고르거나 아래에 직접 입력해 주세요. 둘 중 하나면 충분해요.", + "catalogNone": "백신 선택", + "catalogSearch": "백신 검색", + "catalogNoMatch": "목록에 없는 백신이에요", + "catalogClear": "선택 지우기", + "freeTextLabel": "직접 입력", + "freeTextPlaceholder": "기록에 적힌 그대로", + "seriesToggle": "접종 차수 정보 추가", + "doseNumber": "접종 차수", + "seriesDoses": "총 접종 횟수", + "lot": "로트 번호", + "site": "접종 부위", + "siteNone": "지정 안 함", + "practitioner": "의료기관 또는 의사", + "note": "메모", + "linkDocuments": "문서 연결", + "linkNothingToOffer": "아직 문서가 없어요." + }, + "site": { + "LEFT_ARM": "왼쪽 위팔", + "RIGHT_ARM": "오른쪽 위팔", + "LEFT_THIGH": "왼쪽 허벅지", + "RIGHT_THIGH": "오른쪽 허벅지", + "ORAL": "경구", + "NASAL": "비강", + "OTHER": "기타" + }, + "createTitle": "예방접종 기록 추가", + "editTitle": "예방접종 기록 편집", + "formDescription": "날짜와 백신만 있으면 충분해요. 나머지는 모두 선택이에요.", + "created": "예방접종 기록을 저장했어요", + "updated": "예방접종 기록을 수정했어요", + "saveFailed": "예방접종 기록을 저장하지 못했어요.", + "deleteTitle": "이 예방접종 기록을 삭제할까요?", + "deleteDescription": "기록에서 항목이 사라져요. 예정된 추가 접종 알림은 그대로 남아요.", + "deleteFailed": "예방접종 기록을 삭제하지 못했어요.", + "info": { + "primarySeriesOne": "기초 접종: {count}회", + "primarySeriesFew": "기초 접종: {count}회", + "primarySeriesOther": "기초 접종: {count}회", + "yearly": "매년 접종", + "boosterYearsOne": "보통 {count}년마다 추가 접종", + "boosterYearsFew": "보통 {count}년마다 추가 접종", + "boosterYearsOther": "보통 {count}년마다 추가 접종", + "boosterMonthsOne": "보통 {count}개월마다 추가 접종", + "boosterMonthsFew": "보통 {count}개월마다 추가 접종", + "boosterMonthsOther": "보통 {count}개월마다 추가 접종", + "standard60": "60세부터 표준 접종", + "sourceLabel": "출처: {source}", + "affordanceLabel": "접종 일정 정보" + }, + "booster": { + "title": "추가 접종을 예약할까요?", + "body": "이 접종을 기준으로 다음 추가 접종 알림을 설정할 수 있어요. 간격이나 이름을 바꿔도 되고, 지금 안 해도 돼요.", + "interval": "간격", + "label": "이름", + "labelSuffix": "추가 접종", + "decline": "나중에", + "confirm": "알림 설정", + "planned": "추가 접종을 예약했어요", + "failed": "알림을 만들지 못했어요.", + "everyYearsOne": "{count}년마다", + "everyYearsFew": "{count}년마다", + "everyYearsOther": "{count}년마다", + "everyMonthsOne": "{count}개월마다", + "everyMonthsFew": "{count}개월마다", + "everyMonthsOther": "{count}개월마다" + }, + "suggestion": { + "single": "이 예방접종 기록에 넣을까요?", + "choose": "이 스캔은 어느 예방접종 기록일까요?", + "linked": "예방접종 기록에 연결했어요", + "failed": "연결하지 못했어요.", + "unnamed": "예방접종" + }, + "empty": { + "title": "아직 예방접종 기록이 없어요", + "description": "예방접종 수첩을 한 줄씩 옮겨 적어 보세요. 날짜와 백신만 있으면 충분해요." + }, + "catalog": { + "tetanus": "파상풍", + "diphtheria": "디프테리아", + "pertussis": "백일해", + "polio": "폴리오(소아마비)", + "measles": "홍역", + "mumps": "유행성이하선염", + "rubella": "풍진", + "varicella": "수두", + "hib": "Hib(b형 헤모필루스 인플루엔자)", + "hepatitis-a": "A형간염", + "hepatitis-b": "B형간염", + "influenza": "인플루엔자(독감)", + "pneumococcal": "폐렴구균", + "herpes-zoster": "대상포진", + "rsv": "RSV(호흡기세포융합바이러스)", + "covid-19": "코로나19", + "fsme": "진드기매개뇌염(TBE)", + "hpv": "HPV(사람유두종바이러스)", + "meningococcal-acwy": "수막구균 ACWY", + "meningococcal-b": "수막구균 B", + "rotavirus": "로타바이러스", + "typhoid": "장티푸스", + "rabies": "광견병", + "yellow-fever": "황열", + "japanese-encephalitis": "일본뇌염", + "cholera": "콜레라", + "td": "Td(파상풍, 디프테리아)", + "tdap": "Tdap(파상풍, 디프테리아, 백일해)", + "tdap-ipv": "Tdap-IPV(4종 혼합)", + "dtap-ipv": "DTaP-IPV(소아용 4종 혼합)", + "hexavalent": "6종 혼합(DTaP-IPV-Hib-HepB)", + "mmr": "MMR(홍역, 유행성이하선염, 풍진)", + "mmrv": "MMRV(홍역, 유행성이하선염, 풍진, 수두)", + "hepa-hepb": "A형·B형간염", + "hepa-typhoid": "A형간염·장티푸스" + } + }, + "illness": { + "title": "아픈 날 일지", + "subtitle": "아픈 날과 컨디션 에피소드를 돌아보는 기록이에요.", + "settings": { + "viewDescription": "에피소드를 카드로 볼지 간단한 목록으로 볼지 선택하세요.", + "orderDescription": "에피소드를 원하는 순서대로 끌어서 옮기세요." + }, + "customize": "아픈 날 일지 맞춤 설정", + "newEpisode": "새 에피소드", + "logDay": "하루 기록", + "markRecovered": "회복 표시", + "deletedToast": "에피소드를 삭제했어요", + "restoreError": "에피소드를 복원하지 못했어요", + "onsetOn": "{date} 시작", + "recoveredOn": "{date} 회복", + "status": { + "active": "진행 중", + "ongoing": "지속 중", + "recovered": "회복" + }, + "empty": { + "title": "아직 에피소드가 없어요", + "body": "에피소드를 시작하면 매일 증상을 기록하고 나중에 경과를 돌아볼 수 있어요." + }, + "type": { + "INFECTION": "감염", + "ALLERGY": "알레르기", + "INJURY": "부상", + "MENTAL_HEALTH": "정신 건강", + "AUTOIMMUNE": "자가면역", + "CHRONIC": "만성", + "OTHER": "기타" + }, + "lifecycle": { + "ACUTE": "급성", + "CHRONIC_ONGOING": "만성 / 지속", + "RECURRING": "재발성", + "FLARE": "악화" + }, + "new": { + "title": "새 에피소드", + "description": "아픈 날이나 컨디션을 기록하세요. 이후 매일 증상을 기록할 수 있어요.", + "label": "이름", + "labelPlaceholder": "예: 감기", + "type": "유형", + "lifecycle": "경과", + "onset": "시작일", + "note": "메모", + "notePlaceholder": "기억해 둘 내용 (암호화됨).", + "saveError": "에피소드를 저장하지 못했어요. 다시 시도해 주세요.", + "parent": "상위 에피소드", + "parentNone": "연결 안 함", + "resolved": "종료일", + "resolvedHelp": "아직 진행 중이면 비워 두세요.", + "markOngoing": "아직 진행 중" + }, + "sheet": { + "title": "{date} 기록", + "description": "오늘의 증상, 활동 정도, 발열을 기록하세요.", + "symptoms": "증상", + "functionalImpact": "활동 정도", + "fever": "체온", + "note": "메모", + "noteSet": "입력됨", + "notePlaceholder": "오늘 하루가 어땠는지 (암호화됨).", + "severityFor": "{symptom} 심각도", + "saveError": "하루 기록을 저장하지 못했어요. 다시 시도해 주세요.", + "day": "날짜" + }, + "impact": { + "0": "정상 활동", + "1": "약간 제한", + "2": "대부분 휴식", + "3": "종일 누워 지냄" + }, + "symptom": { + "runnyNose": "콧물", + "stuffyNose": "코막힘", + "sneezing": "재채기", + "soreThroat": "인후통", + "cough": "기침", + "headache": "두통", + "bodyAches": "몸살", + "fatigue": "피로" + }, + "correlation": { + "title": "어떻게 진행됐나", + "learning": "아직 학습 중이에요 — 며칠 더 기록하면 패턴이 보여요.", + "preOnsetTitle": "어떤 신호가 먼저 왔나", + "preOnsetBody": "시작일로 표시하기 전에 기준값에서 벗어났던 신호예요.", + "nadirTitle": "무엇이 달라졌나", + "nadirBody": "기록한 신호가 평소 범위에서 얼마나 벗어났는지 보여줘요.", + "recoveryGapTitle": "회복 시차", + "recoveryGapLag": "몸이 나아진 뒤 {days}일 만에 수치가 안정됐어요.", + "recoveryGapLead": "회복으로 표시하기 {days}일 전에 수치가 안정됐어요.", + "recoveryGapLagSymptom": "기록한 증상이 가라앉기 {days}일 전에 몸이 먼저 나아졌어요.", + "recoveryGapLeadSymptom": "회복으로 표시하기 {days}일 전에 기록한 증상이 가라앉았어요.", + "recoveryGapSame": "몸이 나아진 날 수치도 함께 안정됐어요.", + "sleepContextMore": "이번 에피소드 동안 평소보다 눈에 띄게 많이 잤어요 (하룻밤 약 {hours}h).", + "sleepContextLess": "이번 에피소드 동안 평소보다 눈에 띄게 적게 잤어요 (하룻밤 약 {hours}h).", + "redFlagTitle": "한번 더 살펴볼 점", + "redFlagSpo2": "산소포화도가 며칠 동안 낮게 유지됐어요. 다시 이러면 진료를 받아 보세요.", + "redFlagFever": "발열이 계속 이어졌어요. 다시 이러면 진료를 받아 보세요.", + "direction": { + "above": "평소보다 높음", + "below": "평소보다 낮음" + }, + "settled": "이번 에피소드에서 평소 범위를 벗어난 뚜렷한 신호는 없었어요." + }, + "insights": { + "title": "지난 아픈 날 기록", + "countOne": "이 기간에 에피소드를 1개 기록했어요.", + "countOther": "이 기간에 에피소드를 {count}개 기록했어요.", + "typicalGap": "평균 회복 시차: 몸이 나아지고 수치가 안정되기까지 {days}일.", + "typicalGapWithDriver": "평균 회복 시차: {days}일 — {vital} 안정보다 몸이 먼저 나아지는 편이에요.", + "typicalGapLearning": "종료된 에피소드가 몇 개 더 쌓이면 평균 회복 시차가 나와요.", + "none": "이 기간에 기록한 에피소드가 없어요.", + "analyze": "회복 시차 분석" + }, + "section": { + "active": "진행 중", + "resolved": "종료" + }, + "flares": { + "countOne": "악화 1회", + "countOther": "악화 {count}회" + }, + "timeline": { + "title": "일별 타임라인", + "error": "타임라인을 불러오지 못했어요.", + "severity": "심각도 {severity}", + "noSymptoms": "기록한 증상이 없어요.", + "impact": "활동 정도: {impact}", + "fever": "체온: {value}°C", + "empty": "아직 기록한 날이 없어요." + }, + "loadError": "이 에피소드를 불러오지 못했어요.", + "backToList": "일지로 돌아가기", + "detail": { + "note": "메모" + }, + "edit": { + "title": "에피소드 편집", + "description": "이 에피소드의 정보를 수정하세요." + }, + "menu": { + "label": "에피소드 작업" + }, + "deleteConfirm": { + "title": "이 에피소드를 삭제할까요?", + "body": "'{label}' 에피소드와 모든 하루 기록이 사라져요. 잠시 동안은 되돌릴 수 있어요." + }, + "vital": { + "RESTING_HEART_RATE": "안정 시 심박수", + "HEART_RATE_VARIABILITY": "심박변이도", + "RECOVERY_SCORE": "회복", + "OXYGEN_SATURATION": "산소포화도", + "BODY_TEMPERATURE": "체온", + "RESPIRATORY_RATE": "호흡수", + "BLOOD_PRESSURE_SYS": "수축기 혈압", + "BLOOD_PRESSURE_DIA": "이완기 혈압", + "PULSE": "맥박", + "WEIGHT": "체중", + "FUNCTIONAL_IMPACT": "활동 정도" + }, + "listLoadError": "에피소드를 불러오지 못했어요." + }, + "coach": { + "reminderSuggestion": { + "title": "알림을 설정할까요?", + "accept": "알림 설정", + "dismiss": "나중에", + "stop": "충분히 측정하고 있어요", + "accepted": "알림을 설정했어요", + "dismissed": "알겠어요, 다시 제안하지 않을게요", + "stopped": "알겠어요 — 알림 제안은 그만할게요", + "failed": "설정하지 못했어요 — 다시 시도해 주세요", + "cadence": { + "weightDaily": "매일 비슷한 시간에 체중을 재세요", + "bp722": "일주일 동안 하루 두 번 혈압을 재세요", + "glucoseStructured": "정해진 하루 일정에 맞춰 혈당을 재세요" + } + }, + "suggestedAction": { + "apply": "추가", + "dismiss": "나중에", + "applied": "추가했어요", + "dismissed": "닫았어요", + "failed": "추가하지 못했어요 — 다시 시도해 주세요", + "checkup": { + "title": "건강검진 알림을 추가할까요?" + }, + "note": { + "title": "알림을 저장할까요?" + }, + "interval": { + "yearly": "매년", + "halfYearly": "6개월마다", + "quarterly": "3개월마다", + "monthly": "매달" + } + }, + "settings": { + "reminderSuggestionsLabel": "측정 주기 제안", + "reminderSuggestionsDescription": "코치가 특정 지표를 더 규칙적으로 측정하도록 근거 기반 알림을 가끔 제안하게 해요." + }, + "plans": { + "title": "계획", + "pageDescription": "코치와의 대화에서 합의한 조건-실행 계획이에요. 제안을 확정하면 코치가 기억해요.", + "proposalTitle": "계획 제안", + "ifThen": "만약 {cue}, 그러면 {action}.", + "targetPrefix": "목표: {target}", + "reviewPrefix": "점검: {when}", + "accept": "계획 수락", + "decline": "거절", + "accepted": "계획을 수락했어요 — 코치가 기억할게요.", + "declined": "제안을 거절했어요.", + "failed": "잘 안 됐어요 — 다시 시도해 주세요.", + "markMet": "목표 달성", + "abandon": "계획 종료", + "remove": "삭제", + "removeTitle": "이 계획을 삭제할까요?", + "removeBody": "계획과 진행 내역이 기록에서 사라져요. 되돌릴 방법은 없어요.", + "removeConfirm": "계획 삭제", + "empty": "아직 계획이 없어요. 대화에서 구체적인 조건-실행 다짐을 하면 코치가 여기에 확정용으로 제안해요.", + "groupProposed": "제안", + "groupActive": "진행 중", + "groupPast": "완료", + "status": { + "proposed": "제안됨", + "active": "진행 중", + "met": "달성", + "abandoned": "종료", + "review_due": "점검 시기", + "reviewed": "점검함" + } + }, + "refusal": { + "outOfScope": "저는 기록에 있는 건강 지표만 도와드릴 수 있어요 — 측정값, 약, 기분, 추이 같은 것들이요. 그 범위 밖의 질문에는 답할 수 없어요.", + "promptInjection": "제 지침을 무시하게 만들려는 표현이 보였어요. 저는 HealthLog 코치이고 본인의 건강 데이터만 정리해 드려요 — 그 범위에 맞게 다시 질문해 주세요.", + "conversationPoisoned": "이 대화의 앞선 메시지에 제 지침을 무시하게 만드는 표현이 있어요. 새 대화를 시작해 주세요." + }, + "noRecordedData": "물어보신 내용에 대해 기록된 측정값이 없어서 수치를 알려드릴 수 없어요. 기록을 남기신 뒤에 다시 물어보시면 본인 수치로 답해 드릴게요." + }, + "safety": { + "floor": { + "bpHighTitle": "매우 높은 혈압", + "bpHighDoctor": "최근 측정값 두 번이 {systolic}/{diastolic}mmHg 이상이었어요. 안정을 취하고 몇 분 뒤 다시 재 보세요. 계속 이렇게 높으면 되도록 빨리 의사에게 연락하세요.", + "bpHighEmergency": "최근 측정값 두 번이 {systolic}/{diastolic}mmHg 이상이었고 증상도 기록하셨어요. 가슴 통증, 호흡 곤란, 쇠약감, 시야나 언어 이상이 있으면 지금 응급 서비스에 전화하세요.", + "bpLowTitle": "낮은 혈압", + "bpLowDoctor": "최근 측정값 두 번의 수축기 혈압이 {systolic}mmHg 미만이었어요. 어지럽거나 쓰러질 것 같으면 앉거나 누우세요. 계속되면 의사에게 연락하세요.", + "bpLowEmergency": "최근 측정값 두 번의 수축기 혈압이 {systolic}mmHg 미만이었고 증상도 기록하셨어요. 쓰러질 것 같거나 정신이 혼미하거나 피부가 차고 축축하면 지금 응급 서비스에 전화하세요.", + "glucoseLowTitle": "낮은 혈당", + "glucoseLowDoctor": "최근 측정값 두 번이 {threshold} {unit} 미만이었어요 ({value} {unit}). 빠르게 흡수되는 탄수화물을 드시고 15분 뒤 다시 재 보세요. 저혈당이 계속되면 의사에게 연락하세요.", + "glucoseLowEmergency": "최근 측정값 두 번이 {threshold} {unit} 미만이었고 ({value} {unit}) 증상도 기록하셨어요. 지금 바로 빠르게 흡수되는 당분으로 대처하세요. 그럴 수 없거나 많이 안 좋으면 응급 서비스에 전화하세요.", + "glucoseLowSevereTitle": "매우 낮은 혈당", + "glucoseLowSevereDoctor": "최근 측정값 두 번이 {threshold} {unit} 미만이었어요 ({value} {unit}). 즉시 빠르게 흡수되는 탄수화물을 드시고 15분 뒤 다시 잰 다음 의사에게 연락하세요.", + "glucoseLowSevereEmergency": "최근 측정값 두 번이 {threshold} {unit} 미만이었고 ({value} {unit}) 증상도 기록하셨어요. 지금 바로 대처하세요. 음식을 삼키기 어렵거나 많이 안 좋으면 응급 서비스에 전화하세요.", + "glucoseHighTitle": "매우 높은 혈당", + "glucoseHighDoctor": "최근 측정값 두 번이 {value} {unit} 이상이었어요. 다시 재고 수분을 충분히 드세요. 계속 이렇게 높으면 되도록 빨리 의사에게 연락하세요.", + "glucoseHighEmergency": "최근 측정값 두 번이 {value} {unit} 이상이었고 증상도 기록하셨어요. 구토, 빠른 호흡, 혼미함이 있으면 지금 응급 서비스에 전화하세요." + } + }, + "version": { + "updateAvailable": { + "title": "새 버전이 나왔어요", + "reload": "새로고침", + "dismiss": "닫기" + } + }, + "mentalHealth": { + "pageTitle": "마음 건강", + "pageDescription": "검증된 자가 점검 네 가지예요. 한 번의 점수보다 시간에 따른 추이가 더 많은 걸 말해줘요.", + "attributionLabel": "출처", + "attributionShow": "{instrument} 출처와 라이선스", + "choosePrompt": "점검을 선택하세요", + "start": "시작", + "back": "뒤로", + "submit": "결과 보기", + "submitting": "저장 중…", + "saved": "저장됨", + "error": "문제가 생겼어요. 다시 시도해 주세요.", + "instrument": { + "phq9": "PHQ-9 · 기분", + "gad7": "GAD-7 · 걱정", + "who5": "WHO-5 · 웰빙", + "sci": "SCI · 수면" + }, + "instrumentSub": { + "phq9": "우울 선별, 9문항", + "gad7": "불안 선별, 7문항", + "who5": "웰빙 점검, 5문항", + "sci": "수면 점검, 8문항" + }, + "options": { + "0": "전혀 방해 받지 않았다", + "1": "며칠 동안 방해 받았다", + "2": "7 일 이상 방해 받았다", + "3": "거의 매일 방해 받았다" + }, + "functionalTitle": "만일 당신이 위의 문제 중 하나 이상 \"예\" 라고 응답하셨으면, 이러한 문제들로 인해서 당신은 일을 하거나 가정일을 돌보거나 다른 사람과 어울리는 것이 얼마나 어려웠습니까?", + "functional": { + "0": "전혀 어렵지 않았다", + "1": "약간 어려웠다", + "2": "많이 어려웠다", + "3": "매우 많이 어려웠다" + }, + "functionalOptionalHint": "선택 사항이에요 — 이 문항에 답하지 않아도 제출할 수 있어요.", + "result": { + "title": "내 결과", + "totalLabel": "총점", + "bandLabel": "이 검사의 구간", + "takeAnother": "다른 점검 하기" + }, + "band": { + "PHQ9": { + "minimal": "없음 / 최소", + "mild": "경도", + "moderate": "중등도", + "modSevere": "중등도-중증", + "severe": "중증" + }, + "GAD7": { + "minimal": "없음 / 최소", + "mild": "경도", + "moderate": "중등도", + "severe": "중증" + }, + "WHO5": { + "low": "웰빙 낮음", + "good": "웰빙 좋음" + }, + "SCI": { + "belowThreshold": "불면증 가능 구간", + "aboveThreshold": "불면증 징후 없음" + } + }, + "history": { + "title": "내 기록", + "empty": "아직 점검 기록이 없어요. 하나를 마치면 추이가 여기에 나타나요.", + "flaggedBadge": "지원 안내 표시됨", + "chartTitle": "점수 추이", + "totalLabel": "점수", + "bandLabel": "분류", + "answersToggle": "답변", + "answersUnavailable": "이 점검에 저장된 답변을 복호화할 수 없어요." + }, + "crisis": { + "title": "혼자 견디지 않아도 돼요", + "intro": "죽는 편이 낫겠다는 생각이나 스스로를 해치려는 생각이 있다고 답하셨어요. 지금 바로 받을 수 있는 도움이 있고, 도움을 청하는 건 강함의 표시예요.", + "ifDanger": "당장 위험한 상황이라면 지금 {emergency}로 전화하세요.", + "resourcesTitle": "도움을 줄 수 있는 곳" + }, + "items": { + "phq9": { + "1": "일 또는 여가 활동을 하는 데 흥미나 즐거움을 느끼지 못함", + "2": "기분이 가라앉거나, 우울하거나, 희망이 없음", + "3": "잠이 들거나 계속 잠을 자는 것이 어려움, 또는 잠을 너무 많이 잠", + "4": "피곤하다고 느끼거나 기운이 거의 없음", + "5": "입맛이 없거나 과식을 함", + "6": "자신을 부정적으로 봄 — 혹은 자신이 실패자라고 느끼거나 자신 또는 가족을 실망시킴", + "7": "신문을 읽거나 텔레비전 보는 것과 같은 일에 집중하는 것이 어려움", + "8": "다른 사람들이 주목할 정도로 너무 느리게 움직이거나 말을 함. 또는 반대로 평상시보다 많이 움직여서, 너무 안절부절 못하거나 들떠 있음", + "9": "자신이 죽는 것이 더 낫다고 생각하거나 어떤 식으로든 자신을 해칠 것이라고 생각함" + }, + "gad7": { + "1": "초조하거나 불안하거나 조마조마하게 느낀다", + "2": "걱정하는 것을 멈추거나 조절할 수가 없다", + "3": "여러 가지 것들에 대해 걱정을 너무 많이 한다", + "4": "편하게 있기가 어렵다", + "5": "너무 안절부절못해서 가만히 있기가 힘들다", + "6": "쉽게 짜증이 나거나 쉽게 성을 내게 된다", + "7": "마치 끔찍한 일이 생길 것처럼 두렵게 느껴진다" + }, + "who5": { + "1": "나는 즐겁고 기분이 좋다.", + "2": "나는 평온하고 느긋하다.", + "3": "나는 의욕적이고 활기차다.", + "4": "나는 자고 일어나면 상쾌하고 푹 쉬었다는 느낌이 든다.", + "5": "나의 일상생활은 흥미로운 일들로 가득하다." + }, + "sci": { + "1": "… how long does it take you to fall asleep?", + "2": "… if you then wake up during the night … how long are you awake for in total? (add all the wakenings up)", + "3": "… how many nights a week do you have a problem with your sleep?", + "4": "… how would you rate your sleep quality?", + "5": "… affected your mood, energy, or relationships?", + "6": "… affected your concentration, productivity, or ability to stay awake?", + "7": "… troubled you in general?", + "8": "… how long have you had a problem with your sleep?" + } + }, + "crisisResource": { + "telefonSeelsorge": { + "name": "TelefonSeelsorge (24시간 연중무휴, 무료, 비밀 보장)" + }, + "nummerGegenKummer": { + "name": "Nummer gegen Kummer (청소년 대상)" + }, + "telefonSeelsorgeAt": { + "name": "Telefonseelsorge Österreich (24시간 연중무휴, 무료, 비밀 보장)" + }, + "dargeboteneHand": { + "name": "Die Dargebotene Hand (24시간 연중무휴, 무료, 비밀 보장)" + }, + "krisenchat": { + "name": "krisenchat (24시간 채팅, 청소년 대상)" + }, + "findahelpline": { + "name": "Find a helpline (전 세계 상담 전화 목록)" + }, + "euEmotionalSupport": { + "name": "정서 지원 상담 전화" + }, + "lifeline988": { + "name": "988 Suicide & Crisis Lifeline" + }, + "crisisTextLine": { + "name": "Crisis Text Line" + } + }, + "instrumentDescription": { + "phq9": "9문항 우울 선별 검사예요. 지난 2주를 기준으로 답해 주세요.", + "gad7": "7문항 불안 선별 검사예요. 지난 2주를 기준으로 답해 주세요.", + "who5": "5문항 웰빙 점검이에요. 지난 2주를 기준으로 답해 주세요. 숫자가 클수록 웰빙이 좋아요.", + "sci": "8문항 수면 점검이에요. 대부분 지난 한 달을 묻고, 점수가 높을수록 수면이 좋아요." + }, + "progress": "{current} / {total}", + "next": "다음", + "lastResult": "최근 검사", + "lastScore": "최근 결과", + "noResultYet": "아직 점검 기록 없음", + "backToList": "개요", + "followUpHint": { + "phq9": "점수가 전문가와 이야기해 보면 도움이 될 수 있는 구간이에요. 진단이 아니라 가벼운 제안이에요.", + "gad7": "점수가 전문가와 이야기해 보면 도움이 될 수 있는 구간이에요. 진단이 아니라 가벼운 제안이에요.", + "who5": "웰빙 점수가 50점 이하라면 좀 더 살펴볼 만해요. 원한다면 이 페이지의 PHQ-9 점검이 기분을 더 자세히 살펴봐요. 진단이 아니라 가벼운 제안이에요.", + "sci": "이 설문의 바탕이 된 연구에서는 16점 이하가 불면증 가능성과 관련이 있었어요. 수면 문제가 계속 힘들게 한다면 전문가와 이야기해 보는 게 도움이 될 수 있어요. 진단은 아니에요." + }, + "validatedInEnglishNote": "이 설문은 영어로 검증된 도구예요. 문항은 원문 영어 표현 그대로 보여줘요.", + "who5Options": { + "0": "전혀 그렇지 않다.", + "1": "가끔 그렇다.", + "2": "절반 미만이 그렇다.", + "3": "절반 이상이 그렇다.", + "4": "거의 그렇다.", + "5": "항상 그렇다." + }, + "sciOptions": { + "duration": { + "0": "≥ 61 min", + "1": "46–60 min", + "2": "31–45 min", + "3": "16–30 min", + "4": "0–15 min" + }, + "nights": { + "0": "5–7", + "1": "4", + "2": "3", + "3": "2", + "4": "0–1" + }, + "quality": { + "0": "Very poor", + "1": "Poor", + "2": "Average", + "3": "Good", + "4": "Very good" + }, + "impact": { + "0": "Very much", + "1": "Much", + "2": "Somewhat", + "3": "A little", + "4": "Not at all" + }, + "problemDuration": { + "0": "> 1 year", + "1": "7–12 months", + "2": "3–6 months", + "3": "1–2 months", + "4": "I don't have a problem / < 1 month" + } + }, + "stems": { + "who5": { + "period": "지난 2주 동안" + }, + "sci": { + "night": "Thinking about a typical night in the last month …", + "impact": "Thinking about the past month, to what extent has poor sleep …", + "finally": "Finally …" + } + }, + "openDetail": "자세히 보기", + "historyLoadError": "검사 기록을 불러오지 못했어요", + "historyLoadErrorHint": "결과가 없다는 뜻은 아니에요 — 목록을 가져오지 못했을 뿐이에요. 다시 시도해 주세요.", + "privacyNote": "답변은 암호화되어 저장되고, AI 코치에게는 전달되지 않아요." + }, + "records": { + "aiInclusion": { + "cardTitle": "AI 프로필 접근", + "cardDescription": "코치와 종합 브리핑이 사용할 수 있는 건강 프로필 항목을 골라 주세요.", + "sectionAboutMe": "나에 대해", + "sectionConditions": "만성 질환", + "sectionAllergies": "알레르기와 불내성", + "sectionCoachFocus": "코치 관심 항목", + "sectionFamilyHistory": "가족력", + "sectionSmoking": "흡연 상태", + "sectionAlcohol": "음주 패턴", + "sectionShiftSchedule": "근무 일정", + "save": "접근 설정 저장", + "savedToast": "AI 프로필 접근을 저장했어요", + "saveError": "AI 프로필 접근을 저장하지 못했어요", + "loadError": "AI 프로필 접근을 불러오지 못했어요" + }, + "profileFacts": { + "cardTitle": "생활 습관 정보", + "cardDescription": "측정값을 어떻게 해석해야 하는지에 영향을 주는 현재 상황을 기록해 주세요.", + "smokingLabel": "흡연 상태", + "alcoholLabel": "음주 패턴", + "shiftLabel": "근무 일정", + "notRecorded": "기록 없음", + "unreadable": "저장된 값을 읽지 못했어요", + "save": "저장", + "savedToast": "생활 습관 정보를 저장했어요", + "savePartialError": "저장 실패: {kinds}. 다시 시도해 주세요.", + "saveKindError": "저장 안 됨", + "loadError": "생활 습관 정보를 불러오지 못했어요", + "remove": "삭제: {kind}", + "removeShort": "삭제", + "removeConfirmTitle": "{kind} 삭제할까요?", + "removeConfirmDescription": "현재 값이 '기록 없음'으로 바뀌어요. 날짜별 이력은 그대로 남아요.", + "removedToast": "현재 생활 습관 정보에서 삭제함: {kind}", + "removeError": "삭제하지 못함: {kind}", + "historyTitle": "이전 값", + "to": "~", + "values": { + "SMOKING_STATUS": { + "NEVER": "피운 적 없음", + "FORMER": "과거 흡연", + "CURRENT": "현재 흡연" + }, + "ALCOHOL_PATTERN": { + "NONE": "안 마심", + "OCCASIONAL": "가끔", + "WEEKLY": "매주", + "MOST_DAYS": "거의 매일" + }, + "SHIFT_SCHEDULE": { + "NONE": "교대 근무 없음", + "FIXED_SHIFT": "고정 교대", + "ROTATING": "순환 교대" + } + } + }, + "disclaimer": "여기 있는 건 직접 남긴 기록이지 의학적 진단이 아니에요. HealthLog는 진단하거나 치료하거나 해석하지 않아요.", + "allergies": { + "cardTitle": "알레르기 및 불내성", + "cardDescription": "반응이 나타나는 물질과 그 반응, 심각도를 기록해요.", + "description": "알레르기나 불내성을 기록해요.", + "add": "알레르기 추가", + "addFirst": "첫 알레르기 추가", + "addTitle": "알레르기 추가", + "editTitle": "알레르기 편집", + "formDescription": "물질명은 평문으로 저장되고, 반응과 메모는 암호화돼요.", + "emptyTitle": "기록된 알레르기가 없어요", + "emptyDescription": "반응이 나타나는 물질과 반응 내용을 추가해 주세요.", + "loadError": "알레르기를 불러오지 못했어요.", + "deletedToast": "알레르기를 삭제했어요", + "deleteError": "알레르기를 삭제하지 못했어요.", + "deleteConfirmTitle": "이 알레르기를 삭제할까요?", + "deleteConfirmDescription": "기록이 삭제돼요. 되돌릴 수 없어요.", + "category": { + "FOOD": "음식", + "MEDICATION": "약", + "ENVIRONMENT": "환경", + "BIOLOGIC": "생물학적 제제", + "OTHER": "기타" + }, + "type": { + "ALLERGY": "알레르기", + "INTOLERANCE": "불내성" + }, + "severity": { + "NONE": "평가 안 함", + "MILD": "경증", + "MODERATE": "중등증", + "SEVERE": "중증" + }, + "status": { + "ACTIVE": "진행 중", + "INACTIVE": "비활성", + "RESOLVED": "해소됨" + }, + "form": { + "substance": "물질", + "substancePlaceholder": "예: 페니실린, 땅콩", + "category": "분류", + "type": "유형", + "severity": "심각도", + "status": "상태", + "onset": "처음 알게 된 때", + "reaction": "반응", + "reactionPlaceholder": "예: 두드러기, 부종, 아나필락시스", + "note": "메모", + "notePlaceholder": "그 밖에 기억해 둘 내용", + "requiredError": "물질을 입력해 주세요.", + "savedToast": "알레르기를 저장했어요", + "updatedToast": "알레르기를 수정했어요", + "saveError": "알레르기를 저장하지 못했어요." + }, + "freeText": { + "label": "자유 메모 (알레르기 및 불내성)", + "hint": "위 목록에 담기 어려운 맥락을 자유롭게 덧붙이는 메모예요.", + "savedToast": "알레르기 메모를 저장했어요", + "saveError": "저장하지 못했어요. 다시 시도해 주세요." + } + }, + "family": { + "cardTitle": "가족력", + "cardDescription": "가족의 질환을 질환마다 하나씩 기록해요.", + "description": "가족의 질환을 기록해요.", + "add": "기록 추가", + "addFirst": "첫 기록 추가", + "addTitle": "가족력 추가", + "editTitle": "가족력 편집", + "formDescription": "관계와 질환은 평문으로 저장되고, 메모는 암호화돼요.", + "emptyTitle": "기록된 가족력이 없어요", + "emptyDescription": "가족에게 있는 질환을 추가해 주세요.", + "loadError": "가족력을 불러오지 못했어요.", + "deletedToast": "기록을 삭제했어요", + "deleteError": "기록을 삭제하지 못했어요.", + "deleteConfirmTitle": "이 기록을 삭제할까요?", + "deleteConfirmDescription": "기록이 삭제돼요. 되돌릴 수 없어요.", + "ageAtOnsetShort": "{age}세", + "relationship": { + "MOTHER": "어머니", + "FATHER": "아버지", + "SISTER": "자매", + "BROTHER": "형제", + "DAUGHTER": "딸", + "SON": "아들", + "GRANDMOTHER_MATERNAL": "외할머니", + "GRANDFATHER_MATERNAL": "외할아버지", + "GRANDMOTHER_PATERNAL": "친할머니", + "GRANDFATHER_PATERNAL": "친할아버지", + "AUNT": "이모/고모", + "UNCLE": "삼촌/외삼촌", + "COUSIN": "사촌", + "HALF_SIBLING": "이복 형제자매", + "OTHER": "기타 친척" + }, + "form": { + "relationship": "가족 관계", + "condition": "질환", + "conditionPlaceholder": "예: 제2형 당뇨병, 유방암", + "ageAtOnset": "발병 나이", + "ageAtOnsetPlaceholder": "예: 55", + "note": "메모", + "notePlaceholder": "그 밖에 기억해 둘 내용", + "requiredError": "질환을 입력해 주세요.", + "ageError": "0에서 120 사이의 나이를 입력해 주세요.", + "savedToast": "기록을 저장했어요", + "updatedToast": "기록을 수정했어요", + "saveError": "기록을 저장하지 못했어요." + } + }, + "conditions": { + "cardTitle": "기저 질환", + "cardDescription": "만성 질환과 코치가 지켜보는 항목이에요.", + "conditionsLabel": "만성 질환", + "focusLabel": "코치가 지켜보는 항목", + "loadError": "질환 정보를 불러오지 못했어요.", + "savedToast": "질환 정보를 저장했어요", + "saveError": "저장하지 못했어요. 다시 시도해 주세요." + }, + "emergency": { + "cardTitle": "응급 정보", + "cardDescription": "응급 구조자가 가장 먼저 봐야 할 정보예요: 혈액형, 연락처, 사전의향서.", + "bloodType": "혈액형", + "organDonor": "장기 기증", + "advanceDirective": "사전의향서", + "contacts": "비상 연락처", + "implants": "삽입물 및 의료기기", + "note": "추가 메모", + "notRecorded": "기록 없음", + "bloodTypeValues": { + "A_POS": "A형 Rh 양성", + "A_NEG": "A형 Rh 음성", + "B_POS": "B형 Rh 양성", + "B_NEG": "B형 Rh 음성", + "AB_POS": "AB형 Rh 양성", + "AB_NEG": "AB형 Rh 음성", + "O_POS": "O형 Rh 양성", + "O_NEG": "O형 Rh 음성", + "UNKNOWN": "모름" + }, + "organDonorValues": { + "YES": "예", + "NO": "아니요", + "UNKNOWN": "밝히지 않음" + }, + "advanceDirectiveValues": { + "EXISTS": "있음", + "NONE": "없음", + "UNKNOWN": "밝히지 않음" + }, + "contactsPlaceholder": "연락할 사람의 이름과 전화번호", + "implantsPlaceholder": "예: 심박조율기, 인슐린 펌프, 금속 삽입물", + "notePlaceholder": "응급 상황에서 임상의가 알아야 할 내용", + "reportHint": "진료용 리포트 첫 페이지에 표시돼요.", + "save": "저장", + "savedToast": "응급 정보를 저장했어요", + "saveError": "응급 정보를 저장하지 못했어요.", + "loadError": "응급 정보를 불러오지 못했어요." + } + }, + "environment": { + "fields": { + "tempMean": "일평균 기온", + "tempMin": "밤 최저 기온", + "sunshine": "일조", + "daylight": "낮 길이", + "precip": "강수량", + "pressureMean": "기압", + "pressureDelta": "기압 변화" + } + }, + "documents": { + "title": "문서", + "subtitle": "모든 소견서·리포트·영상을 한곳에서 검색해요 — 저장할 때 암호화돼요.", + "pageUpload": "업로드", + "kind": { + "DOCTOR_REPORT": "진료용 리포트", + "DISCHARGE_LETTER": "퇴원 소견서", + "OTHER": "기타", + "LAB_RESULT": "검사 결과", + "IMAGING": "영상 검사", + "PRESCRIPTION": "처방", + "REFERRAL": "진료 의뢰서", + "INSURANCE": "보험", + "VACCINATION": "예방접종" + }, + "filter": { + "searchLabel": "문서 검색", + "searchPlaceholder": "제목이나 파일명 검색…", + "typeGroup": "문서 종류", + "conditionGroup": "질환", + "yearGroup": "연도", + "clear": "필터 해제", + "typeAll": "모든 종류", + "typeCount": "{count}개 종류", + "conditionAll": "모든 질환", + "yearAll": "모든 연도" + }, + "card": { + "untitled": "제목 없는 문서", + "selectLabel": "선택: {title}", + "openLabel": "열기: {title}", + "attachmentBadge": "다운로드 전용", + "uploading": "업로드 중…", + "dismissFailed": "실패한 업로드 지우기", + "processing": "처리 중…", + "ready": "완료", + "deleteKey": "Del" + }, + "error": { + "fileTooLarge": "이 파일은 {maxSize} 제한보다 커요.", + "quotaExceeded": "저장 공간이 가득 찼어요 — {quota} 중 {used} 사용 중이에요.", + "unsupportedType": "지원하지 않는 파일 형식이에요.", + "purged": "이 문서는 완전히 삭제되어 복구할 수 없어요.", + "duplicateExists": "같은 문서가 이미 저장돼 있어요.", + "rateLimited": "지금은 업로드가 너무 많아요 — 몇 분 뒤에 다시 시도해 주세요.", + "generic": "업로드하지 못했어요. 다시 시도해 주세요." + }, + "toast": { + "duplicate": "이미 저장돼 있어요 — 기존 문서를 표시해요.", + "deleted": "문서를 삭제했어요", + "restoreFailed": "문서를 복구하지 못했어요." + }, + "timeline": { + "loadingMore": "더 불러오는 중…", + "listLabel": "문서 타임라인" + }, + "upload": { + "browse": "파일 선택", + "quotaAlmostFull": "저장 공간이 거의 찼어요", + "quotaUsed": "{quota} 중 {used} 사용", + "queuedAnnouncement": "{count}개 파일을 업로드 대기열에 추가했어요" + }, + "selection": { + "count": "{count}개 선택함", + "clear": "선택 해제" + }, + "list": { + "loadError": "문서를 불러오지 못했어요." + }, + "empty": { + "title": "문서 보관함이 비어 있어요", + "description": "의사 소견서, 검사 리포트, 영상, 처방전, 예방접종 기록을 암호화해 보관하고 종류·질환·연도로 검색해요.", + "action": "첫 문서 올리기", + "noMatchesTitle": "일치하는 문서 없음", + "noMatchesDescription": "현재 검색·필터에 맞는 문서가 없어요." + }, + "detail": { + "title": "문서 상세", + "summary": { + "title": "요약", + "pending": "요약을 준비하고 있어요.", + "none": "이 문서는 아직 요약이 없어요.", + "withheld": "요약을 만들었지만 안전 검사를 통과하지 못해 표시하지 않아요. 다시 시도하거나 문서를 직접 읽어 보세요.", + "unavailable": "이 문서는 요약을 만들 수 없었어요.", + "generate": "요약 생성", + "generating": "생성 중…" + }, + "index": { + "outcome": { + "local-empty": "읽을 수 있는 텍스트가 없어요. 스캔 문서로 보이는데, AI 비전 제공자로는 읽을 수 있어요.", + "local-unsupported": "이 파일 형식은 검색용으로 읽을 수 없어요.", + "decrypt-error": "저장된 파일을 열어 읽지 못했어요.", + "raster-failed": "PDF 페이지를 읽기용으로 렌더링하지 못했어요. 스캔 팩스 압축 형식이 흔한 원인이에요.", + "provider-error": "지난번에 AI 제공자가 이 문서를 읽지 못했어요.", + "pdf-needs-anthropic": "설정된 AI 제공자는 PDF 파일을 읽을 수 없어요. Claude 제공자나 로컬 OCR로는 읽을 수 있어요.", + "empty-transcription": "AI 제공자가 이 문서에서 텍스트를 찾지 못했어요." + } + }, + "loadError": "이 문서를 불러오지 못했어요.", + "saveError": "변경 내용을 저장하지 못했어요.", + "deleteError": "문서를 삭제하지 못했어요.", + "download": "다운로드", + "delete": "삭제", + "titleLabel": "제목", + "titlePlaceholder": "제목 입력", + "kindLabel": "종류", + "dateLabel": "문서 날짜", + "conditionsLabel": "연결된 질환", + "linkCondition": "연결", + "noConditions": "질환에 연결되지 않음", + "previewUnavailable": "이 파일 형식은 미리보기를 지원하지 않아요", + "uploadedMeta": "추가 {date} · {size}", + "share": "공유", + "labValuesLink": "검사 수치 {count}개 보기", + "visitsLabel": "연결된 진료" + }, + "dropOverlay": { + "title": "파일을 놓아 업로드", + "hint": "문서 보관함에 암호화되어 저장돼요" + }, + "bulk": { + "barLabel": "선택한 문서 작업", + "setKind": "종류 변경", + "linkCondition": "질환 연결", + "delete": "삭제", + "updated": "{count}개 문서를 수정했어요", + "deleted": "{count}개 문서를 삭제했어요", + "restored": "{count}개 문서를 복구했어요", + "partialFailure": "{total}개 중 {failed}개를 수정하지 못했어요", + "failed": "작업을 완료하지 못했어요", + "share": "공유", + "shareTooMany": "공유 링크에는 문서를 최대 {max}개까지 담을 수 있어요. 일부를 선택 해제하고 다시 시도해 주세요.", + "linkVisit": "진료에 연결" + }, + "episodeCard": { + "title": "문서", + "link": "연결", + "upload": "업로드", + "viewAll": "전체 보기", + "emptyLine": "이 질환에 연결된 문서가 아직 없어요.", + "loadError": "연결된 문서를 불러오지 못했어요" + }, + "linkPicker": { + "title": "문서 연결", + "description": "이 질환에 해당하는 문서를 보관함에서 골라 주세요.", + "searchPlaceholder": "제목이나 파일명으로 검색", + "empty": "문서를 찾지 못했어요." + }, + "assist": { + "suggest": "정보 추천", + "suggesting": "읽는 중…", + "suggestedTitle": "추천 제목", + "suggestedKind": "추천 종류", + "suggestedDate": "추천 날짜", + "apply": "적용", + "useTitle": "제목 사용", + "applied": "적용함", + "dismiss": "닫기", + "empty": "이 문서에서 읽어낼 내용이 없었어요.", + "unavailableBody": "AI 제공자를 설정하면 제목·종류·날짜를 추천해요.", + "unavailableLocalOcr": "AI 설정에서 로컬 OCR을 켜면 이 기기에서 이미지를 읽어요.", + "unavailableAction": "AI 설정 열기", + "errorGeneric": "문서를 읽지 못했어요. 다시 시도해 주세요.", + "errorOcr": "이 이미지에서 텍스트를 읽지 못했어요.", + "errorRateLimited": "시간당 AI 읽기 한도에 도달했어요. 나중에 다시 시도해 주세요.", + "errorRateLimitedWaitOne": "시간당 AI 읽기 한도에 도달했어요. 1분쯤 뒤에 다시 시도해 주세요.", + "errorRateLimitedWaitFew": "시간당 AI 읽기 한도에 도달했어요. {count}분쯤 뒤에 다시 시도해 주세요.", + "errorRateLimitedWaitOther": "시간당 AI 읽기 한도에 도달했어요. {count}분쯤 뒤에 다시 시도해 주세요.", + "errorBudget": "오늘 쓸 수 있는 AI 예산을 다 썼어요.", + "errorPdfNeedsVision": "PDF를 읽으려면 Claude 비전 제공자가 필요해요.", + "errorFileType": "이 파일 형식은 읽을 수 없어요.", + "errorNotIndexed": "문서를 먼저 읽은 뒤에 추출해 주세요.", + "errorLocalOcrDisabled": "AI 설정에서 로컬 OCR을 먼저 켜 주세요.", + "errorProvider": "이 작업에 쓸 AI 제공자가 설정돼 있지 않아요.", + "errorTextImageOnly": "로컬 OCR은 이미지만 읽어요 — 이 파일은 읽을 수 없어요." + }, + "summary": { + "summarise": "요약하기", + "showText": "추출한 텍스트 보기", + "summaryTitle": "요약", + "textTitle": "추출한 텍스트", + "close": "숨기기" + }, + "ai": { + "readDone": "AI가 문서를 읽었어요.", + "readStagedOne": "AI가 문서를 읽었어요. 검사 수치 1개를 검토할 수 있어요.", + "readStagedFew": "AI가 문서를 읽었어요. 검사 수치 {count}개를 검토할 수 있어요.", + "readStagedOther": "AI가 문서를 읽었어요. 검사 수치 {count}개를 검토할 수 있어요.", + "read": "AI로 읽기", + "reread": "다시 읽기", + "reading": "읽는 중…", + "notReadYet": "이 문서는 아직 읽지 않았어요." + }, + "review": { + "title": "추출한 값 검토", + "hint": "문서와 일치하는 항목만 승인해 주세요. 체크하지 않은 값은 버려지고, 확인 없이는 아무것도 저장되지 않아요.", + "openOne": "추출한 값 검토", + "openFew": "추출한 값 검토 ({count})", + "openOther": "추출한 값 검토 ({count})", + "includeAria": "포함: {value}", + "type": { + "OBSERVATION": "검사 수치", + "CONDITION": "질환", + "MEDICATION_STATEMENT": "약" + }, + "statedRange": "기재된 범위 {range}", + "needsCheck": "이 값을 문서와 대조한 뒤 저장해 주세요.", + "verify": "확인", + "verifySave": "값 저장", + "verifyInvalid": "이름과 함께 숫자 값이나 텍스트 결과 중 하나만 입력해 주세요.", + "verifyFailed": "수정 내용을 저장하지 못했어요. 다시 시도해 주세요.", + "fieldName": "이름", + "fieldDate": "날짜", + "fieldValue": "값", + "fieldValueText": "결과(텍스트)", + "fieldUnit": "단위", + "fieldDose": "용량", + "unverifiedNoteOne": "값 1개는 아직 확인이 필요해 보류돼요.", + "unverifiedNoteFew": "값 {count}개는 아직 확인이 필요해 보류돼요.", + "unverifiedNoteOther": "값 {count}개는 아직 확인이 필요해 보류돼요.", + "finish": "검토 완료", + "confirmFailed": "검토 내용을 저장하지 못했어요. 다시 시도해 주세요.", + "savedToastOne": "값 1개를 저장했어요", + "savedToastFew": "값 {count}개를 저장했어요", + "savedToastOther": "값 {count}개를 저장했어요", + "nothingSavedToast": "검토를 저장했어요. 추가된 값은 없어요.", + "extract": "검사 수치 추출", + "extracting": "추출 중…", + "extractHint": "이 문서에서 이미 읽은 텍스트를 사용해요. 추출한 값은 검토를 기다리며, 자동으로 저장되지 않아요.", + "extractEmpty": "이 문서에서 추출할 수 있는 값이 없었어요.", + "failUnitRequired": "숫자 값을 저장하려면 단위가 필요해요.", + "failUnitMismatch": "기재된 단위가 이 지표에 저장된 단위와 달라요. 수정한 뒤 검토를 다시 완료해 주세요.", + "failGeneric": "이 값을 저장하지 못했어요." + }, + "contentIndex": { + "indexPrompt": "문서 내용을 검색할 수 있게 만들어요.", + "indexAll": "전체 검색 색인 만들기", + "indexAllPending": "시작하는 중…", + "indexAllQueued": "백그라운드에서 문서 {count}개를 색인하고 있어요.", + "indexAllNothing": "이미 모두 색인돼 있어요.", + "indexAllError": "색인을 시작하지 못했어요." + }, + "share": { + "title": "이 문서 공유", + "multiTitle": "이 문서들 공유", + "multiLabel": "문서 {count}개", + "description": "이 문서를 첨부한 읽기 전용 의료진 링크를 만들어요. 링크와 암호는 한 번만 표시돼요." + }, + "chat": { + "title": "이 문서에 대해 대화", + "openAria": "이 문서에 대해 코치에게 묻기", + "openButton": "코치에게 묻기" + } + }, + "selfDescription": { + "dataPosture": "HealthLog는 셀프 호스팅이에요. 건강 데이터는 본인 인스턴스에 암호화되어 저장돼요. AI 기능은 직접 준비한 제공자 키나 로컬 모델을 쓰기 때문에 설정한 제공자 외에는 아무것도 인스턴스 밖으로 나가지 않고, 건강 데이터에 대한 서드파티 분석도 없어요." + }, + "customMetrics": { + "sectionTitle": "사용자 지표", + "sectionSubtitle": "직접 정의하고 기록하는 측정이에요.", + "add": "사용자 지표 추가", + "addValue": "값 추가", + "defineTitle": "사용자 지표 정의", + "defineDescription": "측정 이름과 단위를 정하고, 원하면 목표 범위도 설정하세요.", + "editTitle": "사용자 지표 편집", + "edit": "편집", + "delete": "삭제", + "deleteConfirmTitle": "이 사용자 지표를 삭제할까요?", + "deleteConfirmDescription": "지표가 목록에서 사라져요. 기록된 값은 그대로 남아요.", + "deletedToast": "사용자 지표를 삭제했어요", + "deleteError": "사용자 지표를 삭제하지 못했어요", + "showAllValues": "모든 값 보기", + "emptyDescription": "직접 측정 항목을 정의하면 기록하고 차트로 볼 수 있어요.", + "noValuesYet": "아직 값이 없어요", + "loadError": "사용자 지표를 불러오지 못했어요", + "loadMore": "값 더 불러오기", + "showingLatestOf": "{total}개 중 최근 {shown}개를 보여줘요", + "backToMeasurements": "측정으로 돌아가기", + "form": { + "name": "이름", + "namePlaceholder": "예: 아침 악력", + "unit": "단위", + "unitPlaceholder": "예: kg", + "decimals": "소수점 자릿수", + "decimalsPlaceholder": "예: 1", + "targetLow": "목표 하한", + "targetLowPlaceholder": "하한값", + "targetHigh": "목표 상한", + "targetHighPlaceholder": "상한값", + "description": "설명", + "descriptionPlaceholder": "무엇을 재는지 (선택)", + "targetHint": "목표 범위는 선택이고, 차트에 띠로 표시돼요.", + "correlationEnabled": "상관관계 발견에 사용", + "correlationEnabledHint": "이 지표를 범위가 정해진 일일 행동 채널로 추가해요. 기본값은 꺼짐이에요.", + "requiredError": "이름과 단위는 필수예요", + "rangeOrderError": "목표 하한은 목표 상한보다 클 수 없어요", + "save": "저장", + "savedToast": "사용자 지표를 저장했어요", + "updatedToast": "사용자 지표를 수정했어요", + "saveError": "사용자 지표를 저장하지 못했어요" + }, + "entry": { + "value": "값", + "measuredAt": "날짜", + "note": "메모", + "addDescription": "이 지표의 값을 기록하세요.", + "edit": "값 편집", + "editTitle": "값 편집", + "editDescription": "값, 날짜, 메모를 수정하세요.", + "delete": "값 삭제", + "deleteConfirmTitle": "이 값을 삭제할까요?", + "deleteConfirmDescription": "이 값은 완전히 삭제돼요.", + "valueError": "숫자 값을 입력해 주세요", + "timestampError": "올바른 날짜를 입력해 주세요", + "savedToast": "값을 기록했어요", + "saveError": "값을 기록하지 못했어요", + "editedToast": "값을 수정했어요", + "editError": "값을 수정하지 못했어요", + "deletedToast": "값을 삭제했어요", + "restoredToast": "값을 복원했어요", + "restoreError": "복원하지 못했어요", + "deleteError": "값을 삭제하지 못했어요" + }, + "chart": { + "empty": "이 범위에는 값이 없어요", + "valueLabel": "값", + "range": { + "30": "30일", + "90": "90일", + "365": "1년", + "all": "전체" + } + }, + "detail": { + "title": "사용자 지표", + "genericDescription": "직접 기록하는 측정이에요. 값을 추가하면 시간에 따른 변화를 볼 수 있어요.", + "emptyTitle": "아직 값이 없어요", + "emptyDescription": "첫 값을 추가하면 이 지표를 차트로 볼 수 있어요.", + "valuesTitle": "{metric} — 전체 값", + "valuesDescription": "이 지표에 기록된 모든 값이에요.", + "backToMetric": "돌아가기: {metric}" + } + }, + "nutrients": { + "names": { + "vitamin_a": "비타민 A", + "thiamin": "티아민(B1)", + "riboflavin": "리보플라빈(B2)", + "niacin": "나이아신(B3)", + "pantothenic_acid": "판토텐산(B5)", + "vitamin_b6": "비타민 B6", + "biotin": "비오틴(B7)", + "folate": "엽산(B9)", + "vitamin_b12": "비타민 B12", + "vitamin_c": "비타민 C", + "vitamin_d": "비타민 D", + "vitamin_e": "비타민 E", + "vitamin_k": "비타민 K", + "calcium": "칼슘", + "iron": "철분", + "magnesium": "마그네슘", + "phosphorus": "인", + "zinc": "아연", + "copper": "구리", + "manganese": "망간", + "selenium": "셀레늄", + "chromium": "크롬", + "molybdenum": "몰리브덴", + "iodine": "요오드", + "water": "수분", + "caffeine": "카페인" + }, + "page": { + "title": "영양소 섭취", + "description": "휴대폰 건강 앱에서 동기화된 수분·비타민/미네랄 섭취량과 직접 입력한 수분 기록이에요.", + "moduleOffTitle": "영양소 섭취가 꺼져 있어요", + "moduleOffDescription": "영양소 모듈을 켜면 수분과 비타민/미네랄 섭취량을 여기서 볼 수 있어요. 동기화된 섭취량은 코치가 읽는 기록에 포함돼요.", + "moduleOffCta": "영양소 켜기", + "emptyTitle": "아직 영양소 데이터가 없어요", + "emptyDescription": "휴대폰 건강 앱 동기화나 직접 입력한 수분 기록에서 데이터가 들어와요.", + "loadError": "영양소 데이터를 불러오지 못했어요", + "lastAttemptTitle": "동기화는 도착했지만 아무것도 저장하지 못했어요", + "lastAttemptAt": "마지막 시도: {date}", + "lastAttemptReason": { + "unitMismatch": "도착한 값이 이 앱에서 해당 영양소에 쓰는 단위와 맞지 않아 저장하지 못했어요.", + "valueOutOfRange": "도착한 양이 이 앱이 저장할 수 있는 범위를 넘어서 저장하지 못했어요.", + "dayInvalid": "기록에 서버가 받을 수 없는 날짜가 담겨 있었어요. 보내는 쪽의 시계나 시간대가 잘못됐을 때 생길 수 있으니 한번 확인해 보세요.", + "upsertFailed": "기록이 서버까지 도착했지만 이쪽 문제로 저장되지 않았어요. 따로 하실 일은 없어요.", + "unknown": "기록이 도착했지만 저장하지 못했어요." + }, + "moduleEnableError": "영양소 모듈을 켜지 못했어요. 다시 시도해 주세요." + }, + "hydration": { + "referenceMeta": "권장 섭취량 {value} {unit}/일 · EFSA" + }, + "caffeine": { + "ceilingMeta": "안전 상한 {value} {unit}/일 · EFSA" + }, + "micronutrients": { + "title": "비타민·미네랄", + "rowMeta": "{window}일 중 {days}일 · 기준 {value} {unit} (EFSA)", + "rowMetaNoRef": "{window}일 중 {days}일", + "footer": "이 기간에 데이터가 있는 미량영양소는 추적 중인 {total}개 중 {tracked}개예요.", + "emptyTitle": "아직 동기화된 미량영양소 데이터가 없어요", + "loadError": "영양소 데이터를 불러오지 못했어요" + }, + "sparseNote": "여기 수치가 낮은 건 보통 결핍이 아니라 기록이 드물다는 뜻이에요 — Apple Health의 하루 합계는 앱이 기록한 양만 반영하고, 전체 식이 섭취량은 아니에요." + }, + "daily": { + "line": { + "score": "오늘 건강 점수는 {score}점이에요.", + "allClear": "오늘은 신경 쓸 일이 없어요." + }, + "push": { + "title": "아침 브리핑", + "bodyProvisional": "{line} · 어젯밤 수면은 아직 들어오지 않았어요." + }, + "item": { + "doseWindow": { + "title": "복약 시간", + "body": "곧 복용할 시간이에요.", + "bodyNamed": "{name} — 곧 복용할 시간이에요.", + "overdueTitle": "복약 시간 지남", + "overdueBody": "복용 시간이 지났어요.", + "overdueBodyNamed": "{name} — 복용 시간이 지났어요." + }, + "preventiveCare": { + "title": "건강검진 시기", + "body": "{label} — 받을 때가 됐어요.", + "bodyMany": "건강검진 {count}건을 받을 때가 됐어요." + }, + "syncIssue": { + "title": "동기화 확인이 필요해요", + "body": "{integration} 동기화가 멈췄어요 — 데이터를 최신으로 유지하려면 다시 연결해 주세요." + }, + "coachCheckin": { + "title": "계획을 점검할 때예요", + "body": "이 계획을 세운 지 일주일쯤 됐어요 — 계속할지, 조정할지, 놓아줄지 정해 보세요. 어느 쪽이든 부담 갖지 않아도 돼요.", + "bodyPlan": "일주일쯤 전에 이렇게 정했어요: “{plan}”. 계속할지, 조정할지, 놓아줄지 정해 보세요 — 어느 쪽이든 부담 갖지 않아도 돼요.", + "adjustPrompt": "점검 계획을 조정하고 싶어요.", + "adjustPromptPlan": "이 계획을 조정하고 싶어요: “{plan}”" + }, + "tensionWindow": { + "title": "오늘 긴장이 있었을 수 있어요", + "body": { + "morning": "오늘 아침 가만히 있는 동안에도 심박수가 안정 시 기준값 위로 한동안 유지됐어요 — 긴장이 있었을 수 있어요.", + "afternoon": "오늘 오후 가만히 있는 동안에도 심박수가 안정 시 기준값 위로 한동안 유지됐어요 — 긴장이 있었을 수 있어요.", + "evening": "오늘 저녁 가만히 있는 동안에도 심박수가 안정 시 기준값 위로 한동안 유지됐어요 — 긴장이 있었을 수 있어요.", + "night": "밤사이 가만히 있는 동안에도 심박수가 안정 시 기준값 위로 한동안 유지됐어요 — 긴장이 있었을 수 있어요." + } + }, + "ecgNewRecording": { + "title": "새 심전도 기록", + "body": "기기가 새 심전도를 기록했어요 — 지금 볼 수 있어요.", + "bodyVerdict": "기기가 새 심전도를 기록하고 이렇게 알렸어요: {verdict}.", + "verdict": { + "irregular": "불규칙한 리듬 가능성", + "notDetected": "불규칙한 리듬 없음", + "inconclusive": "판정 불가 결과" + } + }, + "sameTimeBaseline": { + "title": { + "below": "평소보다 느린 속도", + "above": "평소보다 빠른 속도" + }, + "body": { + "below": "{time} 기준 {today}걸음이에요. 평소 이맘때면 {typical}걸음쯤이었어요.", + "above": "{time} 기준 {today}걸음으로, 평소 이맘때의 {typical}걸음보다 앞서 있어요." + } + }, + "upcomingVisit": { + "title": "다가오는 예약", + "bodyToday": "{what} — 오늘", + "bodyTomorrow": "{what} — 내일" + } + }, + "action": { + "logDose": "복용 기록", + "reconnect": "다시 연결", + "viewCheckups": "건강검진 보기", + "checkinKeep": "계속하기", + "checkinAdjust": "조정", + "checkinLetGo": "놓아주기", + "viewMilestone": "보기", + "viewPulse": "맥박 보기", + "viewEcg": "기록 보기", + "viewSteps": "걸음 수 보기" + }, + "today": { + "scoreLabel": "건강 점수", + "deltaVsBaseline": "기준값 대비 {delta}", + "deltaFlat": "기준값과 비슷해요", + "sleepPending": "어젯밤 수면이 아직 들어오지 않아 오늘 결과는 잠정치예요.", + "worthALook": "살펴볼 만한 것", + "allClear": "오늘은 신경 쓸 일이 없어요.", + "ringLink": "{metric} 자세히 보기" + }, + "milestone": { + "metric": { + "restingHeartRate": "안정 시 심박수", + "heartRateVariability": "심박변이도", + "respiratoryRate": "호흡수", + "weight": "체중", + "generic": "이 지표" + }, + "record": { + "title": "{metric} 최고 기록", + "body": "오늘 {metric} 개인 최고 기록이에요 — 조용히 적어 뒀어요, 하실 일은 없어요." + }, + "returnToBaseline": { + "title": "범위 복귀: {metric}", + "body": "{metric} 수치가 평소 범위로 돌아왔어요. 무슨 일이었든 이제 지나갔어요." + }, + "sustainedInRange": { + "week1": { + "title": "일주일째 범위 안", + "body": "{metric} 수치가 일주일째 평소 범위 안에서 유지되고 있어요. 잔잔하고 꾸준해요." + }, + "week2": { + "title": "2주째 범위 안", + "body": "{metric} 수치가 2주 동안 평소 범위에 머물렀어요. 차분하고 꾸준해요." + }, + "week3": { + "title": "3주째 범위 안", + "body": "{metric} 수치가 3주 내내 평소 범위 안에 있었어요. 꽤 긴 안정 구간이에요." + }, + "week4": { + "title": "한 달째 범위 안", + "body": "{metric} 수치가 꼬박 한 달 동안 평소 범위 안이었어요. 아주 꾸준해요." + } + } + } + }, + "reportSelection": { + "leafAnamnesis": "건강 프로필", + "groupIdentity": "개인 정보", + "groupVitals": "활력징후", + "groupCardio": "심장·순환", + "groupBody": "신체 계측", + "groupSleepRecovery": "수면·회복", + "groupActivity": "활동", + "groupMobility": "이동성·보행", + "groupEnvironment": "환경", + "groupGlucose": "혈당", + "groupLabs": "검사 결과", + "groupMedications": "복약", + "groupHistory": "질환·알레르기", + "groupSensitive": "민감 정보", + "leafPatientIdentity": "이름, 생년월일, 성별, 키", + "leafInsurance": "보험사와 보험 번호", + "leafGlucosePanel": "혈당 패널 (목표 범위 내 시간, GMI, 변동성)", + "leafLabResults": "검사 결과", + "leafMedicationList": "약 목록", + "leafMedicationAdministrations": "기록된 모든 복용", + "leafMedicationCompliance": "복약 순응도", + "leafGlp1Therapy": "GLP-1 치료 (용량 이력, 부작용)", + "leafAllergies": "알레르기와 불내성", + "leafIllnessEpisodes": "아픈 날 기록", + "leafFamilyHistory": "가족력", + "leafMood": "기분", + "leafCycle": "생리 주기", + "templateStandard": "기본 진료용 리포트", + "applyStandard": "{template} 적용", + "standardHint": "한 번 누르면 일반 진료에 보통 필요한 항목이 채워져요. 모든 항목은 그대로 수정할 수 있어요.", + "groupCount": "{on}/{total}", + "groupDisclosure": "{group} 항목 보기", + "sensitiveHint": "여기 있는 항목은 미리 선택되지 않아요. 하나씩 직접 골라 주세요.", + "scopeNone": "아직 선택한 항목이 없어요. 포함할 항목을 고르거나 기본 리포트를 적용해 주세요.", + "scopeCount": "{groups}개 영역에서 {count}개 항목", + "scopeSensitive": "{names} 포함", + "leafVisits": "진료", + "leafImmunizations": "예방접종", + "leafEmergency": "응급 정보 (혈액형, 연락처, 사전지시)" + }, + "recordSharing": { + "banner": { + "viewing": "{name} 님의 기록을 보고 있어요", + "readOnly": "열람만 할 수 있고 수정은 할 수 없어요.", + "canAdd": "기록을 추가할 수 있지만, 이미 있는 내용은 수정하거나 삭제할 수 없어요.", + "leave": "내 기록으로 돌아가기" + }, + "switcher": { + "label": "기록 열기", + "ownRecord": "내 기록" + }, + "switch": { + "failed": "그 기록을 열지 못했어요. 다시 시도해 주세요.", + "noLongerAvailable": "그 기록은 더 이상 공유되지 않아요." + }, + "unavailable": { + "title": "공유 범위에 없는 항목", + "description": "공유되는 것은 건강 기록 자체예요. 문진에 있는 건강 배경도 포함돼요. 계정 설정, 연동 서비스, 알림, AI 기능은 계정 주인에게만 남아요.", + "leave": "내 기록으로 돌아가기" + }, + "state": { + "active": "활성", + "pending": "수락 대기 중", + "expired": "만료됨", + "revoked": "종료됨" + }, + "row": { + "readOnly": "기록 열람 가능", + "canWrite": "열람과 기록 추가 가능", + "canManage": "열람, 추가, 그리고 있는 내용의 수정·삭제까지 가능", + "entireRecord": "기록 전체가 대상", + "sections": "대상: {sections}", + "noSections": "대상 없음 — 이 권한으로는 기록의 어떤 부분도 열 수 없어요", + "invited": "초대", + "accepted": "수락", + "expires": "만료", + "lastUsed": "마지막 열람", + "neverUsed": "없음", + "endedByYou": "내가 종료함", + "endedByOwner": "철회됨", + "handedBack": "반납함", + "ended": "종료됨" + }, + "invite": { + "title": "다른 사람 초대", + "description": "상대가 수락하기 전에는 아무것도 공유되지 않아요.", + "identifierLabel": "사용자 이름 또는 이메일", + "identifierPlaceholder": "로그인에 쓰는 이름", + "identifierHint": "이 인스턴스에 계정이 있어야 해요. 가입하지 않은 이메일 주소로는 초대를 보낼 수 없어요.", + "accessLegend": "상대가 할 수 있는 일", + "accessReadLabel": "열람 가능", + "accessReadCapability": "이 기록에서 공유되는 내용을 모두 열람할 수 있어요. 추가하거나 수정할 수는 없어요.", + "accessWriteLabel": "열람·추가 가능", + "accessWriteCapability": "기록을 추가할 수 있지만, 자기가 추가한 것까지 포함해 어떤 것도 수정하거나 삭제할 수 없어요.", + "accessWriteDetail": "측정값, 검사 결과, 새로 추적할 검사 항목, 아픈 날 기록, 약의 부작용, 새 약을 추가할 수 있고 복용이나 건너뜀을 표시할 수 있어요. 자기가 추가한 것까지 포함해 무엇도 수정하거나 삭제할 수 없고, 내 알림을 미룰 수도 없어요. 그건 나만 할 수 있어요.", + "accessManageLabel": "관리 가능", + "accessManageCapability": "내가 직접 입력한 것까지 포함해 기록을 추가·수정·삭제할 수 있어요.", + "accessManageDetail": "내 기록에서 만들어진 인사이트도 볼 수 있어요. 내 로그인, 보안 설정, 연동, 다른 사람의 권한은 바꿀 수 없어요. 언제든 이 권한을 끝낼 수 있어요.", + "manageStepUpNote": "이 권한을 주려면 2단계 인증이 필요해서 브라우저에서만 할 수 있어요.", + "scopeLegend": "상대가 열 수 있는 범위", + "scopeAllLabel": "기록 전체", + "scopeAllCapability": "기록의 모든 부분. 지금까지의 공유 방식 그대로예요.", + "scopeSomeLabel": "고른 섹션만", + "scopeSomeCapability": "고르지 않은 것은 상대에게 열리지 않아요.", + "scopeManageNote": "관리 권한은 항상 기록 전체가 대상이에요.", + "scopeLeakNote": "섹션의 메모나 이름에 다른 건강 정보가 언급될 수 있어요. 섹션을 공유하면 그 안에 적힌 내용이 전부 공유돼요.", + "scopeAggregateNote": "대시보드 개요, 건강 점수, 일일 요약은 기록 전체를 공유할 때만 보여요.", + "scopeDocumentsNote": "문서에는 무엇이든 들어 있을 수 있어요. 전부 읽어도 되는 상대에게만 공유하세요.", + "scopeEmptyHint": "섹션을 하나 이상 고르거나, 기록 전체 공유로 돌아가세요.", + "scopeChooseHint": "기록 전체를 공유할지, 고른 섹션만 공유할지 선택하세요.", + "expiresLabel": "권한 만료일 (선택)", + "expiresHint": "비워 두면 누군가 끝낼 때까지 권한이 이어져요. 날짜를 넣으면 요청마다 확인해서 그날이 끝날 때 권한이 저절로 멈춰요.", + "submit": "초대 보내기", + "submitting": "보내는 중…", + "sent": "{name} 님에게 초대를 보냈어요.", + "errorNoAccount": "이 인스턴스에 그 이름을 쓰는 계정이 없어요.", + "errorDuplicate": "그 계정에는 이미 내 기록 권한이 있어요.", + "errorSelf": "내 계정이에요.", + "errorInvalid": "사용자 이름이나 이메일 주소를 입력해 주세요.", + "errorRateLimited": "초대가 너무 많아요. 잠시 후 다시 시도해 주세요.", + "errorFailed": "초대를 보내지 못했어요. 다시 시도해 주세요.", + "errorScope": "그 섹션들은 그렇게 공유할 수 없어요. 섹션을 하나 이상 고르거나 기록 전체를 공유하세요.", + "errorManageBrowserOnly": "관리 권한은 2단계 인증이 필요해서 브라우저에서만 줄 수 있어요.", + "errorStepUp": "2단계 인증을 확인한 뒤 다시 시도해 주세요.", + "errorOffline": "서버에 닿지 못했어요. 초대 내용은 그대로 있으니 연결을 확인하고 다시 시도해 주세요." + }, + "given": { + "title": "내 기록을 열 수 있는 사람", + "description": "끝난 권한도 목록에 남아서, 누가 언제 가지고 있었는지 언제든 볼 수 있어요.", + "emptyTitle": "내 기록을 열 수 있는 사람이 없어요", + "emptyDescription": "누군가 읽을 수 있게 하려면 위에서 초대하세요.", + "loadError": "목록을 불러오지 못했어요.", + "revoke": "권한 종료", + "withdrawInvite": "철회", + "revokeTitle": "{name} 님의 권한을 끝낼까요?", + "revokeBody": "곧바로 권한이 사라져요. 내 기록을 열어 둔 브라우저에서도요. 이미 본 내용은 되돌릴 수 없어요. 언제든 다시 초대할 수 있어요.", + "revokeEntriesStay": "상대가 입력한 내용은 내 기록에 남고, 내가 직접 지울 수 있어요.", + "revokeAttribution": "누가 무엇을 입력했는지는 활동 내역에서 {days}일 동안 볼 수 있어요.", + "revokeConfirm": "권한 종료" + }, + "received": { + "title": "내가 열 수 있는 기록", + "description": "초대는 수락하기 전까지 아무 권한도 주지 않아요.", + "emptyTitle": "나에게 기록을 공유한 사람이 없어요", + "emptyDescription": "누군가 초대하면 여기에 초대가 나타나요.", + "loadError": "목록을 불러오지 못했어요.", + "accept": "수락", + "accepting": "수락하는 중…", + "writeConsent": "수락하면 내가 추가한 내용이 {name} 님의 기록에 들어가요. 나중에 그 내용을 수정하거나 지울 수 없고, {name} 님만 할 수 있어요.", + "manageConsent": "수락하면 {name} 님의 기록에 추가할 수 있고, 이미 있는 내용도 수정하거나 지울 수 있어요. {name} 님이 직접 입력한 것도요. 내가 한 일은 전부 내 이름으로 상대의 활동 내역에 남고, {name} 님은 언제든 내 권한을 끝낼 수 있어요.", + "open": "열기", + "renounce": "권한 반납", + "renounceTitle": "{name} 님의 기록 권한을 반납할까요?", + "renounceBody": "곧바로 권한이 사라져요. 상대는 언제든 다시 초대할 수 있어요.", + "renounceConfirm": "반납", + "detail": "언제든 권한을 반납할 수 있어요." + }, + "activity": { + "title": "내 기록을 연 내역", + "description": "다른 사람이 내 기록을 연 모든 순간이에요.", + "retention": "최근 {days}일치예요. 더 오래된 항목은 삭제돼요.", + "emptyTitle": "내 기록을 연 사람이 없어요", + "emptyDescription": "나 말고 다른 사람이 연 내역이 여기 나와요. 하루에 사람마다 한 줄씩이에요.", + "loadError": "활동 내역을 불러오지 못했어요.", + "opened": "{name} 님이 내 기록을 {count}번 열었어요", + "acted": "{name} 님이 내 기록을 수정했어요", + "deletedAccount": "삭제된 계정", + "truncated": "이 목록은 최근 활동만 보여줘요. 전부는 아니에요." + }, + "delegate": { + "addOnlyNote": "이 기록에 추가할 수 있어요. 수정과 삭제는 {name} 님만 할 수 있어요." + }, + "lookingAfter": { + "kindShared": "공유된 기록", + "kindManaged": "관리 프로필" + }, + "managed": { + "title": "내가 돌보는 기록", + "description": "자기 로그인이 없는 건강 기록이에요.", + "explainer": "관리 프로필은 직접 로그인하지 않는 사람의 건강 기록이에요. 아이나 내가 돌보는 사람이요. 로그인도 이메일 주소도 없고, 내가 첫 보호자가 돼요. 보호자는 나중에 더 추가할 수 있고, 보호자 누구나 그 기록으로 전환해 최신 상태로 유지할 수 있어요.", + "nameLabel": "이름", + "namePlaceholder": "이 기록을 알아볼 이름", + "nameHint": "최대 80자예요. 이 기록이 나오는 곳마다 표시돼요.", + "nameRequired": "기록에 이름을 지어 주세요.", + "nameTooLong": "이름이 80자를 넘어요.", + "dobLabel": "생년월일 (선택)", + "dobHint": "적고 싶지 않으면 비워 두세요. 연도로 뭔가를 추측하지는 않아요.", + "localeLabel": "이 기록의 언어", + "localeHint": "이 기록에 적히는 글의 언어예요. 내 화면은 내 언어 그대로예요.", + "timezoneHint": "이 기록의 하루를 재는 기준 시간대예요.", + "timezoneInvalid": "이 브라우저가 모르는 시간대예요.", + "create": "프로필 만들기", + "creating": "만드는 중…", + "created": "{name} 님 프로필을 만들었어요. 계정 메뉴에서 그 기록으로 전환할 수 있어요.", + "errorStepUp": "2단계 인증을 확인한 뒤 다시 시도해 주세요.", + "errorOffline": "서버에 닿지 못했어요. 바뀐 것은 없으니 연결을 확인하고 다시 시도해 주세요.", + "errorInvalid": "서버가 그 내용을 거절했어요. 이름과 날짜, 시간대를 확인해 주세요.", + "errorFailed": "프로필을 만들지 못했어요.", + "delete": "프로필 삭제", + "deleteTitle": "{name} 님 프로필을 삭제할까요?", + "deleteBody": "{name} 님에 대해 기록한 모든 것이 프로필과 함께 사라져요. 측정값, 약, 문서, 메모 전부요. 되돌릴 수 없어요.", + "deleteGuardiansOne": "지금 {count}명이 이 기록을 돌보고 있고, 모두 권한을 잃어요.", + "deleteGuardiansFew": "지금 {count}명이 이 기록을 돌보고 있고, 모두 권한을 잃어요.", + "deleteGuardiansOther": "지금 {count}명이 이 기록을 돌보고 있고, 모두 권한을 잃어요.", + "deleteFloorNote": "마지막 보호자일 때 빠져나오는 방법이기도 해요. 기록을 없앨 수는 있지만, 돌보는 사람 없이 남겨 둘 수는 없어요.", + "deleteConfirm": "프로필 삭제", + "guardiansTitle": "{name} 님을 돌보는 사람", + "guardianYou": "(나)", + "guardianSince": "{date}부터 이 기록을 돌보는 중", + "guardianPending": "{date}에 초대함, 아직 수락 전", + "guardianRemove": "권한 종료", + "guardianRemoveTitle": "{name} 님의 권한을 끝낼까요?", + "guardianRemoveBody": "{name} 님은 {profile} 기록을 더 이상 열 수 없어요. 입력한 내용은 기록에 남아요.", + "guardianWithdrawBody": "{name} 님에게 보낸 초대를 철회해요. {profile} 기록을 연 적이 없어서 기록에는 아무 변화가 없어요.", + "guardianRemoveConfirm": "권한 종료", + "soleGuardian": "{name} 님을 돌보는 사람은 나뿐이라, 여기서 끝낼 권한이 없어요. 다른 사람을 초대해 수락을 기다리거나, '내가 열 수 있는 기록'에서 내 권한을 반납하거나, 프로필을 삭제하세요.", + "guardianInviteLabel": "보호자 더 초대하기", + "guardianInvite": "초대 보내기", + "guardianInviting": "보내는 중…", + "guardianInvited": "{name} 님에게 초대를 보냈어요.", + "guardianExpiresHint": "선택이에요. 내 시간대로 그날이 끝날 때 권한이 만료돼요.", + "guardianPendingNote": "초대한 보호자는 수락해야 인원으로 쳐요. 그전까지는 내가 여전히 마지막 한 명이라, 내 권한을 반납하는 건 거절돼요.", + "guardianErrorDuplicate": "그 계정에는 이미 이 기록 권한이 있어요.", + "guardianErrorManagedInvitee": "관리 프로필은 다른 기록을 돌볼 수 없어요. 직접 로그인하는 계정을 초대해 주세요.", + "guardianErrorNoAccount": "이 인스턴스에 그 이름이나 이메일을 쓰는 계정이 없어요.", + "guardianErrorRateLimit": "최근 한 시간 동안 초대가 너무 많았어요. 잠시 기다렸다 다시 시도해 주세요.", + "guardianErrorFailed": "초대를 보내지 못했어요.", + "errorMfaRequired": "여기에는 2단계 인증이 필요한데 계정에 아직 없어요. 설정 → 보안에서 등록한 뒤 다시 시도해 주세요.", + "rosterLoadError": "이 기록을 돌보는 사람을 불러오지 못했어요.", + "errorRateLimit": "최근 한 시간 동안 만든 프로필이 너무 많아요. 잠시 기다렸다 다시 시도해 주세요." + }, + "toast": { + "savedTo": "{name} 님의 기록에 저장했어요" + }, + "activityVerb": { + "measurementCreate": "{name} 님이 측정값을 추가했어요", + "labResultCreate": "{name} 님이 검사 결과를 추가했어요", + "allergyCreate": "{name} 님이 알레르기를 추가했어요", + "familyHistoryCreate": "{name} 님이 가족력 항목을 추가했어요", + "illnessEpisodeCreate": "{name} 님이 아픈 날 기록을 추가했어요", + "biomarkerCreate": "{name} 님이 바이오마커를 추가했어요", + "customMetricEntryCreate": "{name} 님이 추적 값을 기록했어요", + "medicationSideEffectCreate": "{name} 님이 부작용을 남겼어요", + "medicationCreate": "{name} 님이 약을 추가했어요", + "medicationIntake": "{name} 님이 복용을 표시했어요", + "medicationIntakeRefused": "{name} 님이 복용을 수정하거나 알림을 미루려다 거절됐어요", + "measurementUpdate": "{name} 님이 측정값을 수정했어요", + "measurementDelete": "{name} 님이 측정값을 삭제했어요", + "measurementDeleteBulk": "{name} 님이 측정값 여러 개를 한 번에 삭제했어요", + "measurementRestore": "{name} 님이 측정값을 복원했어요", + "measurementReminderCreate": "{name} 님이 측정 알림을 추가했어요", + "measurementReminderUpdate": "{name} 님이 측정 알림을 수정했어요", + "measurementReminderDelete": "{name} 님이 측정 알림을 삭제했어요", + "measurementReminderComplete": "{name} 님이 측정 알림을 완료했어요", + "measurementReminderSatisfy": "{name} 님이 측정 알림을 해제했어요", + "measurementReminderSkip": "{name} 님이 측정 알림을 건너뛰었어요", + "measurementReminderSnooze": "{name} 님이 측정 알림을 미뤘어요", + "labResultUpdate": "{name} 님이 검사 결과를 수정했어요", + "labResultDelete": "{name} 님이 검사 결과를 삭제했어요", + "labResultRestore": "{name} 님이 검사 결과를 복원했어요", + "biomarkerUpdate": "{name} 님이 바이오마커를 수정했어요", + "biomarkerDelete": "{name} 님이 바이오마커를 삭제했어요", + "allergyUpdate": "{name} 님이 알레르기를 수정했어요", + "allergyDelete": "{name} 님이 알레르기를 삭제했어요", + "familyHistoryUpdate": "{name} 님이 가족력 항목을 수정했어요", + "familyHistoryDelete": "{name} 님이 가족력 항목을 삭제했어요", + "illnessEpisodeUpdate": "{name} 님이 아픈 날 기록을 수정했어요", + "illnessEpisodeDelete": "{name} 님이 아픈 날 기록을 삭제했어요", + "illnessEpisodeResolve": "{name} 님이 아픈 날 기록을 종료로 표시했어요", + "illnessEpisodeRestore": "{name} 님이 아픈 날 기록을 복원했어요", + "encounterVisitCreate": "{name} 님이 진료를 추가했어요", + "encounterVisitUpdate": "{name} 님이 진료를 수정했어요", + "encounterVisitDelete": "{name} 님이 진료를 삭제했어요", + "encounterVisitRestore": "{name} 님이 진료를 복원했어요", + "vaccinationRecordCreate": "{name} 님이 예방접종을 추가했어요", + "vaccinationRecordUpdate": "{name} 님이 예방접종을 수정했어요", + "vaccinationRecordDelete": "{name} 님이 예방접종을 삭제했어요", + "vaccinationRecordRestore": "{name} 님이 예방접종을 복원했어요", + "practitionerContactCreate": "{name} 님이 주소록에 병원을 추가했어요", + "practitionerContactUpdate": "{name} 님이 주소록 항목을 수정했어요", + "practitionerContactDelete": "{name} 님이 주소록 항목을 삭제했어요", + "practitionerContactRestore": "{name} 님이 주소록 항목을 복원했어요", + "illnessDayLogUpsert": "{name} 님이 아픈 날 하루를 기록했어요", + "medicationUpdate": "{name} 님이 약을 수정했어요", + "medicationDelete": "{name} 님이 약을 삭제했어요", + "medicationGlp1Update": "{name} 님이 GLP-1 요법을 수정했어요", + "medicationScheduleCreate": "{name} 님이 복용 일정을 추가했어요", + "medicationScheduleUpdate": "{name} 님이 복용 일정을 수정했어요", + "medicationScheduleDelete": "{name} 님이 복용 일정을 삭제했어요", + "medicationInventoryCreate": "{name} 님이 약 포장을 추가했어요", + "medicationInventoryUpdate": "{name} 님이 약 포장을 수정했어요", + "medicationInventoryDelete": "{name} 님이 약 포장을 삭제했어요", + "medicationSideEffectDelete": "{name} 님이 부작용을 삭제했어요", + "medicationIntakeUpdate": "{name} 님이 복용 기록을 수정했어요", + "medicationIntakeDelete": "{name} 님이 복용 기록을 삭제했어요", + "medicationIntakeBulkDelete": "{name} 님이 복용 기록 여러 개를 한 번에 삭제했어요", + "medicationIntakePurge": "{name} 님이 어떤 약의 복용 기록 전체를 삭제했어요", + "medicationIntakeImport": "{name} 님이 약 하나의 복용 기록 불러오기를 시작했어요", + "medicationIntakeHistoryImport": "{name} 님이 복용 기록 불러오기를 시작했어요", + "moodEntryCreate": "{name} 님이 기분 기록을 추가했어요", + "moodEntryUpdate": "{name} 님이 기분 기록을 수정했어요", + "moodEntryDelete": "{name} 님이 기분 기록을 삭제했어요", + "moodDeleteBulk": "{name} 님이 기분 기록 여러 개를 한 번에 삭제했어요", + "moodRestore": "{name} 님이 기분 기록을 복원했어요", + "mentalHealthCreate": "{name} 님이 설문 결과를 기록했어요", + "cycleDelete": "{name} 님이 생리 주기를 삭제했어요", + "cycleDayLogUpsert": "{name} 님이 주기 하루를 기록했어요", + "cycleDayLogUpdate": "{name} 님이 주기 하루 기록을 수정했어요", + "cycleDayLogDelete": "{name} 님이 주기 하루 기록을 삭제했어요", + "cyclePeriodBoundary": "{name} 님이 생리 시작일이나 종료일을 수정했어요", + "cycleSymptomCreate": "{name} 님이 주기 증상을 추가했어요", + "nutrientWaterWrite": "{name} 님이 물 섭취를 기록했어요", + "healthRecordExport": "{name} 님이 기록을 내보냈어요", + "rejectedMeasurementQuery": "{name} 님이 이 기록에서 읽을 수 없는 측정값 검색을 보냈어요", + "rejectedDoseQuery": "{name} 님이 이 기록에서 읽을 수 없는 복용 검색을 보냈어요", + "rejectedMoodQuery": "{name} 님이 이 기록에서 읽을 수 없는 기분 검색을 보냈어요", + "rejectedMeasurementCreate": "{name} 님이 측정값을 추가하려다 거절됐어요", + "rejectedMeasurementBatch": "{name} 님이 측정값 여러 개를 추가하려다 거절됐어요", + "rejectedMeasurementUpdate": "{name} 님이 측정값을 수정하려다 거절됐어요", + "rejectedLabCreate": "{name} 님이 검사 결과를 추가하려다 거절됐어요", + "rejectedLabUpdate": "{name} 님이 검사 결과를 수정하려다 거절됐어요", + "rejectedBiomarkerCreate": "{name} 님이 바이오마커를 추가하려다 거절됐어요", + "rejectedBiomarkerUpdate": "{name} 님이 바이오마커를 수정하려다 거절됐어요", + "rejectedReminderCreate": "{name} 님이 측정 알림을 추가하려다 거절됐어요", + "rejectedReminderUpdate": "{name} 님이 측정 알림을 수정하려다 거절됐어요", + "rejectedMoodCreate": "{name} 님이 기분 기록을 추가하려다 거절됐어요", + "rejectedMoodUpdate": "{name} 님이 기분 기록을 수정하려다 거절됐어요", + "rejectedDoseCreate": "{name} 님이 복용을 기록하려다 거절됐어요", + "rejectedDoseUpdate": "{name} 님이 복용 기록을 수정하려다 거절됐어요", + "rejectedDoseImport": "{name} 님이 복용 기록을 불러오려다 거절됐어요", + "rejectedTrackedValue": "{name} 님이 추적 값을 기록하려다 거절됐어요", + "deniedDoseImport": "{name} 님이 시작한 복용 기록 불러오기를 대기열에 넣지 못했어요", + "anamnesisFactCreate": "{name} 님이 건강 배경 항목을 추가했어요", + "customMetricCreate": "{name} 님이 추적 값 종류를 추가했어요", + "customMetricUpdate": "{name} 님이 추적 값 종류를 수정했어요", + "customMetricDelete": "{name} 님이 추적 값 종류를 삭제했어요", + "documentStore": "{name} 님이 문서를 추가했어요", + "documentUpdate": "{name} 님이 문서를 수정했어요", + "documentDiscard": "{name} 님이 문서를 폐기했어요", + "medicationIntakeImportFinished": "{name} 님이 시작한 복용 기록 불러오기가 끝났어요", + "medicationOneShotReconciled": "{name} 님이 기록한 복용에 따라 일회성 약이 시작되거나 종료됐어요", + "vaccinationBoosterPlanned": "{name} 님이 추가 접종을 계획했어요" + }, + "section": { + "measurements": { + "label": "측정값", + "detail": "체중, 혈압, 맥박, 혈당, 수면, 운동, 그리고 직접 추적하는 값이에요." + }, + "medications": { + "label": "복약", + "detail": "약 목록과 복용 일정, 복용 기록, 부작용, 재고예요." + }, + "labs": { + "label": "검사 결과", + "detail": "검사 결과와 그 뒤에 있는 검사 항목 목록이에요." + }, + "profile": { + "label": "건강 배경", + "detail": "알레르기, 가족력, 건강 프로필의 항목들, 진료와 병원, 예방접종 기록이에요." + }, + "illness": { + "label": "아픈 날", + "detail": "아픈 기간 기록과 그 아래 남긴 하루 기록이에요." + }, + "mind": { + "label": "기분과 마음", + "detail": "기분 기록과 정신건강 설문 이력이에요." + }, + "cycle": { + "label": "생리 주기", + "detail": "생리 주기 기록이에요." + }, + "documents": { + "label": "문서", + "detail": "문서 보관함에 저장된 모든 것이에요." + } + }, + "actionError": { + "retry": "다시 시도", + "offline": "서버에 닿지 못했어요. 바뀐 것은 없으니 연결을 확인하고 다시 시도해 주세요.", + "rateLimited": "연속 시도가 너무 많아요. 잠시 기다렸다 다시 시도해 주세요.", + "acceptExpired": "그 초대는 만료됐어요. 새로 받아 주세요.", + "acceptWithdrawn": "그 초대는 철회됐어요.", + "acceptNotPending": "그 초대는 이미 수락됐어요.", + "acceptNotFound": "그 초대는 더 이상 없어요.", + "acceptFailed": "초대를 수락하지 못했어요.", + "lastGuardian": "그 프로필을 돌보는 사람이 이 사람뿐이라 아직 권한을 끝낼 수 없어요. 다른 보호자를 먼저 추가하거나 프로필을 삭제하세요.", + "alreadyEnded": "그 권한은 이미 끝났어요.", + "notFound": "그 권한은 더 이상 없어요.", + "revokeFailed": "권한을 끝내지 못했어요.", + "renounceFailed": "권한을 반납하지 못했어요.", + "profileNotFound": "그 프로필이 더 이상 없어요. 이미 삭제됐거나, 내 권한이 끝났을 수 있어요.", + "deleteProfileFailed": "프로필을 삭제하지 못했어요.", + "removeGuardianFailed": "그 권한을 끝내지 못했어요." + }, + "anamnesis": { + "relativeNote": "가족력 항목은 이 사람의 친척에 대한 내용이에요. 여기 기록되기를 상대가 원할 때만 추가하세요." + } + }, + "encounters": { + "kind": { + "routine": "정기 진료", + "acute": "급성 진료", + "specialist": "전문의 진료", + "preventive": "예방 검진", + "emergency": "응급", + "hospital": "병원", + "therapy": "치료 세션", + "other": "예약" + }, + "sectionTitle": "진료", + "sectionDescription": "병원 방문과 잡아 둔 예약을 한곳에서 봐요.", + "add": "진료", + "loadError": "진료 기록을 불러오지 못했어요.", + "emptyTitle": "아직 기록한 진료가 없어요", + "emptyDescription": "날짜만 있으면 충분해요. 나머지는 모두 선택이에요.", + "upcomingHeading": "예정", + "pastHeading": "지난 기록", + "createTitle": "진료 기록하기", + "editTitle": "진료 편집", + "formDescription": "날짜만 필수예요.", + "created": "진료를 저장했어요", + "updated": "진료를 수정했어요", + "saveFailed": "진료를 저장하지 못했어요.", + "checkupNotClosed": "진료를 저장했어요. 건강검진은 열린 채로 남아요 — 권한이 거기까지는 미치지 않아요.", + "status": { + "planned": "예정", + "done": "방문함", + "cancelled": "취소됨", + "noShow": "불참" + }, + "card": { + "documentCount": "연결된 문서", + "labCount": "연결된 검사 결과", + "conditionCount": "연결된 질환" + }, + "form": { + "date": "날짜와 시간", + "dateDerivesStatusHint": "미래 날짜는 잡아 둔 예약으로 처리돼요.", + "status": "상태", + "practitioner": "병원 또는 의사", + "practitionerHint": "선택 — 이름은 나중에 추가해도 돼요.", + "practitionerNone": "지정 안 함", + "practitionerSearch": "이름이나 병원 검색", + "practitionerNoMatch": "항목을 찾지 못했어요.", + "practitionerAdd": "새로 추가", + "practitionerClear": "선택 해제", + "kind": "종류", + "reason": "사유", + "outcome": "결과", + "outcomeReveal": "결과 추가", + "linkDocuments": "문서", + "linkLabResults": "검사 결과", + "linkConditions": "질환", + "linkNothingToOffer": "아직 연결할 항목이 없어요." + }, + "deleteTitle": "이 진료를 삭제할까요?", + "deleteDescription": "기록이 삭제돼요. 예약된 진료라면 알림도 함께 사라져요.", + "deleteFailed": "진료를 삭제하지 못했어요.", + "suggestion": { + "single": "이 진료에 속하는 항목인가요?", + "choose": "어느 진료에 속하나요?" + }, + "episodeCard": { + "title": "관련 진료", + "emptyLine": "아직 이 질환에 연결된 진료가 없어요.", + "add": "진료 추가" + }, + "dashboard": { + "nextVisit": "다음 예약: {what} ({when})" + } + }, + "checkups": { + "viewSwitchLabel": "건강검진 또는 진료 보기", + "tabVorsorge": "건강검진", + "tabVisits": "진료" + }, + "practitioners": { + "title": "병원과 의사", + "description": "나만의 주소록이에요 — 병원을 한 번만 입력하면 어디서든 골라 쓸 수 있어요.", + "linkLabel": "주소록", + "add": "항목", + "searchPlaceholder": "이름, 병원, 진료과 검색", + "loadError": "주소록을 불러오지 못했어요.", + "emptyTitle": "아직 항목이 없어요", + "emptyDescription": "병원을 추가해 두면 진료를 기록할 때 골라 쓸 수 있어요.", + "noMatchTitle": "결과 없음", + "noMatchDescription": "이 검색어와 맞는 항목이 없어요.", + "createTitle": "항목 추가", + "editTitle": "항목 편집", + "formDescription": "이름만 필수예요.", + "name": "이름", + "specialty": "진료과", + "practice": "병원", + "location": "주소", + "phone": "전화", + "note": "메모", + "noteHint": "암호화해서 저장돼요.", + "saveFailed": "항목을 저장하지 못했어요.", + "deleteTitle": "이 항목을 삭제할까요?", + "deleteDescription": "기존 진료 기록은 그대로 남고, 이 이름도 유지돼요.", + "deleteFailed": "항목을 삭제하지 못했어요.", + "noSpecialtyGroup": "진료과 없음" + }, + "links": { + "picker": { + "add": "추가", + "done": "완료", + "selectAll": "전체 선택", + "searchPlaceholder": "검색", + "searchNoMatch": "결과 없음", + "remove": "제거" + } + } +} diff --git a/scripts/generate-i18n-boot.mjs b/scripts/generate-i18n-boot.mjs index 74bd331c0..16b565b37 100644 --- a/scripts/generate-i18n-boot.mjs +++ b/scripts/generate-i18n-boot.mjs @@ -28,7 +28,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); const repoRoot = join(__dirname, ".."); const outDir = join(repoRoot, "public", "i18n"); -const LOCALES = ["de", "en", "fr", "es", "it", "pl"]; +const LOCALES = ["de", "en", "fr", "es", "it", "pl", "ko"]; mkdirSync(outDir, { recursive: true }); diff --git a/scripts/i18n-key-tool.mjs b/scripts/i18n-key-tool.mjs index f928ceecb..e8fb2baa8 100644 --- a/scripts/i18n-key-tool.mjs +++ b/scripts/i18n-key-tool.mjs @@ -11,7 +11,7 @@ */ import { readFileSync, writeFileSync } from "node:fs"; -const LOCALES = ["de", "en", "es", "fr", "it", "pl"]; +const LOCALES = ["de", "en", "es", "fr", "it", "pl", "ko"]; const file = (loc) => `messages/${loc}.json`; function read(loc) { diff --git a/scripts/split-description.mjs b/scripts/split-description.mjs index 38d3f6241..d26634386 100644 --- a/scripts/split-description.mjs +++ b/scripts/split-description.mjs @@ -18,7 +18,7 @@ */ import { readFileSync, writeFileSync } from "node:fs"; -const LOCALES = ["de", "en", "es", "fr", "it", "pl"]; +const LOCALES = ["de", "en", "es", "fr", "it", "pl", "ko"]; const BOUNDARY = /(?<=[.!?])\s+(?=[A-ZÄÖÜÀÂÉÈÊÎÔÙÛÇŁŚŹŻĄĆĘŃÓ0-9„"„«])/u; /** diff --git a/src/__tests__/copy-matches-behaviour-guard.test.ts b/src/__tests__/copy-matches-behaviour-guard.test.ts index 00189f358..b656cda6a 100644 --- a/src/__tests__/copy-matches-behaviour-guard.test.ts +++ b/src/__tests__/copy-matches-behaviour-guard.test.ts @@ -40,7 +40,7 @@ import { join } from "node:path"; import { describe, expect, it } from "vitest"; const MESSAGES = join(__dirname, "../../messages"); -const LOCALES = ["de", "en", "es", "fr", "it", "pl"] as const; +const LOCALES = ["de", "en", "es", "fr", "it", "pl", "ko"] as const; function bundle(locale: string): Record { return JSON.parse(readFileSync(join(MESSAGES, `${locale}.json`), "utf8")); @@ -78,6 +78,7 @@ const FINALITY_PHRASES: Record = { "permanente", ], pl: ["nie można cofnąć", "nie można tego cofnąć", "trwale", "nieodwracaln"], + ko: ["되돌릴 수 없", "복구할 수 없", "영구", "완전히 삭제"], }; /** Per-locale phrase that promises the short undo window. */ @@ -88,6 +89,7 @@ const UNDO_PHRASES: Record = { fr: "annuler cette action", it: "annullare", pl: "cofnąć", + ko: "되돌릴 수 있", }; /** Confirmation bodies whose surface renders an Undo affordance. */ @@ -171,6 +173,7 @@ describe("sleep debt is labelled as a balance, not a summed deficit", () => { fr: ["cumulé", "cumulée", "déficit"], it: ["cumulato", "cumulata", "accumulato", "deficit"], pl: ["skumulowan", "niedobór", "niedoboru"], + ko: ["누적", "부족분", "적자", "쌓인"], }; for (const locale of LOCALES) { @@ -296,6 +299,7 @@ describe("the mood prognosis never states a cause", () => { "peggiora il tuo", ], pl: ["ponieważ", "powoduje", "z powodu", "wywołuje", "poprawia twój"], + ko: ["때문", "탓", "원인", "유발", "초래", "야기", "덕분", "인해"], }; /** Every leaf string under `insights.mood.prognosis`, flattened. */ @@ -394,6 +398,7 @@ describe("the MCP token copy never claims the mint is read-only", () => { fr: ["jeton en lecture seule"], it: ["token in sola lettura"], pl: ["token tylko do odczytu"], + ko: ["읽기 전용 토큰", "읽기전용 토큰"], }; for (const locale of LOCALES) { diff --git a/src/__tests__/dynamic-key-exhaustiveness.test.ts b/src/__tests__/dynamic-key-exhaustiveness.test.ts index 2d9614684..337d49a7f 100644 --- a/src/__tests__/dynamic-key-exhaustiveness.test.ts +++ b/src/__tests__/dynamic-key-exhaustiveness.test.ts @@ -166,13 +166,14 @@ describe("dynamic-key exhaustiveness (enum-derived)", () => { expect(LOCALES.length).toBeGreaterThanOrEqual(6); }); - it("discovers all six shipped locales", () => { + it("discovers all seven shipped locales", () => { expect(LOCALES.map((l) => l.locale).sort()).toEqual([ "de", "en", "es", "fr", "it", + "ko", "pl", ]); }); diff --git a/src/__tests__/i18n-english-leak-guard.test.ts b/src/__tests__/i18n-english-leak-guard.test.ts index ea51a04c2..424ddf033 100644 --- a/src/__tests__/i18n-english-leak-guard.test.ts +++ b/src/__tests__/i18n-english-leak-guard.test.ts @@ -33,7 +33,7 @@ const EN_PATH = join(MESSAGES_DIR, "en.json"); // intentionally excluded — it carries accepted anglicisms (e.g. "Health // Score") that would be noise here, and is covered by its own pinned checks in // `i18n-locale-integrity`. -const LEAK_LOCALES = ["es", "fr", "it", "pl"] as const; +const LEAK_LOCALES = ["es", "fr", "it", "pl", "ko"] as const; function flattenValues( obj: unknown, @@ -193,6 +193,14 @@ const LEGIT_IDENTICAL = new Set([ "mentalHealth.sciOptions.problemDuration.2", "mentalHealth.sciOptions.problemDuration.1", "mentalHealth.sciOptions.problemDuration.0", + // Proper nouns, a URL label and an acronym — the accepted written form in + // Korean is the Latin original, so these stay identical to en.json. + "auth.emailPlaceholder", + "settings.fitbitTag", + "settings.googleHealthTag", + "settings.nightscoutUrl", + "admin.aiServerKey.baseUrlLabel", + "admin.webPushVapidSubject", ]); describe("i18n English-leak guard", () => { diff --git a/src/__tests__/insights-unit-literal-guard.test.ts b/src/__tests__/insights-unit-literal-guard.test.ts index 907dbd673..4af147f2a 100644 --- a/src/__tests__/insights-unit-literal-guard.test.ts +++ b/src/__tests__/insights-unit-literal-guard.test.ts @@ -20,7 +20,7 @@ import { join } from "node:path"; * the key is a prop, not a call argument. Both halves are asserted here. */ -const LOCALES = ["en", "de", "es", "fr", "it", "pl"] as const; +const LOCALES = ["en", "de", "es", "fr", "it", "pl", "ko"] as const; const INSIGHTS_PAGES_DIR = join(process.cwd(), "src/app/insights"); /** diff --git a/src/__tests__/manage-activity-inventory.test.ts b/src/__tests__/manage-activity-inventory.test.ts index 6f0b765ec..57a68c1c3 100644 --- a/src/__tests__/manage-activity-inventory.test.ts +++ b/src/__tests__/manage-activity-inventory.test.ts @@ -93,7 +93,7 @@ const NOT_IN_THE_FEED = [ /** What an action with no sentence of its own renders as. */ const FALLBACK_KEY = "recordSharing.activity.acted"; /** The six bundles the product ships. */ -const LOCALES = ["de", "en", "es", "fr", "it", "pl"] as const; +const LOCALES = ["de", "en", "es", "fr", "it", "pl", "ko"] as const; /** Every `route.ts` under `src/app/api`, walked rather than globbed: a glob's * `*` skips a leading dot and this tree has dot-directories. */ diff --git a/src/__tests__/moodlog-removal-guard.test.ts b/src/__tests__/moodlog-removal-guard.test.ts index d734121c5..708b1c6e7 100644 --- a/src/__tests__/moodlog-removal-guard.test.ts +++ b/src/__tests__/moodlog-removal-guard.test.ts @@ -25,7 +25,7 @@ import { INTEGRATION_DISPLAY_NAMES } from "@/components/settings/integrations/in const ROOT = path.resolve(__dirname, "../.."); -const LOCALES = ["de", "en", "es", "fr", "it", "pl"] as const; +const LOCALES = ["de", "en", "es", "fr", "it", "pl", "ko"] as const; /** Every path the retired bridge occupied. */ const REMOVED_PATHS = [ diff --git a/src/__tests__/record-settings-ui.test.tsx b/src/__tests__/record-settings-ui.test.tsx index 5d9e17ef3..cff7a4479 100644 --- a/src/__tests__/record-settings-ui.test.tsx +++ b/src/__tests__/record-settings-ui.test.tsx @@ -50,7 +50,7 @@ describe("managed record settings UI", () => { fromRoot("src/components/settings/record-settings-section-gate.tsx"), "utf8", ), - ...["de", "en", "es", "fr", "it", "pl"].map((locale) => + ...["de", "en", "es", "fr", "it", "pl", "ko"].map((locale) => readFile(fromRoot(`messages/${locale}.json`), "utf8"), ), ]); diff --git a/src/__tests__/sharing-boundary-copy.test.ts b/src/__tests__/sharing-boundary-copy.test.ts index a96aaa9a0..7c6233c7f 100644 --- a/src/__tests__/sharing-boundary-copy.test.ts +++ b/src/__tests__/sharing-boundary-copy.test.ts @@ -38,7 +38,7 @@ import { * it — which is the part that rots silently. */ const ROOT = process.cwd(); -const LOCALES = ["de", "en", "es", "fr", "it", "pl"] as const; +const LOCALES = ["de", "en", "es", "fr", "it", "pl", "ko"] as const; /** The published description of the level, off the Zod schema itself. */ function accessDescription(): string { @@ -109,10 +109,14 @@ describe("the published boundary matches the one the code draws", () => { // accent: the section is "Anamnèse" in French and "Anamneza" in Polish, // so anything past "anamn" is already language-specific and a matcher // tuned to one of them reports the others as broken. - if (!/anamn/i.test(text)) missing.push(locale); + // Korean shares no Latin stem for either concept, so it is matched on + // its own words: "문진" is the anamnesis section, "계정" the account. + if (!/anamn|문진/i.test(text)) missing.push(locale); // And it must still say the account itself stays with its owner — // dropping that would fix one over-claim by removing the boundary. - expect(text, locale).toMatch(/konto|account|compte|cuenta|konta|koncie/i); + expect(text, locale).toMatch( + /konto|account|compte|cuenta|konta|koncie|계정/i, + ); } expect(missing).toEqual([]); }); diff --git a/src/__tests__/v1341-locale-parity.test.ts b/src/__tests__/v1341-locale-parity.test.ts index 19ac817c4..10d6e7733 100644 --- a/src/__tests__/v1341-locale-parity.test.ts +++ b/src/__tests__/v1341-locale-parity.test.ts @@ -4,7 +4,7 @@ import { join } from "node:path"; import { describe, expect, it } from "vitest"; const ROOT = join(__dirname, "../.."); -const LOCALES = ["en", "de", "es", "fr", "it", "pl"] as const; +const LOCALES = ["en", "de", "es", "fr", "it", "pl", "ko"] as const; const REQUIRED_KEYS = [ // Security: adding a passkey now requires an existing sign-in factor. diff --git a/src/app/__tests__/not-found-copy.test.ts b/src/app/__tests__/not-found-copy.test.ts index af29e919b..b3c0bab44 100644 --- a/src/app/__tests__/not-found-copy.test.ts +++ b/src/app/__tests__/not-found-copy.test.ts @@ -10,7 +10,7 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; const ROOT = join(__dirname, "../../.."); -const LOCALES = ["en", "de", "fr", "es", "it", "pl"] as const; +const LOCALES = ["en", "de", "fr", "es", "it", "pl", "ko"] as const; interface Messages { notFound: { diff --git a/src/app/api/admin/notifications/test/route.ts b/src/app/api/admin/notifications/test/route.ts index 12d27c357..33492125f 100644 --- a/src/app/api/admin/notifications/test/route.ts +++ b/src/app/api/admin/notifications/test/route.ts @@ -69,7 +69,8 @@ function isLocale(value: string | null | undefined): value is Locale { value === "fr" || value === "es" || value === "it" || - value === "pl" + value === "pl" || + value === "ko" ); } diff --git a/src/app/api/medications/extract/route.ts b/src/app/api/medications/extract/route.ts index 5b84598a8..d86e399d2 100644 --- a/src/app/api/medications/extract/route.ts +++ b/src/app/api/medications/extract/route.ts @@ -91,7 +91,7 @@ const MAX_OUTPUT_TOKENS = 600; const requestSchema = z.object({ text: z.string().min(1).max(MAX_TEXT_LENGTH), /** Optional UI locale; informational hint for the model. */ - locale: z.enum(["en", "de", "es", "fr", "it", "pl"]).optional(), + locale: z.enum(["en", "de", "es", "fr", "it", "pl", "ko"]).optional(), /** * Optional override of the reference date used to resolve relative * phrases ("tomorrow", "next Monday"). Defaults to the server's diff --git a/src/app/api/notifications/apns/test/route.ts b/src/app/api/notifications/apns/test/route.ts index ec024804b..c3318238c 100644 --- a/src/app/api/notifications/apns/test/route.ts +++ b/src/app/api/notifications/apns/test/route.ts @@ -32,7 +32,8 @@ function isLocale(value: string | null | undefined): value is Locale { value === "fr" || value === "es" || value === "it" || - value === "pl" + value === "pl" || + value === "ko" ); } diff --git a/src/app/api/settings/telegram/test/route.ts b/src/app/api/settings/telegram/test/route.ts index 7f87e79d5..5b387ab21 100644 --- a/src/app/api/settings/telegram/test/route.ts +++ b/src/app/api/settings/telegram/test/route.ts @@ -15,7 +15,8 @@ function isLocale(value: string | null | undefined): value is Locale { value === "fr" || value === "es" || value === "it" || - value === "pl" + value === "pl" || + value === "ko" ); } diff --git a/src/components/charts/__tests__/trend-card-baseline-alignment.test.tsx b/src/components/charts/__tests__/trend-card-baseline-alignment.test.tsx index 94dc50ec9..cffc653c9 100644 --- a/src/components/charts/__tests__/trend-card-baseline-alignment.test.tsx +++ b/src/components/charts/__tests__/trend-card-baseline-alignment.test.tsx @@ -202,7 +202,7 @@ describe("dashboard.*Short keys exist across all six locales", () => { // Resolved at test time so the assertion runs against the current // bundle, not a stale cached snapshot. - const locales = ["en", "de", "fr", "es", "it", "pl"] as const; + const locales = ["en", "de", "fr", "es", "it", "pl", "ko"] as const; it.each(locales)("%s carries every dashboard.*Short key", async (locale) => { const messages = (await import(`../../../../messages/${locale}.json`)) diff --git a/src/components/clinician/__tests__/clinician-view-locales.test.tsx b/src/components/clinician/__tests__/clinician-view-locales.test.tsx index 6d8e6e188..ba729e5e8 100644 --- a/src/components/clinician/__tests__/clinician-view-locales.test.tsx +++ b/src/components/clinician/__tests__/clinician-view-locales.test.tsx @@ -223,9 +223,9 @@ const FULL_RECORD = { const DOTTED_KEY = /\b[a-z][A-Za-z0-9]*(\.[A-Za-z0-9_]+){2,}\b/g; describe(" resolves every label in every locale", () => { - it("covers all six shipped locales", () => { + it("covers all seven shipped locales", () => { // A floor: an empty locale list would make the loop below assert nothing. - expect(locales.length).toBe(6); + expect(locales.length).toBe(7); }); for (const locale of locales) { diff --git a/src/components/i18n/maintainership-banner.tsx b/src/components/i18n/maintainership-banner.tsx index d4f3f9dc1..5c957d42b 100644 --- a/src/components/i18n/maintainership-banner.tsx +++ b/src/components/i18n/maintainership-banner.tsx @@ -15,7 +15,7 @@ import { useTranslations } from "@/lib/i18n/context"; * but the prose has not been hand-reviewed for register, regional * vocabulary, or domain-specific nuance. * - * This banner renders ONLY on non-maintained locales (FR/ES/IT/PL + * This banner renders ONLY on non-maintained locales (FR/ES/IT/PL/KO * today) and surfaces a small, dismissible strip at the top of the * authenticated app shell. It tells the user the translation is * AI-maintained and links to the GitHub translation-feedback issue diff --git a/src/components/layout/__tests__/offline-banner.test.tsx b/src/components/layout/__tests__/offline-banner.test.tsx index ae5c58a49..de8e63aaa 100644 --- a/src/components/layout/__tests__/offline-banner.test.tsx +++ b/src/components/layout/__tests__/offline-banner.test.tsx @@ -66,7 +66,7 @@ describe("", () => { }); it("ships the message key in every supported locale", () => { - for (const locale of ["en", "de", "fr", "es", "it", "pl"] as const) { + for (const locale of ["en", "de", "fr", "es", "it", "pl", "ko"] as const) { const m = loadMessages(locale); expect(m.offlineBanner.message.length, locale).toBeGreaterThan(0); } diff --git a/src/components/measurement-reminders/__tests__/type-labels-i18n.test.ts b/src/components/measurement-reminders/__tests__/type-labels-i18n.test.ts index 5f5317c8b..6122de57a 100644 --- a/src/components/measurement-reminders/__tests__/type-labels-i18n.test.ts +++ b/src/components/measurement-reminders/__tests__/type-labels-i18n.test.ts @@ -41,13 +41,14 @@ const REQUIRED_KEYS = [ ]; describe("measurement-reminder + bulk-share dynamic i18n keys", () => { - it("discovers all six shipped locales", () => { + it("discovers all seven shipped locales", () => { expect(LOCALES.map((l) => l.locale).sort()).toEqual([ "de", "en", "es", "fr", "it", + "ko", "pl", ]); }); diff --git a/src/components/medications/scheduling/natural-language-extractor.tsx b/src/components/medications/scheduling/natural-language-extractor.tsx index 5778947a0..8aad94571 100644 --- a/src/components/medications/scheduling/natural-language-extractor.tsx +++ b/src/components/medications/scheduling/natural-language-extractor.tsx @@ -71,7 +71,7 @@ export interface NaturalLanguageExtractorProps { * UI locale forwarded to the route as a hint. The route accepts an * undefined value and falls back to the resolver chain. */ - locale?: "en" | "de" | "es" | "fr" | "it" | "pl"; + locale?: "en" | "de" | "es" | "fr" | "it" | "pl" | "ko"; /** * Optional override of the API path so the wizard test harness can * point at a mock server. Defaults to the production route. diff --git a/src/components/settings/__tests__/import-panel.test.tsx b/src/components/settings/__tests__/import-panel.test.tsx index 96ae81dbc..a16248279 100644 --- a/src/components/settings/__tests__/import-panel.test.tsx +++ b/src/components/settings/__tests__/import-panel.test.tsx @@ -108,7 +108,7 @@ describe(" — SSR smoke", () => { }); describe("", () => { - it.each(["en", "de", "es", "fr", "it", "pl"] as const)( + it.each(["en", "de", "es", "fr", "it", "pl", "ko"] as const)( "renders localized estimate disclosure for %s", (locale) => { const html = render(, locale); diff --git a/src/components/settings/__tests__/managed-record-settings-section.test.tsx b/src/components/settings/__tests__/managed-record-settings-section.test.tsx index d40aaa14f..88f7941d0 100644 --- a/src/components/settings/__tests__/managed-record-settings-section.test.tsx +++ b/src/components/settings/__tests__/managed-record-settings-section.test.tsx @@ -7,7 +7,7 @@ import { ALL_METRICS } from "@/lib/validations/thresholds"; import { ManagedRecordSettingsForm } from "../managed-record-settings-section"; -const LOCALES = ["de", "en", "es", "fr", "it", "pl"] as const; +const LOCALES = ["de", "en", "es", "fr", "it", "pl", "ko"] as const; const render = ( family: Parameters[0]["family"], diff --git a/src/components/settings/access/__tests__/managed-profile-affordances.test.tsx b/src/components/settings/access/__tests__/managed-profile-affordances.test.tsx index 58c07f1c3..347184c99 100644 --- a/src/components/settings/access/__tests__/managed-profile-affordances.test.tsx +++ b/src/components/settings/access/__tests__/managed-profile-affordances.test.tsx @@ -276,7 +276,7 @@ describe("the managed-profile card, in the shared-access section", () => { /]*data-slot="managed-profile-locale"[\s\S]*?<\/select>/ .exec(html)?.[0] .match(/