1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-01 19:18:28 -04:00

i18n: term overhaul

- Delete ~160-ish useless terms
- Add context to more terms
- Add a mechanism to duplicate the same terms with different contexts
- sync
This commit is contained in:
bbedward
2026-07-16 12:15:20 -04:00
parent 6c45413d7a
commit 3c0f2cbc48
83 changed files with 19570 additions and 20947 deletions
+8
View File
@@ -36,6 +36,14 @@ repos:
language: system language: system
files: ^quickshell/(.*\.qml|translations/(term_freeze\.json|check_term_freeze\.py|extract_translations\.py))$ files: ^quickshell/(.*\.qml|translations/(term_freeze\.json|check_term_freeze\.py|extract_translations\.py))$
pass_filenames: false pass_filenames: false
- repo: local
hooks:
- id: i18n-term-variants
name: i18n term variants (no case/punctuation duplicates)
entry: python3 quickshell/translations/check_term_variants.py
language: system
files: ^quickshell/(.*\.qml|translations/(check_term_variants\.py|extract_translations\.py))$
pass_filenames: false
- repo: local - repo: local
hooks: hooks:
- id: no-console-in-qml - id: no-console-in-qml
+9 -4
View File
@@ -117,12 +117,17 @@ Singleton {
log.warn("Falling back to built-in English strings"); log.warn("Falling back to built-in English strings");
} }
function tr(term, context) { // isRealContext is consumed by translations/extract_translations.py only:
// pass a literal `true` (same line) to give (term, context) its own POEditor
// translation slot. Lookup ignores it -- a real context exists as a bucket
// in the export, a comment-only context does not.
function tr(term, context, isRealContext) {
if (!translationsLoaded || !translations) if (!translationsLoaded || !translations)
return term; return term;
const ctx = context || term; if (context && translations[context] && translations[context][term])
if (translations[ctx] && translations[ctx][term]) return translations[context][term];
return translations[ctx][term]; if (translations[term] && translations[term][term])
return translations[term][term];
for (const c in translations) { for (const c in translations) {
if (translations[c] && translations[c][term]) if (translations[c] && translations[c][term])
return translations[c][term]; return translations[c][term];
+2 -2
View File
@@ -291,7 +291,7 @@ Singleton {
_parseError = true; _parseError = true;
const msg = e.message; const msg = e.message;
log.error("Failed to parse session.json - file will not be overwritten."); log.error("Failed to parse session.json - file will not be overwritten.");
Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse session.json"), msg)); Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse %1").arg("session.json"), msg));
} }
} }
@@ -371,7 +371,7 @@ Singleton {
_parseError = true; _parseError = true;
const msg = e.message; const msg = e.message;
log.error("Failed to parse session.json - file will not be overwritten."); log.error("Failed to parse session.json - file will not be overwritten.");
Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse session.json"), msg)); Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse %1").arg("session.json"), msg));
} }
} }
+3 -3
View File
@@ -1793,7 +1793,7 @@ Singleton {
_parseError = true; _parseError = true;
const msg = e.message; const msg = e.message;
log.error("Failed to parse settings.json - file will not be overwritten. Error:", msg); log.error("Failed to parse settings.json - file will not be overwritten. Error:", msg);
Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse settings.json"), msg)); Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse %1").arg("settings.json"), msg));
applyStoredTheme(); applyStoredTheme();
} finally { } finally {
_loading = false; _loading = false;
@@ -1891,7 +1891,7 @@ Singleton {
_pluginParseError = true; _pluginParseError = true;
const msg = e.message; const msg = e.message;
log.error("Failed to parse plugin_settings.json - file will not be overwritten. Error:", msg); log.error("Failed to parse plugin_settings.json - file will not be overwritten. Error:", msg);
Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse plugin_settings.json"), msg)); Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse %1").arg("plugin_settings.json"), msg));
pluginSettings = {}; pluginSettings = {};
} finally { } finally {
_pluginSettingsLoading = false; _pluginSettingsLoading = false;
@@ -3653,7 +3653,7 @@ Singleton {
_parseError = true; _parseError = true;
const msg = e.message; const msg = e.message;
log.error("Failed to reload settings.json - file will not be overwritten. Error:", msg); log.error("Failed to reload settings.json - file will not be overwritten. Error:", msg);
Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse settings.json"), msg)); Qt.callLater(() => ToastService.showError(I18n.tr("Failed to parse %1").arg("settings.json"), msg));
} finally { } finally {
_loading = false; _loading = false;
} }
+4 -4
View File
@@ -1956,7 +1956,7 @@ Singleton {
function applyGtkColors() { function applyGtkColors() {
if (!matugenAvailable) { if (!matugenAvailable) {
if (typeof ToastService !== "undefined") { if (typeof ToastService !== "undefined") {
ToastService.showError(I18n.tr("matugen not available or disabled - cannot apply GTK colors")); ToastService.showError(I18n.tr("matugen not available or disabled - cannot apply %1 colors").arg("GTK"));
} }
return; return;
} }
@@ -1969,7 +1969,7 @@ Singleton {
} }
} else { } else {
if (typeof ToastService !== "undefined") { if (typeof ToastService !== "undefined") {
ToastService.showError(I18n.tr("Failed to apply GTK colors")); ToastService.showError(I18n.tr("Failed to apply %1 colors").arg("GTK"));
} }
} }
}); });
@@ -1978,7 +1978,7 @@ Singleton {
function applyQtColors() { function applyQtColors() {
if (!matugenAvailable) { if (!matugenAvailable) {
if (typeof ToastService !== "undefined") { if (typeof ToastService !== "undefined") {
ToastService.showError(I18n.tr("matugen not available or disabled - cannot apply Qt colors")); ToastService.showError(I18n.tr("matugen not available or disabled - cannot apply %1 colors").arg("Qt"));
} }
return; return;
} }
@@ -1990,7 +1990,7 @@ Singleton {
} }
} else { } else {
if (typeof ToastService !== "undefined") { if (typeof ToastService !== "undefined") {
ToastService.showError(I18n.tr("Failed to apply Qt colors")); ToastService.showError(I18n.tr("Failed to apply %1 colors").arg("Qt"));
} }
} }
}); });
+1 -1
View File
@@ -646,7 +646,7 @@ Singleton {
onExited: exitCode => { onExited: exitCode => {
if (exitCode === 0) { if (exitCode === 0) {
const message = root.authApplyTerminalFallbackFromPrecheck ? I18n.tr("Terminal opened. Complete authentication setup there; it will close automatically when done.") : I18n.tr("Terminal fallback opened. Complete authentication setup there; it will close automatically when done."); const message = root.authApplyTerminalFallbackFromPrecheck ? I18n.tr("Terminal opened. Complete authentication there; it will close automatically when done.") : I18n.tr("Terminal fallback opened. Complete authentication there; it will close automatically when done.");
ToastService.showInfo(message, "", "", "auth-sync"); ToastService.showInfo(message, "", "", "auth-sync");
} else { } else {
let details = (root.authApplyTerminalFallbackStderr || "").trim(); let details = (root.authApplyTerminalFallbackStderr || "").trim();
+1 -1
View File
@@ -323,7 +323,7 @@ Item {
target: TrashService target: TrashService
function onEmptyTrashConfirmRequested(itemCount) { function onEmptyTrashConfirmRequested(itemCount) {
emptyTrashConfirm.showWithOptions({ emptyTrashConfirm.showWithOptions({
title: I18n.tr("Empty Trash?"), title: I18n.tr("Empty Trash"),
message: I18n.tr("Permanently delete %1 item(s)? This cannot be undone.").arg(itemCount), message: I18n.tr("Permanently delete %1 item(s)? This cannot be undone.").arg(itemCount),
confirmText: I18n.tr("Empty"), confirmText: I18n.tr("Empty"),
cancelText: I18n.tr("Cancel"), cancelText: I18n.tr("Cancel"),
@@ -408,14 +408,14 @@ FocusScope {
StyledText { StyledText {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: "↵ " + I18n.tr("open") text: "↵ " + I18n.tr("Open")
font.pixelSize: Theme.fontSizeSmall - 1 font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
} }
StyledText { StyledText {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: "Tab " + I18n.tr("actions") text: "Tab " + I18n.tr("Actions")
font.pixelSize: Theme.fontSizeSmall - 1 font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
visible: actionPanel.hasActions visible: actionPanel.hasActions
@@ -156,7 +156,7 @@ Item {
function statusTitle() { function statusTitle() {
if (root.controller?.isSearching || root.controller?.isFileSearching) if (root.controller?.isSearching || root.controller?.isFileSearching)
return I18n.tr("Searching"); return I18n.tr("Searching...");
if ((root.controller?.searchMode ?? "") === "files" && !DSearchService.dsearchAvailable) if ((root.controller?.searchMode ?? "") === "files" && !DSearchService.dsearchAvailable)
return I18n.tr("File search unavailable"); return I18n.tr("File search unavailable");
if ((root.controller?.searchMode ?? "") === "files" && (root.controller?.searchQuery?.length ?? 0) < 2) if ((root.controller?.searchMode ?? "") === "files" && (root.controller?.searchQuery?.length ?? 0) < 2)
+1 -1
View File
@@ -294,7 +294,7 @@ FocusScope {
} }
StyledText { StyledText {
text: I18n.tr("Authentication failed, please try again") text: I18n.tr("Authentication failed - try again")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.error color: Theme.error
width: parent.width width: parent.width
+2 -2
View File
@@ -494,7 +494,7 @@ FloatingWindow {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: I18n.tr("Processes:", "process count label in footer") text: I18n.tr("Processes", "process count label in footer") + ":"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
} }
@@ -511,7 +511,7 @@ FloatingWindow {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: I18n.tr("Uptime:", "uptime label in footer") text: I18n.tr("Uptime", "uptime label in footer") + ":"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
} }
@@ -16,7 +16,7 @@ PluginComponent {
ccWidgetPrimaryText: I18n.tr("Printers") ccWidgetPrimaryText: I18n.tr("Printers")
ccWidgetSecondaryText: { ccWidgetSecondaryText: {
if (CupsService.cupsAvailable && CupsService.getPrintersNum() > 0) { if (CupsService.cupsAvailable && CupsService.getPrintersNum() > 0) {
return I18n.tr("Printers: ") + CupsService.getPrintersNum() + " - " + I18n.tr("Jobs: ") + CupsService.getTotalJobsNum(); return I18n.tr("Printers") + ": " + CupsService.getPrintersNum() + " - " + I18n.tr("Jobs") + ": " + CupsService.getTotalJobsNum();
} else { } else {
if (!CupsService.cupsAvailable) { if (!CupsService.cupsAvailable) {
return I18n.tr("Print Server not available"); return I18n.tr("Print Server not available");
@@ -18,7 +18,7 @@ PluginComponent {
ccWidgetPrimaryText: I18n.tr("VPN") ccWidgetPrimaryText: I18n.tr("VPN")
ccWidgetSecondaryText: { ccWidgetSecondaryText: {
if (vpnActivating) if (vpnActivating)
return I18n.tr("Connecting"); return I18n.tr("Connecting...");
if (!vpnActivated) if (!vpnActivated)
return I18n.tr("Disconnected"); return I18n.tr("Disconnected");
const names = DMSNetworkService.activeNames || []; const names = DMSNetworkService.activeNames || [];
@@ -166,7 +166,7 @@ Rectangle {
} }
StyledText { StyledText {
text: scanButton.isDiscovering ? I18n.tr("Scanning") : I18n.tr("Scan") text: scanButton.isDiscovering ? I18n.tr("Scanning...") : I18n.tr("Scan")
color: scanButton.adapterEnabled ? Theme.primary : Theme.surfaceVariantText color: scanButton.adapterEnabled ? Theme.primary : Theme.surfaceVariantText
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium font.weight: Font.Medium
@@ -624,7 +624,7 @@ Rectangle {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: wifiDelegate.isConnecting ? I18n.tr("Connecting...") + " \u2022" : (wifiDelegate.isConnected ? I18n.tr("Connected") + " \u2022" : (modelData.secured ? I18n.tr("Secured") + " \u2022" : I18n.tr("Open") + " \u2022")) text: wifiDelegate.isConnecting ? I18n.tr("Connecting...") + " \u2022" : (wifiDelegate.isConnected ? I18n.tr("Connected") + " \u2022" : (modelData.secured ? I18n.tr("Secured") + " \u2022" : I18n.tr("Open", "network security type", true) + " \u2022"))
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: wifiDelegate.isConnecting ? Theme.warning : Theme.surfaceVariantText color: wifiDelegate.isConnecting ? Theme.warning : Theme.surfaceVariantText
} }
@@ -329,7 +329,7 @@ Item {
spacing: Theme.spacingS spacing: Theme.spacingS
DankButton { DankButton {
text: root.saving ? I18n.tr("Saving") : I18n.tr("Save") text: root.saving ? I18n.tr("Saving...") : I18n.tr("Save")
iconName: "check" iconName: "check"
buttonHeight: 32 buttonHeight: 32
backgroundColor: Theme.primary backgroundColor: Theme.primary
+2 -2
View File
@@ -692,14 +692,14 @@ Item {
spacing: Theme.spacingM spacing: Theme.spacingM
StyledText { StyledText {
text: I18n.tr("Unsaved Changes") text: I18n.tr("Unsaved changes")
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
StyledText { StyledText {
text: root.pendingAction === "new" ? I18n.tr("You have unsaved changes. Save before creating a new file?") : root.pendingAction.startsWith("close_tab_") ? I18n.tr("You have unsaved changes. Save before closing this tab?") : root.pendingAction === "load_file" || root.pendingAction === "open" ? I18n.tr("You have unsaved changes. Save before opening a file?") : I18n.tr("You have unsaved changes. Save before continuing?") text: I18n.tr("You have unsaved changes. Save before continuing?")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceTextMedium color: Theme.surfaceTextMedium
width: parent.width width: parent.width
@@ -172,7 +172,7 @@ Rectangle {
} }
StyledText { StyledText {
text: I18n.tr("Notification Timeouts") text: I18n.tr("Timeouts")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium font.weight: Font.Medium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
@@ -182,7 +182,6 @@ Rectangle {
id: lowTimeoutDropdown id: lowTimeoutDropdown
transientSurfaceTracker: root.transientSurfaceTracker transientSurfaceTracker: root.transientSurfaceTracker
text: I18n.tr("Low Priority") text: I18n.tr("Low Priority")
description: I18n.tr("Timeout for low priority notifications")
currentValue: getTimeoutText(SettingsData.notificationTimeoutLow) currentValue: getTimeoutText(SettingsData.notificationTimeoutLow)
options: timeoutOptions.map(opt => opt.text) options: timeoutOptions.map(opt => opt.text)
onValueChanged: value => { onValueChanged: value => {
@@ -199,7 +198,6 @@ Rectangle {
id: normalTimeoutDropdown id: normalTimeoutDropdown
transientSurfaceTracker: root.transientSurfaceTracker transientSurfaceTracker: root.transientSurfaceTracker
text: I18n.tr("Normal Priority") text: I18n.tr("Normal Priority")
description: I18n.tr("Timeout for normal priority notifications")
currentValue: getTimeoutText(SettingsData.notificationTimeoutNormal) currentValue: getTimeoutText(SettingsData.notificationTimeoutNormal)
options: timeoutOptions.map(opt => opt.text) options: timeoutOptions.map(opt => opt.text)
onValueChanged: value => { onValueChanged: value => {
@@ -216,7 +214,6 @@ Rectangle {
id: criticalTimeoutDropdown id: criticalTimeoutDropdown
transientSurfaceTracker: root.transientSurfaceTracker transientSurfaceTracker: root.transientSurfaceTracker
text: I18n.tr("Critical Priority") text: I18n.tr("Critical Priority")
description: I18n.tr("Timeout for critical priority notifications")
currentValue: getTimeoutText(SettingsData.notificationTimeoutCritical) currentValue: getTimeoutText(SettingsData.notificationTimeoutCritical)
options: timeoutOptions.map(opt => opt.text) options: timeoutOptions.map(opt => opt.text)
onValueChanged: value => { onValueChanged: value => {
+6 -5
View File
@@ -48,8 +48,9 @@ done
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "battery_charging_full" iconName: "battery_charging_full"
title: I18n.tr("Battery Status") title: I18n.tr("Status")
settingKey: "batteryStatusCard" settingKey: "batteryStatusCard"
tags: ["battery", "status", "charge", "health"]
Column { Column {
width: parent.width - Theme.spacingM * 2 width: parent.width - Theme.spacingM * 2
@@ -167,8 +168,9 @@ done
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "tune" iconName: "tune"
title: I18n.tr("Battery Protection") title: I18n.tr("Protection")
settingKey: "batteryProtection" settingKey: "batteryProtection"
tags: ["battery", "protection", "charge", "limit"]
SettingsSliderRow { SettingsSliderRow {
settingKey: "batteryChargeLimit" settingKey: "batteryChargeLimit"
@@ -230,8 +232,9 @@ done
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "notifications" iconName: "notifications"
title: I18n.tr("Battery Alerts") title: I18n.tr("Alerts")
settingKey: "batteryAlerts" settingKey: "batteryAlerts"
tags: ["battery", "alerts", "low", "warning"]
SettingsSliderRow { SettingsSliderRow {
settingKey: "batteryLowThreshold" settingKey: "batteryLowThreshold"
@@ -332,7 +335,6 @@ done
SettingsDropdownRow { SettingsDropdownRow {
settingKey: "acProfileName" settingKey: "acProfileName"
text: I18n.tr("Profile when Plugged In (AC)") text: I18n.tr("Profile when Plugged In (AC)")
description: I18n.tr("Power profile to use when AC power is connected.")
options: [I18n.tr("Don't Change"), Theme.getPowerProfileLabel(0), Theme.getPowerProfileLabel(1), Theme.getPowerProfileLabel(2)] options: [I18n.tr("Don't Change"), Theme.getPowerProfileLabel(0), Theme.getPowerProfileLabel(1), Theme.getPowerProfileLabel(2)]
currentValue: { currentValue: {
const val = SettingsData.acProfileName; const val = SettingsData.acProfileName;
@@ -350,7 +352,6 @@ done
SettingsDropdownRow { SettingsDropdownRow {
settingKey: "batteryProfileName" settingKey: "batteryProfileName"
text: I18n.tr("Profile when on Battery") text: I18n.tr("Profile when on Battery")
description: I18n.tr("Power profile to use when running on battery power.")
options: [I18n.tr("Don't Change"), Theme.getPowerProfileLabel(0), Theme.getPowerProfileLabel(1), Theme.getPowerProfileLabel(2)] options: [I18n.tr("Don't Change"), Theme.getPowerProfileLabel(0), Theme.getPowerProfileLabel(1), Theme.getPowerProfileLabel(2)]
currentValue: { currentValue: {
const val = SettingsData.batteryProfileName; const val = SettingsData.batteryProfileName;
+1 -2
View File
@@ -153,7 +153,7 @@ Item {
] ]
readonly property var entryActionKeys: ["copy", "paste", "pin", "edit", "delete"] readonly property var entryActionKeys: ["copy", "paste", "pin", "edit", "delete"]
readonly property var entryActionLabels: [I18n.tr("Copy"), I18n.tr("Paste"), I18n.tr("Pin"), I18n.tr("Edit"), I18n.tr("Delete")] readonly property var entryActionLabels: [I18n.tr("Copy"), I18n.tr("Paste"), I18n.tr("Pin", "pin item action"), I18n.tr("Edit"), I18n.tr("Delete")]
function getMaxHistoryText(value) { function getMaxHistoryText(value) {
if (value <= 0) if (value <= 0)
@@ -341,7 +341,6 @@ Item {
tags: ["clipboard", "entry", "size", "limit"] tags: ["clipboard", "entry", "size", "limit"]
settingKey: "maxEntrySize" settingKey: "maxEntrySize"
text: I18n.tr("Maximum Entry Size") text: I18n.tr("Maximum Entry Size")
description: I18n.tr("Maximum size per clipboard entry")
options: root.maxEntrySizeOptions.map(opt => opt.text) options: root.maxEntrySizeOptions.map(opt => opt.text)
Component.onCompleted: { Component.onCompleted: {
@@ -94,7 +94,7 @@ Item {
function fixLayoutInclude() { function fixLayoutInclude() {
if (readOnly) { if (readOnly) {
ToastService.showWarning(I18n.tr("Hyprland conf mode"), I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before editing layout settings."), "dms setup", "hyprland-migration"); ToastService.showWarning(I18n.tr("Hyprland conf mode"), I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before changing these settings."), "dms setup", "hyprland-migration");
return; return;
} }
const paths = getLayoutConfigPaths(); const paths = getLayoutConfigPaths();
@@ -208,7 +208,7 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
StyledText { StyledText {
text: { text: {
if (warningBox.showLegacy) if (warningBox.showLegacy)
return I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before editing layout settings."); return I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before changing these settings.");
if (warningBox.showSetup) if (warningBox.showSetup)
return I18n.tr("Click 'Setup' to create %1 and add include to your compositor config.").arg("dms/layout"); return I18n.tr("Click 'Setup' to create %1 and add include to your compositor config.").arg("dms/layout");
return ""; return "";
@@ -270,7 +270,7 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
SettingsCard { SettingsCard {
width: parent.width width: parent.width
tags: ["niri", "layout", "gaps", "radius", "window", "border"] tags: ["niri", "layout", "gaps", "radius", "window", "border"]
title: I18n.tr("Niri Layout Overrides") title: I18n.tr("%1 Layout Overrides").arg("niri")
settingKey: "niriLayout" settingKey: "niriLayout"
iconName: "layers" iconName: "layers"
visible: CompositorService.isNiri visible: CompositorService.isNiri
@@ -279,7 +279,7 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
tags: ["niri", "gaps", "override", "unmanaged"] tags: ["niri", "gaps", "override", "unmanaged"]
settingKey: "niriLayoutGapsMode" settingKey: "niriLayoutGapsMode"
text: I18n.tr("Gaps") text: I18n.tr("Gaps")
description: I18n.tr("Auto matches bar spacing; Off leaves gaps to your niri config") description: I18n.tr("Auto matches bar spacing; Off leaves gaps to your %1 config").arg("niri")
model: [I18n.tr("Auto"), I18n.tr("Custom"), I18n.tr("Off")] model: [I18n.tr("Auto"), I18n.tr("Custom"), I18n.tr("Off")]
currentIndex: { currentIndex: {
if (SettingsData.niriLayoutGapsOverride === -2) if (SettingsData.niriLayoutGapsOverride === -2)
@@ -399,7 +399,7 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
SettingsCard { SettingsCard {
width: parent.width width: parent.width
tags: ["hyprland", "layout", "gaps", "radius", "window", "border", "rounding"] tags: ["hyprland", "layout", "gaps", "radius", "window", "border", "rounding"]
title: I18n.tr("Hyprland Layout Overrides") title: I18n.tr("%1 Layout Overrides").arg("Hyprland")
settingKey: "hyprlandLayout" settingKey: "hyprlandLayout"
iconName: "crop_square" iconName: "crop_square"
visible: CompositorService.isHyprland visible: CompositorService.isHyprland
@@ -408,7 +408,7 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
tags: ["hyprland", "gaps", "override", "inner", "outer", "unmanaged"] tags: ["hyprland", "gaps", "override", "inner", "outer", "unmanaged"]
settingKey: "hyprlandLayoutGapsMode" settingKey: "hyprlandLayoutGapsMode"
text: I18n.tr("Gaps") text: I18n.tr("Gaps")
description: I18n.tr("Auto matches bar spacing; Off leaves gaps to your Hyprland config") description: I18n.tr("Auto matches bar spacing; Off leaves gaps to your %1 config").arg("Hyprland")
model: [I18n.tr("Auto"), I18n.tr("Custom"), I18n.tr("Off")] model: [I18n.tr("Auto"), I18n.tr("Custom"), I18n.tr("Off")]
currentIndex: { currentIndex: {
if (SettingsData.hyprlandLayoutGapsOverride === -2) if (SettingsData.hyprlandLayoutGapsOverride === -2)
@@ -492,7 +492,6 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
tags: ["hyprland", "border", "override"] tags: ["hyprland", "border", "override"]
settingKey: "hyprlandLayoutBorderSizeEnabled" settingKey: "hyprlandLayoutBorderSizeEnabled"
text: I18n.tr("Override Border Size") text: I18n.tr("Override Border Size")
description: I18n.tr("Use custom border size")
checked: SettingsData.hyprlandLayoutBorderSize >= 0 checked: SettingsData.hyprlandLayoutBorderSize >= 0
onToggled: checked => { onToggled: checked => {
if (checked) { if (checked) {
@@ -551,7 +550,7 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
SettingsCard { SettingsCard {
width: parent.width width: parent.width
tags: ["mangowc", "mango", "dwl", "layout", "gaps", "radius", "window", "border"] tags: ["mangowc", "mango", "dwl", "layout", "gaps", "radius", "window", "border"]
title: I18n.tr("MangoWC Layout Overrides") title: I18n.tr("%1 Layout Overrides").arg("MangoWC")
settingKey: "mangoLayout" settingKey: "mangoLayout"
iconName: "crop_square" iconName: "crop_square"
visible: CompositorService.isMango visible: CompositorService.isMango
@@ -560,7 +559,7 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
tags: ["mangowc", "mango", "gaps", "override", "inner", "outer", "unmanaged"] tags: ["mangowc", "mango", "gaps", "override", "inner", "outer", "unmanaged"]
settingKey: "mangoLayoutGapsMode" settingKey: "mangoLayoutGapsMode"
text: I18n.tr("Gaps") text: I18n.tr("Gaps")
description: I18n.tr("Auto matches bar spacing; Off leaves gaps to your MangoWC config") description: I18n.tr("Auto matches bar spacing; Off leaves gaps to your %1 config").arg("MangoWC")
model: [I18n.tr("Auto"), I18n.tr("Custom"), I18n.tr("Off")] model: [I18n.tr("Auto"), I18n.tr("Custom"), I18n.tr("Off")]
currentIndex: { currentIndex: {
if (SettingsData.mangoLayoutGapsOverride === -2) if (SettingsData.mangoLayoutGapsOverride === -2)
@@ -644,7 +643,6 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
tags: ["mangowc", "mango", "border", "override"] tags: ["mangowc", "mango", "border", "override"]
settingKey: "mangoLayoutBorderSizeEnabled" settingKey: "mangoLayoutBorderSizeEnabled"
text: I18n.tr("Override Border Size") text: I18n.tr("Override Border Size")
description: I18n.tr("Use custom border size")
checked: SettingsData.mangoLayoutBorderSize >= 0 checked: SettingsData.mangoLayoutBorderSize >= 0
onToggled: checked => { onToggled: checked => {
if (checked) { if (checked) {
+2 -7
View File
@@ -266,7 +266,7 @@ Item {
SettingsCard { SettingsCard {
iconName: "dashboard" iconName: "dashboard"
title: I18n.tr("Bar Configurations") title: I18n.tr("Configurations")
settingKey: "barConfigurations" settingKey: "barConfigurations"
visible: !dankBarTab.appearanceOnly visible: !dankBarTab.appearanceOnly
@@ -490,7 +490,7 @@ Item {
DankButtonGroup { DankButtonGroup {
id: positionButtonGroup id: positionButtonGroup
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
model: [I18n.tr("Top"), I18n.tr("Bottom"), I18n.tr("Left"), I18n.tr("Right")] model: [I18n.tr("Top", "screen edge position"), I18n.tr("Bottom", "screen edge position"), I18n.tr("Left", "screen edge position"), I18n.tr("Right", "screen edge position")]
currentIndex: { currentIndex: {
selectedBarId; selectedBarId;
const config = SettingsData.getBarConfig(selectedBarId); const config = SettingsData.getBarConfig(selectedBarId);
@@ -820,7 +820,6 @@ Item {
id: barTransparencySlider id: barTransparencySlider
visible: !SettingsData.frameEnabled visible: !SettingsData.frameEnabled
text: I18n.tr("Bar Opacity") text: I18n.tr("Bar Opacity")
description: I18n.tr("Controls opacity of the bar background")
value: (selectedBarConfig?.transparency ?? 1.0) * 100 value: (selectedBarConfig?.transparency ?? 1.0) * 100
minimum: 0 minimum: 0
maximum: 100 maximum: 100
@@ -843,7 +842,6 @@ Item {
SettingsSliderRow { SettingsSliderRow {
id: widgetTransparencySlider id: widgetTransparencySlider
text: I18n.tr("Widget Opacity") text: I18n.tr("Widget Opacity")
description: I18n.tr("Controls opacity of widget backgrounds")
value: (selectedBarConfig?.widgetTransparency ?? 1.0) * 100 value: (selectedBarConfig?.widgetTransparency ?? 1.0) * 100
minimum: 0 minimum: 0
maximum: 100 maximum: 100
@@ -1463,7 +1461,6 @@ Item {
SettingsSliderRow { SettingsSliderRow {
id: borderOpacitySlider id: borderOpacitySlider
text: I18n.tr("Opacity") text: I18n.tr("Opacity")
description: I18n.tr("Controls opacity of the border")
value: (selectedBarConfig?.borderOpacity ?? 1.0) * 100 value: (selectedBarConfig?.borderOpacity ?? 1.0) * 100
minimum: 0 minimum: 0
maximum: 100 maximum: 100
@@ -1558,7 +1555,6 @@ Item {
SettingsSliderRow { SettingsSliderRow {
id: widgetOutlineOpacitySlider id: widgetOutlineOpacitySlider
text: I18n.tr("Opacity") text: I18n.tr("Opacity")
description: I18n.tr("Controls opacity of the widget outline")
value: (selectedBarConfig?.widgetOutlineOpacity ?? 1.0) * 100 value: (selectedBarConfig?.widgetOutlineOpacity ?? 1.0) * 100
minimum: 0 minimum: 0
maximum: 100 maximum: 100
@@ -1667,7 +1663,6 @@ Item {
SettingsSliderRow { SettingsSliderRow {
visible: shadowCard.shadowActive visible: shadowCard.shadowActive
text: I18n.tr("Opacity") text: I18n.tr("Opacity")
description: I18n.tr("Controls opacity of the shadow layer")
minimum: 10 minimum: 10
maximum: 100 maximum: 100
unit: "%" unit: "%"
@@ -312,7 +312,6 @@ Item {
text: I18n.tr("Calendar", "Calendar") text: I18n.tr("Calendar", "Calendar")
category: root.appCategory.Calendar category: root.appCategory.Calendar
tags: ["calendar", "events"] tags: ["calendar", "events"]
description: I18n.tr("Manages calendar events", "Manages calendar events")
} }
} }
@@ -330,7 +329,6 @@ Item {
text: I18n.tr("PDF Reader", "PDF Reader") text: I18n.tr("PDF Reader", "PDF Reader")
category: root.appCategory.PDFReader category: root.appCategory.PDFReader
tags: ["pdf", "reader"] tags: ["pdf", "reader"]
description: I18n.tr("For reading PDF files", "For reading PDF files")
} }
} }
@@ -341,13 +339,11 @@ Item {
text: I18n.tr("Image Viewer", "Image Viewer") text: I18n.tr("Image Viewer", "Image Viewer")
category: root.appCategory.ImageViewer category: root.appCategory.ImageViewer
tags: ["image", "viewer"] tags: ["image", "viewer"]
description: I18n.tr("Opens image files", "Opens image files")
} }
AppSelector { AppSelector {
text: I18n.tr("Video Player", "Video Player") text: I18n.tr("Video Player", "Video Player")
category: root.appCategory.VideoPlayer category: root.appCategory.VideoPlayer
tags: ["video", "player"] tags: ["video", "player"]
description: I18n.tr("Plays video files", "Plays video files")
} }
AppSelector { AppSelector {
text: I18n.tr("Music Player", "Music Player") text: I18n.tr("Music Player", "Music Player")
@@ -1518,7 +1518,7 @@ Singleton {
} }
function showHyprlandReadOnlyWarning() { function showHyprlandReadOnlyWarning() {
ToastService.showWarning(I18n.tr("Hyprland conf mode"), I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before editing display settings."), "dms setup", "display-config"); ToastService.showWarning(I18n.tr("Hyprland conf mode"), I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before changing these settings."), "dms setup", "display-config");
} }
function buildOutputsMap() { function buildOutputsMap() {
@@ -2639,7 +2639,7 @@ Singleton {
function getTransformLabel(transform) { function getTransformLabel(transform) {
switch (transform) { switch (transform) {
case "Normal": case "Normal":
return I18n.tr("Normal"); return I18n.tr("Normal", "display rotation option", true);
case "90": case "90":
return I18n.tr("90°"); return I18n.tr("90°");
case "180": case "180":
@@ -2655,12 +2655,12 @@ Singleton {
case "Flipped270": case "Flipped270":
return I18n.tr("Flipped 270°"); return I18n.tr("Flipped 270°");
default: default:
return I18n.tr("Normal"); return I18n.tr("Normal", "display rotation option", true);
} }
} }
function getTransformValue(label) { function getTransformValue(label) {
if (label === I18n.tr("Normal")) if (label === I18n.tr("Normal", "display rotation option", true))
return "Normal"; return "Normal";
if (label === I18n.tr("90°")) if (label === I18n.tr("90°"))
return "90"; return "90";
@@ -60,9 +60,9 @@ StyledRect {
StyledText { StyledText {
text: { text: {
if (root.showLegacy) if (root.showLegacy)
return I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before editing display settings."); return I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before changing these settings.");
if (root.showSetup) if (root.showSetup)
return I18n.tr("Click 'Setup' to create the outputs config and add include to your compositor config."); return I18n.tr("Click 'Setup' to create %1 and add include to your compositor config.").arg("dms/outputs");
return ""; return "";
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -93,21 +93,21 @@ Column {
currentValue: { currentValue: {
if (!hotCornersData) if (!hotCornersData)
return I18n.tr("Inherit"); return I18n.tr("Inherit", "inherit from global setting");
if (hotCornersData.off) if (hotCornersData.off)
return I18n.tr("Off"); return I18n.tr("Off");
const corners = hotCornersData.corners || []; const corners = hotCornersData.corners || [];
if (corners.length === 0) if (corners.length === 0)
return I18n.tr("Inherit"); return I18n.tr("Inherit", "inherit from global setting");
if (corners.length === 4) if (corners.length === 4)
return I18n.tr("All"); return I18n.tr("All");
return I18n.tr("Select..."); return I18n.tr("Select...");
} }
options: [I18n.tr("Inherit"), I18n.tr("Off"), I18n.tr("All"), I18n.tr("Select...")] options: [I18n.tr("Inherit", "inherit from global setting"), I18n.tr("Off"), I18n.tr("All"), I18n.tr("Select...")]
onValueChanged: value => { onValueChanged: value => {
switch (value) { switch (value) {
case I18n.tr("Inherit"): case I18n.tr("Inherit", "inherit from global setting"):
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "hotCorners", null); DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "hotCorners", null);
break; break;
case I18n.tr("Off"): case I18n.tr("Off"):
@@ -232,7 +232,7 @@ Column {
DankTextField { DankTextField {
width: parent.width width: parent.width
height: 40 height: 40
placeholderText: I18n.tr("Inherit") placeholderText: I18n.tr("Inherit", "inherit from global setting")
enabled: !settingsColumn.isDisabled enabled: !settingsColumn.isDisabled
text: { text: {
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null); const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
@@ -270,7 +270,7 @@ Column {
DankTextField { DankTextField {
width: parent.width width: parent.width
height: 40 height: 40
placeholderText: I18n.tr("Inherit") placeholderText: I18n.tr("Inherit", "inherit from global setting")
enabled: !settingsColumn.isDisabled enabled: !settingsColumn.isDisabled
text: { text: {
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null); const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
@@ -321,7 +321,7 @@ Column {
DankTextField { DankTextField {
width: parent.width width: parent.width
height: 40 height: 40
placeholderText: I18n.tr("Inherit") placeholderText: I18n.tr("Inherit", "inherit from global setting")
enabled: !settingsColumn.isDisabled enabled: !settingsColumn.isDisabled
text: { text: {
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null); const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
@@ -308,7 +308,7 @@ StyledRect {
return DisplayConfigState.getTransformLabel(pendingTransform); return DisplayConfigState.getTransformLabel(pendingTransform);
return DisplayConfigState.getTransformLabel(root.outputData?.logical?.transform ?? "Normal"); return DisplayConfigState.getTransformLabel(root.outputData?.logical?.transform ?? "Normal");
} }
options: [I18n.tr("Normal"), I18n.tr("90°"), I18n.tr("180°"), I18n.tr("270°"), I18n.tr("Flipped"), I18n.tr("Flipped 90°"), I18n.tr("Flipped 180°"), I18n.tr("Flipped 270°")] options: [I18n.tr("Normal", "display rotation option", true), I18n.tr("90°"), I18n.tr("180°"), I18n.tr("270°"), I18n.tr("Flipped"), I18n.tr("Flipped 90°"), I18n.tr("Flipped 180°"), I18n.tr("Flipped 270°")]
onValueChanged: value => DisplayConfigState.setPendingChange(root.outputName, "transform", DisplayConfigState.getTransformValue(value)) onValueChanged: value => DisplayConfigState.setPendingChange(root.outputName, "transform", DisplayConfigState.getTransformValue(value))
} }
} }
@@ -399,7 +399,6 @@ Item {
DankToggle { DankToggle {
width: parent.width width: parent.width
text: I18n.tr("All displays") text: I18n.tr("All displays")
description: I18n.tr("Show on all connected displays")
checked: { checked: {
var prefs = root.getScreenPreferences(parent.componentId); var prefs = root.getScreenPreferences(parent.componentId);
return prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all"); return prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all");
@@ -420,7 +419,6 @@ Item {
DankToggle { DankToggle {
width: parent.width width: parent.width
text: I18n.tr("Focused Monitor Only") text: I18n.tr("Focused Monitor Only")
description: I18n.tr("Show notifications only on the currently focused monitor")
visible: parent.componentId === "notifications" visible: parent.componentId === "notifications"
checked: SettingsData.notificationFocusedMonitor checked: SettingsData.notificationFocusedMonitor
onToggled: checked => SettingsData.set("notificationFocusedMonitor", checked) onToggled: checked => SettingsData.set("notificationFocusedMonitor", checked)
+2 -4
View File
@@ -37,7 +37,7 @@ Item {
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "dock_to_bottom" iconName: "dock_to_bottom"
title: I18n.tr("Dock Visibility") title: I18n.tr("Visibility")
settingKey: "dockVisibility" settingKey: "dockVisibility"
SettingsToggleRow { SettingsToggleRow {
@@ -228,7 +228,6 @@ Item {
settingKey: "dockShowOverflowBadge" settingKey: "dockShowOverflowBadge"
tags: ["dock", "overflow", "badge", "count", "indicator"] tags: ["dock", "overflow", "badge", "count", "indicator"]
text: I18n.tr("Show Overflow Badge Count") text: I18n.tr("Show Overflow Badge Count")
description: I18n.tr("Displays count when overflow is active")
checked: SettingsData.dockShowOverflowBadge checked: SettingsData.dockShowOverflowBadge
onToggled: checked => SettingsData.set("dockShowOverflowBadge", checked) onToggled: checked => SettingsData.set("dockShowOverflowBadge", checked)
} }
@@ -461,7 +460,7 @@ Item {
if (!PopoutService.colorPickerModal) if (!PopoutService.colorPickerModal)
return; return;
PopoutService.colorPickerModal.selectedColor = SettingsData.dockLauncherLogoColorOverride; PopoutService.colorPickerModal.selectedColor = SettingsData.dockLauncherLogoColorOverride;
PopoutService.colorPickerModal.pickerTitle = I18n.tr("Choose Dock Launcher Logo Color"); PopoutService.colorPickerModal.pickerTitle = I18n.tr("Choose Launcher Logo Color");
PopoutService.colorPickerModal.onColorSelectedCallback = function (selectedColor) { PopoutService.colorPickerModal.onColorSelectedCallback = function (selectedColor) {
SettingsData.set("dockLauncherLogoColorOverride", selectedColor); SettingsData.set("dockLauncherLogoColorOverride", selectedColor);
}; };
@@ -683,7 +682,6 @@ Item {
SettingsButtonGroupRow { SettingsButtonGroupRow {
text: I18n.tr("Border Color") text: I18n.tr("Border Color")
description: I18n.tr("Choose the border accent color")
visible: SettingsData.dockBorderEnabled visible: SettingsData.dockBorderEnabled
model: [I18n.tr("Surface", "color option"), I18n.tr("Secondary", "color option"), I18n.tr("Primary", "color option")] model: [I18n.tr("Surface", "color option"), I18n.tr("Secondary", "color option"), I18n.tr("Primary", "color option")]
buttonPadding: Theme.spacingS buttonPadding: Theme.spacingS
+1 -1
View File
@@ -31,7 +31,7 @@ Item {
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "frame_source" iconName: "frame_source"
title: I18n.tr("Frame") title: I18n.tr("General")
settingKey: "frameEnabled" settingKey: "frameEnabled"
SettingsToggleRow { SettingsToggleRow {
@@ -122,7 +122,6 @@ Item {
tags: ["gamma", "day", "temperature", "kelvin", "color"] tags: ["gamma", "day", "temperature", "kelvin", "color"]
width: parent.width - parent.leftPadding - parent.rightPadding width: parent.width - parent.leftPadding - parent.rightPadding
text: I18n.tr("Day Temperature") text: I18n.tr("Day Temperature")
description: I18n.tr("Color temperature for day time")
minimum: SessionData.nightModeTemperature minimum: SessionData.nightModeTemperature
maximum: 10000 maximum: 10000
step: 100 step: 100
+6 -6
View File
@@ -305,7 +305,7 @@ Item {
onExited: exitCode => { onExited: exitCode => {
root.greeterSyncRunning = false; root.greeterSyncRunning = false;
if (exitCode === 0) { if (exitCode === 0) {
var launched = root.greeterTerminalFallbackFromPrecheck ? I18n.tr("Terminal opened. Complete sync authentication there; it will close automatically when done.") : I18n.tr("Terminal fallback opened. Complete sync there; it will close automatically when done."); var launched = root.greeterTerminalFallbackFromPrecheck ? I18n.tr("Terminal opened. Complete authentication there; it will close automatically when done.") : I18n.tr("Terminal fallback opened. Complete authentication there; it will close automatically when done.");
root.greeterStatusText = root.greeterStatusText ? root.greeterStatusText + "\n\n" + launched : launched; root.greeterStatusText = root.greeterStatusText ? root.greeterStatusText + "\n\n" + launched : launched;
SettingsData.clearGreeterSyncPending(); SettingsData.clearGreeterSyncPending();
return; return;
@@ -396,7 +396,7 @@ Item {
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "info" iconName: "info"
title: I18n.tr("Greeter Status") title: I18n.tr("Status")
settingKey: "greeterStatus" settingKey: "greeterStatus"
StyledText { StyledText {
@@ -470,7 +470,7 @@ Item {
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "fingerprint" iconName: "fingerprint"
title: I18n.tr("Login Authentication") title: I18n.tr("Authentication")
settingKey: "greeterAuth" settingKey: "greeterAuth"
StyledText { StyledText {
@@ -517,7 +517,7 @@ Item {
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "palette" iconName: "palette"
title: I18n.tr("Greeter Appearance") title: I18n.tr("Appearance")
settingKey: "greeterAppearance" settingKey: "greeterAppearance"
StyledText { StyledText {
@@ -558,7 +558,7 @@ Item {
currentValue: { currentValue: {
var current = (SettingsData.greeterLockDateFormat !== undefined && SettingsData.greeterLockDateFormat !== "") ? SettingsData.greeterLockDateFormat : SettingsData.lockDateFormat || ""; var current = (SettingsData.greeterLockDateFormat !== undefined && SettingsData.greeterLockDateFormat !== "") ? SettingsData.greeterLockDateFormat : SettingsData.lockDateFormat || "";
var match = root._lockDateFormatPresets.find(p => p.format === current); var match = root._lockDateFormatPresets.find(p => p.format === current);
return match ? match.label : (current ? I18n.tr("Custom: ") + current : root._lockDateFormatPresets[0].label); return match ? match.label : (current ? I18n.tr("Custom") + ": " + current : root._lockDateFormatPresets[0].label);
} }
onValueChanged: value => { onValueChanged: value => {
var preset = root._lockDateFormatPresets.find(p => p.label === value); var preset = root._lockDateFormatPresets.find(p => p.label === value);
@@ -601,7 +601,7 @@ Item {
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "history" iconName: "history"
title: I18n.tr("Greeter Behavior") title: I18n.tr("Behavior")
settingKey: "greeterBehavior" settingKey: "greeterBehavior"
StyledText { StyledText {
+2 -2
View File
@@ -117,7 +117,7 @@ Item {
function confirmResetBind(key, remainingKey) { function confirmResetBind(key, remainingKey) {
removeBindConfirm.showWithOptions({ removeBindConfirm.showWithOptions({
title: I18n.tr("Reset to Default?"), title: I18n.tr("Reset to default"),
message: I18n.tr("Drop your override for %1 so the DMS default action re-applies?").arg(key), message: I18n.tr("Drop your override for %1 so the DMS default action re-applies?").arg(key),
confirmText: I18n.tr("Reset"), confirmText: I18n.tr("Reset"),
confirmColor: Theme.primary, confirmColor: Theme.primary,
@@ -397,7 +397,7 @@ Item {
StyledText { StyledText {
text: { text: {
if (warningBox.showLegacy) if (warningBox.showLegacy)
return I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before editing shortcuts in Settings."); return I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before changing these settings.");
if (warningBox.showSetup) if (warningBox.showSetup)
return I18n.tr("Click 'Setup' to create %1 and add include to your compositor config.").arg("dms/binds"); return I18n.tr("Click 'Setup' to create %1 and add include to your compositor config.").arg("dms/binds");
if (warningBox.showWarning) { if (warningBox.showWarning) {
+2 -3
View File
@@ -139,7 +139,7 @@ Item {
} }
StyledText { StyledText {
text: !root.keybindsAvailable ? I18n.tr("Bind the spotlight IPC action in your compositor config.") : SettingsData.connectedFrameModeActive ? I18n.tr("Opens the connected launcher in Connected Frame Mode.") : I18n.tr("Follows the default launcher choice selected above.") text: !root.keybindsAvailable ? I18n.tr("Bind the %1 IPC action in your compositor config.").arg("spotlight") : SettingsData.connectedFrameModeActive ? I18n.tr("Opens the connected launcher in Connected Frame Mode.") : I18n.tr("Follows the default launcher choice selected above.")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
width: parent.width width: parent.width
@@ -173,7 +173,6 @@ Item {
settingKey: "launcherUseOverlayLayer" settingKey: "launcherUseOverlayLayer"
tags: ["launcher", "fullscreen", "overlay", "layer"] tags: ["launcher", "fullscreen", "overlay", "layer"]
text: I18n.tr("Use Overlay Layer", "launcher layer toggle: use Wayland overlay layer") text: I18n.tr("Use Overlay Layer", "launcher layer toggle: use Wayland overlay layer")
description: I18n.tr("Use the overlay layer when opening the launcher")
checked: SettingsData.launcherUseOverlayLayer checked: SettingsData.launcherUseOverlayLayer
onToggled: checked => SettingsData.set("launcherUseOverlayLayer", checked) onToggled: checked => SettingsData.set("launcherUseOverlayLayer", checked)
} }
@@ -233,7 +232,7 @@ Item {
} }
StyledText { StyledText {
text: !root.keybindsAvailable ? I18n.tr("Bind the spotlight-bar IPC action in your compositor config.") : I18n.tr("Uses the spotlight-bar IPC action and always opens the minimal bar.") text: !root.keybindsAvailable ? I18n.tr("Bind the %1 IPC action in your compositor config.").arg("spotlight-bar") : I18n.tr("Uses the spotlight-bar IPC action and always opens the minimal bar.")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
width: parent.width width: parent.width
+1 -1
View File
@@ -45,7 +45,7 @@ Item {
SettingsCard { SettingsCard {
tab: "locale" tab: "locale"
tags: ["locale", "language", "country"] tags: ["locale", "language", "country"]
title: I18n.tr("Locale Settings") title: I18n.tr("General")
iconName: "language" iconName: "language"
SettingsDropdownRow { SettingsDropdownRow {
@@ -266,7 +266,7 @@ Item {
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "lock" iconName: "lock"
title: I18n.tr("Lock Screen layout") title: I18n.tr("Layout")
settingKey: "lockLayout" settingKey: "lockLayout"
SettingsToggleRow { SettingsToggleRow {
@@ -345,7 +345,7 @@ Item {
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "palette" iconName: "palette"
title: I18n.tr("Lock Screen Appearance") title: I18n.tr("Appearance")
settingKey: "lockAppearance" settingKey: "lockAppearance"
StyledText { StyledText {
@@ -476,7 +476,6 @@ Item {
settingKey: "lockPamExternallyManaged" settingKey: "lockPamExternallyManaged"
tags: ["lock", "screen", "pam", "managed", "external", "authentication", "policy"] tags: ["lock", "screen", "pam", "managed", "external", "authentication", "policy"]
text: I18n.tr("Use system PAM authentication", "system PAM policy toggle") text: I18n.tr("Use system PAM authentication", "system PAM policy toggle")
description: I18n.tr("System PAM sets the authentication policy.", "system PAM policy toggle")
checked: SettingsData.lockPamExternallyManaged checked: SettingsData.lockPamExternallyManaged
onToggled: checked => SettingsData.set("lockPamExternallyManaged", checked) onToggled: checked => SettingsData.set("lockPamExternallyManaged", checked)
} }
@@ -582,7 +581,7 @@ Item {
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "lock" iconName: "lock"
title: I18n.tr("Lock Screen behaviour") title: I18n.tr("Behavior")
settingKey: "lockBehavior" settingKey: "lockBehavior"
StyledText { StyledText {
@@ -721,7 +720,7 @@ Item {
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "monitor" iconName: "monitor"
title: I18n.tr("Lock Screen Display") title: I18n.tr("Display Assignment")
settingKey: "lockDisplay" settingKey: "lockDisplay"
StyledText { StyledText {
@@ -34,7 +34,7 @@ Item {
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "music_note" iconName: "music_note"
title: I18n.tr("Media Player Settings") title: I18n.tr("General")
settingKey: "mediaPlayer" settingKey: "mediaPlayer"
SettingsToggleRow { SettingsToggleRow {
@@ -146,7 +146,7 @@ Item {
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "do_not_disturb_on" iconName: "do_not_disturb_on"
title: I18n.tr("Excluded Media Players") title: I18n.tr("Excluded Players")
settingKey: "mediaExcludePlayers" settingKey: "mediaExcludePlayers"
tags: ["media", "music", "exclude", "ignore", "player", "mpris"] tags: ["media", "music", "exclude", "ignore", "player", "mpris"]
+1 -2
View File
@@ -25,7 +25,7 @@ Item {
SettingsCard { SettingsCard {
tab: "mux" tab: "mux"
tags: ["mux", "multiplexer", "tmux", "zellij", "type"] tags: ["mux", "multiplexer", "tmux", "zellij", "type"]
title: I18n.tr("Multiplexer") title: I18n.tr("General")
iconName: "terminal" iconName: "terminal"
SettingsDropdownRow { SettingsDropdownRow {
@@ -33,7 +33,6 @@ Item {
tags: ["mux", "multiplexer", "tmux", "zellij", "type", "backend"] tags: ["mux", "multiplexer", "tmux", "zellij", "type", "backend"]
settingKey: "muxType" settingKey: "muxType"
text: I18n.tr("Multiplexer Type") text: I18n.tr("Multiplexer Type")
description: I18n.tr("Terminal multiplexer backend to use")
options: root.muxTypeOptions options: root.muxTypeOptions
currentValue: SettingsData.muxType currentValue: SettingsData.muxType
onValueChanged: value => SettingsData.set("muxType", value) onValueChanged: value => SettingsData.set("muxType", value)
@@ -37,7 +37,7 @@ Item {
SettingsCard { SettingsCard {
id: root id: root
title: I18n.tr("Network Status") title: I18n.tr("Status")
iconName: "lan" iconName: "lan"
settingKey: "networkStatus" settingKey: "networkStatus"
tags: ["status", "network", "connectivity", "internet"] tags: ["status", "network", "connectivity", "internet"]
@@ -128,7 +128,7 @@ Item {
} }
StyledText { StyledText {
text: I18n.tr("Primary") text: I18n.tr("Primary", "primary network connection label", true)
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
visible: NetworkService.primaryConnection.length > 0 visible: NetworkService.primaryConnection.length > 0
@@ -318,7 +318,7 @@ Item {
visible: NetworkService.wifiConnected visible: NetworkService.wifiConnected
StyledText { StyledText {
text: I18n.tr("Signal:") text: I18n.tr("Signal") + ":"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
width: 100 width: 100
@@ -563,7 +563,7 @@ Item {
spacing: Theme.spacingXS spacing: Theme.spacingXS
StyledText { StyledText {
text: isConnecting ? I18n.tr("Connecting...") : (isConnected ? I18n.tr("Connected") : (modelData.secured ? I18n.tr("Secured") : I18n.tr("Open"))) text: isConnecting ? I18n.tr("Connecting...") : (isConnected ? I18n.tr("Connected") : (modelData.secured ? I18n.tr("Secured") : I18n.tr("Open", "network security type", true)))
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: isConnecting ? Theme.warning : (isConnected ? Theme.primary : Theme.surfaceVariantText) color: isConnecting ? Theme.warning : (isConnected ? Theme.primary : Theme.surfaceVariantText)
} }
@@ -770,7 +770,7 @@ Item {
}); });
fields.push({ fields.push({
label: I18n.tr("Security"), label: I18n.tr("Security"),
value: net.secured ? (net.enterprise ? I18n.tr("Enterprise") : I18n.tr("WPA/WPA2")) : I18n.tr("Open") value: net.secured ? (net.enterprise ? I18n.tr("Enterprise") : I18n.tr("WPA/WPA2")) : I18n.tr("Open", "network security type", true)
}); });
return fields; return fields;
@@ -966,7 +966,7 @@ Item {
text: { text: {
if (isConnecting) if (isConnecting)
return I18n.tr("Connecting..."); return I18n.tr("Connecting...");
const parts = [isConnected ? I18n.tr("Connected") : (modelData.secured ? I18n.tr("Secured") : I18n.tr("Open"))]; const parts = [isConnected ? I18n.tr("Connected") : (modelData.secured ? I18n.tr("Secured") : I18n.tr("Open", "network security type", true))];
parts.push(isOutOfRange ? I18n.tr("Unavailable") : (modelData.signal || 0) + "%"); parts.push(isOutOfRange ? I18n.tr("Unavailable") : (modelData.signal || 0) + "%");
if (modelData.hidden || false) if (modelData.hidden || false)
parts.push(I18n.tr("Hidden")); parts.push(I18n.tr("Hidden"));
@@ -1131,7 +1131,7 @@ Item {
}); });
fields.push({ fields.push({
label: I18n.tr("Security"), label: I18n.tr("Security"),
value: net.secured ? (net.enterprise ? I18n.tr("Enterprise") : I18n.tr("WPA/WPA2")) : I18n.tr("Open") value: net.secured ? (net.enterprise ? I18n.tr("Enterprise") : I18n.tr("WPA/WPA2")) : I18n.tr("Open", "network security type", true)
}); });
return fields; return fields;
@@ -203,7 +203,7 @@ Item {
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "notifications" iconName: "notifications"
title: I18n.tr("Notification Popups") title: I18n.tr("Popups")
settingKey: "notificationPopups" settingKey: "notificationPopups"
// Font size selectors for summary and body // Font size selectors for summary and body
@@ -211,7 +211,6 @@ Item {
settingKey: "notificationSummaryFontSize" settingKey: "notificationSummaryFontSize"
tags: ["notification", "font", "summary", "size"] tags: ["notification", "font", "summary", "size"]
text: I18n.tr("Summary Font Size") text: I18n.tr("Summary Font Size")
description: I18n.tr("Set the font size for notification summary text")
options: [I18n.tr("Unset"), "10", "12", "14", "16", "18"] options: [I18n.tr("Unset"), "10", "12", "14", "16", "18"]
currentValue: (SettingsData.notificationSummaryFontSize || I18n.tr("Unset")).toString() currentValue: (SettingsData.notificationSummaryFontSize || I18n.tr("Unset")).toString()
onValueChanged: value => { onValueChanged: value => {
@@ -340,7 +339,6 @@ Item {
settingKey: "notificationFocusedMonitor" settingKey: "notificationFocusedMonitor"
tags: ["notification", "popup", "focused", "monitor", "display", "screen", "active"] tags: ["notification", "popup", "focused", "monitor", "display", "screen", "active"]
text: I18n.tr("Focused Monitor Only") text: I18n.tr("Focused Monitor Only")
description: I18n.tr("Show notification popups only on the currently focused monitor")
checked: SettingsData.notificationFocusedMonitor checked: SettingsData.notificationFocusedMonitor
onToggled: checked => SettingsData.set("notificationFocusedMonitor", checked) onToggled: checked => SettingsData.set("notificationFocusedMonitor", checked)
} }
@@ -445,7 +443,7 @@ Item {
id: notificationRulesCard id: notificationRulesCard
width: parent.width width: parent.width
iconName: "rule_settings" iconName: "rule_settings"
title: I18n.tr("Notification Rules") title: I18n.tr("Rules")
settingKey: "notificationRules" settingKey: "notificationRules"
tags: ["notification", "rules", "mute", "ignore", "priority", "regex", "history"] tags: ["notification", "rules", "mute", "ignore", "priority", "regex", "history"]
collapsible: true collapsible: true
@@ -510,7 +508,7 @@ Item {
StyledText { StyledText {
id: ruleLabel id: ruleLabel
text: I18n.tr("Rule") + " " + (index + 1) text: I18n.tr("Rule %1").arg(index + 1)
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -785,7 +783,7 @@ Item {
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "timer" iconName: "timer"
title: I18n.tr("Notification Timeouts") title: I18n.tr("Timeouts")
settingKey: "notificationTimeouts" settingKey: "notificationTimeouts"
collapsible: true collapsible: true
expanded: false expanded: false
@@ -794,7 +792,6 @@ Item {
settingKey: "notificationTimeoutLow" settingKey: "notificationTimeoutLow"
tags: ["notification", "timeout", "low", "priority", "duration"] tags: ["notification", "timeout", "low", "priority", "duration"]
text: I18n.tr("Low Priority") text: I18n.tr("Low Priority")
description: I18n.tr("Timeout for low priority notifications")
currentValue: root.getTimeoutText(SettingsData.notificationTimeoutLow) currentValue: root.getTimeoutText(SettingsData.notificationTimeoutLow)
options: root.timeoutOptions.map(opt => opt.text) options: root.timeoutOptions.map(opt => opt.text)
onValueChanged: value => { onValueChanged: value => {
@@ -811,7 +808,6 @@ Item {
settingKey: "notificationTimeoutNormal" settingKey: "notificationTimeoutNormal"
tags: ["notification", "timeout", "normal", "priority", "duration"] tags: ["notification", "timeout", "normal", "priority", "duration"]
text: I18n.tr("Normal Priority") text: I18n.tr("Normal Priority")
description: I18n.tr("Timeout for normal priority notifications")
currentValue: root.getTimeoutText(SettingsData.notificationTimeoutNormal) currentValue: root.getTimeoutText(SettingsData.notificationTimeoutNormal)
options: root.timeoutOptions.map(opt => opt.text) options: root.timeoutOptions.map(opt => opt.text)
onValueChanged: value => { onValueChanged: value => {
@@ -828,7 +824,6 @@ Item {
settingKey: "notificationTimeoutCritical" settingKey: "notificationTimeoutCritical"
tags: ["notification", "timeout", "critical", "priority", "duration"] tags: ["notification", "timeout", "critical", "priority", "duration"]
text: I18n.tr("Critical Priority") text: I18n.tr("Critical Priority")
description: I18n.tr("Timeout for critical priority notifications")
currentValue: root.getTimeoutText(SettingsData.notificationTimeoutCritical) currentValue: root.getTimeoutText(SettingsData.notificationTimeoutCritical)
options: root.timeoutOptions.map(opt => opt.text) options: root.timeoutOptions.map(opt => opt.text)
onValueChanged: value => { onValueChanged: value => {
-9
View File
@@ -91,7 +91,6 @@ Item {
SettingsToggleRow { SettingsToggleRow {
settingKey: "osdVolumeEnabled" settingKey: "osdVolumeEnabled"
text: I18n.tr("Volume") text: I18n.tr("Volume")
description: I18n.tr("Show on-screen display when volume changes")
checked: SettingsData.osdVolumeEnabled checked: SettingsData.osdVolumeEnabled
onToggled: checked => SettingsData.set("osdVolumeEnabled", checked) onToggled: checked => SettingsData.set("osdVolumeEnabled", checked)
} }
@@ -99,7 +98,6 @@ Item {
SettingsToggleRow { SettingsToggleRow {
settingKey: "osdMediaVolumeEnabled" settingKey: "osdMediaVolumeEnabled"
text: I18n.tr("Media Volume") text: I18n.tr("Media Volume")
description: I18n.tr("Show on-screen display when media player volume changes")
checked: SettingsData.osdMediaVolumeEnabled checked: SettingsData.osdMediaVolumeEnabled
onToggled: checked => SettingsData.set("osdMediaVolumeEnabled", checked) onToggled: checked => SettingsData.set("osdMediaVolumeEnabled", checked)
} }
@@ -107,7 +105,6 @@ Item {
SettingsToggleRow { SettingsToggleRow {
settingKey: "osdMediaPlaybackEnabled" settingKey: "osdMediaPlaybackEnabled"
text: I18n.tr("Media Playback") text: I18n.tr("Media Playback")
description: I18n.tr("Show on-screen display when media player status changes")
checked: SettingsData.osdMediaPlaybackEnabled checked: SettingsData.osdMediaPlaybackEnabled
onToggled: checked => SettingsData.set("osdMediaPlaybackEnabled", checked) onToggled: checked => SettingsData.set("osdMediaPlaybackEnabled", checked)
} }
@@ -115,7 +112,6 @@ Item {
SettingsToggleRow { SettingsToggleRow {
settingKey: "osdBrightnessEnabled" settingKey: "osdBrightnessEnabled"
text: I18n.tr("Brightness") text: I18n.tr("Brightness")
description: I18n.tr("Show on-screen display when brightness changes")
checked: SettingsData.osdBrightnessEnabled checked: SettingsData.osdBrightnessEnabled
onToggled: checked => SettingsData.set("osdBrightnessEnabled", checked) onToggled: checked => SettingsData.set("osdBrightnessEnabled", checked)
} }
@@ -123,7 +119,6 @@ Item {
SettingsToggleRow { SettingsToggleRow {
settingKey: "osdIdleInhibitorEnabled" settingKey: "osdIdleInhibitorEnabled"
text: I18n.tr("Idle Inhibitor") text: I18n.tr("Idle Inhibitor")
description: I18n.tr("Show on-screen display when idle inhibitor state changes")
checked: SettingsData.osdIdleInhibitorEnabled checked: SettingsData.osdIdleInhibitorEnabled
onToggled: checked => SettingsData.set("osdIdleInhibitorEnabled", checked) onToggled: checked => SettingsData.set("osdIdleInhibitorEnabled", checked)
} }
@@ -131,7 +126,6 @@ Item {
SettingsToggleRow { SettingsToggleRow {
settingKey: "osdMicMuteEnabled" settingKey: "osdMicMuteEnabled"
text: I18n.tr("Microphone Mute") text: I18n.tr("Microphone Mute")
description: I18n.tr("Show on-screen display when microphone is muted/unmuted")
checked: SettingsData.osdMicMuteEnabled checked: SettingsData.osdMicMuteEnabled
onToggled: checked => SettingsData.set("osdMicMuteEnabled", checked) onToggled: checked => SettingsData.set("osdMicMuteEnabled", checked)
} }
@@ -139,7 +133,6 @@ Item {
SettingsToggleRow { SettingsToggleRow {
settingKey: "osdCapsLockEnabled" settingKey: "osdCapsLockEnabled"
text: I18n.tr("Caps Lock") text: I18n.tr("Caps Lock")
description: I18n.tr("Show on-screen display when caps lock state changes")
checked: SettingsData.osdCapsLockEnabled checked: SettingsData.osdCapsLockEnabled
onToggled: checked => SettingsData.set("osdCapsLockEnabled", checked) onToggled: checked => SettingsData.set("osdCapsLockEnabled", checked)
} }
@@ -147,7 +140,6 @@ Item {
SettingsToggleRow { SettingsToggleRow {
settingKey: "osdPowerProfileEnabled" settingKey: "osdPowerProfileEnabled"
text: I18n.tr("Power Profile") text: I18n.tr("Power Profile")
description: I18n.tr("Show on-screen display when power profile changes")
checked: SettingsData.osdPowerProfileEnabled checked: SettingsData.osdPowerProfileEnabled
onToggled: checked => SettingsData.set("osdPowerProfileEnabled", checked) onToggled: checked => SettingsData.set("osdPowerProfileEnabled", checked)
} }
@@ -155,7 +147,6 @@ Item {
SettingsToggleRow { SettingsToggleRow {
settingKey: "osdAudioOutputEnabled" settingKey: "osdAudioOutputEnabled"
text: I18n.tr("Audio Output Switch") text: I18n.tr("Audio Output Switch")
description: I18n.tr("Show on-screen display when cycling audio output devices")
checked: SettingsData.osdAudioOutputEnabled checked: SettingsData.osdAudioOutputEnabled
onToggled: checked => SettingsData.set("osdAudioOutputEnabled", checked) onToggled: checked => SettingsData.set("osdAudioOutputEnabled", checked)
} }
@@ -46,7 +46,8 @@ StyledRect {
} }
function updateSingle(plugin) { function updateSingle(plugin) {
if (isUpdating) return; if (isUpdating)
return;
isUpdating = true; isUpdating = true;
currentUpdatingPlugin = plugin.name; currentUpdatingPlugin = plugin.name;
@@ -68,7 +69,8 @@ StyledRect {
} }
function updateAll() { function updateAll() {
if (isUpdating) return; if (isUpdating)
return;
isUpdating = true; isUpdating = true;
var list = updatesList.slice(); var list = updatesList.slice();
@@ -149,7 +151,9 @@ StyledRect {
clip: true clip: true
Behavior on height { Behavior on height {
NumberAnimation { duration: Theme.shortDuration } NumberAnimation {
duration: Theme.shortDuration
}
} }
Row { Row {
@@ -220,7 +224,7 @@ StyledRect {
} }
StyledText { StyledText {
text: modelData.author ? I18n.tr("By %1").arg(modelData.author) : "" text: modelData.author ? I18n.tr("by %1", "author attribution").arg(modelData.author) : ""
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
elide: Text.ElideRight elide: Text.ElideRight
+3 -2
View File
@@ -21,7 +21,8 @@ FocusScope {
property var filteredPlugins: [] property var filteredPlugins: []
readonly property var pluginsWithUpdates: { readonly property var pluginsWithUpdates: {
if (!DMSService.installedPlugins) return []; if (!DMSService.installedPlugins)
return [];
return DMSService.installedPlugins.filter(p => p.hasUpdate === true); return DMSService.installedPlugins.filter(p => p.hasUpdate === true);
} }
@@ -438,7 +439,7 @@ FocusScope {
StyledText { StyledText {
width: parent.width width: parent.width
text: I18n.tr("No plugins found.") + "\n" + I18n.tr("Place plugins in %1").arg(PluginService.pluginDirectory) text: I18n.tr("No plugins found", "empty plugin list") + "\n" + I18n.tr("Place plugins in %1").arg(PluginService.pluginDirectory)
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
@@ -71,7 +71,6 @@ Item {
tags: ["sound", "enable", "system"] tags: ["sound", "enable", "system"]
settingKey: "soundsEnabled" settingKey: "soundsEnabled"
text: I18n.tr("Enable System Sounds") text: I18n.tr("Enable System Sounds")
description: I18n.tr("Play sounds for system events")
checked: SettingsData.soundsEnabled checked: SettingsData.soundsEnabled
onToggled: checked => SettingsData.set("soundsEnabled", checked) onToggled: checked => SettingsData.set("soundsEnabled", checked)
} }
@@ -106,7 +105,6 @@ Item {
visible: SettingsData.useSystemSoundTheme && AudioService.availableSoundThemes.length > 0 visible: SettingsData.useSystemSoundTheme && AudioService.availableSoundThemes.length > 0
enabled: SettingsData.useSystemSoundTheme && AudioService.availableSoundThemes.length > 0 enabled: SettingsData.useSystemSoundTheme && AudioService.availableSoundThemes.length > 0
text: I18n.tr("Sound Theme") text: I18n.tr("Sound Theme")
description: I18n.tr("Select system sound theme")
options: AudioService.availableSoundThemes options: AudioService.availableSoundThemes
currentValue: { currentValue: {
const theme = AudioService.currentSoundTheme; const theme = AudioService.currentSoundTheme;
@@ -143,7 +143,7 @@ Item {
function fixCursorInclude() { function fixCursorInclude() {
if (cursorReadOnly) { if (cursorReadOnly) {
ToastService.showWarning(I18n.tr("Hyprland conf mode"), I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before editing cursor settings."), "dms setup", "hyprland-migration"); ToastService.showWarning(I18n.tr("Hyprland conf mode"), I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before changing these settings."), "dms setup", "hyprland-migration");
return; return;
} }
const paths = getCursorConfigPaths(); const paths = getCursorConfigPaths();
@@ -1626,7 +1626,6 @@ Item {
tags: ["widget", "background", "color", "surface", "material"] tags: ["widget", "background", "color", "surface", "material"]
settingKey: "widgetBackgroundColor" settingKey: "widgetBackgroundColor"
text: I18n.tr("Widget Background Color") text: I18n.tr("Widget Background Color")
description: I18n.tr("Choose the background color for widgets")
dropdownWidth: 220 dropdownWidth: 220
options: themeColorsTab.widgetBackgroundOptions options: themeColorsTab.widgetBackgroundOptions
currentMode: SettingsData.widgetBackgroundColor currentMode: SettingsData.widgetBackgroundColor
@@ -1943,7 +1942,6 @@ Item {
tags: ["elevation", "shadow", "opacity", "transparency", "m3"] tags: ["elevation", "shadow", "opacity", "transparency", "m3"]
settingKey: "m3ElevationOpacity" settingKey: "m3ElevationOpacity"
text: I18n.tr("Shadow Opacity") text: I18n.tr("Shadow Opacity")
description: I18n.tr("Controls the opacity of the shadow")
value: SettingsData.m3ElevationOpacity ?? 30 value: SettingsData.m3ElevationOpacity ?? 30
minimum: 0 minimum: 0
maximum: 100 maximum: 100
@@ -2220,7 +2218,7 @@ Item {
StyledText { StyledText {
text: { text: {
if (cursorWarningBox.showLegacy) if (cursorWarningBox.showLegacy)
return I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before editing cursor settings."); return I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before changing these settings.");
if (cursorWarningBox.showSetup) if (cursorWarningBox.showSetup)
return I18n.tr("Click 'Setup' to create %1 and add include to your compositor config.").arg("dms/cursor"); return I18n.tr("Click 'Setup' to create %1 and add include to your compositor config.").arg("dms/cursor");
return ""; return "";
@@ -84,7 +84,6 @@ Item {
tags: ["time", "seconds", "clock"] tags: ["time", "seconds", "clock"]
settingKey: "showSeconds" settingKey: "showSeconds"
text: I18n.tr("Show Seconds") text: I18n.tr("Show Seconds")
description: I18n.tr("Display seconds in the clock")
checked: SettingsData.showSeconds checked: SettingsData.showSeconds
onToggled: checked => SettingsData.set("showSeconds", checked) onToggled: checked => SettingsData.set("showSeconds", checked)
} }
@@ -113,7 +112,6 @@ Item {
tags: ["show", "week"] tags: ["show", "week"]
settingKey: "showWeekNumber" settingKey: "showWeekNumber"
text: I18n.tr("Show Week Number") text: I18n.tr("Show Week Number")
description: I18n.tr("Show week number in the calendar")
checked: SettingsData.showWeekNumber checked: SettingsData.showWeekNumber
onToggled: checked => SettingsData.set("showWeekNumber", checked) onToggled: checked => SettingsData.set("showWeekNumber", checked)
} }
@@ -218,7 +216,7 @@ Item {
} }
]; ];
const match = presets.find(p => p.format === SettingsData.clockDateFormat); const match = presets.find(p => p.format === SettingsData.clockDateFormat);
return match ? match.label : I18n.tr("Custom: ") + SettingsData.clockDateFormat; return match ? match.label : I18n.tr("Custom") + ": " + SettingsData.clockDateFormat;
} }
onValueChanged: value => { onValueChanged: value => {
const formatMap = {}; const formatMap = {};
@@ -305,7 +303,7 @@ Item {
} }
]; ];
const match = presets.find(p => p.format === SettingsData.lockDateFormat); const match = presets.find(p => p.format === SettingsData.lockDateFormat);
return match ? match.label : I18n.tr("Custom: ") + SettingsData.lockDateFormat; return match ? match.label : I18n.tr("Custom") + ": " + SettingsData.lockDateFormat;
} }
onValueChanged: value => { onValueChanged: value => {
const formatMap = {}; const formatMap = {};
@@ -455,7 +455,7 @@ Item {
buttonPadding: parent.width < 480 ? Theme.spacingXS : Theme.spacingS buttonPadding: parent.width < 480 ? Theme.spacingXS : Theme.spacingS
minButtonWidth: parent.width < 480 ? 40 : 56 minButtonWidth: parent.width < 480 ? 40 : 56
textSize: parent.width < 480 ? Theme.fontSizeSmall : Theme.fontSizeMedium textSize: parent.width < 480 ? Theme.fontSizeSmall : Theme.fontSizeMedium
model: [I18n.tr("Default"), I18n.tr("Low"), I18n.tr("Normal"), I18n.tr("High"), I18n.tr("Very High")] model: [I18n.tr("Default"), I18n.tr("Low", "quality level option"), I18n.tr("Normal", "quality level option"), I18n.tr("High", "quality level option"), I18n.tr("Very High", "quality level option")]
selectionMode: "single" selectionMode: "single"
currentIndex: SettingsData.textRenderQuality currentIndex: SettingsData.textRenderQuality
onSelectionChanged: (index, selected) => { onSelectionChanged: (index, selected) => {
@@ -622,7 +622,6 @@ Item {
tags: ["animation", "duration", "custom", "speed", "popout"] tags: ["animation", "duration", "custom", "speed", "popout"]
settingKey: "popoutCustomAnimationDuration" settingKey: "popoutCustomAnimationDuration"
text: I18n.tr("Custom Duration") text: I18n.tr("Custom Duration")
description: I18n.tr("%1 custom animation duration").arg(I18n.tr("Popouts"))
minimum: 0 minimum: 0
maximum: 1000 maximum: 1000
value: Theme.popoutAnimationDuration value: Theme.popoutAnimationDuration
@@ -706,7 +705,6 @@ Item {
tags: ["animation", "duration", "custom", "speed", "modal"] tags: ["animation", "duration", "custom", "speed", "modal"]
settingKey: "modalCustomAnimationDuration" settingKey: "modalCustomAnimationDuration"
text: I18n.tr("Custom Duration") text: I18n.tr("Custom Duration")
description: I18n.tr("%1 custom animation duration").arg(I18n.tr("Modals"))
minimum: 0 minimum: 0
maximum: 1000 maximum: 1000
value: Theme.modalAnimationDuration value: Theme.modalAnimationDuration
+2 -2
View File
@@ -290,7 +290,7 @@ Item {
return; return;
const makeAdmin = !userRow.modelData.isAdmin; const makeAdmin = !userRow.modelData.isAdmin;
adminToggleConfirm.showWithOptions({ adminToggleConfirm.showWithOptions({
title: makeAdmin ? I18n.tr("Grant admin?") : I18n.tr("Remove admin?"), title: makeAdmin ? I18n.tr("Make admin") : I18n.tr("Remove admin"),
message: makeAdmin ? I18n.tr("Add \"%1\" to the %2 group?").arg(userRow.modelData.username).arg(UsersService.adminGroup) : I18n.tr("Remove \"%1\" from the %2 group?").arg(userRow.modelData.username).arg(UsersService.adminGroup), message: makeAdmin ? I18n.tr("Add \"%1\" to the %2 group?").arg(userRow.modelData.username).arg(UsersService.adminGroup) : I18n.tr("Remove \"%1\" from the %2 group?").arg(userRow.modelData.username).arg(UsersService.adminGroup),
confirmText: makeAdmin ? I18n.tr("Grant") : I18n.tr("Remove"), confirmText: makeAdmin ? I18n.tr("Grant") : I18n.tr("Remove"),
confirmColor: Theme.primary, confirmColor: Theme.primary,
@@ -317,7 +317,7 @@ Item {
if (actionBlocked) if (actionBlocked)
return; return;
deleteUserConfirm.showWithOptions({ deleteUserConfirm.showWithOptions({
title: I18n.tr("Delete user?"), title: I18n.tr("Delete user"),
message: I18n.tr("Delete \"%1\" and remove the home directory? This cannot be undone.").arg(userRow.modelData.username), message: I18n.tr("Delete \"%1\" and remove the home directory? This cannot be undone.").arg(userRow.modelData.username),
confirmText: I18n.tr("Delete"), confirmText: I18n.tr("Delete"),
confirmColor: Theme.primary, confirmColor: Theme.primary,
+4 -5
View File
@@ -804,7 +804,6 @@ Item {
settingKey: "selectedMonitor" settingKey: "selectedMonitor"
width: parent.width - Theme.spacingM * 2 width: parent.width - Theme.spacingM * 2
text: I18n.tr("Wallpaper Monitor") text: I18n.tr("Wallpaper Monitor")
description: I18n.tr("Select monitor to configure wallpaper")
currentValue: { currentValue: {
var screens = Quickshell.screens; var screens = Quickshell.screens;
for (var i = 0; i < screens.length; i++) { for (var i = 0; i < screens.length; i++) {
@@ -843,7 +842,7 @@ Item {
currentValue: { currentValue: {
var screens = Quickshell.screens; var screens = Quickshell.screens;
if (!SettingsData.matugenTargetMonitor || SettingsData.matugenTargetMonitor === "") { if (!SettingsData.matugenTargetMonitor || SettingsData.matugenTargetMonitor === "") {
return screens.length > 0 ? SettingsData.getScreenDisplayName(screens[0]) + " " + I18n.tr("(Default)", "default monitor label suffix") : I18n.tr("No monitors", "no monitors available label"); return screens.length > 0 ? SettingsData.getScreenDisplayName(screens[0]) + " " + "(" + I18n.tr("Default") + ")" : I18n.tr("No monitors", "no monitors available label");
} }
for (var i = 0; i < screens.length; i++) { for (var i = 0; i < screens.length; i++) {
if (screens[i].name === SettingsData.matugenTargetMonitor) { if (screens[i].name === SettingsData.matugenTargetMonitor) {
@@ -858,14 +857,14 @@ Item {
for (var i = 0; i < screens.length; i++) { for (var i = 0; i < screens.length; i++) {
var label = SettingsData.getScreenDisplayName(screens[i]); var label = SettingsData.getScreenDisplayName(screens[i]);
if (i === 0 && (!SettingsData.matugenTargetMonitor || SettingsData.matugenTargetMonitor === "")) { if (i === 0 && (!SettingsData.matugenTargetMonitor || SettingsData.matugenTargetMonitor === "")) {
label += " " + I18n.tr("(Default)", "default monitor label suffix"); label += " " + "(" + I18n.tr("Default") + ")";
} }
screenNames.push(label); screenNames.push(label);
} }
return screenNames; return screenNames;
} }
onValueChanged: value => { onValueChanged: value => {
var cleanValue = value.replace(" " + I18n.tr("(Default)", "default monitor label suffix"), ""); var cleanValue = value.replace(" " + "(" + I18n.tr("Default") + ")", "");
var screens = Quickshell.screens; var screens = Quickshell.screens;
for (var i = 0; i < screens.length; i++) { for (var i = 0; i < screens.length; i++) {
if (SettingsData.getScreenDisplayName(screens[i]) === cleanValue) { if (SettingsData.getScreenDisplayName(screens[i]) === cleanValue) {
@@ -924,7 +923,7 @@ Item {
width: parent.width - Theme.spacingM * 2 width: parent.width - Theme.spacingM * 2
StyledText { StyledText {
text: I18n.tr("Mode:") text: I18n.tr("Mode") + ":"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
+1 -1
View File
@@ -192,7 +192,7 @@ Item {
{ {
"id": "battery", "id": "battery",
"text": I18n.tr("Battery"), "text": I18n.tr("Battery"),
"description": I18n.tr("Battery level and power management"), "description": I18n.tr("Battery and power management"),
"icon": "battery_std", "icon": "battery_std",
"enabled": true "enabled": true
}, },
@@ -45,7 +45,7 @@ Item {
"appId": I18n.tr("App ID"), "appId": I18n.tr("App ID"),
"title": I18n.tr("Title"), "title": I18n.tr("Title"),
"isFloating": I18n.tr("Floating"), "isFloating": I18n.tr("Floating"),
"isActive": I18n.tr("Active"), "isActive": I18n.tr("Active", "active state label"),
"isFocused": I18n.tr("Focused"), "isFocused": I18n.tr("Focused"),
"isActiveInColumn": I18n.tr("Active in Column"), "isActiveInColumn": I18n.tr("Active in Column"),
"isWindowCastTarget": I18n.tr("Cast Target"), "isWindowCastTarget": I18n.tr("Cast Target"),
@@ -343,7 +343,7 @@ Item {
} }
function showHyprlandReadOnlyWarning() { function showHyprlandReadOnlyWarning() {
ToastService.showWarning(I18n.tr("Hyprland conf mode"), I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before editing window rules in Settings."), "dms setup", "hyprland-migration"); ToastService.showWarning(I18n.tr("Hyprland conf mode"), I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before changing these settings."), "dms setup", "hyprland-migration");
} }
Component.onCompleted: { Component.onCompleted: {
@@ -509,7 +509,7 @@ Item {
} }
StyledText { StyledText {
text: warningBox.showLegacy ? I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before editing window rules in Settings.") : I18n.tr("Click 'Setup' to create %1 and add include to your compositor config.").arg("dms/windowrules") text: warningBox.showLegacy ? I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before changing these settings.") : I18n.tr("Click 'Setup' to create %1 and add include to your compositor config.").arg("dms/windowrules")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
@@ -8,7 +8,7 @@ SettingsCard {
id: root id: root
iconName: "palette" iconName: "palette"
title: I18n.tr("Workspace Appearance") title: I18n.tr("Appearance")
settingKey: "workspaceAppearance" settingKey: "workspaceAppearance"
tags: ["workspace", "focused", "color", "custom"] tags: ["workspace", "focused", "color", "custom"]
collapsible: true collapsible: true
@@ -200,10 +200,10 @@ SettingsCard {
tabHeight: 44 tabHeight: 44
showIcons: false showIcons: false
model: [({ model: [({
"text": I18n.tr("Focused Display", "workspace appearance tab") "text": I18n.tr("Focused Display", "workspace appearance tab")
}), ({ }), ({
"text": I18n.tr("Unfocused Display(s)", "workspace appearance tab") "text": I18n.tr("Unfocused Display(s)", "workspace appearance tab")
})] })]
onTabClicked: index => currentIndex = index onTabClicked: index => currentIndex = index
Component.onCompleted: Qt.callLater(updateIndicator) Component.onCompleted: Qt.callLater(updateIndicator)
@@ -25,7 +25,7 @@ Item {
SettingsCard { SettingsCard {
width: parent.width width: parent.width
iconName: "view_module" iconName: "view_module"
title: I18n.tr("Workspace Settings") title: I18n.tr("General")
settingKey: "workspaceSettings" settingKey: "workspaceSettings"
SettingsToggleRow { SettingsToggleRow {
+1 -1
View File
@@ -547,7 +547,7 @@ Singleton {
} }
function showHyprlandReadOnlyWarning() { function showHyprlandReadOnlyWarning() {
ToastService.showWarning(I18n.tr("Hyprland conf mode"), I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before editing shortcuts in Settings."), "dms setup", "hyprland-migration"); ToastService.showWarning(I18n.tr("Hyprland conf mode"), I18n.tr("This install is still using hyprland.conf. Run dms setup to migrate before changing these settings."), "dms setup", "hyprland-migration");
} }
function removeBind(key) { function removeBind(key) {
+15 -5
View File
@@ -90,15 +90,20 @@ def json_changed(file_path, new_data):
old_data = normalize_json(file_path) old_data = normalize_json(file_path)
return json.dumps(old_data, sort_keys=True) != json.dumps(new_data, sort_keys=True) return json.dumps(old_data, sort_keys=True) != json.dumps(new_data, sort_keys=True)
def upload_source_strings(api_token, project_id): def upload_source_strings(api_token, project_id, prune=False):
if not EN_JSON.exists(): if not EN_JSON.exists():
warn("No en.json to upload") warn("No en.json to upload")
return False return False
info("Uploading source strings to POEditor...") info("Uploading source strings to POEditor..." + (" (pruning terms not in en.json)" if prune else ""))
with open(EN_JSON, 'rb') as f: with open(EN_JSON, 'rb') as f:
boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW' boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'
sync_part = (
f'--{boundary}\r\n'
f'Content-Disposition: form-data; name="sync_terms"\r\n\r\n'
f'1\r\n'
) if prune else ''
body = ( body = (
f'--{boundary}\r\n' f'--{boundary}\r\n'
f'Content-Disposition: form-data; name="api_token"\r\n\r\n' f'Content-Disposition: form-data; name="api_token"\r\n\r\n'
@@ -109,6 +114,7 @@ def upload_source_strings(api_token, project_id):
f'--{boundary}\r\n' f'--{boundary}\r\n'
f'Content-Disposition: form-data; name="updating"\r\n\r\n' f'Content-Disposition: form-data; name="updating"\r\n\r\n'
f'terms\r\n' f'terms\r\n'
f'{sync_part}'
f'--{boundary}\r\n' f'--{boundary}\r\n'
f'Content-Disposition: form-data; name="file"; filename="en.json"\r\n' f'Content-Disposition: form-data; name="file"; filename="en.json"\r\n'
f'Content-Type: application/json\r\n\r\n' f'Content-Type: application/json\r\n\r\n'
@@ -253,7 +259,7 @@ def save_sync_state():
def main(): def main():
if len(sys.argv) < 2: if len(sys.argv) < 2:
error("Usage: i18nsync.py [check|sync|test|local]") error("Usage: i18nsync.py [check|sync [--prune]|test|local]")
command = sys.argv[1] command = sys.argv[1]
@@ -290,6 +296,10 @@ def main():
elif command == "sync": elif command == "sync":
api_token = get_env_or_error('POEDITOR_API_TOKEN') api_token = get_env_or_error('POEDITOR_API_TOKEN')
project_id = get_env_or_error('POEDITOR_PROJECT_ID') project_id = get_env_or_error('POEDITOR_PROJECT_ID')
prune = "--prune" in sys.argv[2:]
if prune:
warn("--prune deletes every POEditor term missing from the local en.json, including its translations.")
warn("Terms from dms-plugins/ are machine-dependent: make sure all official plugins are present before pruning.")
extract_strings() extract_strings()
@@ -310,8 +320,8 @@ def main():
strings_changed = json.dumps(current_en, sort_keys=True) != json.dumps(staged_en, sort_keys=True) strings_changed = json.dumps(current_en, sort_keys=True) != json.dumps(staged_en, sort_keys=True)
if strings_changed: if strings_changed or prune:
upload_source_strings(api_token, project_id) upload_source_strings(api_token, project_id, prune)
else: else:
info("No changes in source strings") info("No changes in source strings")
@@ -4,6 +4,9 @@
While term_freeze.json exists, any I18n.tr()/qsTr() term not in it fails the check. While term_freeze.json exists, any I18n.tr()/qsTr() term not in it fails the check.
Existing terms may be reused or moved freely. Existing terms may be reused or moved freely.
The freeze is term-granular: adding a new real context to a frozen term via
I18n.tr(term, ctx, true) does not trip it (same English string, new slot).
--update re-snapshot the current terms into term_freeze.json --update re-snapshot the current terms into term_freeze.json
(delete term_freeze.json to lift the freeze) (delete term_freeze.json to lift the freeze)
""" """
@@ -0,0 +1,68 @@
#!/usr/bin/env python3
"""Block near-variant translation terms (case/trailing-punctuation duplicates).
Unlike a term freeze, new terms are always allowed. This only fails when two
terms collapse to the same string after lowercasing and stripping trailing
".:?! " punctuation - e.g. "Signal" vs "Signal:", "Reset to Default?" vs
"Reset to default". Build punctuation outside tr() instead:
I18n.tr("Signal") + ":"
"Term" vs "Term..." pairs are NOT flagged: a trailing ellipsis is meaningful
(progress states, opens-a-dialog affordances).
"""
import sys
from collections import defaultdict
from extract_translations import extract_qstr_strings
from pathlib import Path
ROOT_DIR = Path(__file__).parent.parent
# Intentional pairs that survive normalization on purpose.
ALLOWED = [
{"PIN", "Pin"}, # WPS PIN acronym vs the verb "pin"
{"Device", "device"}, # label vs inline generic-noun fallback
{"Until %1", "until %1"}, # sentence-initial vs mid-sentence position
]
def normalize(term):
t = term.replace("", "...")
ellipsis = t.rstrip().endswith("...")
t = t.lower().rstrip(".:?! ")
return t + "..." if ellipsis else t
def main():
translations = extract_qstr_strings(ROOT_DIR)
groups = defaultdict(set)
for term in translations:
groups[normalize(term)].add(term)
failures = []
for variants in groups.values():
if len(variants) < 2 or any(variants == a for a in ALLOWED):
continue
# term vs term+"..." is allowed; flag only same-ellipsis-class variants
plain = {v for v in variants if not v.replace("", "...").rstrip().endswith("...")}
dotted = variants - plain
for cls in (plain, dotted):
if len(cls) > 1:
failures.append(sorted(cls))
if not failures:
print(f"No term variants ({len(translations)} terms checked)")
return 0
print("Near-variant terms found - reuse one exact term (or add to ALLOWED "
"in check_term_variants.py if genuinely intentional):", file=sys.stderr)
for variants in sorted(failures):
print(f" {variants}", file=sys.stderr)
for v in variants:
for occ in translations[v]["occurrences"][:3]:
print(f" {v!r}: {occ['file']}:{occ['line']}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())
+13188 -4427
View File
File diff suppressed because it is too large Load Diff
@@ -435,7 +435,7 @@ def parse_tabs_from_sidebar(sidebar_file):
with open(sidebar_file, "r", encoding="utf-8") as f: with open(sidebar_file, "r", encoding="utf-8") as f:
content = f.read() content = f.read()
pattern = r'"text"\s*:\s*I18n\.tr\("([^"]+)"(?:,\s*"[^"]+")?\).*?"icon"\s*:\s*"([^"]+)".*?"tabIndex"\s*:\s*(\d+)' pattern = r'"text"\s*:\s*I18n\.tr\("([^"]+)"(?:,\s*"[^"]+"(?:,\s*true)?)?\).*?"icon"\s*:\s*"([^"]+)".*?"tabIndex"\s*:\s*(\d+)'
tabs = [] tabs = []
for match in re.finditer(pattern, content, re.DOTALL): for match in re.finditer(pattern, content, re.DOTALL):
+82 -41
View File
@@ -18,11 +18,29 @@ def spans_overlap(a, b):
def extract_qstr_strings(root_dir): def extract_qstr_strings(root_dir):
translations = defaultdict(lambda: {'contexts': set(), 'occurrences': []}) translations = defaultdict(lambda: {
'contexts': set(),
'real_contexts': defaultdict(list),
'occurrences': [],
'plain_occurrences': []
})
qstr_patterns = [ qstr_patterns = [
(re.compile(r'qsTr\(\s*"((?:\\.|[^"\\])*)"\s*\)'), '"'), (re.compile(r'qsTr\(\s*"((?:\\.|[^"\\])*)"\s*\)'), '"'),
(re.compile(r"qsTr\(\s*'((?:\\.|[^'\\])*)'\s*\)"), "'") (re.compile(r"qsTr\(\s*'((?:\\.|[^'\\])*)'\s*\)"), "'")
] ]
# I18n.tr(term, context, true) -- the literal `true` flag uploads the
# context as a real POEditor context, giving (term, context) its own
# translation slot. Must be on one line with a literal `true`.
i18n_real_context_patterns = [
(
re.compile(r'I18n\.tr\(\s*"((?:\\.|[^"\\])*)"\s*,\s*"((?:\\.|[^"\\])*)"\s*,\s*true\s*\)'),
'"'
),
(
re.compile(r"I18n\.tr\(\s*'((?:\\.|[^'\\])*)'\s*,\s*'((?:\\.|[^'\\])*)'\s*,\s*true\s*\)"),
"'"
)
]
i18n_context_patterns = [ i18n_context_patterns = [
( (
re.compile(r'I18n\.tr\(\s*"((?:\\.|[^"\\])*)"\s*,\s*"((?:\\.|[^"\\])*)"\s*\)'), re.compile(r'I18n\.tr\(\s*"((?:\\.|[^"\\])*)"\s*,\s*"((?:\\.|[^"\\])*)"\s*\)'),
@@ -46,75 +64,96 @@ def extract_qstr_strings(root_dir):
for pattern, quote in qstr_patterns: for pattern, quote in qstr_patterns:
for match in pattern.finditer(line): for match in pattern.finditer(line):
term = decode_string_literal(match.group(1), quote) term = decode_string_literal(match.group(1), quote)
translations[term]['occurrences'].append({ occ = {'file': str(relative_path), 'line': line_num}
'file': str(relative_path), translations[term]['occurrences'].append(occ)
'line': line_num translations[term]['plain_occurrences'].append(occ)
})
real_spans = []
for pattern, quote in i18n_real_context_patterns:
for match in pattern.finditer(line):
term = decode_string_literal(match.group(1), quote)
context = decode_string_literal(match.group(2), quote)
occ = {'file': str(relative_path), 'line': line_num}
translations[term]['real_contexts'][context].append(occ)
translations[term]['occurrences'].append(occ)
real_spans.append(match.span())
context_spans = [] context_spans = []
for pattern, quote in i18n_context_patterns: for pattern, quote in i18n_context_patterns:
for match in pattern.finditer(line): for match in pattern.finditer(line):
if any(spans_overlap(match.span(), span) for span in real_spans):
continue
term = decode_string_literal(match.group(1), quote) term = decode_string_literal(match.group(1), quote)
context = decode_string_literal(match.group(2), quote) context = decode_string_literal(match.group(2), quote)
occ = {'file': str(relative_path), 'line': line_num}
translations[term]['contexts'].add(context) translations[term]['contexts'].add(context)
translations[term]['occurrences'].append({ translations[term]['occurrences'].append(occ)
'file': str(relative_path), translations[term]['plain_occurrences'].append(occ)
'line': line_num
})
context_spans.append(match.span()) context_spans.append(match.span())
for pattern, quote in i18n_simple_patterns: for pattern, quote in i18n_simple_patterns:
for match in pattern.finditer(line): for match in pattern.finditer(line):
if any(spans_overlap(match.span(), span) for span in context_spans): if any(spans_overlap(match.span(), span) for span in real_spans + context_spans):
continue continue
term = decode_string_literal(match.group(1), quote) term = decode_string_literal(match.group(1), quote)
translations[term]['occurrences'].append({ occ = {'file': str(relative_path), 'line': line_num}
'file': str(relative_path), translations[term]['occurrences'].append(occ)
'line': line_num translations[term]['plain_occurrences'].append(occ)
})
return translations return translations
def area_tags(occurrences):
tags = set()
for occ in occurrences:
path = occ['file']
if path.startswith('dms-plugins/'):
tags.add('plugin-' + path.split('/')[1].lower())
elif path.startswith(('Modules/Settings/', 'Modals/Settings/')):
tags.add('settings')
else:
tags.add('shell')
return sorted(tags)
def create_poeditor_json(translations): def create_poeditor_json(translations):
poeditor_data = [] poeditor_data = []
for term, data in sorted(translations.items()): for term, data in sorted(translations.items()):
references = [] if data['plain_occurrences']:
references = [f"{occ['file']}:{occ['line']}" for occ in data['plain_occurrences']]
contexts = sorted(data['contexts']) if data['contexts'] else []
comment = " | ".join(contexts) if contexts else ""
for occ in data['occurrences']: poeditor_data.append({
ref = f"{occ['file']}:{occ['line']}" "term": term,
references.append(ref) "context": term,
"reference": ", ".join(references),
"comment": comment,
"tags": area_tags(data['plain_occurrences'])
})
contexts = sorted(data['contexts']) if data['contexts'] else [] for context in sorted(data['real_contexts']):
comment = " | ".join(contexts) if contexts else "" references = [f"{occ['file']}:{occ['line']}" for occ in data['real_contexts'][context]]
poeditor_data.append({
entry = { "term": term,
"term": term, "context": context,
"context": term, "reference": ", ".join(references),
"reference": ", ".join(references), "comment": "",
"comment": comment "tags": area_tags(data['real_contexts'][context])
} })
poeditor_data.append(entry)
return poeditor_data return poeditor_data
def create_template_json(translations): def create_template_json(translations):
template_data = [] return [
{
for term, data in sorted(translations.items()): "term": entry["term"],
contexts = sorted(data['contexts']) if data['contexts'] else []
context_str = " | ".join(contexts) if contexts else ""
entry = {
"term": term,
"translation": "", "translation": "",
"context": context_str, "context": entry["context"],
"reference": "", "reference": "",
"comment": "" "comment": entry["comment"]
} }
template_data.append(entry) for entry in create_poeditor_json(translations)
]
return template_data
def main(): def main():
script_dir = Path(__file__).parent script_dir = Path(__file__).parent
@@ -142,6 +181,8 @@ def main():
print(f" - Unique strings: {len(translations)}") print(f" - Unique strings: {len(translations)}")
print(f" - Total occurrences: {sum(len(data['occurrences']) for data in translations.values())}") print(f" - Total occurrences: {sum(len(data['occurrences']) for data in translations.values())}")
print(f" - Strings with contexts: {sum(1 for data in translations.values() if data['contexts'])}") print(f" - Strings with contexts: {sum(1 for data in translations.values() if data['contexts'])}")
print(f" - Real-context entries: {sum(len(data['real_contexts']) for data in translations.values())}")
print(f" - POEditor entries: {len(poeditor_data)}")
print(f" - Source file: {en_json_path}") print(f" - Source file: {en_json_path}")
print(f" - Template file: {template_json_path}") print(f" - Template file: {template_json_path}")
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff