From 7baad907f7a28a8843635f229da20ee2e3e8b4d8 Mon Sep 17 00:00:00 2001 From: maylton Date: Fri, 24 Jul 2026 09:27:36 -0300 Subject: [PATCH 1/6] tools(i18n): add local AI translation helper --- scripts/i18n/translate_ts_with_ollama.py | 508 ++++++++++++++++++ .../sleex/translations/glossary_pt_BR.json | 31 ++ 2 files changed, 539 insertions(+) create mode 100755 scripts/i18n/translate_ts_with_ollama.py create mode 100644 src/share/sleex/translations/glossary_pt_BR.json diff --git a/scripts/i18n/translate_ts_with_ollama.py b/scripts/i18n/translate_ts_with_ollama.py new file mode 100755 index 0000000..8fbef26 --- /dev/null +++ b/scripts/i18n/translate_ts_with_ollama.py @@ -0,0 +1,508 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +import json +import re +import shutil +import sys +import time +from pathlib import Path +from typing import Any +from urllib import error, request + +from lxml import etree + + +DEFAULT_API_URL = "http://127.0.0.1:11434/api/chat" + +# Qt placeholders such as %1, %2, %L1 and %n. +PLACEHOLDER_RE = re.compile( + r"%(?:L?\d+|n)|\$\{[^{}]+\}|\{[A-Za-z_][^{}]*\}" +) + +# HTML-like tags embedded inside translation strings. +TAG_RE = re.compile(r"]*>") + + +def parse_arguments() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Pre-translate a Qt TS catalog using a local Ollama model." + ) + + parser.add_argument( + "ts_file", + type=Path, + help="Qt .ts translation file", + ) + parser.add_argument( + "--model", + default="qwen3:8b", + help="Ollama model name", + ) + parser.add_argument( + "--api-url", + default=DEFAULT_API_URL, + help="Ollama chat API URL", + ) + parser.add_argument( + "--glossary", + type=Path, + help="Optional JSON glossary", + ) + parser.add_argument( + "--batch-size", + type=int, + default=10, + help="Number of strings sent in each request", + ) + parser.add_argument( + "--limit", + type=int, + default=0, + help="Translate at most this many strings; 0 means no limit", + ) + parser.add_argument( + "--overwrite", + action="store_true", + help="Replace existing unfinished translations", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Request translations without modifying the TS file", + ) + + return parser.parse_args() + + +def load_glossary(path: Path | None) -> dict[str, str]: + if path is None: + return {} + + if not path.exists(): + raise FileNotFoundError(f"Glossary not found: {path}") + + with path.open("r", encoding="utf-8") as file: + data = json.load(file) + + if not isinstance(data, dict): + raise ValueError("The glossary must be a JSON object.") + + return { + str(source): str(translation) + for source, translation in data.items() + } + + +def element_text(element: etree._Element | None) -> str: + if element is None: + return "" + + return "".join(element.itertext()) + + +def collect_candidates( + tree: etree._ElementTree, + overwrite: bool, +) -> list[dict[str, Any]]: + candidates: list[dict[str, Any]] = [] + + for context in tree.getroot().findall("context"): + context_name = context.findtext("name", default="") + + for message in context.findall("message"): + # Plural forms require separate handling and human review. + if message.get("numerus") == "yes": + continue + + source_element = message.find("source") + if source_element is None: + continue + + source = element_text(source_element) + + if not source.strip(): + continue + + translation_element = message.find("translation") + + if translation_element is None: + translation_element = etree.SubElement( + message, + "translation", + ) + translation_element.set("type", "unfinished") + + translation_type = translation_element.get("type", "") + + if translation_type in {"vanished", "obsolete"}: + continue + + current_translation = element_text( + translation_element + ) + + if current_translation.strip() and not overwrite: + continue + + locations = [] + + for location in message.findall("location"): + filename = location.get("filename", "") + line = location.get("line", "") + + if filename: + locations.append(f"{filename}:{line}") + + candidates.append( + { + "id": len(candidates), + "source": source, + "context": context_name, + "locations": locations, + "_translation_element": translation_element, + } + ) + + return candidates + + +def extract_tokens(text: str) -> list[str]: + return sorted(PLACEHOLDER_RE.findall(text)) + + +def extract_tags(text: str) -> list[str]: + return TAG_RE.findall(text) + + +def validate_translation( + source: str, + translation: str, +) -> tuple[bool, str]: + if not translation.strip(): + return False, "empty translation" + + if extract_tokens(source) != extract_tokens(translation): + return False, "placeholders were changed" + + if extract_tags(source) != extract_tags(translation): + return False, "HTML/XML tags were changed" + + if source.count("\n") != translation.count("\n"): + return False, "explicit line breaks were changed" + + return True, "" + + +def build_schema() -> dict[str, Any]: + return { + "type": "object", + "properties": { + "translations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + }, + "translation": { + "type": "string", + }, + }, + "required": [ + "id", + "translation", + ], + }, + } + }, + "required": ["translations"], + } + + +def request_translation( + api_url: str, + model: str, + items: list[dict[str, Any]], + glossary: dict[str, str], +) -> dict[int, str]: + clean_items = [] + + for item in items: + clean_items.append( + { + "id": item["id"], + "source": item["source"], + "context": item["context"], + "locations": item["locations"], + } + ) + + system_prompt = f""" +You are translating a Linux desktop interface from English +to natural Brazilian Portuguese. + +Rules: +- Return only data matching the supplied JSON schema. +- Translate only user-facing text. +- Use concise and natural Brazilian Portuguese. +- Preserve Qt placeholders exactly, including %1, %2, %L1 and %n. +- Preserve variables such as ${{name}} and {{name}} exactly. +- Preserve HTML/XML tags exactly. +- Preserve explicit line breaks. +- Do not translate commands, paths or file names. +- Do not translate product and technology names such as: + Sleex, AxOS, Hyprland, Quickshell, Wayland, PipeWire, + KDE, Qt, Wi-Fi, Bluetooth, GitHub and Ollama. +- Use the component context to resolve ambiguous words. +- Do not add explanations or translator notes. + +Preferred glossary: +{json.dumps(glossary, ensure_ascii=False, indent=2)} +""".strip() + + user_prompt = json.dumps( + {"items": clean_items}, + ensure_ascii=False, + indent=2, + ) + + payload = { + "model": model, + "messages": [ + { + "role": "system", + "content": system_prompt, + }, + { + "role": "user", + "content": user_prompt, + }, + ], + "stream": False, + "think": False, + "format": build_schema(), + "options": { + "temperature": 0.0, + }, + "keep_alive": "10m", + } + + encoded_payload = json.dumps(payload).encode("utf-8") + + http_request = request.Request( + api_url, + data=encoded_payload, + headers={"Content-Type": "application/json"}, + method="POST", + ) + + last_exception: Exception | None = None + + for attempt in range(1, 4): + try: + with request.urlopen( + http_request, + timeout=600, + ) as response: + response_data = json.load(response) + + content = response_data["message"]["content"] + parsed_content = json.loads(content) + + translations: dict[int, str] = {} + + for result in parsed_content["translations"]: + translations[int(result["id"])] = str( + result["translation"] + ) + + return translations + + except ( + error.URLError, + TimeoutError, + KeyError, + ValueError, + json.JSONDecodeError, + ) as exception: + last_exception = exception + + print( + f"Request attempt {attempt}/3 failed: " + f"{exception}", + file=sys.stderr, + ) + + time.sleep(attempt * 2) + + raise RuntimeError( + f"Ollama request failed after three attempts: " + f"{last_exception}" + ) + + +def save_tree( + tree: etree._ElementTree, + path: Path, +) -> None: + doctype = tree.docinfo.doctype or "" + + tree.write( + str(path), + encoding="utf-8", + xml_declaration=True, + pretty_print=False, + doctype=doctype, + ) + + +def main() -> int: + args = parse_arguments() + + if not args.ts_file.exists(): + print( + f"TS file not found: {args.ts_file}", + file=sys.stderr, + ) + return 1 + + if args.batch_size < 1: + print( + "--batch-size must be at least 1", + file=sys.stderr, + ) + return 1 + + glossary = load_glossary(args.glossary) + + parser = etree.XMLParser( + remove_blank_text=False, + recover=False, + ) + + tree = etree.parse( + str(args.ts_file), + parser, + ) + + candidates = collect_candidates( + tree, + overwrite=args.overwrite, + ) + + if args.limit > 0: + candidates = candidates[: args.limit] + + if not candidates: + print("No untranslated strings found.") + return 0 + + print( + f"Selected {len(candidates)} strings " + f"for translation." + ) + + if not args.dry_run: + timestamp = time.strftime("%Y%m%d-%H%M%S") + backup_path = args.ts_file.with_name( + f"{args.ts_file.name}.backup-{timestamp}" + ) + + shutil.copy2( + args.ts_file, + backup_path, + ) + + print(f"Backup created: {backup_path}") + + accepted = 0 + rejected = 0 + + for start in range( + 0, + len(candidates), + args.batch_size, + ): + batch = candidates[ + start : start + args.batch_size + ] + + print( + f"\nBatch {start + 1}-" + f"{start + len(batch)}" + ) + + translations = request_translation( + api_url=args.api_url, + model=args.model, + items=batch, + glossary=glossary, + ) + + for item in batch: + item_id = item["id"] + source = item["source"] + translated = translations.get(item_id, "") + + valid, reason = validate_translation( + source, + translated, + ) + + if not valid: + rejected += 1 + + print( + f"[REJECTED] {source!r}: {reason}" + ) + continue + + accepted += 1 + + print( + f"[OK] {source!r} -> " + f"{translated!r}" + ) + + if args.dry_run: + continue + + translation_element = item[ + "_translation_element" + ] + + translation_element.clear() + translation_element.set( + "type", + "unfinished", + ) + translation_element.text = translated + + if not args.dry_run: + save_tree( + tree, + args.ts_file, + ) + + print( + f"\nFinished: {accepted} accepted, " + f"{rejected} rejected." + ) + + if args.dry_run: + print("Dry run: no files were modified.") + else: + print( + "Translations remain marked as unfinished " + "for human review." + ) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/share/sleex/translations/glossary_pt_BR.json b/src/share/sleex/translations/glossary_pt_BR.json new file mode 100644 index 0000000..3ef9108 --- /dev/null +++ b/src/share/sleex/translations/glossary_pt_BR.json @@ -0,0 +1,31 @@ +{ + "Settings": "Configurações", + "Dashboard": "Painel", + "Overview": "Visão geral", + "Workspace": "Área de trabalho", + "Workspaces": "Áreas de trabalho", + "Wallpaper": "Papel de parede", + "Lock screen": "Tela de bloqueio", + "Clipboard": "Área de transferência", + "Power profile": "Perfil de energia", + "Power settings": "Configurações de energia", + "Do not disturb": "Não perturbe", + "Quick settings": "Configurações rápidas", + "Notifications": "Notificações", + "Brightness": "Brilho", + "Volume": "Volume", + "Network": "Rede", + "Connect": "Conectar", + "Disconnect": "Desconectar", + "Connected": "Conectado", + "Disconnected": "Desconectado", + "Shutdown": "Desligar", + "Restart": "Reiniciar", + "Suspend": "Suspender", + "Log out": "Encerrar sessão", + "Cancel": "Cancelar", + "Apply": "Aplicar", + "Save": "Salvar", + "Search": "Pesquisar", + "No results found": "Nenhum resultado encontrado" +} From b6af945877a79cbb46c4471ba41bdb4cdef8cd76 Mon Sep 17 00:00:00 2001 From: Maylton Fernandes Date: Fri, 24 Jul 2026 11:05:43 -0300 Subject: [PATCH 2/6] feat(i18n): add Brazilian Portuguese translation --- src/share/sleex/translations/sleex_pt_BR.ts | 768 ++++++++++++++++++++ 1 file changed, 768 insertions(+) create mode 100644 src/share/sleex/translations/sleex_pt_BR.ts diff --git a/src/share/sleex/translations/sleex_pt_BR.ts b/src/share/sleex/translations/sleex_pt_BR.ts new file mode 100644 index 0000000..80cd8cc --- /dev/null +++ b/src/share/sleex/translations/sleex_pt_BR.ts @@ -0,0 +1,768 @@ + + + + + ActiveWindow + + + Desktop + Área de Trabalho + + + + Workspace + Espaço de Trabalho + + + + AudioDeviceSelectorButton + + + Input + Entrada + + + + Output + Saída + + + + Unknown + Desconhecido + + + + Bar + + + Scroll to change brightness + Rolle para alterar brilho + + + + Scroll to change volume + Rolle para alterar volume + + + + BatteryPopup + + + Critical Battery + Bateria crítica + + + + Low Battery + Bateria baixa + + + + %1% battery remaining. + %1% da bateria restante. + + + + Power Saver Mode + Modo Economia de Energia + + + + Close + Fechar + + + + BatteryWarningOverlay + + + Critical Battery + Bateria Crítica + + + + Low Battery + Bateria Baixa + + + + %1% battery remaining. + %1% de bateria restante. + + + + Power Saver Mode + Modo Economia de Energia + + + + Close + Fechar + + + + Bluetooth + + + %1 device%2 available + %1 dispositivo%2 disponível + + + + (%1 connected) + (%1 conectado) + + + + (Connected) + (Conectado) + + + + (Paired) + (Parado) + + + + BluetoothToggle + + + {0} | Right-click to configure + {0} | Clique com o botão direito para configurar + + + + Bluetooth + Bluetooth + + + + Brightness + + + Increase brightness + Aumentar brilho + + + + Decrease brightness + Diminuir brilho + + + + CalendarAdd + + + Add a new event. + Adicionar um novo evento. + + + + Title + Título + + + + Date + Data + + + + All Day Event + Evento de todo o dia + + + + Validate + Validar + + + + Cancel + Cancelar + + + + + Done + Concluído + + + + CalendarEdit + + + Edit an event. + Editar um evento. + + + + Title + Título + + + + Date + Data + + + + All Day Event + Evento do dia inteiro + + + + Validate + Validar + + + + Cancel + Cancelar + + + + + Done + Concluído + + + + CalendarTimeTable + + + Event + Evento + + + + All day + Todo dia + + + + CalendarWidget + + + Jump to current month + Ir para o mês atual + + + + Cheatsheet + + + Cheat sheet + Folha de atalhos + + + + Toggles cheatsheet on press + Ativa folha de atalhos ao pressionar + + + + Opens cheatsheet on press + Abre folha de atalhos ao pressionar + + + + Closes cheatsheet on press + Fecha a folha de atalhos ao pressionar + + + + ConfigLoader + + + + Shell configuration failed to load + Configuração do shell falhou ao carregar + + + + Shell configuration created + Configuração do shell criada + + + + Dashboard + + + Uptime: {0} + Tempo de atividade: {0} + + + + Reload Hyprland & Quickshell + Recarregar Hyprland & Quickshell + + + + Settings + Configurações + + + + Session + Sessão + + + + Toggles dashboard on press + Alternar painel ao pressionar + + + + Opens dashboard on press + Abrir painel ao pressionar + + + + Closes dashboard on press + Fechar painel ao pressionar + + + + DashboardTabs + + + Home + Início + + + + Todo + Tarefas + + + + Calendar + Calendário + + + + Display + + + Intensity + Intensidade + + + + Schedule + Agenda + + + + Night light start time + Horário de início da luz noturna + + + + Night light end time + Horário de fim da luz noturna + + + + Cancel + Cancelar + + + + Done + Concluído + + + + GameMode + + + Game mode + GameMode + + + + GlobalStates + + + Hold to show workspace numbers, release to show icons + Segure para mostrar números da área de trabalho, solte para mostrar ícones + + + + Lock screen (obviously) + Tela de bloqueio (óbvio) + + + + HomeGithubWidget + + + Loading... + Carregando... + + + + -- + -- + + + + contributions in the last year + contribuições do último ano + + + + HomeUserInfoWidget + + + Welcome, %1! + Bem-vindo, %1! + + + + IdleInhibitor + + + Keep system awake + Mantenha o sistema acordado + + + + KeyringStorage + + + For storing API keys and other sensitive information + Para armazenar chaves de API e outras informações sensíveis + + + + {0} Safe Storage + {0} Armazenamento Seguro + + + + LockSurface + + + -- + -- + + + + Enter password + Insira a senha + + + + MprisController + + + Unknown Title + Título desconhecido + + + + Unknown Artist + Artista desconhecido + + + + Unknown Album + Álbum desconhecido + + + + NetworkToggle + + + {0} | Right-click to configure + {0} | Clique com o botão direito para configurar + + + + Wi-Fi Disabled + Wi-Fi Desativado + + + + NotificationItem + + + Close + Fechar + + + + NotificationList + + + No notifications + Nenhuma notificação + + + + Silent + Silencioso + + + + Clear + Limpar + + + + OnScreenDisplayBrightness + + + Brightness + Brilho + + + + Triggers brightness OSD on press + Ativa o OSD de brilho ao pressionar + + + + Hides brightness OSD on press + Oculta o OSD de brilho ao pressionar + + + + OnScreenDisplayVolume + + + Volume + Volume + + + + Triggers volume OSD on press + Ativa o OSD de volume ao pressionar + + + + Hides volume OSD on press + Oculta o OSD de volume ao pressionar + + + + Overview + + + Toggles overview on press + Ativa a visão geral ao pressionar + + + + Closes overview + Fecha a visão geral + + + + Toggles overview on release + Ativa a visão geral ao soltar + + + + Interrupts possibility of overview being toggled on release. + Interrompe a possibilidade de ativar a visão geral ao soltar. + + + + This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. + Isso é necessário porque GlobalShortcut.onReleased no quickshell determina se você pressiona algo else enquanto segura a tecla. + + + + To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything. + Para garantir que isso funcione consistentemente, use binditn = MODKEYS, catchall em uma submapa automaticamente acionada que inclui tudo. + + + + Toggle clipboard query on overview widget + Alternar consulta de área de transferência no widget de visão geral + + + + Toggle emoji query on overview widget + Alternar consulta de emoji no widget de visão geral + + + + PlayerControlBlank + + + Nothing playing + Nada tocando + + + + No music is currently playing + Nenhuma música está tocando no momento + + + + SelectionDialog + + + Cancel + Cancelar + + + + OK + OK + + + + Session + + + Session + Sessão + + + + Arrow keys to navigate, Enter to select +Esc or click anywhere to cancel + Setas para navegar, Enter para selecionar +Esc ou clique em qualquer lugar para cancelar + + + + Lock + Bloquear + + + + Sleep + Suspender + + + + Logout + Encerrar sessão + + + + Task Manager + Gerenciador de tarefas + + + + Hibernate + Hibernate + + + + Shutdown + Desligar + + + + Reboot + Reiniciar + + + + Reboot to firmware settings + Reiniciar para configurações do firmware + + + + Toggles session screen on press + Ativa a tela de sessão ao pressionar + + + + Opens session screen on press + Abre a tela de sessão ao pressionar + + + + TodoWidget + + + Nothing here! + Nada aqui! + + + + Finished tasks goes there! + Tarefas concluídas ficam por aqui! + + + + Edit Task + Editar tarefa + + + + Add Task + Adicionar tarefa + + + + Task description + Descrição da tarefa + + + + Cancel + Cancelar + + + + Edit + Editar + + + + Add + Adicionar + + + + WallpaperSelector + + + Toggles wallpaper selector on press + Ativa o seletor de papel de parede ao pressionar + + + + Opens wallpaper selector on press + Abre o seletor de papel de parede ao pressionar + + + + Closes wallpaper selector on press + Fecha o seletor de papel de parede ao pressionar + + + + Wifi + + + %1 network%2 available + %1 rede%2 disponível + + + + (%1 connected) + (%1 conectado) + + + + greetd + + + Enter password + Insira a senha + + + From 5949902ba4c6b4dc0c1fc71f558e3befb2c11367 Mon Sep 17 00:00:00 2001 From: Maylton Fernandes Date: Fri, 24 Jul 2026 11:28:08 -0300 Subject: [PATCH 3/6] feat(i18n): load translations from system locale --- src/share/sleex/CMakeLists.txt | 9 +++ .../sleex/plugins/src/Sleex/CMakeLists.txt | 75 +++++++++++++++++-- .../plugins/src/Sleex/core/CMakeLists.txt | 15 +++- .../sleex/plugins/src/Sleex/core/plugin.cpp | 56 ++++++++++++-- 4 files changed, 139 insertions(+), 16 deletions(-) diff --git a/src/share/sleex/CMakeLists.txt b/src/share/sleex/CMakeLists.txt index 069da01..7dbe663 100644 --- a/src/share/sleex/CMakeLists.txt +++ b/src/share/sleex/CMakeLists.txt @@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.19) project(sleex VERSION 1.15 LANGUAGES CXX) +find_package(Qt6 REQUIRED COMPONENTS LinguistTools) + set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) @@ -13,3 +15,10 @@ set(INSTALL_LIBDIR "usr/lib/sleex" CACHE STRING "Library install dir") set(INSTALL_QMLDIR "usr/lib/qt6/qml" CACHE STRING "QML install dir") add_subdirectory(plugins) + +qt6_add_translations( + TARGETS sleex-coreplugin + TS_FILES "${CMAKE_CURRENT_SOURCE_DIR}/translations/sleex_pt_BR.ts" + RESOURCE_PREFIX "/i18n" + LRELEASE_OPTIONS -nounfinished +) diff --git a/src/share/sleex/plugins/src/Sleex/CMakeLists.txt b/src/share/sleex/plugins/src/Sleex/CMakeLists.txt index ef3fd39..3e2d347 100644 --- a/src/share/sleex/plugins/src/Sleex/CMakeLists.txt +++ b/src/share/sleex/plugins/src/Sleex/CMakeLists.txt @@ -5,11 +5,39 @@ set(QT_QML_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qml") qt_standard_project_setup(REQUIRES 6.9) function(qml_module arg_TARGET) - cmake_parse_arguments(PARSE_ARGV 1 arg "" "URI" "SOURCES;LIBRARIES") + cmake_parse_arguments( + PARSE_ARGV 1 arg + "CUSTOM_PLUGIN" + "URI;CLASS_NAME" + "SOURCES;PLUGIN_SOURCES;LIBRARIES" + ) + + set(plugin_arguments) + + if(arg_CUSTOM_PLUGIN) + if(NOT arg_CLASS_NAME) + message(FATAL_ERROR + "${arg_TARGET}: CUSTOM_PLUGIN requires CLASS_NAME" + ) + endif() + + if(NOT arg_PLUGIN_SOURCES) + message(FATAL_ERROR + "${arg_TARGET}: CUSTOM_PLUGIN requires PLUGIN_SOURCES" + ) + endif() + + list(APPEND plugin_arguments + NO_GENERATE_PLUGIN_SOURCE + NO_PLUGIN_OPTIONAL + CLASS_NAME "${arg_CLASS_NAME}" + ) + endif() qt_add_qml_module(${arg_TARGET} URI ${arg_URI} VERSION ${VERSION} + ${plugin_arguments} SOURCES ${arg_SOURCES} ) @@ -22,18 +50,53 @@ function(qml_module arg_TARGET) TYPEINFO module_typeinfo ) - message(STATUS "Created QML module ${module_uri}, version ${module_version}") + if(arg_CUSTOM_PLUGIN) + target_sources( + "${module_plugin_target}" + PRIVATE + ${arg_PLUGIN_SOURCES} + ) + + target_link_libraries( + "${module_plugin_target}" + PRIVATE + Qt::Core + Qt::Qml + ) + endif() + + message( + STATUS + "Created QML module ${module_uri}, version ${module_version}" + ) set(module_dir "${INSTALL_QMLDIR}/${module_target_path}") - install(TARGETS ${arg_TARGET} LIBRARY DESTINATION "${module_dir}" RUNTIME DESTINATION "${module_dir}") - install(TARGETS "${module_plugin_target}" LIBRARY DESTINATION "${module_dir}" RUNTIME DESTINATION "${module_dir}") + + install( + TARGETS ${arg_TARGET} + LIBRARY DESTINATION "${module_dir}" + RUNTIME DESTINATION "${module_dir}" + ) + + install( + TARGETS "${module_plugin_target}" + LIBRARY DESTINATION "${module_dir}" + RUNTIME DESTINATION "${module_dir}" + ) + install(FILES "${module_qmldir}" DESTINATION "${module_dir}") install(FILES "${module_typeinfo}" DESTINATION "${module_dir}") - target_link_libraries(${arg_TARGET} PRIVATE Qt::Core Qt::Qml ${arg_LIBRARIES}) + target_link_libraries( + ${arg_TARGET} + PRIVATE + Qt::Core + Qt::Qml + ${arg_LIBRARIES} + ) endfunction() add_subdirectory(services) add_subdirectory(core) add_subdirectory(utils) -add_subdirectory(widgets) \ No newline at end of file +add_subdirectory(widgets) diff --git a/src/share/sleex/plugins/src/Sleex/core/CMakeLists.txt b/src/share/sleex/plugins/src/Sleex/core/CMakeLists.txt index 99b71e5..0c0def6 100755 --- a/src/share/sleex/plugins/src/Sleex/core/CMakeLists.txt +++ b/src/share/sleex/plugins/src/Sleex/core/CMakeLists.txt @@ -2,11 +2,18 @@ pkg_check_modules(GLIB REQUIRED glib-2.0 gobject-2.0 gio-2.0) qml_module(sleex-core URI Sleex.Core - SOURCES - init.cpp init.hpp + + CUSTOM_PLUGIN + CLASS_NAME SleexCorePlugin + PLUGIN_SOURCES plugin.cpp + + SOURCES + init.cpp + init.hpp + DEPENDENCIES - Qt::Sql + Qt::Sql ) target_include_directories(sleex-core PRIVATE @@ -29,4 +36,4 @@ target_link_libraries(sleex-core Qt6::Core Qt6::Qml Qt6::Sql -) \ No newline at end of file +) diff --git a/src/share/sleex/plugins/src/Sleex/core/plugin.cpp b/src/share/sleex/plugins/src/Sleex/core/plugin.cpp index b55f290..0c2afa6 100644 --- a/src/share/sleex/plugins/src/Sleex/core/plugin.cpp +++ b/src/share/sleex/plugins/src/Sleex/core/plugin.cpp @@ -1,14 +1,58 @@ -#include +#include +#include +#include +#include +#include +#include -class SleexCorePlugin : public QQmlExtensionPlugin { +class SleexCorePlugin final : public QQmlEngineExtensionPlugin { Q_OBJECT - Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) + Q_PLUGIN_METADATA(IID QQmlEngineExtensionInterface_iid) public: - void registerTypes(const char *uri) override { - // QML_ELEMENT and QML_SINGLETON macros handle registration automatically + void initializeEngine(QQmlEngine *engine, const char *uri) override { + Q_UNUSED(engine) Q_UNUSED(uri) + + if (translationInstalled) { + return; + } + + const QLocale locale = QLocale::system(); + + qInfo().noquote() + << "[Sleex Core] Initializing translations for locale" + << locale.name(); + + if (!translator.load( + locale, + QStringLiteral("sleex"), + QStringLiteral("_"), + QStringLiteral(":/i18n"))) { + qInfo().noquote() + << "[Sleex Core] No translation available for" + << locale.name() + << "- using the source language."; + return; + } + + translationInstalled = + QCoreApplication::installTranslator(&translator); + + if (translationInstalled) { + qInfo().noquote() + << "[Sleex Core] Translation installed for" + << locale.name(); + } else { + qWarning().noquote() + << "[Sleex Core] Translation catalog loaded," + << "but installation failed."; + } } + +private: + QTranslator translator; + bool translationInstalled = false; }; -#include "plugin.moc" \ No newline at end of file +#include "plugin.moc" From 8f5f6b979ab938f89b6462d683401e6ff46ccaf4 Mon Sep 17 00:00:00 2001 From: Maylton Fernandes Date: Fri, 24 Jul 2026 13:55:56 -0300 Subject: [PATCH 4/6] tools(i18n): add AI-assisted QML string marker --- scripts/i18n/mark_qml_strings_with_ollama.py | 747 +++++++++++++++++++ 1 file changed, 747 insertions(+) create mode 100755 scripts/i18n/mark_qml_strings_with_ollama.py diff --git a/scripts/i18n/mark_qml_strings_with_ollama.py b/scripts/i18n/mark_qml_strings_with_ollama.py new file mode 100755 index 0000000..67b895c --- /dev/null +++ b/scripts/i18n/mark_qml_strings_with_ollama.py @@ -0,0 +1,747 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import difflib +import json +import re +import shutil +import subprocess +import sys +import tempfile +import time +from pathlib import Path +from typing import Any +from urllib import error, request + +DEFAULT_API_URL = "http://127.0.0.1:11434/api/chat" + +UI_PROPERTY_RE = re.compile( + r"\b(text|title|subtitle|placeholderText|description|" + r"tooltip|toolTipText|label|message)\s*:" +) + +SIMPLE_LITERAL_RE = re.compile( + r"""^\s* + (?:text|title|subtitle|placeholderText|description| + tooltip|toolTipText|label|message) + \s*:\s* + (?P["']) + (?P.*) + (?P=quote) + \s*;?\s*$ + """, + re.VERBOSE, +) + +ICON_NAME_RE = re.compile(r"^[a-z0-9]+(?:_[a-z0-9]+)+$") + +COMMON_ICON_NAMES = { + "add", + "apps", + "backspace", + "bedtime", + "bluetooth", + "bolt", + "cancel", + "check", + "close", + "cloud", + "delete", + "edit", + "error", + "fingerprint", + "info", + "keep", + "lock", + "logout", + "lyrics", + "menu", + "monitor", + "pause", + "percent", + "play_arrow", + "refresh", + "remove", + "restart_alt", + "schedule", + "search", + "settings", + "sync", + "terminal", + "visibility", + "volume_up", +} + +NON_LANGUAGE_VALUES = { + "", + "AM", + "PM", + "AxOS", + "KDE", + "Qt", + "Sleex", + "Wayland", +} + + +def parse_arguments() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Use a local Ollama model to mark hard-coded, user-facing " + "QML strings with qsTr(). Dry-run is the default." + ) + ) + parser.add_argument("paths", nargs="+", type=Path) + parser.add_argument("--model", default="qwen3:8b") + parser.add_argument("--api-url", default=DEFAULT_API_URL) + parser.add_argument( + "--apply", + action="store_true", + help="Write accepted changes. Without this flag, only show diffs.", + ) + parser.add_argument("--min-confidence", type=float, default=0.90) + parser.add_argument("--batch-size", type=int, default=25) + parser.add_argument("--limit-files", type=int, default=0) + parser.add_argument( + "--report", + type=Path, + default=Path("/tmp/sleex-qml-i18n-report.json"), + ) + return parser.parse_args() + + +def discover_qml_files(paths: list[Path]) -> list[Path]: + files: set[Path] = set() + + for supplied in paths: + path = supplied.expanduser().resolve() + + if path.is_file() and path.suffix == ".qml": + files.add(path) + continue + + if path.is_dir(): + for item in path.rglob("*.qml"): + if not any( + part in {".git", "build", "_build", "node_modules"} + for part in item.parts + ): + files.add(item.resolve()) + continue + + print(f"[WARN] Ignoring invalid path: {path}", file=sys.stderr) + + return sorted(files) + + +def is_comment_line(line: str) -> bool: + stripped = line.lstrip() + return stripped.startswith(("//", "/*", "*", "*/")) + + +def is_non_language_literal(line: str) -> bool: + match = SIMPLE_LITERAL_RE.match(line) + + if not match: + return False + + value = match.group("value").strip() + + if value in NON_LANGUAGE_VALUES: + return True + + if value.startswith(("http://", "https://", "/", "~/")): + return True + + if ICON_NAME_RE.fullmatch(value) or value in COMMON_ICON_NAMES: + return True + + if re.fullmatch(r"[\d\s%•→←‹›:+\-*/=.,_]+", value): + return True + + return False + + +def extract_candidates(source: str) -> list[dict[str, Any]]: + lines = source.splitlines() + candidates: list[dict[str, Any]] = [] + + for index, line in enumerate(lines): + if is_comment_line(line): + continue + + if not UI_PROPERTY_RE.search(line): + continue + + if "qsTr(" in line or "qsTranslate(" in line: + continue + + # Only send lines containing a literal string/template. Purely dynamic + # values such as text: device.name should not be translated here. + if not any(token in line for token in ('"', "'", "`")): + continue + + property_match = UI_PROPERTY_RE.search(line) + + if property_match is None: + continue + + expression = line[property_match.end():].strip() + + # A runtime expression without a complete same-line ternary is + # probably the beginning of a multiline QML binding. + if ( + expression + and not expression.startswith(('"', "'", "`")) + and "?" not in expression + ): + continue + + if "MaterialSymbol" in line: + continue + + if is_non_language_literal(line): + continue + + previous_line = lines[index - 1] if index > 0 else "" + next_line = lines[index + 1] if index + 1 < len(lines) else "" + + candidates.append( + { + "id": len(candidates), + "line_number": index + 1, + "code": line, + "context_before": previous_line, + "context_after": next_line, + } + ) + + return candidates + + +def response_schema() -> dict[str, Any]: + return { + "type": "object", + "properties": { + "changes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": {"type": "integer"}, + "replacement": {"type": "string"}, + "reason": {"type": "string"}, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1, + }, + }, + "required": [ + "id", + "replacement", + "reason", + "confidence", + ], + }, + } + }, + "required": ["changes"], + } + + +def request_changes( + *, + path: Path, + candidates: list[dict[str, Any]], + model: str, + api_url: str, +) -> list[dict[str, Any]]: + system_prompt = """ +You are a conservative QML internationalization refactoring assistant. + +Your task is NOT to translate into another language. Keep source messages +in English and add Qt translation markers only where needed. + +You receive preselected candidate lines. Return ONLY candidates that need +a real code change. Never return an unchanged line. + +Rules: +- Refer to a candidate only by its numeric id. +- replacement must contain the complete replacement for candidate.code. +- Preserve the original indentation and all unrelated code. +- Add qsTr() only to genuine user-facing interface language. +- Skip Material Symbols and icon identifiers. +- Skip URLs, paths, commands, IDs, enum values, filenames and technical data. +- Skip external/dynamic content such as application names, device names, + Wi-Fi SSIDs, usernames, song titles, artists, lyrics, window titles, + notification content and calendar event content. +- Do not wrap runtime-generated values in qsTr(). +- For a ternary, wrap each visible literal separately. +- For concatenation or template literals, use Qt placeholders and .arg(). +- Preserve every variable and expression. +- Do not change wording, spelling or capitalization. +- Do not add imports. +- Prefer returning no change when uncertain. + +Examples: + +Candidate: +text: "Back" +Replacement: +text: qsTr("Back") + +Candidate: +text: enabled ? "Enabled" : "Disabled" +Replacement: +text: enabled ? qsTr("Enabled") : qsTr("Disabled") + +Candidate: +text: `${count} notifications` +Replacement: +text: qsTr("%1 notifications").arg(count) + +Candidate: +text: "Uptime: " + DateTime.uptime +Replacement: +text: qsTr("Uptime: %1").arg(DateTime.uptime) + +Do not change: +text: device.name +text: DateTime.time +MaterialSymbol { text: "close" } +""".strip() + + payload = { + "model": model, + "messages": [ + {"role": "system", "content": system_prompt}, + { + "role": "user", + "content": json.dumps( + { + "file": str(path), + "candidates": candidates, + }, + ensure_ascii=False, + indent=2, + ), + }, + ], + "stream": False, + "think": False, + "format": response_schema(), + "options": {"temperature": 0.0}, + "keep_alive": "10m", + } + + encoded = json.dumps(payload).encode("utf-8") + http_request = request.Request( + api_url, + data=encoded, + headers={"Content-Type": "application/json"}, + method="POST", + ) + + last_exception: Exception | None = None + + for attempt in range(1, 4): + try: + with request.urlopen(http_request, timeout=900) as response: + response_data = json.load(response) + + content = response_data["message"]["content"] + parsed = json.loads(content) + return parsed["changes"] + + except ( + error.URLError, + TimeoutError, + KeyError, + ValueError, + TypeError, + json.JSONDecodeError, + ) as exception: + last_exception = exception + print( + f"[WARN] Attempt {attempt}/3 failed for {path}: " + f"{exception}", + file=sys.stderr, + ) + time.sleep(attempt * 2) + + raise RuntimeError( + f"Ollama request failed after three attempts: {last_exception}" + ) + + +def validate_change( + *, + change: dict[str, Any], + candidate_by_id: dict[int, dict[str, Any]], + minimum_confidence: float, +) -> tuple[bool, str]: + candidate_id = change.get("id") + replacement = change.get("replacement") + confidence = change.get("confidence") + + if not isinstance(candidate_id, int): + return False, "invalid candidate id" + + candidate = candidate_by_id.get(candidate_id) + + if candidate is None: + return False, "unknown candidate id" + + if not isinstance(replacement, str): + return False, "replacement is not text" + + if not isinstance(confidence, (int, float)): + return False, "confidence is not numeric" + + if confidence < minimum_confidence: + return False, "low confidence" + + original = candidate["code"] + + if replacement == original: + return False, "unchanged proposal" + + if "qsTr(" not in replacement and "qsTranslate(" not in replacement: + return False, "replacement has no translation helper" + + if re.search(r"""\bqsTr\(\s*(?!["'])""", replacement): + return False, "qsTr() argument is not a string literal" + + if "MaterialSymbol" in replacement: + return False, "Material Symbol line" + + translated_literals = re.findall( + r"""qsTr\(\s*["']([^"']+)["']\s*\)""", + replacement, + ) + + for literal in translated_literals: + if ( + ICON_NAME_RE.fullmatch(literal) + or literal in COMMON_ICON_NAMES + ): + return False, f"icon name passed to qsTr(): {literal}" + + if re.search( + r"""qsTr\([^)]*\)\s*\+""", + replacement, + ): + return False, ( + "translated fragment concatenated with runtime data" + ) + + original_indent = original[: len(original) - len(original.lstrip())] + replacement_first_line = replacement.splitlines()[0] + replacement_indent = replacement_first_line[ + : len(replacement_first_line) - len(replacement_first_line.lstrip()) + ] + + if replacement_indent != original_indent: + return False, "indentation changed" + + return True, "" + + +def apply_line_changes( + source: str, + accepted: list[dict[str, Any]], + candidate_by_id: dict[int, dict[str, Any]], +) -> str: + lines = source.splitlines(keepends=True) + + replacements: list[tuple[int, str]] = [] + + for change in accepted: + candidate = candidate_by_id[change["id"]] + line_index = candidate["line_number"] - 1 + current = lines[line_index] + newline = "\n" if current.endswith("\n") else "" + + if current.rstrip("\n") != candidate["code"]: + raise RuntimeError( + f"Source changed while processing line " + f"{candidate['line_number']}" + ) + + replacement = change["replacement"].rstrip("\n") + newline + replacements.append((line_index, replacement)) + + for line_index, replacement in sorted(replacements, reverse=True): + lines[line_index] = replacement + + return "".join(lines) + + +def find_qmlformat() -> str | None: + candidates = [ + shutil.which("qmlformat"), + "/usr/lib/qt6/bin/qmlformat", + ] + + for candidate in candidates: + if candidate and Path(candidate).is_file(): + return str(candidate) + + return None + + +def validate_qml(source: str, formatter: str | None) -> tuple[bool, str]: + if formatter is None: + return True, "qmlformat unavailable" + + with tempfile.NamedTemporaryFile( + "w", + suffix=".qml", + encoding="utf-8", + delete=False, + ) as handle: + handle.write(source) + temp_path = Path(handle.name) + + try: + result = subprocess.run( + [formatter, str(temp_path)], + capture_output=True, + text=True, + timeout=60, + ) + finally: + temp_path.unlink(missing_ok=True) + + if result.returncode != 0: + return False, (result.stderr or result.stdout).strip() + + return True, "" + + +def create_backup(path: Path, stamp: str) -> Path: + try: + root = Path( + subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + check=True, + capture_output=True, + text=True, + ).stdout.strip() + ) + relative = path.relative_to(root) + destination = ( + root / ".git" / "ai-i18n-backups" / stamp / relative + ) + except (OSError, ValueError, subprocess.CalledProcessError): + destination = Path("/tmp") / f"sleex-i18n-{stamp}" / path.name + + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(path, destination) + return destination + + +def print_diff(path: Path, before: str, after: str) -> None: + sys.stdout.writelines( + difflib.unified_diff( + before.splitlines(keepends=True), + after.splitlines(keepends=True), + fromfile=f"a/{path}", + tofile=f"b/{path}", + ) + ) + + +def main() -> int: + args = parse_arguments() + + if args.batch_size < 1: + print("--batch-size must be at least 1", file=sys.stderr) + return 1 + + files = discover_qml_files(args.paths) + + if args.limit_files: + files = files[: args.limit_files] + + if not files: + print("No QML files found.", file=sys.stderr) + return 1 + + formatter = find_qmlformat() + stamp = time.strftime("%Y%m%d-%H%M%S") + report: dict[str, Any] = { + "mode": "apply" if args.apply else "dry-run", + "model": args.model, + "files": [], + } + + for file_index, path in enumerate(files, 1): + print(f"\n[{file_index}/{len(files)}] {path}") + + source = path.read_text(encoding="utf-8") + candidates = extract_candidates(source) + + if not candidates: + print("[SKIP] No unmarked literal UI candidates") + report["files"].append( + { + "path": str(path), + "candidate_count": 0, + "accepted": [], + "rejected": [], + } + ) + continue + + print(f"[CANDIDATES] {len(candidates)}") + + candidate_by_id = { + candidate["id"]: candidate for candidate in candidates + } + + proposals: list[dict[str, Any]] = [] + + for start in range(0, len(candidates), args.batch_size): + batch = candidates[start : start + args.batch_size] + + try: + proposals.extend( + request_changes( + path=path, + candidates=batch, + model=args.model, + api_url=args.api_url, + ) + ) + except RuntimeError as exception: + print(f"[ERROR] {exception}", file=sys.stderr) + + accepted: list[dict[str, Any]] = [] + rejected: list[dict[str, Any]] = [] + seen_ids: set[int] = set() + + for proposal in proposals: + proposal_id = proposal.get("id") + + if isinstance(proposal_id, int) and proposal_id in seen_ids: + rejected.append( + { + **proposal, + "rejected_reason": "duplicate candidate id", + } + ) + continue + + valid, reason = validate_change( + change=proposal, + candidate_by_id=candidate_by_id, + minimum_confidence=args.min_confidence, + ) + + if valid: + accepted.append(proposal) + seen_ids.add(proposal_id) + else: + rejected.append( + { + **proposal, + "rejected_reason": reason, + } + ) + + updated = source + + baseline_valid, baseline_error = validate_qml( + source, + formatter, + ) + + if not baseline_valid: + print( + "[WARN] The original file already fails qmlformat; " + "the whole-file validation gate will be skipped." + ) + + if accepted: + updated = apply_line_changes( + source, + accepted, + candidate_by_id, + ) + + if baseline_valid: + syntax_valid, syntax_error = validate_qml( + updated, + formatter, + ) + + if not syntax_valid: + rejected.extend( + { + **change, + "rejected_reason": ( + "whole-file QML syntax validation failed" + ), + } + for change in accepted + ) + accepted = [] + updated = source + print( + f"[REJECT FILE] qmlformat: {syntax_error}" + ) + + if accepted: + print( + f"[READY] {len(accepted)} accepted, " + f"{len(rejected)} rejected" + ) + + if args.apply: + backup_path = create_backup(path, stamp) + path.write_text(updated, encoding="utf-8") + print(f"[APPLIED] Backup: {backup_path}") + else: + print_diff(path, source, updated) + else: + print( + f"[NO CHANGE] {len(candidates)} candidates, " + f"{len(rejected)} rejected proposals" + ) + + report["files"].append( + { + "path": str(path), + "candidate_count": len(candidates), + "accepted": [ + { + **change, + "line_number": candidate_by_id[change["id"]][ + "line_number" + ], + "original": candidate_by_id[change["id"]]["code"], + } + for change in accepted + ], + "rejected": rejected, + } + ) + + args.report.write_text( + json.dumps(report, ensure_ascii=False, indent=2), + encoding="utf-8", + ) + + print(f"\nReport: {args.report}") + + if not args.apply: + print("Dry-run complete: no QML files were modified.") + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 5597eb89df7faaa071c3664d1c793112c5e7dd02 Mon Sep 17 00:00:00 2001 From: Maylton Fernandes Date: Fri, 24 Jul 2026 13:55:59 -0300 Subject: [PATCH 5/6] feat(i18n): localize settings interface --- src/share/sleex/modules/settings/About.qml | 2 +- .../sleex/modules/settings/Applications.qml | 12 +- .../sleex/modules/settings/BehaviorConfig.qml | 48 +- .../sleex/modules/settings/Bluetooth.qml | 12 +- src/share/sleex/modules/settings/Display.qml | 10 +- .../sleex/modules/settings/Interface.qml | 124 +- src/share/sleex/modules/settings/Privacy.qml | 18 +- src/share/sleex/modules/settings/Sound.qml | 22 +- src/share/sleex/modules/settings/Style.qml | 20 +- src/share/sleex/modules/settings/Wifi.qml | 28 +- .../displaySettings/DisplaySettings.qml | 4 +- src/share/sleex/settings.qml | 23 +- src/share/sleex/translations/sleex_pt_BR.ts | 2364 +++++++++++------ 13 files changed, 1761 insertions(+), 926 deletions(-) diff --git a/src/share/sleex/modules/settings/About.qml b/src/share/sleex/modules/settings/About.qml index 9d61654..1461202 100644 --- a/src/share/sleex/modules/settings/About.qml +++ b/src/share/sleex/modules/settings/About.qml @@ -13,7 +13,7 @@ ContentPage { ContentSection { visible: SystemInfo.distroName == "AxOS" - title: "Distro" + title: qsTr("Distro") icon: "info" RowLayout { diff --git a/src/share/sleex/modules/settings/Applications.qml b/src/share/sleex/modules/settings/Applications.qml index 92861af..b8e0521 100644 --- a/src/share/sleex/modules/settings/Applications.qml +++ b/src/share/sleex/modules/settings/Applications.qml @@ -69,7 +69,7 @@ ContentPage { } ContentSection { - title: "Default Applications" + title: qsTr("Default Applications") icon: "apps" ColumnLayout { @@ -79,7 +79,7 @@ ContentPage { Layout.bottomMargin: 10 StyledText { - text: "Web Browser" + text: qsTr("Web Browser") color: Appearance.colors.colSubtext } @@ -98,7 +98,7 @@ ContentPage { } StyledText { - text: "File Manager" + text: qsTr("File Manager") color: Appearance.colors.colSubtext } @@ -117,7 +117,7 @@ ContentPage { } StyledText { - text: "Image Viewer" + text: qsTr("Image Viewer") color: Appearance.colors.colSubtext } @@ -136,7 +136,7 @@ ContentPage { } StyledText { - text: "Video Player" + text: qsTr("Video Player") color: Appearance.colors.colSubtext } @@ -155,7 +155,7 @@ ContentPage { } StyledText { - text: "Document Viewer" + text: qsTr("Document Viewer") color: Appearance.colors.colSubtext } diff --git a/src/share/sleex/modules/settings/BehaviorConfig.qml b/src/share/sleex/modules/settings/BehaviorConfig.qml index 1dc9e17..d758966 100644 --- a/src/share/sleex/modules/settings/BehaviorConfig.qml +++ b/src/share/sleex/modules/settings/BehaviorConfig.qml @@ -10,13 +10,13 @@ ContentPage { // forceWidth: true ContentSection { - title: "Time and date" + title: qsTr("Time and date") icon: "schedule" ColumnLayout { // Format ContentSubsectionLabel { - text: "Time format" + text: qsTr("Time format") } StyledComboBox { id: timeFormatComboBox @@ -52,7 +52,7 @@ ContentPage { ColumnLayout { // Format ContentSubsectionLabel { - text: "Date format" + text: qsTr("Date format") } StyledComboBox { id: dateFormatComboBox @@ -102,14 +102,14 @@ ContentPage { } ContentSection { - title: "Power" + title: qsTr("Power") icon: "battery_android_full" ConfigRow { visible: UPower.displayDevice.isLaptopBattery uniform: true ConfigSpinBox { - text: "Low warning" + text: qsTr("Low warning") value: Config.options.battery.low from: 0 to: 100 @@ -120,7 +120,7 @@ ContentPage { } ConfigSpinBox { visible: UPower.displayDevice.isLaptopBattery - text: "Critical warning" + text: qsTr("Critical warning") value: Config.options.battery.critical from: 0 to: 100 @@ -131,7 +131,7 @@ ContentPage { } } ContentSubsectionLabel { - text: "Power profile" + text: qsTr("Power profile") } ConfigSelectionArray { currentValue: PowerProfiles.profile @@ -148,12 +148,12 @@ ContentPage { } ContentSection { - title: "Idle daemon" + title: qsTr("Idle daemon") icon: "timer" ConfigSwitch { - text: "Enable idle actions" + text: qsTr("Enable idle actions") checked: Idle.hypnos.enabled onClicked: checked = !checked; onCheckedChanged: { @@ -162,15 +162,15 @@ ContentPage { } ContentSubsection { - title: "Dim screen" - tooltip: "Decrease the screen brightness after the specified timeout." + title: qsTr("Dim screen") + tooltip: qsTr("Decrease the screen brightness after the specified timeout.") ConfigRow { spacing: 10 Layout.fillWidth: true ConfigSwitch { - text: "Enable dimming" + text: qsTr("Enable dimming") checked: Idle.hypnos.rules.dim.enabled onClicked: checked = !checked; onCheckedChanged: { @@ -179,7 +179,7 @@ ContentPage { } ConfigSwitch { - text: "Only on battery power" + text: qsTr("Only on battery power") checked: Idle.hypnos.rules.dim.on_battery onClicked: checked = !checked; onCheckedChanged: { @@ -189,7 +189,7 @@ ContentPage { } ConfigSpinBox { - text: "Timeout (seconds)" + text: qsTr("Timeout (seconds)") value: Idle.hypnos.rules.dim.timeout from: 10 to: 3600 @@ -201,15 +201,15 @@ ContentPage { } ContentSubsection { - title: "Lock screen" - tooltip: "Lock the session after the specified timeout." + title: qsTr("Lock screen") + tooltip: qsTr("Lock the session after the specified timeout.") ConfigRow { spacing: 10 Layout.fillWidth: true ConfigSwitch { - text: "Enable locking" + text: qsTr("Enable locking") checked: Idle.hypnos.rules.lock.enabled onClicked: checked = !checked; onCheckedChanged: { @@ -217,7 +217,7 @@ ContentPage { } } ConfigSwitch { - text: "Only on battery power" + text: qsTr("Only on battery power") checked: Idle.hypnos.rules.lock.on_battery onClicked: checked = !checked; onCheckedChanged: { @@ -227,7 +227,7 @@ ContentPage { } ConfigSpinBox { - text: "Timeout (seconds)" + text: qsTr("Timeout (seconds)") value: Idle.hypnos.rules.lock.timeout from: 10 to: 3600 @@ -239,15 +239,15 @@ ContentPage { } ContentSubsection { - title: "Suspend system" - tooltip: "Suspend the system after the specified timeout." + title: qsTr("Suspend system") + tooltip: qsTr("Suspend the system after the specified timeout.") ConfigRow { spacing: 10 Layout.fillWidth: true ConfigSwitch { - text: "Enable suspend" + text: qsTr("Enable suspend") checked: Idle.hypnos.rules.suspend.enabled onClicked: checked = !checked; onCheckedChanged: { @@ -255,7 +255,7 @@ ContentPage { } } ConfigSwitch { - text: "Only on battery power" + text: qsTr("Only on battery power") checked: Idle.hypnos.rules.suspend.on_battery onClicked: checked = !checked; onCheckedChanged: { @@ -265,7 +265,7 @@ ContentPage { } ConfigSpinBox { - text: "Timeout (seconds)" + text: qsTr("Timeout (seconds)") value: Idle.hypnos.rules.suspend.timeout from: 10 to: 3600 diff --git a/src/share/sleex/modules/settings/Bluetooth.qml b/src/share/sleex/modules/settings/Bluetooth.qml index e28ea61..d23c2b6 100644 --- a/src/share/sleex/modules/settings/Bluetooth.qml +++ b/src/share/sleex/modules/settings/Bluetooth.qml @@ -34,7 +34,7 @@ ContentPage { ContentSection { - title: "Bluetooth settings" + title: qsTr("Bluetooth settings") icon: "bluetooth" visible: Bluetooth.adapters.values.length > 0 @@ -43,7 +43,7 @@ ContentPage { uniformCellSizes: true ConfigSwitch { - text: "Enabled" + text: qsTr("Enabled") checked: Config.options.bar.showTitle onClicked: checked = !checked; onCheckedChanged: { @@ -53,7 +53,7 @@ ContentPage { } ConfigSwitch { - text: "Discoverable" + text: qsTr("Discoverable") checked: Bluetooth.defaultAdapter.discoverable onClicked: checked = !checked; onCheckedChanged: { @@ -109,7 +109,7 @@ ContentPage { StyledToolTip { extraVisibleCondition: discoverArea.containsMouse - text: "Discover new devices" + text: qsTr("Discover new devices") } } } @@ -119,7 +119,7 @@ ContentPage { Text { - text: "No bluetooth adapter found" + text: qsTr("No bluetooth adapter found") color: Appearance.colors.colOnLayer1 font.pixelSize: 30 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter @@ -129,7 +129,7 @@ ContentPage { StyledTextArea { id: deviceSearch Layout.fillWidth: true - placeholderText: "Search devices" + placeholderText: qsTr("Search devices") visible: Bluetooth.adapters.values.length > 0 } diff --git a/src/share/sleex/modules/settings/Display.qml b/src/share/sleex/modules/settings/Display.qml index c06df54..22a6060 100644 --- a/src/share/sleex/modules/settings/Display.qml +++ b/src/share/sleex/modules/settings/Display.qml @@ -35,7 +35,7 @@ ContentPage { // forceWidth: true ContentSection { - title: "Monitor arrangement" + title: qsTr("Monitor arrangement") icon: "display_settings" DS.DisplaySettings { @@ -45,7 +45,7 @@ ContentPage { } ContentSection { - title: "Brightness" + title: qsTr("Brightness") icon: "brightness_medium" StyledSlider { @@ -57,12 +57,12 @@ ContentPage { } ContentSection { - title: "Night light" + title: qsTr("Night light") icon: "nightlight" ConfigSwitch { id: enableSwitch - text: "Enable" + text: qsTr("Enable") checked: NightLight.active onClicked: { NightLight.toggle() @@ -72,7 +72,7 @@ ContentPage { ConfigSwitch { id: autoSwitch - text: "Automatic toggle" + text: qsTr("Automatic toggle") checked: Config.options.display.nightLightAuto onClicked: checked = !checked onCheckedChanged: { diff --git a/src/share/sleex/modules/settings/Interface.qml b/src/share/sleex/modules/settings/Interface.qml index 17c24c7..5866777 100644 --- a/src/share/sleex/modules/settings/Interface.qml +++ b/src/share/sleex/modules/settings/Interface.qml @@ -15,7 +15,7 @@ ContentPage { FileDialog { id: sddmFaceDialog - title: "Select profile picture" + title: qsTr("Select profile picture") nameFilters: ["Image files (*.png *.jpg *.jpeg *.bmp *.webp)"] onAccepted: { _selectedFaceImage = selectedFile.toString().replace("file://", "") @@ -58,7 +58,7 @@ ContentPage { FileDialog { id: avatarPickerDialog - title: "Select avatar image" + title: qsTr("Select avatar image") nameFilters: ["Image files (*.png *.jpg *.jpeg *.bmp *.webp)"] onAccepted: { Config.options.dashboard.avatarPath = selectedFile.toString().replace("file://", "") @@ -67,19 +67,19 @@ ContentPage { } ContentSection { - title: "Shell style" + title: qsTr("Shell style") icon: "style" ConfigSwitch { - text: "Transparency" + text: qsTr("Transparency") checked: Config.options.appearance.transparency onClicked: checked = !checked; - StyledToolTip { text: "Enable the blur effect on the shell." } + StyledToolTip { text: qsTr("Enable the blur effect on the shell.") } onCheckedChanged: Config.options.appearance.transparency = checked; } ConfigSpinBox { - text: "Opacity" + text: qsTr("Opacity") value: Config.options.appearance.opacity from: 0 to: 100 @@ -92,7 +92,7 @@ ContentPage { } ContentSection { - title: "Bar" + title: qsTr("Bar") icon: "toolbar" RowLayout { @@ -100,14 +100,14 @@ ContentPage { uniformCellSizes: true ConfigSwitch { - text: "Show app name" + text: qsTr("Show app name") checked: Config.options.bar.showTitle onClicked: checked = !checked; onCheckedChanged: Config.options.bar.showTitle = checked; } ConfigSwitch { - text: "Show resources usage" + text: qsTr("Show resources usage") checked: Config.options.bar.showRessources onClicked: checked = !checked; onCheckedChanged: Config.options.bar.showRessources = checked; @@ -119,14 +119,14 @@ ContentPage { uniformCellSizes: true ConfigSwitch { - text: "Show Workspaces" + text: qsTr("Show Workspaces") checked: Config.options.bar.showWorkspaces onClicked: checked = !checked; onCheckedChanged: Config.options.bar.showWorkspaces = checked; } ConfigSwitch { - text: "Show clock" + text: qsTr("Show clock") checked: Config.options.bar.showClock onClicked: checked = !checked; onCheckedChanged: Config.options.bar.showClock = checked; @@ -138,14 +138,14 @@ ContentPage { uniformCellSizes: true ConfigSwitch { - text: "Show system icons" + text: qsTr("Show system icons") checked: Config.options.bar.showTrayAndIcons onClicked: checked = !checked; onCheckedChanged: Config.options.bar.showTrayAndIcons = checked; } ConfigSwitch { - text: "Enable bar background" + text: qsTr("Enable bar background") checked: Config.options.bar.background onClicked: checked = !checked; onCheckedChanged: Config.options.bar.background = checked; @@ -154,13 +154,13 @@ ContentPage { } ContentSubsection { - title: "Workspaces" - tooltip: "Tip: Hide icons for the\n classic Sleex experience" + title: qsTr("Workspaces") + tooltip: qsTr("Tip: Hide icons for the\nclassic Sleex experience") ConfigRow { uniform: true ConfigSwitch { - text: 'Show app icons' + text: qsTr("Show app icons") onClicked: checked = !checked; checked: Config.options.bar.workspaces.showAppIcons onCheckedChanged: { @@ -168,7 +168,7 @@ ContentPage { } } ConfigSwitch { - text: 'Use Material icons' + text: qsTr("Use Material icons") onClicked: checked = !checked; checked: Config.options.bar.workspaces.useMaterialIcons onCheckedChanged: { @@ -177,7 +177,7 @@ ContentPage { } } ConfigSwitch { - text: 'Always show numbers' + text: qsTr("Always show numbers") onClicked: checked = !checked; checked: Config.options.bar.workspaces.alwaysShowNumbers onCheckedChanged: { @@ -185,7 +185,7 @@ ContentPage { } } ConfigSpinBox { - text: "Workspaces shown" + text: qsTr("Workspaces shown") value: Config.options.bar.workspaces.shown from: 1 to: 30 @@ -195,7 +195,7 @@ ContentPage { } } ConfigSpinBox { - text: "Number show delay when pressing Super (ms)" + text: qsTr("Number show delay when pressing Super (ms)") value: Config.options.bar.workspaces.showNumberDelay from: 0 to: 1000 @@ -209,11 +209,11 @@ ContentPage { } ContentSection { - title: "Dashboard" + title: qsTr("Dashboard") icon: "dashboard" ConfigSpinBox { - text: "Scale" + text: qsTr("Scale") value: Config.options.dashboard.dashboardScale * 100 from: 0 to: 200 @@ -226,7 +226,7 @@ ContentPage { MaterialTextField { id: ghUsername Layout.fillWidth: true - placeholderText: "Github username" + placeholderText: qsTr("Github username") text: Config.options.dashboard.ghUsername wrapMode: TextEdit.Wrap onTextChanged: { @@ -237,7 +237,7 @@ ContentPage { MaterialTextField { id: userDesc Layout.fillWidth: true - placeholderText: "User description" + placeholderText: qsTr("User description") text: Config.options.dashboard.userDesc onTextChanged: { Config.options.dashboard.userDesc = text; @@ -252,7 +252,7 @@ ContentPage { MaterialTextField { id: avatarPathField Layout.fillWidth: true - placeholderText: "Avatar image path" + placeholderText: qsTr("Avatar image path") text: Config.options.dashboard.avatarPath onEditingFinished: { Config.options.dashboard.avatarPath = text; @@ -268,17 +268,17 @@ ContentPage { } ContentSubsection { - title: "Optional features" - tooltip: "Affects performances.\nWill make dashboard load slower" + title: qsTr("Optional features") + tooltip: qsTr("May affect performance.\nThe dashboard may load more slowly.") ConfigSwitch { - text: "Todo list" + text: qsTr("Todo list") checked: Config.options.dashboard.opt.enableTodo onClicked: checked = !checked; onCheckedChanged: Config.options.dashboard.opt.enableTodo = checked; } ConfigSwitch { - text: "Calendar tab" + text: qsTr("Calendar tab") checked: Config.options.dashboard.opt.enableCalendar onClicked: checked = !checked; onCheckedChanged: Config.options.dashboard.opt.enableCalendar = checked; @@ -286,7 +286,7 @@ ContentPage { } StyledText { - text: "Animation Direction" + text: qsTr("Animation Direction") color: Appearance.colors.colSubtext } @@ -304,7 +304,7 @@ ContentPage { } StyledText { - text: "Animation Intensity" + text: qsTr("Animation Intensity") color: Appearance.colors.colSubtext } @@ -323,14 +323,14 @@ ContentPage { } ContentSection { - title: "Dock" + title: qsTr("Dock") icon: "dock" ConfigRow { uniform: true ConfigSwitch { - text: "Enabled" + text: qsTr("Enabled") onClicked: checked = !checked; checked: Config.options.dock.enabled onCheckedChanged: { @@ -338,7 +338,7 @@ ContentPage { } } ConfigSpinBox { - text: "Height" + text: qsTr("Height") value: Config.options.dock.height stepSize: 5 onValueChanged: { @@ -350,7 +350,7 @@ ContentPage { ConfigRow { uniform: true ConfigSwitch { - text: "Hover to reveal" + text: qsTr("Hover to reveal") onClicked: checked = !checked; checked: Config.options.dock.hoverToReveal onCheckedChanged: { @@ -358,7 +358,7 @@ ContentPage { } } ConfigSwitch { - text: "Pinned on startup" + text: qsTr("Pinned on startup") onClicked: checked = !checked checked: Config.options.dock.pinnedOnStartup onCheckedChanged: { @@ -369,7 +369,7 @@ ContentPage { } ConfigSpinBox { - text: "Hover region height" + text: qsTr("Hover region height") value: Config.options.dock.hoverRegionHeight onValueChanged: { Config.options.dock.hoverRegionHeight = value @@ -380,7 +380,7 @@ ContentPage { ContentSection { - title: "Background" + title: qsTr("Background") icon: "wallpaper" ColumnLayout { @@ -391,14 +391,14 @@ ContentPage { uniform: true ConfigSwitch { - text: "Show clock" + text: qsTr("Show clock") checked: Config.options.background.enableClock onClicked: checked = !checked; onCheckedChanged: Config.options.background.enableClock = checked; } ConfigSwitch { - text: "Fixed clock position" + text: qsTr("Fixed clock position") checked: Config.options.background.fixedClockPosition onClicked: checked = !checked; onCheckedChanged: Config.options.background.fixedClockPosition = checked; @@ -409,14 +409,14 @@ ContentPage { uniform: true ConfigSwitch { - text: "Show watermark" + text: qsTr("Show watermark") checked: Config.options.background.showWatermark onClicked: checked = !checked; onCheckedChanged: Config.options.background.showWatermark = checked; } ConfigSwitch { - text: "Show quotes" + text: qsTr("Show quotes") checked: Config.options.background.enableQuote onClicked: checked = !checked onCheckedChanged: { @@ -428,14 +428,14 @@ ContentPage { } ConfigSwitch { - text: "Show desktop icons" + text: qsTr("Show desktop icons") checked: Config.options.background.showDesktopIcons onClicked: checked = !checked; onCheckedChanged: Config.options.background.showDesktopIcons = checked; } ContentSubsection { - title: "Clock mode" + title: qsTr("Clock mode") ConfigSelectionArray { currentValue: Config.options.background.clockMode @@ -451,7 +451,7 @@ ContentPage { } ContentSubsection { - title: "Clock Font" + title: qsTr("Clock Font") StyledComboBox { id: fontComboBox @@ -468,8 +468,8 @@ ContentPage { } ContentSubsection { - title: "Quote Source" - tooltip: "The local quotes are stored in /usr/share/sleex/assets/quotes.json.\nThese quotes are made by the AxOS community and are tech related." + title: qsTr("Quote Source") + tooltip: qsTr("The local quotes are stored in /usr/share/sleex/assets/quotes.json.\nThese quotes are made by the AxOS community and are tech related.") ConfigSelectionArray { currentValue: Config.options.background.quoteSource @@ -488,7 +488,7 @@ ContentPage { } ContentSection { - title: "Notifications" + title: qsTr("Notifications") icon: "notifications" ConfigSelectionArray { @@ -506,7 +506,7 @@ ContentPage { ConfigSwitch { visible: UPower.displayDevice.isLaptopBattery - text: "Battery overlay warnings" + text: qsTr("Battery overlay warnings") checked: Config.options.battery.overlayEnabled onClicked: checked = !checked; onCheckedChanged: { @@ -516,11 +516,11 @@ ContentPage { } ContentSection { - title: "Lock Screen" + title: qsTr("Lock Screen") icon: "lock" ConfigSwitch { - text: "Scrim background" + text: qsTr("Scrim background") checked: Config.options.lockscreen.enableScrim onClicked: checked = !checked; onCheckedChanged: Config.options.lockscreen.enableScrim = checked; @@ -528,25 +528,25 @@ ContentPage { ConfigSwitch { id: mediaShowOnLockScreenSwitch - text: "Player integration" + text: qsTr("Player integration") checked: Config.options.lockscreen.showLyricsOnLockScreen onClicked: checked = !checked; onCheckedChanged: Config.options.lockscreen.showLyricsOnLockScreen = checked - StyledToolTip { text: "Show the media player widget on the lock screen." } + StyledToolTip { text: qsTr("Show the media player widget on the lock screen.") } } ConfigSwitch { visible: mediaShowOnLockScreenSwitch.checked - text: "Resizable widget" + text: qsTr("Resizable widget") checked: Config.options.lockscreen.resizableLockScreenWidget ?? false onClicked: checked = !checked; onCheckedChanged: Config.options.lockscreen.resizableLockScreenWidget = checked - StyledToolTip { text: "Allow resizing & repositioning of the media player widget." } + StyledToolTip { text: qsTr("Allow resizing & repositioning of the media player widget.") } } } ContentSection { - title: "Login Screen" + title: qsTr("Login Screen") icon: "key" RowLayout { @@ -556,7 +556,7 @@ ContentPage { Text { id: faceImageLabel Layout.fillWidth: true - text: _selectedFaceImage !== "" ? " Custom picture set" : " No picture selected" + text: _selectedFaceImage !== "" ? qsTr("Custom picture set") : qsTr("No picture selected") elide: Text.ElideMiddle color: palette.windowText } @@ -589,22 +589,22 @@ ContentPage { } ContentSection { - title: "Calendar" + title: qsTr("Calendar") icon: "event" ContentSubsection { - title: "Advanced" - tooltip: "Vdirsyncer is not configured by default.\nPlease refer to the documentation\n to set it up. Enable only after configuring it." + title: qsTr("Advanced") + tooltip: qsTr("Vdirsyncer is not configured by default.\nPlease refer to the documentation\nto set it up. Enable it only after configuration.") } ConfigSwitch { - text: "Use vdirsyncer" + text: qsTr("Use vdirsyncer") checked: Config.options.dashboard.calendar.useVdirsyncer onClicked: checked = !checked; onCheckedChanged: Config.options.dashboard.calendar.useVdirsyncer = checked; } ConfigSpinBox { - text: "Sync interval (minutes)" + text: qsTr("Sync interval (minutes)") value: Config.options.dashboard.calendar.syncInterval from: 1 to: 1440 // 24 hours diff --git a/src/share/sleex/modules/settings/Privacy.qml b/src/share/sleex/modules/settings/Privacy.qml index 6ce49d2..17dcc1f 100644 --- a/src/share/sleex/modules/settings/Privacy.qml +++ b/src/share/sleex/modules/settings/Privacy.qml @@ -9,36 +9,36 @@ ContentPage { forceSingleColumn: true ContentSection { - title: "Weather" + title: qsTr("Weather") icon: "cloud" ConfigSwitch { id: weatherSwitch - text: "Enabled" + text: qsTr("Enabled") checked: Config.options.dashboard.enableWeather onClicked: checked = !checked; onCheckedChanged: Config.options.dashboard.enableWeather = checked - StyledToolTip { text: "View weather forecasts directly in your dashboard.\nIt uses the https://open-meteo.com provider." } + StyledToolTip { text: qsTr("View weather forecasts directly in your dashboard.\nIt uses the https://open-meteo.com provider.") } } ConfigSwitch { id: autoLocationSwitch visible: weatherSwitch.checked - text: "Automatic Location" + text: qsTr("Automatic Location") checked: Config.options.dashboard.autoWeatherLocation ?? true onClicked: checked = !checked; onCheckedChanged: { Config.options.dashboard.autoWeatherLocation = checked; Weather.updateWeather(); } - StyledToolTip { text: "IP-based approximate location." } + StyledToolTip { text: qsTr("IP-based approximate location.") } } MaterialTextField { id: weatherLocation visible: weatherSwitch.checked && !autoLocationSwitch.checked Layout.fillWidth: true - placeholderText: "Weather Location" + placeholderText: qsTr("Weather Location") text: Config.options.dashboard.weatherLocation wrapMode: TextEdit.Wrap onEditingFinished: { @@ -48,16 +48,16 @@ ContentPage { } ContentSection { - title: "Media Player" + title: qsTr("Media Player") icon: "music_note" ConfigSwitch { id: lyricsSwitch - text: "Lyrics" + text: qsTr("Lyrics") checked: Config.options.dashboard.enableLyrics onClicked: checked = !checked; onCheckedChanged: Config.options.dashboard.enableLyrics = checked - StyledToolTip { text: "Fetch and display synced lyrics (LRCLIB provider)." } + StyledToolTip { text: qsTr("Fetch and display synced lyrics (LRCLIB provider).") } } } diff --git a/src/share/sleex/modules/settings/Sound.qml b/src/share/sleex/modules/settings/Sound.qml index cf1e365..41a9cb6 100644 --- a/src/share/sleex/modules/settings/Sound.qml +++ b/src/share/sleex/modules/settings/Sound.qml @@ -38,23 +38,23 @@ ContentPage { // forceWidth: true ContentSection { - title: "Protection" + title: qsTr("Protection") icon: "spatial_audio_off" ConfigSwitch { - text: "Earbang protection" + text: qsTr("Earbang protection") checked: Config.options.audio.protection.enable onClicked: checked = !checked; onCheckedChanged: { Config.options.audio.protection.enable = checked; } StyledToolTip { - text: "Prevents abrupt increments and restricts volume limit" + text: qsTr("Prevents abrupt increments and restricts volume limit") } } ConfigSpinBox { id: earbangLimitSpinBox - text: "Earbang limit" + text: qsTr("Earbang limit") value: Config.options.audio.protection.maxAllowed from: 0 to: 100 @@ -63,13 +63,13 @@ ContentPage { Config.options.audio.protection.maxAllowed = value; } StyledToolTip { - text: "Maximum volume level allowed by earbang protection" + text: qsTr("Maximum volume level allowed by earbang protection") } } } ContentSection { - title: "Devices" + title: qsTr("Devices") icon: "headphones" AudioDeviceSelectorButton { @@ -81,7 +81,7 @@ ContentPage { } ContentSection { - title: "Volume mixer" + title: qsTr("Volume mixer") icon: "tune" // Rectangle { @@ -122,12 +122,12 @@ ContentPage { } ContentSection { - title: "System sounds" + title: qsTr("System sounds") icon: "graphic_eq" ConfigSwitch { visible: UPower.displayDevice.isLaptopBattery - text: "Enable battery notification sounds" + text: qsTr("Enable battery notification sounds") checked: Config.options.battery.sound onClicked: checked = !checked; onCheckedChanged: { @@ -137,7 +137,7 @@ ContentPage { } ContentSection { - title: "Default Media Player" + title: qsTr("Default Media Player") icon: "play_circle" @@ -146,7 +146,7 @@ ContentPage { id: mediaPlayer Layout.fillWidth: true - placeholderText: "Default Media Player" + placeholderText: qsTr("Default Media Player") text: Config.options.dashboard.mediaPlayer wrapMode: TextEdit.Wrap onEditingFinished: { diff --git a/src/share/sleex/modules/settings/Style.qml b/src/share/sleex/modules/settings/Style.qml index f7815cc..4f920d4 100644 --- a/src/share/sleex/modules/settings/Style.qml +++ b/src/share/sleex/modules/settings/Style.qml @@ -26,7 +26,7 @@ ContentPage { } ContentSection { - title: "Colors" + title: qsTr("Colors") icon: "palette" ButtonGroup { @@ -37,7 +37,7 @@ ContentPage { } StyledText { - text: "Material Palette" + text: qsTr("Material Palette") color: Appearance.colors.colSubtext } @@ -61,7 +61,7 @@ ContentPage { ConfigSwitch { id: staticColorsSwitch - text: "Static colors" + text: qsTr("Static colors") onClicked: checked = !checked; checked: Config.options.appearance.palette.useStaticColors onCheckedChanged: { @@ -70,7 +70,7 @@ ContentPage { // else Quickshell.execDetached(["sh", `${Directories.wallpaperSwitchScriptPath}`, "--noswitch", "--color", `${Config.options.appearance.palette.accentColorHex}`]) } StyledToolTip { - text: "Use a static accent color instead of extracting colors from wallpaper" + text: qsTr("Use a static accent color instead of extracting colors from wallpaper") } } @@ -102,11 +102,11 @@ ContentPage { } ContentSection { - title: "Wallpaper" + title: qsTr("Wallpaper") icon: "wallpaper" StyledText { - text: "Transition Style" + text: qsTr("Transition Style") color: Appearance.colors.colSubtext } @@ -134,7 +134,7 @@ ContentPage { } StyledText { - text: "Wipe Direction" + text: qsTr("Wipe Direction") color: Appearance.colors.colSubtext visible: isWipeSelected } @@ -154,7 +154,7 @@ ContentPage { } StyledText { - text: "Animation Intensity" + text: qsTr("Animation Intensity") color: Appearance.colors.colSubtext } @@ -174,7 +174,7 @@ ContentPage { MaterialTextField { id: wallpaperPathField Layout.fillWidth: true - placeholderText: "Wallpaper selector directory path" + placeholderText: qsTr("Wallpaper selector directory path") text: Config.options.background.wallpaperSelectorPath wrapMode: TextEdit.Wrap onTextChanged: { @@ -202,7 +202,7 @@ ContentPage { StyledText { anchors.centerIn: parent - text: "No wallpaper set" + text: qsTr("No wallpaper set") color: Appearance.colors.colSubtext visible: !Config.options.background.wallpaperPath } diff --git a/src/share/sleex/modules/settings/Wifi.qml b/src/share/sleex/modules/settings/Wifi.qml index 9a6cc43..cc780bb 100644 --- a/src/share/sleex/modules/settings/Wifi.qml +++ b/src/share/sleex/modules/settings/Wifi.qml @@ -129,7 +129,7 @@ ContentPage { // } ContentSection { - title: "Wifi settings" + title: qsTr("Wifi settings") icon: "network_wifi" RowLayout { @@ -137,7 +137,7 @@ ContentPage { uniformCellSizes: true ConfigSwitch { - text: "Enabled" + text: qsTr("Enabled") checked: Network.wifiEnabled || false onClicked: { const newState = !checked; @@ -145,7 +145,7 @@ ContentPage { Network.toggleWifi(); } StyledToolTip { - text: Network.wifiEnabled ? "Click to disable WiFi" : "Click to enable WiFi" + text: Network.wifiEnabled ? qsTr("Click to disable WiFi") : qsTr("Click to enable WiFi") } } } @@ -196,7 +196,7 @@ ContentPage { StyledToolTip { extraVisibleCondition: discoverArea.containsMouse - text: "Discover new networks" + text: qsTr("Discover new networks") } } } @@ -218,7 +218,7 @@ ContentPage { StyledText { Layout.alignment: Qt.AlignHCenter - text: "Turn on WiFi to see available networks" + text: qsTr("Turn on WiFi to see available networks") color: Appearance.colors.colOnLayer1 font.pixelSize: Appearance.font.pixelSize.large horizontalAlignment: Text.AlignHCenter @@ -230,7 +230,7 @@ ContentPage { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 - placeholderText: "Search networks" + placeholderText: qsTr("Search networks") visible: Network.wifiEnabled || false } @@ -325,7 +325,7 @@ ContentPage { StyledText { Layout.fillWidth: true - text: "Open Network" + text: qsTr("Open Network") font.pixelSize: Appearance.font.pixelSize.small color: Appearance.colors.colSubtext visible: !(networkItem.modelData?.isSecure || false) @@ -334,7 +334,7 @@ ContentPage { // Connection error display for this network StyledText { Layout.fillWidth: true - text: "Failed to connect: " + root.lastConnectionError + text: qsTr("Failed to connect: %1").arg(root.lastConnectionError) font.pixelSize: Appearance.font.pixelSize.small color: Appearance.m3colors.m3error wrapMode: Text.WordWrap @@ -371,7 +371,7 @@ ContentPage { StyledToolTip { extraVisibleCondition: expandArea.containsMouse - text: networkItem.expanded ? "Collapse" : "Expand" + text: networkItem.expanded ? qsTr("Collapse") : qsTr("Expand") } } } @@ -461,14 +461,14 @@ ContentPage { anchors.fill: parent anchors.margins: 8 spacing: 4 - StyledText { text: "BSSID: " + networkItem.modelData.bssid } - StyledText { text: "Frequence: " + networkItem.modelData.frequency } - StyledText { text: "Security: " + networkItem.modelData.security } + StyledText { text: qsTr("BSSID: %1").arg(networkItem.modelData.bssid) } + StyledText { text: qsTr("Frequency: %1").arg(networkItem.modelData.frequency) } + StyledText { text: qsTr("Security: %1").arg(networkItem.modelData.security) } StyledText { - text: "Password:" + text: qsTr("Password:") visible: (networkItem.modelData?.isSecure || false) && (!(networkItem.modelData?.isKnown || false) || Network.hasConnectionFailed(networkItem.modelData.ssid)) } Rectangle { @@ -501,7 +501,7 @@ ContentPage { verticalAlignment: TextInput.AlignVCenter Text { - text: "Enter password..." + text: qsTr("Enter password...") color: Appearance.m3colors.m3outline font.pixelSize: Appearance?.font.pixelSize.small visible: !passwdInput.text && !passwdInput.activeFocus diff --git a/src/share/sleex/modules/settings/displaySettings/DisplaySettings.qml b/src/share/sleex/modules/settings/displaySettings/DisplaySettings.qml index e154e08..efa2162 100644 --- a/src/share/sleex/modules/settings/displaySettings/DisplaySettings.qml +++ b/src/share/sleex/modules/settings/displaySettings/DisplaySettings.qml @@ -97,7 +97,7 @@ Item { } StyledText { anchors.horizontalCenter: parent.horizontalCenter - text: "No monitors detected" + text: qsTr("No monitors detected") color: Appearance.colors.colSubtext } } @@ -234,7 +234,7 @@ Item { const m = Monitors.monitors.find(m => m.name === root.selectedMonitorName) return m ? Math.round(m.scale * 100) : 100 } - text: "Scale" + text: qsTr("Scale") onValueChanged: { Monitors.applyScale(root.selectedMonitorName, value / 100.0) } diff --git a/src/share/sleex/settings.qml b/src/share/sleex/settings.qml index 6098bcc..cd1e51f 100644 --- a/src/share/sleex/settings.qml +++ b/src/share/sleex/settings.qml @@ -12,6 +12,7 @@ import Quickshell import Quickshell.Io import Quickshell.Hyprland import Quickshell.Widgets +import Sleex.Core import qs import qs.services import qs.modules.common @@ -33,22 +34,22 @@ ApplicationWindow { property bool showNextTime: false property int currentPage: 0 property var pages: [ - { name: "Style", icon: "palette", component: "modules/settings/Style.qml", type: "item" }, - { name: "Interface", icon: "space_dashboard", component: "modules/settings/Interface.qml", type: "item" }, + { name: qsTr("Style"), icon: "palette", component: "modules/settings/Style.qml", type: "item" }, + { name: qsTr("Interface"), icon: "space_dashboard", component: "modules/settings/Interface.qml", type: "item" }, { type: "divider" }, - { name: "Behavior", icon: "settings", component: "modules/settings/BehaviorConfig.qml", type: "item" }, - { name: "Sound", icon: "brand_awareness", component: "modules/settings/Sound.qml", type: "item" }, - { name: "Bluetooth", icon: "bluetooth", component: "modules/settings/Bluetooth.qml", type: "item" }, - { name: "Wifi", icon: "wifi", component: "modules/settings/Wifi.qml", type: "item" }, - { name: "Applications",icon: "apps", component: "modules/settings/Applications.qml", type: "item" }, - { name: "Display", icon: "display_settings", component: "modules/settings/Display.qml", type: "item" }, + { name: qsTr("Behavior"), icon: "settings", component: "modules/settings/BehaviorConfig.qml", type: "item" }, + { name: qsTr("Sound"), icon: "brand_awareness", component: "modules/settings/Sound.qml", type: "item" }, + { name: qsTr("Bluetooth"), icon: "bluetooth", component: "modules/settings/Bluetooth.qml", type: "item" }, + { name: qsTr("Wifi"), icon: "wifi", component: "modules/settings/Wifi.qml", type: "item" }, + { name: qsTr("Applications"),icon: "apps", component: "modules/settings/Applications.qml", type: "item" }, + { name: qsTr("Display"), icon: "display_settings", component: "modules/settings/Display.qml", type: "item" }, { type: "divider" }, - { name: "Privacy", icon: "lock", component: "modules/settings/Privacy.qml", type: "item" }, - { name: "About", icon: "info", component: "modules/settings/About.qml", type: "item" } + { name: qsTr("Privacy"), icon: "lock", component: "modules/settings/Privacy.qml", type: "item" }, + { name: qsTr("About"), icon: "info", component: "modules/settings/About.qml", type: "item" } ] visible: true - title: "Sleex Settings" + title: qsTr("Sleex Settings") onClosing: Qt.quit() diff --git a/src/share/sleex/translations/sleex_pt_BR.ts b/src/share/sleex/translations/sleex_pt_BR.ts index 80cd8cc..2ceb5f8 100644 --- a/src/share/sleex/translations/sleex_pt_BR.ts +++ b/src/share/sleex/translations/sleex_pt_BR.ts @@ -1,768 +1,1602 @@ - - + - - ActiveWindow - - - Desktop - Área de Trabalho - - - - Workspace - Espaço de Trabalho - - - - AudioDeviceSelectorButton - - - Input - Entrada - - - - Output - Saída - - - - Unknown - Desconhecido - - - - Bar - - - Scroll to change brightness - Rolle para alterar brilho - - - - Scroll to change volume - Rolle para alterar volume - - - - BatteryPopup - - - Critical Battery - Bateria crítica - - - - Low Battery - Bateria baixa - - - - %1% battery remaining. - %1% da bateria restante. - - - - Power Saver Mode - Modo Economia de Energia - - - - Close - Fechar - - - - BatteryWarningOverlay - - - Critical Battery - Bateria Crítica - - - - Low Battery - Bateria Baixa - - - - %1% battery remaining. - %1% de bateria restante. - - - - Power Saver Mode - Modo Economia de Energia - - - - Close - Fechar - - - - Bluetooth - - - %1 device%2 available - %1 dispositivo%2 disponível - - - - (%1 connected) - (%1 conectado) - - - - (Connected) - (Conectado) - - - - (Paired) - (Parado) - - - - BluetoothToggle - - - {0} | Right-click to configure - {0} | Clique com o botão direito para configurar - - - - Bluetooth - Bluetooth - - - - Brightness - - - Increase brightness - Aumentar brilho - - - - Decrease brightness - Diminuir brilho - - - - CalendarAdd - - - Add a new event. - Adicionar um novo evento. - - - - Title - Título - - - - Date - Data - - - - All Day Event - Evento de todo o dia - - - - Validate - Validar - - - - Cancel - Cancelar - - - - - Done - Concluído - - - - CalendarEdit - - - Edit an event. - Editar um evento. - - - - Title - Título - - - - Date - Data - - - - All Day Event - Evento do dia inteiro - - - - Validate - Validar - - - - Cancel - Cancelar - - - - - Done - Concluído - - - - CalendarTimeTable - - - Event - Evento - - - - All day - Todo dia - - - - CalendarWidget - - - Jump to current month - Ir para o mês atual - - - - Cheatsheet - - - Cheat sheet - Folha de atalhos - - - - Toggles cheatsheet on press - Ativa folha de atalhos ao pressionar - - - - Opens cheatsheet on press - Abre folha de atalhos ao pressionar - - - - Closes cheatsheet on press - Fecha a folha de atalhos ao pressionar - - - - ConfigLoader - - - - Shell configuration failed to load - Configuração do shell falhou ao carregar - - - - Shell configuration created - Configuração do shell criada - - - - Dashboard - - - Uptime: {0} - Tempo de atividade: {0} - - - - Reload Hyprland & Quickshell - Recarregar Hyprland & Quickshell - - - - Settings - Configurações - - - - Session - Sessão - - - - Toggles dashboard on press - Alternar painel ao pressionar - - - - Opens dashboard on press - Abrir painel ao pressionar - - - - Closes dashboard on press - Fechar painel ao pressionar - - - - DashboardTabs - - - Home - Início - - - - Todo - Tarefas - - - - Calendar - Calendário - - - - Display - - - Intensity - Intensidade - - - - Schedule - Agenda - - - - Night light start time - Horário de início da luz noturna - - - - Night light end time - Horário de fim da luz noturna - - - - Cancel - Cancelar - - - - Done - Concluído - - - - GameMode - - - Game mode - GameMode - - - - GlobalStates - - - Hold to show workspace numbers, release to show icons - Segure para mostrar números da área de trabalho, solte para mostrar ícones - - - - Lock screen (obviously) - Tela de bloqueio (óbvio) - - - - HomeGithubWidget - - - Loading... - Carregando... - - - - -- - -- - - - - contributions in the last year - contribuições do último ano - - - - HomeUserInfoWidget - - - Welcome, %1! - Bem-vindo, %1! - - - - IdleInhibitor - - - Keep system awake - Mantenha o sistema acordado - - - - KeyringStorage - - - For storing API keys and other sensitive information - Para armazenar chaves de API e outras informações sensíveis - - - - {0} Safe Storage - {0} Armazenamento Seguro - - - - LockSurface - - - -- - -- - - - - Enter password - Insira a senha - - - - MprisController - - - Unknown Title - Título desconhecido - - - - Unknown Artist - Artista desconhecido - - - - Unknown Album - Álbum desconhecido - - - - NetworkToggle - - - {0} | Right-click to configure - {0} | Clique com o botão direito para configurar - - - - Wi-Fi Disabled - Wi-Fi Desativado - - - - NotificationItem - - - Close - Fechar - - - - NotificationList - - - No notifications - Nenhuma notificação - - - - Silent - Silencioso - - - - Clear - Limpar - - - - OnScreenDisplayBrightness - - - Brightness - Brilho - - - - Triggers brightness OSD on press - Ativa o OSD de brilho ao pressionar - - - - Hides brightness OSD on press - Oculta o OSD de brilho ao pressionar - - - - OnScreenDisplayVolume - - - Volume - Volume - - - - Triggers volume OSD on press - Ativa o OSD de volume ao pressionar - - - - Hides volume OSD on press - Oculta o OSD de volume ao pressionar - - - - Overview - - - Toggles overview on press - Ativa a visão geral ao pressionar - - - - Closes overview - Fecha a visão geral - - - - Toggles overview on release - Ativa a visão geral ao soltar - - - - Interrupts possibility of overview being toggled on release. - Interrompe a possibilidade de ativar a visão geral ao soltar. - - - - This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. - Isso é necessário porque GlobalShortcut.onReleased no quickshell determina se você pressiona algo else enquanto segura a tecla. - - - - To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything. - Para garantir que isso funcione consistentemente, use binditn = MODKEYS, catchall em uma submapa automaticamente acionada que inclui tudo. - - - - Toggle clipboard query on overview widget - Alternar consulta de área de transferência no widget de visão geral - - - - Toggle emoji query on overview widget - Alternar consulta de emoji no widget de visão geral - - - - PlayerControlBlank - - - Nothing playing - Nada tocando - - - - No music is currently playing - Nenhuma música está tocando no momento - - - - SelectionDialog - - - Cancel - Cancelar - - - - OK - OK - - - - Session - - - Session - Sessão - - - - Arrow keys to navigate, Enter to select + + About + + + Distro + Distro + + + + ActiveWindow + + + Desktop + Área de Trabalho + + + + Workspace + Espaço de Trabalho + + + + Applications + + + Default Applications + Aplicações Padrão + + + + Web Browser + Navegador Web + + + + File Manager + Gerenciador de Arquivos + + + + Image Viewer + Visualizador de Imagens + + + + Video Player + Player de Vídeo + + + + Document Viewer + Visualizador de Documentos + + + + AudioDeviceSelectorButton + + + Input + Entrada + + + + Output + Saída + + + + Unknown + Desconhecido + + + + Bar + + + Scroll to change brightness + Rolle para alterar brilho + + + + Scroll to change volume + Rolle para alterar volume + + + + BatteryPopup + + + Critical Battery + Bateria crítica + + + + Low Battery + Bateria baixa + + + + %1% battery remaining. + %1% da bateria restante. + + + + Power Saver Mode + Modo Economia de Energia + + + + Close + Fechar + + + + BatteryWarningOverlay + + + Critical Battery + Bateria Crítica + + + + Low Battery + Bateria Baixa + + + + %1% battery remaining. + %1% de bateria restante. + + + + Power Saver Mode + Modo Economia de Energia + + + + Close + Fechar + + + + BehaviorConfig + + + Time and date + Horário e data + + + + Time format + Formato do horário + + + + Date format + Formato da data + + + + Power + Energia + + + + Low warning + Aviso de baixo nível + + + + Critical warning + Aviso crítico + + + + Power profile + Perfil de energia + + + + Idle daemon + Demonstrador de inatividade + + + + Enable idle actions + Ativar ações de inatividade + + + + Dim screen + Tela em modo econômico + + + + Decrease the screen brightness after the specified timeout. + Diminua o brilho da tela após o tempo limite especificado. + + + + Enable dimming + Ativar modo econômico + + + + + + Only on battery power + Apenas em modo de bateria + + + + + + Timeout (seconds) + Tempo limite (segundos) + + + + Lock screen + Tela de bloqueio + + + + Lock the session after the specified timeout. + Bloqueie a sessão após o tempo limite especificado. + + + + Enable locking + Habilitar bloqueio + + + + Suspend system + Suspender o sistema + + + + Suspend the system after the specified timeout. + Suspender o sistema após o tempo limite especificado. + + + + Enable suspend + Habilitar suspensão + + + + Bluetooth + + + Bluetooth settings + Configurações do Bluetooth + + + + Enabled + Habilitado + + + + Discoverable + Discoverable + + + + %1 device%2 available + %1 dispositivo%2 disponível + + + + (%1 connected) + (%1 conectado) + + + + Discover new devices + Discover new devices + + + + No bluetooth adapter found + No bluetooth adapter found + + + + Search devices + Search devices + + + + (Connected) + (Conectado) + + + + (Paired) + (Parado) + + + + BluetoothToggle + + + {0} | Right-click to configure + {0} | Clique com o botão direito para configurar + + + + Bluetooth + Bluetooth + + + + Brightness + + + Increase brightness + Aumentar brilho + + + + Decrease brightness + Diminuir brilho + + + + CalendarAdd + + + Add a new event. + Adicionar um novo evento. + + + + Title + Título + + + + Date + Data + + + + All Day Event + Evento de todo o dia + + + + Validate + Validar + + + + Cancel + Cancelar + + + + + Done + Concluído + + + + CalendarEdit + + + Edit an event. + Editar um evento. + + + + Title + Título + + + + Date + Data + + + + All Day Event + Evento do dia inteiro + + + + Validate + Validar + + + + Cancel + Cancelar + + + + + Done + Concluído + + + + CalendarTimeTable + + + Event + Evento + + + + All day + Todo dia + + + + CalendarWidget + + + Jump to current month + Ir para o mês atual + + + + Cheatsheet + + + Cheat sheet + Folha de atalhos + + + + Toggles cheatsheet on press + Ativa folha de atalhos ao pressionar + + + + Opens cheatsheet on press + Abre folha de atalhos ao pressionar + + + + Closes cheatsheet on press + Fecha a folha de atalhos ao pressionar + + + + ConfigLoader + + + + Shell configuration failed to load + Configuração do shell falhou ao carregar + + + + Shell configuration created + Configuração do shell criada + + + + Dashboard + + + Uptime: {0} + Tempo de atividade: {0} + + + + Reload Hyprland & Quickshell + Recarregar Hyprland & Quickshell + + + + Settings + Configurações + + + + Session + Sessão + + + + Toggles dashboard on press + Alternar painel ao pressionar + + + + Opens dashboard on press + Abrir painel ao pressionar + + + + Closes dashboard on press + Fechar painel ao pressionar + + + + DashboardTabs + + + Home + Início + + + + Todo + Tarefas + + + + Calendar + Calendário + + + + Display + + + Monitor arrangement + Monitor arrangement + + + + Brightness + Brilho + + + + Night light + Night light + + + + Enable + Enable + + + + Automatic toggle + Automatic toggle + + + + Intensity + Intensidade + + + + Schedule + Agenda + + + + Night light start time + Horário de início da luz noturna + + + + Night light end time + Horário de fim da luz noturna + + + + Cancel + Cancelar + + + + Done + Concluído + + + + DisplaySettings + + + No monitors detected + No monitors detected + + + + Scale + Escala + + + + GameMode + + + Game mode + GameMode + + + + GlobalStates + + + Hold to show workspace numbers, release to show icons + Segure para mostrar números da área de trabalho, solte para mostrar ícones + + + + Lock screen (obviously) + Tela de bloqueio (óbvio) + + + + HomeGithubWidget + + + Loading... + Carregando... + + + + -- + -- + + + + contributions in the last year + contribuições do último ano + + + + HomeUserInfoWidget + + + Welcome, %1! + Bem-vindo, %1! + + + + IdleInhibitor + + + Keep system awake + Mantenha o sistema acordado + + + + Interface + + + Select profile picture + Selecione a foto de perfil + + + + Select avatar image + Selecione a imagem do avatar + + + + Shell style + Estilo do shell + + + + Transparency + Transparência + + + + Enable the blur effect on the shell. + Ative o efeito de desfoque no shell. + + + + Opacity + Opacidade + + + + Bar + Barra + + + + Show app name + Mostrar nome da aplicação + + + + Show resources usage + Mostrar uso de recursos + + + + Show Workspaces + Mostrar áreas de trabalho + + + + + Show clock + Mostrar relógio + + + + Show system icons + Mostrar ícones do sistema + + + + Enable bar background + Ativar fundo da barra + + + + Workspaces + Áreas de trabalho + + + + Tip: Hide icons for the +classic Sleex experience + Dica: oculte os ícones para ter a +experiência clássica do Sleex + + + + Show app icons + Mostrar ícones das aplicações + + + + Use Material icons + Usar ícones Material + + + + Always show numbers + Sempre mostrar números + + + + Workspaces shown + Áreas de trabalho exibidas + + + + Number show delay when pressing Super (ms) + Atraso para exibir números ao pressionar Super (ms) + + + + Dashboard + Painel + + + + Scale + Escala + + + + Github username + Nome de usuário do Github + + + + User description + Descrição do usuário + + + + Avatar image path + Caminho da imagem do avatar + + + + Optional features + Funcionalidades opcionais + + + + May affect performance. +The dashboard may load more slowly. + Pode afetar o desempenho. +O painel pode carregar mais lentamente. + + + + Todo list + Lista de tarefas + + + + Calendar tab + Guia do calendário + + + + Animation Direction + Direção da animação + + + + Animation Intensity + Intensidade da animação + + + + Dock + Dock + + + + Enabled + Ativado + + + + Height + Altura + + + + Hover to reveal + Mostrar ao passar o mouse + + + + Pinned on startup + Fixo no início + + + + Hover region height + Altura da região de passagem + + + + Background + Fundo + + + + Fixed clock position + Posição fixa do relógio + + + + Show watermark + Mostrar marca d'água + + + + Show quotes + Mostrar citações + + + + Show desktop icons + Mostrar ícones do desktop + + + + Clock mode + Modo do relógio + + + + Clock Font + Fonte do relógio + + + + Quote Source + Fonte da citação + + + + The local quotes are stored in /usr/share/sleex/assets/quotes.json. +These quotes are made by the AxOS community and are tech related. + As citações locais são armazenadas em /usr/share/sleex/assets/quotes.json. +Essas citações são feitas pela comunidade do AxOS e são relacionadas à tecnologia. + + + + Notifications + Notificações + + + + Battery overlay warnings + Avisos da sobreposição da bateria + + + + Lock Screen + Tela de bloqueio + + + + Scrim background + Fundo da tela de bloqueio + + + + Player integration + Integração do player + + + + Show the media player widget on the lock screen. + Mostrar o widget do player na tela de bloqueio. + + + + Resizable widget + Widget redimensionável + + + + Allow resizing & repositioning of the media player widget. + Permitir redimensionamento e reorganização do widget do player. + + + + Login Screen + Tela de login + + + + Custom picture set + Conjunto de imagens personalizado + + + + No picture selected + Nenhuma imagem selecionada + + + + Calendar + Calendário + + + + Advanced + Avançado + + + + Vdirsyncer is not configured by default. +Please refer to the documentation +to set it up. Enable it only after configuration. + O Vdirsyncer não está configurado por padrão. +Por favor, consulte a documentação +para configurá-lo. Ative-o apenas após a configuração. + + + + Use vdirsyncer + Use vdirsyncer + + + + Sync interval (minutes) + Intervalo de sincronização (minutos) + + + + KeyringStorage + + + For storing API keys and other sensitive information + Para armazenar chaves de API e outras informações sensíveis + + + + {0} Safe Storage + {0} Armazenamento Seguro + + + + LockSurface + + + -- + -- + + + + Enter password + Insira a senha + + + + MprisController + + + Unknown Title + Título desconhecido + + + + Unknown Artist + Artista desconhecido + + + + Unknown Album + Álbum desconhecido + + + + NetworkToggle + + + {0} | Right-click to configure + {0} | Clique com o botão direito para configurar + + + + Wi-Fi Disabled + Wi-Fi Desativado + + + + NotificationItem + + + Close + Fechar + + + + NotificationList + + + No notifications + Nenhuma notificação + + + + Silent + Silencioso + + + + Clear + Limpar + + + + OnScreenDisplayBrightness + + + Brightness + Brilho + + + + Triggers brightness OSD on press + Ativa o OSD de brilho ao pressionar + + + + Hides brightness OSD on press + Oculta o OSD de brilho ao pressionar + + + + OnScreenDisplayVolume + + + Volume + Volume + + + + Triggers volume OSD on press + Ativa o OSD de volume ao pressionar + + + + Hides volume OSD on press + Oculta o OSD de volume ao pressionar + + + + Overview + + + Toggles overview on press + Ativa a visão geral ao pressionar + + + + Closes overview + Fecha a visão geral + + + + Toggles overview on release + Ativa a visão geral ao soltar + + + + Interrupts possibility of overview being toggled on release. + Interrompe a possibilidade de ativar a visão geral ao soltar. + + + + This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. + Isso é necessário porque GlobalShortcut.onReleased no quickshell determina se você pressiona algo else enquanto segura a tecla. + + + + To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything. + Para garantir que isso funcione consistentemente, use binditn = MODKEYS, catchall em uma submapa automaticamente acionada que inclui tudo. + + + + Toggle clipboard query on overview widget + Alternar consulta de área de transferência no widget de visão geral + + + + Toggle emoji query on overview widget + Alternar consulta de emoji no widget de visão geral + + + + PlayerControlBlank + + + Nothing playing + Nada tocando + + + + No music is currently playing + Nenhuma música está tocando no momento + + + + Privacy + + + Weather + Clima + + + + Enabled + Habilitado + + + + View weather forecasts directly in your dashboard. +It uses the https://open-meteo.com provider. + Visualize previsões do clima diretamente no seu painel. +Ele usa o provedor https://open-meteo.com. + + + + Automatic Location + Localização Automática + + + + IP-based approximate location. + Localização aproximada baseada no IP. + + + + Weather Location + Localização do Clima + + + + Media Player + Player de Mídia + + + + Lyrics + Letras + + + + Fetch and display synced lyrics (LRCLIB provider). + Buscar e exibir letras sincronizadas (provedor LRCLIB). + + + + SelectionDialog + + + Cancel + Cancelar + + + + OK + OK + + + + Session + + + Session + Sessão + + + + Arrow keys to navigate, Enter to select Esc or click anywhere to cancel - Setas para navegar, Enter para selecionar + Setas para navegar, Enter para selecionar Esc ou clique em qualquer lugar para cancelar - - - - Lock - Bloquear - - - - Sleep - Suspender - - - - Logout - Encerrar sessão - - - - Task Manager - Gerenciador de tarefas - - - - Hibernate - Hibernate - - - - Shutdown - Desligar - - - - Reboot - Reiniciar - - - - Reboot to firmware settings - Reiniciar para configurações do firmware - - - - Toggles session screen on press - Ativa a tela de sessão ao pressionar - - - - Opens session screen on press - Abre a tela de sessão ao pressionar - - - - TodoWidget - - - Nothing here! - Nada aqui! - - - - Finished tasks goes there! - Tarefas concluídas ficam por aqui! - - - - Edit Task - Editar tarefa - - - - Add Task - Adicionar tarefa - - - - Task description - Descrição da tarefa - - - - Cancel - Cancelar - - - - Edit - Editar - - - - Add - Adicionar - - - - WallpaperSelector - - - Toggles wallpaper selector on press - Ativa o seletor de papel de parede ao pressionar - - - - Opens wallpaper selector on press - Abre o seletor de papel de parede ao pressionar - - - - Closes wallpaper selector on press - Fecha o seletor de papel de parede ao pressionar - - - - Wifi - - - %1 network%2 available - %1 rede%2 disponível - - - - (%1 connected) - (%1 conectado) - - - - greetd - - - Enter password - Insira a senha - - - + + + + Lock + Bloquear + + + + Sleep + Suspender + + + + Logout + Encerrar sessão + + + + Task Manager + Gerenciador de tarefas + + + + Hibernate + Hibernate + + + + Shutdown + Desligar + + + + Reboot + Reiniciar + + + + Reboot to firmware settings + Reiniciar para configurações do firmware + + + + Toggles session screen on press + Ativa a tela de sessão ao pressionar + + + + Opens session screen on press + Abre a tela de sessão ao pressionar + + + + Sound + + + Protection + Proteção + + + + Earbang protection + Proteção contra ruídos + + + + Prevents abrupt increments and restricts volume limit + Impede aumentos abruptos e limita o limite de volume + + + + Earbang limit + Limite de ruído + + + + Maximum volume level allowed by earbang protection + Nível máximo de volume permitido pela proteção contra ruídos + + + + Devices + Dispositivos + + + + Volume mixer + Mixer de volume + + + + System sounds + Sons do sistema + + + + Enable battery notification sounds + Ativar sons de notificação de bateria + + + + + Default Media Player + Player de mídia padrão + + + + Style + + + Colors + Cores + + + + Material Palette + Paleta de materiais + + + + Static colors + Cores estáticas + + + + Use a static accent color instead of extracting colors from wallpaper + Use uma cor de destaque estática em vez de extrair cores do papel de parede + + + + Wallpaper + Papel de parede + + + + Transition Style + Estilo de transição + + + + Wipe Direction + Direção de apagamento + + + + Animation Intensity + Intensidade da animação + + + + Wallpaper selector directory path + Caminho do diretório do seletor de papel de parede + + + + No wallpaper set + Nenhum papel de parede definido + + + + TodoWidget + + + Nothing here! + Nada aqui! + + + + Finished tasks goes there! + Tarefas concluídas ficam por aqui! + + + + Edit Task + Editar tarefa + + + + Add Task + Adicionar tarefa + + + + Task description + Descrição da tarefa + + + + Cancel + Cancelar + + + + Edit + Editar + + + + Add + Adicionar + + + + WallpaperSelector + + + Toggles wallpaper selector on press + Ativa o seletor de papel de parede ao pressionar + + + + Opens wallpaper selector on press + Abre o seletor de papel de parede ao pressionar + + + + Closes wallpaper selector on press + Fecha o seletor de papel de parede ao pressionar + + + + Wifi + + + Wifi settings + Configurações Wi-Fi + + + + Enabled + Ativado + + + + Click to disable WiFi + Clique para desativar Wi-Fi + + + + Click to enable WiFi + Clique para ativar Wi-Fi + + + + %1 network%2 available + %1 rede%2 disponível + + + + (%1 connected) + (%1 conectado) + + + + Discover new networks + Descubra novas redes + + + + Turn on WiFi to see available networks + Ative o Wi-Fi para ver redes disponíveis + + + + Search networks + Pesquisar redes + + + + Open Network + Abrir Rede + + + + Failed to connect: %1 + Falha na conexão: %1 + + + + Collapse + Colapsar + + + + Expand + Expandir + + + + BSSID: %1 + BSSID: %1 + + + + Frequency: %1 + Frequência: %1 + + + + Security: %1 + Segurança: %1 + + + + Password: + Senha: + + + + Enter password... + Digite a senha... + + + + greetd + + + Enter password + Insira a senha + + + + settings + + + Style + Estilo + + + + Interface + Interface + + + + Behavior + Comportamento + + + + Sound + Som + + + + Bluetooth + Bluetooth + + + + Wifi + Wi-Fi + + + + Applications + Aplicativos + + + + Display + Tela + + + + Privacy + Privacidade + + + + About + Sobre + + + + Sleex Settings + Configurações do Sleex + + + \ No newline at end of file From 6ea75c3a22466a19f987804e2af0a85c97ec17d4 Mon Sep 17 00:00:00 2001 From: Maylton Fernandes Date: Fri, 24 Jul 2026 13:57:24 -0300 Subject: [PATCH 6/6] chore: ignore Python cache files --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e4e69e7..3280929 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ *.tar.zst pkg/ -.vscode/ \ No newline at end of file +.vscode/ +# Python cache +__pycache__/ +*.py[cod]