ruyi 0.44.0a20251118__tar.gz → 0.45.0__tar.gz

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 (105) hide show
  1. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/PKG-INFO +4 -2
  2. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/README.md +2 -1
  3. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/pyproject.toml +2 -1
  4. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/__main__.py +16 -4
  5. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/cli/cmd.py +6 -5
  6. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/cli/config_cli.py +14 -11
  7. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/cli/main.py +34 -17
  8. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/cli/oobe.py +10 -10
  9. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/cli/self_cli.py +49 -36
  10. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/cli/user_input.py +42 -12
  11. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/cli/version_cli.py +11 -5
  12. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/config/__init__.py +30 -10
  13. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/config/errors.py +19 -7
  14. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/device/provision.py +116 -55
  15. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/device/provision_cli.py +6 -3
  16. ruyi-0.45.0/ruyi/i18n/__init__.py +129 -0
  17. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/log/__init__.py +6 -5
  18. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/mux/runtime.py +19 -6
  19. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/mux/venv/maker.py +93 -35
  20. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/mux/venv/venv_cli.py +13 -10
  21. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/pluginhost/plugin_cli.py +4 -3
  22. ruyi-0.45.0/ruyi/resource_bundle/__init__.py +34 -0
  23. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/resource_bundle/__main__.py +6 -5
  24. ruyi-0.45.0/ruyi/resource_bundle/data.py +30 -0
  25. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/admin_checksum.py +4 -1
  26. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/admin_cli.py +9 -6
  27. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/augmented_pkg.py +15 -14
  28. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/checksum.py +8 -2
  29. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/distfile.py +33 -9
  30. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/entity.py +12 -2
  31. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/entity_cli.py +20 -12
  32. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/entity_provider.py +11 -2
  33. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/fetcher.py +38 -9
  34. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/install.py +163 -64
  35. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/install_cli.py +18 -15
  36. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/list.py +27 -20
  37. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/list_cli.py +12 -7
  38. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/news.py +23 -11
  39. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/news_cli.py +10 -7
  40. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/profile_cli.py +8 -2
  41. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/repo.py +22 -8
  42. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/unpack.py +42 -8
  43. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/unpack_method.py +5 -1
  44. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/update_cli.py +8 -3
  45. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/telemetry/aggregate.py +5 -0
  46. ruyi-0.45.0/ruyi/telemetry/provider.py +631 -0
  47. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/telemetry/store.py +68 -15
  48. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/telemetry/telemetry_cli.py +32 -13
  49. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/utils/git.py +18 -11
  50. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/utils/prereqs.py +10 -5
  51. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/utils/ssl_patch.py +2 -1
  52. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/version.py +9 -3
  53. ruyi-0.44.0a20251118/ruyi/resource_bundle/__init__.py +0 -20
  54. ruyi-0.44.0a20251118/ruyi/resource_bundle/data.py +0 -26
  55. ruyi-0.44.0a20251118/ruyi/telemetry/provider.py +0 -444
  56. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/LICENSE-Apache.txt +0 -0
  57. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/__init__.py +0 -0
  58. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/cli/__init__.py +0 -0
  59. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/cli/builtin_commands.py +0 -0
  60. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/cli/completer.py +0 -0
  61. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/cli/completion.py +0 -0
  62. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/config/editor.py +0 -0
  63. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/config/news.py +0 -0
  64. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/config/schema.py +0 -0
  65. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/device/__init__.py +0 -0
  66. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/mux/__init__.py +0 -0
  67. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/mux/venv/__init__.py +0 -0
  68. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/mux/venv/emulator_cfg.py +0 -0
  69. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/mux/venv_cfg.py +0 -0
  70. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/pluginhost/__init__.py +0 -0
  71. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/pluginhost/api.py +0 -0
  72. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/pluginhost/ctx.py +0 -0
  73. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/pluginhost/paths.py +0 -0
  74. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/pluginhost/unsandboxed.py +0 -0
  75. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/py.typed +0 -0
  76. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/__init__.py +0 -0
  77. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/atom.py +0 -0
  78. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/canonical_dump.py +0 -0
  79. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/cli_completion.py +0 -0
  80. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/host.py +0 -0
  81. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/list_filter.py +0 -0
  82. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/msg.py +0 -0
  83. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/news_store.py +0 -0
  84. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/pkg_manifest.py +0 -0
  85. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/profile.py +0 -0
  86. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/protocols.py +0 -0
  87. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/ruyipkg/state.py +0 -0
  88. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/telemetry/__init__.py +0 -0
  89. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/telemetry/event.py +0 -0
  90. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/telemetry/scope.py +0 -0
  91. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/utils/__init__.py +0 -0
  92. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/utils/ar.py +0 -0
  93. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/utils/ci.py +0 -0
  94. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/utils/frontmatter.py +0 -0
  95. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/utils/global_mode.py +0 -0
  96. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/utils/l10n.py +0 -0
  97. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/utils/markdown.py +0 -0
  98. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/utils/mounts.py +0 -0
  99. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/utils/node_info.py +0 -0
  100. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/utils/nuitka.py +0 -0
  101. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/utils/porcelain.py +0 -0
  102. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/utils/templating.py +0 -0
  103. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/utils/toml.py +0 -0
  104. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/utils/url.py +0 -0
  105. {ruyi-0.44.0a20251118 → ruyi-0.45.0}/ruyi/utils/xdg_basedir.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ruyi
3
- Version: 0.44.0a20251118
3
+ Version: 0.45.0
4
4
  Summary: Package manager for RuyiSDK
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -224,6 +224,7 @@ Classifier: Topic :: System :: Software Distribution
224
224
  Classifier: Typing :: Typed
225
225
  Requires-Dist: argcomplete (>=2.0.0)
226
226
  Requires-Dist: arpy
227
+ Requires-Dist: babel (>=2.8.0)
227
228
  Requires-Dist: fastjsonschema (>=2.15.1)
228
229
  Requires-Dist: jinja2 (>=3,<4)
229
230
  Requires-Dist: pygit2 (>=1.6) ; python_version >= "3.11"
@@ -408,7 +409,8 @@ described below.
408
409
  There are 3 telemetry modes available:
409
410
 
410
411
  * `local`: data will be collected but not uploaded without user action.
411
- * `off`: data will not be collected nor uploaded.
412
+ * `off`: data will neither be collected nor uploaded, except for a one-time
413
+ upload of `ruyi`'s version number on first run.
412
414
  * `on`: data will be collected and periodically uploaded.
413
415
 
414
416
  By default the `local` mode is active from `ruyi` 0.42.0 (inclusive) on, which
@@ -163,7 +163,8 @@ described below.
163
163
  There are 3 telemetry modes available:
164
164
 
165
165
  * `local`: data will be collected but not uploaded without user action.
166
- * `off`: data will not be collected nor uploaded.
166
+ * `off`: data will neither be collected nor uploaded, except for a one-time
167
+ upload of `ruyi`'s version number on first run.
167
168
  * `on`: data will be collected and periodically uploaded.
168
169
 
169
170
  By default the `local` mode is active from `ruyi` 0.42.0 (inclusive) on, which
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [project]
6
6
  name = "ruyi"
7
- version = "0.44.0-alpha.20251118"
7
+ version = "0.45.0"
8
8
  description = "Package manager for RuyiSDK"
9
9
  keywords = ["ruyi", "ruyisdk"]
10
10
  license = { file = "LICENSE-Apache.txt" }
@@ -32,6 +32,7 @@ requires-python = ">=3.10"
32
32
  dependencies = [
33
33
  "argcomplete>=2.0.0",
34
34
  "arpy",
35
+ "babel>=2.8.0",
35
36
  "fastjsonschema>=2.15.1",
36
37
  "jinja2 (>=3, <4)",
37
38
  "pygit2 (>=1.6, <1.19); python_version<'3.11'",
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
3
  import os
4
+ import pathlib
4
5
  import sys
5
6
 
6
7
  import ruyi
8
+ from ruyi.i18n import _, ADAPTER
7
9
  from ruyi.utils.ci import is_running_in_ci
8
10
  from ruyi.utils.global_mode import (
9
11
  EnvGlobalModeProvider,
@@ -41,6 +43,8 @@ def _is_allowed_to_run_as_root() -> bool:
41
43
 
42
44
  def entrypoint() -> None:
43
45
  gm = EnvGlobalModeProvider(os.environ, sys.argv)
46
+ ADAPTER.init_from_env(os.environ)
47
+ ADAPTER.hook()
44
48
 
45
49
  # NOTE: import of `ruyi.log` takes ~90ms on my machine, so initialization
46
50
  # of logging is deferred as late as possible
@@ -50,11 +54,16 @@ def entrypoint() -> None:
50
54
 
51
55
  logger = RuyiConsoleLogger(gm)
52
56
 
53
- logger.F("refusing to run as super user outside CI without explicit consent")
57
+ logger.F(_("refusing to run as super user outside CI without explicit consent"))
54
58
 
55
59
  choices = ", ".join(f"'{x}'" for x in TRUTHY_ENV_VAR_VALUES)
56
60
  logger.I(
57
- f"re-run with environment variable [yellow]{ENV_FORCE_ALLOW_ROOT}[/] set to one of [yellow]{choices}[/] to signify consent"
61
+ _(
62
+ "re-run with environment variable [yellow]{env_var}[/] set to one of [yellow]{choices}[/] to signify consent"
63
+ ).format(
64
+ env_var=ENV_FORCE_ALLOW_ROOT,
65
+ choices=choices,
66
+ )
58
67
  )
59
68
  sys.exit(1)
60
69
 
@@ -63,7 +72,7 @@ def entrypoint() -> None:
63
72
 
64
73
  logger = RuyiConsoleLogger(gm)
65
74
 
66
- logger.F("no argv?")
75
+ logger.F(_("no argv?"))
67
76
  sys.exit(1)
68
77
 
69
78
  if gm.is_packaged and ruyi.__compiled__.standalone:
@@ -85,8 +94,11 @@ def entrypoint() -> None:
85
94
  #
86
95
  # we assume the one-file build if Nuitka is detected; sys.argv[0] does NOT
87
96
  # work if it's just `ruyi` so we have to check our parent process in that case
88
- self_exe = get_nuitka_self_exe() if gm.is_packaged else __file__
89
97
  sys.argv[0] = get_argv0()
98
+ if gm.is_packaged:
99
+ self_exe = get_nuitka_self_exe()
100
+ else:
101
+ self_exe = str(pathlib.Path(sys.argv[0]).resolve())
90
102
  gm.record_self_exe(sys.argv[0], __file__, self_exe)
91
103
 
92
104
  from ruyi.config import GlobalConfig
@@ -1,6 +1,7 @@
1
1
  import argparse
2
2
  from typing import Callable, IO, Protocol, TYPE_CHECKING
3
3
 
4
+ from ..i18n import _
4
5
  from . import RUYI_ENTRYPOINT_NAME
5
6
 
6
7
  if TYPE_CHECKING:
@@ -114,7 +115,7 @@ class BaseCommand:
114
115
  return
115
116
 
116
117
  sp = p.add_subparsers(
117
- title="subcommands",
118
+ title=_("subcommands"),
118
119
  required=cls.is_subcommand_required,
119
120
  )
120
121
  for subcmd_cls in cls.parsers:
@@ -158,7 +159,7 @@ class RootCommand(
158
159
  has_subcommands=True,
159
160
  has_main=True,
160
161
  prog=RUYI_ENTRYPOINT_NAME,
161
- description="RuyiSDK Package Manager",
162
+ description=_("RuyiSDK Package Manager"),
162
163
  ):
163
164
  @classmethod
164
165
  def configure_args(cls, gc: "GlobalConfig", p: "ArgumentParser") -> None:
@@ -170,12 +171,12 @@ class RootCommand(
170
171
  action="store_const",
171
172
  dest="func",
172
173
  const=cli_version,
173
- help="Print version information",
174
+ help=_("Print version information"),
174
175
  )
175
176
  p.add_argument(
176
177
  "--porcelain",
177
178
  action="store_true",
178
- help="Give the output in a machine-friendly format if applicable",
179
+ help=_("Give the output in a machine-friendly format if applicable"),
179
180
  )
180
181
 
181
182
  # https://github.com/python/cpython/issues/67037 prevents the registration
@@ -219,7 +220,7 @@ class AdminCommand(
219
220
  has_subcommands=True,
220
221
  # https://github.com/python/cpython/issues/67037
221
222
  # help=argparse.SUPPRESS,
222
- help="(NOT FOR REGULAR USERS) Subcommands for managing Ruyi repos",
223
+ help=_("(NOT FOR REGULAR USERS) Subcommands for managing Ruyi repos"),
223
224
  ):
224
225
  @classmethod
225
226
  def configure_args(cls, gc: "GlobalConfig", p: "ArgumentParser") -> None:
@@ -1,6 +1,7 @@
1
1
  import argparse
2
2
  from typing import TYPE_CHECKING
3
3
 
4
+ from ..i18n import _
4
5
  from .cmd import RootCommand
5
6
 
6
7
  if TYPE_CHECKING:
@@ -13,7 +14,7 @@ class ConfigCommand(
13
14
  RootCommand,
14
15
  cmd="config",
15
16
  has_subcommands=True,
16
- help="Manage Ruyi's config options",
17
+ help=_("Manage Ruyi's config options"),
17
18
  ):
18
19
  @classmethod
19
20
  def configure_args(
@@ -27,7 +28,7 @@ class ConfigCommand(
27
28
  class ConfigGetCommand(
28
29
  ConfigCommand,
29
30
  cmd="get",
30
- help="Query the value of a Ruyi config option",
31
+ help=_("Query the value of a Ruyi config option"),
31
32
  ):
32
33
  @classmethod
33
34
  def configure_args(
@@ -38,7 +39,7 @@ class ConfigGetCommand(
38
39
  p.add_argument(
39
40
  "key",
40
41
  type=str,
41
- help="The Ruyi config option to query",
42
+ help=_("The Ruyi config option to query"),
42
43
  )
43
44
 
44
45
  @classmethod
@@ -66,7 +67,7 @@ class ConfigGetCommand(
66
67
  class ConfigSetCommand(
67
68
  ConfigCommand,
68
69
  cmd="set",
69
- help="Set the value of a Ruyi config option",
70
+ help=_("Set the value of a Ruyi config option"),
70
71
  ):
71
72
  @classmethod
72
73
  def configure_args(
@@ -77,12 +78,12 @@ class ConfigSetCommand(
77
78
  p.add_argument(
78
79
  "key",
79
80
  type=str,
80
- help="The Ruyi config option to set",
81
+ help=_("The Ruyi config option to set"),
81
82
  )
82
83
  p.add_argument(
83
84
  "value",
84
85
  type=str,
85
- help="The value to set the option to",
86
+ help=_("The value to set the option to"),
86
87
  )
87
88
 
88
89
  @classmethod
@@ -100,7 +101,9 @@ class ConfigSetCommand(
100
101
  ed.set_value(key, pyval)
101
102
  except ProtectedGlobalConfigError:
102
103
  cfg.logger.F(
103
- f"the config [yellow]{key}[/] is protected and not meant to be overridden by users",
104
+ _(
105
+ "the config [yellow]{key}[/] is protected and not meant to be overridden by users"
106
+ ).format(key=key)
104
107
  )
105
108
  return 2
106
109
 
@@ -112,7 +115,7 @@ class ConfigSetCommand(
112
115
  class ConfigUnsetCommand(
113
116
  ConfigCommand,
114
117
  cmd="unset",
115
- help="Unset a Ruyi config option",
118
+ help=_("Unset a Ruyi config option"),
116
119
  ):
117
120
  @classmethod
118
121
  def configure_args(
@@ -123,7 +126,7 @@ class ConfigUnsetCommand(
123
126
  p.add_argument(
124
127
  "key",
125
128
  type=str,
126
- help="The Ruyi config option to unset",
129
+ help=_("The Ruyi config option to unset"),
127
130
  )
128
131
 
129
132
  @classmethod
@@ -142,7 +145,7 @@ class ConfigUnsetCommand(
142
145
  class ConfigRemoveSectionCommand(
143
146
  ConfigCommand,
144
147
  cmd="remove-section",
145
- help="Remove a section from the Ruyi config",
148
+ help=_("Remove a section from the Ruyi config"),
146
149
  ):
147
150
  @classmethod
148
151
  def configure_args(
@@ -153,7 +156,7 @@ class ConfigRemoveSectionCommand(
153
156
  p.add_argument(
154
157
  "section",
155
158
  type=str,
156
- help="The section to remove",
159
+ help=_("The section to remove"),
157
160
  )
158
161
 
159
162
  @classmethod
@@ -4,6 +4,7 @@ import sys
4
4
  from typing import Final, TYPE_CHECKING
5
5
 
6
6
  from ..config import GlobalConfig
7
+ from ..i18n import _
7
8
  from ..telemetry.scope import TelemetryScope
8
9
  from ..utils.global_mode import GlobalModeProvider
9
10
  from ..version import RUYI_SEMVER
@@ -39,32 +40,40 @@ def main(gm: GlobalModeProvider, gc: GlobalConfig, argv: list[str]) -> int:
39
40
  if not gm.is_cli_autocomplete:
40
41
  oobe = OOBE(gc)
41
42
 
42
- if tm := gc.telemetry:
43
- tm.check_first_run_status()
44
- tm.init_installation(False)
45
- atexit.register(tm.flush)
46
- oobe.handlers.append(tm.oobe_prompt)
43
+ tm = gc.telemetry
44
+ tm.check_first_run_status()
45
+ tm.init_installation(False)
46
+ atexit.register(tm.flush)
47
+ oobe.handlers.append(tm.oobe_prompt)
47
48
 
48
49
  oobe.maybe_prompt()
49
50
 
50
51
  if not is_called_as_ruyi(gm.argv0):
51
52
  if should_prompt_for_renaming(gm.argv0):
52
53
  logger.F(
53
- f"the {RUYI_ENTRYPOINT_NAME} executable must be named [green]'{RUYI_ENTRYPOINT_NAME}'[/] to work"
54
+ _(
55
+ "the {ruyi_exe} executable must be named [green]'{expected_name}'[/] to work"
56
+ ).format(
57
+ ruyi_exe=RUYI_ENTRYPOINT_NAME,
58
+ expected_name=RUYI_ENTRYPOINT_NAME,
59
+ )
54
60
  )
55
- logger.I(f"it is now [yellow]'{gm.argv0}'[/]")
56
- logger.I("please rename the command file and retry")
61
+ logger.I(
62
+ _("it is now [yellow]'{current_name}'[/]").format(
63
+ current_name=gm.argv0,
64
+ )
65
+ )
66
+ logger.I(_("please rename the command file and retry"))
57
67
  return 1
58
68
 
59
69
  from ..mux.runtime import mux_main
60
70
 
61
71
  # record an invocation and the command name being proxied to
62
- if tm := gc.telemetry:
63
- tm.record(
64
- TelemetryScope(None),
65
- "cli:mux-invocation-v1",
66
- target=os.path.basename(gm.argv0),
67
- )
72
+ gc.telemetry.record(
73
+ TelemetryScope(None),
74
+ "cli:mux-invocation-v1",
75
+ target=os.path.basename(gm.argv0),
76
+ )
68
77
 
69
78
  return mux_main(gm, gc, argv)
70
79
 
@@ -116,7 +125,7 @@ def main(gm: GlobalModeProvider, gc: GlobalConfig, argv: list[str]) -> int:
116
125
  try:
117
126
  telemetry_key: str = args.tele_key
118
127
  except AttributeError:
119
- logger.F("internal error: CLI entrypoint was added without a telemetry key")
128
+ logger.F(_("internal error: CLI entrypoint was added without a telemetry key"))
120
129
  return 1
121
130
 
122
131
  # Special-case the `--output-completion-script` argument; treat it as if
@@ -127,8 +136,16 @@ def main(gm: GlobalModeProvider, gc: GlobalConfig, argv: list[str]) -> int:
127
136
  except AttributeError:
128
137
  pass
129
138
 
130
- if tm := gc.telemetry:
131
- tm.print_telemetry_notice()
139
+ tm = gc.telemetry
140
+ tm.print_telemetry_notice()
141
+
142
+ # Do not record `ruyi telemetry --cron-upload` invocations.
143
+ skip_recording_invocation = telemetry_key == "telemetry" and getattr(
144
+ args,
145
+ "cron_upload",
146
+ False,
147
+ )
148
+ if not skip_recording_invocation:
132
149
  tm.record(
133
150
  TelemetryScope(None),
134
151
  "cli:invocation-v1",
@@ -2,13 +2,16 @@
2
2
 
3
3
  import os
4
4
  import sys
5
- from typing import Callable, TYPE_CHECKING
5
+ from typing import Callable, Final, TYPE_CHECKING
6
+
7
+ from ..i18n import _, d_
6
8
 
7
9
  if TYPE_CHECKING:
8
10
  from ..config import GlobalConfig
9
11
 
10
12
 
11
- SHELL_AUTO_COMPLETION_TIP = """
13
+ SHELL_AUTO_COMPLETION_TIP: Final = d_(
14
+ """
12
15
  [bold green]tip[/]: you can enable shell auto-completion for [yellow]ruyi[/] by adding the
13
16
  following line to your [green]{shrc}[/], if you have not done so already:
14
17
 
@@ -18,6 +21,7 @@ You can do so by running the following command later:
18
21
 
19
22
  [green]echo 'eval "$(ruyi --output-completion-script={shell})"' >> {shrc}[/]
20
23
  """
24
+ )
21
25
 
22
26
 
23
27
  class OOBE:
@@ -30,13 +34,9 @@ class OOBE:
30
34
  ]
31
35
 
32
36
  def is_first_run(self) -> bool:
33
- if tm := self._gc.telemetry:
34
- return tm.is_first_run
35
- # cannot reliably determine first run status without telemetry
36
- # we may revisit this later if it turns out users want OOBE tips even
37
- # if they know how to disable telemetry (hence more likely to be power
38
- # users)
39
- return False
37
+ # We now always have our first-run indicator because of the minimal
38
+ # telemetry mode.
39
+ return self._gc.telemetry.is_first_run
40
40
 
41
41
  def should_prompt(self) -> bool:
42
42
  from ..utils.global_mode import is_env_var_truthy
@@ -78,7 +78,7 @@ class OOBE:
78
78
  return
79
79
 
80
80
  self._gc.logger.stdout(
81
- SHELL_AUTO_COMPLETION_TIP.format(
81
+ _(SHELL_AUTO_COMPLETION_TIP).format(
82
82
  shell=shell,
83
83
  shrc=f"~/.{shell}rc",
84
84
  )
@@ -4,13 +4,16 @@ import pathlib
4
4
  import shutil
5
5
  from typing import Final, TYPE_CHECKING
6
6
 
7
+ from ..i18n import _, d_
7
8
  from .cmd import RootCommand
8
9
 
9
10
  if TYPE_CHECKING:
10
11
  from .completion import ArgumentParser
11
12
  from .. import config
12
13
 
13
- UNINSTALL_NOTICE: Final = """
14
+
15
+ UNINSTALL_NOTICE: Final = d_(
16
+ """
14
17
  [bold]Thanks for hacking with [yellow]Ruyi[/]![/]
15
18
 
16
19
  This will uninstall [yellow]Ruyi[/] from your system, and optionally remove
@@ -21,6 +24,7 @@ Note that your [yellow]Ruyi[/] virtual environments [bold]will become unusable[/
21
24
  [yellow]Ruyi[/] is uninstalled. You should take care of migrating or cleaning
22
25
  them yourselves afterwards.
23
26
  """
27
+ )
24
28
 
25
29
 
26
30
  # Self-management commands
@@ -28,7 +32,7 @@ class SelfCommand(
28
32
  RootCommand,
29
33
  cmd="self",
30
34
  has_subcommands=True,
31
- help="Manage this Ruyi installation",
35
+ help=_("Manage this Ruyi installation"),
32
36
  ):
33
37
  @classmethod
34
38
  def configure_args(
@@ -42,7 +46,7 @@ class SelfCommand(
42
46
  class SelfCleanCommand(
43
47
  SelfCommand,
44
48
  cmd="clean",
45
- help="Remove various Ruyi-managed data to reclaim storage",
49
+ help=_("Remove various Ruyi-managed data to reclaim storage"),
46
50
  ):
47
51
  @classmethod
48
52
  def configure_args(
@@ -54,42 +58,42 @@ class SelfCleanCommand(
54
58
  "--quiet",
55
59
  "-q",
56
60
  action="store_true",
57
- help="Do not print out the actions being performed",
61
+ help=_("Do not print out the actions being performed"),
58
62
  )
59
63
  p.add_argument(
60
64
  "--all",
61
65
  action="store_true",
62
- help="Remove all covered data",
66
+ help=_("Remove all covered data"),
63
67
  )
64
68
  p.add_argument(
65
69
  "--distfiles",
66
70
  action="store_true",
67
- help="Remove all downloaded distfiles if any",
71
+ help=_("Remove all downloaded distfiles if any"),
68
72
  )
69
73
  p.add_argument(
70
74
  "--installed-pkgs",
71
75
  action="store_true",
72
- help="Remove all installed packages if any",
76
+ help=_("Remove all installed packages if any"),
73
77
  )
74
78
  p.add_argument(
75
79
  "--news-read-status",
76
80
  action="store_true",
77
- help="Mark all news items as unread",
81
+ help=_("Mark all news items as unread"),
78
82
  )
79
83
  p.add_argument(
80
84
  "--progcache",
81
85
  action="store_true",
82
- help="Clear the Ruyi program cache",
86
+ help=_("Clear the Ruyi program cache"),
83
87
  )
84
88
  p.add_argument(
85
89
  "--repo",
86
90
  action="store_true",
87
- help="Remove the Ruyi repo if located in Ruyi-managed cache directory",
91
+ help=_("Remove the Ruyi repo if located in Ruyi-managed cache directory"),
88
92
  )
89
93
  p.add_argument(
90
94
  "--telemetry",
91
95
  action="store_true",
92
- help="Remove all telemetry data recorded if any",
96
+ help=_("Remove all telemetry data recorded if any"),
93
97
  )
94
98
 
95
99
  @classmethod
@@ -122,9 +126,11 @@ class SelfCleanCommand(
122
126
  telemetry,
123
127
  ]
124
128
  ):
125
- logger.F("no data specified for cleaning")
129
+ logger.F(_("no data specified for cleaning"))
126
130
  logger.I(
127
- "please check [yellow]ruyi self clean --help[/] for a list of cleanable data"
131
+ _(
132
+ "please check [yellow]ruyi self clean --help[/] for a list of cleanable data"
133
+ )
128
134
  )
129
135
  return 1
130
136
 
@@ -149,7 +155,7 @@ class SelfCleanCommand(
149
155
  class SelfUninstallCommand(
150
156
  SelfCommand,
151
157
  cmd="uninstall",
152
- help="Uninstall Ruyi",
158
+ help=_("Uninstall Ruyi"),
153
159
  ):
154
160
  @classmethod
155
161
  def configure_args(
@@ -160,13 +166,15 @@ class SelfUninstallCommand(
160
166
  p.add_argument(
161
167
  "--purge",
162
168
  action="store_true",
163
- help="Remove all installed packages and Ruyi-managed remote repo data",
169
+ help=_("Remove all installed packages and Ruyi-managed remote repo data"),
164
170
  )
165
171
  p.add_argument(
166
172
  "-y",
167
173
  action="store_true",
168
174
  dest="consent",
169
- help="Give consent for uninstallation on CLI; do not ask for confirmation",
175
+ help=_(
176
+ "Give consent for uninstallation on CLI; do not ask for confirmation"
177
+ ),
170
178
  )
171
179
 
172
180
  @classmethod
@@ -180,24 +188,28 @@ class SelfUninstallCommand(
180
188
 
181
189
  if cfg.is_installation_externally_managed:
182
190
  logger.F(
183
- "this [yellow]ruyi[/] is externally managed, for example, by the system package manager, and cannot be uninstalled this way"
191
+ _(
192
+ "this [yellow]ruyi[/] is externally managed, for example, by the system package manager, and cannot be uninstalled this way"
193
+ )
184
194
  )
185
- logger.I("please uninstall via the external manager instead")
195
+ logger.I(_("please uninstall via the external manager instead"))
186
196
  return 1
187
197
 
188
198
  if not cfg.is_packaged:
189
199
  logger.F(
190
- "this [yellow]ruyi[/] is not in standalone form, and cannot be uninstalled this way"
200
+ _(
201
+ "this [yellow]ruyi[/] is not in standalone form, and cannot be uninstalled this way"
202
+ )
191
203
  )
192
204
  return 1
193
205
 
194
206
  if not consent:
195
- logger.stdout(UNINSTALL_NOTICE)
196
- if not user_input.ask_for_yesno_confirmation(logger, "Continue?"):
197
- logger.I("aborting uninstallation")
207
+ logger.stdout(_(UNINSTALL_NOTICE))
208
+ if not user_input.ask_for_yesno_confirmation(logger, _("Continue?")):
209
+ logger.I(_("aborting uninstallation"))
198
210
  return 0
199
211
  else:
200
- logger.I("uninstallation consent given over CLI, proceeding")
212
+ logger.I(_("uninstallation consent given over CLI, proceeding"))
201
213
 
202
214
  _do_reset(
203
215
  cfg,
@@ -208,7 +220,7 @@ class SelfUninstallCommand(
208
220
  self_binary=True,
209
221
  )
210
222
 
211
- logger.I("[yellow]ruyi[/] is uninstalled")
223
+ logger.I(_("[yellow]ruyi[/] is uninstalled"))
212
224
 
213
225
  return 0
214
226
 
@@ -235,38 +247,37 @@ def _do_reset(
235
247
  logger.I(s)
236
248
 
237
249
  if installed_pkgs:
238
- status("removing installed packages")
250
+ status(_("removing installed packages"))
239
251
  shutil.rmtree(cfg.data_root, True)
240
252
  cfg.ruyipkg_global_state.purge_installation_info()
241
253
 
242
254
  # do not record any telemetry data if we're purging it
243
255
  if all_state or telemetry:
244
- if tm := cfg.telemetry:
245
- tm.discard_events(True)
256
+ cfg.telemetry.discard_events(True)
246
257
 
247
258
  if all_state:
248
- status("removing state data")
259
+ status(_("removing state data"))
249
260
  shutil.rmtree(cfg.state_root, True)
250
261
  else:
251
262
  if news_read_status:
252
- status("removing read status of news items")
263
+ status(_("removing read status of news items"))
253
264
  cfg.news_read_status.remove()
254
265
 
255
266
  if telemetry:
256
- status("removing all telemetry data")
267
+ status(_("removing all telemetry data"))
257
268
  shutil.rmtree(cfg.telemetry_root, True)
258
269
 
259
270
  if all_cache:
260
- status("removing cached data")
271
+ status(_("removing cached data"))
261
272
  shutil.rmtree(cfg.cache_root, True)
262
273
  else:
263
274
  if distfiles:
264
- status("removing downloaded distfiles")
275
+ status(_("removing downloaded distfiles"))
265
276
  # TODO: deduplicate the path derivation
266
277
  shutil.rmtree(os.path.join(cfg.cache_root, "distfiles"), True)
267
278
 
268
279
  if progcache:
269
- status("clearing the Ruyi program cache")
280
+ status(_("clearing the Ruyi program cache"))
270
281
  # TODO: deduplicate the path derivation
271
282
  shutil.rmtree(os.path.join(cfg.cache_root, "progcache"), True)
272
283
 
@@ -284,14 +295,16 @@ def _do_reset(
284
295
 
285
296
  if not repo_is_below_cache_root:
286
297
  logger.W(
287
- "not removing the Ruyi repo: it is outside of the Ruyi cache directory"
298
+ _(
299
+ "not removing the Ruyi repo: it is outside of the Ruyi cache directory"
300
+ )
288
301
  )
289
302
  else:
290
- status("removing the Ruyi repo")
303
+ status(_("removing the Ruyi repo"))
291
304
  shutil.rmtree(repo_dir, True)
292
305
 
293
306
  if self_binary:
294
- status("removing the ruyi binary")
307
+ status(_("removing the ruyi binary"))
295
308
  try:
296
309
  os.unlink(cfg.self_exe)
297
310
  except FileNotFoundError: