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/logout.py CHANGED
@@ -2,21 +2,22 @@ import rich_click as click
2
2
  import webview
3
3
  from rich import print
4
4
 
5
+ from atcdr.util.i18n import _
5
6
  from atcdr.util.session import delete_session, load_session, validate_session
6
7
 
7
8
  ATCODER_LOGIN_URL = 'https://atcoder.jp/login'
8
9
 
9
10
 
10
- @click.command(short_help='AtCoderへログアウト')
11
+ @click.command(short_help=_('cmd_logout'), help=_('cmd_logout'))
11
12
  def logout() -> None:
12
13
  """AtCoderからログアウトします."""
13
14
  session = load_session()
14
15
  if not validate_session(session):
15
- print('[red][-][/] ログインしていません.')
16
+ print('[red][-][/] ' + _('not_logged_in'))
16
17
  return
17
18
 
18
19
  delete_session()
19
- print('[green][+][/] ログアウトしました.')
20
+ print('[green][+][/] ' + _('logout_success'))
20
21
 
21
22
  window = webview.create_window('AtCoder Logout', ATCODER_LOGIN_URL, hidden=True)
22
23
 
atcdr/markdown.py CHANGED
@@ -6,6 +6,7 @@ from rich.markdown import Markdown
6
6
 
7
7
  from atcdr.util.fileops import add_file_selector
8
8
  from atcdr.util.filetype import FILE_EXTENSIONS, Lang
9
+ from atcdr.util.i18n import _, i18n
9
10
  from atcdr.util.parse import ProblemHTML
10
11
 
11
12
 
@@ -19,7 +20,7 @@ def save_markdown(html_path: str, lang: str) -> None:
19
20
 
20
21
  with open(md_path, 'w', encoding='utf-8') as f:
21
22
  f.write(md)
22
- console.print('[green][+][/green] Markdownファイルを作成しました.')
23
+ console.print('[green][+][/green] ' + _('markdown_created'))
23
24
 
24
25
 
25
26
  def print_markdown(html_path: str, lang: str) -> None:
@@ -30,12 +31,15 @@ def print_markdown(html_path: str, lang: str) -> None:
30
31
  console.print(Markdown(md))
31
32
 
32
33
 
33
- @click.command(short_help='Markdown形式で問題を表示します')
34
+ @click.command(short_help=_('cmd_markdown'), help=_('cmd_markdown'))
34
35
  @add_file_selector('files', filetypes=[Lang.HTML])
35
- @click.option('--lang', default='ja', help='出力する言語を指定')
36
- @click.option('--save', is_flag=True, help='変換結果をファイルに保存')
36
+ @click.option('--lang', default=None, help=_('opt_lang'))
37
+ @click.option('--save', is_flag=True, help=_('opt_save'))
37
38
  def markdown(files, lang, save):
38
- """Markdown形式で問題を表示します"""
39
+ """問題をMarkdown形式で表示します"""
40
+ # langが指定されていない場合は現在のロケールを使用
41
+ if lang is None:
42
+ lang = i18n.language
39
43
  for path in files:
40
44
  if save:
41
45
  save_markdown(path, lang)
atcdr/open.py CHANGED
@@ -2,6 +2,7 @@ import webbrowser # noqa: I001
2
2
  from rich.panel import Panel
3
3
  from rich.console import Console
4
4
 
5
+ from atcdr.util.i18n import _
5
6
  from atcdr.util.filetype import Lang
6
7
  from atcdr.util.fileops import add_file_selector
7
8
  import rich_click as click
@@ -16,7 +17,7 @@ def open_html(file: str) -> None:
16
17
  except FileNotFoundError:
17
18
  console.print(
18
19
  Panel(
19
- f"{file}' [red]が見つかりません[/]",
20
+ f"{file}' [red]" + _('not_found') + '[/]',
20
21
  border_style='red',
21
22
  )
22
23
  )
@@ -27,20 +28,20 @@ def open_html(file: str) -> None:
27
28
  webbrowser.open_new_tab(url)
28
29
  console.print(
29
30
  Panel(
30
- f'[green]URLを開きました[/] {url}',
31
+ '[green]' + _('url_opened') + f'[/] {url}',
31
32
  border_style='green',
32
33
  )
33
34
  )
34
35
  else:
35
36
  console.print(
36
37
  Panel(
37
- f'{file} [yellow]にURLが見つかりませんでした[/]',
38
+ f'{file} [yellow]' + _('url_not_found_in') + '[/]',
38
39
  border_style='yellow',
39
40
  )
40
41
  )
41
42
 
42
43
 
43
- @click.command(short_help='HTMLファイルを開く')
44
+ @click.command(short_help=_('cmd_open'), help=_('cmd_open'))
44
45
  @add_file_selector('files', filetypes=[Lang.HTML])
45
46
  def open_files(files):
46
47
  """指定したHTMLファイルをブラウザで開きます。"""
atcdr/submit.py CHANGED
@@ -29,6 +29,7 @@ from atcdr.util.filetype import (
29
29
  lang2str,
30
30
  str2lang,
31
31
  )
32
+ from atcdr.util.i18n import _
32
33
  from atcdr.util.parse import ProblemHTML, get_submission_id
33
34
  from atcdr.util.session import load_session, validate_session
34
35
 
@@ -61,14 +62,14 @@ def choose_langid_interactively(lang_dict: dict, lang: Lang) -> int:
61
62
  options = [*filter(lambda option: option.lang == lang, options)]
62
63
 
63
64
  langid = q.select(
64
- message=f'以下の一覧から{lang2str(lang)}の実装/コンパイラーを選択してください',
65
+ message=_('select_implementation', lang2str(lang)),
65
66
  qmark='',
66
67
  pointer='❯❯❯',
67
68
  choices=[
68
69
  q.Choice(title=f'{option.display_name}', value=option.id)
69
70
  for option in options
70
71
  ],
71
- instruction='\n 十字キーで移動,[enter]で実行',
72
+ instruction='\n ' + _('navigate_with_arrows'),
72
73
  style=q.Style(
73
74
  [
74
75
  ('question', 'fg:#2196F3 bold'),
@@ -136,24 +137,24 @@ def post_source(source_path: str, url: str, session: requests.Session) -> Option
136
137
 
137
138
  window.events.loaded += on_loaded
138
139
 
139
- with Status('キャプチャー認証を解決してください', spinner='circleHalves'):
140
+ with Status(_('solve_captcha'), spinner='circleHalves'):
140
141
  webview.start(private_mode=False)
141
142
 
142
143
  if 'submit' in api.url:
143
- print('[red][-][/red] 提出に失敗しました')
144
+ print('[red][-][/red] ' + _('submission_failed'))
144
145
  return None
145
146
  elif 'submissions' in api.url:
146
147
  submission_id = get_submission_id(api.html)
147
148
  if not submission_id:
148
- print('[red][-][/red] 提出IDが取得できませんでした')
149
+ print('[red][-][/red] ' + _('submission_id_not_found'))
149
150
  return None
150
151
 
151
152
  url = api.url.replace('/me', f'/{submission_id}')
152
- print('[green][+][/green] 提出に成功しました!')
153
- print(f'提出ID: {submission_id}, URL: {url}')
153
+ print('[green][+][/green] ' + _('submission_success'))
154
+ print(_('submission_details', submission_id, url))
154
155
  return url + '/status/json'
155
156
  else:
156
- print('[red][-][/red] 提出に失敗しました')
157
+ print('[red][-][/red] ' + _('submission_failed'))
157
158
  return None
158
159
 
159
160
 
@@ -211,19 +212,19 @@ def print_status_submission(
211
212
  BarColumn(),
212
213
  )
213
214
 
214
- with Status('ジャッジ待機中', spinner='dots'):
215
- for _ in range(15):
215
+ with Status(_('waiting_judge'), spinner='dots'):
216
+ for i in range(15):
216
217
  time.sleep(1)
217
218
  data = session.get(api_url).json()
218
219
  status = parse_submission_status_json(data)
219
220
  if status.total or status.current:
220
221
  break
221
222
  else:
222
- print('[red][-][/] 15秒待ってもジャッジが開始されませんでした')
223
+ print('[red][-][/] ' + _('judge_timeout'))
223
224
  return
224
225
 
225
226
  total = status.total or 0
226
- task_id = progress.add_task(description='ジャッジ中', total=total)
227
+ task_id = progress.add_task(description=_('judging'), total=total)
227
228
 
228
229
  test_info = TestInformation(
229
230
  lang=detect_language(path),
@@ -248,24 +249,22 @@ def print_status_submission(
248
249
  test_info.summary = status.status
249
250
  test_info.results = [ResultStatus.AC] * total
250
251
 
251
- progress.update(task_id, description='ジャッジ完了', completed=total)
252
+ progress.update(task_id, description=_('judge_completed'), completed=total)
252
253
  live.update(create_renderable_test_info(test_info, progress))
253
254
 
254
255
 
255
256
  def submit_source(path: str, no_test: bool, no_feedback: bool) -> None:
256
257
  session = load_session()
257
258
  if not validate_session(session):
258
- print('[red][-][/] ログインしていません.')
259
+ print('[red][-][/] ' + _('not_logged_in'))
259
260
  login()
260
261
  if not validate_session(session):
261
- print('[red][-][/] ログインに失敗しました.')
262
+ print('[red][-][/] ' + _('login_failed'))
262
263
  return
263
264
 
264
265
  html_files = [file for file in os.listdir('.') if file.endswith('.html')]
265
266
  if not html_files:
266
- print(
267
- '問題のファイルが見つかりません \n問題のファイルが存在するディレクトリーに移動してから実行してください'
268
- )
267
+ print(_('problem_file_not_found'))
269
268
  return
270
269
 
271
270
  with open(html_files[0], 'r') as file:
@@ -279,7 +278,7 @@ def submit_source(path: str, no_test: bool, no_feedback: bool) -> None:
279
278
  print(create_renderable_test_info(test.info))
280
279
 
281
280
  if test.info.summary != ResultStatus.AC and not no_test:
282
- print('[red][-][/] サンプルケースが AC していないので提出できません')
281
+ print('[red][-][/] ' + _('sample_not_ac'))
283
282
  return
284
283
 
285
284
  api_status_link = post_source(path, url, session)
@@ -290,12 +289,10 @@ def submit_source(path: str, no_test: bool, no_feedback: bool) -> None:
290
289
  print_status_submission(api_status_link, path, session)
291
290
 
292
291
 
293
- @click.command(short_help='ソースを提出')
292
+ @click.command(short_help=_('cmd_submit'), help=_('cmd_submit'))
294
293
  @add_file_selector('files', filetypes=COMPILED_LANGUAGES + INTERPRETED_LANGUAGES)
295
- @click.option('--no-test', is_flag=True, default=False, help='テストをスキップ')
296
- @click.option(
297
- '--no-feedback', is_flag=True, default=False, help='フィードバックをスキップ'
298
- )
294
+ @click.option('--no-test', is_flag=True, default=False, help=_('opt_no_test'))
295
+ @click.option('--no-feedback', is_flag=True, default=False, help=_('opt_no_feedback'))
299
296
  def submit(files, no_test, no_feedback):
300
297
  """指定したファイルをAtCoderへ提出します。"""
301
298
  for path in files:
atcdr/test.py CHANGED
@@ -26,6 +26,7 @@ from atcdr.util.filetype import (
26
26
  detect_language,
27
27
  lang2str,
28
28
  )
29
+ from atcdr.util.i18n import _
29
30
  from atcdr.util.parse import ProblemHTML
30
31
 
31
32
 
@@ -159,7 +160,7 @@ class TestRunner:
159
160
  ]
160
161
  self.exe = None
161
162
  else:
162
- raise ValueError(f'{lang}の適切な言語のランナーが見つかりませんでした.')
163
+ raise ValueError(_('runner_not_found', lang))
163
164
 
164
165
  return self
165
166
 
@@ -300,9 +301,9 @@ def create_renderable_test_info(
300
301
  status_text = STATUS_TEXT_MAP[test_info.summary]
301
302
 
302
303
  header_text = Text.assemble(
303
- Text.from_markup(f'[cyan]{test_info.sourcename}[/]のテスト \n'),
304
+ Text.from_markup(f'[cyan]{test_info.sourcename}[/]' + _('test_of', '') + '\n'),
304
305
  Text.from_markup(
305
- f'[italic #0f0f0f]コンパイルにかかった時間: [not italic cyan]{test_info.compile_time}[/] ms[/]\n'
306
+ '[italic #0f0f0f]' + _('compile_time', test_info.compile_time) + '\n'
306
307
  )
307
308
  if test_info.compile_time
308
309
  else Text(''),
@@ -319,7 +320,7 @@ def create_renderable_test_info(
319
320
 
320
321
  if test_info.compiler_message:
321
322
  rule = Rule(
322
- title='コンパイラーのメッセージ',
323
+ title=_('compiler_message'),
323
324
  style=COLOR_MAP[ResultStatus.CE],
324
325
  )
325
326
  components.append(rule)
@@ -342,27 +343,29 @@ def create_renderable_test_result(
342
343
 
343
344
  # 以下の部分は if-else ブロックの外に移動
344
345
  status_header = Text.assemble(
345
- 'ステータス ',
346
+ _('status'),
346
347
  STATUS_TEXT_MAP[test_result.result.passed], # status_text をここに追加
347
348
  )
348
349
 
349
350
  execution_time_text = None
350
351
  if test_result.result.executed_time is not None:
351
352
  execution_time_text = Text.from_markup(
352
- f'実行時間 [cyan]{test_result.result.executed_time}[/cyan] ms'
353
+ _('execution_time', test_result.result.executed_time)
353
354
  )
354
355
 
355
356
  table = Table(show_header=True, header_style='bold')
356
- table.add_column('入力', style='cyan', min_width=10)
357
+ table.add_column(_('input'), style='cyan', min_width=10)
357
358
 
358
359
  if test_result.result.passed != ResultStatus.AC:
359
360
  table.add_column(
360
- '出力',
361
+ _('output'),
361
362
  style=COLOR_MAP[test_result.result.passed],
362
363
  min_width=10,
363
364
  overflow='fold',
364
365
  )
365
- table.add_column('正解の出力', style=COLOR_MAP[ResultStatus.AC], min_width=10)
366
+ table.add_column(
367
+ _('expected_output'), style=COLOR_MAP[ResultStatus.AC], min_width=10
368
+ )
366
369
  table.add_row(
367
370
  escape(test_result.testcase.input),
368
371
  escape(test_result.result.output),
@@ -370,7 +373,7 @@ def create_renderable_test_result(
370
373
  )
371
374
  else:
372
375
  table.add_column(
373
- '出力', style=COLOR_MAP[test_result.result.passed], min_width=10
376
+ _('output'), style=COLOR_MAP[test_result.result.passed], min_width=10
374
377
  )
375
378
  table.add_row(
376
379
  escape(test_result.testcase.input), escape(test_result.result.output)
@@ -393,7 +396,9 @@ def render_results(test: TestRunner) -> None:
393
396
  SpinnerColumn(style='white', spinner_name='simpleDots'),
394
397
  BarColumn(),
395
398
  )
396
- task_id = progress.add_task(description='テスト進行中', total=test.info.case_number)
399
+ task_id = progress.add_task(
400
+ description=_('test_in_progress'), total=test.info.case_number
401
+ )
397
402
 
398
403
  current_display = [create_renderable_test_info(test.info, progress)]
399
404
 
@@ -404,7 +409,9 @@ def render_results(test: TestRunner) -> None:
404
409
  current_display.insert(-1, (create_renderable_test_result(i, result)))
405
410
  live.update(Group(*current_display))
406
411
 
407
- progress.update(task_id, description='テスト完了') # 完了メッセージに更新
412
+ progress.update(
413
+ task_id, description=_('test_completed')
414
+ ) # 完了メッセージに更新
408
415
  current_display[-1] = create_renderable_test_info(test.info, progress)
409
416
  live.update(Group(*current_display))
410
417
 
@@ -412,9 +419,7 @@ def render_results(test: TestRunner) -> None:
412
419
  def run_test(path_of_code: str) -> None:
413
420
  html_paths = [f for f in os.listdir('.') if f.endswith('.html')]
414
421
  if not html_paths:
415
- print(
416
- '問題のファイルが見つかりません。\n問題のファイルが存在するディレクトリーに移動してから実行してください。'
417
- )
422
+ print(_('problem_file_not_found'))
418
423
  return
419
424
 
420
425
  with open(html_paths[0], 'r') as file:
@@ -425,7 +430,7 @@ def run_test(path_of_code: str) -> None:
425
430
  render_results(test)
426
431
 
427
432
 
428
- @click.command(short_help='テストを実行')
433
+ @click.command(short_help=_('cmd_test'), help=_('cmd_test'))
429
434
  @add_file_selector('files', filetypes=COMPILED_LANGUAGES + INTERPRETED_LANGUAGES)
430
435
  def test(files):
431
436
  """指定したソースコードをサンプルケースでテストします。"""
atcdr/util/fileops.py CHANGED
@@ -7,6 +7,7 @@ import questionary as q
7
7
  import rich_click as click
8
8
 
9
9
  from atcdr.util.filetype import FILE_EXTENSIONS, Lang
10
+ from atcdr.util.i18n import _
10
11
 
11
12
 
12
13
  def collect_files(
@@ -41,9 +42,9 @@ def collect_files(
41
42
 
42
43
  def select_files_interactively(files: List[str]) -> List[str]:
43
44
  target_file = q.select(
44
- message='複数のファイルが見つかりました.ファイルを選択してください:',
45
+ message=_('multiple_files_found'),
45
46
  choices=[q.Choice(title=file, value=file) for file in files],
46
- instruction='\n 十字キーで移動, [enter]で実行',
47
+ instruction='\n ' + _('navigate_with_arrows'),
47
48
  pointer='❯',
48
49
  qmark='',
49
50
  style=q.Style(
@@ -78,7 +79,7 @@ def add_file_selector(
78
79
  # 2) ファイル収集 (非再帰固定)
79
80
  files = collect_files(patterns, tuple(exts), recursive=False)
80
81
  if not files:
81
- click.echo('対象ファイルが見つかりません。')
82
+ click.echo(_('target_file_not_found'))
82
83
  ctx.exit(1)
83
84
 
84
85
  # 3) 候補が1つなら即実行
@@ -89,7 +90,7 @@ def add_file_selector(
89
90
  if not patterns:
90
91
  selected = select_files_interactively(files)
91
92
  if not selected:
92
- click.echo('ファイルが選択されませんでした。')
93
+ click.echo(_('file_not_selected'))
93
94
  ctx.exit(1)
94
95
  selected_list = [selected]
95
96
  return ctx.invoke(f, **{arg_name: selected_list}, **kwargs)
atcdr/util/gpt.py CHANGED
@@ -4,6 +4,8 @@ from typing import Dict, List, Optional
4
4
 
5
5
  import requests
6
6
 
7
+ from atcdr.util.i18n import _
8
+
7
9
 
8
10
  class Model(Enum):
9
11
  GPT4O = 'gpt-4o'
@@ -24,27 +26,23 @@ def set_api_key() -> Optional[str]:
24
26
  if api_key and validate_api_key(api_key):
25
27
  return api_key
26
28
  elif api_key:
27
- print('環境変数に設定されているAPIキーの検証に失敗しました ')
29
+ print(_('api_key_validation_failed'))
28
30
  else:
29
31
  pass
30
32
 
31
- api_key = input(
32
- 'https://platform.openai.com/api-keys からchatGPTのAPIキーを入手しましょう。\nAPIキー入力してください: '
33
- )
33
+ api_key = input(_('get_api_key_prompt'))
34
34
  if validate_api_key(api_key):
35
- print('APIキーのテストに成功しました。')
36
- print('以下, ~/.zshrcにAPIキーを保存しますか? [y/n]')
35
+ print(_('api_key_test_success'))
36
+ print(_('save_api_key_prompt'))
37
37
  if input() == 'y':
38
38
  zshrc_path = os.path.expanduser('~/.zshrc')
39
39
  with open(zshrc_path, 'a') as f:
40
40
  f.write(f'export OPENAI_API_KEY={api_key}\n')
41
- print(
42
- f'APIキーを {zshrc_path} に保存しました。次回シェル起動時に読み込まれます。'
43
- )
41
+ print(_('api_key_saved', zshrc_path))
44
42
  os.environ['OPENAI_API_KEY'] = api_key
45
43
  return api_key
46
44
  else:
47
- print('コード生成にはAPIキーが必要です。')
45
+ print(_('api_key_required'))
48
46
  return None
49
47
 
50
48
 
@@ -59,7 +57,7 @@ def validate_api_key(api_key: str) -> bool:
59
57
  if response.status_code == 200:
60
58
  return True
61
59
  else:
62
- print('APIキーの検証に失敗しました。')
60
+ print(_('api_key_validation_error'))
63
61
  return False
64
62
 
65
63
 
@@ -106,7 +104,7 @@ class ChatGPT:
106
104
  try:
107
105
  reply = responsej['choices'][0]['message']['content']
108
106
  except KeyError:
109
- print('Error:レスポンスの形式が正しくありません. \n' + str(responsej))
107
+ print(_('response_format_error') + str(responsej))
110
108
  return 'Error: Unable to retrieve response.'
111
109
 
112
110
  self.messages.append({'role': 'assistant', 'content': reply})