Browse Source

Merge remote-tracking branch 'upstream/update2017'

Nikolay Suslov 7 years ago
parent
commit
9e8c111981
4 changed files with 314 additions and 207 deletions
  1. 48 58
      public/index.html
  2. 116 110
      public/web/lib/app.js
  3. 65 3
      public/web/lib/polyglot-lang.js
  4. 85 36
      public/webapps.json

+ 48 - 58
public/index.html

@@ -1,6 +1,7 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
 
 <head>
+  <meta charset="utf-8">
   <title>Live Coding Space</title>
   <script type="text/javascript" src="./web/lib/cell.js"></script>
   <script type="text/javascript" src="./web/lib/he.js"></script>
@@ -25,11 +26,30 @@
     gtag('config', 'UA-11815598-9');
 
   </script>
-  <!-- <script src="./web/lib/polyglot/polyglot.min.js"></script> -->
+ <script src="./web/lib/polyglot/polyglot.min.js"></script>
   <script type="module">
-    import {getAppDetails} from '/web/lib/app.js';
+    import {WebApp} from '/web/lib/app.js';
+    //import * as app from "/web/lib/app.js";
+
+    const initApp = () => {
+      let app = new WebApp;      
+      app.getAppDetails('webapps.json');
+      app.generateFrontPage();
+
+      document.querySelector('#ruLang').addEventListener('click', function(e){
+        app.setLanguage('ru');
+        window.location.reload(true);
+      });
+
+      document.querySelector('#enLang').addEventListener('click', function(e){
+        app.setLanguage('en');
+        window.location.reload(true);
+      });
+
+    }
+
     //var socket = initWebSocket();
-    window.onload = getAppDetails('webapps.json');
+    window.onload = initApp();
   </script>
 
 </head>
@@ -43,62 +63,33 @@
 
   <div id="header" class="mdc-layout-grid mdc-layout-grid--align-left">
     <div class="mdc-layout-grid__inner">
-      <div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-10">
-        <h1 class="mdc-typography--display3 mdc-theme--text-secondary-on-background mdc-typography">
-            <a class="mdc-typography link-in-text" style="cursor: pointer;" onclick="window.location.reload(true)"><strong>LiveCoding</strong>.space</a>
-          <!--<strong>LiveCoding</strong>.space -->
-        </h1>
+        <div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-12">
+            <span class="mdc-typography--subheading2 mdc-theme--text-secondary-on-background mdc-typography">
+                <a id="ruLang" class="mdc-typography link-in-text mdc-theme--text-hint-on-background" style="cursor:pointer">RU</a>
+              <!--<strong>LiveCoding</strong>.space -->
+            </span>
+            <span class="mdc-typography--subheading2 mdc-theme--text-secondary-on-background mdc-typography">
+                <a id="enLang" class="mdc-typography link-in-text mdc-theme--text-hint-on-background" style="cursor:pointer">EN</a>
+              <!--<strong>LiveCoding</strong>.space -->
+            </span>
+          </div>
+      <div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-12">
+          <div id="titleText">
+            </div>
+        
       </div>
-      <div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-2">
-          <span class="mdc-typography--subheading2 mdc-theme--text-secondary-on-background mdc-typography">
-              <a class="mdc-typography link-in-text mdc-theme--text-hint-on-background" href="#">RU</a>
-            <!--<strong>LiveCoding</strong>.space -->
-          </span>
-          <span class="mdc-typography--subheading2 mdc-theme--text-secondary-on-background mdc-typography">
-              <a class="mdc-typography link-in-text mdc-theme--text-hint-on-background" href="#">EN</a>
-            <!--<strong>LiveCoding</strong>.space -->
-          </span>
-        </div>
+      
       <div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-12">
-        <h1 class="mdc-typography mdc-typography--headline mdc-typography--adjust-margin mdc-theme--text-hint-on-background">
-          Collaborative Live Coding Space with support of user-defined languages and WebVR ready 3D graphics.
-          <br> Based on:
-          <strong>Virtual World Framework | A-Frame | Ohm language | OSC.js | and more... </strong> by
-          <a class="mdc-typography link-in-text mdc-theme--text-hint-on-background"
-            href="https://www.krestianstvo.org"><strong>Krestianstvo.org</strong></a>
-        </h1>
+        <div id="headerText">
+      </div>
       </div>
     </div>
   </div>
   <div id="about" class="mdc-layout-grid">
     <div class="mdc-layout-grid__inner">
       <div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-6">
-        <h1 class="mdc-typography--display1 mdc-theme--text-hint-on-background">Features</h1>
-
-        <ul class="featureList mdc-typography mdc-typography--title mdc-theme--text-hint-on-background">
-          <li>
-            <strong>Decentralized network model</strong> for
-            <strong>A-Frame</strong> components and entities based on
-            <strong>VWF</strong> replicated computation architecture</li>
-          <li>
-            <strong>Ohm</strong> language driver for sharing user-defined grammars, parsers, tokenisers inside virtual space</li>
-          <li>In browser
-            <strong>Code and Properties editor</strong> based on Cell.js</li>
-          <li>
-            <strong>OSC </strong>messaging through
-            <a class="mdc-typography link-in-text mdc-theme--text-hint-on-background" href="https://github.com/NikolaySuslov/osc-relay-lcs">OSC relay</a> on the client</li>
-          <li>
-            <strong>Avatars</strong> (Simple and GLTF models with animation)</li>
-          <li>
-            <strong>Multi-window</strong> or multi-monitor/multi-machine setups with view
-            <strong>offset cameras</strong>
-          </li>
-          <li>
-            <strong>WebRTC</strong> for video/audio streaming,
-            <strong>3D positional audio</strong> support</li>
-          <li>
-            <strong>GearVR, Windows MixedReality</strong> motion controllers</li>
-        </ul>
+          <div id="featuresText">
+            </div>
       </div>
 
       <div class="mdc-layout-grid__cell mdc-layout-grid__cell">
@@ -114,12 +105,8 @@
   <div id="info" class="mdc-layout-grid">
     <div class="mdc-layout-grid__inner">
       <div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-12">
-        <h1 class="mdc-typography--display1 mdc-theme--text-hint-on-background">Virtual Worlds</h1>
-        <h1 class="mdc-typography mdc-typography--headline mdc-theme--text-hint-on-background">
-          To begin collaborative coding in virtual space, just start one of the listed prototypes and connect to it from another browser
-          window using the generated link. The link will apper near the
-          <strong>Start new</strong> button.
-        </h1>
+          <div id="worldInfo">
+            </div>
       </div>
     </div>
   </div>
@@ -132,7 +119,10 @@
     <div id="about" class="mdc-layout-grid">
       <div class="mdc-layout-grid__inner">
         <div class="mdc-layout-grid__cell mdc-layout-grid__cell--span-12">
-          <h1 class="mdc-typography--display1 mdc-theme--text-hint-on-background">Demo videos</h1>
+            <div id="demoText">
+              </div>
+
+         
 
           <iframe src="https://player.vimeo.com/video/243291223?title=0&byline=0&portrait=0" width="600" height="337" frameborder="0"
             webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>

+ 116 - 110
public/web/lib/app.js

@@ -1,27 +1,55 @@
 import { Lang } from '/web/lib/polyglot-lang.js';
 
-var options = {
-    
-        query: 'pathname=' + window.location.pathname.slice(1,
-            window.location.pathname.lastIndexOf("/")),
-        secure: window.location.protocol === "https:",
-        reconnection: false,
-        transports: ['websocket']
-    
-    };
-    
-    var socket = io.connect(window.location.protocol + "//" + window.location.host, options);
-    
-    socket.on('getWebAppUpdate', function (msg) {
-        parseAppInstancesData(msg)
-        //console.log(msg);
-    });
-    
-    
-    function parseAppInstancesData(data) {
-    
+class WebApp {
+    constructor() {
+        console.log("app constructor");
+
+        this.options = {
+
+            query: 'pathname=' + window.location.pathname.slice(1,
+                window.location.pathname.lastIndexOf("/")),
+            secure: window.location.protocol === "https:",
+            reconnection: false,
+            transports: ['websocket']
+        }
+
+        this.lang = new Lang;
+        this.language = this.prepareLang();
+
+        var socket = io.connect(window.location.protocol + "//" + window.location.host, this.options);
+
+        var self = this;
+
+        socket.on('getWebAppUpdate', function (msg) {
+            self.parseAppInstancesData(msg)
+            //console.log(msg);
+        });
+    }
+
+    prepareLang() {
+        let phrases = this.lang.langPhrases;
+        return new this.lang.polyglot({ phrases });
+    }
+
+    setLanguage(langID) {
+        this.lang.setLocale(langID);
+        this.generateFrontPage();
+    }
+
+    generateFrontPage() {
+
+        this.lang.initLocale();
+        let allTextEl = ["titleText", "headerText", "featuresText", "worldInfo", "demoText"];
+        allTextEl.forEach(el => {
+            let textEl = document.querySelector("#" + el);
+            textEl.innerHTML = this.lang.getTranslationFor(el);
+        })
+    }
+
+    parseAppInstancesData(data) {
+
         var needToUpdate = true;
-    
+
         if (data == "{}") {
             var el = document.querySelector(".instance");
             if (el) {
@@ -30,7 +58,7 @@ var options = {
             }
             // let removeElements = elms => Array.from(elms).forEach(el => el.remove()); 
         }
-    
+
         let jsonObj = JSON.parse(data);
         var parsed = {};
         for (var prop in jsonObj) {
@@ -41,12 +69,12 @@ var options = {
                 parsed[name] = {};
                 parsed[name][prop] = jsonObj[prop];
             }
-    
+
         }
         //console.log(parsed);
-    
+
         document.querySelector("#main")._emptyLists();
-    
+
         for (var prop in parsed) {
             var name = prop;
             let element = document.getElementById(name + 'List');
@@ -57,49 +85,19 @@ var options = {
         }
         // console.log(data)
     }
-    
-    function getAllAppInstances() {
-    
-        let allInatances = httpGetJson('allinstances.json')
+
+    getAllAppInstances() {
+
+        let allInatances = this.httpGetJson('allinstances.json')
             .then(res => {
-                parseAppInstancesData(res);
+                this.parseAppInstancesData(res);
             });
     }
-    
-    function getSiteHeader(){
-        return  {
-            $type: "div",
-            class: "mdc-layout-grid",
-            $components: [
-                {
-                    $type: "div",
-                    class: "mdc-layout-grid__inner",
-                    $components: [
-                        {
-                            $cell: true,
-                            $type: "div",
-                            class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-12",
-                            $components: [
-                                
-                        {
-                            $cell: true,
-                            $type: "h1",
-                            class: "mdc-typography--display1 mdc-theme--text-hint-on-background",
-                            $text: "Virtual Worlds"
-                        }
-                    ]
-                }
-                    ]
-                }
-            ]
 
-        }
-        
-        
-    }
+    parseWebAppDataForCell(data) {
+
+        var self = this;
 
-    function parseWebAppDataForCell(data) {
-    
         document.querySelector("#main").$cell({
             $cell: true,
             $type: "div",
@@ -115,19 +113,26 @@ var options = {
                 this._jsonData = JSON.parse(data);
             },
             _makeWorldCard: function (m) {
+                let langID = localStorage.getItem('krestianstvo_locale');
+                var appInfo = m
+                if (langID) {
+                    if (m[1][langID]) {
+                        appInfo = [m[0], m[1][langID]]
+                    }
+                }
                 return {
                     $cell: true,
                     $type: "div",
                     class: "mdc-layout-grid__cell mdc-layout-grid__cell--span-4",
                     $components: [
-                        this._worldCardDef(m)
+                        this._worldCardDef(appInfo)
                     ]
                 }
-    
+
             },
             $update: function () {
                 this.$components = [
-                   //getSiteHeader(),// siteHeader,
+                    //getSiteHeader(),// siteHeader,
                     {
                         $type: "div",
                         class: "mdc-layout-grid",
@@ -138,14 +143,14 @@ var options = {
                                 $components: Object.entries(this._jsonData).map(this._makeWorldCard)
                             }
                         ]
-    
+
                     },
-    
-    
+
+
                 ]
             },
             _worldCardDef: function (desc) {
-    
+
                 return {
                     $cell: true,
                     $type: "div",
@@ -183,23 +188,23 @@ var options = {
                                 {
                                     $type: "a",
                                     class: "mdc-button mdc-button--compact mdc-card__action mdc-button--stroked",
-                                    $text: "Start new",
+                                    $text: self.language.t('start'),//"Start new",
                                     target: "_blank",
                                     href: "/" + desc[0],
                                     onclick: function (e) {
-                                        refresh();
+                                        self.refresh();
                                     }
                                 }
-    
-    
+
+
                             ]
                         },
-    
+
                         {
                             $type: "section",
                             class: "mdc-card__actions",
                             $components: [
-    
+
                                 {
                                     $type: "ul",
                                     _listData: {},
@@ -210,7 +215,7 @@ var options = {
                                     id: desc[0] + 'List',
                                     $update: function () {
                                         var connectText = {}
-    
+
                                         if (Object.entries(this._listData).length !== 0) {
                                             connectText = {
                                                 // $type: "span",
@@ -226,8 +231,8 @@ var options = {
                                         ].concat(Object.entries(this._listData).map(this._worldListItem))
                                         //     [connectText]
                                         // }].concat(Object.entries(this._listData).map(this._worldListItem))
-    
-    
+
+
                                     },
                                     _worldListItem: function (m) {
                                         return {
@@ -244,21 +249,21 @@ var options = {
                                                             target: "_blank",
                                                             href: window.location.protocol + "//" + m[1].instance,
                                                             onclick: function (e) {
-                                                                refresh();
+                                                                self.refresh();
                                                             }
                                                         },
                                                         {
                                                             $type: "span",
                                                             class: "mdc-list-item__text__secondary",
-                                                            $text: "Users online: " + m[1].clients
+                                                            $text: self.language.t('users') + m[1].clients
                                                         }
                                                     ]
                                                 }
-    
-    
-    
+
+
+
                                             ]
-    
+
                                         }
                                     }
                                 }
@@ -267,35 +272,33 @@ var options = {
                     ]
                 }
             }
-    
-    
+
+
         })
-    
-    
-    
+
     }
-    
-    function getAppDetails(val) {
-    
-        let appDetails = httpGetJson(val)
+
+    getAppDetails(val) {
+
+        let appDetails = this.httpGetJson(val)
             .then(res => {
-                parseWebAppDataForCell(res)
+                this.parseWebAppDataForCell(res)
             })
-            .then(res => refresh());
+            .then(res => this.refresh());
     }
-    
-    
-    function refresh() {
+
+
+    refresh() {
         // socket.emit('getWebAppUpdate', "");
     }
-    
-    
-    function httpGet(url) {
+
+
+    httpGet(url) {
         return new Promise(function (resolve, reject) {
             // do the usual Http request
             let request = new XMLHttpRequest();
             request.open('GET', url);
-    
+
             request.onload = function () {
                 if (request.status == 200) {
                     resolve(request.response);
@@ -303,24 +306,27 @@ var options = {
                     reject(Error(request.statusText));
                 }
             };
-    
+
             request.onerror = function () {
                 reject(Error('Network Error'));
             };
-    
+
             request.send();
         });
     }
-    async function httpGetJson(url) {
+    
+    async httpGetJson(url) {
         // check if the URL looks like a JSON file and call httpGet.
         let regex = /\.(json)$/i;
-    
+
         if (regex.test(url)) {
             // call the async function, wait for the result
-            return await httpGet(url);
+            return await this.httpGet(url);
         } else {
             throw Error('Bad Url Format');
         }
     }
-    
-    export {getAppDetails};
+
+}
+export { WebApp }
+    //export {getAppDetails, generateFrontPage, setLanguage, initLocale};

+ 65 - 3
public/web/lib/polyglot-lang.js

@@ -1,4 +1,5 @@
 class Lang {
+
     constructor() {
         console.log("lang constructor");
         this.polyglot = Polyglot;
@@ -10,6 +11,12 @@ class Lang {
         this.locale = localStorage.getItem('krestianstvo_locale');
     }
 
+    initLocale() {
+
+        if (!localStorage.getItem('krestianstvo_locale'))
+            localStorage.setItem('krestianstvo_locale', 'en');
+    }
+
     async getLang(langID) {
         let response = await fetch("/web/locale/" + langID + ".json");
         let data = await response.json();
@@ -23,16 +30,71 @@ class Lang {
         });
     }
 
-    setLocale(langID){
+    setLocale(langID) {
         localStorage.setItem('krestianstvo_locale', langID);
         this.locale = langID;
     }
 
-    changeLanguageTo(langID){
+    getTranslationFor(aString) {
+        let locale = localStorage.getItem('krestianstvo_locale');
+        return this.translations[aString][locale]
+    }
+
+    changeLanguageTo(langID) {
         this.setLocale(langID);
         this.setLanguage(langID);
     }
 
+    get langPhrases() {
+
+        // let langID = localStorage.getItem('krestianstvo_locale');
+        let phrases = {
+            "en": {
+                "start": "Start new",
+                "users": "Users online: "
+
+            },
+            "ru": {
+                "start": "Создать",
+                "users": "Пользователей онлайн: "
+            }
+        }
+
+        return phrases[this.locale]
+    }
+
+    get translations() {
+
+        let appText = {
+            "titleText": {
+                "en": '<h1 class="mdc-typography--display3 mdc-theme--text-secondary-on-background mdc-typography"><a class="mdc-typography link-in-text" style="cursor: pointer;" onclick="window.location.reload(true)"><strong>LiveCoding</strong>.space</a><!--<strong>LiveCoding</strong>.space --></h1>',
+
+                "ru": '<h1 class="mdc-typography--display3 mdc-theme--text-secondary-on-background mdc-typography"><a class="mdc-typography link-in-text" style="cursor: pointer;" onclick="window.location.reload(true)"><strong>LiveCoding</strong>.пространство</a><!--<strong>LiveCoding</strong>.space --></h1>'
+            },
+            "headerText": {
+                'en': '<h1 class="mdc-typography mdc-typography--headline mdc-typography--adjust-margin mdc-theme--text-hint-on-background">Collaborative Live Coding Space with support of user-defined languages and WebVR ready 3D graphics.<br> Based on: <strong>Virtual World Framework | A-Frame | Ohm language | OSC.js | and more... </strong> by <a class="mdc-typography link-in-text mdc-theme--text-hint-on-background" href="https://www.krestianstvo.org"><strong>Krestianstvo.org</strong></a> </h1>',
+
+                "ru": '<h1 class="mdc-typography mdc-typography--headline mdc-typography--adjust-margin mdc-theme--text-hint-on-background">Виртуальное обучающее пространство в веб-браузере с функциями живого кодирования,  возможностью создания собственных языков программирования, технологий виртуальной/дополненной/смешанной реальности WebVR.<br> На основе: <strong>Virtual World Framework | A-Frame | Ohm language | OSC.js | ... </strong> проект <a class="mdc-typography link-in-text mdc-theme--text-hint-on-background" href="https://www.krestianstvo.org"><strong>Krestianstvo.org</strong></a> </h1>'
+            },
+
+            "featuresText": {
+                "en": '<h1 class="mdc-typography--display1 mdc-theme--text-hint-on-background">Features</h1> <ul class="featureList mdc-typography mdc-typography--title mdc-theme--text-hint-on-background"> <li><strong>Decentralized network model</strong> for <strong>A-Frame</strong> components and entities based on <strong>VWF</strong> replicated computation architecture</li> <li><strong>Ohm</strong> language driver for sharing user-defined grammars, parsers, tokenisers inside virtual space</li><li>In browser <strong>Code and Properties editor</strong> based on Cell.js</li><li><strong>OSC </strong>messaging through <a class="mdc-typography link-in-text mdc-theme--text-hint-on-background" href="https://github.com/NikolaySuslov/osc-relay-lcs">OSC relay</a> on the client</li><li><strong>Avatars</strong> (Simple and GLTF models with animation)</li><li><strong>Multi-window</strong> or multi-monitor/multi-machine setups with view <strong>offset cameras</strong></li><li><strong>WebRTC</strong> for video/audio streaming,<strong>3D positional audio</strong> support</li><li><strong>GearVR, Windows MixedReality</strong> motion controllers</li> </ul>',
+
+                "ru": '<h1 class="mdc-typography--display1 mdc-theme--text-hint-on-background">О программе</h1> <ul class="featureList mdc-typography mdc-typography--title mdc-theme--text-hint-on-background"> <li><strong> Децентрализованная модель приложения </strong> на основе <strong>A-Frame</strong> компонентов и <strong>VWF</strong> архитектуры распределенных вычислений (репликация и виртуальное время) в сети.  </li> <li><strong>Ohm</strong> драйвер для совместного создания пользовательских языков программирования, грамматик, парсеров, токенайзеров внутри виртуального пространства</li><li><strong>Редактор кода и параметров объектов</strong> прямо в веб-браузере на основе Cell.js</li><li>Работа с <strong>OSC </strong>сообщениями через <a class="mdc-typography link-in-text mdc-theme--text-hint-on-background" href="https://github.com/NikolaySuslov/osc-relay-lcs">OSC relay</a></li><li><strong>Аватары</strong> (простые или GLTF модели с анимацией)</li><li><strong>Мульти-оконные</strong> и мульти-мониторные/компьютерные/телефонные проекции с применением виртуальных камер со <strong>смещением вида</strong></li><li><strong>WebRTC</strong> для видео/аудио потоковой передачи данных P2P, с функциями <strong>звукового 3D позиционирования</strong> в виртуальном пространстве</li><li><strong>GearVR, Windows MixedReality</strong> контроллеры движения</li> </ul>'
+            },
+            "worldInfo": {
+                "en": '<h1 class="mdc-typography--display1 mdc-theme--text-hint-on-background">Virtual Worlds</h1><h1 class="mdc-typography mdc-typography--headline mdc-theme--text-hint-on-background">To begin collaborative coding in virtual space, just start one of the listed prototypes and connect to it from another browser window using the generated link. The link will apper near the <strong>Start new</strong> button.</h1>',
+
+                "ru": '<h1 class="mdc-typography--display1 mdc-theme--text-hint-on-background">Виртуальные миры</h1><h1 class="mdc-typography mdc-typography--headline mdc-theme--text-hint-on-background">Чтобы начать работу в виртуальном обучающем пространстве, выберите один из прототипов миров и запустите его нажав на кнопку <strong>Создать</strong>. Для вновь созданного мира сгенерируется уникальная ссылка, которая отобразиться под его описанием. Для совместной работы, войдите с помощью этой ссылки с другого компьютера или окна браузера. Из прототипа можно создавать неограниченное количество миров. Рядом с ссылками так же указывается количество пользователей, находящихся онлайн в указанном мире.</h1>'
+            },
+            "demoText": {
+                "en": '<h1 class="mdc-typography--display1 mdc-theme--text-hint-on-background">Demo videos</h1>',
+                "ru": '<h1 class="mdc-typography--display1 mdc-theme--text-hint-on-background">Видео демонстрации</h1>'
+            }
+        }
+        return appText
+    }
+    
 }
 
-  export {Lang};
+export { Lang };

+ 85 - 36
public/webapps.json

@@ -1,51 +1,100 @@
 {
-    "aframe":{
-        "title":"Simple VWF & A-Frame app",
-        "imgUrl": "./aframe/webimg.jpg",
-        "text": "Example application showing Virtual World Framework & A-Frame integration",
-        "featured": false
+    "aframe": {
+        "en": {
+            "title": "Simple VWF & A-Frame app",
+            "imgUrl": "./aframe/webimg.jpg",
+            "text": "Example application showing Virtual World Framework & A-Frame integration",
+            "featured": false
+        },
+        "ru": {
+            "title": " Мир VWF & A-Frame",
+            "imgUrl": "./aframe/webimg.jpg",
+            "text": "Пример распределенного приложения, показывающего возможности интеграции Virtual World Framework & A-Frame",
+            "featured": false
+        }
     },
-
-    "aframe2":{
-        "title":"Textures and Models in VWF & A-Frame",
-        "imgUrl": "./aframe2/webimg.jpg",
-        "text": "Example app with loaded textures and models",
-        "featured": true
+    "aframe2": {
+        "en": {
+            "title": "Textures and Models in VWF & A-Frame",
+            "imgUrl": "./aframe2/webimg.jpg",
+            "text": "Example app with loaded textures and models",
+            "featured": true
+        },
+        "ru": {
+            "title": "Текстуры и модели в VWF & A-Frame",
+            "imgUrl": "./aframe2/webimg.jpg",
+            "text": "Пример мира с текстурами и моделями",
+            "featured": true
+        }
     },
-      "ohmlang-calc":{
-        "title":"Calculator in Ohm and VWF",
-        "imgUrl": "./ohmlang-calc/webimg.jpg",
-        "text": "Example calc app with simple Ohm grammar",
-        "featured": true
+    "ohmlang-calc": {
+        "en": {
+            "title": "Calculator in Ohm & VWF",
+            "imgUrl": "./ohmlang-calc/webimg.jpg",
+            "text": "Example calc app with simple Ohm grammar",
+            "featured": true
+        },
+        "ru": {
+            "title": "Калькулятор на Ohm & VWF",
+            "imgUrl": "./ohmlang-calc/webimg.jpg",
+            "text": "Пример создания децентрализованной грамматики калькулятора",
+            "featured": true
+        }
     },
-     "ohmlang-lsys":{
-        "title":"L-System parser example in Ohm and VWF",
-        "imgUrl": "./ohmlang-lsys/webimg.jpg",
-        "text": "Example app with L-System grammar",
-        "featured": true
+    "ohmlang-lsys": {
+        "en": {
+            "title": "L-System parser example in Ohm & VWF",
+            "imgUrl": "./ohmlang-lsys/webimg.jpg",
+            "text": "Example app with L-System grammar",
+            "featured": true
+        },
+        "ru": {
+            "title": "L-система и черепашка в Ohm & VWF",
+            "imgUrl": "./ohmlang-lsys/webimg.jpg",
+            "text": "Пример грамматики/парсера для генератора L-систем и языка черепашки",
+            "featured": true
+        }
+       
     },
 
-    "osc-example":{
-        "title":"Simple app with Ohm & OSC control",
-        "imgUrl": "./osc-example/webimg.jpg",
-        "text": "Example app with OSC control",
-        "featured": false
+    "osc-example": {
+        "en":{
+            "title": "Simple app with Ohm & OSC control",
+            "imgUrl": "./osc-example/webimg.jpg",
+            "text": "Example app with OSC control",
+            "featured": false
+        },
+        "ru":{
+            "title": "Протокол OSC сообщений и Ohm",
+            "imgUrl": "./osc-example/webimg.jpg",
+            "text": "Пример приложения, демонстрирующего работу с сообщениями OSC",
+            "featured": false
+        }
+       
     },
-
-    "webrtc":{
-        "title":" WebRTC app",
-        "imgUrl": "./webrtc/webimg.jpg",
-        "text": "Audio and video streaming for Avatars",
-        "featured": false
+    "webrtc": {
+        "en":{
+            "title": " WebRTC app",
+            "imgUrl": "./webrtc/webimg.jpg",
+            "text": "Audio and video streaming for Avatars",
+            "featured": false
+        }, 
+          "ru":{
+            "title": " WebRTC приложение",
+            "imgUrl": "./webrtc/webimg.jpg",
+            "text": "Пример приложения с использованием WebRTC потокового аудио и видео",
+            "featured": false
+           
+        }     
     },
-    "gearvr":{
-        "title":"Control in VR app",
+    "gearvr": {
+        "title": "Control in VR app",
         "imgUrl": "./gearvr/webimg.jpg",
         "text": "VR controler example",
         "featured": false
     },
-    "multipixel":{
-        "title":"Multi-Pixel",
+    "multipixel": {
+        "title": "Multi-Pixel",
         "imgUrl": "./multipixel/webimg.jpg",
         "text": "Multi offset view camera example",
         "featured": false