Skip to content

Instantly share code, notes, and snippets.

@flodolo
Last active August 29, 2026 09:06
Show Gist options
  • Select an option

  • Save flodolo/dcf48d10c5250f926ebc94985ccfae0f to your computer and use it in GitHub Desktop.

Select an option

Save flodolo/dcf48d10c5250f926ebc94985ccfae0f to your computer and use it in GitHub Desktop.
Rewrite XLIFF original
from django.db import transaction
from pontoon.base.models import Entity, Project, Resource, Section
XLIFF = [Resource.Format.XLIFF, Resource.Format.XCODE]
project = Project.objects.get(slug="firefox-for-ios")
resources = list(project.resources.filter(obsolete=False, format__in=XLIFF).order_by("path"))
print(f"{len(resources)} XLIFF/XCODE resources, sync_disabled={project.sync_disabled}")
for res in resources:
print(f"{res.path} [{res.format}]")
originals = sorted({s.key[0] for s in Section.objects.filter(resource=res) if s.key})
for original in originals:
n = Entity.objects.filter(resource=res, obsolete=False, key__0=original).count()
print(f" {original!r}: {n} live entities")
# ---- STEP 2: the renames, and the counters ---------------------------------
# Copy the old values verbatim from the STEP 1 output. Re-paste this whole STEP
# before each run of STEP 3, so the counters restart at zero.
RENAMES = {
"Client/en.lproj/InfoPlist.strings": "Client/en-US.lproj/InfoPlist.strings",
"Extensions/ActionExtension/en.lproj/InfoPlist.strings": "Extensions/ActionExtension/en-US.lproj/InfoPlist.strings",
"Shared/Supporting Files/en.lproj/ActivityStream.strings": "Shared/Supporting Files/en-US.lproj/ActivityStream.strings",
"Shared/Supporting Files/en.lproj/AddressToolbar.strings": "Shared/Supporting Files/en-US.lproj/AddressToolbar.strings",
"Shared/Supporting Files/en.lproj/Alert.strings": "Shared/Supporting Files/en-US.lproj/Alert.strings",
"Shared/Supporting Files/en.lproj/Alerts.strings": "Shared/Supporting Files/en-US.lproj/Alerts.strings",
"Shared/Supporting Files/en.lproj/AppIconSelection.strings": "Shared/Supporting Files/en-US.lproj/AppIconSelection.strings",
"Shared/Supporting Files/en.lproj/BiometricAuthentication.strings": "Shared/Supporting Files/en-US.lproj/BiometricAuthentication.strings",
"Shared/Supporting Files/en.lproj/BookmarkPanel.strings": "Shared/Supporting Files/en-US.lproj/BookmarkPanel.strings",
"Shared/Supporting Files/en.lproj/BookmarkPanelDeleteConfirm.strings": "Shared/Supporting Files/en-US.lproj/BookmarkPanelDeleteConfirm.strings",
"Shared/Supporting Files/en.lproj/Bookmarks.strings": "Shared/Supporting Files/en-US.lproj/Bookmarks.strings",
"Shared/Supporting Files/en.lproj/BottomSheet.strings": "Shared/Supporting Files/en-US.lproj/BottomSheet.strings",
"Shared/Supporting Files/en.lproj/Camera.strings": "Shared/Supporting Files/en-US.lproj/Camera.strings",
"Shared/Supporting Files/en.lproj/ContextualHints.strings": "Shared/Supporting Files/en-US.lproj/ContextualHints.strings",
"Shared/Supporting Files/en.lproj/CredentialProvider.strings": "Shared/Supporting Files/en-US.lproj/CredentialProvider.strings",
"Shared/Supporting Files/en.lproj/Credentials.strings": "Shared/Supporting Files/en-US.lproj/Credentials.strings",
"Shared/Supporting Files/en.lproj/CustomizeFirefoxHome.strings": "Shared/Supporting Files/en-US.lproj/CustomizeFirefoxHome.strings",
"Shared/Supporting Files/en.lproj/DisplayCard.strings": "Shared/Supporting Files/en-US.lproj/DisplayCard.strings",
"Shared/Supporting Files/en.lproj/Edit Card.strings": "Shared/Supporting Files/en-US.lproj/Edit Card.strings",
"Shared/Supporting Files/en.lproj/EditAddress.strings": "Shared/Supporting Files/en-US.lproj/EditAddress.strings",
"Shared/Supporting Files/en.lproj/EditCard.strings": "Shared/Supporting Files/en-US.lproj/EditCard.strings",
"Shared/Supporting Files/en.lproj/EngagementNotification.strings": "Shared/Supporting Files/en-US.lproj/EngagementNotification.strings",
"Shared/Supporting Files/en.lproj/EnhancedTrackingProtection.strings": "Shared/Supporting Files/en-US.lproj/EnhancedTrackingProtection.strings",
"Shared/Supporting Files/en.lproj/ErrorState.strings": "Shared/Supporting Files/en-US.lproj/ErrorState.strings",
"Shared/Supporting Files/en.lproj/ExternalLink.strings": "Shared/Supporting Files/en-US.lproj/ExternalLink.strings",
"Shared/Supporting Files/en.lproj/FirefoxHomepage.strings": "Shared/Supporting Files/en-US.lproj/FirefoxHomepage.strings",
"Shared/Supporting Files/en.lproj/FirefoxLogins.strings": "Shared/Supporting Files/en-US.lproj/FirefoxLogins.strings",
"Shared/Supporting Files/en.lproj/FirefoxSync.strings": "Shared/Supporting Files/en-US.lproj/FirefoxSync.strings",
"Shared/Supporting Files/en.lproj/Footer.strings": "Shared/Supporting Files/en-US.lproj/Footer.strings",
"Shared/Supporting Files/en.lproj/FxANotification.strings": "Shared/Supporting Files/en-US.lproj/FxANotification.strings",
"Shared/Supporting Files/en.lproj/GoogleLens.strings": "Shared/Supporting Files/en-US.lproj/GoogleLens.strings",
"Shared/Supporting Files/en.lproj/JumpBackIn.strings": "Shared/Supporting Files/en-US.lproj/JumpBackIn.strings",
"Shared/Supporting Files/en.lproj/KeyboardAccessory.strings": "Shared/Supporting Files/en-US.lproj/KeyboardAccessory.strings",
"Shared/Supporting Files/en.lproj/LibraryPanel.strings": "Shared/Supporting Files/en-US.lproj/LibraryPanel.strings",
"Shared/Supporting Files/en.lproj/LiveActivity.strings": "Shared/Supporting Files/en-US.lproj/LiveActivity.strings",
"Shared/Supporting Files/en.lproj/LoginsHelper.strings": "Shared/Supporting Files/en-US.lproj/LoginsHelper.strings",
"Shared/Supporting Files/en.lproj/MainMenu.strings": "Shared/Supporting Files/en-US.lproj/MainMenu.strings",
"Shared/Supporting Files/en.lproj/Microsurvey.strings": "Shared/Supporting Files/en-US.lproj/Microsurvey.strings",
"Shared/Supporting Files/en.lproj/NativeErrorPage.strings": "Shared/Supporting Files/en-US.lproj/NativeErrorPage.strings",
"Shared/Supporting Files/en.lproj/Notification.strings": "Shared/Supporting Files/en-US.lproj/Notification.strings",
"Shared/Supporting Files/en.lproj/Onboarding.strings": "Shared/Supporting Files/en-US.lproj/Onboarding.strings",
"Shared/Supporting Files/en.lproj/PasswordAutofill.strings": "Shared/Supporting Files/en-US.lproj/PasswordAutofill.strings",
"Shared/Supporting Files/en.lproj/PasswordGenerator.strings": "Shared/Supporting Files/en-US.lproj/PasswordGenerator.strings",
"Shared/Supporting Files/en.lproj/PrivacyDashboard.strings": "Shared/Supporting Files/en-US.lproj/PrivacyDashboard.strings",
"Shared/Supporting Files/en.lproj/QRCode.strings": "Shared/Supporting Files/en-US.lproj/QRCode.strings",
"Shared/Supporting Files/en.lproj/ReaderModeBar.strings": "Shared/Supporting Files/en-US.lproj/ReaderModeBar.strings",
"Shared/Supporting Files/en.lproj/RelayMask.strings": "Shared/Supporting Files/en-US.lproj/RelayMask.strings",
"Shared/Supporting Files/en.lproj/RememberCard.strings": "Shared/Supporting Files/en-US.lproj/RememberCard.strings",
"Shared/Supporting Files/en.lproj/ResearchSurface.strings": "Shared/Supporting Files/en-US.lproj/ResearchSurface.strings",
"Shared/Supporting Files/en.lproj/ScanQRCode.strings": "Shared/Supporting Files/en-US.lproj/ScanQRCode.strings",
"Shared/Supporting Files/en.lproj/SearchEngineSelection.strings": "Shared/Supporting Files/en-US.lproj/SearchEngineSelection.strings",
"Shared/Supporting Files/en.lproj/SearchHeaderTitle.strings": "Shared/Supporting Files/en-US.lproj/SearchHeaderTitle.strings",
"Shared/Supporting Files/en.lproj/SearchZero.strings": "Shared/Supporting Files/en-US.lproj/SearchZero.strings",
"Shared/Supporting Files/en.lproj/SelectCreditCard.strings": "Shared/Supporting Files/en-US.lproj/SelectCreditCard.strings",
"Shared/Supporting Files/en.lproj/Settings.strings": "Shared/Supporting Files/en-US.lproj/Settings.strings",
"Shared/Supporting Files/en.lproj/Share.strings": "Shared/Supporting Files/en-US.lproj/Share.strings",
"Shared/Supporting Files/en.lproj/SnackBar.strings": "Shared/Supporting Files/en-US.lproj/SnackBar.strings",
"Shared/Supporting Files/en.lproj/SocialMediaApp.strings": "Shared/Supporting Files/en-US.lproj/SocialMediaApp.strings",
"Shared/Supporting Files/en.lproj/SocialShare.strings": "Shared/Supporting Files/en-US.lproj/SocialShare.strings",
"Shared/Supporting Files/en.lproj/SuggestedSites.strings": "Shared/Supporting Files/en-US.lproj/SuggestedSites.strings",
"Shared/Supporting Files/en.lproj/Summarize.strings": "Shared/Supporting Files/en-US.lproj/Summarize.strings",
"Shared/Supporting Files/en.lproj/Summarizer.strings": "Shared/Supporting Files/en-US.lproj/Summarizer.strings",
"Shared/Supporting Files/en.lproj/TabLocation.strings": "Shared/Supporting Files/en-US.lproj/TabLocation.strings",
"Shared/Supporting Files/en.lproj/TabToolbar.strings": "Shared/Supporting Files/en-US.lproj/TabToolbar.strings",
"Shared/Supporting Files/en.lproj/TabsTray.strings": "Shared/Supporting Files/en-US.lproj/TabsTray.strings",
"Shared/Supporting Files/en.lproj/TermsOfUse.strings": "Shared/Supporting Files/en-US.lproj/TermsOfUse.strings",
"Shared/Supporting Files/en.lproj/Toolbar.strings": "Shared/Supporting Files/en-US.lproj/Toolbar.strings",
"Shared/Supporting Files/en.lproj/ToolbarLocation.strings": "Shared/Supporting Files/en-US.lproj/ToolbarLocation.strings",
"Shared/Supporting Files/en.lproj/Translations.strings": "Shared/Supporting Files/en-US.lproj/Translations.strings",
"Shared/Supporting Files/en.lproj/UpdateCard.strings": "Shared/Supporting Files/en-US.lproj/UpdateCard.strings",
"Shared/Supporting Files/en.lproj/Upgrade.strings": "Shared/Supporting Files/en-US.lproj/Upgrade.strings",
"Shared/Supporting Files/en.lproj/WebCompatReporter.strings": "Shared/Supporting Files/en-US.lproj/WebCompatReporter.strings",
"Shared/Supporting Files/en.lproj/WebContextMenu.strings": "Shared/Supporting Files/en-US.lproj/WebContextMenu.strings",
"Shared/Supporting Files/en.lproj/WebView.strings": "Shared/Supporting Files/en-US.lproj/WebView.strings",
"Shared/Supporting Files/en.lproj/WorldCup.strings": "Shared/Supporting Files/en-US.lproj/WorldCup.strings",
"Shared/Supporting Files/en.lproj/ZoomPageBar.strings": "Shared/Supporting Files/en-US.lproj/ZoomPageBar.strings",
"Shared/en.lproj/3DTouchActions.strings": "Shared/en-US.lproj/3DTouchActions.strings",
"Shared/en.lproj/AuthenticationManager.strings": "Shared/en-US.lproj/AuthenticationManager.strings",
"Shared/en.lproj/ClearHistoryConfirm.strings": "Shared/en-US.lproj/ClearHistoryConfirm.strings",
"Shared/en.lproj/ClearPrivateData.strings": "Shared/en-US.lproj/ClearPrivateData.strings",
"Shared/en.lproj/ClearPrivateDataConfirm.strings": "Shared/en-US.lproj/ClearPrivateDataConfirm.strings",
"Shared/en.lproj/Default Browser.strings": "Shared/en-US.lproj/Default Browser.strings",
"Shared/en.lproj/ErrorPages.strings": "Shared/en-US.lproj/ErrorPages.strings",
"Shared/en.lproj/FindInPage.strings": "Shared/en-US.lproj/FindInPage.strings",
"Shared/en.lproj/HistoryPanel.strings": "Shared/en-US.lproj/HistoryPanel.strings",
"Shared/en.lproj/Intro.strings": "Shared/en-US.lproj/Intro.strings",
"Shared/en.lproj/Localizable.strings": "Shared/en-US.lproj/Localizable.strings",
"Shared/en.lproj/LoginManager.strings": "Shared/en-US.lproj/LoginManager.strings",
"Shared/en.lproj/Menu.strings": "Shared/en-US.lproj/Menu.strings",
"Shared/en.lproj/PrivateBrowsing.strings": "Shared/en-US.lproj/PrivateBrowsing.strings",
"Shared/en.lproj/Search.strings": "Shared/en-US.lproj/Search.strings",
"Shared/en.lproj/Shared.strings": "Shared/en-US.lproj/Shared.strings",
"Shared/en.lproj/Storage.strings": "Shared/en-US.lproj/Storage.strings",
"Shared/en.lproj/Today.strings": "Shared/en-US.lproj/Today.strings",
"WidgetKit/en.lproj/Localizable.strings": "WidgetKit/en-US.lproj/Localizable.strings"
}
DRY_RUN = True
sections_done = 0
entities_done = 0
problems = []
# ---- STEP 3: preview which resources are affected --------------------------
for res in resources:
for old, new in RENAMES.items():
live = Entity.objects.filter(resource=res, obsolete=False, key__0=old).count()
gone = Entity.objects.filter(resource=res, obsolete=True, key__0=old).count()
clash = Entity.objects.filter(resource=res, obsolete=False, key__0=new).count()
if live or gone or clash:
note = f", {clash} ALREADY ON THE NEW KEY" if clash else ""
print(f"{res.path}: {old!r} -> {new!r}: {live} live, {gone} obsolete{note}")
# ---- STEP 4: rewrite key[0] ------------------------------------------------
# Obsolete entities are left alone on purpose: they can no longer match
# anything, and rewriting them could collide with a live entity. The `clash`
# check guards the same risk -- there is no unique constraint on
# (resource, key), so sync would collapse a duplicate key into one dict entry
# and silently obsolete one entity of the pair.
assert project.sync_disabled, "Set sync_disabled=True in the Django admin first"
with transaction.atomic():
for res in resources:
for old, new in RENAMES.items():
sections = list(Section.objects.filter(resource=res, key__0=old))
entities = list(Entity.objects.filter(resource=res, obsolete=False, key__0=old))
if not sections and not entities:
continue
clash = Entity.objects.filter(resource=res, obsolete=False, key__0=new).count()
if clash:
problems.append(f"{res.path}: {clash} live entities already use {new!r}")
continue
for s in sections:
s.key[0] = new
for e in entities:
e.key[0] = new
Section.objects.bulk_update(sections, ["key"], batch_size=10000)
Entity.objects.bulk_update(entities, ["key"], batch_size=10000)
sections_done += len(sections)
entities_done += len(entities)
print(f"{res.path}: {old!r} -> {new!r} ({len(sections)} sections, {len(entities)} entities)")
print(f"TOTAL: {sections_done} sections, {entities_done} entities")
for p in problems:
print(f"PROBLEM: {p}")
if problems or DRY_RUN:
transaction.set_rollback(True)
print("ROLLED BACK -- nothing written")
else:
print("COMMITTED")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment