2629 lines
153 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
경량 i18n.
`tr('key')` → 현재 언어의 번역 문자열. 키 미존재 시 ko 폴백, ko도 없으면
키 그대로 반환 (미번역도 동작). 점진 도입 가능.
새 언어 추가:
1. _DICT['en'] 옆에 'ja'/'zh' 등 추가
2. 사용자가 설정 → 언어 콤보에서 선택
"""
from __future__ import annotations
_current_lang = 'ko'
_DICT = {
'ko': {
# === 메뉴/버튼 ===
'menu.stats': '통계',
'menu.calendar': '캘린더',
'menu.daily_report': '일일보고',
'menu.help': '도움말',
'menu.settings': '설정',
'btn.clock_out': '퇴근하기',
'btn.clock_out_cancel': '퇴근 취소',
'btn.lunch_add': '점심시간 추가',
'btn.lunch_applied': '점심시간 (적용됨)',
'btn.dinner_add': '저녁시간 추가',
'btn.dinner_applied': '저녁시간 (적용됨)',
'btn.break_out': '외출 시작',
'btn.break_in': '복귀',
'btn.save': '저장',
'btn.close': '닫기',
'btn.apply': '적용',
'btn.cancel': '취소',
'btn.add': '추가',
'btn.delete': '삭제',
'btn.edit': '편집',
'btn.confirm': '확인',
'btn.ok': '확인',
# === 윈도우/다이얼로그 제목 ===
'window.main_title': '퇴근시간 계산기',
'window.settings': '설정',
'window.help': '사용 설명서',
'window.stats': '근무 통계',
'window.calendar': '캘린더',
'window.mini_widget': '퇴근시간',
'window.clock_in_dialog': '출근 시간',
'window.break_view': '외출 관리',
'window.overtime_view': '연장근무 관리',
'window.leave_view': '연차 관리',
# === 라벨 ===
'label.remaining': '남은 시간',
'label.overtime_progress': '추가 근무 중',
'label.expected_clock_out': '예상 퇴근',
'label.clock_in_time': '출근',
'label.current_time': '현재',
'label.clock_out_time': '퇴근 시간',
'label.work_hours_label': '하루 기본 근무:',
'label.lunch_default': '점심시간 기본:',
'label.dinner_default': '저녁시간 기본:',
'label.work_pattern': '근무 패턴:',
'label.time_format': '시간 형식:',
'label.theme': '테마:',
'label.language': '언어 / Language:',
'label.unit_hour': '시간',
'label.unit_minute': '',
'label.unit_day': '',
'label.unit_count': '',
'label.weekday_mon': '',
'label.weekday_tue': '',
'label.weekday_wed': '',
'label.weekday_thu': '',
'label.weekday_fri': '',
'label.weekday_sat': '',
'label.weekday_sun': '',
'label.am': '오전',
'label.pm': '오후',
'label.recurring_yearly': ' (매년)',
# === 알림 ===
'notif.clock_out_soon.title': '⏰ 퇴근 시간 임박',
'notif.clock_out_soon.body': '퇴근까지 {minutes}분 남았습니다.\n마무리 준비를 시작하세요!',
'notif.lunch_reminder.title': '🍱 점심시간 등록',
'notif.lunch_reminder.body': '점심시간을 등록하지 않으셨네요.\n점심시간을 추가하시겠어요?',
'notif.dinner_reminder.title': '🍽️ 저녁시간 등록',
'notif.dinner_reminder.body': '저녁시간을 등록하지 않으셨네요.\n저녁시간을 추가하시겠어요?',
# === 온보딩 근무 패턴 프리셋 ===
'onboarding.preset.standard_8h': '표준 8시간 (점심 60분)',
'onboarding.preset.short_7h30m': '단축근무 7시간 30분 (점심 30분)',
'onboarding.preset.short_7h': '단축근무 7시간 (점심 60분)',
'onboarding.preset.short_6h': '단축근무 6시간 (점심 30분)',
'onboarding.preset.half_4h': '반일 4시간 (점심 0분)',
'onboarding.preset.custom_box': '사용자 정의',
'onboarding.preset.custom_radio': '직접 입력:',
'onboarding.preset.suffix_hours': ' 시간',
'onboarding.preset.suffix_minutes': '',
'onboarding.preset.lunch_prefix': '점심 ',
'onboarding.preset.dinner_prefix': '저녁 ',
'onboarding.preset.dinner_tooltip': '야근으로 저녁 식사가 자주 발생하면 입력 (보통 0~60분)',
'notif.health_break.title': '🌿 휴식 권고',
'notif.health_break.body': '{hours}시간 이상 자리에 계셨습니다.\n잠시 일어나서 스트레칭하세요.',
'notif.overtime_earning.title': '🔥 연장근무 적립 예정',
'notif.overtime_earning.body': '오늘 {time_str}의 연장근무가 적립될 예정입니다!',
'notif.overtime_threshold.title': '💰 연장근무 적립 알림',
'notif.overtime_threshold.body': '적립된 연장근무가 {hours:.1f}시간 입니다.\n사용을 고려해보세요!',
'notif.health.title': '⚠️ 건강 경고',
'notif.health.body': '{days}일 연속 연장근무 중입니다.\n건강을 챙기세요!',
'notif.weekly_52.title': '🚨 주 52시간 초과',
'notif.weekly_52.body': '이번 주 총 근무시간이 {hours:.1f}시간입니다.\n법정 근로시간을 초과했습니다!',
'notif.weekly_report.title': '📊 지난주 요약',
'notif.weekly_report.body': '기간: {start} ~ {end}\n총 근무: {total_h:.1f}시간 ({days}일)\n일 평균: {avg_h:.1f}시간\n연장근무: {ot_h}시간 {ot_m}\n가장 긴 날: {longest}',
'field.total_work': '총 근무',
'field.avg_daily': '일 평균',
'field.overtime': '연장근무',
'field.longest_day': '가장 긴 날',
'notif.achievement.title': '{icon} 도전과제 달성!',
'notif.achievement.body': '{name}\n{description}',
'discord.achievement.title': '🏆 도전과제 {count}개 달성!',
'discord.achievement.body': '새로 잠금 해제된 도전과제 입니다.{extra}',
# === 메시지박스 ===
'msg.save_success.title': '저장 완료',
'msg.save_success.body': '설정이 저장되었습니다.',
'msg.input_error.title': '입력 오류',
'msg.work_min_too_small': '하루 기본 근무 시간은 최소 30분 이상이어야 합니다.',
'msg.no_clock_in.title': '외출 불가',
'msg.no_clock_in.body': '출근하지 않은 상태입니다.',
'msg.already_break.body': '이미 외출 중입니다.',
'msg.no_record.title': '기록 없음',
'msg.no_record.body': '오늘 출근 기록이 없습니다.',
'msg.confirm_delete.title': '삭제 확인',
'msg.no_data.title': '데이터 없음',
'msg.manual_added': '수동 추가',
# === 트레이 ===
'tray.open': '프로그램 열기',
'tray.mini_widget': '미니 위젯',
'tray.toggle_lunch': '점심시간 토글',
'tray.quit': '종료',
'tray.tooltip_remaining': '퇴근까지: {time}',
'tray.tooltip_overtime': '추가 근무 중: {time}',
'tray.background': '프로그램이 트레이에서 실행 중입니다.',
# === 그룹 박스 ===
'group.work_time': '근무 시간 설정',
'group.notification': '알림 및 표시 설정',
'group.overtime': '연장근무 설정',
'group.leave': '휴가 설정',
'group.holiday': '공휴일 설정',
'group.data': '데이터 관리',
# === StatsView ===
'stats.title': '근무 통계',
'stats.tab_weekly': '주간',
'stats.tab_monthly': '월간',
'stats.tab_pattern': '패턴 분석',
'stats.weekly_summary': '이번 주 요약',
'stats.monthly_summary': '이번 달 요약',
'stats.total_hours': '총 근무시간:',
'stats.work_days': '근무일수:',
'stats.avg_hours': '평균 근무시간:',
'stats.total_overtime': '총 연장근무:',
'stats.pattern_insights': '근무 패턴 인사이트',
'stats.analyzing': '데이터를 분석 중입니다...',
'stats.no_data': '아직 충분한 데이터가 없습니다.',
'stats.total_work_hours': '총 근무 시간',
'stats.card_work_days': '근무 일수',
'stats.card_avg_hours': '일평균',
'stats.card_overtime': '연장근무',
'stats.this_week': '이번 주',
'stats.this_month': '이번 달',
'stats.daily_work_hours': '일별 근무 시간',
'stats.weekday_avg': '요일별 평균',
'stats.clock_in_distribution': '출근 시각 분포',
'stats.no_pattern_data': '패턴을 분석할 데이터가 부족합니다.',
'stats.value_hours': '{hours}시간',
'stats.value_hours_minutes': '{hours}시간 {minutes}',
'stats.value_days': '{days}',
'stats.avg_clock_in': '📌 평균 출근시간: {time}',
'stats.overtime_frequency': '📌 연장근무 빈도: {rate}% ({days}/{total}일)',
'stats.longest_work': '📌 최장 근무: {date} ({hours}시간)',
'stats.consecutive_ot_warning': '⚠️ 최근 {days}일 연속 연장근무 발생!',
'stats.weekly_52_exceeded': '🚨 주 52시간 초과: {hours}시간',
'stats.salary_estimate': '💰 이번 달 추정 급여: {total} (기본 {base} + 연장 {overtime})',
# === ChartWidget ===
'chart.need_matplotlib': '차트 표시에는 matplotlib가 필요합니다.\npip install matplotlib',
'chart.no_records': '기록 없음',
'chart.label_normal': '정상',
'chart.label_overtime': '연장',
'chart.ylabel_hours': '시간',
'chart.ylabel_days': '일수',
'chart.ylabel_avg_hours': '평균 시간',
'chart.hover_text': '{date}\n근무 {hours}h',
'chart.hover_overtime': '연장 +{hours}h',
'chart.avg_line': '평균 {time}',
# === ChartWidget ===
'chart.need_matplotlib': 'matplotlib is required to display charts.\npip install matplotlib',
'chart.no_records': 'No records',
'chart.label_normal': 'Normal',
'chart.label_overtime': 'Overtime',
'chart.ylabel_hours': 'Hours',
'chart.ylabel_days': 'Days',
'chart.ylabel_avg_hours': 'Avg hours',
'chart.hover_text': '{date}\nWork {hours}h',
'chart.hover_overtime': 'OT +{hours}h',
'chart.avg_line': 'Avg {time}',
# === CalendarView ===
'cal.title': '월간 근무 캘린더',
'cal.year_label': '',
'cal.month_label': '',
'cal.prev': '◀ 이전',
'cal.next': '다음 ▶',
'cal.today': '오늘',
'cal.no_record': '기록 없음',
'cal.edit_record': '기록 편집',
# === HelpView (각 탭의 큰 HTML은 별도 키) ===
'help.tab_intro': '시작하기',
'help.tab_work_hours': '근무시간',
'help.tab_overtime': '연장근무',
'help.tab_leave': '연차/휴가',
'help.tab_break': '외출/저녁',
'help.tab_faq': '자주 묻는 질문',
# === clock_in_dialog ===
'dlg.clock_in.prompt': '오늘의 출근시간을 입력해주세요',
'dlg.clock_in.label': '출근시간:',
'dlg.clock_in.quick': '빠른 선택:',
'dlg.clock_in.btn_now': '현재',
# === break_view ===
'dlg.break.edit_title': '외출 기록 수정',
'dlg.break.out_label': '외출 시간:',
'dlg.break.in_label': '복귀 시간:',
'dlg.break.reason_label': '사유:',
'view.break.today_title': '오늘의 외출 기록',
'view.break.col_out': '외출 시간',
'view.break.col_in': '복귀 시간',
'view.break.col_duration': '소요 시간',
'view.break.col_reason': '사유',
'view.break.in_progress': '진행중',
'view.break.total_zero': '총 외출 시간: 0분',
'view.break.total_fmt': '총 외출 시간: {h}시간 {m}',
'view.break.total_min_only': '총 외출 시간: {m}',
'view.break.duration_fmt': '{h}시간 {m}',
'view.break.duration_min_only': '{m}',
'view.break.delete_confirm': '이 외출 기록을 삭제하시겠습니까?',
'btn.refresh': '새로고침',
'btn.edit_short': '수정',
'btn.delete_short': '삭제',
# === overtime_view ===
'view.overtime.title': '연장근무 내역',
'view.overtime.balance_zero': '잔액: 0분',
'view.overtime.balance_fmt': '현재 잔액: {h}시간 {m}분 ({total}분)',
'view.overtime.earned_group': '적립 내역',
'view.overtime.used_group': '사용 내역',
'view.overtime.col_date': '날짜',
'view.overtime.col_earned': '적립',
'view.overtime.col_used': '사용',
'view.overtime.col_memo': '메모',
'view.overtime.col_reason': '사유',
'view.overtime.btn_add_earned': '수동 적립',
'view.overtime.btn_add_used': '수동 사용',
'view.overtime.menu_delete': '삭제',
'view.overtime.delete_confirm_body': '다음 사용 기록을 삭제하시겠습니까?\n\n날짜: {date}\n시간: {time}\n사유: {reason}',
'view.overtime.delete_earned_confirm_body': '다음 적립 기록을 삭제하시겠습니까?\n\n날짜: {date}\n적립: {time}\n\n삭제 시 잔액에서 차감됩니다.',
'view.overtime.manual_earned_title': '추가근무 수동 적립',
'view.overtime.manual_used_title': '추가근무 수동 사용',
'view.overtime.field_date': '날짜:',
'view.overtime.field_time': '시간:',
'view.overtime.field_memo': '메모:',
'view.overtime.field_reason': '사유:',
'view.overtime.unit_hour_suffix': '시간',
'view.overtime.minute_0': '0분',
'view.overtime.minute_30': '30분',
'view.overtime.placeholder_memo': '선택사항',
'view.overtime.placeholder_reason': '예: 개인 사유',
'view.overtime.zero_add_error': '0분은 추가할 수 없습니다.',
'view.overtime.zero_use_error': '0분은 사용할 수 없습니다.',
'view.overtime.balance_short_title': '잔액 부족',
'view.overtime.balance_short_body': '사용 가능한 시간이 부족합니다.\n\n요청: {req_h}시간 {req_m}\n잔액: {bal_h}시간 {bal_m}',
'view.overtime.saved_earned': '{h}시간 {m}분이 적립되었습니다.',
'view.overtime.saved_used': '{h}시간 {m}분이 사용 처리되었습니다.',
# === leave_view ===
'view.leave.title': '연차 관리',
'view.leave.balance_zero': '잔여: 0일',
'view.leave.balance_fmt': '잔여: {days}일 (총 {hours}시간)',
'view.leave.btn_set_balance': '잔여 설정',
'view.leave.used_group': '사용 내역',
'view.leave.col_date': '날짜',
'view.leave.col_type': '구분',
'view.leave.col_used': '사용',
'view.leave.col_reason': '사유',
'view.leave.btn_add': '연차 사용 추가',
'view.leave.btn_calendar': '캘린더 보기',
'view.leave.delete_confirm_body': '다음 연차 사용 기록을 삭제하시겠습니까?\n\n날짜: {date}\n구분: {type}\n사용: {days}',
'view.leave.set_title': '연차 시간 설정',
'view.leave.set_prompt': '연차 잔여 시간을 입력하세요 (0.5시간 단위):\n예) 8시간 = 1일, 4시간 = 0.5일(반차), 2시간 = 0.25일, 0.5시간 = 30분',
'view.leave.set_done_title': '설정 완료',
'view.leave.set_done_body': '연차 잔여 개수가 {days}일 ({hours}시간)로 설정되었습니다.',
'view.leave.add_title': '연차 사용 기록 추가',
'view.leave.field_date': '날짜:',
'view.leave.field_type': '구분:',
'view.leave.field_hours': '시간:',
'view.leave.field_reason': '사유:',
'view.leave.type_annual': '연차',
'view.leave.type_half': '반차',
'view.leave.type_quarter': '반반차',
'view.leave.type_hourly': '시간',
'view.leave.placeholder_reason': '예) 개인 사유, 병원 방문 등',
'view.leave.note_auto_deduct': '※ 잔여 연차가 자동 차감됩니다.',
'view.leave.short_title': '잔여 연차 부족',
'view.leave.short_body': '잔여 연차가 부족합니다.\n현재 잔여: {balance}\n사용 요청: {req}',
'view.leave.confirm_title': '연차 사용 기록 추가',
'view.leave.confirm_body': '날짜: {date}\n구분: {type}\n사용: {days}일 ({hours}시간)\n사유: {reason}\n\n이 기록을 추가하시겠습니까?',
'view.leave.added_title': '추가 완료',
'view.leave.added_body': '{days}일 ({hours}시간)의 연차 사용이 기록되었습니다.',
'view.leave.error_title': '오류',
'view.leave.error_body': '연차 기록 추가 중 오류가 발생했습니다:\n{err}',
# === 메인 윈도우 추가 ===
'app.title': '퇴근시간 계산기',
'group.today_work': '오늘의 근무',
'group.remaining_time': '남은 시간',
'group.overtime_leave': '연장근무 및 연차 현황',
'tooltip.meal_click': '좌클릭: 토글 / 우클릭: 실제 시간 입력',
'tooltip.clock_in_edit': '클릭하여 출근 시간 수정',
'btn.achievements': '도전과제',
'btn.break_manage': '외출 관리',
'section.overtime_earned': '연장근무 적립',
'section.leave': '연차',
'section.total_time': '총 보유 시간',
'btn.use_30min': '30분',
'btn.use_1hour': '1시간',
'btn.use_2hour': '2시간',
'btn.custom_input': '직접입력',
'btn.detail': '상세',
'btn.half_leave': '반차',
'btn.full_leave': '연차',
'msg.auto_clock_in.title': '자동 출근 감지',
'msg.auto_clock_in.body': '출근 시간이 자동으로 감지되었습니다.\n출근: {time}\n\n잘못된 경우 수정할 수 있습니다.',
'msg.manual_clock_in.title': '출근 시간 입력',
'msg.manual_clock_in.body': '출근 시간을 자동으로 감지하지 못했습니다.\n\n수동으로 입력하시겠습니까?\n(관리자 권한으로 실행하면 자동 감지됩니다)',
'msg.full_day_leave.title': '종일 연차 등록됨',
'msg.full_day_leave.body': '오늘은 종일 연차로 등록되어 있습니다.\n그래도 출근하시겠어요?\n\n(출근 시 모든 시간이 연장근무로 적립됩니다.)',
'label.full_day_leave_override': '연차 override (전체 적립)',
'label.weekend_work': '주말 근무 (전체 적립)',
'label.holiday_work': '공휴일 근무 (전체 적립)',
'label.holiday_work_no_clock_out': '휴일 근무 (정해진 퇴근시각 없음)',
'label.holiday_default': '공휴일',
'label.expected_clock_out_prefix': '예상 퇴근: ',
'label.total_work_hours': '총 근무시간: {hours:.1f}시간',
'label.weekend_work_tag': '[주말 근무]',
'label.holiday_work_tag': '[공휴일 근무 - {name}]',
'label.overtime_earned_msg': '연장근무 적립: {time} (🕐×{tokens})',
'label.full_earned_msg': '전체 적립: {time} (🕐×{tokens})',
'label.full_day_leave_today': '오늘은 휴가',
'label.full_day_leave_in_use': '연차 사용 중',
'label.full_day_leave_format': '{type}{memo}',
'label.vacation': '🌴 휴가',
'label.time_hours_minutes': '{hours}시간 {minutes}',
'time_format.12h': '{period} {hour}:{minute}',
'break.status_in_progress': '외출 중 ({time}부터)',
'break.status_total_hours_minutes': '오늘 총 외출: {hours}시간 {minutes}',
'break.status_total_minutes': '오늘 총 외출: {minutes}',
'break.reason.lock': '화면 잠금',
'break.cannot_no_clock_in.title': '외출 불가',
'break.cannot_no_clock_in.body': '출근하지 않은 상태입니다.',
'break.cannot_already_on_break.body': '이미 외출 중입니다.',
'break.cannot_no_record.body': '출근 기록을 찾을 수 없습니다.',
'break.started.title': '외출',
'break.started.body': '외출 시간: {time}',
'break.cannot_return_no_active.body': '진행 중인 외출 기록을 찾을 수 없습니다.',
'break.cannot_return_corrupt.body': '외출 시간 기록이 손상되었습니다.',
'break.cannot_return_unusable.body': '외출 시간 기록이 손상되어 복귀 처리를 할 수 없습니다.',
'break.return.title': '복귀',
'break.return.body': '복귀 시간: {time}\n외출 시간: {minutes}',
'mini.open_main': '메인 창 열기',
'mini.close': '미니 위젯 닫기',
'msg.clock_out_confirm.title': '퇴근 확인',
'msg.clock_out_confirm.body': '퇴근 처리하시겠습니까?\n\n퇴근 시간: {time}',
'msg.auto_overtime_confirm.title': '연장근무 적립 확인',
'msg.auto_overtime_confirm.body': '연장근무 {actual} 발생, {earned} 적립 대상입니다.\n\n적립하시겠습니까?\n(아니오 선택 시 이번 퇴근분은 적립되지 않습니다)',
'msg.clock_out_done.title': '퇴근 완료',
'msg.clock_out_done.body': '퇴근 처리되었습니다!\n\n{type_info}{total_work}\n{overtime_info}',
'msg.cancel_clock_out_confirm.body': '퇴근을 취소하시겠습니까?\n\n퇴근 시간과 연장근무 적립 내역이 삭제됩니다.',
'msg.cancel_clock_out_done.title': '퇴근 취소 완료',
'msg.cancel_clock_out_done.body': '퇴근이 취소되었습니다.\n다시 근무 중 상태로 전환되었습니다.',
'msg.cancel_clock_out_fail.title': '취소 실패',
'msg.cancel_clock_out_fail.body': '퇴근 기록을 찾을 수 없습니다.',
'msg.error.title': '오류',
'msg.error.body': '{action} 중 오류가 발생했습니다:\n{error}',
'msg.input.title': '시간 입력',
'msg.input_error.date_format': '날짜 형식이 잘못되었습니다.\n올바른 형식: YYYY-MM-DD (예: 2024-01-15)',
'msg.input_error.overtime_unit': '30분 단위로만 사용 가능합니다.\n예) 0.5시간, 1시간, 1.5시간',
'msg.overtime_use.title': '연장근무 사용',
'msg.overtime_use_minus.title': '연장근무 사용 (마이너스 전환)',
'msg.overtime_use.body': '{minutes}분의 연장근무를 사용하시겠습니까?\n\n현재 잔액: {balance}\n사용 후 잔액: {new_balance}',
'msg.overtime_use_minus.body': '{minutes}분의 연장근무를 사용하시겠습니까?\n\n현재 잔액: {balance}\n사용 후 잔액: {new_balance}분 (마이너스)\n\n⚠️ 잔액이 마이너스가 됩니다.\n나중에 초과근무로 갚아야 합니다.',
'msg.overtime_use_done.title': '사용 완료',
'msg.overtime_use_done.body': '{minutes}분이 사용되었습니다.',
'msg.overtime_use_fail.title': '사용 실패',
'msg.overtime_input.body': '사용할 시간을 입력하세요 (0.5시간 단위):\n예) 0.5, 1, 1.5, 2, 3, 4',
'msg.leave_use.title': '연차 사용',
'msg.leave_use_date.title': '연차 사용 날짜',
'msg.leave_use_date.body': '사용 날짜를 입력하세요 (YYYY-MM-DD):',
'msg.leave_use_reason.title': '연차 사유',
'msg.leave_use_reason.body': '사유를 입력하세요 (선택):',
'msg.leave_use_confirm.body': '{date}{type} {days}를 사용하시겠습니까?\n\n사용 후 잔액: {balance_after}',
'msg.leave_use_done.title': '사용 완료',
'msg.leave_use_done.body': '{type}가 사용되었습니다.',
'msg.leave_use_impossible.title': '사용 불가',
'msg.leave_short.title': '잔액 부족',
'msg.leave_short.body': '사용 가능한 연차가 부족합니다.\n현재 잔액: {balance}\n요청: {days}',
'msg.leave_short_hours.body': '사용 가능한 연차가 부족합니다.\n현재 잔액: {balance}일 ({balance_hours}시간)\n요청: {days}일 ({hours}시간)',
'msg.settings_updated.title': '설정 업데이트',
'msg.settings_updated.body': '변경된 설정이 즉시 반영되었습니다.',
'msg.meal_need_clock_in.title': '출근 필요',
'msg.meal_need_clock_in.body': '출근 후에만 식사 시간을 기록할 수 있습니다.',
'msg.meal_recorded.title': '기록 완료',
'msg.meal_recorded.body': '{meal} {minutes}분 기록되었습니다.\n({start} ~ {end})',
'msg.meal_actual_input': '{meal} 실제 시간 입력...',
'msg.workday_boundary.title': '근무일 경계 경과',
'msg.workday_boundary.body': '근무일 경계 시간({hour}시)이 지나 자동으로 처리되었습니다.\n\n전날 근무: {before} 퇴근 처리\n금일 근무: {boundary} 출근 처리\n\n자정~{hour}시 전까지의 야근은 전날 초과근무로 인정됩니다.',
'msg.new_workday.title': '새 근무일',
'msg.new_workday.body': '새로운 근무일입니다. ({date})\n\n출근 처리를 하시겠습니까?',
'msg.clock_in_set.title': '출근 시간 설정',
'msg.clock_in_set.body': '출근 시간이 설정되었습니다.\n\n출근: {time}',
'leave.type.annual': '연차',
'leave.type.sick': '병가',
'leave.type.hourly_leave': '시간연차',
'leave.use.full_day': '1일',
'leave.use.half_day': '0.5일 (4시간)',
'leave.use.hour_1': '0.125일 (1시간)',
'leave.use.min_30': '0.0625일 (30분)',
'leave.use.custom': '{days}일 ({hours}시간)',
'leave.type.half_am': '오전 반차',
'leave.type.half_pm': '오후 반차',
'leave.type.time_off': '시간 연차',
'leave.type.half': '반차',
'leave.type.quarter': '반반차',
'report.leave_used': '🌴 연차 사용: {value}',
'report.leave_used_days_hours': '{days}{hours}시간',
'report.leave_used_days': '{days}',
'report.leave_used_hours': '{hours}시간',
'report.leave_detail': ' - {type}: {time}{memo}',
'report.title': '📋 일일 근무 보고서 - {date}',
'report.clock_in': '🕐 출근 시간: {time}',
'report.clock_out': '🕐 퇴근 시간: {time}',
'report.not_clocked_out': '🕐 퇴근 시간: 미퇴근',
'report.total_work': '⏱️ 총 근무: {time}',
'report.break_time': '🚶 외출 시간: {time}',
'report.break_detail': ' - {start} ~ {end} ({duration}분){reason}',
'report.break_in_progress': ' - {start} ~ 복귀중{reason}',
'report.meal_actual': '{label}: {time} (실측)',
'report.meal_in_progress': ' - {start} ~ 진행중',
'report.meal_default': '{label}: 포함 ({time})',
'report.memo': '📝 메모: {memo}',
'report.copied.title': '보고서 복사 완료',
'report.copied.body': '일일 근무 보고서가 클립보드에 복사되었습니다.\n\n{report}',
'label.lunch': '🍱 점심시간',
'label.lunch_short': '점심',
'label.dinner': '🍽️ 저녁시간',
'label.dinner_short': '저녁',
'label.overtime_balance_zero': '0분 (×0)',
'label.leave_balance_zero': '잔여: 0일',
'label.today_estimate': '오늘 추정: {amount}',
'label.in_progress': '진행중',
'label.break_returning': '복귀중',
'label.meal_actual_suffix': '실측',
'label.meal_included': '포함',
'report.overtime_occurred': '⏰ 추가 근무 발생: {time}',
'report.overtime_banked': ' 💰 적립: {time} (30분 단위 절삭)',
'report.overtime_used': '🕐 추가 근무 사용: {time}',
'report.overtime_used_detail': ' - {time}{reason}',
'update.new_version_title': '새 버전 발견',
'update.apply_failed_title': '업데이트 실패',
'update.check_title': '업데이트 확인',
'update.up_to_date': '현재 최신 버전입니다 (v{version}).',
'update.network_error': '업데이트 서버에 연결할 수 없습니다.\n네트워크 상태를 확인해 주세요.',
'update.no_release': '업데이트 저장소에서 릴리스를 찾을 수 없습니다.\n(저장소 비공개 또는 첫 릴리스 전)',
'update.no_asset': '새 버전은 있지만 다운로드 가능한 main.exe 자산이 없습니다.\n관리자에게 문의하세요.',
'update.unknown': '알 수 없는 응답입니다.',
'update.new_found_dev': '새 버전 {version}이 있습니다.\n(개발 환경에서는 자동 적용 불가 — git pull 또는 빌드 후 사용)',
'update.new_found': '현재: v{current}\n새 버전: v{new}\n\n릴리스 노트:\n{notes}\n\n지금 다운로드 후 업데이트할까요?',
'update.downloading': '다운로드 중...',
'update.download_title': '업데이트 다운로드',
'update.download_failed': '새 버전 다운로드 중 오류가 발생했습니다.',
'update.updater_failed': 'updater.exe를 찾을 수 없거나 실행에 실패했습니다.',
'update.restart': '업데이트 적용을 위해 프로그램이 종료됩니다.',
'settings.title': '설정',
'settings.work_pattern': '근무 패턴:',
'settings.preset.standard_8h': '표준 8시간 (점심 60분)',
'settings.preset.short_7h30m': '단축근무 7시간 30분 (점심 30분)',
'settings.preset.short_7h': '단축근무 7시간 (점심 60분)',
'settings.preset.short_6h': '단축근무 6시간 (점심 30분)',
'settings.preset.half_4h': '반일 4시간 (점심 0분)',
'settings.preset.custom': '사용자 정의',
'settings.daily_work': '하루 기본 근무:',
'settings.lunch_default': '점심시간 기본:',
'settings.dinner_default': '저녁시간 기본:',
'settings.auto_apply': '자동 적용',
'settings.auto_apply_tooltip': '출근 후 4시간 경과 시 자동 적용',
'settings.suffix_hour': ' 시간',
'settings.suffix_minute': '',
'settings.notif_clock_out': '퇴근 30분 전 알림',
'settings.notif_lunch': '점심시간 등록 알림',
'settings.notif_dinner': '저녁시간 등록 알림',
'settings.notif_overtime': '연장근무 적립 알림',
'settings.notif_health': '건강 경고 알림',
'settings.notif_break': '휴식 권고 알림',
'settings.notif_break_tooltip': '오랜 시간 자리에서 일하면 스트레칭을 권유 (연속 근무 N시간 기준)',
'settings.notif_before': '퇴근 알림 시점:',
'settings.notif_before_spin_suffix': '분 전',
'settings.notif_before_tooltip': '퇴근 임박 알림이 표시될 시점 (분 단위)',
'settings.advanced_thresholds': '고급 임계값',
'settings.advanced_thresholds_tooltip': '회사 정책·개인 선호에 맞춰 알림 발생 시점 조정',
'settings.lunch_alert_after': '점심 알림 (출근 +):',
'settings.lunch_alert_tooltip': '출근 후 N시간 경과 시 점심 미등록 알림',
'settings.dinner_alert_after': '저녁 알림 (출근 +):',
'settings.dinner_alert_tooltip': '출근 후 N시간 경과 시 저녁 미등록 알림',
'settings.overtime_alert_at': '연장 누적 알림:',
'settings.overtime_alert_tooltip': '연장근무 잔액이 N시간 이상이면 알림',
'settings.weekly_limit': '주간 한도 경고:',
'settings.weekly_limit_tooltip': '주간 총 근무가 N시간 초과 시 경고 (한국 노동법 기본 52)',
'settings.consecutive_ot': '연속 연장 경고:',
'settings.consecutive_ot_tooltip': 'N일 이상 연속 연장근무 시 건강 경고',
'settings.break_after': '휴식 권고 시점:',
'settings.break_after_tooltip': '연속 근무 N시간 경과 시 스트레칭 권유',
'settings.time_format': '시간 형식:',
'settings.time_format_12': '오전/오후 (오후 5:30)',
'settings.time_format_24': '24시간 (17:30)',
'settings.theme': '테마:',
'settings.theme_light': '라이트',
'settings.theme_dark': '다크',
'settings.font_scale': '글꼴 크기:',
'settings.high_contrast': '고대비 모드',
'settings.high_contrast_tooltip': '검정 배경 + 노란 텍스트 (시각약자/야간)',
'settings.language_restart_tooltip': '언어 변경은 재시작 후 완전히 적용됩니다.',
'settings.current_balance': '현재 잔액: 계산 중...',
'settings.calc_unit': '계산 단위:',
'settings.initial_overtime': '기존 연장근무:',
'settings.auto_bank': '자동 적립',
'settings.auto_bank_tooltip': '퇴근 시 연장근무 자동 적립',
'settings.initial_overtime_note': '※ 프로그램 사용 전 쌓인 연장근무 시간 (절대값)',
'settings.goal_group': '월간 목표 (0=비활성)',
'settings.monthly_ot_cap': '월 연장근무 상한:',
'settings.daily_avg_goal': '일 평균 근무 목표:',
'settings.goal_note': '※ 통계 → 월간 탭에서 진행률 확인',
'settings.annual_leave': '연간 연차:',
'settings.remaining_leave': '남은 연차: 계산 중...',
'settings.used_leave': '기존 사용:',
'settings.used_leave_note': '※ 프로그램 사용 전 이미 사용한 연차 (1일=8시간)',
'settings.registered': '등록:',
'settings.add_korean_holidays': '한국 공휴일 (자동)',
'settings.add_korean_holidays_tooltip': '음력 명절(설/추석) + 임시공휴일 포함 자동 등록',
'settings.holiday_note': '※ 공휴일 근무 시 모든 시간이 연장근무로 적립됩니다',
'settings.list': '목록',
'settings.korean_holidays_title': '한국 공휴일 자동 추가',
'settings.korean_holidays_years_label': '{start}년 + {end}',
'settings.korean_holidays_years_label_single': '{year}',
'settings.korean_holidays_years_label': '{start}년 + {end}',
'settings.korean_holidays_years_label_single': '{year}',
'settings.korean_holidays_body': '{years} 한국 공휴일을 자동으로 등록하시겠습니까?\n\n포함:\n• 양력 공휴일 (신정/삼일절/어린이날/근로자의 날 등)\n• 음력 명절 (설날 연휴/추석 연휴/석가탄신일)\n• 정부 지정 대체·임시공휴일\n\n※ 1차: 공공데이터포털 특일정보 API (정부 공인, 임시공휴일 포함)\n※ 2차 fallback: \'holidays\' 패키지 (오프라인)',
'settings.korean_holidays_added': '{year}년 한국 공휴일 {count}개가 추가되었습니다.',
'settings.korean_holidays_included': '포함:\n',
'settings.package_not_installed': '패키지 미설치',
'settings.package_fallback_body': '\'holidays\' 패키지가 설치되지 않아 고정 공휴일만 추가했습니다.\n\n{hint} pip install holidays',
'settings.package_install_hint': '음력/임시공휴일 자동 등록을 원하시면:\n',
'settings.add_done': '추가 완료',
'settings.holiday_added': '공휴일이 추가되었습니다.\n{date}: {name}',
'settings.holiday_add_title': '공휴일 추가',
'settings.holiday_date_prompt': '공휴일 날짜를 입력하세요 (YYYY-MM-DD):',
'settings.holiday_name_prompt': '공휴일 이름을 입력하세요:',
'settings.holiday_list_title': '공휴일 목록',
'settings.holiday_list_header': '=== {year}년 공휴일 목록 ===\n\n',
'settings.holiday_list_item': '{date} ({weekday}): {name}{recurring}',
'settings.holiday_total': '{count}',
'settings.holiday_delete_confirm': '\n\n공휴일을 삭제하시겠습니까?',
'settings.holiday_delete_title': '공휴일 삭제',
'settings.holiday_delete_prompt': '삭제할 공휴일을 선택하세요:',
'settings.delete_done': '삭제 완료',
'settings.holiday_deleted': '{item}이(가) 삭제되었습니다.',
'settings.export_csv': 'CSV 내보내기',
'settings.export_work': '근무기록',
'settings.export_overtime': '연장근무',
'settings.export_monthly': '월간 요약',
'settings.import_csv': 'CSV 가져오기',
'settings.import_tooltip': 'date,clock_in,clock_out,lunch_minutes,memo 헤더 포맷',
'settings.import_format': '우리 표준 포맷 (헤더: date,clock_in,clock_out,lunch_minutes,memo)',
'settings.db_path_label': 'DB 경로:',
'settings.change': '변경...',
'settings.db_path_tooltip': '클라우드 폴더(OneDrive/Dropbox 등) 경로로 변경 가능. 재시작 필요.',
'settings.auto_break_lock_tooltip': 'PC가 잠기면 외출 시작, 풀리면 복귀를 자동 처리합니다.',
'settings.gitea_feedback_label': 'Gitea 피드백:',
'settings.gitea_token_placeholder': 'PAT (issue 쓰기 권한, 옵션)',
'settings.clock_in_unlock_tooltip': 'PC를 끄지 않고 출근하는 경우 — 부팅 이벤트가 없어도 화면 잠금 해제 시점을 출근으로 기록합니다.',
'settings.auto_break_lock': '화면 잠금 시 자동 외출/복귀',
'settings.gitea_feedback_tooltip': "오류 발생 시 'Gitea에 보고' 버튼 활성화",
'settings.clock_in_unlock': '첫 잠금 해제 시각을 출근시간으로 사용',
'settings.version': '버전: v{version}',
'settings.check_update': '업데이트 확인 (F5)',
'settings.select_db': '데이터베이스 파일 선택',
'settings.db_path_saved': '새 경로가 저장되었습니다:\n{path}\n\n기존 데이터를 사용하려면 현재 database.db 파일을 새 위치로 복사하고\n프로그램을 재시작하세요.',
'settings.parse_failed': '파싱 실패',
'settings.empty_file': '빈 파일',
'settings.empty_file_body': '유효한 행이 없습니다.',
'settings.conflict_title': '충돌 처리',
'settings.conflict_body': '기존 일자와 충돌하면 어떻게 처리할까요?\n',
'settings.conflict_body_detailed': '기존 일자와 충돌하면 어떻게 처리할까요?\nYes = 덮어쓰기\nNo = 건너뛰기\nCancel = 취소',
'settings.import_failed': '가져오기 실패',
'settings.import_result': '가져오기 결과:\n• 추가: {added}\n• 갱신: {updated}\n• 건너뜀: {skipped}',
'settings.save_done': '저장 완료',
'settings.save_done_body': '설정이 저장되었습니다.',
'settings.restart_title': '재시작 / Restart',
'settings.restart_body': '주요 화면은 즉시 적용됩니다. 일부 다이얼로그는 재시작 후 완전히 반영됩니다.\n지금 재시작할까요?\n\n',
'settings.initial_overtime_title': '기존 연장근무 설정',
'settings.initial_overtime_body': '현재 설정: {old_hours}시간 {old_mins}\n변경할 값: {hours}시간 {mins}\n\n기존 연장근무 시간을 변경하시겠습니까?',
'settings.initial_overtime_done': '설정 완료',
'settings.initial_overtime_done_body': '기존 연장근무가 {hours}시간 {mins}분으로 설정되었습니다.',
'settings.initial_overtime_error': '기존 연장근무 설정 중 오류가 발생했습니다:\n{error}',
'settings.current_overtime_balance': '현재 잔액: {hours}시간 {minutes}분 ({balance}분)',
'settings.remaining_leave_fmt': '남은 연차: {remaining:.1f}일 (총 {total}일 중 {used}일 사용)',
'settings.holiday_count': '{count}개 ({year}년)',
'settings.error': '오류',
'settings.export_no_records': '내보낼 기록이 없습니다.',
'settings.save_work_title': '근무 기록 저장',
'settings.export_done': '내보내기 완료',
'settings.work_exported': '근무 기록이 저장되었습니다.\n{path}',
'settings.save_ot_title': '연장근무 내역 저장',
'settings.ot_exported': '연장근무 내역이 저장되었습니다.\n{path}',
'settings.save_monthly_title': '월간 요약 저장',
'settings.monthly_exported': '월간 요약이 저장되었습니다.\n{path}',
'settings.export_failed': '내보내기 실패',
'settings.export_error': '오류: {error}',
'settings.initial_leave_title': '기존 사용 연차 설정',
'settings.initial_leave_body': '현재 설정: {old_hours}시간 {old_mins}\n변경할 값: {hours}시간 {mins}\n\n기존 사용 연차를 변경하시겠습니까?',
'settings.initial_leave_done': '설정 완료',
'settings.initial_leave_done_body': '기존 사용 연차가 {hours}시간 {mins}분으로 설정되었습니다.',
'date_format.full': '{year}{month}{day}일 ({weekday})',
'achieve.cat_ambition': '야망',
'achieve.cat_balance': '워라밸',
'achieve.cat_break_use': '외출',
'achieve.cat_health': '건강',
'achieve.cat_korea': '한국 문화',
'achieve.cat_leave': '연차',
'achieve.cat_meal': '식사',
'achieve.cat_meta': '메타',
'achieve.cat_milestone': '마일스톤',
'achieve.cat_ot_bank': '연장 적립',
'achieve.cat_ot_use': '연장 사용',
'achieve.cat_pattern': '패턴',
'achieve.cat_punctual': '시간 엄수',
'achieve.cat_season': '시즌',
'achieve.cat_secret': '시크릿',
'achieve.cat_settings': '설정',
'achieve.cat_special_day': '특별일',
'achieve.cat_stats': '통계',
'achieve.cat_streak': '출근 streak',
'achieve.cat_time_slot': '시간대',
'achieve.completion_rate': '달성률',
'achieve.earned_date': '{date} 달성 ',
'achieve.empty': '(아직 없음)',
'achieve.secret_locked': '🔒 달성하면 공개됩니다',
'achieve.tab_all': '🌐 전체 · {count}',
'achieve.tab_completed': '✓ 완료 · {count}',
'achieve.tab_in_progress': '⚡ 진행 중 · {count}',
'achieve.tab_secret': '🌑 시크릿 · {earned}/{total}',
'achieve.tier_bronze': '브론즈',
'achieve.tier_gold': '골드',
'achieve.tier_legend': '레전드',
'achieve.tier_platinum': '플래티넘',
'achieve.tier_silver': '실버',
'achieve.title': '도전과제',
'cal.add_done_body': '{date} 기록이 추가되었습니다.',
'cal.add_done_title': '추가 완료',
'cal.add_error_body': '기록 추가 실패: {error}',
'cal.add_error_title': '오류',
'cal.btn_minus_30': '-30분',
'cal.btn_plus_30': '+30분',
'cal.check_dinner_1h': '저녁 (1시간)',
'cal.check_lunch_1h': '점심 (1시간)',
'cal.context_add': '{date} 기록 추가',
'cal.context_delete': '{date} 삭제',
'cal.context_edit': '{date} 편집',
'cal.delete_confirm_body': '{date} 기록을 정말 삭제하시겠습니까?\n(연장근무 적립 내역도 함께 삭제됩니다)',
'cal.delete_confirm_title': '삭제 확인',
'cal.delete_done_body': '{date} 기록 삭제됨',
'cal.delete_done_title': '삭제 완료',
'cal.delete_record': '기록 삭제',
'cal.delete_selected_body': '{date}의 출근 기록을 삭제하시겠습니까?\n\n※ 연관된 연장근무 적립/사용 기록도 함께 삭제됩니다.\n※ 이 작업은 되돌릴 수 없습니다.',
'cal.delete_selected_title': '출근 기록 삭제',
'cal.detail_clock_in': '출근: {time}',
'cal.detail_clock_out': '퇴근: {time}',
'cal.detail_clock_out_none': '퇴근: 미기록',
'cal.detail_date_fmt': '{year}{month}{day}',
'cal.detail_dinner_unused': '저녁시간: 미사용',
'cal.detail_dinner_used': '저녁시간: 사용함',
'cal.detail_group_title': '선택된 날짜 정보',
'cal.detail_lunch_unused': '점심시간: 미사용',
'cal.detail_lunch_used': '점심시간: 사용함',
'cal.detail_memo': '메모: {memo}',
'cal.detail_overtime_earned': '🔥 연장근무 적립: {hours}시간 {minutes}',
'cal.detail_total_hours': '총 근무시간: {hours:.1f}시간',
'cal.dialog_title': '월간 근무 기록',
'cal.edit_dialog_subtitle': '{date} 출퇴근 시간 수정',
'cal.edit_dialog_title': '출퇴근 시간 수정',
'cal.edit_done_body': '{date}의 출퇴근 시간이 수정되었습니다.\n\n출근: {clock_in}\n퇴근: {clock_out}\n점심시간: {lunch}\n저녁시간: {dinner}\n외출시간: {break_minutes}\n총 근무시간: {total_hours:.1f}시간\n연장근무: {overtime_earned}분 적립',
'cal.edit_done_title': '수정 완료',
'cal.edit_error_body': '수정 중 오류가 발생했습니다:\n{error}',
'cal.edit_error_title': '오류',
'cal.edit_note': '※ 수정 시 연장근무 내역이 재계산됩니다.',
'cal.edit_time': '시간 수정',
'cal.label_clock_in': '출근:',
'cal.label_clock_out': '퇴근:',
'cal.legend_leave': '휴가',
'cal.legend_none': '없음',
'cal.legend_normal': '정상',
'cal.legend_overtime': '연장',
'cal.memo_group': '메모',
'cal.memo_placeholder': '추가근무 사유, 특이사항 등...',
'cal.no_record': '기록이 없습니다.',
'cal.save_memo': '메모 저장',
'cal.save_memo_body': '{date}의 메모가 저장되었습니다.',
'cal.save_memo_title': '메모 저장',
'cal.time_error_body': '퇴근 시간은 출근 시간보다 늦어야 합니다.',
'cal.time_error_title': '시간 오류',
'clock_in_dialog.cancelled': '취소됨',
'clock_in_dialog.selected': '선택된 시간: {time}',
'field.avg_daily_value': '{hours:.1f}시간',
'field.overtime_value': '{hours}시간 {minutes}',
'field.total_work_value': '{hours:.1f}시간 ({days}일)',
'goal.avg_daily': '일평균:',
'goal.overtime': '연장근무:',
'goal.title': '이번 달 목표',
'help.onboarding_button': '온보딩 다시 보기',
'leave_cal.detail_label': '{type} {days}',
'leave_cal.detail_memo': '{type} {days}일 ({memo})',
'leave_cal.detail_no_record': '{date} — 연차 사용 없음',
'leave_cal.header': '잔여 {balance:.2f}일 / 총 {total:.0f}일 (사용 {used:.2f}일)',
'leave_cal.legend_full': '종일(1.0)',
'leave_cal.legend_full_planned': '종일+예정',
'leave_cal.legend_half': '반차(0.5)',
'leave_cal.legend_planned': '예정',
'leave_cal.legend_quarter': '반반차(0.25)',
'leave_cal.title': '연차 캘린더',
'meal.dialog_title': '{meal} 시간 입력',
'meal.error_after_clock_out': '퇴근({time}) 이후입니다',
'meal.error_before_clock_in': '출근({time}) 이전입니다',
'meal.error_start_after_end': '시작이 종료보다 늦습니다',
'meal.error_too_long': '식사 시간이 8시간을 초과합니다',
'meal.info_clock_in_limit': '\n출근 {time} 이후만 입력 가능.',
'meal.info_text': '{meal} 시작·종료 시각을 입력하세요.\n자동 적용된 {minutes}분 대신 정확한 시간으로 기록됩니다.',
'meal.input_error_title': '입력 오류',
'meal.label_end': '종료:',
'meal.label_start': '시작:',
'meal.preview_total': '{minutes}',
'mini.close': '미니 위젯 닫기',
'mini.open_main': '메인 창 열기',
'onboarding.detection_boot': 'PC 부팅 시간 (기본 — 매일 PC를 끄는 경우)',
'onboarding.detection_info': '\nPC를 항상 켜둔 채 출근하시는 분은 두 번째 옵션을 권장합니다.',
'onboarding.detection_manual': '수동 입력만 (자동 감지 안 함)',
'onboarding.detection_subtitle': '앱이 출근 시간을 자동으로 어떻게 감지할지 선택하세요.',
'onboarding.detection_title': '출근 시간 감지 방식',
'onboarding.detection_unlock': '화면 잠금 해제 시간 (PC를 안 끄고 다니는 경우)',
'onboarding.discord_enable': 'Discord 웹훅 알림 사용',
'onboarding.discord_failed': '실패',
'onboarding.discord_failed_body': '전송 실패. URL을 다시 확인해주세요.',
'onboarding.discord_guide': '셋업 방법:\n1. Discord 서버에서 채널 우클릭 → 편집 → 연동 → 웹훅\n2. 새 웹훅 만들기 → URL 복사\n3. 위 입력란에 붙여넣기',
'onboarding.discord_subtitle': '출퇴근 시각·휴식 권고를 Discord로 받으려면 웹훅 URL을 입력하세요. (모바일에서 푸시 알림)',
'onboarding.discord_success': '성공',
'onboarding.discord_success_body': 'Discord 채널에서 테스트 메시지를 확인하세요.',
'onboarding.discord_test': '테스트 메시지 보내기',
'onboarding.discord_title': 'Discord 알림 (선택)',
'onboarding.discord_url_invalid_body': 'Discord 웹훅 URL 형식이 아닙니다.\n예: https://discord.com/api/webhooks/{ID}/{TOKEN}',
'onboarding.discord_url_invalid_title': 'URL 형식 오류',
'onboarding.discord_url_placeholder': 'https://discord.com/api/webhooks/...',
'onboarding.discord_url_required_body': '웹훅 URL을 먼저 입력해주세요.',
'onboarding.discord_url_required_title': 'URL 필요',
'onboarding.finish_msg': '설정한 내용은 [설정] 메뉴에서 언제든 바꿀 수 있습니다.\n온보딩을 다시 보고 싶으면 [도움말 → 온보딩 다시 보기]를 누르세요.\n\n단축키:\n • Ctrl+O — 출퇴근 토글\n • F1 — 도움말\n • F5 — 업데이트 확인\n • Ctrl+, — 설정',
'onboarding.finish_subtitle': '이제 출근부터 자동 추적됩니다.',
'onboarding.finish_title': '준비 완료!',
'onboarding.hourly_wage': '시급:',
'onboarding.input_error_title': '입력 오류',
'onboarding.leave_group': '연간 연차',
'onboarding.leave_salary_subtitle': '연차 일수와 급여(선택)를 입력하세요.',
'onboarding.leave_salary_title': '연차 + 급여 (옵션)',
'onboarding.my_leave': '내 연차:',
'onboarding.overtime_rate': '연장수당 가산률:',
'onboarding.rate_1_5x': '1.5배 (한국 노동법 기본)',
'onboarding.rate_1x': '1.0배 (가산 없음)',
'onboarding.rate_2x': '2.0배 (야근/휴일 가산)',
'onboarding.salary_enabled': '급여 추정 활성화',
'onboarding.salary_group': '급여 추정 (옵션 — 포괄임금이면 비활성)',
'onboarding.wage_suffix': ' 원/시간',
'onboarding.welcome_intro': '이 앱은:\n• 컴퓨터 부팅/잠금 해제로 출근 시간 자동 감지\n• 30분 단위 연장근무 적립\n• 연차·반차·외출 시간 추적\n• 매일 퇴근 시간을 1초마다 카운트다운\n\n[다음] 버튼을 눌러 시작하세요.',
'onboarding.welcome_subtitle': 'Clock-out Time Calculator를 처음 사용하시는군요. 5단계로 빠르게 설정하겠습니다.',
'onboarding.welcome_title': '환영합니다!',
'onboarding.window_title': 'Clock-out Calculator — 시작 설정',
'onboarding.work_min_too_small': '하루 근무는 최소 30분 이상이어야 합니다.',
'onboarding.work_pattern_subtitle': '본인의 하루 근무 시간을 선택하세요. 나중에 설정에서 바꿀 수 있습니다.',
'onboarding.work_pattern_title': '근무 패턴',
'past_record.check_clock_out': '입력',
'past_record.check_dinner': '저녁시간 포함',
'past_record.check_lunch': '점심시간 포함',
'past_record.dialog_title': '기록 추가 — {date}',
'past_record.info': '{date} 근무 기록을 입력하세요.',
'past_record.input_error_body': '퇴근 시간이 출근 시간보다 빠르거나 같습니다.',
'past_record.input_error_title': '입력 오류',
'past_record.label_clock_in': '출근:',
'past_record.label_clock_out': '퇴근:',
'past_record.label_memo': '메모 (선택):',
'past_record.memo_placeholder': '예: 재택근무 / 외근 / 휴가',
'recurring.add_done_body': '반복 패턴이 등록되었습니다.\n{pattern}',
'recurring.add_done_title': '추가 완료',
'recurring.add_group': '신규 패턴 추가',
'recurring.biweekly': '격주',
'recurring.btn_add': '추가',
'recurring.btn_delete_selected': '선택 삭제',
'recurring.day_suffix': '',
'recurring.deduction_full': '1.0일 (종일)',
'recurring.deduction_half': '0.5일 (반차)',
'recurring.deduction_quarter': '0.25일 (반반차)',
'recurring.delete_confirm_body': '이 반복 패턴을 삭제하시겠습니까?\n\n{item}',
'recurring.delete_confirm_title': '삭제 확인',
'recurring.input_error_title': '입력 오류',
'recurring.input_error_weekday': '최소 한 개 요일을 선택하세요.',
'recurring.label_cycle': '주기:',
'recurring.label_deduction': '차감:',
'recurring.label_end': '종료:',
'recurring.label_memo': '메모:',
'recurring.label_monthly_day': '매월:',
'recurring.label_start': '시작:',
'recurring.label_weekday': '요일:',
'recurring.list_group': '등록된 반복 패턴',
'recurring.memo_placeholder': '예: 육아 단축근무',
'recurring.monthly': '매월 N일',
'recurring.no_end': '종료 없음 (무기한)',
'recurring.title': '반복 연차 관리',
'recurring.pattern_weekly': '{prefix} {weekdays}요일',
'recurring.pattern_monthly': '매월 {day}',
'recurring.weekly': '매주',
'schedule.btn_add_leave': '연차 등록',
'schedule.btn_recurring': '반복 패턴 관리',
'schedule.delete': '삭제',
'schedule.delete_leave_confirm_body': '이 연차 기록을 삭제하시겠습니까? (잔액이 자동 복구됩니다.)',
'schedule.delete_leave_confirm_title': '삭제 확인',
'schedule.delete_recurring_confirm_body': '이 반복 패턴을 삭제하시겠습니까? (이후 모든 인스턴스 제거)',
'schedule.delete_recurring_confirm_title': '삭제 확인',
'schedule.detail_placeholder': '날짜를 선택하세요',
'schedule.header': '월간 스케줄 — 휴일 + 연차 + 반복 패턴',
'schedule.holiday': '공휴일: {name}',
'schedule.leave_label': '{type} {days}',
'schedule.recurring_item': '{pattern} · {days}일 ({type})',
'schedule.legend_half': '반차/반반차',
'schedule.legend_holiday': '공휴일',
'schedule.legend_leave_planned': '연차 예정',
'schedule.legend_leave_used': '연차 사용',
'schedule.legend_recurring': '반복 패턴',
'schedule.no_events': '일정 없음',
'schedule.title': '스케줄',
'schedule.weekend': '주말 ({weekday})',
'schedule.weekday_suffix': '요일',
'today.detail_break': '외출 {minutes}',
'today.detail_dinner': '저녁 {minutes}',
'today.detail_lunch': '점심 {minutes}',
'today.detail_overtime': '연장 {actual}분 → 적립 {earned}',
'today.title': '오늘의 요약',
'today.total_work': '총 근무: {hours}시간 {minutes}',
'view.leave.btn_schedule': '스케줄',
'view.leave.duplicate_register_body': '{date}에 이미 {existing_days:.2f}일이 등록되어 있어\n추가 {days:.2f}일을 더하면 1일을 초과합니다.',
'view.leave.duplicate_register_title': '중복 등록 초과',
'view.leave.holiday_register_forbidden_body': '{date}는 이미 공휴일({name})입니다.\n연차를 차감할 필요가 없습니다.',
'view.leave.holiday_register_forbidden_title': '공휴일 등록 불가',
'view.leave.schedule_tooltip': '휴일 + 연차 + 반복 패턴 통합 보기',
'view.leave.used_1day': '1일',
'view.leave.used_half_day': '0.5일 (4시간)',
'view.leave.used_hours_fmt': '{days}일 ({hours}시간)',
'view.leave.used_days_fmt': '{days}',
'view.leave.weekend_register_forbidden_body': '주말에는 연차를 등록할 수 없습니다. (이미 비근무일)',
'view.leave.weekend_register_forbidden_title': '주말 등록 불가',
# === Achievements ===
'achieve.streak_first.name': '첫걸음',
'achieve.streak_first.desc': '첫 출근 기록',
'achieve.streak_3.name': '뿌리내림',
'achieve.streak_3.desc': '3일 연속 영업일 출근',
'achieve.streak_5.name': '첫 주 완주',
'achieve.streak_5.desc': '5 영업일 연속 출근',
'achieve.streak_7_cal.name': '7일 연속',
'achieve.streak_7_cal.desc': '주말 포함 7일 연속 출근',
'achieve.streak_10.name': '2주 연속',
'achieve.streak_10.desc': '10 영업일 연속 출근',
'achieve.streak_22.name': '한 달 개근',
'achieve.streak_22.desc': '한 달 영업일 100% 출근 (22일)',
'achieve.streak_50.name': '50일 연속',
'achieve.streak_50.desc': '50 영업일 연속 출근',
'achieve.streak_100.name': '100일 연속',
'achieve.streak_100.desc': '100 영업일 연속 출근',
'achieve.streak_quarter.name': '분기 완주',
'achieve.streak_quarter.desc': '약 65 영업일 (3개월)',
'achieve.streak_half_year.name': '반년 마라톤',
'achieve.streak_half_year.desc': '약 130 영업일 (6개월)',
'achieve.streak_year.name': '1년 풀 시즌',
'achieve.streak_year.desc': '약 260 영업일 (1년)',
'achieve.streak_200.name': '사이언스',
'achieve.streak_200.desc': '200 영업일 연속',
'achieve.streak_365_cal.name': '불사신',
'achieve.streak_365_cal.desc': '365일 달력 연속',
'achieve.streak_resilience.name': '회복력',
'achieve.streak_resilience.desc': '결근 후 다음날 즉시 출근 (자동: 달력 streak 깨진 후 재시작)',
'achieve.streak_total_100.name': '누적 100회',
'achieve.streak_total_100.desc': '누적 출근 100회',
'achieve.streak_total_500.name': '누적 500회',
'achieve.streak_total_500.desc': '누적 출근 500회',
'achieve.streak_total_1000.name': '누적 1000회',
'achieve.streak_total_1000.desc': '누적 출근 1000회',
'achieve.punc_before_8_1.name': '얼리버드',
'achieve.punc_before_8_1.desc': '08:00 이전 출근 1회',
'achieve.punc_before_8_10.name': '참새족',
'achieve.punc_before_8_10.desc': '08:00 이전 10회',
'achieve.punc_before_8_30.name': '일찍 자고 일찍',
'achieve.punc_before_8_30.desc': '08:00 이전 30회',
'achieve.punc_before_6_1.name': '새벽잠 없음',
'achieve.punc_before_6_1.desc': '06:00 이전 1회',
'achieve.punc_before_6_10.name': '어둠을 가르는 자',
'achieve.punc_before_6_10.desc': '06:00 이전 10회',
'achieve.punc_before_5.name': '새벽 챔피언',
'achieve.punc_before_5.desc': '05:00 이전 출근',
'achieve.punc_at_9.name': '9시 정각',
'achieve.punc_at_9.desc': '09:00 정각(±1분) 출근 1회',
'achieve.punc_at_9_5.name': '완벽한 9시',
'achieve.punc_at_9_5.desc': '09:00 정각(±1분) 5회',
'achieve.punc_late_5min.name': '5분 늦음',
'achieve.punc_late_5min.desc': '09:00~09:05 출근 1회 (자조)',
'achieve.punc_at_909.name': '운명의 시각',
'achieve.punc_at_909.desc': '09:09 출근 (시크릿)',
'achieve.bal_first_punct.name': '첫 칼퇴',
'achieve.bal_first_punct.desc': '정시 퇴근 첫 달성',
'achieve.bal_punct_10.name': '칼퇴러',
'achieve.bal_punct_10.desc': '정시 퇴근 10회',
'achieve.bal_punct_30.name': '칼퇴 챔프',
'achieve.bal_punct_30.desc': '정시 퇴근 30회',
'achieve.bal_punct_100.name': '진정한 자유',
'achieve.bal_punct_100.desc': '정시 퇴근 100회',
'achieve.bal_punct_300.name': '워라밸 마스터',
'achieve.bal_punct_300.desc': '정시 퇴근 300회',
'achieve.ot_first_30m.name': '첫 30분',
'achieve.ot_first_30m.desc': '첫 연장 적립',
'achieve.ot_total_60m.name': '1시간 적금',
'achieve.ot_total_60m.desc': '누적 1시간 적립',
'achieve.ot_total_5h.name': '5시간 적립',
'achieve.ot_total_5h.desc': '누적 5시간',
'achieve.ot_total_10h.name': '10시간 적립',
'achieve.ot_total_10h.desc': '누적 10시간',
'achieve.ot_total_25h.name': '25시간 적립',
'achieve.ot_total_25h.desc': '누적 25시간',
'achieve.ot_total_50h.name': '50시간 적립',
'achieve.ot_total_50h.desc': '누적 50시간',
'achieve.ot_total_100h.name': '마라토너',
'achieve.ot_total_100h.desc': '누적 100시간 (걱정 메시지)',
'achieve.ot_total_200h.name': '워크홀릭 경고',
'achieve.ot_total_200h.desc': '누적 200시간 (경고)',
'achieve.ot_total_300h.name': '위험 신호',
'achieve.ot_total_300h.desc': '누적 300시간 (강한 경고)',
'achieve.ot_total_500h.name': '응급실 단골',
'achieve.ot_total_500h.desc': '누적 500시간 (자조)',
'achieve.use_first.name': '첫 휴식',
'achieve.use_first.desc': '적립 첫 사용',
'achieve.use_total_5h.name': '선물 사용',
'achieve.use_total_5h.desc': '누적 5시간 사용',
'achieve.use_total_25h.name': '휴식의 가치',
'achieve.use_total_25h.desc': '누적 25시간 사용',
'achieve.use_total_50h.name': '회복 마스터',
'achieve.use_total_50h.desc': '누적 50시간 사용',
'achieve.use_total_100h.name': '마사지',
'achieve.use_total_100h.desc': '누적 100시간 사용',
'achieve.leave_first.name': '첫 연차',
'achieve.leave_first.desc': '첫 연차 사용',
'achieve.leave_half.name': '첫 반차',
'achieve.leave_half.desc': '0.5일 연차 사용',
'achieve.leave_quarter.name': '시간 연차',
'achieve.leave_quarter.desc': '0.25일 연차 사용',
'achieve.leave_streak_3.name': '미니 휴가',
'achieve.leave_streak_3.desc': '연속 3일 연차',
'achieve.leave_streak_5.name': '본격 휴가',
'achieve.leave_streak_5.desc': '연속 5일 연차',
'achieve.leave_streak_7.name': '장거리 휴가',
'achieve.leave_streak_7.desc': '연속 7일 이상 연차',
'achieve.leave_total_10.name': '연차 10회',
'achieve.leave_total_10.desc': '연차 기록 10건',
'achieve.leave_sick.name': '병가',
'achieve.leave_sick.desc': 'sick 타입 연차 사용',
'achieve.meal_lunch_first.name': '첫 점심 등록',
'achieve.meal_lunch_first.desc': '점심 첫 토글',
'achieve.meal_lunch_30.name': '점심 마스터',
'achieve.meal_lunch_30.desc': '점심 등록 30회',
'achieve.meal_lunch_100.name': '점심 챔프',
'achieve.meal_lunch_100.desc': '점심 등록 100회',
'achieve.meal_dinner_first.name': '첫 저녁 등록',
'achieve.meal_dinner_first.desc': '저녁 첫 토글',
'achieve.meal_dinner_10.name': '저녁 단골',
'achieve.meal_dinner_10.desc': '저녁 등록 10회 (경고)',
'achieve.meal_dinner_30.name': '야식 단골',
'achieve.meal_dinner_30.desc': '저녁 등록 30회 (경고)',
'achieve.meal_lunch_actual.name': '실측 점심',
'achieve.meal_lunch_actual.desc': '실제 점심 시각 입력',
'achieve.meal_dinner_actual.name': '실측 저녁',
'achieve.meal_dinner_actual.desc': '실제 저녁 시각 입력',
'achieve.break_first.name': '첫 외출',
'achieve.break_first.desc': '첫 외출 시작',
'achieve.break_10.name': '외출 챔프',
'achieve.break_10.desc': '외출 10회',
'achieve.break_50.name': '산책러',
'achieve.break_50.desc': '외출 50회',
'achieve.slot_in_06.name': '06시대 출근',
'achieve.slot_in_06.desc': '06:00-06:59 출근 1회',
'achieve.slot_in_07.name': '07시대 출근',
'achieve.slot_in_07.desc': '07:00-07:59 출근 1회',
'achieve.slot_in_08.name': '08시대 출근',
'achieve.slot_in_08.desc': '08:00-08:59 출근 1회',
'achieve.slot_in_10.name': '10시대 출근',
'achieve.slot_in_10.desc': '10시대 출근 (지각/유연근무)',
'achieve.slot_in_11.name': '11시대 출근',
'achieve.slot_in_11.desc': '11시대 출근 (자조)',
'achieve.slot_out_19.name': '19시대 퇴근',
'achieve.slot_out_19.desc': '19시대 퇴근 10회 (경고)',
'achieve.slot_out_20.name': '20시대 퇴근',
'achieve.slot_out_20.desc': '20시대 퇴근 10회 (경고)',
'achieve.slot_out_21.name': '21시대 퇴근',
'achieve.slot_out_21.desc': '21시대 퇴근 5회 (경고)',
'achieve.slot_out_22.name': '22시대 퇴근',
'achieve.slot_out_22.desc': '22시대 퇴근 1회 (경고)',
'achieve.slot_out_23.name': '23시대 퇴근',
'achieve.slot_out_23.desc': '23시대 퇴근 1회 (경고)',
'achieve.slot_midnight.name': '자정 퇴근',
'achieve.slot_midnight.desc': '자정 이후 퇴근 (경고)',
'achieve.slot_midnight_3.name': '올빼미 트리오',
'achieve.slot_midnight_3.desc': '자정 이후 퇴근 3회 (경고)',
'achieve.weekend_1.name': '주말 출근 1회',
'achieve.weekend_1.desc': '토/일 출근 1회',
'achieve.weekend_5.name': '주말 워커',
'achieve.weekend_5.desc': '주말 출근 5회 (경고)',
'achieve.weekend_20.name': '진짜 워크홀릭',
'achieve.weekend_20.desc': '주말 출근 20회 (강한 자조)',
'achieve.holiday_1.name': '공휴일 출근',
'achieve.holiday_1.desc': '한국 공휴일 출근 1회',
'achieve.holiday_5.name': '공휴일 워커홀릭',
'achieve.holiday_5.desc': '한국 공휴일 출근 5회 (경고)',
'achieve.day_christmas.name': '크리스마스 출근',
'achieve.day_christmas.desc': '12/25 출근 (자조)',
'achieve.day_newyear.name': '신정 출근',
'achieve.day_newyear.desc': '1/1 출근 (자조)',
'achieve.day_liberation.name': '광복절 출근',
'achieve.day_liberation.desc': '8/15 출근',
'achieve.day_children.name': '어린이날 출근',
'achieve.day_children.desc': '5/5 출근 (자조)',
'achieve.day_hangul.name': '한글날 출근',
'achieve.day_hangul.desc': '10/9 출근',
'achieve.day_valentine.name': '발렌타인데이 출근',
'achieve.day_valentine.desc': '2/14 출근',
'achieve.day_white.name': '화이트데이 출근',
'achieve.day_white.desc': '3/14 출근',
'achieve.day_pepero.name': '빼빼로데이',
'achieve.day_pepero.desc': '11/11 출근',
'achieve.day_halloween.name': '핼러윈 출근',
'achieve.day_halloween.desc': '10/31 출근',
'achieve.day_aprilfools.name': '만우절 출근',
'achieve.day_aprilfools.desc': '4/1 출근',
'achieve.day_77.name': '칠월칠석',
'achieve.day_77.desc': '7/7 출근',
'achieve.day_dongji.name': '동지 출근',
'achieve.day_dongji.desc': '12/22 출근',
'achieve.day_parents.name': '어버이날 정시 퇴근',
'achieve.day_parents.desc': '5/8 정시 퇴근',
'achieve.day_teacher.name': '스승의 날 정시 퇴근',
'achieve.day_teacher.desc': '5/15 정시 퇴근',
'achieve.day_xmas_eve.name': '크리스마스이브 정시 퇴근',
'achieve.day_xmas_eve.desc': '12/24 정시 퇴근',
'achieve.day_earth.name': '지구의 날',
'achieve.day_earth.desc': '4/22 출근 (시크릿)',
'achieve.season_jan.name': '1월 정착',
'achieve.season_jan.desc': '1월 한 달 출근',
'achieve.season_feb.name': '2월 정착',
'achieve.season_feb.desc': '2월 영업일 모두 출근',
'achieve.season_mar.name': '봄을 맞이',
'achieve.season_mar.desc': '3월 첫 출근',
'achieve.season_apr.name': '4월 정착',
'achieve.season_apr.desc': '4월 한 달 출근',
'achieve.season_may.name': '5월 정착',
'achieve.season_may.desc': '5월 영업일 모두 출근',
'achieve.season_jun.name': '여름의 시작',
'achieve.season_jun.desc': '6월 첫 출근',
'achieve.season_jul.name': '7월 정착',
'achieve.season_jul.desc': '7월 한 달 출근',
'achieve.season_aug.name': '8월 정착',
'achieve.season_aug.desc': '8월 영업일 모두 출근',
'achieve.season_sep.name': '가을의 시작',
'achieve.season_sep.desc': '9월 첫 출근',
'achieve.season_oct.name': '10월 정착',
'achieve.season_oct.desc': '10월 한 달 출근',
'achieve.season_nov.name': '11월 단풍',
'achieve.season_nov.desc': '11월 영업일 모두 출근',
'achieve.season_dec.name': '겨울의 시작',
'achieve.season_dec.desc': '12월 첫 출근',
'achieve.mile_first.name': 'Hello, World!',
'achieve.mile_first.desc': '앱 첫 실행',
'achieve.mile_7days.name': '일주일 사용',
'achieve.mile_7days.desc': '7일 사용',
'achieve.mile_30days.name': '한 달 사용',
'achieve.mile_30days.desc': '30일 사용',
'achieve.mile_365days.name': '1주년',
'achieve.mile_365days.desc': '365일 사용',
'achieve.mile_730days.name': '2주년',
'achieve.mile_730days.desc': '730일 사용',
'achieve.mile_1095days.name': '3주년',
'achieve.mile_1095days.desc': '3년 사용',
'achieve.mile_5years.name': '5년 사용자',
'achieve.mile_5years.desc': '5년 사용',
'achieve.mile_10years.name': '10년 사용자',
'achieve.mile_10years.desc': '10년 사용',
'achieve.stat_weekly_10.name': '주간 통계러',
'achieve.stat_weekly_10.desc': '주간 탭 10회 조회',
'achieve.stat_monthly_10.name': '월간 통계러',
'achieve.stat_monthly_10.desc': '월간 탭 10회',
'achieve.stat_pattern_10.name': '패턴 분석가',
'achieve.stat_pattern_10.desc': '패턴 탭 10회',
'achieve.stat_calendar_30.name': '캘린더 챔프',
'achieve.stat_calendar_30.desc': '캘린더 30회 조회',
'achieve.stat_report_first.name': '일일 보고서 첫 생성',
'achieve.stat_report_first.desc': '일일 보고 1회',
'achieve.stat_report_30.name': '보고서 챔프',
'achieve.stat_report_30.desc': '일일 보고 30회',
'achieve.stat_chart_hover.name': '차트 호버 발견',
'achieve.stat_chart_hover.desc': '차트 hover 첫 발견',
'achieve.stat_achievements_open.name': '도전과제 박물관',
'achieve.stat_achievements_open.desc': '도전과제 뷰 50회',
'achieve.secret_palindrome.name': '회문 시각',
'achieve.secret_palindrome.desc': '출근 시각이 회문',
'achieve.secret_jackpot.name': '잭팟 시각',
'achieve.secret_jackpot.desc': '출근 시각 모든 자릿수 동일',
'achieve.secret_fri13.name': '13일 금요일',
'achieve.secret_fri13.desc': '13일 금요일 출근',
'achieve.secret_777.name': '7-7-7',
'achieve.secret_777.desc': '7월 7일 7시 7분 출근',
'achieve.secret_exact_8h.name': '정확 8시간',
'achieve.secret_exact_8h.desc': '정확히 8h 0m 근무',
'achieve.secret_pi_day.name': '파이 데이',
'achieve.secret_pi_day.desc': '3/14 01:59 출근',
'achieve.secret_fibonacci.name': '피보나치',
'achieve.secret_fibonacci.desc': '출근 분이 피보나치 수',
'achieve.secret_double_six.name': '더블 식스',
'achieve.secret_double_six.desc': '6/6 18:06 출근',
'achieve.secret_anniversary.name': '마법사',
'achieve.secret_anniversary.desc': '가입 후 정확히 365일 후 출근',
'achieve.set_dark.name': '다크 사이드',
'achieve.set_dark.desc': '다크 테마 1회 사용',
'achieve.set_lang.name': '이중언어',
'achieve.set_lang.desc': '언어 변경 (en 사용)',
'achieve.set_a11y.name': '접근성 활용',
'achieve.set_a11y.desc': '글꼴 크기≠100% 또는 고대비 ON',
'achieve.set_overtime_unit.name': '단위 변경',
'achieve.set_overtime_unit.desc': 'overtime_unit 변경',
'achieve.set_goal_full.name': '목표 마스터',
'achieve.set_goal_full.desc': '월 연장+일평균 둘 다 설정',
'achieve.set_discord_full.name': '풀 셋업',
'achieve.set_discord_full.desc': 'Discord URL + 모든 알림 ON',
'achieve.set_cloud.name': '클라우드 동기화',
'achieve.set_cloud.desc': 'DB 경로 변경',
'achieve.meta_first.name': '첫 도전과제',
'achieve.meta_first.desc': '첫 도전과제 획득',
'achieve.meta_10.name': '10개 달성',
'achieve.meta_10.desc': '10개 보유',
'achieve.meta_25.name': '25개 달성',
'achieve.meta_25.desc': '25개 보유',
'achieve.meta_50.name': '50개 달성',
'achieve.meta_50.desc': '50개 보유',
'achieve.meta_75.name': '75개 달성',
'achieve.meta_75.desc': '75개 보유',
'achieve.meta_100.name': '100개 달성',
'achieve.meta_100.desc': '100개 보유',
'achieve.meta_secret_1.name': '시크릿 발견',
'achieve.meta_secret_1.desc': '첫 시크릿 발견',
'achieve.meta_secret_5.name': '시크릿 헌터',
'achieve.meta_secret_5.desc': '시크릿 5개 발견',
'achieve.streak_monday_10.name': '월요일 정복',
'achieve.streak_monday_10.desc': '월요일 10주 연속 출근',
'achieve.streak_friday_10.name': '금요일 무결',
'achieve.streak_friday_10.desc': '금요일 10주 연속 출근',
},
'en': {
# === Menu/Buttons ===
'menu.stats': 'Stats',
'menu.calendar': 'Calendar',
'menu.daily_report': 'Daily Report',
'menu.help': 'Help',
'menu.settings': 'Settings',
'btn.clock_out': 'Clock Out',
'btn.clock_out_cancel': 'Cancel Clock-out',
'btn.lunch_add': 'Add Lunch',
'btn.lunch_applied': 'Lunch (Applied)',
'btn.dinner_add': 'Add Dinner',
'btn.dinner_applied': 'Dinner (Applied)',
'btn.break_out': 'Start Break',
'btn.break_in': 'Return',
'btn.save': 'Save',
'btn.close': 'Close',
'btn.apply': 'Apply',
'btn.cancel': 'Cancel',
'btn.add': 'Add',
'btn.delete': 'Delete',
'btn.edit': 'Edit',
'btn.confirm': 'Confirm',
'btn.ok': 'OK',
# === Windows ===
'window.main_title': 'Clock-out Time Calculator',
'window.settings': 'Settings',
'window.help': 'User Guide',
'window.stats': 'Statistics',
'window.calendar': 'Calendar',
'window.mini_widget': 'Clock-out',
'window.clock_in_dialog': 'Clock-in Time',
'window.break_view': 'Break Management',
'window.overtime_view': 'Overtime Management',
'window.leave_view': 'Leave Management',
# === Labels ===
'label.remaining': 'Remaining',
'label.overtime_progress': 'Overtime',
'label.expected_clock_out': 'Expected Clock-out',
'label.clock_in_time': 'Clock-in',
'label.current_time': 'Current',
'label.clock_out_time': 'Clock-out Time',
'label.work_hours_label': 'Daily work:',
'label.lunch_default': 'Lunch break:',
'label.dinner_default': 'Dinner break:',
'label.work_pattern': 'Work pattern:',
'label.time_format': 'Time format:',
'label.theme': 'Theme:',
'label.language': 'Language / 언어:',
'label.unit_hour': 'h',
'label.unit_minute': 'min',
'label.unit_day': 'day(s)',
'label.unit_count': '',
'label.weekday_mon': 'Mon',
'label.weekday_tue': 'Tue',
'label.weekday_wed': 'Wed',
'label.weekday_thu': 'Thu',
'label.weekday_fri': 'Fri',
'label.weekday_sat': 'Sat',
'label.weekday_sun': 'Sun',
'label.am': 'AM',
'label.pm': 'PM',
'label.recurring_yearly': ' (yearly)',
# === Notifications ===
'notif.clock_out_soon.title': '⏰ Clock-out Soon',
'notif.clock_out_soon.body': "{minutes} minutes until clock-out.\nWrap things up!",
'notif.lunch_reminder.title': '🍱 Lunch Reminder',
'notif.lunch_reminder.body': "You haven't registered lunch yet.\nWant to add it?",
'notif.dinner_reminder.title': '🍽️ Dinner Reminder',
'notif.dinner_reminder.body': "You haven't registered dinner yet.\nWant to add it?",
# === Onboarding work pattern presets ===
'onboarding.preset.standard_8h': 'Standard 8h (Lunch 60min)',
'onboarding.preset.short_7h30m': 'Reduced 7h 30m (Lunch 30min)',
'onboarding.preset.short_7h': 'Reduced 7h (Lunch 60min)',
'onboarding.preset.short_6h': 'Reduced 6h (Lunch 30min)',
'onboarding.preset.half_4h': 'Half-day 4h (No Lunch)',
'onboarding.preset.custom_box': 'Custom',
'onboarding.preset.custom_radio': 'Manual entry:',
'onboarding.preset.suffix_hours': ' h',
'onboarding.preset.suffix_minutes': ' min',
'onboarding.preset.lunch_prefix': 'Lunch ',
'onboarding.preset.dinner_prefix': 'Dinner ',
'onboarding.preset.dinner_tooltip': 'Set if you often have dinner during overtime (typically 0~60 min)',
'notif.health_break.title': '🌿 Take a Break',
'notif.health_break.body': "You've been at your desk for over {hours} hours.\nStand up and stretch!",
'notif.overtime_earning.title': '🔥 Overtime Will Accrue',
'notif.overtime_earning.body': "{time_str} of overtime will be banked today!",
'notif.overtime_threshold.title': '💰 Overtime Balance High',
'notif.overtime_threshold.body': "{hours:.1f} hours of overtime banked.\nConsider using some.",
'notif.health.title': '⚠️ Health Warning',
'notif.health.body': "{days} consecutive days of overtime.\nTake care of your health!",
'notif.weekly_52.title': '🚨 Weekly 52h Exceeded',
'notif.weekly_52.body': "This week's total work hours: {hours:.1f}\nLegal limit exceeded!",
'notif.weekly_report.title': '📊 Last Week Summary',
'notif.weekly_report.body': 'Period: {start} ~ {end}\nTotal: {total_h:.1f}h ({days} days)\nDaily Avg: {avg_h:.1f}h\nOvertime: {ot_h}h {ot_m}m\nLongest Day: {longest}',
'field.total_work': 'Total Work',
'field.avg_daily': 'Daily Avg',
'field.overtime': 'Overtime',
'field.longest_day': 'Longest Day',
'notif.achievement.title': '{icon} Achievement Unlocked!',
'notif.achievement.body': '{name}\n{description}',
'discord.achievement.title': '🏆 {count} Achievements Unlocked!',
'discord.achievement.body': 'Newly unlocked achievements.{extra}',
# === Message Boxes ===
'msg.save_success.title': 'Saved',
'msg.save_success.body': 'Settings saved successfully.',
'msg.input_error.title': 'Input Error',
'msg.work_min_too_small': 'Daily work time must be at least 30 minutes.',
'msg.no_clock_in.title': 'Cannot Take Break',
'msg.no_clock_in.body': 'Not clocked in.',
'msg.already_break.body': 'Already on break.',
'msg.no_record.title': 'No Record',
'msg.no_record.body': 'No clock-in record for today.',
'msg.confirm_delete.title': 'Confirm Delete',
'msg.no_data.title': 'No Data',
'msg.manual_added': 'Manual',
# === Tray ===
'tray.open': 'Open Program',
'tray.mini_widget': 'Mini Widget',
'tray.toggle_lunch': 'Toggle Lunch',
'tray.quit': 'Quit',
'tray.tooltip_remaining': 'Until clock-out: {time}',
'tray.tooltip_overtime': 'Overtime: {time}',
'tray.background': 'Program is running in the tray.',
# === Groups ===
'group.work_time': 'Work Time Settings',
'group.notification': 'Notifications & Display',
'group.overtime': 'Overtime Settings',
'group.leave': 'Leave Settings',
'group.holiday': 'Holidays',
'group.data': 'Data Management',
# === StatsView ===
'stats.title': 'Work Statistics',
'stats.tab_weekly': 'Weekly',
'stats.tab_monthly': 'Monthly',
'stats.tab_pattern': 'Patterns',
'stats.weekly_summary': 'This Week',
'stats.monthly_summary': 'This Month',
'stats.total_hours': 'Total hours:',
'stats.work_days': 'Work days:',
'stats.avg_hours': 'Avg hours/day:',
'stats.total_overtime': 'Total overtime:',
'stats.pattern_insights': 'Work Pattern Insights',
'stats.analyzing': 'Analyzing...',
'stats.no_data': 'Not enough data yet.',
'stats.total_work_hours': 'Total Work Hours',
'stats.card_work_days': 'Work Days',
'stats.card_avg_hours': 'Daily Avg',
'stats.card_overtime': 'Overtime',
'stats.this_week': 'This Week',
'stats.this_month': 'This Month',
'stats.daily_work_hours': 'Daily Work Hours',
'stats.weekday_avg': 'Avg by Weekday',
'stats.clock_in_distribution': 'Clock-in Distribution',
'stats.no_pattern_data': 'Not enough data to analyze patterns.',
'stats.value_hours': '{hours}h',
'stats.value_hours_minutes': '{hours}h {minutes}m',
'stats.value_days': '{days}d',
'stats.avg_clock_in': '📌 Avg clock-in: {time}',
'stats.overtime_frequency': '📌 OT frequency: {rate}% ({days}/{total} days)',
'stats.longest_work': '📌 Longest day: {date} ({hours}h)',
'stats.consecutive_ot_warning': '⚠️ {days} consecutive OT days!',
'stats.weekly_52_exceeded': '🚨 Weekly 52h exceeded: {hours}h',
'stats.salary_estimate': '💰 Est. salary: {total} (base {base} + OT {overtime})',
# === CalendarView ===
'cal.title': 'Monthly Calendar',
'cal.year_label': 'Y',
'cal.month_label': 'M',
'cal.prev': '◀ Prev',
'cal.next': 'Next ▶',
'cal.today': 'Today',
'cal.no_record': 'No record',
'cal.edit_record': 'Edit record',
# === HelpView ===
'help.tab_intro': 'Getting Started',
'help.tab_work_hours': 'Work Hours',
'help.tab_overtime': 'Overtime',
'help.tab_leave': 'Leave',
'help.tab_break': 'Break/Dinner',
'help.tab_faq': 'FAQ',
# === clock_in_dialog ===
'dlg.clock_in.prompt': "Enter today's clock-in time",
'dlg.clock_in.label': 'Clock-in time:',
'dlg.clock_in.quick': 'Quick pick:',
'dlg.clock_in.btn_now': 'Now',
# === break_view ===
'dlg.break.edit_title': 'Edit Break Record',
'dlg.break.out_label': 'Break out:',
'dlg.break.in_label': 'Return:',
'dlg.break.reason_label': 'Reason:',
'view.break.today_title': "Today's Break Records",
'view.break.col_out': 'Break out',
'view.break.col_in': 'Return',
'view.break.col_duration': 'Duration',
'view.break.col_reason': 'Reason',
'view.break.in_progress': 'In progress',
'view.break.total_zero': 'Total break: 0 min',
'view.break.total_fmt': 'Total break: {h}h {m}m',
'view.break.total_min_only': 'Total break: {m} min',
'view.break.duration_fmt': '{h}h {m}m',
'view.break.duration_min_only': '{m} min',
'view.break.delete_confirm': 'Delete this break record?',
'btn.refresh': 'Refresh',
'btn.edit_short': 'Edit',
'btn.delete_short': 'Delete',
# === overtime_view ===
'view.overtime.title': 'Overtime History',
'view.overtime.balance_zero': 'Balance: 0 min',
'view.overtime.balance_fmt': 'Current balance: {h}h {m}m ({total} min)',
'view.overtime.earned_group': 'Earned',
'view.overtime.used_group': 'Used',
'view.overtime.col_date': 'Date',
'view.overtime.col_earned': 'Earned',
'view.overtime.col_used': 'Used',
'view.overtime.col_memo': 'Memo',
'view.overtime.col_reason': 'Reason',
'view.overtime.btn_add_earned': 'Manual Earn',
'view.overtime.btn_add_used': 'Manual Use',
'view.overtime.menu_delete': 'Delete',
'view.overtime.delete_confirm_body': 'Delete this usage record?\n\nDate: {date}\nTime: {time}\nReason: {reason}',
'view.overtime.delete_earned_confirm_body': 'Delete this accrual record?\n\nDate: {date}\nEarned: {time}\n\nThe balance will be reduced accordingly.',
'view.overtime.manual_earned_title': 'Manual Overtime Earn',
'view.overtime.manual_used_title': 'Manual Overtime Use',
'view.overtime.field_date': 'Date:',
'view.overtime.field_time': 'Time:',
'view.overtime.field_memo': 'Memo:',
'view.overtime.field_reason': 'Reason:',
'view.overtime.unit_hour_suffix': 'h',
'view.overtime.minute_0': '0 min',
'view.overtime.minute_30': '30 min',
'view.overtime.placeholder_memo': 'Optional',
'view.overtime.placeholder_reason': 'e.g., personal',
'view.overtime.zero_add_error': 'Cannot add 0 minutes.',
'view.overtime.zero_use_error': 'Cannot use 0 minutes.',
'view.overtime.balance_short_title': 'Insufficient Balance',
'view.overtime.balance_short_body': 'Not enough balance.\n\nRequested: {req_h}h {req_m}m\nBalance: {bal_h}h {bal_m}m',
'view.overtime.saved_earned': '{h}h {m}m banked.',
'view.overtime.saved_used': '{h}h {m}m used.',
# === leave_view ===
'view.leave.title': 'Leave Management',
'view.leave.balance_zero': 'Balance: 0 days',
'view.leave.balance_fmt': 'Balance: {days} days ({hours}h total)',
'view.leave.btn_set_balance': 'Set Balance',
'view.leave.used_group': 'Used',
'view.leave.col_date': 'Date',
'view.leave.col_type': 'Type',
'view.leave.col_used': 'Used',
'view.leave.col_reason': 'Reason',
'view.leave.btn_add': 'Add Leave Usage',
'view.leave.btn_calendar': 'Calendar',
'view.leave.delete_confirm_body': 'Delete this leave record?\n\nDate: {date}\nType: {type}\nUsed: {days}',
'view.leave.set_title': 'Set Leave Hours',
'view.leave.set_prompt': 'Enter leave hours remaining (0.5h step):\ne.g. 8h = 1d, 4h = 0.5d (half), 2h = 0.25d, 0.5h = 30min',
'view.leave.set_done_title': 'Saved',
'view.leave.set_done_body': 'Leave balance set to {days} days ({hours}h).',
'view.leave.add_title': 'Add Leave Usage',
'view.leave.field_date': 'Date:',
'view.leave.field_type': 'Type:',
'view.leave.field_hours': 'Hours:',
'view.leave.field_reason': 'Reason:',
'view.leave.type_annual': 'Annual',
'view.leave.type_half': 'Half',
'view.leave.type_quarter': 'Quarter',
'view.leave.type_hourly': 'Hourly',
'view.leave.placeholder_reason': 'e.g., personal, medical',
'view.leave.note_auto_deduct': '※ Leave balance is auto-deducted.',
'view.leave.short_title': 'Insufficient Leave',
'view.leave.short_body': 'Not enough leave.\nCurrent: {balance} days\nRequested: {req} days',
'view.leave.confirm_title': 'Add Leave Usage',
'view.leave.confirm_body': 'Date: {date}\nType: {type}\nUsed: {days} days ({hours}h)\nReason: {reason}\n\nAdd this record?',
'view.leave.added_title': 'Added',
'view.leave.added_body': '{days} days ({hours}h) of leave usage recorded.',
'view.leave.error_title': 'Error',
'view.leave.error_body': 'Failed to add leave record:\n{err}',
# === Main Window additions ===
'app.title': 'Clock-out Time Calculator',
'group.today_work': "Today's Work",
'group.remaining_time': 'Remaining Time',
'group.overtime_leave': 'Overtime & Leave Status',
'tooltip.meal_click': 'Left click: toggle / Right click: enter actual time',
'tooltip.clock_in_edit': 'Click to edit clock-in time',
'btn.achievements': 'Achievements',
'btn.break_manage': 'Manage Breaks',
'section.overtime_earned': 'Overtime Banked',
'section.leave': 'Annual Leave',
'section.total_time': 'Total Banked Time',
'btn.use_30min': '30m',
'btn.use_1hour': '1h',
'btn.use_2hour': '2h',
'btn.custom_input': 'Custom',
'btn.detail': 'Detail',
'btn.half_leave': 'Half-day',
'btn.full_leave': 'Full-day',
'msg.auto_clock_in.title': 'Auto Clock-in Detected',
'msg.auto_clock_in.body': 'Clock-in time was automatically detected.\nClock-in: {time}\n\nYou can edit it if incorrect.',
'msg.manual_clock_in.title': 'Enter Clock-in Time',
'msg.manual_clock_in.body': 'Could not detect clock-in time automatically.\n\nEnter manually?\n(Run as administrator for auto detection.)',
'msg.full_day_leave.title': 'Full-day Leave Registered',
'msg.full_day_leave.body': 'Today is registered as full-day leave.\nDo you still want to clock in?\n\n(All time will be banked as overtime.)',
'label.full_day_leave_override': 'Leave Override (Full Bank)',
'label.weekend_work': 'Weekend Work (Full Bank)',
'label.holiday_work': 'Holiday Work (Full Bank)',
'label.holiday_work_no_clock_out': 'Holiday Work (No fixed clock-out)',
'label.holiday_default': 'Holiday',
'label.expected_clock_out_prefix': 'Expected: ',
'label.total_work_hours': 'Total work: {hours:.1f}h',
'label.weekend_work_tag': '[Weekend Work]',
'label.holiday_work_tag': '[Holiday Work - {name}]',
'label.overtime_earned_msg': 'Overtime earned: {time} (🕐×{tokens})',
'label.full_earned_msg': 'Full earned: {time} (🕐×{tokens})',
'label.full_day_leave_today': 'Today is leave',
'label.full_day_leave_in_use': 'Leave in use',
'label.full_day_leave_format': '{type}{memo}',
'label.vacation': '🌴 Leave',
'label.time_hours_minutes': '{hours}h {minutes}m',
'time_format.12h': '{hour}:{minute} {period}',
'break.status_in_progress': 'On break (since {time})',
'break.status_total_hours_minutes': 'Total break today: {hours}h {minutes}m',
'break.status_total_minutes': 'Total break today: {minutes}m',
'break.reason.lock': 'Screen lock',
'break.cannot_no_clock_in.title': 'Cannot Start Break',
'break.cannot_no_clock_in.body': 'Not clocked in.',
'break.cannot_already_on_break.body': 'Already on break.',
'break.cannot_no_record.body': 'No clock-in record found.',
'break.started.title': 'Break Started',
'break.started.body': 'Break started: {time}',
'break.cannot_return_no_active.body': 'No active break record found.',
'break.cannot_return_corrupt.body': 'Break record is corrupted.',
'break.cannot_return_unusable.body': 'Break record too corrupted to return.',
'break.return.title': 'Return',
'break.return.body': 'Returned: {time}\nBreak: {minutes}m',
'mini.open_main': 'Open main window',
'mini.close': 'Close mini widget',
'msg.clock_out_confirm.title': 'Clock-out Confirm',
'msg.clock_out_confirm.body': 'Clock out now?\n\nClock-out: {time}',
'msg.auto_overtime_confirm.title': 'Confirm Overtime Bank',
'msg.auto_overtime_confirm.body': 'Overtime {actual} occurred, {earned} eligible for banking.\n\nBank it?\n(No = this clock-out will not be banked)',
'msg.clock_out_done.title': 'Clock-out Complete',
'msg.clock_out_done.body': 'Clocked out!\n\n{type_info}{total_work}\n{overtime_info}',
'msg.cancel_clock_out_confirm.body': 'Cancel clock-out?\n\nClock-out time and overtime bank entries will be deleted.',
'msg.cancel_clock_out_done.title': 'Clock-out Cancelled',
'msg.cancel_clock_out_done.body': 'Clock-out cancelled.\nReturned to working state.',
'msg.cancel_clock_out_fail.title': 'Cancel Failed',
'msg.cancel_clock_out_fail.body': 'Clock-out record not found.',
'msg.error.title': 'Error',
'msg.error.body': 'Error while {action}:\n{error}',
'msg.input.title': 'Enter Time',
'msg.input_error.date_format': 'Invalid date format.\nCorrect format: YYYY-MM-DD (e.g. 2024-01-15)',
'msg.input_error.overtime_unit': 'Only 30-minute units allowed.\nExamples: 0.5h, 1h, 1.5h',
'msg.overtime_use.title': 'Use Overtime',
'msg.overtime_use_minus.title': 'Use Overtime (Negative)',
'msg.overtime_use.body': 'Use {minutes}m of overtime?\n\nCurrent balance: {balance}m\nNew balance: {new_balance}m',
'msg.overtime_use_minus.body': 'Use {minutes}m of overtime?\n\nCurrent balance: {balance}m\nNew balance: {new_balance}m (negative)\n\n⚠️ Balance will be negative.\nYou must earn overtime later to cover it.',
'msg.overtime_use_done.title': 'Used',
'msg.overtime_use_done.body': '{minutes}m used.',
'msg.overtime_use_fail.title': 'Use Failed',
'msg.overtime_input.body': 'Enter hours to use (0.5h units):\nEx) 0.5, 1, 1.5, 2, 3, 4',
'msg.leave_use.title': 'Use Leave',
'msg.leave_use_date.title': 'Leave Date',
'msg.leave_use_date.body': 'Enter date (YYYY-MM-DD):',
'msg.leave_use_reason.title': 'Leave Reason',
'msg.leave_use_reason.body': 'Enter reason (optional):',
'msg.leave_use_confirm.body': 'Use {type} {days} on {date}?\n\nBalance after: {balance_after} days',
'msg.leave_use_done.title': 'Used',
'msg.leave_use_done.body': '{type} used.',
'msg.leave_use_impossible.title': 'Cannot Use',
'msg.leave_short.title': 'Insufficient Balance',
'msg.leave_short.body': 'Insufficient leave balance.\nCurrent: {balance} days\nRequested: {days} days',
'msg.leave_short_hours.body': 'Insufficient leave balance.\nCurrent: {balance} days ({balance_hours}h)\nRequested: {days} days ({hours}h)',
'msg.settings_updated.title': 'Settings Updated',
'msg.settings_updated.body': 'Settings changes applied.',
'msg.meal_need_clock_in.title': 'Clock-in Required',
'msg.meal_need_clock_in.body': 'Meal times can only be recorded after clocking in.',
'msg.meal_recorded.title': 'Recorded',
'msg.meal_recorded.body': '{meal} {minutes}m recorded.\n({start} ~ {end})',
'msg.meal_actual_input': 'Enter actual {meal} time...',
'msg.workday_boundary.title': 'Workday Boundary Crossed',
'msg.workday_boundary.body': 'Workday boundary ({hour}:00) passed; auto-processed.\n\nPrevious day: clocked out at {before}\nToday: clocked in at {boundary}\n\nWork between midnight and {hour}:00 is counted as previous day overtime.',
'msg.new_workday.title': 'New Workday',
'msg.new_workday.body': 'New workday ({date}).\n\nClock in?',
'msg.clock_in_set.title': 'Set Clock-in',
'msg.clock_in_set.body': 'Clock-in time set.\n\nClock-in: {time}',
'leave.type.annual': 'Annual Leave',
'leave.type.sick': 'Sick Leave',
'leave.type.hourly_leave': 'Hourly Leave',
'leave.use.full_day': '1 day',
'leave.use.half_day': '0.5 day (4h)',
'leave.use.hour_1': '0.125 day (1h)',
'leave.use.min_30': '0.0625 day (30m)',
'leave.use.custom': '{days} days ({hours}h)',
'leave.type.half_am': 'AM Half-day',
'leave.type.half_pm': 'PM Half-day',
'leave.type.time_off': 'Time Off',
'leave.type.half': 'Half-day',
'leave.type.quarter': 'Quarter-day',
'report.leave_used': '🌴 Leave used: {value}',
'report.leave_used_days_hours': '{days}d {hours}h',
'report.leave_used_days': '{days}d',
'report.leave_used_hours': '{hours}h',
'report.leave_detail': ' - {type}: {time}{memo}',
'report.title': '📋 Daily Work Report - {date}',
'report.clock_in': '🕐 Clock-in: {time}',
'report.clock_out': '🕐 Clock-out: {time}',
'report.not_clocked_out': '🕐 Clock-out: not yet',
'report.total_work': '⏱️ Total work: {time}',
'report.break_time': '🚶 Break: {time}',
'report.break_detail': ' - {start} ~ {end} ({duration}m){reason}',
'report.break_in_progress': ' - {start} ~ on break{reason}',
'report.meal_actual': '{label}: {time} (actual)',
'report.meal_in_progress': ' - {start} ~ in progress',
'report.meal_default': '{label}: included ({time})',
'report.memo': '📝 Memo: {memo}',
'report.copied.title': 'Report Copied',
'report.copied.body': 'Daily work report copied to clipboard.\n\n{report}',
'label.lunch': '🍱 Lunch',
'label.lunch_short': 'Lunch',
'label.dinner': '🍽️ Dinner',
'label.dinner_short': 'Dinner',
'label.overtime_balance_zero': '0m (×0)',
'label.leave_balance_zero': 'Balance: 0 days',
'label.today_estimate': 'Today est.: {amount}',
'label.in_progress': 'in progress',
'label.break_returning': 'on break',
'label.meal_actual_suffix': 'actual',
'label.meal_included': 'included',
'report.overtime_occurred': '⏰ Overtime occurred: {time}',
'report.overtime_banked': ' 💰 Banked: {time} (30-min trunc)',
'report.overtime_used': '🕐 Overtime used: {time}',
'report.overtime_used_detail': ' - {time}{reason}',
'update.new_version_title': 'New Version Found',
'update.apply_failed_title': 'Update Failed',
'update.check_title': 'Update Check',
'update.up_to_date': 'You are on the latest version (v{version}).',
'update.network_error': 'Cannot connect to update server.\nPlease check your network.',
'update.no_release': 'No release found in update repository.\n(Private repo or before first release)',
'update.no_asset': 'New version exists but no downloadable main.exe asset.\nContact administrator.',
'update.unknown': 'Unknown response.',
'update.new_found_dev': 'New version {version} available.\n(Auto update not available in development — git pull or build)',
'update.new_found': 'Current: v{current}\nNew: v{new}\n\nRelease notes:\n{notes}\n\nDownload and update now?',
'update.downloading': 'Downloading...',
'update.download_title': 'Update Download',
'update.download_failed': 'Failed to download new version.',
'update.updater_failed': 'updater.exe not found or failed to run.',
'update.restart': 'Program will restart to apply update.',
'settings.title': 'Settings',
'settings.work_pattern': 'Work pattern:',
'settings.preset.standard_8h': 'Standard 8h (Lunch 60min)',
'settings.preset.short_7h30m': 'Reduced 7h 30m (Lunch 30min)',
'settings.preset.short_7h': 'Reduced 7h (Lunch 60min)',
'settings.preset.short_6h': 'Reduced 6h (Lunch 30min)',
'settings.preset.half_4h': 'Half-day 4h (No lunch)',
'settings.preset.custom': 'Custom',
'settings.daily_work': 'Daily work:',
'settings.lunch_default': 'Lunch break:',
'settings.dinner_default': 'Dinner break:',
'settings.auto_apply': 'Auto apply',
'settings.auto_apply_tooltip': 'Auto-apply 4 hours after clock-in',
'settings.suffix_hour': ' h',
'settings.suffix_minute': ' min',
'settings.notif_clock_out': 'Clock-out 30-min reminder',
'settings.notif_lunch': 'Lunch reminder',
'settings.notif_dinner': 'Dinner reminder',
'settings.notif_overtime': 'Overtime bank reminder',
'settings.notif_health': 'Health warning',
'settings.notif_break': 'Break reminder',
'settings.notif_break_tooltip': 'Suggest stretching after long continuous work',
'settings.notif_before': 'Clock-out alert:',
'settings.notif_before_spin_suffix': 'min before',
'settings.notif_before_tooltip': 'Minutes before clock-out to show alert',
'settings.advanced_thresholds': 'Advanced thresholds',
'settings.advanced_thresholds_tooltip': 'Adjust alert thresholds',
'settings.lunch_alert_after': 'Lunch alert (clock-in +):',
'settings.lunch_alert_tooltip': 'Hours after clock-in to remind lunch',
'settings.dinner_alert_after': 'Dinner alert (clock-in +):',
'settings.dinner_alert_tooltip': 'Hours after clock-in to remind dinner',
'settings.overtime_alert_at': 'Overtime balance alert:',
'settings.overtime_alert_tooltip': 'Alert when overtime balance reaches N hours',
'settings.weekly_limit': 'Weekly limit warning:',
'settings.weekly_limit_tooltip': 'Warn when weekly work exceeds N hours',
'settings.consecutive_ot': 'Consecutive OT warning:',
'settings.consecutive_ot_tooltip': 'Health warning after N consecutive OT days',
'settings.break_after': 'Break suggestion:',
'settings.break_after_tooltip': 'Suggest break after N hours',
'settings.time_format': 'Time format:',
'settings.time_format_12': 'AM/PM (5:30 PM)',
'settings.time_format_24': '24-hour (17:30)',
'settings.theme': 'Theme:',
'settings.theme_light': 'Light',
'settings.theme_dark': 'Dark',
'settings.font_scale': 'Font scale:',
'settings.high_contrast': 'High contrast mode',
'settings.high_contrast_tooltip': 'Black background + yellow text',
'settings.language_restart_tooltip': 'Language changes fully apply after restart.',
'settings.current_balance': 'Current balance: calculating...',
'settings.calc_unit': 'Calculation unit:',
'settings.initial_overtime': 'Previous overtime:',
'settings.auto_bank': 'Auto bank',
'settings.auto_bank_tooltip': 'Bank overtime on clock-out',
'settings.initial_overtime_note': '※ Overtime accumulated before using this program (absolute value)',
'settings.goal_group': 'Monthly goals (0=disabled)',
'settings.monthly_ot_cap': 'Monthly OT cap:',
'settings.daily_avg_goal': 'Daily avg goal:',
'settings.goal_note': '※ Check progress in Stats → Monthly tab',
'settings.annual_leave': 'Annual leave:',
'settings.remaining_leave': 'Remaining leave: calculating...',
'settings.used_leave': 'Previously used:',
'settings.used_leave_note': '※ Leave already used before this program (1 day = 8h)',
'settings.registered': 'Registered:',
'settings.add_korean_holidays': 'Korean holidays (auto)',
'settings.add_korean_holidays_tooltip': 'Auto-register lunar holidays + temporary holidays',
'settings.holiday_note': '※ All work on holidays is banked as overtime',
'settings.list': 'List',
'settings.korean_holidays_title': 'Korean holidays added',
'settings.korean_holidays_years_label': '{start} + {end}',
'settings.korean_holidays_years_label_single': '{year}',
'settings.korean_holidays_body': 'Auto-register Korean holidays for {years}?\n\nIncludes:\n• Solar holidays (New Year, Independence, Children\'s, Labor Day, etc.)\n• Lunar holidays (Seollal, Chuseok, Buddha\'s Birthday)\n• Government-designated substitute/temporary holidays\n\n※ Primary: Public Data Portal special-day API\n※ Fallback: \'holidays\' package (offline)',
'settings.korean_holidays_added': '{count} Korean holidays added for {year}.',
'settings.korean_holidays_included': 'Includes:\n',
'settings.package_not_installed': 'Package not installed',
'settings.package_fallback_body': '\'holidays\' package not installed; only fixed holidays were added.\n\n{hint} pip install holidays',
'settings.package_install_hint': 'For lunar/temp holidays auto-register:\n',
'settings.add_done': 'Added',
'settings.holiday_added': 'Holiday added.\n{date}: {name}',
'settings.holiday_add_title': 'Add Holiday',
'settings.holiday_date_prompt': 'Enter holiday date (YYYY-MM-DD):',
'settings.holiday_name_prompt': 'Enter holiday name:',
'settings.holiday_list_title': 'Holiday List',
'settings.holiday_list_header': '=== Holidays for {year} ===\n\n',
'settings.holiday_list_item': '{date} ({weekday}): {name}{recurring}',
'settings.holiday_total': 'Total: {count}',
'settings.holiday_delete_confirm': '\n\nDelete a holiday?',
'settings.holiday_delete_title': 'Delete Holiday',
'settings.holiday_delete_prompt': 'Select holiday to delete:',
'settings.delete_done': 'Deleted',
'settings.holiday_deleted': '{item} deleted.',
'settings.export_csv': 'CSV Export',
'settings.export_work': 'Work records',
'settings.export_overtime': 'Overtime',
'settings.export_monthly': 'Monthly summary',
'settings.import_csv': 'CSV Import',
'settings.import_tooltip': 'Header format: date,clock_in,clock_out,lunch_minutes,memo',
'settings.import_format': 'Standard format (header: date,clock_in,clock_out,lunch_minutes,memo)',
'settings.db_path_label': 'DB path:',
'settings.change': 'Change...',
'settings.db_path_tooltip': 'Change to cloud folder path. Restart required.',
'settings.auto_break_lock_tooltip': 'Auto start/end break on PC lock/unlock.',
'settings.gitea_feedback_label': 'Gitea feedback:',
'settings.gitea_token_placeholder': 'PAT (issue write permission, optional)',
'settings.clock_in_unlock_tooltip': 'For users who do not shut down PC — record screen unlock as clock-in.',
'settings.auto_break_lock': 'Auto break on screen lock',
'settings.gitea_feedback_tooltip': "Enable 'Report on Gitea' button on errors",
'settings.clock_in_unlock': 'Use first unlock as clock-in',
'settings.version': 'Version: v{version}',
'settings.check_update': 'Check update (F5)',
'settings.select_db': 'Select database file',
'settings.db_path_saved': 'New path saved:\n{path}\n\nCopy current database.db to new location and restart.',
'settings.parse_failed': 'Parse failed',
'settings.empty_file': 'Empty file',
'settings.empty_file_body': 'No valid rows.',
'settings.conflict_title': 'Conflict handling',
'settings.conflict_body': 'How to handle conflicts with existing dates?\n',
'settings.conflict_body_detailed': 'How to handle conflicts with existing dates?\nYes = Overwrite\nNo = Skip\nCancel = Cancel',
'settings.import_rows_intro': '{count} rows will be imported.\n\n',
'settings.import_failed': 'Import failed',
'settings.import_result': 'Import result:\n• Added: {added}\n• Updated: {updated}\n• Skipped: {skipped}',
'settings.import_complete': 'Complete',
'settings.save_done': 'Saved',
'settings.save_done_body': 'Settings saved.',
'settings.restart_title': 'Restart',
'settings.restart_body': 'Main screen applies immediately. Some dialogs require restart.\nRestart now?\n\n',
'settings.initial_overtime_title': 'Set previous overtime',
'settings.initial_overtime_body': 'Current: {old_hours}h {old_mins}m\nNew: {hours}h {mins}m\n\nChange previous overtime?',
'settings.initial_overtime_done': 'Set',
'settings.initial_overtime_done_body': 'Previous overtime set to {hours}h {mins}m.',
'settings.initial_overtime_error': 'Error setting previous overtime:\n{error}',
'settings.current_overtime_balance': 'Current balance: {hours}h {minutes}m ({balance}m)',
'settings.remaining_leave_fmt': 'Remaining: {remaining:.1f} days ({used} of {total} used)',
'settings.holiday_count': '{count} ({year})',
'settings.error': 'Error',
'settings.export_no_records': 'No records to export.',
'settings.save_work_title': 'Save work records',
'settings.export_done': 'Export complete',
'settings.work_exported': 'Work records saved.\n{path}',
'settings.save_ot_title': 'Save overtime records',
'settings.ot_exported': 'Overtime records saved.\n{path}',
'settings.save_monthly_title': 'Save monthly summary',
'settings.monthly_exported': 'Monthly summary saved.\n{path}',
'settings.export_failed': 'Export failed',
'settings.export_error': 'Error: {error}',
'settings.initial_leave_title': 'Set previous leave',
'settings.initial_leave_body': 'Current: {old_hours}h {old_mins}m\nNew: {hours}h {mins}m\n\nChange previous leave?',
'settings.initial_leave_done': 'Saved',
'settings.initial_leave_done_body': 'Previous leave set to {hours}h {mins}m.',
'date_format.full': '{year}-{month}-{day} ({weekday})',
'achieve.cat_ambition': 'Ambition',
'achieve.cat_balance': 'Work-Life Balance',
'achieve.cat_break_use': 'Break',
'achieve.cat_health': 'Health',
'achieve.cat_korea': 'Korean Culture',
'achieve.cat_leave': 'Leave',
'achieve.cat_meal': 'Meal',
'achieve.cat_meta': 'Meta',
'achieve.cat_milestone': 'Milestone',
'achieve.cat_ot_bank': 'Overtime Bank',
'achieve.cat_ot_use': 'Overtime Use',
'achieve.cat_pattern': 'Pattern',
'achieve.cat_punctual': 'Punctual',
'achieve.cat_season': 'Season',
'achieve.cat_secret': 'Secret',
'achieve.cat_settings': 'Settings',
'achieve.cat_special_day': 'Special Day',
'achieve.cat_stats': 'Stats',
'achieve.cat_streak': 'Streak',
'achieve.cat_time_slot': 'Time Slot',
'achieve.completion_rate': 'Completion',
'achieve.earned_date': ' ✓ Earned {date} ',
'achieve.empty': '(None yet)',
'achieve.secret_locked': '🔒 Revealed when achieved',
'achieve.tab_all': '🌐 All · {count}',
'achieve.tab_completed': '✓ Completed · {count}',
'achieve.tab_in_progress': '⚡ In Progress · {count}',
'achieve.tab_secret': '🌑 Secret · {earned}/{total}',
'achieve.tier_bronze': 'Bronze',
'achieve.tier_gold': 'Gold',
'achieve.tier_legend': 'Legend',
'achieve.tier_platinum': 'Platinum',
'achieve.tier_silver': 'Silver',
'achieve.title': 'Achievements',
'cal.add_done_body': 'Record added for {date}.',
'cal.add_done_title': 'Added',
'cal.add_error_body': 'Failed to add record: {error}',
'cal.add_error_title': 'Error',
'cal.btn_minus_30': '-30m',
'cal.btn_plus_30': '+30m',
'cal.check_dinner_1h': 'Dinner (1h)',
'cal.check_lunch_1h': 'Lunch (1h)',
'cal.context_add': 'Add record {date}',
'cal.context_delete': 'Delete {date}',
'cal.context_edit': 'Edit {date}',
'cal.delete_confirm_body': 'Really delete {date} record?\n(Overtime bank entries will also be deleted)',
'cal.delete_confirm_title': 'Confirm Delete',
'cal.delete_done_body': '{date} record deleted.',
'cal.delete_done_title': 'Deleted',
'cal.delete_record': 'Delete Record',
'cal.delete_selected_body': 'Delete clock-in record for {date}?\n\n※ Related overtime bank/usage entries will also be deleted.\n※ This cannot be undone.',
'cal.delete_selected_title': 'Delete Clock-in Record',
'cal.detail_clock_in': 'Clock-in: {time}',
'cal.detail_clock_out': 'Clock-out: {time}',
'cal.detail_clock_out_none': 'Clock-out: not recorded',
'cal.detail_date_fmt': '{year}-{month}-{day}',
'cal.detail_dinner_unused': 'Dinner: not used',
'cal.detail_dinner_used': 'Dinner: used',
'cal.detail_group_title': 'Selected Date Info',
'cal.detail_lunch_unused': 'Lunch: not used',
'cal.detail_lunch_used': 'Lunch: used',
'cal.detail_memo': 'Memo: {memo}',
'cal.detail_overtime_earned': '🔥 Overtime earned: {hours}h {minutes}m',
'cal.detail_total_hours': 'Total work: {hours:.1f}h',
'cal.dialog_title': 'Monthly Work Records',
'cal.edit_dialog_subtitle': 'Edit clock-in/out for {date}',
'cal.edit_dialog_title': 'Edit Clock-in/out Time',
'cal.edit_done_body': 'Clock-in/out updated for {date}.\n\nClock-in: {clock_in}\nClock-out: {clock_out}\nLunch: {lunch}\nDinner: {dinner}\nBreak: {break_minutes}m\nTotal work: {total_hours:.1f}h\nOvertime: {overtime_earned}m banked',
'cal.edit_done_title': 'Updated',
'cal.edit_error_body': 'Error while updating:\n{error}',
'cal.edit_error_title': 'Error',
'cal.edit_note': '※ Overtime will be recalculated.',
'cal.edit_time': 'Edit Time',
'cal.label_clock_in': 'Clock-in:',
'cal.label_clock_out': 'Clock-out:',
'cal.legend_leave': 'Leave',
'cal.legend_none': 'None',
'cal.legend_normal': 'Normal',
'cal.legend_overtime': 'Overtime',
'cal.memo_group': 'Memo',
'cal.memo_placeholder': 'Overtime reason, notes...',
'cal.no_record': 'No record.',
'cal.save_memo': 'Save Memo',
'cal.save_memo_body': 'Memo saved for {date}.',
'cal.save_memo_title': 'Save Memo',
'cal.time_error_body': 'Clock-out must be later than clock-in.',
'cal.time_error_title': 'Time Error',
'clock_in_dialog.cancelled': 'Cancelled',
'clock_in_dialog.selected': 'Selected time: {time}',
'field.avg_daily_value': '{hours:.1f}h',
'field.overtime_value': '{hours}h {minutes}m',
'field.total_work_value': '{hours:.1f}h ({days} days)',
'goal.avg_daily': 'Daily Avg:',
'goal.overtime': 'Overtime:',
'goal.title': 'Monthly Goals',
'help.onboarding_button': 'Re-run Onboarding',
'leave_cal.detail_label': '{type} {days} days',
'leave_cal.detail_memo': '{type} {days} days ({memo})',
'leave_cal.detail_no_record': '{date} — No leave usage',
'leave_cal.header': 'Remaining {balance:.2f}d / Total {total:.0f}d (Used {used:.2f}d)',
'leave_cal.legend_full': 'Full (1.0)',
'leave_cal.legend_full_planned': 'Full+Planned',
'leave_cal.legend_half': 'Half (0.5)',
'leave_cal.legend_planned': 'Planned',
'leave_cal.legend_quarter': 'Quarter (0.25)',
'leave_cal.title': 'Leave Calendar',
'meal.dialog_title': 'Enter {meal} Time',
'meal.error_after_clock_out': 'After clock-out ({time})',
'meal.error_before_clock_in': 'Before clock-in ({time})',
'meal.error_start_after_end': 'Start is later than end',
'meal.error_too_long': 'Meal time exceeds 8 hours',
'meal.info_clock_in_limit': '\nMust be after clock-in ({time}).',
'meal.info_text': 'Enter {meal} start and end times.\nThis records the exact time instead of the default {minutes} minutes.',
'meal.input_error_title': 'Input Error',
'meal.label_end': 'End:',
'meal.label_start': 'Start:',
'meal.preview_total': 'Total {minutes} min',
'mini.close': 'Close mini widget',
'mini.open_main': 'Open main window',
'onboarding.detection_boot': 'PC boot time (default — if you shut down daily)',
'onboarding.detection_info': '\nRecommended for users who leave their PC running.',
'onboarding.detection_manual': 'Manual only (no auto detection)',
'onboarding.detection_subtitle': 'Choose how the app detects your clock-in time.',
'onboarding.detection_title': 'Clock-in Detection',
'onboarding.detection_unlock': 'First screen unlock (if you leave PC on)',
'onboarding.discord_enable': 'Use Discord webhook notifications',
'onboarding.discord_failed': 'Failed',
'onboarding.discord_failed_body': 'Send failed. Please check the URL.',
'onboarding.discord_guide': 'Setup:\n1. In Discord, right-click channel → Integrations → Webhooks\n2. New Webhook → Copy URL\n3. Paste it above',
'onboarding.discord_subtitle': 'Enter a webhook URL to receive clock-in/out and break reminders on Discord. (Mobile push)',
'onboarding.discord_success': 'Success',
'onboarding.discord_success_body': 'Check the Discord channel for the test message.',
'onboarding.discord_test': 'Send test message',
'onboarding.discord_title': 'Discord Notifications (Optional)',
'onboarding.discord_url_invalid_body': 'Not a valid Discord webhook URL.\nExample: https://discord.com/api/webhooks/{ID}/{TOKEN}',
'onboarding.discord_url_invalid_title': 'Invalid URL',
'onboarding.discord_url_placeholder': 'https://discord.com/api/webhooks/...',
'onboarding.discord_url_required_body': 'Please enter a webhook URL first.',
'onboarding.discord_url_required_title': 'URL Required',
'onboarding.finish_msg': 'You can change these settings anytime in [Settings].\nTo re-run onboarding, use [Help → Re-run Onboarding].\n\nShortcuts:\n • Ctrl+O — Toggle clock-in/out\n • F1 — Help\n • F5 — Check update\n • Ctrl+, — Settings',
'onboarding.finish_subtitle': 'Your clock-in will now be tracked automatically.',
'onboarding.finish_title': 'Ready!',
'onboarding.hourly_wage': 'Hourly wage:',
'onboarding.input_error_title': 'Input Error',
'onboarding.leave_group': 'Annual Leave',
'onboarding.leave_salary_subtitle': 'Enter annual leave days and optional salary info.',
'onboarding.leave_salary_title': 'Leave + Salary (Optional)',
'onboarding.my_leave': 'My leave:',
'onboarding.overtime_rate': 'Overtime rate:',
'onboarding.rate_1_5x': '1.5x (Korean labor law default)',
'onboarding.rate_1x': '1.0x (no premium)',
'onboarding.rate_2x': '2.0x (night/holiday premium)',
'onboarding.salary_enabled': 'Enable salary estimate',
'onboarding.salary_group': 'Salary Estimate (Optional — disable if flat rate)',
'onboarding.wage_suffix': ' KRW/h',
'onboarding.welcome_intro': 'This app:\n• Auto-detects clock-in from boot/unlock\n• Banks overtime in 30-min units\n• Tracks annual/half-day leave and breaks\n• Counts down to clock-out every second\n\nPress [Next] to start.',
'onboarding.welcome_subtitle': "First time using Clock-out Time Calculator? Let's set it up in 5 steps.",
'onboarding.welcome_title': 'Welcome!',
'onboarding.window_title': 'Clock-out Calculator — Setup',
'onboarding.work_min_too_small': 'Daily work must be at least 30 minutes.',
'onboarding.work_pattern_subtitle': 'Choose your daily work hours. You can change this later in Settings.',
'onboarding.work_pattern_title': 'Work Pattern',
'past_record.check_clock_out': 'Enter',
'past_record.check_dinner': 'Include dinner',
'past_record.check_lunch': 'Include lunch',
'past_record.dialog_title': 'Add Record — {date}',
'past_record.info': 'Enter work record for {date}.',
'past_record.input_error_body': 'Clock-out must be later than clock-in.',
'past_record.input_error_title': 'Input Error',
'past_record.label_clock_in': 'Clock-in:',
'past_record.label_clock_out': 'Clock-out:',
'past_record.label_memo': 'Memo (optional):',
'past_record.memo_placeholder': 'e.g. remote work / business trip / leave',
'recurring.add_done_body': 'Recurring pattern registered.\n{pattern}',
'recurring.add_done_title': 'Added',
'recurring.add_group': 'Add New Pattern',
'recurring.biweekly': 'Biweekly',
'recurring.btn_add': 'Add',
'recurring.btn_delete_selected': 'Delete Selected',
'recurring.day_suffix': '',
'recurring.deduction_full': '1.0 day (full)',
'recurring.deduction_half': '0.5 day (half)',
'recurring.deduction_quarter': '0.25 day (quarter)',
'recurring.delete_confirm_body': 'Delete this recurring pattern?\n\n{item}',
'recurring.delete_confirm_title': 'Confirm Delete',
'recurring.input_error_title': 'Input Error',
'recurring.input_error_weekday': 'Select at least one weekday.',
'recurring.label_cycle': 'Cycle:',
'recurring.label_deduction': 'Deduct:',
'recurring.label_end': 'End:',
'recurring.label_memo': 'Memo:',
'recurring.label_monthly_day': 'Day:',
'recurring.label_start': 'Start:',
'recurring.label_weekday': 'Weekday:',
'recurring.list_group': 'Registered Recurring Patterns',
'recurring.memo_placeholder': 'e.g. childcare reduced hours',
'recurring.monthly': 'Monthly Nth day',
'recurring.no_end': 'No end (indefinite)',
'recurring.title': 'Recurring Leave Management',
'recurring.pattern_weekly': '{prefix} {weekdays}',
'recurring.pattern_monthly': 'Monthly {day}',
'recurring.weekly': 'Weekly',
'schedule.btn_add_leave': 'Register Leave',
'schedule.btn_recurring': 'Manage Recurring Patterns',
'schedule.delete': 'Delete',
'schedule.delete_leave_confirm_body': 'Delete this leave record? (Balance will be restored automatically.)',
'schedule.delete_leave_confirm_title': 'Confirm Delete',
'schedule.delete_recurring_confirm_body': 'Delete this recurring pattern? (Removes all future instances)',
'schedule.delete_recurring_confirm_title': 'Confirm Delete',
'schedule.detail_placeholder': 'Select a date',
'schedule.header': 'Monthly Schedule — Holidays + Leave + Recurring Patterns',
'schedule.holiday': 'Holiday: {name}',
'schedule.leave_label': '{type} {days} days',
'schedule.recurring_item': '{pattern} · {days}d ({type})',
'schedule.legend_half': 'Half/Quarter Day',
'schedule.legend_holiday': 'Holiday',
'schedule.legend_leave_planned': 'Leave Planned',
'schedule.legend_leave_used': 'Leave Used',
'schedule.legend_recurring': 'Recurring Pattern',
'schedule.no_events': 'No events',
'schedule.title': 'Schedule',
'schedule.weekend': 'Weekend ({weekday})',
'schedule.weekday_suffix': '',
'today.detail_break': 'Break {minutes}m',
'today.detail_dinner': 'Dinner {minutes}m',
'today.detail_lunch': 'Lunch {minutes}m',
'today.detail_overtime': 'Overtime {actual}m → banked {earned}m',
'today.title': "Today's Summary",
'today.total_work': 'Total work: {hours}h {minutes}m',
'view.leave.btn_schedule': 'Schedule',
'view.leave.duplicate_register_body': '{date} already has {existing_days:.2f} days registered.\nAdding {days:.2f} more days would exceed 1 day.',
'view.leave.duplicate_register_title': 'Duplicate Exceeds Limit',
'view.leave.holiday_register_forbidden_body': '{date} is already a holiday ({name}).\nNo need to deduct leave.',
'view.leave.holiday_register_forbidden_title': 'Cannot Register on Holiday',
'view.leave.schedule_tooltip': 'Unified view of holidays + leave + recurring patterns',
'view.leave.used_1day': '1 day',
'view.leave.used_half_day': '0.5 day (4h)',
'view.leave.used_hours_fmt': '{days} days ({hours}h)',
'view.leave.used_days_fmt': '{days} days',
'view.leave.weekend_register_forbidden_body': 'Leave cannot be registered on weekends. (Already non-working day)',
'view.leave.weekend_register_forbidden_title': 'Cannot Register on Weekend',
# === Achievements ===
'achieve.streak_first.name': 'First Step',
'achieve.streak_first.desc': 'First clock-in record.',
'achieve.streak_3.name': 'Taking Root',
'achieve.streak_3.desc': '3 consecutive business days clocked in.',
'achieve.streak_5.name': 'First Week Clear',
'achieve.streak_5.desc': '5 consecutive business days clocked in.',
'achieve.streak_7_cal.name': '7 Days Straight',
'achieve.streak_7_cal.desc': '7 consecutive calendar days clocked in, weekends included.',
'achieve.streak_10.name': 'Two Weeks Running',
'achieve.streak_10.desc': '10 consecutive business days clocked in.',
'achieve.streak_22.name': 'Monthly Perfect Attendance',
'achieve.streak_22.desc': '100% business-day attendance for one month (22 days).',
'achieve.streak_50.name': '50-Day Streak',
'achieve.streak_50.desc': '50 consecutive business days clocked in.',
'achieve.streak_100.name': '100-Day Streak',
'achieve.streak_100.desc': '100 consecutive business days clocked in.',
'achieve.streak_quarter.name': 'Quarter Clear',
'achieve.streak_quarter.desc': 'About 65 business days (3 months).',
'achieve.streak_half_year.name': 'Half-Year Marathon',
'achieve.streak_half_year.desc': 'About 130 business days (6 months).',
'achieve.streak_year.name': 'Full-Year Season',
'achieve.streak_year.desc': 'About 260 business days (1 year).',
'achieve.streak_200.name': 'Science',
'achieve.streak_200.desc': '200 consecutive business days clocked in.',
'achieve.streak_365_cal.name': 'Immortal',
'achieve.streak_365_cal.desc': '365 consecutive calendar days clocked in.',
'achieve.streak_resilience.name': 'Bounce Back',
'achieve.streak_resilience.desc': 'Clocked in the day immediately after absence (auto: restart after calendar streak breaks).',
'achieve.streak_total_100.name': '100 Total Clock-ins',
'achieve.streak_total_100.desc': '100 total clock-ins.',
'achieve.streak_total_500.name': '500 Total Clock-ins',
'achieve.streak_total_500.desc': '500 total clock-ins.',
'achieve.streak_total_1000.name': '1000 Total Clock-ins',
'achieve.streak_total_1000.desc': '1000 total clock-ins.',
'achieve.punc_before_8_1.name': 'Early Bird',
'achieve.punc_before_8_1.desc': 'Clocked in before 08:00 once.',
'achieve.punc_before_8_10.name': 'Early Bird Flock',
'achieve.punc_before_8_10.desc': 'Clocked in before 08:00 10 times.',
'achieve.punc_before_8_30.name': 'Early to Bed, Early to Rise',
'achieve.punc_before_8_30.desc': 'Clocked in before 08:00 30 times.',
'achieve.punc_before_6_1.name': 'No Dawn Sleep',
'achieve.punc_before_6_1.desc': 'Clocked in before 06:00 once.',
'achieve.punc_before_6_10.name': 'Cutter of Darkness',
'achieve.punc_before_6_10.desc': 'Clocked in before 06:00 10 times.',
'achieve.punc_before_5.name': 'Dawn Champion',
'achieve.punc_before_5.desc': 'Clocked in before 05:00.',
'achieve.punc_at_9.name': 'Exactly Nine',
'achieve.punc_at_9.desc': 'Clocked in exactly at 09:00 (±1 min) once.',
'achieve.punc_at_9_5.name': 'Perfect Nine',
'achieve.punc_at_9_5.desc': 'Clocked in exactly at 09:00 (±1 min) 5 times.',
'achieve.punc_late_5min.name': '5 Minutes Late',
'achieve.punc_late_5min.desc': 'Clocked in at 09:0009:05 once (self-deprecating).',
'achieve.punc_at_909.name': 'Fateful Moment',
'achieve.punc_at_909.desc': 'Clocked in at 09:09 (secret).',
'achieve.bal_first_punct.name': 'First On-Time Leave',
'achieve.bal_first_punct.desc': 'First on-time clock-out.',
'achieve.bal_punct_10.name': 'Clock-outer',
'achieve.bal_punct_10.desc': 'On-time clock-out 10 times.',
'achieve.bal_punct_30.name': 'On-Time Champ',
'achieve.bal_punct_30.desc': 'On-time clock-out 30 times.',
'achieve.bal_punct_100.name': 'True Freedom',
'achieve.bal_punct_100.desc': 'On-time clock-out 100 times.',
'achieve.bal_punct_300.name': 'Work-Life Master',
'achieve.bal_punct_300.desc': 'On-time clock-out 300 times.',
'achieve.ot_first_30m.name': 'First 30 Minutes',
'achieve.ot_first_30m.desc': 'First overtime banked.',
'achieve.ot_total_60m.name': '1-Hour Savings',
'achieve.ot_total_60m.desc': '1 hour banked in total.',
'achieve.ot_total_5h.name': '5 Hours Banked',
'achieve.ot_total_5h.desc': '5 hours banked in total.',
'achieve.ot_total_10h.name': '10 Hours Banked',
'achieve.ot_total_10h.desc': '10 hours banked in total.',
'achieve.ot_total_25h.name': '25 Hours Banked',
'achieve.ot_total_25h.desc': '25 hours banked in total.',
'achieve.ot_total_50h.name': '50 Hours Banked',
'achieve.ot_total_50h.desc': '50 hours banked in total.',
'achieve.ot_total_100h.name': 'Marathoner',
'achieve.ot_total_100h.desc': '100 hours banked in total (concerned message).',
'achieve.ot_total_200h.name': 'Workaholic Warning',
'achieve.ot_total_200h.desc': '200 hours banked in total (warning).',
'achieve.ot_total_300h.name': 'Danger Signal',
'achieve.ot_total_300h.desc': '300 hours banked in total (strong warning).',
'achieve.ot_total_500h.name': 'ER Regular',
'achieve.ot_total_500h.desc': '500 hours banked in total (self-deprecating).',
'achieve.use_first.name': 'First Break',
'achieve.use_first.desc': 'First time using banked time.',
'achieve.use_total_5h.name': 'Using the Gift',
'achieve.use_total_5h.desc': '5 hours used in total.',
'achieve.use_total_25h.name': 'Value of Rest',
'achieve.use_total_25h.desc': '25 hours used in total.',
'achieve.use_total_50h.name': 'Recovery Master',
'achieve.use_total_50h.desc': '50 hours used in total.',
'achieve.use_total_100h.name': 'Massage',
'achieve.use_total_100h.desc': '100 hours used in total.',
'achieve.leave_first.name': 'First Leave',
'achieve.leave_first.desc': 'First leave usage.',
'achieve.leave_half.name': 'First Half-Day',
'achieve.leave_half.desc': '0.5-day leave used.',
'achieve.leave_quarter.name': 'Hourly Leave',
'achieve.leave_quarter.desc': '0.25-day leave used.',
'achieve.leave_streak_3.name': 'Mini Vacation',
'achieve.leave_streak_3.desc': '3 consecutive days of leave.',
'achieve.leave_streak_5.name': 'Serious Vacation',
'achieve.leave_streak_5.desc': '5 consecutive days of leave.',
'achieve.leave_streak_7.name': 'Long-Distance Vacation',
'achieve.leave_streak_7.desc': '7 or more consecutive days of leave.',
'achieve.leave_total_10.name': 'Leave x10',
'achieve.leave_total_10.desc': '10 leave records.',
'achieve.leave_sick.name': 'Sick Leave',
'achieve.leave_sick.desc': 'Sick-type leave used.',
'achieve.meal_lunch_first.name': 'First Lunch Entry',
'achieve.meal_lunch_first.desc': 'First lunch toggle.',
'achieve.meal_lunch_30.name': 'Lunch Master',
'achieve.meal_lunch_30.desc': 'Lunch toggled 30 times.',
'achieve.meal_lunch_100.name': 'Lunch Champ',
'achieve.meal_lunch_100.desc': 'Lunch toggled 100 times.',
'achieve.meal_dinner_first.name': 'First Dinner Entry',
'achieve.meal_dinner_first.desc': 'First dinner toggle.',
'achieve.meal_dinner_10.name': 'Dinner Regular',
'achieve.meal_dinner_10.desc': 'Dinner toggled 10 times (warning).',
'achieve.meal_dinner_30.name': 'Late-Night Regular',
'achieve.meal_dinner_30.desc': 'Dinner toggled 30 times (warning).',
'achieve.meal_lunch_actual.name': 'Measured Lunch',
'achieve.meal_lunch_actual.desc': 'Entered actual lunch time.',
'achieve.meal_dinner_actual.name': 'Measured Dinner',
'achieve.meal_dinner_actual.desc': 'Entered actual dinner time.',
'achieve.break_first.name': 'First Break',
'achieve.break_first.desc': 'First break started.',
'achieve.break_10.name': 'Break Champ',
'achieve.break_10.desc': '10 breaks.',
'achieve.break_50.name': 'Walker',
'achieve.break_50.desc': '50 breaks.',
'achieve.slot_in_06.name': '06:00 Clock-in',
'achieve.slot_in_06.desc': 'Clocked in during 06:0006:59 once.',
'achieve.slot_in_07.name': '07:00 Clock-in',
'achieve.slot_in_07.desc': 'Clocked in during 07:0007:59 once.',
'achieve.slot_in_08.name': '08:00 Clock-in',
'achieve.slot_in_08.desc': 'Clocked in during 08:0008:59 once.',
'achieve.slot_in_10.name': '10:00 Clock-in',
'achieve.slot_in_10.desc': 'Clocked in during 10:0010:59 (late / flexible).',
'achieve.slot_in_11.name': '11:00 Clock-in',
'achieve.slot_in_11.desc': 'Clocked in during 11:0011:59 (self-deprecating).',
'achieve.slot_out_19.name': '19:00 Clock-out',
'achieve.slot_out_19.desc': 'Clocked out during 19:0019:59 10 times (warning).',
'achieve.slot_out_20.name': '20:00 Clock-out',
'achieve.slot_out_20.desc': 'Clocked out during 20:0020:59 10 times (warning).',
'achieve.slot_out_21.name': '21:00 Clock-out',
'achieve.slot_out_21.desc': 'Clocked out during 21:0021:59 5 times (warning).',
'achieve.slot_out_22.name': '22:00 Clock-out',
'achieve.slot_out_22.desc': 'Clocked out during 22:0022:59 once (warning).',
'achieve.slot_out_23.name': '23:00 Clock-out',
'achieve.slot_out_23.desc': 'Clocked out during 23:0023:59 once (warning).',
'achieve.slot_midnight.name': 'Midnight Clock-out',
'achieve.slot_midnight.desc': 'Clocked out after midnight (warning).',
'achieve.slot_midnight_3.name': 'Owl Trio',
'achieve.slot_midnight_3.desc': 'Clocked out after midnight 3 times (warning).',
'achieve.weekend_1.name': 'Weekend Work Once',
'achieve.weekend_1.desc': 'Clocked in on a Saturday/Sunday once.',
'achieve.weekend_5.name': 'Weekend Worker',
'achieve.weekend_5.desc': 'Clocked in on weekends 5 times (warning).',
'achieve.weekend_20.name': 'True Workaholic',
'achieve.weekend_20.desc': 'Clocked in on weekends 20 times (strong self-deprecating).',
'achieve.holiday_1.name': 'Holiday Work',
'achieve.holiday_1.desc': 'Clocked in on a Korean public holiday once.',
'achieve.holiday_5.name': 'Holiday Workaholic',
'achieve.holiday_5.desc': 'Clocked in on Korean public holidays 5 times (warning).',
'achieve.day_christmas.name': 'Christmas at Work',
'achieve.day_christmas.desc': 'Clocked in on 12/25 (self-deprecating).',
'achieve.day_newyear.name': 'New Year at Work',
'achieve.day_newyear.desc': 'Clocked in on 1/1 (self-deprecating).',
'achieve.day_liberation.name': 'Liberation Day at Work',
'achieve.day_liberation.desc': 'Clocked in on 8/15 (Liberation Day).',
'achieve.day_children.name': "Children's Day at Work",
'achieve.day_children.desc': "Clocked in on 5/5 (Children's Day, self-deprecating).",
'achieve.day_hangul.name': 'Hangeul Day at Work',
'achieve.day_hangul.desc': 'Clocked in on 10/9 (Hangeul Day).',
'achieve.day_valentine.name': 'Valentine at Work',
'achieve.day_valentine.desc': 'Clocked in on 2/14.',
'achieve.day_white.name': 'White Day at Work',
'achieve.day_white.desc': 'Clocked in on 3/14.',
'achieve.day_pepero.name': 'Pepero Day',
'achieve.day_pepero.desc': 'Clocked in on 11/11 (Pepero Day).',
'achieve.day_halloween.name': 'Halloween at Work',
'achieve.day_halloween.desc': 'Clocked in on 10/31.',
'achieve.day_aprilfools.name': 'April Fools at Work',
'achieve.day_aprilfools.desc': 'Clocked in on 4/1.',
'achieve.day_77.name': 'Chilseok (July 7)',
'achieve.day_77.desc': 'Clocked in on 7/7 (Chilseok).',
'achieve.day_dongji.name': 'Dongji at Work',
'achieve.day_dongji.desc': 'Clocked in on 12/22 (Dongji, winter solstice).',
'achieve.day_parents.name': 'Parents Day On-Time Leave',
'achieve.day_parents.desc': 'On-time clock-out on 5/8 (Parents Day).',
'achieve.day_teacher.name': "Teachers' Day On-Time Leave",
'achieve.day_teacher.desc': "On-time clock-out on 5/15 (Teachers' Day).",
'achieve.day_xmas_eve.name': 'Christmas Eve On-Time Leave',
'achieve.day_xmas_eve.desc': 'On-time clock-out on 12/24.',
'achieve.day_earth.name': 'Earth Day',
'achieve.day_earth.desc': 'Clocked in on 4/22 (secret).',
'achieve.season_jan.name': 'January Settled',
'achieve.season_jan.desc': 'Clocked in during January.',
'achieve.season_feb.name': 'February Perfect',
'achieve.season_feb.desc': 'Attended all business days in February.',
'achieve.season_mar.name': 'Spring Greeting',
'achieve.season_mar.desc': 'First clock-in of March.',
'achieve.season_apr.name': 'April Settled',
'achieve.season_apr.desc': 'Attended all business days in April.',
'achieve.season_may.name': 'May Perfect',
'achieve.season_may.desc': 'Attended all business days in May.',
'achieve.season_jun.name': 'Start of Summer',
'achieve.season_jun.desc': 'First clock-in of June.',
'achieve.season_jul.name': 'July Settled',
'achieve.season_jul.desc': 'Attended all business days in July.',
'achieve.season_aug.name': 'August Perfect',
'achieve.season_aug.desc': 'Attended all business days in August.',
'achieve.season_sep.name': 'Start of Autumn',
'achieve.season_sep.desc': 'First clock-in of September.',
'achieve.season_oct.name': 'October Settled',
'achieve.season_oct.desc': 'Attended all business days in October.',
'achieve.season_nov.name': 'November Maple',
'achieve.season_nov.desc': 'Attended all business days in November.',
'achieve.season_dec.name': 'Start of Winter',
'achieve.season_dec.desc': 'First clock-in of December.',
'achieve.mile_first.name': 'Hello, World!',
'achieve.mile_first.desc': 'First app run.',
'achieve.mile_7days.name': 'One Week User',
'achieve.mile_7days.desc': 'Used the app for 7 days.',
'achieve.mile_30days.name': 'One Month User',
'achieve.mile_30days.desc': 'Used the app for 30 days.',
'achieve.mile_365days.name': '1st Anniversary',
'achieve.mile_365days.desc': 'Used the app for 365 days.',
'achieve.mile_730days.name': '2nd Anniversary',
'achieve.mile_730days.desc': 'Used the app for 730 days.',
'achieve.mile_1095days.name': '3rd Anniversary',
'achieve.mile_1095days.desc': 'Used the app for 3 years.',
'achieve.mile_5years.name': '5-Year User',
'achieve.mile_5years.desc': 'Used the app for 5 years.',
'achieve.mile_10years.name': '10-Year User',
'achieve.mile_10years.desc': 'Used the app for 10 years.',
'achieve.stat_weekly_10.name': 'Weekly Stats Viewer',
'achieve.stat_weekly_10.desc': 'Viewed the Weekly tab 10 times.',
'achieve.stat_monthly_10.name': 'Monthly Stats Viewer',
'achieve.stat_monthly_10.desc': 'Viewed the Monthly tab 10 times.',
'achieve.stat_pattern_10.name': 'Pattern Analyst',
'achieve.stat_pattern_10.desc': 'Viewed the Pattern tab 10 times.',
'achieve.stat_calendar_30.name': 'Calendar Champ',
'achieve.stat_calendar_30.desc': 'Viewed the Calendar 30 times.',
'achieve.stat_report_first.name': 'First Daily Report',
'achieve.stat_report_first.desc': 'Generated a daily report once.',
'achieve.stat_report_30.name': 'Report Champ',
'achieve.stat_report_30.desc': 'Generated daily reports 30 times.',
'achieve.stat_chart_hover.name': 'Chart Hover Discovery',
'achieve.stat_chart_hover.desc': 'Discovered chart hover for the first time.',
'achieve.stat_achievements_open.name': 'Achievement Museum',
'achieve.stat_achievements_open.desc': 'Opened the Achievements view 50 times.',
'achieve.secret_palindrome.name': 'Palindrome Time',
'achieve.secret_palindrome.desc': 'Clock-in time is a palindrome.',
'achieve.secret_jackpot.name': 'Jackpot Time',
'achieve.secret_jackpot.desc': 'Clock-in time has all identical digits.',
'achieve.secret_fri13.name': 'Friday the 13th',
'achieve.secret_fri13.desc': 'Clocked in on Friday the 13th.',
'achieve.secret_777.name': '7-7-7',
'achieve.secret_777.desc': 'Clocked in at 07:07 on July 7.',
'achieve.secret_exact_8h.name': 'Exactly 8 Hours',
'achieve.secret_exact_8h.desc': 'Worked exactly 8h 0m.',
'achieve.secret_pi_day.name': 'Pi Day',
'achieve.secret_pi_day.desc': 'Clocked in at 01:59 on 3/14.',
'achieve.secret_fibonacci.name': 'Fibonacci',
'achieve.secret_fibonacci.desc': 'Clock-in minute is a Fibonacci number.',
'achieve.secret_double_six.name': 'Double Six',
'achieve.secret_double_six.desc': 'Clocked in at 18:06 on 6/6.',
'achieve.secret_anniversary.name': 'Wizard',
'achieve.secret_anniversary.desc': 'Clocked in exactly 365 days after joining.',
'achieve.set_dark.name': 'Dark Side',
'achieve.set_dark.desc': 'Used dark theme once.',
'achieve.set_lang.name': 'Bilingual',
'achieve.set_lang.desc': 'Changed language (used en).',
'achieve.set_a11y.name': 'Accessibility User',
'achieve.set_a11y.desc': 'Font scale ≠ 100% or high contrast ON.',
'achieve.set_overtime_unit.name': 'Unit Changer',
'achieve.set_overtime_unit.desc': 'Changed overtime_unit.',
'achieve.set_goal_full.name': 'Goal Master',
'achieve.set_goal_full.desc': 'Set both monthly OT cap and daily average goal.',
'achieve.set_discord_full.name': 'Full Setup',
'achieve.set_discord_full.desc': 'Discord URL + all notifications ON.',
'achieve.set_cloud.name': 'Cloud Sync',
'achieve.set_cloud.desc': 'Changed DB path.',
'achieve.meta_first.name': 'First Achievement',
'achieve.meta_first.desc': 'Earned first achievement.',
'achieve.meta_10.name': '10 Achievements',
'achieve.meta_10.desc': 'Hold 10 achievements.',
'achieve.meta_25.name': '25 Achievements',
'achieve.meta_25.desc': 'Hold 25 achievements.',
'achieve.meta_50.name': '50 Achievements',
'achieve.meta_50.desc': 'Hold 50 achievements.',
'achieve.meta_75.name': '75 Achievements',
'achieve.meta_75.desc': 'Hold 75 achievements.',
'achieve.meta_100.name': '100 Achievements',
'achieve.meta_100.desc': 'Hold 100 achievements.',
'achieve.meta_secret_1.name': 'Secret Found',
'achieve.meta_secret_1.desc': 'Discovered first secret.',
'achieve.meta_secret_5.name': 'Secret Hunter',
'achieve.meta_secret_5.desc': 'Discovered 5 secrets.',
'achieve.streak_monday_10.name': 'Monday Conqueror',
'achieve.streak_monday_10.desc': 'Clocked in 10 Mondays in a row.',
'achieve.streak_friday_10.name': 'Friday Flawless',
'achieve.streak_friday_10.desc': 'Clocked in 10 Fridays in a row.',
},
}
# === HelpView 큰 HTML 콘텐츠 (별도 사전) ===
_HELP_HTML = {
'ko': {
'help.html.intro': """
<h2>👋 환영합니다!</h2>
<p><b>퇴근시간 계산기</b>는 출근 시간 자동 감지부터 연장근무 적립·사용까지
하루 근무를 정리해 주는 데스크톱 앱입니다.</p>
<h3>한눈에 보는 기본 흐름</h3>
<ol>
<li><b>출근</b> — 컴퓨터 켜진 시각이 자동으로 출근 시간으로 기록돼요.</li>
<li><b>근무 중</b> — 메인 화면에 퇴근까지 남은 시간이 1초마다 갱신됩니다.</li>
<li><b>점심/저녁</b> — 식사 시간 버튼을 누르면 그만큼 근무시간이 늘어납니다.</li>
<li><b>외출</b> — "외출 시작/복귀" 버튼으로 잠깐 자리 비운 시간을 추적합니다.</li>
<li><b>퇴근</b> — 퇴근 버튼을 누르면 연장근무가 30분 단위로 자동 적립됩니다.</li>
</ol>
<h3>처음 켰다면 꼭 확인하세요</h3>
<ul>
<li><b>설정 → 근무 시간</b>: 본인 근무 패턴(8시간 / 단축 7h30m / 6시간 등)을
프리셋에서 선택하거나 직접 입력하세요.</li>
<li><b>설정 → 휴가 → 연간 연차</b>: 본인 연차 일수를 맞춰 두면 잔여 연차가
자동 계산됩니다.</li>
<li><b>관리자 권한</b>이 필요할 수 있어요. 부팅 시간이 자동 감지되지 않으면
관리자 권한으로 실행해 보세요.</li>
</ul>
""",
'help.html.work_hours': """
<h2>🕘 근무시간 설정</h2>
<h3>표준 근무 / 단축근무 / 시간제 모두 지원</h3>
<ul>
<li><b>표준 8시간</b> — 점심 60분 (기본값)</li>
<li><b>단축근무 7시간 30분</b> — 점심 30분</li>
<li><b>단축근무 7시간</b> — 점심 60분</li>
<li><b>단축근무 6시간</b> — 점심 30분</li>
<li><b>반일 4시간</b> — 점심 없음</li>
<li><b>사용자 정의</b> — 시간/분 직접 입력 (5분 단위)</li>
</ul>
<h3>💡 단축근무 사용자 안내</h3>
<p>예) 하루 7시간 30분 근무 + 점심 30분</p>
<ol>
<li>설정 → 근무 시간 → <b>근무 패턴</b>에서
<i>"단축근무 7시간 30분 (점심 30분)"</i> 선택</li>
<li>또는 직접 입력: <b>하루 기본 근무</b>에 <code>7 시간 30 분</code>,
<b>점심시간 기본</b>에 <code>30 분</code></li>
<li><b>저장</b> 클릭하면 즉시 메인 화면 계산이 갱신됩니다.</li>
</ol>
<h3>점심시간 자동 적용</h3>
<p>설정에서 <b>"자동 적용"</b>을 체크하면 출근 후 <b>4시간 경과</b> 시
점심시간이 자동으로 켜집니다.</p>
""",
'help.html.overtime': """
<h2>🏦 연장근무 30분 단위 적립 시스템</h2>
<h3>적립 규칙</h3>
<p>정규 퇴근시간 이후 일한 시간은 <b>30분 단위로 절삭</b>되어 적립됩니다.</p>
<ul>
<li>1시간 35분 일했다면 → <b>1시간 30분</b> 적립</li>
<li>55분 일했다면 → <b>30분</b> 적립</li>
<li>29분 일했다면 → <b>0분</b> 적립</li>
</ul>
<h3>사용 방법</h3>
<p>적립된 연장근무는 메인 화면 <b>"30분 사용" / "1시간 사용"</b> 버튼으로
쓸 수 있어요. 사용한 만큼 그날 퇴근시간이 앞당겨집니다.</p>
<h3>주말·공휴일 근무</h3>
<p>주말 또는 등록된 공휴일에 일한 시간은
<b>모든 시간이 연장근무로 적립</b>됩니다.</p>
""",
'help.html.leave': """
<h2>🌴 연차·반차 관리</h2>
<h3>연차 잔액 자동 계산</h3>
<p>잔액 = <b>연간 연차</b> (프로그램 외 사용분 + 프로그램에서 기록된 사용분)</p>
<h3>반차·반반차 지원</h3>
<ul>
<li><b>1.0일</b> — 종일 연차</li>
<li><b>0.5일</b> — 반차 (4시간)</li>
<li><b>0.25일</b> — 반반차 (2시간)</li>
</ul>
<h3>단축근무자의 연차 환산</h3>
<p>1일 연차의 시간 길이는 설정의 <b>하루 기본 근무</b>를 따릅니다.
예: 7시간 30분 근무자는 1일 연차가 7시간 30분(=450분)으로 환산됩니다.</p>
""",
'help.html.break': """
<h2>🚪 외출 / 저녁시간</h2>
<h3>외출 (잠깐 자리 비움)</h3>
<p>병원, 잠깐 외근 등으로 자리를 비울 때 <b>외출 시작 → 복귀</b> 버튼으로
시간을 추적하세요.</p>
<h3>화면 잠금 자동 외출</h3>
<p>설정에서 <b>"화면 잠금 시 자동 외출/복귀"</b>를 켜면 PC 잠금 시 자동으로
외출이 시작되고, 풀리면 복귀로 처리됩니다.</p>
<h3>저녁시간</h3>
<p>야근하면서 저녁을 먹는다면 <b>저녁시간 추가</b> 버튼을 눌러주세요.</p>
""",
'help.html.faq': """
<h2>❓ 자주 묻는 질문</h2>
<h3>Q. 출근 시간이 잘못 잡혔어요</h3>
<p>메인 화면 출근 시각 옆 <b>편집(연필)</b> 아이콘으로 수정할 수 있어요.</p>
<h3>Q. 단축근무 7시간 30분으로 설정하고 싶어요</h3>
<p>설정 → 근무 시간 → 근무 패턴에서 프리셋을 선택하거나 시·분을 직접 입력하세요.</p>
<h3>Q. 데이터는 어디에 저장되나요?</h3>
<p>실행 폴더의 <code>database.db</code> (SQLite). 자동 백업은
<code>~/.clockout_backups/</code>에 1일 1회 회전됩니다.</p>
""",
},
'en': {
'help.html.intro': """
<h2>👋 Welcome!</h2>
<p><b>Clock-out Time Calculator</b> is a desktop app that organizes your daily work
— from auto-detecting clock-in time to banking and using overtime.</p>
<h3>Basic Flow</h3>
<ol>
<li><b>Clock in</b> — System boot time is auto-recorded as your clock-in.</li>
<li><b>Working</b> — Remaining time updates every second on the main screen.</li>
<li><b>Lunch/Dinner</b> — Press the meal buttons to extend work time.</li>
<li><b>Break</b> — Track time away with "Start Break / Return" buttons.</li>
<li><b>Clock out</b> — Press the button; overtime is banked in 30-min units.</li>
</ol>
<h3>First-Time Setup</h3>
<ul>
<li><b>Settings → Work Time</b>: pick your pattern from presets
(8h / 7h30m / 6h / 4h half-day) or enter custom values.</li>
<li><b>Settings → Leave</b>: set your annual leave days.</li>
<li><b>Admin rights</b> may be required for boot-time auto-detection.</li>
</ul>
""",
'help.html.work_hours': """
<h2>🕘 Work Time Settings</h2>
<h3>Supports Standard / Reduced / Part-time</h3>
<ul>
<li><b>Standard 8h</b> — 60-min lunch (default)</li>
<li><b>Reduced 7h30m</b> — 30-min lunch</li>
<li><b>Reduced 7h</b> — 60-min lunch</li>
<li><b>Reduced 6h</b> — 30-min lunch</li>
<li><b>Half-day 4h</b> — no lunch</li>
<li><b>Custom</b> — direct input (5-min granularity)</li>
</ul>
<h3>💡 For Reduced-Hours Users</h3>
<p>Example: 7h30m daily + 30-min lunch</p>
<ol>
<li>Settings → Work Time → pick <i>"Reduced 7h30m (30-min lunch)"</i> preset</li>
<li>Or enter directly: 7h 30min daily, 30min lunch</li>
<li>Click <b>Save</b> — main screen recalculates immediately.</li>
</ol>
<h3>Auto Lunch</h3>
<p>Enable "Auto Apply" in settings to automatically turn on lunch
after 4 hours from clock-in.</p>
""",
'help.html.overtime': """
<h2>🏦 30-Minute Overtime Banking</h2>
<h3>Banking Rule</h3>
<p>Time worked past your scheduled clock-out is <b>truncated to 30-min units</b>.</p>
<ul>
<li>1h35m worked → <b>1h30m</b> banked</li>
<li>55min worked → <b>30min</b> banked</li>
<li>29min worked → <b>0min</b> banked</li>
</ul>
<h3>Using Banked Overtime</h3>
<p>Use buttons on the main screen to consume banked overtime.
Each unit lets you clock out earlier on a chosen day.</p>
<h3>Weekend/Holiday Work</h3>
<p>All hours worked on weekends or registered holidays are
<b>banked entirely as overtime</b>.</p>
""",
'help.html.leave': """
<h2>🌴 Annual Leave Management</h2>
<h3>Auto Balance Calculation</h3>
<p>Balance = annual leave (pre-program usage + recorded usage)</p>
<h3>Half / Quarter Day</h3>
<ul>
<li><b>1.0 day</b> — full leave</li>
<li><b>0.5 day</b> — half (4h)</li>
<li><b>0.25 day</b> — quarter (2h)</li>
</ul>
<h3>Reduced-Hours Conversion</h3>
<p>1 leave day equals your configured daily work time.
Example: 7h30m worker → 1 day = 7h30m (450 min).</p>
""",
'help.html.break': """
<h2>🚪 Break / Dinner</h2>
<h3>Break (Briefly Away)</h3>
<p>For short absences (medical, errands), use <b>Start Break / Return</b>.</p>
<h3>Auto Break on Screen Lock</h3>
<p>Enable "Auto break on screen lock" in settings — when the PC locks,
a break starts automatically; on unlock, you return.</p>
<h3>Dinner</h3>
<p>For overtime with dinner, press <b>Add Dinner</b>.</p>
""",
'help.html.faq': """
<h2>❓ FAQ</h2>
<h3>Q. Clock-in time is wrong</h3>
<p>Click the pencil icon next to clock-in time on the main screen to edit.</p>
<h3>Q. How to set 7h30m work day?</h3>
<p>Settings → Work Time → pick the preset or enter hours/minutes directly.</p>
<h3>Q. Where is data stored?</h3>
<p><code>database.db</code> in the program folder (SQLite). Daily auto-backups
rotate in <code>~/.clockout_backups/</code>.</p>
""",
},
}
def set_language(lang: str) -> None:
global _current_lang
if lang in _DICT:
_current_lang = lang
def get_language() -> str:
return _current_lang
def tr(key: str, **kwargs) -> str:
"""번역 조회. 키 미존재 시 ko 폴백 → 키 그대로."""
table = _DICT.get(_current_lang, _DICT['ko'])
text = table.get(key) or _DICT['ko'].get(key) or key
if kwargs:
try:
return text.format(**kwargs)
except (KeyError, IndexError):
return text
return text
def tr_html(key: str) -> str:
"""HTML 콘텐츠 조회 (HelpView용)."""
table = _HELP_HTML.get(_current_lang, _HELP_HTML['ko'])
return table.get(key) or _HELP_HTML['ko'].get(key) or f"<p>missing: {key}</p>"
def available_languages() -> list:
return list(_DICT.keys())
def language_label(code: str) -> str:
return {'ko': '한국어', 'en': 'English'}.get(code, code)