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
+9 -4
View File
@@ -117,12 +117,17 @@ Singleton {
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)
return term;
const ctx = context || term;
if (translations[ctx] && translations[ctx][term])
return translations[ctx][term];
if (context && translations[context] && translations[context][term])
return translations[context][term];
if (translations[term] && translations[term][term])
return translations[term][term];
for (const c in translations) {
if (translations[c] && translations[c][term])
return translations[c][term];
+2 -2
View File
@@ -291,7 +291,7 @@ Singleton {
_parseError = true;
const msg = e.message;
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;
const msg = e.message;
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;
const msg = e.message;
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();
} finally {
_loading = false;
@@ -1891,7 +1891,7 @@ Singleton {
_pluginParseError = true;
const msg = e.message;
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 = {};
} finally {
_pluginSettingsLoading = false;
@@ -3653,7 +3653,7 @@ Singleton {
_parseError = true;
const msg = e.message;
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 {
_loading = false;
}
+4 -4
View File
@@ -1956,7 +1956,7 @@ Singleton {
function applyGtkColors() {
if (!matugenAvailable) {
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;
}
@@ -1969,7 +1969,7 @@ Singleton {
}
} else {
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() {
if (!matugenAvailable) {
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;
}
@@ -1990,7 +1990,7 @@ Singleton {
}
} else {
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 => {
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");
} else {
let details = (root.authApplyTerminalFallbackStderr || "").trim();
+1 -1
View File
@@ -323,7 +323,7 @@ Item {
target: TrashService
function onEmptyTrashConfirmRequested(itemCount) {
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),
confirmText: I18n.tr("Empty"),
cancelText: I18n.tr("Cancel"),
@@ -408,14 +408,14 @@ FocusScope {
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: "↵ " + I18n.tr("open")
text: "↵ " + I18n.tr("Open")
font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceVariantText
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: "Tab " + I18n.tr("actions")
text: "Tab " + I18n.tr("Actions")
font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceVariantText
visible: actionPanel.hasActions
@@ -156,7 +156,7 @@ Item {
function statusTitle() {
if (root.controller?.isSearching || root.controller?.isFileSearching)
return I18n.tr("Searching");
return I18n.tr("Searching...");
if ((root.controller?.searchMode ?? "") === "files" && !DSearchService.dsearchAvailable)
return I18n.tr("File search unavailable");
if ((root.controller?.searchMode ?? "") === "files" && (root.controller?.searchQuery?.length ?? 0) < 2)
+1 -1
View File
@@ -294,7 +294,7 @@ FocusScope {
}
StyledText {
text: I18n.tr("Authentication failed, please try again")
text: I18n.tr("Authentication failed - try again")
font.pixelSize: Theme.fontSizeSmall
color: Theme.error
width: parent.width
+2 -2
View File
@@ -494,7 +494,7 @@ FloatingWindow {
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Processes:", "process count label in footer")
text: I18n.tr("Processes", "process count label in footer") + ":"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
@@ -511,7 +511,7 @@ FloatingWindow {
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Uptime:", "uptime label in footer")
text: I18n.tr("Uptime", "uptime label in footer") + ":"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
@@ -16,7 +16,7 @@ PluginComponent {
ccWidgetPrimaryText: I18n.tr("Printers")
ccWidgetSecondaryText: {
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 {
if (!CupsService.cupsAvailable) {
return I18n.tr("Print Server not available");
@@ -18,7 +18,7 @@ PluginComponent {
ccWidgetPrimaryText: I18n.tr("VPN")
ccWidgetSecondaryText: {
if (vpnActivating)
return I18n.tr("Connecting");
return I18n.tr("Connecting...");
if (!vpnActivated)
return I18n.tr("Disconnected");
const names = DMSNetworkService.activeNames || [];
@@ -166,7 +166,7 @@ Rectangle {
}
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
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
@@ -624,7 +624,7 @@ Rectangle {
spacing: Theme.spacingXS
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
color: wifiDelegate.isConnecting ? Theme.warning : Theme.surfaceVariantText
}
@@ -329,7 +329,7 @@ Item {
spacing: Theme.spacingS
DankButton {
text: root.saving ? I18n.tr("Saving") : I18n.tr("Save")
text: root.saving ? I18n.tr("Saving...") : I18n.tr("Save")
iconName: "check"
buttonHeight: 32
backgroundColor: Theme.primary
+2 -2
View File
@@ -692,14 +692,14 @@ Item {
spacing: Theme.spacingM
StyledText {
text: I18n.tr("Unsaved Changes")
text: I18n.tr("Unsaved changes")
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
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
color: Theme.surfaceTextMedium
width: parent.width
@@ -172,7 +172,7 @@ Rectangle {
}
StyledText {
text: I18n.tr("Notification Timeouts")
text: I18n.tr("Timeouts")
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceVariantText
@@ -182,7 +182,6 @@ Rectangle {
id: lowTimeoutDropdown
transientSurfaceTracker: root.transientSurfaceTracker
text: I18n.tr("Low Priority")
description: I18n.tr("Timeout for low priority notifications")
currentValue: getTimeoutText(SettingsData.notificationTimeoutLow)
options: timeoutOptions.map(opt => opt.text)
onValueChanged: value => {
@@ -199,7 +198,6 @@ Rectangle {
id: normalTimeoutDropdown
transientSurfaceTracker: root.transientSurfaceTracker
text: I18n.tr("Normal Priority")
description: I18n.tr("Timeout for normal priority notifications")
currentValue: getTimeoutText(SettingsData.notificationTimeoutNormal)
options: timeoutOptions.map(opt => opt.text)
onValueChanged: value => {
@@ -216,7 +214,6 @@ Rectangle {
id: criticalTimeoutDropdown
transientSurfaceTracker: root.transientSurfaceTracker
text: I18n.tr("Critical Priority")
description: I18n.tr("Timeout for critical priority notifications")
currentValue: getTimeoutText(SettingsData.notificationTimeoutCritical)
options: timeoutOptions.map(opt => opt.text)
onValueChanged: value => {
+6 -5
View File
@@ -48,8 +48,9 @@ done
SettingsCard {
width: parent.width
iconName: "battery_charging_full"
title: I18n.tr("Battery Status")
title: I18n.tr("Status")
settingKey: "batteryStatusCard"
tags: ["battery", "status", "charge", "health"]
Column {
width: parent.width - Theme.spacingM * 2
@@ -167,8 +168,9 @@ done
SettingsCard {
width: parent.width
iconName: "tune"
title: I18n.tr("Battery Protection")
title: I18n.tr("Protection")
settingKey: "batteryProtection"
tags: ["battery", "protection", "charge", "limit"]
SettingsSliderRow {
settingKey: "batteryChargeLimit"
@@ -230,8 +232,9 @@ done
SettingsCard {
width: parent.width
iconName: "notifications"
title: I18n.tr("Battery Alerts")
title: I18n.tr("Alerts")
settingKey: "batteryAlerts"
tags: ["battery", "alerts", "low", "warning"]
SettingsSliderRow {
settingKey: "batteryLowThreshold"
@@ -332,7 +335,6 @@ done
SettingsDropdownRow {
settingKey: "acProfileName"
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)]
currentValue: {
const val = SettingsData.acProfileName;
@@ -350,7 +352,6 @@ done
SettingsDropdownRow {
settingKey: "batteryProfileName"
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)]
currentValue: {
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 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) {
if (value <= 0)
@@ -341,7 +341,6 @@ Item {
tags: ["clipboard", "entry", "size", "limit"]
settingKey: "maxEntrySize"
text: I18n.tr("Maximum Entry Size")
description: I18n.tr("Maximum size per clipboard entry")
options: root.maxEntrySizeOptions.map(opt => opt.text)
Component.onCompleted: {
@@ -94,7 +94,7 @@ Item {
function fixLayoutInclude() {
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;
}
const paths = getLayoutConfigPaths();
@@ -208,7 +208,7 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
StyledText {
text: {
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)
return I18n.tr("Click 'Setup' to create %1 and add include to your compositor config.").arg("dms/layout");
return "";
@@ -270,7 +270,7 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
SettingsCard {
width: parent.width
tags: ["niri", "layout", "gaps", "radius", "window", "border"]
title: I18n.tr("Niri Layout Overrides")
title: I18n.tr("%1 Layout Overrides").arg("niri")
settingKey: "niriLayout"
iconName: "layers"
visible: CompositorService.isNiri
@@ -279,7 +279,7 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
tags: ["niri", "gaps", "override", "unmanaged"]
settingKey: "niriLayoutGapsMode"
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")]
currentIndex: {
if (SettingsData.niriLayoutGapsOverride === -2)
@@ -399,7 +399,7 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
SettingsCard {
width: parent.width
tags: ["hyprland", "layout", "gaps", "radius", "window", "border", "rounding"]
title: I18n.tr("Hyprland Layout Overrides")
title: I18n.tr("%1 Layout Overrides").arg("Hyprland")
settingKey: "hyprlandLayout"
iconName: "crop_square"
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"]
settingKey: "hyprlandLayoutGapsMode"
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")]
currentIndex: {
if (SettingsData.hyprlandLayoutGapsOverride === -2)
@@ -492,7 +492,6 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
tags: ["hyprland", "border", "override"]
settingKey: "hyprlandLayoutBorderSizeEnabled"
text: I18n.tr("Override Border Size")
description: I18n.tr("Use custom border size")
checked: SettingsData.hyprlandLayoutBorderSize >= 0
onToggled: checked => {
if (checked) {
@@ -551,7 +550,7 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
SettingsCard {
width: parent.width
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"
iconName: "crop_square"
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"]
settingKey: "mangoLayoutGapsMode"
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")]
currentIndex: {
if (SettingsData.mangoLayoutGapsOverride === -2)
@@ -644,7 +643,6 @@ awk '$1 == "xray" { print FILENAME ":" FNR; exit }' $files 2>/dev/null`;
tags: ["mangowc", "mango", "border", "override"]
settingKey: "mangoLayoutBorderSizeEnabled"
text: I18n.tr("Override Border Size")
description: I18n.tr("Use custom border size")
checked: SettingsData.mangoLayoutBorderSize >= 0
onToggled: checked => {
if (checked) {
+2 -7
View File
@@ -266,7 +266,7 @@ Item {
SettingsCard {
iconName: "dashboard"
title: I18n.tr("Bar Configurations")
title: I18n.tr("Configurations")
settingKey: "barConfigurations"
visible: !dankBarTab.appearanceOnly
@@ -490,7 +490,7 @@ Item {
DankButtonGroup {
id: positionButtonGroup
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: {
selectedBarId;
const config = SettingsData.getBarConfig(selectedBarId);
@@ -820,7 +820,6 @@ Item {
id: barTransparencySlider
visible: !SettingsData.frameEnabled
text: I18n.tr("Bar Opacity")
description: I18n.tr("Controls opacity of the bar background")
value: (selectedBarConfig?.transparency ?? 1.0) * 100
minimum: 0
maximum: 100
@@ -843,7 +842,6 @@ Item {
SettingsSliderRow {
id: widgetTransparencySlider
text: I18n.tr("Widget Opacity")
description: I18n.tr("Controls opacity of widget backgrounds")
value: (selectedBarConfig?.widgetTransparency ?? 1.0) * 100
minimum: 0
maximum: 100
@@ -1463,7 +1461,6 @@ Item {
SettingsSliderRow {
id: borderOpacitySlider
text: I18n.tr("Opacity")
description: I18n.tr("Controls opacity of the border")
value: (selectedBarConfig?.borderOpacity ?? 1.0) * 100
minimum: 0
maximum: 100
@@ -1558,7 +1555,6 @@ Item {
SettingsSliderRow {
id: widgetOutlineOpacitySlider
text: I18n.tr("Opacity")
description: I18n.tr("Controls opacity of the widget outline")
value: (selectedBarConfig?.widgetOutlineOpacity ?? 1.0) * 100
minimum: 0
maximum: 100
@@ -1667,7 +1663,6 @@ Item {
SettingsSliderRow {
visible: shadowCard.shadowActive
text: I18n.tr("Opacity")
description: I18n.tr("Controls opacity of the shadow layer")
minimum: 10
maximum: 100
unit: "%"
@@ -312,7 +312,6 @@ Item {
text: I18n.tr("Calendar", "Calendar")
category: root.appCategory.Calendar
tags: ["calendar", "events"]
description: I18n.tr("Manages calendar events", "Manages calendar events")
}
}
@@ -330,7 +329,6 @@ Item {
text: I18n.tr("PDF Reader", "PDF Reader")
category: root.appCategory.PDFReader
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")
category: root.appCategory.ImageViewer
tags: ["image", "viewer"]
description: I18n.tr("Opens image files", "Opens image files")
}
AppSelector {
text: I18n.tr("Video Player", "Video Player")
category: root.appCategory.VideoPlayer
tags: ["video", "player"]
description: I18n.tr("Plays video files", "Plays video files")
}
AppSelector {
text: I18n.tr("Music Player", "Music Player")
@@ -1518,7 +1518,7 @@ Singleton {
}
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() {
@@ -2639,7 +2639,7 @@ Singleton {
function getTransformLabel(transform) {
switch (transform) {
case "Normal":
return I18n.tr("Normal");
return I18n.tr("Normal", "display rotation option", true);
case "90":
return I18n.tr("90°");
case "180":
@@ -2655,12 +2655,12 @@ Singleton {
case "Flipped270":
return I18n.tr("Flipped 270°");
default:
return I18n.tr("Normal");
return I18n.tr("Normal", "display rotation option", true);
}
}
function getTransformValue(label) {
if (label === I18n.tr("Normal"))
if (label === I18n.tr("Normal", "display rotation option", true))
return "Normal";
if (label === I18n.tr("90°"))
return "90";
@@ -60,9 +60,9 @@ StyledRect {
StyledText {
text: {
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)
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 "";
}
font.pixelSize: Theme.fontSizeSmall
@@ -93,21 +93,21 @@ Column {
currentValue: {
if (!hotCornersData)
return I18n.tr("Inherit");
return I18n.tr("Inherit", "inherit from global setting");
if (hotCornersData.off)
return I18n.tr("Off");
const corners = hotCornersData.corners || [];
if (corners.length === 0)
return I18n.tr("Inherit");
return I18n.tr("Inherit", "inherit from global setting");
if (corners.length === 4)
return I18n.tr("All");
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 => {
switch (value) {
case I18n.tr("Inherit"):
case I18n.tr("Inherit", "inherit from global setting"):
DisplayConfigState.setNiriSetting(root.outputData, root.outputName, "hotCorners", null);
break;
case I18n.tr("Off"):
@@ -232,7 +232,7 @@ Column {
DankTextField {
width: parent.width
height: 40
placeholderText: I18n.tr("Inherit")
placeholderText: I18n.tr("Inherit", "inherit from global setting")
enabled: !settingsColumn.isDisabled
text: {
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
@@ -270,7 +270,7 @@ Column {
DankTextField {
width: parent.width
height: 40
placeholderText: I18n.tr("Inherit")
placeholderText: I18n.tr("Inherit", "inherit from global setting")
enabled: !settingsColumn.isDisabled
text: {
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
@@ -321,7 +321,7 @@ Column {
DankTextField {
width: parent.width
height: 40
placeholderText: I18n.tr("Inherit")
placeholderText: I18n.tr("Inherit", "inherit from global setting")
enabled: !settingsColumn.isDisabled
text: {
const layout = DisplayConfigState.getNiriSetting(root.outputData, root.outputName, "layout", null);
@@ -308,7 +308,7 @@ StyledRect {
return DisplayConfigState.getTransformLabel(pendingTransform);
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))
}
}
@@ -399,7 +399,6 @@ Item {
DankToggle {
width: parent.width
text: I18n.tr("All displays")
description: I18n.tr("Show on all connected displays")
checked: {
var prefs = root.getScreenPreferences(parent.componentId);
return prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all");
@@ -420,7 +419,6 @@ Item {
DankToggle {
width: parent.width
text: I18n.tr("Focused Monitor Only")
description: I18n.tr("Show notifications only on the currently focused monitor")
visible: parent.componentId === "notifications"
checked: SettingsData.notificationFocusedMonitor
onToggled: checked => SettingsData.set("notificationFocusedMonitor", checked)
+2 -4
View File
@@ -37,7 +37,7 @@ Item {
SettingsCard {
width: parent.width
iconName: "dock_to_bottom"
title: I18n.tr("Dock Visibility")
title: I18n.tr("Visibility")
settingKey: "dockVisibility"
SettingsToggleRow {
@@ -228,7 +228,6 @@ Item {
settingKey: "dockShowOverflowBadge"
tags: ["dock", "overflow", "badge", "count", "indicator"]
text: I18n.tr("Show Overflow Badge Count")
description: I18n.tr("Displays count when overflow is active")
checked: SettingsData.dockShowOverflowBadge
onToggled: checked => SettingsData.set("dockShowOverflowBadge", checked)
}
@@ -461,7 +460,7 @@ Item {
if (!PopoutService.colorPickerModal)
return;
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) {
SettingsData.set("dockLauncherLogoColorOverride", selectedColor);
};
@@ -683,7 +682,6 @@ Item {
SettingsButtonGroupRow {
text: I18n.tr("Border Color")
description: I18n.tr("Choose the border accent color")
visible: SettingsData.dockBorderEnabled
model: [I18n.tr("Surface", "color option"), I18n.tr("Secondary", "color option"), I18n.tr("Primary", "color option")]
buttonPadding: Theme.spacingS
+1 -1
View File
@@ -31,7 +31,7 @@ Item {
SettingsCard {
width: parent.width
iconName: "frame_source"
title: I18n.tr("Frame")
title: I18n.tr("General")
settingKey: "frameEnabled"
SettingsToggleRow {
@@ -122,7 +122,6 @@ Item {
tags: ["gamma", "day", "temperature", "kelvin", "color"]
width: parent.width - parent.leftPadding - parent.rightPadding
text: I18n.tr("Day Temperature")
description: I18n.tr("Color temperature for day time")
minimum: SessionData.nightModeTemperature
maximum: 10000
step: 100
+6 -6
View File
@@ -305,7 +305,7 @@ Item {
onExited: exitCode => {
root.greeterSyncRunning = false;
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;
SettingsData.clearGreeterSyncPending();
return;
@@ -396,7 +396,7 @@ Item {
SettingsCard {
width: parent.width
iconName: "info"
title: I18n.tr("Greeter Status")
title: I18n.tr("Status")
settingKey: "greeterStatus"
StyledText {
@@ -470,7 +470,7 @@ Item {
SettingsCard {
width: parent.width
iconName: "fingerprint"
title: I18n.tr("Login Authentication")
title: I18n.tr("Authentication")
settingKey: "greeterAuth"
StyledText {
@@ -517,7 +517,7 @@ Item {
SettingsCard {
width: parent.width
iconName: "palette"
title: I18n.tr("Greeter Appearance")
title: I18n.tr("Appearance")
settingKey: "greeterAppearance"
StyledText {
@@ -558,7 +558,7 @@ Item {
currentValue: {
var current = (SettingsData.greeterLockDateFormat !== undefined && SettingsData.greeterLockDateFormat !== "") ? SettingsData.greeterLockDateFormat : SettingsData.lockDateFormat || "";
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 => {
var preset = root._lockDateFormatPresets.find(p => p.label === value);
@@ -601,7 +601,7 @@ Item {
SettingsCard {
width: parent.width
iconName: "history"
title: I18n.tr("Greeter Behavior")
title: I18n.tr("Behavior")
settingKey: "greeterBehavior"
StyledText {
+2 -2
View File
@@ -117,7 +117,7 @@ Item {
function confirmResetBind(key, remainingKey) {
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),
confirmText: I18n.tr("Reset"),
confirmColor: Theme.primary,
@@ -397,7 +397,7 @@ Item {
StyledText {
text: {
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)
return I18n.tr("Click 'Setup' to create %1 and add include to your compositor config.").arg("dms/binds");
if (warningBox.showWarning) {
+2 -3
View File
@@ -139,7 +139,7 @@ Item {
}
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
color: Theme.surfaceVariantText
width: parent.width
@@ -173,7 +173,6 @@ Item {
settingKey: "launcherUseOverlayLayer"
tags: ["launcher", "fullscreen", "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
onToggled: checked => SettingsData.set("launcherUseOverlayLayer", checked)
}
@@ -233,7 +232,7 @@ Item {
}
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
color: Theme.surfaceVariantText
width: parent.width
+1 -1
View File
@@ -45,7 +45,7 @@ Item {
SettingsCard {
tab: "locale"
tags: ["locale", "language", "country"]
title: I18n.tr("Locale Settings")
title: I18n.tr("General")
iconName: "language"
SettingsDropdownRow {
@@ -266,7 +266,7 @@ Item {
SettingsCard {
width: parent.width
iconName: "lock"
title: I18n.tr("Lock Screen layout")
title: I18n.tr("Layout")
settingKey: "lockLayout"
SettingsToggleRow {
@@ -345,7 +345,7 @@ Item {
SettingsCard {
width: parent.width
iconName: "palette"
title: I18n.tr("Lock Screen Appearance")
title: I18n.tr("Appearance")
settingKey: "lockAppearance"
StyledText {
@@ -476,7 +476,6 @@ Item {
settingKey: "lockPamExternallyManaged"
tags: ["lock", "screen", "pam", "managed", "external", "authentication", "policy"]
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
onToggled: checked => SettingsData.set("lockPamExternallyManaged", checked)
}
@@ -582,7 +581,7 @@ Item {
SettingsCard {
width: parent.width
iconName: "lock"
title: I18n.tr("Lock Screen behaviour")
title: I18n.tr("Behavior")
settingKey: "lockBehavior"
StyledText {
@@ -721,7 +720,7 @@ Item {
SettingsCard {
width: parent.width
iconName: "monitor"
title: I18n.tr("Lock Screen Display")
title: I18n.tr("Display Assignment")
settingKey: "lockDisplay"
StyledText {
@@ -34,7 +34,7 @@ Item {
SettingsCard {
width: parent.width
iconName: "music_note"
title: I18n.tr("Media Player Settings")
title: I18n.tr("General")
settingKey: "mediaPlayer"
SettingsToggleRow {
@@ -146,7 +146,7 @@ Item {
SettingsCard {
width: parent.width
iconName: "do_not_disturb_on"
title: I18n.tr("Excluded Media Players")
title: I18n.tr("Excluded Players")
settingKey: "mediaExcludePlayers"
tags: ["media", "music", "exclude", "ignore", "player", "mpris"]
+1 -2
View File
@@ -25,7 +25,7 @@ Item {
SettingsCard {
tab: "mux"
tags: ["mux", "multiplexer", "tmux", "zellij", "type"]
title: I18n.tr("Multiplexer")
title: I18n.tr("General")
iconName: "terminal"
SettingsDropdownRow {
@@ -33,7 +33,6 @@ Item {
tags: ["mux", "multiplexer", "tmux", "zellij", "type", "backend"]
settingKey: "muxType"
text: I18n.tr("Multiplexer Type")
description: I18n.tr("Terminal multiplexer backend to use")
options: root.muxTypeOptions
currentValue: SettingsData.muxType
onValueChanged: value => SettingsData.set("muxType", value)
@@ -37,7 +37,7 @@ Item {
SettingsCard {
id: root
title: I18n.tr("Network Status")
title: I18n.tr("Status")
iconName: "lan"
settingKey: "networkStatus"
tags: ["status", "network", "connectivity", "internet"]
@@ -128,7 +128,7 @@ Item {
}
StyledText {
text: I18n.tr("Primary")
text: I18n.tr("Primary", "primary network connection label", true)
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
visible: NetworkService.primaryConnection.length > 0
@@ -318,7 +318,7 @@ Item {
visible: NetworkService.wifiConnected
StyledText {
text: I18n.tr("Signal:")
text: I18n.tr("Signal") + ":"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
width: 100
@@ -563,7 +563,7 @@ Item {
spacing: Theme.spacingXS
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
color: isConnecting ? Theme.warning : (isConnected ? Theme.primary : Theme.surfaceVariantText)
}
@@ -770,7 +770,7 @@ Item {
});
fields.push({
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;
@@ -966,7 +966,7 @@ Item {
text: {
if (isConnecting)
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) + "%");
if (modelData.hidden || false)
parts.push(I18n.tr("Hidden"));
@@ -1131,7 +1131,7 @@ Item {
});
fields.push({
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;
@@ -203,7 +203,7 @@ Item {
SettingsCard {
width: parent.width
iconName: "notifications"
title: I18n.tr("Notification Popups")
title: I18n.tr("Popups")
settingKey: "notificationPopups"
// Font size selectors for summary and body
@@ -211,7 +211,6 @@ Item {
settingKey: "notificationSummaryFontSize"
tags: ["notification", "font", "summary", "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"]
currentValue: (SettingsData.notificationSummaryFontSize || I18n.tr("Unset")).toString()
onValueChanged: value => {
@@ -340,7 +339,6 @@ Item {
settingKey: "notificationFocusedMonitor"
tags: ["notification", "popup", "focused", "monitor", "display", "screen", "active"]
text: I18n.tr("Focused Monitor Only")
description: I18n.tr("Show notification popups only on the currently focused monitor")
checked: SettingsData.notificationFocusedMonitor
onToggled: checked => SettingsData.set("notificationFocusedMonitor", checked)
}
@@ -445,7 +443,7 @@ Item {
id: notificationRulesCard
width: parent.width
iconName: "rule_settings"
title: I18n.tr("Notification Rules")
title: I18n.tr("Rules")
settingKey: "notificationRules"
tags: ["notification", "rules", "mute", "ignore", "priority", "regex", "history"]
collapsible: true
@@ -510,7 +508,7 @@ Item {
StyledText {
id: ruleLabel
text: I18n.tr("Rule") + " " + (index + 1)
text: I18n.tr("Rule %1").arg(index + 1)
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
@@ -785,7 +783,7 @@ Item {
SettingsCard {
width: parent.width
iconName: "timer"
title: I18n.tr("Notification Timeouts")
title: I18n.tr("Timeouts")
settingKey: "notificationTimeouts"
collapsible: true
expanded: false
@@ -794,7 +792,6 @@ Item {
settingKey: "notificationTimeoutLow"
tags: ["notification", "timeout", "low", "priority", "duration"]
text: I18n.tr("Low Priority")
description: I18n.tr("Timeout for low priority notifications")
currentValue: root.getTimeoutText(SettingsData.notificationTimeoutLow)
options: root.timeoutOptions.map(opt => opt.text)
onValueChanged: value => {
@@ -811,7 +808,6 @@ Item {
settingKey: "notificationTimeoutNormal"
tags: ["notification", "timeout", "normal", "priority", "duration"]
text: I18n.tr("Normal Priority")
description: I18n.tr("Timeout for normal priority notifications")
currentValue: root.getTimeoutText(SettingsData.notificationTimeoutNormal)
options: root.timeoutOptions.map(opt => opt.text)
onValueChanged: value => {
@@ -828,7 +824,6 @@ Item {
settingKey: "notificationTimeoutCritical"
tags: ["notification", "timeout", "critical", "priority", "duration"]
text: I18n.tr("Critical Priority")
description: I18n.tr("Timeout for critical priority notifications")
currentValue: root.getTimeoutText(SettingsData.notificationTimeoutCritical)
options: root.timeoutOptions.map(opt => opt.text)
onValueChanged: value => {
-9
View File
@@ -91,7 +91,6 @@ Item {
SettingsToggleRow {
settingKey: "osdVolumeEnabled"
text: I18n.tr("Volume")
description: I18n.tr("Show on-screen display when volume changes")
checked: SettingsData.osdVolumeEnabled
onToggled: checked => SettingsData.set("osdVolumeEnabled", checked)
}
@@ -99,7 +98,6 @@ Item {
SettingsToggleRow {
settingKey: "osdMediaVolumeEnabled"
text: I18n.tr("Media Volume")
description: I18n.tr("Show on-screen display when media player volume changes")
checked: SettingsData.osdMediaVolumeEnabled
onToggled: checked => SettingsData.set("osdMediaVolumeEnabled", checked)
}
@@ -107,7 +105,6 @@ Item {
SettingsToggleRow {
settingKey: "osdMediaPlaybackEnabled"
text: I18n.tr("Media Playback")
description: I18n.tr("Show on-screen display when media player status changes")
checked: SettingsData.osdMediaPlaybackEnabled
onToggled: checked => SettingsData.set("osdMediaPlaybackEnabled", checked)
}
@@ -115,7 +112,6 @@ Item {
SettingsToggleRow {
settingKey: "osdBrightnessEnabled"
text: I18n.tr("Brightness")
description: I18n.tr("Show on-screen display when brightness changes")
checked: SettingsData.osdBrightnessEnabled
onToggled: checked => SettingsData.set("osdBrightnessEnabled", checked)
}
@@ -123,7 +119,6 @@ Item {
SettingsToggleRow {
settingKey: "osdIdleInhibitorEnabled"
text: I18n.tr("Idle Inhibitor")
description: I18n.tr("Show on-screen display when idle inhibitor state changes")
checked: SettingsData.osdIdleInhibitorEnabled
onToggled: checked => SettingsData.set("osdIdleInhibitorEnabled", checked)
}
@@ -131,7 +126,6 @@ Item {
SettingsToggleRow {
settingKey: "osdMicMuteEnabled"
text: I18n.tr("Microphone Mute")
description: I18n.tr("Show on-screen display when microphone is muted/unmuted")
checked: SettingsData.osdMicMuteEnabled
onToggled: checked => SettingsData.set("osdMicMuteEnabled", checked)
}
@@ -139,7 +133,6 @@ Item {
SettingsToggleRow {
settingKey: "osdCapsLockEnabled"
text: I18n.tr("Caps Lock")
description: I18n.tr("Show on-screen display when caps lock state changes")
checked: SettingsData.osdCapsLockEnabled
onToggled: checked => SettingsData.set("osdCapsLockEnabled", checked)
}
@@ -147,7 +140,6 @@ Item {
SettingsToggleRow {
settingKey: "osdPowerProfileEnabled"
text: I18n.tr("Power Profile")
description: I18n.tr("Show on-screen display when power profile changes")
checked: SettingsData.osdPowerProfileEnabled
onToggled: checked => SettingsData.set("osdPowerProfileEnabled", checked)
}
@@ -155,7 +147,6 @@ Item {
SettingsToggleRow {
settingKey: "osdAudioOutputEnabled"
text: I18n.tr("Audio Output Switch")
description: I18n.tr("Show on-screen display when cycling audio output devices")
checked: SettingsData.osdAudioOutputEnabled
onToggled: checked => SettingsData.set("osdAudioOutputEnabled", checked)
}
@@ -46,7 +46,8 @@ StyledRect {
}
function updateSingle(plugin) {
if (isUpdating) return;
if (isUpdating)
return;
isUpdating = true;
currentUpdatingPlugin = plugin.name;
@@ -68,7 +69,8 @@ StyledRect {
}
function updateAll() {
if (isUpdating) return;
if (isUpdating)
return;
isUpdating = true;
var list = updatesList.slice();
@@ -149,7 +151,9 @@ StyledRect {
clip: true
Behavior on height {
NumberAnimation { duration: Theme.shortDuration }
NumberAnimation {
duration: Theme.shortDuration
}
}
Row {
@@ -220,7 +224,7 @@ StyledRect {
}
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
color: Theme.surfaceVariantText
elide: Text.ElideRight
+3 -2
View File
@@ -21,7 +21,8 @@ FocusScope {
property var filteredPlugins: []
readonly property var pluginsWithUpdates: {
if (!DMSService.installedPlugins) return [];
if (!DMSService.installedPlugins)
return [];
return DMSService.installedPlugins.filter(p => p.hasUpdate === true);
}
@@ -438,7 +439,7 @@ FocusScope {
StyledText {
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
color: Theme.surfaceVariantText
horizontalAlignment: Text.AlignHCenter
@@ -71,7 +71,6 @@ Item {
tags: ["sound", "enable", "system"]
settingKey: "soundsEnabled"
text: I18n.tr("Enable System Sounds")
description: I18n.tr("Play sounds for system events")
checked: SettingsData.soundsEnabled
onToggled: checked => SettingsData.set("soundsEnabled", checked)
}
@@ -106,7 +105,6 @@ Item {
visible: SettingsData.useSystemSoundTheme && AudioService.availableSoundThemes.length > 0
enabled: SettingsData.useSystemSoundTheme && AudioService.availableSoundThemes.length > 0
text: I18n.tr("Sound Theme")
description: I18n.tr("Select system sound theme")
options: AudioService.availableSoundThemes
currentValue: {
const theme = AudioService.currentSoundTheme;
@@ -143,7 +143,7 @@ Item {
function fixCursorInclude() {
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;
}
const paths = getCursorConfigPaths();
@@ -1626,7 +1626,6 @@ Item {
tags: ["widget", "background", "color", "surface", "material"]
settingKey: "widgetBackgroundColor"
text: I18n.tr("Widget Background Color")
description: I18n.tr("Choose the background color for widgets")
dropdownWidth: 220
options: themeColorsTab.widgetBackgroundOptions
currentMode: SettingsData.widgetBackgroundColor
@@ -1943,7 +1942,6 @@ Item {
tags: ["elevation", "shadow", "opacity", "transparency", "m3"]
settingKey: "m3ElevationOpacity"
text: I18n.tr("Shadow Opacity")
description: I18n.tr("Controls the opacity of the shadow")
value: SettingsData.m3ElevationOpacity ?? 30
minimum: 0
maximum: 100
@@ -2220,7 +2218,7 @@ Item {
StyledText {
text: {
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)
return I18n.tr("Click 'Setup' to create %1 and add include to your compositor config.").arg("dms/cursor");
return "";
@@ -84,7 +84,6 @@ Item {
tags: ["time", "seconds", "clock"]
settingKey: "showSeconds"
text: I18n.tr("Show Seconds")
description: I18n.tr("Display seconds in the clock")
checked: SettingsData.showSeconds
onToggled: checked => SettingsData.set("showSeconds", checked)
}
@@ -113,7 +112,6 @@ Item {
tags: ["show", "week"]
settingKey: "showWeekNumber"
text: I18n.tr("Show Week Number")
description: I18n.tr("Show week number in the calendar")
checked: SettingsData.showWeekNumber
onToggled: checked => SettingsData.set("showWeekNumber", checked)
}
@@ -218,7 +216,7 @@ Item {
}
];
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 => {
const formatMap = {};
@@ -305,7 +303,7 @@ Item {
}
];
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 => {
const formatMap = {};
@@ -455,7 +455,7 @@ Item {
buttonPadding: parent.width < 480 ? Theme.spacingXS : Theme.spacingS
minButtonWidth: parent.width < 480 ? 40 : 56
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"
currentIndex: SettingsData.textRenderQuality
onSelectionChanged: (index, selected) => {
@@ -622,7 +622,6 @@ Item {
tags: ["animation", "duration", "custom", "speed", "popout"]
settingKey: "popoutCustomAnimationDuration"
text: I18n.tr("Custom Duration")
description: I18n.tr("%1 custom animation duration").arg(I18n.tr("Popouts"))
minimum: 0
maximum: 1000
value: Theme.popoutAnimationDuration
@@ -706,7 +705,6 @@ Item {
tags: ["animation", "duration", "custom", "speed", "modal"]
settingKey: "modalCustomAnimationDuration"
text: I18n.tr("Custom Duration")
description: I18n.tr("%1 custom animation duration").arg(I18n.tr("Modals"))
minimum: 0
maximum: 1000
value: Theme.modalAnimationDuration
+2 -2
View File
@@ -290,7 +290,7 @@ Item {
return;
const makeAdmin = !userRow.modelData.isAdmin;
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),
confirmText: makeAdmin ? I18n.tr("Grant") : I18n.tr("Remove"),
confirmColor: Theme.primary,
@@ -317,7 +317,7 @@ Item {
if (actionBlocked)
return;
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),
confirmText: I18n.tr("Delete"),
confirmColor: Theme.primary,
+4 -5
View File
@@ -804,7 +804,6 @@ Item {
settingKey: "selectedMonitor"
width: parent.width - Theme.spacingM * 2
text: I18n.tr("Wallpaper Monitor")
description: I18n.tr("Select monitor to configure wallpaper")
currentValue: {
var screens = Quickshell.screens;
for (var i = 0; i < screens.length; i++) {
@@ -843,7 +842,7 @@ Item {
currentValue: {
var screens = Quickshell.screens;
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++) {
if (screens[i].name === SettingsData.matugenTargetMonitor) {
@@ -858,14 +857,14 @@ Item {
for (var i = 0; i < screens.length; i++) {
var label = SettingsData.getScreenDisplayName(screens[i]);
if (i === 0 && (!SettingsData.matugenTargetMonitor || SettingsData.matugenTargetMonitor === "")) {
label += " " + I18n.tr("(Default)", "default monitor label suffix");
label += " " + "(" + I18n.tr("Default") + ")";
}
screenNames.push(label);
}
return screenNames;
}
onValueChanged: value => {
var cleanValue = value.replace(" " + I18n.tr("(Default)", "default monitor label suffix"), "");
var cleanValue = value.replace(" " + "(" + I18n.tr("Default") + ")", "");
var screens = Quickshell.screens;
for (var i = 0; i < screens.length; i++) {
if (SettingsData.getScreenDisplayName(screens[i]) === cleanValue) {
@@ -924,7 +923,7 @@ Item {
width: parent.width - Theme.spacingM * 2
StyledText {
text: I18n.tr("Mode:")
text: I18n.tr("Mode") + ":"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
+1 -1
View File
@@ -192,7 +192,7 @@ Item {
{
"id": "battery",
"text": I18n.tr("Battery"),
"description": I18n.tr("Battery level and power management"),
"description": I18n.tr("Battery and power management"),
"icon": "battery_std",
"enabled": true
},
@@ -45,7 +45,7 @@ Item {
"appId": I18n.tr("App ID"),
"title": I18n.tr("Title"),
"isFloating": I18n.tr("Floating"),
"isActive": I18n.tr("Active"),
"isActive": I18n.tr("Active", "active state label"),
"isFocused": I18n.tr("Focused"),
"isActiveInColumn": I18n.tr("Active in Column"),
"isWindowCastTarget": I18n.tr("Cast Target"),
@@ -343,7 +343,7 @@ Item {
}
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: {
@@ -509,7 +509,7 @@ Item {
}
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
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
@@ -8,7 +8,7 @@ SettingsCard {
id: root
iconName: "palette"
title: I18n.tr("Workspace Appearance")
title: I18n.tr("Appearance")
settingKey: "workspaceAppearance"
tags: ["workspace", "focused", "color", "custom"]
collapsible: true
@@ -200,10 +200,10 @@ SettingsCard {
tabHeight: 44
showIcons: false
model: [({
"text": I18n.tr("Focused Display", "workspace appearance tab")
}), ({
"text": I18n.tr("Unfocused Display(s)", "workspace appearance tab")
})]
"text": I18n.tr("Focused Display", "workspace appearance tab")
}), ({
"text": I18n.tr("Unfocused Display(s)", "workspace appearance tab")
})]
onTabClicked: index => currentIndex = index
Component.onCompleted: Qt.callLater(updateIndicator)
@@ -25,7 +25,7 @@ Item {
SettingsCard {
width: parent.width
iconName: "view_module"
title: I18n.tr("Workspace Settings")
title: I18n.tr("General")
settingKey: "workspaceSettings"
SettingsToggleRow {
+1 -1
View File
@@ -547,7 +547,7 @@ Singleton {
}
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) {
+15 -5
View File
@@ -90,15 +90,20 @@ def json_changed(file_path, new_data):
old_data = normalize_json(file_path)
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():
warn("No en.json to upload")
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:
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 = (
f'--{boundary}\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'Content-Disposition: form-data; name="updating"\r\n\r\n'
f'terms\r\n'
f'{sync_part}'
f'--{boundary}\r\n'
f'Content-Disposition: form-data; name="file"; filename="en.json"\r\n'
f'Content-Type: application/json\r\n\r\n'
@@ -253,7 +259,7 @@ def save_sync_state():
def main():
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]
@@ -290,6 +296,10 @@ def main():
elif command == "sync":
api_token = get_env_or_error('POEDITOR_API_TOKEN')
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()
@@ -310,8 +320,8 @@ def main():
strings_changed = json.dumps(current_en, sort_keys=True) != json.dumps(staged_en, sort_keys=True)
if strings_changed:
upload_source_strings(api_token, project_id)
if strings_changed or prune:
upload_source_strings(api_token, project_id, prune)
else:
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.
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
(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:
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 = []
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):
translations = defaultdict(lambda: {'contexts': set(), 'occurrences': []})
translations = defaultdict(lambda: {
'contexts': set(),
'real_contexts': defaultdict(list),
'occurrences': [],
'plain_occurrences': []
})
qstr_patterns = [
(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 = [
(
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 match in pattern.finditer(line):
term = decode_string_literal(match.group(1), quote)
translations[term]['occurrences'].append({
'file': str(relative_path),
'line': line_num
})
occ = {'file': str(relative_path), 'line': line_num}
translations[term]['occurrences'].append(occ)
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 = []
for pattern, quote in i18n_context_patterns:
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)
context = decode_string_literal(match.group(2), quote)
occ = {'file': str(relative_path), 'line': line_num}
translations[term]['contexts'].add(context)
translations[term]['occurrences'].append({
'file': str(relative_path),
'line': line_num
})
translations[term]['occurrences'].append(occ)
translations[term]['plain_occurrences'].append(occ)
context_spans.append(match.span())
for pattern, quote in i18n_simple_patterns:
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
term = decode_string_literal(match.group(1), quote)
translations[term]['occurrences'].append({
'file': str(relative_path),
'line': line_num
})
occ = {'file': str(relative_path), 'line': line_num}
translations[term]['occurrences'].append(occ)
translations[term]['plain_occurrences'].append(occ)
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):
poeditor_data = []
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']:
ref = f"{occ['file']}:{occ['line']}"
references.append(ref)
poeditor_data.append({
"term": term,
"context": term,
"reference": ", ".join(references),
"comment": comment,
"tags": area_tags(data['plain_occurrences'])
})
contexts = sorted(data['contexts']) if data['contexts'] else []
comment = " | ".join(contexts) if contexts else ""
entry = {
"term": term,
"context": term,
"reference": ", ".join(references),
"comment": comment
}
poeditor_data.append(entry)
for context in sorted(data['real_contexts']):
references = [f"{occ['file']}:{occ['line']}" for occ in data['real_contexts'][context]]
poeditor_data.append({
"term": term,
"context": context,
"reference": ", ".join(references),
"comment": "",
"tags": area_tags(data['real_contexts'][context])
})
return poeditor_data
def create_template_json(translations):
template_data = []
for term, data in sorted(translations.items()):
contexts = sorted(data['contexts']) if data['contexts'] else []
context_str = " | ".join(contexts) if contexts else ""
entry = {
"term": term,
return [
{
"term": entry["term"],
"translation": "",
"context": context_str,
"context": entry["context"],
"reference": "",
"comment": ""
"comment": entry["comment"]
}
template_data.append(entry)
return template_data
for entry in create_poeditor_json(translations)
]
def main():
script_dir = Path(__file__).parent
@@ -142,6 +181,8 @@ def main():
print(f" - Unique strings: {len(translations)}")
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" - 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" - 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