Skip to content

Add per-locale translation imports and job expansion#61

Merged
stevejalim merged 5 commits intomainfrom
issues-28-and-37
Jan 26, 2026
Merged

Add per-locale translation imports and job expansion#61
stevejalim merged 5 commits intomainfrom
issues-28-and-37

Conversation

@stevejalim
Copy link
Collaborator

@stevejalim stevejalim commented Jan 26, 2026

Translations can now be imported as individual locales complete, rather than waiting for the entire Smartling job to finish.

This is tracked via a new JobTranslation through model with imported_at and content_hash
fields.

Also adds support for expanding existing jobs with new locales when they
are in UNSYNCED, DRAFT, or AWAITING_AUTHORIZATION states.

Also updates min Python, Django, Wagtail and postgres versions to modern versions - see diff for details.

Key changes:

  • New JobTranslation model tracks per-locale import status
  • Content hashing prevents redundant re-imports of unchanged translations
  • New API client methods: get_file_status_for_locale(), download_translation_for_locale(), add_locale_to_job()
  • Migrations 0005-0008 introduce JobTranslation and migrate existing data

Resolves #28
Resolves #37

Includes fixups from manual testing.


Test plan used:

Manual Test Plan: Issues #28 and #37

This test plan covers the new functionality for per-locale translation import and adding locales to existing jobs.

Prerequisites

  1. A Wagtail site with wagtail-localize-smartling configured
  2. Access to the Smartling dashboard for the configured project
  3. At least 3 target locales configured (e.g., French, German, Spanish)
  4. A translatable page type (e.g., InfoPage)

Test Scenario 1: Basic Per-Locale Import (Issue #37)

Goal: Verify that completed translations are imported individually before the entire job closes.

Steps

  1. Create a test page
  • Create a new page with translatable content (title, body text)
  • Note the page title for reference
  1. Submit for translation to multiple locales
  • Go to the page's edit view
  • Click "Translate" and select French, German, and Spanish
  • Submit to Smartling
  1. Verify job creation
  • Check the Smartling Jobs report in Wagtail admin
  • Confirm a single job was created with all 3 locales
  1. Complete ONE locale in Smartling
  • In Smartling dashboard, complete the French translation only
  • Leave German and Spanish incomplete
  1. Run sync
  • Trigger a sync (via management command or scheduled task)
  • Or wait for the next sync cycle
  1. Verify partial import
  • Check the French translation page exists and has translated content
  • Check the German and Spanish pages still show "Waiting for translations"
  • Check the job status is still IN_PROGRESS (not COMPLETED)
  1. Complete remaining locales
  • Complete German and Spanish in Smartling
  • Run sync again
  1. Verify full import
  • All 3 translations should now be imported
  • Job status should transition to COMPLETED or CLOSED

Expected Results

  • French translation imports while job is still in progress
  • German and Spanish translations import when completed
  • No duplicate imports occur

RESULT: ✅ PASSED


Test Scenario 2: Adding Locales to UNSYNCED Job (Issue #28)

Goal: Verify that new locales can be added to a job that hasn't synced to Smartling yet.

Steps

  1. Create a test page
  • Create a new page with translatable content
  1. Submit for translation to ONE locale
  • Submit the page for French translation only
  • Do NOT run sync yet (job should be UNSYNCED)
  1. Verify job status
  • Check the Smartling Jobs report
  • Confirm job status is "Unsynced"
  1. Submit same page for additional locales
  • Go back to the page
  • Submit for German and Spanish translation
  1. Verify locale addition
  • Check the Smartling Jobs report
  • The SAME job should now show French, German, AND Spanish
  • No new job should have been created
  1. Run sync
  • Trigger a sync to push the job to Smartling
  1. Verify in Smartling
  • The job in Smartling should have all 3 locales
  • Only one job should exist for this content

Expected Results

  • Additional locales added to existing UNSYNCED job
  • No duplicate jobs created
  • Single job synced to Smartling with all locales

RESULT: ✅ PASSED


Test Scenario 3: Adding Locales to DRAFT Job (Issue #28)

Goal: Verify that new locales can be added to a job that's in DRAFT status in Smartling.

Steps

  1. Create a test page
  • Create a new page with translatable content
  1. Submit for translation and sync
  • Submit the page for French translation
  • Run sync to push to Smartling
  • Verify job status is "Draft" in Smartling (don't authorize it)
  1. Submit same page for additional locale
  • Submit the same page for German translation
  1. Run sync again
  • Trigger another sync
  1. Verify in Smartling
  • The same job should now include both French AND German
  • Check Smartling dashboard to confirm German was added to the existing job

Expected Results

  • German locale added to existing DRAFT job via Smartling API
  • No duplicate jobs created
  • Both locales visible in Smartling job

RESULT: ✅ PASSED


Test Scenario 4: New Job for IN_PROGRESS Content (Issue #28)

Goal: Verify that a new parallel job is created when adding locales to a job that's already in progress.

Steps

  1. Create a test page
  • Create a new page with translatable content
  1. Submit and authorize job
  • Submit for French translation
  • Run sync
  • In Smartling, authorize and start the job (status becomes IN_PROGRESS)
  1. Verify job is in progress
  • Run sync again to update status
  • Confirm job status is "In Progress" in Wagtail
  1. Submit same page for new locale
  • Submit the same page for German translation
  1. Verify new job created
  • Check the Smartling Jobs report
  • A NEW job should have been created for German
  • The original French job should still exist separately
  1. Run sync
  • Sync the new job to Smartling
  1. Verify in Smartling
  • Two separate jobs should exist for this page
  • One for French (in progress)
  • One for German (new)

Expected Results

  • New job created for German (not added to IN_PROGRESS job)
  • Original French job unaffected
  • Both jobs can complete independently

RESULT: ✅ PASSED


Test Scenario 5: Content Change Creates New Job

Goal: Verify that changing page content creates a new job even if a pending job exists.

Steps

  1. Create and submit a page
  • Create a page with title "Original Title"
  • Submit for French translation
  • Run sync (job is now in Smartling)
  1. Modify the page content
  • Edit the page and change title to "Updated Title"
  • Save the page
  1. Submit modified page for same locale
  • Submit the modified page for French translation again
  1. Verify new job created
  • A NEW job should be created (different content hash)
  • The original job should still exist

Expected Results

  • New job created for modified content
  • Content hash difference detected
  • Both jobs tracked separately

RESULT: ✅ PASSED


Test Scenario 6: Translation Correction Re-import (Issue #37)

Goal: Verify that corrected translations are re-imported when the content hash changes.

Steps

  1. Complete a translation
  • Submit a page for French translation
  • Complete and import the translation
  • Verify the French page has the translated content
  1. Make a correction in Smartling
  • In Smartling, edit the French translation (fix a typo, improve wording)
  • The job may still be open or could be reopened
  1. Run sync
  • Trigger a sync
  1. Verify re-import
  • The French translation should be updated with the correction
  • Check that JobTranslation.content_hash was updated

Expected Results

  • Corrected translation detected via content hash comparison
  • Translation re-imported with corrections
  • imported_at timestamp updated

RESULT: ❌ FAILED because sync_smartling didn't try to re-check the status if of the original job. This can be deferred to another ticket


Test Scenario 7: Skipping Unchanged Translations

Goal: Verify that unchanged translations are not re-imported unnecessarily.

Steps

  1. Complete a translation
  • Submit and complete a French translation
  • Note the imported_at timestamp
  1. Run sync multiple times
  • Trigger sync several times without any changes in Smartling
  1. Verify no re-import
  • The imported_at timestamp should NOT change
  • No "translation imported" signals should fire
  • Logs should indicate skipping due to matching content hash

Expected Results

  • Unchanged translations not re-imported
  • Content hash comparison working correctly
  • No unnecessary processing

RESULT: ✅ PASSED


Test Scenario 8: Mixed Workflow

Goal: Test a realistic workflow combining multiple features.

Steps

  1. Initial submission
  • Create a page
  • Submit for French and German
  1. Add locale before sync
  • Before syncing, submit for Spanish
  • Verify Spanish added to same job
  1. Sync and start translation
  • Run sync
  • In Smartling, start working on translations
  • Complete French first
  1. Verify partial import
  • Run sync
  • French should be imported
  • German and Spanish still pending
  1. Add another locale
  • Submit for Italian
  • Since job is IN_PROGRESS, a new job should be created
  1. Complete remaining translations
  • Complete German, Spanish in original job
  • Complete Italian in new job
  1. Final sync
  • Run sync
  • All translations should be imported

Expected Results

  • All features work together correctly
  • No lost translations
  • No duplicate imports
  • Jobs tracked correctly

RESULT: ✅ PASSED

@stevejalim stevejalim force-pushed the issues-28-and-37 branch 2 times, most recently from 9a52b4f to 842ec2d Compare January 26, 2026 14:57
Note: drops support for earlier versions of Python and Django

Note: Postgres min version is 14 for Django 5.2 - this moves it to 16 for CI
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request updates dependency versions and implements two significant features for handling multi-locale translation jobs. The changes address issues #28 (adding locales to existing jobs) and #37 (per-locale import of translations).

Changes:

  • Updated Python, Django, and Wagtail version support to newer releases
  • Implemented per-locale translation import functionality to allow importing completed locales before entire job finishes
  • Added ability to dynamically add new locales to existing jobs in expandable states
  • Introduced JobTranslation through model to track per-locale import status

Reviewed changes

Copilot reviewed 25 out of 26 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tox.ini Updated test matrix to support Python 3.13, Django 5.1/5.2/6.0, and Wagtail 6.2/7.2
tests/views/test_resubmit_job_view.py Removed noqa comments and reformatted long lines
tests/test_sync.py Added comprehensive tests for per-locale import functionality
tests/test_models.py Added tests for JobTranslation model and locale addition to jobs
tests/test_client.py Added tests for new per-locale API methods; removed unused import
tests/conftest.py Added fixtures for multi-locale jobs and per-locale API mocking
testapp/settings.py Removed deprecated USE_L10N, added django-tasks configuration, reformatted settings
src/wagtail_localize_smartling/viewsets.py Added pyright ignore comment for method override
src/wagtail_localize_smartling/views.py Added pyright ignore comments for method overrides
src/wagtail_localize_smartling/sync.py Implemented per-locale import logic and translation hash computation
src/wagtail_localize_smartling/models.py Added JobTranslation through model and locale addition logic
src/wagtail_localize_smartling/migrations/* Added migrations for JobTranslation model
src/wagtail_localize_smartling/constants.py Added EXPANDABLE_JOB_STATUSES constant
src/wagtail_localize_smartling/components.py Removed noqa comment
src/wagtail_localize_smartling/api/types.py Added type definitions for new API endpoints
src/wagtail_localize_smartling/api/serializers.py Added serializers for new API endpoints
src/wagtail_localize_smartling/api/client.py Implemented per-locale API methods; reformatted code
src/wagtail_localize_smartling/init.py Bumped version to 0.11.0
pyproject.toml Updated version requirements and ruff configuration
.pre-commit-config.yaml Updated ruff version
.github/workflows/test.yml Updated test matrix and PostgreSQL version

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


# Get all JobTranslation records that haven't been imported yet
pending_job_translations: list[JobTranslation] = list(
job.job_translations.filter(imported_at__isnull=True).select_related( # pyright: ignore[reportAttributeAccessIssue]
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

The pyright ignore comment suggests a type checking issue that should be addressed rather than suppressed. Consider adding proper type annotations or model definitions to resolve the underlying type issue.

Copilot uses AI. Check for mistakes.
logger.info("Translation not found for locale %s, skipping", wagtail_locale_id)
continue

job_translation = job.job_translations.filter( # pyright: ignore[reportAttributeAccessIssue]
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

The pyright ignore comment suggests a type checking issue that should be addressed rather than suppressed. Consider adding proper type annotations or model definitions to resolve the underlying type issue.

Copilot uses AI. Check for mistakes.
Translations can now be imported as individual locales complete, rather
than waiting for the entire Smartling job to finish. This is tracked via
a new JobTranslation through model with imported_at and content_hash
fields.

Also adds support for expanding existing jobs with new locales when they
are in UNSYNCED, DRAFT, or AWAITING_AUTHORIZATION states.

Key changes:
- New JobTranslation model tracks per-locale import status
- Content hashing prevents redundant re-imports of unchanged translations
- New API client methods: get_file_status_for_locale(),
  download_translation_for_locale(), add_locale_to_job()
- Migrations 0005-0008 introduce JobTranslation and migrate existing data

Resolves #28
Resolves #37

Includes fixups from manual testing
@stevejalim stevejalim changed the title WIP Issues 28 and 37 Add per-locale translation imports and job expansion Jan 26, 2026
@stevejalim stevejalim marked this pull request as ready for review January 26, 2026 16:24
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@stevejalim
Copy link
Collaborator Author

Self-review: r+
Manual testing checks out OK

@stevejalim stevejalim merged commit 88bb292 into main Jan 26, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support syncing down complete translations even if the Job is not yet closed Support for submitting additional languages for translation

2 participants