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); ?> - - - - - - - - - - - - - - - - - - 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()) { ?> - - - - - 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()) { ?> - - - - - - - - - - getDocumentAllowanceCharge($actualAmount, $isCharge, $taxCategoryCode, $taxTypeCode, $rateApplicablePercent, $sequence, $calculationPercent, $basisAmount, $basisQuantity, $basisQuantityUnitCode, $reasonCode, $reason); ?> - - - - - - - - nextDocumentAllowanceCharge()); ?> - - - - - getDocumentSummation($grandTotalAmount, $duePayableAmount, $lineTotalAmount, $chargeTotalAmount, $allowanceTotalAmount, $taxBasisTotalAmount, $taxTotalAmount, $roundingAmount, $totalPrepaidAmount); ?> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - firstDocumentTax()) { ?> - - - - - - - - - - - getDocumentTax($categoryCode, $typeCode, $basisAmount, $calculatedAmount, $rateApplicablePercent, $exemptionReason, $exemptionReasonCode, $lineTotalBasisAmount, $allowanceChargeBasisAmount, $taxPointDate, $dueDateTypeCode); ?> - - - - - - - - - nextDocumentTax()); ?> - - - - - - - - - - - firstDocumentPaymentTerms()) { ?> - - - - getDocumentPaymentTerm($description, $dueDate, $directDebitMandateID); ?> - - - - nextDocumentPaymentTerms()); ?> - - -
Pos.DescriptionQtyPriceAmountVAT %
  - getDocumentPositionNote($posnoteContent, $posnoteContentCode, $posnoteSubjectCode); ?> - - -
% 
   ()
 
 Allowance/Charge
 
 
 Totals
 Net Total
 Charge Total
 Allowance Total
 Tax
 Gross Total
 Already paid
 Amount to pay
 
 VAT Breakdown
 %
 Total
- -
- + + + + + + 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); ?> + + + + + + + + + + + + + + + + + + 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()) { ?> + + + + + 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()) { ?> + + + + + + + + + + getDocumentAllowanceCharge($actualAmount, $isCharge, $taxCategoryCode, $taxTypeCode, $rateApplicablePercent, $sequence, $calculationPercent, $basisAmount, $basisQuantity, $basisQuantityUnitCode, $reasonCode, $reason); ?> + + + + + + + + nextDocumentAllowanceCharge()); ?> + + + + + getDocumentSummation($grandTotalAmount, $duePayableAmount, $lineTotalAmount, $chargeTotalAmount, $allowanceTotalAmount, $taxBasisTotalAmount, $taxTotalAmount, $roundingAmount, $totalPrepaidAmount); ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + firstDocumentTax()) { ?> + + + + + + + + + + + getDocumentTax($categoryCode, $typeCode, $basisAmount, $calculatedAmount, $rateApplicablePercent, $exemptionReason, $exemptionReasonCode, $lineTotalBasisAmount, $allowanceChargeBasisAmount, $taxPointDate, $dueDateTypeCode); ?> + + + + + + + + + nextDocumentTax()); ?> + + + + + + + + + + + firstDocumentPaymentTerms()) { ?> + + + + getDocumentPaymentTerm($description, $dueDate, $directDebitMandateID); ?> + + + + nextDocumentPaymentTerms()); ?> + + +
translateWithoutPlaceholders('postableheader.posno', 'generaltexts') ?>translateWithoutPlaceholders('postableheader.description', 'generaltexts') ?>translateWithoutPlaceholders('postableheader.quantity', 'generaltexts') ?>translateWithoutPlaceholders('postableheader.price', 'generaltexts') ?>translateWithoutPlaceholders('postableheader.linemount', 'generaltexts') ?>translateWithoutPlaceholders('postableheader.vatpercent', 'generaltexts') ?>
  + getDocumentPositionNote($posnoteContent, $posnoteContentCode, $posnoteSubjectCode); ?> + + +
translateWithoutPlaceholders($billedquantityunitcode, 'unitcodes') ?> % 
 translateWithoutPlaceholders('chargeindicator.charge', 'generaltexts') : $translator->translateWithoutPlaceholders('chargeindicator.allowance', 'generaltexts')) ?>  ()
 
 translateWithoutPlaceholders('chargeindicator.allowance', 'generaltexts') ?>/translateWithoutPlaceholders('chargeindicator.charge', 'generaltexts') ?>
 
 
 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') ?>
 
 translateWithoutPlaceholders('vattotals.heading', 'generaltexts') ?>
 %
 translateWithoutPlaceholders('vattotals.heading2', 'generaltexts') ?>
+ +
+ \ 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); + } +}