ruyi 0.44.0a20251118__py3-none-any.whl → 0.45.0__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.
Files changed (55) hide show
  1. ruyi/__main__.py +16 -4
  2. ruyi/cli/cmd.py +6 -5
  3. ruyi/cli/config_cli.py +14 -11
  4. ruyi/cli/main.py +34 -17
  5. ruyi/cli/oobe.py +10 -10
  6. ruyi/cli/self_cli.py +49 -36
  7. ruyi/cli/user_input.py +42 -12
  8. ruyi/cli/version_cli.py +11 -5
  9. ruyi/config/__init__.py +30 -10
  10. ruyi/config/errors.py +19 -7
  11. ruyi/device/provision.py +116 -55
  12. ruyi/device/provision_cli.py +6 -3
  13. ruyi/i18n/__init__.py +129 -0
  14. ruyi/log/__init__.py +6 -5
  15. ruyi/mux/runtime.py +19 -6
  16. ruyi/mux/venv/maker.py +93 -35
  17. ruyi/mux/venv/venv_cli.py +13 -10
  18. ruyi/pluginhost/plugin_cli.py +4 -3
  19. ruyi/resource_bundle/__init__.py +22 -8
  20. ruyi/resource_bundle/__main__.py +6 -5
  21. ruyi/resource_bundle/data.py +13 -9
  22. ruyi/ruyipkg/admin_checksum.py +4 -1
  23. ruyi/ruyipkg/admin_cli.py +9 -6
  24. ruyi/ruyipkg/augmented_pkg.py +15 -14
  25. ruyi/ruyipkg/checksum.py +8 -2
  26. ruyi/ruyipkg/distfile.py +33 -9
  27. ruyi/ruyipkg/entity.py +12 -2
  28. ruyi/ruyipkg/entity_cli.py +20 -12
  29. ruyi/ruyipkg/entity_provider.py +11 -2
  30. ruyi/ruyipkg/fetcher.py +38 -9
  31. ruyi/ruyipkg/install.py +163 -64
  32. ruyi/ruyipkg/install_cli.py +18 -15
  33. ruyi/ruyipkg/list.py +27 -20
  34. ruyi/ruyipkg/list_cli.py +12 -7
  35. ruyi/ruyipkg/news.py +23 -11
  36. ruyi/ruyipkg/news_cli.py +10 -7
  37. ruyi/ruyipkg/profile_cli.py +8 -2
  38. ruyi/ruyipkg/repo.py +22 -8
  39. ruyi/ruyipkg/unpack.py +42 -8
  40. ruyi/ruyipkg/unpack_method.py +5 -1
  41. ruyi/ruyipkg/update_cli.py +8 -3
  42. ruyi/telemetry/aggregate.py +5 -0
  43. ruyi/telemetry/provider.py +292 -105
  44. ruyi/telemetry/store.py +68 -15
  45. ruyi/telemetry/telemetry_cli.py +32 -13
  46. ruyi/utils/git.py +18 -11
  47. ruyi/utils/prereqs.py +10 -5
  48. ruyi/utils/ssl_patch.py +2 -1
  49. ruyi/version.py +9 -3
  50. {ruyi-0.44.0a20251118.dist-info → ruyi-0.45.0.dist-info}/METADATA +4 -2
  51. ruyi-0.45.0.dist-info/RECORD +103 -0
  52. {ruyi-0.44.0a20251118.dist-info → ruyi-0.45.0.dist-info}/WHEEL +1 -1
  53. ruyi-0.44.0a20251118.dist-info/RECORD +0 -102
  54. {ruyi-0.44.0a20251118.dist-info → ruyi-0.45.0.dist-info}/entry_points.txt +0 -0
  55. {ruyi-0.44.0a20251118.dist-info → ruyi-0.45.0.dist-info}/licenses/LICENSE-Apache.txt +0 -0
ruyi/ruyipkg/fetcher.py CHANGED
@@ -7,6 +7,7 @@ from typing import Any, Final
7
7
  import requests
8
8
  from rich import progress
9
9
 
10
+ from ..i18n import _
10
11
  from ..log import RuyiLogger
11
12
 
12
13
  ENV_OVERRIDE_FETCHER: Final = "RUYI_OVERRIDE_FETCHER"
@@ -40,19 +41,31 @@ class BaseFetcher:
40
41
  ) -> bool:
41
42
  for t in range(retries):
42
43
  if t > 0:
43
- self._logger.I(f"retrying download ({t + 1} of {retries} times)")
44
+ self._logger.I(
45
+ _("retrying download ({current} of {total} times)").format(
46
+ current=t + 1,
47
+ total=retries,
48
+ )
49
+ )
44
50
  if self.fetch_one(url, dest, resume):
45
51
  return True
46
52
  return False
47
53
 
48
54
  def fetch(self, *, resume: bool = False, retries: int = 3) -> None:
49
55
  for url in self.urls:
50
- self._logger.I(f"downloading {url} to {self.dest}")
56
+ self._logger.I(
57
+ _("downloading {url} to {dest}").format(
58
+ url=url,
59
+ dest=self.dest,
60
+ )
61
+ )
51
62
  if self.fetch_one_with_retry(url, self.dest, resume, retries):
52
63
  return
53
64
  # all URLs have been tried and all have failed
54
65
  raise RuntimeError(
55
- f"failed to fetch '{self.dest}': all source URLs have failed"
66
+ _("failed to fetch '{dest}': all source URLs have failed").format(
67
+ dest=self.dest,
68
+ )
56
69
  )
57
70
 
58
71
  @classmethod
@@ -78,7 +91,7 @@ def get_usable_fetcher_cls(logger: RuyiLogger) -> type[BaseFetcher]:
78
91
 
79
92
  if _fetcher_cache_populated:
80
93
  if _cached_usable_fetcher_class is None:
81
- raise RuntimeError("no fetcher is available on the system")
94
+ raise RuntimeError(_("no fetcher is available on the system"))
82
95
  return _cached_usable_fetcher_class
83
96
 
84
97
  _fetcher_cache_populated = True
@@ -88,10 +101,16 @@ def get_usable_fetcher_cls(logger: RuyiLogger) -> type[BaseFetcher]:
88
101
 
89
102
  cls = KNOWN_FETCHERS.get(override_name)
90
103
  if cls is None:
91
- raise RuntimeError(f"unknown fetcher '{override_name}'")
104
+ raise RuntimeError(
105
+ _("unknown fetcher '{name}'").format(
106
+ name=override_name,
107
+ )
108
+ )
92
109
  if not cls.is_available(logger):
93
110
  raise RuntimeError(
94
- f"the requested fetcher '{override_name}' is unavailable on the system"
111
+ _("the requested fetcher '{name}' is unavailable on the system").format(
112
+ name=override_name,
113
+ )
95
114
  )
96
115
  _cached_usable_fetcher_class = cls
97
116
  return cls
@@ -103,7 +122,7 @@ def get_usable_fetcher_cls(logger: RuyiLogger) -> type[BaseFetcher]:
103
122
  _cached_usable_fetcher_class = cls
104
123
  return cls
105
124
 
106
- raise RuntimeError("no fetcher is available on the system")
125
+ raise RuntimeError(_("no fetcher is available on the system"))
107
126
 
108
127
 
109
128
  class CurlFetcher(BaseFetcher):
@@ -152,7 +171,12 @@ class CurlFetcher(BaseFetcher):
152
171
  retcode = subprocess.call(argv)
153
172
  if retcode != 0:
154
173
  self._logger.W(
155
- f"failed to fetch distfile: command '{' '.join(argv)}' returned {retcode}"
174
+ _(
175
+ "failed to fetch distfile: command '{cmd}' returned {retcode}"
176
+ ).format(
177
+ cmd=" ".join(argv),
178
+ retcode=retcode,
179
+ )
156
180
  )
157
181
  return False
158
182
 
@@ -190,7 +214,12 @@ class WgetFetcher(BaseFetcher):
190
214
  retcode = subprocess.call(argv)
191
215
  if retcode != 0:
192
216
  self._logger.W(
193
- f"failed to fetch distfile: command '{' '.join(argv)}' returned {retcode}"
217
+ _(
218
+ "failed to fetch distfile: command '{cmd}' returned {retcode}"
219
+ ).format(
220
+ cmd=" ".join(argv),
221
+ retcode=retcode,
222
+ )
194
223
  )
195
224
  return False
196
225
 
ruyi/ruyipkg/install.py CHANGED
@@ -8,6 +8,7 @@ from ruyi.ruyipkg.state import BoundInstallationStateStore
8
8
 
9
9
  from ..cli.user_input import ask_for_yesno_confirmation
10
10
  from ..config import GlobalConfig
11
+ from ..i18n import _
11
12
  from ..telemetry.scope import TelemetryScope
12
13
  from .atom import Atom
13
14
  from .distfile import Distfile
@@ -43,12 +44,16 @@ def do_extract_atoms(
43
44
  a = Atom.parse(a_str)
44
45
  pm = a.match_in_repo(mr, cfg.include_prereleases)
45
46
  if pm is None:
46
- logger.F(f"atom {a_str} matches no package in the repository")
47
+ logger.F(
48
+ _("atom {atom} matches no package in the repository").format(
49
+ atom=a_str,
50
+ )
51
+ )
47
52
  return 1
48
53
 
49
54
  sv = pm.service_level
50
55
  if sv.has_known_issues:
51
- logger.W("package has known issue(s)")
56
+ logger.W(_("package has known issue(s)"))
52
57
  for s in sv.render_known_issues(pm.repo.messages, cfg.lang_code):
53
58
  logger.I(s)
54
59
 
@@ -97,12 +102,20 @@ def _do_extract_pkg(
97
102
  bm = pm.binary_metadata
98
103
  sm = pm.source_metadata
99
104
  if bm is None and sm is None:
100
- logger.F(f"don't know how to extract package [green]{pkg_name}[/]")
105
+ logger.F(
106
+ _("don't know how to extract package [green]{pkg}[/]").format(
107
+ pkg=pkg_name,
108
+ )
109
+ )
101
110
  return 2
102
111
 
103
112
  if bm is not None and sm is not None:
104
113
  logger.F(
105
- f"cannot handle package [green]{pkg_name}[/]: package is both binary and source"
114
+ _(
115
+ "cannot handle package [green]{pkg}[/]: package is both binary and source"
116
+ ).format(
117
+ pkg=pkg_name,
118
+ )
106
119
  )
107
120
  return 2
108
121
 
@@ -114,7 +127,10 @@ def _do_extract_pkg(
114
127
 
115
128
  if not distfiles_for_host:
116
129
  logger.F(
117
- f"package [green]{pkg_name}[/] declares no distfile for host {canonicalized_host}"
130
+ _("package [green]{pkg}[/] declares no distfile for host {host}").format(
131
+ pkg=pkg_name,
132
+ host=canonicalized_host,
133
+ )
118
134
  )
119
135
  return 2
120
136
 
@@ -130,13 +146,21 @@ def _do_extract_pkg(
130
146
  logger.D("skipping extraction because [yellow]--fetch-only[/] is given")
131
147
  continue
132
148
 
133
- logger.I(f"extracting [green]{df_name}[/] for package [green]{pkg_name}[/]")
149
+ logger.I(
150
+ _("extracting [green]{distfile}[/] for package [green]{pkg}[/]").format(
151
+ distfile=df_name,
152
+ pkg=pkg_name,
153
+ )
154
+ )
134
155
  # unpack into destination
135
156
  df.unpack(dest_dir, logger)
136
157
 
137
158
  if not fetch_only:
138
159
  logger.I(
139
- f"package [green]{pkg_name}[/] has been extracted to {dest_dir}",
160
+ _("package [green]{pkg}[/] has been extracted to {dest_dir}").format(
161
+ pkg=pkg_name,
162
+ dest_dir=dest_dir,
163
+ )
140
164
  )
141
165
 
142
166
  return 0
@@ -158,27 +182,28 @@ def do_install_atoms(
158
182
  a = Atom.parse(a_str)
159
183
  pm = a.match_in_repo(mr, config.include_prereleases)
160
184
  if pm is None:
161
- logger.F(f"atom {a_str} matches no package in the repository")
185
+ logger.F(
186
+ _("atom {atom} matches no package in the repository").format(atom=a_str)
187
+ )
162
188
  return 1
163
189
  pkg_name = pm.name_for_installation
164
190
 
165
191
  sv = pm.service_level
166
192
  if sv.has_known_issues:
167
- logger.W("package has known issue(s)")
193
+ logger.W(_("package has known issue(s)"))
168
194
  for s in sv.render_known_issues(pm.repo.messages, config.lang_code):
169
195
  logger.I(s)
170
196
 
171
- if tm := config.telemetry:
172
- tm.record(
173
- TelemetryScope(mr.repo_id),
174
- "repo:package-install-v1",
175
- atom=a_str,
176
- host=canonicalized_host,
177
- pkg_category=pm.category,
178
- pkg_kinds=pm.kind,
179
- pkg_name=pm.name,
180
- pkg_version=pm.ver,
181
- )
197
+ config.telemetry.record(
198
+ TelemetryScope(mr.repo_id),
199
+ "repo:package-install-v1",
200
+ atom=a_str,
201
+ host=canonicalized_host,
202
+ pkg_category=pm.category,
203
+ pkg_kinds=pm.kind,
204
+ pkg_name=pm.name,
205
+ pkg_version=pm.ver,
206
+ )
182
207
 
183
208
  if pm.binary_metadata is not None:
184
209
  ret = _do_install_binary_pkg(
@@ -214,7 +239,11 @@ def do_install_atoms(
214
239
  return ret
215
240
  continue
216
241
 
217
- logger.F(f"don't know how to handle non-binary package [green]{pkg_name}[/]")
242
+ logger.F(
243
+ _("don't know how to handle non-binary package [green]{pkg}[/]").format(
244
+ pkg=pkg_name,
245
+ )
246
+ )
218
247
  return 2
219
248
 
220
249
  return 0
@@ -250,11 +279,17 @@ def _do_install_binary_pkg(
250
279
 
251
280
  if is_installed:
252
281
  if not reinstall:
253
- logger.I(f"skipping already installed package [green]{pkg_name}[/]")
282
+ logger.I(
283
+ _("skipping already installed package [green]{pkg}[/]").format(
284
+ pkg=pkg_name,
285
+ )
286
+ )
254
287
  return 0
255
288
 
256
289
  logger.W(
257
- f"package [green]{pkg_name}[/] seems already installed; purging and re-installing due to [yellow]--reinstall[/]"
290
+ _(
291
+ "package [green]{pkg}[/] seems already installed; purging and re-installing due to [yellow]--reinstall[/]"
292
+ ).format(pkg=pkg_name)
258
293
  )
259
294
  # Remove from state tracking before purging
260
295
  rgs.remove_installation(
@@ -291,7 +326,12 @@ def _do_install_binary_pkg(
291
326
  install_path=install_root,
292
327
  )
293
328
 
294
- logger.I(f"package [green]{pkg_name}[/] installed to [yellow]{install_root}[/]")
329
+ logger.I(
330
+ _("package [green]{pkg}[/] installed to [yellow]{install_root}[/]").format(
331
+ pkg=pkg_name,
332
+ install_root=install_root,
333
+ )
334
+ )
295
335
  return 0
296
336
 
297
337
 
@@ -313,7 +353,10 @@ def _do_install_binary_pkg_to(
313
353
  distfiles_for_host = bm.get_distfile_names_for_host(str(canonicalized_host))
314
354
  if not distfiles_for_host:
315
355
  logger.F(
316
- f"package [green]{pkg_name}[/] declares no binary for host {canonicalized_host}"
356
+ _("package [green]{pkg}[/] declares no binary for host {host}").format(
357
+ pkg=pkg_name,
358
+ host=canonicalized_host,
359
+ )
317
360
  )
318
361
  return 2
319
362
 
@@ -324,10 +367,17 @@ def _do_install_binary_pkg_to(
324
367
  df.ensure(logger)
325
368
 
326
369
  if fetch_only:
327
- logger.D("skipping installation because [yellow]--fetch-only[/] is given")
370
+ logger.D(
371
+ _("skipping installation because [yellow]--fetch-only[/] is given")
372
+ )
328
373
  continue
329
374
 
330
- logger.I(f"extracting [green]{df_name}[/] for package [green]{pkg_name}[/]")
375
+ logger.I(
376
+ _("extracting [green]{distfile}[/] for package [green]{pkg}[/]").format(
377
+ distfile=df_name,
378
+ pkg=pkg_name,
379
+ )
380
+ )
331
381
  df.unpack(install_root, logger)
332
382
 
333
383
  return 0
@@ -362,11 +412,17 @@ def _do_install_blob_pkg(
362
412
 
363
413
  if is_installed:
364
414
  if not reinstall:
365
- logger.I(f"skipping already installed package [green]{pkg_name}[/]")
415
+ logger.I(
416
+ _("skipping already installed package [green]{pkg}[/]").format(
417
+ pkg=pkg_name,
418
+ )
419
+ )
366
420
  return 0
367
421
 
368
422
  logger.W(
369
- f"package [green]{pkg_name}[/] seems already installed; purging and re-installing due to [yellow]--reinstall[/]"
423
+ _(
424
+ "package [green]{pkg}[/] seems already installed; purging and re-installing due to [yellow]--reinstall[/]"
425
+ ).format(pkg=pkg_name)
370
426
  )
371
427
  # Remove from state tracking before purging
372
428
  rgs.remove_installation(
@@ -402,7 +458,12 @@ def _do_install_blob_pkg(
402
458
  install_path=install_root,
403
459
  )
404
460
 
405
- logger.I(f"package [green]{pkg_name}[/] installed to [yellow]{install_root}[/]")
461
+ logger.I(
462
+ _("package [green]{pkg}[/] installed to [yellow]{install_root}[/]").format(
463
+ pkg=pkg_name,
464
+ install_root=install_root,
465
+ )
466
+ )
406
467
  return 0
407
468
 
408
469
 
@@ -421,7 +482,9 @@ def _do_install_blob_pkg_to(
421
482
  dfs = pm.distfiles
422
483
  distfile_names = bm.get_distfile_names()
423
484
  if not distfile_names:
424
- logger.F(f"package [green]{pkg_name}[/] declares no blob distfile")
485
+ logger.F(
486
+ _("package [green]{pkg}[/] declares no blob distfile").format(pkg=pkg_name)
487
+ )
425
488
  return 2
426
489
 
427
490
  for df_name in distfile_names:
@@ -431,10 +494,17 @@ def _do_install_blob_pkg_to(
431
494
  df.ensure(logger)
432
495
 
433
496
  if fetch_only:
434
- logger.D("skipping installation because [yellow]--fetch-only[/] is given")
497
+ logger.D(
498
+ _("skipping installation because [yellow]--fetch-only[/] is given")
499
+ )
435
500
  continue
436
501
 
437
- logger.I(f"extracting [green]{df_name}[/] for package [green]{pkg_name}[/]")
502
+ logger.I(
503
+ _("extracting [green]{distfile}[/] for package [green]{pkg}[/]").format(
504
+ distfile=df_name,
505
+ pkg=pkg_name,
506
+ )
507
+ )
438
508
  df.unpack_or_symlink(install_root, logger)
439
509
 
440
510
  return 0
@@ -458,37 +528,46 @@ def do_uninstall_atoms(
458
528
  a = Atom.parse(a_str)
459
529
  pm = a.match_in_repo(bis, config.include_prereleases)
460
530
  if pm is None:
461
- logger.F(f"atom [yellow]{a_str}[/] is non-existent or not installed")
531
+ logger.F(
532
+ _("atom [yellow]{atom}[/] is non-existent or not installed").format(
533
+ atom=a_str,
534
+ )
535
+ )
462
536
  return 1
463
537
  pms_to_uninstall.append((a_str, pm))
464
538
 
465
539
  if not pms_to_uninstall:
466
- logger.I("no packages to uninstall")
540
+ logger.I(_("no packages to uninstall"))
467
541
  return 0
468
542
 
469
- logger.I("the following packages will be uninstalled:")
470
- for _, pm in pms_to_uninstall:
471
- logger.I(f" - [green]{pm.category}/{pm.name}[/] ({pm.ver})")
543
+ logger.I(_("the following packages will be uninstalled:"))
544
+ for _unused, pm in pms_to_uninstall:
545
+ logger.I(
546
+ _(" - [green]{category}/{name}[/] ({version})").format(
547
+ category=pm.category,
548
+ name=pm.name,
549
+ version=pm.ver,
550
+ )
551
+ )
472
552
 
473
553
  if not assume_yes:
474
- if not ask_for_yesno_confirmation(logger, "Proceed?", default=False):
475
- logger.I("uninstallation aborted")
554
+ if not ask_for_yesno_confirmation(logger, _("Proceed?"), default=False):
555
+ logger.I(_("uninstallation aborted"))
476
556
  return 0
477
557
 
478
558
  for a_str, pm in pms_to_uninstall:
479
559
  pkg_name = pm.name_for_installation
480
560
 
481
- if tm := config.telemetry:
482
- tm.record(
483
- TelemetryScope(mr.repo_id),
484
- "repo:package-uninstall-v1",
485
- atom=a_str,
486
- host=canonicalized_host,
487
- pkg_category=pm.category,
488
- pkg_kinds=pm.kind,
489
- pkg_name=pm.name,
490
- pkg_version=pm.ver,
491
- )
561
+ config.telemetry.record(
562
+ TelemetryScope(mr.repo_id),
563
+ "repo:package-uninstall-v1",
564
+ atom=a_str,
565
+ host=canonicalized_host,
566
+ pkg_category=pm.category,
567
+ pkg_kinds=pm.kind,
568
+ pkg_name=pm.name,
569
+ pkg_version=pm.ver,
570
+ )
492
571
 
493
572
  if pm.binary_metadata is not None:
494
573
  ret = _do_uninstall_binary_pkg(
@@ -506,7 +585,11 @@ def do_uninstall_atoms(
506
585
  return ret
507
586
  continue
508
587
 
509
- logger.F(f"don't know how to handle non-binary package [green]{pkg_name}[/]")
588
+ logger.F(
589
+ _("don't know how to handle non-binary package [green]{pkg}[/]").format(
590
+ pkg=pkg_name,
591
+ )
592
+ )
510
593
  return 2
511
594
 
512
595
  return 0
@@ -536,21 +619,29 @@ def _do_uninstall_binary_pkg(
536
619
  # Check directory existence if the PM state says the package is not installed
537
620
  if not is_installed:
538
621
  if not os.path.exists(install_root):
539
- logger.I(f"skipping not-installed package [green]{pkg_name}[/]")
622
+ logger.I(
623
+ _("skipping not-installed package [green]{pkg}[/]").format(
624
+ pkg=pkg_name,
625
+ )
626
+ )
540
627
  return 0
541
628
 
542
629
  # There may be potentially user-generated data in the directory,
543
630
  # let's be safe and fail the process.
544
631
  logger.F(
545
- f"package [green]{pkg_name}[/] is not tracked as installed, but its directory [yellow]{install_root}[/] exists."
632
+ _(
633
+ "package [green]{pkg}[/] is not tracked as installed, but its directory [yellow]{install_root}[/] exists."
634
+ ).format(pkg=pkg_name, install_root=install_root)
546
635
  )
547
- logger.I("Please remove it manually if you are sure it's safe to do so.")
636
+ logger.I(_("Please remove it manually if you are sure it's safe to do so."))
548
637
  logger.I(
549
- "If you believe this is a bug, please file an issue at [yellow]https://github.com/ruyisdk/ruyi/issues[/]."
638
+ _(
639
+ "If you believe this is a bug, please file an issue at [yellow]https://github.com/ruyisdk/ruyi/issues[/]."
640
+ )
550
641
  )
551
642
  return 1
552
643
 
553
- logger.I(f"uninstalling package [green]{pkg_name}[/]")
644
+ logger.I(_("uninstalling package [green]{pkg}[/]").format(pkg=pkg_name))
554
645
  if is_installed:
555
646
  rgs.remove_installation(
556
647
  pm.repo_id,
@@ -563,7 +654,7 @@ def _do_uninstall_binary_pkg(
563
654
  if os.path.exists(install_root):
564
655
  shutil.rmtree(install_root)
565
656
 
566
- logger.I(f"package [green]{pkg_name}[/] uninstalled")
657
+ logger.I(_("package [green]{pkg}[/] uninstalled").format(pkg=pkg_name))
567
658
  return 0
568
659
 
569
660
 
@@ -590,21 +681,29 @@ def _do_uninstall_blob_pkg(
590
681
  # Check directory existence if the PM state says the package is not installed
591
682
  if not is_installed:
592
683
  if not os.path.exists(install_root):
593
- logger.I(f"skipping not-installed package [green]{pkg_name}[/]")
684
+ logger.I(
685
+ _("skipping not-installed package [green]{pkg}[/]").format(
686
+ pkg=pkg_name,
687
+ )
688
+ )
594
689
  return 0
595
690
 
596
691
  # There may be potentially user-generated data in the directory,
597
692
  # let's be safe and fail the process.
598
693
  logger.F(
599
- f"package [green]{pkg_name}[/] is not tracked as installed, but its directory [yellow]{install_root}[/] exists."
694
+ _(
695
+ "package [green]{pkg}[/] is not tracked as installed, but its directory [yellow]{install_root}[/] exists."
696
+ ).format(pkg=pkg_name, install_root=install_root)
600
697
  )
601
- logger.I("Please remove it manually if you are sure it's safe to do so.")
698
+ logger.I(_("Please remove it manually if you are sure it's safe to do so."))
602
699
  logger.I(
603
- "If you believe this is a bug, please file an issue at [yellow]https://github.com/ruyisdk/ruyi/issues[/]."
700
+ _(
701
+ "If you believe this is a bug, please file an issue at [yellow]https://github.com/ruyisdk/ruyi/issues[/]."
702
+ )
604
703
  )
605
704
  return 1
606
705
 
607
- logger.I(f"uninstalling package [green]{pkg_name}[/]")
706
+ logger.I(_("uninstalling package [green]{pkg}[/]").format(pkg=pkg_name))
608
707
  if is_installed:
609
708
  rgs.remove_installation(
610
709
  pm.repo_id,
@@ -617,5 +716,5 @@ def _do_uninstall_blob_pkg(
617
716
  if os.path.exists(install_root):
618
717
  shutil.rmtree(install_root)
619
718
 
620
- logger.I(f"package [green]{pkg_name}[/] uninstalled")
719
+ logger.I(_("package [green]{pkg}[/] uninstalled").format(pkg=pkg_name))
621
720
  return 0
@@ -3,6 +3,7 @@ import pathlib
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  from ..cli.cmd import RootCommand
6
+ from ..i18n import _
6
7
  from .cli_completion import package_completer_builder
7
8
  from .host import get_native_host
8
9
 
@@ -14,7 +15,7 @@ if TYPE_CHECKING:
14
15
  class ExtractCommand(
15
16
  RootCommand,
16
17
  cmd="extract",
17
- help="Fetch package(s) then extract to current directory",
18
+ help=_("Fetch package(s) then extract to current directory"),
18
19
  ):
19
20
  @classmethod
20
21
  def configure_args(cls, gc: "GlobalConfig", p: "ArgumentParser") -> None:
@@ -22,7 +23,7 @@ class ExtractCommand(
22
23
  "atom",
23
24
  type=str,
24
25
  nargs="+",
25
- help="Specifier (atom) of the package(s) to extract",
26
+ help=_("Specifier (atom) of the package(s) to extract"),
26
27
  )
27
28
  if gc.is_cli_autocomplete:
28
29
  a.completer = package_completer_builder(gc)
@@ -33,24 +34,26 @@ class ExtractCommand(
33
34
  type=str,
34
35
  metavar="DESTDIR",
35
36
  default=".",
36
- help="Destination directory to extract to (default: current directory)",
37
+ help=_("Destination directory to extract to (default: current directory)"),
37
38
  )
38
39
  p.add_argument(
39
40
  "--extract-without-subdir",
40
41
  action="store_true",
41
- help="Extract files directly into DESTDIR instead of package-named subdirectories",
42
+ help=_(
43
+ "Extract files directly into DESTDIR instead of package-named subdirectories"
44
+ ),
42
45
  )
43
46
  p.add_argument(
44
47
  "-f",
45
48
  "--fetch-only",
46
49
  action="store_true",
47
- help="Fetch distribution files only without installing",
50
+ help=_("Fetch distribution files only without installing"),
48
51
  )
49
52
  p.add_argument(
50
53
  "--host",
51
54
  type=str,
52
55
  default=get_native_host(),
53
- help="Override the host architecture (normally not needed)",
56
+ help=_("Override the host architecture (normally not needed)"),
54
57
  )
55
58
 
56
59
  @classmethod
@@ -81,7 +84,7 @@ class InstallCommand(
81
84
  RootCommand,
82
85
  cmd="install",
83
86
  aliases=["i"],
84
- help="Install package from configured repository",
87
+ help=_("Install package from configured repository"),
85
88
  ):
86
89
  @classmethod
87
90
  def configure_args(cls, gc: "GlobalConfig", p: "ArgumentParser") -> None:
@@ -89,7 +92,7 @@ class InstallCommand(
89
92
  "atom",
90
93
  type=str,
91
94
  nargs="+",
92
- help="Specifier (atom) of the package to install",
95
+ help=_("Specifier (atom) of the package to install"),
93
96
  )
94
97
  if gc.is_cli_autocomplete:
95
98
  a.completer = package_completer_builder(gc)
@@ -98,18 +101,18 @@ class InstallCommand(
98
101
  "-f",
99
102
  "--fetch-only",
100
103
  action="store_true",
101
- help="Fetch distribution files only without installing",
104
+ help=_("Fetch distribution files only without installing"),
102
105
  )
103
106
  p.add_argument(
104
107
  "--host",
105
108
  type=str,
106
109
  default=get_native_host(),
107
- help="Override the host architecture (normally not needed)",
110
+ help=_("Override the host architecture (normally not needed)"),
108
111
  )
109
112
  p.add_argument(
110
113
  "--reinstall",
111
114
  action="store_true",
112
- help="Force re-installation of already installed packages",
115
+ help=_("Force re-installation of already installed packages"),
113
116
  )
114
117
 
115
118
  @classmethod
@@ -136,7 +139,7 @@ class UninstallCommand(
136
139
  RootCommand,
137
140
  cmd="uninstall",
138
141
  aliases=["remove", "rm"],
139
- help="Uninstall installed packages",
142
+ help=_("Uninstall installed packages"),
140
143
  ):
141
144
  @classmethod
142
145
  def configure_args(cls, gc: "GlobalConfig", p: argparse.ArgumentParser) -> None:
@@ -144,20 +147,20 @@ class UninstallCommand(
144
147
  "atom",
145
148
  type=str,
146
149
  nargs="+",
147
- help="Specifier (atom) of the package to uninstall",
150
+ help=_("Specifier (atom) of the package to uninstall"),
148
151
  )
149
152
  p.add_argument(
150
153
  "--host",
151
154
  type=str,
152
155
  default=get_native_host(),
153
- help="Override the host architecture (normally not needed)",
156
+ help=_("Override the host architecture (normally not needed)"),
154
157
  )
155
158
  p.add_argument(
156
159
  "-y",
157
160
  "--yes",
158
161
  action="store_true",
159
162
  dest="assume_yes",
160
- help="Assume yes to all prompts",
163
+ help=_("Assume yes to all prompts"),
161
164
  )
162
165
 
163
166
  @classmethod