В Первой части мы подготовили нашу страницу.


После прочтения комментариев хочется сделать небольшое отступление: я не говорю что этот подход идеальный, или всем надо следовать и теперь писать только так. Я знаю о наличии программ для этого. Но суть статьи была поделиться мыслями как это можно сделать самому, и заодно разобраться в разных технологиях. Всегда интересно искать новые пути, пусть они и неверные, или слишком длинные, но они учат нас чему-то новому, пусть даже мы их прошли с ошибками.


Имитация базы, как мне уже писали в комментариях, это 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>

Вот в принципе и все!


image


Исходный код можно найти ТУТ.


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

Комментарии (4)


  1. urgotto
    20.02.2018 15:48

    Пролистал обе части в безуспешной надежде посмотреть какой такой новый механизм изобрели под vue для локализации, что ради него статью опубликовали :) Но стремления похвальные, продолжайте в том-же духе и будете хорошим frontend.


  1. argonavtt
    20.02.2018 18:41

    По прежнему смысл не ясен. Аргумент «буду писать так потому что хочу попробовать что то новое» не принимается. Пробовать можно, но не пилить же цикл статей с того. Сейчас новички посмотрят и за вами повторять начнут.


  1. yarkov
    20.02.2018 23:22

    Или я переработал и глаза замылились, или с Vue один пример на обе части статьи.
    Что-то жиденько.


  1. Samouvazhektra
    21.02.2018 11:43

    Тема Vue не раскрыта, вся суть сводится к идее выноса контента с локализацией в json-файлы и их подгрузкой аяксом и описывается парой предложений


    "Зачем тут Vue? Если это можно написать и на скриптах?". Если честно — для красоты html верстки. Ну и изучения новых технологий.

    Для этой цели лучше изучить pug и stylus — красота и удобство, а у вас в итоге оверхед по весу от смеси vue и jquery, причем в данном контексте можно было бы обойтись вообще VanillaJs


    В общем совсем не тот контент, который ожидался от человека со статусом Software Engineer
    И хоть я бекендер, даже для меня жиденько