简体   繁体   English

angular i18n 应用程序的 Nginx 配置

[英]Nginx configuration for angular i18n application

I built an angular-5 application using i18n that supports both french and english.我使用 i18n 构建了一个支持法语和英语的 angular-5 应用程序。 I then deployed a separate version of the app for each supported language然后我为每种支持的语言部署了一个单独的应用程序版本

- dist
    |___ en/
    |    |__ index.html
    |___ fr/
         |__ index.html

I also added the following nginx configuration to serve the application in both languages;我还添加了以下 nginx 配置来为两种语言的应用程序提供服务;

server {
    root /var/www/dist;
    index index.html index.htm;
    server_name host.local;

    location ^/(fr|en)/(.*)$ {
        try_files $2 $2/ /$1/index.html;
    }
}

What I wanted to do is to serve both applications and allow switching between the english and the french version.我想做的是为这两个应用程序提供服务,并允许在英文和法文版本之间切换。

Let's say for example I'm on host.local/en/something if I switch to host.local/fr/something I should get the french version of the "something" page.例如, host.local/en/something我在host.local/en/something如果我切换到host.local/fr/something我应该获得“something”页面的法语版本。

With the nginx configuration I shared, I get a 404 not found error every time I refresh pages when browing my apps which also prevents me from browsing my apps independently from one another and switching between them.使用我共享的 nginx 配置,每次我在浏览应用程序时刷新页面时都会收到 404 not found 错误,这也阻止我独立浏览我的应用程序并在它们之间切换。

What did I miss?我错过了什么? What's the appropriate Nginx conf to achieve that?什么是合适的 Nginx conf 来实现这一目标?

i've done the same thing on iis, first you have to build your app with "base-href" option:我在 iis 上做了同样的事情,首先你必须使用“base-href”选项构建你的应用程序:

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

and for nginx use this config对于 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;
}

and for navigation from /en/someroute to /fr/someroute , you can get the current router url in your component where you have the language switcher为了从 /en/someroute 导航到 /fr/someroute ,您可以在具有语言切换器的组件中获取当前路由器 URL

getCurrentRoute() {
    return this.router.url;
}

and when click on a language selector you redirect to the same route with the selected language :当单击语言选择器时,您将重定向到与所选语言相同的路线:

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

change language method改变语言方法

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);
    }
}

After building with:构建后:

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

copy the builds to nginx serve directory:将构建复制到 nginx 服务目录:

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

Then I found 2 different NGINX configs that work for me:然后我发现了 2 个不同的 NGINX 配置对我有用:

Using root path使用根路径

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;
    }
}

Using alias使用别名

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;
    }
}

Note : In the root path solution you can remove the autoindex on option but you'll also have to remove the $uri$args/ part from the try_files or else you'll get " directory index of "[directory]" is forbidden error ".注意:在根路径解决方案中,您可以删除autoindex on选项,但您还必须从try_files删除$uri$args/部分,否则您将获得“ 目录索引”“[directory]”被禁止错误”。

FYI : You can find useful those nice explanations on ROOT vs ALIAS .仅供参考:您可以在 ROOT 与 ALIAS 上找到有用的那些很好的解释

Versions版本

Angular CLI: 6.0.7
Node: 8.11.2
Angular: 6.0.3

Angular 9 has a new option to build all language versions at once. Angular 9 有一个新选项可以一次构建所有语言版本。 It also sets base HREF for each version of the application by adding the locale to the configured baseHref.它还通过将区域设置添加到配置的 baseHref 来为应用程序的每个版本设置基本 HREF。

ng build --prod --localize

Then you need to copy all builds to nginx serve directory然后你需要将所有构建复制到 nginx 服务目录

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

And configure nginx as shown in the previous answers.并按照前面的答案配置 nginx。

This is my solution to solve this for multiple projects:这是我为多个项目解决此问题的解决方案:

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;
        }
    }
}

Then i have an additional index.html with that small script inside, which is copied to the nginx root:然后我有一个额外的 index.html 里面有那个小脚本,它被复制到 nginx 根目录:

additional index.html额外的 index.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>

This file is used to route a user to the correct language subproject depending on its browser language or when the user change the language within the application it's stored in the localstorage and this language is used with higher priority.该文件用于根据用户的浏览器语言将用户路由到正确的语言子项目,或者当用户更改应用程序中的语言时,它存储在本地存储中,并且该语言具有更高的优先级。

And my angular.json还有我的 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"
}

And finally some important npm script for that stuff from my package.json最后一些重要的 npm 脚本来自我的 package.json

"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"

There is a common misunderstanding in the way that http://nginx.org/r/try_files works. http://nginx.org/r/try_files 的工作方式存在一个常见的误解。 If you look closer in the docs (from the above link), you'll notice that although the first and intermediate parameters in the try_files directive are of type "file", the final one is called the "uri";如果您仔细查看文档(从上面的链接),您会注意到尽管try_files指令中的第一个和中间参数是“file”类型,但最后一个称为“uri”; which, in your case, once you fix your http://nginx.org/r/location to handle the regexp appropriately (your location code is missing the ~ modifier), may result in an infinite loop, as you confirm in your comments.在您的情况下,一旦您修复了http://nginx.org/r/location以适当处理正则表达式(您的location代码缺少~修饰符),可能会导致无限循环,正如您在评论中所确认的.

Note that generally, the regular expressions are not recommended to be used in nginx for maximum performance in simple situations where regex use might as well be avoided, so, I'd recommend you to have two independent locations, one each for English and French.请注意,通常不建议在 nginx 中使用正则表达式以在可能避免使用正则表达式的简单情况下获得最大性能,因此,我建议您有两个独立的位置,一个分别用于英语和法语。

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

Note that the above code assumes that you perform correct URL handling from within your webapp frontend itself — if a given resource is shared between the /en/ and /fr/ versions, then it would be requested directly via the / base, without a /en/ or /fr/ specifier.请注意,上面的代码假定您从 webapp 前端本身执行正确的 URL 处理 - 如果给定的资源在/en//fr/版本之间共享,那么它将直接通过/ base 请求,没有/en//fr/说明符。 The =410 part of the code behaves similarly to how =404 would, except that the error is slightly different to make it easier to debug which directive is responsible for the error.代码的=410部分的行为与=404行为类似,只是错误略有不同,以便更容易调试导致错误的指令。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM