Skip to content

Instantly share code, notes, and snippets.

@loicteixeira
Last active November 26, 2022 12:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save loicteixeira/bcc5e678e3d06af07bdb3afe9d149575 to your computer and use it in GitHub Desktop.
Save loicteixeira/bcc5e678e3d06af07bdb3afe9d149575 to your computer and use it in GitHub Desktop.
[Wagtail] Conversion scripts for wagtail/wagtail#9377

Steps taken to merge this (for posterity, and also because I'll have to do it all over again for wagtail-localize which is in the same Transifex project).

BRANCH_NAME="7111-enforce-translation-string-formatting"

# Merge
git switch main
git pull --ff-only upstream main
git switch "${BRANCH_NAME}"
git reset $(git merge-base main "${BRANCH_NAME}")
git add .
git commit -m "Enforce the use of a single string formatting mechanism for translation source strings" -m "Close #9377"
git rebase main
git merge --ff-only "${BRANCH_NAME}"

# Pull & commit existing translations
cd scripts
./fetch-translations.sh > ../../logs/translation.out 2>../../logs/translation.err
python ./check-translation-strings.py
cd ..
git add .
git commit -m "Fetch new translations from Transifex"

# Update the strings and sanity check with French translation (I can't possibly check all languages...)
python ./scripts/source_strings_update.py
find wagtail -name *.po -type f -path **/fr/** -exec git diff --staged --word-diff {} \;
git add .

# Rebuild English translations to check if there's diff from what the script has done
cd scripts
./rebuild-translation-sources.sh
cd ..
git add .

git commit -m "Update translations placeholder names" -m "See 364de3d4dc and #9377"

# Last linting check
semgrep --config .semgrep.yml --error .

Now the tricky part:

# Upload English source strings to Transifex
tx push --source

Languages now show 160 untranslated strings as expected.

# Upload French translations to Transifex for sanity check on Transifex
tx push --translations --language=fr

Sadly it did not work, files were being "skipped", and we still had 160 strings to translate.

Trying to fix it:

git checkout HEAD~1  # Come back to the commit before the placeholder update
tx push --source  # Re-push the original English sources

At this point, Transifex is back to what it was before I started.

git switch main

# Delete translations
find wagtail -iname *.po ! -iwholename */en/* -delete

# Re-fetch, but without using `scripts/fetch-translations.sh` to keep strings location information.
tx pull --all --minimum-perc=1

# Re-update placeholders
python ./scripts/source_strings_update.py

# Re-upload English updated source strings to Transifex
tx push --source

# Re-upload French translations
tx push --translations --language=fr

Unfortunately, files are still being "skipped". While the location information "might" be required anyway, the files seem to be skipped because of their modification timestamp. Trying to force the upload.

# Re-upload French translations
tx push --translations --language=fr --force --no-interactive

This work, but it means that the time between fetch and upload has to be as short as possible, or we will overwrite work from translators which might be updating strings at the same time we do this work.

# Upload other translations
tx push --translations --force --no-interactive

So at this point I'm not sure if files were skipped because of the missing location information or simply the timestamps. It feels like it would be the latter (location information shouldn't be needed as long as you have the string "id") but I don't want to fiddle with it any more for today.

In the end, there are: 14 strings which weren't updated (that's 10 more than the 5 impossible/ambiguous auto-update we had identified).

So 160 strings down to 14. Not perfect but not bad either.

# Cleanup & merge
git restore .
git push --dry-run upstream main
git push upstream main

So this might need some refinement before we start using it on a "regular" basis.

a cartoon dog sitting at a table in the middle of a room in fire saying "this is fine" and looking into its coffee cup

Anyway, thank you @lb-, @th3hamm0r, @gasman & @laymonage for your help on this issue.

# client/src/components/Minimap/MinimapItem.tsx
- {num} error
+ %(num)s error
- {num} errors
+ %(num)s errors
# client/src/components/PageExplorer/PageExplorerItem.tsx
- Edit '{title}'
+ Edit '%(title)s'
- View child pages of '{title}'
+ View child pages of '%(title)s'
# client/src/components/Sidebar/menu/SubMenuItem.tsx
- ({number} new items in this menu)
+ (%(number)s new items in this menu)
# client/src/entrypoints/admin/page-editor.js
- ({errorCount} error)
+ (%(errorCount)s error)
- ({errorCount} errors)
+ (%(errorCount)s errors)
# client/src/includes/sidePanel.js
- {num} pixel
+ %(num)s pixel
- {num} pixels
+ %(num)s pixels
# wagtail/admin/action_menu.py
- Resubmit to {}
+ Resubmit to %(task_name)s
- Submit to {}
+ Submit to %(workflow_name)s
# wagtail/admin/forms/auth.py
- Enter your %s
+ Enter your %(username_field_name)s
# wagtail/admin/forms/pages.py
- This slug is already in use within the context of its parent page "%s"
+ This slug is already in use within the context of its parent page "%(parent_page_title)s"
# wagtail/admin/forms/workflows.py
- This page already has workflow '{0}' assigned.
+ This page already has workflow '%(workflow_name)s' assigned.
# wagtail/admin/localization.py
- {0} pages selected
+ %(objects)s pages selected
- All {0} pages on this screen selected
+ All %(objects)s pages on this screen selected
- {0} documents selected
+ %(objects)s documents selected
- All {0} documents on this screen selected
+ All %(objects)s documents on this screen selected
- {0} images selected
+ %(objects)s images selected
- All {0} images on this screen selected
+ All %(objects)s images on this screen selected
- {0} users selected
+ %(objects)s users selected
- All {0} users on this screen selected
+ All %(objects)s users on this screen selected
- {0} snippets selected
+ %(objects)s snippets selected
- All {0} snippets on this screen selected
+ All %(objects)s snippets on this screen selected
- {0} items selected
+ %(objects)s items selected
- All {0} items on this screen selected
+ All %(objects)s items on this screen selected
# wagtail/admin/wagtail_hooks.py
- What's new in Wagtail {version}
+ What's new in Wagtail %(version)s
# wagtail/admin/views/collections.py
- Collection '{0}' created.
+ Collection '%(object)s' created.
- Collection '{0}' updated.
+ Collection '%(object)s' updated.
- Collection '{0}' deleted.
+ Collection '%(object)s' deleted.
# wagtail/admin/views/generic/mixins.py
- {model_name} '{instance}' has been replaced with version from {timestamp}.
+ %(model_name)s '%(object)s' has been replaced with version from %(timestamp)s.
- Version from {timestamp} of {model_name} '{instance}' has been published.
+ Version from %(timestamp)s of %(model_name)s '%(object)s' has been published.
- Version from {timestamp} of {model_name} '{instance}' has been scheduled for publishing.
+ Version from %(timestamp)s of %(model_name)s '%(object)s' has been scheduled for publishing.
# wagtail/admin/views/generic/models.py
- '{0}' created and scheduled for publishing.
+ '%(object)s' created and scheduled for publishing.
- '{0}' created and published.
+ '%(object)s' created and published.
- '{0}' updated and scheduled for publishing.
+ '%(object)s' updated and scheduled for publishing.
- '{0}' updated and published.
+ '%(object)s' updated and published.
- '{object_name}' unpublished.
+ '%(object)s' unpublished.
- Version {revision_id} of "{object}" unscheduled.
+ Version %(revision_id)s of "%(object)s" unscheduled.
- revision {revision_id} of "{object}"
+ revision %(revision_id)s of "%(object)s"
# wagtail/admin/views/pages/convert_alias.py
- Page '{0}' has been converted into an ordinary page.
+ Page '%(page_title)s' has been converted into an ordinary page.
# wagtail/admin/views/pages/copy.py
- Page '{0}' and {1} subpages copied.
+ Page '%(page_title)s' and %(subpages_count)s subpages copied.
- Page '{0}' copied.
+ Page '%(page_title)s' copied.
# wagtail/admin/views/pages/create.py
- Page '{0}' created.
+ Page '%(page_title)s' created.
- Page '{0}' created and scheduled for publishing.
+ Page '%(page_title)s' created and scheduled for publishing.
- Page '{0}' created and published.
+ Page '%(page_title)s' created and published.
- Page '{0}' created and submitted for moderation.
+ Page '%(page_title)s' created and submitted for moderation.
# wagtail/admin/views/pages/delete.py
- Page '{0}' deleted.
+ Page '%(page_title)s' deleted.
# wagtail/admin/views/pages/edit.py
- Page '{0}' has been replaced with version from {1}.
+ Page '%(page_title)s' has been replaced with version from %(previous_revision_datetime)s.
- Page '{0}' has been updated.
+ Page '%(page_title)s' has been updated.
- Workflow on page '{0}' has been cancelled.
+ Workflow on page '%(page_title)s' has been cancelled.
- Version from {0} of page '{1}' has been scheduled for publishing.
+ Version from %(previous_revision_datetime)s of page '%(page_title)s' has been scheduled for publishing.
- Page '{0}' is live and this version has been scheduled for publishing.
+ Page '%(page_title)s' is live and this version has been scheduled for publishing.
- Page '{0}' has been scheduled for publishing.
+ Page '%(page_title)s' has been scheduled for publishing.
- Version from {0} of page '{1}' has been published.
+ Version from %(datetime)s of page '%(page_title)s' has been published.
- Page '{0}' has been published.
+ Page '%(page_title)s' has been published.
- Page '{0}' has been submitted for moderation.
+ Page '%(page_title)s' has been submitted for moderation.
- Workflow on page '{0}' has been restarted.
+ Workflow on page '%(page_title)s' has been restarted.
# wagtail/admin/views/pages/lock.py
- Page '{0}' is now unlocked.
+ Page '%(page_title)s' is now unlocked.
# wagtail/admin/views/pages/moderation.py
- The page '{0}' is not currently awaiting moderation.
+ The page '%(page_title)s' is not currently awaiting moderation.
- Page '{0}' published.
+ Page '%(page_title)s' published.
- The page '{0}' is not currently awaiting moderation.
+ The page '%(page_title)s' is not currently awaiting moderation.
- Page '{0}' rejected for publication.
+ Page '%(page_title)s' rejected for publication.
- The page '{0}' is not currently awaiting moderation.
+ The page '%(page_title)s' is not currently awaiting moderation.
# wagtail/admin/views/pages/move.py
- The slug '{0}' is already in use at the selected parent page. Make sure the slug is unique and try again
+ The slug '%(page_slug)s' is already in use at the selected parent page. Make sure the slug is unique and try again
- Page '{0}' moved.
+ Page '%(page_title)s' moved.
# wagtail/admin/views/pages/unpublish.py
- Page '{0}' unpublished.
+ Page '%(page_title)s' unpublished.
# wagtail/admin/views/pages/workflow.py
- The page '{0}' is not currently awaiting moderation.
+ The page '%(page_title)s' is not currently awaiting moderation.
- The page '{0}' is not currently awaiting moderation in task '{1}'.
+ The page '%(page_title)s' is not currently awaiting moderation in task '%(task_name)s'.
# wagtail/admin/views/workflows.py
- Workflow '{0}' created.
+ Workflow '%(object)s' created.
- Workflow '{0}' updated.
+ Workflow '%(object)s' updated.
- Workflow '{0}' disabled.
+ Workflow '%(object)s' disabled.
- Workflow '{0}' enabled.
+ Workflow '%(workflow_name)s' enabled.
- Workflow removed from Page '{0}'.
+ Workflow removed from Page '%(page_title)s'.
- Task '{0}' created.
+ Task '%(object)s' created.
- Task '{0}' updated.
+ Task '%(object)s' updated.
- Task '{0}' disabled.
+ Task '%(object)s' disabled.
- Task '{0}' enabled.
+ Task '%(task_name)s' enabled.
# wagtail/blocks/list_block.py
- The minimum number of items is %d
+ The minimum number of items is %(min_num)d
- The maximum number of items is %d
+ The maximum number of items is %(max_num)d
# wagtail/blocks/stream_block.py
- The minimum number of items is %d
+ The minimum number of items is %(min_num)d
- The maximum number of items is %d
+ The maximum number of items is %(max_num)d
- The minimum number of items is %d
+ The minimum number of items is %(min_num)d
- The maximum number of items is %d
+ The maximum number of items is %(max_num)d
# wagtail/contrib/forms/forms.py
- There is another field with the label %s, please change one of them.
+ There is another field with the label %(label_name)s, please change one of them.
# wagtail/contrib/forms/panels.py
- %s submissions
+ %(model_name)s submissions
# wagtail/contrib/modeladmin/helpers/button.py
- Add %s
+ Add %(object)s
- Add a new %s
+ Add a new %(object)s
- Inspect this %s
+ Inspect this %(object)s
- Edit this %s
+ Edit this %(object)s
- Delete this %s
+ Delete this %(object)s
- Unpublish this %s
+ Unpublish this %(object)s
- Copy this %s
+ Copy this %(object)s
# wagtail/contrib/modeladmin/views.py
- %(model_name)s '%(instance)s' created.
+ %(model_name)s '%(object)s' created.
- The %s could not be created due to errors.
+ The %(object)s could not be created due to errors.
- Create new %s
+ Create new %(object)s
- Editing %s
+ Editing %(object)s
- %(model_name)s '%(instance)s' updated.
+ %(model_name)s '%(object)s' updated.
- The %s could not be saved due to errors.
+ The %(object)s could not be saved due to errors.
- Add %s
+ Add %(object)s
- Confirm deletion of %s
+ Confirm deletion of %(object)s
- Are you sure you want to delete this %s? If other things in your site are related to it, they may also be affected.
+ Are you sure you want to delete this %(object)s? If other things in your site are related to it, they may also be affected.
- %(model_name)s '%(instance)s' deleted.
+ %(model_name)s '%(object)s' deleted.
- Inspecting %s
+ Inspecting %(object)s
# wagtail/contrib/redirects/views.py
- Redirect '{0}' updated.
+ Redirect '%(redirect_title)s' updated.
- Redirect '{0}' deleted.
+ Redirect '%(redirect_title)s' deleted.
- Redirect '{0}' added.
+ Redirect '%(redirect_title)s' added.
- File format of type "{}" is not supported
+ File format of type "%(extension)s" is not supported
- Imported file has a wrong encoding: %s
+ Imported file has a wrong encoding: %(error_message)s
# wagtail/contrib/search_promotions/views.py
- Editor's picks for '{0}' created.
+ Editor's picks for '%(query)s' created.
- Editor's picks for '{0}' updated.
+ Editor's picks for '%(query)s' updated.
# wagtail/contrib/simple_translation/forms.py
- Include subtree ({} page)
+ Include subtree (%(descendant_count)s page)
- Include subtree ({} pages)
+ Include subtree (%(descendant_count)s pages)
# wagtail/contrib/simple_translation/views.py
- {} locales
+ %(locales_count)s locales
- The page '{page_title}' was successfully created in {locales}
+ The page '%(page_title)s' was successfully created in %(locales)s
- Translate {model_name}
+ Translate %(model_name)s
- Successfully created {locales} for {model_name} '{object}'
+ Successfully created %(locales)s for %(model_name)s '%(object)s'
# wagtail/documents/views/documents.py
- Document '{0}' added.
+ Document '%(document_title)s' added.
- Document '{0}' updated
+ Document '%(document_title)s' updated
- Document '{0}' deleted.
+ Document '%(document_title)s' deleted.
- (Private %s)
+ (Private %(object)s)
- Edit this %s
+ Edit this %(object)s
# wagtail/images/fields.py
- Not a supported image format. Supported formats: %s.
+ Not a supported image format. Supported formats: %(supported_formats)s.
- Not a valid %s image.
+ Not a valid .%(extension)s image. The extension does not match the file format (%(image_format)s)
- This file is too big (%%s). Maximum filesize %s.
+ This file is too big (%(file_size)s). Maximum filesize %(max_filesize)s.
- This file is too big. Maximum filesize %s.
+ This file is too big. Maximum filesize %(max_filesize)s.
- This file has too many pixels (%%s). Maximum pixels %s.
+ This file has too many pixels (%(num_pixels)s). Maximum pixels %(max_pixels_count)s.
# wagtail/images/views/images.py
- Image '{0}' updated.
+ Image '%(image_title)s' updated.
- Image '{0}' deleted.
+ Image '%(image_title)s' deleted.
- Image '{0}' added.
+ Image '%(image_title)s' added.
- (Private %s)
+ (Private %(object)s)
- Edit this %s
+ Edit this %(object)s
# wagtail/locales/views.py
- Locale '{0}' created.
+ Locale '%(object)s' created.
- Locale '{0}' updated.
+ Locale '%(object)s' updated.
- Locale '{0}' deleted.
+ Locale '%(object)s' deleted.
# wagtail/locks.py
- <b>Page '{}' was locked</b> by <b>you</b> on <b>{}</b>.
+ <b>Page '{page_title}' was locked</b> by <b>you</b> on <b>{datetime}</b>.
- <b>Page '{}' is locked</b> by <b>you</b>.
+ <b>Page '{page_title}' is locked</b> by <b>you</b>.
- <b>Page '{}' was locked</b> by <b>{}</b> on <b>{}</b>.
+ <b>Page '{page_title}' was locked</b> by <b>{user}</b> on <b>{datetime}</b>.
- <b>Page '{}' is locked</b>.
+ <b>Page '{page_title}' is locked</b>.
- This page is awaiting <b>'{}'</b> in the <b>'{}'</b> workflow.
+ This page is awaiting <b>'{task_name}'</b> in the <b>'{workflow_name}'</b> workflow.
- Page '{}' is locked and has been scheduled to go live at {}
+ Page '{page_title}' is locked and has been scheduled to go live at {datetime}
# wagtail/models/__init__.py
- Workflow '{0}' on Page '{1}': {2}
+ Workflow '%(workflow_name)s' on Page '%(page_title)s': %(status)s
- Task '{0}' on Page Revision '{1}': {2}
+ Task '%(task_name)s' on Page Revision '%(revision_info)s': %(status)s
# wagtail/models/audit_log.py
- The log action '{}' has not been registered.
+ The log action '%(action_name)s' has not been registered.
# wagtail/sites/views.py
- Site '{0}' created.
+ Site '%(object)s' created.
- Site '{0}' updated.
+ Site '%(object)s' updated.
- Site '{0}' deleted.
+ Site '%(object)s' deleted.
# wagtail/snippets/bulk_actions/delete.py
- %(snippet_type)s '%(instance)s' deleted.
+ %(model_name)s '%(object)s' deleted.
- %(count)d %(snippet_type)s deleted.
+ %(count)d %(model_name)s deleted.
# wagtail/snippets/views/snippets.py
- %(snippet_type)s '%(instance)s' created.
+ %(model_name)s '%(object)s' created.
- %(snippet_type)s '%(instance)s' created and published.
+ %(model_name)s '%(object)s' created and published.
- %(snippet_type)s '%(instance)s' created and scheduled for publishing.
+ %(model_name)s '%(object)s' created and scheduled for publishing.
- %(snippet_type)s '%(instance)s' updated.
+ %(model_name)s '%(object)s' updated.
- %(snippet_type)s '%(instance)s' updated and published.
+ %(model_name)s '%(object)s' updated and published.
- %(snippet_type)s '%(instance)s' has been scheduled for publishing.
+ %(model_name)s '%(object)s' has been scheduled for publishing.
- %(snippet_type)s '%(instance)s' is live and this version has been scheduled for publishing.
+ %(model_name)s '%(object)s' is live and this version has been scheduled for publishing.
- %(snippet_type)s '%(instance)s' deleted.
+ %(model_name)s '%(object)s' deleted.
- %(count)d %(snippet_type)s deleted.
+ %(count)d %(model_name)s deleted.
- (Private %s)
+ (Private %(object)s)
- Edit this %s
+ Edit this %(object)s
- Edit this {model_name}
+ Edit this %(model_name)s
- {model_name} history
+ %(model_name)s history
# wagtail/snippets/widgets.py
- Choose %s
+ Choose %(object)s
- Choose another %s
+ Choose another %(object)s
- Edit this %s
+ Edit this %(object)s
# wagtail/users/views/groups.py
- Group '{0}' created.
+ Group '%(object)s' created.
- Group '{0}' updated.
+ Group '%(object)s' updated.
- Group '{0}' deleted.
+ Group '%(object)s' deleted.
# wagtail/users/views/users.py
- User '{0}' updated.
+ User '%(object)s' updated.
- Editing %s
+ Editing %(object)s
- User '{0}' deleted.
+ User '%(object)s' deleted.
from pathlib import Path
import polib
# Those source strings had more than one anonymous placeholder,
# or the number of placeholder changed,
# so we cannot reliably replace them in target strings.
# It will be up to the translators to update them in Transifex.
AMBIGUOUS_REPLACEMENTS = {
# => <b>Page '{page_title}' was locked</b> by <b>you</b> on <b>{datetime}</b>.
"<b>Page '{}' was locked</b> by <b>you</b> on <b>{}</b>.": (
("{}", "{page_title}"),
("{}", "{datetime}"),
),
# => <b>Page '{page_title}' was locked</b> by <b>{user}</b> on <b>{datetime}</b>.
"<b>Page '{}' was locked</b> by <b>{}</b> on <b>{}</b>.": (
("{}", "{page_title}"),
("{}", "{user}"),
("{}", "{datetime}"),
),
"This page is awaiting <b>'{}'</b> in the <b>'{}'</b> workflow.": (
("{}", "{task_name}"),
("{}", "{workflow_name}"),
),
# => Page '{page_title}' is locked and has been scheduled to go live at {datetime}
"Page '{}' is locked and has been scheduled to go live at {}": (
("{}", "{page_title}"),
("{}", "{datetime}"),
),
# => Not a valid .%(extension)s image. The extension does not match the file format (%(image_format)s)
"Not a valid %s image.": (
("%s", "%(extension)s"),
# The string was modified to have a second placeholder, so this little hack
# hooks on the end of the string to insert the second part with the second placeholder.
# But it actually only work for the source string, not the target string.
(
" image.",
" image. The extension does not match the file format (%(image_format)s)",
),
),
}
# Those source strings had only one anonymous argument, or 1+ positional/named arguments
# so we can replace them in target strings.
REPLACEMENTS = {
# => %(num)s error
"{num} error": (
("{num}", "%(num)s"),
),
# => %(num)s errors
"{num} errors": (
("{num}", "%(num)s"),
),
# => Edit '%(title)s'
"Edit '{title}'": (
("{title}", "%(title)s"),
),
# => View child pages of '%(title)s'
"View child pages of '{title}'": (
("{title}", "%(title)s"),
),
# => (%(number)s new items in this menu)
"({number} new items in this menu)": (
("{number}", "%(number)s"),
),
# => (%(errorCount)s error)
"({errorCount} error)": (
("{errorCount}", "%(errorCount)s"),
),
# => (%(errorCount)s errors)
"({errorCount} errors)": (
("{errorCount}", "%(errorCount)s"),
),
# => %(num)s pixel
"{num} pixel": (
("{num}", "%(num)s"),
),
# => %(num)s pixels
"{num} pixels": (
("{num}", "%(num)s"),
),
# => Resubmit to %(task_name)s
"Resubmit to {}": (("{}", "%(task_name)s"),),
# => Submit to %(workflow_name)s
"Submit to {}": (("{}", "%(workflow_name)s"),),
# => Enter your %(username_field_name)s
"Enter your %s": (("%s", "%(username_field_name)s"),),
# => This slug is already in use within the context of its parent page "%(parent_page_title)s"
'This slug is already in use within the context of its parent page "%s"': (
("%s", "%(parent_page_title)s"),
),
# => This page already has workflow '%(workflow_name)s' assigned.
"This page already has workflow '{0}' assigned.": (("{0}", "%(workflow_name)s"),),
# => %(objects)s pages selected
"{0} pages selected": (("{0}", "%(objects)s"),),
# => All %(objects)s pages on this screen selected
"All {0} pages on this screen selected": (("{0}", "%(objects)s"),),
# => %(objects)s documents selected
"{0} documents selected": (("{0}", "%(objects)s"),),
# => All %(objects)s documents on this screen selected
"All {0} documents on this screen selected": (("{0}", "%(objects)s"),),
# => %(objects)s images selected
"{0} images selected": (("{0}", "%(objects)s"),),
# => All %(objects)s images on this screen selected
"All {0} images on this screen selected": (("{0}", "%(objects)s"),),
# => %(objects)s users selected
"{0} users selected": (("{0}", "%(objects)s"),),
# => All %(objects)s users on this screen selected
"All {0} users on this screen selected": (("{0}", "%(objects)s"),),
# => %(objects)s snippets selected
"{0} snippets selected": (("{0}", "%(objects)s"),),
# => All %(objects)s snippets on this screen selected
"All {0} snippets on this screen selected": (("{0}", "%(objects)s"),),
# => %(objects)s items selected
"{0} items selected": (("{0}", "%(objects)s"),),
# => All %(objects)s items on this screen selected
"All {0} items on this screen selected": (("{0}", "%(objects)s"),),
# => What's new in Wagtail %(version)s
"What's new in Wagtail {version}": (("{version}", "%(version)s"),),
# => Collection '%(object)s' created.
"Collection '{0}' created.": (("{0}", "%(object)s"),),
# => Collection '%(object)s' updated.
"Collection '{0}' updated.": (("{0}", "%(object)s"),),
# => Collection '%(object)s' deleted.
"Collection '{0}' deleted.": (("{0}", "%(object)s"),),
# => %(model_name)s '%(instance)s' has been replaced with version from %(timestamp)s.
"{model_name} '{instance}' has been replaced with version from {timestamp}.": (
("{model_name}", "%(model_name)s"),
("{instance}", "%(object)s"),
("{timestamp}", "%(timestamp)s"),
),
# => Version from %(timestamp)s of %(model_name)s '%(instance)s' has been published.
"Version from {timestamp} of {model_name} '{instance}' has been published.": (
("{timestamp}", "%(timestamp)s"),
("{model_name}", "%(model_name)s"),
("{instance}", "%(object)s"),
),
# => Version from %(timestamp)s of %(model_name)s '%(instance)s' has been scheduled for publishing.
"Version from {timestamp} of {model_name} '{instance}' has been scheduled for publishing.": (
("{timestamp}", "%(timestamp)s"),
("{model_name}", "%(model_name)s"),
("{instance}", "%(object)s"),
),
# => '%(object)s' created and scheduled for publishing.
"'{0}' created and scheduled for publishing.": (("{0}", "%(object)s"),),
# => '%(object)s' created and published.
"'{0}' created and published.": (("{0}", "%(object)s"),),
# => '%(object)s' updated and scheduled for publishing.
"'{0}' updated and scheduled for publishing.": (("{0}", "%(object)s"),),
# => '%(object)s' updated and published.
"'{0}' updated and published.": (("{0}", "%(object)s"),),
# => '%(object)s' unpublished.
"'{object_name}' unpublished.": (("{object_name}", "%(object)s"),),
# => Version %(revision_id)s of "%(object)s" unscheduled.
'Version {revision_id} of "{object}" unscheduled.': (
("{revision_id}", "%(revision_id)s"),
("{object}", "%(object)s"),
),
# => revision %(revision_id)s of "%(object)s"
'revision {revision_id} of "{object}"': (
("{revision_id}", "%(revision_id)s"),
("{object}", "%(object)s"),
),
# => Page '%(page_title)s' has been converted into an ordinary page.
"Page '{0}' has been converted into an ordinary page.": (
("{0}", "%(page_title)s"),
),
# => Page '%(page_title)s' and %(subpages_count)s subpages copied.
"Page '{0}' and {1} subpages copied.": (
("{0}", "%(page_title)s"),
("{1}", "%(subpages_count)s"),
),
# => Page '%(page_title)s' copied.
"Page '{0}' copied.": (("{0}", "%(page_title)s"),),
# => Page '%(page_title)s' created.
"Page '{0}' created.": (("{0}", "%(page_title)s"),),
# => Page '%(page_title)s' created and scheduled for publishing.
"Page '{0}' created and scheduled for publishing.": (("{0}", "%(page_title)s"),),
# => Page '%(page_title)s' created and published.
"Page '{0}' created and published.": (("{0}", "%(page_title)s"),),
# => Page '%(page_title)s' created and submitted for moderation.
"Page '{0}' created and submitted for moderation.": (("{0}", "%(page_title)s"),),
# => Page '%(page_title)s' deleted.
"Page '{0}' deleted.": (("{0}", "%(page_title)s"),),
# => Page '%(page_title)s' has been replaced with version from %(previous_revision_datetime)s.
"Page '{0}' has been replaced with version from {1}.": (
("{0}", "%(page_title)s"),
("{1}", "%(previous_revision_datetime)s"),
),
# => Page '%(page_title)s' has been updated.
"Page '{0}' has been updated.": (("{0}", "%(page_title)s"),),
# => Workflow on page '%(page_title)s' has been cancelled.
"Workflow on page '{0}' has been cancelled.": (("{0}", "%(page_title)s"),),
# => Version from %(previous_revision_datetime)s of page '%(page_title)s' has been scheduled for publishing.
"Version from {0} of page '{1}' has been scheduled for publishing.": (
("{0}", "%(previous_revision_datetime)s"),
("{1}", "%(page_title)s"),
),
# => Page '%(page_title)s' is live and this version has been scheduled for publishing.
"Page '{0}' is live and this version has been scheduled for publishing.": (
("{0}", "%(page_title)s"),
),
# => Page '%(page_title)s' has been scheduled for publishing.
"Page '{0}' has been scheduled for publishing.": (("{0}", "%(page_title)s"),),
# => Version from %(datetime)s of page '%(page_title)s' has been published.
"Version from {0} of page '{1}' has been published.": (
("{0}", "%(datetime)s"),
("{1}", "%(page_title)s"),
),
# => Page '%(page_title)s' has been published.
"Page '{0}' has been published.": (("{0}", "%(page_title)s"),),
# => Page '%(page_title)s' has been submitted for moderation.
"Page '{0}' has been submitted for moderation.": (("{0}", "%(page_title)s"),),
# => Workflow on page '%(page_title)s' has been restarted.
"Workflow on page '{0}' has been restarted.": (("{0}", "%(page_title)s"),),
# => Page '%(page_title)s' is now unlocked.
"Page '{0}' is now unlocked.": (("{0}", "%(page_title)s"),),
# => The page '%(page_title)s' is not currently awaiting moderation.
"The page '{0}' is not currently awaiting moderation.": (
("{0}", "%(page_title)s"),
),
# => Page '%(page_title)s' published.
"Page '{0}' published.": (("{0}", "%(page_title)s"),),
# => The page '%(page_title)s' is not currently awaiting moderation.
"The page '{0}' is not currently awaiting moderation.": (
("{0}", "%(page_title)s"),
),
# => Page '%(page_title)s' rejected for publication.
"Page '{0}' rejected for publication.": (("{0}", "%(page_title)s"),),
# => The page '%(page_title)s' is not currently awaiting moderation.
"The page '{0}' is not currently awaiting moderation.": (
("{0}", "%(page_title)s"),
),
# => The slug '%(page_slug)s' is already in use at the selected parent page. Make sure the slug is unique and try again
"The slug '{0}' is already in use at the selected parent page. Make sure the slug is unique and try again": (
("{0}", "%(page_slug)s"),
),
# => Page '%(page_title)s' moved.
"Page '{0}' moved.": (("{0}", "%(page_title)s"),),
# => Page '%(page_title)s' unpublished.
"Page '{0}' unpublished.": (("{0}", "%(page_title)s"),),
# => The page '%(page_title)s' is not currently awaiting moderation.
"The page '{0}' is not currently awaiting moderation.": (
("{0}", "%(page_title)s"),
),
# => The page '%(page_title)s' is not currently awaiting moderation in task '%(task_name)s'.
"The page '{0}' is not currently awaiting moderation in task '{1}'.": (
("{0}", "%(page_title)s"),
("{1}", "%(task_name)s"),
),
# => Workflow '%(object)s' created.
"Workflow '{0}' created.": (("{0}", "%(object)s"),),
# => Workflow '%(object)s' updated.
"Workflow '{0}' updated.": (("{0}", "%(object)s"),),
# => Workflow '%(object)s' disabled.
"Workflow '{0}' disabled.": (("{0}", "%(object)s"),),
# => Workflow '%(workflow_name)s' enabled.
"Workflow '{0}' enabled.": (("{0}", "%(workflow_name)s"),),
# => Workflow removed from Page '%(page_title)s'.
"Workflow removed from Page '{0}'.": (("{0}", "%(page_title)s"),),
# => Task '%(object)s' created.
"Task '{0}' created.": (("{0}", "%(object)s"),),
# => Task '%(object)s' updated.
"Task '{0}' updated.": (("{0}", "%(object)s"),),
# => Task '%(object)s' disabled.
"Task '{0}' disabled.": (("{0}", "%(object)s"),),
# => Task '%(task_name)s' enabled.
"Task '{0}' enabled.": (("{0}", "%(task_name)s"),),
# => The minimum number of items is %(min_num)d
"The minimum number of items is %d": (("%d", "%(min_num)d"),),
# => The maximum number of items is %(max_num)d
"The maximum number of items is %d": (("%d", "%(max_num)d"),),
# => The minimum number of items is %(min_num)d
"The minimum number of items is %d": (("%d", "%(min_num)d"),),
# => The maximum number of items is %(max_num)d
"The maximum number of items is %d": (("%d", "%(max_num)d"),),
# => The minimum number of items is %(min_num)d
"The minimum number of items is %d": (("%d", "%(min_num)d"),),
# => The maximum number of items is %(max_num)d
"The maximum number of items is %d": (("%d", "%(max_num)d"),),
# => There is another field with the label %(label_name)s, please change one of them.
"There is another field with the label %s, please change one of them.": (
("%s", "%(label_name)s"),
),
# => %(model_name)s submissions
"%s submissions": (("%s", "%(model_name)s"),),
# => Add %(object)s
"Add %s": (("%s", "%(object)s"),),
# => Add a new %(object)s
"Add a new %s": (("%s", "%(object)s"),),
# => Inspect this %(object)s
"Inspect this %s": (("%s", "%(object)s"),),
# => Delete this %(object)s
"Delete this %s": (("%s", "%(object)s"),),
# => Unpublish this %(object)s
"Unpublish this %s": (("%s", "%(object)s"),),
# => Copy this %(object)s
"Copy this %s": (("%s", "%(object)s"),),
# => %(model_name)s '%(object)s' created.
"%(model_name)s '%(instance)s' created.": (
# `model_name` isn't renamed
("%(instance)s", "%(object)s"),
),
# => The %(object)s could not be created due to errors.
"The %s could not be created due to errors.": (("%s", "%(object)s"),),
# => Create new %(object)s
"Create new %s": (("%s", "%(object)s"),),
# => Editing %(object)s
"Editing %s": (("%s", "%(object)s"),),
# => %(model_name)s '%(object)s' updated.
"%(model_name)s '%(instance)s' updated.": (
# `model_name` isn't renamed
("%(instance)s", "%(object)s"),
),
# => The %(object)s could not be saved due to errors.
"The %s could not be saved due to errors.": (("%s", "%(object)s"),),
# => Add %(object)s
"Add %s": (("%s", "%(object)s"),),
# => Confirm deletion of %(object)s
"Confirm deletion of %s": (("%s", "%(object)s"),),
# => Are you sure you want to delete this %(object)s? If other things in your site are related to it, they may also be affected.
"Are you sure you want to delete this %s? If other things in your site are related to it, they may also be affected.": (
("%s", "%(object)s"),
),
# => %(model_name)s '%(object)s' deleted.
"%(model_name)s '%(instance)s' deleted.": (
# `model_name` isn't renamed
("%(instance)s", "%(object)s"),
),
# => Inspecting %(object)s
"Inspecting %s": (("%s", "%(object)s"),),
# => Redirect '%(redirect_title)s' updated.
"Redirect '{0}' updated.": (("{0}", "%(redirect_title)s"),),
# => Redirect '%(redirect_title)s' deleted.
"Redirect '{0}' deleted.": (("{0}", "%(redirect_title)s"),),
# => Redirect '%(redirect_title)s' added.
"Redirect '{0}' added.": (("{0}", "%(redirect_title)s"),),
# => File format of type "%(extension)s" is not supported
'File format of type "{}" is not supported': (("{}", "%(extension)s"),),
# => Imported file has a wrong encoding: %(error_message)s
"Imported file has a wrong encoding: %s": (("%s", "%(error_message)s"),),
# => Editor's picks for '%(query)s' created.
"Editor's picks for '{0}' created.": (("{0}", "%(query)s"),),
# => Editor's picks for '%(query)s' updated.
"Editor's picks for '{0}' updated.": (("{0}", "%(query)s"),),
# => Include subtree ({descendant_count} page)
"Include subtree ({} page)": (("{}", "%(descendant_count)s"),),
# => Include subtree ({descendant_count} pages)
"Include subtree ({} pages)": (("{}", "%(descendant_count)s"),),
# => %(locales_count)s locales
"{} locales": (("{}", "%(locales_count)s"),),
# => The page '%(page_title)s' was successfully created in %(locales)s
"The page '{page_title}' was successfully created in {locales}": (
("{page_title}", "%(page_title)s"),
("{locales}", "%(locales)s"),
),
# => Translate %(model_name)s
"Translate {model_name}": (("{model_name}", "%(model_name)s"),),
# => Successfully created %(locales)s for %(model_name)s '%(object)s'
"Successfully created {locales} for {model_name} '{object}'": (
("{locales}", "%(locales)s"),
("{model_name}", "%(model_name)s"),
("{object}", "%(object)s"),
),
# => Document '%(document_title)s' added.
"Document '{0}' added.": (("{0}", "%(document_title)s"),),
# => Document '%(document_title)s' updated
"Document '{0}' updated": (("{0}", "%(document_title)s"),),
# => Document '%(document_title)s' deleted.
"Document '{0}' deleted.": (("{0}", "%(document_title)s"),),
# => %(model_name)s '%(object)s' created.
"%(snippet_type)s '%(instance)s' created.": (
("%(snippet_type)s", "%(model_name)s"),
("%(instance)s", "%(object)s"),
),
# => %(model_name)s '%(object)s' created and published.
"%(snippet_type)s '%(instance)s' created and published.": (
("%(snippet_type)s", "%(model_name)s"),
("%(instance)s", "%(object)s"),
),
# => %(model_name)s '%(object)s' created and scheduled for publishing.
"%(snippet_type)s '%(instance)s' created and scheduled for publishing.": (
("%(snippet_type)s", "%(model_name)s"),
("%(instance)s", "%(object)s"),
),
# => %(model_name)s '%(object)s' updated.
"%(snippet_type)s '%(instance)s' updated.": (
("%(snippet_type)s", "%(model_name)s"),
("%(instance)s", "%(object)s"),
),
# => %(model_name)s '%(object)s' updated and published.
"%(snippet_type)s '%(instance)s' updated and published.": (
("%(snippet_type)s", "%(model_name)s"),
("%(instance)s", "%(object)s"),
),
# => %(model_name)s '%(object)s' has been scheduled for publishing.
"%(snippet_type)s '%(instance)s' has been scheduled for publishing.": (
("%(snippet_type)s", "%(model_name)s"),
("%(instance)s", "%(object)s"),
),
# => %(model_name)s '%(object)s' is live and this version has been scheduled for publishing.
"%(snippet_type)s '%(instance)s' is live and this version has been scheduled for publishing.": (
("%(snippet_type)s", "%(model_name)s"),
("%(instance)s", "%(object)s"),
),
# => %(model_name)s '%(object)s' deleted.
"%(snippet_type)s '%(instance)s' deleted.": (
("%(snippet_type)s", "%(model_name)s"),
("%(instance)s", "%(object)s"),
),
# => %(count)d %(model_name)s deleted.
"%(count)d %(snippet_type)s deleted.": (
# `count` isn't renamed
("%(snippet_type)s", "%(model_name)s"),
),
# => (Private %(object)s)
"(Private %s)": (("%s", "%(object)s"),),
# => Edit this %(object)s
"Edit this %s": (("%s", "%(object)s"),),
# => Not a supported image format. Supported formats: %(supported_formats)s.
"Not a supported image format. Supported formats: %s.": (
("%s", "%(supported_formats)s"),
),
# => This file is too big (%%(file_size)s). Maximum filesize %(max_filesize)s.
"This file is too big (%%s). Maximum filesize %s.": (
("%%s", "%(file_size)s"),
("%s", "%(max_filesize)s"),
),
# => This file is too big. Maximum filesize %(max_filesize)s.
"This file is too big. Maximum filesize %s.": (("%s", "%(max_filesize)s"),),
# => This file has too many pixels (%%(num_pixels)s). Maximum pixels %(max_pixels_count)s.
"This file has too many pixels (%%s). Maximum pixels %s.": (
("%%s", "%(num_pixels)s"),
("%s", "%(max_pixels_count)s"),
),
# => Image '%(image_title)s' updated.
"Image '{0}' updated.": (("{0}", "%(image_title)s"),),
# => Image '%(image_title)s' deleted.
"Image '{0}' deleted.": (("{0}", "%(image_title)s"),),
# => Image '%(image_title)s' added.
"Image '{0}' added.": (("{0}", "%(image_title)s"),),
# => Locale '%(object)s' created.
"Locale '{0}' created.": (("{0}", "%(object)s"),),
# => Locale '%(object)s' updated.
"Locale '{0}' updated.": (("{0}", "%(object)s"),),
# => Locale '%(object)s' deleted.
"Locale '{0}' deleted.": (("{0}", "%(object)s"),),
# => <b>Page '{page_title}' is locked</b> by <b>you</b>.
"<b>Page '{}' is locked</b> by <b>you</b>.": (("{}", "{page_title}"),),
# => <b>Page '{page_title}' is locked</b>.
"<b>Page '{}' is locked</b>.": (("{}", "{page_title}"),),
# => This page is awaiting <b>'{task_name}'</b> in the <b>'{workflow_name}'</b> workflow.
# => Workflow '%(workflow_name)s' on Page '%(page_title)s': %(status)s
"Workflow '{0}' on Page '{1}': {2}": (
("{0}", "%(workflow_name)s"),
("{1}", "%(page_title)s"),
("{2}", "%(status)s"),
),
# => Task '%(task_name)s' on Page Revision '%(revision_info)s': %(status)s
"Task '{0}' on Page Revision '{1}': {2}": (
("{0}", "%(task_name)s"),
("{1}", "%(revision_info)s"),
("{2}", "%(status)s"),
),
# => The log action '%(action_name)s' has not been registered.
"The log action '{}' has not been registered.": (("{}", "%(action_name)s"),),
# => Site '%(object)s' created.
"Site '{0}' created.": (("{0}", "%(object)s"),),
# => Site '%(object)s' updated.
"Site '{0}' updated.": (("{0}", "%(object)s"),),
# => Site '%(object)s' deleted.
"Site '{0}' deleted.": (("{0}", "%(object)s"),),
# => Edit this %(model_name)s
"Edit this {model_name}": (("{model_name}", "%(model_name)s"),),
# => %(model_name)s history
"{model_name} history": (("{model_name}", "%(model_name)s"),),
# => Choose %(object)s
"Choose %s": (("%s", "%(object)s"),),
# => Choose another %(object)s
"Choose another %s": (("%s", "%(object)s"),),
# => Group '%(object)s' created.
"Group '{0}' created.": (("{0}", "%(object)s"),),
# => Group '%(object)s' updated.
"Group '{0}' updated.": (("{0}", "%(object)s"),),
# => Group '%(object)s' deleted.
"Group '{0}' deleted.": (("{0}", "%(object)s"),),
# => User '%(object)s' updated.
"User '{0}' updated.": (("{0}", "%(object)s"),),
# => Editing %(object)s
"Editing %s": (("%s", "%(object)s"),),
# => User '%(object)s' deleted.
"User '{0}' deleted.": (("{0}", "%(object)s"),),
}
project_root = Path(__file__).resolve().parent.parent
python_root = project_root / "wagtail"
for path in python_root.rglob("LC_MESSAGES/*.po"):
print(f"Processing {path}")
po = polib.pofile(path)
updated = False
for entry in po:
if not (replacements := REPLACEMENTS.get(entry.msgid)):
continue # ignore source strings for which there are no replacements
updated = True
for old, new in replacements:
# Keys
entry.msgid = entry.msgid.replace(old, new, 1)
entry.msgid_plural = entry.msgid_plural.replace(old, new, 1)
# Singular translation
entry.msgstr = entry.msgstr.replace(old, new, 1)
# Plural translations
for plural_count in entry.msgstr_plural.keys():
entry.msgstr_plural[plural_count] = entry.msgstr_plural[plural_count].replace(old, new, 1)
if updated:
print("Saving")
po.save(path)
else:
print("No changes")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment