角度i18n应用的Nginx配置

时间:2018-04-17 23:51:24

标签: angular nginx internationalization

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

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

我还添加了以下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;
    }
}

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

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

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

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

5 个答案:

答案 0 :(得分:6)

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

答案 1 :(得分:4)

建成之后:

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选项,但您还必须删除$uri$args/部分try_files或者你会得到“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

答案 2 :(得分:2)

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

ng build --prod --localize

然后,您需要将所有内部版本复制到nginx服务目录

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

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

答案 3 :(得分:2)

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

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根目录:

其他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>

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

还有我的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"

答案 4 :(得分:0)

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

请注意,一般情况下,建议不要在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的行为类似,只是错误略有不同,以便更容易调试哪个指令导致错误。