From 9438355a36963735f92fa4fe4afbce3682c8e0d4 Mon Sep 17 00:00:00 2001 From: vineethvk11 Date: Sun, 31 Mar 2024 12:53:53 +0530 Subject: [PATCH 01/11] removing html inside translation strings --- src/i18n/i18n.js | 13 +++++++++++++ src/i18n/translations/ar.js | 4 ++-- src/i18n/translations/bn.js | 4 ++-- src/i18n/translations/br.js | 4 ++-- src/i18n/translations/da.js | 4 ++-- src/i18n/translations/de.js | 4 ++-- src/i18n/translations/en.js | 4 ++-- src/i18n/translations/es.js | 4 ++-- src/i18n/translations/fa.js | 4 ++-- src/i18n/translations/fi.js | 2 +- src/i18n/translations/fr.js | 4 ++-- src/i18n/translations/hy.js | 4 ++-- src/i18n/translations/it.js | 4 ++-- src/i18n/translations/ko.js | 4 ++-- src/i18n/translations/nb.js | 4 ++-- src/i18n/translations/nl.js | 4 ++-- src/i18n/translations/nn.js | 4 ++-- src/i18n/translations/pl.js | 4 ++-- src/i18n/translations/pt.js | 4 ++-- src/i18n/translations/ro.js | 4 ++-- src/i18n/translations/sv.js | 4 ++-- src/i18n/translations/th.js | 4 ++-- src/i18n/translations/ur.js | 2 +- src/i18n/translations/zh.js | 4 ++-- 24 files changed, 57 insertions(+), 44 deletions(-) diff --git a/src/i18n/i18n.js b/src/i18n/i18n.js index db0ba8b5..23f1efeb 100644 --- a/src/i18n/i18n.js +++ b/src/i18n/i18n.js @@ -20,6 +20,18 @@ import translations from './translations/translations.js'; window.listSupportedLanguages = () => Object.keys(translations).map(lang => translations[lang]); +const variables = { + docs: "https://docs.puter.com/", + terms: "https://puter.com/terms", + privacy: "https://puter.com/privacy" +}; + +function ReplacePlaceholders(str) { + str = str.replace(/{{link=(.*?)}}(.*?){{\/link}}/g, (_, key, text) => `${text}`); + str = str.replace(/{{(.*?)}}/g, (_, key) => variables[key]); + return str; +} + window.i18n = function (key, replacements = [], encode_html = true) { if(Array.isArray(replacements) === false){ replacements = [replacements]; @@ -31,6 +43,7 @@ window.i18n = function (key, replacements = [], encode_html = true) { if (!str) { str = key; } + str = ReplacePlaceholders(str); str = encode_html ? html_encode(str) : str; // replace %% occurrences with the values in replacements // %% is for simple text replacements diff --git a/src/i18n/translations/ar.js b/src/i18n/translations/ar.js index 279d8119..a7b3f4e0 100644 --- a/src/i18n/translations/ar.js +++ b/src/i18n/translations/ar.js @@ -110,7 +110,7 @@ const ar = { paste_into_folder: "الصق داخل الملف", pick_name_for_website: "اختيار اسم لموقع الويب ", picture: "صورة ", - powered_by_puter_js: `مشغل بواسطةPuter.js`, + powered_by_puter_js: `مشغل بواسطة{{link=docs}}Puter.js{{/link}}`, preparing: "إعداده", preparing_for_upload: "التحضير للتحميل ", properties: "ملكيات ", @@ -148,7 +148,7 @@ const ar = { start: 'إبدأ ', taking_longer_than_usual: 'يستغرق وقتا أطول من المعتاد ', text_document: 'وثيقة نصية', - tos_fineprint: `بالنقر على "إنشاء حساب مجاني"، فإنك توافق على شروط الاستخدام و حماية البيانات`, + tos_fineprint: `بالنقر على "إنشاء حساب مجاني"، فإنك توافق على {{link=terms}}شروط الاستخدام{{/link}} و {{link=privacy}}حماية البيانات{{/link}}`, trash: 'نفاية', type: 'اكتب', undo: 'الغاء التحميل', diff --git a/src/i18n/translations/bn.js b/src/i18n/translations/bn.js index 8cc68092..e5a89f13 100644 --- a/src/i18n/translations/bn.js +++ b/src/i18n/translations/bn.js @@ -109,7 +109,7 @@ const bn = { paste_into_folder: "ফোল্ডার আয়ে পেস্ট করুন", pick_name_for_website: "আপনার ওয়েবসাইটের জন্য একটি নাম পছন্দ করুন:", picture: "ছবি", - powered_by_puter_js: `দ্বারা চালিত Puter.js`, + powered_by_puter_js: `দ্বারা চালিত {{link=docs}}Puter.js{{/link}}`, preparing: "প্রস্তুত হচ্ছে...", preparing_for_upload: "আপলোডের জন্য প্রস্তুত হচ্ছে...", properties: "বৈশিষ্ট্য", @@ -147,7 +147,7 @@ const bn = { start: 'শুরু করুন', taking_longer_than_usual: 'স্বাভাবিকের চেয়ে একটু বেশি সময় নিচ্ছে। অনুগ্রহপূর্বক অপেক্ষা করুন...', text_document: 'পাঠ্য নথি', - tos_fineprint: `'ফ্রি অ্যাকাউন্ট তৈরি করুন'-এ ক্লিক করার মাধ্যমে আপনি Puter-এর পরিষেবার শর্তাবলী এবং গোপনীয়তা নীতি।`, + tos_fineprint: `'ফ্রি অ্যাকাউন্ট তৈরি করুন'-এ ক্লিক করার মাধ্যমে আপনি Puter-এর {{link=terms}}পরিষেবার শর্তাবলী{{/link}} এবং {{link=privacy}}গোপনীয়তা নীতি{{/link}}।`, trash: 'আবর্জনা', type: 'টাইপ', undo: 'পূর্বাবস্থায় ফেরান', diff --git a/src/i18n/translations/br.js b/src/i18n/translations/br.js index c1f041b3..ae8ca572 100644 --- a/src/i18n/translations/br.js +++ b/src/i18n/translations/br.js @@ -132,7 +132,7 @@ const br = { pick_name_for_website: "Escolha um nome para seu site:", picture: "Imagem", plural_suffix: 's', - powered_by_puter_js: `Criado por Puter.js`, + powered_by_puter_js: `Criado por {{link=docs}}Puter.js{{/link}}`, preparing: "Preparando...", preparing_for_upload: "Preparando para o envio...", privacy: "Privacidade", @@ -184,7 +184,7 @@ const br = { taking_longer_than_usual: 'Está a levar mais tempo que o usual. Por favor, aguarde...', terms: "Termos", text_document: 'Documento de Texto', - tos_fineprint: `Clicando em 'Criar Conta Gratuita' você concorda com os Termos de Serviço e Política de Privacidade do Puter.`, + tos_fineprint: `Clicando em 'Criar Conta Gratuita' você concorda com os {{link=terms}}Termos de Serviço{{/link}} e {{link=privacy}}Política de Privacidade{{/link}} do Puter.`, trash: 'Lixo', type: 'Tipo', type_confirm_to_delete_account: "Digite 'confirm' para excluir sua conta.", diff --git a/src/i18n/translations/da.js b/src/i18n/translations/da.js index 21f9ab30..891fc56a 100644 --- a/src/i18n/translations/da.js +++ b/src/i18n/translations/da.js @@ -110,7 +110,7 @@ const da = { paste_into_folder: "Indsæt i mappe", pick_name_for_website: "Vælg et navn til dit websted:", picture: "Billede", - powered_by_puter_js: "Drevet af Puter.js", + powered_by_puter_js: "Drevet af {{link=docs}}Puter.js{{/link}}", preparing: "Forbereder...", preparing_for_upload: "Forbereder upload...", properties: "Egenskaber", @@ -148,7 +148,7 @@ const da = { start: "Start", taking_longer_than_usual: "Dette tager længere tid end sædvanligt. Vent venligst...", text_document: "Tekstdokument", - tos_fineprint: "Ved at klikke på 'Opret gratis konto' accepterer du Puters servicevilkår og privatlivspolitik.", + tos_fineprint: "Ved at klikke på 'Opret gratis konto' accepterer du Puters {{link=terms}}servicevilkår{{/link}} og {{link=privacy}}privatlivspolitik{{/link}}.", trash: "Papirkurv", type: "Type", undo: "Fortryd", diff --git a/src/i18n/translations/de.js b/src/i18n/translations/de.js index 5f665b8c..4e899e15 100644 --- a/src/i18n/translations/de.js +++ b/src/i18n/translations/de.js @@ -110,7 +110,7 @@ const de = { paste_into_folder: "In Ordner einfügen", pick_name_for_website: "Wählen Sie einen Namen für Ihre Webseite:", picture: "Bild", - powered_by_puter_js: `Betrieben von Puter.js`, + powered_by_puter_js: `Betrieben von {{link=docs}}Puter.js{{/link}}`, preparing: "Bereitet vor...", preparing_for_upload: "Bereitet für das Hochladen vor...", properties: "Einstellungen", @@ -148,7 +148,7 @@ const de = { start: 'Start', taking_longer_than_usual: 'Dauert etwas länger als gewöhnlich. Bitte warten...', text_document: 'Textdokument', - tos_fineprint: `Indem Sie auf „Kostenloses Konto erstellen“ klicken, stimmen Sie den Nutzungsbedingungen und der Datenschutzerklärung von Puter zu.`, + tos_fineprint: `Indem Sie auf „Kostenloses Konto erstellen“ klicken, stimmen Sie den {{link=terms}}Nutzungsbedingungen{{/link}} und der {{link=privacy}}Datenschutzerklärung{{/link}} von Puter zu.`, trash: 'Papierkorb', type: 'Typ', undo: 'Zurück', diff --git a/src/i18n/translations/en.js b/src/i18n/translations/en.js index 832ccd72..3ced17a9 100644 --- a/src/i18n/translations/en.js +++ b/src/i18n/translations/en.js @@ -132,7 +132,7 @@ const en = { pick_name_for_website: "Pick a name for your website:", picture: "Picture", plural_suffix: 's', - powered_by_puter_js: `Powered by Puter.js`, + powered_by_puter_js: `Powered by {{link=docs}}Puter.js{{/link}}`, preparing: "Preparing...", preparing_for_upload: "Preparing for upload...", privacy: "Privacy", @@ -185,7 +185,7 @@ const en = { taking_longer_than_usual: 'Taking a little longer than usual. Please wait...', terms: "Terms", text_document: 'Text document', - tos_fineprint: `By clicking 'Create Free Account' you agree to Puter's Terms of Service and Privacy Policy.`, + tos_fineprint: `By clicking 'Create Free Account' you agree to Puter's {{link=terms}}Terms of Service{{/link}} and {{link=privacy}}Privacy Policy{{/link}}.`, trash: 'Trash', type: 'Type', type_confirm_to_delete_account: "Type 'confirm' to delete your account.", diff --git a/src/i18n/translations/es.js b/src/i18n/translations/es.js index e6ccca5b..917ef1c3 100644 --- a/src/i18n/translations/es.js +++ b/src/i18n/translations/es.js @@ -110,7 +110,7 @@ const es = { paste_into_folder: "Pegar en la Carpeta", pick_name_for_website: "Escoge un nombre para tu página web:", picture: "Imagen", - powered_by_puter_js: `Creado por Puter.js`, + powered_by_puter_js: `Creado por {{link=docs}}Puter.js{{/link}}`, preparing: "Preparando...", preparing_for_upload: "Preparando para la subida...", properties: "Propiedades", @@ -148,7 +148,7 @@ const es = { start: 'Inicio', taking_longer_than_usual: 'Tardando un poco más de lo usual. Por favor, espere...', text_document: 'Documento de Texto', - tos_fineprint: `Pulsando sobre 'Crear una cuenta gratuita' aceptas los términos del servicio de Puter y la política de privacidad.`, + tos_fineprint: `Pulsando sobre 'Crear una cuenta gratuita' aceptas los {{link=terms}}términos del servicio{{/link}} de Puter y {{link=privacy}}la política de privacidad{{/link}}.`, trash: 'Papelera', type: 'Tipo', undo: 'Deshacer', diff --git a/src/i18n/translations/fa.js b/src/i18n/translations/fa.js index e2d3b94d..d0db2ae4 100644 --- a/src/i18n/translations/fa.js +++ b/src/i18n/translations/fa.js @@ -111,7 +111,7 @@ const fa = { paste_into_folder: "چسباندن در پوشه", pick_name_for_website: "یک نام برای وبسایت خود انتخاب کنید:", picture: "تصویر", - powered_by_puter_js: `پشتیبانی شده توسط Puter.js`, + powered_by_puter_js: `پشتیبانی شده توسط {{link=docs}}Puter.js{{/link}}`, preparing: "در حال آماده سازی...", preparing_for_upload: "آماده سازی برای بارگذاری...", properties: "ویژگی ها", @@ -149,7 +149,7 @@ const fa = { start: 'شروع', taking_longer_than_usual: 'کمی بیشتر از معمول طول می کشد. لطفا صبر کنید...', text_document: 'سند متنی', - tos_fineprint: `با کلیک بر روی 'ایجاد حساب کاربری رایگان' شما با شرایط خدمات و سیاست حفظ حریم خصوصی Puter موافقت می کنید.`, + tos_fineprint: `با کلیک بر روی 'ایجاد حساب کاربری رایگان' شما با {{link=terms}}شرایط خدمات{{/link}} و {{link=privacy}}سیاست حفظ حریم خصوصی{{/link}} Puter موافقت می کنید.`, trash: 'سطل زباله', type: 'نوع', undo: 'بازگشت', diff --git a/src/i18n/translations/fi.js b/src/i18n/translations/fi.js index 9950f10f..9dc4eb62 100644 --- a/src/i18n/translations/fi.js +++ b/src/i18n/translations/fi.js @@ -154,7 +154,7 @@ const fi = { paste_into_folder: "Liitä Kansioon", pick_name_for_website: "Valitse nimi verkkosivustollesi:", picture: "Kuva", - powered_by_puter_js: `Tämän Mahdollistaa Puter.js`, + powered_by_puter_js: `Tämän Mahdollistaa {{link=docs}}Puter.js{{/link}}`, preparing: "Valmistellaan...", preparing_for_upload: "Valmistellaan latausta...", properties: "Ominaisuudet", diff --git a/src/i18n/translations/fr.js b/src/i18n/translations/fr.js index bff28b64..1b5b8e28 100644 --- a/src/i18n/translations/fr.js +++ b/src/i18n/translations/fr.js @@ -110,7 +110,7 @@ const fr = { paste_into_folder: "Coller dans le dossier", pick_name_for_website: "Choisissez un nom pour votre site Web :", picture: "Image", - powered_by_puter_js: `Alimenté par Puter.js`, + powered_by_puter_js: `Alimenté par {{link=docs}}Puter.js{{/link}}`, preparing: "Préparation...", preparing_for_upload: "Préparation du chargement...", properties: "Propriétés", @@ -148,7 +148,7 @@ const fr = { start: 'Démarrer', taking_longer_than_usual: 'Cela prend un peu plus de temps que d\'habitude. Veuillez patienter...', text_document: 'Document texte', - tos_fineprint: `En cliquant sur "Créer un compte gratuit", vous acceptez les conditions d'utilisation et la politique de confidentialité.`, + tos_fineprint: `En cliquant sur "Créer un compte gratuit", vous acceptez les {{link=terms}}conditions d'utilisation{{/link}} et la {{link=privacy}}politique de confidentialité{{/link}}.`, trash: 'Corbeille', type: 'Type', undo: 'Annuler', diff --git a/src/i18n/translations/hy.js b/src/i18n/translations/hy.js index 01c4d6cc..86ee13c6 100644 --- a/src/i18n/translations/hy.js +++ b/src/i18n/translations/hy.js @@ -110,7 +110,7 @@ const hy = { paste_into_folder: "Տեղադրել պանակում", pick_name_for_website: "Ընտրել անուն ձեր կայքի համար", picture: "Նկար", - powered_by_puter_js: `Աջակցվում է Puter.js-ի կողմից`, + powered_by_puter_js: `Աջակցվում է {{link=docs}}Puter.js{{/link}}-ի կողմից`, preparing: "Պատրաստվում է...", preparing_for_upload: "Պատրաստվում է վերբեռնել...", properties: "Հատկություններ", @@ -148,7 +148,7 @@ const hy = { start: "Սկսել", taking_longer_than_usual: "Սովորականից մի փոքր ավելի երկար է տևում: Խնդրում ենք սպասել...", text_document: "Text նիշք", - tos_fineprint: `Սեղմելով «Ստեղծել անվճար հաշիվ»՝ դուք համաձայնում եք Փութերի ծառայությունների պայմաններին և գաղտնիության քաղաքականությանը:`, + tos_fineprint: `Սեղմելով «Ստեղծել անվճար հաշիվ»՝ դուք համաձայնում եք Փութերի {{link=terms}}ծառայությունների պայմաններին{{/link}} և {{link=privacy}}գաղտնիության քաղաքականությանը{{/link}}:`, trash: "Աղբաման", type: "Տեսակ", undo: "Հետարկել", diff --git a/src/i18n/translations/it.js b/src/i18n/translations/it.js index abace233..6efe23db 100644 --- a/src/i18n/translations/it.js +++ b/src/i18n/translations/it.js @@ -110,7 +110,7 @@ const it = { paste_into_folder: "Incolla nella cartella", pick_name_for_website: "Scegli un nome per il tuo sito web:", picture: "Immagine", - powered_by_puter_js: `Powered by Puter.js`, + powered_by_puter_js: `Powered by {{link=docs}}Puter.js{{/link}}`, preparing: "Preparazione in corso...", preparing_for_upload: "Preparazione per l’upload...", properties: "Proprietà", @@ -148,7 +148,7 @@ const it = { start: 'Start', taking_longer_than_usual: 'Il processo in corso ci sta mettendo più del solito. Attendere prego...', text_document: 'Documento di testo', - tos_fineprint: `Cliccando su 'Crea un account gratis' accetti i Termini di Servizio e l'Informativa sulla Privacy di Puter.`, + tos_fineprint: `Cliccando su 'Crea un account gratis' accetti i {{link=terms}}Termini di Servizio{{/link}} e l'{{link=privacy}}Informativa sulla Privacy{{/link}} di Puter.`, trash: 'Cestino', type: 'Tipo', undo: 'Annulla', diff --git a/src/i18n/translations/ko.js b/src/i18n/translations/ko.js index 6ec427e2..e0229928 100644 --- a/src/i18n/translations/ko.js +++ b/src/i18n/translations/ko.js @@ -110,7 +110,7 @@ const ko = { paste_into_folder: "폴더에 붙여넣기", pick_name_for_website: "웹사이트 이름을 선택하세요:", picture: "사진", - powered_by_puter_js: `Powered by Puter.js`, + powered_by_puter_js: `Powered by {{link=docs}}Puter.js{{/link}}`, preparing: "준비 중...", preparing_for_upload: "업로드 준비 중...", properties: "속성", @@ -148,7 +148,7 @@ const ko = { start: '시작', taking_longer_than_usual: '보통보다 조금 더 오래 걸립니다. 잠시만 기다려 주십시오...', text_document: '텍스트 문서', - tos_fineprint: `무료 계정 생성을 클릭하면 Puter의 서비스 약관개인정보 보호정책에 동의하는 것입니다.`, + tos_fineprint: `무료 계정 생성을 클릭하면 Puter의 {{link=terms}}서비스 약관{{/link}}과 {{link=privacy}}개인정보 보호정책{{/link}}에 동의하는 것입니다.`, trash: '휴지통', type: '유형', undo: '실행 취소', diff --git a/src/i18n/translations/nb.js b/src/i18n/translations/nb.js index 50bfbe83..5d361085 100644 --- a/src/i18n/translations/nb.js +++ b/src/i18n/translations/nb.js @@ -120,7 +120,7 @@ const nb = { paste_into_folder: "Lim inn i mappe", pick_name_for_website: "Velg et navn for nettstedet ditt:", picture: "Bilde", - powered_by_puter_js: "Drevet av Puter.js", + powered_by_puter_js: "Drevet av {{link=docs}}Puter.js{{/link}}", preparing: "Forbereder...", preparing_for_upload: "Forbereder opplasting...", properties: "Egenskaper", @@ -166,7 +166,7 @@ const nb = { start: "Start", taking_longer_than_usual: "Dette tar litt lenger tid enn vanlig. Vennligst vent...", text_document: "Tekstdokument", - tos_fineprint: "Ved å klikke på 'Opprett gratis konto' godtar du Puters tjenestevilkår og personvernpolicy.", + tos_fineprint: "Ved å klikke på 'Opprett gratis konto' godtar du Puters {{link=terms}}tjenestevilkår{{/link}} og {{link=privacy}}personvernpolicy{{/link}}.", trash: "Papirkurv", type: "Type", undo: "Angre", diff --git a/src/i18n/translations/nl.js b/src/i18n/translations/nl.js index df8764bf..1591147d 100644 --- a/src/i18n/translations/nl.js +++ b/src/i18n/translations/nl.js @@ -132,7 +132,7 @@ const nl = { paste_into_folder: "Plakken in Map", pick_name_for_website: "Kies een naam voor uw website:", picture: "Foto", - powered_by_puter_js: `Aangedreven door Puter.js`, + powered_by_puter_js: `Aangedreven door {{link=docs}}Puter.js{{/link}}`, preparing: "Voorbereiden...", preparing_for_upload: "Upload voorbereiden...", proceed_to_login: 'Doorgaan naar Inloggen', @@ -184,7 +184,7 @@ const nl = { taking_longer_than_usual: 'Het duurt iets langer dan normaal. Even geduld aub...', terms: "Voorwaarden", text_document: 'Tekst document', - tos_fineprint: `Door te klikken op 'Maak Gratis Account' gaat u akkoord met Puter's Gebruiksvoorwaarden en Privacybeleid.`, + tos_fineprint: `Door te klikken op 'Maak Gratis Account' gaat u akkoord met Puter's {{link=terms}}Gebruiksvoorwaarden{{/link}} en {{link=privacy}}Privacybeleid{{/link}}.`, trash: 'Prullenbak', type: 'Type', type_confirm_to_delete_account: "Type 'bevestig' om uw account te verwijderen.", diff --git a/src/i18n/translations/nn.js b/src/i18n/translations/nn.js index 471762ea..15d2e4d2 100644 --- a/src/i18n/translations/nn.js +++ b/src/i18n/translations/nn.js @@ -110,7 +110,7 @@ const nn = { paste_into_folder: "Lim inn i mappe", pick_name_for_website: "Vel eit namn for nettstaden din:", picture: "Bilete", - powered_by_puter_js: "Dreve av Puter.js", + powered_by_puter_js: "Dreve av {{link=docs}}Puter.js{{/link}}", preparing: "Førebur…", preparing_for_upload: "Førebur opplasting…", properties: "Eigenskapar", @@ -148,7 +148,7 @@ const nn = { start: "Start", taking_longer_than_usual: "Dette tar litt lengre tid enn vanleg. Vennligst vent...", text_document: "Tekstdokument", - tos_fineprint: "Ved å klikke på 'Opprett gratis konto' godtek du Puters tenestevilkår og personvernpolitikk.", + tos_fineprint: "Ved å klikke på 'Opprett gratis konto' godtek du Puters {{link=terms}}tenestevilkår{{/link}} og {{link=privacy}}personvernpolitikk{{/link}}.", trash: "Papirkorg", type: "Type", undo: "Angra", diff --git a/src/i18n/translations/pl.js b/src/i18n/translations/pl.js index f958a2c5..c12d30b6 100644 --- a/src/i18n/translations/pl.js +++ b/src/i18n/translations/pl.js @@ -120,7 +120,7 @@ const pl = { paste_into_folder: "Wklej do folderu", pick_name_for_website: "Wybierz nazwę dla swojej strony:", picture: "Obraz", - powered_by_puter_js: `Zasilane za pomocą Puter.js`, + powered_by_puter_js: `Zasilane za pomocą {{link=docs}}Puter.js{{/link}}`, preparing: "Przygotowywanie...", preparing_for_upload: "Przygotowywanie do wgrania...", proceed_to_login: 'Przejdź do logowania', @@ -166,7 +166,7 @@ const pl = { start: 'Start', taking_longer_than_usual: 'To trwa chwilę dłużej niż zwyklę. Prosimy poczekać...', text_document: 'Dokument tekstowy', - tos_fineprint: `Klikając 'Stwórz darmowe konto' Zgadzasz się z Warunkami Obsługi i Polityką Prywatności.`, + tos_fineprint: `Klikając 'Stwórz darmowe konto' Zgadzasz się z {{link=terms}}Warunkami Obsługi{{/link}} i {{link=privacy}}Polityką Prywatności{{/link}}.`, trash: 'Kosz', type: 'Wpisz', undo: 'Cofnij', diff --git a/src/i18n/translations/pt.js b/src/i18n/translations/pt.js index 480f9d6b..1e0279b1 100644 --- a/src/i18n/translations/pt.js +++ b/src/i18n/translations/pt.js @@ -132,7 +132,7 @@ const pt = { pick_name_for_website: "Escolha um nome para seu site:", picture: "Imagem", plural_suffix: 's', - powered_by_puter_js: `Criado por Puter.js`, + powered_by_puter_js: `Criado por {{link=docs}}Puter.js{{/link}}`, preparing: "A preparar...", preparing_for_upload: "A preparar o envio...", privacy: "Privacidade", @@ -184,7 +184,7 @@ const pt = { taking_longer_than_usual: 'Está a levar mais tempo que o usual. Por favor, aguarde...', terms: "Termos", text_document: 'Documento de Texto', - tos_fineprint: `Ao clicar em 'Criar Conta Gratuita' concordas com os Termos de Serviço e Política de Privacidade do Puter.`, + tos_fineprint: `Ao clicar em 'Criar Conta Gratuita' concordas com os {{link=terms}}Termos de Serviço{{/link}} e {{link=privacy}}Política de Privacidade{{/link}} do Puter.`, trash: 'Lixo', type: 'Tipo', type_confirm_to_delete_account: "Digite 'confirm' para excluires vossa conta.", diff --git a/src/i18n/translations/ro.js b/src/i18n/translations/ro.js index feaa56b7..823b860c 100644 --- a/src/i18n/translations/ro.js +++ b/src/i18n/translations/ro.js @@ -109,7 +109,7 @@ const ro = { paste_into_folder: "Inserează in folder", pick_name_for_website: "Alegeți un nume pentru site-ul dvs:", picture: "Poza", - powered_by_puter_js: `Creat de Puter.js`, + powered_by_puter_js: `Creat de {{link=docs}}Puter.js{{/link}}`, preparing: "Preparare...", preparing_for_upload: "Preparare pentru încărcare...", properties: "Proprietăți", @@ -147,7 +147,7 @@ const ro = { start: 'Start', taking_longer_than_usual: 'Durează puțin mai mult decât de obicei. Vă rugăm așteptați...', text_document: 'Document Text', - tos_fineprint: `Făcând clic pe „Creați un cont gratuit”, sunteți de acord cu Termenii si conditiile si Politia de Confidentialitate Puter.com.`, + tos_fineprint: `Făcând clic pe „Creați un cont gratuit”, sunteți de acord cu {{link=terms}}Termenii si conditiile{{/link}} si {{link=privacy}}Politia de Confidentialitate Puter.com{{/link}}.`, trash: 'Coș de gunoi', type: 'Type', undo: 'Undo', diff --git a/src/i18n/translations/sv.js b/src/i18n/translations/sv.js index 6136cdfb..6d7d1ece 100644 --- a/src/i18n/translations/sv.js +++ b/src/i18n/translations/sv.js @@ -110,7 +110,7 @@ const sv = { paste_into_folder: "Klistra in i mapp", pick_name_for_website: "Välj ett namn för din webbplats:", picture: "Bild", - powered_by_puter_js: "Drivs av Puter.js", + powered_by_puter_js: "Drivs av {{link=docs}}Puter.js{{/link}}", preparing: "Förbereder...", preparing_for_upload: "Förbereder för uppladdning...", properties: "Egenskaper", @@ -148,7 +148,7 @@ const sv = { start: "Start", taking_longer_than_usual: "Detta tar längre tid än vanligt. Vänligen vänta...", text_document: "Textdokument", - tos_fineprint: "Genom att klicka på 'Skapa gratis konto' godkänner du Puters användarvillkor och integritetspolicy.", + tos_fineprint: "Genom att klicka på 'Skapa gratis konto' godkänner du Puters {{link=terms}}användarvillkor{{/link}} och {{link=privacy}}integritetspolicy{{/link}}.", trash: "Papperskorg", type: "Typ", undo: "Ångra", diff --git a/src/i18n/translations/th.js b/src/i18n/translations/th.js index 99383292..0be6cc90 100644 --- a/src/i18n/translations/th.js +++ b/src/i18n/translations/th.js @@ -119,7 +119,7 @@ const th = { paste_into_folder: "วางลงในโฟลเดอร์", pick_name_for_website: "เลือกชื่อสำหรับเว็บไซต์ของคุณ:", picture: "รูปภาพ", - powered_by_puter_js: `สนับสนุนโดย Puter.js`, + powered_by_puter_js: `สนับสนุนโดย {{link=docs}}Puter.js{{/link}}`, preparing: "กำลังเตรียม...", preparing_for_upload: "กำลังเตรียมสำหรับอัปโหลด...", proceed_to_login: "ดำเนินการเข้าสู่ระบบ", @@ -165,7 +165,7 @@ const th = { start: "เริ่มต้น", taking_longer_than_usual: "ใช้เวลานานกว่าปกติเล็กน้อย กรุณารอสักครู่...", text_document: "เอกสารข้อความ", - tos_fineprint: `การคลิก 'สร้างบัญชีฟรี' หมายความว่าคุณยอมรับ ข้อกำหนดการให้บริการ และ นโยบายความเป็นส่วนตัว.`, + tos_fineprint: `การคลิก 'สร้างบัญชีฟรี' หมายความว่าคุณยอมรับ {{link=terms}}ข้อกำหนดการให้บริการ{{/link}} และ {{link=privacy}}นโยบายความเป็นส่วนตัว{{/link}}.`, trash: "ถังขยะ", type: "ประเภท", undo: "เลิกทำ", diff --git a/src/i18n/translations/ur.js b/src/i18n/translations/ur.js index 65c79eaf..f6492e40 100644 --- a/src/i18n/translations/ur.js +++ b/src/i18n/translations/ur.js @@ -110,7 +110,7 @@ const ur = { paste_into_folder: "فولڈر میں چسپاں کریں", pick_name_for_website: "ویب سائٹ کے لئے نام منتخب کریں ", picture: "تصویر ", - powered_by_puter_js: 'پیوٹر جے ایس کے زریعے محرکPuter.js', + powered_by_puter_js: 'پیوٹر جے ایس کے زریعے محرک{{link=docs}}Puter.js{{/link}}', preparing: "تیاری ", preparing_for_upload: "اپلوڈ کے لئے تیاری ", properties: "خصوصیات ", diff --git a/src/i18n/translations/zh.js b/src/i18n/translations/zh.js index 82e44ab0..caee7a79 100644 --- a/src/i18n/translations/zh.js +++ b/src/i18n/translations/zh.js @@ -110,7 +110,7 @@ const zh = { paste_into_folder: "粘贴到文件夹", pick_name_for_website: "为您的网站选择一个名称:", picture: "图片", - powered_by_puter_js: `由 Puter.js 提供支持`, + powered_by_puter_js: `由 {{link=docs}}Puter.js{{/link}} 提供支持`, preparing: "准备中...", preparing_for_upload: "准备上传...", properties: "属性", @@ -148,7 +148,7 @@ const zh = { start: '开始', taking_longer_than_usual: '需要的时间比平时长一点。请稍等...', text_document: '文本文档', - tos_fineprint: `点击“创建免费帐户”即表示您同意 Puter 的 服务条款隐私政策。`, + tos_fineprint: `点击“创建免费帐户”即表示您同意 Puter 的 {{link=terms}}服务条款{{/link}} 和 {{link=privacy}}隐私政策{{/link}}。`, trash: '回收站', type: '类型', undo: '撤销', From 14996439a0ef3e8409cd097b75529e538d90b606 Mon Sep 17 00:00:00 2001 From: Humberto <10425094+berto@users.noreply.github.com> Date: Sun, 31 Mar 2024 20:06:03 +0100 Subject: [PATCH 02/11] Update pt.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct many instances of PT-BR to PT-PT. The original had some typos too. Kept “Password” as “Password” instead of a mix of “Senha” and “Palavra Chave”. --- src/i18n/translations/pt.js | 150 ++++++++++++++++++------------------ 1 file changed, 77 insertions(+), 73 deletions(-) diff --git a/src/i18n/translations/pt.js b/src/i18n/translations/pt.js index 480f9d6b..524e73e0 100644 --- a/src/i18n/translations/pt.js +++ b/src/i18n/translations/pt.js @@ -23,183 +23,187 @@ const pt = { dictionary: { about: "Sobre", account: "Conta", - access_granted_to: "Acesso garantido a", - add_existing_account: "Incluir Conta Existente", + access_granted_to: "Acesso Dado A", + add_existing_account: "Adicionar Conta Existente", all_fields_required: 'Todos os campos são obrigatórios.', apply: "Aplicar", ascending: 'Ascendente', auto_arrange: 'Auto Organizar', background: "Fundo", - browse: "Pesquisar", + browse: "Explorar", cancel: 'Cancelar', center: 'Centrar', change_desktop_background: 'Alterar o fundo…', change_language: "Alterar a Língua", - change_password: "Alterar a Palavra Passe", - change_username: "Alterar Nome de Utilizador", + change_password: "Alterar a Password", + change_username: "Alterar o Nome de Utilizador", close_all_windows: "Fechar Todas as Janelas", close_all_windows_and_log_out: 'Fechar Janelas e Sair', - change_allways_open_with: "Quer sempre abrir ficheiros deste tipo com", + change_allways_open_with: "Queres que ficheiros deste tipo abram sempre com", color: 'Cor', - confirm_account_for_free_referral_storage_c2a: 'Cries uma conta e confirme o endereço do email para receber 1 GB de armazenamento gratuito. Vosso amigo receberá 1 GB de armazenamento gratuito também.', - confirm_delete_multiple_items: 'Queres apagar estes itens permanentemente?', + confirm_account_for_free_referral_storage_c2a: 'Cria uma conta e confirma o endereço do email para receber 1 GB de armazenamento gratuito. O teu amigo também receberá 1 GB de armazenamento gratuito.', + confirm_delete_multiple_items: 'Tens a certeza que queres apagar estes itens permanentemente?', confirm_delete_single_item: 'Queres apagar este item permanentemente?', - confirm_open_apps_log_out: 'Possui aplicações abertas. Queres mesmo fechar vossa sessão?', - confirm_new_password: "Confirme vossa Nova Palavra Passe", - confirm_delete_user: "Queres excluir vossa conta? Todos os ficheiros e informações serão destruídas permanentemente. Esta operação não pode ser desfeita.", - contact_us: "Contacte-nos", + confirm_open_apps_log_out: 'Tens aplicações abertas. Queres mesmo terminar a sessão?', + confirm_new_password: "Confirma a Nova Password", + confirm_delete_user: "Tens a certeza que queres apagar a tua conta? Todos os ficheiros e dados serão apagados permanentemente. Esta operação é final.", + contact_us: "Contacta-nos", contain: 'Contém', continue: "Continua", copy: 'Copia', copy_link: "Copia Link", - copying: "Copiando", + copying: "A copiar", cover: 'Capa', create_account: "Criar Conta", create_free_account: "Criar Conta Gratuita", create_shortcut: "Criar Atalho", credits: "Créditos", - current_password: "Palavra Passe Atual", + current_password: "Password Atual", cut: 'Cortar', + clock: 'Relógio', + clock_visible_hide: 'Esconder - Sempre escondido', + clock_visible_show: 'Mostrar - Sempre visível', + clock_visible_auto: 'Auto - Por defeito, mostra apenas em modo full-screen', date_modified: 'Data alterada', - delete: 'Excluir', - delete_account: "Excluir Conta", - delete_permanently: "Excluir Permanentemente", + delete: 'Apagar', + delete_account: "Apagar Conta", + delete_permanently: "Apagar Permanentemente", deploy_as_app: 'Publicar como aplicativo', descending: 'Descendente', - desktop_background_fit: "Caber", - developers: "Desenvolvedores", - dir_published_as_website: `%strong% foi publicado para:`, + desktop_background_fit: "Ajustado", + developers: "Developers", + dir_published_as_website: `%strong% foi publicado em:`, disassociate_dir: "Desassociar Diretório", download: 'Descarregar', download_file: 'Descarregar Ficheiro', - downloading: "Efectuando a Descarga", + downloading: "Fazendo a descarga", email: "Email", email_or_username: "Email ou Nome de Utilizador", empty_trash: 'Esvaziar Lixo', empty_trash_confirmation: `Queres apagar os itens do Lixo permanentemente?`, emptying_trash: 'Deitando o Lixo fora…', - enter_password_to_confirm_delete_user: "Entre vossa palavra passe para confirmar a exclusão da conta", + enter_password_to_confirm_delete_user: "Insere a Password para confirmar a remoção da conta", feedback: "Feedback", - feedback_c2a: "Favor usares o formulário abaixo para enviar vossos comentários e comunicados.", - feedback_sent_confirmation: "Obrigado por contactar-nos. Se tiveres email associado a esta conta, esperamos ver-nos novamente em breve.", + feedback_c2a: "Pff usa o formulário abaixo para enviar feedback, comentários e bugs.", + feedback_sent_confirmation: "Obrigado por nos contactares. Se tiveres um email associado a esta conta, receberás notícias o mais brevemente que nos seja possível.", forgot_pass_c2a: "Esqueceste a senha?", from: "De", general: "Geral", - get_a_copy_of_on_puter: `Obter uma cópia de '%%' no Puter.com!`, + get_a_copy_of_on_puter: `Obter uma cópia de '%%' em Puter.com!`, get_copy_link: 'Copiar Link', hide_all_windows: "Ocultar Todas as Janelas", html_document: 'Documento HTML', image: 'Imagem', invite_link: "Link do Convite", item: 'item', - items_in_trash_cannot_be_renamed: `Item não pode ser renomeado porque está no lixo. Para renomear, arraste-o para fora do Lixo.`, + items_in_trash_cannot_be_renamed: `Este item não pode ser renomeado porque está no lixo. Para alterar o nome, primeiro arrasta-o para fora do Lixo.`, jpeg_image: 'Imagem JPEG', - keep_in_taskbar: 'Armazenar na Barra de Tarefas', + keep_in_taskbar: 'Manter na Barra de Tarefas', language: "Língua", license: "Licença", loading: 'Carregando', log_in: "Entrar", - log_into_another_account_anyway: 'Entrar com outra conta de qualquer maneira', + log_into_another_account_anyway: 'Entrar com outra conta na mesma', log_out: 'Sair', move: 'Mover', moving: "Movendo", my_websites: "Meus Sites", name: 'Nome', name_cannot_be_empty: 'Nome não pode ser vazio.', - name_cannot_contain_double_period: "Nome não pode conter o caracters '..'.", - name_cannot_contain_period: "Nome não pode conter o caracter '.'.", - name_cannot_contain_slash: "Nome não pode conter o caracter '/'.", + name_cannot_contain_double_period: "Nome não pode conter o caractere '..'.", + name_cannot_contain_period: "Nome não pode conter o caractere '.'.", + name_cannot_contain_slash: "Nome não pode conter o caractere '/'.", name_must_be_string: "Nome tem que ser apenas texto.", - name_too_long: `Nome não pode ter mais que %% characters.`, + name_too_long: `Nome não pode ter mais que %% caracteres.`, new: 'Novo', new_folder: 'Nova Pasta', - new_password: "Nova Senha", - new_username: "Novo Utilizador", + new_password: "Nova Password", + new_username: "Novo Nome de Utilizador", no: 'Não', no_dir_associated_with_site: 'Não existe diretório associado com este endereço.', - no_websites_published: "Ainda não publicaste sites.", + no_websites_published: "Ainda não tens sites publicados.", ok: 'OK', open: "Abrir", open_in_new_tab: "Abrir em Nova Aba", open_in_new_window: "Abrir em Nova Janela", open_with: "Abrir Com", oss_code_and_content: "Software de Código Aberto", - password: "Palavra Passe", - password_changed: "Palavra Passe alterada.", - passwords_do_not_match: '`Nova Palavra Passe` e `Confirmação de Nova Palavra Passe` não conferem como idênticas.', + password: "Password", + password_changed: "Password alterada.", + passwords_do_not_match: '`Nova Password` e `Confirmação de Nova Password` são diferentes.', paste: 'Colar', - paste_into_folder: "Cole na Pasta", + paste_into_folder: "Cola na Pasta", pick_name_for_website: "Escolha um nome para seu site:", picture: "Imagem", plural_suffix: 's', - powered_by_puter_js: `Criado por Puter.js`, + powered_by_puter_js: `Criado com Puter.js`, preparing: "A preparar...", - preparing_for_upload: "A preparar o envio...", + preparing_for_upload: "A preparar o upload...", privacy: "Privacidade", - proceed_to_login: 'Proceguir para a entrada', - proceed_with_account_deletion: "Prosseguir com Exclusão da Conta", + proceed_to_login: 'Prosseguir para o login', + proceed_with_account_deletion: "Prosseguir com Remoção da Conta", properties: "Propriedades", publish: "Publicar", publish_as_website: 'Publicar como Site', - puter_description: `Puter é uma nuvem pessoal que prioriza a privacidade para manter todos os seus ficheiros, aplicativos e jogos em um local seguro, acessível de qualquer lugar e a qualquer hora.`, + puter_description: `Puter é uma nuvem pessoal que prioriza a privacidade e que mantém todos os teus ficheiros, aplicativos e jogos num local seguro, acessível de qualquer lugar e a qualquer hora.`, recent: "Recentes", - recover_password: "Recuperar Senha", - refer_friends_c2a: "Obtenhas 1 GB para cada amigo que criar e confirmar uma conta no Puter. Vosso amigo ganhará 1 GB também!", - refer_friends_social_media_c2a: `Obtenhas 1 GB de armazenamento gratuito no Puter.com!`, + recover_password: "Recuperar Password", + refer_friends_c2a: "Ganha 1 GB por cada amigo que criar e confirmar uma conta Puter. Os teus amigos também ganham 1 GB!", + refer_friends_social_media_c2a: `Ganha 1 GB de armazenamento gratuito em Puter.com!`, refresh: 'Atualizar', - release_address_confirmation: `Desejas liberar este endereço?`, + release_address_confirmation: `Queres libertar este endereço?`, remove_from_taskbar:'Remover da Barra de Tarefas', rename: 'Renomear', repeat: 'Repetir', replace: 'Substituir', - replace_all: 'Substituir Todas', + replace_all: 'Substituir Todos', resend_confirmation_code: "Re-enviar o Código de Confirmação", restore: "Restaurar", save_account: 'Gravar conta', - save_account_to_get_copy_link: "Favor criares uma conta para prosseguir.", - save_account_to_publish: 'Favor criares uma conta para prosseguir.', + save_account_to_get_copy_link: "Para continuar, pff cria uma conta.", + save_account_to_publish: 'Para continuar, pff cria uma conta.', save_session: 'Gravar sessão', - save_session_c2a: 'Crie uma conta para gravares a sessão atual e evitar a perda de vosso trabalho.', - scan_qr_c2a: 'Escaneie o código abaixo para entrares nesta sessão com outros dispositivos', + save_session_c2a: 'Cria uma conta para gravares a sessão atual e evitares perder o teu trabalho.', + scan_qr_c2a: 'Digitaliza o código abaixo para entrares nesta sessão com outros dispositivos', select: "Selecionar", selected: 'selecionado', select_color: 'Selecionar cor…', send: "Enviar", - send_password_recovery_email: "Enviar Email de Recuperação de Senha", + send_password_recovery_email: "Enviar Email de Recuperação de Password", session_saved: "Obrigado por criares uma conta. Esta sessão foi gravada.", - settings: "Configurações", - set_new_password: "Informar Palavra Passe", + settings: "Definições", + set_new_password: "Definir nova Password", share_to: "Partilhar com", - show_all_windows: "Exibir Todas as Janelas", - show_hidden: 'Exibir oculto', - sign_in_with_puter: "Entrar no Puter", + show_all_windows: "Mostrar Todas as Janelas", + show_hidden: 'Exibir janelas ocultas', + sign_in_with_puter: "Entrar em Puter", sign_up: "Registar", signing_in: "Entrar…", size: 'Tamanho', - skip: 'Pular', - sort_by: 'Organizar por', - start: 'Início', + skip: 'Passar à frente', + sort_by: 'Ordenar por', + start: 'Iniciar', status: "Status", storage_usage: "Uso do Armazenamento", - taking_longer_than_usual: 'Está a levar mais tempo que o usual. Por favor, aguarde...', + taking_longer_than_usual: 'Está a levar mais tempo que o usual. Por favor aguarda...', terms: "Termos", text_document: 'Documento de Texto', - tos_fineprint: `Ao clicar em 'Criar Conta Gratuita' concordas com os Termos de Serviço e Política de Privacidade do Puter.`, + tos_fineprint: `Ao clicares em 'Criar Conta Gratuita' concordas com os Termos de Serviço e Política de Privacidade de Puter.com.`, trash: 'Lixo', type: 'Tipo', - type_confirm_to_delete_account: "Digite 'confirm' para excluires vossa conta.", - undo: 'Desfazer', + type_confirm_to_delete_account: "Escreve 'confirm' para apagares esta conta.", + undo: 'Voltar atrás', unlimited: 'Ilimitado', - unzip: "Deszipar", - upload: 'Enviar', - upload_here: 'Enviar aqui', - usage: 'Uso', + unzip: "Abrir zip", + upload: 'Carregar', + upload_here: 'Carregar para aqui', + usage: 'Utilização', username: "Nome de Utilizador", - username_changed: 'Nome de Utilizador atualizado com sucesso.', + username_changed: 'Nome de Utilizador atualizado.', versions: "Versões", yes: 'Sim', - yes_release_it: 'Sim, Libere Isto', - you_have_been_referred_to_puter_by_a_friend: "Indicaste o Puter a um amigo!", + yes_release_it: 'Sim, libertar', + you_have_been_referred_to_puter_by_a_friend: "Um amigo teu recomendou-te a Puter.com!", zip: "Zipar", } }; From 4845bd28a13ee9b56fea89696178f7e9c6438b2a Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Sun, 31 Mar 2024 21:21:10 -0400 Subject: [PATCH 03/11] Create default user --- package-lock.json | 40 ++++++++++ packages/backend/package.json | 1 + packages/backend/src/CoreModule.js | 3 + packages/backend/src/Kernel.js | 1 + .../backend/src/fun/dev-console-ui-utils.js | 32 ++++++++ .../src/services/DefaultUserService.js | 80 +++++++++++++++++++ .../backend/src/services/WebServerService.js | 16 +--- 7 files changed, 161 insertions(+), 12 deletions(-) create mode 100644 packages/backend/src/fun/dev-console-ui-utils.js create mode 100644 packages/backend/src/services/DefaultUserService.js diff --git a/package-lock.json b/package-lock.json index e1fbd60d..0b1c2824 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9637,6 +9637,45 @@ "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", "integrity": "sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==" }, + "node_modules/string-length": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-6.0.0.tgz", + "integrity": "sha512-1U361pxZHEQ+FeSjzqRpV+cu2vTzYeWeafXFLykiFlv4Vc0n3njgU8HrMbyik5uwm77naWMuVG8fhEF+Ovb1Kg==", + "dependencies": { + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -10807,6 +10846,7 @@ "socket.io": "^4.6.2", "ssh2": "^1.13.0", "string-hash": "^1.1.3", + "string-length": "^6.0.0", "svgo": "^3.0.2", "tiktoken": "^1.0.11", "uglify-js": "^3.17.4", diff --git a/packages/backend/package.json b/packages/backend/package.json index 01c4678a..eb18aa61 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -61,6 +61,7 @@ "socket.io": "^4.6.2", "ssh2": "^1.13.0", "string-hash": "^1.1.3", + "string-length": "^6.0.0", "svgo": "^3.0.2", "tiktoken": "^1.0.11", "uglify-js": "^3.17.4", diff --git a/packages/backend/src/CoreModule.js b/packages/backend/src/CoreModule.js index 5357b3e8..105edb5e 100644 --- a/packages/backend/src/CoreModule.js +++ b/packages/backend/src/CoreModule.js @@ -182,6 +182,9 @@ const install = async ({ services, app }) => { const { EventService } = require('./services/EventService'); services.registerService('event', EventService); + const DefaultUserService = require('./services/DefaultUserService'); + services.registerService('__default-user', DefaultUserService); + } const install_legacy = async ({ services }) => { diff --git a/packages/backend/src/Kernel.js b/packages/backend/src/Kernel.js index 4f7cef53..f1366804 100644 --- a/packages/backend/src/Kernel.js +++ b/packages/backend/src/Kernel.js @@ -183,6 +183,7 @@ class Kernel extends AdvancedBase { await services.emit('start.webserver'); + await services.emit('ready.webserver'); } } diff --git a/packages/backend/src/fun/dev-console-ui-utils.js b/packages/backend/src/fun/dev-console-ui-utils.js new file mode 100644 index 00000000..db9ac2a4 --- /dev/null +++ b/packages/backend/src/fun/dev-console-ui-utils.js @@ -0,0 +1,32 @@ +const { TeePromise } = require('../util/promise'); + +const es_import_promise = new TeePromise(); +let stringLength; +(async () => { + stringLength = (await import('string-length')).default; + es_import_promise.resolve(); + // console.log('STRING LENGTH', stringLength); + // process.exit(0); +})(); +const surrounding_box = (col, lines, lengths) => { + if ( ! lengths ) { + lengths = lines.map(line => stringLength(line)); + } + + const max_length = Math.max(...lengths); + const c = str => `\x1b[${col}m${str}\x1b[0m`; + const bar = c(Array(max_length + 4).fill('━').join('')); + for ( let i = 0 ; i < lines.length ; i++ ) { + while ( stringLength(lines[i]) < max_length ) { + lines[i] += ' '; + } + lines[i] = `${c('┃ ')} ${lines[i]} ${c(' ┃')}`; + } + lines.unshift(`${c('┏')}${bar}${c('┓')}`); + lines.push(`${c('┗')}${bar}${c('┛')}`); +}; + +module.exports = { + surrounding_box, + es_import_promise, +}; diff --git a/packages/backend/src/services/DefaultUserService.js b/packages/backend/src/services/DefaultUserService.js new file mode 100644 index 00000000..e553f7a6 --- /dev/null +++ b/packages/backend/src/services/DefaultUserService.js @@ -0,0 +1,80 @@ +const { surrounding_box } = require("../fun/dev-console-ui-utils"); +const { get_user, generate_system_fsentries } = require("../helpers"); +const { asyncSafeSetInterval } = require("../util/promise"); +const BaseService = require("./BaseService"); +const { DB_WRITE } = require("./database/consts"); + +const DEFAULT_PASSWORD = 'changeme'; +const USERNAME = 'default_user'; + +class DefaultUserService extends BaseService { + static MODULES = { + bcrypt: require('bcrypt'), + uuidv4: require('uuid').v4, + } + async _init () { + } + async ['__on_ready.webserver'] () { + // check if a user named `default-user` exists + let user = await get_user({ username: USERNAME }); + if ( ! user ) user = await this.create_default_user_(); + + // check if user named `default-user` is using default password + const require = this.require; + const bcrypt = require('bcrypt'); + const is_default_password = await bcrypt.compare(DEFAULT_PASSWORD, user.password); + if ( ! is_default_password ) return; + + // show console widget + this.default_user_widget = () => { + const lines = [ + `Your default user has been created!`, + `\x1B[31;1musername:\x1B[0m ${USERNAME}`, + `\x1B[32;1mpassword:\x1B[0m ${DEFAULT_PASSWORD}`, + `(change the password to remove this message)` + ]; + surrounding_box('31;1', lines); + return lines; + }; + this.start_poll_(); + const svc_devConsole = this.services.get('dev-console'); + svc_devConsole.add_widget(this.default_user_widget); + } + start_poll_ () { + const interval = 1000 * 3; // 3 seconds + const poll_interval = asyncSafeSetInterval(async () => { + const user = await get_user({ username: USERNAME }); + const require = this.require; + const bcrypt = require('bcrypt'); + const is_default_password = await bcrypt.compare(DEFAULT_PASSWORD, user.password); + if ( ! is_default_password ) { + const svc_devConsole = this.services.get('dev-console'); + svc_devConsole.remove_widget(this.default_user_widget); + clearInterval(poll_interval); + return; + } + }, interval); + } + async create_default_user_ () { + const require = this.require; + const bcrypt = require('bcrypt'); + const db = this.services.get('database').get(DB_WRITE, 'default-user'); + await db.write( + ` + INSERT INTO user (uuid, username, password, free_storage) + VALUES (?, ?, ?, ?) + `, + [ + this.modules.uuidv4(), + USERNAME, + await bcrypt.hash(DEFAULT_PASSWORD, 8), + 1024 * 1024 * 1024 * 10, // 10 GB + ], + ); + const user = await get_user({ username: USERNAME }); + await generate_system_fsentries(user); + return user; + } +} + +module.exports = DefaultUserService; diff --git a/packages/backend/src/services/WebServerService.js b/packages/backend/src/services/WebServerService.js index 4c987074..07337e7b 100644 --- a/packages/backend/src/services/WebServerService.js +++ b/packages/backend/src/services/WebServerService.js @@ -27,6 +27,7 @@ var http = require('http'); const fs = require('fs'); const auth = require('../middleware/auth'); const { osclink } = require('../util/strutil'); +const { surrounding_box, es_import_promise } = require('../fun/dev-console-ui-utils'); class WebServerService extends BaseService { static MODULES = { @@ -42,6 +43,7 @@ class WebServerService extends BaseService { }; async ['__on_start.webserver'] () { + await es_import_promise; // error handling middleware goes last, as per the // expressjs documentation: @@ -122,17 +124,7 @@ class WebServerService extends BaseService { lines[2].length, 0, ]; - const max_length = Math.max(...lengths); - const c = str => `\x1b[34;1m${str}\x1b[0m`; - const bar = c(Array(max_length + 4).fill('━').join('')); - for ( let i = 0 ; i < lines.length ; i++ ) { - while ( lines[i].length < max_length ) { - lines[i] += ' '; - } - lines[i] = `${c('┃ ')} ${lines[i]} ${c(' ┃')}`; - } - lines.unshift(`${c('┏')}${bar}${c('┓')}`); - lines.push(`${c('┗')}${bar}${c('┛')}`); + surrounding_box('34;1', lines, lengths); return lines; }; { @@ -360,7 +352,7 @@ class WebServerService extends BaseService { const pad = (width - last_logo.sz) / 2; const asymmetrical = pad % 1 !== 0; const pad_left = Math.floor(pad); - const pad_right = Math.ceil(pad) + (asymmetrical ? 1 : 0); + const pad_right = Math.ceil(pad); for ( let i = 0 ; i < lines.length ; i++ ) { lines[i] = ' '.repeat(pad_left) + lines[i] + ' '.repeat(pad_right); } From 5eebc9a5dec1e6e8d49b75bf9bc9132c18d9e1fa Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Sun, 31 Mar 2024 21:48:42 -0400 Subject: [PATCH 04/11] Update ports for repo docker --- Dockerfile | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index a33734e9..624fb208 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ USER node RUN npm cache clean --force \ && npm install -EXPOSE 4000 +EXPOSE 4100 CMD [ "npm", "start" ] diff --git a/docker-compose.yml b/docker-compose.yml index 47dc2947..e99230b0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,4 +4,4 @@ services: app: build: ./ ports: - - 4000:4000 + - 4100:4100 From a8a03d3f871b9af40582afd9267b83f124e4c616 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Sun, 31 Mar 2024 22:42:11 -0400 Subject: [PATCH 05/11] Add some config documentation --- README.md | 19 ++++++++++ doc/self-hosters/domains.md | 72 +++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 doc/self-hosters/domains.md diff --git a/README.md b/README.md index db4509d5..9aa06079 100644 --- a/README.md +++ b/README.md @@ -52,9 +52,28 @@ docker compose up
+See [Configuration](#configuration) for next steps. + +
+ ## ⚠️ Self-Hosting ⚠️ The self-hosted version of Puter is currently in alpha stage and should not be used in production yet. It is under active development and may contain bugs, other issues. Please exercise caution and use it for testing and evaluation purposes only. +## Configuration + +Running the server will generate a configuration file at `volatile/config/config.json`. + +To access Puter on your device, you can simply go to the address printed in +the server console (usually `puter.localhost:4100`). + +To access Puter from another device, a domain name must be configured, as well as +an `api` subdomain. For example, `example.local` might be the domain name pointing +to the IP address of the server running puter, and `api.example.com` must point to +this address as well. This domain must be specified in the configuration file +(usually `volatile/config/config.json`) as well. + +See [domain configuration](./doc/self-hosters/domains.md) for more information. +
## FAQ diff --git a/doc/self-hosters/domains.md b/doc/self-hosters/domains.md new file mode 100644 index 00000000..ad7fdbbc --- /dev/null +++ b/doc/self-hosters/domains.md @@ -0,0 +1,72 @@ +# Configurating Domains for Self-Hosted Puter + +## Local Network Configuration + +### Prerequisite Conditions + +Ensure the hosting device has a static IP address to prevent potential connectivity issues due to IP changes. This setup will enable seamless access to Puter and its services across your local network. + +### Using Hosts Files + +The hosts file is a straightforward way to map domain names to IP addresses on individual devices. It's simple to set up but requires manual changes on each device that needs access to the domains. + +#### Windows +1. Open Notepad as an administrator. +2. Open the file located at `C:\Windows\System32\drivers\etc\hosts`. +3. Add lines for your domain and subdomain with the server's IP address, in the + following format: + ``` + 192.168.1.10 puter.local + 192.168.1.10 api.puter.local + ``` + +### For macOS and Linux: +1. Open a terminal. +2. Edit the hosts file with a text editor, e.g., `sudo nano /etc/hosts`. +3. Add lines for your domain and subdomain with the server's IP address, in the + following format: + ``` + 192.168.1.10 puter.local + 192.168.1.10 api.puter.local + ``` +4. Save and exit the editor. + + +### Using Router Configuration + +Some routers allow you to add custom DNS rules, letting you configure domain names network-wide without touching each device. + +1. Access your router’s admin interface (usually through a web browser). +2. Look for DNS or DHCP settings. +3. Add custom DNS mappings for `puter.local` and `api.puter.local` to the hosting device's IP address. +4. Save the changes and reboot the router if necessary. + +This method's availability and steps may vary depending on your router's model and firmware. + +### Using Local DNS + +Setting up a local DNS server on your network allows for flexible and scalable domain name resolution. This method works across all devices automatically once they're configured to use the DNS server. + +#### Options for DNS Software: + +- **Pi-hole**: Acts as both an ad-blocker and a DNS server. Ideal for easy setup and maintenance. +- **BIND9**: Offers comprehensive DNS server capabilities for complex setups. +- **Dnsmasq**: Lightweight and suitable for smaller networks or those new to running a DNS server. + +**contributors note:** feel free to add any software you're aware of +which might help with this to the list. Also, feel free to add instructions here for specific software; our goal is for Puter to be easy to setup with tools you're already familiar with. + +#### General Steps: + +1. Choose and install DNS server software on a device within your network. +2. Configure the DNS server to resolve `puter.local` and `api.puter.local` to the IP address of your Puter hosting device. +3. Update your router’s DHCP settings to distribute the DNS server's IP address to all devices on the network. + +By setting up a local DNS server, you gain the most flexibility and control over your network's domain name resolution, ensuring that all devices can access Puter and its API without manual configuration. + +## Production Configuration + +Please note the self-hosting feature is still in alpha and a public production +deployment is not recommended at this time. However, if you wish to host +publically you can do so following the same steps you normally would to configure +a domain name and ensuring the `api` subdomain points to the server as well. From 1cdda3dda8013d6f5d95dd20dc83e31c3eff4adb Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Sun, 31 Mar 2024 22:47:13 -0400 Subject: [PATCH 06/11] Document default user --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 9aa06079..1121a611 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,8 @@ The self-hosted version of Puter is currently in alpha stage and should not be u Running the server will generate a configuration file at `volatile/config/config.json`. +### Domain Name + To access Puter on your device, you can simply go to the address printed in the server console (usually `puter.localhost:4100`). @@ -74,6 +76,14 @@ this address as well. This domain must be specified in the configuration file See [domain configuration](./doc/self-hosters/domains.md) for more information. +### Default User + +By default, Puter will create a user called `default_user` with the password +`changeme`. A warning will persist in the dev console until this user's +password is changed. Please login to this user and change the password as +your first step. This user by default has 10GB storage instead of the default +(500MB storage) for new/temporary users. +
## FAQ From 92b3f4ff760868d07f6f288c87b9346de7df2cb5 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Mon, 1 Apr 2024 01:23:53 -0400 Subject: [PATCH 07/11] Default user random pass, sqlite query patches --- packages/backend/src/Kernel.js | 5 +- .../src/services/DefaultUserService.js | 68 +++++++++++++++---- .../drivers/implementations/DBKVStore.js | 31 ++++++--- .../src/services/sla/MonthlyUsageService.js | 45 +++++++++--- 4 files changed, 113 insertions(+), 36 deletions(-) diff --git a/packages/backend/src/Kernel.js b/packages/backend/src/Kernel.js index f1366804..e1fac6f3 100644 --- a/packages/backend/src/Kernel.js +++ b/packages/backend/src/Kernel.js @@ -95,11 +95,8 @@ class Kernel extends AdvancedBase { root_context.arun(async () => { await this._install_modules(); - }); - - (async () => { await this._boot_services(); - })(); + }); // Error.stackTraceLimit = Infinity; diff --git a/packages/backend/src/services/DefaultUserService.js b/packages/backend/src/services/DefaultUserService.js index e553f7a6..424795b3 100644 --- a/packages/backend/src/services/DefaultUserService.js +++ b/packages/backend/src/services/DefaultUserService.js @@ -1,7 +1,9 @@ const { surrounding_box } = require("../fun/dev-console-ui-utils"); -const { get_user, generate_system_fsentries } = require("../helpers"); +const { get_user, generate_system_fsentries, invalidate_cached_user } = require("../helpers"); +const { Context } = require("../util/context"); const { asyncSafeSetInterval } = require("../util/promise"); const BaseService = require("./BaseService"); +const { Actor, UserActorType } = require("./auth/Actor"); const { DB_WRITE } = require("./database/consts"); const DEFAULT_PASSWORD = 'changeme'; @@ -16,13 +18,24 @@ class DefaultUserService extends BaseService { } async ['__on_ready.webserver'] () { // check if a user named `default-user` exists - let user = await get_user({ username: USERNAME }); + let user = await get_user({ username: USERNAME, cached: false }); if ( ! user ) user = await this.create_default_user_(); // check if user named `default-user` is using default password const require = this.require; + const tmp_password = await this.get_tmp_password_(user); + console.log(`second input [${tmp_password}]`); const bcrypt = require('bcrypt'); - const is_default_password = await bcrypt.compare(DEFAULT_PASSWORD, user.password); + console.log(...[ + 'THESE ARE THE ARGS', + tmp_password, + // password_hashed, + user.password + ].map(l => l + '\n')); + const is_default_password = await bcrypt.compare( + tmp_password, + user.password + ); if ( ! is_default_password ) return; // show console widget @@ -30,23 +43,26 @@ class DefaultUserService extends BaseService { const lines = [ `Your default user has been created!`, `\x1B[31;1musername:\x1B[0m ${USERNAME}`, - `\x1B[32;1mpassword:\x1B[0m ${DEFAULT_PASSWORD}`, + `\x1B[32;1mpassword:\x1B[0m ${tmp_password}`, `(change the password to remove this message)` ]; surrounding_box('31;1', lines); return lines; }; - this.start_poll_(); + this.start_poll_({ tmp_password, user }); const svc_devConsole = this.services.get('dev-console'); svc_devConsole.add_widget(this.default_user_widget); } - start_poll_ () { + start_poll_ ({ tmp_password, user }) { const interval = 1000 * 3; // 3 seconds const poll_interval = asyncSafeSetInterval(async () => { const user = await get_user({ username: USERNAME }); const require = this.require; const bcrypt = require('bcrypt'); - const is_default_password = await bcrypt.compare(DEFAULT_PASSWORD, user.password); + const is_default_password = await bcrypt.compare( + tmp_password, + user.password + ); if ( ! is_default_password ) { const svc_devConsole = this.services.get('dev-console'); svc_devConsole.remove_widget(this.default_user_widget); @@ -56,25 +72,53 @@ class DefaultUserService extends BaseService { }, interval); } async create_default_user_ () { - const require = this.require; - const bcrypt = require('bcrypt'); const db = this.services.get('database').get(DB_WRITE, 'default-user'); await db.write( ` - INSERT INTO user (uuid, username, password, free_storage) - VALUES (?, ?, ?, ?) + INSERT INTO user (uuid, username, free_storage) + VALUES (?, ?, ?) `, [ this.modules.uuidv4(), USERNAME, - await bcrypt.hash(DEFAULT_PASSWORD, 8), 1024 * 1024 * 1024 * 10, // 10 GB ], ); const user = await get_user({ username: USERNAME }); + const tmp_password = await this.get_tmp_password_(user); + console.log(`first input [${tmp_password}]`); + const bcrypt = require('bcrypt'); + const password_hashed = await bcrypt.hash(tmp_password, 8); + await db.write( + `UPDATE user SET password = ? WHERE id = ?`, + [ + password_hashed, + user.id, + ], + ); + user.password = password_hashed; await generate_system_fsentries(user); + invalidate_cached_user(user); + await new Promise(rslv => setTimeout(rslv, 2000)); return user; } + async get_tmp_password_ (user) { + const actor = await Actor.create(UserActorType, { user }); + return await Context.get().sub({ actor }).arun(async () => { + const svc_driver = this.services.get('driver'); + const driver_response = await svc_driver.call( + 'puter-kvstore', 'get', { key: 'tmp_password' }); + + if ( driver_response.result ) return driver_response.result.value; + + const tmp_password = require('crypto').randomBytes(4).toString('hex'); + await svc_driver.call( + 'puter-kvstore', 'set', { + key: 'tmp_password', + value: tmp_password }); + return tmp_password; + }); + } } module.exports = DefaultUserService; diff --git a/packages/backend/src/services/drivers/implementations/DBKVStore.js b/packages/backend/src/services/drivers/implementations/DBKVStore.js index c203184e..07094c1b 100644 --- a/packages/backend/src/services/drivers/implementations/DBKVStore.js +++ b/packages/backend/src/services/drivers/implementations/DBKVStore.js @@ -84,15 +84,28 @@ class DBKVStore extends BaseImplementation { const db = this.services.get('database').get(DB_WRITE, 'kvstore'); const key_hash = this.modules.murmurhash.v3(key); - await db.write( - `INSERT INTO kv - (user_id, app, kkey_hash, kkey, value) - VALUES - (?, ?, ?, ?, ?) - ON DUPLICATE KEY UPDATE - value = ?`, - [ user.id, app?.uid ?? 'global', key_hash, key, value, value ] - ); + try { + await db.write( + `INSERT INTO kv (user_id, app, kkey_hash, kkey, value) + VALUES (?, ?, ?, ?, ?) ` + + db.case({ + mysql: 'ON DUPLICATE KEY UPDATE value = ?', + sqlite: ' ', + // sqlite: 'ON CONFLICT(user_id, app, kkey_hash) DO UPDATE SET value = ?', + }), + [ + user.id, app?.uid ?? 'global', key_hash, key, value, + ...db.case({ mysql: [value], otherwise: [] }), + ] + ); + } catch (e) { + // if ( e.code !== 'SQLITE_ERROR' && e.code !== 'SQLITE_CONSTRAINT_PRIMARYKEY' ) throw e; + // The "ON CONFLICT" clause isn't currently working. + await db.write( + `UPDATE kv SET value = ? WHERE user_id=? AND app=? AND kkey_hash=?`, + [ value, user.id, app?.uid ?? 'global', key_hash ] + ); + } return true; }, diff --git a/packages/backend/src/services/sla/MonthlyUsageService.js b/packages/backend/src/services/sla/MonthlyUsageService.js index 2a8ab7c9..015c685b 100644 --- a/packages/backend/src/services/sla/MonthlyUsageService.js +++ b/packages/backend/src/services/sla/MonthlyUsageService.js @@ -34,18 +34,41 @@ class MonthlyUsageService extends BaseService { const maybe_app_id = actor.type.app?.id; + if ( this.db.case({ sqlite: true, otherwise: false }) ) { + return; + } + // UPSERT increment count - await this.db.write( - 'INSERT INTO `service_usage_monthly` (`year`, `month`, `key`, `count`, `user_id`, `app_id`, `extra`) ' + - 'VALUES (?, ?, ?, 1, ?, ?, ?) ' + - 'ON DUPLICATE KEY UPDATE `count` = `count` + 1', - [ - year, month, key, - actor.type.user?.id || null, - maybe_app_id || null, - JSON.stringify(extra) - ] - ); + try { + await this.db.write( + 'INSERT INTO `service_usage_monthly` (`year`, `month`, `key`, `count`, `user_id`, `app_id`, `extra`) ' + + 'VALUES (?, ?, ?, 1, ?, ?, ?) ' + + this.db.case({ + mysql: 'ON DUPLICATE KEY UPDATE `count` = `count` + 1, `extra` = ?', + sqlite: ' ', + // sqlite: 'ON CONFLICT(`year`, `month`, `key`, `user_id`, `app_id`) ' + + // 'DO UPDATE SET `count` = `count` + 1 AND `extra` = ?', + }), + [ + year, month, key, actor.type.user.id, maybe_app_id, JSON.stringify(extra), + ...this.db.case({ mysql: [JSON.stringify(extra)], otherwise: [] }), + ] + ); + } catch (e) { + // if ( e.code !== 'SQLITE_ERROR' && e.code !== 'SQLITE_CONSTRAINT_PRIMARYKEY' ) throw e; + // The "ON CONFLICT" clause isn't currently working. + await this.db.write( + 'UPDATE `service_usage_monthly` ' + + 'SET `count` = `count` + 1, `extra` = ? ' + + 'WHERE `year` = ? AND `month` = ? AND `key` = ? ' + + 'AND `user_id` = ? AND `app_id` = ?', + [ + JSON.stringify(extra), + year, month, key, actor.type.user.id, maybe_app_id, + ] + ); + + } } async check (actor, specifiers) { From c2f73c37c5645414aed807a0b0bfd93b4bb19f85 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Mon, 1 Apr 2024 11:26:07 +0100 Subject: [PATCH 08/11] Correct capitalisation of i18n() keys Found by searching for `i18n\(\'[^\']*[A-Z]+[^\']*\'\)` --- src/UI/UIDesktop.js | 2 +- src/UI/UIItem.js | 6 +++--- src/UI/UIPrompt.js | 4 ++-- src/UI/UIWindowFontPicker.js | 2 +- src/UI/UIWindowUploadProgress.js | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/UI/UIDesktop.js b/src/UI/UIDesktop.js index a351cc0e..1a2da3a0 100644 --- a/src/UI/UIDesktop.js +++ b/src/UI/UIDesktop.js @@ -1217,7 +1217,7 @@ $(document).on('click', '.user-options-menu-btn', async function(e){ message: `

${i18n('confirm_open_apps_log_out')}

`, buttons:[ { - label: i18n('close_all_Windows_and_log_out'), + label: i18n('close_all_windows_and_log_out'), value: 'close_and_log_out', type: 'primary', }, diff --git a/src/UI/UIItem.js b/src/UI/UIItem.js index 01219ec6..ec6bd932 100644 --- a/src/UI/UIItem.js +++ b/src/UI/UIItem.js @@ -783,7 +783,7 @@ function UIItem(options){ // Donwload // ------------------------------------------- menu_items.push({ - html: i18n('Download'), + html: i18n('download'), onClick: async function(){ let items = []; for (let index = 0; index < $selected_items.length; index++) { @@ -1113,11 +1113,11 @@ function UIItem(options){ }); } // ------------------------------------------- - // Donwload + // Download // ------------------------------------------- if(!is_trash && !is_trashed && (options.associated_app_name === null || options.associated_app_name === undefined)){ menu_items.push({ - html: i18n('Download'), + html: i18n('download'), disabled: options.is_dir && !window.feature_flags.download_directory, onClick: async function(){ if(options.is_dir) diff --git a/src/UI/UIPrompt.js b/src/UI/UIPrompt.js index f30f43a8..7d48f72c 100644 --- a/src/UI/UIPrompt.js +++ b/src/UI/UIPrompt.js @@ -38,7 +38,7 @@ function UIPrompt(options){ if(!options.buttons || options.buttons.length === 0){ options.buttons = [ {label: i18n('cancel'), value: false, type: 'default'}, - {label: i18n('OK'), value: true, type: 'primary'}, + {label: i18n('ok'), value: true, type: 'primary'}, ] } @@ -53,7 +53,7 @@ function UIPrompt(options){ if(options.buttons && options.buttons.length > 0){ h += `
`; h += ``; - h += ``; + h += ``; h += `
`; } diff --git a/src/UI/UIWindowFontPicker.js b/src/UI/UIWindowFontPicker.js index f4208b14..517e8152 100644 --- a/src/UI/UIWindowFontPicker.js +++ b/src/UI/UIWindowFontPicker.js @@ -60,7 +60,7 @@ async function UIWindowFontPicker(options){ h += ``; // Select - h += `` + h += `` h += ``; h += ``; h += ``; diff --git a/src/UI/UIWindowUploadProgress.js b/src/UI/UIWindowUploadProgress.js index e436fc74..23c5d5ec 100644 --- a/src/UI/UIWindowUploadProgress.js +++ b/src/UI/UIWindowUploadProgress.js @@ -41,7 +41,7 @@ async function UIWindowUploadProgress(options){ h += ``; const el_window = await UIWindow({ - title: i18n('Upload'), + title: i18n('upload'), icon: window.icons[`app-icon-uploader.svg`], uid: null, is_dir: false, From b111e05ef79ff1c31091715ef2877c185bc68e11 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Mon, 1 Apr 2024 12:00:58 +0100 Subject: [PATCH 09/11] Fix typo in 'change_always_open_with' i18n key --- src/UI/UIItem.js | 2 +- src/i18n/translations/br.js | 2 +- src/i18n/translations/en.js | 2 +- src/i18n/translations/nb.js | 2 +- src/i18n/translations/nl.js | 2 +- src/i18n/translations/pl.js | 2 +- src/i18n/translations/pt.js | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/UI/UIItem.js b/src/UI/UIItem.js index ec6bd932..996611e6 100644 --- a/src/UI/UIItem.js +++ b/src/UI/UIItem.js @@ -987,7 +987,7 @@ function UIItem(options){ ) ){ const alert_resp = await UIAlert({ - message: `${i18n('change_allways_open_with')} ` + html_encode(suggested_app.title) + '?', + message: `${i18n('change_always_open_with')} ` + html_encode(suggested_app.title) + '?', body_icon: suggested_app.icon, buttons:[ { diff --git a/src/i18n/translations/br.js b/src/i18n/translations/br.js index c1f041b3..2a4c036d 100644 --- a/src/i18n/translations/br.js +++ b/src/i18n/translations/br.js @@ -39,7 +39,7 @@ const br = { change_username: "Alterar Nome de Utilizador", close_all_windows: "Fechar Todas as Janelas", close_all_windows_and_log_out: 'Fechar Janelas e Sair', - change_allways_open_with: "Quer sempre abrir arquivos deste tipo com", + change_always_open_with: "Quer sempre abrir arquivos deste tipo com", color: 'Cor', confirm_account_for_free_referral_storage_c2a: 'Crie uma conta e confirme o endereço do email para receber 1 GB de armazenamento gratuito. Seu amigo receberá 1 GB de armazenamento gratuito também.', confirm_delete_multiple_items: 'Quer apagar estes itens permanentemente?', diff --git a/src/i18n/translations/en.js b/src/i18n/translations/en.js index 4f22ecac..d292549b 100644 --- a/src/i18n/translations/en.js +++ b/src/i18n/translations/en.js @@ -39,7 +39,7 @@ const en = { change_username: "Change Username", close_all_windows: "Close All Windows", close_all_windows_and_log_out: 'Close Windows and Log Out', - change_allways_open_with: "Do you want to always open this type of file with", + change_always_open_with: "Do you want to always open this type of file with", color: 'Color', confirm_account_for_free_referral_storage_c2a: 'Create an account and confirm your email address to receive 1 GB of free storage. Your friend will get 1 GB of free storage too.', confirm_delete_multiple_items: 'Are you sure you want to permanently delete these items?', diff --git a/src/i18n/translations/nb.js b/src/i18n/translations/nb.js index 50bfbe83..117e2a2d 100644 --- a/src/i18n/translations/nb.js +++ b/src/i18n/translations/nb.js @@ -36,7 +36,7 @@ const nb = { change_username: "Endre brukernavn", close_all_windows: "Lukk alle vinduer", close_all_windows_and_log_out: 'Lukk alle vinduer og logg ut', - change_allways_open_with: "Ønsker du å alltid åpne denne filtypen med", + change_always_open_with: "Ønsker du å alltid åpne denne filtypen med", color: "Farge", confirm_account_for_free_referral_storage_c2a: "Opprett en konto og bekreft e-postadressen din for å motta 1 GB gratis lagringsplass. Din venn vil også få 1 GB gratis lagringsplass.", confirm_delete_multiple_items: 'Er du sikker på at du vil slette disse elementene permanent?', diff --git a/src/i18n/translations/nl.js b/src/i18n/translations/nl.js index df8764bf..f71403de 100644 --- a/src/i18n/translations/nl.js +++ b/src/i18n/translations/nl.js @@ -39,7 +39,7 @@ const nl = { change_username: "Gebruikersnaam veranderen", close_all_windows: "Alle schermen sluiten", close_all_windows_and_log_out: 'Alle schermen sluiten en afmelden', - change_allways_open_with: "Dit type bestand altijd openen met", + change_always_open_with: "Dit type bestand altijd openen met", color: 'Kleur', confirm_account_for_free_referral_storage_c2a: 'Maak een account en bevestig uw emailadres om 1 GB aan gratis opslag te ontvangen. Uw vriend krijgt ook 1 GB aan gratis opslag.', confirm_delete_multiple_items: 'Weet u zeker dat u deze bestanden permanent wilt verwijderen?', diff --git a/src/i18n/translations/pl.js b/src/i18n/translations/pl.js index f958a2c5..59ca7e91 100644 --- a/src/i18n/translations/pl.js +++ b/src/i18n/translations/pl.js @@ -36,7 +36,7 @@ const pl = { change_username: "Zmień użytkownika", close_all_windows: "Zamknij wszystkie okna", close_all_windows_and_log_out: 'Zamknij wszystkie okna i wyloguj', - change_allways_open_with: "Czy chcesz zawsze otwierać ten typ pliku używając", + change_always_open_with: "Czy chcesz zawsze otwierać ten typ pliku używając", color: 'Kolor', confirm_account_for_free_referral_storage_c2a: 'Stwórz konto i potwierdź swój adres e-mail, żeby dostać 1 GB darmowego miejsca. Twój znajomy również dostanie 1 GB darmowego miejsca.', confirm_delete_multiple_items: 'Czy na pewno chcesz na zawsze usunąć te przedmioty?', diff --git a/src/i18n/translations/pt.js b/src/i18n/translations/pt.js index 524e73e0..c427a0d1 100644 --- a/src/i18n/translations/pt.js +++ b/src/i18n/translations/pt.js @@ -39,7 +39,7 @@ const pt = { change_username: "Alterar o Nome de Utilizador", close_all_windows: "Fechar Todas as Janelas", close_all_windows_and_log_out: 'Fechar Janelas e Sair', - change_allways_open_with: "Queres que ficheiros deste tipo abram sempre com", + change_always_open_with: "Queres que ficheiros deste tipo abram sempre com", color: 'Cor', confirm_account_for_free_referral_storage_c2a: 'Cria uma conta e confirma o endereço do email para receber 1 GB de armazenamento gratuito. O teu amigo também receberá 1 GB de armazenamento gratuito.', confirm_delete_multiple_items: 'Tens a certeza que queres apagar estes itens permanentemente?', From 29b3b4ecbaa81881c06ffcb50a9c4c978a0e80d3 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Mon, 1 Apr 2024 12:10:31 +0100 Subject: [PATCH 10/11] Add or correct missing i18n keys 'save_account_to_publish' isn't used anywhere, so I assume that 'save_account_to_publish_website' is supposed to use that. --- src/UI/UIWindow.js | 2 +- src/i18n/translations/en.js | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/UI/UIWindow.js b/src/UI/UIWindow.js index 26863d52..88ead0f4 100644 --- a/src/UI/UIWindow.js +++ b/src/UI/UIWindow.js @@ -1956,7 +1956,7 @@ async function UIWindow(options) { if (window.user.is_temp && !await UIWindowSaveAccount({ send_confirmation_code: true, - message: i18n('save_account_to_publish_website'), + message: i18n('save_account_to_publish'), window_options: { backdrop: true, close_on_backdrop_click: false, diff --git a/src/i18n/translations/en.js b/src/i18n/translations/en.js index d292549b..d1efac4f 100644 --- a/src/i18n/translations/en.js +++ b/src/i18n/translations/en.js @@ -34,6 +34,7 @@ const en = { cancel: 'Cancel', center: 'Center', change_desktop_background: 'Change desktop background…', + change_email: "Change Email", change_language: "Change Language", change_password: "Change Password", change_username: "Change Username", @@ -47,6 +48,7 @@ const en = { confirm_open_apps_log_out: 'You have open apps. Are you sure you want to log out?', confirm_new_password: "Confirm New Password", confirm_delete_user: "Are you sure you want to delete your account? All your files and data will be permanently deleted. This action cannot be undone.", + confirm_delete_user_title: "Delete Account?", contact_us: "Contact Us", contain: 'Contain', continue: "Continue", @@ -86,6 +88,7 @@ const en = { feedback: "Feedback", feedback_c2a: "Please use the form below to send us your feedback, comments, and bug reports.", feedback_sent_confirmation: "Thank you for contacting us. If you have an email associated with your account, you will hear back from us as soon as possible.", + fit: "Fit", forgot_pass_c2a: "Forgot password?", from: "From", general: "General", @@ -94,6 +97,7 @@ const en = { hide_all_windows: "Hide All Windows", html_document: 'HTML document', image: 'Image', + incorrect_password: "Incorrect password", invite_link: "Invite Link", item: 'item', items_in_trash_cannot_be_renamed: `This item can't be renamed because it's in the trash. To rename this item, first drag it out of the Trash.`, From 8f8b7f0fbf59099dcb6549b2edc713943954801a Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Mon, 1 Apr 2024 12:12:31 +0100 Subject: [PATCH 11/11] Check that all uses of i18n() use keys that exist If code uses `i18n('foo')` and 'foo' is not in the dictionary for the en translation, then this script will now report it as an error. This has already helped catch a few mistakes. --- tools/check-translations.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tools/check-translations.js b/tools/check-translations.js index b718ec63..f5f45b38 100644 --- a/tools/check-translations.js +++ b/tools/check-translations.js @@ -53,6 +53,7 @@ async function checkTranslationRegistrations() { } } +// Ensure that translations only contain keys that exist in the en dictionary function checkTranslationKeys() { const enDictionary = translations.en.dictionary; @@ -71,8 +72,40 @@ function checkTranslationKeys() { } } +// Ensure that all keys passed to i18n() exist in the en dictionary +async function checkTranslationUsage() { + const enDictionary = translations.en.dictionary; + + const sourceDirectories = [ + './src/helpers', + './src/UI', + ]; + + // Looks for i18n() calls using either ' or " for the key string. + // The key itself is at index 2 of the result. + const i18nRegex = /i18n\((['"])(.*?)\1\)/g; + + for (const dir of sourceDirectories) { + const files = await fs.promises.readdir(dir, { recursive: true }); + for (const relativeFileName of files) { + if (!relativeFileName.endsWith('.js')) continue; + const fileName = `${dir}/${relativeFileName}`; + + const fileContents = await fs.promises.readFile(fileName, { encoding: 'utf8' }); + const i18nUses = fileContents.matchAll(i18nRegex); + for (const use of i18nUses) { + const key = use[2]; + if (!enDictionary.hasOwnProperty(key)) { + reportError(`Unrecognized i18n key: call ${use[0]} in ${fileName}`); + } + } + } + } +} + await checkTranslationRegistrations(); checkTranslationKeys(); +await checkTranslationUsage(); if (hadError) { process.stdout.write('Errors were found in translation files.\n');