Browse Source

feat(server): Use ICU MessageFormat for translation strings

Michael Bromley 7 years ago
parent
commit
37fcec5839

+ 1 - 0
server/package.json

@@ -43,6 +43,7 @@
     "graphql-tools": "^3.0.2",
     "i18next": "^11.3.3",
     "i18next-express-middleware": "^1.2.0",
+    "i18next-icu": "^0.4.0",
     "i18next-node-fs-backend": "^1.0.0",
     "jsonwebtoken": "^8.2.2",
     "mysql": "^2.15.0",

+ 13 - 3
server/src/i18n/i18n.service.ts

@@ -4,8 +4,10 @@ import { GraphQLError } from 'graphql-request/dist/src/types';
 import * as i18next from 'i18next';
 import { TranslationFunction } from 'i18next';
 import * as i18nextMiddleware from 'i18next-express-middleware';
+import * as ICU from 'i18next-icu';
 import * as Backend from 'i18next-node-fs-backend';
 import * as path from 'path';
+import { ConfigService } from '../service/config.service';
 import { I18nError } from './i18n-error';
 
 export interface I18nRequest extends Request {
@@ -24,17 +26,19 @@ export interface WrappedGraphQLError extends GraphQLError {
  */
 @Injectable()
 export class I18nService {
-    constructor() {
+    constructor(private configService: ConfigService) {
         i18next
             .use(i18nextMiddleware.LanguageDetector)
             .use(Backend)
+            .use(ICU)
             .init({
                 preload: ['en', 'de'],
+                fallbackLng: 'en',
                 detection: {
                     lookupQuerystring: 'lang',
                 },
                 backend: {
-                    loadPath: path.join(__dirname, 'translations/{{lng}}.json'),
+                    loadPath: path.join(__dirname, 'messages/{{lng}}.json'),
                     jsonIndent: 2,
                 },
             });
@@ -49,7 +53,13 @@ export class I18nService {
             const originalError = error.originalError;
             if (req && req.t && originalError instanceof I18nError) {
                 const t: TranslationFunction = req.t;
-                error.message = t(originalError.message, originalError.variables);
+                let translation = originalError.message;
+                try {
+                    translation = t(originalError.message, originalError.variables);
+                } catch (e) {
+                    translation += ` (Translation format error: ${e.message})`;
+                }
+                error.message = translation;
             }
 
             return error;

+ 5 - 0
server/src/i18n/messages/en.json

@@ -0,0 +1,5 @@
+{
+  "error": {
+    "customer-with-id-not-found": "No customer with the id { customerId } was found"
+  }
+}

+ 0 - 3
server/src/i18n/translations/en.json

@@ -1,3 +0,0 @@
-{
-  "No customer with the id {{ customerId }} was found": "No customer with the id {{ customerId }} was found"
-}

+ 1 - 1
server/src/service/customer.service.ts

@@ -56,7 +56,7 @@ export class CustomerService {
         const customer = await this.connection.manager.findOne(Customer, customerId, { relations: ['addresses'] });
 
         if (!customer) {
-            throw new I18nError(`No customer with the id {{ customerId }} was found`, { customerId });
+            throw new I18nError('error.customer-with-id-not-found', { customerId });
         }
 
         const address = new Address(createAddressDto);

+ 16 - 0
server/yarn.lock

@@ -2472,6 +2472,12 @@ i18next-express-middleware@^1.2.0:
   dependencies:
     cookies "0.7.1"
 
+i18next-icu@^0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/i18next-icu/-/i18next-icu-0.4.0.tgz#df39694f1522cebe1f94d33c9198b037069a9b32"
+  dependencies:
+    intl-messageformat "2.2.0"
+
 i18next-node-fs-backend@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/i18next-node-fs-backend/-/i18next-node-fs-backend-1.0.0.tgz#f5a625a3b287c1d098c7171b7dd376bb07299b59"
@@ -2544,6 +2550,16 @@ interpret@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
 
+intl-messageformat-parser@1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-1.4.0.tgz#b43d45a97468cadbe44331d74bb1e8dea44fc075"
+
+intl-messageformat@2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-2.2.0.tgz#345bcd46de630b7683330c2e52177ff5eab484fc"
+  dependencies:
+    intl-messageformat-parser "1.4.0"
+
 invariant@^2.2.2:
   version "2.2.2"
   resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"