diff --git a/README.md b/README.md
index dfd248d..2d659e3 100644
--- a/README.md
+++ b/README.md
@@ -1,296 +1,297 @@
-# ZUGFeRD/XRechnung/Factur-X Visualizer
-
-[![Latest Stable Version](https://img.shields.io/packagist/v/horstoeko/zugferdvisualizer.svg?style=plastic)](https://packagist.org/packages/horstoeko/zugferdvisualizer)
-[![PHP version](https://img.shields.io/packagist/php-v/horstoeko/zugferdvisualizer.svg?style=plastic)](https://packagist.org/packages/horstoeko/zugferdvisualizer)
-[![License](https://img.shields.io/packagist/l/horstoeko/zugferdvisualizer.svg?style=plastic)](https://packagist.org/packages/horstoeko/zugferdvisualizer)
-
-[![Build Status](https://github.com/horstoeko/zugferdvisualizer/actions/workflows/build.ci.yml/badge.svg)](https://github.com/horstoeko/zugferdvisualizer/actions/workflows/build.ci.yml)
-[![Release Status](https://github.com/horstoeko/zugferdvisualizer/actions/workflows/build.release.yml/badge.svg)](https://github.com/horstoeko/zugferdvisualizer/actions/workflows/build.release.yml)
-
-## Table of Contents
-
-- [ZUGFeRD/XRechnung/Factur-X Visualizer](#zugferdxrechnungfactur-x-visualizer)
- - [Table of Contents](#table-of-contents)
- - [License](#license)
- - [Overview](#overview)
- - [Dependencies](#dependencies)
- - [Installation](#installation)
- - [Usage](#usage)
- - [Create HTML markup from existing invoice document (XML) using built-in template](#create-html-markup-from-existing-invoice-document-xml-using-built-in-template)
- - [Create a PDF file from existing invoice document (XML) using built-in template](#create-a-pdf-file-from-existing-invoice-document-xml-using-built-in-template)
- - [Create a PDF string from existing invoice document (XML) using built-in template](#create-a-pdf-string-from-existing-invoice-document-xml-using-built-in-template)
- - [Create a PDF string from document builder and merge XML with generated PDF](#create-a-pdf-string-from-document-builder-and-merge-xml-with-generated-pdf)
- - [Create a custom renderer](#create-a-custom-renderer)
- - [Use a custom renderer](#use-a-custom-renderer)
- - [Use the built-in Laravel renderer](#use-the-built-in-laravel-renderer)
- - [Set PDF-Options](#set-pdf-options)
- - [Set options before instanciating the internal PDF-Engine (```setPdfPreInitCallback```)](#set-options-before-instanciating-the-internal-pdf-engine-setpdfpreinitcallback)
- - [Set options after instanciating the internal PDF-Engine (```setPdfRuntimeInitCallback```)](#set-options-after-instanciating-the-internal-pdf-engine-setpdfruntimeinitcallback)
- - [Working with custom fonts](#working-with-custom-fonts)
-
-## License
-
-The code in this project is provided under the [MIT](https://opensource.org/licenses/MIT) license.
-
-## Overview
-
-With `horstoeko/zugferdvisualizer` you can visualize ZUGFeRD/XRechnung/Factur-X documents. This package is an addon for [horstoeko/zugferd](https://github.com/horstoeko/zugferd) package. The system uses a markup template (HTML) to render the output. On top you can create a PDF from the rendered markup
-
-## Dependencies
-
-This package makes use of
-
-- [horstoeko/zugferd](https://github.com/horstoeko/zugferd)
-- [mPdf](https://github.com/mpdf/mpdf)
-
-## Installation
-
-There is one recommended way to install `horstoeko/zugferdvisualizer` via [Composer](https://getcomposer.org/):
-
-* adding the dependency to your ``composer.json`` file:
-
-```js
- "require": {
- ..
- "horstoeko/zugferdvisualizer":"^1",
- ..
- },
-```
-
-## Usage
-
-### Create HTML markup from existing invoice document (XML) using built-in template
-
-```php
-use horstoeko\zugferd\ZugferdDocumentReader;
-use horstoeko\zugferdvisualizer\ZugferdVisualizer;
-
-require dirname(__FILE__) . "/../vendor/autoload.php";
-
-$document = ZugferdDocumentReader::readAndGuessFromFile(dirname(__FILE__) . "/invoice_1.xml");
-
-$visualizer = new ZugferdVisualizer($document);
-$visualizer->setDefaultTemplate();
-
-echo $visualizer->renderMarkup();
-```
-
-### Create a PDF file from existing invoice document (XML) using built-in template
-
-Find there [full example here](https://github.com/horstoeko/zugferdvisualizer/blob/master/examples/BuildFromDocumentReader.php)
-
-```php
-use horstoeko\zugferd\ZugferdDocumentReader;
-use horstoeko\zugferdvisualizer\ZugferdVisualizer;
-
-require dirname(__FILE__) . "/../vendor/autoload.php";
-
-$document = ZugferdDocumentReader::readAndGuessFromFile(dirname(__FILE__) . "/invoice_1.xml");
-
-$visualizer = new ZugferdVisualizer($document);
-$visualizer->setDefaultTemplate();
-$visualizer->setPdfFontDefault("courier");
-$visualizer->renderPdfFile(dirname(__FILE__) . "/invoice_1.pdf");
-```
-
-### Create a PDF string from existing invoice document (XML) using built-in template
-
-```php
-use horstoeko\zugferd\ZugferdDocumentReader;
-use horstoeko\zugferdvisualizer\ZugferdVisualizer;
-
-require dirname(__FILE__) . "/../vendor/autoload.php";
-
-$document = ZugferdDocumentReader::readAndGuessFromFile(dirname(__FILE__) . "/invoice_1.xml");
-
-$visualizer = new ZugferdVisualizer($document);
-$visualizer->setDefaultTemplate();
-$visualizer->setPdfFontDefault("courier");
-
-$pdfString = $visualizer->renderPdf();
-```
-
-### Create a PDF string from document builder and merge XML with generated PDF
-
-Find there [full example here](https://github.com/horstoeko/zugferdvisualizer/blob/master/examples/BuildFromDocumentBuilder.php)
-
-```php
-$document = ZugferdDocumentBuilder::CreateNew(ZugferdProfiles::PROFILE_EN16931);
-$document
- ->setDocumentInformation("471102", "380", \DateTime::createFromFormat("Ymd", "20180305"), "EUR")
- ->...
-
-$reader = ZugferdDocumentReader::readAndGuessFromContent($document->getContent());
-
-$visualizer = new ZugferdVisualizer($reader);
-$visualizer->setDefaultTemplate();
-$visualizer->setPdfFontDefault("courier");
-$visualizer->setPdfPaperSize('A4-P');
-
-$merger = new ZugferdDocumentPdfMerger($document->getContent(), $visualizer->renderPdf());
-$merger->generateDocument();
-$merger->saveDocument(dirname(__FILE__) . "/invoice_2.pdf");
-```
-
-### Create a custom renderer
-
-If you want to implement your own markup renderer, then your class must implement the interface `ZugferdVisualizerMarkupRendererContract`. The interface defines two methods:
-
-* `templateExists`
-* `render`
-
-```php
-use horstoeko\zugferd\ZugferdDocumentReader;
-use horstoeko\zugferdvisualizer\contracts\ZugferdVisualizerMarkupRendererContract;
-
-class MyOwnRenderer implements ZugferdVisualizerMarkupRendererContract
-{
- public function templateExists(string $template): bool
- {
- // Put your logic here
- // Method must return a boolean value
- }
-
- public function render(ZugferdDocumentReader $document, string $template): string
- {
- // Put your logic here
- // Method must return a string (rendered HTML markup)
- }
-}
-```
-
-### Use a custom renderer
-
-```php
-use horstoeko\zugferd\ZugferdDocumentReader;
-use horstoeko\zugferdvisualizer\ZugferdVisualizer;
-
-require dirname(__FILE__) . "/../vendor/autoload.php";
-
-$document = ZugferdDocumentReader::readAndGuessFromFile(dirname(__FILE__) . "/invoice_1.xml");
-
-$visualizer = new ZugferdVisualizer($document);
-$visualizer->setRenderer(new MyOwnRenderer());
-$visualizer->setTemplate('/assets/myowntemplate.tmpl');
-
-echo $visualizer->renderMarkup();
-```
-
-### Use the built-in Laravel renderer
-
-The ```ZugferdVisualizerLaravelRenderer``` can be used within the Laravel-Framework:
-
-```php
-namespace App\Http\Controllers;
-
-use Illuminate\Http\Request;
-use horstoeko\zugferd\ZugferdDocumentReader;
-use horstoeko\zugferdvisualizer\renderer\ZugferdVisualizerLaravelRenderer;
-use horstoeko\zugferdvisualizer\ZugferdVisualizer;
-
-class ZugferdController extends Controller
-{
- public function index(Request $request)
- {
- $document = ZugferdDocumentReader::readAndGuessFromFile(storage_path('app/invoice_1.xml'));
-
- $visualizer = new ZugferdVisualizer($document);
- $visualizer->setRenderer(app(ZugferdVisualizerLaravelRenderer::class));
- $visualizer->setTemplate('zugferd'); // ~/resources/views/zugferd.blade.php
-
- return $visualizer->renderMarkup();
- }
-
- public function download(Request $request)
- {
- $document = ZugferdDocumentReader::readAndGuessFromFile(storage_path('app/invoice_1.xml'));
-
- $visualizer = new ZugferdVisualizer($document);
- $visualizer->setRenderer(app(ZugferdVisualizerLaravelRenderer::class));
- $visualizer->setTemplate('zugferd');
- $visualizer->setPdfFontDefault("courier");
- $visualizer->setPdfPaperSize('A4-P');
- $visualizer->renderPdfFile(storage_path('app/invoice_1.pdf'));
-
- $headers = [
- 'Content-Type: application/pdf',
- ];
-
- return response()->download(storage_path('app/invoice_1.pdf'), "invoice_1.pdf", $headers);
- }
-}
-```
-
-### Set PDF-Options
-
-If you want to make further settings to the internal PDF engine, then you can change further settings using a callback.
-The usage is as follows:
-
-#### Set options before instanciating the internal PDF-Engine (```setPdfPreInitCallback```)
-
-```php
-use horstoeko\zugferdvisualizer\ZugferdVisualizer;
-use Mpdf\Mpdf;
-
-$visualizer = new ZugferdVisualizer(static::$document);
-$visualizer->setDefaultTemplate();
-$visualizer->setPdfPreInitCallback(function (array $config, ZugferdVisualizer $visualizer) {
- $config["orientation"] = "L";
- return $config;
-});
-```
-
-#### Set options after instanciating the internal PDF-Engine (```setPdfRuntimeInitCallback```)
-
-```php
-use horstoeko\zugferdvisualizer\ZugferdVisualizer;
-use Mpdf\Mpdf;
-
-$visualizer = new ZugferdVisualizer(static::$document);
-$visualizer->setDefaultTemplate();
-$visualizer->setPdfRuntimeInitCallback(function (Mpdf $mpdf, ZugferdVisualizer $visualizer) {
- $mpdf->pdf_version = "1.7";
-});
-```
-
-#### Working with custom fonts
-
-If you would like to use your own fonts, that's no problem at all. First you have to specify one or more directories in which your fonts are located:
-
-```php
-use horstoeko\zugferdvisualizer\ZugferdVisualizer;
-use Mpdf\Mpdf;
-
-$visualizer = new ZugferdVisualizer(static::$document);
-$visualizer->addPdfFontDirectory('/var/fonts1/');
-$visualizer->addPdfFontDirectory('/var/fonts2/');
-```
-
-Next, you need to define the font properties:
-
-* The first parameter sets the name of the font-family
-* Thé second parameter sets the type of the font
- * R - Regular
- * I - Italic
- * B - Bold
- * BI - Bold & Italic
-* The third parameter sets the filename under which the font can be found in the specified font-directories
-
-```php
-$visualizer->addPdfFontData('comicsans', 'R', 'comic.ttf');
-$visualizer->addPdfFontData('comicsans', 'I', 'comici.ttf');
-```
-
-If you want to set a custom font as the default font, you can use the following method:
-
-```php
-$visualizer->setPdfFontDefault("comicsans");
-```
-
-You can also use the name of the font family in the style attribute of any HTML elements in your template:
-
-```html
-
Text in Comic Sans
-```
-
-For more configuration options, please consult the documentation of [mPdf](https://mpdf.github.io/configuration/configuration-v7-x.html)
+# ZUGFeRD/XRechnung/Factur-X Visualizer
+
+[![Latest Stable Version](https://img.shields.io/packagist/v/horstoeko/zugferdvisualizer.svg?style=plastic)](https://packagist.org/packages/horstoeko/zugferdvisualizer)
+[![PHP version](https://img.shields.io/packagist/php-v/horstoeko/zugferdvisualizer.svg?style=plastic)](https://packagist.org/packages/horstoeko/zugferdvisualizer)
+[![License](https://img.shields.io/packagist/l/horstoeko/zugferdvisualizer.svg?style=plastic)](https://packagist.org/packages/horstoeko/zugferdvisualizer)
+
+[![Build Status](https://github.com/horstoeko/zugferdvisualizer/actions/workflows/build.ci.yml/badge.svg)](https://github.com/horstoeko/zugferdvisualizer/actions/workflows/build.ci.yml)
+[![Release Status](https://github.com/horstoeko/zugferdvisualizer/actions/workflows/build.release.yml/badge.svg)](https://github.com/horstoeko/zugferdvisualizer/actions/workflows/build.release.yml)
+
+## Table of Contents
+
+- [ZUGFeRD/XRechnung/Factur-X Visualizer](#zugferdxrechnungfactur-x-visualizer)
+ - [Table of Contents](#table-of-contents)
+ - [License](#license)
+ - [Overview](#overview)
+ - [Dependencies](#dependencies)
+ - [Installation](#installation)
+ - [Usage](#usage)
+ - [Create HTML markup from existing invoice document (XML) using built-in template](#create-html-markup-from-existing-invoice-document-xml-using-built-in-template)
+ - [Create a PDF file from existing invoice document (XML) using built-in template](#create-a-pdf-file-from-existing-invoice-document-xml-using-built-in-template)
+ - [Create a PDF string from existing invoice document (XML) using built-in template](#create-a-pdf-string-from-existing-invoice-document-xml-using-built-in-template)
+ - [Create a PDF string from document builder and merge XML with generated PDF](#create-a-pdf-string-from-document-builder-and-merge-xml-with-generated-pdf)
+ - [Create a custom renderer](#create-a-custom-renderer)
+ - [Use a custom renderer](#use-a-custom-renderer)
+ - [Use the built-in Laravel renderer](#use-the-built-in-laravel-renderer)
+ - [Set PDF-Options](#set-pdf-options)
+ - [Set options before instanciating the internal PDF-Engine (```setPdfPreInitCallback```)](#set-options-before-instanciating-the-internal-pdf-engine-setpdfpreinitcallback)
+ - [Set options after instanciating the internal PDF-Engine (```setPdfRuntimeInitCallback```)](#set-options-after-instanciating-the-internal-pdf-engine-setpdfruntimeinitcallback)
+ - [Working with custom fonts](#working-with-custom-fonts)
+
+## License
+
+The code in this project is provided under the [MIT](https://opensource.org/licenses/MIT) license.
+
+## Overview
+
+With `horstoeko/zugferdvisualizer` you can visualize ZUGFeRD/XRechnung/Factur-X documents. This package is an addon for [horstoeko/zugferd](https://github.com/horstoeko/zugferd) package. The system uses a markup template (HTML) to render the output. On top you can create a PDF from the rendered markup
+
+## Dependencies
+
+This package makes use of
+
+- [horstoeko/zugferd](https://github.com/horstoeko/zugferd)
+- [mPdf](https://github.com/mpdf/mpdf)
+
+## Installation
+
+There is one recommended way to install `horstoeko/zugferdvisualizer` via [Composer](https://getcomposer.org/):
+
+* adding the dependency to your ``composer.json`` file:
+
+```js
+ "require": {
+ ..
+ "horstoeko/zugferdvisualizer":"^1",
+ ..
+ },
+```
+
+## Usage
+
+### Create HTML markup from existing invoice document (XML) using built-in template
+
+```php
+use horstoeko\zugferd\ZugferdDocumentReader;
+use horstoeko\zugferdvisualizer\ZugferdVisualizer;
+
+require dirname(__FILE__) . "/../vendor/autoload.php";
+
+$document = ZugferdDocumentReader::readAndGuessFromFile(dirname(__FILE__) . "/invoice_1.xml");
+
+$visualizer = new ZugferdVisualizer($document);
+$visualizer->setDefaultTemplate();
+
+echo $visualizer->renderMarkup();
+```
+
+### Create a PDF file from existing invoice document (XML) using built-in template
+
+Find there [full example here](https://github.com/horstoeko/zugferdvisualizer/blob/master/examples/BuildFromDocumentReader.php)
+
+```php
+use horstoeko\zugferd\ZugferdDocumentReader;
+use horstoeko\zugferdvisualizer\ZugferdVisualizer;
+
+require dirname(__FILE__) . "/../vendor/autoload.php";
+
+$document = ZugferdDocumentReader::readAndGuessFromFile(dirname(__FILE__) . "/invoice_1.xml");
+
+$visualizer = new ZugferdVisualizer($document);
+$visualizer->setDefaultTemplate();
+$visualizer->setPdfFontDefault("courier");
+$visualizer->renderPdfFile(dirname(__FILE__) . "/invoice_1.pdf");
+```
+
+### Create a PDF string from existing invoice document (XML) using built-in template
+
+```php
+use horstoeko\zugferd\ZugferdDocumentReader;
+use horstoeko\zugferdvisualizer\ZugferdVisualizer;
+
+require dirname(__FILE__) . "/../vendor/autoload.php";
+
+$document = ZugferdDocumentReader::readAndGuessFromFile(dirname(__FILE__) . "/invoice_1.xml");
+
+$visualizer = new ZugferdVisualizer($document);
+$visualizer->setDefaultTemplate();
+$visualizer->setPdfFontDefault("courier");
+
+$pdfString = $visualizer->renderPdf();
+```
+
+### Create a PDF string from document builder and merge XML with generated PDF
+
+Find there [full example here](https://github.com/horstoeko/zugferdvisualizer/blob/master/examples/BuildFromDocumentBuilder.php)
+
+```php
+$document = ZugferdDocumentBuilder::CreateNew(ZugferdProfiles::PROFILE_EN16931);
+$document
+ ->setDocumentInformation("471102", "380", \DateTime::createFromFormat("Ymd", "20180305"), "EUR")
+ ->...
+
+$reader = ZugferdDocumentReader::readAndGuessFromContent($document->getContent());
+
+$visualizer = new ZugferdVisualizer($reader);
+$visualizer->setDefaultTemplate();
+$visualizer->setPdfFontDefault("courier");
+$visualizer->setPdfPaperSize('A4-P');
+
+$merger = new ZugferdDocumentPdfMerger($document->getContent(), $visualizer->renderPdf());
+$merger->generateDocument();
+$merger->saveDocument(dirname(__FILE__) . "/invoice_2.pdf");
+```
+
+### Create a custom renderer
+
+If you want to implement your own markup renderer, then your class must implement the interface `ZugferdVisualizerMarkupRendererContract`. The interface defines two methods:
+
+* `templateExists`
+* `render`
+
+```php
+use horstoeko\zugferd\ZugferdDocumentReader;
+use horstoeko\zugferdvisualizer\contracts\ZugferdVisualizerTranslatorContract;
+use horstoeko\zugferdvisualizer\contracts\ZugferdVisualizerMarkupRendererContract;
+
+class MyOwnRenderer implements ZugferdVisualizerMarkupRendererContract
+{
+ public function templateExists(string $template): bool
+ {
+ // Put your logic here
+ // Method must return a boolean value
+ }
+
+ public function render(ZugferdDocumentReader $document, ZugferdVisualizerTranslatorContract $translator, string $template): string
+ {
+ // Put your logic here
+ // Method must return a string (rendered HTML markup)
+ }
+}
+```
+
+### Use a custom renderer
+
+```php
+use horstoeko\zugferd\ZugferdDocumentReader;
+use horstoeko\zugferdvisualizer\ZugferdVisualizer;
+
+require dirname(__FILE__) . "/../vendor/autoload.php";
+
+$document = ZugferdDocumentReader::readAndGuessFromFile(dirname(__FILE__) . "/invoice_1.xml");
+
+$visualizer = new ZugferdVisualizer($document);
+$visualizer->setRenderer(new MyOwnRenderer());
+$visualizer->setTemplate('/assets/myowntemplate.tmpl');
+
+echo $visualizer->renderMarkup();
+```
+
+### Use the built-in Laravel renderer
+
+The ```ZugferdVisualizerLaravelRenderer``` can be used within the Laravel-Framework:
+
+```php
+namespace App\Http\Controllers;
+
+use Illuminate\Http\Request;
+use horstoeko\zugferd\ZugferdDocumentReader;
+use horstoeko\zugferdvisualizer\renderer\ZugferdVisualizerLaravelRenderer;
+use horstoeko\zugferdvisualizer\ZugferdVisualizer;
+
+class ZugferdController extends Controller
+{
+ public function index(Request $request)
+ {
+ $document = ZugferdDocumentReader::readAndGuessFromFile(storage_path('app/invoice_1.xml'));
+
+ $visualizer = new ZugferdVisualizer($document);
+ $visualizer->setRenderer(app(ZugferdVisualizerLaravelRenderer::class));
+ $visualizer->setTemplate('zugferd'); // ~/resources/views/zugferd.blade.php
+
+ return $visualizer->renderMarkup();
+ }
+
+ public function download(Request $request)
+ {
+ $document = ZugferdDocumentReader::readAndGuessFromFile(storage_path('app/invoice_1.xml'));
+
+ $visualizer = new ZugferdVisualizer($document);
+ $visualizer->setRenderer(app(ZugferdVisualizerLaravelRenderer::class));
+ $visualizer->setTemplate('zugferd');
+ $visualizer->setPdfFontDefault("courier");
+ $visualizer->setPdfPaperSize('A4-P');
+ $visualizer->renderPdfFile(storage_path('app/invoice_1.pdf'));
+
+ $headers = [
+ 'Content-Type: application/pdf',
+ ];
+
+ return response()->download(storage_path('app/invoice_1.pdf'), "invoice_1.pdf", $headers);
+ }
+}
+```
+
+### Set PDF-Options
+
+If you want to make further settings to the internal PDF engine, then you can change further settings using a callback.
+The usage is as follows:
+
+#### Set options before instanciating the internal PDF-Engine (```setPdfPreInitCallback```)
+
+```php
+use horstoeko\zugferdvisualizer\ZugferdVisualizer;
+use Mpdf\Mpdf;
+
+$visualizer = new ZugferdVisualizer(static::$document);
+$visualizer->setDefaultTemplate();
+$visualizer->setPdfPreInitCallback(function (array $config, ZugferdVisualizer $visualizer) {
+ $config["orientation"] = "L";
+ return $config;
+});
+```
+
+#### Set options after instanciating the internal PDF-Engine (```setPdfRuntimeInitCallback```)
+
+```php
+use horstoeko\zugferdvisualizer\ZugferdVisualizer;
+use Mpdf\Mpdf;
+
+$visualizer = new ZugferdVisualizer(static::$document);
+$visualizer->setDefaultTemplate();
+$visualizer->setPdfRuntimeInitCallback(function (Mpdf $mpdf, ZugferdVisualizer $visualizer) {
+ $mpdf->pdf_version = "1.7";
+});
+```
+
+#### Working with custom fonts
+
+If you would like to use your own fonts, that's no problem at all. First you have to specify one or more directories in which your fonts are located:
+
+```php
+use horstoeko\zugferdvisualizer\ZugferdVisualizer;
+use Mpdf\Mpdf;
+
+$visualizer = new ZugferdVisualizer(static::$document);
+$visualizer->addPdfFontDirectory('/var/fonts1/');
+$visualizer->addPdfFontDirectory('/var/fonts2/');
+```
+
+Next, you need to define the font properties:
+
+* The first parameter sets the name of the font-family
+* Thé second parameter sets the type of the font
+ * R - Regular
+ * I - Italic
+ * B - Bold
+ * BI - Bold & Italic
+* The third parameter sets the filename under which the font can be found in the specified font-directories
+
+```php
+$visualizer->addPdfFontData('comicsans', 'R', 'comic.ttf');
+$visualizer->addPdfFontData('comicsans', 'I', 'comici.ttf');
+```
+
+If you want to set a custom font as the default font, you can use the following method:
+
+```php
+$visualizer->setPdfFontDefault("comicsans");
+```
+
+You can also use the name of the font family in the style attribute of any HTML elements in your template:
+
+```html
+Text in Comic Sans
+```
+
+For more configuration options, please consult the documentation of [mPdf](https://mpdf.github.io/configuration/configuration-v7-x.html)
diff --git a/build/phpstan.neon b/build/phpstan.neon
index 5059614..dc14ced 100644
--- a/build/phpstan.neon
+++ b/build/phpstan.neon
@@ -1,8 +1,9 @@
-parameters:
- paths:
- - ../src
- - ../tests
- level: 5
- treatPhpDocTypesAsCertain: false
- excludePaths:
- - ../src/entities/*
+parameters:
+ paths:
+ - ../src
+ - ../tests
+ level: 5
+ treatPhpDocTypesAsCertain: false
+ excludePaths:
+ - ../src/renderer/ZugferdVisualizerLaravelRenderer.php
+ - ../src/translators/ZugferdVisualizerLaravelTranslator.php
diff --git a/build/phpunit.xml b/build/phpunit.xml
index 099f447..6e29156 100644
--- a/build/phpunit.xml
+++ b/build/phpunit.xml
@@ -1,29 +1,31 @@
-
-
-
-
- ../tests/testcases/BasicTest.php
- ../tests/testcases/VisualizerText.php
-
-
-
-
- ../src
-
-
- ../src/contracts
- ../src/exception
- ../src/renderer/ZugferdVisualizerLaravelRenderer.php
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ ../tests/testcases/BasicTest.php
+ ../tests/testcases/DefaultTranslatorTest.php
+ ../tests/testcases/VisualizerText.php
+
+
+
+
+ ../src
+
+
+ ../src/contracts
+ ../src/exception
+ ../src/renderer/ZugferdVisualizerLaravelRenderer.php
+ ../src/translators/ZugferdVisualizerLaravelTranslator.php
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/composer.json b/composer.json
index da5ab3e..c666945 100644
--- a/composer.json
+++ b/composer.json
@@ -1,65 +1,74 @@
-{
- "name": "horstoeko/zugferdvisualizer",
- "keywords": [],
- "description": "A library",
- "homepage": "https://github.com/horstoeko/zugferdvisualizer",
- "type": "package",
- "license": "MIT",
- "prefer-stable": true,
- "authors": [
- {
- "name": "Daniel Erling",
- "email": "daniel@erling.com.de",
- "role": "lead"
- }
- ],
- "config": {
- "optimize-autoloader": true,
- "sort-packages": true
- },
- "autoload": {
- "psr-4": {
- "horstoeko\\zugferdvisualizer\\": "src"
- }
- },
- "autoload-dev": {
- "psr-4": {
- "horstoeko\\zugferdvisualizer\\tests\\": "tests"
- }
- },
- "require": {
- "php": "^7.3|^7.4|^8",
- "ext-dom": "*",
- "ext-mbstring": "*",
- "dompdf/dompdf": "^2.0",
- "horstoeko/zugferd": "^1",
- "league/commonmark": "^1|^2",
- "mpdf/mpdf": "^8"
- },
- "require-dev": {
- "ext-gd": "*",
- "ext-json": "*",
- "ext-zip": "*",
- "pdepend/pdepend": "^2",
- "phploc/phploc": "^7",
- "phpmd/phpmd": "^2",
- "phpstan/phpstan": "^1.8",
- "phpunit/phpunit": "^9",
- "sebastian/phpcpd": "^6",
- "squizlabs/php_codesniffer": "^3"
- },
- "scripts": {
- "tests": "./vendor/bin/phpunit ./tests/",
- "testsreal": "./vendor/bin/phpunit --configuration ./build/phpunit.xml",
- "phpcs": "./vendor/bin/phpcs --standard=./build/phpcsrules.xml --extensions=php --ignore=autoload.php ./src ./tests",
- "phpcs12": "./vendor/bin/phpcs --standard=./build/phpcsrules_psr12.xml --extensions=php --ignore=autoload.php ./src ./tests",
- "phpcbf": "./vendor/bin/phpcbf -q ./src ./tests",
- "phpcbf1": "./vendor/bin/phpcbf --standard=./build/phpcsrules_psr1.xml -q ./src ./tests",
- "phpcbf2": "./vendor/bin/phpcbf --standard=./build/phpcsrules_psr2.xml -q ./src ./tests",
- "phpcbf12": "./vendor/bin/phpcbf --standard=./build/phpcsrules_psr12.xml -q ./src ./tests",
- "phpcbfsq": "./vendor/bin/phpcbf --standard=./build/phpcsrules_squiz.xml -q ./src ./tests",
- "phpstan": "./vendor/bin/phpstan analyze -c ./build/phpstan.neon --autoload-file=vendor/autoload.php --no-interaction --no-progress --xdebug",
- "phpstan_cs": "./vendor/bin/phpstan analyze -c ./build/phpstan.neon --autoload-file=vendor/autoload.php --no-interaction --no-progress --error-format=checkstyle --xdebug",
- "makedoc": "phing -f ./build.xml projectdoc"
- }
-}
+{
+ "name": "horstoeko/zugferdvisualizer",
+ "keywords": [],
+ "description": "A library",
+ "homepage": "https://github.com/horstoeko/zugferdvisualizer",
+ "type": "package",
+ "license": "MIT",
+ "prefer-stable": true,
+ "authors": [
+ {
+ "name": "Daniel Erling",
+ "email": "daniel@erling.com.de",
+ "role": "lead"
+ }
+ ],
+ "config": {
+ "optimize-autoloader": true,
+ "sort-packages": true
+ },
+ "autoload": {
+ "psr-4": {
+ "horstoeko\\zugferdvisualizer\\": "src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "horstoeko\\zugferdvisualizer\\tests\\": "tests"
+ }
+ },
+ "require": {
+ "php": "^7.3|^7.4|^8",
+ "ext-dom": "*",
+ "ext-mbstring": "*",
+ "dompdf/dompdf": "^2.0",
+ "horstoeko/zugferd": "^1",
+ "league/commonmark": "^1|^2",
+ "mpdf/mpdf": "^8"
+ },
+ "require-dev": {
+ "ext-gd": "*",
+ "ext-json": "*",
+ "ext-zip": "*",
+ "pdepend/pdepend": "^2",
+ "phploc/phploc": "^7",
+ "phpmd/phpmd": "^2",
+ "phpstan/phpstan": "^1.8",
+ "phpunit/phpunit": "^9",
+ "sebastian/phpcpd": "^6",
+ "squizlabs/php_codesniffer": "^3"
+ },
+ "scripts": {
+ "tests": "./vendor/bin/phpunit ./tests/",
+ "testsreal": "./vendor/bin/phpunit --configuration ./build/phpunit.xml",
+ "phpcs": "./vendor/bin/phpcs --standard=./build/phpcsrules.xml --extensions=php --ignore=autoload.php ./src ./tests",
+ "phpcs12": "./vendor/bin/phpcs --standard=./build/phpcsrules_psr12.xml --extensions=php --ignore=autoload.php ./src ./tests",
+ "phpcbf": "./vendor/bin/phpcbf -q ./src ./tests",
+ "phpcbf1": "./vendor/bin/phpcbf --standard=./build/phpcsrules_psr1.xml -q ./src ./tests",
+ "phpcbf2": "./vendor/bin/phpcbf --standard=./build/phpcsrules_psr2.xml -q ./src ./tests",
+ "phpcbf12": "./vendor/bin/phpcbf --standard=./build/phpcsrules_psr12.xml -q ./src ./tests",
+ "phpcbfsq": "./vendor/bin/phpcbf --standard=./build/phpcsrules_squiz.xml -q ./src ./tests",
+ "phpstan": "./vendor/bin/phpstan analyze -c ./build/phpstan.neon --autoload-file=vendor/autoload.php --no-interaction --no-progress --xdebug",
+ "phpstan_cs": "./vendor/bin/phpstan analyze -c ./build/phpstan.neon --autoload-file=vendor/autoload.php --no-interaction --no-progress --error-format=checkstyle --xdebug",
+ "makedoc": "phing -f ./build.xml projectdoc",
+ "checkstyle": [
+ "Composer\\Config::disableProcessTimeout",
+ "vendor/bin/phploc --count-tests --log-csv build/logs/phploc.csv --log-xml build/logs/phploc.xml src tests",
+ "vendor/bin/pdepend --jdepend-xml=build/logs/jdepend.xml --jdepend-chart=build/pdepend/dependencies.svg --overview-pyramid=build/pdepend/overview-pyramid.svg src",
+ "vendor/bin/phpmd src xml build/phpmd.xml --reportfile build/logs/pmd.xml --exclude src/entities/",
+ "vendor/bin/phpcs --report=checkstyle --report-file=build/logs/checkstyle.xml --standard=build/phpcsrules.xml --extensions=php --ignore=autoload.php src tests",
+ "vendor/bin/phpstan analyze -c build/phpstan.neon --autoload-file=vendor/autoload.php --no-interaction --no-progress --error-format=checkstyle > build/logs/checkstyle_phpstan.xml",
+ "vendor/bin/phpcpd --log-pmd build/logs/pmd-cpd.xml --exclude src/entities/ src"
+ ]
+ }
+}
diff --git a/examples/BuildFromDocumentBuilder.php b/examples/BuildFromDocumentBuilder.php
index fc66da8..b8c20e5 100644
--- a/examples/BuildFromDocumentBuilder.php
+++ b/examples/BuildFromDocumentBuilder.php
@@ -1,70 +1,72 @@
-setDocumentInformation("471102", "380", \DateTime::createFromFormat("Ymd", "20180305"), "EUR")
- ->addDocumentNote('Rechnung gemäß Bestellung vom 01.03.2018.')
- ->addDocumentNote('Lieferant GmbH' . PHP_EOL . 'Lieferantenstraße 20' . PHP_EOL . '80333 München' . PHP_EOL . 'Deutschland' . PHP_EOL . 'Geschäftsführer: Hans Muster' . PHP_EOL . 'Handelsregisternummer: H A 123' . PHP_EOL . PHP_EOL, null, 'REG')
- ->setDocumentSupplyChainEvent(\DateTime::createFromFormat('Ymd', '20180305'))
- ->addDocumentPaymentMean(ZugferdPaymentMeans::UNTDID_4461_58, null, null, null, null, null, "DE12500105170648489890", null, null, null)
- ->setDocumentSeller("Lieferant GmbH", "549910")
- ->addDocumentSellerGlobalId("4000001123452", "0088")
- ->addDocumentSellerTaxRegistration("FC", "201/113/40209")
- ->addDocumentSellerTaxRegistration("VA", "DE123456789")
- ->setDocumentSellerAddress("Lieferantenstraße 20", "", "", "80333", "München", "DE")
- ->setDocumentSellerContact("Heinz Mükker", "Buchhaltung", "+49-111-2222222", "+49-111-3333333", "info@lieferant.de")
- ->setDocumentBuyer("Kunden AG Mitte", "GE2020211")
- ->setDocumentBuyerReference("34676-342323")
- ->setDocumentBuyerAddress("Kundenstraße 15", "", "", "69876", "Frankfurt", "DE")
- ->addDocumentTax("S", "VAT", 275.0, 19.25, 7.0)
- ->addDocumentTax("S", "VAT", 198.0, 37.62, 19.0)
- ->setDocumentSummation(529.87, 529.87, 473.00, 0.0, 0.0, 473.00, 56.87, null, 0.0)
- ->addDocumentPaymentTerm("Zahlbar innerhalb 30 Tagen netto bis 04.04.2018, 3% Skonto innerhalb 10 Tagen bis 15.03.2018")
- ->addNewPosition("1")
- ->setDocumentPositionNote("Bemerkung zu Zeile 1")
- ->setDocumentPositionProductDetails("Trennblätter A4", "", "TB100A4", null, "0160", "4012345001235")
- ->setDocumentPositionProductOriginTradeCountry("CN")
- ->setDocumentPositionGrossPrice(9.9000)
- ->setDocumentPositionNetPrice(9.9000)
- ->setDocumentPositionQuantity(20, "H87")
- ->addDocumentPositionTax('S', 'VAT', 19)
- ->setDocumentPositionLineSummation(198.0)
- ->addNewPosition("2")
- ->setDocumentPositionNote("Bemerkung zu Zeile 2")
- ->setDocumentPositionProductDetails("Joghurt Banane", "", "ARNR2", null, "0160", "4000050986428")
- ->SetDocumentPositionGrossPrice(5.5000)
- ->SetDocumentPositionNetPrice(5.5000)
- ->SetDocumentPositionQuantity(50, "H87")
- ->AddDocumentPositionTax('S', 'VAT', 7)
- ->SetDocumentPositionLineSummation(275.0);
-
-// Read content
-
-$reader = ZugferdDocumentReader::readAndGuessFromContent($document->getContent());
-
-// Run the visualizer using the default renderer
-
-$visualizer = new ZugferdVisualizer($reader);
-$visualizer->setDefaultTemplate();
-$visualizer->addPdfFontDirectory(dirname(__FILE__) . '/fonts/');
-$visualizer->addPdfFontData('comicsans', 'R', 'comic.ttf');
-$visualizer->addPdfFontData('comicsans', 'I', 'comici.ttf');
-$visualizer->setPdfFontDefault("courier");
-$visualizer->setPdfPaperSize('A4-P');
-
-// Merge XML and PDF
-
-$merger = new ZugferdDocumentPdfMerger($document->getContent(), $visualizer->renderPdf());
-$merger->generateDocument();
-$merger->saveDocument(dirname(__FILE__) . "/invoice_2.pdf");
+setDocumentInformation("471102", "380", \DateTime::createFromFormat("Ymd", "20180305"), "EUR")
+ ->addDocumentNote('Rechnung gemäß Bestellung vom 01.03.2018.')
+ ->addDocumentNote('Lieferant GmbH' . PHP_EOL . 'Lieferantenstraße 20' . PHP_EOL . '80333 München' . PHP_EOL . 'Deutschland' . PHP_EOL . 'Geschäftsführer: Hans Muster' . PHP_EOL . 'Handelsregisternummer: H A 123' . PHP_EOL . PHP_EOL, null, 'REG')
+ ->setDocumentSupplyChainEvent(\DateTime::createFromFormat('Ymd', '20180305'))
+ ->addDocumentPaymentMean(ZugferdPaymentMeans::UNTDID_4461_58, null, null, null, null, null, "DE12500105170648489890", null, null, null)
+ ->setDocumentSeller("Lieferant GmbH", "549910")
+ ->addDocumentSellerGlobalId("4000001123452", "0088")
+ ->addDocumentSellerTaxRegistration("FC", "201/113/40209")
+ ->addDocumentSellerTaxRegistration("VA", "DE123456789")
+ ->setDocumentSellerAddress("Lieferantenstraße 20", "", "", "80333", "München", "DE")
+ ->setDocumentSellerContact("Heinz Mükker", "Buchhaltung", "+49-111-2222222", "+49-111-3333333", "info@lieferant.de")
+ ->setDocumentBuyer("Kunden AG Mitte", "GE2020211")
+ ->setDocumentBuyerReference("34676-342323")
+ ->setDocumentBuyerAddress("Kundenstraße 15", "", "", "69876", "Frankfurt", "DE")
+ ->addDocumentTax("S", "VAT", 275.0, 19.25, 7.0)
+ ->addDocumentTax("S", "VAT", 198.0, 37.62, 19.0)
+ ->setDocumentSummation(529.87, 529.87, 473.00, 0.0, 0.0, 473.00, 56.87, null, 0.0)
+ ->addDocumentPaymentTerm("Zahlbar innerhalb 30 Tagen netto bis 04.04.2018, 3% Skonto innerhalb 10 Tagen bis 15.03.2018")
+ ->addNewPosition("1")
+ ->setDocumentPositionNote("Bemerkung zu Zeile 1")
+ ->setDocumentPositionProductDetails("Trennblätter A4", "", "TB100A4", null, "0160", "4012345001235")
+ ->setDocumentPositionProductOriginTradeCountry("CN")
+ ->setDocumentPositionGrossPrice(9.9000)
+ ->setDocumentPositionNetPrice(9.9000)
+ ->setDocumentPositionQuantity(20, "H87")
+ ->addDocumentPositionTax('S', 'VAT', 19)
+ ->setDocumentPositionLineSummation(198.0)
+ ->addNewPosition("2")
+ ->setDocumentPositionNote("Bemerkung zu Zeile 2")
+ ->setDocumentPositionProductDetails("Joghurt Banane", "", "ARNR2", null, "0160", "4000050986428")
+ ->SetDocumentPositionGrossPrice(5.5000)
+ ->SetDocumentPositionNetPrice(5.5000)
+ ->SetDocumentPositionQuantity(50, "H87")
+ ->AddDocumentPositionTax('S', 'VAT', 7)
+ ->SetDocumentPositionLineSummation(275.0);
+
+// Read content
+
+$reader = ZugferdDocumentReader::readAndGuessFromContent($document->getContent());
+
+// Run the visualizer using the default renderer
+
+$visualizer = new ZugferdVisualizer($reader);
+$visualizer->setDefaultTemplate();
+$visualizer->setTranslator((new ZugferdVisualizerDefaultTranslator())->addLanguageDirectory(__DIR__)->setCurrentLanguage('de-DE')->setFallbackLanguage('en-US'));
+$visualizer->addPdfFontDirectory(dirname(__FILE__) . '/fonts/');
+$visualizer->addPdfFontData('comicsans', 'R', 'comic.ttf');
+$visualizer->addPdfFontData('comicsans', 'I', 'comici.ttf');
+$visualizer->setPdfFontDefault("courier");
+$visualizer->setPdfPaperSize('A4-P');
+
+// Merge XML and PDF
+
+$merger = new ZugferdDocumentPdfMerger($document->getContent(), $visualizer->renderPdf());
+$merger->generateDocument();
+$merger->saveDocument(dirname(__FILE__) . "/invoice_2.pdf");
diff --git a/examples/BuildFromDocumentReader2.php b/examples/BuildFromDocumentReader2.php
index 833c5a8..733a6a3 100644
--- a/examples/BuildFromDocumentReader2.php
+++ b/examples/BuildFromDocumentReader2.php
@@ -1,21 +1,23 @@
-setDefaultTemplate();
-$visualizer->addPdfFontDirectory(dirname(__FILE__) . '/fonts/');
-$visualizer->addPdfFontData('comicsans', 'R', 'comic.ttf');
-$visualizer->addPdfFontData('comicsans', 'I', 'comici.ttf');
-$visualizer->setPdfFontDefault("courier");
-$visualizer->setPdfPaperSize('A4-P');
-$visualizer->renderPdfFile(dirname(__FILE__) . "/invoice_2.pdf");
+setTranslator((new ZugferdVisualizerDefaultTranslator())->addLanguageDirectory(__DIR__)->setCurrentLanguage('de-DE')->setFallbackLanguage('en-US'));
+$visualizer->setDefaultTemplate();
+$visualizer->addPdfFontDirectory(dirname(__FILE__) . '/fonts/');
+$visualizer->addPdfFontData('comicsans', 'R', 'comic.ttf');
+$visualizer->addPdfFontData('comicsans', 'I', 'comici.ttf');
+$visualizer->setPdfFontDefault("courier");
+$visualizer->setPdfPaperSize('A4-P');
+$visualizer->renderPdfFile(dirname(__FILE__) . "/invoice_2.pdf");
diff --git a/examples/translation/de.php b/examples/translation/de.php
new file mode 100644
index 0000000..e076338
--- /dev/null
+++ b/examples/translation/de.php
@@ -0,0 +1,13 @@
+ [
+ 'H87' => 'St.',
+ 'MTK' => 'm²',
+ 'KGM' => 'kg',
+ 'C62' => 'mal',
+ ],
+ 'documenttype' => [
+ '380' => 'Rechnung',
+ ],
+];
diff --git a/examples/translation/en.php b/examples/translation/en.php
new file mode 100644
index 0000000..09171d5
--- /dev/null
+++ b/examples/translation/en.php
@@ -0,0 +1,13 @@
+ [
+ 'H87' => 'Piece',
+ 'MTK' => 'm²',
+ 'KGM' => 'kg',
+ 'C62' => 'times',
+ ],
+ 'documenttype' => [
+ '380' => 'Invoice',
+ ],
+];
diff --git a/src/ZugferdVisualizer.php b/src/ZugferdVisualizer.php
index 80685d8..1067ca1 100644
--- a/src/ZugferdVisualizer.php
+++ b/src/ZugferdVisualizer.php
@@ -15,9 +15,11 @@
use horstoeko\zugferd\ZugferdDocumentBuilder;
use horstoeko\zugferd\ZugferdDocumentReader;
use horstoeko\zugferdvisualizer\contracts\ZugferdVisualizerMarkupRendererContract;
+use horstoeko\zugferdvisualizer\contracts\ZugferdVisualizerTranslatorContract;
use horstoeko\zugferdvisualizer\exception\ZugferdVisualizerNoTemplateDefinedException;
use horstoeko\zugferdvisualizer\exception\ZugferdVisualizerNoTemplateNotExistsException;
use horstoeko\zugferdvisualizer\renderer\ZugferdVisualizerDefaultRenderer;
+use horstoeko\zugferdvisualizer\translators\ZugferdVisualizerDefaultTranslator;
use Mpdf\Config\ConfigVariables;
use Mpdf\Config\FontVariables;
use Mpdf\Exception\AssetFetchingException;
@@ -52,6 +54,13 @@ class ZugferdVisualizer
*/
protected $renderer = null;
+ /**
+ * The translator to use
+ *
+ * @var \horstoeko\zugferdvisualizer\contracts\ZugferdVisualizerTranslatorContract
+ */
+ protected $translator = null;
+
/**
* The template for the renderer to use
*
@@ -111,20 +120,20 @@ class ZugferdVisualizer
/**
* Factory for creating a visualizer by a ZugferdDocumentReader
*
- * @param ZugferdDocumentReader $documentReader
- * @param ZugferdVisualizerMarkupRendererContract|null $renderer
+ * @param ZugferdDocumentReader $documentReader
+ * @param ZugferdVisualizerMarkupRendererContract|null $renderer
* @return ZugferdVisualizer
*/
public static function fromDocumentReader(ZugferdDocumentReader $documentReader, ?ZugferdVisualizerMarkupRendererContract $renderer = null): ZugferdVisualizer
{
- return new static($documentReader, $renderer);
+ return new ZugferdVisualizer($documentReader, $renderer);
}
/**
* Factory for creating a visualizer by a ZugferdDocumentReader
*
- * @param ZugferdDocumentBuilder $documentBuilder
- * @param ZugferdVisualizerMarkupRendererContract|null $renderer
+ * @param ZugferdDocumentBuilder $documentBuilder
+ * @param ZugferdVisualizerMarkupRendererContract|null $renderer
* @return ZugferdVisualizer
*/
public static function fromDocumentBuilder(ZugferdDocumentBuilder $documentBuilder, ?ZugferdVisualizerMarkupRendererContract $renderer = null): ZugferdVisualizer
@@ -137,9 +146,9 @@ public static function fromDocumentBuilder(ZugferdDocumentBuilder $documentBuild
/**
* Constructor
*
- * @param ZugferdDocumentReader $documentReader
- * @param null|ZugferdVisualizerMarkupRendererContract $renderer
- * @return void
+ * @param ZugferdDocumentReader $documentReader
+ * @param null|ZugferdVisualizerMarkupRendererContract $renderer
+ * @return void
* @deprecated v2.0.0 Direct call of constructor will be removed in the future. Use static factory methods instead
*/
public function __construct(ZugferdDocumentReader $documentReader, ?ZugferdVisualizerMarkupRendererContract $renderer = null)
@@ -162,6 +171,17 @@ public function setRenderer(ZugferdVisualizerMarkupRendererContract $renderer):
$this->renderer = $renderer;
}
+ /**
+ * Setup the translator to use
+ *
+ * @param ZugferdVisualizerTranslatorContract $translator
+ * @return void
+ */
+ public function setTranslator(ZugferdVisualizerTranslatorContract $translator): void
+ {
+ $this->translator = $translator;
+ }
+
/**
* Set the template to use in the specified renderer
*
@@ -206,9 +226,9 @@ public function addPdfFontDirectory(string $directory): void
* - Example 1: ``$visualizer->addPdfFont('frutiger', 'R', 'Frutiger-Normal.ttf')``
* - Example 2: ``$visualizer->addPdfFont('frutiger', 'I', 'FrutigerObl-Normal.ttf')``
*
- * @param string $name
- * @param string $style
- * @param string $filename
+ * @param string $name
+ * @param string $style
+ * @param string $filename
* @return void
*/
public function addPdfFontData(string $name, string $style, string $filename): void
@@ -274,10 +294,11 @@ public function setPdfRuntimeInitCallback(callable $callback): void
public function renderMarkup(): string
{
$this->testMustUseDefaultRenderer();
+ $this->testMustUseDefaultTranslator();
$this->testTemplateIsSet();
$this->testTemplateExists();
- return $this->renderer->render($this->documentReader, $this->template);
+ return $this->renderer->render($this->documentReader, $this->translator, $this->template);
}
/**
@@ -356,6 +377,19 @@ private function testMustUseDefaultRenderer(): void
}
}
+ /**
+ * If no translator is specified the default translator is
+ * instanciated and used
+ *
+ * @return void
+ */
+ private function testMustUseDefaultTranslator(): void
+ {
+ if (!$this->translator) {
+ $this->setTranslator(new ZugferdVisualizerDefaultTranslator());
+ }
+ }
+
/**
* Check if a template for the renderer is defined. If no one is set then an exception
* is raised
diff --git a/src/assets/translation/de.php b/src/assets/translation/de.php
new file mode 100644
index 0000000..e06332e
--- /dev/null
+++ b/src/assets/translation/de.php
@@ -0,0 +1,39 @@
+ [
+ ],
+ 'documenttype' => [
+ ],
+ 'generaltexts' => [
+ 'greeting' => 'Werter Kunde',
+ 'leadingtext1' => 'Wir nehmen die Freiheit, folgende Positionen zu berechnen',
+ 'documentdate' => 'Rechnungsdatum',
+ 'postableheader' => [
+ 'posno' => 'Pos.',
+ 'description' => 'Beschreibung',
+ 'quantity' => 'Menge',
+ 'price' => 'Preis',
+ 'linemount' => 'Betrag',
+ 'vatpercent' => 'Steuer %',
+ ],
+ 'chargeindicator' => [
+ 'allowance' => 'Rabatt',
+ 'charge' => 'Zuschlag',
+ ],
+ 'totals' => [
+ 'heading' => 'Summen',
+ 'netamount' => 'Nettogesamtbetrag',
+ 'chargetotalamount' => 'Zuschlagsbetrag',
+ 'allowancetotalamount' => 'Rabattbetrag',
+ 'taxtotalamount' => 'Steuergesamtbetrag',
+ 'grandtotalamount' => 'Bruttogesamtbetrag',
+ 'alreadypaid' => 'Bereits bezahlt',
+ 'amounttopay' => 'Fälliger Gesamtbtrag',
+ ],
+ 'vattotals' => [
+ 'heading' => 'Steuer',
+ 'heading2' => 'Total',
+ ]
+ ],
+];
diff --git a/src/assets/translation/en.php b/src/assets/translation/en.php
new file mode 100644
index 0000000..e5cd921
--- /dev/null
+++ b/src/assets/translation/en.php
@@ -0,0 +1,39 @@
+ [
+ ],
+ 'documenttype' => [
+ ],
+ 'generaltexts' => [
+ 'greeting' => 'Dear customer',
+ 'leadingtext1' => 'We take the liberty of invoicing you for the following items',
+ 'documentdate' => 'Invoice date',
+ 'postableheader' => [
+ 'posno' => 'Pos.',
+ 'description' => 'Desc.',
+ 'quantity' => 'Qty.',
+ 'price' => 'Price',
+ 'linemount' => 'Amount',
+ 'vatpercent' => 'VAT %',
+ ],
+ 'chargeindicator' => [
+ 'allowance' => 'Allowance',
+ 'charge' => 'Charge',
+ ],
+ 'totals' => [
+ 'heading' => 'Totals',
+ 'netamount' => 'Net Total',
+ 'chargetotalamount' => 'Charge Total',
+ 'allowancetotalamount' => 'Allowance Total',
+ 'taxtotalamount' => 'Tax',
+ 'grandtotalamount' => 'Gross Total',
+ 'alreadypaid' => 'Already paid',
+ 'amounttopay' => 'Amount to pay',
+ ],
+ 'vattotals' => [
+ 'heading' => 'VAT Breakdown',
+ 'heading2' => 'Total',
+ ]
+ ],
+];
diff --git a/src/contracts/ZugferdVisualizerMarkupRendererContract.php b/src/contracts/ZugferdVisualizerMarkupRendererContract.php
index 8cf1805..ae1834b 100644
--- a/src/contracts/ZugferdVisualizerMarkupRendererContract.php
+++ b/src/contracts/ZugferdVisualizerMarkupRendererContract.php
@@ -33,9 +33,10 @@ public function templateExists(string $template): bool;
/**
* Render the HTML markup for the Zugferd document
*
- * @param ZugferdDocumentReader $document
- * @param string $template
+ * @param ZugferdDocumentReader $document
+ * @param ZugferdVisualizerTranslatorContract $translator
+ * @param string $template
* @return string
*/
- public function render(ZugferdDocumentReader $document, string $template): string;
+ public function render(ZugferdDocumentReader $document, ZugferdVisualizerTranslatorContract $translator, string $template): string;
}
diff --git a/src/contracts/ZugferdVisualizerTranslatorContract.php b/src/contracts/ZugferdVisualizerTranslatorContract.php
new file mode 100644
index 0000000..d732775
--- /dev/null
+++ b/src/contracts/ZugferdVisualizerTranslatorContract.php
@@ -0,0 +1,33 @@
+
+ * @license https://opensource.org/licenses/MIT MIT
+ * @link https://github.com/horstoeko/zugferdvisualizer
+ */
+interface ZugferdVisualizerTranslatorContract
+{
+ /**
+ * Translated a key with optional place holders.
+ * The key can be specified in the path.to.translation 'to search nested arrays.
+ *
+ * @param string $key
+ * @param array $placeholders
+ * @param string|null $domain
+ * @return string
+ */
+ public function translate(string $key, array $placeholders = [], ?string $domain = null): string;
+}
diff --git a/src/renderer/ZugferdVisualizerDefaultRenderer.php b/src/renderer/ZugferdVisualizerDefaultRenderer.php
index ea1e78e..4ab3cea 100644
--- a/src/renderer/ZugferdVisualizerDefaultRenderer.php
+++ b/src/renderer/ZugferdVisualizerDefaultRenderer.php
@@ -10,6 +10,7 @@
namespace horstoeko\zugferdvisualizer\renderer;
use horstoeko\zugferd\ZugferdDocumentReader;
+use horstoeko\zugferdvisualizer\contracts\ZugferdVisualizerTranslatorContract;
use horstoeko\zugferdvisualizer\contracts\ZugferdVisualizerMarkupRendererContract;
/**
@@ -34,7 +35,7 @@ public function templateExists(string $template): bool
/**
* @inheritDoc
*/
- public function render(ZugferdDocumentReader $document, string $template): string
+ public function render(ZugferdDocumentReader $document, ZugferdVisualizerTranslatorContract $translator, string $template): string
{
ob_start();
include $template;
diff --git a/src/renderer/ZugferdVisualizerLaravelRenderer.php b/src/renderer/ZugferdVisualizerLaravelRenderer.php
index 1adb3b3..9e77499 100644
--- a/src/renderer/ZugferdVisualizerLaravelRenderer.php
+++ b/src/renderer/ZugferdVisualizerLaravelRenderer.php
@@ -10,6 +10,7 @@
namespace horstoeko\zugferdvisualizer\renderer;
use horstoeko\zugferd\ZugferdDocumentReader;
+use horstoeko\zugferdvisualizer\contracts\ZugferdVisualizerTranslatorContract;
use horstoeko\zugferdvisualizer\contracts\ZugferdVisualizerMarkupRendererContract;
/**
@@ -43,7 +44,7 @@ public function templateExists(string $template): bool
/**
* @inheritDoc
*/
- public function render(ZugferdDocumentReader $document, string $template): string
+ public function render(ZugferdDocumentReader $document, ZugferdVisualizerTranslatorContract $translator, string $template): string
{
if (!function_exists("view")) {
return "";
@@ -52,7 +53,7 @@ public function render(ZugferdDocumentReader $document, string $template): strin
/**
* @var \Illuminate\Contracts\View\View
*/
- $view = call_user_func_array("view", [$template, ["document" => $document]]);
+ $view = call_user_func_array("view", [$template, ["document" => $document, 'translator' => $translator]]);
return $view->render();
}
diff --git a/src/template/default.tmpl b/src/template/default.tmpl
index 1ac8391..f311cf6 100644
--- a/src/template/default.tmpl
+++ b/src/template/default.tmpl
@@ -1,406 +1,406 @@
-
-
-
-
-
- getDocumentInformation($documentno, $documenttypecode, $documentdate, $invoiceCurrency, $taxCurrency, $documentname, $documentlanguage, $effectiveSpecifiedPeriod);
- $document->getDocumentBuyer($buyername, $buyerids, $buyerdescription);
- $document->getDocumentBuyerAddress($buyeraddressline1, $buyeraddressline2, $buyeraddressline3, $buyerpostcode, $buyercity, $buyercounty, $buyersubdivision);
- ?>
-
-
-
-
-
-
-
-
- Invoice
-
-
- Invoice Date format("d.m.Y"); ?>
-
-
- Dear customer,
-
-
- We take the liberty of invoicing you for the following items
-
- getDocumentNotes($documentNotes); ?>
-
-
-
-
-
-
-
-
- Pos. |
- Description |
- Qty |
- Price |
- Amount |
- VAT % |
-
-
-
- firstDocumentPosition()) {
- $isfirstposition = true;
- do {
- $document->getDocumentPositionGenerals($lineid, $linestatuscode, $linestatusreasoncode);
- $document->getDocumentPositionProductDetails($prodname, $proddesc, $prodsellerid, $prodbuyerid, $prodglobalidtype, $prodglobalid);
- $document->getDocumentPositionGrossPrice($grosspriceamount, $grosspricebasisquantity, $grosspricebasisquantityunitcode);
- $document->getDocumentPositionNetPrice($netpriceamount, $netpricebasisquantity, $netpricebasisquantityunitcode);
- $document->getDocumentPositionLineSummation($lineTotalAmount, $totalAllowanceChargeAmount);
- $document->getDocumentPositionQuantity($billedquantity, $billedquantityunitcode, $chargeFreeQuantity, $chargeFreeQuantityunitcode, $packageQuantity, $packageQuantityunitcode);
- ?>
- firstDocumentPositionNote()) { ?>
-
- |
-
- getDocumentPositionNote($posnoteContent, $posnoteContentCode, $posnoteSubjectCode); ?>
-
-
- |
-
- nextDocumentPositionNote()); ?>
-
- |
- |
- |
- |
- |
- firstDocumentPositionTax()) { ?>
- getDocumentPositionTax($categoryCode, $typeCode, $rateApplicablePercent, $calculatedAmount, $exemptionReason, $exemptionReasonCode); ?>
- % |
-
- |
-
-
- firstDocumentPositionGrossPriceAllowanceCharge()) { ?>
-
- getDocumentPositionGrossPrice($grossAmount, $grossBasisQuantity, $grossBasisQuantityUnitCode); ?>
- getDocumentPositionGrossPriceAllowanceCharge($actualAmount, $isCharge, $calculationPercent, $basisAmount, $reason, $taxTypeCode, $taxCategoryCode, $rateApplicablePercent, $sequence, $basisQuantity, $basisQuantityUnitCode, $reasonCode); ?>
-
- |
- |
- |
- () |
-
- nextDocumentPositionGrossPriceAllowanceCharge()); ?>
-
-
- nextDocumentPosition()); ?>
-
-
-
-
- firstDocumentAllowanceCharge()) { ?>
-
- |
-
-
- |
- Allowance/Charge |
-
-
-
- getDocumentAllowanceCharge($actualAmount, $isCharge, $taxCategoryCode, $taxTypeCode, $rateApplicablePercent, $sequence, $calculationPercent, $basisAmount, $basisQuantity, $basisQuantityUnitCode, $reasonCode, $reason); ?>
-
- |
- |
- |
- |
-
-
- nextDocumentAllowanceCharge()); ?>
-
-
-
-
- getDocumentSummation($grandTotalAmount, $duePayableAmount, $lineTotalAmount, $chargeTotalAmount, $allowanceTotalAmount, $taxBasisTotalAmount, $taxTotalAmount, $roundingAmount, $totalPrepaidAmount); ?>
-
- |
-
-
- |
- Totals |
-
-
- |
- Net Total |
- |
-
-
-
- |
- Charge Total |
- |
-
-
-
-
- |
- Allowance Total |
- |
-
-
-
- |
- Tax |
- |
-
-
- |
- Gross Total |
- |
-
-
- |
- Already paid |
- |
-
-
- |
- Amount to pay |
- |
-
-
-
-
- firstDocumentTax()) { ?>
-
- |
-
-
- |
- VAT Breakdown |
-
-
-
-
- getDocumentTax($categoryCode, $typeCode, $basisAmount, $calculatedAmount, $rateApplicablePercent, $exemptionReason, $exemptionReasonCode, $lineTotalBasisAmount, $allowanceChargeBasisAmount, $taxPointDate, $dueDateTypeCode); ?>
-
- |
- % |
- |
- |
-
-
-
- nextDocumentTax()); ?>
-
- |
- Total |
- |
- |
-
-
-
-
-
- firstDocumentPaymentTerms()) { ?>
-
-
-
- getDocumentPaymentTerm($description, $dueDate, $directDebitMandateID); ?>
-
-
- |
-
-
- nextDocumentPaymentTerms()); ?>
-
-
-
-
+
+
+
+
+
+ getDocumentInformation($documentno, $documenttypecode, $documentdate, $invoiceCurrency, $taxCurrency, $documentname, $documentlanguage, $effectiveSpecifiedPeriod);
+ $document->getDocumentBuyer($buyername, $buyerids, $buyerdescription);
+ $document->getDocumentBuyerAddress($buyeraddressline1, $buyeraddressline2, $buyeraddressline3, $buyerpostcode, $buyercity, $buyercounty, $buyersubdivision);
+ ?>
+
+
+
+
+
+
+
+
+ translateWithoutPlaceholders($documenttypecode, 'documenttype') ?>
+
+
+ translateWithoutPlaceholders('documentdate', 'generaltexts') ?> format("d.m.Y"); ?>
+
+
+ translateWithoutPlaceholders('greeting', 'generaltexts') ?>,
+
+
+ translateWithoutPlaceholders('leadingtext1', 'generaltexts') ?>
+
+ getDocumentNotes($documentNotes); ?>
+
+
+
+
+
+
+
+
+ translateWithoutPlaceholders('postableheader.posno', 'generaltexts') ?> |
+ translateWithoutPlaceholders('postableheader.description', 'generaltexts') ?> |
+ translateWithoutPlaceholders('postableheader.quantity', 'generaltexts') ?> |
+ translateWithoutPlaceholders('postableheader.price', 'generaltexts') ?> |
+ translateWithoutPlaceholders('postableheader.linemount', 'generaltexts') ?> |
+ translateWithoutPlaceholders('postableheader.vatpercent', 'generaltexts') ?> |
+
+
+
+ firstDocumentPosition()) {
+ $isfirstposition = true;
+ do {
+ $document->getDocumentPositionGenerals($lineid, $linestatuscode, $linestatusreasoncode);
+ $document->getDocumentPositionProductDetails($prodname, $proddesc, $prodsellerid, $prodbuyerid, $prodglobalidtype, $prodglobalid);
+ $document->getDocumentPositionGrossPrice($grosspriceamount, $grosspricebasisquantity, $grosspricebasisquantityunitcode);
+ $document->getDocumentPositionNetPrice($netpriceamount, $netpricebasisquantity, $netpricebasisquantityunitcode);
+ $document->getDocumentPositionLineSummation($lineTotalAmount, $totalAllowanceChargeAmount);
+ $document->getDocumentPositionQuantity($billedquantity, $billedquantityunitcode, $chargeFreeQuantity, $chargeFreeQuantityunitcode, $packageQuantity, $packageQuantityunitcode);
+ ?>
+ firstDocumentPositionNote()) { ?>
+
+ |
+
+ getDocumentPositionNote($posnoteContent, $posnoteContentCode, $posnoteSubjectCode); ?>
+
+
+ |
+
+ nextDocumentPositionNote()); ?>
+
+ |
+ |
+ translateWithoutPlaceholders($billedquantityunitcode, 'unitcodes') ?> |
+ |
+ |
+ firstDocumentPositionTax()) { ?>
+ getDocumentPositionTax($categoryCode, $typeCode, $rateApplicablePercent, $calculatedAmount, $exemptionReason, $exemptionReasonCode); ?>
+ % |
+
+ |
+
+
+ firstDocumentPositionGrossPriceAllowanceCharge()) { ?>
+
+ getDocumentPositionGrossPrice($grossAmount, $grossBasisQuantity, $grossBasisQuantityUnitCode); ?>
+ getDocumentPositionGrossPriceAllowanceCharge($actualAmount, $isCharge, $calculationPercent, $basisAmount, $reason, $taxTypeCode, $taxCategoryCode, $rateApplicablePercent, $sequence, $basisQuantity, $basisQuantityUnitCode, $reasonCode); ?>
+
+ |
+ translateWithoutPlaceholders('chargeindicator.charge', 'generaltexts') : $translator->translateWithoutPlaceholders('chargeindicator.allowance', 'generaltexts')) ?> |
+ |
+ () |
+
+ nextDocumentPositionGrossPriceAllowanceCharge()); ?>
+
+
+ nextDocumentPosition()); ?>
+
+
+
+
+ firstDocumentAllowanceCharge()) { ?>
+
+ |
+
+
+ |
+ translateWithoutPlaceholders('chargeindicator.allowance', 'generaltexts') ?>/translateWithoutPlaceholders('chargeindicator.charge', 'generaltexts') ?> |
+
+
+
+ getDocumentAllowanceCharge($actualAmount, $isCharge, $taxCategoryCode, $taxTypeCode, $rateApplicablePercent, $sequence, $calculationPercent, $basisAmount, $basisQuantity, $basisQuantityUnitCode, $reasonCode, $reason); ?>
+
+ |
+ |
+ |
+ |
+
+
+ nextDocumentAllowanceCharge()); ?>
+
+
+
+
+ getDocumentSummation($grandTotalAmount, $duePayableAmount, $lineTotalAmount, $chargeTotalAmount, $allowanceTotalAmount, $taxBasisTotalAmount, $taxTotalAmount, $roundingAmount, $totalPrepaidAmount); ?>
+
+ |
+
+
+ |
+ translateWithoutPlaceholders('totals.heading', 'generaltexts') ?> |
+
+
+ |
+ translateWithoutPlaceholders('totals.netamount', 'generaltexts') ?> |
+ |
+
+
+
+ |
+ translateWithoutPlaceholders('totals.chargetotalamount', 'generaltexts') ?> |
+ |
+
+
+
+
+ |
+ translateWithoutPlaceholders('totals.allowancetotalamount', 'generaltexts') ?> |
+ |
+
+
+
+ |
+ translateWithoutPlaceholders('totals.taxtotalamount', 'generaltexts') ?> |
+ |
+
+
+ |
+ translateWithoutPlaceholders('totals.grandtotalamount', 'generaltexts') ?> |
+ |
+
+
+ |
+ translateWithoutPlaceholders('totals.alreadypaid', 'generaltexts') ?> |
+ |
+
+
+ |
+ translateWithoutPlaceholders('totals.amounttopay', 'generaltexts') ?> |
+ |
+
+
+
+
+ firstDocumentTax()) { ?>
+
+ |
+
+
+ |
+ translateWithoutPlaceholders('vattotals.heading', 'generaltexts') ?> |
+
+
+
+
+ getDocumentTax($categoryCode, $typeCode, $basisAmount, $calculatedAmount, $rateApplicablePercent, $exemptionReason, $exemptionReasonCode, $lineTotalBasisAmount, $allowanceChargeBasisAmount, $taxPointDate, $dueDateTypeCode); ?>
+
+ |
+ % |
+ |
+ |
+
+
+
+ nextDocumentTax()); ?>
+
+ |
+ translateWithoutPlaceholders('vattotals.heading2', 'generaltexts') ?> |
+ |
+ |
+
+
+
+
+
+ firstDocumentPaymentTerms()) { ?>
+
+
+
+ getDocumentPaymentTerm($description, $dueDate, $directDebitMandateID); ?>
+
+
+ |
+
+
+ nextDocumentPaymentTerms()); ?>
+
+
+
+
\ No newline at end of file
diff --git a/src/translators/ZugferdVisualizerDefaultTranslator.php b/src/translators/ZugferdVisualizerDefaultTranslator.php
new file mode 100644
index 0000000..b9656dc
--- /dev/null
+++ b/src/translators/ZugferdVisualizerDefaultTranslator.php
@@ -0,0 +1,330 @@
+
+ * @license https://opensource.org/licenses/MIT MIT
+ * @link https://github.com/horstoeko/zugferdvisualizer
+ */
+class ZugferdVisualizerDefaultTranslator implements ZugferdVisualizerTranslatorContract
+{
+ /**
+ * Translations loaded
+ *
+ * @var array
+ */
+ private $translations = [];
+
+ /**
+ * The directories containing the translation file
+ *
+ * @var array
+ */
+ private $languageDirectories = [];
+
+ /**
+ * The currently selected language
+ *
+ * @var string
+ */
+ private $currentLanguage = 'en-US';
+
+ /**
+ * The fallback language
+ *
+ * @var string
+ */
+ private $fallbackLanguage = 'en-US';
+
+ /**
+ * The internal cache for translations
+ *
+ * @var array
+ */
+ private $translationsCache = [];
+
+ /**
+ * Constructor
+ *
+ * @param array $languageDirectories The directories where the language files are located
+ * @param string $currentLanguage The primary language code to use
+ * @param string $fallbackLanguage The fallback language code to use
+ * @return void
+ */
+ public function __construct(array $languageDirectories = [], $currentLanguage = 'en-US', $fallbackLanguage = 'en-US')
+ {
+ $this->initialize($languageDirectories, $currentLanguage, $fallbackLanguage);
+ }
+
+ /**
+ * Initialized the translator with language directories and languages.
+ *
+ * @param array $languageDirectories The directories where the language files are located
+ * @param string $currentLanguage The primary language code to use
+ * @param string $fallbackLanguage The fallback language code to use
+ * @return void
+ */
+ public function initialize(array $languageDirectories = [], $currentLanguage = 'en-US', $fallbackLanguage = 'en-US')
+ {
+ foreach (!empty($languageDirectories) ?: [__DIR__ . '/../assets/translation'] as $languageDirectory) {
+ $this->addLanguageDirectory($languageDirectory);
+ }
+
+ $this->setCurrentLanguage($currentLanguage);
+ $this->setFallbackLanguage($fallbackLanguage);
+ }
+
+ /**
+ * Add a directory that contains translation files.
+ *
+ * @param string $languageDirectory
+ * @return ZugferdVisualizerDefaultTranslator
+ * @throws InvalidArgumentException
+ */
+ public function addLanguageDirectory(string $languageDirectory): ZugferdVisualizerDefaultTranslator
+ {
+ if (!is_dir($languageDirectory)) {
+ throw new InvalidArgumentException("The specified directory does not exist: {$languageDirectory}");
+ }
+
+ if (in_array($languageDirectory, $this->languageDirectories)) {
+ return $this;
+ }
+
+ $this->languageDirectories[] = rtrim($languageDirectory, DIRECTORY_SEPARATOR);
+
+ $this->translationsCache = []; // Force reload
+
+ return $this;
+ }
+
+ /**
+ * Sets the current language.
+ *
+ * @param string $language
+ * @return ZugferdVisualizerDefaultTranslator
+ * @throws InvalidArgumentException
+ */
+ public function setCurrentLanguage(string $language): ZugferdVisualizerDefaultTranslator
+ {
+ $language = $this->getNormalizedLanguageCode($language);
+
+ if (!$this->getIsValidLanguage($language)) {
+ throw new InvalidArgumentException("Invalid language format: {$language}. The format XX or XX-XX is expected.");
+ }
+
+ $this->currentLanguage = $language;
+
+ return $this;
+ }
+
+ /**
+ * Sets the fallback language.
+ *
+ * @param string $language
+ * @return ZugferdVisualizerDefaultTranslator
+ * @throws InvalidArgumentException
+ */
+ public function setFallbackLanguage(string $language): ZugferdVisualizerDefaultTranslator
+ {
+ $language = $this->getNormalizedLanguageCode($language);
+
+ if (!$this->getIsValidLanguage($language)) {
+ throw new InvalidArgumentException("Invalid language format: {$language}. The format XX or XX-XX is expected.");
+ }
+
+ $this->fallbackLanguage = $language;
+
+ return $this;
+ }
+
+ /**
+ * Clear the list of observed language directories
+ *
+ * @return ZugferdVisualizerDefaultTranslator
+ */
+ public function clearLanguageDirectories(): ZugferdVisualizerDefaultTranslator
+ {
+ $this->languageDirectories = [];
+ $this->translationsCache = [];
+
+ return $this;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function translate(string $key, array $placeholders = [], ?string $domain = null): string
+ {
+ $this->loadTranslations();
+
+ $translation = $this->getByPath($this->translations, $key, $domain);
+
+ if ($translation === null) {
+ return $key;
+ }
+
+ foreach ($placeholders as $placeholder => $placeholderValue) {
+ $translation = str_replace(sprintf(":%s", $placeholder), $placeholderValue, $translation);
+ $translation = str_replace(sprintf("{{%s}}", $placeholder), $placeholderValue, $translation);
+ }
+
+ return $translation;
+ }
+
+ /**
+ * Translated a key without having optional place holders.
+ * The key can be specified in the path.to.translation 'to search nested arrays.
+ *
+ * @param string $key
+ * @param null|string $domain
+ * @return string
+ */
+ public function translateWithoutPlaceholders(string $key, ?string $domain = null): string
+ {
+ return $this->translate($key, [], $domain);
+ }
+
+ /**
+ * Invites the translations for the current and the fallback language.
+ *
+ * @return void
+ */
+ private function loadTranslations(): void
+ {
+ $this->translations = $this->getMergedArray(
+ $this->loadTranslationsForLanguage($this->fallbackLanguage),
+ $this->loadTranslationsForLanguage($this->currentLanguage)
+ );
+ }
+
+ /**
+ * Loads the translation files for a specific language.
+ *
+ * @param string $language
+ * @return array
+ */
+ private function loadTranslationsForLanguage(string $language): array
+ {
+ if (isset($this->translationsCache[$language])) {
+ return $this->translationsCache[$language];
+ }
+
+ $translations = [];
+
+ foreach ($this->languageDirectories as $languageDirectory) {
+ $filePath = $languageDirectory . DIRECTORY_SEPARATOR . "{$language}.php";
+
+ if (file_exists($filePath)) {
+ $fileTranslations = include $filePath;
+ if (is_array($fileTranslations)) {
+ $translations = $this->getMergedArray($translations, $fileTranslations);
+ }
+ } else {
+ $genericLanguage = explode('-', $language)[0];
+ $genericFilePath = $languageDirectory . DIRECTORY_SEPARATOR . "{$genericLanguage}.php";
+ if (file_exists($genericFilePath)) {
+ $fileTranslations = include $genericFilePath;
+ if (is_array($fileTranslations)) {
+ $translations = $this->getMergedArray($translations, $fileTranslations);
+ }
+ }
+ }
+ }
+
+ $this->translationsCache[$language] = $translations;
+
+ return $translations;
+ }
+
+ /**
+ * Merge translations
+ *
+ * @param array $array1
+ * @param array $array2
+ * @return array
+ */
+ private function getMergedArray(array $array1, array $array2)
+ {
+ foreach ($array2 as $key => $value) {
+ if (array_key_exists($key, $array1) && is_array($array1[$key]) && is_array($value)) {
+ $array1[$key] = $this->getMergedArray($array1[$key], $value);
+ } else {
+ $array1[$key] = $value;
+ }
+ }
+
+ return $array1;
+ }
+
+ /**
+ * Get a normalized language code on the XX or XX-XX format.
+ *
+ * @param string $language
+ * @return string
+ */
+ private function getNormalizedLanguageCode(string $language): string
+ {
+ $languageParts = explode('-', $language);
+
+ if (count($languageParts) === 2) {
+ return strtolower($languageParts[0]) . '-' . strtoupper($languageParts[1]);
+ }
+
+ return strtolower($language);
+ }
+
+ /**
+ * Validate a language
+ *
+ * @param mixed $language
+ * @return bool
+ */
+ private function getIsValidLanguage($language)
+ {
+ return preg_match('/^([a-z]{2})(-[A-Z]{2})?$/', $language) === 1;
+ }
+
+ /**
+ * Get a value from a nested array based on a path (array of keys).
+ *
+ * @param array $array
+ * @param string|array $path
+ * @param string|null $domain
+ * @return string|null
+ */
+ private function getByPath(array $array, $path, ?string $domain = null): ?string
+ {
+ if (is_string($path)) {
+ $path = explode('.', $path);
+ }
+
+ if ($domain) {
+ array_unshift($path, $domain);
+ }
+
+ foreach ($path as $key) {
+ if (!isset($array[$key])) {
+ return null;
+ }
+ $array = $array[$key];
+ }
+
+ return $array;
+ }
+}
diff --git a/src/translators/ZugferdVisualizerLaravelTranslator.php b/src/translators/ZugferdVisualizerLaravelTranslator.php
new file mode 100644
index 0000000..aab779f
--- /dev/null
+++ b/src/translators/ZugferdVisualizerLaravelTranslator.php
@@ -0,0 +1,40 @@
+
+ * @license https://opensource.org/licenses/MIT MIT
+ * @link https://github.com/horstoeko/zugferdvisualizer
+ */
+class ZugferdVisualizerLaravelTranslator implements ZugferdVisualizerTranslatorContract
+{
+ /**
+ * @inheritDoc
+ */
+ public function translate(string $key, array $placeHolders = [], ?string $domain = null): string
+ {
+ if (!function_exists("trans")) {
+ return $key;
+ }
+
+ if ($domain) {
+ $key = rtrim(ltrim($domain, ". \t\n\r\0\x0B"), ". \t\n\r\0\x0B") . "." . rtrim(ltrim($key, ". \t\n\r\0\x0B"), ". \t\n\r\0\x0B");
+ }
+
+ return call_user_func("trans", $key, $placeHolders);
+ }
+}
diff --git a/tests/assets/de-AT.php b/tests/assets/de-AT.php
new file mode 100644
index 0000000..aa25305
--- /dev/null
+++ b/tests/assets/de-AT.php
@@ -0,0 +1,10 @@
+ [
+ 'H87' => 'S.',
+ ],
+ 'documenttype' => [
+ '380' => 'Rchng',
+ ],
+];
diff --git a/tests/assets/de-DE.php b/tests/assets/de-DE.php
new file mode 100644
index 0000000..e965117
--- /dev/null
+++ b/tests/assets/de-DE.php
@@ -0,0 +1,14 @@
+ [
+ 'H87' => 'St.',
+ ],
+ 'documenttype' => [
+ '380' => 'Rechnungsbeleg',
+ ],
+ 'general' => [
+ 'greeting' => 'Hello :name',
+ 'greeting2' => 'Hello {{name}}',
+ ],
+];
diff --git a/tests/assets/de.php b/tests/assets/de.php
new file mode 100644
index 0000000..0880897
--- /dev/null
+++ b/tests/assets/de.php
@@ -0,0 +1,14 @@
+ [
+ 'H87' => 'St.',
+ ],
+ 'documenttype' => [
+ '380' => 'Rechnung',
+ ],
+ 'general' => [
+ 'greeting' => 'Hello :name',
+ 'greeting2' => 'Hello {{name}}',
+ ],
+];
diff --git a/tests/assets/en.php b/tests/assets/en.php
new file mode 100644
index 0000000..b61d1ef
--- /dev/null
+++ b/tests/assets/en.php
@@ -0,0 +1,10 @@
+ [
+ 'H87' => 'Piece',
+ ],
+ 'documenttype' => [
+ '380' => 'Invoice',
+ ],
+];
diff --git a/tests/testcases/DefaultTranslatorTest.php b/tests/testcases/DefaultTranslatorTest.php
new file mode 100644
index 0000000..42e6bbb
--- /dev/null
+++ b/tests/testcases/DefaultTranslatorTest.php
@@ -0,0 +1,276 @@
+assertEmpty($this->getPrivatePropertyFromObject($translator, 'translations')->getValue($translator));
+ $this->assertNotEmpty($this->getPrivatePropertyFromObject($translator, 'languageDirectories')->getValue($translator));
+ $this->assertCount(1, $this->getPrivatePropertyFromObject($translator, 'languageDirectories')->getValue($translator));
+ $this->assertEquals('en-US', $this->getPrivatePropertyFromObject($translator, 'currentLanguage')->getValue($translator));
+ $this->assertEquals('en-US', $this->getPrivatePropertyFromObject($translator, 'fallbackLanguage')->getValue($translator));
+ $this->assertEmpty($this->getPrivatePropertyFromObject($translator, 'translationsCache')->getValue($translator));
+ }
+
+ public function testAddExistingLanguageDirectory(): void
+ {
+ $translator = new ZugferdVisualizerDefaultTranslator();
+ $translator->addLanguageDirectory(__DIR__ . '/../assets');
+
+ $this->assertNotEmpty($this->getPrivatePropertyFromObject($translator, 'languageDirectories')->getValue($translator));
+ $this->assertCount(2, $this->getPrivatePropertyFromObject($translator, 'languageDirectories')->getValue($translator));
+ }
+
+ public function testAddNotExistingLanguageDirectory(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessageMatches('/The specified directory does not exist/');
+
+ $translator = new ZugferdVisualizerDefaultTranslator();
+ $translator->addLanguageDirectory(__DIR__ . '/../translations');
+ }
+
+ public function testSetValidCurrentLanguage(): void
+ {
+ $translator = new ZugferdVisualizerDefaultTranslator();
+ $translator->setCurrentLanguage('de-DE');
+
+ $this->assertEquals('de-DE', $this->getPrivatePropertyFromObject($translator, 'currentLanguage')->getValue($translator));
+ }
+
+ public function testSetInvalidCurrentLanguage(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid language format: invalid. The format XX or XX-XX is expected.');
+
+ $translator = new ZugferdVisualizerDefaultTranslator();
+ $translator->setCurrentLanguage('invalid');
+ }
+
+ public function testSetValidFallbackLanguage(): void
+ {
+ $translator = new ZugferdVisualizerDefaultTranslator();
+ $translator->setFallbackLanguage('de-DE');
+
+ $this->assertEquals('de-DE', $this->getPrivatePropertyFromObject($translator, 'fallbackLanguage')->getValue($translator));
+ }
+
+ public function testSetInvalidFallbackLanguage(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('Invalid language format: invalid. The format XX or XX-XX is expected.');
+
+ $translator = new ZugferdVisualizerDefaultTranslator();
+ $translator->setFallbackLanguage('invalid');
+ }
+
+ public function testTranslateWithTranslateableKey(): void
+ {
+ $translator = new ZugferdVisualizerDefaultTranslator();
+ $translator->clearLanguageDirectories();
+ $translator->addLanguageDirectory(__DIR__ . '/../assets');
+
+ $this->assertEquals('Piece', $translator->translate('H87', [], 'unitcodes'));
+ $this->assertTranslationCacheNotEmpty($translator);
+
+ $translator->setCurrentLanguage('de-DE');
+
+ $this->assertEquals('St.', $translator->translate('H87', [], 'unitcodes'));
+ $this->assertTranslationCacheNotEmpty($translator);
+ }
+
+ public function testTranslateWithNonTranslateableKey(): void
+ {
+ $translator = new ZugferdVisualizerDefaultTranslator();
+ $translator->clearLanguageDirectories();
+ $translator->addLanguageDirectory(__DIR__ . '/../assets');
+
+ $this->assertEquals('C62', $translator->translate('C62', [], 'unitcodes'));
+ $this->assertTranslationCacheNotEmpty($translator);
+
+ $translator->setCurrentLanguage('de-DE');
+
+ $this->assertEquals('C62', $translator->translate('C62', [], 'unitcodes'));
+ $this->assertTranslationCacheNotEmpty($translator);
+ }
+
+ public function testTranslateWithTranslateableKeyNoDomain(): void
+ {
+ $translator = new ZugferdVisualizerDefaultTranslator();
+ $translator->clearLanguageDirectories();
+ $translator->addLanguageDirectory(__DIR__ . '/../assets');
+
+ $this->assertEquals('Piece', $translator->translate('unitcodes.H87', []));
+ $this->assertTranslationCacheNotEmpty($translator);
+
+ $translator->setCurrentLanguage('de-DE');
+
+ $this->assertEquals('St.', $translator->translate('unitcodes.H87', []));
+ $this->assertTranslationCacheNotEmpty($translator);
+ }
+
+ public function testTranslateWithNonTranslateableKeyNoDomain(): void
+ {
+ $translator = new ZugferdVisualizerDefaultTranslator();
+ $translator->clearLanguageDirectories();
+ $translator->addLanguageDirectory(__DIR__ . '/../assets');
+
+ $this->assertEquals('unitcodes.C62', $translator->translate('unitcodes.C62', []));
+ $this->assertTranslationCacheNotEmpty($translator);
+
+ $translator->setCurrentLanguage('de-DE');
+
+ $this->assertEquals('unitcodes.C62', $translator->translate('unitcodes.C62', []));
+ $this->assertTranslationCacheNotEmpty($translator);
+ }
+
+ public function testTranslateWithTranslateableKeyAndPlaceHolders(): void
+ {
+ $translator = new ZugferdVisualizerDefaultTranslator();
+ $translator->clearLanguageDirectories();
+ $translator->addLanguageDirectory(__DIR__ . '/../assets');
+
+ $this->assertEquals('greeting', $translator->translate('greeting', ['name' => 'John Doe'], 'general'));
+ $this->assertTranslationCacheNotEmpty($translator);
+
+ $translator->setCurrentLanguage('de-DE');
+
+ $this->assertEquals('Hello John Doe', $translator->translate('greeting', ['name' => 'John Doe'], 'general'));
+ $this->assertTranslationCacheNotEmpty($translator);
+
+ $this->assertEquals('Hello John Doe', $translator->translate('greeting2', ['name' => 'John Doe'], 'general'));
+ $this->assertTranslationCacheNotEmpty($translator);
+ }
+
+ public function testTranslateWithTranslateableKeyAndPlaceHoldersNotPresentPlaceholder(): void
+ {
+ $translator = new ZugferdVisualizerDefaultTranslator();
+ $translator->clearLanguageDirectories();
+ $translator->addLanguageDirectory(__DIR__ . '/../assets');
+
+ $this->assertEquals('greeting', $translator->translate('greeting', ['name2' => 'John Doe'], 'general'));
+ $this->assertTranslationCacheNotEmpty($translator);
+
+ $translator->setCurrentLanguage('de-DE');
+
+ $this->assertEquals('Hello :name', $translator->translate('greeting', ['name2' => 'John Doe'], 'general'));
+ $this->assertTranslationCacheNotEmpty($translator);
+
+ $this->assertEquals('Hello {{name}}', $translator->translate('greeting2', ['name2' => 'John Doe'], 'general'));
+ $this->assertTranslationCacheNotEmpty($translator);
+ }
+
+ public function testTranslateWithTranslateableKeyAndWithNoPlaceholer(): void
+ {
+ $translator = new ZugferdVisualizerDefaultTranslator();
+ $translator->clearLanguageDirectories();
+ $translator->addLanguageDirectory(__DIR__ . '/../assets');
+
+ $this->assertEquals('greeting', $translator->translateWithoutPlaceholders('greeting', 'general'));
+ $this->assertTranslationCacheNotEmpty($translator);
+ }
+
+ public function testTranslateWithNonTranslateableKeyNoPlaceHolder(): void
+ {
+ $translator = new ZugferdVisualizerDefaultTranslator();
+ $translator->clearLanguageDirectories();
+ $translator->addLanguageDirectory(__DIR__ . '/../assets');
+
+ $this->assertEquals('C62', $translator->translateWithoutPlaceholders('C62', 'unitcodes'));
+ $this->assertTranslationCacheNotEmpty($translator);
+
+ $translator->setCurrentLanguage('de-DE');
+
+ $this->assertEquals('C62', $translator->translateWithoutPlaceholders('C62', 'unitcodes'));
+ $this->assertTranslationCacheNotEmpty($translator);
+ }
+
+ public function testForceReloadOnAddDirectoryNotRegistered(): void
+ {
+ $translator = new ZugferdVisualizerDefaultTranslator();
+ $translator->clearLanguageDirectories();
+ $translator->addLanguageDirectory(__DIR__ . '/../assets');
+
+ $this->assertEquals('unitcodes.C62', $translator->translate('unitcodes.C62', []));
+ $this->assertTranslationCacheNotEmpty($translator);
+
+ $translator->addLanguageDirectory(__DIR__);
+ $this->assertTranslationCacheEmpty($translator);
+ }
+
+ public function testForceReloadOnAddDirectoryAlreadyRegistered(): void
+ {
+ $translator = new ZugferdVisualizerDefaultTranslator();
+ $translator->clearLanguageDirectories();
+ $translator->addLanguageDirectory(__DIR__ . '/../assets');
+
+ $this->assertEquals('unitcodes.C62', $translator->translate('unitcodes.C62', []));
+ $this->assertTranslationCacheNotEmpty($translator);
+
+ $translator->addLanguageDirectory(__DIR__ . '/../assets');
+ $this->assertTranslationCacheNotEmpty($translator);
+ }
+
+ public function testLanguagePriority(): void
+ {
+ $translator = new ZugferdVisualizerDefaultTranslator();
+ $translator->clearLanguageDirectories();
+ $translator->addLanguageDirectory(__DIR__ . '/../assets');
+
+ $this->assertEquals('Invoice', $translator->translate('380', [], 'documenttype'));
+ $this->assertEquals('Piece', $translator->translate('H87', [], 'unitcodes'));
+ $this->assertTranslationCacheNotEmpty($translator);
+
+ $translator->setCurrentLanguage('de-DE');
+
+ $this->assertEquals('Rechnungsbeleg', $translator->translate('380', [], 'documenttype'));
+ $this->assertEquals('St.', $translator->translate('H87', [], 'unitcodes'));
+ $this->assertTranslationCacheNotEmpty($translator);
+
+ $translator->setCurrentLanguage('de-AT');
+
+ $this->assertEquals('Rchng', $translator->translate('380', [], 'documenttype'));
+ $this->assertEquals('S.', $translator->translate('H87', [], 'unitcodes'));
+ $this->assertTranslationCacheNotEmpty($translator);
+
+ $translator->setCurrentLanguage('de-DE');
+
+ $this->assertEquals('Rechnungsbeleg', $translator->translate('380', [], 'documenttype'));
+ $this->assertEquals('St.', $translator->translate('H87', [], 'unitcodes'));
+ $this->assertTranslationCacheNotEmpty($translator);
+
+ $translator->setCurrentLanguage('de');
+
+ $this->assertEquals('Rechnung', $translator->translate('380', [], 'documenttype'));
+ $this->assertEquals('St.', $translator->translate('H87', [], 'unitcodes'));
+ $this->assertTranslationCacheNotEmpty($translator);
+
+ $translator->setCurrentLanguage('en');
+
+ $this->assertEquals('Invoice', $translator->translate('380', [], 'documenttype'));
+ $this->assertEquals('Piece', $translator->translate('H87', [], 'unitcodes'));
+ $this->assertTranslationCacheNotEmpty($translator);
+ }
+
+ private function assertTranslationCacheEmpty($translator): void
+ {
+ $cache = $this->getPrivatePropertyFromObject($translator, 'translationsCache')->getValue($translator);
+
+ $this->assertEmpty($cache);
+ }
+
+ private function assertTranslationCacheNotEmpty(ZugferdVisualizerDefaultTranslator $translator): void
+ {
+ $cache = $this->getPrivatePropertyFromObject($translator, 'translationsCache')->getValue($translator);
+
+ $this->assertNotEmpty($cache);
+ }
+}