initial translation support

This commit is contained in:
MathMan05 2024-10-17 20:27:34 -05:00
parent 33db337a7a
commit f8e10a1e09
7 changed files with 154 additions and 26 deletions

View file

@ -2,12 +2,12 @@ class Contextmenu<x, y>{
static currentmenu: HTMLElement | "";
name: string;
buttons: [
string,
(this: x, arg: y, e: MouseEvent) => void,
string | null,
(this: x, arg: y) => boolean,
(this: x, arg: y) => boolean,
string
string|(()=>string),
(this: x, arg: y, e: MouseEvent) => void,
string | null,
(this: x, arg: y) => boolean,
(this: x, arg: y) => boolean,
string
][];
div!: HTMLDivElement;
static setup(){
@ -27,7 +27,7 @@ class Contextmenu<x, y>{
this.buttons = [];
}
addbutton(
text: string,
text: string|(()=>string),
onclick: (this: x, arg: y, e: MouseEvent) => void,
img: null | string = null,
shown: (this: x, arg: y) => boolean = _=>true,
@ -58,7 +58,11 @@ class Contextmenu<x, y>{
const intext = document.createElement("button");
intext.disabled = !thing[4].bind(addinfo).call(addinfo, other);
intext.classList.add("contextbutton");
intext.textContent = thing[0];
if(thing[0] instanceof Function){
intext.textContent = thing[0]();
}else{
intext.textContent = thing[0];
}
console.log(thing);
if(thing[5] === "button" || thing[5] === "submenu"){
intext.onclick = thing[1].bind(addinfo, other);

96
src/webpage/i18n.ts Normal file
View file

@ -0,0 +1,96 @@
type translation={
[key:string]:string|{[key:string]:string}
};
let res:()=>unknown=()=>{};
class I18n{
static lang:string;
static translations:{[key:string]:string}[]=[];
static done=new Promise<void>((res2,_reject)=>{
res=res2;
});
static async create(json:translation|string,lang:string){
if(typeof json === "string"){
json=await (await fetch(json)).json() as translation;
}
const translations:{[key:string]:string}[]=[];
let translation=json[lang];
if(!translation){
translation=json[lang[0]+lang[1]];
if(!translation){
console.error(lang+" does not exist in the translations");
translation=json["en"];
lang="en";
}
}
translations.push(await this.toTranslation(translation,lang));
if(lang!=="en"){
translations.push(await this.toTranslation(json["en"],"en"))
}
this.lang=lang;
this.translations=translations;
res();
}
static getTranslation(msg:string,...params:string[]):string{
let str:string|undefined;
for(const json of this.translations){
str=json[msg];
if(str){
break;
}
}
if(str){
return this.fillInBlanks(str,params);
}else{
throw new Error(msg+" not found")
}
}
static fillInBlanks(msg:string,params:string[]):string{
//thanks to geotale for the regex
msg=msg.replace(/{{(.+?)}}/g,
(str, match:string) => {
const [op,strsSplit]=this.fillInBlanks(match,params).split(":");
const [first,...strs]=strsSplit.split("|");
switch(op.toUpperCase()){
case "PLURAL":{
const numb=Number(first);
if(numb===0){
return strs[strs.length-1];
}
return strs[Math.min(strs.length-1,numb-1)];
}
case "GENDER":{
if(first==="male"){
return strs[0];
}else if(first==="female"){
return strs[1];
}else if(first==="neutral"){
if(strs[2]){
return strs[2];
}else{
return strs[0];
}
}
}
}
return str;
}
);
msg=msg.replace(/\$\d+/g,(str, match:string) => {
const number=Number(match);
if(params[number-1]){
return params[number-1];
}else{
return str;
}
});
return msg;
}
private static async toTranslation(trans:string|{[key:string]:string},lang:string):Promise<{[key:string]:string}>{
if(typeof trans==='string'){
return this.toTranslation((await (await fetch(trans)).json() as translation)[lang],lang);
}else{
return trans;
}
}
}
export{I18n};

View file

@ -12,14 +12,12 @@ import{ MarkDown }from"./markdown.js";
import { Bot } from "./bot.js";
import { Role } from "./role.js";
import { VoiceFactory } from "./voice.js";
import { I18n } from "./i18n.js";
const wsCodesRetry = new Set([4000, 4003, 4005, 4007, 4008, 4009]);
class Localuser{
badges: Map<
string,
{ id: string; description: string; icon: string; link: string }
> = new Map();
badges: Map<string,{ id: string; description: string; icon: string; link: string }> = new Map();
lastSequence: number | null = null;
token!: string;
userinfo!: Specialuser;
@ -67,8 +65,10 @@ class Localuser{
"Content-type": "application/json; charset=UTF-8",
Authorization: this.userinfo.token,
};
I18n.create("/translations/en.json","en")
}
gottenReady(ready: readyjson): void{
async gottenReady(ready: readyjson): Promise<void>{
await I18n.done;
this.initialized = true;
this.ready = ready;
this.guilds = [];
@ -200,10 +200,10 @@ class Localuser{
try{
const temp = JSON.parse(build);
build = "";
await this.handleEvent(temp);
if(temp.op === 0 && temp.t === "READY"){
returny();
}
await this.handleEvent(temp);
}catch{}
}
})();
@ -231,9 +231,9 @@ class Localuser{
if(
!(
array[len - 1] === 255 &&
array[len - 2] === 255 &&
array[len - 3] === 0 &&
array[len - 4] === 0
array[len - 2] === 255 &&
array[len - 3] === 0 &&
array[len - 4] === 0
)
){
return;
@ -244,10 +244,11 @@ class Localuser{
}else{
temp = JSON.parse(event.data);
}
await this.handleEvent(temp as readyjson);
if(temp.op === 0 && temp.t === "READY"){
returny();
}
await this.handleEvent(temp as readyjson);
}catch(e){
console.error(e);
}finally{
@ -367,7 +368,7 @@ class Localuser{
break;
}
case"READY":
this.gottenReady(temp as readyjson);
await this.gottenReady(temp as readyjson);
break;
case"MESSAGE_UPDATE": {
temp.d.guild_id ??= "@me";

View file

@ -11,6 +11,7 @@ import{ SnowFlake }from"./snowflake.js";
import{ memberjson, messagejson }from"./jsontypes.js";
import{ Emoji }from"./emoji.js";
import{ Dialog }from"./dialog.js";
import { I18n } from "./i18n.js";
class Message extends SnowFlake{
static contextmenu = new Contextmenu<Message, undefined>("message menu");
@ -44,9 +45,7 @@ class Message extends SnowFlake{
return this.weakdiv?.deref();
}
//*/
div:
| (HTMLDivElement & { pfpparent?: Message | undefined; txt?: HTMLElement })
| undefined;
div:(HTMLDivElement & { pfpparent?: Message | undefined; txt?: HTMLElement })| undefined;
member: Member | undefined;
reactions!: messagejson["reactions"];
static setup(){
@ -56,13 +55,13 @@ class Message extends SnowFlake{
Message.setupcmenu();
}
static setupcmenu(){
Message.contextmenu.addbutton("Copy raw text", function(this: Message){
Message.contextmenu.addbutton(I18n.getTranslation.bind(I18n,"copyrawtext"), function(this: Message){
navigator.clipboard.writeText(this.content.rawString);
});
Message.contextmenu.addbutton("Reply", function(this: Message){
Message.contextmenu.addbutton(I18n.getTranslation.bind(I18n,"reply"), function(this: Message){
this.channel.setReplying(this);
});
Message.contextmenu.addbutton("Copy message id", function(this: Message){
Message.contextmenu.addbutton(I18n.getTranslation.bind(I18n,"copymessageid"), function(this: Message){
navigator.clipboard.writeText(this.id);
});
Message.contextmenu.addsubmenu(

View file

@ -4,7 +4,7 @@ interface OptionsElement<x> {
submit: () => void;
readonly watchForChange: (func: (arg1: x) => void) => void;
value: x;
}
}
//future me stuff
class Buttons implements OptionsElement<unknown>{
readonly name: string;

View file

@ -0,0 +1,16 @@
{
"@metadata": {
"authors": [
"MathMan05"
],
"last-updated": "2024/15/24",
"locale": "en",
"comment":"Don't know how often I'll update this top part lol"
},
"en": {
"reply": "Reply",
"copyrawtext":"Copy raw text",
"copymessageid":"Copy message id"
},
"ru": "./ru.json"
}

View file

@ -0,0 +1,12 @@
{
"@metadata": {
"authors": [
],
"last-updated": "2024/15/24",
"locale": "ru",
"comment":"I need some help with this :P"
},
"ru": {
}
}