Kaynağa Gözat

Merge branch 'main' into #10

vineeth kumar 1 yıl önce
ebeveyn
işleme
a2fedfcd97
48 değiştirilmiş dosya ile 1561 ekleme ve 344 silme
  1. 79 0
      .github/workflows/docker-image.yaml
  2. 4 5
      dev-server.js
  3. 17 0
      incubator/x86emu/README.md
  4. 92 1
      src/IPC.js
  5. 0 0
      src/UI/PuterDialog.js
  6. 62 38
      src/UI/UIContextMenu.js
  7. 22 22
      src/UI/UIDesktop.js
  8. 32 33
      src/UI/UIItem.js
  9. 4 4
      src/UI/UIPrompt.js
  10. 4 4
      src/UI/UITaskbar.js
  11. 6 6
      src/UI/UITaskbarItem.js
  12. 23 32
      src/UI/UIWindow.js
  13. 6 6
      src/UI/UIWindowChangePassword.js
  14. 5 5
      src/UI/UIWindowChangeUsername.js
  15. 3 3
      src/UI/UIWindowClaimReferral.js
  16. 2 2
      src/UI/UIWindowColorPicker.js
  17. 5 5
      src/UI/UIWindowConfirmDownload.js
  18. 2 2
      src/UI/UIWindowCopyProgress.js
  19. 14 14
      src/UI/UIWindowDesktopBGSettings.js
  20. 1 1
      src/UI/UIWindowDownloadDirProg.js
  21. 1 1
      src/UI/UIWindowDownloadProgress.js
  22. 2 2
      src/UI/UIWindowEmailConfirmationRequired.js
  23. 4 4
      src/UI/UIWindowFeedback.js
  24. 1 1
      src/UI/UIWindowFontPicker.js
  25. 5 1
      src/UI/UIWindowGetCopyLink.js
  26. 3 3
      src/UI/UIWindowItemProperties.js
  27. 5 5
      src/UI/UIWindowLogin.js
  28. 1 1
      src/UI/UIWindowMoveProgress.js
  29. 7 6
      src/UI/UIWindowMyWebsites.js
  30. 1 1
      src/UI/UIWindowNewFolderProgress.js
  31. 3 3
      src/UI/UIWindowNewPassword.js
  32. 2 2
      src/UI/UIWindowProgressEmptyTrash.js
  33. 3 3
      src/UI/UIWindowPublishWebsite.js
  34. 1 1
      src/UI/UIWindowQR.js
  35. 3 3
      src/UI/UIWindowRecoverPassword.js
  36. 4 4
      src/UI/UIWindowRefer.js
  37. 8 8
      src/UI/UIWindowSaveAccount.js
  38. 3 3
      src/UI/UIWindowSessionList.js
  39. 7 7
      src/UI/UIWindowSignup.js
  40. 3 3
      src/UI/UIWindowUploadProgress.js
  41. 0 6
      src/globals.js
  42. 8 93
      src/helpers.js
  43. 43 0
      src/helpers/determine_active_container_parent.js
  44. 78 0
      src/helpers/new_context_menu_item.js
  45. 617 0
      src/i18n/i18n.js
  46. 40 0
      src/initgui.js
  47. 323 0
      src/lib/jquery.menu-aim.js
  48. 2 0
      src/static-assets.js

+ 79 - 0
.github/workflows/docker-image.yaml

@@ -0,0 +1,79 @@
+#
+name: Docker Image CI
+
+# Configures this workflow to run every time a change is pushed to the
+# branch called `main`.
+on:
+  push:
+    branches: ['main']
+  
+
+# Defines two custom environment variables for the workflow. These are used
+# for the Container registry domain, and a name for the Docker image that
+# this workflow builds.
+env:
+  REGISTRY: ghcr.io
+  IMAGE_NAME: ${{ github.repository }}
+
+# There is a single job in this workflow. It's configured to run on the
+# latest available version of Ubuntu.
+jobs:
+  build-and-push-image:
+    runs-on: ubuntu-latest
+
+    # Sets the permissions granted to the `GITHUB_TOKEN` for the actions
+    # in this job.
+    permissions:
+      contents: read
+      packages: write
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      # Uses the `docker/login-action` action to log in to the Container
+      # registry using the account and password that will publish the packages.
+      # Once published, the packages are scoped to the account defined here.
+      - name: Log in to GitHub Package Container registry
+        uses: docker/login-action@v3
+        with:
+          registry: ${{ env.REGISTRY }}
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about)
+      # to extract tags and labels that will be applied to the specified image.
+      # The `id` "meta" allows the output of this step to be referenced in
+      # a subsequent step. The `images` value provides the base name for the
+      # tags and labels.
+      - name: Extract metadata (tags, labels) for Docker
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+          tags: |
+            type=schedule
+            type=ref,event=branch
+            type=ref,event=pr
+            type=semver,pattern={{version}}
+            type=semver,pattern={{major}}.{{minor}}
+            type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
+            type=sha
+            type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
+
+      # This step uses the `docker/build-push-action` action to build the
+      # image, based on your repository's `Dockerfile`. If the build succeeds,
+      # it pushes the image to GitHub Packages.
+      # It uses the `context` parameter to define the build's context as the
+      # set of files located in the specified path. For more information, see
+      # "[Usage](https://github.com/docker/build-push-action#usage)" in the
+      # README of the `docker/build-push-action` repository.
+      # It uses the `tags` and `labels` parameters to tag and label the image
+      # with the output from the "meta" step.
+      - name: Build and push Docker image
+        uses: docker/build-push-action@v5
+        with:
+          context: .
+          push: true
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}

+ 4 - 5
dev-server.js

@@ -10,15 +10,14 @@ let port = process.env.PORT ?? 4000; // Starting port
 const maxAttempts = 10; // Maximum number of ports to try
 const env = argv[2] ?? "dev";
 
-const startServer = (attempt) => {
+const startServer = (attempt, useAnyFreePort = false) => {
     if (attempt > maxAttempts) {
-        console.error(chalk.red(`ERROR: Unable to find an available port after ${maxAttempts} attempts.`));
-        return;
+        useAnyFreePort = true; // Use any port that is free
     }
 
-    app.listen(port, () => {
+    const server = app.listen(useAnyFreePort ? 0 : port, () => {
         console.log("\n-----------------------------------------------------------\n");
-        console.log(`Puter is now live at: `, chalk.underline.blue(`http://localhost:${port}`));
+        console.log(`Puter is now live at: `, chalk.underline.blue(`http://localhost:${server.address().port}`));
         console.log("\n-----------------------------------------------------------\n");
     }).on('error', (err) => {
         if (err.code === 'EADDRINUSE') { // Check if the error is because the port is already in use

+ 17 - 0
incubator/x86emu/README.md

@@ -0,0 +1,17 @@
+# Research + Planning for x86 Emulation in Puter
+
+## Resources
+- [copy.sh/v86 docs](https://github.com/copy/v86/blob/master/docs)
+- [greenfield github](https://github.com/udevbe/greenfield)
+
+## TODO
+
+### Documents to Write
+
+- [ ] specification for Puter network driver
+- [ ] specification for Puter network relay
+
+### Things to Try
+
+- [ ] greenfield/wayland/arch/v86
+- [ ] puter-fuse in v86

+ 92 - 1
src/IPC.js

@@ -333,7 +333,6 @@ window.addEventListener('message', async (event) => {
             initiating_app_uuid: app_uuid,
         });
     }
-
     //--------------------------------------------------------
     // setWindowTitle
     //--------------------------------------------------------
@@ -347,6 +346,98 @@ window.addEventListener('message', async (event) => {
         }, '*');
     }
     //--------------------------------------------------------
+    // setWindowWidth
+    //--------------------------------------------------------
+    else if(event.data.msg === 'setWindowWidth' && event.data.width !== undefined){
+        event.data.width = parseFloat(event.data.width);
+        // must be at least 200
+        if(event.data.width < 200)
+            event.data.width = 200;
+        // set window width
+        $($el_parent_window).css('width', event.data.width);
+        // send confirmation to requester window
+        target_iframe.contentWindow.postMessage({
+            original_msg_id: msg_id, 
+        }, '*');
+    }
+    //--------------------------------------------------------
+    // setWindowHeight
+    //--------------------------------------------------------
+    else if(event.data.msg === 'setWindowHeight' && event.data.height !== undefined){
+        event.data.height = parseFloat(event.data.height);
+        // must be at least 200
+        if(event.data.height < 200)
+            event.data.height = 200;
+
+        // convert to number and set
+        $($el_parent_window).css('height', event.data.height);
+
+        // send confirmation to requester window
+        target_iframe.contentWindow.postMessage({
+            original_msg_id: msg_id, 
+        }, '*');
+    }
+    //--------------------------------------------------------
+    // setWindowSize
+    //--------------------------------------------------------
+    else if(event.data.msg === 'setWindowSize' && (event.data.width !== undefined || event.data.height !== undefined)){
+        // convert to number and set
+        if(event.data.width !== undefined){
+            event.data.width = parseFloat(event.data.width);
+            // must be at least 200
+            if(event.data.width < 200)
+                event.data.width = 200;
+            $($el_parent_window).css('width', event.data.width);
+        }
+        
+        if(event.data.height !== undefined){
+            event.data.height = parseFloat(event.data.height);
+            // must be at least 200
+            if(event.data.height < 200)
+                event.data.height = 200;
+            $($el_parent_window).css('height', event.data.height);
+        }
+
+        // send confirmation to requester window
+        target_iframe.contentWindow.postMessage({
+            original_msg_id: msg_id, 
+        }, '*');
+    }
+    //--------------------------------------------------------
+    // setWindowPosition
+    //--------------------------------------------------------
+    else if(event.data.msg === 'setWindowPosition' && (event.data.x !== undefined || event.data.y !== undefined)){
+        // convert to number and set
+        if(event.data.x !== undefined){
+            event.data.x = parseFloat(event.data.x);
+            // we don't want the window to go off the left edge of the screen
+            if(event.data.x < 0)
+                event.data.x = 0;
+            // we don't want the window to go off the right edge of the screen
+            if(event.data.x > window.innerWidth - 100)
+                event.data.x = window.innerWidth - 100;
+            // set window left
+            $($el_parent_window).css('left', parseFloat(event.data.x));
+        }
+
+        if(event.data.y !== undefined){
+            event.data.y = parseFloat(event.data.y);
+            // we don't want the window to go off the top edge of the screen
+            if(event.data.y < window.taskbar_height)
+                event.data.y = window.taskbar_height;
+            // we don't want the window to go off the bottom edge of the screen
+            if(event.data.y > window.innerHeight - 100)
+                event.data.y = window.innerHeight - 100;
+            // set window top
+            $($el_parent_window).css('top', parseFloat(event.data.y));
+        }
+
+        // send confirmation to requester window
+        target_iframe.contentWindow.postMessage({
+            original_msg_id: msg_id, 
+        }, '*');
+    }
+    //--------------------------------------------------------
     // watchItem
     //--------------------------------------------------------
     else if(event.data.msg === 'watchItem' && event.data.item_uid !== undefined){

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
src/UI/PuterDialog.js


+ 62 - 38
src/UI/UIContextMenu.js

@@ -145,48 +145,70 @@ function UIContextMenu(options){
                 $(contextMenu).remove();
             });
         }
-
         return false;
     });
 
-    // when mouse is over an item    
-    $(contextMenu).find('.context-menu-item').on('mouseover', function (e) {
-        // mark other items as inactive
-        $(contextMenu).find('.context-menu-item').removeClass('context-menu-item-active');
-        // mark this item as active
-        $(this).addClass('context-menu-item-active');
-        // close any submenu that doesn't belong to this item
-        $(`.context-menu[data-parent-id="${menu_id}"]`).remove();
-        // mark this context menu as active
-        $(contextMenu).addClass('context-menu-active');
-    })
-
-    // open submenu if applicable
-    $(`#context-menu-${menu_id} > li.context-menu-item-submenu`).on('mouseover', function (e) {
-        
-        // open submenu only if it's not already open
-        if($(`.context-menu[data-id="${menu_id}-${$(this).attr('data-action')}"]`).length === 0){
-            let item_rect_box = this.getBoundingClientRect();
-            
-            // close other submenus
-            $(`.context-menu[parent-element-id="${menu_id}"]`).remove();
-
-            // open the new submenu
-            UIContextMenu({ 
-                items: options.items[parseInt($(this).attr('data-action'))].items,
-                parent_id: menu_id,
-                is_submenu: true,
-                id: menu_id + '-' + $(this).attr('data-action'),
-                position:{
-                    top: item_rect_box.top - 5,
-                    left: x_pos + item_rect_box.width + 15,
-                } 
-            })
+    // initialize menuAim plugin (../libs/jquery.menu-aim.js)
+    $(contextMenu).menuAim({
+        submenuDirection: function(){
+            //if not submenu
+            if(!options.is_submenu){
+                // if submenu left postiton is greater than main menu left position
+                if($(contextMenu).offset().left + 2 * $(contextMenu).width() + 15 < window.innerWidth ){     
+                    return "right";
+                } else {
+                    return "left";
+                }
+            }
+        },
+        //activates item when mouse enters depending in mouse position and direction
+        activate: function (e) {
+
+            //activate items
+            let item = $(e).closest('.context-menu-item');
+
+            // mark other items as inactive
+            $(contextMenu).find('.context-menu-item').removeClass('context-menu-item-active');
+            // mark this item as active
+            $(item).addClass('context-menu-item-active');
+            // close any submenu that doesn't belong to this item
+            $(`.context-menu[data-parent-id="${menu_id}"]`).remove();
+            // mark this context menu as active
+            $(contextMenu).addClass('context-menu-active');
+
+
+            // activate submenu
+            // open submenu if applicable
+            if($(e).hasClass('context-menu-item-submenu')){
+                let item_rect_box = e.getBoundingClientRect();
+                // open submenu only if it's not already open
+                if($(`.context-menu[data-id="${menu_id}-${$(e).attr('data-action')}"]`).length === 0){
+                    // close other submenus
+                    $(`.context-menu[parent-element-id="${menu_id}"]`).remove();
+                    // open the new submenu
+                    UIContextMenu({ 
+                        items: options.items[parseInt($(e).attr('data-action'))].items,
+                        parent_id: menu_id,
+                        is_submenu: true,
+                        id: menu_id + '-' + $(e).attr('data-action'),
+                        position:{
+                            top: item_rect_box.top - 5,
+                            left: x_pos + item_rect_box.width + 15,
+                        } 
+                    })
+                }
+            }
+        },
+        //deactivates row when mouse leavess
+        deactivate: function (e) {
+            //deactivate submenu
+            if($(e).hasClass('context-menu-item-submenu')){
+                $(`.context-menu[data-id="${menu_id}-${$(e).attr('data-action')}"]`).remove();
+            }
         }
-        return false;    
     });
-
-    // useful in cases such as where a menue item is over a window, this prevents from the mousedown event
+    
+    // useful in cases such as where a menu item is over a window, this prevents from the mousedown event
     // reaching the window underneath
     $(`#context-menu-${menu_id} > li:not(.context-menu-item-disabled)`).on('mousedown', function (e) {
         e.preventDefault();
@@ -227,4 +249,6 @@ window.select_ctxmenu_item = function ($ctxmenu_item){
     $($ctxmenu_item).addClass('context-menu-item-active');
 }
 
-export default UIContextMenu;
+export default UIContextMenu;
+
+

+ 22 - 22
src/UI/UIDesktop.js

@@ -33,6 +33,7 @@ import UIWindowLogin from "./UIWindowLogin.js"
 import UIWindowQR from "./UIWindowQR.js"
 import UIWindowRefer from "./UIWindowRefer.js"
 import UITaskbar from "./UITaskbar.js"
+import new_context_menu_item from "../helpers/new_context_menu_item.js"
 
 async function UIDesktop(options){
     let h = '';
@@ -621,7 +622,7 @@ async function UIDesktop(options){
                     // Sort by
                     // -------------------------------------------
                     {
-                        html: "Sort by",
+                        html: i18n('sort_by'),
                         items: [
                             {
                                 html: `Auto Arrange`,
@@ -645,6 +646,7 @@ async function UIDesktop(options){
                             {
                                 html: `Name`,
                                 disabled: !is_auto_arrange_enabled,
+                                html: i18n('name'),
                                 icon: $(el_desktop).attr('data-sort_by') === 'name' ? '✓' : '',
                                 onClick: async function(){
                                     sort_items(el_desktop, 'name', $(el_desktop).attr('data-sort_order'));
@@ -654,6 +656,7 @@ async function UIDesktop(options){
                             {
                                 html: `Date modified`,
                                 disabled: !is_auto_arrange_enabled,
+                                html: i18n('date_modified'),
                                 icon: $(el_desktop).attr('data-sort_by') === 'modified' ? '✓' : '',
                                 onClick: async function(){
                                     sort_items(el_desktop, 'modified', $(el_desktop).attr('data-sort_order'));
@@ -663,6 +666,7 @@ async function UIDesktop(options){
                             {
                                 html: `Type`,
                                 disabled: !is_auto_arrange_enabled,
+                                html: i18n('type'),
                                 icon: $(el_desktop).attr('data-sort_by') === 'type' ? '✓' : '',
                                 onClick: async function(){
                                     sort_items(el_desktop, 'type', $(el_desktop).attr('data-sort_order'));
@@ -672,6 +676,7 @@ async function UIDesktop(options){
                             {
                                 html: `Size`,
                                 disabled: !is_auto_arrange_enabled,
+                                html: i18n('size'),
                                 icon: $(el_desktop).attr('data-sort_by') === 'size' ? '✓' : '',
                                 onClick: async function(){
                                     sort_items(el_desktop, 'size', $(el_desktop).attr('data-sort_order'));
@@ -685,6 +690,7 @@ async function UIDesktop(options){
                             {
                                 html: `Ascending`,
                                 disabled: !is_auto_arrange_enabled,
+                                html: i18n('ascending'),
                                 icon: $(el_desktop).attr('data-sort_order') === 'asc' ? '✓' : '',
                                 onClick: async function(){
                                     const sort_by = $(el_desktop).attr('data-sort_by')
@@ -695,6 +701,7 @@ async function UIDesktop(options){
                             {
                                 html: `Descending`,
                                 disabled: !is_auto_arrange_enabled,
+                                html: i18n('descending'),
                                 icon: $(el_desktop).attr('data-sort_order') === 'desc' ? '✓' : '',
                                 onClick: async function(){
                                     const sort_by = $(el_desktop).attr('data-sort_by')
@@ -708,7 +715,7 @@ async function UIDesktop(options){
                     // Refresh
                     // -------------------------------------------
                     {
-                        html: "Refresh",
+                        html: i18n('refresh'),
                         onClick: function(){
                             refresh_item_container(el_desktop);
                         }
@@ -717,7 +724,8 @@ async function UIDesktop(options){
                     // Show/Hide hidden files
                     // -------------------------------------------
                     {
-                        html: `${window.user_preferences.show_hidden_files ? 'Hide' : 'Show'} hidden files`,
+                        html: i18n('show_hidden'),
+                        icon: window.user_preferences.show_hidden_files ? '✓' : '',
                         onClick: function(){
                             window.mutate_user_preferences({
                                 show_hidden_files : !window.user_preferences.show_hidden_files,
@@ -732,7 +740,7 @@ async function UIDesktop(options){
                     // -------------------------------------------
                     // New File
                     // -------------------------------------------
-                    window.new_context_menu_item(desktop_path, el_desktop),
+                    new_context_menu_item(desktop_path, el_desktop),
                     // -------------------------------------------
                     // -
                     // -------------------------------------------
@@ -741,7 +749,7 @@ async function UIDesktop(options){
                     // Paste
                     // -------------------------------------------
                     {
-                        html: "Paste",
+                        html: i18n('paste'),
                         disabled: clipboard.length > 0 ? false : true,
                         onClick: function(){
                             if(clipboard_op === 'copy')
@@ -754,7 +762,7 @@ async function UIDesktop(options){
                     // Undo
                     // -------------------------------------------
                     {
-                        html: "Undo",
+                        html: i18n('undo'),
                         disabled: actions_history.length > 0 ? false : true,
                         onClick: function(){
                             undo_last_action();
@@ -764,21 +772,12 @@ async function UIDesktop(options){
                     // Upload Here
                     // -------------------------------------------
                     {
-                        html: "Upload Here",
+                        html: i18n('upload_here'),
                         onClick: function(){
                             init_upload_using_dialog(el_desktop);
                         }
                     },
                     // -------------------------------------------
-                    // Request Files
-                    // -------------------------------------------
-                    // {
-                    //     html: "Request Files",
-                    //     onClick: function(){
-                    //         UIWindowRequestFiles({dir_path: desktop_path})                       
-                    //     }
-                    // },
-                    // -------------------------------------------
                     // -
                     // -------------------------------------------
                     '-',
@@ -786,7 +785,7 @@ async function UIDesktop(options){
                     // Change Desktop Background…
                     // -------------------------------------------
                     {
-                        html: "Change Desktop Background…",
+                        html: i18n('change_desktop_background'),
                         onClick: function(){
                             UIWindowDesktopBGSettings();
                         }
@@ -955,6 +954,7 @@ async function UIDesktop(options){
                 name: app_launched_from_url,
                 readURL: qparams.get('readURL'),
                 maximized: qparams.get('maximized'),
+                params: app_query_params ?? [],
                 is_fullpage: window.is_fullpage_mode,
                 window_options: {
                     stay_on_top: false,
@@ -1159,7 +1159,7 @@ $(document).on('click', '.user-options-menu-btn', async function(e){
             // My Websites
             //--------------------------------------------------
             {
-                html: "My Websites",
+                html: i18n('my_websites'),
                 onClick: async function(){
                     UIWindowMyWebsites();
                 }
@@ -1168,7 +1168,7 @@ $(document).on('click', '.user-options-menu-btn', async function(e){
             // Change Username
             //--------------------------------------------------
             {
-                html: "Change Username",
+                html: i18n('change_username'),
                 onClick: async function(){
                     UIWindowChangeUsername();
                 }
@@ -1178,7 +1178,7 @@ $(document).on('click', '.user-options-menu-btn', async function(e){
             // Change Password
             //--------------------------------------------------
             {
-                html: "Change Password",
+                html: i18n('change_password'),
                 onClick: async function(){
                     UIWindowChangePassword();
                 }
@@ -1187,7 +1187,7 @@ $(document).on('click', '.user-options-menu-btn', async function(e){
             // Contact Us
             //--------------------------------------------------
             {
-                html: "Contact Us",
+                html: i18n('contact_us'),
                 onClick: async function(){
                     UIWindowFeedback();
                 }
@@ -1201,7 +1201,7 @@ $(document).on('click', '.user-options-menu-btn', async function(e){
             // Log Out
             //--------------------------------------------------
             {
-                html: "Log Out",
+                html: i18n('log_out'),
                 onClick: async function(){
                     // see if there are any open windows, if yes notify user
                     if($('.window-app').length > 0){

+ 32 - 33
src/UI/UIItem.js

@@ -760,7 +760,7 @@ function UIItem(options){
             // -------------------------------------------
             if(are_trashed){
                 menu_items.push({
-                    html: "Restore",
+                    html: i18n('restore'),
                     onClick: function(){
                         $selected_items.each(function() {
                             const ell = this;
@@ -779,7 +779,7 @@ function UIItem(options){
                 // Donwload
                 // -------------------------------------------
                 menu_items.push({
-                    html: 'Download',
+                    html: i18n('Download'),
                     onClick: async function(){
                         let items = [];
                         for (let index = 0; index < $selected_items.length; index++) {
@@ -793,7 +793,7 @@ function UIItem(options){
                 // Zip
                 // -------------------------------------------
                 menu_items.push({
-                    html: 'Zip',
+                    html: i18n('zip'),
                     onClick: async function(){
                         let items = [];
                         for (let index = 0; index < $selected_items.length; index++) {
@@ -812,7 +812,7 @@ function UIItem(options){
             // Cut
             // -------------------------------------------
             menu_items.push({
-                html: "Cut",
+                html: i18n('cut'),
                 onClick: function(){
                     window.clipboard_op= 'move';
                     window.clipboard = [];
@@ -828,7 +828,7 @@ function UIItem(options){
             // -------------------------------------------
             if(!are_trashed){
                 menu_items.push({
-                    html: "Copy",
+                    html: i18n('copy'),
                     onClick: function(){
                         window.clipboard_op= 'copy';
                         window.clipboard = [];
@@ -848,7 +848,7 @@ function UIItem(options){
             // -------------------------------------------
             if(are_trashed){
                 menu_items.push({
-                    html: 'Delete Permanently',
+                    html: i18n('delete_permanently'),
                     onClick: async function(){
                         const alert_resp = await UIAlert({
                             message: `Are you sure you want to permanently delete these items?`,
@@ -887,7 +887,7 @@ function UIItem(options){
             // -------------------------------------------
             if(!are_trashed && window.feature_flags.create_shortcut){
                 menu_items.push({
-                    html: 'Create Shortcut',
+                    html: i18n('create_shortcut'),
                     onClick: async function(){
                         $selected_items.each(function() {
                             let base_dir = path.dirname($(this).attr('data-path'));
@@ -913,7 +913,7 @@ function UIItem(options){
             // -------------------------------------------
             if(!are_trashed){
                 menu_items.push({
-                    html: 'Delete',
+                    html: i18n('delete'),
                     onClick: async function(){
                         move_items($selected_items, trash_path);
                     }
@@ -933,7 +933,7 @@ function UIItem(options){
             // -------------------------------------------
             if(!is_trashed){
                 menu_items.push({
-                    html: 'Open',
+                    html: i18n('open'),
                     onClick: function(){
                         open_item({item: el_item});
                     }
@@ -989,7 +989,7 @@ function UIItem(options){
                 }
                 // add all suitable apps
                 menu_items.push({
-                    html: 'Open With',
+                    html: i18n('open_with'),
                     items: items,
                 });
 
@@ -1005,7 +1005,7 @@ function UIItem(options){
             // -------------------------------------------
             if($(el_item).closest('.window-body').length > 0 && options.is_dir){
                 menu_items.push({
-                    html: 'Open in New Window',
+                    html: i18n('open_in_new_window'),
                     onClick: function(){
                         if(options.is_dir){
                             open_item({item: el_item, new_window: true})
@@ -1024,7 +1024,7 @@ function UIItem(options){
             // -------------------------------------------
             if(!is_trashed && !is_trash && options.is_dir){
                 menu_items.push({
-                    html: 'Publish As Website',
+                    html: i18n('publish_as_website'),
                     disabled: !options.is_dir,
                     onClick: async function () {
                         if(window.require_email_verification_to_publish_website){
@@ -1051,7 +1051,7 @@ function UIItem(options){
             // -------------------------------------------
             if(!is_trashed && !is_trash && options.is_dir){
                 menu_items.push({
-                    html: 'Deploy As App',
+                    html: i18n('deploy_as_app'),
                     disabled: !options.is_dir,
                     onClick: async function () {
                         launch_app({
@@ -1073,19 +1073,18 @@ function UIItem(options){
             // -------------------------------------------
             if(is_trash){
                 menu_items.push({
-                    html: 'Empty Trash',
+                    html: i18n('empty_trash'),
                     onClick: async function(){
                         empty_trash();
                     }
                 });
-
             }
             // -------------------------------------------
             // Donwload
             // -------------------------------------------
             if(!is_trash && !is_trashed && (options.associated_app_name === null || options.associated_app_name === undefined)){
                 menu_items.push({
-                    html: 'Download',
+                    html: i18n('Download'),
                     disabled: options.is_dir && !window.feature_flags.download_directory,
                     onClick: async function(){
                         if(options.is_dir)
@@ -1102,11 +1101,11 @@ function UIItem(options){
             // -------------------------------------------
             if(!is_trashed && !is_trash && (options.associated_app_name === null || options.associated_app_name === undefined)){
                 menu_items.push({
-                    html: 'Get Copy Link',
+                    html: i18n('get_copy_link'),
                     onClick: async function(){
                         if(window.user.is_temp && 
                             !await UIWindowSaveAccount({
-                                message: 'Please create an account to proceed.',
+                                message: i18n('save_account_to_get_copy_link'),
                                 send_confirmation_code: true,
                                 window_options: {
                                     backdrop: true,
@@ -1131,7 +1130,7 @@ function UIItem(options){
             // -------------------------------------------
             if(!is_trash && !is_trashed && !$(el_item).attr('data-path').endsWith('.zip')){
                 menu_items.push({
-                    html: "Zip",
+                    html: i18n('zip'),
                     onClick: function(){
                         zipItems(el_item, path.dirname($(el_item).attr('data-path')), false);
                     }
@@ -1142,7 +1141,7 @@ function UIItem(options){
             // -------------------------------------------
             if(!is_trash && !is_trashed && $(el_item).attr('data-path').endsWith('.zip')){
                 menu_items.push({
-                    html: "Unzip",
+                    html: i18n('unzip'),
                     onClick: async function(){
                         const zip = new JSZip();
                         let filPath = $(el_item).attr('data-path');
@@ -1170,7 +1169,7 @@ function UIItem(options){
             // -------------------------------------------
             if(is_trashed){
                 menu_items.push({
-                    html: 'Restore',
+                    html: i18n('restore'),
                     onClick: async function(){
                         let metadata = $(el_item).attr('data-metadata') === '' ? {} : JSON.parse($(el_item).attr('data-metadata'))
                         move_items([el_item], path.dirname(metadata.original_path));
@@ -1187,7 +1186,7 @@ function UIItem(options){
             // -------------------------------------------
             if($(el_item).attr('data-immutable') === '0'){
                 menu_items.push({
-                    html: "Cut",
+                    html: i18n('cut'),
                     onClick: function(){
                         window.clipboard_op= 'move';
                         window.clipboard= [options.path];
@@ -1199,7 +1198,7 @@ function UIItem(options){
             // -------------------------------------------
             if(!is_trashed && !is_trash){
                 menu_items.push({
-                    html: "Copy",
+                    html: i18n('copy'),
                     onClick: function(){
                         window.clipboard_op= 'copy';
                         window.clipboard= [{path: options.path}];
@@ -1211,7 +1210,7 @@ function UIItem(options){
             // -------------------------------------------
             if($(el_item).attr('data-is_dir') === '1' && !is_trashed && !is_trash){
                 menu_items.push({
-                    html: "Paste Into Folder",
+                    html: i18n('paste_into_folder'),
                     disabled: clipboard.length > 0 ? false : true,
                     onClick: function(){
                         if(clipboard_op === 'copy')
@@ -1232,7 +1231,7 @@ function UIItem(options){
             // -------------------------------------------
             if(!is_trashed && window.feature_flags.create_shortcut){
                 menu_items.push({
-                    html: 'Create Shortcut',
+                    html: i18n('create_shortcut'),
                     onClick: async function(){
                         let base_dir = path.dirname($(el_item).attr('data-path'));
                         // Trash on Desktop is a special case
@@ -1256,7 +1255,7 @@ function UIItem(options){
             // -------------------------------------------
             if($(el_item).attr('data-immutable') === '0' && !is_trashed){
                 menu_items.push({
-                    html: 'Delete',
+                    html: i18n('delete'),
                     onClick: async function(){
                         move_items([el_item], trash_path);
                     }
@@ -1267,7 +1266,7 @@ function UIItem(options){
             // -------------------------------------------
             if(is_trashed){
                 menu_items.push({
-                    html: 'Delete Permanently',
+                    html: i18n('delete_permanently'),
                     onClick: async function(){
                         const alert_resp = await UIAlert({
                             message: `Are you sure you want to permanently delete this item?`,
@@ -1304,7 +1303,7 @@ function UIItem(options){
             // -------------------------------------------
             if($(el_item).attr('data-immutable') === '0' && !is_trashed && !is_trash){
                 menu_items.push({
-                    html: "Rename",
+                    html: i18n('rename'),
                     onClick: function(){
                         activate_item_name_editor(el_item)
                     }
@@ -1318,7 +1317,7 @@ function UIItem(options){
             // Properties
             // -------------------------------------------
             menu_items.push({
-                html: "Properties",
+                html: i18n('properties'),
                 onClick: function(){
                     let window_height = 500;
                     let window_width = 450;
@@ -1411,8 +1410,8 @@ $(document).on('contextmenu', '.item-has-website-url-badge', async function(e){
         items: [
             // Open
             {
-                html: `Open in New Tab <img src="${window.icons['launch.svg']}" style="width:10px; height:10px; margin-left: 5px;">` ,
-                html_active: `Open in New Tab <img src="${window.icons['launch-white.svg']}" style="width:10px; height:10px; margin-left: 5px;">` ,
+                html: `${i18n('open_in_new_tab')} <img src="${window.icons['launch.svg']}" style="width:10px; height:10px; margin-left: 5px;">` ,
+                html_active: `${i18n('open_in_new_tab')} <img src="${window.icons['launch-white.svg']}" style="width:10px; height:10px; margin-left: 5px;">` ,
                 onClick: function(){
                     const website_url = $(e.target).closest('.item').attr('data-website_url');
                     if(website_url){
@@ -1422,7 +1421,7 @@ $(document).on('contextmenu', '.item-has-website-url-badge', async function(e){
             },
             // Copy Link
             {
-                html: 'Copy Link',
+                html: i18n('copy_link'),
                 onClick: async function(){
                     const website_url = $(e.target).closest('.item').attr('data-website_url');
                     if(website_url){
@@ -1523,7 +1522,7 @@ window.activate_item_name_editor= function(el_item){
     }
     // files in trash cannot be renamed, user should be notified with an Alert.
     else if(path.dirname($(el_item).attr('data-path')) === window.trash_path){
-        UIAlert(`This item can't be renamed because it's in the trash. To rename this item, first drag it out of the Trash.`)
+        UIAlert(i18n('items_in_trash_cannot_be_renamed'));
         return;
     }
 

+ 4 - 4
src/UI/UIPrompt.js

@@ -37,8 +37,8 @@ function UIPrompt(options){
         // provide an 'OK' button if no buttons are provided
         if(!options.buttons || options.buttons.length === 0){
             options.buttons = [
-                {label: 'Cancel', value: false, type: 'default'},
-                {label: 'OK', value: true, type: 'primary'},
+                {label: i18n('Cancel'), value: false, type: 'default'},
+                {label: i18n('OK'), value: true, type: 'primary'},
             ]
         }
 
@@ -52,8 +52,8 @@ function UIPrompt(options){
         // buttons
         if(options.buttons && options.buttons.length > 0){
             h += `<div style="overflow:hidden; margin-top:20px; float:right;">`;
-                h += `<button class="button button-default prompt-resp-button prompt-resp-btn-cancel" data-label="Cancel" style="padding: 0 20px;">Cancel</button>`;
-                h += `<button class="button button-primary prompt-resp-button prompt-resp-btn-ok" data-label="OK" data-value="true" autofocus>OK</button>`;
+                h += `<button class="button button-default prompt-resp-button prompt-resp-btn-cancel" data-label="${i18n('Cancel')}" style="padding: 0 20px;">${i18n('Cancel')}</button>`;
+                h += `<button class="button button-primary prompt-resp-button prompt-resp-btn-ok" data-label="${i18n('OK')}" data-value="true" autofocus>${i18n('OK')}</button>`;
             h += `</div>`;
         }
 

+ 4 - 4
src/UI/UITaskbar.js

@@ -50,7 +50,7 @@ async function UITaskbar(options){
     //---------------------------------------------
     UITaskbarItem({
         icon: window.icons['start.svg'],
-        name: 'Start',
+        name: i18n('start'),
         sortable: false,
         keep_in_taskbar: true,
         disable_context_menu: true,
@@ -95,7 +95,7 @@ async function UITaskbar(options){
             // -------------------------------------------
             if(launch_apps.recent.length > 0){
                 // heading
-                apps_str += `<h1 class="start-section-heading start-section-heading-recent">Recent</h1>`;
+                apps_str += `<h1 class="start-section-heading start-section-heading-recent">${i18n('recent')}</h1>`;
 
                 // apps
                 apps_str += `<div class="launch-apps-recent">`;
@@ -116,7 +116,7 @@ async function UITaskbar(options){
             if(launch_apps.recommended.length > 0){
                 // heading
                 apps_str += `<h1 class="start-section-heading start-section-heading-recommended" style="${launch_apps.recent.length > 0 ? 'padding-top: 30px;' : ''}">Recommended</h1>`;
-                
+
                 // apps
                 apps_str += `<div class="launch-apps-recommended">`;
                 for (let index = 0; index < launch_apps.recommended.length; index++) {
@@ -158,7 +158,7 @@ async function UITaskbar(options){
                 }
             });
         }
-    });            
+    });
 
     //---------------------------------------------
     // add `Explorer` to the taskbar

+ 6 - 6
src/UI/UITaskbarItem.js

@@ -137,7 +137,7 @@ function UITaskbarItem(options){
 
             // Empty Trash menu item
             menu_items.push({
-                html: 'Empty Trash',
+                html: i18n('empty_trash'),
                 val: $(this).attr('data-id'),
                 onClick: async function(){
                     empty_trash();
@@ -150,7 +150,7 @@ function UITaskbarItem(options){
         //------------------------------------------
         if(options.keep_in_taskbar && !options.lock_keep_in_taskbar){
             menu_items.push({
-                html: 'Remove from Taskbar',
+                html: i18n('remove_from_taskbar'),
                 val: $(this).attr('data-id'),
                 onClick: function(){
                     $(el_taskbar_item).attr('data-keep-in-taskbar', 'false');
@@ -167,7 +167,7 @@ function UITaskbarItem(options){
         //------------------------------------------
         else if(!options.keep_in_taskbar){
             menu_items.push({
-                html: 'Keep in Taskbar',
+                html: i18n('keep_in_taskbar'),
                 val: $(this).attr('data-id'),
                 onClick: function(){
                     $(el_taskbar_item).attr('data-keep-in-taskbar', 'true');
@@ -186,7 +186,7 @@ function UITaskbarItem(options){
             // Show All Windows
             // -------------------------------------------
             menu_items.push({
-                html: "Show All Windows",
+                html: i18n('show_all_windows'),
                 onClick: function(){
                     if(open_windows > 0)
                         $(el_taskbar_item).trigger('click');
@@ -196,7 +196,7 @@ function UITaskbarItem(options){
             // Hide All Windows
             // -------------------------------------------
             menu_items.push({
-                html: "Hide All Windows",
+                html: i18n('hide_all_windows'),
                 onClick: function(){
                     if(open_windows > 0)
                         $(`.window[data-app="${options.app}"]`).hideWindow();
@@ -206,7 +206,7 @@ function UITaskbarItem(options){
             // Close All Windows
             // -------------------------------------------
             menu_items.push({
-                html: "Close All Windows",
+                html: i18n('close_all_windows'),
                 onClick: function(){
                     $(`.window[data-app="${options.app}"]`).close();
                 }

+ 23 - 32
src/UI/UIWindow.js

@@ -24,6 +24,7 @@ import UITaskbarItem from './UITaskbarItem.js';
 import UIWindowLogin from './UIWindowLogin.js';
 import UIWindowPublishWebsite from './UIWindowPublishWebsite.js';
 import UIWindowItemProperties from './UIWindowItemProperties.js';
+import new_context_menu_item from '../helpers/new_context_menu_item.js';
 
 const el_body = document.getElementsByTagName('body')[0];
 
@@ -1811,10 +1812,10 @@ async function UIWindow(options) {
                         // Sort by
                         // -------------------------------------------
                         {
-                            html: "Sort by",
+                            html: i18n('sort_by'),
                             items: [
                                 {
-                                    html: `Name`,
+                                    html: i18n('name'),
                                     icon: $(el_window).attr('data-sort_by') === 'name' ? '✓' : '',
                                     onClick: async function(){
                                         sort_items(el_window_body, 'name', $(el_window).attr('data-sort_order'));
@@ -1822,7 +1823,7 @@ async function UIWindow(options) {
                                     }
                                 },
                                 {
-                                    html: `Date modified`,
+                                    html: i18n('date_modified'),
                                     icon: $(el_window).attr('data-sort_by') === 'modified' ? '✓' : '',
                                     onClick: async function(){
                                         sort_items(el_window_body, 'modified', $(el_window).attr('data-sort_order'));
@@ -1830,7 +1831,7 @@ async function UIWindow(options) {
                                     }
                                 },
                                 {
-                                    html: `Type`,
+                                    html: i18n('type'),
                                     icon: $(el_window).attr('data-sort_by') === 'type' ? '✓' : '',
                                     onClick: async function(){
                                         sort_items(el_window_body, 'type', $(el_window).attr('data-sort_order'));
@@ -1838,7 +1839,7 @@ async function UIWindow(options) {
                                     }
                                 },
                                 {
-                                    html: `Size`,
+                                    html: i18n('size'),
                                     icon: $(el_window).attr('data-sort_by') === 'size' ? '✓' : '',
                                     onClick: async function(){
                                         sort_items(el_window_body, 'size', $(el_window).attr('data-sort_order'));
@@ -1850,7 +1851,7 @@ async function UIWindow(options) {
                                 // -------------------------------------------
                                 '-',
                                 {
-                                    html: `Ascending`,
+                                    html: i18n('ascending'),
                                     icon: $(el_window).attr('data-sort_order') === 'asc' ? '✓' : '',
                                     onClick: async function(){
                                         const sort_by = $(el_window).attr('data-sort_by')
@@ -1859,7 +1860,7 @@ async function UIWindow(options) {
                                     }
                                 },
                                 {
-                                    html: `Descending`,
+                                    html: i18n('descending'),
                                     icon: $(el_window).attr('data-sort_order') === 'desc' ? '✓' : '',
                                     onClick: async function(){
                                         const sort_by = $(el_window).attr('data-sort_by')
@@ -1874,7 +1875,7 @@ async function UIWindow(options) {
                         // Refresh
                         // -------------------------------------------
                         {
-                            html: "Refresh",
+                            html: i18n('refresh'),
                             onClick: function(){
                                 refresh_item_container(el_window_body, options);
                             }
@@ -1883,7 +1884,8 @@ async function UIWindow(options) {
                         // Show/Hide hidden files
                         // -------------------------------------------
                         {
-                            html: `${window.user_preferences.show_hidden_files ? "Hide" : "Show"} hidden files`,
+                            html: i18n('show_hidden'),
+                            icon: window.user_preferences.show_hidden_files ? '✓' : '',
                             onClick: function(){
                                 window.mutate_user_preferences({
                                     show_hidden_files : !window.user_preferences.show_hidden_files,
@@ -1898,7 +1900,7 @@ async function UIWindow(options) {
                         // -------------------------------------------
                         // New
                         // -------------------------------------------
-                        window.new_context_menu_item($(el_window).attr('data-path'), el_window_body),
+                        new_context_menu_item($(el_window).attr('data-path'), el_window_body),
                         // -------------------------------------------
                         // -
                         // -------------------------------------------
@@ -1907,7 +1909,7 @@ async function UIWindow(options) {
                         // Paste
                         // -------------------------------------------
                         {
-                            html: "Paste",
+                            html: i18n('paste'),
                             disabled: (clipboard.length === 0 || $(el_window).attr('data-path') === '/') ? true : false,
                             onClick: function(){
                                 if(clipboard_op === 'copy')
@@ -1920,7 +1922,7 @@ async function UIWindow(options) {
                         // Undo
                         // -------------------------------------------
                         {
-                            html: "Undo",
+                            html: i18n('undo'),
                             disabled: actions_history.length > 0 ? false : true,
                             onClick: function(){
                                 undo_last_action();
@@ -1930,22 +1932,13 @@ async function UIWindow(options) {
                         // Upload Here
                         // -------------------------------------------
                         {
-                            html: "Upload Here",
+                            html: i18n('upload_here'),
                             disabled: $(el_window).attr('data-path') === '/' ? true : false,
                             onClick: function(){
                                 init_upload_using_dialog(el_window_body, $(el_window).attr('data-path') + '/');
                             }
                         },
                         // -------------------------------------------
-                        // Request Files
-                        // -------------------------------------------
-                        // {
-                        //     html: "Request Files",
-                        //     onClick: function(){
-                        //         UIWindowRequestFiles({dir_path: $(el_window).attr('data-path')})
-                        //     }
-                        // },
-                        // -------------------------------------------
                         // -
                         // -------------------------------------------
                         '-',
@@ -1953,14 +1946,14 @@ async function UIWindow(options) {
                         // Publish As Website
                         // -------------------------------------------
                         {
-                            html: 'Publish As Website',
+                            html: i18n('publish_as_website'),
                             disabled: !options.is_dir,
                             onClick: async function () {
                                 if (window.require_email_verification_to_publish_website) {
                                     if (window.user.is_temp &&
                                         !await UIWindowSaveAccount({
                                             send_confirmation_code: true,
-                                            message: 'Please create an account to proceed.',
+                                            message: i18n('save_account_to_publish_website'),
                                             window_options: {
                                                 backdrop: true,
                                                 close_on_backdrop_click: false,
@@ -1977,7 +1970,7 @@ async function UIWindow(options) {
                         // Deploy as App
                         // -------------------------------------------
                         {
-                            html: 'Deploy as App',
+                            html: i18n('deploy_as_app'),
                             disabled: !options.is_dir,
                             onClick: async function () {
                                 launch_app({
@@ -1988,7 +1981,6 @@ async function UIWindow(options) {
                                         source_path: $(el_window).attr('data-path'),
                                     }
                                 })
-        
                             }
                         },
                         // -------------------------------------------
@@ -1999,19 +1991,18 @@ async function UIWindow(options) {
                         // Properties
                         // -------------------------------------------
                         {
-                            html: "Properties",
+                            html: i18n('properties'),
                             onClick: function(){
                                 let window_height = 500;
                                 let window_width = 450;
-            
+
                                 let left = mouseX;
                                 left -= 200;
                                 left = left > (window.innerWidth - window_width)? (window.innerWidth - window_width) : left;
-            
+
                                 let top = mouseY;
                                 top = top > (window.innerHeight - (window_height + window.taskbar_height + window.toolbar_height))? (window.innerHeight - (window_height + window.taskbar_height + window.toolbar_height)) : top;
-            
-            
+
                                 UIWindowItemProperties(options.title, options.path, options.uid, left, top, window_width, window_height);
                             }
                         },
@@ -2031,7 +2022,7 @@ async function UIWindow(options) {
                             disabled: false,
                             onClick: async function(){
                                 const alert_resp = await UIAlert({
-                                    message: `Are you sure you want to permanently delete the items in Trash?`,
+                                    message: i18n('empty_trash_confirmation'),
                                     buttons:[
                                         {
                                             label: 'Yes',

+ 6 - 6
src/UI/UIWindowChangePassword.js

@@ -29,22 +29,22 @@ async function UIWindowChangePassword(){
         h += `<div class="form-success-msg"></div>`;
         // current password
         h += `<div style="overflow: hidden; margin-bottom: 20px;">`;
-            h += `<label for="current-password-${internal_id}">Current Password</label>`;
+            h += `<label for="current-password-${internal_id}">${i18n('current_password')}</label>`;
             h += `<input id="current-password-${internal_id}" class="current-password" type="password" name="current-password" autocomplete="current-password" />`;
         h += `</div>`;
         // new password
         h += `<div style="overflow: hidden; margin-top: 20px; margin-bottom: 20px;">`;
-            h += `<label for="new-password-${internal_id}">New Password</label>`;
+            h += `<label for="new-password-${internal_id}">${i18n('new_password')}</label>`;
             h += `<input id="new-password-${internal_id}" type="password" class="new-password" name="new-password" autocomplete="off" />`;
         h += `</div>`;
         // confirm new password
         h += `<div style="overflow: hidden; margin-top: 20px; margin-bottom: 20px;">`;
-            h += `<label for="confirm-new-password-${internal_id}">Confirm New Password</label>`;
+            h += `<label for="confirm-new-password-${internal_id}">${i18n('confirm_new_password')}</label>`;
             h += `<input id="confirm-new-password-${internal_id}" type="password" name="confirm-new-password" class="confirm-new-password" autocomplete="off" />`;
         h += `</div>`;
 
         // Change Password
-        h += `<button class="change-password-btn button button-primary button-block button-normal">Change Password</button>`;
+        h += `<button class="change-password-btn button button-primary button-block button-normal">${i18n('change_password')}</button>`;
     h += `</div>`;
 
     const el_window = await UIWindow({
@@ -93,7 +93,7 @@ async function UIWindowChangePassword(){
             return;
         }
         else if(new_password !== confirm_new_password){
-            $(el_window).find('.form-error-msg').html('`New Password` and `Confirm New Password` do not match.');
+            $(el_window).find('.form-error-msg').html(i18n('passwords_do_not_match'));
             $(el_window).find('.form-error-msg').fadeIn();
             return;
         }
@@ -113,7 +113,7 @@ async function UIWindowChangePassword(){
                 new_pass: new_password,
             }),				
             success: function (data){
-                $(el_window).find('.form-success-msg').html('Password changed successfully.');
+                $(el_window).find('.form-success-msg').html(i18n('password_changed'));
                 $(el_window).find('.form-success-msg').fadeIn();
                 $(el_window).find('input').val('');
             },

+ 5 - 5
src/UI/UIWindowChangeUsername.js

@@ -30,16 +30,16 @@ async function UIWindowChangeUsername(){
         h += `<div class="form-success-msg"></div>`;
         // new username
         h += `<div style="overflow: hidden; margin-top: 10px; margin-bottom: 30px;">`;
-            h += `<label for="confirm-new-username-${internal_id}">New Username</label>`;
+            h += `<label for="confirm-new-username-${internal_id}">${i18n('new_username')}</label>`;
             h += `<input id="confirm-new-username-${internal_id}" type="text" name="new-username" class="new-username" autocomplete="off" />`;
         h += `</div>`;
 
         // Change Username
-        h += `<button class="change-username-btn button button-primary button-block button-normal">Change Username</button>`;
+        h += `<button class="change-username-btn button button-primary button-block button-normal">${i18n('change_username')}</button>`;
     h += `</div>`;
 
     const el_window = await UIWindow({
-        title: 'Change Username',
+        title: i18n('change_username'),
         app: 'change-username',
         single_instance: true,
         icon: null,
@@ -78,7 +78,7 @@ async function UIWindowChangeUsername(){
         const new_username = $(el_window).find('.new-username').val();
 
         if(!new_username){
-            $(el_window).find('.form-error-msg').html('All fields are required.');
+            $(el_window).find('.form-error-msg').html(i18n('all_fields_required'));
             $(el_window).find('.form-error-msg').fadeIn();
             return;
         }
@@ -102,7 +102,7 @@ async function UIWindowChangeUsername(){
                 new_username: new_username, 
             }),				
             success: function (data){
-                $(el_window).find('.form-success-msg').html('Username updated successfully.');
+                $(el_window).find('.form-success-msg').html(i18n('username_changed'));
                 $(el_window).find('.form-success-msg').fadeIn();
                 $(el_window).find('input').val('');
                 // update auth data

+ 3 - 3
src/UI/UIWindowClaimReferral.js

@@ -26,9 +26,9 @@ async function UIWindowClaimReferral(options){
     h += `<div>`;
         h += `<div class="qr-code-window-close-btn generic-close-window-button disable-user-select"> &times; </div>`;
         h += `<img src="${window.icons['present.svg']}" style="width: 70px; margin: 20px auto 20px; display: block; margin-bottom: 20px;">`;
-        h += `<h1 style="font-weight: 400; padding: 0 10px; font-size: 21px; text-align: center; margin-bottom: 0; color: #60626d; -webkit-font-smoothing: antialiased;">You have been referred to Puter by a friend!</h1>`;
-        h += `<p style="text-align: center; font-size: 16px; padding: 20px; font-weight: 400; margin: -10px 10px 0px 10px; -webkit-font-smoothing: antialiased; color: #5f626d;">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.</p>`;
-        h += `<button class="button button-primary button-block create-account-ref-btn" style="display: block;">Create Account</button>`;
+        h += `<h1 style="font-weight: 400; padding: 0 10px; font-size: 21px; text-align: center; margin-bottom: 0; color: #60626d; -webkit-font-smoothing: antialiased;">${i18n('you_have_been_referred_to_puter_by_a_friend')}</h1>`;
+        h += `<p style="text-align: center; font-size: 16px; padding: 20px; font-weight: 400; margin: -10px 10px 0px 10px; -webkit-font-smoothing: antialiased; color: #5f626d;">${i18n('confirm_account_for_free_referral_storage_c2a')}</p>`;
+        h += `<button class="button button-primary button-block create-account-ref-btn" style="display: block;">${i18n('create_account')}</button>`;
     h += `</div>`;
 
     const el_window = await UIWindow({

+ 2 - 2
src/UI/UIWindowColorPicker.js

@@ -42,13 +42,13 @@ async function UIWindowColorPicker(options){
                     h += `</div>`;
 
                     // Select button
-                    h += `<button class="select-btn button button-primary button-block button-normal">Select</button>`
+                    h += `<button class="select-btn button button-primary button-block button-normal">${i18n('select')}</button>`
                 h += `</form>`;
             h += `</div>`;
         h += `</div>`;
         
         const el_window = await UIWindow({
-            title: 'Select color…',
+            title: i18n('select_color'),
             app: 'color-picker',
             single_instance: true,
             icon: null,

+ 5 - 5
src/UI/UIWindowConfirmDownload.js

@@ -34,17 +34,17 @@ async function UIWindowConfirmDownload(options){
                 // Item information
                 h += `<div  style="overflow:hidden;">`;
                     // Name
-                    h += `<p style="text-overflow: ellipsis; overflow: hidden;"><span class="dl-conf-item-attr">Name:</span> ${options.name ?? options.url}</p>`;
+                    h += `<p style="text-overflow: ellipsis; overflow: hidden;"><span class="dl-conf-item-attr">${i18n('name')}:</span> ${options.name ?? options.url}</p>`;
                     // Type
-                    h += `<p style="text-overflow: ellipsis; overflow: hidden;"><span class="dl-conf-item-attr">Type:</span> ${options.is_dir === '1' || options.is_dir === 'true' ? 'Folder' : options.type  ?? 'Unknown File Type'}</p>`;
+                    h += `<p style="text-overflow: ellipsis; overflow: hidden;"><span class="dl-conf-item-attr">${i18n('type')}:</span> ${options.is_dir === '1' || options.is_dir === 'true' ? 'Folder' : options.type  ?? 'Unknown File Type'}</p>`;
                     // Source
-                    h += `<p style="text-overflow: ellipsis; overflow: hidden;"><span class="dl-conf-item-attr">From:</span> ${options.source}</p>`;
+                    h += `<p style="text-overflow: ellipsis; overflow: hidden;"><span class="dl-conf-item-attr">${i18n('from')}:</span> ${options.source}</p>`;
                 h += `</div>`;
             h += `</div>`;
             // Download
-            h += `<button style="float:right; margin-top: 15px; margin-right: -2px; margin-left:10px;" class="button button-small button-primary btn-download-confirm">Download</button>`;
+            h += `<button style="float:right; margin-top: 15px; margin-right: -2px; margin-left:10px;" class="button button-small button-primary btn-download-confirm">${i18n('download')}</button>`;
             // Cancel
-            h += `<button style="float:right; margin-top: 15px;" class="button button-small btn-download-cancel">Cancel</button>`;
+            h += `<button style="float:right; margin-top: 15px;" class="button button-small btn-download-cancel">${i18n('cancel')}</button>`;
         h +=`</div>`;
 
         const el_window = await UIWindow({

+ 2 - 2
src/UI/UIWindowCopyProgress.js

@@ -29,7 +29,7 @@ async function UIWindowCopyProgress(options){
             // Progress report
             h +=`<div style="margin-bottom:20px; float:left; padding-top:3px; font-size:15px; overflow: hidden; width: calc(100% - 40px); text-overflow: ellipsis; white-space: nowrap;">`;
                 // msg
-                h += `<span class="copy-progress-msg">Copying </span>`;
+                h += `<span class="copy-progress-msg">${i18n('copying')} </span>`;
                 h += `<span class="copy-from" style="font-weight:strong;"></span>`;
             h += `</div>`;
             // progress
@@ -42,7 +42,7 @@ async function UIWindowCopyProgress(options){
     h += `</div>`;
 
     const el_window = await UIWindow({
-        title: `Copying`,
+        title: i18n('copying'),
         icon: window.icons[`app-icon-copying.svg`],
         uid: null,
         is_dir: false,

+ 14 - 14
src/UI/UIWindowDesktopBGSettings.js

@@ -30,28 +30,28 @@ async function UIWindowDesktopBGSettings(){
         h += `<div style="padding: 10px; border-bottom: 1px solid #ced7e1;">`;
 
             // type
-            h += `<label>Background:</label>`;
+            h += `<label>${i18n('background')}:</label>`;
             h += `<select class="desktop-bg-type" style="width: 150px; margin-bottom: 20px;">`
-                h += `<option value="picture">Picture</option>`;
-                h += `<option value="color">Color</option>`;
+                h += `<option value="picture">${i18n('picture')}</option>`;
+                h += `<option value="color">${i18n('color')}</option>`;
             h += `</select>`;
 
             // Picture
             h += `<div class="desktop-bg-settings-wrapper desktop-bg-settings-picture">`;
-                h += `<label>Image:</label>`;
-                h += `<button class="button button-default button-small browse">Browse</button>`;
-                h += `<label style="margin-top: 20px;">Fit:</label>`;
+                h += `<label>${i18n('image')}:</label>`;
+                h += `<button class="button button-default button-small browse">${i18n('browse')}</button>`;
+                h += `<label style="margin-top: 20px;">${i18n('fit')}:</label>`;
                 h += `<select class="desktop-bg-fit" style="width: 150px;">`
-                    h += `<option value="cover">Cover</option>`;
-                    h += `<option value="center">Center</option>`;
-                    h += `<option value="contain">Contain</option>`;
-                    h += `<option value="repeat">Repeat</option>`;
+                    h += `<option value="cover">${i18n('cover')}</option>`;
+                    h += `<option value="center">${i18n('center')}</option>`;
+                    h += `<option value="contain">${i18n('contain')}</option>`;
+                    h += `<option value="repeat">${i18n('repeat')}</option>`;
                 h += `</select>`;
             h += `</div>`
 
             // Color
             h += `<div class="desktop-bg-settings-wrapper desktop-bg-settings-color">`;
-                h += `<label>Color:</label>`;
+                h += `<label>${i18n('color')}:</label>`;
                 h += `<div class="desktop-bg-color-blocks">`;
                     h += `<div class="desktop-bg-color-block" data-color="#4F7BB5" style="background-color: #4F7BB5"></div>`;
                     h += `<div class="desktop-bg-color-block" data-color="#545554" style="background-color: #545554"></div>`;
@@ -69,14 +69,14 @@ async function UIWindowDesktopBGSettings(){
             h += `</div>`;
 
             h += `<div style="padding-top: 5px; overflow:hidden; margin-top: 25px; border-top: 1px solid #CCC;">`
-                h += `<button class="button button-primary apply" style="float:right;">Apply</button>`;
-                h += `<button class="button button-default cancel" style="float:right; margin-right: 10px;">Cancel</button>`;
+                h += `<button class="button button-primary apply" style="float:right;">${i18n('apply')}</button>`;
+                h += `<button class="button button-default cancel" style="float:right; margin-right: 10px;">${i18n('cancel')}</button>`;
             h += `</div>`;
 
         h += `</div>`;
 
         const el_window = await UIWindow({
-            title: 'Change Desktop Background…',
+            title: i18n('change_desktop_background'),
             icon: null,
             uid: null,
             is_dir: false,

+ 1 - 1
src/UI/UIWindowDownloadDirProg.js

@@ -25,7 +25,7 @@ async function UIWindowDownloadDirProg(options){
     let h = '';
     // Loading spinner
     h +=`<svg style="height: 40px; width: 40px; padding: 10px; display: block; float: left;" xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24"><title>circle anim</title><g fill="#212121" class="nc-icon-wrapper"><g class="nc-loop-circle-24-icon-f"><path d="M12 24a12 12 0 1 1 12-12 12.013 12.013 0 0 1-12 12zm0-22a10 10 0 1 0 10 10A10.011 10.011 0 0 0 12 2z" fill="#212121" opacity=".4"></path><path d="M24 12h-2A10.011 10.011 0 0 0 12 2V0a12.013 12.013 0 0 1 12 12z" data-color="color-2"></path></g><style>.nc-loop-circle-24-icon-f{--animation-duration:0.5s;transform-origin:12px 12px;animation:nc-loop-circle-anim var(--animation-duration) infinite linear}@keyframes nc-loop-circle-anim{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}</style></g></svg>`;
-    h += `<p style="text-align:left; padding-left:20px; padding-right:20px; overflow:hidden; width: 310px; text-overflow: ellipsis; white-space: nowrap; float:left; font-size:14px;" class="dir-dl-status">${options.defaultText ?? 'Preparing...'}</p>`;
+    h += `<p style="text-align:left; padding-left:20px; padding-right:20px; overflow:hidden; width: 310px; text-overflow: ellipsis; white-space: nowrap; float:left; font-size:14px;" class="dir-dl-status">${options.defaultText ?? i18n('preparing')}</p>`;
 
     const el_window = await UIWindow({
         title: 'Instant Login!',

+ 1 - 1
src/UI/UIWindowDownloadProgress.js

@@ -29,7 +29,7 @@ async function UIWindowDownloadProgress(options){
             // Progress report
             h +=`<div style="margin-bottom:20px; float:left; padding-top:3px; font-size:15px; overflow: hidden; width: calc(100% - 40px); text-overflow: ellipsis; white-space: nowrap;">`;
                 // msg
-                h += `<span class="upload-progress-msg">Downloading <strong>${options.item_name ?? ''}</strong></span>`;
+                h += `<span class="upload-progress-msg">${i18n('downloading')} <strong>${options.item_name ?? ''}</strong></span>`;
             h += `</div>`;
             // Progress
             h += `<div class="download-progress-bar-container" style="clear:both; margin-top:20px; border-radius:3px;">`;

+ 2 - 2
src/UI/UIWindowEmailConfirmationRequired.js

@@ -47,10 +47,10 @@ function UIWindowEmailConfirmationRequired(options){
                 h += `<button type="submit" class="button button-block button-primary email-confirm-btn" style="margin-top:10px;" disabled>${submit_btn_txt}</button>`;
             h += `</form>`;
             h += `<div style="text-align:center; padding:10px; font-size:14px; margin-top:10px;">`;
-                h += `<span class="send-conf-email">Re-send Confirmation Code</span>`;
+                h += `<span class="send-conf-email">${i18n('resend_confirmation_code')}</span>`;
                 if(options.logout_in_footer){
                     h += ` &bull; `;
-                    h += `<span class="conf-email-log-out">Log Out</span>`;
+                    h += `<span class="conf-email-log-out">${i18n('log_out')}</span>`;
                 }
             h += `</div>`;
         h += `</div>`;

+ 4 - 4
src/UI/UIWindowFeedback.js

@@ -28,18 +28,18 @@ async function UIWindowQR(options){
             // success
             h += `<div class="feedback-sent-success">`;
                 h += `<img src="${html_encode(window.icons['c-check.svg'])}" style="width:50px; height:50px; display: block; margin:10px auto;">`;
-                h += `<p style="text-align:center; margin-bottom:10px; color: #005300; padding: 10px;">Thank you for contacting us. If you have an email associated with your account, you will hear back from us as soon as possible.</p>`;
+                h += `<p style="text-align:center; margin-bottom:10px; color: #005300; padding: 10px;">${i18n('feedback_sent_confirmation')}</p>`;
             h+= `</div>`;
             // form
             h += `<div class="feedback-form">`;
-                h += `<p style="margin-top:0; font-size: 15px; -webkit-font-smoothing: antialiased;">Please use the form below to send us your feedback, comments, and bug reports.</p>`;
+                h += `<p style="margin-top:0; font-size: 15px; -webkit-font-smoothing: antialiased;">${i18n('feedback_c2a')}</p>`;
                 h += `<textarea class="feedback-message" style="width:100%; height: 200px; padding: 10px; box-sizing: border-box;"></textarea>`;
-                h += `<button class="button button-primary send-feedback-btn" style="float: right; margin-bottom: 15px; margin-top: 10px;">Send</button>`;
+                h += `<button class="button button-primary send-feedback-btn" style="float: right; margin-bottom: 15px; margin-top: 10px;">${i18n('send')}</button>`;
             h += `</div>`;
         h += `</div>`;
 
         const el_window = await UIWindow({
-            title: 'Contact Us',
+            title: i18n('contact_us'),
             app: 'feedback',
             single_instance: true,
             icon: null,

+ 1 - 1
src/UI/UIWindowFontPicker.js

@@ -60,7 +60,7 @@ async function UIWindowFontPicker(options){
                     h += `</div>`;
 
                     // Select
-                    h += `<button class="select-btn button button-primary button-block button-normal">Select</button>`
+                    h += `<button class="select-btn button button-primary button-block button-normal">${i18n('Select')}</button>`
                 h += `</form>`;
             h += `</div>`;
         h += `</div>`;

+ 5 - 1
src/UI/UIWindowGetCopyLink.js

@@ -69,7 +69,11 @@ async function UIWindowGetCopyLink(options){
     $(el_window).find('.window-body .downloadable-link').val(url);
 
     $(el_window).find('.window-body .share-copy-link-on-social').on('click', function(e){    
-        const social_links = socialLink({url: url, title: `Get a copy of '${options.name}' on Puter.com!`, description: `Get a copy of '${options.name}' on Puter.com!`});
+        const social_links = socialLink({
+            url: url, 
+            title: i18n('get_a_copy_of_on_puter', options.name, false), 
+            description: i18n('get_a_copy_of_on_puter', options.name, false),
+        });
 
         let social_links_html = ``;
         social_links_html += `<div style="padding: 10px;">`;

+ 3 - 3
src/UI/UIWindowItemProperties.js

@@ -25,8 +25,8 @@ async function UIWindowItemProperties(item_name, item_path, item_uid, left, top,
     h += `<div class="item-props-tabview" style="display: flex; flex-direction: column; height: 100%;">`;
         // tabs
         h += `<div class="item-props-tab">`;
-            h += `<div class="item-props-tab-btn antialiased disable-user-select item-props-tab-selected" data-tab="general">General</div>`;
-            h += `<div class="item-props-tab-btn antialiased disable-user-select item-props-tab-btn-versions" data-tab="versions">Versions</div>`;
+            h += `<div class="item-props-tab-btn antialiased disable-user-select item-props-tab-selected" data-tab="general">${i18n('general')}</div>`;
+            h += `<div class="item-props-tab-btn antialiased disable-user-select item-props-tab-btn-versions" data-tab="versions">${i18n('versions')}</div>`;
         h += `</div>`;
 
         h+= `<div class="item-props-tab-content item-props-tab-content-selected" data-tab="general" style="border-top-left-radius:0;">`;
@@ -44,7 +44,7 @@ async function UIWindowItemProperties(item_name, item_path, item_uid, left, top,
                 h += `<tr><td class="item-prop-label">Versions</td><td class="item-prop-val item-prop-val-versions"></td></tr>`;
                 h += `<tr><td class="item-prop-label">Associated Websites</td><td class="item-prop-val item-prop-val-websites">`;
                 h += `</td></tr>`;
-                h += `<tr><td class="item-prop-label">Access Granted To</td><td class="item-prop-val item-prop-val-permissions"></td></tr>`;
+                h += `<tr><td class="item-prop-label">${i18n('access_granted_to')}</td><td class="item-prop-val item-prop-val-permissions"></td></tr>`;
             h += `</table>`;
         h += `</div>`;
 

+ 5 - 5
src/UI/UIWindowLogin.js

@@ -43,12 +43,12 @@ async function UIWindowLogin(options){
                     h += `<div class="login-error-msg"></div>`;
                     // username/email
                     h += `<div style="overflow: hidden;">`;
-                        h += `<label for="email_or_username-${internal_id}">Email or Username</label>`;
+                        h += `<label for="email_or_username-${internal_id}">${i18n('email_or_username')}</label>`;
                         h += `<input id="email_or_username-${internal_id}" class="email_or_username" type="text" name="email_or_username" spellcheck="false" autocorrect="off" autocapitalize="off" data-gramm_editor="false" autocomplete="username"/>`;
                     h += `</div>`;
                     // password with conditional type based based on options.show_password
                     h += `<div style="overflow: hidden; margin-top: 20px; margin-bottom: 20px; position: relative;">`;
-                    h += `<label for="password-${internal_id}">Password</label>`;
+                    h += `<label for="password-${internal_id}">${i18n('password')}</label>`;
                     h += `<input id="password-${internal_id}" class="password" type="${options.show_password ? "text" : "password"}" name="password" autocomplete="current-password"/>`;
                     // show/hide icon
                     h += `<span style="position: absolute; right: 5%; top: 50%; cursor: pointer;" id="toggle-show-password-${internal_id}">
@@ -56,15 +56,15 @@ async function UIWindowLogin(options){
                             </span>`;
                     h += `</div>`;
                     // login
-                    h += `<button class="login-btn button button-primary button-block button-normal">Log in</button>`;
+                    h += `<button class="login-btn button button-primary button-block button-normal">${i18n('log_in')}</button>`;
                     // password recovery
-                    h += `<p style="text-align:center; margin-bottom: 0;"><span class="forgot-password-link">Forgot password?</span></p>`;
+                    h += `<p style="text-align:center; margin-bottom: 0;"><span class="forgot-password-link">${i18n('forgot_pass_c2a')}</span></p>`;
                 h += `</form>`;
             h += `</div>`;
             // create account link
             if(options.show_signup_button === undefined || options.show_signup_button){
                 h += `<div class="c2a-wrapper" style="padding:20px;">`;
-                    h += `<button class="signup-c2a-clickable">Create Free Account</button>`;
+                    h += `<button class="signup-c2a-clickable">${i18n('create_free_account')}</button>`;
                 h += `</div>`;
             }
         h += `</div>`;

+ 1 - 1
src/UI/UIWindowMoveProgress.js

@@ -29,7 +29,7 @@ async function UIWindowMoveProgress(options){
             // Progress report
             h +=`<div style="margin-bottom:20px; float:left; padding-top:3px; font-size:15px; overflow: hidden; width: calc(100% - 40px); text-overflow: ellipsis; white-space: nowrap;">`;
                 // msg
-                h += `<span class="move-progress-msg">Moving </span>`;
+                h += `<span class="move-progress-msg">${i18n('moving')} </span>`;
                 h += `<span class="move-from" style="font-weight:strong;"></span>`;
             h += `</div>`;
             // progress

+ 7 - 6
src/UI/UIWindowMyWebsites.js

@@ -90,10 +90,10 @@ async function UIWindowMyWebsites(options){
                             h += `</p>`;
                             h += `<p style="margin-bottom:0; margin-top: 20px; font-size: 13px;">`;
                                 h += `<span class="mywebsites-dis-dir" data-dir-uuid="${sites[i].root_dir.id}" data-site-uuid="${sites[i].uid}">`;
-                                h += `<img style="width: 16px; margin-bottom: -2px; margin-right: 4px;" src="${html_encode(window.icons['plug.svg'])}">Disassociate Folder</span>`;
+                                h += `<img style="width: 16px; margin-bottom: -2px; margin-right: 4px;" src="${html_encode(window.icons['plug.svg'])}">${i18n('disassociate_dir')}</span>`;
                             h += `</p>`;
                         }
-                        h += `<p class="mywebsites-no-dir-notice" data-site-uuid="${sites[i].uid}" style="${sites[i].root_dir ? `display:none;` : `display:block;`}">No directory associated with this address.</p>`;
+                        h += `<p class="mywebsites-no-dir-notice" data-site-uuid="${sites[i].uid}" style="${sites[i].root_dir ? `display:none;` : `display:block;`}">${i18n('no_dir_associated_with_site')}</p>`;
                     h += `</div>`;
                 }
                 $(el_window).find('.window-body').html(h);
@@ -105,7 +105,7 @@ async function UIWindowMyWebsites(options){
                 margin-bottom: 50px;
                 -webkit-font-smoothing: antialiased;
                 -moz-osx-font-smoothing: grayscale;
-                color: #596c7c;">You haven't published any websites!</p>`);
+                color: #596c7c;">${i18n('no_websites_published')}</p>`);
             }
         }, Date.now() - init_ts < 1000 ? 0 : 2000);
     })
@@ -136,10 +136,11 @@ $(document).on('click', '.mywebsites-site-setting', function(e){
                 html: `Release Address`,
                 onClick: async function(){
                     const alert_resp = await UIAlert({
-                        message: `Are you sure you want to release this address?`,
+                        message: i18n('release_address_confirmation'),
                         buttons:[
                             {
-                                label: 'Yes, Release It',
+                                label: i18n('yes_release_it'),
+                                value: 'yes',
                                 type: 'primary',
                             },
                             {
@@ -147,7 +148,7 @@ $(document).on('click', '.mywebsites-site-setting', function(e){
                             },
                         ]
                     })
-                    if(alert_resp !== 'Yes, Release It'){
+                    if(alert_resp !== 'yes'){
                         return;
                     }
                 

+ 1 - 1
src/UI/UIWindowNewFolderProgress.js

@@ -29,7 +29,7 @@ async function UIWindowNewFolderProgress(options){
             // message
             h +=`<div style="margin-bottom:20px; float:left; padding-top:3px; font-size:15px; overflow: hidden; width: calc(100% - 40px); text-overflow: ellipsis; white-space: nowrap;">`;
                 // text
-                h += `<span class="newfolder-progress-msg">Taking a little longer than usual. Please wait...</span>`;
+                h += `<span class="newfolder-progress-msg">${i18n('taking_longer_than_usual')}</span>`;
             h += `</div>`;
         h +=`</div>`;
     h += `</div>`;

+ 3 - 3
src/UI/UIWindowNewPassword.js

@@ -34,17 +34,17 @@ async function UIWindowNewPassword(options){
             h += `<div class="form-success-msg"></div>`;
             // new password
             h += `<div style="overflow: hidden; margin-top: 20px; margin-bottom: 20px;">`;
-                h += `<label for="new-password-${internal_id}">New Password</label>`;
+                h += `<label for="new-password-${internal_id}">${i18n('new_password')}</label>`;
                 h += `<input class="new-password" id="new-password-${internal_id}" type="password" name="new-password" autocomplete="off" />`;
             h += `</div>`;
             // confirm new password
             h += `<div style="overflow: hidden; margin-top: 20px; margin-bottom: 20px;">`;
-                h += `<label for="confirm-new-password-${internal_id}">Confirm New Password</label>`;
+                h += `<label for="confirm-new-password-${internal_id}">${i18n('confirm_new_password')}</label>`;
                 h += `<input class="confirm-new-password" id="confirm-new-password-${internal_id}" type="password" name="confirm-new-password" autocomplete="off" />`;
             h += `</div>`;
 
             // Change Password
-            h += `<button class="change-password-btn button button-primary button-block button-normal">Set New Password</button>`;
+            h += `<button class="change-password-btn button button-primary button-block button-normal">${i18n('set_new_password')}</button>`;
         h += `</div>`;
 
         const el_window = await UIWindow({

+ 2 - 2
src/UI/UIWindowProgressEmptyTrash.js

@@ -29,13 +29,13 @@ async function UIWindowProgressEmptyTrash(options){
             // message
             h +=`<div style="margin-bottom:20px; float:left; padding-top:3px; font-size:15px; overflow: hidden; width: calc(100% - 40px); text-overflow: ellipsis; white-space: nowrap;">`;
                 // text
-                h += `<span class="newfolder-progress-msg">Emptying the Trash...</span>`;
+                h += `<span class="newfolder-progress-msg">${i18n('emptying_trash')}</span>`;
             h += `</div>`;
         h +=`</div>`;
     h += `</div>`;
 
     const el_window = await UIWindow({
-        title: `Creating New Folder`,
+        title: i18n('emptying_trash'),
         icon: window.icons[`app-icon-newfolder.svg`],
         uid: null,
         is_dir: false,

+ 3 - 3
src/UI/UIWindowPublishWebsite.js

@@ -26,7 +26,7 @@ async function UIWindowPublishWebsite(target_dir_uid, target_dir_name, target_di
         // success
         h += `<div class="window-publishWebsite-success">`;
             h += `<img src="${html_encode(window.icons['c-check.svg'])}" style="width:80px; height:80px; display: block; margin:10px auto;">`;
-            h += `<p style="text-align:center;"><strong>${target_dir_name}</strong> has been published to:<p>`;
+            h += `<p style="text-align:center;">${i18n('dir_published_as_website', `<strong>${target_dir_name}</strong>`)}<p>`;
             h += `<p style="text-align:center;"><a class="publishWebsite-published-link" target="_blank"></a><img class="publishWebsite-published-link-icon" src="${html_encode(window.icons['launch.svg'])}"></p>`;
             h += `<button class="button button-normal button-block button-primary publish-window-ok-btn" style="margin-top:20px;">OK</button>`;
         h+= `</div>`;
@@ -36,13 +36,13 @@ async function UIWindowPublishWebsite(target_dir_uid, target_dir_name, target_di
             h += `<div class="publish-website-error-msg"></div>`;
             // subdomain
             h += `<div style="overflow: hidden;">`;
-                h += `<label style="margin-bottom: 10px;">Pick a name for your website:</label>`;
+                h += `<label style="margin-bottom: 10px;">${i18n('pick_name_for_website')}</label>`;
                 h += `<div style="font-family: monospace;">https://<input class="publish-website-subdomain" style="width:235px;" type="text" autocomplete="subdomain" spellcheck="false" autocorrect="off" autocapitalize="off" data-gramm_editor="false"/>.${window.hosting_domain}</div>`;
             h += `</div>`;
             // uid
             h += `<input class="publishWebsiteTargetDirUID" type="hidden" value="${target_dir_uid}"/>`;
             // Publish
-            h += `<button class="publish-btn button button-action button-block button-normal">Publish</button>`
+            h += `<button class="publish-btn button button-action button-block button-normal">${i18n('publish')}</button>`
         h += `</form>`;
     h += `</div>`;
 

+ 1 - 1
src/UI/UIWindowQR.js

@@ -27,7 +27,7 @@ async function UIWindowQR(options){
         // close button containing the multiplication sign
         h += `<div class="qr-code-window-close-btn generic-close-window-button"> &times; </div>`;
         h += `<div class="otp-qr-code">`;
-            h += `<h1 style="text-align: center; font-size: 16px; padding: 10px; font-weight: 400; margin: -10px 10px 20px 10px; -webkit-font-smoothing: antialiased; color: #5f626d;">Scan the code below to log into this session from other devices</h1>`;
+            h += `<h1 style="text-align: center; font-size: 16px; padding: 10px; font-weight: 400; margin: -10px 10px 20px 10px; -webkit-font-smoothing: antialiased; color: #5f626d;">${i18n('scan_qr_c2a')}</h1>`;
         h += `</div>`;
 
         const el_window = await UIWindow({

+ 3 - 3
src/UI/UIWindowRecoverPassword.js

@@ -26,13 +26,13 @@ function UIWindowRecoverPassword(options){
 
         let h = '';
         h += `<div style="-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #3e5362;">`;
-            h += `<h3 style="text-align:center; font-weight: 400; font-size: 20px;">Recover Password</h3>`;
+            h += `<h3 style="text-align:center; font-weight: 400; font-size: 20px;">${i18n('recover_password')}</h3>`;
             h += `<form class="pass-recovery-form">`;
                 h += `<p style="text-align:center; padding: 0 20px;"></p>`;
                 h += `<div class="error"></div>`;
-                h += `<label>Email or Username</label>`;
+                h += `<label>${i18n('email_or_username')}</label>`;
                 h += `<input class="pass-recovery-username-or-email" type="text"/>`;
-                h += `<button type="submit" class="send-recovery-email button button-block button-primary" style="margin-top:10px;">Send Recovery Email</button>`;
+                h += `<button type="submit" class="send-recovery-email button button-block button-primary" style="margin-top:10px;">${i18n('send_password_recovery_email')}</button>`;
             h += `</form>`;
         h += `</div>`;
 

+ 4 - 4
src/UI/UIWindowRefer.js

@@ -29,8 +29,8 @@ async function UIWindowRefer(options){
     h += `<div>`;
         h += `<div class="qr-code-window-close-btn generic-close-window-button disable-user-select"> &times; </div>`;
         h += `<img src="${window.icons['present.svg']}" style="width: 70px; margin: 20px auto 20px; display: block; margin-bottom: 20px;">`;
-        h += `<p style="text-align: center; font-size: 16px; padding: 20px; font-weight: 400; margin: -10px 10px 20px 10px; -webkit-font-smoothing: antialiased; color: #5f626d;">Get 1 GB for every friend who creates and confirms an account on Puter. Your friend will get 1 GB too!</p>`;
-        h += `<label style="font-weight: bold;">Invite link</label>`;
+        h += `<p style="text-align: center; font-size: 16px; padding: 20px; font-weight: 400; margin: -10px 10px 20px 10px; -webkit-font-smoothing: antialiased; color: #5f626d;">${i18n('refer_friends_c2a')}</p>`;
+        h += `<label style="font-weight: bold;">${i18n('invite_link')}</label>`;
         h += `<input type="text" style="margin-bottom:10px;" class="downloadable-link" readonly />`;
         h += `<button class="button button-primary copy-downloadable-link" style="width:130px;">${copy_btn_text}</button>`
         h += `<img class="share-copy-link-on-social" src="${window.icons['share-outline.svg']}">`;
@@ -72,11 +72,11 @@ async function UIWindowRefer(options){
     $(el_window).find('.window-body .downloadable-link').val(url);
 
     $(el_window).find('.window-body .share-copy-link-on-social').on('click', function(e){    
-        const social_links = socialLink({url: url, title: `Get 1 GB of free storage on Puter.com!`, description: `Get 1 GB of free storage on Puter.com!`});
+        const social_links = socialLink({url: url, title: i18n('refer_friends_social_media_c2a'), description: i18n('refer_friends_social_media_c2a')});
 
         let social_links_html = ``;
         social_links_html += `<div style="padding: 10px;">`;
-            social_links_html += `<p style="margin: 0; text-align: center; margin-bottom: 6px; color: #484a57; font-weight: bold; font-size: 14px;">Share to</p>`
+            social_links_html += `<p style="margin: 0; text-align: center; margin-bottom: 6px; color: #484a57; font-weight: bold; font-size: 14px;">${i18n('share_to')}</p>`
             social_links_html += `<a class="copy-link-social-btn" target="_blank" href="${social_links.twitter}" style=""><svg viewBox="0 0 24 24" aria-hidden="true" style="opacity: 0.7;"><g><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"></path></g></svg></a>`
             social_links_html += `<a class="copy-link-social-btn" target="_blank" href="${social_links.whatsapp}" style=""><img src="${window.icons['logo-whatsapp.svg']}"></a>`
             social_links_html += `<a class="copy-link-social-btn" target="_blank" href="${social_links.facebook}" style=""><img src="${window.icons['logo-facebook.svg']}"></a>`

+ 8 - 8
src/UI/UIWindowSaveAccount.js

@@ -32,39 +32,39 @@ async function UIWindowSaveAccount(options){
             // success
             h += `<div class="save-account-success">`;
                 h += `<img src="${html_encode(window.icons['c-check.svg'])}" style="width:50px; height:50px; display: block; margin:10px auto;">`;
-                h += `<p style="text-align:center; margin-bottom:10px;">Thank you for creating an account. This session has been saved.</p>`;
-                h += `<button class="button button-action button-block save-account-success-ok-btn">OK</button>`
+                h += `<p style="text-align:center; margin-bottom:10px;">${i18n('session_saved')}</p>`;
+                h += `<button class="button button-action button-block save-account-success-ok-btn">${i18n('ok')}</button>`
             h+= `</div>`;
     
             // form
             h += `<div class="save-account-form" style="padding: 20px; border-bottom: 1px solid #ced7e1; width: 100%; box-sizing: border-box;">`;
                 // title
-                h += `<h1 class="signup-form-title" style="margin-bottom:0;">Create Account</h1>`;
+                h += `<h1 class="signup-form-title" style="margin-bottom:0;">${i18n('create_account')}</h1>`;
                 // description
-                h += `<p class="create-account-desc">${options.message ?? 'Create an account to save your current session and avoid losing your work.'}</p>`;
+                h += `<p class="create-account-desc">${options.message ?? i18n('save_session_c2a')}</p>`;
                 // signup form
                 h += `<form class="signup-form">`;
                     // error msg
                     h += `<div class="signup-error-msg"></div>`;
                     // username
                     h += `<div style="overflow: hidden;">`;
-                        h += `<label for="username-${internal_id}">Username</label>`;
+                        h += `<label for="username-${internal_id}">${i18n('username')}</label>`;
                         h += `<input id="username-${internal_id}" class="username" value="${options.default_username ?? ''}" type="text" autocomplete="username" spellcheck="false" autocorrect="off" autocapitalize="off" data-gramm_editor="false"/>`;
                     h += `</div>`;
                     // email
                     h += `<div style="overflow: hidden; margin-top: 20px;">`;
-                        h += `<label for="email-${internal_id}">Email</label>`;
+                        h += `<label for="email-${internal_id}">${i18n('email')}</label>`;
                         h += `<input id="email-${internal_id}" class="email" type="email" autocomplete="email" spellcheck="false" autocorrect="off" autocapitalize="off" data-gramm_editor="false"/>`;
                     h += `</div>`;
                     // password
                     h += `<div style="overflow: hidden; margin-top: 20px; margin-bottom: 20px;">`;
-                        h += `<label for="password-${internal_id}">Password</label>`;
+                        h += `<label for="password-${internal_id}">${i18n('password')}</label>`;
                         h += `<input id="password-${internal_id}" class="password" type="password" name="password" autocomplete="new-password" />`;
                     h += `</div>`;
                     // bot trap - if this value is submitted server will ignore the request
                     h += `<input type="text" name="p102xyzname" class="p102xyzname" value="">`;
                     // Create Account
-                    h += `<button class="signup-btn button button-primary button-block button-normal">Create Account</button>`
+                    h += `<button class="signup-btn button button-primary button-block button-normal">${i18n('create_account')}</button>`
                 h += `</form>`;
             h += `</div>`;
         h += `</div>`;

+ 3 - 3
src/UI/UIWindowSessionList.js

@@ -28,16 +28,16 @@ async function UIWindowSessionList(options){
     return new Promise(async (resolve) => {
         let h = '';
         h += `<div style="margin:10px;">`;
-            h += `<div class="loading">Signing in...</div>`
+            h += `<div class="loading">${i18n('signing_in')}</div>`
             h += `<div style="overflow-y: scroll; max-width: 400px; margin: 0 auto;">`;
-            h += `<h1 style="text-align: center; font-size: 18px; font-weight: normal; color: #757575;"><img src="${icons['logo-white.svg']}" style="padding: 4px; background-color: blue; border-radius: 5px; width: 25px; box-sizing: border-box; margin-bottom: -6px; margin-right: 6px;">Sign in with Puter</h1>`
+            h += `<h1 style="text-align: center; font-size: 18px; font-weight: normal; color: #757575;"><img src="${icons['logo-white.svg']}" style="padding: 4px; background-color: blue; border-radius: 5px; width: 25px; box-sizing: border-box; margin-bottom: -6px; margin-right: 6px;">${i18n('sign_in_with_puter')}</h1>`
             for (let index = 0; index < logged_in_users.length; index++) {
                 const l_user = logged_in_users[index];
                 h += `<div data-uuid="${l_user.uuid}" class="session-entry">${l_user.username}</div>`;
             }
             h += `</div>`;
 
-            h += `<div style="margin-top: 20px; margin-bottom: 20px; text-align:center;"><span class="login-c2a-session-list">Log Into Another Account</span> &bull; <span class="signup-c2a-session-list">Create Account</span></div>`;
+            h += `<div style="margin-top: 20px; margin-bottom: 20px; text-align:center;"><span class="login-c2a-session-list">Log Into Another Account</span> &bull; <span class="signup-c2a-session-list">${i18n('create_account')}</span></div>`;
         h += `</div>`;
 
         const el_window = await UIWindow({

+ 7 - 7
src/UI/UIWindowSignup.js

@@ -40,39 +40,39 @@ function UIWindowSignup(options){
             // Form
             h += `<div style="padding: 20px; border-bottom: 1px solid #ced7e1;">`;
                 // title
-                h += `<h1 class="signup-form-title">Create Free Account</h1>`;
+                h += `<h1 class="signup-form-title">${i18n('create_free_account')}</h1>`;
                 // signup form
                 h += `<form class="signup-form">`;
                     // error msg
                     h += `<div class="signup-error-msg"></div>`;
                     // username
                     h += `<div style="overflow: hidden;">`;
-                        h += `<label for="username-${internal_id}">Username</label>`;
+                        h += `<label for="username-${internal_id}">${i18n('username')}</label>`;
                         h += `<input id="username-${internal_id}" class="username" type="text" autocomplete="username" spellcheck="false" autocorrect="off" autocapitalize="off" data-gramm_editor="false"/>`;
                     h += `</div>`;
                     // email
                     h += `<div style="overflow: hidden; margin-top: 20px;">`;
-                        h += `<label for="email-${internal_id}">Email</label>`;
+                        h += `<label for="email-${internal_id}">${i18n('email')}</label>`;
                         h += `<input id="email-${internal_id}" class="email" type="email" autocomplete="email" spellcheck="false" autocorrect="off" autocapitalize="off" data-gramm_editor="false"/>`;
                     h += `</div>`;
                     // password
                     h += `<div style="overflow: hidden; margin-top: 20px; margin-bottom: 20px;">`;
-                        h += `<label for="password-${internal_id}">Password</label>`;
+                        h += `<label for="password-${internal_id}">${i18n('password')}</label>`;
                         h += `<input id="password-${internal_id}" class="password" type="password" name="password" autocomplete="new-password" />`;
                     h += `</div>`;
                     // bot trap - if this value is submitted server will ignore the request
                     h += `<input type="text" name="p102xyzname" class="p102xyzname" value="">`;
 
                     // terms and privacy
-                    h += `<p class="signup-terms">By clicking 'Create Free Account' you agree to Puter's <a href="https://puter.com/terms" target="_blank">Terms of Service</a> and <a href="https://puter.com/privacy" target="_blank">Privacy Policy</a>.</p>`;
+                    h += `<p class="signup-terms">${i18n('tos_fineprint', false)}</p>`;
                     // Create Account
-                    h += `<button class="signup-btn button button-primary button-block button-normal">Create Free Account</button>`
+                    h += `<button class="signup-btn button button-primary button-block button-normal">${i18n('create_free_account')}</button>`
                 h += `</form>`;
             h += `</div>`;
             // login link
             // create account link
             h += `<div class="c2a-wrapper" style="padding:20px;">`;
-                h += `<button class="login-c2a-clickable">Log In</button>`;
+                h += `<button class="login-c2a-clickable">${i18n('log_in')}</button>`;
             h += `</div>`;
         h += `</div>`;
 

+ 3 - 3
src/UI/UIWindowUploadProgress.js

@@ -29,19 +29,19 @@ async function UIWindowUploadProgress(options){
             // Progress report
             h +=`<div style="margin-bottom:20px; float:left; padding-top:3px; font-size:15px; overflow: hidden; width: calc(100% - 40px); text-overflow: ellipsis; white-space: nowrap;">`;
                 // msg
-                h += `<span class="upload-progress-msg">Preparing for upload...</span>`;
+                h += `<span class="upload-progress-msg">${i18n('preparing_for_upload')}</span>`;
             h += `</div>`;
             // progress
             h += `<div class="upload-progress-bar-container" style="clear:both; margin-top:20px; border-radius:3px;">`;
                 h += `<div class="upload-progress-bar"></div>`;
             h += `</div>`;
             // cancel
-            h += `<button style="float:right; margin-top: 15px; margin-right: -2px;" class="button button-small upload-cancel-btn">Cancel</button>`;
+            h += `<button style="float:right; margin-top: 15px; margin-right: -2px;" class="button button-small upload-cancel-btn">${i18n('cancel')}</button>`;
         h +=`</div>`;
     h += `</div>`;
 
     const el_window = await UIWindow({
-        title: `Upload`,
+        title: i18n('Upload'),
         icon: window.icons[`app-icon-uploader.svg`],
         uid: null,
         is_dir: false,

+ 0 - 6
src/globals.js

@@ -141,12 +141,6 @@ if (window.location !== window.parent.location) {
 window.desktop_height = window.innerHeight - window.toolbar_height - window.taskbar_height;
 window.desktop_width = window.innerWidth;
 
-// recalculate desktop height and width on window resize
-$( window ).on( "resize", function() {
-    window.desktop_height = window.innerHeight - window.toolbar_height - window.taskbar_height;
-    window.desktop_width = window.innerWidth;
-});
-  
 // for now `active_element` is basically the last element that was clicked,
 // later on though (todo) `active_element` will also be set by keyboard movements 
 // such as arrow keys, tab key, ... and when creating new windows...

+ 8 - 93
src/helpers.js

@@ -409,17 +409,17 @@ window.globToRegExp = function (glob, opts) {
  */
 window.validate_fsentry_name = function(name){
     if(!name)
-        throw {message: 'Name cannot be empty.'}
+        throw {message: i18n('name_cannot_be_empty')}
     else if(!isString(name))
-        throw {message: "Name can only be a string."}
+        throw {message: i18n('name_must_be_string')}
     else if(name.includes('/'))
-        throw {message: "Name cannot contain the '/' character."}
+        throw {message: i18n('name_cannot_contain_slash')}
     else if(name === '.')
-        throw {message: "Name can not be the '.' character."};
+        throw {message: i18n('name_cannot_contain_period')};
     else if(name === '..')
-        throw {message: "Name can not be the '..' character."};
+        throw {message: i18n('name_cannot_contain_double_period')};
     else if(name.length > window.max_item_name_length)
-        throw {message: `Name can not be longer than ${config.max_item_name_length} characters`}
+        throw {message: i18n('name_too_long', config.max_item_name_length)}
     else
         return true
 }
@@ -1906,6 +1906,7 @@ window.launch_app = async (options)=>{
 
         // add app_instance_id to URL
         iframe_url.searchParams.append('puter.app_instance_id', uuid);
+
         // add app_id to URL
         iframe_url.searchParams.append('puter.app.id', app_info.uuid);
 
@@ -1941,6 +1942,7 @@ window.launch_app = async (options)=>{
         else if(options.token){
             iframe_url.searchParams.append('puter.auth.token', options.token);
         }
+
         // Try to acquire app token from the server
         else{
             let response = await fetch(window.api_origin + "/auth/get-user-app-token", {
@@ -1979,13 +1981,6 @@ window.launch_app = async (options)=>{
             window_class: 'window-app',
             update_window_url: true,
             app_uuid: app_info.uuid ?? app_info.uid,
-            // has_head: options.has_head ?? true,
-            // top: options.top ?? undefined,
-            // left: options.left ?? undefined,
-            // width: options.width ?? undefined,
-            // height: options.height ?? undefined,
-            // is_resizable: options.is_resizable ?? undefined,
-            // window_css: options.window_css ?? undefined,
             top: options.maximized ? 0 : undefined,
             left: options.maximized ? 0 : undefined,
             height: options.maximized ? `calc(100% - ${window.taskbar_height + window.toolbar_height + 1}px)` : undefined,
@@ -2243,63 +2238,6 @@ window.open_item = async function(options){
     }    
 }
 
-/**
- * Returns a context menu item to create a new file/folder. 
- * 
- * @param {string} dirname - The directory path to create the item in
- * @param {HTMLElement} append_to_element - Element to append the new item to 
- * @returns {Object} The context menu item object
- */
-
-window.new_context_menu_item = function(dirname, append_to_element){
-    return {
-        html: "New",
-        items: [
-            // New Folder
-            {
-                html: "New Folder",
-                icon: `<img src="${html_encode(window.icons['folder.svg'])}" class="ctx-item-icon">`,
-                onClick: function(){
-                    create_folder(dirname, append_to_element);
-                }
-            },
-            // divider
-            '-',
-            // Text Document
-            {
-                html: `Text Document`,
-                icon: `<img src="${html_encode(window.icons['file-text.svg'])}" class="ctx-item-icon">`,
-                onClick: async function(){
-                    create_file({dirname: dirname, append_to_element: append_to_element, name: 'New File.txt'});
-                }
-            },
-            // HTML Document
-            {
-                html: `HTML Document`,
-                icon: `<img src="${html_encode(window.icons['file-html.svg'])}" class="ctx-item-icon">`,
-                onClick: async function(){
-                    create_file({dirname: dirname, append_to_element: append_to_element, name: 'New File.html'});
-                }
-            },
-            // JPG Image
-            {
-                html: `JPG Image`,
-                icon: `<img src="${html_encode(window.icons['file-image.svg'])}" class="ctx-item-icon">`,
-                onClick: async function(){
-                    var canvas = document.createElement("canvas");
-
-                    canvas.width = 800;
-                    canvas.height = 600;
-                    
-                    canvas.toBlob((blob) =>{
-                        create_file({dirname: dirname, append_to_element: append_to_element, name: 'New Image.jpg', content: blob});
-                    });
-                }
-            },
-        ]
-    }
-}
-
 /**
  * Moves the given items to the destination path. 
  * 
@@ -3127,29 +3065,6 @@ window.getUsage = () => {
 
 }  
 
-window.determine_active_container_parent = function(){
-    // the container is either an ancestor of active element...
-    let parent_container = $(active_element).closest('.item-container');
-    // ... or a descendant of it...
-    if(parent_container.length === 0){
-        parent_container = $(active_element).find('.item-container');
-    }
-    // ... or siblings or cousins
-    if(parent_container.length === 0){
-        parent_container = $(active_element).closest('.window').find('.item-container');
-    }
-    // ... or the active element itself (if it's a container)
-    if(parent_container.length === 0 && active_element && $(active_element).hasClass('item-container')){
-        parent_container = $(active_element);
-    }
-    // ... or if there is no active element, the selected item that is not blurred
-    if(parent_container.length === 0 && active_item_container){
-        parent_container = active_item_container;
-    }
-
-    return parent_container;
-}
-
 window.getAppUIDFromOrigin = async function(origin) {
     try {
         const response = await fetch(window.api_origin + "/auth/app-uid-from-origin", {

+ 43 - 0
src/helpers/determine_active_container_parent.js

@@ -0,0 +1,43 @@
+/**
+ * Copyright (C) 2024 Puter Technologies Inc.
+ *
+ * This file is part of Puter.
+ *
+ * Puter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+const determine_active_container_parent = function(){
+    // the container is either an ancestor of active element...
+    let parent_container = $(active_element).closest('.item-container');
+    // ... or a descendant of it...
+    if(parent_container.length === 0){
+        parent_container = $(active_element).find('.item-container');
+    }
+    // ... or siblings or cousins
+    if(parent_container.length === 0){
+        parent_container = $(active_element).closest('.window').find('.item-container');
+    }
+    // ... or the active element itself (if it's a container)
+    if(parent_container.length === 0 && active_element && $(active_element).hasClass('item-container')){
+        parent_container = $(active_element);
+    }
+    // ... or if there is no active element, the selected item that is not blurred
+    if(parent_container.length === 0 && active_item_container){
+        parent_container = active_item_container;
+    }
+
+    return parent_container;
+}
+
+export default determine_active_container_parent;

+ 78 - 0
src/helpers/new_context_menu_item.js

@@ -0,0 +1,78 @@
+/**
+ * Copyright (C) 2024 Puter Technologies Inc.
+ *
+ * This file is part of Puter.
+ *
+ * Puter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+
+/**
+ * Returns a context menu item to create a new folder and a variety of file types.
+ * 
+ * @param {string} dirname - The directory path to create the item in
+ * @param {HTMLElement} append_to_element - Element to append the new item to 
+ * @returns {Object} The context menu item object
+ */
+
+const new_context_menu_item = function(dirname, append_to_element){
+    return {
+        html: i18n('new'),
+        items: [
+            // New Folder
+            {
+                html: i18n('new_folder'),
+                icon: `<img src="${html_encode(window.icons['folder.svg'])}" class="ctx-item-icon">`,
+                onClick: function(){
+                    create_folder(dirname, append_to_element);
+                }
+            },
+            // divider
+            '-',
+            // Text Document
+            {
+                html: i18n('text_document'),
+                icon: `<img src="${html_encode(window.icons['file-text.svg'])}" class="ctx-item-icon">`,
+                onClick: async function(){
+                    create_file({dirname: dirname, append_to_element: append_to_element, name: 'New File.txt'});
+                }
+            },
+            // HTML Document
+            {
+                html: i18n('html_document'),
+                icon: `<img src="${html_encode(window.icons['file-html.svg'])}" class="ctx-item-icon">`,
+                onClick: async function(){
+                    create_file({dirname: dirname, append_to_element: append_to_element, name: 'New File.html'});
+                }
+            },
+            // JPG Image
+            {
+                html: i18n('jpeg_image'),
+                icon: `<img src="${html_encode(window.icons['file-image.svg'])}" class="ctx-item-icon">`,
+                onClick: async function(){
+                    var canvas = document.createElement("canvas");
+
+                    canvas.width = 800;
+                    canvas.height = 600;
+                    
+                    canvas.toBlob((blob) =>{
+                        create_file({dirname: dirname, append_to_element: append_to_element, name: 'New Image.jpg', content: blob});
+                    });
+                }
+            },
+        ]
+    }
+}
+
+export default new_context_menu_item;

+ 617 - 0
src/i18n/i18n.js

@@ -0,0 +1,617 @@
+window.locale = 'en';
+
+window.i18n = function (key, replacements = [], encode_html = true) {
+    if(typeof replacements === 'boolean' && encode_html === undefined){
+        encode_html = replacements;
+        replacements = [];
+    }else if(Array.isArray(replacements) === false){
+        replacements = [replacements];
+    }
+
+    // if locale is not set, default to en
+    if(!window.translations[window.locale])
+        window.locale = 'en';
+
+    let str = window.translations[window.locale][key];
+    
+    if (!str) {
+        str = key;
+    }
+    str = encode_html ? html_encode(str) : str;
+    // replace %% occurrences with the values in replacements
+    // %% is for simple text replacements
+    // %strong% is for <strong> tags
+    // e.g. "Hello, %strong%" => "Hello, <strong>World</strong>"
+    // e.g. "Hello, %%" => "Hello, World"
+    // e.g. "Hello, %strong%, %%!" => "Hello, <strong>World</strong>, Universe!"
+    for (let i = 0; i < replacements.length; i++) {
+        // sanitize the replacement
+        replacements[i] = encode_html ? html_encode(replacements[i]) : replacements[i];
+        // find first occurrence of %strong%
+        let index = str.indexOf('%strong%');
+        // find first occurrence of %%
+        let index2 = str.indexOf('%%');
+        // decide which one to replace
+        if (index === -1 && index2 === -1) {
+            break;
+        } else if (index === -1) {
+            str = str.replace('%%', replacements[i]);
+        } else if (index2 === -1) {
+            str = str.replace('%strong%', '<strong>' + replacements[i] + '</strong>');
+        } else if (index < index2) {
+            str = str.replace('%strong%', '<strong>' + replacements[i] + '</strong>');
+        } else {
+            str = str.replace('%%', replacements[i]);
+        }
+    }
+    return str;
+}
+
+window.translations = {
+    en: {
+        access_granted_to: "Access Granted To",
+        add_existing_account: "Add Existing Account",
+        all_fields_required: 'All fields are required.',
+        apply: "Apply",
+        ascending: 'Ascending',
+        background: "Background",
+        browse: "Browse",
+        cancel: 'Cancel',
+        center: 'Center',
+        change_desktop_background: 'Change desktop background…',
+        change_password: "Change Password",
+        change_username: "Change Username",
+        close_all_windows: "Close All Windows",
+        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_new_password: "Confirm New Password",
+        contact_us: "Contact Us",
+        contain: 'Contain',
+        continue: "Continue",
+        copy: 'Copy',
+        copy_link: "Copy Link",
+        copying: "Copying",
+        cover: 'Cover',
+        create_account: "Create Account",
+        create_free_account: "Create Free Account",
+        create_shortcut: "Create Shortcut",
+        current_password: "Current Password",
+        cut: 'Cut',
+        date_modified: 'Date modified',
+        delete: 'Delete',
+        delete_permanently: "Delete Permanently",
+        deploy_as_app: 'Deploy as app',
+        descending: 'Descending',
+        desktop_background_fit: "Fit",
+        dir_published_as_website: `%strong% has been published to:`,
+        disassociate_dir: "Disassociate Directory",
+        download: 'Download',
+        downloading: "Downloading",
+        email: "Email",
+        email_or_username: "Email or Username",
+        empty_trash: 'Empty Trash',
+        empty_trash_confirmation: `Are you sure you want to permanently delete the items in Trash?`,
+        emptying_trash: 'Emptying Trash…',
+        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.",
+        forgot_pass_c2a: "Forgot password?",
+        from: "From",
+        general: "General",
+        get_a_copy_of_on_puter: `Get a copy of '%%' on Puter.com!`,
+        get_copy_link: 'Get Copy Link',
+        hide_all_windows: "Hide All Windows",
+        html_document: 'HTML document',
+        image: 'Image',
+        invite_link: "Invite Link",
+        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.`,
+        jpeg_image: 'JPEG image',
+        keep_in_taskbar: 'Keep in Taskbar',
+        log_in: "Log In",
+        log_out: 'Log Out',
+        move: 'Move',
+        moving: "Moving",
+        my_websites: "My Websites",
+        name: 'Name',
+        name_cannot_be_empty: 'Name cannot be empty.',
+        name_cannot_contain_double_period: "Name can not be the '..' character.",
+        name_cannot_contain_period: "Name can not be the '.' character.",
+        name_cannot_contain_slash: "Name cannot contain the '/' character.",
+        name_must_be_string: "Name can only be a string.",
+        name_too_long: `Name can not be longer than %% characters.`,
+        new: 'New',
+        new_folder: 'New folder',
+        new_password: "New Password",
+        new_username: "New Username",
+        no_dir_associated_with_site: 'No directory associated with this address.',
+        no_websites_published: "You have not published any websites yet.",
+        ok: 'OK',
+        open: "Open",
+        open_in_new_tab: "Open in New Tab",
+        open_in_new_window: "Open in New Window",
+        open_with: "Open With",
+        password: "Password",
+        password_changed: "Password changed.",
+        passwords_do_not_match: '`New Password` and `Confirm New Password` do not match.',
+        paste: 'Paste',
+        paste_into_folder: "Paste Into Folder",
+        pick_name_for_website: "Pick a name for your website:",
+        picture: "Picture",
+        powered_by_puter_js: `Powered by <a href="https://docs.puter.com/" target="_blank">Puter.js</a>`,
+        preparing: "Preparing...",
+        preparing_for_upload: "Preparing for upload...",
+        properties: "Properties",
+        publish: "Publish",
+        publish_as_website: 'Publish as website',
+        recent: "Recent",
+        recover_password: "Recover Password",
+        refer_friends_c2a: "Get 1 GB for every friend who creates and confirms an account on Puter. Your friend will get 1 GB too!",
+        refer_friends_social_media_c2a: `Get 1 GB of free storage on Puter.com!`,
+        refresh: 'Refresh',
+        release_address_confirmation: `Are you sure you want to release this address?`,
+        remove_from_taskbar:'Remove from Taskbar',
+        rename: 'Rename',
+        repeat: 'Repeat',
+        resend_confirmation_code: "Re-send Confirmation Code",
+        restore: "Restore",
+        save_account_to_get_copy_link: "Please create an account to proceed.",
+        save_account_to_publish: 'Please create an account to proceed.',
+        save_session_c2a: 'Create an account to save your current session and avoid losing your work.',
+        scan_qr_c2a: 'Scan the code below to log into this session from other devices',
+        select: "Select",
+        select_color: 'Select color…',
+        send: "Send",
+        send_password_recovery_email: "Send Password Recovery Email",
+        session_saved: "Thank you for creating an account. This session has been saved.",
+        set_new_password: "Set New Password",
+        share_to: "Share to",
+        show_all_windows: "Show All Windows",
+        show_hidden: 'Show hidden',
+        sign_in_with_puter: "Sign in with Puter",
+        sign_up: "Sign Up",
+        signing_in: "Signing in…",
+        size: 'Size',
+        sort_by: 'Sort by',
+        start: 'Start',
+        taking_longer_than_usual: 'Taking a little longer than usual. Please wait...',
+        text_document: 'Text document',
+        tos_fineprint: `By clicking 'Create Free Account' you agree to Puter's <a href="https://puter.com/terms" target="_blank">Terms of Service</a> and <a href="https://puter.com/privacy" target="_blank">Privacy Policy</a>.`,
+        trash: 'Trash',
+        type: 'Type',
+        undo: 'Undo',
+        unzip: "Unzip",
+        upload: 'Upload',
+        upload_here: 'Upload here',
+        username: "Username",
+        username_changed: 'Username updated successfully.',
+        versions: "Versions",
+        yes_release_it: 'Yes, Release It',
+        you_have_been_referred_to_puter_by_a_friend: "You have been referred to Puter by a friend!",
+        zip: "Zip",
+    },
+    // farsi
+    fa: {
+        access_granted_to: "دسترسی داده شده به",
+        add_existing_account: "افزودن حساب کاربری موجود",
+        all_fields_required: 'تمامی فیلدها الزامی هستند.',
+        apply: "اعمال",
+        ascending: 'صعودی',
+        background: "پس زمینه",
+        browse: "مرور",
+        cancel: 'لغو',
+        center: 'مرکز',
+        change_desktop_background: 'تغییر پس زمینه دسکتاپ…',
+        change_password: "تغییر رمز عبور",
+        change_username: "تغییر نام کاربری",
+        close_all_windows: "بستن همه پنجره ها",
+        color: 'رنگ',
+        confirm_account_for_free_referral_storage_c2a: 'حساب کاربری خود را ایجاد کرده و آدرس ایمیل خود را تأیید کنید تا 1 گیگابایت فضای ذخیره سازی رایگان دریافت کنید. دوست شما هم 1 گیگابایت فضای ذخیره سازی رایگان دریافت خواهد کرد.',
+        confirm_new_password: "تأیید رمز عبور جدید",
+        contact_us: "تماس با ما",
+        contain: 'شامل',
+        continue: "ادامه",
+        copy: 'کپی',
+        copy_link: "کپی لینک",
+        copying: "کپی",
+        cover: 'جلد',
+        create_account: "ایجاد حساب کاربری",
+        create_free_account: "ایجاد حساب کاربری رایگان",
+        create_shortcut: "ایجاد میانبر",
+        current_password: "رمز عبور فعلی",
+        cut: 'برش',
+        date_modified: 'تاریخ تغییر',
+        delete: 'حذف',
+        delete_permanently: "حذف دائمی",
+        deploy_as_app: 'نصب به عنوان برنامه',
+        descending: 'نزولی',
+        desktop_background_fit: "متناسب",
+        dir_published_as_website: `%strong% منتشر شده به:`,
+        disassociate_dir: "قطع ارتباط دایرکتوری",
+        download: 'دانلود',
+        downloading: "دانلود",
+        email: "ایمیل",
+        email_or_username: "ایمیل یا نام کاربری",
+        empty_trash: 'خالی کردن سطل زباله',
+        empty_trash_confirmation: `آیا از حذف دائمی موارد در سطل زباله مطمئن هستید؟`,
+        emptying_trash: 'خالی کردن سطل زباله…',
+        feedback: "بازخورد",
+        feedback_c2a: "لطفا از فرم زیر برای ارسال بازخورد، نظرات و گزارش خطا استفاده کنید.",
+        feedback_sent_confirmation: "با تشکر از تماس شما. اگر ایمیلی به حساب کاربری شما متصل است، در اسرع وقت پاسخ خواهیم داد.",
+        forgot_pass_c2a: "رمز عبور را فراموش کرده اید؟",
+        from: "از",
+        general: "عمومی",
+        get_a_copy_of_on_puter: `یک نسخه از '%%' را در Puter.com بگیرید!`,
+        get_copy_link: 'گرفتن لینک کپی',
+        hide_all_windows: "پنهان کردن همه پنجره ها",
+        html_document: 'سند HTML',
+        image: 'تصویر',
+        invite_link: "لینک دعوت",
+        items_in_trash_cannot_be_renamed: `این مورد نمی تواند تغییر نام دهد زیرا در سطل زباله است. برای تغییر نام این مورد، ابتدا آن را از سطل زباله بیرون بکشید.`,
+        jpeg_image: 'تصویر JPEG',
+        keep_in_taskbar: 'در نوار وظایف نگه دارید',
+        log_in: "ورود",
+        log_out: 'خروج',
+        move: 'انتقال',
+        moving: "انتقال",
+        my_websites: "وبسایت های من",
+        name: 'نام',
+        name_cannot_be_empty: 'نام نمی تواند خالی باشد.',
+        name_cannot_contain_double_period: "نام نمی تواند شامل '..' باشد.",
+        name_cannot_contain_period: "نام نمی تواند شامل '.' باشد.",
+        name_cannot_contain_slash: "نام نمی تواند شامل '/' باشد.",
+        name_must_be_string: "نام فقط می تواند یک رشته باشد.",
+        name_too_long: `نام نمی تواند بیشتر از %% کاراکتر باشد.`,
+        new: 'جدید',
+        new_folder: 'پوشه جدید',
+        new_password: "رمز عبور جدید",
+        new_username: "نام کاربری جدید",
+        no_dir_associated_with_site: 'هیچ دایرکتوری مرتبط با این آدرس وجود ندارد.',
+        no_websites_published: "هنوز هیچ وبسایتی منتشر نکرده اید.",
+        ok: 'خوب',
+        open: "باز کردن",
+        open_in_new_tab: "در تب جدید باز کن",
+        open_in_new_window: "در پنجره جدید باز کن",
+        open_with: "باز کردن با",
+        password: "رمز عبور",
+        password_changed: "رمز عبور تغییر یافت.",
+        passwords_do_not_match: '`رمز عبور جدید` و `تأیید رمز عبور جدید` مطابقت ندارند.',
+        paste: 'چسباندن',
+        paste_into_folder: "چسباندن در پوشه",
+        pick_name_for_website: "یک نام برای وبسایت خود انتخاب کنید:",
+        picture: "تصویر",
+        powered_by_puter_js: `پشتیبانی شده توسط <a href="https://docs.puter.com/" target="_blank">Puter.js</a>`,
+        preparing: "در حال آماده سازی...",
+        preparing_for_upload: "آماده سازی برای بارگذاری...",
+        properties: "ویژگی ها",
+        publish: "انتشار",
+        publish_as_website: 'انتشار به عنوان وبسایت',
+        recent: "اخیر",
+        recover_password: "بازیابی رمز عبور",
+        refer_friends_c2a: "برای هر دوستی که حساب کاربری Puter ایجاد و تأیید کند، 1 گیگابایت دریافت کنید. دوست شما هم 1 گیگابایت دریافت خواهد کرد!",
+        refer_friends_social_media_c2a: `1 گیگابایت فضای ذخیره سازی رایگان را در Puter.com بگیرید!`,
+        refresh: 'تازه کردن',
+        release_address_confirmation: `آیا مطمئن هستید که می خواهید این آدرس را آزاد کنید؟`,
+        remove_from_taskbar:'از نوار وظایف حذف کن',
+        rename: 'تغییر نام',
+        repeat: 'تکرار',
+        resend_confirmation_code: "ارسال مجدد کد تأیید",
+        restore: "بازیابی",
+        save_account_to_get_copy_link: "لطفا برای ادامه یک حساب کاربری ایجاد کنید.",
+        save_account_to_publish: 'لطفا برای ادامه یک حساب کاربری ایجاد کنید.',
+        save_session_c2a: 'برای ذخیره جلسه فعلی و جلوگیری از از دست دادن کار خود یک حساب کاربری ایجاد کنید.',
+        scan_qr_c2a: 'کد زیر را از دستگاه های دیگر اسکن کنید تا به این جلسه وارد شوید',
+        select: "انتخاب",
+        select_color: 'انتخاب رنگ…',
+        send: "ارسال",
+        send_password_recovery_email: "ارسال ایمیل بازیابی رمز عبور",
+        session_saved: "با تشکر از ایجاد حساب کاربری. این جلسه ذخیره شده است.",
+        set_new_password: "تنظیم رمز عبور جدید",
+        share_to: "اشتراک گذاری به",
+        show_all_windows: "نمایش همه پنجره ها",
+        show_hidden: 'نمایش مخفی',
+        sign_in_with_puter: "ورود با Puter",
+        sign_up: "ثبت نام",
+        signing_in: "ورود…",
+        size: 'اندازه',
+        sort_by: 'مرتب سازی بر اساس',
+        start: 'شروع',
+        taking_longer_than_usual: 'کمی بیشتر از معمول طول می کشد. لطفا صبر کنید...',
+        text_document: 'سند متنی',
+        tos_fineprint: `با کلیک بر روی 'ایجاد حساب کاربری رایگان' شما با <a href="https://puter.com/terms" target="_blank">شرایط خدمات</a> و <a href="https://puter.com/privacy" target="_blank">سیاست حفظ حریم خصوصی</a> Puter موافقت می کنید.`,
+        trash: 'سطل زباله',
+        type: 'نوع',
+        undo: 'بازگشت',
+        unzip: "باز کردن فایل فشرده",
+        upload: 'بارگذاری',
+        upload_here: 'اینجا بارگذاری کنید',
+        username: "نام کاربری",
+        username_changed: 'نام کاربری با موفقیت به روز شد.',
+        versions: "نسخه ها",
+        yes_release_it: 'بله، آن را آزاد کن',
+        you_have_been_referred_to_puter_by_a_friend: "شما توسط یک دوست به Puter معرفی شده اید!",
+        zip: "فشرده سازی",
+    },
+    // korean
+    ko: {
+        access_granted_to: "접근 권한 부여",
+        add_existing_account: "기존 계정 추가",
+        all_fields_required: '모든 필드는 필수입니다.',
+        apply: "적용",
+        ascending: '오름차순',
+        background: "배경",
+        browse: "찾아보기",
+        cancel: '취소',
+        center: '중앙',
+        change_desktop_background: '바탕 화면 배경 변경…',
+        change_password: "비밀번호 변경",
+        change_username: "사용자 이름 변경",
+        close_all_windows: "모든 창 닫기",
+        color: '색상',
+        confirm_account_for_free_referral_storage_c2a: '계정을 생성하고 이메일 주소를 확인하여 1GB의 무료 저장 공간을 받으십시오. 친구도 1GB의 무료 저장 공간을 받게 됩니다.',
+        confirm_new_password: "새 비밀번호 확인",
+        contact_us: "문의하기",
+        contain: '포함',
+        continue: "계속",
+        copy: '복사',
+        copy_link: "링크 복사",
+        copying: "복사 중",
+        cover: '표지',
+        create_account: "계정 생성",
+        create_free_account: "무료 계정 생성",
+        create_shortcut: "바로 가기 만들기",
+        current_password: "현재 비밀번호",
+        cut: '잘라내기',
+        date_modified: '수정한 날짜',
+        delete: '삭제',
+        delete_permanently: "영구 삭제",
+        deploy_as_app: '앱으로 배포',
+        descending: '내림차순',
+        desktop_background_fit: "맞추기",
+        dir_published_as_website: `%strong% 다음에 게시되었습니다:`,
+        disassociate_dir: "디렉토리 연결 해제",
+        download: '다운로드',
+        downloading: "다운로드 중",
+        email: "이메일",
+        email_or_username: "이메일 또는 사용자 이름",
+        empty_trash: '휴지통 비우기',
+        empty_trash_confirmation: `휴지통의 항목을 영구적으로 삭제하시겠습니까?`,
+        emptying_trash: '휴지통 비우는 중…',
+        feedback: "피드백",
+        feedback_c2a: "아래 양식을 사용하여 피드백, 의견 및 버그 보고를 보내십시오.",
+        feedback_sent_confirmation: "문의해 주셔서 감사합니다. 계정에 이메일이 연결되어 있으면 가능한 빨리 회신 드리겠습니다.",
+        forgot_pass_c2a: "비밀번호를 잊으셨나요?",
+        from: "보낸 사람",
+        general: "일반",
+        get_a_copy_of_on_puter: `Puter.com에서 '%%'의 사본을 받으세요!`,
+        get_copy_link: '링크 복사',
+        hide_all_windows: "모든 창 숨기기",
+        html_document: 'HTML 문서',
+        image: '이미지',
+        invite_link: "초대 링크",
+        items_in_trash_cannot_be_renamed: `이 항목은 휴지통에 있기 때문에 이름을 바꿀 수 없습니다. 이 항목의 이름을 바꾸려면 먼저 휴지통에서 끌어내십시오.`,
+        jpeg_image: 'JPEG 이미지',
+        keep_in_taskbar: '작업 표시줄에 유지',
+        log_in: "로그인",
+        log_out: '로그아웃',
+        move: '이동',
+        moving: "이동 중",
+        my_websites: "내 웹사이트",
+        name: '이름',
+        name_cannot_be_empty: '이름은 비워둘 수 없습니다.',
+        name_cannot_contain_double_period: "이름은 '..' 문자일 수 없습니다.",
+        name_cannot_contain_period: "이름은 '.' 문자일 수 없습니다.",
+        name_cannot_contain_slash: "이름에 '/' 문자를 포함할 수 없습니다.",
+        name_must_be_string: "이름은 문자열만 가능합니다.",
+        name_too_long: `이름은 %%자보다 길 수 없습니다.`,
+        new: '새로운',
+        new_folder: '새 폴더',
+        new_password: "새 비밀번호",
+        new_username: "새 사용자 이름",
+        no_dir_associated_with_site: '이 주소에 연결된 디렉토리가 없습니다.',
+        no_websites_published: "아직 웹사이트를 게시하지 않았습니다.",
+        ok: '확인',
+        open: "열기",
+        open_in_new_tab: "새 탭에서 열기",
+        open_in_new_window: "새 창에서 열기",
+        open_with: "열기 방법",
+        password: "비밀번호",
+        password_changed: "비밀번호가 변경되었습니다.",
+        passwords_do_not_match: '`새 비밀번호`와 `새 비밀번호 확인`이 일치하지 않습니다.',
+        paste: '붙여넣기',
+        paste_into_folder: "폴더에 붙여넣기",
+        pick_name_for_website: "웹사이트 이름을 선택하세요:",
+        picture: "사진",
+        powered_by_puter_js: `Powered by <a href="https://docs.puter.com/" target="_blank">Puter.js</a>`,
+        preparing: "준비 중...",
+        preparing_for_upload: "업로드 준비 중...",
+        properties: "속성",
+        publish: "게시",
+        publish_as_website: '웹사이트로 게시',
+        recent: "최근",
+        recover_password: "비밀번호 찾기",
+        refer_friends_c2a: "Puter에서 계정을 생성하고 확인한 친구마다 1GB를 받으십시오. 친구도 1GB를 받게 됩니다!",
+        refer_friends_social_media_c2a: `Puter.com에서 1GB의 무료 저장 공간을 받으십시오!`,
+        refresh: '새로 고침',
+        release_address_confirmation: `이 주소를 해제하시겠습니까?`,
+        remove_from_taskbar:'작업 표시줄에서 제거',
+        rename: '이름 바꾸기',
+        repeat: '반복',
+        resend_confirmation_code: "확인 코드 다시 보내기",
+        restore: "복원",
+        save_account_to_get_copy_link: "계속하려면 계정을 생성하십시오.",
+        save_account_to_publish: '계속하려면 계정을 생성하십시오.',
+        save_session_c2a: '현재 세션을 저장하고 작업을 잃지 않으려면 계정을 생성하십시오.',
+        scan_qr_c2a: '다른 기기에서 이 세션으로 로그인하려면 아래 코드를 스캔하십시오',
+        select: "선택",
+        select_color: '색상 선택…',
+        send: "보내기",
+        send_password_recovery_email: "비밀번호 복구 이메일 보내기",
+        session_saved: "계정을 생성해 주셔서 감사합니다. 이 세션이 저장되었습니다.",
+        set_new_password: "새 비밀번호 설정",
+        share_to: "공유",
+        show_all_windows: "모든 창 표시",
+        show_hidden: '숨김 항목 표시',
+        sign_in_with_puter: "Puter로 로그인",
+        sign_up: "가입",
+        signing_in: "로그인 중…",
+        size: '크기',
+        sort_by: '정렬 기준',
+        start: '시작',
+        taking_longer_than_usual: '보통보다 조금 더 오래 걸립니다. 잠시만 기다려 주십시오...',
+        text_document: '텍스트 문서',
+        tos_fineprint: `무료 계정 생성을 클릭하면 Puter의 <a href="https://puter.com/terms" target="_blank">서비스 약관</a>과 <a href="https://puter.com/privacy" target="_blank">개인정보 보호정책</a>에 동의하는 것입니다.`,
+        trash: '휴지통',
+        type: '유형',
+        undo: '실행 취소',
+        unzip: "압축 해제",
+        upload: '업로드',
+        upload_here: '여기에 업로드',
+        username: "사용자 이름",
+        username_changed: '사용자 이름이 성공적으로 업데이트되었습니다.',
+        versions: "버전",
+        yes_release_it: '예, 해제합니다',
+        you_have_been_referred_to_puter_by_a_friend: "친구가 Puter로 추천했습니다!",
+        zip: "압축",
+    },
+    zh: {
+        access_granted_to: "访问授权给",
+        add_existing_account: "添加现有帐户",
+        all_fields_required: '所有字段都是必需的。',
+        apply: "应用",
+        ascending: '升序',
+        background: "背景",
+        browse: "浏览",
+        cancel: '取消',
+        center: '中心',
+        change_desktop_background: '更改桌面背景…',
+        change_password: "更改密码",
+        change_username: "更改用户名",
+        close_all_windows: "关闭所有窗口",
+        color: '颜色',
+        confirm_account_for_free_referral_storage_c2a: '创建帐户并确认您的电子邮件地址,以获得1 GB的免费存储空间。您的朋友也将获得1 GB的免费存储空间。',
+        confirm_new_password: "确认新密码",
+        contact_us: "联系我们",
+        contain: '包含',
+        continue: "继续",
+        copy: '复制',
+        copy_link: "复制链接",
+        copying: "复制",
+        cover: '封面',
+        create_account: "创建帐户",
+        create_free_account: "创建免费帐户",
+        create_shortcut: "创建快捷方式",
+        current_password: "当前密码",
+        cut: '剪切',
+        date_modified: '修改日期',
+        delete: '删除',
+        delete_permanently: "永久删除",
+        deploy_as_app: '部署为应用',
+        descending: '降序',
+        desktop_background_fit: "适合",
+        dir_published_as_website: `%strong% 已发布到:`,
+        disassociate_dir: "取消关联目录",
+        download: '下载',
+        downloading: "下载",
+        email: "电子邮件",
+        email_or_username: "电子邮件或用户名",
+        empty_trash: '清空回收站',
+        empty_trash_confirmation: `您确定要永久删除回收站中的项目吗?`,
+        emptying_trash: '清空回收站…',
+        feedback: "反馈",
+        feedback_c2a: "请使用下面的表格向我们发送您的反馈、评论和错误报告。",
+        feedback_sent_confirmation: "感谢您与我们联系。如果您的帐户关联有电子邮件,我们会尽快回复您。",
+        forgot_pass_c2a: "忘记密码?",
+        from: "从",
+        general: "一般",
+        get_a_copy_of_on_puter: `在 Puter.com 上获取 '%%' 的副本!`,
+        get_copy_link: '获取复制链接',
+        hide_all_windows: "隐藏所有窗口",
+        html_document: 'HTML 文档',
+        image: '图像',
+        invite_link: "邀请链接",
+        items_in_trash_cannot_be_renamed: `此项目无法重命名,因为它在回收站中。要重命名此项目,请先将其拖出回收站。`,
+        jpeg_image: 'JPEG 图像',
+        keep_in_taskbar: '保持在任务栏',
+        log_in: "登录",
+        log_out: '登出',
+        move: '移动',
+        moving: "移动",
+        my_websites: "我的网站",
+        name: '名称',
+        name_cannot_be_empty: '名称不能为空。',
+        name_cannot_contain_double_period: "名称不能是'..'字符。",
+        name_cannot_contain_period: "名称不能是'.'字符。",
+        name_cannot_contain_slash: "名称不能包含'/'字符。",
+        name_must_be_string: "名称只能是字符串。",
+        name_too_long: `名称不能超过 %% 个字符。`,
+        new: '新',
+        new_folder: '新文件夹',
+        new_password: "新密码",
+        new_username: "新用户名",
+        no_dir_associated_with_site: '此地址没有关联的目录。',
+        no_websites_published: "您尚未发布任何网站。",
+        ok: '好的',
+        open: "打开",
+        open_in_new_tab: "在新标签页中打开",
+        open_in_new_window: "在新窗口中打开",
+        open_with: "打开方式",
+        password: "密码",
+        password_changed: "密码已更改。",
+        passwords_do_not_match: '`新密码` 和 `确认新密码` 不匹配。',
+        paste: '粘贴',
+        paste_into_folder: "粘贴到文件夹",
+        pick_name_for_website: "为您的网站选择一个名称:",
+        picture: "图片",
+        powered_by_puter_js: `由 <a href="https://docs.puter.com/" target="_blank">Puter.js</a> 提供支持`,
+        preparing: "准备中...",
+        preparing_for_upload: "准备上传...",
+        properties: "属性",
+        publish: "发布",
+        publish_as_website: '发布为网站',
+        recent: "最近",
+        recover_password: "找回密码",
+        refer_friends_c2a: "每个创建并确认 Puter 帐户的朋友都会为您获得 1 GB。您的朋友也将获得 1 GB!",
+        refer_friends_social_media_c2a: `在 Puter.com 上获取 1 GB 的免费存储空间!`,
+        refresh: '刷新',
+        release_address_confirmation: `您确定要释放此地址吗?`,
+        remove_from_taskbar:'从任务栏中删除',
+        rename: '重命名',
+        repeat: '重复',
+        resend_confirmation_code: "重新发送确认码",
+        restore: "还原",
+        save_account_to_get_copy_link: "请创建帐户以继续。",
+        save_account_to_publish: '请创建帐户以继续。',
+        save_session_c2a: '创建帐户以保存当前会话,避免丢失工作。',
+        scan_qr_c2a: '扫描下面的代码以从其他设备登录此会话',
+        select: "选择",
+        select_color: '选择颜色…',
+        send: "发送",
+        send_password_recovery_email: "发送密码恢复电子邮件",
+        session_saved: "感谢您创建帐户。此会话已保存。",
+        set_new_password: "设置新密码",
+        share_to: "分享到",
+        show_all_windows: "显示所有窗口",
+        show_hidden: '显示隐藏',
+        sign_in_with_puter: "使用 Puter 登录",
+        sign_up: "注册",
+        signing_in: "登录中…",
+        size: '大小',
+        sort_by: '排序方式',
+        start: '开始',
+        taking_longer_than_usual: '需要的时间比平时长一点。请稍等...',
+        text_document: '文本文档',
+        tos_fineprint: `点击“创建免费帐户”即表示您同意 Puter 的 <a href="https://puter.com/terms" target="_blank">服务条款</a> 和 <a href="https://puter.com/privacy" target="_blank">隐私政策</a>。`,
+        trash: '回收站',
+        type: '类型',
+        undo: '撤销',
+        unzip: "解压缩",
+        upload: '上传',
+        upload_here: '在此上传',
+        username: "用户名",
+        username_changed: '用户名已成功更新。',
+        versions: "版本",
+        yes_release_it: '是的,释放它',
+        you_have_been_referred_to_puter_by_a_friend: "您已经被朋友推荐到 Puter!",
+        zip: "压缩",
+    },
+}

+ 40 - 0
src/initgui.js

@@ -33,6 +33,7 @@ import UIWindowChangeUsername from './UI/UIWindowChangeUsername.js';
 import update_last_touch_coordinates from './helpers/update_last_touch_coordinates.js';
 import update_title_based_on_uploads from './helpers/update_title_based_on_uploads.js';
 import PuterDialog from './UI/PuterDialog.js';
+import determine_active_container_parent from './helpers/determine_active_container_parent.js';
 
 window.initgui = async function(){
     let url = new URL(window.location);
@@ -45,6 +46,10 @@ window.initgui = async function(){
     if(window.api_origin && puter.APIOrigin !== window.api_origin)
         puter.setAPIOrigin(api_origin);
 
+    // determine locale
+    const userLang = navigator.language || navigator.userLanguage || 'en';
+    window.locale = userLang?.split('-')[0] ?? 'en';
+
     // Checks the type of device the user is on (phone, tablet, or desktop).
     // Depending on the device type, it sets a class attribute on the body tag 
     // to style or script the page differently for each device type.
@@ -76,6 +81,13 @@ window.initgui = async function(){
     const url_paths = window.location.pathname.split('/').filter(element => element);
     if(url_paths[0]?.toLocaleLowerCase() === 'app' && url_paths[1]){
         window.app_launched_from_url = url_paths[1];
+
+        // get query params, any param that doesn't start with 'puter.' will be passed to the app
+        window.app_query_params = {};
+        for (let [key, value] of url_query_params) {
+            if(!key.startsWith('puter.'))
+                app_query_params[key] = value;
+        }
     }
 
     //--------------------------------------------------------------------------------------
@@ -1978,4 +1990,32 @@ function requestOpenerOrigin() {
 
 $(document).on('click', '.generic-close-window-button', function(e){
     $(this).closest('.window').close();
+});
+
+// Re-calculate desktop height and width on window resize and re-position the login and signup windows
+$(window).on("resize", function () {
+    // If host env is popup, don't continue because the popup window has its own resize requirements.
+    if (window.embedded_in_popup)
+        return;
+
+    const ratio = window.desktop_width / window.innerWidth;
+
+    window.desktop_height = window.innerHeight - window.toolbar_height - window.taskbar_height;
+    window.desktop_width = window.innerWidth;
+
+    // Re-center the login window
+    const top = $(".window-login").position()?.top;
+    const width = $(".window-login").width();
+    $(".window-login").css({
+        left: (window.desktop_width - width) / 2,
+        top: top / ratio,
+    });
+
+    // Re-center the create account window
+    const top2 = $(".window-signup").position()?.top;
+    const width2 = $(".window-signup").width();
+    $(".window-signup").css({
+        left: (window.desktop_width - width2) / 2,
+        top: top2 / ratio,
+    });
 });

+ 323 - 0
src/lib/jquery.menu-aim.js

@@ -0,0 +1,323 @@
+/**
+ * menu-aim is a jQuery plugin for dropdown menus that can differentiate
+ * between a user trying hover over a dropdown item vs trying to navigate into
+ * a submenu's contents.
+ *
+ * menu-aim assumes that you have are using a menu with submenus that expand
+ * to the menu's right. It will fire events when the user's mouse enters a new
+ * dropdown item *and* when that item is being intentionally hovered over.
+ *
+ * __________________________
+ * | Monkeys  >|   Gorilla  |
+ * | Gorillas >|   Content  |
+ * | Chimps   >|   Here     |
+ * |___________|____________|
+ *
+ * In the above example, "Gorillas" is selected and its submenu content is
+ * being shown on the right. Imagine that the user's cursor is hovering over
+ * "Gorillas." When they move their mouse into the "Gorilla Content" area, they
+ * may briefly hover over "Chimps." This shouldn't close the "Gorilla Content"
+ * area.
+ *
+ * This problem is normally solved using timeouts and delays. menu-aim tries to
+ * solve this by detecting the direction of the user's mouse movement. This can
+ * make for quicker transitions when navigating up and down the menu. The
+ * experience is hopefully similar to amazon.com/'s "Shop by Department"
+ * dropdown.
+ *
+ * Use like so:
+ *
+ *      $("#menu").menuAim({
+ *          activate: $.noop,  // fired on row activation
+ *          deactivate: $.noop  // fired on row deactivation
+ *      });
+ *
+ *  ...to receive events when a menu's row has been purposefully (de)activated.
+ *
+ * The following options can be passed to menuAim. All functions execute with
+ * the relevant row's HTML element as the execution context ('this'):
+ *
+ *      .menuAim({
+ *          // Function to call when a row is purposefully activated. Use this
+ *          // to show a submenu's content for the activated row.
+ *          activate: function() {},
+ *
+ *          // Function to call when a row is deactivated.
+ *          deactivate: function() {},
+ *
+ *          // Function to call when mouse enters a menu row. Entering a row
+ *          // does not mean the row has been activated, as the user may be
+ *          // mousing over to a submenu.
+ *          enter: function() {},
+ *
+ *          // Function to call when mouse exits a menu row.
+ *          exit: function() {},
+ *
+ *          // Selector for identifying which elements in the menu are rows
+ *          // that can trigger the above events. Defaults to "> li".
+ *          rowSelector: "> li",
+ *
+ *          // You may have some menu rows that aren't submenus and therefore
+ *          // shouldn't ever need to "activate." If so, filter submenu rows w/
+ *          // this selector. Defaults to "*" (all elements).
+ *          submenuSelector: "*",
+ *
+ *          // Direction the submenu opens relative to the main menu. Can be
+ *          // left, right, above, or below. Defaults to "right".
+ *          submenuDirection: "right"
+ *      });
+ *
+ * https://github.com/kamens/jQuery-menu-aim
+*/
+(function($) {
+
+  $.fn.menuAim = function(opts) {
+      // Initialize menu-aim for all elements in jQuery collection
+      this.each(function() {
+          init.call(this, opts);
+      });
+
+      return this;
+  };
+
+  function init(opts) {
+      var $menu = $(this),
+          activeRow = null,
+          mouseLocs = [],
+          lastDelayLoc = null,
+          timeoutId = null,
+          options = $.extend({
+              rowSelector: "> li",
+              submenuSelector: "*",
+              submenuDirection: $.noop,
+              tolerance: 75,  // bigger = more forgivey when entering submenu
+              enter: $.noop,
+              exit: $.noop,
+              activate: $.noop,
+              deactivate: $.noop,
+              exitMenu: $.noop
+          }, opts);
+
+      var MOUSE_LOCS_TRACKED = 3,  // number of past mouse locations to track
+          DELAY = 300;  // ms delay when user appears to be entering submenu
+
+      /**
+       * Keep track of the last few locations of the mouse.
+       */
+      var mousemoveDocument = function(e) {
+              mouseLocs.push({x: e.pageX, y: e.pageY});
+
+              if (mouseLocs.length > MOUSE_LOCS_TRACKED) {
+                  mouseLocs.shift();
+              }
+          };
+
+      /**
+       * Cancel possible row activations when leaving the menu entirely
+       */
+      var mouseleaveMenu = function() {
+              if (timeoutId) {
+                  clearTimeout(timeoutId);
+              }
+
+              // If exitMenu is supplied and returns true, deactivate the
+              // currently active row on menu exit.
+              if (options.exitMenu(this)) {
+                  if (activeRow) {
+                      options.deactivate(activeRow);
+                  }
+
+                  activeRow = null;
+              }
+          };
+
+      /**
+       * Trigger a possible row activation whenever entering a new row.
+       */
+      var mouseenterRow = function() {
+              if (timeoutId) {
+                  // Cancel any previous activation delays
+                  clearTimeout(timeoutId);
+              }
+
+              options.enter(this);
+              possiblyActivate(this);
+          },
+          mouseleaveRow = function() {
+              options.exit(this);
+          };
+
+      /*
+       * Immediately activate a row if the user clicks on it.
+       */
+      var clickRow = function() {
+              activate(this);
+          };
+
+      /**
+       * Activate a menu row.
+       */
+      var activate = function(row) {
+              if (row == activeRow) {
+                  return;
+              }
+
+              if (activeRow) {
+                  options.deactivate(activeRow);
+              }
+
+
+              options.activate(row);
+              activeRow = row;
+          };
+
+      /**
+       * Possibly activate a menu row. If mouse movement indicates that we
+       * shouldn't activate yet because user may be trying to enter
+       * a submenu's content, then delay and check again later.
+       */
+      var possiblyActivate = function(row) {
+              var delay = activationDelay();
+
+              if (delay) {
+                  timeoutId = setTimeout(function() {
+                      possiblyActivate(row);
+                  }, delay);
+              } else {
+                  activate(row);
+              }
+          };
+
+      /**
+       * Return the amount of time that should be used as a delay before the
+       * currently hovered row is activated.
+       *
+       * Returns 0 if the activation should happen immediately. Otherwise,
+       * returns the number of milliseconds that should be delayed before
+       * checking again to see if the row should be activated.
+       */
+      var activationDelay = function() {
+              if (!activeRow || !$(activeRow).is(options.submenuSelector)) {
+                  // If there is no other submenu row already active, then
+                  // go ahead and activate immediately.
+                  return 0;
+              }
+
+              var offset = $menu.offset(),
+                  upperLeft = {
+                      x: offset.left,
+                      y: offset.top - options.tolerance
+                  },
+                  upperRight = {
+                      x: offset.left + $menu.outerWidth(),
+                      y: upperLeft.y
+                  },
+                  lowerLeft = {
+                      x: offset.left,
+                      y: offset.top + $menu.outerHeight() + options.tolerance
+                  },
+                  lowerRight = {
+                      x: offset.left + $menu.outerWidth(),
+                      y: lowerLeft.y
+                  },
+                  loc = mouseLocs[mouseLocs.length - 1],
+                  prevLoc = mouseLocs[0];
+
+              if (!loc) {
+                  return 0;
+              }
+
+              if (!prevLoc) {
+                  prevLoc = loc;
+              }
+
+              if (prevLoc.x < offset.left || prevLoc.x > lowerRight.x ||
+                  prevLoc.y < offset.top || prevLoc.y > lowerRight.y) {
+                  // If the previous mouse location was outside of the entire
+                  // menu's bounds, immediately activate.
+                  return 0;
+              }
+
+              if (lastDelayLoc &&
+                      loc.x == lastDelayLoc.x && loc.y == lastDelayLoc.y) {
+                  // If the mouse hasn't moved since the last time we checked
+                  // for activation status, immediately activate.
+                  return 0;
+              }
+
+              // Detect if the user is moving towards the currently activated
+              // submenu.
+              //
+              // If the mouse is heading relatively clearly towards
+              // the submenu's content, we should wait and give the user more
+              // time before activating a new row. If the mouse is heading
+              // elsewhere, we can immediately activate a new row.
+              //
+              // We detect this by calculating the slope formed between the
+              // current mouse location and the upper/lower right points of
+              // the menu. We do the same for the previous mouse location.
+              // If the current mouse location's slopes are
+              // increasing/decreasing appropriately compared to the
+              // previous's, we know the user is moving toward the submenu.
+              //
+              // Note that since the y-axis increases as the cursor moves
+              // down the screen, we are looking for the slope between the
+              // cursor and the upper right corner to decrease over time, not
+              // increase (somewhat counterintuitively).
+              function slope(a, b) {
+                  return (b.y - a.y) / (b.x - a.x);
+              };
+
+              var decreasingCorner = upperRight,
+                  increasingCorner = lowerRight;
+
+              // Our expectations for decreasing or increasing slope values
+              // depends on which direction the submenu opens relative to the
+              // main menu. By default, if the menu opens on the right, we
+              // expect the slope between the cursor and the upper right
+              // corner to decrease over time, as explained above. If the
+              // submenu opens in a different direction, we change our slope
+              // expectations.
+              if (options.submenuDirection() == "left") {
+                  decreasingCorner = lowerLeft;
+                  increasingCorner = upperLeft;
+              } else if (options.submenuDirection() == "below") {
+                  decreasingCorner = lowerRight;
+                  increasingCorner = lowerLeft;
+              } else if (options.submenuDirection() == "above") {
+                  decreasingCorner = upperLeft;
+                  increasingCorner = upperRight;
+              }
+
+              var decreasingSlope = slope(loc, decreasingCorner),
+                  increasingSlope = slope(loc, increasingCorner),
+                  prevDecreasingSlope = slope(prevLoc, decreasingCorner),
+                  prevIncreasingSlope = slope(prevLoc, increasingCorner);
+
+              if (decreasingSlope < prevDecreasingSlope &&
+                      increasingSlope > prevIncreasingSlope) {
+                  // Mouse is moving from previous location towards the
+                  // currently activated submenu. Delay before activating a
+                  // new menu row, because user may be moving into submenu.
+                  lastDelayLoc = loc;
+                  return DELAY;
+              }
+
+              lastDelayLoc = null;
+              return 0;
+          };
+
+      /**
+       * Hook up initial menu events
+       */
+      $menu
+          .mouseleave(mouseleaveMenu)
+          .find(options.rowSelector)
+              .mouseenter(mouseenterRow)
+              .mouseleave(mouseleaveRow)
+              .click(clickRow);
+
+      $(document).mousemove(mousemoveDocument);
+
+  };
+})(jQuery);

+ 2 - 0
src/static-assets.js

@@ -27,11 +27,13 @@ const lib_paths =[
     `/lib/jquery-ui-1.13.2/jquery-ui.min.js`,
     `/lib/lodash@4.17.21.min.js`,
     `/lib/jquery.dragster.js`,
+    '/lib/jquery.menu-aim.js',
     `/lib/html-entities.js`,
     `/lib/timeago.min.js`,
     `/lib/iro.min.js`,
     `/lib/isMobile.min.js`,
     `/lib/jszip-3.10.1.min.js`,
+    `/i18n/i18n.js`,
 ]
 
 // Ordered list of CSS stylesheets

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor