A PyQt6-based graphical XLIFF editor for professional translation workflows. Supports XLIFF 2.0, 2.1, and 2.2 files. Provides tag-aware editing, AI/MT translation via multiple providers (Claude, Gemini, OpenAI, DeepL), translation memory (TMX) support, spell checking, SDL Trados SDLXLIFF conversion, memoQ MQXLIFF conversion, bilingual Excel import/export, and automatic light/dark theme switching.
**A note for Windows users: ** To clone this repository, first you need to install the git binary for Windows (Git Guides - install git Β· GitHub) and then open a command line window (cmd) and enter the following:
git clone https://github.com/piotr-bienkowski/XLIFF2Editor.git
From any directory:
python -m XLIFF2EditorOr from inside the XLIFF2Editor directory:
python Xedaibt.pyThe application detects the system theme at startup and applies light or dark mode automatically. The theme can be toggled manually at any time via Options β Switch to Light/Dark Theme.
| Package | Purpose |
|---|---|
PyQt6 |
GUI framework |
beautifulsoup4 |
XML/XLIFF parsing |
lxml |
XML processing (used by BeautifulSoup and converters) |
fuzzywuzzy |
Fuzzy string matching for TM lookups |
AI/MT translation, spell checking, and Excel import/export are optional features. Install only the packages for the functionality you intend to use.
| Package | Provider/Feature | Purpose |
|---|---|---|
anthropic |
Claude | Claude API client |
google-generativeai |
Gemini | Google Gemini API client |
openai |
OpenAI | OpenAI API client |
deepl |
DeepL | DeepL MT API client |
pyenchant |
β | Spell checking (gracefully disabled if missing) |
openpyxl |
Excel | Read/write Excel workbooks (.xlsx) |
regex |
Excel | Unicode-aware SRX segmentation (falls back to stdlib re if missing) |
| Dependency | Purpose |
|---|---|
Enchant dictionaries (e.g., hunspell-pl, hunspell-en-us) |
Language dictionaries for spell checking |
| Module | Purpose |
|---|---|
sdlxliff_xliff22_converter.py |
Converts SDL Trados SDLXLIFF files to XLIFF 2.2 |
xliff22_to_sdlxliff_batch_merger.py |
Merges XLIFF 2.2 translations back into SDLXLIFF files |
mqxliff_xliff22_converter.py |
Converts memoQ MQXLIFF files to XLIFF 2.2 |
xliff22_to_mqxliff_merger.py |
Merges XLIFF 2.2 translations back into MQXLIFF files |
excel_xliff22_converter.py |
Converts bilingual Excel workbooks to XLIFF 2.2 |
xliff22_to_excel_merger.py |
Merges XLIFF 2.2 translations back into Excel |
srx_segmenter.py |
SRX 2.0 sentence segmenter (used by Excel converter) |
segment.srx |
SRX 2.0 segmentation rules |
pip install PyQt6 beautifulsoup4 lxml fuzzywuzzyFor AI/MT translation, install the client(s) for the provider(s) you want to use:
pip install anthropic # Claude
pip install google-generativeai # Gemini
pip install openai # OpenAI
pip install deepl # DeepLFor Excel import/export:
pip install openpyxl regexFor spell checking dictionaries (Debian/Ubuntu):
sudo apt install libenchant-2-2 hunspell-en-us hunspell-pl| File | Purpose |
|---|---|
~/XLIFF2Editor/xconfig.json |
All settings: API keys, preferences, recent files, context history |
~/xliff_editor_glossary.tsv |
Default glossary (tab-delimited, UTF-8) |
All persistent settings are stored in a single file inside the module directory. It is created automatically on first launch. Edit it directly to add API keys.
{
"api_keys": {
"ANTHROPIC_API_KEY": "",
"GOOGLE_API_KEY": "",
"OPENAI_API_KEY": "",
"DEEPL_API_KEY": ""
},
"settings": {
"font_size": 10,
"underline_color": {"r": 57, "g": 255, "b": 20},
"ai_provider": "Claude",
"glossary_file": ""
},
"recent_files": [],
"contexts": []
}Fill in only the key(s) for the provider(s) you intend to use. glossary_file is optional β if empty, ~/xliff_editor_glossary.tsv is used.
On first launch after upgrading, the editor automatically migrates settings from the old ~/.xliff_editor_*.json files and API keys from ~/config.json, then deletes the old files. ~/config.json itself is not deleted (it may be shared with other scripts). GEMINI_API_KEY in the old config is imported as GOOGLE_API_KEY.
| Feature | Shortcut | Description |
|---|---|---|
| Open XLIFF | Ctrl+O |
Open XLIFF files (.xliff, .xlf) |
| Open Recent | Menu | Quick access to recently opened files |
| Save XLIFF | Ctrl+S |
Save changes to current file |
| Save As | Ctrl+Shift+S |
Save to a new file location |
| Close XLIFF | Ctrl+W |
Close current file with save prompt |
| Exit | Ctrl+Q |
Exit application |
| Feature | Description |
|---|---|
| Import from SDLXLIFF | Convert one or more SDLXLIFF files to XLIFF 2.2 format |
| Export to SDLXLIFF | Merge XLIFF 2.2 translations back into original SDLXLIFF files |
| Feature | Description |
|---|---|
| Import from memoQ (MQXLIFF) | Convert one or more MQXLIFF files to XLIFF 2.2 format |
| Export to memoQ (MQXLIFF) | Merge XLIFF 2.2 translations back into original MQXLIFF files |
| Feature | Description |
|---|---|
| Import from Excel | Convert a bilingual Excel workbook (.xlsx) to XLIFF 2.2 for editing |
| Export to Excel | Merge XLIFF 2.2 translations back into the original Excel file in-place |
An Import dialog lets you specify:
| Setting | Default | Description |
|---|---|---|
| Source language | en-US |
BCP-47 language tag for the source column |
| Target language | pl-PL |
BCP-47 language tag for the target column |
| Source column | A |
Excel column letter(s) containing source text |
| Target column | B |
Excel column letter(s) where translations will be merged |
| First data row | 2 |
First row number containing translatable content (skips header rows) |
| Segment source | off | When checked, splits source cells into sentence-level units using SRX 2.0 rules |
After import, the XLIFF is offered for saving under the original filename with a .xlf extension.
Round-trip metadata is stored in the XLIFF so the merger knows where to write each translation back:
x-excel-rowon each<unit>β the 1-based Excel row number the segment came fromx-excel-src-col/x-excel-tgt-colon<file>β the source and target column letters
Segmentation (Segment source checkbox):
When segmentation is enabled, long source cells are split into sentence-level <unit> elements using SRX 2.0 rules (segment.srx). Multiple units from the same cell share the same x-excel-row and are joined back on export. Alt+Enter line breaks (\n) within a cell are handled separately from SRX rules:
\nfollowed by an uppercase letter is treated as a segment boundary regardless of punctuation.\nfollowed by a lowercase letter (a typographic line wrap within a sentence) is preserved as an inline<ph equiv="\n"/>tag.
On export, the merger reconstructs the original cell structure: segments are joined with a space for SRX-based boundaries and with \n for Alt+Enter boundaries. Inline <ph equiv="\n"/> tags in translated segments are converted back to \n characters in the Excel cell.
The memoQ converter handles XLIFF 1.2 files with the MQXliff namespace extension as exported by memoQ. Inline tags (bpt/ept flat pairs, g, ph, x) are converted to XLIFF 2.2 pc/ph elements for editing and converted back on export. The mq:status attribute is mapped to/from XLIFF 2.2 state using the values defined in MQXliffSchema-4-0-21.xsd.
| Feature | Shortcut | Description |
|---|---|---|
| Load TMX | Ctrl+T |
Load a TMX file for fuzzy matching |
| TM Matching | Automatic | Displays matches β₯ 70% similarity in the right panel |
| Insert TM Match | Double-click | Overwrites target with the selected TM match |
| Insert Glossary Term | Single-click | Inserts the translation at cursor (does not overwrite) |
The right panel shows two sections when a segment is selected:
- TM matches β sorted by score descending, format
85% β translation. Double-clicking a match overwrites the entire target. - Glossary matches β terms from the loaded glossary file that appear in the source segment, format
source term β translation. Single-clicking inserts the translation at the current cursor position in the target editor. If the editor is not focused, the translation is appended to the existing target text.
A greyed-out ββ glossary ββ separator divides the two sections when both are present.
TMX loading uses lxml.etree.iterparse β the file is streamed element by element and each parsed node is discarded immediately after use, keeping memory consumption flat at approximately 1Γ the file size regardless of how large the TMX is.
A filter bar sits between the AI toolbar and the segment table. It hides non-matching rows in real time without modifying the file.
| Control | Description |
|---|---|
| Filter source⦠| Show only segments whose source text contains the entered string |
| Filter target⦠| Show only segments whose target text contains the entered string |
| AND / OR | When both fields are filled: AND requires both to match; OR requires either to match |
| Regex | Treat filter strings as regular expressions (Python re syntax, case-sensitive) |
| π | Apply the filter (Enter in either field also applies) |
| β | Clear both fields and show all rows |
- Filtering is case-insensitive in plain-text mode and case-sensitive in Regex mode.
- Batch operations (AI Translate All, Copy All Sources to Targets, Clear All Targets) operate only on the visible (non-hidden) rows while a filter is active.
- The filter is cleared automatically when a file is opened or closed.
| Feature | Shortcut | Description |
|---|---|---|
| Insert Next Tag | Ctrl+N |
Insert the next missing tag from source |
| Clear Current Target | Ctrl+D |
Clear target text of selected segment |
| Clear All Targets | Ctrl+Shift+D |
Clear all target segments (with confirmation) |
| Set Status to Translated | Ctrl+E |
Mark current segment as translated |
| Copy Source to Target | Alt+S |
Copy source text to target for current row |
| Copy All Sources to Targets | Ctrl+Shift+C |
Copy all source texts to targets |
| Feature | Shortcut | Description |
|---|---|---|
| Set Target Language | Ctrl+L |
Set or change target language code |
| Edit Languages | Ctrl+Shift+L |
Edit both source and target language codes |
| Feature | Shortcut | Description |
|---|---|---|
| AI Translate Current | Ctrl+Shift+A |
Translate current segment with the selected provider |
| AI Translate All Initial | Ctrl+Shift+T |
Batch translate all untranslated segments |
The AI toolbar at the top of the window contains two controls:
- AI selector β choose the active provider (saved between sessions)
- Context field β optional free-text context injected into every prompt (editable combo box with history; expands to fill remaining toolbar width)
| Provider | Key in xconfig.json |
Model used |
|---|---|---|
| Claude | ANTHROPIC_API_KEY |
claude-sonnet-4-20250514 |
| Gemini | GOOGLE_API_KEY |
gemini-2.0-flash |
| OpenAI | OPENAI_API_KEY |
gpt-4o-mini |
| DeepL | DEEPL_API_KEY |
DeepL MT |
Rate limiting: all providers are throttled to a maximum of one API call per second.
Completion dialog: shown after batch translation only; single-segment translation gives no dialog.
Glossary: loaded from a tab-delimited file (source term TAB target term, one pair per line, UTF-8). Only terms that actually appear in the source segment are included in the prompt β this prevents the model from reproducing the entire glossary instead of translating. Switch the glossary file at runtime via Options β Load Glossaryβ¦
Context history: each context string used for translation is saved to xconfig.json. Reselect previous contexts from the toolbar dropdown without retyping.
DeepL note: tags (<1>, </1>, <2/>) are temporarily renamed to valid XML names before sending to the DeepL API and restored afterwards. DeepL does not use the glossary file.
DeepL language codes: most target languages use the 2-letter code (PL, DEβ¦). English and Portuguese require a regional variant (EN-US, EN-GB, PT-BR, PT-PT) and are converted automatically from the XLIFF trgLang attribute.
| Feature | Description |
|---|---|
| Change Font Size | Adjust font size (6β24pt) |
| Change Spell Check Underline Color | Customize the color of spell check underlines (dark mode only) |
| Load Glossary⦠| Switch the active glossary TSV file at runtime |
| Switch to Light/Dark Theme | Toggle between light and dark theme; system theme is applied on launch |
- Automatic spell checking for target segments
- Wavy underline for misspelled words (configurable color)
- Right-click context menu with spelling suggestions
- Add words to personal dictionary
- Supports multiple languages via enchant dictionaries
The custom editor (TagProtectedTextEdit) provides:
- Visual highlighting of tags in red bold text
- Protection against accidental tag modification
- Tags displayed as simplified tokens:
<1>,</1>,<1/> - Cursor navigation around protected tag regions
- Locked segments (
translate="no") are displayed with a π padlock icon - Locked segments cannot be edited
- Grey text and darker background indicate locked status
+-------------------------------------------------------------------+
| Menu Bar: File | Tasks | Options |
+-------------------------------------------------------------------+
| AI: [Claude βΌ] Context: [context text... βΌ] |
+-------------------------------------------------------------------+
| ID | π | Source | Target | Status | Matches|
|-----+----+---------------------+----------------+--------|--------|
| 1 | π | Source text here | Translation | initial| 85% ββ¦ |
| 2 | π | Another segment... | ... | transl.| stud ββ¦|
+-------------------------------------------------------------------+
- AI selector β choose provider; persisted between sessions
- Context field β free-text context appended to every translation prompt; previous values available in the dropdown
- ID β segment identifier from the XLIFF file; π prefix for locked segments
- π β file column; hover to see the full file ID in a tooltip
- Source β read-only source text with inline tag highlighting
- Target β editable target text with tag protection and spell checking
- Status β XLIFF
stateattribute value - Right panel β TM matches (double-click to overwrite) followed by glossary matches (single-click to insert at cursor)
- XLIFF 2.0, 2.1, 2.2 (.xliff, .xlf) β native format
- TMX (.tmx) β translation memory (read-only)
- SDLXLIFF (.sdlxliff) β SDL Trados format (import/export)
- MQXLIFF (.mqxliff) β memoQ format (import/export)
- Excel (.xlsx) β bilingual workbook format (import/export)
| Class | Purpose |
|---|---|
TagProtectedTextEdit |
Custom QPlainTextEdit with tag protection and spell checking |
RichTextDelegate |
Qt delegate for rendering tags in red within table cells |
TMXLoadThread |
Background thread for loading TMX files |
XLIFFLoadThread |
Background thread for loading XLIFF files |
AITranslationThread |
Background thread for AI translation operations |
XLIFFEditor |
Main application window and controller (XLIFF2Editor package) |
| SDL Status | XLIFF 2.2 State |
|---|---|
| Draft | initial |
| Translated | translated |
| ApprovedTranslation | translated |
| ApprovedSignOff | final |
| RejectedTranslation | needs-review-translation |
| RejectedSignOff | needs-review-translation |
| XLIFF 2.2 State | SDL Status |
|---|---|
| initial | Draft |
| translated | Translated |
| reviewed | ApprovedTranslation |
| final | ApprovedSignOff |
| needs-review-* | RejectedTranslation |
| memoQ status | XLIFF 2.2 state |
|---|---|
| NotStarted | initial |
| PreTranslated | translated |
| PartiallyEdited | translated |
| ManuallyConfirmed | final |
| AssembledFromFragments | translated |
| AutoJoined | translated |
| AutoSplit | initial |
| AutoSplitAndEmpty | initial |
| Ackknowledged (schema) | reviewed |
| XLIFF 2.2 state | memoQ status |
|---|---|
| initial | NotStarted |
| translated | PartiallyEdited |
| reviewed | Ackknowledged |
| final | ManuallyConfirmed |
| needs-review-* | PartiallyEdited |
Converts SDLXLIFF (SDL Trados) files to XLIFF 2.2 format.
python sdlxliff_xliff22_converter.py input.sdlxliff -o output.xlf
python sdlxliff_xliff22_converter.py *.sdlxliff -o merged.xlfImport directly from the XLIFF2Editor package:
from XLIFF2Editor.sdlxliff_xliff22_converter import convert_sdlxliff_to_xliff22
from pathlib import Path
# Convert a single file
stats = convert_sdlxliff_to_xliff22(
input_paths=["project.sdlxliff"],
output_path="project.xlf",
)
print(f"Converted {stats['total_segments']} segments from {stats['total_files']} file(s).")
# Merge an entire job package into one XLIFF for batch translation
sdlxliff_files = sorted(Path("job/").glob("*.sdlxliff"))
stats = convert_sdlxliff_to_xliff22(
input_paths=sdlxliff_files,
output_path="job/merged.xlf",
verbose=False, # suppress progress output
)The function returns a dict with at least total_segments and total_files. Set verbose=False to suppress console output when calling from a pipeline.
Merges XLIFF 2.2 translations back into original SDLXLIFF files.
python xliff22_to_sdlxliff_batch_merger.py merged.xlf \
--sdlxliff-dir ./originals \
--output-dir ./updatedConverts memoQ MQXLIFF (XLIFF 1.2 + MQXliff namespace) files to XLIFF 2.2 format.
python mqxliff_xliff22_converter.py input.mqxliff -o output.xlf
python mqxliff_xliff22_converter.py *.mqxliff -o merged.xlffrom XLIFF2Editor.mqxliff_xliff22_converter import convert_mqxliff_to_xliff22
from pathlib import Path
stats = convert_mqxliff_to_xliff22(
input_paths=["project.mqxliff"],
output_path="project.xlf",
)
print(f"Converted {stats['total_segments']} segments from {stats['total_files']} file(s).")memoQ uses bpt/ept as flat siblings (not nested) to mark paired codes such as bold or italic. The converter collects the tail text of bpt and all content until the matching ept and wraps it in an XLIFF 2.2 pc element. g elements are converted to pc directly; ph, x, and it become ph. On export back to MQXLIFF, pc is written as g and ph as x.
Merges XLIFF 2.2 translations back into original MQXLIFF files.
python xliff22_to_mqxliff_merger.py merged.xlf \
--mqxliff-dir ./originals \
--output-dir ./updatedFile matching uses exact filename lookup (the file ID in XLIFF 2.2 is set to the original MQXLIFF filename), with case-insensitive fallback. Updated files are written to --output-dir; originals are not modified.
Converts a bilingual Excel workbook (.xlsx) to XLIFF 2.2. Can be used standalone or via the File β Excel β Import from Excel menu.
from XLIFF2Editor.excel_xliff22_converter import convert_excel_to_xliff22
stats = convert_excel_to_xliff22(
input_path="brochure.xlsx",
output_path="brochure.xlf",
src_lang="en-US",
tgt_lang="pl-PL",
src_col="A", # column letter(s) for source text
tgt_col="B", # column letter(s) for target text
first_row=2, # first data row (skip header)
segment=True, # split cells into sentence-level units
srx_path=None, # defaults to segment.srx next to this file
)
print(f"Converted {stats['total_rows']} rows into {stats['total_units']} units.")Excel cells often contain Alt+Enter (\n) line breaks. The converter handles them in two ways:
\nbefore an uppercase letter β treated as a segment boundary. The text is pre-split at this point before SRX rules are applied. The boundary is marked with a trailing<ph equiv="\n"/>on the last sentence of the preceding block.\nbefore a lowercase letter β a typographic line wrap within a sentence. Preserved as an inline<ph equiv="\n"/>in the<source>element.
This ensures that cells like "Sentence one.\nBecause of this." and "Title line\ncontinued in lower case" are handled correctly on both import and export.
Merges XLIFF 2.2 translations back into the original Excel workbook in-place (target column only).
from XLIFF2Editor.xliff22_to_excel_merger import merge_xliff22_to_excel
result = merge_xliff22_to_excel(
xliff_path="brochure.xlf",
excel_path="brochure.xlsx", # modified in-place
)
print(f"Written {result['rows_written']} row(s), skipped {result['units_skipped']}.")The merger reads x-excel-tgt-col from the XLIFF <file> element to determine which Excel column to write to, so the correct column is always used regardless of what was entered in the import dialog. It raises ValueError if the XLIFF was not produced by convert_excel_to_xliff22.
Segment boundaries that originated from Alt+Enter line breaks are identified by a trailing <ph> on the source element (empty tail, any equiv value β translation tools may change equiv="\n" to a space). Such boundaries are joined with \n in the merged cell; SRX-based sentence boundaries are joined with a space. Trailing <ph> tags are stripped from the target text before writing. Inline <ph equiv="\n"/> tags within a target are converted back to \n.