-
Notifications
You must be signed in to change notification settings - Fork 0
Phase 36 catalog plan
Part of the Amiga port design log.
amiga.catalog wraps locale.library's catalog lookup so
localized apps can read their translated strings, plus surfaces
the system's preferred language.
from amiga import catalog
print(catalog.language()) # 'english' / 'german' / 'français'
# Stock-Workbench tip: pass built_in_language= so locale.library
# actually loads a translation file rather than short-circuiting
# on "you already have English built in".
with catalog.open("MyApp.catalog", version=1,
language="english",
built_in_language="german") as cat:
print(cat.lookup(1, "Default English string"))
print(cat.lookup(2, "Cancel"))| Call | Returns | Notes |
|---|---|---|
catalog.open(name, version=0, language=None, built_in_language=None) |
Catalog |
OpenCatalogA(NULL, name, [OC_Version, OC_BuiltInLanguage?, OC_Language?, TAG_DONE]). OSError(ENOENT) if locale.library returns NULL (catalog not found, or language matches built_in_language so nothing to load). OSError(EIO) if locale.library itself can't open. |
catalog.language() |
str |
First preferred language from Locale->loc_PrefLanguages[0]. Returns "english" if no preference is set or locale.library is unavailable. |
catalog.Catalog |
type | Re-exported so isinstance(c, catalog.Catalog) works. |
Catalog methods:
| Op | Notes |
|---|---|
cat.lookup(id, default) |
GetCatalogStr(cat, id, default). Returns the catalog string or the default if id is absent. Defaults to AmigaOS contract: never raises on a missing entry. A closed catalog also returns the default (the NULL is forwarded straight into GetCatalogStr). |
cat.close() / __del__
|
CloseCatalog. Idempotent. |
with catalog.open(...) as cat: |
__enter__ / __exit__ close the catalog on block exit. |
built_in_language= kwarg note: AmigaOS OpenCatalog short-circuits
when the requested language matches the catalog's built-in
language — there's nothing to load, so it returns NULL. To force a
translation lookup even when asking for the "same" language as the
binary's built-in strings (e.g. when probing English-only catalogs
on an English Workbench), pass a different code via
built_in_language. The implementation step plan below has a
worked example.
- Writing catalogs (
flexcat-style compilation). That's a build- time tool, not a runtime API. - Conversion of locale-specific date / number / currency formats
— separate phase if needed; would wrap
FormatStringetc. - Multi-catalog merging / fallback chains —
OpenCatalogalready picks the right language automatically. - Exposing
Localedirectly.language()covers the one field callers actually want; the rest ofstruct Localeis a read-only system snapshot that would invite lifetime confusion.
-
locale.libraryv38+ (OS 2.1). Lazy open; no explicit close.
This section is the step-by-step ship plan — how to chunk the work into landable PRs. The design above answers what and why.
Step 1: _catalog C module + Catalog type + language() + vamos smoke
↓
Step 2: docs flip + closing tests
| # | Step | Output | On-target smoke |
|---|---|---|---|
| 1 |
ports/amiga/modcatalog.c registering _catalog. open(name, version=0, language=None) → Catalog. Catalog.lookup(id, default) → str. Catalog.close() / __enter__ / __exit__ / __del__. Module-level language(). Wired through Makefile + frozen amiga.py. |
New C module + facade entry. | Under vamos: import works, alias amiga.catalog is _catalog, missing catalog → OSError, language() returns a non-empty string when vamos exposes a Locale. Under Amiberry: catalog.open("workbench.catalog") round-trip with lookup against a known string id. |
| 2 | Docs flip + comprehensive tests. |
docs/amiga.md Phase 36 → ✅; docs/amiga-testing.md gains a catalog subsection; tests/ports/amiga/test_catalog_smoke.py covers the surface. |
Amiberry verification of language() + lookup against a system catalog. |
Step 1 is ~150 LOC C; Step 2 is paperwork.
-
ports/amiga/modcatalog.c(~150 LOC). Module registered as_catalogviaMP_REGISTER_MODULE(MP_QSTR__catalog, ...), matching the_intuition/_asl/_iconconvention. - Module globals:
-
open(name, version=0, language=None)→Catalog. CallsOpenCatalogA(NULL, name, [OC_Version, OC_Language?, TAG_DONE]). RaisesOSError(ENOENT)on NULL return. -
language()→str.OpenLocale(NULL)→ first non-NULLloc_PrefLanguages[0]. Falls back to"english"if no preference, returns immediately iflocale.librarycan't open. -
Catalogtype symbol re-exported on the module.
-
-
CatalogMicroPython type:- Wraps
struct Catalog *cat. NULL after.close(). -
.lookup(id, default)—GetCatalogStr(cat, id, default). Returns the catalog string if present, the default otherwise. Matches the AmigaOS contract (no exception on miss). -
.close()—CloseCatalog. Idempotent. -
__del__/__exit__forward to.close(). -
__enter__returns self forwithuse.
- Wraps
-
ports/amiga/Makefile:SRC_C += modcatalog.cSRC_QSTR += modcatalog.c
-
ports/amiga/modules/amiga.py:import _catalog as catalog
-
LocaleBaseis opened lazily on first call (OpenLibrary ("locale.library", 38)). No explicit close —locale.libraryis a system-wide library that AmigaOS reaps at process exit; this matches the intuition / icon / asl pattern. -
OpenCatalogAis the tag-list form; build a small stack array:struct TagItem tags[4]; int n = 0; tags[n].ti_Tag = OC_Version; tags[n].ti_Data = version; n++; if (language != NULL) { tags[n].ti_Tag = OC_Language; tags[n].ti_Data = (ULONG)language; n++; } tags[n].ti_Tag = TAG_DONE;
-
GetCatalogStraccepts a NULL catalog and returns the default in that case; we don't need to guard. - The default-string lifetime contract:
GetCatalogStrreturns either the catalog's own string (held bylocale.library) or the caller's default pointer. The catalog-string case is safe to copy viamp_obj_new_strbecause the lifetime tracks the Catalog object the Python caller owns.
Vamos smoke (tests/ports/amiga/test_catalog_smoke.py):
import _catalog
import amiga
assert _catalog is amiga.catalog
assert callable(_catalog.open)
assert callable(_catalog.language)
# language() should always return a non-empty str.
lang = _catalog.language()
assert isinstance(lang, str) and len(lang) > 0
# A catalog that definitely doesn't exist -> OSError.
try:
_catalog.open("nonexistent_phase36.catalog")
except OSError:
pass
else:
raise AssertionError("expected OSError")
print("OK")Amiberry interactive verification:
>>> from amiga import catalog
>>> catalog.language()
'english'
>>> with catalog.open("workbench.catalog", version=44) as cat:
... print(cat.lookup(1, "fallback"))
...
<some workbench string>-
docs/amiga.mdPhase 36 status → ✅; section gains a full surface matrix (open/lookup/close/language). -
docs/amiga-testing.mdgains acatalogsubsection with Amiberry REPL examples coveringlanguage()andlookup. -
tests/ports/amiga/test_catalog_smoke.pyexpanded:- Module + alias +
Catalogtype re-export. - Missing-catalog OSError path.
-
language()returns str. - With-statement /
__enter__/__exit__round trip. - Closed-object
.lookupreturns the default (GetCatalogStrforwards a NULL catalog to the caller's default; no exception — matches the AmigaOS contract).
- Module + alias +
Step 1 added a built_in_language= kwarg beyond the original
scope so on-target round-trip testing works on a stock English
Workbench. AmigaOS OpenCatalog refuses to open when the
requested language matches the catalog's built-in language
(nothing to load); the kwarg surfaces OC_BuiltInLanguage so
callers can force a translation file lookup anyway.
-
locale.libraryversion. v38 (OS 2.1) is the baseline. Earlier Kickstarts don't ship it;open()raisesOSError(EIO)cleanly in that case. -
String encoding. Catalog strings are byte sequences in
whatever code set the catalog declares. We surface them as
Python
str(latin-1 trip-safe), matching the rest of the port's string handling. A future change could exposecat_CodeSetif anyone needs strict UTF-8 conversion. -
Vamos coverage. vamos doesn't ship a real
locale.library; the smoke test only confirms the surface doesn't crash, not actual catalog lookups. Amiberry covers the round trip.
- Writing catalogs (
flexcat-style compilation). That's a build-time tool, not a runtime API. - Conversion of locale-specific date / number / currency formats
— separate phase if needed; would wrap
FormatStringetc. - Multi-catalog merging / fallback chains —
OpenCatalogalready picks the right language automatically. - Exposing
Localedirectly (it's the system Locale, not a per-application object;language()covers the one field callers actually want).
ports/amiga/modcatalog.c — C module + Catalog type
ports/amiga/modules/amiga.py — `import _catalog as catalog`
tests/ports/amiga/test_catalog_smoke.py — vamos arg-shape smoke
Variants: all three. ~1 KB text per variant.