mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
startup: faster i18n and plugins startup
This commit is contained in:
+73
-8
@@ -39,7 +39,9 @@ Singleton {
|
||||
Logger.e("I18n", `Failed to scan translation directory`);
|
||||
// Fallback to default languages
|
||||
availableLanguages = ["en"];
|
||||
detectLanguage();
|
||||
if (!root.isLoaded) {
|
||||
detectLanguage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,8 +66,19 @@ Singleton {
|
||||
}
|
||||
}
|
||||
onLoadFailed: function (error) {
|
||||
setLanguage("en");
|
||||
Logger.e("I18n", `Failed to load translation file: ${error}`);
|
||||
if (root.langCode !== "en") {
|
||||
// Fast-path language file not found, fall back to English directly
|
||||
Logger.w("I18n", `Translation file for "${root.langCode}" not found, falling back to English`);
|
||||
root.langCode = "en";
|
||||
root.fullLocaleCode = "en";
|
||||
root.locale = Qt.locale("en");
|
||||
loadTranslations();
|
||||
} else {
|
||||
Logger.e("I18n", `Failed to load English translation file: ${error}`);
|
||||
// English also failed - still emit signal to unblock startup
|
||||
root.isLoaded = true;
|
||||
root.translationsLoaded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,9 +103,47 @@ Singleton {
|
||||
|
||||
Component.onCompleted: {
|
||||
Logger.i("I18n", "Service started");
|
||||
|
||||
// Fast path: immediately determine language and start loading translations
|
||||
// without waiting for the directory scan
|
||||
var lang = determineFastLanguage();
|
||||
langCode = lang.code;
|
||||
fullLocaleCode = lang.fullLocale;
|
||||
locale = Qt.locale(lang.fullLocale);
|
||||
systemDetectedLangCode = lang.code;
|
||||
Logger.i("I18n", `Fast path: loading "${lang.code}" (locale: "${lang.fullLocale}")`);
|
||||
loadTranslations();
|
||||
|
||||
// Scan available languages in background (needed for settings UI language picker)
|
||||
scanAvailableLanguages();
|
||||
}
|
||||
|
||||
// Determine the most likely language without waiting for directory scan
|
||||
function determineFastLanguage() {
|
||||
// Try user preference from Settings (defaults to "" if not yet loaded from disk)
|
||||
var userLang = Settings.data.general.language;
|
||||
if (userLang !== "") {
|
||||
return {
|
||||
code: userLang,
|
||||
fullLocale: userLang
|
||||
};
|
||||
}
|
||||
|
||||
// Fall back to system locale
|
||||
for (var i = 0; i < Qt.locale().uiLanguages.length; i++) {
|
||||
var fullLang = Qt.locale().uiLanguages[i];
|
||||
var shortLang = fullLang.substring(0, 2);
|
||||
return {
|
||||
code: shortLang,
|
||||
fullLocale: fullLang
|
||||
};
|
||||
}
|
||||
return {
|
||||
code: "en",
|
||||
fullLocale: "en"
|
||||
};
|
||||
}
|
||||
|
||||
// -------------------------------------------
|
||||
function scanAvailableLanguages() {
|
||||
Logger.d("I18n", "Scanning for available translation files...");
|
||||
@@ -107,7 +158,9 @@ Singleton {
|
||||
if (!output || output.trim() === "") {
|
||||
Logger.w("I18n", "Empty directory listing output");
|
||||
availableLanguages = ["en"];
|
||||
detectLanguage();
|
||||
if (!root.isLoaded) {
|
||||
detectLanguage();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -141,13 +194,25 @@ Singleton {
|
||||
availableLanguages = languages;
|
||||
Logger.d("I18n", `Found ${languages.length} available languages: ${languages.join(', ')}`);
|
||||
|
||||
// Detect language after scanning
|
||||
// If translations already loaded via fast path, only correct if user preference differs
|
||||
if (root.isLoaded) {
|
||||
var userLang = Settings.data.general.language;
|
||||
if (userLang !== "" && userLang !== root.langCode && availableLanguages.includes(userLang)) {
|
||||
Logger.i("I18n", `Correcting fast-path: switching to user preference "${userLang}"`);
|
||||
setLanguage(userLang);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Detect language after scanning (fallback if fast path hasn't completed yet)
|
||||
detectLanguage();
|
||||
} catch (e) {
|
||||
Logger.e("I18n", `Failed to parse directory listing: ${e}`);
|
||||
// Fallback to default languages
|
||||
availableLanguages = ["en"];
|
||||
detectLanguage();
|
||||
if (!root.isLoaded) {
|
||||
detectLanguage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,8 +293,8 @@ Singleton {
|
||||
isLoaded = false;
|
||||
Logger.d("I18n", `Loading translations: ${langCode}`);
|
||||
|
||||
// Only load fallback translations if we are not using english and english is available
|
||||
if (langCode !== "en" && availableLanguages.includes("en")) {
|
||||
// Load English fallback for non-English languages (English is always bundled)
|
||||
if (langCode !== "en") {
|
||||
fallbackFileView.path = `file://${Quickshell.shellDir}/Assets/Translations/en.json`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,45 +249,62 @@ Singleton {
|
||||
checkProcess.running = true;
|
||||
}
|
||||
|
||||
// Scan plugin folder to discover installed plugins
|
||||
// Scan plugin folder to discover installed plugins (single process reads all manifests)
|
||||
function scanPluginFolder() {
|
||||
Logger.i("PluginRegistry", "Scanning plugin folder:", root.pluginsDir);
|
||||
|
||||
var lsProcess = Qt.createQmlObject(`
|
||||
var scanProcess = Qt.createQmlObject(`
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
Process {
|
||||
command: ["sh", "-c", "ls -1 '${root.pluginsDir}' 2>/dev/null || true"]
|
||||
command: ["sh", "-c", "for d in '${root.pluginsDir}'/*/; do [ -d \\"$d\\" ] || continue; [ -f \\"$d/manifest.json\\" ] || continue; echo \\"@@PLUGIN@@$(basename \\"$d\\")\\" ; cat \\"$d/manifest.json\\" ; done"]
|
||||
stdout: StdioCollector {}
|
||||
running: true
|
||||
}
|
||||
`, root, "ScanPlugins");
|
||||
`, root, "ScanAllPlugins");
|
||||
|
||||
lsProcess.exited.connect(function (exitCode) {
|
||||
var output = String(lsProcess.stdout.text || "");
|
||||
var pluginDirs = output.trim().split('\n').filter(function (dir) {
|
||||
return dir.length > 0;
|
||||
});
|
||||
scanProcess.exited.connect(function (exitCode) {
|
||||
var output = String(scanProcess.stdout.text || "");
|
||||
var sections = output.split("@@PLUGIN@@");
|
||||
var loadedCount = 0;
|
||||
|
||||
Logger.i("PluginRegistry", "Found", pluginDirs.length, "potential plugin directories");
|
||||
for (var i = 1; i < sections.length; i++) {
|
||||
var section = sections[i];
|
||||
var newlineIdx = section.indexOf('\n');
|
||||
if (newlineIdx === -1)
|
||||
continue;
|
||||
|
||||
if (pluginDirs.length === 0) {
|
||||
// No plugins to load, emit signal immediately
|
||||
root.pluginsChanged();
|
||||
lsProcess.destroy();
|
||||
return;
|
||||
var pluginId = section.substring(0, newlineIdx).trim();
|
||||
var manifestJson = section.substring(newlineIdx + 1).trim();
|
||||
|
||||
if (!pluginId || !manifestJson)
|
||||
continue;
|
||||
|
||||
try {
|
||||
var manifest = JSON.parse(manifestJson);
|
||||
var validation = validateManifest(manifest);
|
||||
|
||||
if (validation.valid) {
|
||||
root.installedPlugins[pluginId] = manifest;
|
||||
Logger.i("PluginRegistry", "Loaded plugin:", pluginId, "-", manifest.name);
|
||||
|
||||
if (!root.pluginStates[pluginId]) {
|
||||
root.pluginStates[pluginId] = {
|
||||
enabled: false
|
||||
};
|
||||
}
|
||||
loadedCount++;
|
||||
} else {
|
||||
Logger.e("PluginRegistry", "Invalid manifest for", pluginId + ":", validation.error);
|
||||
}
|
||||
} catch (e) {
|
||||
Logger.e("PluginRegistry", "Failed to parse manifest for", pluginId + ":", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Track how many manifests we're loading
|
||||
root.pendingManifests = pluginDirs.length;
|
||||
Logger.i("PluginRegistry", "Starting to load", root.pendingManifests, "manifests");
|
||||
|
||||
// Load each manifest
|
||||
for (var i = 0; i < pluginDirs.length; i++) {
|
||||
loadPluginManifest(pluginDirs[i]);
|
||||
}
|
||||
|
||||
lsProcess.destroy();
|
||||
Logger.i("PluginRegistry", "All plugin manifests loaded. Total plugins:", loadedCount);
|
||||
root.pluginsChanged();
|
||||
scanProcess.destroy();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -89,20 +89,26 @@ ShellRoot {
|
||||
sourceComponent: Item {
|
||||
Component.onCompleted: {
|
||||
Logger.i("Shell", "---------------------------");
|
||||
|
||||
// Critical services needed for initial UI rendering
|
||||
WallpaperService.init();
|
||||
ImageCacheService.init();
|
||||
AppThemeService.init();
|
||||
ColorSchemeService.init();
|
||||
LocationService.init();
|
||||
NightLightService.apply();
|
||||
DarkModeService.init();
|
||||
HooksService.init();
|
||||
BluetoothService.init();
|
||||
IdleInhibitorService.init();
|
||||
PowerProfileService.init();
|
||||
HostService.init();
|
||||
GitHubService.init();
|
||||
SupporterService.init();
|
||||
|
||||
// Defer non-critical services to unblock first frame
|
||||
Qt.callLater(function () {
|
||||
LocationService.init();
|
||||
NightLightService.apply();
|
||||
HooksService.init();
|
||||
BluetoothService.init();
|
||||
IdleInhibitorService.init();
|
||||
PowerProfileService.init();
|
||||
HostService.init();
|
||||
GitHubService.init();
|
||||
SupporterService.init();
|
||||
});
|
||||
|
||||
delayedInitTimer.running = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user