Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: avoid overriding manual edits for newer entries #2177

Merged
merged 4 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 50 additions & 4 deletions .github/workflows/sync-translations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@ jobs:
old_slug: course_discovery
old_project_slug: edx-platform

- new_slug: course-discovery
Copy link
Member Author

Choose a reason for hiding this comment

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

duplicate, removed

old_slug: course_discovery
old_project_slug: edx-platform

- new_slug: course-discovery-js
old_slug: course_discovery-js
old_project_slug: edx-platform
Expand Down Expand Up @@ -116,6 +112,56 @@ jobs:
old_slug: paragon
old_project_slug: edx-platform

# Start: edx-platform repo resources
# The edx-platform repo resources has been consolidated into a two resources
# - https://github.com/openedx/edx-platform/blob/master/docs/decisions/0018-standarize-django-po-files.rst

- new_slug: edx-platform
old_slug: django-partial
old_project_slug: edx-platform

- new_slug: edx-platform
old_slug: django-studio
old_project_slug: edx-platform

- new_slug: edx-platform
old_slug: edx_proctoring_proctortrack
old_project_slug: edx-platform

- new_slug: edx-platform
old_slug: mako
old_project_slug: edx-platform

- new_slug: edx-platform
old_slug: mako-studio
old_project_slug: edx-platform

- new_slug: edx-platform
old_slug: wiki
old_project_slug: edx-platform

- new_slug: edx-platform-js
old_slug: underscore
old_project_slug: edx-platform

- new_slug: edx-platform-js
old_slug: djangojs-studio
old_project_slug: edx-platform

- new_slug: edx-platform-js
old_slug: underscore-studio
old_project_slug: edx-platform

- new_slug: edx-platform-js
old_slug: djangojs-account-settings-view
old_project_slug: edx-platform

- new_slug: edx-platform-js
old_slug: djangojs-partial
old_project_slug: edx-platform

# End: edx-platform repo resources

steps:
- uses: actions/checkout@v3
- name: setup python
Expand Down
126 changes: 105 additions & 21 deletions scripts/sync_translations.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
"""
Sync translations from the deprecated Transifex projects into the new openedx-translations project.

- Old projects links:
* edX Platform Core: https://app.transifex.com/open-edx/edx-platform/
* XBlocks: https://app.transifex.com/open-edx/xblocks/

- New project link:
* https://app.transifex.com/open-edx/openedx-translations/


Variable names meaning:

- current_translation: translation in the new "open-edx/openedx-translations" project
- translation_from_old_project: translation in the old "open-edx/edx-platform" or "open-edx/xblocks" projects

"""

import argparse
import configparser
from datetime import datetime
import os
import sys
from os.path import expanduser
import yaml

Expand All @@ -11,26 +30,40 @@
ORGANIZATION_SLUG = 'open-edx'


def parse_tx_date(date_str):
"""
Parse a date string coming from Transifex into a datetime object.
"""
if date_str:
if date_str.endswith('Z'):
date_str = date_str.replace('Z', '+00:00')

return datetime.fromisoformat(date_str)

return None


class Command:

workflow_file_path = '.github/workflows/sync-translations.yml'

def __init__(self, argv, tx_api, environ):
self.argv = argv
def __init__(self, tx_api, dry_run, simulate_github_workflow, environ):
self.dry_run = dry_run
self.simulate_github_workflow = simulate_github_workflow
self.tx_api = tx_api
self.environ = environ

def is_dry_run(self):
"""
Check if the script is running in dry-run mode.
"""
return '--dry-run' in self.argv
return self.dry_run

def is_simulated_github_actions(self):
"""
Check if the script is running in simulated GitHub Actions mode.
"""
return '--simulate-github-workflow' in self.argv
return self.simulate_github_workflow

def get_resource_url(self, resource, project_slug):
return f'https://www.transifex.com/{ORGANIZATION_SLUG}/{project_slug}/{resource.slug}'
Expand Down Expand Up @@ -98,25 +131,61 @@ def sync_translations(self, language_code, old_resource, new_resource):
Sync specific language translations into the new Transifex resource.
"""
print(' syncing', language_code, '...')
old_translations = {
translations_from_old_project = {
self.get_translation_id(translation): translation
for translation in self.get_translations(language_code=language_code, resource=old_resource)
}

for new_translation in self.get_translations(language_code=language_code, resource=new_resource):
translation_id = self.get_translation_id(new_translation)
if old_translation := old_translations.get(translation_id):
updates = {}
for attr in ['reviewed', 'proofread', 'strings']:
if old_attr_value := getattr(old_translation, attr, None):
if old_attr_value != getattr(new_translation, attr, None):
updates[attr] = old_attr_value
for current_translation in self.get_translations(language_code=language_code, resource=new_resource):
translation_id = self.get_translation_id(current_translation)
if translation_from_old_project := translations_from_old_project.get(translation_id):
self.sync_translation_entry(
translation_from_old_project=translation_from_old_project,
current_translation=current_translation,
)

if updates:
print(translation_id, updates)
def sync_translation_entry(self, translation_from_old_project, current_translation):
"""
Sync a single translation entry from the old project to the new one.

if not self.is_dry_run():
new_translation.save(**updates)
Return:
str: status code
- updated: if the entry was updated
- skipped: if the entry was skipped
- updated-dry-run: if the entry was updated in dry-run mode
"""
translation_id = self.get_translation_id(current_translation)

updates = {}
for attr in ['reviewed', 'proofread', 'strings']:
if old_attr_value := getattr(translation_from_old_project, attr, None):
if old_attr_value != getattr(current_translation, attr, None):
updates[attr] = old_attr_value

# Avoid overwriting more recent translations in the open-edx/openedx-translations project
newer_translation_found = False
old_project_translation_time = parse_tx_date(translation_from_old_project.datetime_translated)
current_translation_time = parse_tx_date(current_translation.datetime_translated)

if old_project_translation_time and current_translation_time:
newer_translation_found = current_translation_time > old_project_translation_time
Copy link
Member Author

Choose a reason for hiding this comment

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

This is the gist of the pull request.


if updates:
if newer_translation_found:
print(translation_id, updates,
(
f'[Skipped: current translation "{current_translation_time}" '
f'is more recent than "{old_project_translation_time}"]'
)
)
return 'skipped'
else:
print(translation_id, updates, '[Dry run]' if self.is_dry_run() else '')
if self.is_dry_run():
return 'updated-dry-run'
else:
current_translation.save(**updates)
return 'updated'

def sync_tags(self, old_resource, new_resource):
"""
Expand Down Expand Up @@ -181,7 +250,7 @@ def run_from_workflow_yaml_file(self, workflow_configs):
"""
Run the script from a GitHub Actions migrate-from-transifex-old-project.yml workflow file.
"""
pairs_list = workflow_configs['jobs']['migrate-translations']['strategy']['matrix']['batch']
pairs_list = workflow_configs['jobs']['migrate-translations']['strategy']['matrix']['resource']
Copy link
Member Author

Choose a reason for hiding this comment

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

the key name has changed, the sync is broken otherwise due to KeyError


print('Verifying existence of resource pairs...')
for pair in pairs_list:
Expand Down Expand Up @@ -213,6 +282,21 @@ def run(self):
)


if __name__ == '__main__':
command = Command(sys.argv, environ=os.environ, tx_api=transifex_api)
def main(): # pragma: no cover
parser = argparse.ArgumentParser(description=__doc__)
Copy link
Member Author

Choose a reason for hiding this comment

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

Added argparse to protect against argv typos.

parser.add_argument('--simulate-github-workflow', action='store_true',
dest='simulate_github_workflow')
parser.add_argument('--dry-run', action='store_true', dest='dry_run')
argparse_args = parser.parse_args()

command = Command(
tx_api=transifex_api,
environ=os.environ,
dry_run=argparse_args.dry_run,
simulate_github_workflow=argparse_args.simulate_github_workflow,
)
command.run()


if __name__ == '__main__':
main() # pragma: no cover
Loading