返回博客首页
← 所有文章

使用 Angular 和 NativeScript 在 Web 和移动端之间共享代码

2017 年 7 月 6 日 — 作者 Sebastian Witalec

更新 (2018 年 9 月): 本文中描述的工作流程已发生变化。NativeScript 现在直接与 Angular CLI 集成,并允许您从单个代码库构建 Web 和移动应用程序。您可以 在 Angular 博客上阅读该方法的概述,以及 学习如何在 NativeScript 文档中入门.

NativeScript 解决移动开发中的一个重大问题。那就是使用单个源代码构建 iOS 和 Android 应用程序。

提示:在本 YouTube 视频中学习如何将 Angular Web 应用程序迁移到 NativeScript:将原生移动添加到您的 Angular 项目中:迁移故事

NativeScript 旨在解决的重大挑战之一是在 iOS 和 Android 项目之间共享代码。这意味着您可以使用 JavaScript/TypeScript 或 Angular 构建移动应用程序,这些应用程序使用相同的代码来构建这两个平台。但是,Angular 与 NativeScript 的结合使我们能够更进一步,那就是共享 NativeScript 和 Web 应用程序的代码。

这怎么可能呢?

How is that possible

Angular 是一个与平台无关的框架,其中大部分构建块不特定于 Web、移动或桌面。让我们看一个简单的 WeatherService 示例,它有一个函数,该函数返回一个包含给定位置天气的 Observable。

@Injectable()
export class CheckWeatherService {
  constructor(private http: Http) { }

  checkWeather(location: string): Observable<any> {
    return this.http.get(`http://api.magicweather.org/${location}`)
      .map(response => response.json());
  }
}

此服务可以完美地用于 Web 和移动应用程序。您期望在不同平台(iOS、Android、Web)之间表现不同的唯一部分是 Http 模块。这就是第一个神奇之处开始的地方。NativeScript 有一个 Http 模块,它提供了在 iOS 和 Android 上本地发出 http.get 命令(以及所有其他 http 命令)所需的抽象。因此,当您为 iOS 应用程序构建上述服务时,http.get 调用将导致发出本机 iOS 调用,而为 Android 构建则会导致发出本机 Android 调用。

Http NativeScript

但是,从 Web/移动代码共享的角度来看,我们只是使用 Angular 的 依赖注入 将正确的 HttpModule 提供到我们的项目中。因此,对于 Web 项目,我们使用 import { HttpModule } from '@angular/http';,而对于 NativeScript 项目,我们使用 import { NativeScriptHttpModule } from 'nativescript-angular/http';

这将我们带到了更大的图景,我们将在其中将 HttpModule 看作一个模块,它为我们提供执行 http 调用所需的一切,这将对 Web 和移动项目都完全一样。

Http Angular the big picture

可以共享的内容

Sharing

共享服务并不是全部。您还可以轻松地共享

  • 服务
  • 组件类定义 - 也就是 xyz.component.ts
  • 管道
  • 路由配置
  • SCSS 变量

无法共享的内容

Can't share

显然,并非所有内容都可以共享。主要思想是,您无法共享任何特定于 Web 或移动项目的内容。

从 Angular 项目的角度来看,您的差异主要围绕

  • 组件 UI 定义 - 也就是 xyz.component.htmlxyz.component.scss
  • Web 或移动具有不同实现的模块,例如 angularfire2nativescript-plugin-firebase
  • 依赖于 DOM 的操作 - 这些操作将在浏览器中起作用,但在 {N} 中则不行
  • 移动特定的 UX - 例如页面过渡效果
  • UI 组件 - 例如,您需要使用两个不同的库来向您的移动/Web 应用程序添加数据图表。

阐述挑战

Challenge

Web 版 Angular 和 NativeScript 版 Angular 之间似乎有很大的重叠。那么是什么阻止我们深入研究呢?

问题是,目前 Angular CLI 只能用于生成 Web 项目,而对于 NativeScript,您需要使用 NativeScript CLI。这是一个问题,因为 CLI 不会为我们提供一种机制,让我们可以使用一个能够在 Web 和移动之间切换的项目。

当然,我们可以拥有两个独立的项目,并在两个项目之间复制和粘贴可共享的文件。但是,这只会适用于小型项目。

因此,我们需要一个具有 构建过程Angular Seed,该过程可以帮助我们管理 Web 和 NativeScript 项目。它应该允许我们

  • 定义共享文件和平台特定文件,
  • 轻松浏览项目文件夹结构,
  • 分别管理 Web 和移动的 npm 包,
  • 自动执行 Web 和移动的构建过程,
  • 使用生产力工具 - 例如 Web 的 Angular CLI 或 NativeScript 的实时同步

英雄出现

Hero

有很多这样的种子,例如 angular-seed-advancedpeek-web-nsangular2-webpack-advance-starterangular-starternativescript-ng2-starter-kit。因此,很容易迷失在选择之中。

但是,我最喜欢的种子项目是来自 TeamMaestroangular-native-seed

共享文件和平台特定文件

与大多数类似种子一样,它使用命名约定来定义哪些文件应该共享或不共享。让我们看一个具有一个共享 ts 文件和两个 html 文件的 ExampleComponent。共享的 ts 文件应该简单地命名为:example.component.ts,而两个 html 文件应该分别命名为 example.component.html(对于 Web 版本)和 example.component.tns.html(对于移动版本)。

example
 |- example.component.ts
 |- example.component.html
 |- example.component.tns.html

正如您可能注意到的,诀窍是在任何文件扩展名前添加 .tns 以使其成为 NativeScript 特定的,而另一个文件则成为 Web 特定的。因此,如果您想要为我们的模块创建两个 scss 文件,那么您应该创建 example.component.scssexample.component.tns.scss。但是,如果您想共享样式文件,那么您只需要 example.component.scss 即可。

了解项目结构和管理 npm 模块

从代码共享的角度来看,项目由以下文件和文件夹组成(为了帮助专注于重要的内容,我省略了一些文件和文件夹)。

root:
 |- src
    |- app.module.ts
    |- app.module.tns.ts
 |- nativescript
    |- app
    |- src
    |- node_modules
    |- package.json
 |- node_modules
 |- package.json

我们有两对 node_modulespackage.json。根目录下的第一对专用于 web 项目,而 nativescript 文件夹中的一个用于 NativeScript 项目。要为 Web 项目安装 npm 包,只需从 root 文件夹运行所有 npm 命令,而对于 NativeScript 项目,则从 nativescript 文件夹运行命令。如果这两个项目都需要相同的 npm 模块,那么您需要运行两次 npm 命令。

项目 root 中的 src 文件夹是我们所有代码所在的位置 - 包括 Web 和移动 (.tns) - 因为这是我们的工作文件夹。在这里,您会找到服务、组件、管道等,它们构成了您的应用程序。

此外,我们还有两个版本的 app.module,它们用于提供所有共享的和平台特定的模块。一个好的平台特定模块示例是 HttpModule。其中 app.module.ts 导入

import { HttpModule } from '@angular/http';

@NgModule({
  ...
  imports: [
    HttpModule
    ...
  ]

app.module.tns.ts 导入

import { NativeScriptHttpModule } from 'nativescript-angular/http';

@NgModule({
  ...
  imports: [
    NativeScriptHttpModule
    ...
  ]

接下来是带有 appsrc 文件夹的 nativescript 文件夹。nativescript/src 文件夹是 root/src 的符号链接,它显示了所有特定于 NativeScript 项目的文件,同时忽略了仅 Web 文件。例如,ExampleComponent 将如下所示(注意没有 example.component.html

example
 |- example.component.ts
 |- example.component.tns.html

如果您不熟悉 符号链接,那么了解符号链接创建了一个文件夹到另一个文件夹的不同视图是值得的。这意味着,如果您对该文件夹中的任何文件进行了任何更改,这将反映在其 roor/src 对应文件中,这些文件也会更新。事实上,这些并不是真正的对应文件,而是相同的文件(不是副本)。

最后,我们有 nativescript/app 文件夹。这是 gulp 构建过程(稍后将详细介绍)输出 NativeScript 准备好的文件的文件夹,这些文件来自 nativescript/src。这是查看最终出现在您的 nativescript 文件夹中的内容以及以什么形式的好地方。此外,这也是实际用于 NativeScript 构建过程的 THE 文件夹(即:所有 tns 命令都在此代码上运行)。

使用生产力工具

此种子的结构使我们能够非常轻松地使用 Angular 和 NativeScript 的最佳 生产力工具。在您能使用哪些工具方面,实际上没有限制。唯一的区别是您从哪里运行工具。因此,对于所有 Web 工具(例如 Angular CLI),请从 root 文件夹运行它们,而对于所有 NativeScript 工具 -> 请从 nativescript 文件夹运行它们。

您想使用 Angular CLI 创建服务吗?只需从 root 文件夹运行 ng g s service-name,您将获得所有必要的构建块。请注意,只有 app.module.ts 提供程序将被更新,因此您需要手动更新 app.module.tns.ts 以使此服务在 NativeScript 应用程序中也可用。

要调试 NativeScript 应用程序,只需进入 NativeScript 文件夹并运行 npm run livesync,然后在另一个终端(命令行)中运行 tns debug iostns debug android。这不仅会运行 NativeScript 的调试工具,还会在您对 <root>/srcnativescript/src 文件夹进行任何更改时刷新您的项目。

Angular 语言服务呢?好吧... 只需安装它,您就可以开始使用了。

自动化构建过程

使上述所有操作成为可能的是自动化构建过程,该过程通过几个易于阅读的 gulp 任务 来执行,这些任务由 npm 脚本 管理。

npm 脚本 + gulp 任务仅用于构建 NativeScript 项目,因为 root/src 已经包含了 Web 项目所需的一切。

简而言之,gulp 任务的实质是

  1. nativescript/src 中查找任何 .tns 文件
  2. 通过删除 .tns 部分(这将覆盖 Web 特定文件)来重命名它们
  3. 将它们移动到 nativescript/app 文件夹中

然后,gulp 任务与几个 npm 脚本捆绑在一起,其中

  1. 第一步是运行 gulp 步骤
  2. 第二步是运行 NativeScript CLI

例如,要运行 iOS 构建,您需要调用 npm run ios,该命令在内部 运行

  1. gulp: gulp build.cli.Default
  2. tns: tns run ios

入门

Getting started

要开始使用,您需要做的就是

1. 克隆仓库

git clone https://github.com/TeamMaestro/angular-native-seed

2. 初始化 Web 项目

npm i


3. 初始化 NativeScript 项目(确保你已准备好所有 NativeScript 组件。 安装说明

cd nativescript
npm i


4. 运行 Web 项目

root 文件夹运行

ng serve


5. 运行 NativeScript 项目

nativescript 文件夹运行

npm run ios

npm run android


6. 使用 NativeScript 实时同步

nativescript 文件夹运行以下两个命令,每个命令在单独的终端中运行。

npm run livesync

tns run android/ios

简单

Simple

现在我们已经涵盖了基础知识。让我们尝试添加一个新的 simple 组件。我们将使用 Angular CLI 生成组件,然后更新它使其在 NativeScript 中可用,最后将其添加到菜单和导航中。

完成此练习后,组件的文件结构应如下所示

simple
 |- simple.component.html
 |- simple.component.scss
 |- simple.component.spec.ts
 |- simple.component.tns.html
 |- simple.component.ts (not generated by the CLI)

1. 使用 ng CLI 生成 simple 组件

ng g c simple

2. 将组件添加到 NativeScript @NgModule 提供程序

Angular CLI 会自动将组件添加到 app.module.ts 的声明中。但 NativeScript 的对应部分并非如此。

打开 app.module.tns.ts,导入组件并将其添加到声明中。

import { SimpleComponent } from './simple/simple.component';
...
@NgModule({
    declarations: [ AppComponent, SimpleComponent ],

3. 更新组件类

打开 simple.component.ts,将 moduleId: module.id, 添加到 @Component 中。这是 NativeScript 查找 templateUrlstyleUrls 路径所必需的。

然后向类添加一个带有警报的函数。

你的 simple.component.ts 应如下所示

import { Component, OnInit } from '@angular/core';

@Component({
  moduleId: module.id,
  selector: 'seed-simple',
  templateUrl: './simple.component.html',
  styleUrls: ['./simple.component.scss']
})
export class SimpleComponent implements OnInit {

  ngOnInit() {
  }

  sayHello() {
    alert('One hello to rule them all');
  }
}

4. 更新 UI

首先,让我们更新 Web html,打开 simple.component.html 以显示一些消息并添加一个按钮来调用 sayHello

<h1 class="title">Simply Red</h1>
<p class="description">Something got me started :)</p>

<button (click)="sayHello()">Say Hello</button>

之后,让我们创建 NativeScript 的对应部分。创建一个名为 simple.component.tns.html 的新文件,并添加以下代码

<GridLayout rows="auto,auto">
    <Label class="h1 title"
        text="Simply Red"></Label>
    <Label class="p description" textWrap="true" row="1"
        text="Something got me started"></Label>
    
    <Button (tap)="sayHello()" class="btn btn-primary">Say Hello</Button>
</GridLayout>

请注意,Web 按钮处理 click 事件,而 NativeScript 项目处理 tap 事件。

5. 更新导航以允许导航到 SimpleComponent

打开 home/home.routes.ts。这是配置默认导航的位置。以及 SimpleComponent 的路径(不要忘记先导入它)

{
    path: 'simple',
    component: SimpleComponent
},

现在让我们更新导航菜单。所有菜单项都位于 app.component.ts 中。让我们添加一个新的 MenuItem

{
    title: 'Simple',
    link: ['/simple']
},

6. 让我们测试一下

要运行 Web 应用程序,请运行

ng serve

并导航到 Simple 页面。

要运行 NativeScript 应用程序,请导航到 nativescript 文件夹并运行

npm run ios
// or 
npm run android

示例摘要

请注意,开始运行需要付出多小的努力。在这一点上,我们并没有做任何花哨的事情,但是我们已经能够从 SimpleComponent 中共享 sayHelo。此外,我们还获得了使用 Angular CLI 的额外优势。:)

最好的事情是,阅读此示例的时间比实际构建它所需的时间更长。

使用延迟加载

Lazy loading

现在让我们尝试一个更复杂的示例,该示例包含在其模块中的组件,并具有自己的路由。为了使它更好,我们将将其添加为延迟加载的组件。

我们再次使用 Angular CLI 为我们生成组件文件。然后,我们将重新排列组件文件并添加路由和模块文件。最后,我们将组件添加到应用程序导航中。

完成此练习后,组件的文件结构应如下所示

lazy-cat
 |- components
    |- lazy-cat
      |- lazy-cat.component.html
      |- lazy-cat.component.scss
      |- lazy-cat.component.spec.ts
      |- lazy-cat.component.tns.html
      |- lazy-cat.component.ts
 |- lazy-cat.module.ts
 |- lazy-cat.routes.ts

1. 使用 ng CLI 生成 lazy-cat 组件

ng g c lazy-cat

2. 重新排列组件文件

lazy-cat 文件夹中创建 components 文件夹,然后在 components 文件夹中创建 lazy-cat。最后将所有组件文件移动到 lazy-cat/components/lazy-cat 中。

3. 更新 UI 文件

使用以下内容更新 lazy-cat.component.html

<h1 class="title">Lazy Cat</h1>
<p class="description">Garfield, you could give classes in doing nothing</p>
<p class="description">No, I couldn't</p>

然后创建 lazy-cat.component.tns.html,其中包含以下代码

<StackLayout>
    <Label class="h1 title"
        text="Simply Red"></Label>
    <Label class="p description" textWrap="true"
        text="Garfield, you could give classes in doing nothing"></Label>
    <Label class="p description" textWrap="true"
        text="No, I couldn't"></Label>
</StackLayout>

4. 更新组件类

打开 lazy-cat.component.ts 并添加到 @Component 定义中。

moduleId: module.id,

5. 创建路由

现在,我们需要创建路由定义,它将适用于 Web 项目和 NativeScript 项目。

添加 lazy-cat.routes.ts,其中包含一个空的路径,该路径将导航到 LazyCatComponent

import { Routes } from '@angular/router';
// app
import { LazyCatComponent } from './components/lazy-cat/lazy-cat.component';

export const LazyCatRoutes: Routes = [
    {
        path: '',
        component: LazyCatComponent
    }
];

如果此模块包含多个视图,那么这里将有多个路由。

代码片段

如果你使用的是 Visual Studio Code,那么你可以使用代码片段来帮助你完成此任务。

你可以使用插件 angular-native-seed Snippets,它提供代码片段来快速生成 routesmodule 的代码。

或者你可以 手动将代码片段添加到你的项目中

"share-router": {
  "prefix": "share-router",
  "body": [
    "import { Routes } from '@angular/router';",
    "// app",
    "import { ${Name}Component } from './components/${filename}/${filename}.component';",
    "",
    "export const ${Name}Routes: Routes = [",
    "    {",
    "        path: '',",
    "        component: ${Name}Component",
    "    }",
    "];",
    ""
  ]
},

要使用此代码片段,只需开始键入 share,选择 share-router 并按回车键。然后你需要先键入 LazyCat(作为类名的前缀),然后按 tab 键并键入 lazy-cat(作为文件/文件夹的名称)。

Snippet Routes

6. 创建模块定义

现在我们需要将其包装在 @NgModule 中,它将适用于 Web 项目和 NativeScript 项目。

问题是,虽然 Web 项目使用 CommonModuleFormsModuleRouterModule,但 NativeScript 使用 NativeScriptModuleNativeScriptFormsModuleNativeScriptRouterModule。为了克服这个问题,我们只需要使用 ../shared 中的 SharedModule,它导出我们在每个新模块中需要的核心模块。请注意,SharedModule 在两个文件中定义:shared.module.ts(带有 Web 模块)和 shared.module.tns.ts(带有 NativeScript 模块)。

我们还需要使用 forChild 加载 LazyCatRoutes。为了确保你使用的是正确的 RouterModule(Web 与 NativeScript),请从 ../shared 中导入它。

其余部分是相当标准的 @NgModule 定义。

你的 lazy-cat.module.ts 应该像这样

import { NgModule } from '@angular/core';
// vendor dependencies
import { TranslateModule } from '@ngx-translate/core';
// app
import { LazyCatComponent } from './components/lazy-cat/lazy-cat.component';
import { LazyCatRoutes } from './lazy-cat.routes';
// common
import { SharedModule, RouterModule } from '../shared';

@NgModule({
    imports: [
        SharedModule,

        RouterModule.forChild(LazyCatRoutes),
        TranslateModule.forChild()
    ],
    declarations: [LazyCatComponent]
})
export class LazyCatModule { }

代码片段

你再次可以使用代码片段来完成此操作。这是我的代码片段

"share-module": {
    "prefix": "share-module",
    "body": [
        "import { NgModule } from '@angular/core';",
        "// vendor dependencies",
        "import { TranslateModule } from '@ngx-translate/core';",
        "// app",
        "import { ${Name}Component } from './components/${filename}/${filename}.component';",
        "import { ${Name}Routes } from './${filename}.routes';",
        "// common",
        "import { SharedModule } from '../shared';",
        "import { RouterModule } from '../common';",
        "",
        "@NgModule({",
        "    imports: [",
        "        SharedModule,",
        "",
        "        RouterModule.forChild(${Name}Routes),",
        "        TranslateModule.forChild()",
        "    ],",
        "    declarations: [${Name}Component]",
        "})",
        "export class ${Name}Module { }",
        ""
    ]
},

这次要使用它,只需键入 share 并选择 share-module。我相信你接下来该做什么。

Snippet Module

7. 更新导航以允许使用延迟加载导航到 LazyCatComponent

由于我们将延迟加载此组件,因此我们不需要在应用程序模块中声明它。打开 app.module.ts,删除 LazyCatComponent 导入并 删除它@NgModule 声明 中。

打开 home/home.routes.ts。要添加 LazyCatComponent 的路径(这里不需要导入它),并使用延迟加载

{
    path: 'lazy',
    loadChildren: 'app/lazy-cat/lazy-cat.module#LazyCatModule'
},

现在让我们更新导航菜单。打开 app.component.ts 并添加一个新的 MenuItem

{
    title: 'Lazy',
    link: ['/lazy']
},

8. 运行应用程序

你已经知道该怎么做了。

ng serve 用于 Web,npm run ios/android 用于 NativeScript

示例摘要

模块的结构非常清晰,易于导航。借助提供的代码片段,你可以在几分钟内创建一个新模块。

SharedModule 为你提供了一种简洁的方式来提供独立的 Web 和 NativeScript 模块。如果你有其他模块,你希望将其与大多数组件一起使用,那么你只需将它们添加到 SharedModule 中,但要确保不要使其过大,因为你应该将其与大多数组件一起使用。

更复杂的模块

要创建更复杂的模块,这些模块包含多个 Components,甚至需要提供不适合 SharedModule 的不同 Modules,我们需要将模块拆分为两个文件:name.module.tsname.module.tns.ts。而组件将进入 components 文件夹。

假设你需要一个带有无限滚动的页面,它会在你每次到达页面底部时加载新的项目。对于 Web,我们需要使用 npm 模块 ngx-infinite-scroll 中的 InfiniteScrollModule。而对于 NativeScript,我们可以直接使用 ListView

在这种情况下,我们需要从项目的根目录运行 npm install --save ngx-infinite-scroll,将无限滚动包添加到 Web 项目中。然后,我们需要导入 InfiniteScrollModule 并将其添加到 @NgModule imports 中,以添加到 infinite.module.ts(这应该使其为 Web 项目做好准备)

...
import { InfiniteScrollModule } from 'ngx-infinite-scroll';

@NgModule({
    imports: [
        SharedModule,

        RouterModule.forChild(InfiniteRoutes),
        TranslateModule.forChild(),

        InfiniteScrollModule
    ],
    declarations: [InfiniteComponent, ItemTemplateComponent]
})

然后,对于 NativeScript 模块,我们需要创建 infinite.module.tns.ts,它与 infinite.module.ts 完全相同,只是没有额外的模块(no InfiniteScrollModule

...
@NgModule({
    imports: [
        SharedModule,

        RouterModule.forChild(InfiniteRoutes),
        TranslateModule.forChild()
    ],
    declarations: [InfiniteComponent, ItemTemplateComponent]
})

示例摘要

你可以在 这里 找到完整的示例。

除了 InfiniteScrollModule 和多个 Component declarations 之外,其他所有操作都与 LazyCat 示例中相同。从这个示例中得到的关键教训是,每当你需要将代码块拆分为 Web 和 NativeScript 时,只需将源代码拆分为两个文件即可。同样适用于样式。如果你需要两个样式文件(你很可能需要),那么你可以将它们命名为 name.component.scssname.component.tns.scss

添加翻译(奖励)

Languages

作为奖励,我们将介绍如何使用 i18n 模块来实现特定于语言的文本。

它的工作方式相当直接。对于每种支持的语言,你都需要一个 json 资源文件(如 en.json、fr.json),其中应包含所有必需短语的键 - 翻译值对。然后,要翻译短语,我们使用 translate pipe,它查找键并返回翻译后的值。

让我们试一试。

1. 更新/创建语言文件

让我们在 src/assets/i18n/en.json 中找到现有的英文资源文件,并添加 simplelazy 示例的字段

{
    "menu": {
        "home": "Home",
        "about": "About",
        "lazy": "Lazy",
        "simple": "Simple"
    },
    "home": {
        "title": "Home",
        "description": "This component is part of a module that was directly imported into the main AppModule."
    },
    "about": {
        "title": "About",
        "description": "This component was rendered from a lazy-loaded module."
    },
    "phone": {
        "edition": "Phone Edition!"
    },
    "simple": {
        "title": "Simply Red",
        "description": "Something got me started."
        "sayHello": "Say Hello"
    },
    "lazy": {
        "title": "Lazy joke",
        "jonsLine": "Jon: Garfield, you could give classes in doing nothing",
        "garfieldsLine": "Garfield: No, I couldn't"
    }
}

要添加西班牙语,只需添加一个名为 es.json 的新文件,其中包含相同的字段,但包含翻译成西班牙语的值,如下所示(请注意,我使用了 Google 翻译服务,因此可能会出现错误)

{
    "menu": {
        "home": "Casa",
        "about": "Acerca de",
        "lazy": "Perezoso",
        "simple": "Sencillo"
    },
    "home": {
        "title": "Casa",
        "description": "Este componente es parte de un módulo que fue importado directamente en el AppModule principal."
    },
    "about": {
        "title": "Acerca de",
        "description": "Este componente se procesó desde un módulo cargado de modo lento."
    },
    "phone": {
        "edition": "¡Edición del teléfono!"
    },
    "simple": {
        "title": "Simplemente rojo",
        "description": "Algo me hizo comenzar."
        "sayHello": "Di hola"
    },
    "lazy": {
        "title": "Broma perezosa",
        "jonsLine": "Jon: Garfield, podrías dar clases sin hacer nada",
        "garfieldsLine": "Garfield: No, no pude"
    }
}

2. 更新导航菜单

导航菜单已经期望翻译后的值,因此我们只需要更新 app.component.ts 中的 MenuItem 即可。将 MenuItems 中的 simple 和 lazy 更改为

{
    title: 'menu.simple',
    link: ['/simple']
},
{
    title: 'menu.lazy',
    link: ['/lazy']
},

这将使用来自所需 language.json 文件的 menu.simplemenu.lazy 的文本值。

3. 更新 UI

要提供特定于语言的值,我们将使用 translate 管道,它将在 language.json 文件中查找翻译后的值。

打开 lazy.component.html 并将其更新为

<h1 class="title"
    [innerText]="'lazy.title' | translate"></h1>
<p class="description"
    [innerText]="'lazy.jonsLine' | translate"></p>
<p class="description"
    [innerText]="'lazy.garfieldsLine' | translate"></p>

然后打开 lazy.component.tns.html 并将其更新为

<StackLayout>
    <Label class="h1 title"
        [text]="'lazy.title' | translate"></Label>

    <Label class="p description" textWrap="true"
        [text]="'lazy.jonsLine' | translate"></Label>
    <Label class="p description" textWrap="true"
        [text]="'lazy.garfieldsLine' | translate"></Label>
</StackLayout>

4. 向项目添加语言切换功能

打开 menu/components/menu/menu.component.ts,并将以下函数添加到 MenuComponent 类中(它将切换到提供的语言文件)

switchLanguage(language: string) {
    this.translate.use(language);
}

现在让我们将切换按钮添加到 UI 中。

打开 menu.component.html 并使用两个新按钮更新它,这两个按钮将在英语和西班牙语之间切换

<seed-menu-item *ngFor="let item of items"
    [item]="item"></seed-menu-item>

<Button (click)="switchLanguage('en')" class="language-button">EN</Button>
<Button (click)="switchLanguage('es')" class="language-button">ES</Button>

我们还需要对 NativeScript 菜单进行类似的操作。打开 menu.component.tns.html 并使用以下内容更新它

<ScrollView orientation="horizontal">
    <StackLayout orientation="horizontal">
        <seed-menu-item *ngFor="let item of items"
            [item]="item"></seed-menu-item>
        <Button text="EN" (tap)="switchLanguage('en')" class="btn btn-primary"></Button> 
        <Button text="ES" (tap)="switchLanguage('es')" class="btn btn-primary"></Button>
    </StackLayout>
</ScrollView>

现在,让我们进行最后一步,添加一些样式。打开 menu.component.scss 并添加

.language-button {
    background-color: #30bcff;
    border: none;
    color: white;
    padding: 8px 16px;
    text-align: center;
    text-decoration: none;
    display: inline-block;
    font-size: 16px;
}

5. 测试它

运行项目时,你应该看到类似这样的内容。

Web - 示例

translation-web

移动 - 示例

Translation Mobile

从 TypeScript 中翻译

要在 TypeScript 中使用翻译,你需要将 TranslationService 注入到构造函数中。

import { TranslateService } from '@ngx-translate/core';
...
constructor(private translate: TranslateService) { }

然后,你只需调用 this.translate.get(_key_),它返回一个 Observable,它将为你提供结果。

如果我们要重构 SimpleComponent,那么它将看起来像这样

import { Component, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import 'rxjs/add/operator/first';

@Component({
  moduleId: module.id,
  selector: 'seed-simple',
  templateUrl: './simple.component.html',
  styleUrls: ['./simple.component.scss']
})
export class SimpleComponent implements OnInit {

  constructor(private translate: TranslateService) { }

  ngOnInit() {
  }

  sayHello() {
    // Hello message with translation
    this.translate.get('simple.hello').first()
    .subscribe(message => alert(message));
  }
}

这是完整的示例:simple.component.ts

示例摘要

使用 i18n 翻译模块相当简单。只需为每种语言创建一个 language.json 文件,并对需要翻译的每个文本使用 translation 管道。你甚至可以使用翻译管道为每种语言选择不同的图像。

摘要 - 即使用它

Summary

代码共享绝对是现代开发世界中一个非常重要的挑战。并非所有公司都能负担得起为每个平台(Web、iOS、Android、桌面等)组建多个团队来支持开发。最后,我们找到了一个不仅带来希望,而且提供了解决方案的方案,我们可以继续完善它。

TeamMastro's 种子让任何拥有少量 Angular 和 NativeScript 知识的人都可以轻松共享代码。在 angular-native-seed 中,你可能希望看到很多开箱即用的功能,但一个好的项目模板的关键在于 简单性可扩展性,而这一点正是这里所体现的。

还有很多我想与你分享的,但请别担心,这仅仅是我们代码共享故事的开始。你可以在不久的将来期待更多内容。话虽如此,这应该能为你提供足够的信息来涵盖大多数常见用例。试试看,如果有任何改进建议,请告诉我们,或者发送你可能需要帮助的问题。