Some checks failed
CI / test (push) Has been cancelled
핵심 기능: - 단축근무·표준·반일 등 다양한 근무 패턴 (5개 프리셋 + 사용자 정의) - Windows 이벤트 뷰어 자동 출퇴근 감지 - 30분 단위 연장근무 적립/사용 시스템 - 1.0/0.5/0.25일 연차·반차·반반차 - 자동 점심·저녁·외출·자동 백업·화면 잠금 자동 외출 - 한국 공휴일 자동 등록 (음력 포함, holidays 패키지) - matplotlib 차트 기반 주간/월간/패턴 통계 - 미니 위젯 + 시스템 트레이 통합 - 한국어/English i18n - 자가 업데이트 (updater.exe + Gitea Releases) 아키텍처: - core/ (db, time_calculator, notifier, i18n, version, settings_keys) - ui/ (main_window + 9 dialogs + 3 controllers) - utils/ (backup, lock_detector, debug_log, updater_client, time_format) - tests/ (66 pytest 단위) + 통합/i18n GUI 검증 CI/CD: - .gitea/workflows/ci.yml: push 시 pytest + 통합 테스트 - .gitea/workflows/release.yml: v* 태그 push 시 두 .exe 자동 빌드 + Releases 첨부 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
569 lines
24 KiB
Python
569 lines
24 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.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': '오후',
|
||
|
||
# === 알림 ===
|
||
'notif.clock_out_soon.title': '⏰ 퇴근 시간 임박',
|
||
'notif.clock_out_soon.body': '퇴근까지 {minutes}분 남았습니다.\n마무리 준비를 시작하세요!',
|
||
'notif.lunch_reminder.title': '🍱 점심시간 등록',
|
||
'notif.lunch_reminder.body': '점심시간을 등록하지 않으셨네요.\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법정 근로시간을 초과했습니다!',
|
||
|
||
# === 메시지박스 ===
|
||
'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': '데이터 없음',
|
||
|
||
# === 트레이 ===
|
||
'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': '아직 충분한 데이터가 없습니다.',
|
||
|
||
# === 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': '❓ 자주 묻는 질문',
|
||
},
|
||
'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 Time',
|
||
'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',
|
||
|
||
# === 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.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!",
|
||
|
||
# === 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',
|
||
|
||
# === 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.',
|
||
|
||
# === 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',
|
||
},
|
||
}
|
||
|
||
|
||
# === 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)
|