В Первой части мы подготовили нашу страницу.
После прочтения комментариев хочется сделать небольшое отступление: я не говорю что этот подход идеальный, или всем надо следовать и теперь писать только так. Я знаю о наличии программ для этого. Но суть статьи была поделиться мыслями как это можно сделать самому, и заодно разобраться в разных технологиях. Всегда интересно искать новые пути, пусть они и неверные, или слишком длинные, но они учат нас чему-то новому, пусть даже мы их прошли с ошибками.
Имитация базы, как мне уже писали в комментариях, это json файлы с содержанием нужного текста. Вопрос: "Зачем тут Vue? Если это можно написать и на скриптах?". Если честно — для красоты html верстки. Ну и изучения новых технологий.
Приступим!
На данной странице разделение вовсе не обязательно, ибо переменных тут не очень много, но я предпочитаю делить все на разные части. У нас будет компонент отвечающий за header, content & footer(позже появится).
Первое, это мы создаем json файлы, я создаю папку "data" и в ней создаю два файла "ru.json" и "en.json". В них соответственно названиям будет лежать наш текст. Далее открываем наш html и делаем подмены на будущее, стараясь назвать переменные так чтоб они полностью отображали смысл текста в них. В моем случае это было вот так:
<header class="transition tr-header" id="header"> <div class="container"> <div class="nav-holder"> <nav class="scroll-nav"> <ul> //заменили на переменные <li class="actscroll"><a href="#sec1">{{main}} </a></li> <li><a href="#sec2">{{aboutCompany}}</a></li> <li><a href="#sec3">{{product}}</a></li> <li><a href="#sec4">{{equipment}}</a></li> <li><a href="#sec5">{{whereBuy}}</a></li> <li><a href="#sec6">{{service}}</a></li> <li><a href="#sec7">{{partners}}</a></li> <li><a href="#sec8">{{contacts}}</a></li> </ul> </nav> <div class="lang-dropdown"> <div class="flag-with-menu" id="flag-menu"> <div class="flag flag-ru" lang-value="ru-RU"></div> </div> <div id="lang-menu" class="lang-menu lang-first-init"> <div class="flag flag-us" lang-value="en-US"></div> </div> </div> </div> </div> </header> <!-- End header --> <!--================= Photo home ================--> <section class="is_overlay page-title-bg" id="sec1" name="sec1"> <div class="bg bg-parallax run-par2" style="background-image: url(images/paraplan.jpg) "></div> <div class="overlay over-op6"></div> </section> <!-- section end --> <div id="contentPage"> //добален для компонента <section class="align-text" id="sec2" name="sec2"> <div class="content"> <div class="container"> <div class="row"> <div class="col-md-6 "> <h3>{{aboutCompanyHeader}}</h3> <div class="clearfix"></div> <div class="separator color-separator flt-l"></div> <div class="clearfix"></div> <p>{{aboutCompanyText}}</p> </div> <div class="col-md-6 "> <h3>{{ourMissionHeader}}</h3> <div class="clearfix"></div> <div class="separator color-separator flt-l"></div> <div class="clearfix"></div> <p>{{ourMissionText}}</p> </div> </div> </div> </div> </section> //у вас может быть больше секций, у меня все секции собранны в данном div </div>
Переходим к языковым файлам: (это можно добавить было все в один массив или один объект, но мне удобнее когда текст разбит подобным образом). "ru.json"
[ { "main": "Главная" }, { "aboutCompany": "О компании" }, { "product": "Наша продукция" }, { "equipment": "Наши технологии" }, { "whereBuy": "Где купить" }, { "service": "Поддержка" }, { "partners": "Партнеры" }, { "contacts": "Контакты" }, {"aboutCompanyHeader": "О компании"}, { "aboutCompanyText": [ "«Хабрахабр» — крупнейший в Европе ресурс для IT-специалистов, издаваемый компанией «ТМ». ", "С момента появления в 2006-м году «Хабр» трансформировался из небольшого отраслевого сайта в глобальную ", "профессиональную площадку, которую ежемесячно посещают более 8 миллионов уникальных пользователей.", "«Хабрахабр» одинаково интересен программистам и разработчикам, администраторам и тестировщикам, дизайнерам ", "и верстальщикам, аналитикам и копирайтерам, а также всем тем, для кого IT — это не просто две буквы алфавита.", "Расширение тематики «Хабра» дало начало сайту-спутнику — Geektimes, на который переехали непрофильные хабы ", "и значительная часть контента, не имеющего непосредственного отношен разработке и программированию." ] }, { "ourMissionHeader": "Наша миссия" }, { "ourMissionText": [ "Данный сайт представляет собой платформу для информационного обмена между участниками пользовательского ", "сообщества. Сообщество пользователей сайта является саморегулируемым, поэтому разобраться во всех нюансах ", "работы проекта с первого раза получается далеко не у всех. Чтобы объяснить, как всё устроено, мы подготовили ", "данный справочный раздел. Справа представлен рубрикатор справочного раздела. Для получения разъяснений выберите ", "соответствующий пункт рубрикатора и ознакомьтесь с предложенной информацией. Если вам не удалось найти ответ ", "на интересующий вопрос, пожалуйста, воспользуйтесь формой обратной связи." ] } ]
Ну и вы сами надеюсь справитесь с переводом данного текста на английский!
После проверки у меня перестало нормально работать меню с языками, поэтому если вы столкнулись с подобной проблемой, вот быстрое решение:"multilanguage.js"
replaceElementAndSelect(userLanguage); //измененно $(document).on('click', '.flag ', function () { if (!isMenuClicked && !$(this).hasClass('select-flag')) { var newLang = $(this).attr('lang-value'); language = newLang; setCookie("language", language); languageChange(newLang); hideMenu(); } isMenuClicked = false; }); //измененно $(document).on('click', "#flag-menu", function () { isMenuClicked = true; showOrHideMenu(); }); //замените все где используется menu на $('#lang-menu'). Например: menu.hasClass('lang-first-init') на $('#lang-menu').hasClass('lang-first-init'). В старом варианте у меня не работает. Если есть желание можете найти в чем проблема и ответить в комментариях
В папку "scripts" добавляем новый "main-function.js". И добавляем в него пару методов(т.к в моем случае они потом переиспользовались):
//Тут происходит сопоставление свойств компонента с объектами из json файла function findInArray(langArray, component) { $.each(langArray, function (index, value) { Object.keys(value).forEach(function (key) { var val = value[key]; if ($.isArray(val)) { component[key] = val.join(", "); } else { component[key] = value[key]; } }); }); } //переиспользование для удобства написания кода function getArrayFromJson(url) { return $.ajax({ url: url, dataType: 'json' }); }
В папку "scripts" добавляем новый "index.js". И разбираем его
$(document).ready(function () { var language = getCookie("language") || navigator.language || navigator.browserLanguage; //ищем язык. Если нет в куках то берем браузерный //пути наших файлов с языковыми данными var ruUrl = location.origin + '/data/ru.json'; var enUrl = location.origin + '/data/en.json'; //массивы для данных, и Vue компоненты для обращения к ним var en = [], ru = []; var vm, vmHeader; initialize(); //отлавливаем событие, если был поменян язык $(document).on('onLanguageChange', function (e, eventInfo) { setPageTemplateByLanguage(eventInfo); }); function initialize() { //создаем Vue компоненты createMainComponent(); //ждем пока придут данные с наших json файлов и отправляем их в массив $.when(getArrayFromJson(ruUrl), getArrayFromJson(enUrl)) .done(function (a1, a2) { ru = a1[0]; en = a2[0]; setPageTemplateByLanguage(language); //для изменения языка }); } function createMainComponent() { //записываем все наши переменные принадлежащие этому участку, их должно быть больше,но для примера сойдет vm = new Vue({ el: '#contentPage', data: { siteHeader: "", siteSubHeader: "", aboutCompanyHeader: "", aboutCompanyText: "", ourMissionHeader: "", ourMissionText: "" }, //у меня на странице была сторонняя библиотека которая создавала карусель из картинок, так вот после обновления компонента она "ломалась". Этот метод пересоздавал ее. updated: function () { this.$nextTick(function () { // createCarusel(); }); } }); vmHeader = new Vue({ el: '#header', data: { aboutCompany: "", product: "", equipment: "", whereBuy: "", service: "", partners: "", contacts: "" } }); } //в зависимости от языка перезаписываем данные в компонентах function setPageTemplateByLanguage(lang) { switch (lang) { case "en-US": findInArray(en, vmHeader); findInArray(en, vm); break; case "ru-RU": findInArray(ru, vmHeader); findInArray(ru, vm); break; default: findInArray(ru, vmHeader); findInArray(ru, vm); break; } } });
Теперь осталось это добавить на нашу страницу, и не забыть скачать vue.min.js в папку "scripts"
<script src="scripts/jquery.min.js"></script> <script src="scripts/vue.min.js"></script> <script src="scripts/cookie.js"></script> <script src="scripts/multilanguage.js"></script> <script src="scripts/main-function.js"></script> <script src="scripts/index.js"></script>
В принципе вот и все, совсем немного кода для красивого решения!
Но я хочу пойти чуть дальше и добавлю пару Vue компонентов (для примера). Может будет кому-то от этого польза. Из новых компонентов это будет footer и заголовок сайта, а то у нас картинка выглядит пустой.
Создаем в папке "data" два файла: "footer_ru.json" и "footer_en.json"
[ {"getInTouch": "Связаться"}, {"region": "Россия, Санкт-Петербург"}, {"street": "Невский проспект, дом 13 / 7" }, {"phone": "8 (812) 666-66-66"}, {"mobilePhone": "+7 (966) 666-66-66"}, {"email": "ivanov@mail.ru"}, {"secondEmail": "info@gmai.com"}, {"findUs": "Ищите нас"}, {"firstLine": "Инновация"}, {"secondLine": "Мы помогаем"}, {"firstPartLastLine": "2014 OOO "}, {"colorPartLastLine": "Хабр "}, {"thirdPartLastLine": "блог для разработчиков"} ]
Я это выношу в файл с общими функциями, т.к у себя я это переиспользую. Добавляем в "main-function.js"
// пути для данных var ruFooterUrl = location.origin + '/data/footer_ru.json'; var enFooterUrl = location.origin + '/data/footer_en.json'; var vueFooter; var ruFooterInfo = [], enFooterInfo = []; //следим за изменениями языка $(document).on('onLanguageChange', function (e, eventInfo) { setPageTemplateByLanguageMain(eventInfo); }); //в общем сам футер Vue.component('habr-footer', { props: ['get-in-touch', 'region', 'street', 'email', 'second-email', 'phone', 'mobile-phone', 'find-us', 'first-line', 'second-line', 'first-part-last-line', 'color-part-last-line', 'third-part-last-line'], template: `<div> <section class="page-widgets-holder"> <div class="content"> <div class="container"> <div class="row"> <div class="col-md-4 "> <h3>{{getInTouch}}</h3> <div class="contact-info"> <ul> <li><a class="ci-adress">{{region}}<br> {{street}}</a></li> <li><a class="ci-mail"> {{email}}</a></li> <li><a class="ci-mail"> {{secondEmail}}</a></li> <li> <a v-bind:href="'tel:' + phone" class="ci-phone"> {{phone}} </a></li> <li> <a v-bind:href="'tel:' + mobilePhone" class="ci-phone"> {{mobilePhone}} </a></li> </ul> </div> </div> <div class="col-md-4 "> </div> <div class="col-md-4 "> <h3>{{findUs}}</h3> <div class="social-links"> <ul> <li><a href="#" target="_blank" class="transition"><i class="fa fa-facebook"></i></a></li> <li><a href="#" target="_blank" class="transition"><i class="fa fa-vk"></i></a></li> <li><a href="#" target="_blank" class="transition"><i class="fa fa-twitter"></i></a></li> <li><a href="#" target="_blank" class="transition"><i class="fa fa-youtube"></i></a></li> <li><a href="#" target="_blank" class="transition"><i class="fa fa-instagram"></i></a></li> </ul> </div> </div> </div> </div> </div> </section> <!-- section end --> <!--================= footer ================--> <section class="page-widgets-holder footer"> <div class="container"> <div class="row"> <div class="col-md-3 "> <h4>{{firstLine}}</h4> <h5>{{secondLine}}</h5> </div> <div class="col-md-9"> <div class="policy-box"> <p>{{firstPartLastLine}} <span>{{colorPartLastLine}} </span> {{thirdPartLastLine}}</p> </div> </div> </div> </div> </section> </div>` }); //создание компонента и запись в него информации function createFooterComponent() { vueFooter = new Vue({ el: '#vueFooter', data: { footerInfo: { getInTouch: "", region: "", street: "", email: "", secondEmail: "", phone: "", mobilePhone: "", findUs: "", firstLine: "", secondLine: "", firstPartLastLine: "", colorPartLastLine: "", thirdPartLastLine: "" } }, created: function () { this.loadData(); }, methods: { loadData() { $.when(getArrayFromJson(ruFooterUrl), getArrayFromJson(enFooterUrl) ).done(function (a1, a2) { ruFooterInfo = a1[0]; enFooterInfo = a2[0]; setPageTemplateByLanguageMain(); }); } } }); } //в зависимости от языка перезаписываем данные в компонентах function setPageTemplateByLanguageMain(lang) { var userLanguage = lang || getCookie("language") || language; switch (userLanguage) { case "en-US": findInArray(enFooterInfo, vueFooter.footerInfo); break; case "ru-RU": findInArray(ruFooterInfo, vueFooter.footerInfo); break; default: findInArray(ruFooterInfo, vueFooter.footerInfo); break; } }
Чтобы это все было красиво, скачиваем Font Awesome и добавляем его в "css" папку.
В нашем "style.css" добавляем классы для красивого отображения:
.page-widgets-holder { border-top:1px solid #ccc; } .page-widgets-holder h3 { font-size:14px; text-align:center; color:#666; font-family: 'Montserrat', sans-serif; text-transform:uppercase; margin-bottom:40px; position:relative; } .page-widgets-holder h3:before { content:''; position:absolute; width:40px; height:2px; background:#ccc; bottom:-10px; left:50%; margin-left:-20px; } .contact-info li { float:left; width:100%; margin-bottom:12px; } .contact-info li a { font-family: 'Montserrat', sans-serif; } .ci-adress { text-transform:uppercase; font-size:14px; text-align:left; color:#000; line-height:20px; } .ci-mail { font-size:14px; text-align:left; } .ci-phone { color:#666; line-height:20px; } .social-links { padding-bottom:58px; } .social-links li { display:inline-block; margin:0 1px; box-sizing:border-box; } .social-links li a { width:50px; height:50px; background:#eee; border-radius:100%; line-height:50px; float:left; color:#666; font-size:20px; box-shadow:0 0 0 20px transparent; } .social-links li a:hover { box-shadow:0 0 0 0 rgba(0,0,0,0.1); } .fa { margin-left: 0.75em; margin-top: 0.75em; }
В "index.js" добавляем в метод "initialize()":
function initialize() { createFooterComponent(); //добавлено createMainComponent(); $.when(getArrayFromJson(ruUrl), getArrayFromJson(enUrl)) .done(function (a1, a2) { ........ }); }
И в "index.html" добавляем:
<link rel="stylesheet" href="css/font-awesome-4.7.0/css/font-awesome.min.css" media="all"> ...... <body> <!--================= main start ================--> <div id="main"> <div id="wrapper"> <div class="content-holder"> <!--================= Header ================--> <header class="transition tr-header" id="header"> ........... </header> <!-- End header --> <!--================= Photo home ================--> <section class="is_overlay page-title-bg" id="sec1" name="sec1"> ........ </section> <!-- section end --> <div id="contentPage"> ........ </div> <!-- добавлено --> <div id="vueFooter"> <habr-footer v-bind="footerInfo"></habr-footer> </div> </div> </div> </div> </body>
Ну и завершающий компонент, это заголовок сайта, я его добавляю в "main-function.js":
var vmPageHeader, vueFooter;//добавлено var ruFooterInfo = [], enFooterInfo = []; var ruHeaderInfo = [], enHeaderInfo = []; //добавлено Vue.component('habr-header', { props: ['site-header', 'site-sub-header'], template: `<div class="container"> <div class= "page-title-bg-holder hero-wrapper"> <h2>{{ siteHeader }}</h2> <p>{{siteSubHeader}}</p> </div> </div>` }); //в данном случае создавать файл ради 2х строк невыгодно,поэтому мы добавляем данные в наши основные файлы и ссылки передаем сюда, где происходит поиск и сортировка нужной информации function createHeaderComponent(ruUrl, enUrl) { vmPageHeader = new Vue({ el: '#vueHeader', data: { siteSubHeader: "", siteHeader: "" }, created: function () { this.loadData(); }, methods: { loadData() { $.when( getArrayFromJson(ruUrl), getArrayFromJson(enUrl)) .done(function (a1, a2) { ruHeaderInfo = a1[0]; enHeaderInfo = a2[0]; setPageTemplateByLanguageMain(); }); } } }); } function setPageTemplateByLanguageMain(lang) { var userLanguage = lang || getCookie("language") || language; switch (userLanguage) { case "en-US": findInArray(enHeaderInfo, vmPageHeader);//добавлено findInArray(enFooterInfo, vueFooter.footerInfo); break; case "ru-RU": findInArray(ruHeaderInfo, vmPageHeader);//добавлено findInArray(ruFooterInfo, vueFooter.footerInfo); break; default: findInArray(ruHeaderInfo, vmPageHeader);//добавлено findInArray(ruFooterInfo, vueFooter.footerInfo); break; } }
В "en.json" и "ru.json" добавляем данные:
[ { "siteHeader": "«Хабрахабр»" }, { "siteSubHeader": "Данный сайт представляет собой платформу для информационного обмена между участниками пользовательского сообщества" }, .......... ]
В "index.js" добавляем в метод "initialize()":
function initialize() { createFooterComponent(); createMainComponent(); createHeaderComponent(ruUrl, enUrl);//добавлено $.when(getArrayFromJson(ruUrl), getArrayFromJson(enUrl)) .done(function (a1, a2) { ........ }); }
И в "index.html" добавляем:
<link rel="stylesheet" href="css/font-awesome-4.7.0/css/font-awesome.min.css" media="all"> ...... <body> <!--================= main start ================--> <div id="main"> <div id="wrapper"> <div class="content-holder"> <!--================= Header ================--> <header class="transition tr-header" id="header"> ........... </header> <!-- End header --> <!--================= Photo home ================--> <section class="is_overlay page-title-bg" id="sec1" name="sec1"> <div class="bg bg-parallax run-par2" style="background-image: url(images/paraplan.jpg) "></div> <div class="overlay over-op6"></div> <!-- добавлено --> <div class="content" id="vueHeader"> <habr-header :site-header="siteHeader" :site-sub-header="siteSubHeader"></habr-header> </div> </section> <!-- section end --> <div id="contentPage"> ........ </div> <div id="vueFooter"> ........ </div> </div> </div> </div> </body>
Вот в принципе и все!

Исходный код можно найти ТУТ.
Только внимательно следите за путями к json-файлам, они у вас могут отличаться.