Skip to content
This repository was archived by the owner on Oct 15, 2024. It is now read-only.

Commit 50a9faa

Browse files
committed
feat: add lrelease support
Fix #282
1 parent ba9e29f commit 50a9faa

15 files changed

+289
-48
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ All features support multi-root workspace project.
3838
- Syntax highlighting (`qss` files)
3939
- Provide color picker for HEX, RGBA, HSVA, and HSLA code
4040

41+
### Qt Translation Files
42+
43+
- Syntax highlighting (`ts` files)
44+
- Extract translation strings from Python, QML and UI files
45+
- Edit translations with Qt Linguist (requires PySide6)
46+
- Compile to binary translation files (requires PySide6)
47+
4148
## Supported Environment Variables
4249

4350
The following list shows the supported variables you can use in extension

package.json

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@
138138
"command": "qtForPython.editTranslations",
139139
"title": "Edit Qt Translation File (linguist)",
140140
"category": "Qt for Python"
141+
},
142+
{
143+
"command": "qtForPython.compileTranslations",
144+
"title": "Compile Qt Translation File (lrelease)",
145+
"category": "Qt for Python"
141146
}
142147
],
143148
"menus": {
@@ -176,6 +181,11 @@
176181
"command": "qtForPython.editTranslations",
177182
"when": "resourceExtname == .ts && resourceLangId == xml",
178183
"group": "qtForPython"
184+
},
185+
{
186+
"command": "qtForPython.compileTranslations",
187+
"when": "resourceExtname == .ts && resourceLangId == xml",
188+
"group": "qtForPython"
179189
}
180190
],
181191
"explorer/context": [
@@ -213,6 +223,11 @@
213223
"command": "qtForPython.editTranslations",
214224
"when": "resourceExtname == .ts && resourceLangId == xml",
215225
"group": "qtForPython"
226+
},
227+
{
228+
"command": "qtForPython.compileTranslations",
229+
"when": "resourceExtname == .ts && resourceLangId == xml",
230+
"group": "qtForPython"
216231
}
217232
],
218233
"editor/title": [
@@ -250,6 +265,11 @@
250265
"command": "qtForPython.editTranslations",
251266
"when": "resourceExtname == .ts && resourceLangId == xml",
252267
"group": "qtForPython"
268+
},
269+
{
270+
"command": "qtForPython.compileTranslations",
271+
"when": "resourceExtname == .ts && resourceLangId == xml",
272+
"group": "qtForPython"
253273
}
254274
],
255275
"editor/context": [
@@ -287,6 +307,11 @@
287307
"command": "qtForPython.editTranslations",
288308
"when": "resourceExtname == .ts && resourceLangId == xml",
289309
"group": "qtForPython"
310+
},
311+
{
312+
"command": "qtForPython.compileTranslations",
313+
"when": "resourceExtname == .ts && resourceLangId == xml",
314+
"group": "qtForPython"
290315
}
291316
]
292317
},
@@ -420,6 +445,36 @@
420445
],
421446
"markdownDescription": "The options passed to Qt `lupdate` executable. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
422447
"scope": "resource"
448+
},
449+
"qtForPython.linguist.path": {
450+
"type": "string",
451+
"default": "",
452+
"markdownDescription": "The path to Qt `linguist` executable. Set to empty string to automatically resolve from the installed Python package. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
453+
"scope": "resource"
454+
},
455+
"qtForPython.linguist.options": {
456+
"type": "array",
457+
"items": {
458+
"type": "string"
459+
},
460+
"default": [],
461+
"markdownDescription": "The options passed to Qt `linguist` executable. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
462+
"scope": "resource"
463+
},
464+
"qtForPython.lrelease.path": {
465+
"type": "string",
466+
"default": "",
467+
"markdownDescription": "The path to Qt `lrelease` executable. Set to empty string to automatically resolve from the installed Python package. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
468+
"scope": "resource"
469+
},
470+
"qtForPython.lrelease.options": {
471+
"type": "array",
472+
"items": {
473+
"type": "string"
474+
},
475+
"default": [],
476+
"markdownDescription": "The options passed to Qt `lrelease` executable. See [here](https://github.com/seanwu1105/vscode-qt-for-python#predefined-variables) for a detailed list of predefined variables.",
477+
"scope": "resource"
423478
}
424479
}
425480
},

python/.coveragerc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ omit =
88
scripts/qml.py
99
scripts/lupdate.py
1010
scripts/linguist.py
11+
scripts/lrelease.py
1112

1213
[report]
1314
fail_under = 100

python/scripts/lrelease.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# pylint: disable=import-error,ungrouped-imports
2+
3+
import sys
4+
5+
from utils import is_installed, parse_qt_dependency
6+
7+
if __name__ == "__main__":
8+
dep = parse_qt_dependency()
9+
if dep == "PySide6":
10+
from PySide6.scripts.pyside_tool import lrelease
11+
12+
elif is_installed("PySide6"):
13+
from PySide6.scripts.pyside_tool import lrelease
14+
else:
15+
sys.exit("No lrelease can be found in current Python environment.")
16+
sys.exit(lrelease())

python/tests/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
ASSETS_DIR = os.path.join(TESTS_DIR, "assets")
1313

1414
SupportedScripts = typing.Literal[
15-
"designer", "qml", "qmlls", "rcc", "uic", "lupdate", "linguist"
15+
"designer", "qml", "qmlls", "rcc", "uic", "lupdate", "linguist", "lrelease"
1616
]
1717

1818

python/tests/test_lrelease.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import os
2+
3+
import pytest
4+
5+
from scripts.utils import SupportedQtDependencies
6+
from tests import ASSETS_DIR, filter_available_qt_dependencies, invoke_script
7+
from tests.test_lupdate import invoke_lupdate
8+
9+
10+
@pytest.mark.parametrize(
11+
"qt_dependency",
12+
filter_available_qt_dependencies(["PySide6"]),
13+
)
14+
def test_lrelease_help(qt_dependency: SupportedQtDependencies):
15+
result = invoke_script("lrelease", ["-help"], qt_dependency)
16+
assert result.returncode == 0
17+
assert len(result.stdout.decode("utf-8")) > 0
18+
19+
20+
@pytest.mark.parametrize(
21+
"qt_dependency",
22+
filter_available_qt_dependencies(["PySide6"]),
23+
)
24+
def test_lrelease_sample_ts(qt_dependency: SupportedQtDependencies):
25+
filename_no_ext = "sample"
26+
27+
try:
28+
os.remove(get_assets_path(f"{filename_no_ext}.qm"))
29+
except FileNotFoundError:
30+
pass
31+
32+
invoke_lupdate(filename_no_ext, qt_dependency)
33+
34+
result = invoke_script(
35+
"lrelease", [get_assets_path(f"{filename_no_ext}.ts")], qt_dependency
36+
)
37+
assert result.returncode == 0
38+
assert len(result.stdout.decode("utf-8")) > 0
39+
40+
os.remove(get_assets_path(f"{filename_no_ext}.qm"))
41+
42+
43+
def get_assets_path(filename: str) -> str:
44+
return os.path.join(ASSETS_DIR, "linguist", filename)

python/tests/test_lupdate.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,31 @@ def test_lupdate_help(qt_dependency: SupportedQtDependencies):
2626
"qt_dependency",
2727
filter_available_qt_dependencies(["PySide6", "PySide2", "PyQt6", "PyQt5"]),
2828
)
29-
def test_lupdate_sample_py(qt_dependency: str):
29+
def test_lupdate_sample_py(qt_dependency: SupportedQtDependencies):
30+
filename_no_ext = "sample"
31+
3032
try:
31-
os.remove(get_assets_path("sample.ts"))
33+
os.remove(get_assets_path(f"{filename_no_ext}.ts"))
3234
except FileNotFoundError:
3335
pass
3436

35-
filename = "sample.py"
36-
result = invoke_script(
37+
result = invoke_lupdate(filename_no_ext, qt_dependency)
38+
assert result.returncode == 0
39+
assert os.path.exists(get_assets_path(f"{filename_no_ext}.ts"))
40+
41+
os.remove(get_assets_path(f"{filename_no_ext}.ts"))
42+
43+
44+
def invoke_lupdate(filename_no_exi: str, qt_dependency: SupportedQtDependencies):
45+
return invoke_script(
3746
"lupdate",
38-
[get_assets_path(filename), "-ts", get_assets_path("sample.ts")],
47+
[
48+
get_assets_path(f"{filename_no_exi}.py"),
49+
"-ts",
50+
get_assets_path(f"{filename_no_exi}.ts"),
51+
],
3952
qt_dependency,
4053
)
41-
assert result.returncode == 0
42-
assert os.path.exists(get_assets_path("sample.ts"))
43-
44-
os.remove(get_assets_path("sample.ts"))
4554

4655

4756
def get_assets_path(filename: str) -> str:

src/commands.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { EXTENSION_NAMESPACE } from './constants'
66
import { createUi } from './designer/create-ui'
77
import { editUi } from './designer/edit-ui'
88
import { editTranslations } from './linguist/edit-translations'
9+
import { compileTranslations } from './lrelease/compile-translation'
910
import { extractTranslations } from './lupdate/extract-translation'
1011
import { previewQml } from './qml/preview-qml'
1112
import { compileResource } from './rcc/compile-resource'
@@ -61,6 +62,10 @@ const COMMANDS = [
6162
name: 'editTranslations',
6263
callback: editTranslations,
6364
},
65+
{
66+
name: 'compileTranslations',
67+
callback: compileTranslations,
68+
},
6469
] as const
6570

6671
type CommandCallbackValue = Awaited<

src/lrelease/compile-translation.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { firstValueFrom } from 'rxjs'
2+
import type { CommandDeps } from '../commands'
3+
import { getTargetDocumentUri } from '../commands'
4+
import { run } from '../run'
5+
import { getToolCommand$ } from '../tool-utils'
6+
7+
export async function compileTranslations(
8+
{ extensionUri }: CommandDeps,
9+
...args: any[]
10+
) {
11+
const targetDocumentUriResult = getTargetDocumentUri(...args)
12+
13+
if (targetDocumentUriResult.kind !== 'Success') return targetDocumentUriResult
14+
15+
const translationFile = targetDocumentUriResult.value
16+
17+
const getToolCommandResult = await firstValueFrom(
18+
getToolCommand$({
19+
tool: 'lrelease',
20+
extensionUri,
21+
resource: translationFile,
22+
}),
23+
)
24+
25+
if (getToolCommandResult.kind !== 'Success') return getToolCommandResult
26+
27+
return run({
28+
command: [
29+
...getToolCommandResult.value.command,
30+
translationFile.fsPath,
31+
...getToolCommandResult.value.options,
32+
],
33+
})
34+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as assert from 'node:assert'
2+
import { commands } from 'vscode'
3+
import { EXTENSION_NAMESPACE } from '../../../constants'
4+
5+
suite('compileTranslations', () => {
6+
test('should include the command', async () =>
7+
assert.ok(
8+
(await commands.getCommands(true)).includes(
9+
`${EXTENSION_NAMESPACE}.compileTranslations`,
10+
),
11+
))
12+
})

0 commit comments

Comments
 (0)