Skip to content

feat(i18n): wrap user-facing strings across the editor - #721

Merged
surajshetty3416 merged 12 commits into
frappe:developfrom
surajshetty3416:i18n-coverage-sweep
Aug 13, 2026
Merged

feat(i18n): wrap user-facing strings across the editor#721
surajshetty3416 merged 12 commits into
frappe:developfrom
surajshetty3416:i18n-coverage-sweep

Conversation

@surajshetty3416

@surajshetty3416 surajshetty3416 commented Aug 8, 2026

Copy link
Copy Markdown
Member

Translation support landed with only four demonstration strings, which left
visible inconsistencies: the sidebar rendered a translated "All Pages"
directly above an untranslated "Settings".

Wraps label, tooltip, placeholder, title and Tooltip text attributes along
with toast, confirm and alert messages, taking the catalog from 163 to 337
entries. The extractor reads __() from both the template and the script
block of a .vue file, so both forms reach the POT.

Only prose is wrapped. Bindings, interpolated values and the "Builder"
product name are left alone, and nothing compares a label against a literal
anywhere in the frontend, so no behaviour depends on these strings.

Continuation of #706

Translation support landed with only four demonstration strings, which left
visible inconsistencies: the sidebar rendered a translated "All Pages"
directly above an untranslated "Settings".

Wraps label, tooltip, placeholder, title and Tooltip text attributes along
with toast, confirm and alert messages, taking the catalog from 163 to 337
entries. The extractor reads `__()` from both the template and the script
block of a .vue file, so both forms reach the POT.

Only prose is wrapped. Bindings, interpolated values and the "Builder"
product name are left alone, and nothing compares a label against a literal
anywhere in the frontend, so no behaviour depends on these strings.

Also guards against Crowdin's in-context pseudo language, which exports
`crwdns...` ids that score as fully translated and would render as those ids
across the editor. Esperanto cannot be disabled in the project, so the test
fails the build instead if any catalog carries them.

Co-Authored-By: Claude <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported commented-out strings are no longer extracted.

Reviews (4): Last reviewed commit: "feat(i18n): translate template text node..." | Re-trigger Greptile

Comment thread frontend/src/components/BlockGridLayoutHandler.vue Outdated
@codecov

codecov Bot commented Aug 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 40.00000% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.05%. Comparing base (326ff03) to head (3f6590a).

Files with missing lines Patch % Lines
builder/api.py 16.66% 5 Missing ⚠️
builder/ai_page_generator.py 0.00% 3 Missing ⚠️
...ilder/builder/doctype/builder_page/builder_page.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #721      +/-   ##
===========================================
+ Coverage    66.01%   66.05%   +0.04%     
===========================================
  Files           37       37              
  Lines         4976     4982       +6     
===========================================
+ Hits          3285     3291       +6     
  Misses        1691     1691              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

surajshetty3416 and others added 5 commits August 8, 2026 20:47
Wrapping labels inside commented-out markup put Align, Flow, Place Items
and Place Self into the catalog, giving translators work for controls that
do not render. Reverted those five, per review on frappe#721.

Also wraps the six frappe.throw messages that were still untranslated. The
two f-strings become _("...{0}").format(...), since the extractor cannot
read an interpolated literal.

Co-Authored-By: Claude <noreply@anthropic.com>
emit() sends these straight to progressMessage and errorMessage in the
generator modal, so they render as-is. The dynamic stage becomes
_("Building {0}").format(name).

last_stage only dedupes repeated emits and nothing on either side compares
against these values, so translating them changes no behaviour.

Co-Authored-By: Claude <noreply@anthropic.com>
The earlier sweep only rewrote attributes, so prose sitting between tags
was skipped. That left empty and loading states untranslated, which is the
copy a user reads most closely: "No matching pages found.", "No components
saved", "Select a block to edit properties", "Loading...".

Wraps 87 text nodes across 46 files, taking the catalog to 411 entries.
Comments and pre/code/script content are left alone, as is anything
containing markup, interpolation or code punctuation.

Co-Authored-By: Claude <noreply@anthropic.com>
Wrap the display fields the earlier sweep missed: object literal
labels and titles, static description attributes, property section
names, keyboard shortcut groups, ternary branches, plain text nodes
and toast messages.

Lookup keys stay untranslated throughout. Settings tabs keep their
value, which is persisted to localStorage. Block template categories
keep their stored values and translate at render. The command palette
keeps group, which it matches against a key.

Shortcut groups are wrapped in every file that registers a shortcut.
Translating only one of them makes the shortcuts dialog render a
translated and an untranslated heading for the same group, since
frappe-ui buckets by the string itself.

The extraction test pinned "Select (v)", which no longer exists: the
shortcut key now sits outside the translated string so a translator
cannot alter it. Retarget the fixture at a string unique to that file.

Co-Authored-By: Claude <noreply@anthropic.com>
Translate all 841 messages. Only 34 were filled in before, and they
came from frappe's own catalog, where several are machine translated
wrong: Value read as "price", Script as "a written story", Back to
Home as "I am going home", Save as "rescue". Those are rewritten
rather than carried over.

Unify terminology while filling the rest, so one English term does
not appear under two Hindi words. Insert now uses the standard Insert
menu term instead of the same word used for Add.

Database records stay untranslated: Builder Token names, block
template fixture names, doctype names, keyboard key codes and the
CSS keyword unset, which is compared as a sentinel in cssUtils.

Co-Authored-By: Claude <noreply@anthropic.com>
@mardausdennis

mardausdennis commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

I checked the normal dashboard/editor journey at the current head (c70379e) and found a few remaining rendered strings that still cannot reach the POT:

  • Dashboard/page lifecycle: DashboardContent.vue still has the initial no-pages message and Load More; DashboardHead.vue, DashboardSidebar.vue, PageActionsDropdown.vue, PageCard.vue, PageListItem.vue, and utils/dialogs.ts retain normal filter, sort, folder, status, and page-action prose.
  • Editor/publish: BuilderToolbar.vue, MainMenu.vue, and PublishButton.vue still leave basic tool/menu labels plus Publish, Publish Changes, Version History, and Unpublish literal.
  • Upload: ImageUploadInput.vue, Controls/ImageUploader.vue, and utils/helpers.ts retain image-fit choices and upload progress/success/error text.
  • Restore/errors: VersionHistory.vue still has the empty state and restore confirmation; pageStore.ts retains page open/duplicate/delete/revert messages; BuilderPageRenderer.validate_access() still raises Please log in to view this page. without translation.

The existing unpublish source correctly preserves its quotes and {0} placeholder. Dynamic delete/page-metadata prose should likewise use stable {0} sources rather than interpolated literals. I limited this pass to the normal customer path and excluded AI, developer, analytics, and custom-domain surfaces.

@stravo1

stravo1 commented Aug 11, 2026

Copy link
Copy Markdown
Member

This will collide with #715 . We need to decide which one makes more sense to merge first.

# Conflicts:
#	builder/builder/tests/test_i18n.py
#	builder/locale/ar.po
#	builder/locale/bs.po
#	builder/locale/cs.po
#	builder/locale/da.po
#	builder/locale/de.po
#	builder/locale/es.po
#	builder/locale/fa.po
#	builder/locale/fr.po
#	builder/locale/hr.po
#	builder/locale/it.po
#	builder/locale/nl.po
#	builder/locale/pt.po
#	builder/locale/ru.po
#	builder/locale/sr.po
#	builder/locale/sv.po
#	builder/locale/tr.po
#	frontend/src/components/BlockContextMenu.vue
#	frontend/src/components/BuilderCommandPalette.vue
#	frontend/src/components/BuilderLeftPanel.vue
#	frontend/src/components/BuilderSettings.vue
#	frontend/src/components/BuilderToolbar.vue
#	frontend/src/components/MainMenu.vue
#	frontend/src/pages/PageBuilder.vue
#	frontend/src/utils/builderBlockCopyPaste.ts
#	frontend/src/utils/useBuilderEvents.ts
@greptile-apps

greptile-apps Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Too many files changed for review (177 files, 100 file limit).

Bypass the limit by tagging @greptile-apps to review.

@mardausdennis

Copy link
Copy Markdown
Contributor

Thanks for rebasing this onto the registry refactor. I rechecked the normal customer path at b2dd2f9; many of the earlier items are fixed, but a few rendered strings still bypass translation:

  • Dashboard: the initial no-pages state, No folders yet, the folder-delete confirmation, New, and the dynamic edited/created metadata.
  • Registry toolbar/editor: remaining literals in BuilderToolbar.vue, ToolbarItems/PageTitlePopover.vue, ToolbarItems/ReadOnlyBadge.vue, ToolbarItems/ToolbarActions.vue, Commands/index.ts, and the normal PageBuilder.vue fallbacks.
  • Upload/restore: upload progress and result messages, plus the Version History restore confirmation and Version fallback.
  • Page operations/dialogs: duplicate/delete/revert messages and the default Confirm / Alert titles.

Dynamic prose such as Edited {0}, Last updated {0} by {1}, Uploading {0}%, and the delete confirmation should use stable placeholder sources.

surajshetty3416 and others added 5 commits August 13, 2026 06:59
# Conflicts:
#	builder/export_import_standard_page.py
The develop merge adopted the extendible-UI refactor and dropped a
cluster of wraps in the process: both context-menu confirmations, the
Template / Read Only badge, the Generate with AI and homepage tooltips,
the Get Started dialog and every plain shortcut group. commandShortcuts()
kept translating its groups, so the shortcuts modal bucketed one General
under two headings, one translated and one not.

Covers the rest of the review on frappe#721: dashboard empty states, the New
button, folder deletion, page duplicate/delete/revert messages, upload
progress and results, the version restore confirmation and the default
Confirm and Alert dialog titles. Dynamic prose formats through stable
placeholders: Edited {0}, Last updated {0} by {1}, Uploading {0}%.

Group headings translate at render from raw keys, so the literals now
live in commandGroupLabels and settingsGroupLabels where the extractor
can see them; Edit, Canvas, Tools and Current Page were missing from
the catalog entirely.

The dashboard empty state loses the quotes around + New: the .vue
extractor is regex-based and cannot read a string that escapes both
quote characters.

Hindi stays complete at 908 entries.

Co-Authored-By: Claude <noreply@anthropic.com>
The dev server serves the static index.html, which carries no boot
payload, so window.translated_messages stayed undefined and every
__() fell back to English regardless of the user's language.

Fetch the catalog from frappe.translate.get_boot_translations before
installing the router, so module-scope __() calls in route chunks see
it. Production keeps the synchronous boot injection and skips the
fetch.

Co-Authored-By: Claude <noreply@anthropic.com>
The robots.txt description carried a literal {{ __("...") }} inside a
plain attribute string, which vue never evaluates, so the editor
rendered the raw expression. The description now composes in script
with the guide link as a {0} placeholder.

The extraction regex cannot cross a newline inside a call's argument
list, so the description sits in script where prettier keeps each
argument on one line. Also wraps statics the sweep missed: the
analytics empty-state hint, the persona survey subtitle and greeting,
the More Styles empty state and the settings dialog heading.

Co-Authored-By: Claude <noreply@anthropic.com>
The script dialog composed its title from capitalize(mode), which no
catalog can carry; each mode and script type now maps to a full msgid.
The note beside it translated only the Note: prefix, leaving the
sentence in English.

Also wraps text nodes that earlier sweeps missed because they sliced
the template at the first closing template tag, which in files with
nested slot templates cut most of the file: palette footer hints,
Add Script, Add Domain, Clear Image and Gradient, component update
notice, Upload CSV, Download sample, Replace and the page usage
counts, which now format through {0}.

Co-Authored-By: Claude <noreply@anthropic.com>
@surajshetty3416
surajshetty3416 merged commit ee35bbd into frappe:develop Aug 13, 2026
9 of 10 checks passed
@surajshetty3416
surajshetty3416 deleted the i18n-coverage-sweep branch August 13, 2026 02:58
@surajshetty3416

Copy link
Copy Markdown
Member Author

@mardausdennis give it a try again?

@mardausdennis

mardausdennis commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Rechecked at 38d68fd. The normal customer path reads much better now: folder delete, version restore, revert, delete/unpublish prompts, dashboard states, publish controls, upload progress and Version History all come through the catalog, and the dynamic ones use stable placeholders.

A few rendered strings are still unwrapped:

  • Components: stores/componentStore.tsSave Component, the delete-component confirmation, and the three sync toasts (Syncing component in all the pages..., Component synced in all the pages!, Error syncing component in all the pages!).
  • Route tree: RouteTreeView.vueLoading pages…, Load {n} more, ({n} remaining); the latter two also in RouteTreeNode.vue.
  • Editor shell: PageBuilder.vueScreen too small and Please switch to a larger screen to edit; PageOptions.vue — the URL Variables section name.
  • Command palette: CommandPalette.vueSearch..., Search commands..., No results for "{query}" (the quotes are part of the rendered string), No commands found.
  • Toolbar: ToolbarItems/ViewerAvatars.vue — the & {n} others summary.

The count-bearing ones would need stable sources like the ones you already introduced elsewhere: Load {0} more, ({0} remaining), & {0} others, the delete-component prompt, and the palette query.

One thing worth keeping in mind for future callsites: confirm() and alert() translate their default titles and buttons, but pass message through unchanged, so each caller has to wrap its own message.

@mardausdennis

Copy link
Copy Markdown
Contributor

Opened #746 with the rest of them, including the settings surfaces that earlier sweeps hadn't reached — domains, redirects and users were still almost entirely English.

Two things I left out and explained in the description rather than guessing: the permission messages passed to has_page_write / has_page_read, since a decorator argument resolves at import time, and the gradient/page preset names, which are emitted to callers.

@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 1.33.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants