From 0ab45968365c687262e4cda225d02813c307e0f0 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Sat, 7 Aug 2021 08:08:29 +0200 Subject: [PATCH] fix: middleware i18n localization. --- server/src/api/controllers/BaseController.ts | 38 +++++++++- server/src/api/controllers/Views.ts | 11 +-- server/src/api/index.ts | 3 +- .../middleware/I18nAuthenticatedMiddlware.ts | 30 ++++++++ server/src/api/middleware/I18nMiddleware.ts | 19 +++-- server/src/data/options.js | 70 +++++++++---------- server/src/locales/ar.json | 27 +++---- server/src/locales/en.json | 8 ++- server/src/services/Purchases/constants.ts | 4 +- 9 files changed, 143 insertions(+), 67 deletions(-) create mode 100644 server/src/api/middleware/I18nAuthenticatedMiddlware.ts diff --git a/server/src/api/controllers/BaseController.ts b/server/src/api/controllers/BaseController.ts index 403504c52..ae65bffa3 100644 --- a/server/src/api/controllers/BaseController.ts +++ b/server/src/api/controllers/BaseController.ts @@ -1,7 +1,7 @@ import { Response, Request, NextFunction } from 'express'; import { matchedData, validationResult } from 'express-validator'; import accepts from 'accepts'; -import { camelCase, snakeCase, omit, set, get } from 'lodash'; +import { isArray, drop, first, camelCase, snakeCase, omit, set, get } from 'lodash'; import { mapKeysDeep } from 'utils'; import asyncMiddleware from 'api/middleware/asyncMiddleware'; @@ -57,6 +57,37 @@ export default class BaseController { next(); } + /** + * Sets localization to response object by the given path. + * @param {Response} response - + * @param {string} path - + * @param {Request} req - + */ + private setLocalizationByPath( + response: any, + path: string, + req: Request, + ) { + const DOT = '.'; + + if (isArray(response)) { + response.forEach((va) => { + const currentPath = first(path.split(DOT)); + const value = get(va, currentPath); + + if (isArray(value)) { + const nextPath = drop(path.split(DOT)).join(DOT); + this.setLocalizationByPath(value, nextPath, req); + } else { + set(va, path, req.__(value)); + } + }) + } else { + const value = get(response, path); + set(response, path, req.__(value)); + } + } + /** * Transform the given data to response. * @param {any} data @@ -74,13 +105,14 @@ export default class BaseController { : [translatable]; translatables.forEach((path) => { - const value = get(response, path); - set(response, path, req.__(value)); + this.setLocalizationByPath(response, path, req); }); } return response; } + + /** * Async middleware. * @param {function} callback diff --git a/server/src/api/controllers/Views.ts b/server/src/api/controllers/Views.ts index 92db2dcc2..ee45cea49 100644 --- a/server/src/api/controllers/Views.ts +++ b/server/src/api/controllers/Views.ts @@ -37,9 +37,9 @@ export default class ViewsController extends BaseController { /** * List all views that associated with the given resource. - * @param {Request} req - - * @param {Response} res - - * @param {NextFunction} next - + * @param {Request} req - Request object. + * @param {Response} res - Response object. + * @param {NextFunction} next - Next function. */ async listResourceViews(req: Request, res: Response, next: NextFunction) { const { tenantId } = req; @@ -50,8 +50,9 @@ export default class ViewsController extends BaseController { tenantId, resourceModel ); - - return res.status(200).send({ views }); + return res.status(200).send({ + views: this.transfromToResponse(views, ['name', 'columns.label'], req), + }); } catch (error) { next(error); } diff --git a/server/src/api/index.ts b/server/src/api/index.ts index 1e95d7a05..2eeacd160 100644 --- a/server/src/api/index.ts +++ b/server/src/api/index.ts @@ -10,6 +10,7 @@ import TenancyMiddleware from 'api/middleware/TenancyMiddleware'; import EnsureTenantIsInitialized from 'api/middleware/EnsureTenantIsInitialized'; import SettingsMiddleware from 'api/middleware/SettingsMiddleware'; import I18nMiddleware from 'api/middleware/I18nMiddleware'; +import I18nAuthenticatedMiddlware from 'api/middleware/I18nAuthenticatedMiddlware'; import EnsureConfiguredMiddleware from 'api/middleware/EnsureConfiguredMiddleware'; import EnsureTenantIsSeeded from 'api/middleware/EnsureTenantIsSeeded'; @@ -65,10 +66,10 @@ export default () => { dashboard.use(JWTAuth); dashboard.use(AttachCurrentTenantUser); dashboard.use(TenancyMiddleware); - dashboard.use(I18nMiddleware); dashboard.use(SubscriptionMiddleware('main')); dashboard.use(EnsureTenantIsInitialized); dashboard.use(SettingsMiddleware); + dashboard.use(I18nAuthenticatedMiddlware); dashboard.use(EnsureConfiguredMiddleware); dashboard.use(EnsureTenantIsSeeded); diff --git a/server/src/api/middleware/I18nAuthenticatedMiddlware.ts b/server/src/api/middleware/I18nAuthenticatedMiddlware.ts new file mode 100644 index 000000000..4af8823e7 --- /dev/null +++ b/server/src/api/middleware/I18nAuthenticatedMiddlware.ts @@ -0,0 +1,30 @@ +import { Container } from 'typedi'; +import { Request, Response, NextFunction } from 'express'; +import i18n from 'i18n'; + +/** + * I18n from organization settings. + */ +export default (req: Request, res: Response, next: NextFunction) => { + const Logger = Container.get('logger'); + const { settings } = req; + + if (!req.user) { + throw new Error('Should load this middleware after `JWTAuth`.'); + } + if (!req.settings) { + throw new Error('Should load this middleware after `SettingsMiddleware`.'); + } + // Get the organization language from settings. + const language = settings.get({ + group: 'organization', key: 'language', + }); + if (language) { + i18n.setLocale(req, language); + } + Logger.info('[i18n_authenticated_middleware] set locale language to i18n.', { + language, + user: req.user, + }); + next(); +}; diff --git a/server/src/api/middleware/I18nMiddleware.ts b/server/src/api/middleware/I18nMiddleware.ts index d354464d2..2db5fd9a2 100644 --- a/server/src/api/middleware/I18nMiddleware.ts +++ b/server/src/api/middleware/I18nMiddleware.ts @@ -1,16 +1,23 @@ import { Container } from 'typedi'; import { Request, Response, NextFunction } from 'express'; +import { lowerCase } from 'lodash'; import i18n from 'i18n'; +/** + * Set the language from request `accept-language` header +* or default application language. + */ export default (req: Request, res: Response, next: NextFunction) => { const Logger = Container.get('logger'); - let language = req.headers['accept-language'] || 'en'; - if (req.user && req.user.language) { - language = req.user.language; - } - Logger.info('[i18n_middleware] set locale language to i18n.', { language, user: req.user }); + // Parses the accepted language from request object. + const language = lowerCase(req.headers['accept-language']) || 'en'; + + Logger.info('[i18n_middleware] set locale language to i18n.', { + language, + user: req.user, + }); i18n.setLocale(req, language); next(); -}; \ No newline at end of file +}; diff --git a/server/src/data/options.js b/server/src/data/options.js index f9860b162..ebbc0d707 100644 --- a/server/src/data/options.js +++ b/server/src/data/options.js @@ -1,127 +1,127 @@ export default { organization: { name: { - type: "string", + type: 'string', }, base_currency: { - type: "string", + type: 'string', }, industry: { - type: "string", + type: 'string', }, location: { - type: "string", + type: 'string', }, fiscal_year: { - type: "string", + type: 'string', }, financial_date_start: { - type: "string", + type: 'string', }, language: { - type: "string", + type: 'string', }, time_zone: { - type: "string", + type: 'string', }, date_format: { - type: "string", + type: 'string', }, accounting_basis: { - type: "string", + type: 'string', }, }, manual_journals: { next_number: { - type: "string", + type: 'string', }, number_prefix: { - type: "string", + type: 'string', }, auto_increment: { - type: "boolean", + type: 'boolean', }, }, bill_payments: { withdrawal_account: { - type: "number", + type: 'number', }, }, sales_estimates: { next_number: { - type: "string", + type: 'string', }, number_prefix: { - type: "string", + type: 'string', }, auto_increment: { - type: "boolean", + type: 'boolean', }, }, sales_receipts: { next_number: { - type: "string", + type: 'string', }, number_prefix: { - type: "string", + type: 'string', }, auto_increment: { - type: "boolean", + type: 'boolean', }, preferred_deposit_account: { - type: "number", + type: 'number', }, }, sales_invoices: { next_number: { - type: "string", + type: 'string', }, number_prefix: { - type: "string", + type: 'string', }, auto_increment: { - type: "boolean", + type: 'boolean', }, }, payment_receives: { next_number: { - type: "string", + type: 'string', }, number_prefix: { - type: "string", + type: 'string', }, auto_increment: { - type: "boolean", + type: 'boolean', }, deposit_account: { - type: "number", + type: 'number', }, advance_deposit: { - type: "number", + type: 'number', }, }, items: { sell_account: { - type: "number", + type: 'number', }, cost_account: { - type: "number", + type: 'number', }, inventory_account: { - type: "number", + type: 'number', }, }, expenses: { preferred_payment_account: { - type: "number", + type: 'number', }, }, accounts: { account_code_required: { - type: "boolean", + type: 'boolean', }, account_code_unique: { - type: "boolean", + type: 'boolean', }, }, }; diff --git a/server/src/locales/ar.json b/server/src/locales/ar.json index 2940a3302..21c180c45 100644 --- a/server/src/locales/ar.json +++ b/server/src/locales/ar.json @@ -4,7 +4,6 @@ "Bank": "المصرف", "Other Income": "إيرادات اخري", "Interest Income": "إيرادات الفوائد", - "Opening Balance": "رصيد", "Depreciation Expense": "مصاريف الاهلاك", "Interest Expense": "مصروفات الفوائد", "Sales of Product Income": "مبيعات دخل المنتجات", @@ -89,20 +88,21 @@ "Profit Margin": "هامش الربح", "Value": "القيمة", "Rate": "السعر", - "OPERATING ACTIVITIES": "أنشطة التشغيل", - "FINANCIAL ACTIVITIES": "الأنشطة المالية", + "OPERATING ACTIVITIES": "الأنشطة التشغيلية", + "FINANCIAL ACTIVITIES": "الأنشطة التمويلية", + "INVESTMENT ACTIVITIES": "الانشطة الاستثمارية", "Net income": "صافي الدخل", - "Adjustments net income by operating activities.": "تعديلات صافي الدخل حسب الأنشطة التشغيلية.", - "Net cash provided by operating activities": "صافي النقد الناتج من أنشطة التشغيل", - "Net cash provided by investing activities": "صافي النقد المقدم من أنشطة الاستثمار", - "Net cash provided by financing activities": "صافي النقد الناتج عن أنشطة التمويل", - "Cash at beginning of period": "النقدية في بداية الفترة", - "NET CASH INCREASE FOR PERIOD": "زيادة صافي النقد للفترة", - "CASH AT END OF PERIOD": "النقد في نهاية الفترة", + "Adjustments net income by operating activities.": "تسويات صافي الدخل من الأنشطة التشغيلية.", + "Net cash provided by operating activities": "صافي التدفقات النقدية من أنشطة التشغيل", + "Net cash provided by investing activities": "صافي التدفقات النقدية من أنشطة الاستثمار", + "Net cash provided by financing activities": "صافي التدفقات النقدية من أنشطة التمويلية", + "Cash at beginning of period": "التدفقات النقدية في بداية الفترة", + "NET CASH INCREASE FOR PERIOD": "زيادة التدفقات النقدية للفترة", + "CASH AT END OF PERIOD": "صافي التدفقات النقدية في نهاية الفترة", "Expenses": "مصاريف", "Services": "خدمات", "Inventory": "المخزون", - "Non-Inventory": "غير متعلق بالمخزون", + "Non Inventory": "غير المخزون", "Draft": "مسودة", "Delivered": "تم التوصيل", "Overdue": "متأخر", @@ -157,5 +157,8 @@ "Current Liabilties": "التزامات متداولة", "Long-Term Liabilities": "التزامات طويلة الاجل", "Non-Current Liabilities": "التزامات غير متداولة", - "Liabilities and Equity": "التزامات وحقوق الملكية" + "Liabilities and Equity": "التزامات وحقوق الملكية", + "Closing balance": "الرصيد الختامي", + "Opening balance": "الرصيد الفتاحي", + "Total {{accountName}}": "إجمالي {{accountName}}" } \ No newline at end of file diff --git a/server/src/locales/en.json b/server/src/locales/en.json index 2ba5d9750..8e506540f 100644 --- a/server/src/locales/en.json +++ b/server/src/locales/en.json @@ -4,7 +4,6 @@ "Bank": "Bank", "Other Income": "Other Income", "Interest Income": "Interest Income", - "Opening Balance": "Opening Balance", "Depreciation Expense": "Depreciation Expense", "Interest Expense": "Interest Expense", "Sales of Product Income": "Sales of Product Income", @@ -102,7 +101,7 @@ "Expenses": "Expenses", "Services": "Services", "Inventory": "Inventory", - "Non-Inventory": "Non-Inventory", + "Non Inventory": "Non Inventory", "Draft": "Draft", "Published": "Published", "Delivered": "Delivered", @@ -157,5 +156,8 @@ "Current Liabilties": "Current Liabilties", "Long-Term Liabilities": "Long-Term Liabilities", "Non-Current Liabilities": "Non-Current Liabilities", - "Liabilities and Equity": "Liabilities and Equity" + "Liabilities and Equity": "Liabilities and Equity", + "Closing balance": "Closing balance", + "Opening Balance": "Opening balance", + "Total {{accountName}}": "Total {{accountName}}" } \ No newline at end of file diff --git a/server/src/services/Purchases/constants.ts b/server/src/services/Purchases/constants.ts index f3a6f5dc4..cc54df946 100644 --- a/server/src/services/Purchases/constants.ts +++ b/server/src/services/Purchases/constants.ts @@ -32,7 +32,7 @@ export const DEFAULT_VIEWS = [ columns: DEFAULT_VIEW_COLUMNS, }, { - name: 'Opended', + name: 'Opened', slug: 'opened', rolesLogicExpression: '1', roles: [ @@ -59,7 +59,7 @@ export const DEFAULT_VIEWS = [ columns: DEFAULT_VIEW_COLUMNS, }, { - name: 'Partially Paid', + name: 'Partially paid', slug: 'partially-paid', rolesLogicExpression: '1', roles: [