Skip to content

Generate l10n per locale - #3585

Draft
cheweaaron609-png wants to merge 35 commits into
vacanza:devfrom
cheweaaron609-png:generate-l10n-per-locale
Draft

Generate l10n per locale#3585
cheweaaron609-png wants to merge 35 commits into
vacanza:devfrom
cheweaaron609-png:generate-l10n-per-locale

Conversation

@cheweaaron609-png

@cheweaaron609-png cheweaaron609-png commented May 25, 2026

Copy link
Copy Markdown

Proposed change

This script collects and systematizes all translatable strings from the existing .po files across the entire holidays library as the first step of centralizing the localization architecture.
The script (scripts/l10n/json_builder.py) reads all 666 .po files across 157 language folders, deduplicates entries using msgcomment as the grouping key, and produces one JSON entry per unique holiday containing a unique snake_case id, the English comment as the identifier, all translations per language, and all countries that observe the holiday.

Total unique holidays collected: 1,890

Part of GSoC 2026 project: Generate l10n file on per locale basis.

Type of change

  • New country/market holidays support (thank you!)
  • Supported country/market holidays update (calendar discrepancy fix, localization)
  • Existing code/documentation/test/process quality improvement (best practice, cleanup, refactoring, optimization)
  • Dependency update (version deprecation/pin/upgrade)
  • Bugfix (non-breaking change which fixes an issue)
  • Breaking change (a code change causing existing functionality to break)
  • New feature (new holidays functionality in general)

Checklist

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a Python script that builds a consolidated holidays JSON from gettext .po files, includes a representative sample JSON output, and appends one contributor to CONTRIBUTORS.

Changes

Localization Infrastructure

Layer / File(s) Summary
Contributor metadata
CONTRIBUTORS
Adds "Aaron Chewe" to the contributors list.
Localization JSON builder script
scripts/l10n/json_builder.py
New json_builder.py discovers locale directories, parses .po files via polib, derives stable id values from entry comments, groups translations by comment, ensures unique IDs, records per-language messages and contributing country_codes, and writes grouped output to scripts/l10n/holidays_intermediate.json.
Sample localization data
scripts/l10n/holidays_intermediate_sample.json
Provides a full sample holidays_intermediate.json dataset with entries for afternoon, half-day trading day, estimated, multiple "from " thresholds, half-day closing, and "in lieu", each including per-locale messages and country restrictions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

l10n

Suggested reviewers

  • KJhellico
  • PPsyrius
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Generate l10n per locale' is vague and doesn't clearly summarize the main changes, which include a new JSON builder script, sample data, and contributor additions. Consider a more specific title like 'Add l10n JSON builder script and sample localization data' to better reflect the actual changes.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly relates to the changeset, explaining the purpose of the json_builder.py script and its role in centralizing localization architecture.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 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 `@scripts/l10n/json_builder.py`:
- Around line 45-57: The iteration over locale_path and PO files is
non-deterministic (locale_path.iterdir(), lc_messages.glob("*.po")), causing
unstable output; modify the loops to iterate in a deterministic order by using
sorted() on locale_path.iterdir() and on lc_messages.glob("*.po") (or otherwise
sort by name) when constructing lang_folder and po_file lists, and normalize the
countries collection (the list built from country_code/appends) by
deduplicating/sorting it (e.g., convert to a set then sort) before writing the
final JSON to ensure stable ordering across runs.
- Line 71: The current id generation (clean_id = re.sub(r'[^a-z0-9]+', '_',
comment.lower()).strip('_')) can produce duplicates; implement collision
handling by tracking used IDs (e.g., a set or dict) and, when a generated
clean_id already exists, append a numeric suffix like _2, _3, … until it's
unique; apply this change where clean_id is created (both the occurrence at the
current assignment and the logic around lines 90-96 that also generates ids) and
ensure the chosen unique id is stored in the used-IDs tracker before being
emitted.
- Around line 65-66: The grouping key currently prefers entry.tcomment which can
split/merge records across locales; change the assignment so the canonical
grouping key uses entry.comment first (e.g., set comment = entry.comment or
entry.tcomment or "" then comment = comment.strip()) and ensure any grouping
logic that uses the local variable comment continues to reference this updated
value so tcomment is only a fallback metadata source.
- Around line 76-81: Replace the locale-string heuristics used to set
is_default_file with an authoritative check against the PO file headers: read
the PO metadata (e.g., po.metadata.get('Language') and
po.metadata.get('X-Source-Language')) and determine is_default_file by comparing
those header values to the target lang/country_code (normalize case) instead of
relying on COUNTRY_LANG_MAP, lang_parts, or startswith('en'); if metadata is
absent, you may optionally fall back to the existing heuristic, but primary
decision must come from the PO headers.
- Around line 59-62: The code currently swallows all errors when calling
polib.pofile(str(po_file), ...) in the try/except around po_file; change the
bare except to catch specific errors (e.g., except (IOError, OSError,
UnicodeDecodeError, polib.POFileError) as e:) and log the failure including the
po_file path and the exception (use the module logger or
logger.exception/logger.error(..., exc_info=True)) before continuing; ensure the
variable names po_file and po are left intact and preserve the continue after
logging.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a290741e-dbe0-4753-8b26-b3a45c1f577b

📥 Commits

Reviewing files that changed from the base of the PR and between daadab1 and 2f5f180.

📒 Files selected for processing (2)
  • CONTRIBUTORS
  • scripts/l10n/json_builder.py

Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

6 issues found across 2 files

Confidence score: 2/5

  • High-confidence issues in scripts/l10n/json_builder.py suggest real regression risk: fatal failures use exit() without a non-zero status, which can report success in CI while localization generation actually fails.
  • The most severe behavior risk is data integrity: broad Exception handling can silently drop locale files and produce incomplete JSON output, and normalized-ID collisions can break the uniqueness guarantee.
  • There are also correctness concerns in grouping/default logic (entry.tcomment vs canonical comments, and broad en* default-file detection) that can misgroup entries or backfill translations incorrectly for non-default locales.
  • Pay close attention to scripts/l10n/json_builder.py - error signaling, collision handling, and locale/grouping logic can lead to silent bad output.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="scripts/l10n/json_builder.py">

<violation number="1" location="scripts/l10n/json_builder.py:16">
P1: `exit()` without a status code exits with success (0), masking this fatal error in CI/automation</violation>

<violation number="2" location="scripts/l10n/json_builder.py:45">
P3: Sort locale/PO traversal (and normalize `countries` ordering) so the generated JSON remains deterministic across runs.</violation>

<violation number="3" location="scripts/l10n/json_builder.py:61">
P1: Avoid catching `Exception` and continuing silently here; parse/read failures currently drop locale files without any visibility and can produce incomplete JSON output.</violation>

<violation number="4" location="scripts/l10n/json_builder.py:65">
P2: Use `entry.comment` as the grouping key here; preferring `entry.tcomment` can group entries by translator notes instead of the canonical extracted comment.</violation>

<violation number="5" location="scripts/l10n/json_builder.py:71">
P1: No collision handling for normalized IDs: different comments can normalize to the same `clean_id`, violating the PR's claim of unique identifiers.</violation>

<violation number="6" location="scripts/l10n/json_builder.py:79">
P2: This default-file heuristic is too broad: treating every `en*` locale as default can backfill missing `msgstr` with `msgid` for files that are not true source-language defaults.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@scripts/l10n/sample_output.json`:
- Line 99: The Thai string "ชดเชย%s" is incorrectly used for both s_estimated
and s_in_lieu; update the s_estimated Thai value (not the s_in_lieu one) to the
correct distinct translation for "(estimated)" (e.g., use the Thai phrase for
"estimated") and regenerate or re-run the sample_output.json generation to
ensure s_estimated and s_in_lieu no longer collide; locate the entry with "th":
"ชดเชย%s" and the JSON keys s_estimated / s_in_lieu to make this change.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5839dbe5-f05d-47a8-b4f7-58791ed81950

📥 Commits

Reviewing files that changed from the base of the PR and between 2f5f180 and ef706ee.

📒 Files selected for processing (1)
  • scripts/l10n/sample_output.json

Comment thread scripts/l10n/holidays_intermediate_sample.json Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 issues found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="scripts/l10n/json_builder.py">

<violation number="1" location="scripts/l10n/json_builder.py:45">
P3: Sort locale/PO traversal (and normalize `countries` ordering) so the generated JSON remains deterministic across runs.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread scripts/l10n/holidays_intermediate_sample.json Outdated
Comment thread scripts/l10n/holidays_intermediate_sample.json Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@scripts/l10n/json_builder.py`:
- Around line 118-119: The current logic in scripts/l10n/json_builder.py
silently keeps the first translation for a given (comment, lang) pair and
depends on unsorted lc_messages.glob("*.po") order; change it so when building
grouped (use variables grouped, comment, lang, and entry.msgstr) you sort the
list of .po paths deterministically before iterating and, on encountering an
existing grouped[comment]["messages"][lang], compare the new translation against
the stored value and, if they differ, emit an error or warning that includes the
involved .po filenames and both strings (rather than silently skipping); ensure
the deterministic sort dictates precedence if you must choose one.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: eb8c6b11-4900-4a47-a422-3f0463ce6e9a

📥 Commits

Reviewing files that changed from the base of the PR and between e8c11c1 and 4be033a.

📒 Files selected for processing (1)
  • scripts/l10n/json_builder.py

Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (4)
scripts/l10n/json_builder.py (4)

66-78: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

ID de-duplication can still produce collisions.

_deduplicate_ids() can rename a duplicate foo to foo_1 even when another entry already has base ID foo_1, so uniqueness is still not guaranteed.

Suggested fix
 `@staticmethod`
 def _deduplicate_ids(grouped: dict) -> None:
-    """Ensure all IDs are unique by appending a counter to duplicates."""
-    id_counts: dict = {}
-    for entry in grouped.values():
-        id_counts[entry["id"]] = id_counts.get(entry["id"], 0) + 1
-
-    id_seen: dict = {}
+    """Ensure all IDs are unique by appending a counter to duplicates."""
+    used_ids: set[str] = set()
+    next_suffix: dict[str, int] = {}
     for entry in grouped.values():
         base_id = entry["id"]
-        if id_counts[base_id] > 1:
-            id_seen[base_id] = id_seen.get(base_id, 0) + 1
-            entry["id"] = f"{base_id}_{id_seen[base_id]}"
+        candidate = base_id
+        if candidate in used_ids:
+            idx = next_suffix.get(base_id, 1)
+            while f"{base_id}_{idx}" in used_ids:
+                idx += 1
+            candidate = f"{base_id}_{idx}"
+            next_suffix[base_id] = idx + 1
+        entry["id"] = candidate
+        used_ids.add(candidate)
🤖 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 `@scripts/l10n/json_builder.py` around lines 66 - 78, _deduplicate_ids
currently appends a counter like "foo_1" to duplicates but doesn't check whether
that new ID already exists, causing possible collisions; change the logic in
_deduplicate_ids to first build a set of used_ids from all current entry["id"]
values, then for each entry whose base_id is duplicated, loop incrementing a
suffix (start at 1) until base_id_suffix is not in used_ids, assign entry["id"]
= base_id_suffix and add it to used_ids; you can drop or keep id_counts/id_seen
but ensure the uniqueness check uses the used_ids set to avoid collisions.

108-111: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t silently ignore PO parse errors.

Line 110 catches all exceptions and drops the file quietly, which can hide missing locale data in output.

Suggested fix
-from polib import pofile
+import sys
+from polib import POFileError, pofile
@@
-        except Exception:
+        except (OSError, UnicodeDecodeError, POFileError) as exc:
+            print(f"WARN: Skipping unreadable PO file {po_file_path}: {exc}", file=sys.stderr)
             return
🤖 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 `@scripts/l10n/json_builder.py` around lines 108 - 111, The try/except around
pofile(str(po_file_path), encoding="utf-8") currently swallows all exceptions
and returns silently; change it to catch the exception as a variable (e.g.,
except Exception as e) and emit a clear error (include po_file_path and the
exception message/traceback) instead of silently returning so PO parse failures
are visible; keep the same control flow (skip the bad file) but log the failure
using the module's logger or stderr before continuing.

82-82: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use entry.comment as the canonical grouping key.

Line 82 currently prefers entry.tcomment, which can split/merge groups unexpectedly when grouping is intended by msgcomment.

Suggested fix
-        comment = (entry.tcomment or entry.comment or "").strip()
+        comment = (entry.comment or entry.tcomment or "").strip()
🤖 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 `@scripts/l10n/json_builder.py` at line 82, Change the grouping key so
msgcomment uses entry.comment as the canonical source: replace the current
preference of entry.tcomment with entry.comment when computing the local
variable comment (used for grouping) — i.e., compute comment from entry.comment
first, falling back to entry.tcomment, then strip; update the assignment where
comment is created to reference entry.comment before entry.tcomment so grouping
uses msgcomment consistently.

98-99: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Translation precedence is order-dependent and lossy.

Line 98 keeps first-write-wins for (comment, lang). Combined with unsorted traversal at Line 123, different runs can pick different winners. Also, countries ordering should be normalized before write.

Suggested fix
-        if lang not in grouped[comment]["messages"]:
-            grouped[comment]["messages"][lang] = translation
+        existing = grouped[comment]["messages"].get(lang)
+        if existing is None:
+            grouped[comment]["messages"][lang] = translation
+        elif existing != translation:
+            print(
+                f"WARN: Conflicting translation for comment={comment!r}, lang={lang}, "
+                f"country={country_code}: keeping {existing!r}, skipping {translation!r}"
+            )
@@
-        for po_file_path in locale_path.rglob("*.po"):
+        for po_file_path in sorted(locale_path.rglob("*.po"), key=lambda p: p.as_posix()):
             lang = po_file_path.parents[1].name
             IntermediateJsonBuilder._process_po_file(
                 po_file_path, lang, grouped)
@@
+        for item in grouped.values():
+            item["countries"].sort()
         output = sorted(grouped.values(), key=lambda x: x["comment"])

Also applies to: 123-126, 129-129

🤖 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 `@scripts/l10n/json_builder.py` around lines 98 - 99, The code currently uses
first-write-wins for grouped[comment]["messages"][lang] with an unsorted
traversal, producing non-deterministic and lossy results; change the logic to be
deterministic by iterating sources/countries in a stable, sorted order and then
explicitly setting (overwriting) grouped[comment]["messages"][lang] =
translation so precedence is controlled and repeatable. Locate code manipulating
grouped, comment, lang, translation and the loop over countries (the blocks
around grouped[comment]["messages"][lang] and the traversal at lines ~123-129)
and: sort the list/keys used to drive that traversal (normalize country
ordering) and replace the conditional "if lang not in
grouped[comment]['messages']" with an unconditional assignment (or explicit
precedence check) so the winner is deterministic across runs.
🤖 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.

Duplicate comments:
In `@scripts/l10n/json_builder.py`:
- Around line 66-78: _deduplicate_ids currently appends a counter like "foo_1"
to duplicates but doesn't check whether that new ID already exists, causing
possible collisions; change the logic in _deduplicate_ids to first build a set
of used_ids from all current entry["id"] values, then for each entry whose
base_id is duplicated, loop incrementing a suffix (start at 1) until
base_id_suffix is not in used_ids, assign entry["id"] = base_id_suffix and add
it to used_ids; you can drop or keep id_counts/id_seen but ensure the uniqueness
check uses the used_ids set to avoid collisions.
- Around line 108-111: The try/except around pofile(str(po_file_path),
encoding="utf-8") currently swallows all exceptions and returns silently; change
it to catch the exception as a variable (e.g., except Exception as e) and emit a
clear error (include po_file_path and the exception message/traceback) instead
of silently returning so PO parse failures are visible; keep the same control
flow (skip the bad file) but log the failure using the module's logger or stderr
before continuing.
- Line 82: Change the grouping key so msgcomment uses entry.comment as the
canonical source: replace the current preference of entry.tcomment with
entry.comment when computing the local variable comment (used for grouping) —
i.e., compute comment from entry.comment first, falling back to entry.tcomment,
then strip; update the assignment where comment is created to reference
entry.comment before entry.tcomment so grouping uses msgcomment consistently.
- Around line 98-99: The code currently uses first-write-wins for
grouped[comment]["messages"][lang] with an unsorted traversal, producing
non-deterministic and lossy results; change the logic to be deterministic by
iterating sources/countries in a stable, sorted order and then explicitly
setting (overwriting) grouped[comment]["messages"][lang] = translation so
precedence is controlled and repeatable. Locate code manipulating grouped,
comment, lang, translation and the loop over countries (the blocks around
grouped[comment]["messages"][lang] and the traversal at lines ~123-129) and:
sort the list/keys used to drive that traversal (normalize country ordering) and
replace the conditional "if lang not in grouped[comment]['messages']" with an
unconditional assignment (or explicit precedence check) so the winner is
deterministic across runs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 31355ecb-e5a4-45b7-9065-37a7b5a3c78e

📥 Commits

Reviewing files that changed from the base of the PR and between 4be033a and dd31610.

📒 Files selected for processing (1)
  • scripts/l10n/json_builder.py

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 issues found across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread scripts/l10n/json_builder.py Outdated
@cubic-dev-ai

cubic-dev-ai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor

You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment @cubic-dev-ai review.

@KJhellico KJhellico left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Last refactoring, and you can start analyzing the collected data. ;)

Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_builder.py
@sonarqubecloud

Copy link
Copy Markdown

❌ The last analysis has failed.

See analysis details on SonarQube Cloud

@KJhellico

Copy link
Copy Markdown
Collaborator

I'm not sure what the best way to organize translation discussions is, so for now I will simply share my suggestions.

Issue Comment Proposed solution
"New Year's Day" in Thai (East Asian countries) As I understand it, this literally translates as "International New Year" (as opposed to the traditional Chinese New Year (Spring Festival)) Introduce a separate msgid: "International New Year" (as in Cambodia)
Monaco Labor Day name is clearly incorrect, should be fixed. The remaining names (with articles) are taken from the official source Create a dedicated fr_MC locale for Monaco (a country-specific locale would also be useful in other cases)
Labor Day in African countries Standardize to "Fête du Travail" if no other differences from the generic French translation are found; otherwise, create separate locales
Christmas Day in Spanish This issue is not limited to Christmas; it will likely occur with Spanish translations in general Review the official sources once again. If "Día de Navidad" (and other differences) is confirmed, create separate locales where appropriate
Two Christmas holidays In some European countries, the two holiday dates are indeed called "First Day of Christmas" and "Second Day of Christmas". They are currently translated as "Christmas Day" and "Second Day of Christmas" Introduce a separate msgid such as "First Christmas Day" or "First Day of Christmas". In en_US, it could still be translated as "Christmas Day"
Epiphany in Slovakia A typical example of a country-specific name for a common holiday. This situation is likely to occur repeatedly Use a separate msgid
Independence Day (and similar examples) Use a separate msgid. In en_US it can be translated as "Independence Day"; other languages can follow local traditions and preferences

@KJhellico

Copy link
Copy Markdown
Collaborator

Also we need to think about the technical aspects of making these changes. In particular:

  1. How do we assign a new msgid to an entry? Generally, it will match existing en_US version, but there will be numerous changes required.
  2. How do we implement the process of separating an entry into dedicated msgid?
  3. Provide the ability not only to build a JSON file from scratch, but also to update it. Since it’s obviously not possible to make all changes at once, a realistic scenario is one where certain changes have been made, but translations for some countries have been added or modified in the repository. These changes must be incorporated without losing the edits made to the JSON.

Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_splitter.py Outdated
Comment thread scripts/l10n/json_splitter.py Outdated
Comment thread scripts/l10n/json_builder.py Outdated
Comment thread scripts/l10n/json_splitter.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread scripts/l10n/replace_tr_strings.py

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread scripts/l10n/replace_tr_strings.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread scripts/l10n/replace_tr_strings.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Confidence score: 4/5

  • In scripts/l10n/replace_tr_strings.py, removing --preview means every run now edits files in-place, which raises the chance of unintended bulk changes across hundreds of localization files and makes review/rollback harder—reintroduce a dry-run/preview mode (or equivalent diff-only path) before write operations.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="scripts/l10n/replace_tr_strings.py">

<violation number="1" location="scripts/l10n/replace_tr_strings.py:125">
P3: The `--preview` flag was removed, so the script now always writes changes in-place with no safe-preview path. Users can no longer inspect what would change before 666+ files are modified.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

for start, end, new_val in sorted(spans, key=lambda c: c[0], reverse=True):
source = source[:start] + new_val + source[end:]

return source, len(changes)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: The --preview flag was removed, so the script now always writes changes in-place with no safe-preview path. Users can no longer inspect what would change before 666+ files are modified.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/l10n/replace_tr_strings.py, line 125:

<comment>The `--preview` flag was removed, so the script now always writes changes in-place with no safe-preview path. Users can no longer inspect what would change before 666+ files are modified.</comment>

<file context>
@@ -84,22 +96,38 @@ def to_char_offset(lineno, byte_col):
+        source = source[:start] + new_val + source[end:]
 
-    return source, replacements
+    return source, len(changes)
 
 
</file context>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 2 files (changes from recent commits).

Not reviewed (too large): scripts/l10n/holidays_l10n.json (~8,123 lines) - if these are generated or fixture files, add them to ignored paths to exclude them from future reviews.

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread .gitignore Outdated
Comment thread scripts/l10n/replace_tr_strings.py Outdated
for lang, val in entry["messages"].items():
if isinstance(val, str):
candidates.setdefault(val, set()).add(msgid)
return {val: next(iter(msgids)) for val, msgids in candidates.items() if len(msgids) == 1}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It's wrong to ignore translations for which there is more than one msgid. If this string doesn't appear in .py files, that's fine, but if it's actually there, at least a warning should be displayed.

Comment thread scripts/l10n/replace_tr_strings.py Outdated
Comment on lines +106 to +108
comment_lineno = arg.lineno - 2
if comment_lineno < 0:
continue

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Check multi-line comments to multi-line messages (e.g., in Germany); they are not processed.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Confidence score: 3/5

  • In scripts/l10n/replace_tr_strings.py, the broad startswith("tr(") match can misclassify single-line tr("...") calls as multi-line openers when they appear near commented calls, which risks incorrect localization replacements or misplaced comments in generated output; tighten the detection to distinguish single-line vs multi-line tr() patterns (and add a regression test for the two-lines-above comment case).
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="scripts/l10n/replace_tr_strings.py">

<violation number="1" location="scripts/l10n/replace_tr_strings.py:121">
P1: The `startswith("tr(")` check is too broad: it matches both multi-line `tr(` openers and single-line `tr("...")` calls. When a previous single-line `tr()` call with its own comment sits exactly two lines above the current string, the code incorrectly treats it as a multi-line opener and backs up one more line—replacing the wrong comment. This can corrupt source file l10n metadata.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

comment_line = lines[comment_end_lineno]
stripped = comment_line.lstrip()
# If line above string is tr( itself, look one more line up
if stripped.startswith("tr(") or stripped == "tr(":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1: The startswith("tr(") check is too broad: it matches both multi-line tr( openers and single-line tr("...") calls. When a previous single-line tr() call with its own comment sits exactly two lines above the current string, the code incorrectly treats it as a multi-line opener and backs up one more line—replacing the wrong comment. This can corrupt source file l10n metadata.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/l10n/replace_tr_strings.py, line 121:

<comment>The `startswith("tr(")` check is too broad: it matches both multi-line `tr(` openers and single-line `tr("...")` calls. When a previous single-line `tr()` call with its own comment sits exactly two lines above the current string, the code incorrectly treats it as a multi-line opener and backs up one more line—replacing the wrong comment. This can corrupt source file l10n metadata.</comment>

<file context>
@@ -117,6 +117,13 @@ def to_char_offset(lineno, byte_col):
         comment_line = lines[comment_end_lineno]
         stripped = comment_line.lstrip()
+        # If line above string is tr( itself, look one more line up
+        if stripped.startswith("tr(") or stripped == "tr(":
+            comment_end_lineno -= 1
+            if comment_end_lineno < 0:
</file context>

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
E Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants