Electron support for Linux (#1074)

* init

* SQLite changes

* Move html folder, edit build scripts

* AppApi interface

* Build flags

* AppApi inheritance

* Finishing touches

* Merge upstream changes

* Test CI

* Fix class inits

* Rename AppApi

* Merge upstream changes

* Fix SQLiteLegacy on Linux, Add Linux interop, build tools

* Linux specific localisation strings

* Make it run

* Bring back most of Linux functionality

* Clean up

* Fix TTS voices

* Fix UI var

* Changes

* Electron minimise to tray

* Remove separate toggle for WlxOverlay

* Fixes

* Touchups

* Move csproj

* Window zoom, Desktop Notifications, VR check on Linux

* Fix desktop notifications, VR check spam

* Fix building on Linux

* Clean up

* Fix WebApi headers

* Rewrite VRCX updater

* Clean up

* Linux updater

* Add Linux to build action

* init

* SQLite changes

* Move html folder, edit build scripts

* AppApi interface

* Build flags

* AppApi inheritance

* Finishing touches

* Merge upstream changes

* Test CI

* Fix class inits

* Rename AppApi

* Merge upstream changes

* Fix SQLiteLegacy on Linux, Add Linux interop, build tools

* Linux specific localisation strings

* Make it run

* Bring back most of Linux functionality

* Clean up

* Fix TTS voices

* Changes

* Electron minimise to tray

* Remove separate toggle for WlxOverlay

* Fixes

* Touchups

* Move csproj

* Window zoom, Desktop Notifications, VR check on Linux

* Fix desktop notifications, VR check spam

* Fix building on Linux

* Clean up

* Fix WebApi headers

* Rewrite VRCX updater

* Clean up

* Linux updater

* Add Linux to build action

* Test updater

* Rebase and handle merge conflicts

* Fix Linux updater

* Fix Linux app restart

* Fix friend order

* Handle AppImageInstaller, show an install message on Linux

* Updates to the AppImage installer

* Fix Linux updater, fix set version, check for .NET, copy wine prefix

* Handle random errors

* Rotate tall prints

* try fix Linux restart bug

* Final

---------

Co-authored-by: rs189 <35667100+rs189@users.noreply.github.com>
This commit is contained in:
Natsumi
2025-01-11 13:09:44 +13:00
committed by GitHub
parent a39eb9d5ed
commit 938fff63d0
223 changed files with 15841 additions and 9562 deletions

2012
src/localization/en/en.json Normal file

File diff suppressed because it is too large Load Diff

1784
src/localization/es/en.json Normal file

File diff suppressed because it is too large Load Diff

2006
src/localization/fr/en.json Normal file

File diff suppressed because it is too large Load Diff

1734
src/localization/hu/en.json Normal file

File diff suppressed because it is too large Load Diff

1911
src/localization/ja/en.json Normal file

File diff suppressed because it is too large Load Diff

1734
src/localization/ko/en.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,229 @@
// Because this isn't a package (just a loose js file), we have to use require
// statements
const process = require("node:process")
const fs = require('node:fs');
const path = require('node:path');
const yargs = require("yargs/yargs");
const { hideBin } = require("yargs/helpers")
const getLocalizationObjects = function* () {
const localeFolder = './src/localization';
const folders = fs.readdirSync(localeFolder, { withFileTypes: true }).filter(file => file.isDirectory());
for (const folder of folders) {
const filePath = path.join(localeFolder, folder.name, "en.json");
const jsonStr = fs.readFileSync(filePath);
yield [filePath, JSON.parse(jsonStr)];
}
}
const addKey = function (obj, objects, value, above_key) {
console.log(`Adding key to ${obj.language} at path '${objects.join('.')}' with value '${value}' above key '${above_key}'`);
let currentObj = obj;
let i = 0;
// Last element is final key not object so loop n - 1 times
for (; i < objects.length - 1; i++) {
if (!Object.hasOwn(currentObj, objects[i])) {
currentObj[objects[i]] = {};
}
currentObj = currentObj[objects[i]];
}
InsertKeyInObj(currentObj, objects[i], value, above_key);
}
// Shamelessly stolen from https://stackoverflow.com/a/55017155/11030436
const InsertKeyInObj = (obj, key, value, above_key) => {
const keys = Object.keys(obj);
if (keys.length === 0 || !(Object.hasOwn(obj, above_key))) {
obj[key] = value;
return obj;
}
// Reconstruct object from scratch, inserting our new key when the next
// key is the above_key
// Again utilize the dummy key in case we're adding above the first key
keys.unshift('dummy');
obj.dummy = {};
const ret = keys.reduce((newObj, currKey, i) => {
if (currKey !== key) {
newObj[currKey] = obj[currKey];
}
if (i < keys.length - 1 && keys[i + 1] === above_key) {
newObj[key] = value;
}
return newObj;
}, {})
delete ret.dummy;
// Clear keys on old object
for (const key of keys) {
delete obj[key];
}
// Assign new properties to old object
Object.assign(obj, ret);
}
const addLocalizationKey = (key, value, above_key) => {
const objects = key.split('.');
for (const [localePath, localeObj] of getLocalizationObjects()) {
addKey(localeObj, objects, value, above_key);
fs.writeFileSync(localePath, `${JSON.stringify(localeObj, null, 4)}\n`);
}
console.log(`\`${key}:${value}\` added to every localization file!`);
}
const removeKey = (obj, objects, i = 0) => {
console.log(`Removing key from ${obj.language} at path '${objects.join('.')}'`);
if (!(Object.hasOwn(obj, objects[i]))) {
return;
}
if (objects.length - 1 === i) {
delete obj[objects[i]];
} else {
removeKey(obj[objects[i]], objects, i + 1);
if (Object.keys(obj[objects[i]]).length === 0) {
delete obj[objects[i]];
}
}
}
const removeLocalizationKey = (key) => {
const objects = key.split('.');
for (const [localePath, localeObj] of getLocalizationObjects()) {
removeKey(localeObj, objects, 0);
// All the localization files seem to have a trailing new line, so add
// one ourselves
fs.writeFileSync(localePath, `${JSON.stringify(localeObj, null, 4)}\n`);
}
console.log(`\`${key}\` removed from every localization file!`);
}
// Yes this code is extremely slow, but it doesn't run very often so.
const Validate = function () {
const files = [...getLocalizationObjects()];
const enIndex = files.findIndex(file => path.dirname(file[0]).endsWith("en"));
const [_, enObj] = files.splice(enIndex, 1)[0];
const traverse = function (obj, predicate, pathes = []) {
for (const key in obj) {
if (typeof obj[key] === 'string' || obj[key] instanceof String) {
predicate(obj, key, pathes);
} else {
traverse(obj[key], predicate, [...pathes, key]);
}
}
}
let hasRemoved = false;
for (const [_, localeObj] of files) {
toRemove = []
traverse(localeObj, (_, key, pathes) => {
let currObj = enObj;
for (const pathSegment of pathes) {
if (Object.hasOwn(currObj, pathSegment)) {
currObj = currObj[pathSegment]
} else {
toRemove.push([...pathes, key]);
return;
}
}
if (!Object.hasOwn(currObj, key)) {
toRemove.push([...pathes, key]);
}
});
// Remove after traversal finishes to not modify while iterating
for (const toRemovePath of toRemove) {
removeKey(localeObj, toRemovePath);
hasRemoved = true;
}
}
toAdd = []
traverse(enObj, (obj, key, pathes) => {
// Add above_key to the toAdd entry
if (toAdd.length > 0 && typeof toAdd.at(-1)[3] === 'undefined' && toAdd.at(-1)[1].at(-2) === pathes.at(-1)) {
toAdd.at(-1)[3] = key;
}
for (const [_, localeObj] of files) {
let currObj = localeObj;
for (const pathSegment of pathes) {
if (Object.hasOwn(currObj, pathSegment)) {
currObj = currObj[pathSegment];
} else {
toAdd.push([localeObj, [...pathes, key], obj[key], undefined]);
return;
}
}
if (!Object.hasOwn(currObj, key)) {
toAdd.push([localeObj, [...pathes, key], obj[key], undefined]);
}
}
});
for (const addObj of toAdd) {
addKey(...addObj);
}
if (toAdd.length > 0 || hasRemoved) {
for (const [localePath, localeObj] of files) {
fs.writeFileSync(localePath, `${JSON.stringify(localeObj, null, 4)}\n`);
}
} else {
console.log("validation passed!");
}
}
const cliParser = yargs(hideBin(process.argv))
.command({
command: 'add <key> <value> [above_key]',
aliases: ['a', 'replace', 'r'],
desc: 'adds or replaces a key and value to all localization files above `above_key`',
handler: (argv) => addLocalizationKey(argv.key, argv.value, argv.above_key)
})
.command({
command: 'remove <key>',
aliases: ['rm', 'r'],
desc: 'removes key from all localization files',
handler: (argv) => removeLocalizationKey(argv.key)
})
.command({
command: 'validate',
aliases: [],
desc: 'removes keys from other languages that don\'t exist in the en translation and adds keys that don\'t exist in other languages',
handler: Validate
})
.demandCommand(1)
.example([
['$0 add foo.bar "I\'m adding a key!"', 'Adding a key as `foo.bar`'],
['$0 remove foo.bar', 'removes the foo.bar key'],
['$0 add foo.bar "I\'m adding a key!" baz', 'Adding a key aboe the existing `foo.baz` key']
])
.help(false)
.version(false)
cliParser
.wrap(cliParser.terminalWidth())
.command({
command: 'help',
aliases: ['h'],
desc: 'Shows the help message',
handler: () => cliParser.showHelp()
})
.fail(() => cliParser.showHelp())
.parse()

View File

@@ -0,0 +1,63 @@
import en from './en/en.json' assert { type: 'JSON' };
import elements_en from 'element-ui/lib/locale/lang/en';
import es from './es/en.json' assert { type: 'JSON' };
import elements_es from 'element-ui/lib/locale/lang/es';
import fr from './fr/en.json' assert { type: 'JSON' };
import elements_fr from 'element-ui/lib/locale/lang/fr';
// import hu from './hu/en.json' assert { type: 'JSON' };
// import elements_hu from 'element-ui/lib/locale/lang/hu';
import ja from './ja/en.json' assert { type: 'JSON' };
import elements_ja from 'element-ui/lib/locale/lang/ja';
import ko from './ko/en.json' assert { type: 'JSON' };
import elements_ko from 'element-ui/lib/locale/lang/ko';
import pl from './pl/en.json' assert { type: 'JSON' };
import elements_pl from 'element-ui/lib/locale/lang/pl';
import pt from './pt/en.json' assert { type: 'JSON' };
import elements_pt from 'element-ui/lib/locale/lang/pt';
import ru_RU from './ru/en.json' assert { type: 'JSON' };
import elements_ru from 'element-ui/lib/locale/lang/ru-RU';
import vi from './vi/en.json' assert { type: 'JSON' };
import elements_vi from 'element-ui/lib/locale/lang/vi';
import zh_CN from './zh-CN/en.json' assert { type: 'JSON' };
import elements_zh_CN from 'element-ui/lib/locale/lang/zh-CN';
import zh_TW from './zh-TW/en.json' assert { type: 'JSON' };
import elements_zh_TW from 'element-ui/lib/locale/lang/zh-TW';
const localized_en = { ...en, ...elements_en };
const localized_es = { ...es, ...elements_es };
const localized_fr = { ...fr, ...elements_fr };
// const localized_hu = { ...hu, ...elements_hu };
const localized_ja = { ...ja, ...elements_ja };
const localized_ko = { ...ko, ...elements_ko };
const localized_pl = { ...pl, ...elements_pl };
const localized_pt = { ...pt, ...elements_pt };
const localized_ru = { ...ru_RU, ...elements_ru };
const localized_vi = { ...vi, ...elements_vi };
const localized_zh_CN = { ...zh_CN, ...elements_zh_CN };
const localized_zh_TW = { ...zh_TW, ...elements_zh_TW };
export {
localized_en as en,
localized_es as es,
localized_fr as fr,
// localized_hu as hu,
localized_ja as ja_JP,
localized_ko as ko,
localized_pl as pl,
localized_pt as pt,
localized_ru as ru_RU,
localized_vi as vi,
localized_zh_CN as zh_CN,
localized_zh_TW as zh_TW
};

1734
src/localization/pl/en.json Normal file

File diff suppressed because it is too large Load Diff

1734
src/localization/pt/en.json Normal file

File diff suppressed because it is too large Load Diff

1937
src/localization/ru/en.json Normal file

File diff suppressed because it is too large Load Diff

1734
src/localization/vi/en.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff