Создание мультиязычного приложения NativeScript

Для недавнего проекта мне понадобилось решение для мгновенного переключения языка с Нидерландского на Английский.

В одном концепте на NativeScript я разработал собственное решение на чистом JavaScript с Jed. Оно работало, но было далеко не идеальным.
После этого я решил создать приложение на NativeScript с Angular (2+) и начал поиск существующих библиотек локализации для Angular. Лучшим решением оказалась библиотека ng-translate от Olivier Combe.

В Angular она завелась с полпинка, а вот заставить её работать в NativeScript стоило мне много крови и пота. Но благодаря сообществу вокруг NativeScript и персонально Nathan Walker, мне удалось это сделать. И, в принципе, это было не так сложно.

Вы можете выполнить описанное ниже или же просто скачать готовый пример NSNL_Multilingual с GitHub.

1. Создание приложения на NativeScript и Angular

С помощью следующих команд мы создадим новый проект и добавим в него платформы Android и iOS.

tns create projectname --ng
cd projectname
tns platform add android
tns platform add ios

2. Установка ng2-translate

ng2-translate это пакет npm, поэтому он устанавливается стандартно:
npm install ng2-translate --save

3. Создание языковых файлов

Создадим папку i18n в папке app нашего проекта и добавим файлы nl.json и en.json.
nl.json
{
"EXAMPLE": {
"TITLE": "Hallo wereld!",
"TEXT": "Dit is een zin in het Nederlands.",
"BACK": "Terug"
}
}

en.json
{
"EXAMPLE": {
"TITLE": "Hello world!",
"TEXT": "This is a sentence in English.",
"BACK": "Back"
}
}

4. Отредактируем файл app.module.ts

Здесь нам нужно импортировать следующие модули:
import {NativeScriptHttpModule} from "nativescript-angular/http";
import {TranslateModule, TranslateLoader, TranslateStaticLoader} from "ng2-translate";
import {Http} from "@angular/http";

NativeScript использует AOT компиляцию, поэтому нам нужно экспортировать функцию, которая возвращает TranslateStaticLoader.
// for AoT compilation
export function translateLoaderFactory(http: Http) {
return new TranslateStaticLoader(http, "/i18n", ".json");
};

Также нам нужно расширить импорты @NgModule:
NativeScriptHttpModule,
TranslateModule.forRoot([{
provide: TranslateLoader,
deps: [Http],
useFactory: (translateLoaderFactory)}])

5. Отредактируем app.component.html

Замените содержитое файла app.component.html следующей разметкой.
С помощью неё мы привязываем EXAMPLE.TITLE к тексту в ActionBar, компоненту Label и содержимому TextView.
Также у нас есть кнопки для переключения между Нидерландским и Английским языками. Эти кнопки вызывают функцию changeLanguage при нажатии.








6. Отредактируем app.component.ts

Сначала нам нужно импортировать следующие модули:
import * as Platform from "platform";
import {TranslateService} from 'ng2-translate';

При инициализации мы установим язык по-умолчанию в Нидерландский.
После этого изменим язык, основываясь на предпочитаемом языке на устройстве. В случае, если для такого языка у нас нет файла локализации, ng2-translate вернёт язык по-умолчанию.
constructor(private translate: TranslateService) {
this.translate.setDefaultLang("nl");
this.translate.use(Platform.device.language);
}

Также нам нужна функция для переключения языков:
public changeLanguage(lang: string) {
this.translate.use(lang);
}

Готово! За шесть простых шагов мы создали мультиязычное приложение на NativeScript!

Рекомендую посмотреть более расширенный пример NSNL_Multilingual на GitHub, в котором также используется Angular routing.

*Оригинал взят на NativeScript.nl

Автоматическая установка фото профиля в приложении NativeScript Angular

Как превратить просто хорошее мобильное приложение в отличное мобильное приложение? К примеру, что вы выберете: экран входа в приложение с запросом только логина и пароля или лучше добавить туда фото пользователя?
С помощью сервиса Gravatar вы можете получить аватарку по email адресу пользователя, если он загрузил её туда.
Мы увидим как автоматически установить аватарку в приложении на базе NativeScript Angular с помощью API сервиса Gravatar.

Наша цель — сделать приложение более запоминающимся для пользователя.

Создаём новый проект NativeScript Angular

Для лучшего понимания создадим проект приложения NativeScript с нуля для платформ Android и iOS, в котором будем использовать Angular и TypeScript.
В командной строке (в Windows) или в окне терминала (в Linux и Mac) выполните следующие команды (перед этим убедитесь, что у вас установлен NativeScript):
tns create GravatarProject --ng
cd GravatarProject
tns platform add ios
tns platform add android

Для разработки приложений под iOS обязательно нужно иметь Mac с установленным Xcode!
Gravatar API требует хеширования email адреса алгоритмом MD5 перед отправкой их на сервис с помощью HTTP запроса. В JavaScript нет встроенной функции для этого, поэтому установим необходимую библиотеку.
Для этого выполним следующую команду:
npm install blueimp-md5 --save
Этот код установит JavaScript библиотеку blueimp MD5.

Создание логики на TypeScript для использования Gravatar

Цель нашего алгоритма — захешировать email и отправить его в Gravatar в надежде получить от него картинку. Мы не будем делать запросы Angular HTTP из-за формата API.
Пример файла app/app.component.ts:
import { Component } from "@angular/core";
var MD5 = require("blueimp-md5");

@Component({
selector: "my-app",
templateUrl: "app.component.html"
})
export class AppComponent {

public picture: string;
public email: string;

public constructor() {
this.email = "";
this.picture = "https://www.gravatar.com/avatar/00000000000000000000000000000000?s=150";
}

public getProfilePicture(email: any) {
if(this.validateEmail(email)) {
this.picture = "https://www.gravatar.com/avatar/" + MD5(email) + "?s=150";
}
}

// Taken from http://stackoverflow.com/a/46181/498479
private validateEmail(email: any) {
let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
}

}
Здесь не так много кода, но мы всё равно рассмотрим его более подробно.

После импорта компонентов Angular и MD5, мы создаём класс AppComponent. В этом классе есть две public переменных, одна из которых будет содержать ссылку на изображение из Gravatar, будь это реальная картинка или картинка по-умолчанию, а в другой будем хранить текущий email адрес.
В методе constructor мы инициализируем эти переменные. В переменной picture сначала будет ссылка на шаблон изображения Gravatar.
Обратите внимание на метод validateEmail. Я взял его на Stack Overflow. Он проверяет валидность email адреса с помощью регулярного выражения. С помощью этого метода мы уменьшим трафик и будем посылать в Gravatar только корректные адреса почты.
public getProfilePicture(email: any) {
if(this.validateEmail(email)) {
this.picture = "https://www.gravatar.com/avatar/" + MD5(email) + "?s=150";
}
}

В методе getProfilePicture мы передаём валидный email, хешированный алгоритмом MD5 в Gravatar. Если такого адреса в базе не существует, мы получим обратно картинку по-умолчанию, в противном случае нам вернётся реальная аватарка.
В этом проекте мы используем форму ввода для email адреса, но по-умолчанию к ней нет привязок в Angular. Нам необходимо подключить модуль форм, чтобы сделать привязку.
Откроем файл проекта app/app.module.ts и вставим в него следующий код:
import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/platform";
import { NativeScriptFormsModule } from "nativescript-angular/forms";

import { AppComponent } from "./app.component";

@NgModule({
declarations: [AppComponent],
bootstrap: [AppComponent],
imports: [NativeScriptModule, NativeScriptFormsModule],
schemas: [NO_ERRORS_SCHEMA]
})
export class AppModule { }

В нём мы импортируем модуль NativeScriptFormsModule и добавляем в массив imports в блоке @NgModule.
Код не очень простой, зато хорошо работает. Ещё бы добавить немного анимации.

Включение анимационных переходов и состояний Angular

Есть несколько способов добавить анимацию в приложение NativeScript, но для лучшего контроля мы будем использовать анимации Angular. Эти анимации пропишем в нашем коде на TypeScript.
Откроем файл проекта app/app.component.ts и добавим следующий код в блок @Component:
@Component({
selector: "my-app",
templateUrl: "app.component.html",
animations: [
trigger("state", [
transition("void => *", [
animate(200, style({
transform: 'scale(2, 2)'
})),
]),
transition("* => *", [
animate(500, keyframes([
style({ transform: 'scale(2, 2)' }),
style({ transform: 'scale(0, 0)' }),
style({ transform: 'scale(2, 2)' }),
]))
]),
])
]
})

В нём мы прописываем набор анимаций, называем его state хотя состояний в нём нет, только инструкции анимационных переходов.
Если компонент — void, или, другими словами, не отображается на экране, а состояние компонента меняется, то он будет увеличен на 200% в течение 200 миллисекунд. Если состояние изменится на что-то другое, нежели void, мы увидим keyframe-анимацию. В ней элемент масштабируется с 200% до 0% и обратно до 200% в течение 500 миллимекунд. Анимация будет выглядеть как пульсация.
Чтобы изменить первоначальные состояния, нам нужно создать булеву или любую другую переменную для представления текущего состояния:
public currentState: boolean;
С помощью метода constructor мы можем инициализировать и установить эту переменную в false. А когда будет введён корректный email, мы изменим состояние и тип анимации.
Файл app/app.component.ts полностью будет таким:
import { Component, trigger, state, transition, animate, style, keyframes } from "@angular/core";
var MD5 = require("blueimp-md5");

@Component({
selector: "my-app",
templateUrl: "app.component.html",
animations: [
trigger("state", [
transition("void => *", [
animate(200, style({
transform: 'scale(2, 2)'
})),
]),
transition("* => *", [
animate(500, keyframes([
style({ transform: 'scale(2, 2)' }),
style({ transform: 'scale(0, 0)' }),
style({ transform: 'scale(2, 2)' }),
]))
]),
])
]
})
export class AppComponent {

public picture: any;
public email: string;
public currentState: boolean;

public constructor() {
this.email = "";
this.picture = "https://www.gravatar.com/avatar/00000000000000000000000000000000?s=150";
this.currentState = false;
}

public getProfilePicture(email: any) {
if(this.validateEmail(email)) {
this.currentState = !this.currentState;
this.picture = "https://www.gravatar.com/avatar/" + MD5(email) + "?s=150";
}
}

// Taken from http://stackoverflow.com/a/46181/498479
private validateEmail(email: any) {
let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
}

}
Нам остаётся создать представление на XML и CSS.

Создание приятного интерфейса в NativeScript

Основным интерфейсом будет экран с двумя секциями по вертикали. В верхней секции будет аватарка, а в нижней — форма ввода адреса.
Откроем файл app/app.component.html и вставим в него такую разметку XML:













GridLayout позволяет нам разделить экран на две одинаковые секции.
В первом ряду будет наше изображение в переменной picture на языке TypeScript. Чанк [@state] это состояние state анимации и содержит текущее её состояние. Как только это состояние меняется, переклчается и анимация.
Во втором ряду расположена форма с несколькими полями ввода. Первое поле привязано к нашей переменной email и как только меняется её содержимое, данные передаются в метод getProfilePicture.
Зададим свои стили для полей ввода в файле app/app.css:
.form .input-field .input {
padding: 5;
background-color: #F0F0F0;
}

@import 'nativescript-theme-core/css/core.light.css';
В этом CSS добавлен отступ и фон для всех полей ввода текста.

Готово! Мы сделали простое приложение с автоматической подгрузкой аватарки пользователя и её анимацией.

*Оригинал взят у Nic Raboy