Replies: 10 comments 11 replies
-
|
@bryndin - here are some thoughts on your suggestion, not in any important order
|
Beta Was this translation helpful? Give feedback.
-
|
@bryndin Could you please add a section right at the start of this PR, describing what problem this PR is trying to solve. (I am not in any way trying to criticise this change, just want to make sure that the purpose is clearly understood). I noticed that when I published my CensusCheck addon gramps-project/addons-source#814, the template.pot that @GaryGriffin was working on was for Gramps 6.1, although the current Gramps was 6.0. Even if the template was for 6.0, translations would only happen after publication of the addon, so there was no chance that translations could get picked up if the addon was downloaded by a user immediately. I suppose this might be the problem you are trying to solve, but I'm not sure, and a clearer explanation (clearer than the rather rushed one I am typing now) would be enormously helpful. |
Beta Was this translation helpful? Give feedback.
-
|
@eduralph @GaryGriffin @Nick-Hall I registered at discource, but I can't post the link to this RFC there. Getting a bunch of weird errors:
Update: Apparently, my account there is on hold. To be honest, I'd prefer to spend more time on RFCs and code, than fighting the signup systems, getting approvals, and getting help around the bugs in these systems. By now, I have 4 (four) separate accounts in Github, Gramps bugtracker, Gramps Wiki (not working, bugged out signup), and Gramps Discourse (got approval, but banned now). It might help to review the contributor processes to decrease the friction, as it's not very productive. #letthebuidersbuild. |
Beta Was this translation helpful? Give feedback.
-
|
Discourse automated moderation is suspicious of new users who 'paste' their first postings. It assumes that means a 'bot is spamming the forum and 'silences' them. Then a human moderator has to override. Sorry that I did not recognize your user name and left it for Nick. (Although if something more than a username was filled in on the form... like OS or Gramps version... then I would not have hesitated.) Unfortunately, when Discourse silences because of "typed too fast", it does not show the draft to the moderator. Nick has upgraded your account (2 steps) from "New User" to "Member". Looking forward to seeing your posting. |
Beta Was this translation helpful? Give feedback.
-
|
@bryndin Based on an initial review of the RFC -
|
Beta Was this translation helpful? Give feedback.
-
Personally, I dont think the workload as the maintainer should even be considered. I spend negligible time on translation-related work.
I would argue that the burden is on the Weblate user adding translations. The user also has a burden due to lack of recent translation strings for addons that have not been recently published. It is negligible for maintainters, developers. Right now,
Current case:
|
Beta Was this translation helpful? Give feedback.
-
GeneralAIUI, two proposals would address somewhat similar issues:
but I think that this proposal is much better, because it does not introduce a very complicated parallel versioning scheme, and instead ensures that the addon version is tied to the Gramps version, through Gramps update vs steady stateThere are two different situations:
I don't think that this proposal helps the upgrade state. This is not a criticism, just a comment, because I think that solving the upgrade state is worth what is probably a big complex fix. (During the upgrade state, no new translations are possible for the old version). MotivationThe proposal says:
But actually I think the problem is worse than that.
@GaryGriffin Could you please confirm that this is all correct? Suggestion of addon manager updatesI suspect that strings in addons (for a given Gramps version) are rarely updated, so I suggest that for a given Gramps version, once a string is extracted and made available to Weblate, that string is never deleted. Because such changes are probably rare, the cost should be small, and it resolves some of the problems for users who want to retain old versions of addons. I suggest that the addon manager be enhanced to display two checkboxes, one for addon and one for translation, so users can choose to update only the translation, or only the addon, or both. I suggest that the default for checking for updates be changed to once a week (or once a month), and translations for installed addons be automatically selected, and automatically installed without further user intervention (the progress window could tell users where to access the Addon Manager to turn off this option). Users can of course change this default. I presume that network traffic for this (and so delay) could be quite small using compression. It could be further optimised by only getting translations for languages the user is actually using (is it possible to tell?) |
Beta Was this translation helpful? Give feedback.
-
I believe your problem description is correct. |
Beta Was this translation helpful? Give feedback.
-
We should also consider the option of keeping the addon translations aggregated. In this way the standard Gramps translator could be used for addons with the main catalog being |
Beta Was this translation helpful? Give feedback.
-
|
Let me just throw this heresy here, since I've already mentioned it among other things: I'm not sure Weblate is still a good solution, given LLMs can handle translations within seconds. Even if AI translation is hallucinated, users can report a bug and propose a better translation. It's a nuance, not a blocker. Switching from Weblate to LLM-based translation removes all the problems with backporting translations, handling older releases, and so on.😎👌🔥 |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
RFC: Redesigning the L10n Architecture for Gramps Addons
Status: Proposed
Reviewers: @Nick-Hall @eduralph @GaryGriffin
Subject: Decoupling Gramps Addon localization from addon releases
1. Abstract
This RFC defines a decoupled architecture for Gramps Addon localization. The current workflow couples translation updates to addon releases, requiring manual maintainer intervention and delaying translation improvements for users. By treating localization as independent content delivered over-the-air (OTA), translation updates become independent of addon code releases. This reduces maintainer effort, simplifies repository management, and ensures runtime safety through validated localization artifacts.
2. Motivation
The current localization workflow utilizes a centralized translation platform (Weblate) but relies on a decentralized, manual distribution pipeline. This creates several bottlenecks:
Maintainer Burden: Maintainers must manually pull from Weblate, run extraction scripts (
make.py extract-po), and bundle.mofiles into.tgzrelease archives.Release Coupling: A typo fix in a translation requires a full version bump and release of the addon code.
Repository Pollution: Mixing source code with
.potranslation data confuses contributors and bloats the git history ofaddons-source.Fragility: A malformed translation string (e.g., mismatched
%svariables) can crash the Gramps client if not caught manually.Contributor burden: Translation details distract contributors from addon development.
This RFC decouples localization from addon development and release management, enabling automated translation delivery while preserving compatibility with the existing GNU
gettextecosystem.3. L10n Architecture
The localization system is composed of several independent components. Each component has a single responsibility and communicates with the others through well-defined interfaces.
The architecture does not prescribe how these components are implemented or where they are hosted. The concrete implementation is described later in the Reference Implementation section.
3.1 Architectural Principles
The redesign is based on the following principles.
.mofiles using standardgettexttools.3.2 System Components
Extractor
Extracts translatable strings from the source code files and produces translation templates.
The extractor is triggered whenever the source code changes.
Source (aka Translator)
Provides the user interface where translators create and maintain translations.
The translation source is responsible only for authoring translations. It has no responsibility for packaging or distributing them.
Repository
Stores translation source files independently of addon source code.
It is the authoritative source for localization data and provides the input for translation builds.
Builder
Transforms translation source files into runtime artifacts suitable for distribution.
The builder is responsible for validating translations before publication.
It guarantees that invalid translations cannot prevent valid translations from being published and cannot reach end users.
Registry
Publishes information about available localization artifacts.
The registry allows clients to discover available translations, determine whether updates exist, and retrieve the metadata required to validate downloaded artifacts.
The registry is implementation-independent. Multiple registries may coexist.
Manager
A client-side subsystem responsible for discovering, downloading, validating, caching, and loading localization artifacts.
It presents translations to the runtime through the standard
gettextinterface and is independent of the underlying distribution mechanism.Resolver
Runtime logic used in the source code, responsible for loading and applying the correct translations.
4. Architectural Invariants
The following properties are required regardless of implementation.
4.1 Text Domain Isolation
Each addon owns an independent
gettexttext domain. Translation catalogs are never shared between addons.4.2 Independent Versioning
Localization artifacts are versioned independently of addon releases. Updating translations does not require publishing a new addon version.
4.3 Failure Isolation
Invalid translations must never:
When validation fails, clients must safely fall back to English source strings.
4.4 Atomic Updates
Localization updates must be installed atomically. Clients must never observe partially installed translation data.
4.5 Multiple Translation Providers
The architecture supports multiple translation registries. If multiple registries provide translations for the same addon, the end-user defines the deterministic precedence ordering.
5. Runtime Model
The client maintains a local cache of installed localization artifacts.
When the source code initializes translations, the Resolver resolves directories using the following precedence:
Localization artifacts are downloaded, validated, and installed atomically.
The runtime always prefers newer localization artifacts while preserving compatibility with bundled translations.
6. Security Considerations
The OTA payload relies on SHA-256 hashing to ensure network transit integrity. Preventing supply-chain attacks via cryptographic signing (e.g., GPG) is considered Out of Scope for this RFC, as it should be handled holistically by the Gramps project for all plugin downloads in the future.
7. Migration Strategy
Migration can be performed incrementally.
This approach allows existing code to continue functioning throughout the transition.
8. Reference Implementation
This section describes the planned implementation for the Gramps project.
8.1 L10n Paths
Since the plugin update overwrites the plugin directory, we have to decouple the source code from the translations.
~/.gramps/gramps60/plugins/<plugin-name>(and its equivalents on Windows and macOS), as they were before.~/.gramps/gramps60/plugins_locale/<plugin-name>.If we later decide to decouple Gramps Core translations from its releases,
~/.gramps/gramps60/locale/will work similarly.8.2 Server-Side Infrastructure
Extractor will reuse the existing
make.pyinitandaggregate-potstages. The GitHub Action checks out bothaddons-sourceand the Gramps Coregrampsrepository, allowingaggregate-potto remove strings already translated by Gramps Core. It generates both the individual addontemplate.potfiles and the aggregated translation template. When a PR merges intoaddons-source, the workflow commits the updated templates into theaddons-translationsrepository. See the Repository Ownership section below.Repository: To preserve a Git-backed source of truth for disaster recovery while keeping code history clean, a new repository (
addons-translations) will be created.Artifact Generation: A nightly Builder GitHub Actions cron job pulls the latest translation repository. It first reconstructs per-addon translation catalogs by matching the aggregated translations against each addon's
template.pot. After validation, it compiles per-addon.mofiles and packages them into{addon}_l10n.tgzarchives. These archives contain only thelocale/directory structure. See Translation Catalog Organization for the the artifiact storage opions.Plural-Formsheaders are invalid. This prevents the compiler from generating structurally corrupted.mofiles that could crash the client.polibto prune per-entry offending entries.Binary Hosting (GitHub Releases): To prevent git history bloat, the generated
.tgzfiles are attached to a continuous, rolling tag (e.g.,l10n-gramps60-latest) via GitHub Releases. We store only the latest build.The Registry (
l10n_index.json): A static manifest file is generated and hosted ongh-pages. This acts as a fast, highly-available index for the Gramps client to check for updates.ETag/If-None-Match(gh-pages supports it) to avoid refetching unchanged.Test Isolation: L10n formatting tests reside strictly within this new CI pipeline. Standard python/UI tests remain in
addons-source.Repository Ownership
Because both the automated Extractor (pushing
.potfiles) and Weblate (pushing.pofiles) will write to theaddons-translationsrepository, strict ownership and concurrency controls are required to prevent Git tree conflicts and Weblate merge locks.The implementation may choose one of the following strategies.
Option A (preferred): Branch Isolation
The Extractor commits generated translation templates to a dedicated branch. Weblate maintains translations on its own working branch. The Builder combines the required inputs during the build process. Automated systems must never modify another system's working branch or perform force-pushes to it.
Option B: Pull Request Workflow
Instead of committing directly, the Extractor opens PR containing updated translation templates. Weblate remains the sole automated writer of the primary translation branch. Translation updates are incorporated through the normal review and merge process.
Translation Catalog Organization
The implementation requires both aggregated translations for efficient translation authoring and per-addon catalogs for runtime distribution.
Option A (preferred): Per-addon + Aggregate
The Extractor generates both the individual addon
template.potfiles and an aggregated translation template. Translators work with the aggregated catalog, while the Builder reconstructs per-addon catalogs by matching the aggregated translations against each addon'stemplate.potbefore compiling the runtime.mofiles.This approach keeps the Builder self-contained while preserving the current translation workflow.
Option B: Weblate handles per-Addon Translation Sources
Instead of maintaining a single aggregated translation catalog, the Repository stores independent translation catalogs for each addon. The Builder compiles these catalogs directly into the corresponding runtime artifacts without requiring a reconstruction step.
This approach simplifies the build pipeline at the cost of increased repository organization and translation management complexity.
8.3 Client-Side Resolution
Fetch & Hash Check: The Gramps Addon Manager periodically (using its existing schedule functionality) fetches
l10n_index.json. It iterates through installed addons and compares the remotelatest_hashagainst a locally stored hash.Global Extraction: If an l10n update exists, the client downloads the
.tgzand extracts it into a global user directory:~/.gramps/gramps60/plugins_locale/.~/.gramps/gramps60/tmp(same filesystem).latest_hashin the Registry.os.replace()each.mofile individually into place. Each file swap is atomic; crash mid-loop leaves some languages new and some old, which is fine — they're independent domains.Path Injection (
gettext): Gramps Core's initialization logicGRAMPS_LOCALE(the Resolver) will be modified. We have two options:Option A: by utilizing
gettext.bindtextdomainto follow Runtime Model and try~/.gramps/gramps60/plugins_locale/, the OTA translations natively take precedence over any legacylocale/directories bundled within the addon itself.Option B (preferred): have a per-message fallback via
Manager uninstall lifecycle:
Manager: handles the network fetch itself. The Addon Manager's fetch logic must gracefully handle offline states, CDN outages, and rate limits without throwing user-facing errors, silently failing back to the previously cached
.mofiles or legacy bundled translations.During the migration phase, the Resolver will fall back to legacy bundled
.mofiles. Post-migration, when addons no longer bundle translations, an offline or failed OTA fetch will simply result in the addon rendering its hardcoded English source strings.Extractor cleanup: Post migration, we will change to the existing
make.py do_tarlogic. The packaging script will be configured to cease bundling the locallocale/directories into the primary addon.tgzfiles.8.4 Translation Registry
The registry is implemented as a static JSON document generated by the build pipeline. It is published as static content.
The schema:
{ "schema_version": 1, "gramps_target": "6.0", "addons": { "CardView": { "latest_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "timestamp": "2023-10-27T10:00:00Z", "source_revision": "abc123d4e5f6...", "archive_url": "https://cdn.example.com/CardView_l10n.tgz", "languages": ["en", "es", "fr"] } } }schema_version: the Registry schema version. A placeholder in case we will need to change it in the future.gramps_target: the Gramps version that the translations are compatible with.latest_hash: the SHA-256 checksum of the.tgzpayload.timestamp: Formatted inRFC3339 UTC.source_revision: The exact Git commit hash from the Repository used to generate the build, ensuring traceability.archive_url: The URL to the.tgzpayload.languages: The list of languages supported by the addon, so the client can skip downloading an artifact that lacks the user's language.9. Known Limitations
Users who don't update addon code
User runs CardView v1.0 (code contains msgid
"Foo"). CardView v1.1 renames"Foo"→"Foobar", translators localize it, Builder ships the latest artifact whose.monow contains only"Foobar". The non-updating user's v1.0 code requests"Foo"→ OTA.molacks it → English, losing a translation they previously had.Option A: (preferred) Users refusing an update are on their own.
This is a rare case not worth the overengineering. With the Client-Side Resolution Path Injection, the users can update translations locally for themselves.
Option B: Don't deprecate the pipeline bundling translations with the addons. Addon contributors can customize their translations.
The extra complexity of two concurrent pipelines is confusing and more bug-prone. Translations are a nuance, not a blocker.
Option C: Develop the
addon <-> translationversion. Store all the translation versions. Manager is responsible for matching.Significantly complicates the solution and requires extra storage space, for the corner case benefits. It's overengineering.
Appendix
Compatibility of the Latest-Only Model
This architecture distributes only the latest localization artifact for each addon. Translation artifacts are not versioned alongside addon releases.
This model is compatible with the way
gettextresolves translations.As a result, translation updates remain backward compatible without requiring localization artifacts to be tied to specific addon versions.
Gramps Addons - Current l10n process
Gramps Addons - Current l10n process
Beta Was this translation helpful? Give feedback.
All reactions