feat: localization invoice, estimate and receipt pdf templates.

This commit is contained in:
a.bouhuolia
2021-08-17 18:10:35 +02:00
parent 5c47d1549b
commit f7c3244145
23 changed files with 1938 additions and 82 deletions

View File

@@ -37,4 +37,4 @@ LICENSES_AUTH_PASSWORD=root
AGENDASH_AUTH_USER=agendash
AGENDASH_AUTH_PASSWORD=123123
BROWSER_WS_ENDPOINT=ws://localhost:3000/
BROWSER_WS_ENDPOINT=ws://localhost:4080/

View File

@@ -9,7 +9,7 @@
"copy-18n": "cpy --cwd=src/locales --parents '**/*.json' ../../build/locales",
"clear": "rimraf build",
"build:ts": "tsc -p tsconfig.json",
"build:resources": "gulp --gulpfile=scripts/gulpfile.js styles",
"build:resources": "gulp --gulpfile=scripts/gulpfile.js styles styles-rtl",
"build": "npm-run-all clear build:ts copy-18n"
},
"author": "Ahmed Bouhuolia, <a.bouhuolia@gmail.com>",
@@ -81,6 +81,7 @@
"ramda": "^0.27.1",
"rate-limiter-flexible": "^2.1.14",
"reflect-metadata": "^0.1.13",
"rtl-detect": "^1.0.4",
"ts-transformer-keys": "^0.4.2",
"tsyringe": "^4.3.0",
"uniqid": "^5.2.0",
@@ -102,13 +103,17 @@
"eslint-plugin-import": "^2.19.1",
"faker": "^4.1.0",
"getopts": "^2.2.5",
"gulp-postcss": "^9.0.0",
"gulp-rename": "^2.0.0",
"knex-factory": "0.0.6",
"merge-stream": "^2.0.0",
"mocha": "^5.2.0",
"module-alias": "^2.2.2",
"npm-run-all": "^4.1.5",
"nyc": "^14.1.1",
"regenerator-runtime": "^0.13.7",
"rimraf": "^3.0.2",
"rtlcss": "^3.3.0",
"sass": "^1.37.5",
"sinon": "^7.4.2",
"ts-node": "^9.0.0",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -21,3 +21,15 @@ th {
border-style: solid;
border-width: 0;
}
body{
margin: 0;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
background-color: #fff;
direction: ltr;
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: transparent;
}

File diff suppressed because one or more lines are too long

View File

@@ -1,57 +1,18 @@
@import "../base.scss";
@import "../fonts.scss";
body {
background: #f8f9fa;
font-family: 'Noto Sans', sans-serif;
text-align: left;
html[lang^='ar'] & {
font-family: "Segoe UI";
}
html[lang^='en'] & {
font-family: "Noto Sans";
}
@media print {
background: #fff;
}
}
.page {
background: white;
display: block;
margin: 0.5cm auto;
box-shadow: rgba(122, 136, 146, 0.15) 0px 1px 3px 1px;
width: 21cm;
height: 29.7cm;
@media print {
margin: 0;
box-shadow: 0 0 0;
width: 100%;
height: auto;
}
&[size="A4"] {
width: 21cm;
height: 29.7cm;
}
&[size="A4"][layout="landscape"] {
width: 29.7cm;
height: 21cm;
}
&[size="A3"] {
width: 29.7cm;
height: 42cm;
}
&[size="A3"][layout="landscape"] {
width: 42cm;
height: 29.7cm;
}
&[size="A5"] {
width: 14.8cm;
height: 21cm;
}
&[size="A5"][layout="landscape"] {
width: 21cm;
height: 14.8cm;
}
}

View File

@@ -2,7 +2,7 @@
.invoice{
text-align: left;
padding: 45px;
padding: 45px 40px;
&__header{
display: flex;

View File

@@ -1,4 +1,4 @@
html
html(lang=locale)
head
title My Site - #{title}
block head

View File

@@ -2,7 +2,10 @@ extends ../PaperTemplateLayout.pug
block head
style
include ../../css/modules/estimate.css
if (isRtl)
include ../../css/modules/estimate-rtl.css
else
include ../../css/modules/estimate.css
block content
div.estimate
@@ -18,7 +21,7 @@ block content
div.estimate__meta
div.estimate__meta-item.estimate__meta-item--amount
span.label #{__('estimate.paper.due_amount')}
span.label #{__('estimate.paper.amount')}
span.value #{saleEstimate.formattedAmount}
div.estimate__meta-item.estimate__meta-item--billed-to

View File

@@ -1,8 +1,11 @@
extends ../PaperTemplateLayout.pug
block head
block head
style
include ../../css/modules/invoice.css
if (isRtl)
include ../../css/modules/invoice-rtl.css
else
include ../../css/modules/invoice.css
block content
div.invoice
@@ -19,7 +22,7 @@ block content
div.invoice__meta
div.invoice__meta-item.invoice__meta-item--amount
span.label #{__('estimate.paper.due_amount')}
span.label #{__('invoice.paper.due_amount')}
span.value #{saleInvoice.formattedAmount}
div.invoice__meta-item.invoice__meta-item--billed-to

View File

@@ -2,7 +2,10 @@ extends ../PaperTemplateLayout.pug
block head
style
include ../../css/modules/receipt.css
if (isRtl)
include ../../css/modules/receipt-rtl.css
else
include ../../css/modules/receipt.css
block content
div.receipt

View File

@@ -37,20 +37,20 @@ module.exports = {
clean: ['style.css', 'style.min.css', 'style-rtl.css', 'style-rtl.min.css'],
build: [
{
src: `${RESOURCES_PATH}/scss/modules/invoice.scss`, // Path to main .scss file.
dest: `${RESOURCES_PATH}/css/modules`, // Path to place the compiled CSS file.
src: `${RESOURCES_PATH}/scss/modules/invoice.scss`,
dest: `${RESOURCES_PATH}/css/modules`,
// sourcemaps: true, // Allow to enable/disable sourcemaps or pass object to configure it.
// minify: true, // Allow to enable/disable minify the source.
},
{
src: `${RESOURCES_PATH}/scss/modules/estimate.scss`, // Path to main .scss file.
dest: `${RESOURCES_PATH}/css/modules`, // Path to place the compiled CSS file.
src: `${RESOURCES_PATH}/scss/modules/estimate.scss`,
dest: `${RESOURCES_PATH}/css/modules`,
// sourcemaps: true, // Allow to enable/disable sourcemaps or pass object to configure it.
// minify: true, // Allow to enable/disable minify the source.
},
{
src: `${RESOURCES_PATH}/scss/modules/receipt.scss`, // Path to main .scss file.
dest: `${RESOURCES_PATH}/css/modules`, // Path to place the compiled CSS file.
src: `${RESOURCES_PATH}/scss/modules/receipt.scss`,
dest: `${RESOURCES_PATH}/css/modules`,
// sourcemaps: true, // Allow to enable/disable sourcemaps or pass object to configure it.
// minify: true, // Allow to enable/disable minify the source.
},
@@ -61,11 +61,19 @@ module.exports = {
// minify: true,
// },
],
// RTL builds.
rtl: [
// RTL builds.
{
src: './style.css',
dest: './', // The source files will be converted and suffixed to `-rtl` in this destination.
src: `${RESOURCES_PATH}/css/modules/invoice.css`,
dest: `${RESOURCES_PATH}/css/modules`,
},
{
src: `${RESOURCES_PATH}/css/modules/estimate.css`,
dest: `${RESOURCES_PATH}/css/modules`,
},
{
src: `${RESOURCES_PATH}/css/modules/receipt.css`,
dest: `${RESOURCES_PATH}/css/modules`,
},
],

View File

@@ -2,6 +2,13 @@ const gulp = require('gulp');
const sass = require('sass');
const gulpSass = require('gulp-sass')(sass); // Gulp pluign for Sass compilation.
const mergeStream = require('merge-stream');
const rename = require('gulp-rename'); // Renames files E.g. style.css -> style.min.css
// Style related.
const postcss = require('gulp-postcss'); // Transforming styles with JS plugins
const rtlcss = require('rtlcss'); // Convert LTR CSS to RTL.
const config = require('./gulpConfig');
gulp.task('styles', () => {
@@ -12,4 +19,32 @@ gulp.task('styles', () => {
.pipe(gulp.dest(build.dest));
});
return mergeStream(builds);
});
});
/**
* Task: `styles-rtl`
*
* This task does the following.
* 1. Gets the source css files.
* 2. Covert LTR CSS to RTL.
* 3. Suffix all CSS files to `-rtl`.
* 4. Reloads css files via browser sync stream.
* 5. Combine matching media queries for `.min.css` version.
* 6. Minify all CSS files.
* 7. Reload minified css files via browser sync stream.
*/
gulp.task('styles-rtl', () => {
const builds = config.style.rtl.map((build) => {
return gulp
.src(build.src)
.pipe(
postcss([
rtlcss(config.style.rtlcss), // Convert LTR CSS to RTL.
]),
)
.pipe(rename({ suffix: '-rtl' })) // Append "-rtl" to the filename.
.pipe(gulp.dest(build.dest));
});
return mergeStream(builds);
});

View File

@@ -1,13 +1,15 @@
import { Container } from 'typedi';
import { Request, Response, NextFunction } from 'express';
import i18n from 'i18n';
import HasTenancyService from 'services/Tenancy/TenancyService';
import { injectI18nUtils } from './TenantDependencyInjection';
/**
* I18n from organization settings.
*/
export default (req: Request, res: Response, next: NextFunction) => {
const Logger = Container.get('logger');
const { settings } = req;
const { settings, tenantId } = req;
if (!req.user) {
throw new Error('Should load this middleware after `JWTAuth`.');
@@ -16,9 +18,8 @@ export default (req: Request, res: Response, next: NextFunction) => {
throw new Error('Should load this middleware after `SettingsMiddleware`.');
}
// Get the organization language from settings.
const language = settings.get({
group: 'organization', key: 'language',
});
const language = settings.get({ group: 'organization', key: 'language' });
if (language) {
i18n.setLocale(req, language);
}
@@ -26,5 +27,10 @@ export default (req: Request, res: Response, next: NextFunction) => {
language,
user: req.user,
});
const tenantServices = Container.get(HasTenancyService);
const tenantContainer = tenantServices.tenantContainer(tenantId);
tenantContainer.set('i18n', injectI18nUtils(req));
next();
};

View File

@@ -11,15 +11,11 @@ export default async (req: Request, res: Response, next: NextFunction) => {
if (tenantContainer && !tenantContainer.has('settings')) {
const { settingRepository } = tenantContainer.get('repositories');
Logger.info('[settings_middleware] initialize settings store.');
const settings = new SettingsStore(settingRepository);
tenantContainer.set('settings', settings);
}
Logger.info('[settings_middleware] get settings instance from container.');
const settings = tenantContainer.get('settings');
Logger.info('[settings_middleware] load settings from storage or cache.');
await settings.load();
req.settings = settings;

View File

@@ -3,6 +3,7 @@ import { ITenant } from 'interfaces';
import { Request } from 'express';
import TenancyService from 'services/Tenancy/TenancyService';
import TenantsManagerService from 'services/Tenancy/TenantsManager';
import rtlDetect from 'rtl-detect';
export default (req: Request, tenant: ITenant) => {
const { id: tenantId, organizationId } = tenant;
@@ -20,7 +21,7 @@ export default (req: Request, tenant: ITenant) => {
const tenantContainer = tenantServices.tenantContainer(tenantId);
tenantContainer.set('i18n', { __: req.__ });
tenantContainer.set('i18n', injectI18nUtils(req));
req.knex = knexInstance;
req.organizationId = organizationId;
@@ -29,4 +30,18 @@ export default (req: Request, tenant: ITenant) => {
req.models = models;
req.repositories = repositories;
req.cache = cacheInstance;
}
export const injectI18nUtils =(req) => {
const locale = req.getLocale();
const direction = rtlDetect.getLangDir(locale);
return {
locale,
__: req.__,
direction,
isRtl: direction === 'rtl',
isLtr: direction === 'ltr',
}
}

View File

@@ -160,5 +160,32 @@
"Liabilities and Equity": "التزامات وحقوق الملكية",
"Closing balance": "الرصيد الختامي",
"Opening balance": "الرصيد الفتاحي",
"Total {{accountName}}": "إجمالي {{accountName}}"
"Total {{accountName}}": "إجمالي {{accountName}}",
"invoice.paper.invoice": "فاتورة",
"invoice.paper.due_amount": "القيمة المستحقة",
"invoice.paper.billed_to": "فاتورة إلي",
"invoice.paper.invoice_date": "تاريخ الفاتورة",
"invoice.paper.invoice_number": "رقم الفاتورة",
"invoice.paper.due_date": "تاريخ الاستحقاق",
"invoice.paper.conditions_title": "الشروط والأحكام",
"invoice.paper.notes_title": "ملاحظات",
"item_entry.paper.item_name": "اسم الصنف",
"item_entry.paper.rate": "السعر",
"item_entry.paper.quantity": "الكمية",
"item_entry.paper.total": "إجمالي",
"estimate.paper.estimate": "عرض أسعار",
"estimate.paper.billed_to": "عرض أسعار إلي",
"estimate.paper.estimate_date": "تاريخ العرض",
"estimate.paper.estimate_number": "رقم العرض",
"estimate.paper.expiration_date": "تاريخ انتهاء الصلاحية",
"estimate.paper.conditions_title": "الشروط والأحكام",
"estimate.paper.notes_title": "ملاحظات",
"estimate.paper.amount": "قيمة العرض",
"receipt.paper.receipt": "إيصال",
"receipt.paper.billed_to": "الإيصال إلي",
"receipt.paper.receipt_date": "تاريخ الإيصال",
"receipt.paper.receipt_number": "رقم الإيصال",
"receipt.paper.conditions_title": "الشروط والأحكام",
"receipt.paper.notes_title": "ملاحظات",
"receipt.paper.receipt_amount": "قيمة الإيصال"
}

View File

@@ -161,6 +161,7 @@
"Opening Balance": "Opening balance",
"Total {{accountName}}": "Total {{accountName}}",
"invoice.paper.invoice": "Invoice",
"invoice.paper.due_amount": "Due amount",
"invoice.paper.billed_to": "Billed to",
"invoice.paper.invoice_date": "Invoice date",
"invoice.paper.invoice_number": "Invoice No.",
@@ -178,7 +179,7 @@
"estimate.paper.expiration_date": "Expiration date",
"estimate.paper.conditions_title": "Conditions & terms",
"estimate.paper.notes_title": "Notes",
"estimate.paper.due_amount": "Due amount",
"estimate.paper.amount": "Estimate amount",
"receipt.paper.receipt": "Receipt",
"receipt.paper.billed_to": "Billed to",
"receipt.paper.receipt_date": "Receipt date",