Quickshell qstr seems not to be working, trying to implement custom translation

Add Chinese (zh_CN) translations for Quickshell interface and settings
This commit is contained in:
月月
2025-06-17 12:29:21 +08:00
parent 54dfad1d5b
commit b32734b9f5
50 changed files with 1324 additions and 187 deletions
+28 -27
View File
@@ -4,6 +4,7 @@ pragma ComponentBehavior: Bound
import "root:/modules/common/functions/string_utils.js" as StringUtils
import "root:/modules/common/functions/object_utils.js" as ObjectUtils
import "root:/modules/common"
import "root:/services/"
import Quickshell;
import Quickshell.Io;
import Qt.labs.platform
@@ -53,14 +54,14 @@ Singleton {
"gemini-2.0-flash-search": {
"name": "Gemini 2.0 Flash (Search)",
"icon": "google-gemini-symbolic",
"description": qsTr("Online | Google's model\nGives up-to-date information with search."),
"description": Translation.tr("Online | Google's model\nGives up-to-date information with search."),
"homepage": "https://aistudio.google.com",
"endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent",
"model": "gemini-2.0-flash",
"requires_key": true,
"key_id": "gemini",
"key_get_link": "https://aistudio.google.com/app/apikey",
"key_get_description": qsTr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"),
"key_get_description": Translation.tr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"),
"api_format": "gemini",
"tools": [
{
@@ -71,14 +72,14 @@ Singleton {
"gemini-2.0-flash-tools": {
"name": "Gemini 2.0 Flash (Tools)",
"icon": "google-gemini-symbolic",
"description": qsTr("Experimental | Online | Google's model\nCan do a little more but doesn't search quickly"),
"description": Translation.tr("Experimental | Online | Google's model\nCan do a little more but doesn't search quickly"),
"homepage": "https://aistudio.google.com",
"endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:streamGenerateContent",
"model": "gemini-2.0-flash",
"requires_key": true,
"key_id": "gemini",
"key_get_link": "https://aistudio.google.com/app/apikey",
"key_get_description": qsTr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"),
"key_get_description": Translation.tr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"),
"api_format": "gemini",
"tools": [
{
@@ -116,14 +117,14 @@ Singleton {
"gemini-2.5-flash-search": {
"name": "Gemini 2.5 Flash (Search)",
"icon": "google-gemini-symbolic",
"description": qsTr("Online | Google's model\nGives up-to-date information with search."),
"description": Translation.tr("Online | Google's model\nGives up-to-date information with search."),
"homepage": "https://aistudio.google.com",
"endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:streamGenerateContent",
"model": "gemini-2.5-flash-preview-05-20",
"requires_key": true,
"key_id": "gemini",
"key_get_link": "https://aistudio.google.com/app/apikey",
"key_get_description": qsTr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"),
"key_get_description": Translation.tr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"),
"api_format": "gemini",
"tools": [
{
@@ -134,14 +135,14 @@ Singleton {
"gemini-2.5-flash-tools": {
"name": "Gemini 2.5 Flash (Tools)",
"icon": "google-gemini-symbolic",
"description": qsTr("Experimental | Online | Google's model\nCan do a little more but doesn't search quickly"),
"description": Translation.tr("Experimental | Online | Google's model\nCan do a little more but doesn't search quickly"),
"homepage": "https://aistudio.google.com",
"endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:streamGenerateContent",
"model": "gemini-2.5-flash-preview-05-20",
"requires_key": true,
"key_id": "gemini",
"key_get_link": "https://aistudio.google.com/app/apikey",
"key_get_description": qsTr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"),
"key_get_description": Translation.tr("**Pricing**: free. Data used for training.\n\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key"),
"api_format": "gemini",
"tools": [
{
@@ -179,26 +180,26 @@ Singleton {
"openrouter-llama4-maverick": {
"name": "Llama 4 Maverick",
"icon": "ollama-symbolic",
"description": StringUtils.format(qsTr("Online via {0} | {1}'s model"), "OpenRouter", "Meta"),
"description": StringUtils.format(Translation.tr("Online via {0} | {1}'s model"), "OpenRouter", "Meta"),
"homepage": "https://openrouter.ai/meta-llama/llama-4-maverick:free",
"endpoint": "https://openrouter.ai/api/v1/chat/completions",
"model": "meta-llama/llama-4-maverick:free",
"requires_key": true,
"key_id": "openrouter",
"key_get_link": "https://openrouter.ai/settings/keys",
"key_get_description": qsTr("**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key"),
"key_get_description": Translation.tr("**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key"),
},
"openrouter-deepseek-r1": {
"name": "DeepSeek R1",
"icon": "deepseek-symbolic",
"description": StringUtils.format(qsTr("Online via {0} | {1}'s model"), "OpenRouter", "DeepSeek"),
"description": StringUtils.format(Translation.tr("Online via {0} | {1}'s model"), "OpenRouter", "DeepSeek"),
"homepage": "https://openrouter.ai/deepseek/deepseek-r1:free",
"endpoint": "https://openrouter.ai/api/v1/chat/completions",
"model": "deepseek/deepseek-r1:free",
"requires_key": true,
"key_id": "openrouter",
"key_get_link": "https://openrouter.ai/settings/keys",
"key_get_description": qsTr("**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key"),
"key_get_description": Translation.tr("**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\n\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key"),
},
}
property var modelList: Object.keys(root.models)
@@ -244,7 +245,7 @@ Singleton {
root.models[safeModelName] = {
"name": guessModelName(model),
"icon": guessModelLogo(model),
"description": StringUtils.format(qsTr("Local Ollama model | {0}"), model),
"description": StringUtils.format(Translation.tr("Local Ollama model | {0}"), model),
"homepage": `https://ollama.com/library/${model}`,
"endpoint": "http://localhost:11434/v1/chat/completions",
"model": model,
@@ -283,8 +284,8 @@ Singleton {
function addApiKeyAdvice(model) {
root.addMessage(
StringUtils.format(qsTr('To set an API key, pass it with the command\n\nTo view the key, pass "get" with the command<br/>\n\n### For {0}:\n\n**Link**: {1}\n\n{2}'),
model.name, model.key_get_link, model.key_get_description ?? qsTr("<i>No further instruction provided</i>")),
StringUtils.format(Translation.tr('To set an API key, pass it with the command\n\nTo view the key, pass "get" with the command<br/>\n\n### For {0}:\n\n**Link**: {1}\n\n{2}'),
model.name, model.key_get_link, model.key_get_description ?? Translation.tr("<i>No further instruction provided</i>")),
Ai.interfaceRole
);
}
@@ -314,7 +315,7 @@ Singleton {
}
}
} else {
if (feedback) root.addMessage(qsTr("Invalid model. Supported: \n```\n") + modelList.join("\n```\n```\n"), Ai.interfaceRole) + "\n```"
if (feedback) root.addMessage(Translation.tr("Invalid model. Supported: \n```\n") + modelList.join("\n```\n```\n"), Ai.interfaceRole) + "\n```"
}
}
@@ -324,18 +325,18 @@ Singleton {
function setTemperature(value) {
if (value == NaN || value < 0 || value > 2) {
root.addMessage(qsTr("Temperature must be between 0 and 2"), Ai.interfaceRole);
root.addMessage(Translation.tr("Temperature must be between 0 and 2"), Ai.interfaceRole);
return;
}
PersistentStateManager.setState("ai.temperature", value);
root.temperature = value;
root.addMessage(StringUtils.format(qsTr("Temperature set to {0}"), value), Ai.interfaceRole);
root.addMessage(StringUtils.format(Translation.tr("Temperature set to {0}"), value), Ai.interfaceRole);
}
function setApiKey(key) {
const model = models[currentModelId];
if (!model.requires_key) {
root.addMessage(StringUtils.format(qsTr("{0} does not require an API key"), model.name), Ai.interfaceRole);
root.addMessage(StringUtils.format(Translation.tr("{0} does not require an API key"), model.name), Ai.interfaceRole);
return;
}
if (!key || key.length === 0) {
@@ -344,7 +345,7 @@ Singleton {
return;
}
KeyringStorage.setNestedField(["apiKeys", model.key_id], key.trim());
root.addMessage(StringUtils.format(qsTr("API key set for {0}"), model.name, Ai.interfaceRole));
root.addMessage(StringUtils.format(Translation.tr("API key set for {0}"), model.name, Ai.interfaceRole));
}
function printApiKey() {
@@ -352,17 +353,17 @@ Singleton {
if (model.requires_key) {
const key = root.apiKeys[model.key_id];
if (key) {
root.addMessage(StringUtils.format(qsTr("API key:\n\n```txt\n{0}\n```"), key), Ai.interfaceRole);
root.addMessage(StringUtils.format(Translation.tr("API key:\n\n```txt\n{0}\n```"), key), Ai.interfaceRole);
} else {
root.addMessage(StringUtils.format(qsTr("No API key set for {0}"), model.name), Ai.interfaceRole);
root.addMessage(StringUtils.format(Translation.tr("No API key set for {0}"), model.name), Ai.interfaceRole);
}
} else {
root.addMessage(StringUtils.format(qsTr("{0} does not require an API key"), model.name), Ai.interfaceRole);
root.addMessage(StringUtils.format(Translation.tr("{0} does not require an API key"), model.name), Ai.interfaceRole);
}
}
function printTemperature() {
root.addMessage(StringUtils.format(qsTr("Temperature: {0}"), root.temperature), Ai.interfaceRole);
root.addMessage(StringUtils.format(Translation.tr("Temperature: {0}"), root.temperature), Ai.interfaceRole);
}
function clearMessages() {
@@ -701,7 +702,7 @@ Singleton {
root.setModel("gemini-2.0-flash-search", false);
root.postResponseHook = () => root.setModel("gemini-2.0-flash-tools", false);
}
addFunctionOutputMessage(name, qsTr("Switched to search mode. Continue with the user's request."))
addFunctionOutputMessage(name, Translation.tr("Switched to search mode. Continue with the user's request."))
requester.makeRequest();
} else if (name === "get_shell_config") {
const configJson = ObjectUtils.toPlainObject(ConfigOptions)
@@ -709,7 +710,7 @@ Singleton {
requester.makeRequest();
} else if (name === "set_shell_config") {
if (!args.key || !args.value) {
addFunctionOutputMessage(name, qsTr("Invalid arguments. Must provide `key` and `value`."));
addFunctionOutputMessage(name, Translation.tr("Invalid arguments. Must provide `key` and `value`."));
return;
}
const key = args.key;
@@ -717,7 +718,7 @@ Singleton {
ConfigLoader.setLiveConfigValue(key, value);
ConfigLoader.saveConfig();
}
else root.addMessage(qsTr("Unknown function call: {0}"), "assistant");
else root.addMessage(Translation.tr("Unknown function call: {0}"), "assistant");
}
}
+13 -12
View File
@@ -2,6 +2,7 @@ pragma Singleton
pragma ComponentBehavior: Bound
import "root:/modules/common"
import "root:/services/"
import Quickshell;
import Quickshell.Io;
import Qt.labs.platform
@@ -16,18 +17,18 @@ Singleton {
signal tagSuggestion(string query, var suggestions)
property string failMessage: qsTr("That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number")
property string failMessage: Translation.tr("That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number")
property var responses: []
property int runningRequests: 0
property var defaultUserAgent: ConfigOptions?.networking?.userAgent || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
property var providerList: Object.keys(providers).filter(provider => provider !== "system" && providers[provider].api)
property var providers: {
"system": { "name": qsTr("System") },
"system": { "name": Translation.tr("System") },
"yandere": {
"name": "yande.re",
"url": "https://yande.re",
"api": "https://yande.re/post.json",
"description": qsTr("All-rounder | Good quality, decent quantity"),
"description": Translation.tr("All-rounder | Good quality, decent quantity"),
"mapFunc": (response) => {
return response.map(item => {
return {
@@ -61,7 +62,7 @@ Singleton {
"name": "Konachan",
"url": "https://konachan.com",
"api": "https://konachan.com/post.json",
"description": qsTr("For desktop wallpapers | Good quality"),
"description": Translation.tr("For desktop wallpapers | Good quality"),
"mapFunc": (response) => {
return response.map(item => {
return {
@@ -95,7 +96,7 @@ Singleton {
"name": "Zerochan",
"url": "https://www.zerochan.net",
"api": "https://www.zerochan.net/?json",
"description": qsTr("Clean stuff | Excellent quality, no NSFW"),
"description": Translation.tr("Clean stuff | Excellent quality, no NSFW"),
"mapFunc": (response) => {
response = response.items
return response.map(item => {
@@ -122,7 +123,7 @@ Singleton {
"name": "Danbooru",
"url": "https://danbooru.donmai.us",
"api": "https://danbooru.donmai.us/posts.json",
"description": qsTr("The popular one | Best quantity, but quality can vary wildly"),
"description": Translation.tr("The popular one | Best quantity, but quality can vary wildly"),
"mapFunc": (response) => {
return response.map(item => {
return {
@@ -157,7 +158,7 @@ Singleton {
"name": "Gelbooru",
"url": "https://gelbooru.com",
"api": "https://gelbooru.com/index.php?page=dapi&s=post&q=index&json=1",
"description": qsTr("The hentai one | Great quantity, a lot of NSFW, quality varies wildly"),
"description": Translation.tr("The hentai one | Great quantity, a lot of NSFW, quality varies wildly"),
"mapFunc": (response) => {
response = response.post
return response.map(item => {
@@ -192,7 +193,7 @@ Singleton {
"name": "waifu.im",
"url": "https://waifu.im",
"api": "https://api.waifu.im/search",
"description": qsTr("Waifus only | Excellent quality, limited quantity"),
"description": Translation.tr("Waifus only | Excellent quality, limited quantity"),
"mapFunc": (response) => {
response = response.images
return response.map(item => {
@@ -223,7 +224,7 @@ Singleton {
"name": "Alcy",
"url": "https://t.alcy.cc",
"api": "https://t.alcy.cc/",
"description": qsTr("Large images | God tier quality, no NSFW."),
"description": Translation.tr("Large images | God tier quality, no NSFW."),
"fixedTags": [
{
"name": "ycy",
@@ -287,10 +288,10 @@ Singleton {
provider = provider.toLowerCase()
if (providerList.indexOf(provider) !== -1) {
PersistentStateManager.setState("booru.provider", provider)
root.addSystemMessage(qsTr("Provider set to ") + providers[provider].name
+ (provider == "zerochan" ? qsTr(". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!") : ""))
root.addSystemMessage(Translation.tr("Provider set to ") + providers[provider].name
+ (provider == "zerochan" ? Translation.tr(". Notes for Zerochan:\n- You must enter a color\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!") : ""))
} else {
root.addSystemMessage(qsTr("Invalid API provider. Supported: \n- ") + providerList.join("\n- "))
root.addSystemMessage(Translation.tr("Invalid API provider. Supported: \n- ") + providerList.join("\n- "))
}
}
+3 -2
View File
@@ -4,6 +4,7 @@ pragma ComponentBehavior: Bound
// From https://github.com/caelestia-dots/shell/ (`quickshell` branch) with modifications.
// License: GPLv3
import "root:/services/"
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
@@ -140,13 +141,13 @@ Singleton {
GlobalShortcut {
name: "brightnessIncrease"
description: qsTr("Increase brightness")
description: Translation.tr("Increase brightness")
onPressed: root.increaseBrightness()
}
GlobalShortcut {
name: "brightnessDecrease"
description: qsTr("Decrease brightness")
description: Translation.tr("Decrease brightness")
onPressed: root.decreaseBrightness()
}
}
+4 -4
View File
@@ -44,7 +44,7 @@ Singleton {
} catch (e) {
console.error("[ConfigLoader] Error reading file:", e);
console.log("[ConfigLoader] File content was:", fileContent);
Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration failed to load")}" "${root.filePath}"`)
Hyprland.dispatch(`exec notify-send "${Translation.tr("Shell configuration failed to load")}" "${root.filePath}"`)
return;
}
@@ -105,7 +105,7 @@ Singleton {
} else {
root.applyConfig(configFileView.text())
if (!root.preventNextNotification) {
// Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration reloaded")}" "${root.filePath}"`)
// Hyprland.dispatch(`exec notify-send "${Translation.tr("Shell configuration reloaded")}" "${root.filePath}"`)
} else {
root.preventNextNotification = false;
}
@@ -129,9 +129,9 @@ Singleton {
if(error == FileViewError.FileNotFound) {
console.log("[ConfigLoader] File not found, creating new file.")
root.saveConfig()
Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration created")}" "${root.filePath}"`)
Hyprland.dispatch(`exec notify-send "${Translation.tr("Shell configuration created")}" "${root.filePath}"`)
} else {
Hyprland.dispatch(`exec notify-send "${qsTr("Shell configuration failed to load")}" "${root.filePath}"`)
Hyprland.dispatch(`exec notify-send "${Translation.tr("Shell configuration failed to load")}" "${root.filePath}"`)
}
}
}
@@ -20,14 +20,14 @@ Singleton {
property var properties: {
"application": "illogical-impulse",
"explanation": qsTr("For storing API keys and other sensitive information"),
"explanation": Translation.tr("For storing API keys and other sensitive information"),
}
property var propertiesAsArgs: Object.keys(root.properties).reduce(
function(arr, key) {
return arr.concat([key, root.properties[key]]);
}, []
)
property string keyringLabel: StringUtils.format(qsTr("{0} Safe Storage"), "illogical-impulse")
property string keyringLabel: StringUtils.format(Translation.tr("{0} Safe Storage"), "illogical-impulse")
function setNestedField(path, value) {
if (!root.keyringData) root.keyringData = {};
@@ -4,6 +4,7 @@ pragma ComponentBehavior: Bound
// From https://git.outfoxxed.me/outfoxxed/nixnew
// It does not have a license, but the author is okay with redistribution.
import "root:/services/"
import QtQml.Models
import QtQuick
import Quickshell
@@ -85,9 +86,9 @@ Singleton {
this.activeTrack = {
uniqueId: this.activePlayer?.uniqueId ?? 0,
artUrl: this.activePlayer?.trackArtUrl ?? "",
title: this.activePlayer?.trackTitle || qsTr("Unknown Title"),
artist: this.activePlayer?.trackArtist || qsTr("Unknown Artist"),
album: this.activePlayer?.trackAlbum || qsTr("Unknown Album"),
title: this.activePlayer?.trackTitle || Translation.tr("Unknown Title"),
artist: this.activePlayer?.trackArtist || Translation.tr("Unknown Artist"),
album: this.activePlayer?.trackAlbum || Translation.tr("Unknown Album"),
};
this.trackChanged(__reverse);
+170
View File
@@ -0,0 +1,170 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import "root:/modules/common/"
Singleton {
id: root
property var translations: ({})
property string currentLanguage: "en_US"
property var availableLanguages: ["en_US"]
property bool isScanning: false
property bool isLoading: false
Process {
id: scanLanguagesProcess
command: ["find", Qt.resolvedUrl(Directories.config + "/quickshell/translations/").toString().replace("file://", ""), "-name", "*.json", "-exec", "basename", "{}", ".json", ";"]
running: false
stdout: SplitParser {
onRead: data => {
if (data.trim().length === 0) return
var files = data.trim().split('\n')
for (var i = 0; i < files.length; i++) {
var lang = files[i].trim()
if (lang.length > 0 && root.availableLanguages.indexOf(lang) === -1) {
root.availableLanguages.push(lang)
}
}
}
}
onExited: (exitCode, exitStatus) => {
root.isScanning = false
if (exitCode !== 0) {
root.availableLanguages = ["en_US"]
}
root.loadTranslations()
}
}
FileView {
id: translationFileView
onLoaded: {
var textContent = ""
try {
textContent = text()
} catch (e) {
root.translations = {}
root.isLoading = false
return
}
if (textContent.length === 0) {
root.translations = {}
root.isLoading = false
return
}
try {
var jsonData = JSON.parse(textContent)
root.translations = jsonData
root.isLoading = false
} catch (e) {
root.translations = {}
root.isLoading = false
}
}
onLoadFailed: (error) => {
root.translations = {}
root.isLoading = false
}
}
function detectSystemLanguage() {
var locale = Qt.locale().name
return locale
}
function getLanguageCode() {
var configLang = "auto"
try {
configLang = ConfigOptions.language.ui
} catch (e) {
configLang = "auto"
}
if (configLang === "auto") {
return detectSystemLanguage()
} else {
if (root.availableLanguages.indexOf(configLang) !== -1) {
return configLang
} else {
return detectSystemLanguage()
}
}
}
function loadTranslations() {
if (root.isScanning) {
return
}
var targetLang = getLanguageCode()
root.currentLanguage = targetLang
// Use empty translations for English (default language)
if (targetLang === "en_US" || targetLang === "en") {
root.translations = {}
return
}
// Check if target language is available
if (root.availableLanguages.indexOf(targetLang) === -1) {
root.currentLanguage = "en_US"
root.translations = {}
return
}
// Load translation file
root.isLoading = true
var translationsPath = Qt.resolvedUrl(Directories.config + "/quickshell/translations/" + targetLang + ".json")
translationFileView.path = translationsPath
}
function tr(text) {
if (!text) {
return ""
}
var key = text.toString()
if (root.isLoading) {
return key
}
if (root.currentLanguage === "en_US" || root.currentLanguage === "en" || !root.translations) {
return key
}
if (root.translations.hasOwnProperty(key)) {
var translation = root.translations[key]
if (translation && translation.toString().trim().length > 0) {
return translation.toString()
} else {
return translation.toString()
}
}
return key // Fallback to key name
}
function reloadTranslations() {
root.scanLanguages()
}
function scanLanguages() {
var translationsDir = Qt.resolvedUrl(Directories.config + "/quickshell/translations/").toString().replace("file://", "")
root.isScanning = true
scanLanguagesProcess.running = true
}
Component.onCompleted: {
root.scanLanguages()
}
}