Commit c65f788c authored by Dominik Hebeler's avatar Dominik Hebeler
Browse files

Merge branch '408-arbeit-am-ubersetzungstool' into 'development'

Resolve "Arbeit am Übersetzungstool"

Closes #408

See merge request !904
parents 01b4b9bc 9d44da27
......@@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\LanguageObject;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
......@@ -87,11 +88,11 @@ class LanguageController extends Controller
$langTexts[$dir] += count($this->getValues([$key => $value]));
}
$filePath[basename($filename)] = preg_replace("/lang\/.*?\//si", "lang/$to/", substr($filename, strpos($filename, "lang")));
}
}
}
$langs = [];
$fn = "";
$t = [];
......@@ -99,10 +100,11 @@ class LanguageController extends Controller
if ($exclude !== "") {
try {
$ex = unserialize(base64_decode($exclude));
} catch (\ErrorException $e) {
} catch (ErrorException $e) {
$ex = ['files' => [], 'new' => 0];
}
}
foreach ($texts as $filename => $text) {
$has = false;
foreach ($ex['files'] as $file) {
......@@ -118,7 +120,6 @@ class LanguageController extends Controller
}
# Hier können wir später die bereits bearbeiteten Dateien ausschließen.
foreach ($text as $textname => $languages) {
if ($languages === "") {
continue;
}
......@@ -136,25 +137,111 @@ class LanguageController extends Controller
if (!isset($languages[$to])) {
$fn = $filePath[$filename];
$t = $text;
break;
break 2;
}
}
}
$t = $this->htmlEscape($t, $to);
$t = $this->createHints($t, $to);
return view('languages.edit')
->with('texts', $t)
->with('filename', $fn)
->with('title', trans('titles.languages.edit'))
->with('langs', $langs)
->with('to', $to)
->with('langTexts', $langTexts)
->with('sum', $sum)
->with('new', $ex["new"])
->with('email', $email);
->with('texts', $t) //Array mit vorhandenen Übersetzungen der Datei $fn in beiden Sprachen
->with('filename', $fn) //Pfad zur angezeigten Datei
->with('title', trans('titles.languages.edit'))
->with('langs', $langs) //Ausgangssprache (1 Element)
->with('to', $to) //zu bearbeitende Sprache
->with('langTexts', $langTexts) //Anzahl der vorhandenen Übersetzungen
->with('sum', $sum) //Alle vorhandenen Texte (in allen Dateien) in beiden Sprachen in einem Array
->with('new', $ex["new"]) //
->with('email', $email); //Email-Adresse des Benutzers
}
public function createSynopticEditPage(Request $request, $exclude = "")
{
$languageFilePath = resource_path() . "/lang/";
$languageFolders = scandir($languageFilePath);
#Enthält zu jeder Sprache ein Objekt mit allen Daten
$languageObjects = [];
$to = [];
#Dekodieren ausgeschlossener Dateien anhand des URL-Parameters
$ex = ['files' => [], 'new' => 0];
if ($exclude !== "") {
try {
$ex = unserialize(base64_decode($exclude));
} catch (\ErrorException $e) {
$ex = ['files' => [], 'new' => 0];
}
}
#Instanziiere LanguageObject
foreach ($languageFolders as $folder) {
if (is_dir($languageFilePath . $folder) && $folder !== "." && $folder !== "..") {
$languageObjects[$folder] = new LanguageObject($folder, $languageFilePath.$folder);
}
}
#Speichere Daten in LanguageObject, überspringe ausgeschlossene Dateien
foreach ($languageObjects as $folder => $languageObject) {
$to[] = $folder;
$di = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($languageObject->filePath));
foreach($di as $filename => $file) {
foreach($ex['files'] as $file) {
if($file === basename($filename)) {
continue 2;
}
}
if(!$this->endsWith($filename, ".")) {
$tmp = include $filename;
foreach ($tmp as $key => $value) {
$languageObject->saveData(basename($filename), $key, $value);
}
}
}
}
$fn = "";
#Wähle die erste, unbearbeitete Datei aus
foreach($languageObjects as $folder => $languageObject) {
foreach($languageObject->stringMap as $languageFileName => $languageFile) {
$fn = $languageFileName;
break 2;
}
}
if($fn === "") {
//Alles bearbeitet -> zeige entsprechende Nachricht
}
$snippets = [];
#Speichere den Inhalt der ausgewählten Datei in allen Sprachen in $snippets ab
foreach($languageObjects as $folder => $languageObject) {
foreach($languageObject->stringMap as $languageFileName => $languageFile) {
if($languageFileName === $fn) {
foreach($languageFile as $key => $value) {
$snippets[$key][$languageObject->language] = $value;
}
continue 2;
}
}
}
#Fülle $snippets auf mit leeren Einträgen für übrige Sprachen
foreach($to as $t) {
foreach($snippets as $key => $langArray) {
if(!isset($langArray[$t])) {
$snippets[$key][$t] = "";
}
}
}
return view('languages.synoptic')
->with('to', $to) #Alle vorhandenen Sprachen
->with('texts', $snippets) #Array mit Sprachsnippets
->with('filename', $fn) #Name der Datei
->with('title', trans('titles.languages.edit'));
}
private function htmlEscape($t, $to)
......
......@@ -9,6 +9,7 @@ use Illuminate\Http\Request;
use Illuminate\Http\Response;
use LaravelLocalization;
use Mail;
use ZipArchive;
class MailController extends Controller
{
......@@ -117,14 +118,54 @@ class MailController extends Controller
}
#Ueberprueft ob ein bereits vorhandener Eintrag bearbeitet worden ist
private function isEdited($k, $v, $filename)
{
try {
$temp = include resource_path()."/".$filename;
foreach ($temp as $key => $value) {
if($k === $key && $v !== $value) {
return true;
}
}
} catch (\ErrorException $e) {
#Datei existiert noch nicht
return true;
}
return false;
}
private function extractLanguage($key)
{
#Kürzt bspw. "_new_de_redirect bzw. "de_redirect" zu "de"
preg_match("/^(?:_new_)?([^_]*)/", $key, $matches);
foreach($matches as $dir) {
if(strlen($dir) == 2)
return $dir;
}
}
private function processKey($key)
{
$key = trim($key);
#Kürzt bspw. "_new_de_redirect bzw. "de_redirect" zu "redirect"
preg_match("/^(?:_new_)?(?:[^_]*)_(\w*.?\w*#?.?\w*)/", $key, $matches);
foreach($matches as $processedKey) {
if(strpos($processedKey, "_") === FALSE) {
return $processedKey;
}
}
return $key;
}
public function sendLanguageFile(Request $request, $from, $to, $exclude = "", $email ="")
{
$filename = $request->input('filename');
# Wir erstellen nun zunächst den Inhalt der Datei:
$data = [];
$new = 0;
$emailAddress = "";
$editedKeys = "";
foreach ($request->all() as $key => $value) {
if ($key === "filename" || $value === "") {
......@@ -138,7 +179,13 @@ class MailController extends Controller
if (strpos($key, "_new_") === 0 && $value !== "") {
$new++;
$key = substr($key, strpos($key, "_new_") + 5);
$editedKeys = $editedKeys."\n".$key;
} else if ($this->isEdited($key, $value, $filename)) {
$new++;
$editedKeys = $editedKeys."\n".$key;
}
$key = trim($key);
if (!strpos($key, "#")) {
$data[$key] = $value;
......@@ -151,7 +198,6 @@ class MailController extends Controller
$ref = &$ref[$key];
$ref = $value;
}
}
$output = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
......@@ -161,10 +207,8 @@ class MailController extends Controller
$output = "<?php\n\nreturn $output;\n";
$message = "Moin moin,\n\nein Benutzer hat eine Sprachdatei aktualisiert.\nSollten die Texte so in Ordnung sein, ersetzt, oder erstellt die Datei aus dem Anhang in folgendem Pfad:\n$filename\n\nFolgend zusätzlich der Inhalt der Datei:\n\n$output";
// echo $request->old('email');
//echo $request->input('email','test');
// die("");
$message = "Moin moin,\n\nein Benutzer hat eine Sprachdatei aktualisiert.\nBearbeitet wurden die Einträge: $editedKeys\n\nSollten die Texte so in Ordnung sein, ersetzt, oder erstellt die Datei aus dem Anhang in folgendem Pfad:\n$filename\n\nFolgend zusätzlich der Inhalt der Datei:\n\n$output";
# Wir haben nun eine Mail an uns geschickt, welche die entsprechende Datei beinhaltet.
# Nun müssen wir den Nutzer eigentlich nur noch zurück leiten und die Letzte bearbeitete Datei ausschließen:
$ex = [];
......@@ -187,11 +231,10 @@ class MailController extends Controller
if ($new > 0) {
if($emailAddress !== "") {
Mail::to("dev@suma-ev.de")
Mail::to("aria@suma-ev.de")
->send(new Sprachdatei($message, $output, basename($filename), $emailAddress));
}
else {
Mail::to("dev@suma-ev.de")
} else {
Mail::to("aria@suma-ev.de")
->send(new Sprachdatei($message, $output, basename($filename)));
}
}
......@@ -199,4 +242,124 @@ class MailController extends Controller
return redirect(url('languages/edit', ['from' => $from, 'to' => $to, 'exclude' => $ex, 'email' => $emailAddress]));
}
public function processSynopticPageInput(Request $request, $exclude = "") {
$filename = $request->input('filename');
#Identifizieren des gedrückten Buttons
if(isset($request['nextpage'])) {
#Leite weiter zur nächsten Seite
$ex = [];
if ($exclude !== "") {
try {
$ex = unserialize(base64_decode($exclude));
} catch (\ErrorException $e) {
$ex = [];
}
if (!isset($ex["files"])) {
$ex["files"] = [];
}
}
if (!isset($ex["new"])) {
$ex["new"] = 0;
}
$ex['files'][] = basename($filename);
$ex = base64_encode(serialize($ex));
return redirect(url('synoptic', ['exclude' => $ex]));
}
#Andernfalls auslesen, zippen und herunterladen der veränderten Dateien
$data = [];
$new = 0;
$editedFiles = [];
foreach ($request->all() as $key => $value) {
if ($key === "filename" || $value === "") {
continue;
}
$key = base64_decode($key);
#Pfad zur Datei anhand des Schlüsselnamens rekonstruieren (Schlüssel enthält Sprachkürzel)
$langdir = $this->extractLanguage($key);
$filepath = "lang/".$langdir."/".$filename;
if (strpos($key, "_new_") === 0 && $value !== "" || $this->isEdited($this->processKey($key), $value, $filepath)) {
$new++;
$editedFiles[$langdir] = $filepath;
}
}
#Erneute Iteration über Request, damit Dateien mitsamt vorherigen Einträgen abgespeichert werden
foreach($request->all() as $key => $value) {
if ($key === "filename" || $value === "") {
continue;
}
$key = base64_decode($key);
#Pfad zur Datei anhand des Schlüsselnamens rekonstruieren (Schlüssel enthält Sprachkürzel)
$langdir = $this->extractLanguage($key);
#Überspringe Datei, falls diese nicht bearbeitet worden ist
if(!isset($editedFiles[$langdir])) {
continue;
}
#Key kuerzen, sodass er nur den eigentlichen Keynamen enthält
$key = $this->processKey($key);
if (!strpos($key, "#")) {
$data[$langdir][$key] = $value;
#Aufdröseln von 2D-Arrays
} else {
$ref = &$data;
do {
$ref = &$ref[$langdir][substr($key, 0, strpos($key, "#"))];
$key = substr($key, strpos($key, "#") + 1);
} while (strpos($key, "#"));
$ref = &$ref[$key];
$ref = $value;
}
}
if(empty($data)) {
return redirect(url('synoptic', ['exclude' => $exclude]));
}
$zip = new ZipArchive();
if ($zip->open("langfiles.zip", ZipArchive::OVERWRITE)!==TRUE) {
} else if ($zip->open("langfiles.zip", ZipArchive::CREATE)!==TRUE) {
exit("Cannot open".$filename);
}
#Erstelle Ausgabedateien
foreach($data as $lang => $entries) {
$output = json_encode($entries, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$output = preg_replace("/\{/si", "[", $output);
$output = preg_replace("/\}/si", "]", $output);
$output = preg_replace("/\": ([\"\[])/si", "\"\t=>\t$1", $output);
$output = "<?php\n\nreturn $output;\n";
$zip->addEmptyDir($lang);
$zip->addFromString($lang."/".$filename, $output);
}
$zip->close();
return response()->download("langfiles.zip", $filename.".zip");
}
}
<?php
namespace App\Models;
/*
* Hilfsklasse, welche zu je einer Sprache Angaben zum Pfad der jeweiligen Datei, sowie die vorhandenen Übersetzungen enthält
*/
class LanguageObject
{
public $language = "";
public $filePath = "";
#2D-Array der Form [$filename][$key]
public $stringMap = [];
public function __construct($lang, $path)
{
$this->language = $lang;
$this->filePath = $path;
}
#Speichert Daten in $stringMap, entdimensionalisiert ggbf. $value
public function saveData($filename, $key, $value)
{
if(is_array($value)) {
$this->deMultiDimensionalize($filename, $key, $value);
} else {
$this->stringMap[$filename][$key] = $value;
}
}
private function deMultiDimensionalize($filename, $key, $value)
{
foreach($value as $key2 => $value2) {
$this->saveData($filename, $key."#".$key2, $value2);
}
}
}
\ No newline at end of file
......@@ -8,4 +8,5 @@ return [
'hinweis.1' => 'Sobald du mit deinen Texten zufrieden bist, kannst du uns diese mit einem Klick auf folgenden Knopf automatisch zusenden. Wenn es mehr fehlende Texte in der angegebenen Sprache gibt, wird dein Browser dich danach direkt zu diesen leiten.',
'hinweis.2' => '<b>Hinweis</b>: Die übermittelten Texte werden von diesem Tool erst erkannt, sobald diese von uns gesichtet und eingefügt wurden. Wenn du deine Arbeit sichern möchtest um diese zu einem späteren Zeitpunkt fortzusetzen (auch wenn wir deine bisherige Arbeit noch nicht übernehmen konnten), so reicht es vollkommen, den aktuellen Link aus deiner Browserleiste zu kopieren und zu einem späteren Zeitpunkt wieder aufzurufen.',
'hinweis.3' => '<b>Achtung</b>: Mit der Einsendung stimmst du einer <a href="https://gitlab.metager3.de/open-source/MetaGer" target="_blank" rel="noopener">Veröffentlichung</a> deiner Texte unter AGPL-v3-Lizenz durch uns zu. Falls du namentlich genannt werden möchtest, teile uns dies bitte in einer email an <a href="mailto:office@suma-ev.de">office@suma-ev.de</a> mit.',
'email' => '<b>E-Mail-Adresse für Rückfragen</b> (optional):'
];
......@@ -7,5 +7,6 @@ return [
"progress" => ":uebersetzteTexte/:textCount snippets translated (:percentage%)",
"hinweis.1" => "Please click \"Submit data\" when finished. You don't have to translate all items.",
"hinweis.2" => "<b>Hint</b>: You won't see your translations until we checked and inserted it. Save your work by setting a bookmark.",
"hinweis.3" => "<b>Note</b>: We publish the hole content of your work on a<a href=\"https://gitlab.metager3.de/open-source/MetaGer\" target=\"_blank\" rel=\"noopener\">Publication</a>. This is subject to a AGPL-v3-License. You agree with clicking the \"Submit data\" button."
"hinweis.3" => "<b>Note</b>: We publish the hole content of your work on a<a href=\"https://gitlab.metager3.de/open-source/MetaGer\" target=\"_blank\" rel=\"noopener\">Publication</a>. This is subject to a AGPL-v3-License. You agree with clicking the \"Submit data\" button.",
"email" => "<b>Email address for possible inquiries</b> (optional):"
];
\ No newline at end of file
......@@ -38,7 +38,7 @@
<td class="name language-name">{{preg_replace("/(\s*).*#(.*)$/si", "$1$2", $name)}}</td>
<td>
@if(isset($langValues[$to]))
<textarea class="language-text-area" type="text" rows="1" cols="50" form="submit" name="{{ base64_encode($name) }}" readonly >{{$langValues[$to]}}</textarea>
<textarea class="language-text-area" type="text" rows="1" cols="50" form="submit" name="{{ base64_encode($name) }}">{{$langValues[$to]}}</textarea>
@else
<textarea class="language-text-area" rows="1" cols="50" form="submit" name="{{base64_encode("_new_" . $name)}}"></textarea>
@endif
......@@ -54,7 +54,7 @@
<p>{{ trans('languages.hinweis.1') }}</p>
<p>{!! trans('languages.hinweis.2') !!}</p>
<p>{!! trans('languages.hinweis.3') !!}</p>
<p><b>E-Mail-Adresse für Rückfragen</b> (optional):</p>
<p>{!! trans('languages.email') !!}</p>
<p><input type="email" name="email" form="submit" value="{{$email}}"/></p>
<button class="btn btn-success" type="submit" form="submit">Daten übermitteln</button>
<script type="text/javascript" src="{{ elixir('js/lib.js') }}"></script>
......
@extends('layouts.subPages')
@section('title', $title )
@section('content')
<h1>Übersicht</h1>
<?php /*
<!--
<div class="progress">
<div class="progress-bar progress-bar-success progress-bar-striped" role="progressbar" aria-valuenow="{{ round(100 * (($langTexts[$to]+$new) / count($sum))) }}" aria-valuemin="0" aria-valuemax="100" style="width: {{ round(100 * (($langTexts[$to]+$new) / count($sum))) }}%">
{{ trans('languages.progress', ['uebersetzteTexte'=> ($langTexts[$to]+$new), 'textCount'=>count($sum), 'percentage'=>round(100 * (($langTexts[$to]+$new) / count($sum)))]) }}
</div>
</div>
-->
*/?>
<h2>{{$filename}}</h2>
<form id="submit" method="POST">
<input type="hidden" name="filename" value="{{$filename}}" />
</form>
<table class="table">
<thead>
<tr>
<th>#ID</th>
@foreach($to as $t)
<th>{{$t}}</th>
@endforeach
</tr>
</thead>
<tbody>
@foreach($texts as $key => $language)
<tr> <!--Key -->
<td class="name language-name">{{$key}}</td>
@foreach($language as $lang => $languageValue)
@if($languageValue !== "")
<td>
<textarea class="language-text-area" rows="1" cols="20" form="submit" name="{{base64_encode($lang."_".$key)}}">{{$languageValue}}</textarea>
</td>
@else
<td>
<textarea class="language-text-area" rows="1" cols="20" form="submit" name="{{base64_encode("_new_".$lang."_".$key)}}"></textarea>
</td>
@endif
@endforeach
</tr>
@endforeach
</tbody>
</table>
<!--
<p>{{ trans('languages.hinweis.1') }}</p>
<p>{!! trans('languages.hinweis.2') !!}</p>
<p>{!! trans('languages.hinweis.3') !!}</p>
<p>{!! trans('languages.email') !!}</p>
-->
<button name="download" class="btn btn-success" type="submit" form="submit">Dateien herunterladen</button>
<button name="nextpage" class="btn btn-success" type="submit" form="submit">Nächste Seite</button>
<script type="text/javascript" src="{{ elixir('js/lib.js') }}"></script>
<script type="text/javascript" src="{{ elixir('js/editLanguage.js') }}"></script>
@endsection
......@@ -158,6 +158,8 @@ Route::group(
return redirect('https://metager.de/klassik/databund');
});
Route::get('languages', 'LanguageController@createOverview');
Route::get('synoptic/{exclude?}', 'LanguageController@createSynopticEditPage');
Route::post('synoptic/{exclude?}', 'MailController@processSynopticPageInput');
Route::get('languages/edit/{from}/{to}/{exclude?}/{email?}', 'LanguageController@createEditPage');
Route::post('languages/edit/{from}/{to}/{exclude?}/{email?}', 'MailController@sendLanguageFile');
Route::get('berlin', 'StartpageController@berlin');
......
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>Sprachdateien bearbeiten - MetaGer</title>
<meta name="description" content="Sicher suchen und finden unter Wahrung der Privatsphäre. Das digitale Wissen der Welt muss ohne Bevormundung durch Staaten oder Konzerne frei zugänglich sein und bleiben." />
<meta name="keywords" content="Internetsuche, privatsphäre, privacy, Suchmaschine, Datenschutz, Anonproxy, anonym suchen, Bildersuche, Suchmaschine, anonym, MetaGer, metager, metager.de" />
<meta name="page-topic" content="Dienstleistung" />
<meta name="robots" content="index,follow" />
<meta name="revisit-after" content="7 days" />
<meta name="audience" content="all" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<meta rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
<META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE" />
<meta HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE" />
<link rel="search" type="application/opensearchdescription+xml" title="MetaGer: Sicher suchen &amp; finden, Privatsphäre schützen" href="http://localhost:8000/plugins/YTowOnt9/opensearch.xml">
<link type="text/css" rel="stylesheet" href="/css/themes/default.css" />
<link id="theme" type="text/css" rel="stylesheet" href="/css/theme.css.php" />
</head>
<body>
<header>
<a class="navbar-brand" href="http://localhost:8000/">
<div class="logo">
<h1>MetaGer</h1>
</div>
</a>
<nav id="navbar-static-pages" class="navbar-resultpage">
<ul id="metager-static-nav-list" class="list-inline pull-right">
<li id="toggle-nav-hide" class="hidden">
<a class="metager-navbar-toggle pull-right" href="#" data-original-title="" title="">
<span class="sr-only">Navigation anzeigen</span>
<i class="fa fa-bars" aria-hidden="true"></i>
</a>
<div class="clearfix"></div>
</li>
<li id="toggle-nav-show">
<a class="metager-navbar-toggle pull-right" href="#metager-static-nav-list" data-original-title="" title="">
<span class="sr-only">Navigation anzeigen</span>
<i class="fa fa-bars" aria-hidden="true"></i>
</a>
<div class="clearfix"></div>
</li>
<li class="active" >
<a href="http://localhost:8000/" tabindex="200" id="navigationSuche">Suche</a>
</li>