Parcourir la source

Add first pass at i18n (using i18next).

dom111 il y a 2 ans
Parent
commit
d556e1e7a2

+ 13 - 0
README.md

@@ -11,6 +11,19 @@ other frontend approaches to see which I prefer (and also to weigh up the differ
 frameworks). There's still work to do around code separation and hopefully this will be something I can continue to work
 frameworks). There's still work to do around code separation and hopefully this will be something I can continue to work
 on (as time allows) I feel it's at least as stable as the previous version.
 on (as time allows) I feel it's at least as stable as the previous version.
 
 
+## Features
+
+- Browse, upload, download, rename, delete entries and create directories.
+- File preview for image, video, audio, font, text, PDF files.
+- Basic keyboard navigation.
+- A (very) simple gallery browser for preview-able files.
+
+## Localisation
+
+Currently, the library contains text translated to English, German and Portuguese. If you use this and would like it to
+be localised to your language please submit a PR including the translation (using [en.json](translations/en.json) as a template) and adding the
+language in [UI.js](src/lib/UI/UI.js).
+
 ## Tested in:
 ## Tested in:
 
 
 - Chrome
 - Chrome

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
assets/css/style.css


+ 2 - 0
examples/apache-directory-list-local/webdav.conf

@@ -8,6 +8,8 @@
 
 
     <Location />
     <Location />
         DirectoryIndex disabled
         DirectoryIndex disabled
+        IndexOptions +Charset=UTF-8
+
         DAV on
         DAV on
         AllowOverride None
         AllowOverride None
 
 

+ 2 - 0
examples/apache-directory-list/webdav.conf

@@ -8,6 +8,8 @@
 
 
     <Location />
     <Location />
         DirectoryIndex disabled
         DirectoryIndex disabled
+        IndexOptions +Charset=UTF-8
+
         DAV on
         DAV on
         AllowOverride None
         AllowOverride None
 
 

+ 77 - 0
package-lock.json

@@ -11,6 +11,8 @@
       "dependencies": {
       "dependencies": {
         "basiclightbox": "^5.0.2",
         "basiclightbox": "^5.0.2",
         "esbuild-plugin-copy": "^1.3.0",
         "esbuild-plugin-copy": "^1.3.0",
+        "i18next": "^21.9.1",
+        "i18next-browser-languagedetector": "^6.1.5",
         "melba-toast": "^2.0.0",
         "melba-toast": "^2.0.0",
         "prismjs": "^1.17.1",
         "prismjs": "^1.17.1",
         "whatwg-fetch": "^3.0.0"
         "whatwg-fetch": "^3.0.0"
@@ -538,6 +540,17 @@
         "@babel/core": "^7.0.0-0"
         "@babel/core": "^7.0.0-0"
       }
       }
     },
     },
+    "node_modules/@babel/runtime": {
+      "version": "7.18.9",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz",
+      "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==",
+      "dependencies": {
+        "regenerator-runtime": "^0.13.4"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
     "node_modules/@babel/template": {
     "node_modules/@babel/template": {
       "version": "7.18.10",
       "version": "7.18.10",
       "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
       "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
@@ -3086,6 +3099,36 @@
         "node": ">=10.17.0"
         "node": ">=10.17.0"
       }
       }
     },
     },
+    "node_modules/i18next": {
+      "version": "21.9.1",
+      "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.9.1.tgz",
+      "integrity": "sha512-ITbDrAjbRR73spZAiu6+ex5WNlHRr1mY+acDi2ioTHuUiviJqSz269Le1xHAf0QaQ6GgIHResUhQNcxGwa/PhA==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://locize.com"
+        },
+        {
+          "type": "individual",
+          "url": "https://locize.com/i18next.html"
+        },
+        {
+          "type": "individual",
+          "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+        }
+      ],
+      "dependencies": {
+        "@babel/runtime": "^7.17.2"
+      }
+    },
+    "node_modules/i18next-browser-languagedetector": {
+      "version": "6.1.5",
+      "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.5.tgz",
+      "integrity": "sha512-11t7b39oKeZe4uyMxLSPnfw28BCPNLZgUk7zyufex0zKXZ+Bv+JnmJgoB+IfQLZwDt1d71PM8vwBX1NCgliY3g==",
+      "dependencies": {
+        "@babel/runtime": "^7.18.9"
+      }
+    },
     "node_modules/iconv-lite": {
     "node_modules/iconv-lite": {
       "version": "0.4.24",
       "version": "0.4.24",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -4978,6 +5021,11 @@
         "node": ">=8.10.0"
         "node": ">=8.10.0"
       }
       }
     },
     },
+    "node_modules/regenerator-runtime": {
+      "version": "0.13.9",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
+      "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
+    },
     "node_modules/require-directory": {
     "node_modules/require-directory": {
       "version": "2.1.1",
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -6489,6 +6537,14 @@
         "@babel/helper-plugin-utils": "^7.18.6"
         "@babel/helper-plugin-utils": "^7.18.6"
       }
       }
     },
     },
+    "@babel/runtime": {
+      "version": "7.18.9",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz",
+      "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==",
+      "requires": {
+        "regenerator-runtime": "^0.13.4"
+      }
+    },
     "@babel/template": {
     "@babel/template": {
       "version": "7.18.10",
       "version": "7.18.10",
       "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
       "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
@@ -8366,6 +8422,22 @@
       "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
       "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
       "dev": true
       "dev": true
     },
     },
+    "i18next": {
+      "version": "21.9.1",
+      "resolved": "https://registry.npmjs.org/i18next/-/i18next-21.9.1.tgz",
+      "integrity": "sha512-ITbDrAjbRR73spZAiu6+ex5WNlHRr1mY+acDi2ioTHuUiviJqSz269Le1xHAf0QaQ6GgIHResUhQNcxGwa/PhA==",
+      "requires": {
+        "@babel/runtime": "^7.17.2"
+      }
+    },
+    "i18next-browser-languagedetector": {
+      "version": "6.1.5",
+      "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.5.tgz",
+      "integrity": "sha512-11t7b39oKeZe4uyMxLSPnfw28BCPNLZgUk7zyufex0zKXZ+Bv+JnmJgoB+IfQLZwDt1d71PM8vwBX1NCgliY3g==",
+      "requires": {
+        "@babel/runtime": "^7.18.9"
+      }
+    },
     "iconv-lite": {
     "iconv-lite": {
       "version": "0.4.24",
       "version": "0.4.24",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -9808,6 +9880,11 @@
         "picomatch": "^2.2.1"
         "picomatch": "^2.2.1"
       }
       }
     },
     },
+    "regenerator-runtime": {
+      "version": "0.13.9",
+      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
+      "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
+    },
     "require-directory": {
     "require-directory": {
       "version": "2.1.1",
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",

+ 2 - 0
package.json

@@ -46,6 +46,8 @@
   "dependencies": {
   "dependencies": {
     "basiclightbox": "^5.0.2",
     "basiclightbox": "^5.0.2",
     "esbuild-plugin-copy": "^1.3.0",
     "esbuild-plugin-copy": "^1.3.0",
+    "i18next": "^21.9.1",
+    "i18next-browser-languagedetector": "^6.1.5",
     "melba-toast": "^2.0.0",
     "melba-toast": "^2.0.0",
     "prismjs": "^1.17.1",
     "prismjs": "^1.17.1",
     "whatwg-fetch": "^3.0.0"
     "whatwg-fetch": "^3.0.0"

+ 46 - 13
src/lib/UI/NativeDOM.ts

@@ -2,6 +2,7 @@ import Container from './NativeDOM/Container';
 import Footer from './NativeDOM/Footer';
 import Footer from './NativeDOM/Footer';
 import Melba from 'melba-toast';
 import Melba from 'melba-toast';
 import UI from './UI';
 import UI from './UI';
+import i18next from 'i18next';
 import trailingSlash from '../trailingSlash';
 import trailingSlash from '../trailingSlash';
 
 
 export default class NativeDOM extends UI {
 export default class NativeDOM extends UI {
@@ -104,7 +105,15 @@ export default class NativeDOM extends UI {
     // global listeners
     // global listeners
     this.on('error', ({ method, url, response }) => {
     this.on('error', ({ method, url, response }) => {
       new Melba({
       new Melba({
-        content: `${method} ${url} failed: ${response.statusText} (${response.status})`,
+        content: i18next.t('failure', {
+          interpolation: {
+            escapeValue: false,
+          },
+          method,
+          url,
+          statusText: response.statusText,
+          status: response.status,
+        }),
         type: 'error',
         type: 'error',
       });
       });
     });
     });
@@ -113,12 +122,15 @@ export default class NativeDOM extends UI {
     this.on('upload', async (path, file) => {
     this.on('upload', async (path, file) => {
       const collection = await this.dav.list(path),
       const collection = await this.dav.list(path),
         [existingFile] = collection.filter((entry) => entry.name === file.name);
         [existingFile] = collection.filter((entry) => entry.name === file.name);
+
       if (existingFile) {
       if (existingFile) {
         // TODO: nicer notification
         // TODO: nicer notification
         // TODO: i18m
         // TODO: i18m
         if (
         if (
           !confirm(
           !confirm(
-            `A file called '${existingFile.title}' already exists, would you like to overwrite it?`
+            i18next.t('overwriteFileConfirmation', {
+              file: existingFile.title,
+            })
           )
           )
         ) {
         ) {
           return false;
           return false;
@@ -130,7 +142,12 @@ export default class NativeDOM extends UI {
 
 
     this.on('upload:success', async (path, file) => {
     this.on('upload:success', async (path, file) => {
       new Melba({
       new Melba({
-        content: `'${file.name}' has been successfully uploaded.`,
+        content: i18next.t('successfullyUploaded', {
+          interpolation: {
+            escapeValue: false,
+          },
+          file: file.name,
+        }),
         type: 'success',
         type: 'success',
         hide: 5,
         hide: 5,
       });
       });
@@ -154,20 +171,26 @@ export default class NativeDOM extends UI {
 
 
       if (entry.path === destinationPath || entry.directory) {
       if (entry.path === destinationPath || entry.directory) {
         return new Melba({
         return new Melba({
-          content: `'${
-            entry.title
-          }' successfully renamed to '${decodeURIComponent(
-            destinationFile || destinationPath
-          )}'.`,
+          content: i18next.t('successfullyRenamed', {
+            interpolation: {
+              escapeValue: false,
+            },
+            from: entry.title,
+            to: decodeURIComponent(destinationFile),
+          }),
           type: 'success',
           type: 'success',
           hide: 5,
           hide: 5,
         });
         });
       }
       }
 
 
       new Melba({
       new Melba({
-        content: `'${entry.title}' successfully moved to '${decodeURIComponent(
-          destinationPath
-        )}'.`,
+        content: i18next.t('successfullyMoved', {
+          interpolation: {
+            escapeValue: false,
+          },
+          from: entry.title,
+          to: decodeURIComponent(destinationPath),
+        }),
         type: 'success',
         type: 'success',
         hide: 5,
         hide: 5,
       });
       });
@@ -179,7 +202,12 @@ export default class NativeDOM extends UI {
 
 
     this.on('delete:success', (path, entry) => {
     this.on('delete:success', (path, entry) => {
       new Melba({
       new Melba({
-        content: `'${entry.title}' has been deleted.`,
+        content: i18next.t('successfullyDeleted', {
+          interpolation: {
+            escapeValue: false,
+          },
+          file: entry.title,
+        }),
         type: 'success',
         type: 'success',
         hide: 5,
         hide: 5,
       });
       });
@@ -211,7 +239,12 @@ export default class NativeDOM extends UI {
 
 
     this.on('mkcol:success', (fullPath, directoryName) => {
     this.on('mkcol:success', (fullPath, directoryName) => {
       new Melba({
       new Melba({
-        content: `'${directoryName}' has been created.`,
+        content: i18next.t('successfullyCreated', {
+          interpolation: {
+            escapeValue: false,
+          },
+          directoryName,
+        }),
         type: 'success',
         type: 'success',
         hide: 5,
         hide: 5,
       });
       });

+ 9 - 5
src/lib/UI/NativeDOM/Footer.ts

@@ -1,13 +1,18 @@
 import Element from './Element';
 import Element from './Element';
+import i18next from 'i18next';
 import joinPath from '../../joinPath';
 import joinPath from '../../joinPath';
 import trailingSlash from '../../trailingSlash';
 import trailingSlash from '../../trailingSlash';
 
 
 export default class Footer extends Element {
 export default class Footer extends Element {
   constructor() {
   constructor() {
     const template = `<footer class="upload">
     const template = `<footer class="upload">
-  <span class="droppable">Drop files anywhere to upload</span> or
-  <span class="files">Upload files <input type="file" multiple></span> or
-  <a href="#" class="create-directory">create a new directory</a>
+  <span class="droppable">${i18next.t(
+    'dropFilesAnywhereToUpload'
+  )}</span> ${i18next.t('or')}
+  <span class="files">${i18next.t(
+    'uploadFiles'
+  )} <input type="file" multiple></span> ${i18next.t('or')}
+  <a href="#" class="create-directory">${i18next.t('createNewDirectory')}</a>
 </footer>`;
 </footer>`;
 
 
     super(template);
     super(template);
@@ -31,8 +36,7 @@ export default class Footer extends Element {
       .addEventListener('click', async (event) => {
       .addEventListener('click', async (event) => {
         event.preventDefault();
         event.preventDefault();
 
 
-        // TODO: i18m
-        const directoryName = prompt('', 'Directory name');
+        const directoryName = prompt('', i18next.t('directoryName'));
 
 
         if (!directoryName) {
         if (!directoryName) {
           return;
           return;

+ 18 - 8
src/lib/UI/NativeDOM/List/Item.ts

@@ -1,6 +1,7 @@
 import * as BasicLightbox from 'basiclightbox';
 import * as BasicLightbox from 'basiclightbox';
 import Element from '../Element';
 import Element from '../Element';
 import Prism from 'prismjs';
 import Prism from 'prismjs';
+import i18next from 'i18next';
 import joinPath from '../../../joinPath';
 import joinPath from '../../../joinPath';
 
 
 export default class Item extends Element {
 export default class Item extends Element {
@@ -20,8 +21,8 @@ export default class Item extends Element {
         },
         },
         extension = entry.name.replace(/^.+\.([^.]+)$/, '$1').toLowerCase(),
         extension = entry.name.replace(/^.+\.([^.]+)$/, '$1').toLowerCase(),
         fontName = entry.fullPath.replace(/\W+/g, '_'),
         fontName = entry.fullPath.replace(/\W+/g, '_'),
-        demoText = `The quick brown fox jumps over the lazy dog. 0123456789<br/>
-        Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz`;
+        demoText = `${i18next.t('pangram')} 0123456789<br/>
+        ${i18next.t('alphabet')}`;
       return `<style type="text/css">@font-face{font-family:"${fontName}";src:url("${
       return `<style type="text/css">@font-face{font-family:"${fontName}";src:url("${
         entry.fullPath
         entry.fullPath
       }") format("${formats[extension] || extension}")}</style>
       }") format("${formats[extension] || extension}")}</style>
@@ -41,15 +42,19 @@ export default class Item extends Element {
   });
   });
 
 
   constructor(entry, base64Encoder = btoa) {
   constructor(entry, base64Encoder = btoa) {
-    super(`<li tabindex="0" data-full-path="${entry.fullPath}" data-type="${entry.type}">
+    super(`<li tabindex="0" data-full-path="${entry.fullPath}" data-type="${
+      entry.type
+    }">
   <span class="title">${entry.title}</span>
   <span class="title">${entry.title}</span>
   <input type="text" name="rename" class="hidden" readonly>
   <input type="text" name="rename" class="hidden" readonly>
   <span class="size">${entry.displaySize}</span>
   <span class="size">${entry.displaySize}</span>
-  <a href="#" title="Delete" class="delete"></a>
+  <a href="#" title="${i18next.t('delete')} (␡)" class="delete"></a>
   <!--<a href="#" title="Move" class="move"></a>-->
   <!--<a href="#" title="Move" class="move"></a>-->
-  <a href="#" title="Rename" class="rename"></a>
+  <a href="#" title="${i18next.t('rename')} (F2)" class="rename"></a>
   <!--<a href="#" title="Copy" class="copy"></a>-->
   <!--<a href="#" title="Copy" class="copy"></a>-->
-  <a href="${entry.fullPath}" download="${entry.name}" title="Download"></a>
+  <a href="${entry.fullPath}" download="${entry.name}" title="${i18next.t(
+      'download'
+    )} (⇧+⏎)"></a>
 </li>`);
 </li>`);
 
 
     this.#base64Encoder = base64Encoder;
     this.#base64Encoder = base64Encoder;
@@ -144,8 +149,13 @@ export default class Item extends Element {
 
 
     this.loading();
     this.loading();
 
 
-    // TODO: i18n
-    if (!confirm(`Are you sure you want to delete '${entry.title}?'`)) {
+    if (
+      !confirm(
+        i18next.t('deleteConfirm', {
+          file: entry.title,
+        })
+      )
+    ) {
       return this.loading(false);
       return this.loading(false);
     }
     }
 
 

+ 17 - 0
src/lib/UI/UI.ts

@@ -1,6 +1,11 @@
 import DAV from '../DAV';
 import DAV from '../DAV';
 import EventObject from '../EventObject';
 import EventObject from '../EventObject';
+import LanguageDetector from 'i18next-browser-languagedetector';
 import Unimplemented from '../Unimplemented';
 import Unimplemented from '../Unimplemented';
+import de from '../../../translations/de.json';
+import en from '../../../translations/en.json';
+import i18next from 'i18next';
+import pt from '../../../translations/pt.json';
 
 
 type UIOptions = {
 type UIOptions = {
   bypassCheck?: boolean;
   bypassCheck?: boolean;
@@ -29,6 +34,18 @@ export default class UI extends EventObject {
     this.#container = container;
     this.#container = container;
     this.#dav = dav;
     this.#dav = dav;
     this.#options = options;
     this.#options = options;
+
+    i18next.use(LanguageDetector).init({
+      detection: {
+        caches: [],
+      },
+      fallbackLng: 'en',
+      resources: {
+        de,
+        en,
+        pt,
+      },
+    });
   }
   }
 
 
   get options() {
   get options() {

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
src/webdav-min.js


+ 21 - 0
translations/de.json

@@ -0,0 +1,21 @@
+{
+  "translation": {
+    "pangram": "„Fix, Schwyz!“, quäkt Jürgen blöd vom Paß.",
+    "alphabet": "Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz Ää Öö Üü ẞß",
+    "dropFilesAnywhereToUpload": "Datei/en hineinverschieben um sie hochzuladen",
+    "uploadFiles": "Datei/en hochladen",
+    "or": "oder",
+    "createNewDirectory": "Neuen Ordner erstellen",
+    "delete": "Löschen",
+    "rename": "Umbenennen",
+    "download": "Herunterladen",
+    "deleteConfirmation": "Willst du wirklich die Datei '%s' löschen?",
+    "overwriteFileConfimation": "Die Datei '{{file}}' existiert bereits, willst du sie überschreiben?",
+    "failure": "{{method}} {{url}} fehlgeschlagen: {{statusText}} ({{status}})",
+    "successfullyUploaded": "'{{file}}' wurde erfolgreich hochgeladen.",
+    "successfullyRenamed": "'{{from}}' wurde erfolgreich umbenannt in '{{to}}'.",
+    "successfullyMoved": "'{{from}}' wurde erfolgreich verschoben nach '{{to}}'.",
+    "successfullyDeleted": "'{{file}}' wurde gelöscht.",
+    "successfullyCreated": "'{{directoryName}}' wurde erstellt."
+  }
+}

+ 22 - 0
translations/en.json

@@ -0,0 +1,22 @@
+{
+  "translation": {
+    "pangram": "The quick brown fox jumps over the lazy dog.",
+    "alphabet": "Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz",
+    "dropFilesAnywhereToUpload": "Drop files anywhere to upload",
+    "uploadFiles": "Upload files",
+    "or": "or",
+    "createNewDirectory": "Create new directory",
+    "directoryName": "Directory name",
+    "delete": "Delete",
+    "rename": "Rename",
+    "download": "Download",
+    "deleteConfirmation": "Are you sure you want to delete '{{file}}'?",
+    "overwriteFileConfimation": "A file called '{{file}}' already exists, would you like to overwrite it?",
+    "failure": "{{method}} {{url}} failed: {{statusText}} ({{status}})",
+    "successfullyUploaded": "'{{file}}' has been successfully uploaded.",
+    "successfullyRenamed": "'{{from}}' successfully renamed to '{{to}}'.",
+    "successfullyMoved": "'{{from}}' successfully moved to '{{to}}'.",
+    "successfullyDeleted": "'{{file}}' has been deleted.",
+    "successfullyCreated": "'{{directoryName}}' has been created."
+  }
+}

+ 21 - 0
translations/pt.json

@@ -0,0 +1,21 @@
+{
+  "translation": {
+    "pangram": "Luís argüia à Júlia que «brações, fé, chá, óxido, pôr, zângão» eram palavras do português.",
+    "alphabet": "Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz Áá Ââ Ãã Àà Çç Éé Êê Íí Óó Ôô Õõ Úú",
+    "dropFilesAnywhereToUpload": "Mova o(s) arquivo(s) interno(s) para carregá-los",
+    "uploadFiles": "Carregar arquivo(s)",
+    "or": "ou",
+    "createNewDirectory": "Criar uma nova pasta",
+    "delete": "Eliminar",
+    "rename": "Renomear",
+    "download": "Descarregar",
+    "deleteConfirmation": "Quer mesmo apagar o arquivo \"{{file}}\"?",
+    "overwriteFileConfimation": "O arquivo \"{{file}}\" já existe, você quer sobrescrevê-lo?",
+    "failure": "{{method}} {{url}} falhou: {{statusText}} ({{status}})",
+    "successfullyUploaded": "\"{{file}}\" foi carregada com êxito.",
+    "successfullyRenamed": "\"{{from}}\" foi alterado para \"{{to}}\" com êxito.",
+    "successfullyMoved": "\"{{from}}\" foi transferida com êxito para \"{{to}}\".",
+    "successfullyDeleted": "\"{{file}}\" foi suprimido.",
+    "successfullyCreated": "\"{{directoryName}}\" foi criada."
+  }
+}

+ 1 - 0
tsconfig.json

@@ -3,6 +3,7 @@
     "esModuleInterop": true,
     "esModuleInterop": true,
     "lib": ["dom", "dom.iterable", "es2019"],
     "lib": ["dom", "dom.iterable", "es2019"],
     "moduleResolution": "node",
     "moduleResolution": "node",
+    "resolveJsonModule": true,
     "target": "es6"
     "target": "es6"
   }
   }
 }
 }

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff