added controller to render markdown documentation #129

This commit is contained in:
Kevin Papst
2018-02-07 15:07:53 +01:00
parent 2e60e14132
commit 29699572bd
25 changed files with 658 additions and 140 deletions

View File

@@ -6,8 +6,9 @@
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
## Checklist:
## Checklist
- [ ] My code follows the code style
- [ ] All methods have a docheader with type declarations
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] All files have a license header
- [ ] All methods have a doc header with type declarations
- [ ] I have updated the documentation accordingly
- [ ] I have added tests to cover my changes

2
.gitignore vendored
View File

@@ -6,7 +6,7 @@
/bin/*
!bin/console
/var/data/*.sqlite
/var/data/kimai.sqlite
/var/cache/*
!var/cache/.gitkeep

View File

@@ -9,6 +9,7 @@
"avanzu/admin-theme-bundle": "dev-kevinpapst",
"beberlei/DoctrineExtensions": "^1.0",
"dama/doctrine-test-bundle": "^4.0",
"erusev/parsedown": "^1.6",
"sensio/framework-extra-bundle": "^5.1",
"symfony/asset": "^4.0",
"symfony/console": "^4.0",
@@ -38,6 +39,7 @@
"phpunit/phpunit": "^6.5",
"squizlabs/php_codesniffer": "^3.2",
"symfony/browser-kit": "^4.0",
"symfony/css-selector": "^4.0",
"symfony/debug-pack": "^1.0",
"symfony/dotenv": "^4.0",
"symfony/maker-bundle": "^1.0",

100
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "53194682429edad5d07d635bbb0cb5e0",
"content-hash": "e61ba50d155aed4f2355ddcfa87b6448",
"packages": [
{
"name": "almasaeed2010/adminlte",
@@ -1207,6 +1207,51 @@
],
"time": "2017-11-15T23:40:40+00:00"
},
{
"name": "erusev/parsedown",
"version": "1.6.4",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
"reference": "fbe3fe878f4fe69048bb8a52783a09802004f548"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/fbe3fe878f4fe69048bb8a52783a09802004f548",
"reference": "fbe3fe878f4fe69048bb8a52783a09802004f548",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35"
},
"type": "library",
"autoload": {
"psr-0": {
"Parsedown": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Emanuil Rusev",
"email": "hello@erusev.com",
"homepage": "http://erusev.com"
}
],
"description": "Parser for Markdown.",
"homepage": "http://parsedown.org",
"keywords": [
"markdown",
"parser"
],
"time": "2017-11-14T20:44:03+00:00"
},
{
"name": "jdorn/sql-formatter",
"version": "v1.2.17",
@@ -6681,6 +6726,59 @@
"homepage": "https://symfony.com",
"time": "2018-01-03T07:38:00+00:00"
},
{
"name": "symfony/css-selector",
"version": "v4.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
"reference": "f97600434e3141ef3cbb9ea42cf500fba88022b7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/css-selector/zipball/f97600434e3141ef3cbb9ea42cf500fba88022b7",
"reference": "f97600434e3141ef3cbb9ea42cf500fba88022b7",
"shasum": ""
},
"require": {
"php": "^7.1.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.0-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\CssSelector\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jean-François Simon",
"email": "jeanfrancois.simon@sensiolabs.com"
},
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony CssSelector Component",
"homepage": "https://symfony.com",
"time": "2018-01-03T07:38:00+00:00"
},
{
"name": "symfony/debug-bundle",
"version": "v4.0.3",

View File

@@ -0,0 +1,11 @@
# this configuration simplifies testing URLs protected by the security mechanism
# See https://symfony.com/doc/current/cookbook/testing/http_authentication.html
security:
encoders:
# to make tests much faster, BCrypt cost is changed to its minimum allowed value (4)
# See https://symfony.com/doc/current/reference/configuration/security.html#using-the-bcrypt-password-encoder
App\Entity\User: { algorithm: bcrypt, cost: 4 }
firewalls:
secured_area:
http_basic: ~

25
src/Constants.php Normal file
View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App;
/**
* Some very global constants for Kimai.
*/
class Constants
{
/**
* Currently only used for informational purpose in the footer
*/
const VERSION = '2.0 dev';
/**
* Used in multiple views
*/
const GITHUB = 'https://github.com/kevinpapst/kimai2/';
}

View File

@@ -0,0 +1,83 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Controller;
use App\Constants;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
/**
* This controller can render the markdown documentation from /var/docs/
*
* @Route("/help")
* @Security("has_role('ROLE_USER')")
*/
class HelpController extends Controller
{
const README = 'README';
const DOCS_DIR = 'var/docs/';
/**
* @var string
*/
protected $projectDirectory;
/**
* HelpController constructor.
* @param string $projectDirectory
*/
public function __construct(string $projectDirectory)
{
$this->projectDirectory = $projectDirectory;
}
/**
* @Route("/", defaults={"chapter": "README"}, name="help")
* @Route("/{chapter}", requirements={"chapter": "[a-zA-Z]*"}, name="help_chapter")
* @Method("GET")
*
* @param string $chapter
* @return \Symfony\Component\HttpFoundation\Response
*/
public function indexAction(?string $chapter)
{
$breadcrumb = [self::README];
if ($chapter !== self::README) {
$breadcrumb[] = $chapter;
}
$chapterFile = $this->getFilenameForChapter($chapter);
if (!file_exists($chapterFile)) {
throw $this->createNotFoundException('Documentation chapter not found: ' . $chapter);
}
$content = file_get_contents($chapterFile);
return $this->render('help/index.html.twig', [
'breadcrumb' => $breadcrumb,
'chapter' => $chapter,
'documentation' => $content,
'github' => Constants::GITHUB
]);
}
/**
* @param string $chapter
* @return string
*/
protected function getFilenameForChapter(string $chapter)
{
return $this->projectDirectory . DIRECTORY_SEPARATOR . self::DOCS_DIR . $chapter . '.md';
}
}

View File

@@ -27,6 +27,10 @@ class AppFixtures extends Fixture
use FixturesTrait;
const DEFAULT_PASSWORD = 'kitten';
const USERNAME_USER = 'john_user';
const USERNAME_TEAMLEAD = 'tony_teamlead';
const USERNAME_ADMIN = 'anna_admin';
const USERNAME_SUPER_ADMIN = 'susan_super';
/**
* @var UserPasswordEncoderInterface
@@ -93,7 +97,7 @@ class AppFixtures extends Fixture
'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=monsterid&f=y', true
],
[
'John Doe', 'Developer', 'john_user', 'john_user@example.com', 'ROLE_USER',
'John Doe', 'Developer', self::USERNAME_USER, 'john_user@example.com', 'ROLE_USER',
'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y', true
],
// inactive user to test login
@@ -102,16 +106,16 @@ class AppFixtures extends Fixture
'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y', false
],
[
'Tony Maier', 'Head of Development', 'tony_teamlead', 'tony_teamlead@example.com', 'ROLE_TEAMLEAD',
'Tony Maier', 'Head of Development', self::USERNAME_TEAMLEAD, 'tony_teamlead@example.com', 'ROLE_TEAMLEAD',
'https://en.gravatar.com/userimage/3533186/bf2163b1dd23f3107a028af0195624e9.jpeg', true
],
// no avatar to test default image macro
[
'Anna Smith', 'Administrator', 'anna_admin', 'anna_admin@example.com', 'ROLE_ADMIN', null, true
'Anna Smith', 'Administrator', self::USERNAME_ADMIN, 'anna_admin@example.com', 'ROLE_ADMIN', null, true
],
// no alias to test twig username macro
[
null, 'Super Administrator', 'susan_super', 'susan_super@example.com', 'ROLE_SUPER_ADMIN',
null, 'Super Administrator', self::USERNAME_SUPER_ADMIN, 'susan_super@example.com', 'ROLE_SUPER_ADMIN',
'/bundles/avanzuadmintheme/img/avatar.png', true
]
];

View File

@@ -11,6 +11,7 @@ namespace App\Twig;
use Symfony\Component\Intl\Intl;
use App\Entity\Timesheet;
use Twig\TwigFilter;
/**
* Multiple Twig extensions: filters and functions
@@ -37,11 +38,11 @@ class Extensions extends \Twig_Extension
public function getFilters()
{
return [
new \Twig_SimpleFilter('duration', array($this, 'duration')),
new \Twig_SimpleFilter('durationForEntry', array($this, 'durationForEntry')),
new \Twig_SimpleFilter('money', array($this, 'money')),
new \Twig_SimpleFilter('currency', array($this, 'currency')),
new \Twig_SimpleFilter('country', array($this, 'country')),
new TwigFilter('duration', [$this, 'duration']),
new TwigFilter('durationForEntry', [$this, 'durationForEntry']),
new TwigFilter('money', [$this, 'money']),
new TwigFilter('currency', [$this, 'currency']),
new TwigFilter('country', [$this, 'country']),
];
}
@@ -140,12 +141,4 @@ class Extensions extends \Twig_Extension
return $locales;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'kimai.extension';
}
}

View File

@@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Twig;
use App\Utils\Markdown;
use Twig\TwigFilter;
/**
* A twig extension to handle markdown parser.
*/
class MarkdownExtension extends \Twig_Extension
{
/**
* @var Markdown
*/
private $markdown;
/**
* MarkdownExtension constructor.
* @param Markdown $parser
*/
public function __construct(Markdown $parser)
{
$this->markdown = $parser;
}
/**
* @return TwigFilter[]
*/
public function getFilters()
{
return [
new TwigFilter('md2html', [$this, 'markdownToHtml'], ['is_safe' => ['html']]),
];
}
/**
* Transforms the given Markdown content into HTML
*
* @param string $content
* @return string
*/
public function markdownToHtml(string $content): string
{
return $this->markdown->toHtml($content);
}
}

41
src/Utils/Markdown.php Normal file
View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Utils;
use Parsedown as MarkdownParser;
/**
* A simple class to parse markdown syntax and return HTML.
*/
class Markdown
{
/**
* @var MarkdownParser
*/
private $parser;
/**
* Markdown constructor.
*/
public function __construct()
{
$this->parser = new MarkdownParser();
}
/**
* @param string $text
* @return string
*/
public function toHtml(string $text): string
{
return $this->parser->text($text);
}
}

View File

@@ -101,6 +101,9 @@
"egulias/email-validator": {
"version": "2.1.3"
},
"erusev/parsedown": {
"version": "1.6.4"
},
"friendsofphp/php-cs-fixer": {
"version": "2.2",
"recipe": {
@@ -260,6 +263,9 @@
"ref": "9f94d3ea453cd8a3b95db7f82592d7344fe3a76a"
}
},
"symfony/css-selector": {
"version": "v4.0.4"
},
"symfony/debug": {
"version": "v4.0.3"
},

View File

@@ -41,13 +41,6 @@
{% block avanzu_footer %}
{% block footer %}
<footer class="main-footer no-print">
<div class="pull-right hidden-xs">
<b>Version</b> 2.0 dev
</div>
Made with ♥ by <a href="http://www.kevinpapst.de">Kevin Papst</a>.
{{ 'footer.license'|trans }}
</footer>
<!-- Page rendered on {{ 'now'|localizeddate('long', 'long', null, 'UTC') }} -->
{% endblock %}
{% endblock %}

View File

@@ -0,0 +1,26 @@
{% extends 'base.html.twig' %}
{% block page_title %}{{ 'help.title'|trans }}{% endblock %}
{% block page_subtitle %}{{ 'help.subtitle'|trans }}{% endblock %}
{% block main %}
{% set replacer = {
'.md"': '"',
'<table>': '<table class="table">',
'href="../../': 'target="_blank" href="'~github~'blob/master/',
('href="' ~ github): 'target="_blank" href="'~github
} %}
<div class="box">
{% if chapter != 'README' %}
<div class="box-header with-border">
<a href="{{ path('help') }}">{{ 'action.back'|trans }}</a>
</div>
{% endif %}
<div class="box-body">
{{ documentation|md2html|replace(replacer)|raw }}
</div>
</div>
{% endblock %}

View File

@@ -1,11 +1,24 @@
<h3 class="control-sidebar-heading">{{ 'home.title'|trans({}, 'sidebar') }}</h3>
<div class="form-group">
{{ 'home.betainfo'|trans({}, 'sidebar')|raw|nl2br }}
<ul class="control-sidebar-menu">
<li>
<a href="https://github.com/kevinpapst/kimai2/" target="_blank">{{ 'home.link.issues'|trans({}, 'sidebar') }}</a>
</li>
</ul>
</div>
<h3 class="control-sidebar-heading">{{ 'help.title'|trans({}, 'sidebar') }}</h3>
<div class="form-group">
<a href="{{ path('help') }}" class="btn btn-success">{{ 'help.link'|trans({}, 'sidebar') }}</a>
</div>
<h3 class="control-sidebar-heading">{{ 'about.title'|trans({}, 'sidebar') }}</h3>
<div class="form-group">
<p>
Kimai {{ constant('App\\Constants::VERSION') }}
</p>
<p>
{{ 'made.by.license'|trans({'%kevin%': '<a href="http://www.kevinpapst.de" target="_blank">Kevin Papst</a>', '%simon%': '<a href="https://github.com/simonschaufi" target="_blank">Simon Schaufelberger</a>'}, 'sidebar')|raw }}
</p>
<p>
{{ 'github.link'|trans({'%url%': constant('App\\Constants::GITHUB')}, 'sidebar')|raw }}
</p>
</div>
{#
<ul class="control-sidebar-menu">

View File

@@ -0,0 +1,74 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Controller;
use App\DataFixtures\AppFixtures;
use Symfony\Bundle\FrameworkBundle\Client;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
/**
* ControllerBaseTest adds some useful functions for writing integration tests.
*/
abstract class ControllerBaseTest extends WebTestCase
{
const DEFAULT_LANGUAGE = 'en';
/**
* @return Client
*/
protected function getClientForAuthenticatedUser()
{
$client = self::createClient([], [
'PHP_AUTH_USER' => AppFixtures::USERNAME_USER,
'PHP_AUTH_PW' => AppFixtures::DEFAULT_PASSWORD,
]);
return $client;
}
/**
* @param Client $client
* @param string $url
* @param string $method
* @return \Symfony\Component\DomCrawler\Crawler
*/
protected function request(Client $client, string $url, $method = 'GET')
{
return $client->request($method, '/' . self::DEFAULT_LANGUAGE . $url);
}
/**
* @param string $url
* @param string $method
*/
protected function assertUrlIsSecured(string $url, $method = 'GET')
{
$client = self::createClient();
$client->request($method, '/' . self::DEFAULT_LANGUAGE . $url);
$this->assertTrue($client->getResponse()->isRedirect());
$this->assertEquals(
'http://localhost/' . self::DEFAULT_LANGUAGE . '/login',
$client->getResponse()->getTargetUrl(),
sprintf('The %s secure URL redirects to the login form.', $url)
);
}
/**
* @param Client $client
*/
protected function assertRouteNotFound(Client $client)
{
$this->assertFalse($client->getResponse()->isSuccessful());
$this->assertEquals(404, $client->getResponse()->getStatusCode());
}
}

View File

@@ -1,83 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
/**
* TODO adjust to actual app
*
* Functional test that implements a "smoke test" of all the public and secure
* URLs of the application.
* See http://symfony.com/doc/current/best_practices/tests.html#functional-tests.
*
* Execute the application tests using this command (requires PHPUnit to be installed):
*
* $ cd your-symfony-project/
* $ phpunit -c app
*
* @group integration
*/
class DefaultControllerTest extends WebTestCase
{
/**
* PHPUnit's data providers allow to execute the same tests repeated times
* using a different set of data each time.
* See http://symfony.com/doc/current/cookbook/form/unit_testing.html#testing-against-different-sets-of-data.
*
* @dataProvider getPublicUrls
*/
public function testPublicUrls($url)
{
$this->markTestIncomplete(__METHOD__);
$client = self::createClient();
$client->request('GET', $url);
$this->assertTrue(
$client->getResponse()->isSuccessful(),
sprintf('The %s public URL loads correctly.', $url)
);
}
/**
* The application contains a lot of secure URLs which shouldn't be
* publicly accessible. This tests ensures that whenever a user tries to
* access one of those pages, a redirection to the login form is performed.
*
* @dataProvider getSecureUrls
*/
public function testSecureUrls($url)
{
$this->markTestSkipped('No admin URLs for testing');
$client = self::createClient();
$client->request('GET', $url);
$this->assertTrue($client->getResponse()->isRedirect());
$this->assertEquals(
'http://localhost/en/login',
$client->getResponse()->getTargetUrl(),
sprintf('The %s secure URL redirects to the login form.', $url)
);
}
public function getPublicUrls()
{
yield ['/'];
yield ['/en/login'];
}
public function getSecureUrls()
{
yield ['/en/admin/post/'];
}
}

View File

@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Controller;
/**
* @group integration
*/
class HelpControllerTest extends ControllerBaseTest
{
public function testIsSecure()
{
$this->assertUrlIsSecured('/help/');
}
public function testReadmePage()
{
$client = $this->getClientForAuthenticatedUser();
$this->request($client, '/help/');
$this->assertTrue($client->getResponse()->isSuccessful());
$this->assertContains('<h1>Kimai documentation</h1>', $client->getResponse()->getContent());
$this->assertNotContains('<a href="/en/help/README">', $client->getResponse()->getContent());
}
public function testUsersPage()
{
$client = $this->getClientForAuthenticatedUser();
$this->request($client, '/help/users');
$this->assertTrue($client->getResponse()->isSuccessful());
$this->assertContains('<a href="/en/help/README">', $client->getResponse()->getContent());
}
public function testValidateRouteDoesNotAllowSpecialChars()
{
$client = $this->getClientForAuthenticatedUser();
$this->request($client, '/help/.users');
$this->assertRouteNotFound($client);
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Twig;
use App\Twig\MarkdownExtension;
use App\Utils\Markdown;
use \PHPUnit\Framework\TestCase;
/**
* @covers \App\Twig\MarkdownExtension
*/
class MarkdownExtensionTest extends TestCase
{
public function testGetFilters()
{
$sut = new MarkdownExtension(new Markdown());
$filters = $sut->getFilters();
$this->assertCount(1, $filters);
$this->assertEquals('md2html', $filters[0]->getName());
}
public function testMarkdownToHtml()
{
$sut = new MarkdownExtension(new Markdown());
$this->assertEquals('<p><em>test</em></p>', $sut->markdownToHtml('*test*'));
$this->assertEquals('<h1>foobar</h1>', $sut->markdownToHtml('# foobar'));
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\Utils;
use App\Utils\Markdown;
use \PHPUnit\Framework\TestCase;
/**
* @covers \App\Utils\Markdown
*/
class MarkdownTest extends TestCase
{
public function testMarkdownToHtml()
{
$sut = new Markdown();
$this->assertEquals('<p><em>test</em></p>', $sut->toHtml('*test*'));
$this->assertEquals('<h1>foobar</h1>', $sut->toHtml('# foobar'));
}
}

View File

@@ -116,14 +116,6 @@
<target>Benutzer</target>
</trans-unit>
<!--
Footer
-->
<trans-unit id="footer.license">
<source>footer.license</source>
<target>Alle Rechte vorbehalten.</target>
</trans-unit>
<!--
Error templates
-->
@@ -368,6 +360,18 @@
<target>Aktiviert die minimierte Version der linken Navigationsleiste</target>
</trans-unit>
<!--
Help - Manual
-->
<trans-unit id="help.title">
<source>help.title</source>
<target>Hilfe</target>
</trans-unit>
<trans-unit id="help.subtitle">
<source>help.subtitle</source>
<target>Anleitung zur Benutzung und Konfiguration von Kimai (nur auf Englisch verfügbar)</target>
</trans-unit>
<!--
Admin: Timesheet
-->
@@ -649,11 +653,11 @@
</trans-unit>
<trans-unit id="invoice.number">
<source>invoice.number</source>
<target>Rechnung</target>
<target>Rechnungsnummer</target>
</trans-unit>
<trans-unit id="invoice.subtotal">
<source>invoice.subtotal</source>
<target>Netto</target>
<target>Rechnungsbetrag (netto)</target>
</trans-unit>
<trans-unit id="invoice.tax">
<source>invoice.tax</source>
@@ -661,7 +665,19 @@
</trans-unit>
<trans-unit id="invoice.total">
<source>invoice.total</source>
<target>Rechnungsbetrag</target>
<target>Rechnungsendbetrag</target>
</trans-unit>
<trans-unit id="invoice.service_date">
<source>invoice.service_date</source>
<target>Leistungsdatum</target>
</trans-unit>
<trans-unit id="invoice.amount">
<source>invoice.amount</source>
<target>Anzahl</target>
</trans-unit>
<trans-unit id="invoice.total_rate">
<source>invoice.total_rate</source>
<target>Gesamtpreis</target>
</trans-unit>
<trans-unit id="label.payment_terms">
<source>label.payment_terms</source>

View File

@@ -368,6 +368,18 @@
<target>Toggle the size of the left sidebar's header (full or collapse)</target>
</trans-unit>
<!--
Help - Manual
-->
<trans-unit id="help.title">
<source>help.title</source>
<target>Help</target>
</trans-unit>
<trans-unit id="help.subtitle">
<source>help.subtitle</source>
<target>Instructions for using and configuring Kimai</target>
</trans-unit>
<!--
Admin: Timesheet
-->
@@ -649,7 +661,7 @@
</trans-unit>
<trans-unit id="invoice.number">
<source>invoice.number</source>
<target>Invoice</target>
<target>Invoice number</target>
</trans-unit>
<trans-unit id="invoice.subtotal">
<source>invoice.subtotal</source>
@@ -663,6 +675,18 @@
<source>invoice.total</source>
<target>Total</target>
</trans-unit>
<trans-unit id="invoice.service_date">
<source>invoice.service_date</source>
<target>Service date</target>
</trans-unit>
<trans-unit id="invoice.amount">
<source>invoice.amount</source>
<target>Quantity</target>
</trans-unit>
<trans-unit id="invoice.total_rate">
<source>invoice.total_rate</source>
<target>Total price</target>
</trans-unit>
<trans-unit id="label.payment_terms">
<source>label.payment_terms</source>
<target>Terms of payment</target>

View File

@@ -13,13 +13,27 @@
Diese neue Version 2 ist aktuell noch in einer frühen Phase der Entwicklung und wurde vorab veröffentlicht, um frühzeitig Rückmeldungen ihrer Benutzer zu sammeln und deren Ideen in die weitere Planung zu integrieren.
Um bei der Verbesserung von <strong>Kimai</strong> zu helfen, schicken Sie mir bitte eine Nachricht auf der verlinkten Webseite. Egal ob Fragen, Fehlermeldungen oder Ideen für Erweiterungen, Ihr Feedback ist wertvoll!
Weitere Informationen auf der:
]]></target>
</trans-unit>
<trans-unit id="home.link.issues">
<source>home.link.issues</source>
<target>GitHub Projekt-Webseite</target>
<trans-unit id="made.by.license">
<source>made.by.license</source>
<target>Mit ♥ entwickelt von %kevin% und %simon%.</target>
</trans-unit>
<trans-unit id="github.link">
<source>github.link</source>
<target><![CDATA[Weitere Informationen finden Sie auf der <a href="%url%" target="_blank">Kimai Homepage</a>.]]></target>
</trans-unit>
<trans-unit id="help.title">
<source>help.title</source>
<target>Dokumentation</target>
</trans-unit>
<trans-unit id="help.link">
<source>help.link</source>
<target>Benutzer Handbuch</target>
</trans-unit>
<trans-unit id="about.title">
<source>about.title</source>
<target>Über Kimai</target>
</trans-unit>
<trans-unit id="preferences.title">
<source>preferences.title</source>

View File

@@ -13,13 +13,27 @@
This new version 2 is currently in an early stage of development and has been published in advance to collect feedback from its users and integrate their ideas into further planning.
To help me improve <strong>Kimai</strong>, please send your message on the linked website. Whether you have questions, error messages or ideas for enhancements, your feedback is valuable!
Further information on the:
]]></target>
</trans-unit>
<trans-unit id="home.link.issues">
<source>home.link.issues</source>
<target>GitHub Project-Website</target>
<trans-unit id="made.by.license">
<source>made.by.license</source>
<target><![CDATA[Made with ♥ by %kevin% and %simon%.]]></target>
</trans-unit>
<trans-unit id="github.link">
<source>github.link</source>
<target><![CDATA[Further information can be found on the <a href="%url%" target="_blank">Kimai Homepage</a>.]]></target>
</trans-unit>
<trans-unit id="help.title">
<source>help.title</source>
<target>Documentation</target>
</trans-unit>
<trans-unit id="help.link">
<source>help.link</source>
<target>User manual</target>
</trans-unit>
<trans-unit id="about.title">
<source>about.title</source>
<target>About Kimai</target>
</trans-unit>
<trans-unit id="preferences.title">
<source>preferences.title</source>

BIN
var/data/kimai_test.sqlite Normal file

Binary file not shown.