Skip to content

feat(blender): pt-BR interface translation + i18n tooling (spec 072)#185

Merged
danko-nobre merged 3 commits into
mainfrom
feat/spec-072-multilanguage-i18n
Jul 4, 2026
Merged

feat(blender): pt-BR interface translation + i18n tooling (spec 072)#185
danko-nobre merged 3 commits into
mainfrom
feat/spec-072-multilanguage-i18n

Conversation

@danko-nobre

@danko-nobre danko-nobre commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

What changed

Fills the Blender addon's empty i18n table with a real pt-BR locale plus the tooling that keeps it honest: a bpy-free core/i18n_locales/ package (one module per language), an AST extraction script + a reverse-coverage test, report/label routing through the table, and 592 translated msgids (100% catalog coverage).

Why

First non-English locale requested (Brazilian Portuguese). English stays the canonical msgid on every surface; adding the next language is a new module + a content pass, not a re-architecture.

How to test

  • uv run pytest tests/ (repo root) - includes the reverse-coverage guard (test_i18n_coverage.py).
  • uv run --project apps/blender mypy --config-file apps/blender/pyproject.toml
  • Blender 5.1: blender --background --factory-startup --python apps/blender/tests/run_operator_tests.py (297) + run_tests.py (8/8).
  • Manual pt-BR walk: Blender > Preferences > Interface > Language = Portuguese (Brazilian) + Translate Interface on. Panels, operators, tooltips and enums render pt-BR; fixed report messages translate; any unfilled string falls back to English.

Notes for review

  • First-pass copy, native review pending. Term calls flagged for the reviewer: Empty -> Vazio, Soft/Hard -> Suave/Rigido, stub -> esboco, and panel names partly kept English. Edit core/i18n_locales/pt_br.py freely - the reverse-coverage test guards structure, not word choice.
  • Deferred: 168 f-string dynamic report/label messages (interpolated values) stay English; living worklist via python scripts/blender/extract_i18n.py --unwrapped. Fixed-literal reports DO translate via report.py's injectable translator.
  • Design notes: report.py gained set_translator DI (translates report literals centrally; the Proscenio: prefix stays outside the translated text); i18n.py dropped _as_translations_dict (the fold moved into the package); the locale tables carry a per-file E501 ignore (tooltip/description strings run long).
  • Msgctxt: operators translate under "Operator", everything else under "*" (verified against bl_rna.translation_context).
  • Docs pt-BR (Docusaurus locale + 31 translated pages) landed directly on main per the docs-direct-to-main convention (commit 7dcdd027), not in this PR.

Checklist

  • Per-language lint and format pass (ruff 0.8.4 + 0.15.20, cspell)
  • Per-language typecheck passes (mypy strict)
  • Tests pass for the affected app (pytest 1002, operator suite 297, goldens 8/8)
  • Schemas validate against examples and fixtures (unaffected)
  • No format_version change

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Expanded i18n across many UI labels, dialogs, and panel messages, including confirmation and status text.
    • Report messages now support runtime translation so user-visible report strings appear in the active language.
  • Bug Fixes
    • Improved translation consistency for fixed-literal report messages and localized dialog labels.
  • Tests
    • Added/extended tests to validate i18n extraction, locale folding, Blender registration wiring, and pt-BR catalog coverage/round-tripping.
  • Chores
    • Updated spellcheck term lists and adjusted linting for generated i18n files.

The addon's i18n table shipped empty; this fills it with a real pt-BR
locale and guards it against drift.

- core/i18n_locales/ holds one bpy-free module per locale; i18n.py stays a
  thin assembler that folds + registers them.
- scripts/blender/extract_i18n.py (ast) + a reverse-coverage test re-extract
  the catalog and fail on an untranslated new string or a stale row.
- Dynamic strings route through the table: report.py gains an injectable
  translator (fixed-literal reports translate; the Proscenio: prefix stays
  outside), and layout.label literals are wrapped in iface().
- pt_br.py carries 592 translated msgids (100% catalog). f-string report and
  label messages are deferred (see extract_i18n.py --unwrapped).

Operators translate under the "Operator" msgctxt, everything else under "*"
(bl_rna.translation_context). English stays the canonical msgid; adding a
language is a new module plus a content pass.

Refs: specs/072-multilanguage-i18n

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds per-locale Blender translation tables, registers them with Blender, routes report messages through a runtime translator, extracts translatable msgids from addon source, and localizes many UI strings with iface(...).

Changes

i18n rollout

Layer / File(s) Summary
Locale tables and folding
apps/blender/core/i18n_locales/_types.py, apps/blender/core/i18n_locales/__init__.py, tests/test_i18n_locales.py
Defines LocaleRow, LocaleModule, fold(), and LOCALE_MODULES/LOCALE_TABLES, with tests for context and locale separation.
Blender translation registration
apps/blender/core/bpy_helpers/i18n.py, apps/blender/tests/operators/test_i18n_translation.py, tests/test_i18n_register.py
Registers LOCALE_TABLES with Blender translations and verifies register/unregister plus lookup behavior.
Report translation hook
apps/blender/core/_shared/report.py, apps/blender/__init__.py, tests/test_report_gate.py
Adds runtime report translation and wires addon registration to install i18n.iface as the translator.
Msgid extraction and coverage
scripts/blender/extract_i18n.py, tests/test_i18n_extract.py, tests/test_i18n_coverage.py, apps/blender/pyproject.toml
Adds AST extraction, unwrapped-string reporting, coverage drift checks, and a Ruff ignore for generated locale files.
UI string localization
apps/blender/addon_prefs.py, apps/blender/operators/..., apps/blender/panels/...
Replaces hardcoded UI labels and dialog text with iface(...) across addon preferences, operators, and panels.
Dictionary terms
.cspell/ecosystem-terms.txt, .cspell/project-terms.txt
Adds new spellcheck terms and reorders existing project terms in the updated segment.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: pt-BR Blender interface translation plus supporting i18n tooling.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/spec-072-multilanguage-i18n
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/spec-072-multilanguage-i18n

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
apps/blender/core/i18n_locales/__init__.py (1)

36-47: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider guarding against silent duplicate-key overwrites in fold.

If a single locale module accidentally repeats a (msgctxt, msgid) pair with a different msgstr (easy to happen across 592 hand-authored rows), fold will silently keep whichever row is later in the tuple, masking the mistake. A cheap guard would catch this at test/import time rather than shipping a wrong translation.

♻️ Proposed duplicate-detection guard
 def fold(modules: Iterable[LocaleModule]) -> dict[str, dict[tuple[str, str], str]]:
     """Collapse per-locale modules into ``{locale: {(msgctxt, msgid): msgstr}}``.

     A locale contributing no rows is omitted from the result, so the
     mechanism ships as an empty table until a locale is populated -
     matching the pre-split ``TRANSLATIONS = ()`` behaviour.
     """
     out: dict[str, dict[tuple[str, str], str]] = {}
     for module in modules:
-        for (msgctxt, msgid), msgstr in module.ROWS:
-            out.setdefault(module.LOCALE, {})[(msgctxt, msgid)] = msgstr
+        table = out.setdefault(module.LOCALE, {})
+        for (msgctxt, msgid), msgstr in module.ROWS:
+            key = (msgctxt, msgid)
+            if key in table and table[key] != msgstr:
+                raise ValueError(
+                    f"duplicate ({msgctxt!r}, {msgid!r}) in {module.LOCALE} "
+                    f"with conflicting msgstr: {table[key]!r} vs {msgstr!r}"
+                )
+            table[key] = msgstr
     return out
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/blender/core/i18n_locales/__init__.py` around lines 36 - 47, `fold`
currently overwrites repeated `(msgctxt, msgid)` entries within the same locale
module without any warning. Update `fold` in `i18n_locales.__init__` to detect
duplicate keys while iterating `module.ROWS` and raise an error (or otherwise
fail fast) when the same key appears more than once with a conflicting `msgstr`,
so accidental hand-authored duplicates are caught during import/test time
instead of being silently masked.
apps/blender/panels/mesh_generation.py (1)

57-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate warning strings across two call sites risk translation drift.

"mesh tools are mesh-only (this is a sprite)" and "to rig a sprite, parent it to a bone: Ctrl+P > Bone" are duplicated verbatim in draw() (lines 57-58) and _draw_manual_mesh() (lines 320-321). Since the locale table keys on exact msgid text, a future edit to one copy without the other silently splits it into two separate (and likely one untranslated) catalog entries — exactly the drift risk this PR's extraction/coverage tooling is meant to guard against.

Consider extracting a small shared helper, e.g.:

♻️ Proposed refactor
+def _draw_sprite_mesh_warning(layout: bpy.types.UILayout) -> None:
+    """Shared 'this is a sprite, not a mesh' warning for mesh-only panels."""
+    layout.label(text=iface("mesh tools are mesh-only (this is a sprite)"), icon="INFO")
+    layout.label(text=iface("to rig a sprite, parent it to a bone: Ctrl+P > Bone"))
+
 def draw(self, context: bpy.types.Context) -> None:
     ...
     if not _is_mesh_element(context):
-        layout.label(text=iface("mesh tools are mesh-only (this is a sprite)"), icon="INFO")
-        layout.label(text=iface("to rig a sprite, parent it to a bone: Ctrl+P > Bone"))
+        _draw_sprite_mesh_warning(layout)
         return

Also applies to: 320-321

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/blender/panels/mesh_generation.py` around lines 57 - 58, The warning
text is duplicated in both draw() and _draw_manual_mesh(), which can cause
translation drift because the exact msgid is used for locale lookup. Extract the
repeated labels into a shared helper or shared constants near
mesh_generation.py’s mesh panel drawing logic, then have both draw() and
_draw_manual_mesh() call that shared source so there is only one canonical msgid
for each string.
apps/blender/panels/_draw_bone_attach.py (1)

13-13: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Remove iface() around static UI strings
Blender auto-translates literal layout.*(text=...) strings from the addon translation table, and iface() is only needed for draw-time assembled text. These labels can be plain literals here (and in the other matching call sites).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/blender/panels/_draw_bone_attach.py` at line 13, The UI labels in the
bone attach panel are wrapping static strings with iface() unnecessarily; update
the matching draw call sites in the panel code to use plain literal text for
layout.*(text=...) instead of iface(), and keep iface() only for dynamically
assembled draw-time strings. Use the existing draw methods and label text in the
bone attach panel to locate and update the other matching call sites
consistently.
apps/blender/panels/__init__.py (1)

62-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Version number embedded in a translatable string.

iface("Pipeline v0.1.0") bakes the version literal into the msgid. Every version bump changes the msgid, invalidating the existing pt-BR translation entry and forcing a catalog update purely for a version-number change, which is unrelated to actual localization content.

♻️ Suggested separation of static label and dynamic version
-        row.label(text=iface("Pipeline v0.1.0"))
+        row.label(text=f"{iface('Pipeline')} v0.1.0")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/blender/panels/__init__.py` at line 62, The label in the Blender panel
is embedding the version string inside the translatable msgid, so update the
panel text to separate the static localized label from the dynamic version
value. In the panel drawing code that calls row.label via iface, keep only the
stable text in the translation key and append or format the version from a
separate variable so future version bumps do not change the translation entry.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/blender/panels/weight_paint.py`:
- Around line 396-403: The `prop()` labels in `VIEW3D_PT_tools_weightpaint` are
not wrapped for translation, so the UI strings passed as `text=` will stay
untranslated. Update the `box.prop(...)` calls for
`overlay.weight_paint_mode_opacity` and `tool_settings.vertex_group_user` to use
`iface()` for their labels, matching how the nearby `box.label()` strings are
handled.

---

Nitpick comments:
In `@apps/blender/core/i18n_locales/__init__.py`:
- Around line 36-47: `fold` currently overwrites repeated `(msgctxt, msgid)`
entries within the same locale module without any warning. Update `fold` in
`i18n_locales.__init__` to detect duplicate keys while iterating `module.ROWS`
and raise an error (or otherwise fail fast) when the same key appears more than
once with a conflicting `msgstr`, so accidental hand-authored duplicates are
caught during import/test time instead of being silently masked.

In `@apps/blender/panels/__init__.py`:
- Line 62: The label in the Blender panel is embedding the version string inside
the translatable msgid, so update the panel text to separate the static
localized label from the dynamic version value. In the panel drawing code that
calls row.label via iface, keep only the stable text in the translation key and
append or format the version from a separate variable so future version bumps do
not change the translation entry.

In `@apps/blender/panels/_draw_bone_attach.py`:
- Line 13: The UI labels in the bone attach panel are wrapping static strings
with iface() unnecessarily; update the matching draw call sites in the panel
code to use plain literal text for layout.*(text=...) instead of iface(), and
keep iface() only for dynamically assembled draw-time strings. Use the existing
draw methods and label text in the bone attach panel to locate and update the
other matching call sites consistently.

In `@apps/blender/panels/mesh_generation.py`:
- Around line 57-58: The warning text is duplicated in both draw() and
_draw_manual_mesh(), which can cause translation drift because the exact msgid
is used for locale lookup. Extract the repeated labels into a shared helper or
shared constants near mesh_generation.py’s mesh panel drawing logic, then have
both draw() and _draw_manual_mesh() call that shared source so there is only one
canonical msgid for each string.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 912f1cd5-546a-4e15-898e-92facf2861d3

📥 Commits

Reviewing files that changed from the base of the PR and between 31b97ee and e27c86a.

📒 Files selected for processing (37)
  • .cspell/ecosystem-terms.txt
  • .cspell/project-terms.txt
  • apps/blender/__init__.py
  • apps/blender/addon_prefs.py
  • apps/blender/core/_shared/report.py
  • apps/blender/core/bpy_helpers/i18n.py
  • apps/blender/core/i18n_locales/__init__.py
  • apps/blender/core/i18n_locales/_types.py
  • apps/blender/core/i18n_locales/pt_br.py
  • apps/blender/operators/revert_to_plane.py
  • apps/blender/operators/skinning/clear_empty_vgroups.py
  • apps/blender/operators/skinning/edit_weights.py
  • apps/blender/operators/slot/bind.py
  • apps/blender/operators/sprite/bone_parent.py
  • apps/blender/panels/__init__.py
  • apps/blender/panels/_draw_bone_attach.py
  • apps/blender/panels/_draw_ik.py
  • apps/blender/panels/_draw_sprite.py
  • apps/blender/panels/animation.py
  • apps/blender/panels/atlas.py
  • apps/blender/panels/element.py
  • apps/blender/panels/helpers.py
  • apps/blender/panels/mesh_generation.py
  • apps/blender/panels/outliner.py
  • apps/blender/panels/pipeline.py
  • apps/blender/panels/skeleton.py
  • apps/blender/panels/slots.py
  • apps/blender/panels/validation.py
  • apps/blender/panels/weight_paint.py
  • apps/blender/pyproject.toml
  • apps/blender/tests/operators/test_i18n_translation.py
  • scripts/blender/extract_i18n.py
  • tests/test_i18n_coverage.py
  • tests/test_i18n_extract.py
  • tests/test_i18n_locales.py
  • tests/test_i18n_register.py
  • tests/test_report_gate.py
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: SonarCloud
⚠️ CI failures not shown inline (2)

GitHub Actions: CI / test-blender: feat(blender): pt-BR interface translation + i18n tooling (spec 072)

Conclusion: failure

View job details

##[group]Run ~/blender/blender --background \
 �[36;1m~/blender/blender --background \�[0m
 �[36;1m  --python packages/validator/scripts/validate_automesh.py \�[0m
 �[36;1m  -- --ci-only�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
 Traceback (most recent call last):
   File "/home/runner/work/proscenio/proscenio/packages/validator/scripts/validate_automesh.py", line 50, in <module>
     from proscenio_validator.cli import main  # noqa: E402
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/runner/work/proscenio/proscenio/packages/validator/src/proscenio_validator/cli.py", line 16, in <module>
     from .measurement import load_fixture, run_validation
   File "/home/runner/work/proscenio/proscenio/packages/validator/src/proscenio_validator/measurement.py", line 18, in <module>
     from .coverage import measure_coverage
   File "/home/runner/work/proscenio/proscenio/packages/validator/src/proscenio_validator/coverage.py", line 17, in <module>
     from core.geometry_2d import point_in_triangle_xz
 ModuleNotFoundError: No module named 'core.geometry_2d'
 Blender 5.1.1 (hash b70da489d7f4 built )
 Blender quit

GitHub Actions: CI / 4_test-blender.txt: feat(blender): pt-BR interface translation + i18n tooling (spec 072)

Conclusion: failure

View job details

Current runner version: '2.335.1'
 ##[group]Runner Image Provisioner
 Hosted Compute Agent
 Version: 20260624.560
 Commit: 925d229a51159bc391ae97e54a2dd1fe20af789d
 Build Date:
 Worker ID: {8373cd9b-ffbc-4e3a-8656-9dfae48557dd}
 Azure Region: eastus
 ##[endgroup]
 ##[group]Operating System
 Ubuntu
 24.04.4
 LTS
 ##[endgroup]
 ##[group]Runner Image
 Image: ubuntu-24.04
 Version: 20260628.225.1
 Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20260628.225/images/ubuntu/Ubuntu2404-Readme.md
 Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20260628.225
 ##[endgroup]
 ##[group]GITHUB_TOKEN Permissions
 Contents: read
 Metadata: read
 ##[endgroup]
 Secret source: Actions
 Prepare workflow directory
 Prepare all required actions
 Getting action download info
 Download action repository 'actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0' (SHA:9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0)
 Download action repository 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9' (SHA:55cc8345863c7cc4c66a329aec7e433d2d1c52a9)
 Complete job name: test-blender
 ##[group]Run actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
 with:
   lfs: true
   repository: firebound/proscenio
   ***REDACTED***
   ssh-strict: true
   ssh-user: git
   persist-credentials: true
   clean: true
   sparse-checkout-cone-mode: true
   fetch-depth: 1
   fetch-tags: false
   show-progress: true
   submodules: false
   set-safe-directory: true
   allow-unsafe-pr-checkout: false
 ##[endgroup]
 Syncing repository: firebound/proscenio
 ##[group]Getting Git version info
 Working directory is '/home/runner/work/proscenio/proscenio'
 [command]/usr/bin/git version
 git version 2.54.0
 [command]/usr/bin/git lfs version
 git-lfs/3.7.1 (GitHub; linux amd64; go 1.24.4)
 ##[endgroup]
 Temporarily overriding HOME='/home/runner/work/_temp/05d79976-b6bc-4005-9998-f85a56a83707' before making global git config changes
 Adding repository directory to the te...
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2026-07-01T19:27:59.389Z
Learnt from: danko-nobre
Repo: firebound/proscenio PR: 183
File: apps/blender/tests/operators/test_spritesheet_slicer.py:0-0
Timestamp: 2026-07-01T19:27:59.389Z
Learning: In this repo, pre-commit pins an older ruff version while CI runs a newer ruff via `uvx ruff`, and the differing formatters can change how multiline `assert cond, "msg"` statements are wrapped (causing `ruff format --check` to fail in CI even if pre-commit passes). To avoid formatter drift, write these asserts in a form that both ruff versions preserve—e.g., keep the assert effectively single-line by assigning the condition first and then using `assert cond, "msg"` (such as `cond = <expr>; assert cond, "msg"`), rather than relying on multiline wrapping of the boolean expression or message.

Applied to files:

  • apps/blender/core/i18n_locales/_types.py
  • apps/blender/panels/validation.py
  • apps/blender/panels/animation.py
  • apps/blender/panels/pipeline.py
  • apps/blender/panels/_draw_bone_attach.py
  • apps/blender/panels/helpers.py
  • apps/blender/__init__.py
  • apps/blender/core/i18n_locales/__init__.py
  • apps/blender/operators/skinning/edit_weights.py
  • apps/blender/panels/outliner.py
  • apps/blender/addon_prefs.py
  • apps/blender/operators/slot/bind.py
  • apps/blender/operators/revert_to_plane.py
  • apps/blender/panels/_draw_ik.py
  • apps/blender/operators/skinning/clear_empty_vgroups.py
  • apps/blender/panels/__init__.py
  • tests/test_report_gate.py
  • apps/blender/operators/sprite/bone_parent.py
  • apps/blender/panels/element.py
  • apps/blender/tests/operators/test_i18n_translation.py
  • apps/blender/panels/_draw_sprite.py
  • apps/blender/panels/atlas.py
  • apps/blender/core/_shared/report.py
  • apps/blender/panels/mesh_generation.py
  • tests/test_i18n_locales.py
  • tests/test_i18n_extract.py
  • apps/blender/panels/slots.py
  • tests/test_i18n_coverage.py
  • apps/blender/panels/weight_paint.py
  • apps/blender/panels/skeleton.py
  • scripts/blender/extract_i18n.py
  • apps/blender/core/bpy_helpers/i18n.py
  • tests/test_i18n_register.py
📚 Learning: 2026-06-17T16:09:35.107Z
Learnt from: danko-nobre
Repo: firebound/proscenio PR: 123
File: apps/blender/panels/skeleton.py:0-0
Timestamp: 2026-06-17T16:09:35.107Z
Learning: In the Proscenio Blender add-on panel code under `apps/blender/panels/`, `draw_header` and `draw_header_preset` implementations must read `context.region` from their callback `context` parameter (e.g., `context.region`), and must not use global `bpy.context.region`. This avoids incorrect region targeting in multi-editor layouts where `bpy.context` may refer to a different area/region. Likewise, `draw_subpanel_header` in `apps/blender/panels/_helpers.py` should be called with an explicit `context` argument, since that `context` must be threaded through by every `draw_header_preset` caller.

Applied to files:

  • apps/blender/panels/validation.py
  • apps/blender/panels/animation.py
  • apps/blender/panels/pipeline.py
  • apps/blender/panels/_draw_bone_attach.py
  • apps/blender/panels/helpers.py
  • apps/blender/panels/outliner.py
  • apps/blender/panels/_draw_ik.py
  • apps/blender/panels/__init__.py
  • apps/blender/panels/element.py
  • apps/blender/panels/_draw_sprite.py
  • apps/blender/panels/atlas.py
  • apps/blender/panels/mesh_generation.py
  • apps/blender/panels/slots.py
  • apps/blender/panels/weight_paint.py
  • apps/blender/panels/skeleton.py
📚 Learning: 2026-06-29T21:00:29.712Z
Learnt from: danko-nobre
Repo: firebound/proscenio PR: 167
File: tests/automesh/test_vertex_pen.py:11-19
Timestamp: 2026-06-29T21:00:29.712Z
Learning: In this repository’s Python tests, it is an intentional/acceptable pattern to stub Blender-dependent modules at module import time by assigning `MagicMock()` instances into `sys.modules` (commonly `sys.modules['bpy']`, and sometimes `bmesh`/`mathutils`) before importing the addon/code under test. When reviewing similar test files, don’t flag the presence of `sys.modules[...]` stubbing or Blender-module “missing import” issues as a bug, since it’s being used to isolate pytest from Blender at import time. Only suggest changes if the stubbing isn’t happening before the addon import or appears inconsistent with the test’s intended import-time isolation goals.

Applied to files:

  • tests/test_report_gate.py
  • tests/test_i18n_locales.py
  • tests/test_i18n_extract.py
  • tests/test_i18n_coverage.py
  • tests/test_i18n_register.py
📚 Learning: 2026-06-11T17:28:22.365Z
Learnt from: danko-nobre
Repo: firebound/proscenio PR: 107
File: apps/blender/operators/skinning/sidecar_io.py:1-1
Timestamp: 2026-06-11T17:28:22.365Z
Learning: In the proscenio firebound/proscenio Blender app tests (apps/blender/tests/operators/**/*.py), avoid writing asserts like `assert <very_long_boolean_expression>, "msg"` that depend on ruff version–specific line wrapping. Instead, extract the long boolean expression into a named variable and then assert on that variable (e.g., `cond = <long expr>; assert cond, "msg"`) so ruff pre-commit vs CI produces identical formatting/output.

Applied to files:

  • apps/blender/tests/operators/test_i18n_translation.py
📚 Learning: 2026-06-25T06:48:46.567Z
Learnt from: danko-nobre
Repo: firebound/proscenio PR: 163
File: apps/blender/tests/operators/test_import_placement.py:19-28
Timestamp: 2026-06-25T06:48:46.567Z
Learning: In Blender operator test files (e.g., under apps/blender/tests/operators), ensure the test harness fresh-loads the fixture .blend for each individual test. This forces Blender to reset bpy.data between tests, bounding any temporary datablock/datablock accumulation created by helpers and preventing cross-test contamination/leaks.

Applied to files:

  • apps/blender/tests/operators/test_i18n_translation.py
🪛 GitHub Check: SonarCloud Code Analysis
scripts/blender/extract_i18n.py

[failure] 298-298: Refactor this method to not always return the same value.

See more on https://sonarcloud.io/project/issues?id=firebound_proscenio&issues=AZ8ntmtrt3rOAWHGX1kH&open=AZ8ntmtrt3rOAWHGX1kH&pullRequest=185

🔇 Additional comments (36)
.cspell/ecosystem-terms.txt (1)

104-104: LGTM!

Also applies to: 123-123, 354-354

.cspell/project-terms.txt (1)

163-170: LGTM!

scripts/blender/extract_i18n.py (3)

298-330: LGTM!


82-241: LGTM!


172-177: 🎯 Functional Correctness

Extractor matches current positional call sites

The current iface() and report_*() usages are positional throughout the addon, and report_warn(..., always=...) still keeps the message positional. The keyword-argument drift described here does not appear in this codebase.

			> Likely an incorrect or invalid review comment.
tests/test_i18n_extract.py (1)

1-118: LGTM! Coverage cleanly exercises operator/panel context split, property/enum extraction, iface wrapping, and f-string deferral.

tests/test_i18n_coverage.py (1)

1-42: LGTM! Bidirectional drift guard (orphan + missing coverage) matches the PR's stated goal of catching untranslated or stale entries.

apps/blender/pyproject.toml (1)

50-55: LGTM!

apps/blender/core/i18n_locales/_types.py (1)

1-14: LGTM!

tests/test_i18n_locales.py (1)

1-74: LGTM!

apps/blender/core/_shared/report.py (1)

15-15: LGTM!

Also applies to: 32-51, 68-68, 79-84, 98-98

tests/test_report_gate.py (1)

32-39: LGTM!

Also applies to: 109-132

apps/blender/panels/_draw_ik.py (1)

13-14: LGTM!

Also applies to: 56-56

apps/blender/panels/_draw_sprite.py (1)

16-16: LGTM!

Also applies to: 42-42

apps/blender/core/bpy_helpers/i18n.py (2)

1-12: LGTM!

Also applies to: 24-38


27-33: 📐 Maintainability & Code Quality

iface() wrapping is intentional
iface() is the extractor marker for cataloged strings, so wrapping these literals is deliberate even when Blender could auto-translate them directly.

			> Likely an incorrect or invalid review comment.
tests/test_i18n_register.py (1)

1-47: LGTM!

apps/blender/tests/operators/test_i18n_translation.py (1)

1-65: LGTM!

apps/blender/__init__.py (1)

9-22: LGTM!

apps/blender/operators/skinning/edit_weights.py (1)

31-31: LGTM!

Also applies to: 332-337

apps/blender/panels/outliner.py (1)

9-9: LGTM!

Also applies to: 219-224

apps/blender/panels/slots.py (1)

19-19: LGTM!

Also applies to: 106-147, 163-198

apps/blender/panels/validation.py (1)

9-9: LGTM!

Also applies to: 32-49

apps/blender/addon_prefs.py (1)

15-15: LGTM!

Also applies to: 80-83

apps/blender/panels/animation.py (1)

9-9: Same auto-translation question as raised in _draw_bone_attach.py.

iface("no actions to export") wraps a static literal; see the consolidated concern already raised in apps/blender/panels/_draw_bone_attach.py regarding whether this pattern is redundant given iface()'s own docstring.

Also applies to: 67-67

apps/blender/panels/atlas.py (1)

16-16: Same auto-translation question as raised in _draw_bone_attach.py.

All four wrapped strings here ("no atlas linked in materials", "Atlas packer", "run Pack Atlas first") are static literals; see the consolidated concern already raised in apps/blender/panels/_draw_bone_attach.py.

Also applies to: 38-38, 56-56, 68-68

apps/blender/panels/element.py (1)

21-21: Same auto-translation question as raised in _draw_bone_attach.py.

All wrapped strings here are static literals (the dynamic f"Element: {name}" branch is correctly left untranslated); see the consolidated concern already raised in apps/blender/panels/_draw_bone_attach.py.

Also applies to: 65-65, 74-90

apps/blender/panels/helpers.py (1)

15-15: Same auto-translation question as raised in _draw_bone_attach.py.

iface("3D View Clip:") wraps a static literal; see the consolidated concern already raised in apps/blender/panels/_draw_bone_attach.py.

Also applies to: 48-48

apps/blender/panels/mesh_generation.py (1)

24-24: Same auto-translation question as raised in _draw_bone_attach.py.

All wrapped strings here are static literals; see the consolidated concern already raised in apps/blender/panels/_draw_bone_attach.py.

Also applies to: 52-58, 234-234, 265-265, 315-324

apps/blender/panels/pipeline.py (1)

16-16: Same auto-translation question as raised in _draw_bone_attach.py.

iface("proscenio scene props not registered") wraps a static literal; see the consolidated concern already raised in apps/blender/panels/_draw_bone_attach.py.

Also applies to: 41-41

apps/blender/operators/revert_to_plane.py (1)

34-34: LGTM!

Also applies to: 101-107

apps/blender/operators/skinning/clear_empty_vgroups.py (1)

17-17: LGTM!

Also applies to: 71-73

apps/blender/operators/slot/bind.py (1)

17-17: LGTM!

Also applies to: 80-80

apps/blender/operators/sprite/bone_parent.py (1)

20-20: LGTM!

Also applies to: 73-73

apps/blender/panels/skeleton.py (1)

28-28: LGTM!

Also applies to: 292-292, 313-320, 433-433, 493-493, 549-549, 639-639

apps/blender/panels/weight_paint.py (1)

17-17: LGTM!

Also applies to: 59-61, 135-135, 291-296, 346-348, 425-425

Comment thread apps/blender/panels/weight_paint.py
danko-nobre and others added 2 commits July 4, 2026 00:59
… 072)

CodeRabbit flagged that prop() text= labels stay untranslated; the same gap
hit operator() text= button labels. Blender auto-translates layout text= from
the registered table (translate=True default, verified via bl_rna), so the fix
is to catalog them, not wrap them: the extractor now collects text= string
literals on label / prop / operator / menu as msgids, and pt_br.py gains the
51 rows for them (Pack Atlas, Revert to Plane, Weight Opacity, ...).

f-string text= args stay deferred (they evaluate before Blender can match a
msgid). No call-site changes.

Refs: specs/072-multilanguage-i18n

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nguage-i18n

# Conflicts:
#	.cspell/project-terms.txt
@sonarqubecloud

sonarqubecloud Bot commented Jul 4, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/test_i18n_extract.py (1)

108-114: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Strengthen assertions to also verify exclusion from unwrapped.

The docstring (Lines 5-6) and adjacent report-literal test (fixed literals not in res.unwrapped) suggest these UI text= literals should be catalog-only, not deferred. This test only checks catalog membership; add assert res.unwrapped == [] (or equivalent) to guard against a regression where these strings end up double-counted in both catalog and unwrapped. Also consider covering menu/popover, the remaining members of _UI_TEXT_METHODS, for completeness.

♻️ Proposed strengthening
 def test_ui_text_literals_go_to_catalog() -> None:
     """label/prop/operator text= literals auto-translate (translate=True default),
     so they are catalog entries - no iface() wrapping needed."""
-    cat = _catalog(SAMPLE)
+    res = extract_from_source(SAMPLE)
+    cat = {(e[0], e[1]) for e in res.catalog}
     assert ("*", "Inline label") in cat
     assert ("*", "Prop Label") in cat
     assert ("*", "Op Button") in cat
+    unwrapped_texts = {u.text for u in res.unwrapped}
+    assert "Inline label" not in unwrapped_texts
+    assert "Prop Label" not in unwrapped_texts
+    assert "Op Button" not in unwrapped_texts
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_i18n_extract.py` around lines 108 - 114, The ui text literal test
only verifies catalog membership, but it should also assert these strings are
not present in unwrapped to prevent double-counting regressions. Update
test_ui_text_literals_go_to_catalog in tests/test_i18n_extract.py to check the
extraction result object from _catalog(SAMPLE) (or equivalent) and confirm
unwrapped is empty for these UI literals, using the existing test helpers and
symbols like _catalog and SAMPLE. Consider extending the same coverage to the
remaining _UI_TEXT_METHODS cases such as menu and popover so catalog-only
behavior is validated consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/test_i18n_extract.py`:
- Around line 108-114: The ui text literal test only verifies catalog
membership, but it should also assert these strings are not present in unwrapped
to prevent double-counting regressions. Update
test_ui_text_literals_go_to_catalog in tests/test_i18n_extract.py to check the
extraction result object from _catalog(SAMPLE) (or equivalent) and confirm
unwrapped is empty for these UI literals, using the existing test helpers and
symbols like _catalog and SAMPLE. Consider extending the same coverage to the
remaining _UI_TEXT_METHODS cases such as menu and popover so catalog-only
behavior is validated consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 24e4b137-4d62-46ba-8d59-df7914b550a1

📥 Commits

Reviewing files that changed from the base of the PR and between e27c86a and 70dbe31.

📒 Files selected for processing (4)
  • .cspell/project-terms.txt
  • apps/blender/core/i18n_locales/pt_br.py
  • scripts/blender/extract_i18n.py
  • tests/test_i18n_extract.py
✅ Files skipped from review due to trivial changes (1)
  • .cspell/project-terms.txt
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/blender/extract_i18n.py
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: SonarCloud
⚠️ CI failures not shown inline (2)

GitHub Actions: CI / 4_test-blender.txt: feat(blender): pt-BR interface translation + i18n tooling (spec 072)

Conclusion: failure

View job details

Current runner version: '2.335.1'
 ##[group]Runner Image Provisioner
 Hosted Compute Agent
 Version: 20260624.560
 Commit: 925d229a51159bc391ae97e54a2dd1fe20af789d
 Build Date:
 Worker ID: {67612aac-4ebe-43fe-9111-d1382c2651ff}
 Azure Region: eastus
 ##[endgroup]
 ##[group]Operating System
 Ubuntu
 24.04.4
 LTS
 ##[endgroup]
 ##[group]Runner Image
 Image: ubuntu-24.04
 Version: 20260628.225.1
 Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20260628.225/images/ubuntu/Ubuntu2404-Readme.md
 Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20260628.225
 ##[endgroup]
 ##[group]GITHUB_TOKEN Permissions
 Contents: read
 Metadata: read
 ##[endgroup]
 Secret source: Actions
 Prepare workflow directory
 Prepare all required actions
 Getting action download info
 Download action repository 'actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0' (SHA:9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0)
 Download action repository 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9' (SHA:55cc8345863c7cc4c66a329aec7e433d2d1c52a9)
 Complete job name: test-blender
 ##[group]Run actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
 with:
   lfs: true
   repository: firebound/proscenio
   ***REDACTED***
   ssh-strict: true
   ssh-user: git
   persist-credentials: true
   clean: true
   sparse-checkout-cone-mode: true
   fetch-depth: 1
   fetch-tags: false
   show-progress: true
   submodules: false
   set-safe-directory: true
   allow-unsafe-pr-checkout: false
 ##[endgroup]
 Syncing repository: firebound/proscenio
 ##[group]Getting Git version info
 Working directory is '/home/runner/work/proscenio/proscenio'
 [command]/usr/bin/git version
 git version 2.54.0
 [command]/usr/bin/git lfs version
 git-lfs/3.7.1 (GitHub; linux amd64; go 1.24.4)
 ##[endgroup]
 Temporarily overriding HOME='/home/runner/work/_temp/7d97f8cc-950b-4440-a313-4e1be0c35815' before making global git config changes
 Adding repository directory to the te...

GitHub Actions: CI / test-blender: feat(blender): pt-BR interface translation + i18n tooling (spec 072)

Conclusion: failure

View job details

##[group]Run ~/blender/blender --background \
 �[36;1m~/blender/blender --background \�[0m
 �[36;1m  --python packages/validator/scripts/validate_automesh.py \�[0m
 �[36;1m  -- --ci-only�[0m
 shell: /usr/bin/bash -e {0}
 ##[endgroup]
 Traceback (most recent call last):
   File "/home/runner/work/proscenio/proscenio/packages/validator/scripts/validate_automesh.py", line 50, in <module>
     from proscenio_validator.cli import main  # noqa: E402
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/runner/work/proscenio/proscenio/packages/validator/src/proscenio_validator/cli.py", line 16, in <module>
     from .measurement import load_fixture, run_validation
   File "/home/runner/work/proscenio/proscenio/packages/validator/src/proscenio_validator/measurement.py", line 18, in <module>
     from .coverage import measure_coverage
   File "/home/runner/work/proscenio/proscenio/packages/validator/src/proscenio_validator/coverage.py", line 17, in <module>
     from core.geometry_2d import point_in_triangle_xz
 ModuleNotFoundError: No module named 'core.geometry_2d'
 Blender 5.1.1 (hash b70da489d7f4 built )
 Blender quit
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2026-06-29T21:00:29.712Z
Learnt from: danko-nobre
Repo: firebound/proscenio PR: 167
File: tests/automesh/test_vertex_pen.py:11-19
Timestamp: 2026-06-29T21:00:29.712Z
Learning: In this repository’s Python tests, it is an intentional/acceptable pattern to stub Blender-dependent modules at module import time by assigning `MagicMock()` instances into `sys.modules` (commonly `sys.modules['bpy']`, and sometimes `bmesh`/`mathutils`) before importing the addon/code under test. When reviewing similar test files, don’t flag the presence of `sys.modules[...]` stubbing or Blender-module “missing import” issues as a bug, since it’s being used to isolate pytest from Blender at import time. Only suggest changes if the stubbing isn’t happening before the addon import or appears inconsistent with the test’s intended import-time isolation goals.

Applied to files:

  • tests/test_i18n_extract.py
📚 Learning: 2026-07-01T19:27:59.389Z
Learnt from: danko-nobre
Repo: firebound/proscenio PR: 183
File: apps/blender/tests/operators/test_spritesheet_slicer.py:0-0
Timestamp: 2026-07-01T19:27:59.389Z
Learning: In this repo, pre-commit pins an older ruff version while CI runs a newer ruff via `uvx ruff`, and the differing formatters can change how multiline `assert cond, "msg"` statements are wrapped (causing `ruff format --check` to fail in CI even if pre-commit passes). To avoid formatter drift, write these asserts in a form that both ruff versions preserve—e.g., keep the assert effectively single-line by assigning the condition first and then using `assert cond, "msg"` (such as `cond = <expr>; assert cond, "msg"`), rather than relying on multiline wrapping of the boolean expression or message.

Applied to files:

  • tests/test_i18n_extract.py
🔇 Additional comments (1)
tests/test_i18n_extract.py (1)

108-114: 🎯 Functional Correctness

No change needed here.

@danko-nobre danko-nobre merged commit c865fe0 into main Jul 4, 2026
11 checks passed
@danko-nobre danko-nobre deleted the feat/spec-072-multilanguage-i18n branch July 4, 2026 16:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant