pythonic-peano-arithmetic 0.3.0__py3-none-any.whl → 0.4.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.
peano/__init__.py CHANGED
@@ -28,7 +28,7 @@ from .rational import (
28
28
  rational,
29
29
  z2r,
30
30
  )
31
- from .utils import config_log
31
+ from .utils import SUPPORTED_LOCALES, config_log
32
32
 
33
33
  __all__ = [
34
34
  "NaturalNumber",
@@ -63,4 +63,5 @@ __all__ = [
63
63
  "AlgebraicRoot",
64
64
  "algebraic_root",
65
65
  "config_log",
66
+ "SUPPORTED_LOCALES",
66
67
  ]
peano/algebraic_root.py CHANGED
@@ -8,7 +8,7 @@ from fractions import Fraction
8
8
  from .natural_number import NaturalNumber
9
9
  from .polynomial import Polynomial, count_real_roots
10
10
  from .rational import Rational, rational
11
- from .utils import LogMessage, localized, log
11
+ from .utils import LogMessage, log, translate
12
12
 
13
13
 
14
14
  @dataclass(frozen=True, slots=True)
@@ -132,9 +132,10 @@ def _bisect(
132
132
  result = RationalInterval(midpoint, midpoint)
133
133
  return (
134
134
  result,
135
- lambda: localized(
136
- f"{polynomial_value!r}: midpoint {midpoint!r} is a root",
137
- f"{polynomial_value!r}: 中点 {midpoint!r} は根",
135
+ lambda: translate(
136
+ "midpoint_root",
137
+ polynomial=repr(polynomial_value),
138
+ midpoint=repr(midpoint),
138
139
  ),
139
140
  )
140
141
 
peano/natural_number.py CHANGED
@@ -6,7 +6,7 @@ from dataclasses import dataclass
6
6
  from functools import total_ordering
7
7
  from typing import TYPE_CHECKING, Iterator, cast
8
8
 
9
- from .utils import LogMessage, localized, log
9
+ from .utils import LogMessage, log, translate
10
10
 
11
11
  if TYPE_CHECKING:
12
12
  from .integer import Integer
@@ -67,7 +67,7 @@ class NaturalNumber:
67
67
  return (
68
68
  result,
69
69
  lambda: (
70
- f"{localized('[equality: zero case]', '[等値・0の場合]')} "
70
+ f"{translate('equality.zero')} "
71
71
  f"eq({self.structural_str()}, "
72
72
  f"{other.structural_str()}) -> {result}"
73
73
  ),
@@ -75,7 +75,7 @@ class NaturalNumber:
75
75
  return (
76
76
  left_predecessor == right_predecessor,
77
77
  lambda: (
78
- f"{localized('[equality: successor case]', '[等値・後者の場合]')} "
78
+ f"{translate('equality.successor')} "
79
79
  f"eq({self.structural_str()}, "
80
80
  f"{other.structural_str()}) -> "
81
81
  f"eq({left_predecessor.structural_str()}, "
@@ -128,7 +128,7 @@ class NaturalNumber:
128
128
  return (
129
129
  self,
130
130
  lambda: (
131
- f"{localized('[addition: base]', '[加法・基底]')} "
131
+ f"{translate('addition.base')} "
132
132
  f"add({self.structural_str()}, 0) "
133
133
  f"-> {self.structural_str()}"
134
134
  ),
@@ -136,7 +136,7 @@ class NaturalNumber:
136
136
  return (
137
137
  successor(self + predecessor),
138
138
  lambda: (
139
- f"{localized('[addition: recursive]', '[加法・再帰]')} "
139
+ f"{translate('addition.recursive')} "
140
140
  f"add({self.structural_str()}, "
141
141
  f"{other.structural_str()}) -> "
142
142
  f"S(add({self.structural_str()}, {predecessor.structural_str()}))"
@@ -173,14 +173,14 @@ class NaturalNumber:
173
173
  return (
174
174
  N_ZERO,
175
175
  lambda: (
176
- f"{localized('[multiplication: base]', '[乗法・基底]')} "
176
+ f"{translate('multiplication.base')} "
177
177
  f"mul({self.structural_str()}, 0) -> 0"
178
178
  ),
179
179
  )
180
180
  return (
181
181
  self + self * predecessor,
182
182
  lambda: (
183
- f"{localized('[multiplication: recursive]', '[乗法・再帰]')} "
183
+ f"{translate('multiplication.recursive')} "
184
184
  f"mul({self.structural_str()}, "
185
185
  f"{other.structural_str()}) -> "
186
186
  f"add({self.structural_str()}, "
peano/utils.py CHANGED
@@ -22,14 +22,199 @@ logger.disabled = True
22
22
  P = ParamSpec("P")
23
23
  T = TypeVar("T")
24
24
  LogMessage = str | Callable[[], str]
25
+ Locale = Literal[
26
+ "en",
27
+ "ja",
28
+ "zh-Hans",
29
+ "zh-Hant",
30
+ "es",
31
+ "pt-BR",
32
+ "fr",
33
+ "de",
34
+ "ko",
35
+ "ru",
36
+ "ar",
37
+ "hi",
38
+ ]
39
+ SUPPORTED_LOCALES: tuple[Locale, ...] = (
40
+ "en",
41
+ "ja",
42
+ "zh-Hans",
43
+ "zh-Hant",
44
+ "es",
45
+ "pt-BR",
46
+ "fr",
47
+ "de",
48
+ "ko",
49
+ "ru",
50
+ "ar",
51
+ "hi",
52
+ )
25
53
  _PEANO_HANDLER_MARKER = "_peano_handler"
26
54
  _LOG_LIMIT_NOTICE = "_peano_log_limit_notice"
27
- _locale: Literal["en", "ja"] = "en"
55
+ _locale: Locale = "en"
56
+
57
+ _MESSAGES: dict[Locale, dict[str, str]] = {
58
+ "en": {
59
+ "equality.zero": "[equality: zero case]",
60
+ "equality.successor": "[equality: successor case]",
61
+ "addition.base": "[addition: base]",
62
+ "addition.recursive": "[addition: recursive]",
63
+ "multiplication.base": "[multiplication: base]",
64
+ "multiplication.recursive": "[multiplication: recursive]",
65
+ "truncated": (
66
+ "…Log output was truncated after {max_lines} lines. "
67
+ "Use smaller inputs and run the cell again."
68
+ ),
69
+ "midpoint_root": "{polynomial}: midpoint {midpoint} is a root",
70
+ },
71
+ "ja": {
72
+ "equality.zero": "[等値・0の場合]",
73
+ "equality.successor": "[等値・後者の場合]",
74
+ "addition.base": "[加法・基底]",
75
+ "addition.recursive": "[加法・再帰]",
76
+ "multiplication.base": "[乗法・基底]",
77
+ "multiplication.recursive": "[乗法・再帰]",
78
+ "truncated": (
79
+ "…ログは{max_lines}行で省略しました。入力を小さくして再実行してください。"
80
+ ),
81
+ "midpoint_root": "{polynomial}: 中点 {midpoint} は根",
82
+ },
83
+ "zh-Hans": {
84
+ "equality.zero": "[相等:零情形]",
85
+ "equality.successor": "[相等:后继情形]",
86
+ "addition.base": "[加法:基础情形]",
87
+ "addition.recursive": "[加法:递归情形]",
88
+ "multiplication.base": "[乘法:基础情形]",
89
+ "multiplication.recursive": "[乘法:递归情形]",
90
+ "truncated": "…日志在{max_lines}行后截断。请减小输入后重新运行。",
91
+ "midpoint_root": "{polynomial}:中点 {midpoint} 是根",
92
+ },
93
+ "zh-Hant": {
94
+ "equality.zero": "[相等:零情形]",
95
+ "equality.successor": "[相等:後繼情形]",
96
+ "addition.base": "[加法:基礎情形]",
97
+ "addition.recursive": "[加法:遞迴情形]",
98
+ "multiplication.base": "[乘法:基礎情形]",
99
+ "multiplication.recursive": "[乘法:遞迴情形]",
100
+ "truncated": "…記錄在{max_lines}行後截斷。請縮小輸入後重新執行。",
101
+ "midpoint_root": "{polynomial}:中點 {midpoint} 是根",
102
+ },
103
+ "es": {
104
+ "equality.zero": "[igualdad: caso cero]",
105
+ "equality.successor": "[igualdad: caso sucesor]",
106
+ "addition.base": "[suma: caso base]",
107
+ "addition.recursive": "[suma: caso recursivo]",
108
+ "multiplication.base": "[multiplicación: caso base]",
109
+ "multiplication.recursive": "[multiplicación: caso recursivo]",
110
+ "truncated": (
111
+ "…El registro se truncó tras {max_lines} líneas. "
112
+ "Reduce la entrada y vuelve a ejecutar."
113
+ ),
114
+ "midpoint_root": "{polynomial}: el punto medio {midpoint} es una raíz",
115
+ },
116
+ "pt-BR": {
117
+ "equality.zero": "[igualdade: caso zero]",
118
+ "equality.successor": "[igualdade: caso sucessor]",
119
+ "addition.base": "[adição: caso base]",
120
+ "addition.recursive": "[adição: caso recursivo]",
121
+ "multiplication.base": "[multiplicação: caso base]",
122
+ "multiplication.recursive": "[multiplicação: caso recursivo]",
123
+ "truncated": (
124
+ "…O log foi truncado após {max_lines} linhas. "
125
+ "Reduza a entrada e execute novamente."
126
+ ),
127
+ "midpoint_root": "{polynomial}: o ponto médio {midpoint} é uma raiz",
128
+ },
129
+ "fr": {
130
+ "equality.zero": "[égalité : cas zéro]",
131
+ "equality.successor": "[égalité : cas successeur]",
132
+ "addition.base": "[addition : cas de base]",
133
+ "addition.recursive": "[addition : cas récursif]",
134
+ "multiplication.base": "[multiplication : cas de base]",
135
+ "multiplication.recursive": "[multiplication : cas récursif]",
136
+ "truncated": (
137
+ "…Le journal a été tronqué après {max_lines} lignes. "
138
+ "Réduisez les entrées et relancez."
139
+ ),
140
+ "midpoint_root": "{polynomial} : le milieu {midpoint} est une racine",
141
+ },
142
+ "de": {
143
+ "equality.zero": "[Gleichheit: Nullfall]",
144
+ "equality.successor": "[Gleichheit: Nachfolgerfall]",
145
+ "addition.base": "[Addition: Basisfall]",
146
+ "addition.recursive": "[Addition: Rekursionsfall]",
147
+ "multiplication.base": "[Multiplikation: Basisfall]",
148
+ "multiplication.recursive": "[Multiplikation: Rekursionsfall]",
149
+ "truncated": (
150
+ "…Die Protokollausgabe wurde nach {max_lines} Zeilen gekürzt. "
151
+ "Verkleinern Sie die Eingaben und führen Sie die Zelle erneut aus."
152
+ ),
153
+ "midpoint_root": "{polynomial}: Der Mittelpunkt {midpoint} ist eine Nullstelle",
154
+ },
155
+ "ko": {
156
+ "equality.zero": "[같음: 0인 경우]",
157
+ "equality.successor": "[같음: 후속자 경우]",
158
+ "addition.base": "[덧셈: 기저 경우]",
159
+ "addition.recursive": "[덧셈: 재귀 경우]",
160
+ "multiplication.base": "[곱셈: 기저 경우]",
161
+ "multiplication.recursive": "[곱셈: 재귀 경우]",
162
+ "truncated": (
163
+ "…로그를 {max_lines}줄에서 줄였습니다. 입력을 작게 바꾸고 다시 실행하세요."
164
+ ),
165
+ "midpoint_root": "{polynomial}: 중점 {midpoint}은(는) 근입니다",
166
+ },
167
+ "ru": {
168
+ "equality.zero": "[равенство: случай нуля]",
169
+ "equality.successor": "[равенство: случай следующего]",
170
+ "addition.base": "[сложение: базовый случай]",
171
+ "addition.recursive": "[сложение: рекурсивный случай]",
172
+ "multiplication.base": "[умножение: базовый случай]",
173
+ "multiplication.recursive": "[умножение: рекурсивный случай]",
174
+ "truncated": (
175
+ "…Журнал обрезан после {max_lines} строк. "
176
+ "Уменьшите входные данные и запустите ячейку снова."
177
+ ),
178
+ "midpoint_root": "{polynomial}: середина {midpoint} является корнем",
179
+ },
180
+ "ar": {
181
+ "equality.zero": "[المساواة: حالة الصفر]",
182
+ "equality.successor": "[المساواة: حالة الخليفة]",
183
+ "addition.base": "[الجمع: الحالة الأساسية]",
184
+ "addition.recursive": "[الجمع: الحالة العودية]",
185
+ "multiplication.base": "[الضرب: الحالة الأساسية]",
186
+ "multiplication.recursive": "[الضرب: الحالة العودية]",
187
+ "truncated": (
188
+ "…اختُصر السجل بعد {max_lines} سطرًا. صغّر المدخلات ثم شغّل الخلية من جديد."
189
+ ),
190
+ "midpoint_root": "{polynomial}: نقطة المنتصف {midpoint} جذر",
191
+ },
192
+ "hi": {
193
+ "equality.zero": "[समानता: शून्य स्थिति]",
194
+ "equality.successor": "[समानता: उत्तराधिकारी स्थिति]",
195
+ "addition.base": "[जोड़: आधार स्थिति]",
196
+ "addition.recursive": "[जोड़: पुनरावर्ती स्थिति]",
197
+ "multiplication.base": "[गुणा: आधार स्थिति]",
198
+ "multiplication.recursive": "[गुणा: पुनरावर्ती स्थिति]",
199
+ "truncated": (
200
+ "…लॉग {max_lines} पंक्तियों के बाद छोटा कर दिया गया। इनपुट घटाकर सेल फिर चलाएँ।"
201
+ ),
202
+ "midpoint_root": "{polynomial}: मध्यबिंदु {midpoint} एक मूल है",
203
+ },
204
+ }
205
+
206
+
207
+ def translate(key: str, **values: object) -> str:
208
+ """Return a runtime message in the configured locale."""
209
+
210
+ return _MESSAGES[_locale][key].format(**values)
28
211
 
29
212
 
30
213
  def localized(english: str, japanese: str) -> str:
31
- """Return a localized runtime message."""
214
+ """Return an English/Japanese compatibility message.
32
215
 
216
+ New runtime messages should use :func:`translate`.
217
+ """
33
218
  return japanese if _locale == "ja" else english
34
219
 
35
220
 
@@ -41,11 +226,7 @@ class _LineLimitFormatter(Formatter):
41
226
  if not isinstance(max_lines, int):
42
227
  return super().format(record)
43
228
  notice = copy(record)
44
- notice.msg = localized(
45
- f"…Log output was truncated after {max_lines} lines. "
46
- "Use smaller inputs and run the cell again.",
47
- f"…ログは{max_lines}行で省略しました。入力を小さくして再実行してください。",
48
- )
229
+ notice.msg = translate("truncated", max_lines=max_lines)
49
230
  notice.args = ()
50
231
  return super().format(notice)
51
232
 
@@ -143,7 +324,7 @@ def config_log(
143
324
  fmt: str = "%(message)s",
144
325
  clear_handlers: bool = True,
145
326
  max_lines: int | None = None,
146
- locale: Literal["en", "ja"] = "en",
327
+ locale: Locale = "en",
147
328
  ) -> None:
148
329
  """Write symbolic evaluation logs to standard error.
149
330
 
@@ -151,7 +332,8 @@ def config_log(
151
332
  existing root handlers and manages only the handler installed here.
152
333
  ``max_lines`` emits one truncation notice after the limit. Pass
153
334
  ``"Level %(levelno)s: %(message)s"`` as ``fmt`` to expose internal levels.
154
- Runtime labels are English by default; ``locale="ja"`` selects Japanese.
335
+ Runtime labels are English by default. ``SUPPORTED_LOCALES`` lists the
336
+ available translations.
155
337
  """
156
338
 
157
339
  global _locale
@@ -159,8 +341,9 @@ def config_log(
159
341
  isinstance(max_lines, bool) or not isinstance(max_lines, int) or max_lines < 1
160
342
  ):
161
343
  raise ValueError("max_lines must be a positive integer or None")
162
- if locale not in ("en", "ja"):
163
- raise ValueError("locale must be 'en' or 'ja'")
344
+ if locale not in SUPPORTED_LOCALES:
345
+ supported = ", ".join(SUPPORTED_LOCALES)
346
+ raise ValueError(f"locale must be one of: {supported}")
164
347
  _locale = locale
165
348
 
166
349
  root_logger = getLogger()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonic-peano-arithmetic
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Build arithmetic from Peano axioms and trace algebraic real roots
5
5
  Keywords: arithmetic,education,mathematics,peano
6
6
  Author: Yusuke Hayashi
@@ -35,10 +35,20 @@ The implementation is intentionally small and explicit. Its purpose is to make
35
35
  the correspondence between a mathematical definition and executable Python
36
36
  visible—not to compete with Python's built-in numeric types.
37
37
 
38
- The interactive course runs entirely in the browser with Zensical and Pyodide:
39
-
40
- - [English course](https://peano.yusuke-hayashi.com/en/)
41
- - [日本語の教材](https://peano.yusuke-hayashi.com/)
38
+ The interactive course runs entirely in the browser with Zensical and Pyodide.
39
+ It is available in
40
+ [English](https://peano.yusuke-hayashi.com/en/),
41
+ [日本語](https://peano.yusuke-hayashi.com/),
42
+ [简体中文](https://peano.yusuke-hayashi.com/zh/),
43
+ [繁體中文](https://peano.yusuke-hayashi.com/zh-hant/),
44
+ [Español](https://peano.yusuke-hayashi.com/es/),
45
+ [Português (Brasil)](https://peano.yusuke-hayashi.com/pt-br/),
46
+ [Français](https://peano.yusuke-hayashi.com/fr/),
47
+ [Deutsch](https://peano.yusuke-hayashi.com/de/),
48
+ [한국어](https://peano.yusuke-hayashi.com/ko/),
49
+ [Русский](https://peano.yusuke-hayashi.com/ru/),
50
+ [العربية](https://peano.yusuke-hayashi.com/ar/), and
51
+ [हिन्दी](https://peano.yusuke-hayashi.com/hi/).
42
52
 
43
53
  ## What you can observe
44
54
 
@@ -98,7 +108,9 @@ n + 0 = n
98
108
  n + S(m) = S(n + m)
99
109
  ```
100
110
 
101
- Pass `locale="ja"` to `config_log` to show Japanese rule labels.
111
+ Pass a BCP 47 locale to `config_log` to localize rule labels. Supported values
112
+ are `en`, `ja`, `zh-Hans`, `zh-Hant`, `es`, `pt-BR`, `fr`, `de`, `ko`, `ru`,
113
+ `ar`, and `hi`.
102
114
 
103
115
  ### Integers: equality of representatives
104
116
 
@@ -220,10 +232,10 @@ intermediate Peano representations; returned endpoints are still `Rational`.
220
232
  ## Documentation development
221
233
 
222
234
  ```bash
223
- make docs # build Japanese at / and English at /en/
235
+ make docs # build all 12 course languages
224
236
  make docs-serve # preview Japanese
225
237
  make docs-serve-en # preview English
226
- make docs-a11y # run WCAG checks on both languages
238
+ make docs-a11y # run WCAG checks on all 120 localized pages
227
239
  ```
228
240
 
229
241
  The site is deployed from `main` to Cloudflare Workers Static Assets. Deployment
@@ -0,0 +1,12 @@
1
+ peano/__init__.py,sha256=epwxqb9KrjeVrj2UvtTZqGpVyyRSNC2jhpIqfl59OjI,1224
2
+ peano/algebraic_root.py,sha256=_HcbF6pI4j5lOZQtLhHYpDhSkrNAtnGrcCMIJk51ov0,6190
3
+ peano/integer.py,sha256=TU9bRMfz5CgR_yt_Km9AMYIOiD7H3aAUFq9tic-FaKk,11348
4
+ peano/natural_number.py,sha256=swfvyCHIb1gFbofjsJ1K1V0Z2KLk1uCZcQXwpsMrIAM,11650
5
+ peano/polynomial.py,sha256=MicwzTsFhj4wuK1TJyMOBwjtmDwb6XXp8jCMXZQ-2aA,14785
6
+ peano/py.typed,sha256=OHLhUlJwzVblRS88hQOvYv5HXJG8mzugIVr_EYAOCmU,17
7
+ peano/rational.py,sha256=luLqfc4SHZAwwA6ZEHXaPHkPvzcXcmlS5D5MTsKmtts,10070
8
+ peano/utils.py,sha256=rzSp_ej65wcuMHklD520wMI4PkrPueL4PwOHU0FK160,14406
9
+ pythonic_peano_arithmetic-0.4.0.dist-info/licenses/LICENSE,sha256=E0sqyNNROMqWOfz0_Lem7-mLLWxj5QRMg3R30cUs9GQ,1071
10
+ pythonic_peano_arithmetic-0.4.0.dist-info/WHEEL,sha256=l3MmIxu8qaet7ng2J9fFoJnYGj8IREj7jXTbgsuzmy4,81
11
+ pythonic_peano_arithmetic-0.4.0.dist-info/METADATA,sha256=T0FLZbFGdOicAlveIfe3YBSO7HUgorZFfN6Xgk1d2c8,8298
12
+ pythonic_peano_arithmetic-0.4.0.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- peano/__init__.py,sha256=jBGPaHCGy70ES8dXHX05NtaNYbMEUMgjRT_56S7TRuQ,1180
2
- peano/algebraic_root.py,sha256=2728Iqu2z20sLD5fH1TjK5CjGdXFcYuRdnMtuZreIsk,6208
3
- peano/integer.py,sha256=TU9bRMfz5CgR_yt_Km9AMYIOiD7H3aAUFq9tic-FaKk,11348
4
- peano/natural_number.py,sha256=gfRKqrIFBYhagOIi9LQfqkk16ybGSzLKaP-NsH9zX6w,11817
5
- peano/polynomial.py,sha256=MicwzTsFhj4wuK1TJyMOBwjtmDwb6XXp8jCMXZQ-2aA,14785
6
- peano/py.typed,sha256=OHLhUlJwzVblRS88hQOvYv5HXJG8mzugIVr_EYAOCmU,17
7
- peano/rational.py,sha256=luLqfc4SHZAwwA6ZEHXaPHkPvzcXcmlS5D5MTsKmtts,10070
8
- peano/utils.py,sha256=JAOYjcwAQBigDpC_0KZIJGeXVuK7DhxRVBhyiWzBm7c,6195
9
- pythonic_peano_arithmetic-0.3.0.dist-info/licenses/LICENSE,sha256=E0sqyNNROMqWOfz0_Lem7-mLLWxj5QRMg3R30cUs9GQ,1071
10
- pythonic_peano_arithmetic-0.3.0.dist-info/WHEEL,sha256=l3MmIxu8qaet7ng2J9fFoJnYGj8IREj7jXTbgsuzmy4,81
11
- pythonic_peano_arithmetic-0.3.0.dist-info/METADATA,sha256=HtyjZ0fIcIL5NckCgAgZRYDat19DnFAgSBG9flCBj5A,7638
12
- pythonic_peano_arithmetic-0.3.0.dist-info/RECORD,,