translation of all project, and new translation mechanism

This commit is contained in:
2026-09-03 09:18:48 +03:00
parent 00e7c957e4
commit 01af372ef2
32 changed files with 2813 additions and 537 deletions
+90 -60
View File
@@ -9,14 +9,14 @@ Window {
height: 720
visible: true
flags: Qt.Window
title: qsTr("Minecraft Launcher")
title: Loc.t.app.title
// ── Backend ────────────────────────────────────────────────────────────
LauncherBackend {
id: backend
onLaunched: (profileName, buildName, serverUrl) => {
window.showToast(qsTr("Запущено: %1 — %2").arg(profileName).arg(buildName), "#4b7a1f")
window.showToast(Loc.t.launch.status.started.arg(profileName).arg(buildName), "#4b7a1f")
}
onLaunchProgress: (message) => window.showToast(message, "#3a5a8c", 0)
onLaunchError: (message) => window.showToast(message, "#cc3333", 8000)
@@ -27,20 +27,20 @@ Window {
}
onGameFinished: (exitCode, crashed) => {
window.showToast(crashed || exitCode !== 0
? qsTr("Игра завершилась с ошибкой (код %1)").arg(exitCode)
: qsTr("Игра закрыта"),
? Loc.t.game.status.crashed.arg(exitCode)
: Loc.t.game.status.closed,
crashed || exitCode !== 0 ? "#cc3333" : "#555555")
}
onMicrosoftLoginUrlReady: (url) => window.openMicrosoftLogin(url)
// Выбор в списке здесь не трогаем: новый профиль уже выбран тем, кто
// его создал, а повторный вход мог быть и не в последний профиль.
onMicrosoftLoginSucceeded: (playerName) =>
window.showToast(qsTr("Вход выполнен: %1").arg(playerName), "#4b7a1f")
window.showToast(Loc.t.auth.status.signedInAs.arg(playerName), "#4b7a1f")
onMicrosoftLoginFailed: (message) => window.showToast(message, "#cc3333", 8000)
onMicrosoftReloginRequired: (profileIndex) => backend.startMicrosoftLogin(profileIndex)
onGameOutput: (line) => console.log(line)
onSeasonalInstallFinished: (seasonalId, buildName) => {
window.showToast(qsTr("Сборка «%1» установлена — можно запускать").arg(buildName),
window.showToast(Loc.t.seasonal.status.readyToLaunch.arg(buildName),
"#4b7a1f", 8000)
}
}
@@ -69,7 +69,7 @@ Window {
const component = Qt.createComponent("MicrosoftLoginDialog.qml")
if (component.status !== Component.Ready) {
backend.cancelMicrosoftLogin()
window.showToast(qsTr("Окно входа Microsoft недоступно: сборка без Qt WebEngine"),
window.showToast(Loc.t.auth.msa.error.windowUnavailable,
"#cc3333", 8000)
return
}
@@ -134,11 +134,11 @@ Window {
anchors.leftMargin: 24
anchors.bottomMargin: 24
title: qsTr("Загрузка %1").arg(backend.downloadVersion)
title: Loc.t.common.progress.downloadingNamed.arg(backend.downloadVersion)
fraction: backend.downloadProgress
status: backend.downloadStatus
detail: backend.downloadBytesTotal > 0
? qsTr("%1 — %2 / %3 МБ").arg(backend.downloadStatus)
? Loc.t.common.progress.bytes.arg(backend.downloadStatus)
.arg(window.formatMb(backend.downloadBytesDone))
.arg(window.formatMb(backend.downloadBytesTotal))
: ""
@@ -201,8 +201,8 @@ Window {
// висела бы поверх собственного меню.
ToolTip.visible: profileBox.hovered && !profileBox.down
ToolTip.text: backend.profileNames.length === 0
? qsTr("Профили игрока — добавьте первый профиль")
: qsTr("Профиль игрока: %1").arg(profileBox.displayText)
? Loc.t.profile.hint.addFirst
: Loc.t.profile.label.current.arg(profileBox.displayText)
contentItem: Text {
id: profileText
@@ -263,7 +263,7 @@ Window {
Text {
anchors.centerIn: parent
text: "+ Добавить профиль"
text: Loc.t.profile.button.add
color: "#91B315"
font.pixelSize: 12
}
@@ -394,7 +394,7 @@ Window {
contentItem: Text {
text: backend.activeBuildName !== "" ? backend.activeBuildName
: qsTr("Сборка не выбрана")
: Loc.t.common.status.noBuildSelected
color: "#ffffff"
font: buildButton.font
verticalAlignment: Text.AlignVCenter
@@ -424,7 +424,7 @@ Window {
}
ToolTip.visible: hovered && !buildsDialog.opened
ToolTip.text: qsTr("Пользовательские сборки")
ToolTip.text: Loc.t.common.title.customBuilds
onClicked: buildsDialog.open()
}
@@ -450,7 +450,7 @@ Window {
}
ToolTip.visible: hovered
ToolTip.text: qsTr("Открыть папку с модами Minecraft")
ToolTip.text: Loc.t.app.tooltip.openMods
onClicked: backend.openMinecraftFolder()
}
@@ -478,7 +478,7 @@ Window {
}
ToolTip.visible: hovered
ToolTip.text: qsTr("Настройки запуска: Java, память, папка игры")
ToolTip.text: Loc.t.app.tooltip.settings
onClicked: settingsDialog.load()
}
@@ -497,7 +497,7 @@ Window {
hoverEnabled: true
contentItem: Text {
text: qsTr("Сезонные сборки")
text: Loc.t.common.title.seasonalBuilds
color: "#ffffff"
font.pixelSize: 14
elide: Text.ElideRight
@@ -513,7 +513,7 @@ Window {
}
ToolTip.visible: hovered
ToolTip.text: qsTr("Готовые сборки с сервера: моды, конфиги и Java одной кнопкой")
ToolTip.text: Loc.t.app.tooltip.seasonal
onClicked: seasonalDialog.openCatalog()
}
@@ -548,7 +548,7 @@ Window {
implicitHeight: 52 // tells Dialog how tall the header is
Text {
anchors.centerIn: parent
text: "Новый профиль"
text: Loc.t.profile.title.new
color: "#ffffff"
font.pixelSize: 17
font.bold: true
@@ -570,7 +570,7 @@ Window {
id: pfName
x: 20
width: parent.width - 40
placeholderText: "Имя профиля"
placeholderText: Loc.t.profile.placeholder.name
color: "#ffffff"
placeholderTextColor: "#666666"
background: Rectangle {
@@ -587,7 +587,7 @@ Window {
width: parent.width - 40
// Ник лицензионного профиля приходит из аккаунта: поля здесь нет.
visible: pfAuth.currentIndex !== 2
placeholderText: "Логин"
placeholderText: Loc.t.profile.placeholder.login
color: "#ffffff"
placeholderTextColor: "#666666"
background: Rectangle {
@@ -603,7 +603,7 @@ Window {
x: 20
width: parent.width - 40
visible: pfAuth.currentIndex === 1
placeholderText: "Пароль Ely.by"
placeholderText: Loc.t.profile.placeholder.elyPassword
echoMode: TextInput.Password
color: "#ffffff"
placeholderTextColor: "#666666"
@@ -620,8 +620,8 @@ Window {
x: 20
width: parent.width - 40
model: backend.microsoftAvailable
? ["Офлайн (без пароля)", "Ely.by", "Microsoft (лицензия)"]
: ["Офлайн (без пароля)", "Ely.by"]
? Loc.t.profile.authTypes
: Loc.t.profile.authTypes.slice(0, 2)
}
}
@@ -638,7 +638,7 @@ Window {
spacing: 12
Button {
text: "Отмена"
text: Loc.t.common.button.cancel
width: 110; height: 36
contentItem: Text {
text: parent.text
@@ -654,7 +654,7 @@ Window {
}
Button {
text: "Добавить"
text: Loc.t.common.button.add
width: 110; height: 36
contentItem: Text {
text: parent.text
@@ -736,7 +736,7 @@ Window {
implicitHeight: 52
Text {
anchors.centerIn: parent
text: "Редактирование профиля"
text: Loc.t.profile.title.edit
color: "#ffffff"
font.pixelSize: 17
font.bold: true
@@ -758,7 +758,7 @@ Window {
id: epName
x: 20
width: parent.width - 40
placeholderText: "Имя профиля"
placeholderText: Loc.t.profile.placeholder.name
color: "#ffffff"
placeholderTextColor: "#666666"
background: Rectangle {
@@ -775,7 +775,7 @@ Window {
width: parent.width - 40
// Для лицензии логин не вводят: ниже показан ник из аккаунта.
visible: epAuth.currentIndex !== 2
placeholderText: "Логин"
placeholderText: Loc.t.profile.placeholder.login
color: "#ffffff"
placeholderTextColor: "#666666"
background: Rectangle {
@@ -810,7 +810,7 @@ Window {
x: 20
width: parent.width - 40
visible: epAuth.currentIndex === 1
placeholderText: "Пароль Ely.by"
placeholderText: Loc.t.profile.placeholder.elyPassword
echoMode: TextInput.Password
color: "#ffffff"
placeholderTextColor: "#666666"
@@ -829,8 +829,8 @@ Window {
// Тип уже сохранённого профиля показываем всегда: иначе в
// сборке без WebEngine он молча стал бы офлайновым.
model: (backend.microsoftAvailable || editProfileDialog.msProfile)
? ["Офлайн (без пароля)", "Ely.by", "Microsoft (лицензия)"]
: ["Офлайн (без пароля)", "Ely.by"]
? Loc.t.profile.authTypes
: Loc.t.profile.authTypes.slice(0, 2)
}
// Состояние аккаунта Microsoft и кнопка повторного входа: токен
@@ -842,8 +842,8 @@ Window {
visible: epAuth.currentIndex === 2
Text {
text: editProfileDialog.msLinked ? qsTr("Вход выполнен")
: qsTr("Вход не выполнен")
text: editProfileDialog.msLinked ? Loc.t.auth.status.signedIn
: Loc.t.auth.status.signedOut
color: editProfileDialog.msLinked ? "#91B315" : "#aaaaaa"
font.pixelSize: 13
}
@@ -888,7 +888,7 @@ Window {
spacing: 12
Button {
text: "Отмена"
text: Loc.t.common.button.cancel
width: 110; height: 36
contentItem: Text {
text: parent.text
@@ -904,7 +904,7 @@ Window {
}
Button {
text: "ОК"
text: Loc.t.common.button.ok
width: 110; height: 36
contentItem: Text {
text: parent.text
@@ -972,7 +972,7 @@ Window {
implicitHeight: 52
Text {
anchors.centerIn: parent
text: "Двухфакторная аутентификация"
text: Loc.t.auth.ely.title.twoFactor
color: "#ffffff"
font.pixelSize: 15
font.bold: true
@@ -993,7 +993,7 @@ Window {
Text {
x: 20
width: parent.width - 40
text: qsTr("Введите код из приложения-аутентификатора для «%1»")
text: Loc.t.auth.ely.hint.enterCode
.arg(twoFactorDialog.profileName)
color: "#aaaaaa"
font.pixelSize: 12
@@ -1031,7 +1031,7 @@ Window {
spacing: 12
Button {
text: "Отмена"
text: Loc.t.common.button.cancel
width: 110; height: 36
contentItem: Text {
text: parent.text
@@ -1047,7 +1047,7 @@ Window {
}
Button {
text: "Войти"
text: Loc.t.common.button.signIn
width: 110; height: 36
contentItem: Text {
text: parent.text
@@ -1080,6 +1080,9 @@ Window {
// Выбранная сборка Java живёт в свойстве, а не в поле ввода: её выбирают
// в отдельном окне, а записывается она только по «Сохранить».
property string javaRuntimeId: ""
// Раскрытый путь к папке игры: хранится свойством, а не присваивается
// тексту напрямую, — иначе подпись не пережила бы смену языка.
property string resolvedGameDir: ""
// Не привязка: javaRuntimeInfo() — обычный вызов, и сам он не
// пересчитается, когда сборка докачается. Обновляем по событиям.
property var javaRuntimeInfo: null
@@ -1107,8 +1110,8 @@ Window {
stWidth.text = String(s.windowWidth)
stHeight.text = String(s.windowHeight)
stFullscreen.checked = s.fullscreen === true
stResolved.text = qsTr("Папка игры: %1").arg(s.resolvedGameDir)
stJavaList.text = backend.detectedJava().join("\n") || qsTr("Java не найдена")
stLanguage.currentIndex = Math.max(0, stLanguage.codes.indexOf(s.language || "system"))
settingsDialog.resolvedGameDir = s.resolvedGameDir
open()
}
@@ -1123,7 +1126,7 @@ Window {
implicitHeight: 52
Text {
anchors.centerIn: parent
text: "Настройки запуска"
text: Loc.t.settings.title
color: "#ffffff"
font.pixelSize: 17
font.bold: true
@@ -1141,19 +1144,43 @@ Window {
topPadding: 18
bottomPadding: 18
// Язык — настройка уровня приложения, поэтому стоит над параметрами
// запуска. Подписи в модели переводятся, коды рядом — нет.
Column {
x: 20
width: parent.width - 40
spacing: 3
Text {
text: Loc.t.settings.language.label
color: "#aaaaaa"
font.pixelSize: 11
}
DarkCombo {
id: stLanguage
width: parent.width
height: 32
readonly property var codes: ["system", "ru", "en"]
model: [Loc.t.settings.language.system,
Loc.t.settings.language.ru,
Loc.t.settings.language.en]
}
}
LabelledField {
id: stGameDir
x: 20
width: parent.width - 40
label: "Папка .minecraft"
placeholder: "по умолчанию"
label: Loc.t.settings.label.gameDir
placeholder: Loc.t.settings.placeholder.gameDir
}
LabelledField {
id: stJavaPath
x: 20
width: parent.width - 40
label: "Путь к Java"
placeholder: "автоопределение"
label: Loc.t.settings.label.javaPath
placeholder: Loc.t.settings.placeholder.javaPath
}
// Сборка Java из папки лаунчера. Выбрана — запуск идёт ей, а не тем,
@@ -1164,7 +1191,7 @@ Window {
spacing: 3
Text {
text: "Версия Java из папки лаунчера"
text: Loc.t.settings.label.javaRuntime
color: "#aaaaaa"
font.pixelSize: 11
}
@@ -1186,12 +1213,12 @@ Window {
color: settingsDialog.javaRuntimeId === "" ? "#666666" : "#ffffff"
text: {
if (settingsDialog.javaRuntimeId === "")
return qsTr("не выбрана — искать в системе")
return Loc.t.settings.status.javaRuntimeNone
const info = settingsDialog.javaRuntimeInfo
if (!info)
return settingsDialog.javaRuntimeId
return info.installed ? info.label
: qsTr("%1 — скачивается").arg(info.label)
: Loc.t.settings.status.javaRuntimeDownloading.arg(info.label)
}
}
@@ -1199,7 +1226,7 @@ Window {
anchors.right: clearJava.left
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Выбрать…")
text: Loc.t.common.button.choose
color: "#91B315"
font.pixelSize: 12
}
@@ -1240,25 +1267,25 @@ Window {
LabelledField {
id: stMinMemory
width: 100
label: "-Xms, МБ"
label: Loc.t.settings.label.minMemory
validator: IntValidator { bottom: 0; top: 1048576 }
}
LabelledField {
id: stMaxMemory
width: 100
label: "-Xmx, МБ"
label: Loc.t.settings.label.maxMemory
validator: IntValidator { bottom: 0; top: 1048576 }
}
LabelledField {
id: stWidth
width: 100
label: "Ширина окна"
label: Loc.t.settings.label.windowWidth
validator: IntValidator { bottom: 0; top: 16384 }
}
LabelledField {
id: stHeight
width: 100
label: "Высота окна"
label: Loc.t.settings.label.windowHeight
validator: IntValidator { bottom: 0; top: 16384 }
}
}
@@ -1267,14 +1294,14 @@ Window {
id: stJvmArgs
x: 20
width: parent.width - 40
label: "Дополнительные аргументы JVM"
label: Loc.t.settings.label.jvmArgs
placeholder: "-XX:+UseG1GC …"
}
CheckBox {
id: stFullscreen
x: 20
text: "Запускать в полноэкранном режиме"
text: Loc.t.settings.label.fullscreen
// Индикатор Basic-стиля — крупный светлый квадрат, на тёмном
// фоне диалога он не читается. Рисуем свой в палитре окна.
@@ -1307,6 +1334,7 @@ Window {
Text {
id: stResolved
text: Loc.t.settings.status.resolvedGameDir.arg(settingsDialog.resolvedGameDir)
x: 20
width: parent.width - 40
color: "#888888"
@@ -1315,6 +1343,7 @@ Window {
}
Text {
id: stJavaList
text: backend.detectedJava().join("\n") || Loc.t.settings.status.javaNotFound
x: 20
width: parent.width - 40
color: "#888888"
@@ -1336,7 +1365,7 @@ Window {
spacing: 12
Button {
text: "Отмена"
text: Loc.t.common.button.cancel
width: 110; height: 36
contentItem: Text {
text: parent.text
@@ -1352,7 +1381,7 @@ Window {
}
Button {
text: "Сохранить"
text: Loc.t.common.button.save
width: 110; height: 36
contentItem: Text {
text: parent.text
@@ -1378,7 +1407,8 @@ Window {
"jvmArgs": stJvmArgs.text.trim(),
"windowWidth": parseInt(stWidth.text) || 0,
"windowHeight": parseInt(stHeight.text) || 0,
"fullscreen": stFullscreen.checked
"fullscreen": stFullscreen.checked,
"language": stLanguage.codes[stLanguage.currentIndex]
})
}