Skip to content

Commit

Permalink
feat: add lrelease support
Browse files Browse the repository at this point in the history
Fix #282
  • Loading branch information
seanwu1105 committed Mar 4, 2023
1 parent ba9e29f commit 50a9faa
Show file tree
Hide file tree
Showing 15 changed files with 289 additions and 48 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ All features support multi-root workspace project.
- Syntax highlighting (`qss` files)
- Provide color picker for HEX, RGBA, HSVA, and HSLA code

### Qt Translation Files

- Syntax highlighting (`ts` files)
- Extract translation strings from Python, QML and UI files
- Edit translations with Qt Linguist (requires PySide6)
- Compile to binary translation files (requires PySide6)

## Supported Environment Variables

The following list shows the supported variables you can use in extension
Expand Down
55 changes: 55 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@
"command": "qtForPython.editTranslations",
"title": "Edit Qt Translation File (linguist)",
"category": "Qt for Python"
},
{
"command": "qtForPython.compileTranslations",
"title": "Compile Qt Translation File (lrelease)",
"category": "Qt for Python"
}
],
"menus": {
Expand Down Expand Up @@ -176,6 +181,11 @@
"command": "qtForPython.editTranslations",
"when": "resourceExtname == .ts && resourceLangId == xml",
"group": "qtForPython"
},
{
"command": "qtForPython.compileTranslations",
"when": "resourceExtname == .ts && resourceLangId == xml",
"group": "qtForPython"
}
],
"explorer/context": [
Expand Down Expand Up @@ -213,6 +223,11 @@
"command": "qtForPython.editTranslations",
"when": "resourceExtname == .ts && resourceLangId == xml",
"group": "qtForPython"
},
{
"command": "qtForPython.compileTranslations",
"when": "resourceExtname == .ts && resourceLangId == xml",
"group": "qtForPython"
}
],
"editor/title": [
Expand Down Expand Up @@ -250,6 +265,11 @@
"command": "qtForPython.editTranslations",
"when": "resourceExtname == .ts && resourceLangId == xml",
"group": "qtForPython"
},
{
"command": "qtForPython.compileTranslations",
"when": "resourceExtname == .ts && resourceLangId == xml",
"group": "qtForPython"
}
],
"editor/context": [
Expand Down Expand Up @@ -287,6 +307,11 @@
"command": "qtForPython.editTranslations",
"when": "resourceExtname == .ts && resourceLangId == xml",
"group": "qtForPython"
},
{
"command": "qtForPython.compileTranslations",
"when": "resourceExtname == .ts && resourceLangId == xml",
"group": "qtForPython"
}
]
},
Expand Down Expand Up @@ -420,6 +445,36 @@
],
"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.",
"scope": "resource"
},
"qtForPython.linguist.path": {
"type": "string",
"default": "",
"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.",
"scope": "resource"
},
"qtForPython.linguist.options": {
"type": "array",
"items": {
"type": "string"
},
"default": [],
"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.",
"scope": "resource"
},
"qtForPython.lrelease.path": {
"type": "string",
"default": "",
"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.",
"scope": "resource"
},
"qtForPython.lrelease.options": {
"type": "array",
"items": {
"type": "string"
},
"default": [],
"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.",
"scope": "resource"
}
}
},
Expand Down
1 change: 1 addition & 0 deletions python/.coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ omit =
scripts/qml.py
scripts/lupdate.py
scripts/linguist.py
scripts/lrelease.py

[report]
fail_under = 100
Expand Down
16 changes: 16 additions & 0 deletions python/scripts/lrelease.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# pylint: disable=import-error,ungrouped-imports

import sys

from utils import is_installed, parse_qt_dependency

if __name__ == "__main__":
dep = parse_qt_dependency()
if dep == "PySide6":
from PySide6.scripts.pyside_tool import lrelease

elif is_installed("PySide6"):
from PySide6.scripts.pyside_tool import lrelease
else:
sys.exit("No lrelease can be found in current Python environment.")
sys.exit(lrelease())
2 changes: 1 addition & 1 deletion python/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
ASSETS_DIR = os.path.join(TESTS_DIR, "assets")

SupportedScripts = typing.Literal[
"designer", "qml", "qmlls", "rcc", "uic", "lupdate", "linguist"
"designer", "qml", "qmlls", "rcc", "uic", "lupdate", "linguist", "lrelease"
]


Expand Down
44 changes: 44 additions & 0 deletions python/tests/test_lrelease.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import os

import pytest

from scripts.utils import SupportedQtDependencies
from tests import ASSETS_DIR, filter_available_qt_dependencies, invoke_script
from tests.test_lupdate import invoke_lupdate


@pytest.mark.parametrize(
"qt_dependency",
filter_available_qt_dependencies(["PySide6"]),
)
def test_lrelease_help(qt_dependency: SupportedQtDependencies):
result = invoke_script("lrelease", ["-help"], qt_dependency)
assert result.returncode == 0
assert len(result.stdout.decode("utf-8")) > 0


@pytest.mark.parametrize(
"qt_dependency",
filter_available_qt_dependencies(["PySide6"]),
)
def test_lrelease_sample_ts(qt_dependency: SupportedQtDependencies):
filename_no_ext = "sample"

try:
os.remove(get_assets_path(f"{filename_no_ext}.qm"))
except FileNotFoundError:
pass

invoke_lupdate(filename_no_ext, qt_dependency)

result = invoke_script(
"lrelease", [get_assets_path(f"{filename_no_ext}.ts")], qt_dependency
)
assert result.returncode == 0
assert len(result.stdout.decode("utf-8")) > 0

os.remove(get_assets_path(f"{filename_no_ext}.qm"))


def get_assets_path(filename: str) -> str:
return os.path.join(ASSETS_DIR, "linguist", filename)
27 changes: 18 additions & 9 deletions python/tests/test_lupdate.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,31 @@ def test_lupdate_help(qt_dependency: SupportedQtDependencies):
"qt_dependency",
filter_available_qt_dependencies(["PySide6", "PySide2", "PyQt6", "PyQt5"]),
)
def test_lupdate_sample_py(qt_dependency: str):
def test_lupdate_sample_py(qt_dependency: SupportedQtDependencies):
filename_no_ext = "sample"

try:
os.remove(get_assets_path("sample.ts"))
os.remove(get_assets_path(f"{filename_no_ext}.ts"))
except FileNotFoundError:
pass

filename = "sample.py"
result = invoke_script(
result = invoke_lupdate(filename_no_ext, qt_dependency)
assert result.returncode == 0
assert os.path.exists(get_assets_path(f"{filename_no_ext}.ts"))

os.remove(get_assets_path(f"{filename_no_ext}.ts"))


def invoke_lupdate(filename_no_exi: str, qt_dependency: SupportedQtDependencies):
return invoke_script(
"lupdate",
[get_assets_path(filename), "-ts", get_assets_path("sample.ts")],
[
get_assets_path(f"{filename_no_exi}.py"),
"-ts",
get_assets_path(f"{filename_no_exi}.ts"),
],
qt_dependency,
)
assert result.returncode == 0
assert os.path.exists(get_assets_path("sample.ts"))

os.remove(get_assets_path("sample.ts"))


def get_assets_path(filename: str) -> str:
Expand Down
5 changes: 5 additions & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { EXTENSION_NAMESPACE } from './constants'
import { createUi } from './designer/create-ui'
import { editUi } from './designer/edit-ui'
import { editTranslations } from './linguist/edit-translations'
import { compileTranslations } from './lrelease/compile-translation'
import { extractTranslations } from './lupdate/extract-translation'
import { previewQml } from './qml/preview-qml'
import { compileResource } from './rcc/compile-resource'
Expand Down Expand Up @@ -61,6 +62,10 @@ const COMMANDS = [
name: 'editTranslations',
callback: editTranslations,
},
{
name: 'compileTranslations',
callback: compileTranslations,
},
] as const

type CommandCallbackValue = Awaited<
Expand Down
34 changes: 34 additions & 0 deletions src/lrelease/compile-translation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { firstValueFrom } from 'rxjs'
import type { CommandDeps } from '../commands'
import { getTargetDocumentUri } from '../commands'
import { run } from '../run'
import { getToolCommand$ } from '../tool-utils'

export async function compileTranslations(
{ extensionUri }: CommandDeps,
...args: any[]
) {
const targetDocumentUriResult = getTargetDocumentUri(...args)

if (targetDocumentUriResult.kind !== 'Success') return targetDocumentUriResult

const translationFile = targetDocumentUriResult.value

const getToolCommandResult = await firstValueFrom(
getToolCommand$({
tool: 'lrelease',
extensionUri,
resource: translationFile,
}),
)

if (getToolCommandResult.kind !== 'Success') return getToolCommandResult

return run({
command: [
...getToolCommandResult.value.command,
translationFile.fsPath,
...getToolCommandResult.value.options,
],
})
}
12 changes: 12 additions & 0 deletions src/test/suite/linguist/compile-translations.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as assert from 'node:assert'
import { commands } from 'vscode'
import { EXTENSION_NAMESPACE } from '../../../constants'

suite('compileTranslations', () => {
test('should include the command', async () =>
assert.ok(
(await commands.getCommands(true)).includes(
`${EXTENSION_NAMESPACE}.compileTranslations`,
),
))
})

0 comments on commit 50a9faa

Please sign in to comment.