2629 lines
153 KiB
Python
2629 lines
153 KiB
Python
"""
|
||
경량 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:00–09: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:00–06:59 once.',
|
||
'achieve.slot_in_07.name': '07:00 Clock-in',
|
||
'achieve.slot_in_07.desc': 'Clocked in during 07:00–07:59 once.',
|
||
'achieve.slot_in_08.name': '08:00 Clock-in',
|
||
'achieve.slot_in_08.desc': 'Clocked in during 08:00–08:59 once.',
|
||
'achieve.slot_in_10.name': '10:00 Clock-in',
|
||
'achieve.slot_in_10.desc': 'Clocked in during 10:00–10:59 (late / flexible).',
|
||
'achieve.slot_in_11.name': '11:00 Clock-in',
|
||
'achieve.slot_in_11.desc': 'Clocked in during 11:00–11:59 (self-deprecating).',
|
||
'achieve.slot_out_19.name': '19:00 Clock-out',
|
||
'achieve.slot_out_19.desc': 'Clocked out during 19:00–19:59 10 times (warning).',
|
||
'achieve.slot_out_20.name': '20:00 Clock-out',
|
||
'achieve.slot_out_20.desc': 'Clocked out during 20:00–20:59 10 times (warning).',
|
||
'achieve.slot_out_21.name': '21:00 Clock-out',
|
||
'achieve.slot_out_21.desc': 'Clocked out during 21:00–21:59 5 times (warning).',
|
||
'achieve.slot_out_22.name': '22:00 Clock-out',
|
||
'achieve.slot_out_22.desc': 'Clocked out during 22:00–22:59 once (warning).',
|
||
'achieve.slot_out_23.name': '23:00 Clock-out',
|
||
'achieve.slot_out_23.desc': 'Clocked out during 23:00–23: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)
|