AtCoderStudyBooster 0.3.3__py3-none-any.whl → 0.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
atcdr/util/i18n.py ADDED
@@ -0,0 +1,317 @@
1
+ import locale
2
+ from typing import Dict, Optional
3
+
4
+
5
+ class I18n:
6
+ def __init__(self):
7
+ self._lang: Optional[str] = None
8
+ self._messages: Dict[str, Dict[str, str]] = {'ja': {}, 'en': {}}
9
+ self._load_messages()
10
+ self._detect_language()
11
+
12
+ def _detect_language(self) -> None:
13
+ try:
14
+ system_locale = locale.getdefaultlocale()[0]
15
+ if system_locale and system_locale.startswith('ja'):
16
+ self._lang = 'ja'
17
+ else:
18
+ self._lang = 'en'
19
+ except Exception:
20
+ self._lang = 'en'
21
+
22
+ def _load_messages(self) -> None:
23
+ self._messages = {
24
+ 'ja': {
25
+ # download.py
26
+ 'retry_problem': '再試行します。{}',
27
+ 'redirect_occurred': 'リダイレクトが発生しました。{}',
28
+ 'problem_not_found': '問題が見つかりません。{}',
29
+ 'server_error': 'サーバーエラーが発生しました。{}',
30
+ 'html_fetch_failed': '{}に対応するHTMLファイルを取得できませんでした。',
31
+ 'save_failed': '{}の保存に失敗しました',
32
+ 'file_saved': 'ファイルを保存しました: {}',
33
+ 'solve_contest_problems': 'コンテストの問題を解きたい',
34
+ 'download_one_problem': '1問だけダウンロードする',
35
+ 'exit': '終了する',
36
+ 'download_atcoder_html': 'AtCoderの問題のHTMLファイルをダウンロードします',
37
+ 'navigate_with_arrows': '十字キーで移動,[enter]で実行',
38
+ 'input_contest_name': 'コンテスト名を入力してください (例: abc012, abs, typical90)',
39
+ 'which_problem_download': 'どの問題をダウンロードしますか?',
40
+ 'exiting': '終了します',
41
+ 'invalid_selection': '無効な選択です',
42
+ 'specify_contest_name': 'コンテスト名を指定してください',
43
+ 'invalid_download_args': 'ダウンロードの引数が正しくありません',
44
+ # test.py
45
+ 'runner_not_found': '{}の適切な言語のランナーが見つかりませんでした.',
46
+ 'test_of': '{}のテスト \n',
47
+ 'compile_time': 'コンパイルにかかった時間: [not italic cyan]{}[/] ms[/]',
48
+ 'compiler_message': 'コンパイラーのメッセージ',
49
+ 'status': 'ステータス ',
50
+ 'execution_time': '実行時間 [cyan]{}[/cyan] ms',
51
+ 'input': '入力',
52
+ 'output': '出力',
53
+ 'expected_output': '正解の出力',
54
+ 'test_in_progress': 'テスト進行中',
55
+ 'test_completed': 'テスト完了',
56
+ 'problem_file_not_found': '問題のファイルが見つかりません。\n問題のファイルが存在するディレクトリーに移動してから実行してください。',
57
+ # markdown.py
58
+ 'markdown_created': 'Markdownファイルを作成しました.',
59
+ # login.py
60
+ 'already_logged_in': 'すでにログインしています. ',
61
+ 'username': 'ユーザー名: ',
62
+ 'password': 'パスワード: ',
63
+ 'solve_captcha': 'キャプチャー認証を解決してください',
64
+ 'logging_in': 'ログインします',
65
+ 'waiting_login_result': 'ログインの結果の待機中...',
66
+ 'login_success': 'ログイン成功!',
67
+ 'error': 'エラー: {}',
68
+ # submit.py
69
+ 'select_implementation': '以下の一覧から{}の実装/コンパイラーを選択してください',
70
+ 'submission_failed': '提出に失敗しました',
71
+ 'submission_id_not_found': '提出IDが取得できませんでした',
72
+ 'submission_success': '提出に成功しました!',
73
+ 'waiting_judge': 'ジャッジ待機中',
74
+ 'judge_timeout': '15秒待ってもジャッジが開始されませんでした',
75
+ 'judging': 'ジャッジ中',
76
+ 'judge_completed': 'ジャッジ完了',
77
+ 'not_logged_in': 'ログインしていません.',
78
+ 'login_failed': 'ログインに失敗しました.',
79
+ 'sample_not_ac': 'サンプルケースが AC していないので提出できません',
80
+ 'logout_success': 'ログアウトしました.',
81
+ 'submission_details': '提出ID: {}, URL: {}',
82
+ # open.py
83
+ 'not_found': 'が見つかりません',
84
+ 'url_opened': 'URLを開きました',
85
+ 'url_not_found_in': 'にURLが見つかりませんでした',
86
+ # generate.py
87
+ 'generating_code': 'コード生成中 (by {})',
88
+ 'code_generation_success': 'コードの生成に成功しました. ',
89
+ 'code_by_model': '{}による{}コード',
90
+ 'code_saved': '{} の出力したコードを保存しました:{}',
91
+ 'generating_template': '{}のテンプレートを生成しています...',
92
+ 'template_created': 'テンプレートファイルを作成 :{}',
93
+ 'nth_code_generation': '{}回目のコード生成中 (by {})',
94
+ 'regenerating_with_prompt': '次のプロンプトを{}に与え,再生成します',
95
+ 'code_generation_success_file': 'コードの生成に成功しました!:{}',
96
+ 'testing_generated_code': '{}が生成したコードをテスト中',
97
+ 'test_success': 'コードのテストに成功!',
98
+ 'test_failed': 'コードのテストに失敗!',
99
+ 'log_saved': '{}の出力のログを保存しました:{}',
100
+ # util/gpt.py
101
+ 'api_key_validation_failed': '環境変数に設定されているAPIキーの検証に失敗しました ',
102
+ 'get_api_key_prompt': 'https://platform.openai.com/api-keys からchatGPTのAPIキーを入手しましょう。\nAPIキー入力してください: ',
103
+ 'api_key_test_success': 'APIキーのテストに成功しました。',
104
+ 'save_api_key_prompt': '以下, ~/.zshrcにAPIキーを保存しますか? [y/n]',
105
+ 'api_key_saved': 'APIキーを {} に保存しました。次回シェル起動時に読み込まれます。',
106
+ 'api_key_required': 'コード生成にはAPIキーが必要です。',
107
+ 'api_key_validation_error': 'APIキーの検証に失敗しました。',
108
+ 'response_format_error': 'Error:レスポンスの形式が正しくありません. \n',
109
+ # util/fileops.py
110
+ 'multiple_files_found': '複数のファイルが見つかりました.ファイルを選択してください:',
111
+ 'target_file_not_found': '対象ファイルが見つかりません。',
112
+ 'file_not_selected': 'ファイルが選択されませんでした。',
113
+ # util/problem.py and util/parse.py
114
+ 'name_required': 'nameは必須です',
115
+ 'language_not_supported': '言語は {} に対応していません',
116
+ 'form_not_found': '問題ページにフォームが存在しません',
117
+ 'problem_table_not_found': '問題のテーブルが見つかりませんでした.',
118
+ 'tbody_not_found': 'tbodyが見つかりませんでした.',
119
+ # util/session.py
120
+ 'response_info': 'レスポンス情報',
121
+ 'item': '項目',
122
+ 'content': '内容',
123
+ 'status_code': 'ステータスコード',
124
+ 'reason': '理由',
125
+ 'response_headers': 'レスポンスヘッダー',
126
+ 'key': 'キー',
127
+ 'value': '値',
128
+ 'redirect_history': 'リダイレクト履歴',
129
+ 'step': 'ステップ',
130
+ 'response_body': 'レスポンスボディ',
131
+ 'hello_user': 'こんにちは![cyan]{}[/] さん',
132
+ 'session_check_error': 'セッションチェック中にエラーが発生しました: {}',
133
+ # Click command descriptions
134
+ 'cmd_download': 'AtCoder の問題をダウンロード',
135
+ 'cmd_test': 'テストを実行',
136
+ 'cmd_submit': 'ソースを提出',
137
+ 'cmd_generate': 'コードを生成',
138
+ 'cmd_login': 'AtCoderへログイン',
139
+ 'cmd_logout': 'AtCoderへログアウト',
140
+ 'cmd_markdown': 'Markdown形式で問題を表示します',
141
+ 'cmd_open': 'HTMLファイルを開く',
142
+ # Click option descriptions
143
+ 'opt_no_test': 'テストをスキップ',
144
+ 'opt_no_feedback': 'フィードバックをスキップ',
145
+ 'opt_lang': '出力する言語を指定',
146
+ 'opt_save': '変換結果をファイルに保存',
147
+ 'opt_output_lang': '出力するプログラミング言語',
148
+ 'opt_model': '使用するGPTモデル',
149
+ 'opt_without_test': 'テストケースを省略して生成',
150
+ 'opt_template': 'テンプレートを生成',
151
+ },
152
+ 'en': {
153
+ # download.py
154
+ 'retry_problem': 'Retrying... {}',
155
+ 'redirect_occurred': 'Redirect occurred. {}',
156
+ 'problem_not_found': 'Problem not found. {}',
157
+ 'server_error': 'Server error occurred. {}',
158
+ 'html_fetch_failed': 'Failed to fetch HTML file for {}.',
159
+ 'save_failed': 'Failed to save {}',
160
+ 'file_saved': 'File saved: {}',
161
+ 'solve_contest_problems': 'Solve contest problems',
162
+ 'download_one_problem': 'Download one problem',
163
+ 'exit': 'Exit',
164
+ 'download_atcoder_html': 'Download AtCoder problem HTML files',
165
+ 'navigate_with_arrows': 'Use arrow keys to navigate, [enter] to execute',
166
+ 'input_contest_name': 'Enter contest name (e.g., abc012, abs, typical90)',
167
+ 'which_problem_download': 'Which problem to download?',
168
+ 'exiting': 'Exiting',
169
+ 'invalid_selection': 'Invalid selection',
170
+ 'specify_contest_name': 'Please specify contest name',
171
+ 'invalid_download_args': 'Invalid download arguments',
172
+ # test.py
173
+ 'runner_not_found': 'Appropriate language runner for {} not found.',
174
+ 'test_of': 'Testing {} \n',
175
+ 'compile_time': 'Compile time: [not italic cyan]{}[/] ms[/]',
176
+ 'compiler_message': 'Compiler message',
177
+ 'status': 'Status ',
178
+ 'execution_time': 'Execution time [cyan]{}[/cyan] ms',
179
+ 'input': 'Input',
180
+ 'output': 'Output',
181
+ 'expected_output': 'Expected output',
182
+ 'test_in_progress': 'Testing in progress',
183
+ 'test_completed': 'Test completed',
184
+ 'problem_file_not_found': 'Problem file not found.\nPlease navigate to the directory containing the problem file.',
185
+ # markdown.py
186
+ 'markdown_created': 'Markdown file created.',
187
+ # login.py
188
+ 'already_logged_in': 'Already logged in. ',
189
+ 'username': 'Username: ',
190
+ 'password': 'Password: ',
191
+ 'solve_captcha': 'Please solve the captcha',
192
+ 'logging_in': 'Logging in',
193
+ 'waiting_login_result': 'Waiting for login result...',
194
+ 'login_success': 'Login successful!',
195
+ 'error': 'Error: {}',
196
+ # submit.py
197
+ 'select_implementation': 'Select {} implementation/compiler from the list below',
198
+ 'submission_failed': 'Submission failed',
199
+ 'submission_id_not_found': 'Submission ID not found',
200
+ 'submission_success': 'Submission successful!',
201
+ 'waiting_judge': 'Waiting for judge',
202
+ 'judge_timeout': 'Judge did not start after 15 seconds',
203
+ 'judging': 'Judging',
204
+ 'judge_completed': 'Judge completed',
205
+ 'not_logged_in': 'Not logged in.',
206
+ 'login_failed': 'Login failed.',
207
+ 'sample_not_ac': 'Cannot submit because sample cases are not AC',
208
+ 'logout_success': 'Logged out successfully.',
209
+ 'submission_details': 'Submission ID: {}, URL: {}',
210
+ # open.py
211
+ 'not_found': ' not found',
212
+ 'url_opened': 'URL opened',
213
+ 'url_not_found_in': 'URL not found in ',
214
+ # generate.py
215
+ 'generating_code': 'Generating code (by {})',
216
+ 'code_generation_success': 'Code generation successful. ',
217
+ 'code_by_model': '{} code by {}',
218
+ 'code_saved': 'Code generated by {} saved: {}',
219
+ 'generating_template': 'Generating {} template...',
220
+ 'template_created': 'Template file created: {}',
221
+ 'nth_code_generation': 'Code generation attempt {} (by {})',
222
+ 'regenerating_with_prompt': 'Regenerating with the following prompt for {}',
223
+ 'code_generation_success_file': 'Code generation successful: {}',
224
+ 'testing_generated_code': 'Testing code generated by {}',
225
+ 'test_success': 'Code test successful!',
226
+ 'test_failed': 'Code test failed!',
227
+ 'log_saved': 'Log of {} output saved: {}',
228
+ # util/gpt.py
229
+ 'api_key_validation_failed': 'API key validation failed ',
230
+ 'get_api_key_prompt': 'Get your ChatGPT API key from https://platform.openai.com/api-keys\nEnter API key: ',
231
+ 'api_key_test_success': 'API key test successful.',
232
+ 'save_api_key_prompt': 'Save API key to ~/.zshrc? [y/n]',
233
+ 'api_key_saved': 'API key saved to {}. Will be loaded on next shell startup.',
234
+ 'api_key_required': 'API key required for code generation.',
235
+ 'api_key_validation_error': 'API key validation failed.',
236
+ 'response_format_error': 'Error: Response format is invalid. \n',
237
+ # util/fileops.py
238
+ 'multiple_files_found': 'Multiple files found. Please select a file:',
239
+ 'target_file_not_found': 'Target file not found.',
240
+ 'file_not_selected': 'No file selected.',
241
+ # util/problem.py and util/parse.py
242
+ 'name_required': 'Name is required',
243
+ 'language_not_supported': 'Language {} is not supported',
244
+ 'form_not_found': 'Form not found on problem page',
245
+ 'problem_table_not_found': 'Problem table not found.',
246
+ 'tbody_not_found': 'tbody not found.',
247
+ # util/session.py
248
+ 'response_info': 'Response Information',
249
+ 'item': 'Item',
250
+ 'content': 'Content',
251
+ 'status_code': 'Status Code',
252
+ 'reason': 'Reason',
253
+ 'response_headers': 'Response Headers',
254
+ 'key': 'Key',
255
+ 'value': 'Value',
256
+ 'redirect_history': 'Redirect History',
257
+ 'step': 'Step',
258
+ 'response_body': 'Response Body',
259
+ 'hello_user': 'Hello [cyan]{}[/]!',
260
+ 'session_check_error': 'Error occurred during session check: {}',
261
+ # Click command descriptions
262
+ 'cmd_download': 'Download AtCoder problems',
263
+ 'cmd_test': 'Run tests',
264
+ 'cmd_submit': 'Submit source code',
265
+ 'cmd_generate': 'Generate code',
266
+ 'cmd_login': 'Login to AtCoder',
267
+ 'cmd_logout': 'Logout from AtCoder',
268
+ 'cmd_markdown': 'Display problem in Markdown format',
269
+ 'cmd_open': 'Open HTML file',
270
+ # Click option descriptions
271
+ 'opt_no_test': 'Skip testing',
272
+ 'opt_no_feedback': 'Skip feedback',
273
+ 'opt_lang': 'Specify output language',
274
+ 'opt_save': 'Save conversion result to file',
275
+ 'opt_output_lang': 'Specify output programming language',
276
+ 'opt_model': 'GPT model to use',
277
+ 'opt_without_test': 'Generate without test cases',
278
+ 'opt_template': 'Generate template',
279
+ },
280
+ }
281
+
282
+ def get(self, key: str, *args) -> str:
283
+ """
284
+ メッセージを取得する
285
+ Args:
286
+ key: メッセージキー
287
+ *args: フォーマット引数
288
+ Returns:
289
+ フォーマット済みのメッセージ
290
+ """
291
+ message = self._messages.get(self._lang or 'en', {}).get(key, key)
292
+ if args:
293
+ try:
294
+ return message.format(*args)
295
+ except Exception:
296
+ return message
297
+ return message
298
+
299
+ def set_language(self, lang: str) -> None:
300
+ """言語を明示的に設定する"""
301
+ if lang in self._messages:
302
+ self._lang = lang
303
+
304
+ @property
305
+ def language(self) -> str:
306
+ """現在の言語を取得する"""
307
+ return self._lang or 'en'
308
+
309
+
310
+ # シングルトンインスタンス
311
+ i18n = I18n()
312
+
313
+
314
+ # 便利な関数
315
+ def _(key: str, *args) -> str:
316
+ """i18n.get()のショートカット"""
317
+ return i18n.get(key, *args)
atcdr/util/parse.py CHANGED
@@ -5,6 +5,8 @@ from bs4 import BeautifulSoup as bs
5
5
  from bs4 import Tag
6
6
  from markdownify import MarkdownConverter
7
7
 
8
+ from atcdr.util.i18n import _, i18n
9
+
8
10
 
9
11
  class HTML:
10
12
  def __init__(self, html: str) -> None:
@@ -68,13 +70,16 @@ class ProblemForm(Tag):
68
70
 
69
71
 
70
72
  class ProblemHTML(HTML):
71
- def repair_me(self) -> None:
73
+ def repair_me(self, lang: Optional[str] = None) -> None:
72
74
  html = self.html.replace('//img.atcoder.jp', 'https://img.atcoder.jp')
73
- html = html.replace(
74
- '<meta http-equiv="Content-Language" content="en">',
75
- '<meta http-equiv="Content-Language" content="ja">',
76
- )
77
- html = html.replace('LANG = "en"', 'LANG="ja"')
75
+ # 言語パラメータまたはi18nの設定に基づいて言語を決定
76
+ target_lang = lang or i18n.language
77
+ if target_lang == 'ja':
78
+ html = html.replace(
79
+ '<meta http-equiv="Content-Language" content="en">',
80
+ '<meta http-equiv="Content-Language" content="ja">',
81
+ )
82
+ html = html.replace('LANG = "en"', 'LANG="ja"')
78
83
  self.soup = bs(html, 'html.parser')
79
84
 
80
85
  def abstract_problem_part(self, lang: str) -> Optional[Tag]:
@@ -87,7 +92,7 @@ class ProblemHTML(HTML):
87
92
  elif lang == 'en':
88
93
  lang_class = 'lang-en'
89
94
  else:
90
- raise ValueError(f'言語は {lang} に対応していません')
95
+ raise ValueError(_('language_not_supported', lang))
91
96
  span = task_statement.find('span', {'class': lang_class})
92
97
  return span
93
98
 
@@ -105,7 +110,7 @@ class ProblemHTML(HTML):
105
110
  def load_labeled_testcase(self) -> List:
106
111
  from atcdr.test import LabeledTestCase, TestCase
107
112
 
108
- problem_part = self.abstract_problem_part('en')
113
+ problem_part = self.abstract_problem_part(i18n.language)
109
114
  if problem_part is None:
110
115
  return []
111
116
 
@@ -146,7 +151,7 @@ class ProblemHTML(HTML):
146
151
  def form(self) -> ProblemForm:
147
152
  form = self.soup.find('form', class_='form-code-submit')
148
153
  if not isinstance(form, Tag):
149
- raise ValueError('問題ページにフォームが存在しません')
154
+ raise ValueError(_('form_not_found'))
150
155
  form.__class__ = ProblemForm
151
156
  return form
152
157
 
@@ -178,12 +183,12 @@ def get_problem_urls_from_tasks(html_content: str) -> list[tuple[str, str]]:
178
183
  soup = bs(html_content, 'html.parser')
179
184
  table = soup.find('table')
180
185
  if not table:
181
- raise ValueError('問題のテーブルが見つかりませんでした.')
186
+ raise ValueError(_('problem_table_not_found'))
182
187
 
183
188
  # tbodyタグを見つける
184
189
  tbody = table.find('tbody')
185
190
  if not tbody:
186
- raise ValueError('tbodyが見つかりませんでした.')
191
+ raise ValueError(_('tbody_not_found'))
187
192
 
188
193
  # tbody内の1列目のaタグのリンクと中身を取得
189
194
  links = []
atcdr/util/problem.py CHANGED
@@ -1,94 +1,48 @@
1
- from typing import List, Optional
1
+ import time
2
+ from dataclasses import dataclass
2
3
 
3
4
  import requests
4
5
 
6
+ from atcdr.util.i18n import _
5
7
  from atcdr.util.parse import get_problem_urls_from_tasks
6
8
 
7
9
 
8
10
  class Contest:
9
- def __init__(self, name: str, number: Optional[int] = None):
11
+ def __init__(self, name: str, session: requests.Session):
10
12
  if not name:
11
- raise ValueError('nameは必須です')
12
- self._name = name
13
- self._number = number
14
- if number and number > 0:
15
- self._contest = f'{name}{number:03}'
16
- else:
17
- self._contest = name
18
-
19
- @property
20
- def contest(self) -> str:
21
- return self._contest
22
-
23
- @property
24
- def number(self) -> Optional[int]:
25
- return self._number
26
-
27
- @property
28
- def url(self) -> str:
29
- return f'https://atcoder.jp/contests/{self.contest}/tasks'
30
-
31
- def __str__(self) -> str:
32
- return f'{self.contest}'
33
-
34
- def __repr__(self) -> str:
35
- return f'Contest(name={self._name}, number={self._number})'
36
-
37
- def problems(self, session: Optional[requests.Session] = None) -> List['Problem']:
38
- session = session or requests.Session()
39
- response = session.get(self.url)
40
-
41
- if response.status_code != 200:
42
- return []
43
-
44
- return [
45
- Problem(self, label=label, url=url)
13
+ raise ValueError(_('name_required'))
14
+ self.name = name
15
+
16
+ self.url = f'https://atcoder.jp/contests/{name}/tasks'
17
+ retry_attempts = 2
18
+ retry_wait = 0.30
19
+ for attempt in range(retry_attempts):
20
+ response = session.get(self.url)
21
+ if response.ok:
22
+ break
23
+ else:
24
+ time.sleep(retry_wait)
25
+
26
+ self.problems = [
27
+ Problem(url=url, contest=self, label=label)
46
28
  for label, url in get_problem_urls_from_tasks(response.text)
47
29
  ]
48
30
 
49
-
50
- class Diff(str):
51
- def __new__(cls, diff: str) -> 'Diff':
52
- if isinstance(diff, str) and len(diff) == 1 and diff.isalpha():
53
- return super().__new__(cls, diff.upper())
54
- raise ValueError('diffは英大文字または小文字の1文字である必要があります')
31
+ def __str__(self) -> str:
32
+ return f'{self.name}'
55
33
 
56
34
  def __repr__(self) -> str:
57
- return f"Diff('{self}')"
35
+ return f'Contest(name={self.name})'
58
36
 
59
37
 
38
+ @dataclass
60
39
  class Problem:
61
- def __init__(
62
- self,
63
- contest: Contest,
64
- difficulty: Optional[Diff] = None,
65
- label: Optional[str] = None,
66
- url: Optional[str] = None,
67
- ):
68
- self._contest = contest
69
- if difficulty:
70
- self._label = difficulty.upper()
71
- self._url = contest.url + f'/{contest}_{difficulty.lower()}'
72
- elif label and url:
73
- self._label = label
74
- self._url = url
75
- else:
76
- raise ValueError('labelとurlは両方必須かdifficultyが必要です')
77
-
78
- @property
79
- def contest(self) -> Contest:
80
- return self._contest
40
+ url: str
41
+ contest: Contest
42
+ label: str
81
43
 
82
- @property
83
- def label(self) -> str:
84
- return self._label
85
-
86
- @property
87
- def url(self) -> str:
88
- return self._url
44
+ def __str__(self) -> str:
45
+ return f'{self.label} - {self.contest.name}'
89
46
 
90
47
  def __repr__(self) -> str:
91
- return f'Problem(contest={self.contest}, label={self.label}, url={self.url})'
92
-
93
- def __str__(self) -> str:
94
- return f'{self.contest} {self.label}'
48
+ return f'Problem(url={self.url}, contest={self.contest}, label={self.label})'
atcdr/util/session.py CHANGED
@@ -9,6 +9,7 @@ from rich.panel import Panel
9
9
  from rich.syntax import Syntax
10
10
  from rich.table import Table
11
11
 
12
+ from atcdr.util.i18n import _
12
13
  from atcdr.util.parse import get_username_from_html
13
14
 
14
15
  COOKIE_PATH = os.path.join(os.path.expanduser('~'), '.cache', 'atcdr', 'session.json')
@@ -19,18 +20,18 @@ def print_rich_response(
19
20
  response: requests.Response, body_range: tuple = (0, 24)
20
21
  ) -> None:
21
22
  # レスポンス情報をテーブル形式で表示
22
- info_table = Table(title='レスポンス情報')
23
- info_table.add_column('項目', justify='left', style='cyan', no_wrap=True)
24
- info_table.add_column('内容', justify='left', style='magenta')
23
+ info_table = Table(title=_('response_info'))
24
+ info_table.add_column(_('item'), justify='left', style='cyan', no_wrap=True)
25
+ info_table.add_column(_('content'), justify='left', style='magenta')
25
26
  info_table.add_row('URL', response.url)
26
- info_table.add_row('ステータスコード', str(response.status_code))
27
- info_table.add_row('理由', response.reason)
27
+ info_table.add_row(_('status_code'), str(response.status_code))
28
+ info_table.add_row(_('reason'), response.reason)
28
29
  info_table = Align.center(info_table)
29
30
 
30
31
  # ヘッダー情報をテーブル形式で表示
31
- header_table = Table(title='レスポンスヘッダー')
32
- header_table.add_column('キー', style='cyan', no_wrap=True)
33
- header_table.add_column('', style='magenta', overflow='fold')
32
+ header_table = Table(title=_('response_headers'))
33
+ header_table.add_column(_('key'), style='cyan', no_wrap=True)
34
+ header_table.add_column(_('value'), style='magenta', overflow='fold')
34
35
  for key, value in response.headers.items():
35
36
  value = unquote(value)
36
37
  header_table.add_row(key, value)
@@ -39,9 +40,9 @@ def print_rich_response(
39
40
  # リダイレクトの歴史
40
41
  redirect_table = None
41
42
  if response.history:
42
- redirect_table = Table(title='リダイレクト履歴')
43
- redirect_table.add_column('ステップ', style='cyan')
44
- redirect_table.add_column('ステータスコード', style='magenta')
43
+ redirect_table = Table(title=_('redirect_history'))
44
+ redirect_table.add_column(_('step'), style='cyan')
45
+ redirect_table.add_column(_('status_code'), style='magenta')
45
46
  redirect_table.add_column('URL', style='green')
46
47
  for i, redirect_response in enumerate(response.history):
47
48
  redirect_table.add_row(
@@ -80,7 +81,7 @@ def print_rich_response(
80
81
  if response.text
81
82
  else None
82
83
  )
83
- body_panel = Panel(body, title='レスポンスボディ') if body else None
84
+ body_panel = Panel(body, title=_('response_body')) if body else None
84
85
 
85
86
  print(info_table)
86
87
  print(header_table)
@@ -102,7 +103,7 @@ def load_session() -> requests.Session:
102
103
  response = session.get(ATCODER_URL)
103
104
  username = get_username_from_html(response.text)
104
105
  if username:
105
- print(f'こんにちは![cyan]{username}[/] さん')
106
+ print(_('hello_user', username))
106
107
  return session
107
108
  else:
108
109
  return requests.Session()
@@ -131,7 +132,7 @@ def validate_session(session: requests.Session) -> bool:
131
132
  return False
132
133
  return False
133
134
  except requests.RequestException as e:
134
- print(f'[red][-][/] セッションチェック中にエラーが発生しました: {e}')
135
+ print('[red][-][/] ' + _('session_check_error', e))
135
136
  return False
136
137
 
137
138