From 46bfbad1bbcee4705588d3290ba0a148b514a2e6 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 14 Nov 2023 11:27:42 -0600 Subject: [PATCH 1/4] Add `locale.getlocale()` This returns the localization of the running CircuitPython, such as en_US, fr, etc. Additional changes are needed in build infrastructure since the string "en_US" should not appear to be translated in weblate, ever; instead the value comes from the translation metadata. Closes: #8602 --- Makefile | 2 +- locale/synthetic.po | 3 + .../unix/variants/coverage/mpconfigvariant.mk | 2 + py/circuitpy_defns.mk | 4 ++ py/circuitpy_mpconfig.mk | 3 + py/maketranslationdata.py | 5 +- shared-bindings/locale/__init__.c | 62 +++++++++++++++++++ tools/check_translations.py | 4 +- 8 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 locale/synthetic.po create mode 100644 shared-bindings/locale/__init__.c diff --git a/Makefile b/Makefile index c834e413b1..c24637d527 100644 --- a/Makefile +++ b/Makefile @@ -227,7 +227,7 @@ pseudoxml: all-source: locale/circuitpython.pot: all-source - find $(TRANSLATE_SOURCES) -type d \( $(TRANSLATE_SOURCES_EXC) \) -prune -o -type f \( -iname "*.c" -o -iname "*.h" \) -print | (LC_ALL=C sort) | xgettext -f- -L C -s --add-location=file --keyword=MP_ERROR_TEXT -o - | sed -e '/"POT-Creation-Date: /d' > $@ + find $(TRANSLATE_SOURCES) -type d \( $(TRANSLATE_SOURCES_EXC) \) -prune -o -type f \( -iname "*.c" -o -iname "*.h" \) -print | (LC_ALL=C sort) | xgettext -x locale/synthetic.po -f- -L C -s --add-location=file --keyword=MP_ERROR_TEXT -o - | sed -e '/"POT-Creation-Date: /d' > $@ # Historically, `make translate` updated the .pot file and ran msgmerge. # However, this was a frequent source of merge conflicts. Weblate can perform diff --git a/locale/synthetic.po b/locale/synthetic.po new file mode 100644 index 0000000000..7d9fc2e10e --- /dev/null +++ b/locale/synthetic.po @@ -0,0 +1,3 @@ +# Localization of the locale name is done automagically +msgid "en_US" +msgstr "" diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk index f48084e9dd..478285044c 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.mk +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -33,6 +33,7 @@ SRC_BITMAP := \ shared-bindings/audiomixer/MixerVoice.c \ shared-bindings/bitmaptools/__init__.c \ shared-bindings/displayio/Bitmap.c \ + shared-bindings/locale/__init__.c \ shared-bindings/rainbowio/__init__.c \ shared-bindings/struct/__init__.c \ shared-bindings/synthio/__init__.c \ @@ -82,6 +83,7 @@ CFLAGS += \ -DCIRCUITPY_DISPLAYIO_UNIX=1 \ -DCIRCUITPY_FUTURE=1 \ -DCIRCUITPY_GIFIO=1 \ + -DCIRCUITPY_LOCALE=1 \ -DCIRCUITPY_OS_GETENV=1 \ -DCIRCUITPY_RAINBOWIO=1 \ -DCIRCUITPY_STRUCT=1 \ diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index f3f81d9757..d96350b3f6 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -239,6 +239,9 @@ endif ifeq ($(CIRCUITPY_KEYPAD),1) SRC_PATTERNS += keypad/% endif +ifeq ($(CIRCUITPY_LOCALE),1) +SRC_PATTERNS += locale/% +endif ifeq ($(CIRCUITPY_MATH),1) SRC_PATTERNS += math/% endif @@ -544,6 +547,7 @@ $(filter $(SRC_PATTERNS), \ displayio/Colorspace.c \ fontio/Glyph.c \ imagecapture/ParallelImageCapture.c \ + locale/__init__.c \ math/__init__.c \ microcontroller/ResetReason.c \ microcontroller/RunMode.c \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index b3628a9c10..6f32185443 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -320,6 +320,9 @@ CFLAGS += -DCIRCUITPY_KEYPAD_KEYMATRIX=$(CIRCUITPY_KEYPAD_KEYMATRIX) CIRCUITPY_KEYPAD_SHIFTREGISTERKEYS ?= $(CIRCUITPY_KEYPAD) CFLAGS += -DCIRCUITPY_KEYPAD_SHIFTREGISTERKEYS=$(CIRCUITPY_KEYPAD_SHIFTREGISTERKEYS) +CIRCUITPY_LOCALE ?= $(CIRCUITPY_FULL_BUILD) +CFLAGS += -DCIRCUITPY_LOCALE=$(CIRCUITPY_LOCALE) + CIRCUITPY_MATH ?= 1 CFLAGS += -DCIRCUITPY_MATH=$(CIRCUITPY_MATH) diff --git a/py/maketranslationdata.py b/py/maketranslationdata.py index 8676aab1a5..28a868ae0d 100644 --- a/py/maketranslationdata.py +++ b/py/maketranslationdata.py @@ -92,7 +92,10 @@ def translate(translation_file, i18ns): unescaped = original for s in C_ESCAPES: unescaped = unescaped.replace(C_ESCAPES[s], s) - translation = table.gettext(unescaped) + if original == "en_US": + translation = table.info()["language"] + else: + translation = table.gettext(unescaped) # Add in carriage returns to work in terminals translation = translation.replace("\n", "\r\n") translations.append((original, translation)) diff --git a/shared-bindings/locale/__init__.c b/shared-bindings/locale/__init__.c new file mode 100644 index 0000000000..5ee71fc314 --- /dev/null +++ b/shared-bindings/locale/__init__.c @@ -0,0 +1,62 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * SPDX-FileCopyrightText: Copyright (c) 2023 Jeff Epler for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/obj.h" +#include "py/objtuple.h" + +//| def getlocale(path: str, times: Tuple[int, int]) -> None: +//| """Change the timestamp of a file.""" +//| ... +//| +STATIC mp_obj_t getlocale(void) { + + mp_rom_error_text_t locale_msg = MP_ERROR_TEXT("en_US"); + size_t len_with_nul = decompress_length(locale_msg); + size_t len = len_with_nul - 1; + char buf[len_with_nul]; + decompress(locale_msg, buf); + + mp_obj_t elements[] = { + mp_obj_new_str(buf, len), + MP_OBJ_NEW_QSTR(MP_QSTR_utf_hyphen_8) + }; + return mp_obj_new_tuple(MP_ARRAY_SIZE(elements), elements); +} +MP_DEFINE_CONST_FUN_OBJ_0(getlocale_obj, getlocale); + +STATIC const mp_rom_map_elem_t locale_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_locale) }, + { MP_ROM_QSTR(MP_QSTR_getlocale), MP_ROM_PTR(&getlocale_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(locale_module_globals, locale_module_globals_table); + +const mp_obj_module_t locale_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&locale_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_locale, locale_module); diff --git a/tools/check_translations.py b/tools/check_translations.py index 19bfa8afb0..e6cc1532a8 100644 --- a/tools/check_translations.py +++ b/tools/check_translations.py @@ -13,8 +13,10 @@ import polib template_filename = sys.argv[1] po_filenames = sys.argv[2:] +synthetic = polib.pofile("locale/synthetic.po") +synthetic_ids = set([x.msgid for x in synthetic]) template = polib.pofile(template_filename) -all_ids = set([x.msgid for x in template]) +all_ids = set([x.msgid for x in template]) - synthetic_ids for po_filename in po_filenames: print("Checking", po_filename) po_file = polib.pofile(po_filename) From d79bdbbe6bbd8c0862c896e410523b4de0497b09 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Tue, 14 Nov 2023 21:31:37 -0600 Subject: [PATCH 2/4] update test result --- tests/unix/extra_coverage.py.exp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/unix/extra_coverage.py.exp b/tests/unix/extra_coverage.py.exp index 01ef9beb97..ece0d1271f 100644 --- a/tests/unix/extra_coverage.py.exp +++ b/tests/unix/extra_coverage.py.exp @@ -56,10 +56,11 @@ audiomixer binascii bitmaptools cexample cmath collections cppexample displayio errno example_package gc hashlib heapq io json -math os platform qrio -rainbowio random re select -struct synthio sys time -traceback uctypes ulab zlib +locale math os platform +qrio rainbowio random re +select struct synthio sys +time traceback uctypes ulab +zlib me rainbowio random From 918e244c13076b4d280306081a5d90b7973cb43c Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 15 Nov 2023 07:22:02 -0600 Subject: [PATCH 3/4] Add some more synthetic messages .. so that nobody ever accidentally translates %S, %q, or %s. These only appear inside MP_ERROR_TEXT for technical reasons. --- locale/synthetic.po | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/locale/synthetic.po b/locale/synthetic.po index 7d9fc2e10e..7f814d23a3 100644 --- a/locale/synthetic.po +++ b/locale/synthetic.po @@ -1,3 +1,15 @@ # Localization of the locale name is done automagically msgid "en_US" msgstr "" + +# This string should never be translated, but for technical reasons it has to appear as an MP_ERROR_TEXT +msgid "%S" +msgstr "" + +# This string should never be translated, but for technical reasons it has to appear as an MP_ERROR_TEXT +msgid "%q" +msgstr "" + +# This string should never be translated, but for technical reasons it has to appear as an MP_ERROR_TEXT +msgid "%s" +msgstr "" From 43e7fcb2279a6d28886365bcd8277a63266346dc Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 15 Nov 2023 07:22:11 -0600 Subject: [PATCH 4/4] Fix the locale module docstring --- shared-bindings/locale/__init__.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/shared-bindings/locale/__init__.c b/shared-bindings/locale/__init__.c index 5ee71fc314..0ee0e8f8ab 100644 --- a/shared-bindings/locale/__init__.c +++ b/shared-bindings/locale/__init__.c @@ -27,9 +27,16 @@ #include "py/obj.h" #include "py/objtuple.h" -//| def getlocale(path: str, times: Tuple[int, int]) -> None: -//| """Change the timestamp of a file.""" -//| ... +//| """Locale support module""" +//| +//| def getlocale() -> None: +//| """Returns the current locale setting as a tuple ``(language code, "utf-8")`` +//| +//| The language code comes from the installed translation of CircuitPython, specifically the "Language:" code specified in the translation metadata. +//| This can be useful to allow modules coded in Python to show messages in the user's preferred language. +//| +//| Differences from CPython: No ``LC_*`` argument is permitted. +//| """ //| STATIC mp_obj_t getlocale(void) {