]> BookStack Code Mirror - bookstack/commitdiff
Merge pull request #1180 from vasiliev123/update-pl-language
authorDan Brown <redacted>
Sat, 22 Dec 2018 14:58:30 +0000 (14:58 +0000)
committerGitHub <redacted>
Sat, 22 Dec 2018 14:58:30 +0000 (14:58 +0000)
Updates for PL language

36 files changed:
app/Auth/Access/Ldap.php
app/Auth/Access/LdapService.php
app/Http/Controllers/HomeController.php
app/Notifications/ConfirmEmail.php
app/Notifications/MailNotification.php [new file with mode: 0644]
app/Notifications/ResetPassword.php
app/Providers/TranslationServiceProvider.php [new file with mode: 0644]
app/Translation/Translator.php [new file with mode: 0644]
composer.json
config/app.php
config/mail.php
resources/assets/js/services/code.js
resources/lang/de_informal/activities.php [new file with mode: 0644]
resources/lang/de_informal/auth.php [new file with mode: 0644]
resources/lang/de_informal/common.php [new file with mode: 0644]
resources/lang/de_informal/components.php [new file with mode: 0644]
resources/lang/de_informal/entities.php [new file with mode: 0644]
resources/lang/de_informal/errors.php [new file with mode: 0644]
resources/lang/de_informal/pagination.php [new file with mode: 0644]
resources/lang/de_informal/passwords.php [new file with mode: 0644]
resources/lang/de_informal/settings.php [new file with mode: 0644]
resources/lang/de_informal/validation.php [new file with mode: 0644]
resources/lang/en/activities.php
resources/lang/en/auth.php
resources/lang/en/common.php
resources/lang/en/components.php
resources/lang/en/entities.php
resources/lang/en/errors.php
resources/lang/en/pagination.php
resources/lang/en/passwords.php
resources/lang/en/settings.php
resources/lang/en/validation.php
resources/lang/format.php [new file with mode: 0755]
resources/views/vendor/notifications/email-plain.blade.php
tests/Auth/LdapTest.php
tests/LanguageTest.php

index 468c376268d5ae3e884317e641ecf8e4166894b1..843a2f204920e9bd5154efe477c5212f73efa057 100644 (file)
@@ -92,4 +92,27 @@ class Ldap
     {
         return ldap_bind($ldapConnection, $bindRdn, $bindPassword);
     }
+
+    /**
+     * Explode a LDAP dn string into an array of components.
+     * @param string $dn
+     * @param int $withAttrib
+     * @return array
+     */
+    public function explodeDn(string $dn, int $withAttrib)
+    {
+        return ldap_explode_dn($dn, $withAttrib);
+    }
+
+    /**
+     * Escape a string for use in an LDAP filter.
+     * @param string $value
+     * @param string $ignore
+     * @param int $flags
+     * @return string
+     */
+    public function escape(string $value, string $ignore = "", int $flags = 0)
+    {
+        return ldap_escape($value, $ignore, $flags);
+    }
 }
index d3a177f8e1dffbac095e4d504550b646182eb690..b49ecf129fc1d5b606c60ecb668f2e0c6736eea1 100644 (file)
@@ -107,6 +107,7 @@ class LdapService
         if ($ldapUser === null) {
             return false;
         }
+
         if ($ldapUser['uid'] !== $user->external_auth_id) {
             return false;
         }
@@ -195,7 +196,7 @@ class LdapService
         $newAttrs = [];
         foreach ($attrs as $key => $attrText) {
             $newKey = '${' . $key . '}';
-            $newAttrs[$newKey] = $attrText;
+            $newAttrs[$newKey] = $this->ldap->escape($attrText);
         }
         return strtr($filterString, $newAttrs);
     }
@@ -265,7 +266,8 @@ class LdapService
         $baseDn = $this->config['base_dn'];
         $groupsAttr = strtolower($this->config['group_attribute']);
 
-        $groups = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, 'CN='.$groupName, [$groupsAttr]);
+        $groupFilter = 'CN=' . $this->ldap->escape($groupName);
+        $groups = $this->ldap->searchAndGetEntries($ldapConnection, $baseDn, $groupFilter, [$groupsAttr]);
         if ($groups['count'] === 0) {
             return [];
         }
@@ -277,23 +279,26 @@ class LdapService
     /**
      * Filter out LDAP CN and DN language in a ldap search return
      * Gets the base CN (common name) of the string
-     * @param string $ldapSearchReturn
+     * @param array $userGroupSearchResponse
      * @return array
      */
-    protected function groupFilter($ldapSearchReturn)
+    protected function groupFilter(array $userGroupSearchResponse)
     {
         $groupsAttr = strtolower($this->config['group_attribute']);
         $ldapGroups = [];
         $count = 0;
-        if (isset($ldapSearchReturn[$groupsAttr]['count'])) {
-            $count = (int) $ldapSearchReturn[$groupsAttr]['count'];
+
+        if (isset($userGroupSearchResponse[$groupsAttr]['count'])) {
+            $count = (int) $userGroupSearchResponse[$groupsAttr]['count'];
         }
+
         for ($i=0; $i<$count; $i++) {
-            $dnComponents = ldap_explode_dn($ldapSearchReturn[$groupsAttr][$i], 1);
+            $dnComponents = $this->ldap->explodeDn($userGroupSearchResponse[$groupsAttr][$i], 1);
             if (!in_array($dnComponents[0], $ldapGroups)) {
                 $ldapGroups[] = $dnComponents[0];
             }
         }
+
         return $ldapGroups;
     }
 
index 2bf029b519d76645a4ec807d49d44d0003bf17fb..5a5f34e4a5ce0d61e37bc797cfe1350bb49a5ef0 100644 (file)
@@ -79,6 +79,7 @@ class HomeController extends Controller
     {
         $locale = app()->getLocale();
         $cacheKey = 'GLOBAL_TRANSLATIONS_' . $locale;
+
         if (cache()->has($cacheKey) && config('app.env') !== 'development') {
             $resp = cache($cacheKey);
         } else {
@@ -89,15 +90,6 @@ class HomeController extends Controller
                 'entities' => trans('entities'),
                 'errors' => trans('errors')
             ];
-            if ($locale !== 'en') {
-                $enTrans = [
-                    'common' => trans('common', [], 'en'),
-                    'components' => trans('components', [], 'en'),
-                    'entities' => trans('entities', [], 'en'),
-                    'errors' => trans('errors', [], 'en')
-                ];
-                $translations = array_replace_recursive($enTrans, $translations);
-            }
             $resp = 'window.translations = ' . json_encode($translations);
             cache()->put($cacheKey, $resp, 120);
         }
index f3797e6e228874559439de231fba034fd48d45af..7ecadc298f1f4042a7b887584ca6820bec7bb926 100644 (file)
@@ -1,17 +1,7 @@
-<?php
+<?php namespace BookStack\Notifications;
 
-namespace BookStack\Notifications;
-
-use Illuminate\Bus\Queueable;
-use Illuminate\Contracts\Queue\ShouldQueue;
-use Illuminate\Notifications\Messages\MailMessage;
-use Illuminate\Notifications\Notification;
-
-class ConfirmEmail extends Notification implements ShouldQueue
+class ConfirmEmail extends MailNotification
 {
-
-    use Queueable;
-    
     public $token;
 
     /**
@@ -23,17 +13,6 @@ class ConfirmEmail extends Notification implements ShouldQueue
         $this->token = $token;
     }
 
-    /**
-     * Get the notification's delivery channels.
-     *
-     * @param  mixed  $notifiable
-     * @return array
-     */
-    public function via($notifiable)
-    {
-        return ['mail'];
-    }
-
     /**
      * Get the mail representation of the notification.
      *
@@ -43,10 +22,10 @@ class ConfirmEmail extends Notification implements ShouldQueue
     public function toMail($notifiable)
     {
         $appName = ['appName' => setting('app-name')];
-        return (new MailMessage)
-                    ->subject(trans('auth.email_confirm_subject', $appName))
-                    ->greeting(trans('auth.email_confirm_greeting', $appName))
-                    ->line(trans('auth.email_confirm_text'))
-                    ->action(trans('auth.email_confirm_action'), baseUrl('/register/confirm/' . $this->token));
+        return $this->newMailMessage()
+                ->subject(trans('auth.email_confirm_subject', $appName))
+                ->greeting(trans('auth.email_confirm_greeting', $appName))
+                ->line(trans('auth.email_confirm_text'))
+                ->action(trans('auth.email_confirm_action'), baseUrl('/register/confirm/' . $this->token));
     }
 }
diff --git a/app/Notifications/MailNotification.php b/app/Notifications/MailNotification.php
new file mode 100644 (file)
index 0000000..413ac6d
--- /dev/null
@@ -0,0 +1,35 @@
+<?php namespace BookStack\Notifications;
+
+use Illuminate\Bus\Queueable;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Notifications\Messages\MailMessage;
+use Illuminate\Notifications\Notification;
+
+class MailNotification extends Notification implements ShouldQueue
+{
+    use Queueable;
+
+    /**
+     * Get the notification's channels.
+     *
+     * @param  mixed  $notifiable
+     * @return array|string
+     */
+    public function via($notifiable)
+    {
+        return ['mail'];
+    }
+
+    /**
+     * Create a new mail message.
+     * @return MailMessage
+     */
+    protected function newMailMessage()
+    {
+        return (new MailMessage)->view([
+            'html' => 'vendor.notifications.email',
+            'text' => 'vendor.notifications.email-plain'
+        ]);
+    }
+
+}
\ No newline at end of file
index 86e93228f857a64ed5965126cfd0958180665fba..282aa335a07aed35b52c4ad2520565d567ca217f 100644 (file)
@@ -1,11 +1,7 @@
-<?php
+<?php namespace BookStack\Notifications;
 
-namespace BookStack\Notifications;
 
-use Illuminate\Notifications\Messages\MailMessage;
-use Illuminate\Notifications\Notification;
-
-class ResetPassword extends Notification
+class ResetPassword extends MailNotification
 {
     /**
      * The password reset token.
@@ -24,17 +20,6 @@ class ResetPassword extends Notification
         $this->token = $token;
     }
 
-    /**
-     * Get the notification's channels.
-     *
-     * @param  mixed  $notifiable
-     * @return array|string
-     */
-    public function via($notifiable)
-    {
-        return ['mail'];
-    }
-
     /**
      * Build the mail representation of the notification.
      *
@@ -42,7 +27,7 @@ class ResetPassword extends Notification
      */
     public function toMail()
     {
-        return (new MailMessage)
+            return $this->newMailMessage()
             ->subject(trans('auth.email_reset_subject', ['appName' => setting('app-name')]))
             ->line(trans('auth.email_reset_text'))
             ->action(trans('auth.reset_password'), baseUrl('password/reset/' . $this->token))
diff --git a/app/Providers/TranslationServiceProvider.php b/app/Providers/TranslationServiceProvider.php
new file mode 100644 (file)
index 0000000..0e628c7
--- /dev/null
@@ -0,0 +1,32 @@
+<?php namespace BookStack\Providers;
+
+
+use BookStack\Translation\Translator;
+
+class TranslationServiceProvider extends \Illuminate\Translation\TranslationServiceProvider
+{
+    /**
+     * Register the service provider.
+     *
+     * @return void
+     */
+    public function register()
+    {
+        $this->registerLoader();
+
+        $this->app->singleton('translator', function ($app) {
+            $loader = $app['translation.loader'];
+
+            // When registering the translator component, we'll need to set the default
+            // locale as well as the fallback locale. So, we'll grab the application
+            // configuration so we can easily get both of these values from there.
+            $locale = $app['config']['app.locale'];
+
+            $trans = new Translator($loader, $locale);
+
+            $trans->setFallback($app['config']['app.fallback_locale']);
+
+            return $trans;
+        });
+    }
+}
\ No newline at end of file
diff --git a/app/Translation/Translator.php b/app/Translation/Translator.php
new file mode 100644 (file)
index 0000000..2edfecf
--- /dev/null
@@ -0,0 +1,74 @@
+<?php namespace BookStack\Translation;
+
+
+class Translator extends \Illuminate\Translation\Translator
+{
+
+    /**
+     * Mapping of locales to their base locales
+     * @var array
+     */
+    protected $baseLocaleMap = [
+        'de_informal' => 'de',
+    ];
+
+    /**
+     * Get the translation for a given key.
+     *
+     * @param  string  $key
+     * @param  array   $replace
+     * @param  string  $locale
+     * @return string|array|null
+     */
+    public function trans($key, array $replace = [], $locale = null)
+    {
+        $translation = $this->get($key, $replace, $locale);
+
+        if (is_array($translation)) {
+            $translation = $this->mergeBackupTranslations($translation, $key, $locale);
+        }
+
+        return $translation;
+    }
+
+    /**
+     * Merge the fallback translations, and base translations if existing,
+     * into the provided core key => value array of translations content.
+     * @param array $translationArray
+     * @param string $key
+     * @param null $locale
+     * @return array
+     */
+    protected function mergeBackupTranslations(array $translationArray, string $key, $locale = null)
+    {
+        $fallback = $this->get($key, [], $this->fallback);
+        $baseLocale = $this->getBaseLocale($locale ?? $this->locale);
+        $baseTranslations = $baseLocale ? $this->get($key, [], $baseLocale) : [];
+
+        return array_replace_recursive($fallback, $baseTranslations, $translationArray);
+    }
+
+    /**
+     * Get the array of locales to be checked.
+     *
+     * @param  string|null  $locale
+     * @return array
+     */
+    protected function localeArray($locale)
+    {
+        $primaryLocale = $locale ?: $this->locale;
+        return array_filter([$primaryLocale, $this->getBaseLocale($primaryLocale), $this->fallback]);
+    }
+
+    /**
+     * Get the locale to extend for the given locale.
+     *
+     * @param string $locale
+     * @return string|null
+     */
+    protected function getBaseLocale($locale)
+    {
+        return $this->baseLocaleMap[$locale] ?? null;
+    }
+
+}
\ No newline at end of file
index a34c650916e133c701409cdc7b3e0d1da2ccc3ce..a9b38bff4b435e5b668e903cf7cc5085a14a4509 100644 (file)
@@ -6,6 +6,7 @@
     "type": "project",
     "require": {
         "php": ">=7.0.0",
+        "ext-json": "*",
         "ext-tidy": "*",
         "ext-dom": "*",
         "laravel/framework": "~5.5.44",
index b514263d1e1d0ce18efe3f96cd63accb3c92de16..3040a36c6033b359607ab308b908920936551541 100755 (executable)
@@ -84,7 +84,7 @@ return [
     */
 
     'locale' => env('APP_LANG', 'en'),
-    'locales' => ['en', 'ar', 'de', 'es', 'es_AR', 'fr', 'nl', 'pt_BR', 'sk', 'sv', 'kr', 'ja', 'pl', 'it', 'ru', 'zh_CN', 'zh_TW'],
+    'locales' => ['en', 'ar', 'de', 'de_informal', 'es', 'es_AR', 'fr', 'nl', 'pt_BR', 'sk', 'sv', 'kr', 'ja', 'pl', 'it', 'ru', 'zh_CN', 'zh_TW'],
 
     /*
     |--------------------------------------------------------------------------
@@ -187,7 +187,6 @@ return [
         Illuminate\Redis\RedisServiceProvider::class,
         Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
         Illuminate\Session\SessionServiceProvider::class,
-        Illuminate\Translation\TranslationServiceProvider::class,
         Illuminate\Validation\ValidationServiceProvider::class,
         Illuminate\View\ViewServiceProvider::class,
         Illuminate\Notifications\NotificationServiceProvider::class,
@@ -205,6 +204,7 @@ return [
          * Application Service Providers...
          */
         BookStack\Providers\PaginationServiceProvider::class,
+        BookStack\Providers\TranslationServiceProvider::class,
 
         BookStack\Providers\AuthServiceProvider::class,
         BookStack\Providers\AppServiceProvider::class,
index 689be99b8f2385ba19b60b2c489bf917f0601ce1..a5aff0239886ed285417f48ad0d80ebbf0b595e2 100644 (file)
@@ -110,15 +110,19 @@ return [
 
     /*
     |--------------------------------------------------------------------------
-    | Mail "Pretend"
+    | Markdown Mail Settings
     |--------------------------------------------------------------------------
     |
-    | When this option is enabled, e-mail will not actually be sent over the
-    | web and will instead be written to your application's logs files so
-    | you may inspect the message. This is great for local development.
+    | If you are using Markdown based email rendering, you may configure your
+    | theme and component paths here, allowing you to customize the design
+    | of the emails. Or, you may simply stick with the Laravel defaults!
     |
     */
-
-    'pretend' => env('MAIL_PRETEND', false),
+    'markdown' => [
+        'theme' => 'default',
+        'paths' => [
+            resource_path('views/vendor/mail'),
+        ],
+    ],
 
 ];
index 636b841338401491aa20e95d3a936f9d86d38a6a..cfeabd3be0ce32aa84a5fe4ea6a74f3c38a3915e 100644 (file)
@@ -52,7 +52,7 @@ const modeMap = {
     sh: 'shell',
     bash: 'shell',
     toml: 'toml',
-    sql: 'sql',
+    sql: 'text/x-sql',
     xml: 'xml',
     yaml: 'yaml',
     yml: 'yaml',
diff --git a/resources/lang/de_informal/activities.php b/resources/lang/de_informal/activities.php
new file mode 100644 (file)
index 0000000..c82c9e0
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+
+// Extends 'de'
+return [
+    //
+];
\ No newline at end of file
diff --git a/resources/lang/de_informal/auth.php b/resources/lang/de_informal/auth.php
new file mode 100644 (file)
index 0000000..de9ef91
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+
+// Extends 'de'
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Authentication Language Lines
+    |--------------------------------------------------------------------------
+    |
+    | The following language lines are used during authentication for various
+    | messages that we need to display to the user. You are free to modify
+    | these language lines according to your application's requirements.
+    |
+    */
+    'throttle' => 'Zu viele Anmeldeversuche. Bitte versuche es in :seconds Sekunden erneut.',
+
+    /**
+     * Login & Register
+     */
+    'ldap_email_hint' => 'Bitte gib eine E-Mail-Adresse ein, um diese mit dem Account zu nutzen.',
+    'register_confirm' => 'Bitte prüfe Deinen Posteingang und bestätig die Registrierung.',
+    'registration_email_domain_invalid' => 'Du kannst dich mit dieser E-Mail nicht registrieren.',
+    'register_success' => 'Vielen Dank für Deine Registrierung! Die Daten sind gespeichert und Du bist angemeldet.',
+
+    /**
+     * Password Reset
+     */
+    'reset_password_send_instructions' => 'Bitte gib Deine E-Mail-Adresse ein. Danach erhältst Du eine E-Mail mit einem Link zum Zurücksetzen Deines Passwortes.',
+    'reset_password_sent_success' => 'Eine E-Mail mit dem Link zum Zurücksetzen Deines Passwortes wurde an :email gesendet.',
+    'reset_password_success' => 'Dein Passwort wurde erfolgreich zurückgesetzt.',
+    'email_reset_text' => 'Du erhältsts diese E-Mail, weil jemand versucht hat, Dein Passwort zurückzusetzen.',
+    'email_reset_not_requested' => 'Wenn Du das nicht warst, brauchst Du nichts weiter zu tun.',
+
+    /**
+     * Email Confirmation
+     */
+    'email_confirm_subject' => 'Bestätige Deine E-Mail-Adresse für :appName',
+    'email_confirm_greeting' => 'Danke, dass Du dich für :appName registrierst hast!',
+    'email_confirm_text' => 'Bitte bestätige Deine E-Mail-Adresse, indem Du auf die Schaltfläche klickst:',
+    'email_confirm_send_error' => 'Leider konnte die für die Registrierung notwendige E-Mail zur Bestätigung Deine E-Mail-Adresse nicht versandt werden. Bitte kontaktiere den Systemadministrator!',
+    'email_confirm_success' => 'Deine E-Mail-Adresse wurde best&auml;tigt!',
+    'email_confirm_resent' => 'Bestätigungs-E-Mail wurde erneut versendet, bitte überprüfe Deinen Posteingang.',
+    'email_not_confirmed_text' => 'Deine E-Mail-Adresse ist bisher nicht bestätigt.',
+    'email_not_confirmed_click_link' => 'Bitte klicke auf den Link in der E-Mail, die Du nach der Registrierung erhalten hast.',
+    'email_not_confirmed_resend' => 'Wenn Du die E-Mail nicht erhalten hast, kannst Du die Nachricht erneut anfordern. Fülle hierzu bitte das folgende Formular aus:',
+];
\ No newline at end of file
diff --git a/resources/lang/de_informal/common.php b/resources/lang/de_informal/common.php
new file mode 100644 (file)
index 0000000..d80fa9d
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+
+// Extends 'de'
+return [
+    /**
+     * Email Content
+     */
+    'email_action_help' => 'Sollte es beim Anklicken der Schaltfläche ":action_text" Probleme geben, öffne die folgende URL in Deinem Browser:',
+];
\ No newline at end of file
diff --git a/resources/lang/de_informal/components.php b/resources/lang/de_informal/components.php
new file mode 100644 (file)
index 0000000..31cc9ca
--- /dev/null
@@ -0,0 +1,10 @@
+<?php
+
+// Extends 'de'
+return [
+    /**
+     * Image Manager
+     */
+    'image_delete_confirm' => 'Bitte klicke erneut auf löschen, wenn Du dieses Bild wirklich entfernen möchtest.',
+    'image_dropzone' => 'Ziehe Bilder hierher oder klicke hier, um ein Bild auszuwählen',
+];
\ No newline at end of file
diff --git a/resources/lang/de_informal/entities.php b/resources/lang/de_informal/entities.php
new file mode 100644 (file)
index 0000000..21fdbb1
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+
+// Extends 'de'
+return [
+    /**
+     * Shared
+     */
+    'no_pages_viewed' => 'Du hast bisher keine Seiten angesehen.',
+    'no_pages_recently_created' => 'Du hast bisher keine Seiten angelegt.',
+    'no_pages_recently_updated' => 'Du hast bisher keine Seiten aktualisiert.',
+
+    /**
+     * Books
+     */
+    'books_delete_confirmation' => 'Bist Du sicher, dass Du dieses Buch löschen möchtest?',
+
+    /**
+     * Chapters
+     */
+    'chapters_delete_confirm' => 'Bist Du sicher, dass Du dieses Kapitel löschen möchtest?',
+
+    /**
+     * Pages
+     */
+    'pages_delete_confirm' => 'Bist Du sicher, dass Du diese Seite löschen möchtest?',
+    'pages_delete_draft_confirm' => 'Bist Du sicher, dass Du diesen Seitenentwurf löschen möchtest?',
+    'pages_edit_enter_changelog_desc' => 'Bitte gib eine kurze Zusammenfassung Deiner Änderungen ein',
+    'pages_editing_draft_notification' => 'Du bearbeitest momenten einen Entwurf, der zuletzt :timeDiff gespeichert wurde.',
+    'pages_draft_edit_active' => [
+        'start_a' => ':count Benutzer bearbeiten derzeit diese Seite.',
+        'start_b' => ':userName bearbeitet jetzt diese Seite.',
+        'time_a' => 'seit die Seiten zuletzt aktualisiert wurden.',
+        'time_b' => 'in den letzten :minCount Minuten',
+        'message' => ':start :time. Achte darauf, keine Änderungen von anderen Benutzern zu überschreiben!',
+    ],
+
+    /**
+     * Editor sidebar
+     */
+    'tags_explain' => "Füge Schlagwörter hinzu, um ihren Inhalt zu kategorisieren.\nDu kannst einen erklärenden Inhalt hinzufügen, um eine genauere Unterteilung vorzunehmen.",
+    'attachments_explain' => 'Du kannst auf Deiner Seite Dateien hochladen oder Links hinzufügen. Diese werden in der Seitenleiste angezeigt.',
+    'attachments_delete_confirm' => 'Klicke erneut auf löschen, um diesen Anhang zu entfernen.',
+    'attachments_dropzone' => 'Ziehe Dateien hierher oder klicke hier, um eine Datei auszuwählen',
+    'attachments_explain_link' => 'Wenn Du keine Datei hochladen möchtest, kannst Du stattdessen einen Link hinzufügen. Dieser Link kann auf eine andere Seite oder eine Datei im Internet verweisen.',
+    'attachments_edit_drop_upload' => 'Ziehe Dateien hierher, um diese hochzuladen und zu überschreiben',
+
+    /**
+     * Comments
+     */
+    'comment_placeholder' => 'Gib hier Deine Kommentare ein (Markdown unterstützt)',
+    'comment_delete_confirm' => 'Möchtst Du diesen Kommentar wirklich löschen?',
+
+    /**
+     * Revision
+     */
+    'revision_delete_confirm' => 'Bist Du sicher, dass Du diese Revision löschen möchtest?',
+];
diff --git a/resources/lang/de_informal/errors.php b/resources/lang/de_informal/errors.php
new file mode 100644 (file)
index 0000000..924deee
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+// Extends 'de'
+return [
+    // Pages
+    'permission' => 'Du hast keine Berechtigung, auf diese Seite zuzugreifen.',
+    'permissionJson' => 'Du hast keine Berechtigung, die angeforderte Aktion auszuführen.',
+
+    // Auth
+    'email_already_confirmed' => 'Die E-Mail-Adresse ist bereits bestätigt. Bitte melde dich an.',
+    'email_confirmation_invalid' => 'Der Bestätigungslink ist nicht gültig oder wurde bereits verwendet. Bitte registriere dich erneut.',
+    'social_account_in_use' => 'Dieses :socialAccount-Konto wird bereits verwendet. Bitte melde dich mit dem :socialAccount-Konto an.',
+    'social_account_email_in_use' => 'Die E-Mail-Adresse ":email" ist bereits registriert. Wenn Du bereits registriert bist, kannst Du Dein :socialAccount-Konto in Deinen Profil-Einstellungen verknüpfen.',
+    'social_account_not_used' => 'Dieses :socialAccount-Konto ist bisher keinem Benutzer zugeordnet. Du kannst das in Deinen Profil-Einstellungen tun.',
+    'social_account_register_instructions' => 'Wenn Du bisher kein Social-Media Konto besitzt, kannst Du ein solches Konto mit der :socialAccount Option anlegen.',
+
+    // System
+    'path_not_writable' => 'Die Datei kann nicht in den angegebenen Pfad :filePath hochgeladen werden. Stelle sicher, dass dieser Ordner auf dem Server beschreibbar ist.',
+    'cannot_create_thumbs' => 'Der Server kann keine Vorschau-Bilder erzeugen. Bitte prüfe, ob die GD PHP-Erweiterung installiert ist.',
+    'server_upload_limit' => 'Der Server verbietet das Hochladen von Dateien mit dieser Dateigröße. Bitte versuche es mit einer kleineren Datei.',
+
+    // Pages
+    'page_draft_autosave_fail' => 'Fehler beim Speichern des Entwurfs. Stelle sicher, dass Du mit dem Internet verbunden bist, bevor Du den Entwurf dieser Seite speicherst.',
+    'page_custom_home_deletion' => 'Eine als Startseite gesetzte Seite kann nicht gelöscht werden.',
+
+    // Users
+    'users_cannot_delete_only_admin' => 'Du kannst den einzigen Administrator nicht löschen.',
+    'users_cannot_delete_guest' => 'Du kannst den Gast-Benutzer nicht löschen',
+
+    // Error pages
+    'sorry_page_not_found' => 'Entschuldigung. Die Seite, die Du angefordert hast, wurde nicht gefunden.',
+];
diff --git a/resources/lang/de_informal/pagination.php b/resources/lang/de_informal/pagination.php
new file mode 100644 (file)
index 0000000..c82c9e0
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+
+// Extends 'de'
+return [
+    //
+];
\ No newline at end of file
diff --git a/resources/lang/de_informal/passwords.php b/resources/lang/de_informal/passwords.php
new file mode 100644 (file)
index 0000000..c82c9e0
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+
+// Extends 'de'
+return [
+    //
+];
\ No newline at end of file
diff --git a/resources/lang/de_informal/settings.php b/resources/lang/de_informal/settings.php
new file mode 100644 (file)
index 0000000..c8f5a1b
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+// Extends 'de'
+return [
+    /**
+     * Settings text strings
+     * Contains all text strings used in the general settings sections of BookStack
+     * including users and roles.
+     */
+
+    /**
+     * App settings
+     */
+    'app_editor_desc' => 'Wähle den Editor aus, der von allen Benutzern genutzt werden soll, um Seiten zu editieren.',
+    'app_primary_color_desc' => "Dies sollte ein HEX Wert sein.\nWenn Du nichts eingibst, wird die Anwendung auf die Standardfarbe zurückgesetzt.",
+    'app_homepage_desc' => 'Wähle eine Seite als Startseite aus, die statt der Standardansicht angezeigt werden soll. Seitenberechtigungen werden für die ausgewählten Seiten ignoriert.',
+    'app_homepage_books' => 'Oder wähle die Buch-Übersicht als Startseite. Das wird die Seiten-Auswahl überschreiben.',
+
+    /**
+     * Maintenance settings
+     */
+    'maint_image_cleanup_desc' => 'Überprüft Seiten- und Versionsinhalte auf ungenutzte und mehrfach vorhandene Bilder. Erstelle vor dem Start ein Backup Deiner Datenbank und Bilder.',
+    'maint_image_cleanup_warning' => ':count eventuell unbenutze Bilder wurden gefunden. Möchtest Du diese Bilder löschen?',
+
+    /**
+     * Role settings
+     */
+    'role_delete_confirm' => 'Du möchtest die Rolle ":roleName" löschen.',
+    'role_delete_users_assigned' => 'Diese Rolle ist :userCount Benutzern zugeordnet. Du kannst unten eine neue Rolle auswählen, die Du diesen Benutzern zuordnen möchtest.',
+    'role_delete_sure' => 'Bist Du sicher, dass Du diese Rolle löschen möchtest?',
+
+    /**
+     * Users
+     */
+    'users_password_warning' => 'Fülle die folgenden Felder nur aus, wenn Du Dein Passwort ändern möchtest:',
+    'users_delete_confirm' => 'Bist Du sicher, dass Du diesen Benutzer löschen möchtest?',
+    'users_social_accounts_info' => 'Hier kannst Du andere Social-Media-Konten für eine schnellere und einfachere Anmeldung verknüpfen. Wenn Du ein Social-Media Konto löschst, bleibt der Zugriff erhalten. Entferne in diesem Falle die Berechtigung in Deinen Profil-Einstellungen des verknüpften Social-Media-Kontos.',
+];
diff --git a/resources/lang/de_informal/validation.php b/resources/lang/de_informal/validation.php
new file mode 100644 (file)
index 0000000..c82c9e0
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+
+// Extends 'de'
+return [
+    //
+];
\ No newline at end of file
index 153ae33f0a6124f6c3199252b72f50e85ff70e6c..4cac54b2a706efa35cb8873ebc247c20420e65b5 100644 (file)
@@ -1,12 +1,10 @@
 <?php
-
+/**
+ * Activity text strings.
+ * Is used for all the text within activity logs & notifications.
+ */
 return [
 
-    /**
-     * Activity text strings.
-     * Is used for all the text within activity logs & notifications.
-     */
-
     // Pages
     'page_create'                 => 'created page',
     'page_create_notification'    => 'Page Successfully Created',
index a1232efc6819b7a7310a635c0b5efb775020f8e9..436734816243066e10528389659b2051716d5b5b 100644 (file)
@@ -1,21 +1,15 @@
 <?php
+/**
+ * Authentication Language Lines
+ * The following language lines are used during authentication for various
+ * messages that we need to display to the user.
+ */
 return [
-    /*
-    |--------------------------------------------------------------------------
-    | Authentication Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used during authentication for various
-    | messages that we need to display to the user. You are free to modify
-    | these language lines according to your application's requirements.
-    |
-    */
+
     'failed' => 'These credentials do not match our records.',
     'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
 
-    /**
-     * Login & Register
-     */
+    // Login & Register
     'sign_up' => 'Sign up',
     'log_in' => 'Log in',
     'log_in_with' => 'Login with :socialDriver',
@@ -43,23 +37,18 @@ return [
     'register_success' => 'Thanks for signing up! You are now registered and signed in.',
 
 
-    /**
-     * Password Reset
-     */
+    // Password Reset
     'reset_password' => 'Reset Password',
     'reset_password_send_instructions' => 'Enter your email below and you will be sent an email with a password reset link.',
     'reset_password_send_button' => 'Send Reset Link',
     'reset_password_sent_success' => 'A password reset link has been sent to :email.',
     'reset_password_success' => 'Your password has been successfully reset.',
-
     'email_reset_subject' => 'Reset your :appName password',
     'email_reset_text' => 'You are receiving this email because we received a password reset request for your account.',
     'email_reset_not_requested' => 'If you did not request a password reset, no further action is required.',
 
 
-    /**
-     * Email Confirmation
-     */
+    // Email Confirmation
     'email_confirm_subject' => 'Confirm your email on :appName',
     'email_confirm_greeting' => 'Thanks for joining :appName!',
     'email_confirm_text' => 'Please confirm your email address by clicking the button below:',
index 8e86129e2d49581fb8e025e8d41b8fb41e72a9d0..ac2edc621f9256b80eb1c1729b2bf91aafd3bbba 100644 (file)
@@ -1,9 +1,10 @@
 <?php
+/**
+ * Common elements found throughout many areas of BookStack.
+ */
 return [
 
-    /**
-     * Buttons
-     */
+    // Buttons
     'cancel' => 'Cancel',
     'confirm' => 'Confirm',
     'back' => 'Back',
@@ -12,18 +13,14 @@ return [
     'select' => 'Select',
     'more' => 'More',
 
-    /**
-     * Form Labels
-     */
+    // Form Labels
     'name' => 'Name',
     'description' => 'Description',
     'role' => 'Role',
     'cover_image' => 'Cover image',
     'cover_image_description' => 'This image should be approx 440x250px.',
     
-    /**
-     * Actions
-     */
+    // Actions
     'actions' => 'Actions',
     'view' => 'View',
     'create' => 'Create',
@@ -40,9 +37,7 @@ return [
     'remove' => 'Remove',
     'add' => 'Add',
 
-    /**
-     * Misc
-     */
+    // Misc
     'deleted_user' => 'Deleted User',
     'no_activity' => 'No activity to show',
     'no_items' => 'No items available',
@@ -54,15 +49,11 @@ return [
     'list_view' => 'List View',
     'default' => 'Default',
 
-    /**
-     * Header
-     */
+    // Header
     'view_profile' => 'View Profile',
     'edit_profile' => 'Edit Profile',
 
-    /**
-     * Email Content
-     */
+    // Email Content
     'email_action_help' => 'If you’re having trouble clicking the ":actionText" button, copy and paste the URL below into your web browser:',
     'email_rights' => 'All rights reserved',
 ];
\ No newline at end of file
index c093f731605150d3aa56c162db6e3a649fe85a2c..d8e8981fb5fcf6ba8d15993453d4f8f2d07df970 100644 (file)
@@ -1,9 +1,10 @@
 <?php
+/**
+ * Text used in custom JavaScript driven components.
+ */
 return [
 
-    /**
-     * Image Manager
-     */
+    // Image Manager
     'image_select' => 'Image Select',
     'image_all' => 'All',
     'image_all_title' => 'View all images',
@@ -24,9 +25,7 @@ return [
     'image_delete_success' => 'Image successfully deleted',
     'image_upload_remove' => 'Remove',
 
-    /**
-     * Code editor
-     */
+    // Code Editor
     'code_editor' => 'Edit Code',
     'code_language' => 'Code Language',
     'code_content' => 'Code Content',
index 810b25f456d1b7a6d9962b336298abaef757f265..2a64f57a3700c113814faa01ba8e6d6cbf9b95dd 100644 (file)
@@ -1,9 +1,11 @@
 <?php
+/**
+ * Text used for 'Entities' (Document Structure Elements) such as
+ * Books, Shelves, Chapters & Pages
+ */
 return [
 
-    /**
-     * Shared
-     */
+    // Shared
     'recently_created' => 'Recently Created',
     'recently_created_pages' => 'Recently Created Pages',
     'recently_updated_pages' => 'Recently Updated Pages',
@@ -31,17 +33,13 @@ return [
     'export_pdf' => 'PDF File',
     'export_text' => 'Plain Text File',
 
-    /**
-     * Permissions and restrictions
-     */
+    // Permissions and restrictions
     'permissions' => 'Permissions',
     'permissions_intro' => 'Once enabled, These permissions will take priority over any set role permissions.',
     'permissions_enable' => 'Enable Custom Permissions',
     'permissions_save' => 'Save Permissions',
 
-    /**
-     * Search
-     */
+    // Search
     'search_results' => 'Search Results',
     'search_total_results_found' => ':count result found|:count total results found',
     'search_clear' => 'Clear Search',
@@ -66,9 +64,7 @@ return [
     'search_set_date' => 'Set Date',
     'search_update' => 'Update Search',
 
-    /**
-     * Shelves
-     */
+    // Shelves
     'shelf' => 'Shelf',
     'shelves' => 'Shelves',
     'shelves_long' => 'Bookshelves',
@@ -98,9 +94,7 @@ return [
     'shelves_copy_permissions_explain' => 'This will apply the current permission settings of this bookshelf to all books contained within. Before activating, ensure any changes to the permissions of this bookshelf have been saved.',
     'shelves_copy_permission_success' => 'Bookshelf permissions copied to :count books',
 
-    /**
-     * Books
-     */
+    // Books
     'book' => 'Book',
     'books' => 'Books',
     'x_books' => ':count Book|:count Books',
@@ -134,9 +128,7 @@ return [
     'books_sort_show_other' => 'Show Other Books',
     'books_sort_save' => 'Save New Order',
 
-    /**
-     * Chapters
-     */
+    // Chapters
     'chapter' => 'Chapter',
     'chapters' => 'Chapters',
     'x_chapters' => ':count Chapter|:count Chapters',
@@ -159,9 +151,7 @@ return [
     'chapters_permissions_success' => 'Chapter Permissions Updated',
     'chapters_search_this' => 'Search this chapter',
 
-    /**
-     * Pages
-     */
+    // Pages
     'page' => 'Page',
     'pages' => 'Pages',
     'x_pages' => ':count Page|:count Pages',
@@ -235,9 +225,7 @@ return [
     'pages_draft_discarded' => 'Draft discarded, The editor has been updated with the current page content',
     'pages_specific' => 'Specific Page',
 
-    /**
-     * Editor sidebar
-     */
+    // Editor Sidebar
     'page_tags' => 'Page Tags',
     'chapter_tags' => 'Chapter Tags',
     'book_tags' => 'Book Tags',
@@ -273,18 +261,14 @@ return [
     'attachments_file_updated' => 'File successfully updated',
     'attachments_link_attached' => 'Link successfully attached to page',
 
-    /**
-     * Profile View
-     */
+    // Profile View
     'profile_user_for_x' => 'User for :time',
     'profile_created_content' => 'Created Content',
     'profile_not_created_pages' => ':userName has not created any pages',
     'profile_not_created_chapters' => ':userName has not created any chapters',
     'profile_not_created_books' => ':userName has not created any books',
 
-    /**
-     * Comments
-     */
+    // Comments
     'comment' => 'Comment',
     'comments' => 'Comments',
     'comment_add' => 'Add Comment',
@@ -302,9 +286,7 @@ return [
     'comment_delete_confirm' => 'Are you sure you want to delete this comment?',
     'comment_in_reply_to' => 'In reply to :commentId',
 
-    /**
-     * Revision
-     */
+    // Revision
     'revision_delete_confirm' => 'Are you sure you want to delete this revision?',
     'revision_delete_success' => 'Revision deleted',
     'revision_cannot_delete_latest' => 'Cannot delete the latest revision.'
index fb09841cfd5a61290bb218d81474341c17cf191b..eb73b880bf1fb36a88f2e24533fb271060f4b7ea 100644 (file)
@@ -1,11 +1,9 @@
 <?php
-
+/**
+ * Text shown in error messaging.
+ */
 return [
 
-    /**
-     * Error text strings.
-     */
-
     // Permissions
     'permission' => 'You do not have permission to access the requested page.',
     'permissionJson' => 'You do not have permission to perform the requested action.',
@@ -80,4 +78,5 @@ return [
     'error_occurred' => 'An Error Occurred',
     'app_down' => ':appName is down right now',
     'back_soon' => 'It will be back up soon.',
+
 ];
index fcab34b253172b61762a9cb6a343299977cb564e..85bd12fc319557dcc852fdacc07e882583d66be3 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Pagination Language Lines
+ * The following language lines are used by the paginator library to build
+ * the simple pagination links.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Pagination Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used by the paginator library to build
-    | the simple pagination links. You are free to change them to anything
-    | you want to customize your views to better match your application.
-    |
-    */
-
     'previous' => '&laquo; Previous',
     'next'     => 'Next &raquo;',
 
index 7c10cba1a03011b71cb44cedea3b75223e538935..9f7d9e3cbcaccaaa5631115f4d387785b196c18c 100644 (file)
@@ -1,18 +1,11 @@
 <?php
-
+/**
+ * Password Reminder Language Lines
+ * The following language lines are the default lines which match reasons
+ * that are given by the password broker for a password update attempt has failed.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Password Reminder Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are the default lines which match reasons
-    | that are given by the password broker for a password update attempt
-    | has failed, such as for an invalid token or invalid new password.
-    |
-    */
-
     'password' => 'Passwords must be at least six characters and match the confirmation.',
     'user' => "We can't find a user with that e-mail address.",
     'token' => 'This password reset token is invalid.',
index c32746566403acdf7a13eff19ce2f5bb70594e6f..736d91849c56b3ce780e4547826a6d19794e313d 100755 (executable)
@@ -1,21 +1,17 @@
 <?php
-
+/**
+ * Settings text strings
+ * Contains all text strings used in the general settings sections of BookStack
+ * including users and roles.
+ */
 return [
 
-    /**
-     * Settings text strings
-     * Contains all text strings used in the general settings sections of BookStack
-     * including users and roles.
-     */
-
+    // Common Messages
     'settings' => 'Settings',
     'settings_save' => 'Save Settings',
     'settings_save_success' => 'Settings saved',
 
-    /**
-     * App settings
-     */
-
+    // App Settings
     'app_settings' => 'App Settings',
     'app_name' => 'Application name',
     'app_name_desc' => 'This name is shown in the header and any emails.',
@@ -37,10 +33,7 @@ return [
     'app_disable_comments' => 'Disable comments',
     'app_disable_comments_desc' => 'Disable comments across all pages in the application. Existing comments are not shown.',
 
-    /**
-     * Registration settings
-     */
-
+    // Registration Settings
     'reg_settings' => 'Registration Settings',
     'reg_allow' => 'Allow registration?',
     'reg_default_role' => 'Default user role after registration',
@@ -50,10 +43,7 @@ return [
     'reg_confirm_restrict_domain_desc' => 'Enter a comma separated list of email domains you would like to restrict registration to. Users will be sent an email to confirm their address before being allowed to interact with the application. <br> Note that users will be able to change their email addresses after successful registration.',
     'reg_confirm_restrict_domain_placeholder' => 'No restriction set',
 
-    /**
-     * Maintenance settings
-     */
-
+    // Maintenance settings
     'maint' => 'Maintenance',
     'maint_image_cleanup' => 'Cleanup Images',
     'maint_image_cleanup_desc' => "Scans page & revision content to check which images and drawings are currently in use and which images are redundant. Ensure you create a full database and image backup before running this.",
@@ -63,10 +53,7 @@ return [
     'maint_image_cleanup_success' => ':count potentially unused images found and deleted!',
     'maint_image_cleanup_nothing_found' => 'No unused images found, Nothing deleted!',
 
-    /**
-     * Role settings
-     */
-
+    // Role Settings
     'roles' => 'Roles',
     'role_user_roles' => 'User Roles',
     'role_create' => 'Create New Role',
@@ -99,10 +86,7 @@ return [
     'role_users' => 'Users in this role',
     'role_users_none' => 'No users are currently assigned to this role',
 
-    /**
-     * Users
-     */
-
+    // Users
     'users' => 'Users',
     'user_profile' => 'User Profile',
     'users_add_new' => 'Add New User',
@@ -129,14 +113,15 @@ return [
     'users_social_connected' => ':socialAccount account was successfully attached to your profile.',
     'users_social_disconnected' => ':socialAccount account was successfully disconnected from your profile.',
 
-    // Since these labels are already localized this array does not need to be
-    // translated in the language-specific files.
-    // DELETE BELOW IF COPIED FROM EN
-    ///////////////////////////////////
+    //! Since these labels are already localized this array does not need to be
+    //! translated in the language-specific files.
+    //! DELETE BELOW IF COPIED FROM EN
+    //!////////////////////////////////
     'language_select' => [
         'en' => 'English',
         'ar' => 'العربية',
-        'de' => 'Deutsch',
+        'de' => 'Deutsch (Sie)',
+        'de_informal' => 'Deutsch (Du)',
         'es' => 'Español',
         'es_AR' => 'Español Argentina',
         'fr' => 'Français',
@@ -152,5 +137,5 @@ return [
         'zh_CN' => '简体中文',
         'zh_TW' => '繁體中文'
     ]
-    ///////////////////////////////////
+    //!////////////////////////////////
 ];
index b75af7485ffb7baf86a2faaf46f6ab4107956a3d..0b6a1c170a5fb31d7d470eedf61bb7a89bd96dbb 100644 (file)
@@ -1,18 +1,13 @@
 <?php
-
+/**
+ * Validation Lines
+ * The following language lines contain the default error messages used by
+ * the validator class. Some of these rules have multiple versions such
+ * as the size rules. Feel free to tweak each of these messages here.
+ */
 return [
 
-    /*
-    |--------------------------------------------------------------------------
-    | Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines contain the default error messages used by
-    | the validator class. Some of these rules have multiple versions such
-    | as the size rules. Feel free to tweak each of these messages here.
-    |
-    */
-
+    // Standard laravel validation lines
     'accepted'             => 'The :attribute must be accepted.',
     'active_url'           => 'The :attribute is not a valid URL.',
     'after'                => 'The :attribute must be a date after :date.',
@@ -75,34 +70,13 @@ return [
     'unique'               => 'The :attribute has already been taken.',
     'url'                  => 'The :attribute format is invalid.',
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Language Lines
-    |--------------------------------------------------------------------------
-    |
-    | Here you may specify custom validation messages for attributes using the
-    | convention "attribute.rule" to name the lines. This makes it quick to
-    | specify a specific custom language line for a given attribute rule.
-    |
-    */
-
+    // Custom validation lines
     'custom' => [
         'password-confirm' => [
             'required_with' => 'Password confirmation required',
         ],
     ],
 
-    /*
-    |--------------------------------------------------------------------------
-    | Custom Validation Attributes
-    |--------------------------------------------------------------------------
-    |
-    | The following language lines are used to swap attribute place-holders
-    | with something more reader friendly such as E-Mail Address instead
-    | of "email". This simply helps us make messages a little cleaner.
-    |
-    */
-
+    // Custom validation attributes
     'attributes' => [],
-
 ];
diff --git a/resources/lang/format.php b/resources/lang/format.php
new file mode 100755 (executable)
index 0000000..45d0b48
--- /dev/null
@@ -0,0 +1,330 @@
+#!/usr/bin/env php
+<?php
+
+/**
+ * Format a language file in the same way as the EN equivalent.
+ * Matches the line numbers of translated content.
+ * Potentially destructive, Ensure you have a backup of your translation content before running.
+ */
+
+$args = array_slice($argv, 1);
+
+if (count($args) < 2) {
+    errorOut("Please provide a language code as the first argument and a translation file name, or '--all', as the second (./format.php fr activities)");
+}
+
+$lang = formatLocale($args[0]);
+$fileName = explode('.', $args[1])[0];
+$fileNames = [$fileName];
+if ($fileName === '--all') {
+    $fileNames = getTranslationFileNames();
+}
+
+foreach ($fileNames as $fileName) {
+    $formatted = formatFileContents($lang, $fileName);
+    writeLangFile($lang, $fileName, $formatted);
+}
+
+
+/**
+ * Format the contents of a single translation file in the given language.
+ * @param string $lang
+ * @param string $fileName
+ * @return string
+ */
+function formatFileContents(string $lang, string $fileName) : string {
+    $enLines = loadLangFileLines('en', $fileName);
+    $langContent = loadLang($lang, $fileName);
+    $enContent = loadLang('en', $fileName);
+
+    // Calculate the longest top-level key length
+    $longestKeyLength = calculateKeyPadding($enContent);
+
+    // Start formatted content
+    $formatted = [];
+    $mode = 'header';
+    $skipArray = false;
+    $arrayKeys = [];
+
+    foreach ($enLines as $index => $line) {
+        $trimLine = trim($line);
+        if ($mode === 'header') {
+            $formatted[$index] = $line;
+            if (str_replace(' ', '', $trimLine) === 'return[') $mode = 'body';
+        }
+
+        if ($mode === 'body') {
+            $matches = [];
+            $arrayEndMatch = preg_match('/]\s*,\s*$/', $trimLine);
+
+            if ($skipArray) {
+                if ($arrayEndMatch) $skipArray = false;
+                continue;
+            }
+
+            // Comment to ignore
+            if (strpos($trimLine, '//!') === 0) {
+                $formatted[$index] = "";
+                continue;
+            }
+
+            // Comment
+            if (strpos($trimLine, '//') === 0) {
+                $formatted[$index] = "\t" . $trimLine;
+                continue;
+            }
+
+            // Arrays
+            $arrayStartMatch = preg_match('/^\'(.*)\'\s+?=>\s+?\[(\],)?\s*?$/', $trimLine, $matches);
+
+            $indent = count($arrayKeys) + 1;
+            if ($arrayStartMatch === 1) {
+                if ($fileName === 'settings' && $matches[1] === 'language_select') {
+                    $skipArray = true;
+                    continue;
+                }
+                $arrayKeys[] = $matches[1];
+                $formatted[$index] = str_repeat(" ", $indent * 4) . str_pad("'{$matches[1]}'", $longestKeyLength) . "=> [";
+                if ($arrayEndMatch !== 1) continue;
+            }
+            if ($arrayEndMatch === 1) {
+                unsetArrayByKeys($langContent, $arrayKeys);
+                array_pop($arrayKeys);
+                if (isset($formatted[$index])) {
+                    $formatted[$index] .= '],';
+                } else {
+                    $formatted[$index] = str_repeat(" ", ($indent-1) * 4) . "],";
+                }
+                continue;
+            }
+
+            // Translation
+            $translationMatch = preg_match('/^\'(.*)\'\s+?=>\s+?[\'"](.*)?[\'"].+?$/', $trimLine, $matches);
+            if ($translationMatch === 1) {
+                $key = $matches[1];
+                $keys = array_merge($arrayKeys, [$key]);
+                $langVal = getTranslationByKeys($langContent, $keys);
+                if (empty($langVal)) continue;
+
+                $keyPad = $longestKeyLength;
+                if (count($arrayKeys) === 0) {
+                    unset($langContent[$key]);
+                } else {
+                    $keyPad = calculateKeyPadding(getTranslationByKeys($enContent, $arrayKeys));
+                }
+
+                $formatted[$index] = formatTranslationLine($key, $langVal, $indent, $keyPad);
+                continue;
+            }
+        }
+
+    }
+
+    // Fill missing lines
+    $arraySize = max(array_keys($formatted));
+    $formatted = array_replace(array_fill(0, $arraySize, ''), $formatted);
+
+    // Add remaining translations
+    $langContent = array_filter($langContent, function($item) {
+        return !is_null($item) && !empty($item);
+    });
+    if (count($langContent) > 0) {
+        $formatted[] = '';
+        $formatted[] = "\t// Unmatched";
+    }
+    foreach ($langContent as $key => $value) {
+        if (is_array($value)) {
+            $formatted[] = formatTranslationArray($key, $value);
+        } else {
+            $formatted[] = formatTranslationLine($key, $value);
+        }
+    }
+
+    // Add end line
+    $formatted[] = '];';
+    return implode("\n", $formatted);
+}
+
+/**
+ * Format a translation line.
+ * @param string $key
+ * @param string $value
+ * @param int $indent
+ * @param int $keyPad
+ * @return string
+ */
+function formatTranslationLine(string $key, string $value, int $indent = 1, int $keyPad = 1) : string {
+    $start = str_repeat(" ", $indent * 4) . str_pad("'{$key}'", $keyPad, ' ');
+    if (strpos($value, "\n") !== false) {
+        $escapedValue = '"' .  str_replace("\n", '\n', $value)  . '"';
+        $escapedValue = '"' .  str_replace('"', '\"', $escapedValue)  . '"';
+    } else {
+        $escapedValue = "'" . str_replace("'", "\\'", $value) . "'";
+    }
+    return "{$start} => {$escapedValue},";
+}
+
+/**
+ * Find the longest key in the array and provide the length
+ * for all keys to be used when printed.
+ * @param array $array
+ * @return int
+ */
+function calculateKeyPadding(array $array) : int {
+    $top = 0;
+    foreach ($array as $key => $value) {
+        $keyLen = strlen($key);
+        $top = max($top, $keyLen);
+    }
+    return min(35, $top + 2);
+}
+
+/**
+ * Format an translation array with the given key.
+ * Simply prints as an old-school php array.
+ * Used as a last-resort backup to save unused translations.
+ * @param string $key
+ * @param array $array
+ * @return string
+ */
+function formatTranslationArray(string $key, array $array) : string {
+    $arrayPHP = var_export($array, true);
+    return "    '{$key}' => {$arrayPHP},";
+}
+
+/**
+ * Find a string translation value within a multi-dimensional array
+ * by traversing the given array of keys.
+ * @param array $translations
+ * @param array $keys
+ * @return string|array
+ */
+function getTranslationByKeys(array $translations, array $keys)  {
+    $val = $translations;
+    foreach ($keys as $key) {
+        $val = $val[$key] ?? '';
+        if ($val === '') return '';
+    }
+    return $val;
+}
+
+/**
+ * Unset an inner item of a multi-dimensional array by
+ * traversing the given array of keys.
+ * @param array $input
+ * @param array $keys
+ */
+function unsetArrayByKeys(array &$input, array $keys) {
+    $val = &$input;
+    $lastIndex = count($keys) - 1;
+    foreach ($keys as $index => &$key) {
+        if ($index === $lastIndex && is_array($val)) {
+            unset($val[$key]);
+        }
+        if (!is_array($val)) return;
+        $val = &$val[$key] ?? [];
+    }
+}
+
+/**
+ * Write the given content to a translation file.
+ * @param string $lang
+ * @param string $fileName
+ * @param string $content
+ */
+function writeLangFile(string $lang, string $fileName, string $content) {
+    $path = __DIR__ . "/{$lang}/{$fileName}.php";
+    if (!file_exists($path)) {
+        errorOut("Expected translation file '{$path}' does not exist");
+    }
+    file_put_contents($path, $content);
+}
+
+/**
+ * Load the contents of a language file as an array of text lines.
+ * @param string $lang
+ * @param string $fileName
+ * @return array
+ */
+function loadLangFileLines(string $lang, string $fileName) : array {
+    $path = __DIR__ . "/{$lang}/{$fileName}.php";
+    if (!file_exists($path)) {
+        errorOut("Expected translation file '{$path}' does not exist");
+    }
+    $lines = explode("\n", file_get_contents($path));
+    return array_map(function($line) {
+        return trim($line, "\r");
+    }, $lines);
+}
+
+/**
+ * Load the contents of a language file
+ * @param string $lang
+ * @param string $fileName
+ * @return array
+ */
+function loadLang(string $lang, string $fileName) : array {
+    $path = __DIR__ . "/{$lang}/{$fileName}.php";
+    if (!file_exists($path)) {
+        errorOut("Expected translation file '{$path}' does not exist");
+    }
+
+    $fileData = include($path);
+    return $fileData;
+}
+
+/**
+ * Fetch an array containing the names of all translation files without the extension.
+ * @return array
+ */
+function getTranslationFileNames() : array {
+    $dir = __DIR__ . "/en";
+    if (!file_exists($dir)) {
+        errorOut("Expected directory '{$dir}' does not exist");
+    }
+    $files = scandir($dir);
+    $fileNames = [];
+    foreach ($files as $file) {
+        if (substr($file, -4) === '.php') {
+            $fileNames[] = substr($file, 0, strlen($file) - 4);
+        }
+    }
+    return $fileNames;
+}
+
+/**
+ * Format a locale to follow the lowercase_UPERCASE standard
+ * @param string $lang
+ * @return string
+ */
+function formatLocale(string $lang) : string {
+    $langParts = explode('_', strtoupper($lang));
+    $langParts[0] = strtolower($langParts[0]);
+    return implode('_', $langParts);
+}
+
+/**
+ * Dump a variable then die.
+ * @param $content
+ */
+function dd($content) {
+    print_r($content);
+    exit(1);
+}
+
+/**
+ * Log out some information text in blue
+ * @param $text
+ */
+function info($text) {
+    echo "\e[34m" . $text . "\e[0m\n";
+}
+
+/**
+ * Log out an error in red and exit.
+ * @param $text
+ */
+function errorOut($text) {
+    echo "\e[31m" . $text . "\e[0m\n";
+    exit(1);
+}
\ No newline at end of file
index 7ca1dc8d0312206acc205135560c6dff02443abd..d8e2a0acdba5ddf89b24b5cd591e8b71cbdea065 100644 (file)
@@ -2,8 +2,6 @@
 
 if (! empty($greeting)) {
     echo $greeting, "\n\n";
-} else {
-    echo $level == 'error' ? 'Whoops!' : 'Hello!', "\n\n";
 }
 
 if (! empty($introLines)) {
index 25d8a59064301d618bc2216495383e880bc3bb2a..16ba113587e772d61a545a0cadcaafd6286ff4ca 100644 (file)
@@ -31,6 +31,20 @@ class LdapTest extends BrowserKitTest
         $this->mockUser = factory(User::class)->make();
     }
 
+    protected function mockEscapes($times = 1)
+    {
+        $this->mockLdap->shouldReceive('escape')->times($times)->andReturnUsing(function($val) {
+            return ldap_escape($val);
+        });
+    }
+
+    protected function mockExplodes($times = 1)
+    {
+        $this->mockLdap->shouldReceive('explodeDn')->times($times)->andReturnUsing(function($dn, $withAttrib) {
+            return ldap_explode_dn($dn, $withAttrib);
+        });
+    }
+
     public function test_login()
     {
         $this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
@@ -44,6 +58,7 @@ class LdapTest extends BrowserKitTest
                 'dn' => ['dc=test' . config('services.ldap.base_dn')]
             ]]);
         $this->mockLdap->shouldReceive('bind')->times(6)->andReturn(true);
+        $this->mockEscapes(4);
 
         $this->visit('/login')
             ->see('Username')
@@ -73,6 +88,7 @@ class LdapTest extends BrowserKitTest
                 'mail' => [$this->mockUser->email]
             ]]);
         $this->mockLdap->shouldReceive('bind')->times(3)->andReturn(true);
+        $this->mockEscapes(2);
 
         $this->visit('/login')
             ->see('Username')
@@ -97,6 +113,7 @@ class LdapTest extends BrowserKitTest
                 'dn' => ['dc=test' . config('services.ldap.base_dn')]
             ]]);
         $this->mockLdap->shouldReceive('bind')->times(3)->andReturn(true, true, false);
+        $this->mockEscapes(2);
 
         $this->visit('/login')
             ->see('Username')
@@ -146,7 +163,7 @@ class LdapTest extends BrowserKitTest
             ->dontSee('External Authentication');
     }
 
-    public function test_login_maps_roles_and_retains_existsing_roles()
+    public function test_login_maps_roles_and_retains_existing_roles()
     {
         $roleToReceive = factory(Role::class)->create(['name' => 'ldaptester', 'display_name' => 'LdapTester']);
         $roleToReceive2 = factory(Role::class)->create(['name' => 'ldaptester-second', 'display_name' => 'LdapTester Second']);
@@ -176,6 +193,8 @@ class LdapTest extends BrowserKitTest
                 ]
             ]]);
         $this->mockLdap->shouldReceive('bind')->times(6)->andReturn(true);
+        $this->mockEscapes(5);
+        $this->mockExplodes(6);
 
         $this->visit('/login')
             ->see('Username')
@@ -227,6 +246,8 @@ class LdapTest extends BrowserKitTest
                 ]
             ]]);
         $this->mockLdap->shouldReceive('bind')->times(5)->andReturn(true);
+        $this->mockEscapes(4);
+        $this->mockExplodes(2);
 
         $this->visit('/login')
             ->see('Username')
@@ -279,6 +300,8 @@ class LdapTest extends BrowserKitTest
                 ]
             ]]);
         $this->mockLdap->shouldReceive('bind')->times(5)->andReturn(true);
+        $this->mockEscapes(4);
+        $this->mockExplodes(2);
 
         $this->visit('/login')
             ->see('Username')
@@ -328,6 +351,8 @@ class LdapTest extends BrowserKitTest
                 ]
             ]]);
         $this->mockLdap->shouldReceive('bind')->times(6)->andReturn(true);
+        $this->mockEscapes(5);
+        $this->mockExplodes(6);
 
         $this->visit('/login')
             ->see('Username')
index 2b3b00ac098360d34a2691323b47487ff6d9e824..91abadc91904a97cf8bd14c425ab01f854f9350c 100644 (file)
@@ -11,7 +11,7 @@ class LanguageTest extends TestCase
     public function setUp()
     {
         parent::setUp();
-        $this->langs = array_diff(scandir(resource_path('lang')), ['..', '.', 'check.php']);
+        $this->langs = array_diff(scandir(resource_path('lang')), ['..', '.', 'check.php', 'format.php']);
     }
 
     public function test_locales_config_key_set_properly()
@@ -81,4 +81,30 @@ class LanguageTest extends TestCase
         $this->assertTrue(config('app.rtl'), "App RTL config should have been set to true by middleware");
     }
 
+    public function test_de_informal_falls_base_to_de()
+    {
+        // Base de back value
+        $deBack = trans()->get('common.cancel', [], 'de', false);
+        $this->assertEquals('Abbrechen', $deBack);
+        // Ensure de_informal has no value set
+        $this->assertEquals('common.cancel', trans()->get('common.cancel', [], 'de_informal', false));
+        // Ensure standard trans falls back to de
+        $this->assertEquals($deBack, trans('common.cancel', [], 'de_informal'));
+        // Ensure de_informal gets its own values where set
+        $deEmailActionHelp = trans()->get('common.email_action_help', [], 'de', false);
+        $enEmailActionHelp = trans()->get('common.email_action_help', [], 'en', false);
+        $deInformalEmailActionHelp = trans()->get('common.email_action_help', [], 'de_informal', false);
+        $this->assertNotEquals($deEmailActionHelp, $deInformalEmailActionHelp);
+        $this->assertNotEquals($enEmailActionHelp, $deInformalEmailActionHelp);
+    }
+
+    public function test_de_informal_falls_base_to_de_in_js_endpoint()
+    {
+        $this->asEditor();
+        setting()->putUser($this->getEditor(), 'language', 'de_informal');
+
+        $transResp = $this->get('/translations');
+        $transResp->assertSee('"cancel":"Abbrechen"');
+    }
+
 }
\ No newline at end of file