startup: faster i18n and plugins startup

This commit is contained in:
Lemmy
2026-02-06 11:43:14 -05:00
parent ede5d656e4
commit 77adbbb573
3 changed files with 130 additions and 42 deletions
+73 -8
View File
@@ -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`;
}
}
+42 -25
View File
@@ -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();
});
}
+15 -9
View File
@@ -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;
}