Skip to content

feat: Translation Memory Management#3596

Draft
dkrizan wants to merge 43 commits intomainfrom
dkrizan/translation-memory-management
Draft

feat: Translation Memory Management#3596
dkrizan wants to merge 43 commits intomainfrom
dkrizan/translation-memory-management

Conversation

@dkrizan
Copy link
Copy Markdown
Member

@dkrizan dkrizan commented Apr 8, 2026

This is the main PR for the entire feature — it accumulates commits as the work progresses through the cycle.

Architecture decisions

  • Single suggestion endpoint for all plans. POST /v2/projects/{id}/suggest/translation-memory dispatches at runtime: when the project has any readable TM assignments, it uses ManagedTranslationMemorySuggestionService (queries translation_memory_entry); otherwise it falls back to the legacy TranslationMemoryService (queries the translation table). No URL swap on the frontend, no breaking API change.
  • Plan-aware filter inside the managed service. getReadableTmIdsForSuggestions(projectId, organizationId) returns only the project's own PROJECT-type TM on free plan, or project + shared TMs on paid plan (Feature.TRANSLATION_MEMORY).
  • Free plan opens up to the new TM. Free users get suggestions and single-key auto-translate from their project's own TM. Shared TMs, management UI, TMX import/export, and batch pre-translate by TM remain Business-only.
  • Existing projects fall through to the legacy service. They have no auto-created project TM, so getReadableTmIds returns empty and the dispatch picks the legacy path. No migration required.

Status

✅ Done

Data model & write pipeline

  • Entities: TranslationMemory, TranslationMemoryEntry, TranslationMemoryProject (entity-backed join with read/write/priority).
  • Liquibase schema + GiST trigram index on translation_memory_entry(target_language_tag, source_text).
  • tuid column on entries for TMX round-trip.
  • Auto-create project TM on ProjectCreationService.createProject() for every new project (all plans).
  • Write TM entries on every TranslationService.save() for any project with writable TM assignments.
  • Cleanup hooks: ProjectHardDeletingService removes project TM + assignments; cascade deletes via DB FK on translation removal.

Org-level shared TM management (EE)

  • CRUD API for shared TMs, requires MAINTAINER org role.
  • Frontend: single flat list with All/Shared/Project only filter tabs, type badges per row, kebab menu (Settings + Delete).
  • Unified TM Settings dialog — name, base language, project assignments with Read/Write toggles, single-add project autocomplete, soft-delete with keep/discard confirmation and undo. Replaces the old Edit dialog and the Projects tab on the TM detail view.
  • TM content browser: paginated by distinct source text (language filter narrows columns, never hides source rows); inline click-to-edit, create entry dialog, delete with confirmation.

Project-level TM configuration (EE)

  • API: list / connect / disconnect / update assignments (with keepData=true snapshot on disconnect).
  • Frontend: TM section on Advanced tab with drag-and-drop priority reorder (@dnd-kit), Read/Write chips per row, settings gear (opens unified dialog), "Manage all TMs" button.

Suggestion endpoint (CE)

  • Unified /v2/projects/{id}/suggest/translation-memory dispatches between managed and legacy services.
  • ManagedTranslationMemorySuggestionService lives in backend/data (CE), plan-aware via EnabledFeaturesProvider.
  • TmAutoTranslateProvider simplified to a single CE impl with the same dispatch — EE override deleted.
  • Free plan: project TM only. Paid plan: project TM + assigned shared TMs.

TMX import / export (EE)

  • TMX 1.4b parser (DOM-based, namespace-aware, DTD loading disabled).
  • TMX 1.4b writer (StAX-based, preserves tuid, groups by tuid or source text).
  • Import dialog with keep/override conflict resolution (matched by tuid + language).
  • Import result counts by translation unit (distinct tuid/source).

Plan gating

  • Feature.TRANSLATION_MEMORY flag, backend enforced on management endpoints + batch pre-translate.
  • StartBatchJobController.translate() hard-gated with @RequiresFeatures (regression for free-plan API clients — documented).
  • Frontend: management UI hidden for free users; pre-translate by TM batch option hidden in the operations menu.
  • Free users still see the suggestion panel and use /suggest/translation-memory against their project TM.

🚧 To do

  • Per-row source-TM badge in editor suggestion panel (in progress)
  • Documentation: TM management guide, TMX import/export, migration notes
  • Performance verification on batch pre-translate (40k × 5 baseline)

Deferred / cut

  • Plan gating UI rename ("Translation Memory" → "Similar translations" for free plan) — not needed: free plan now uses the same TM system, so the label stays "Translation Memory".
  • Lazy migration for existing Business plan projects — handled implicitly by the dispatch (existing projects fall through to the legacy service; no backfill required).

Tests

  • CE: TranslationSuggestionControllerTmTest (legacy path), BatchPreTranslateByTmTest (paid happy path + free 400 gate).
  • EE: TranslationSuggestionControllerManagedTmTest (paid + free dispatch, project TM only on free), SharedTranslationMemoryControllerTest, ProjectTranslationMemoryControllerTest, TranslationMemoryEntryControllerTest, TranslationMemoryTmxControllerTest.
  • E2E: orgSettings.cy.ts, projectSettings.cy.ts, tmContentBrowser.cy.ts. All pre-existing TM features covered.

Test plan

  • CI passes
  • Manual smoke: free org new project → suggestion panel works (project TM), management UI hidden, batch pre-translate hidden.
  • Manual smoke: free org old project (no project TM) → suggestion panel still works via legacy path.
  • Manual smoke: paid org → full feature: shared TMs, settings dialog, import/export, batch pre-translate.
  • API: free user POST /suggest/translation-memory → 200; POST /start-batch-job/pre-translate-by-tm → 400 feature_not_enabled.

@dkrizan dkrizan force-pushed the dkrizan/translation-memory-management branch from 4f09c13 to d1f5338 Compare April 9, 2026 07:07
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 9, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bb26d87f-9255-49c1-9161-f777caf5caf6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dkrizan/translation-memory-management

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dkrizan dkrizan force-pushed the dkrizan/translation-memory-management branch from d1f5338 to 325ce9c Compare April 9, 2026 07:53
@dkrizan dkrizan linked an issue Apr 9, 2026 that may be closed by this pull request
@dkrizan dkrizan force-pushed the dkrizan/translation-memory-management branch 2 times, most recently from 5b7d3b8 to ed0dcd6 Compare April 9, 2026 09:52
dkrizan added 2 commits April 9, 2026 13:12
Introduces dedicated Translation Memory entities for the upcoming
shared TM feature, without affecting the existing "similar translations"
suggestion feature which continues querying the translation table directly.

- New entities: TranslationMemory, TranslationMemoryEntry, TranslationMemoryProject
- Liquibase schema + GiST trigram index for similarity search
- Auto-create project TM on project creation via ProjectCreationService
- Write TM entries on translation save (only for projects with TM assignments)
- Clean up TM data on project hard-deletion
- TRANSLATION_MEMORY feature flag for future gating
- Integration tests covering entity lifecycle, write pipeline,
  translation deletion cascade, and project deletion cleanup

Existing "similar translations" feature is unchanged — it will be
renamed on the frontend as part of a later phase.
Introduces the managed Translation Memory feature (Business plan).
All new code lives in the EE module and is gated by
@RequiresFeatures(Feature.TRANSLATION_MEMORY).

Organization-level shared TM CRUD:
- SharedTranslationMemoryService (EE): create/update/delete/find/list
- SharedTranslationMemoryController at /v2/organizations/{orgId}/translation-memories
- Rejects mutations of PROJECT-type TMs via the shared endpoint
- Requires MAINTAINER org role for mutations

Project-level TM configuration:
- ProjectTranslationMemoryConfigService (EE) with assign/unassign/update
- ProjectTranslationMemoryController at /v2/projects/{projectId}/translation-memories
- Individual CRUD endpoints (GET list, POST assign, DELETE unassign, PUT update)
- Cross-organization assignment is rejected
- Cannot unassign the project's own PROJECT-type TM
- Re-assigning an already-assigned TM returns 400

Managed TM suggestions (separate from the original "similar translations"):
- New ManagedTranslationMemorySuggestionService (EE) queries only the
  translation_memory_entry table — it does not touch the translation table.
- New endpoint POST /v2/projects/{projectId}/suggest/managed-translation-memory
- Results come from all assigned TMs (project's own + any readable shared TMs)
- Original /suggest/translation-memory endpoint is completely untouched
  and still queries the translation table for free plan users.

Supporting additions (CE):
- TranslationMemoryRepository: find / findByOrganizationId /
  findByOrganizationIdAndTypePaged / findAssignedToProject
- Message enum: TRANSLATION_MEMORY_NOT_FOUND,
  TRANSLATION_MEMORY_PROJECT_ASSIGNMENT_NOT_FOUND,
  CANNOT_UNASSIGN_PROJECT_FROM_OWN_TRANSLATION_MEMORY,
  CANNOT_MODIFY_PROJECT_TRANSLATION_MEMORY,
  TRANSLATION_MEMORY_ALREADY_ASSIGNED_TO_PROJECT
- ActivityType enum: TRANSLATION_MEMORY_CREATE/UPDATE/DELETE,
  ASSIGN_PROJECT / UNASSIGN_PROJECT / UPDATE_PROJECT_CONFIG

Test data:
- TranslationMemoryTestData gained sharedTm (pre-assigned) and
  unassignedSharedTm (for assign tests); all tests use pre-built data
  instead of inline setup.

Tests (17 new EE tests, all passing):
- SharedTranslationMemoryControllerTest (6 tests)
- ProjectTranslationMemoryControllerTest (7 tests)
- ManagedTranslationMemorySuggestionControllerTest (4 tests)

Existing CE tests for the original "similar translations" feature all
still pass; TranslationMemoryService.kt in the CE module is identical
to origin/main.

Not in this commit (follow-ups):
- Auto-translate / pre-translate integration with shared TMs
- "Keep or delete" choice on shared TM disconnect
- Lazy migration for existing Business plan projects (per pitch)
- TMX import/export
- Frontend UI
@dkrizan dkrizan force-pushed the dkrizan/translation-memory-management branch from ed0dcd6 to 847a5bf Compare April 9, 2026 11:13
dkrizan added 15 commits April 9, 2026 13:29
Adds keepData query param to DELETE /v2/projects/{id}/translation-memories/{tmId}.
When true, entries from the shared TM are snapshotted into the project's own
PROJECT-type TM before the assignment is removed — the project retains the data
after disconnect. Default (false) preserves previous behavior: assignment is
removed, shared TM and its entries remain intact for other projects.

Snapshot drops translation/key back-references since they belong to the original
source project and have no meaning in the destination.
…Memory

Introduces TmAutoTranslateProvider — a feature-aware dispatcher for TM lookups
used by auto-translate and batch pre-translate.

- OSS impl: delegates to the classic TranslationMemoryService (translation table).
- EE impl (@primary): for projects whose organization has Feature.TRANSLATION_MEMORY
  enabled, routes through ManagedTranslationMemorySuggestionService which queries
  translation_memory_entry. This exposes the project's own TM (including snapshots
  from keepData=true disconnect, TMX imports, and future manual edits) plus all
  shared TMs assigned to the project with read access. Projects without the feature
  fall through to the OSS path.

Both softAutoTranslate (single-key) and autoTranslateUsingTm (batch) now go through
the dispatcher.
New endpoints under /v2/organizations/{orgId}/translation-memories/{tmId}/entries
for listing, creating, updating, and deleting individual TM entries. Used for
direct editing of TM content — complements the write-on-save pipeline, shared-TM
snapshots, and future TMX import.

- Paginated list with optional free-text search (source or target) and
  targetLanguageTag filter
- Organization MAINTAINER role required for create/update/delete; list uses
  @UseDefaultPermissions
- Feature-gated via @RequiresFeatures(Feature.TRANSLATION_MEMORY)
- Entries created this way have no translation/key back-references, matching
  the semantics of TMX imports and keepData snapshots
…O bounds

Must-fix items:
- snapshotEntriesIntoProjectTm: deduplicates against existing project-TM
  triples before saveAll. Hibernate's configured jdbc.batch_size=1000
  groups the INSERTs automatically, so repeat disconnect-with-keepData
  is now idempotent and large snapshots batch correctly.
- Partial unique index on translation_memory_entry(translation_memory_id,
  translation_id) WHERE translation_id IS NOT NULL: prevents concurrent
  write-on-save from producing duplicate rows. upsertEntry falls back to
  re-fetch-and-update on DataIntegrityViolationException so the race is
  handled silently.
- Create/UpdateTranslationMemoryEntryRequest.sourceText/targetText:
  @SiZe(max = 10000) aligned with TolgeeProperties.maxTranslationTextLength
  default, preventing unbounded bodies.
- findByProjectIdAndReadAccessTrue: add FLUSH_MODE=COMMIT query hint for
  consistency with the write variant — same hot-path flush risk applies
  to the auto-translate/suggestion read path.

Should-fix follow-ups:
- Drop redundant CascadeType.REMOVE on TranslationMemory.entries /
  projectAssignments. DB-level ON DELETE CASCADE already handles cleanup;
  the JPA cascade would force an N+1 load before delete.
- Delete unused hasTmAssignments / existsByProjectId (dead code).
- @min(0) on AssignSharedTranslationMemoryRequest.priority and
  UpdateProjectTranslationMemoryAssignmentRequest.priority — reject
  negative priority at the DTO boundary.
- Clarify empty-tmIds guard and transactional contract with code comments.

Tests: idempotent keepData re-snapshot; oversized entry payload → 400.
…ist, E2E tests

- Editor TM panel: conditionally call managed-translation-memory endpoint
  when TRANSLATION_MEMORY feature is enabled (Business plan).
- Org settings: new Translation memories page with list (shared + project
  TMs), create/edit/delete dialogs, empty state, feature-gated banner.
- With-stats endpoint: entry count, assigned projects (first 3 names),
  project count — displayed in the list item.
- Create dialog: supports assigning projects on creation (read+write).
- Refactor: extract BaseLanguageSelect from GlossaryBaseLanguageSelect
  into shared component/languages/.
- Fix: ON DELETE CASCADE on translation_memory_project FKs.
- E2E: orgSettings.cy.ts with navigation, CRUD, permission tests.
  Compounds, test data controller, TRANSLATION_MEMORY feature flag.
- New ProjectSettingsTranslationMemory component: project memory row
  (always-on toggle) + shared TMs as priority-ordered chips + Configure
  button linking to org TM settings.
- Fix auto-incrementing priority when assigning TMs from org-level
  create dialog (was hardcoded to 0).
- E2E: projectSettings.cy.ts with section visibility, always-on state,
  chip display, configure navigation tests.
- New TranslationMemoryView: detail page at /translation-memories/:id
  with breadcrumb, search bar, multi-select language filter.
- Entries grouped by sourceText+keyName, rendered in Translations-view
  style: source text row with flag, target language rows below.
- Multi-select language filter with InfiniteMultiSearchSelect, base
  language shown as checked+disabled, persisted to localStorage.
- Backend: entry query switched to native SQL with bytea::text casts,
  supports comma-separated language tags filter, ordered by source_text.
- TM list items are now clickable — navigate to the detail page.
When no saved preference exists, all languages appear checked in the
selector and no filter is sent to the API. Deselecting a language
switches to explicit mode with all-minus-one.
- Org TM list: split into "Shared" and "Project" sections with compact
  grid for project TMs (no empty projects column/kebab).
- TM detail: "Projects" tab with read/write checkboxes, remove button.
  Backend: GET .../assigned-projects endpoint + HATEOAS model.
- Project settings: replaced chips with priority table, up/down arrows
  for reordering (sequential mutateAsync to avoid race conditions).
- EE/OSS pattern: ProjectSettingsTranslationMemory exported via tg.ee
  module with Empty stub in OSS.
- Entry browser: display all selected languages per group (empty rows
  for missing translations), search moved to inline toolbar.
- Various fixes: checkbox toggle refetch, Manage button layout.
- Inline click-to-edit on target language cells (PUT existing, POST new
  entries for empty cells, DELETE with confirmation). Follows
  GlossaryListTranslationCell pattern: Enter saves, Escape cancels.
- Create entry dialog: source text + multiple target languages at once,
  MUI Select for language picker.
- Backend tests (9 new, 57 total passing): with-stats endpoint (entry
  count, project names, search), assigned-projects endpoint, create with
  assignedProjectIds + auto-incrementing priority, comma-separated
  language filter, ON DELETE CASCADE on TM with assignments.
- TM list alignment: unified 4-column grid for both shared and project
  sections so language flags align vertically.
- Fixed listWrapper border selector for split sections.
- tmContentBrowser.cy.ts (9 tests): navigation, entries display, search,
  inline edit/cancel/delete, create entry dialog, empty state.
- tmProjectsTab.cy.ts (6 tests): tab switch, checkbox states, toggle
  read/write, remove project, empty state.
- projectSettings.cy.ts (2 new): priority table display, arrow states.
- orgSettings.cy.ts: lowercase test titles.
- New compounds: E2TranslationMemoryView (entries + projects tab),
  E2ProjectTmSettings (project settings TM section).
- Updated E2TranslationMemoriesView with findAndVisitTm method.
- Fix: wrap language filter in Box with data-cy for testability.
…TM lists

- Backend: added `type` query param to GET /translation-memories-with-stats
  endpoint. Filters by SHARED or PROJECT when provided, returns all when
  omitted.
- Frontend: two separate API calls (type=SHARED, type=PROJECT) each with
  own PaginatedHateoasList and independent pagination. Replaces the
  client-side split with size=100.
- Backend tests: 3 new tests for type filter (SHARED returns 2, PROJECT
  returns 1, no filter returns all). 21 total SharedTM controller tests
  passing.
- Import TMX 1.4b files into any TM with keep/override conflict resolution
- Export TM entries as TMX file download
- DOM parser with DTD loading disabled for robust XML handling
- StAX writer for valid TMX output with xml:lang attributes
- Entry count shows translation units (distinct source texts) not individual entries
- Import result counts by translation unit for consistency with UI grouping
- Frontend: import dialog with file dropzone + mode selector, export button
- Backend tests (9) and E2E tests (3 new, 12 total in content browser)
@dkrizan dkrizan force-pushed the dkrizan/translation-memory-management branch from 627c037 to b16f9dc Compare April 14, 2026 09:57
dkrizan added 4 commits April 14, 2026 13:57
Store tuid from TMX <tu> elements in translation_memory_entry.
Conflict resolution (keep/override) now matches by tuid + target
language. Entries with different or null tuid are always created
regardless of source text match.
Org settings:
- Single flat list with All/Shared/Project only filter tabs
- Inline search + filter in one row
- Type badges (Shared/Project only) on every row
- Kebab menu: Settings + Delete (red)

Project settings:
- Drag-and-drop priority reorder via @dnd-kit
- Read/Write chips, settings gear icon per row
- "Manage all TMs" button

Unified TM settings dialog (replaces edit dialog + Projects tab):
- Name, base language, project assignments with read/write toggles
- Add project via single-select autocomplete (unassigned only)
- Soft-delete with keep/discard confirmation and undo
- Backend: assignedProjects with per-project access in update endpoint

Remove Projects tab from TM detail view.
Update E2E tests and compounds to match new UI.
dkrizan and others added 22 commits April 15, 2026 12:55
Paginate by distinct source text instead of flat entries so the
language filter only controls which translations are included per
source text, not which source texts appear.
Free users now get suggestions from their project's own TM (entries from
translation_memory_entry, written on every save). Shared TMs, management
UI, and batch pre-translate by TM remain Business-only.

Backend
- Unify the suggestion endpoint: /suggest/translation-memory dispatches to
  the managed service when the project has any readable TM assignments,
  falls back to the legacy TranslationMemoryService for projects that
  pre-date the feature.
- TranslationMemoryManagementService.getReadableTmIdsForSuggestions filters
  out shared TMs on free plan via EnabledFeaturesProvider.
- Move ManagedTranslationMemorySuggestionService from EE to CE; pass
  organizationId through so the plan check works inside the service.
- Replace TmAutoTranslateProviderEeImpl with the same dispatch in CE.
- Hard-gate StartBatchJobController.translate (pre-translate by TM) with
  @RequiresFeatures(Feature.TRANSLATION_MEMORY) — free plan returns 400.
- Remove the redundant /suggest/managed-translation-memory endpoint.

Frontend
- Tools panel always calls /suggest/translation-memory, no URL swap.
- Pre-translate by TM batch operation is hidden for free users.
- Regenerate API schema (drops the removed endpoint).

Tests
- New TranslationSuggestionControllerManagedTmTest covers paid + free dispatch.
- Update BatchPreTranslateByTmTest: stub EnabledFeaturesProvider.isFeatureEnabled
  directly (Mockito mocks default interface methods, so stubbing get() is
  not enough); add 400 feature_not_enabled assertion for free plan.
- Update TranslationMemoryEntryControllerTest language-filter assertion to
  match source-text pagination semantics (totalElements counts source texts,
  not language-filtered entries).
Backend
- Add nullable translationMemoryName to TranslationMemoryItemView (with
  @jvmoverloads so the legacy 6-arg JPQL constructor expression in
  TranslationRepository still resolves).
- Add translationMemoryName to the HATEOAS TranslationMemoryItemModel +
  assembler.
- ManagedTranslationMemorySuggestionService SQL joins translation_memory
  and returns the TM name per row. Legacy TranslationMemoryService leaves
  the field null (no TM concept there).

Frontend
- TranslationMemoryItem renders a small muted chip next to the key name
  when translationMemoryName is present. Hidden for legacy-path results
  and for single-TM projects where the source is obvious.
The `/entries` list endpoint now paginates by distinct source text and
returns a page of groups. Each group contains only the entries matching
the `targetLanguageTag` filter, but the source row itself always appears
on the page regardless of whether any translation matches. This makes
the TM content browser show every source text as a row even when the
user filters to a language that has no translations — with blank cells
the user can click to add.

Backend
- New TranslationMemoryEntryGroupModel + assembler (source text, key
  name, entries filtered by language).
- listEntryGroups() on the management service; groups include empty
  entries lists when no translation matches.
- Controller list endpoint returns PagedModel<…EntryGroupModel>.

Frontend
- TranslationMemoryEntriesList reads translationMemoryEntryGroups and
  maps to EntryGroup directly. Language filter keeps being sent so the
  backend narrows the entries payload, not the rows.

Tests
- TranslationMemoryEntryControllerTest list cases rewritten to assert on
  the new translationMemoryEntryGroups shape and empty-entries behavior
  for unmatched language filters.
- Create entry dialog seeds the first target row via useEffect when org
  languages load after the dialog mounted. Previously the initial
  useState ran with availableLanguages=[] and targets stayed empty,
  failing the test in CI where languages took longer to load.
- E2TranslationMemoryView import compound now writes and reads the
  generated fixture from the same path (cypress/fixtures/generated/),
  fixing the file-not-found error in CI.
Pre-translate uses the same plan-aware dispatch as the suggestion
endpoint — free users pre-translate from their project TM only, paid
users get shared TMs too. Remove the @RequiresFeatures hard gate and
the frontend hidden flag so the batch option is available to everyone.
Entries with the same source text but different keys (or null key) now
appear as separate rows in the TM content browser. Previously they were
merged into one group and only one translation per language was visible.
Previously onTranslationSaved returned early on empty text, leaving
the stale TM entry in place. Now it deletes the entry from all writable
TMs when the translation text is cleared.
- Rename test methods to clearly describe scenario and expected outcome
- Split the language-filter list test into two focused tests
- Fix misleading comments (search test, tuid helpers)
- Add documentation comments to TMX builder helpers and multi-step tests
- Revert broken ObjectMapper-to-DSL conversion in SharedTranslationMemoryControllerTest
  (JsonUnit does not support JSONPath filter expressions; lazy-loading
  issue on detached entity)
Members can browse TM entries but cannot create, edit, import, or
delete. Hide the "+ Entry" button, Import button, inline edit click,
and delete button when the user is not an org owner or maintainer.
Export stays visible (read-only).

Also move inline French entry creation from the comma-separated
language filter test into TranslationMemoryTestData. Update all
affected assertion counts.
When the Formik field value is undefined and organization languages
have loaded, automatically select the first available language.
Improves create dialog UX for both TM and glossary.
Users with TRANSLATIONS_VIEW/EDIT see the TM list on project settings
(read-only) but no longer see the drag handle, settings gear, "Manage
all TMs" button, or the drag-to-reorder hint. Backend already requires
PROJECT_EDIT for assign/unassign/update.
Users with PROJECT_EDIT but not org maintainer role see a simplified
dialog from the gear icon: only read/write access toggles, no TM
name/language/project assignment editing. Org maintainers still get
the full TM settings dialog.
- BaseLanguageSelect: fix useEffect dependency array with ref guard to
  prevent re-render loop on auto-select
- Add onError handlers to all TM mutation calls (settings save, entry
  CRUD, import, create dialog) with messageService.error toasts
- Use TmxImportResult type from generated schema instead of `as any`
- Memoize availableProjects in settings dialog
- Add aria-label to remove-language icon button in create entry dialog
- Fix prettier in ProjectTmAssignmentDialog
Extract TmxParsedEntry and TranslationMemoryEntryGroup into their own
files instead of nesting them inside TmxParser and the management
service respectively.
Existing projects created before the TM management feature have no
project TM, so suggestions silently fell back to the legacy path even
after the org gained TRANSLATION_MEMORY.

Listen for OnOrganizationFeaturesChanged and create a project TM for
each project in the org when the feature is gained. Backfill entries
from existing translations in the same call via a native INSERT ...
SELECT so the TM content browser is populated immediately.

Add getOrCreateProjectTm(projectId) as a lazy safety net used by
snapshot-on-disconnect; re-fetches inside the transaction so async
callers don't trip over detached lazy proxies.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds two complementary migration paths for legacy projects that never
received a project TM:

- Spring Batch migration job runs on every app startup via the existing
  MigrationJobRunner framework. Iterates non-deleted projects without a
  project-type TM assignment and creates one for each whose organization
  has the TRANSLATION_MEMORY feature enabled. Dedupes via hashed job
  parameters so it only re-runs when the candidate set changes.

- Lazy fallback in the suggestion endpoint (side panel hot path):
  ensureProjectTmIfFeatureEnabled is cheap when the TM exists (one
  indexed query) and short-circuits on free-plan orgs without a feature
  lookup cost.

Together with the existing feature-gained listener this covers: plan
upgrades while running (listener), orgs paid before the migration code
shipped (startup job), and any residual gap (lazy fallback).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Placing the migration job in backend/data meant it was instantiated in
every OSS test context as well. In CE, the feature check always returns
false so the writer is a no-op, but the runner still queries all projects
without a TM and launches the Spring Batch job. In batch-timing sensitive
tests (BatchJobsGeneralWithRedisTest) this was enough to flake assertions
that wait for Tolgee batch state transitions.

The migration job is inherently EE-specific — it only does meaningful
work when the TRANSLATION_MEMORY feature is enabled. Moving both the
runner and configuration to ee-app ensures OSS installs and OSS test
contexts don't instantiate it at all.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Displays "{N} entries" next to the search field on the TM content
browser, using the totalElements from the paginated response. Helps
users gauge TM size at a glance and provides immediate feedback on
filtering.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

Translation Memory Management

1 participant