Angular i18n 应用程序的 Nginx 配置

Posted

技术标签:

【中文标题】Angular i18n 应用程序的 Nginx 配置【英文标题】:Nginx configuration for angular i18n application 【发布时间】:2018-09-28 01:39:42 【问题描述】:

我使用支持法语和英语的 i18n 构建了一个 angular-5 应用程序。然后,我为每种支持的语言部署了一个单独的应用版本

- 分布 |___ zh/ | |__ 索引.html |___ 前/ |__ 索引.html

我还添加了以下 nginx 配置来为两种语言的应用程序提供服务;

服务器 根 /var/www/dist; 索引 index.html 索引.htm; server_name host.local; 位置 ^/(fr|en)/(.*)$ try_files $2 $2/ /$1/index.html;

我想做的是为这两个应用程序提供服务,并允许在英文版和法文版之间切换。

比如说我在host.local/en/something 如果我切换到host.local/fr/something,我应该会得到法语版的“某事”页面。

使用我共享的 nginx 配置,每次我在浏览我的应用程序时刷新页面时都会收到 404 not found 错误,这也阻止了我彼此独立浏览我的应用程序并在它们之间切换。

我错过了什么?什么是合适的 Nginx conf 来实现这一目标?

【问题讨论】:

你试过调试 nginx 规则看看为什么它没有选择正确的文件吗? 正则表达式location 语句需要~~* 前缀。见this link。 你试过理查德的评论了吗?您没有发布相同的反馈 @David Thx,我在内部重定向到“/index.html”时得到“3 个重写或内部重定向周期”,但在尝试其他一些配置时,我也遇到了其他类型的问题 Richard & TuranThx,我已修复问题,但没有任何改变 如果看起来有问题,您可以发布重写日志吗? 【参考方案1】:

我在 iis 上做了同样的事情,首先你必须使用“base-href”选项构建你的应用程序:

 ng build --output-path=dist/fr --prod --bh /fr/
 ng build --output-path=dist/en --prod --bh /en/

对于 nginx 使用这个配置

location /fr/ 
  alias /var/www/dist/fr/;
  try_files $uri$args $uri$args/ /fr/index.html;

location /en/ 
 alias /var/www/dist/en/;
 try_files $uri$args $uri$args/ /en/index.html;

对于从 /en/someroute 到 /fr/someroute 的导航,您可以在拥有语言切换器的组件中获取当前路由器 url

getCurrentRoute() 
    return this.router.url;

当点击语言选择器时,您会重定向到所选语言的同一条路线:

 <li *ngFor="let language of languages;let i=index" >
    <a  href="/language.lang/#getCurrentRoute()"  (click)="changeLanguage(language.lang)">
        language.lang
    </a>
 </li>

更改语言方法

changeLanguage(lang: string) 
    const langs = ['en', 'fr'];
    this.languages = this.allLanguages.filter((language) => 
        return language.lang !== lang;
    );
    this.curentLanguage = this.allLanguages[langs.indexOf(lang)].name
    localStorage.setItem('Language', lang);
    if (isDevMode()) 
        location.reload(true);
    

【讨论】:

谢谢!它可以按预期进行导航,但是我如何确保当 url 为 host.local 时它会自动重定向到 host.local/en ?我在末尾添加了另一个位置: location / try_files $uri $uri/ /en/index.html; 但它不起作用 changeLanguage 方法有什么作用? 它在本地存储和选择切换器中设置当前语言 在开发模式下你必须强制重新加载,但不是在 prod 上 @FatehMohamed 请帮帮我。【参考方案2】:

构建后:

ng build --prod --base-href /fr/ --output-path dist/fr
ng build --prod --base-href /en/ --output-path dist/en

将构建复制到 nginx 服务目录:

cp -r dist/* /usr/share/nginx/my-app

然后我发现了 2 个适合我的不同 NGINX 配置:

使用根路径

server 
    root /usr/share/nginx/my-app;
    location /en/ 
        autoindex on;
        try_files $uri$args $uri$args/ /en/index.html;
    

    location /fr/ 
        autoindex on;
        try_files $uri$args $uri$args/ /fr/index.html;
    

    # Default to FR
    location / 
        # Autoindex is disabled here + the $uri$args/ is missing from try_files
        try_files $uri$args /fr/index.html;
    

使用别名

server 
    listen 80 default_server;
    index index.html;

    location /en/ 
        alias /usr/share/nginx/my-app/en/;
        try_files $uri$args $uri$args/ /en/index.html;
    

    location /fr/ 
        alias /usr/share/nginx/my-app/fr/;
        try_files $uri$args $uri$args/ /fr/index.html;
    

    # Default to FR
    location / 
        alias /usr/share/nginx/my-app/fr/;
        try_files $uri$args $uri$args/ /fr/index.html;
    

注意:在 根路径 解决方案中,您可以删除 autoindex on 选项,但您还必须从 try_files 中删除 $uri$args/ 部分否则你会得到“directory index of "[directory]" is forbidden error”。

仅供参考:您可以找到有用的those nice explanations on ROOT vs ALIAS。

版本

Angular CLI: 6.0.7
Node: 8.11.2
Angular: 6.0.3

【讨论】:

【参考方案3】:

Angular 9 有一个新选项可以一次构建所有语言版本。它还通过将语言环境添加到配置的 baseHref 来为每个版本的应用程序设置基本 HREF。

ng build --prod --localize

然后你需要将所有构建复制到 nginx 服务目录

COPY /dist/my-app/ /usr/share/nginx/my-app/

并按照前面的答案配置nginx。

【讨论】:

【参考方案4】:

这是我为多个项目解决此问题的解决方案:

nginx.conf

http 
    server 

        # Sets our default language (it's the angular template default language)
        set $defaultLang "de";

        listen 80;

        root /usr/share/nginx/html;
        index index.html;
        include /etc/nginx/mime.types;

        ################## IMPORTANT (don't change this) ##################

        # Make sure when routing to location, server uses the correct angular project subfolder
        # Matches the following urls:
        # http://localhost/de
        # http://localhost/de/
        # http://localhost/de/login
        # http://localhost/notexist/login => In this case, try_files doesn't found a matching index.html and jumps into the @languageFallback
        location ~ "^(/([a-z]2,2)/)(/?.*)?$" 
          try_files $uri $uri /$2/index.html @languageFallback;
        

        # Make sure when routing to the root the root index is used (and we redirect through the small JS script -> redirect.js)
        # Matches the following urls:
        # http://localhost
        # http://localhost/
        location / 
          try_files $uri $uri/ /index.html;
        

        # Language fallback which is used when user tries to open a language which doesn't exist
        # E.g When user trying to open http://localhost/notexist/login but it doesnt exist, then we rewrite the url to
        # http://localhost/de/login
        location @languageFallback 
          rewrite "^(/([a-z]2,2)/)(/?.*)?$" $scheme://$http_host/$defaultLang/$3 last;
        
    

然后我有一个额外的 index.html,里面有那个小脚本,它被复制到 nginx 根目录:

附加索引.html

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta content="IE=edge" http-equiv="X-UA-Compatible"/>
  <title></title>
  <script>
    (function()
    
      let redirectUrl;
      const supportedLanguages = ['de', 'en'];
      const fallbackLanguage = 'de';

      // Read browser locale and use this as default language (only when no locale in localstorage was found)
      let locale = (navigator.language || navigator['userLanguage']).slice(0, 2);
      const storedLocale = localStorage.getItem('locale');

      console.info('BROWSER LOCALE: ', locale);
      console.info('STORED LOCALE: ', storedLocale);

      //Check if a locale was already set in localstorage and use this or set the default language by default
      //and browsers locale is not supported by app we fallback to the fallback language
      if (!storedLocale)
      
        if (supportedLanguages.indexOf(locale) === -1)
        
          locale = fallbackLanguage;
        
      
      else
      
        locale = storedLocale;
      

      redirectUrl = location.origin + '/' + locale + '/';
      console.info('REDIRECT TO: ', redirectUrl);

      // Redirect to correct language
      location.replace(redirectUrl);
    )();

  </script>
</head>
<body>
</body>
</html>

此文件用于根据用户的浏览器语言或当用户更改应用程序中的语言时将其路由到正确的语言子项目,它存储在本地存储中,并且该语言具有更高的优先级。

还有我的 angular.json


  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "",
  "projects": 
    "my-project": 
      "i18n": 
        "sourceLocale": 
          "code": "de",
          "baseHref": "/"
        ,
        "locales": 
          "en": 
            "translation": "src/locales/messages.en.xlf",
            "baseHref": "/"
          
        
      ,
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "prefix": "sg",
      "schematics": 
        "@schematics/angular:component": 
          "style": "scss"
        
      ,
      "architect": 
        "build": 
          "builder": "@angular-devkit/build-angular:browser",
          "options": 
            "aot": true,
            "outputHashing": "all",
            "outputPath": "dist/my-project",
            "resourcesOutputPath": "assets/fonts",
            "baseHref": "/",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.app.json",
            "assets": [
              "src/assets"
            ],
            "styles": [
              "src/styles.scss"
            ],
            "scripts": [],
            "stylePreprocessorOptions": 
              "includePaths": [
                "src/app"
              ]
            
          ,
          "configurations": 
            "production": 
              "optimization": true,
              "sourceMap": false,
              "extractCss": true,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "fileReplacements": [
                
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                
              ],
              "i18nMissingTranslation": "error",
              "budgets": [
                
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                ,
                
                  "type": "anyComponentStyle",
                  "maximumWarning": "6kb"
                
              ]
            ,
            "dev": 
              "budgets": [
                
                  "type": "anyComponentStyle",
                  "maximumWarning": "6kb"
                
              ],
              "i18nMissingTranslation": "error"
            
          
        ,
        "serve": 
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": 
            "browserTarget": "my-project:build"
          ,
          "configurations": 
            "dev": 
              "browserTarget": "my-project:build:dev"
            
          
        ,
        "extract-i18n": 
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": 
            "browserTarget": "my-project:build"
          
        ,
        "test": 
          "builder": "@angular-devkit/build-angular:karma",
          "options": 
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "tsconfig.spec.json",
            "karmaConfig": "karma.conf.js",
            "styles": [
              "src/styles.scss"
            ],
            "scripts": [],
            "assets": [
              "src/assets"
            ]
          
        ,
        "lint": 
          "builder": "@angular-devkit/build-angular:tslint",
          "options": 
            "tsConfig": [
              "tsconfig.app.json",
              "tsconfig.spec.json"
            ],
            "exclude": [
              "**/node_modules/**"
            ]
          
        ,
        "xliffmerge": 
          "builder": "@ngx-i18nsupport/tooling:xliffmerge",
          "options": 
            "xliffmergeOptions": 
              "srcDir": "src/locales",
              "genDir": "src/locales",
              "i18nFile": "messages.xlf",
              "i18nBaseFile": "messages",
              "i18nFormat": "xlf",
              "encoding": "UTF-8",
              "defaultLanguage": "de",
              "languages": [
                "en"
              ],
              "removeUnusedIds": true,
              "supportNgxTranslate": false,
              "ngxTranslateExtractionPattern": "@@|ngx-translate",
              "useSourceAsTarget": true,
              "targetPraefix": "",
              "targetSuffix": "",
              "beautifyOutput": true,
              "allowIdChange": false,
              "autotranslate": false,
              "apikey": "",
              "apikeyfile": "",
              "verbose": false,
              "quiet": false
            
          
        
      
    
  ,
  "defaultProject": "my-project"

最后是来自我的 package.json 的一些重要的 npm 脚本

"build": "ng build --prod --localize",
"i18n": "ng xi18n --format=xlf --output-path=src/locales --out-file=messages.xlf",
"xliffmerge": "ng run my-project:xliffmerge",
"translate": "npm run i18n; npm run xliffmerge"

【讨论】:

【参考方案5】:

http://nginx.org/r/try_files 的工作方式存在一个常见的误解。如果您仔细查看文档(来自上面的链接),您会注意到虽然try_files 指令中的第一个和中间参数是“file”类型,但最后一个参数称为“uri”;在您的情况下,一旦您修复 http://nginx.org/r/location 以正确处理正则表达式(您的 location 代码缺少 ~ 修饰符),可能会导致无限循环,正如您在 cmets 中确认的那样。

请注意,一般情况下,不建议在 nginx 中使用正则表达式,以便在可以避免使用正则表达式的简单情况下获得最大性能,因此,我建议您有两个独立的位置,每个位置用于英语和法语。

location /fr/ 
    try_files $uri /fr/index.html =410;

location /en/ 
    try_files $uri /en/index.html =410;

请注意,上面的代码假定您从 webapp 前端本身执行正确的 URL 处理 - 如果给定资源在 /en//fr/ 版本之间共享,那么它将直接通过 / 请求基础,没有 /en//fr/ 说明符。代码的=410 部分的行为与=404 的行为类似,只是错误略有不同,以便更容易调试导致错误的指令。

【讨论】:

我没有检测到线程。自己的线程:1 次重写,1 次昂贵的重写。 pagespeed:回滚gzip,/etc/nginx/nginx.conf:48中的显式配置 @AhmedSiouani 我看不出您提供的 pagespeed 错误消息与您的原始问题或我的回答有什么关系。 谢谢,这与您的回答有关。 Fateh Mohamed 的回答帮助我修复了从法语到英语的导航,但我还有另一个小问题要解决。重定向到 en 作为默认语言。

以上是关于Angular i18n 应用程序的 Nginx 配置的主要内容,如果未能解决你的问题,请参考以下文章

使用 Firebase 云功能为带有 i18n 的 Angular 10 s-s-r 通用应用程序提供服务

如何在 Angular 中将 i18n 模板用于 eslint?

Angular 国际化 (i18n) 和 *ngFor

占位符文本的 Angular2 i18n

使用 i18n 翻译 Angular 2 中的数据绑定文本

Angular Universal 与 i18n(服务器端渲染)