feat(blender): pt-BR interface translation + i18n tooling (spec 072)#185
Conversation
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>
📝 WalkthroughWalkthroughThis 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 Changesi18n rollout
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
apps/blender/core/i18n_locales/__init__.py (1)
36-47: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winConsider guarding against silent duplicate-key overwrites in
fold.If a single locale module accidentally repeats a
(msgctxt, msgid)pair with a differentmsgstr(easy to happen across 592 hand-authored rows),foldwill 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 winDuplicate 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 indraw()(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) returnAlso 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 valueRemove
iface()around static UI strings
Blender auto-translates literallayout.*(text=...)strings from the addon translation table, andiface()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 winVersion 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
📒 Files selected for processing (37)
.cspell/ecosystem-terms.txt.cspell/project-terms.txtapps/blender/__init__.pyapps/blender/addon_prefs.pyapps/blender/core/_shared/report.pyapps/blender/core/bpy_helpers/i18n.pyapps/blender/core/i18n_locales/__init__.pyapps/blender/core/i18n_locales/_types.pyapps/blender/core/i18n_locales/pt_br.pyapps/blender/operators/revert_to_plane.pyapps/blender/operators/skinning/clear_empty_vgroups.pyapps/blender/operators/skinning/edit_weights.pyapps/blender/operators/slot/bind.pyapps/blender/operators/sprite/bone_parent.pyapps/blender/panels/__init__.pyapps/blender/panels/_draw_bone_attach.pyapps/blender/panels/_draw_ik.pyapps/blender/panels/_draw_sprite.pyapps/blender/panels/animation.pyapps/blender/panels/atlas.pyapps/blender/panels/element.pyapps/blender/panels/helpers.pyapps/blender/panels/mesh_generation.pyapps/blender/panels/outliner.pyapps/blender/panels/pipeline.pyapps/blender/panels/skeleton.pyapps/blender/panels/slots.pyapps/blender/panels/validation.pyapps/blender/panels/weight_paint.pyapps/blender/pyproject.tomlapps/blender/tests/operators/test_i18n_translation.pyscripts/blender/extract_i18n.pytests/test_i18n_coverage.pytests/test_i18n_extract.pytests/test_i18n_locales.pytests/test_i18n_register.pytests/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
##[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
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.pyapps/blender/panels/validation.pyapps/blender/panels/animation.pyapps/blender/panels/pipeline.pyapps/blender/panels/_draw_bone_attach.pyapps/blender/panels/helpers.pyapps/blender/__init__.pyapps/blender/core/i18n_locales/__init__.pyapps/blender/operators/skinning/edit_weights.pyapps/blender/panels/outliner.pyapps/blender/addon_prefs.pyapps/blender/operators/slot/bind.pyapps/blender/operators/revert_to_plane.pyapps/blender/panels/_draw_ik.pyapps/blender/operators/skinning/clear_empty_vgroups.pyapps/blender/panels/__init__.pytests/test_report_gate.pyapps/blender/operators/sprite/bone_parent.pyapps/blender/panels/element.pyapps/blender/tests/operators/test_i18n_translation.pyapps/blender/panels/_draw_sprite.pyapps/blender/panels/atlas.pyapps/blender/core/_shared/report.pyapps/blender/panels/mesh_generation.pytests/test_i18n_locales.pytests/test_i18n_extract.pyapps/blender/panels/slots.pytests/test_i18n_coverage.pyapps/blender/panels/weight_paint.pyapps/blender/panels/skeleton.pyscripts/blender/extract_i18n.pyapps/blender/core/bpy_helpers/i18n.pytests/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.pyapps/blender/panels/animation.pyapps/blender/panels/pipeline.pyapps/blender/panels/_draw_bone_attach.pyapps/blender/panels/helpers.pyapps/blender/panels/outliner.pyapps/blender/panels/_draw_ik.pyapps/blender/panels/__init__.pyapps/blender/panels/element.pyapps/blender/panels/_draw_sprite.pyapps/blender/panels/atlas.pyapps/blender/panels/mesh_generation.pyapps/blender/panels/slots.pyapps/blender/panels/weight_paint.pyapps/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.pytests/test_i18n_locales.pytests/test_i18n_extract.pytests/test_i18n_coverage.pytests/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.
🔇 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 CorrectnessExtractor matches current positional call sites
The current
iface()andreport_*()usages are positional throughout the addon, andreport_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 inapps/blender/panels/_draw_bone_attach.pyregarding whether this pattern is redundant giveniface()'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 inapps/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 inapps/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 inapps/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
… 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
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tests/test_i18n_extract.py (1)
108-114: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winStrengthen 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 UItext=literals should be catalog-only, not deferred. This test only checks catalog membership; addassert res.unwrapped == [](or equivalent) to guard against a regression where these strings end up double-counted in bothcatalogandunwrapped. Also consider coveringmenu/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
📒 Files selected for processing (4)
.cspell/project-terms.txtapps/blender/core/i18n_locales/pt_br.pyscripts/blender/extract_i18n.pytests/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
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
##[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 CorrectnessNo change needed here.



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.tomlblender --background --factory-startup --python apps/blender/tests/run_operator_tests.py(297) +run_tests.py(8/8).Notes for review
core/i18n_locales/pt_br.pyfreely - the reverse-coverage test guards structure, not word choice.python scripts/blender/extract_i18n.py --unwrapped. Fixed-literal reports DO translate via report.py's injectable translator.set_translatorDI (translates report literals centrally; theProscenio:prefix stays outside the translated text);i18n.pydropped_as_translations_dict(the fold moved into the package); the locale tables carry a per-file E501 ignore (tooltip/description strings run long)."Operator", everything else under"*"(verified againstbl_rna.translation_context).mainper the docs-direct-to-main convention (commit7dcdd027), not in this PR.Checklist
format_versionchange🤖 Generated with Claude Code
Summary by CodeRabbit