summaryrefslogtreecommitdiff
path: root/source/net
diff options
context:
space:
mode:
Diffstat (limited to 'source/net')
-rw-r--r--source/net/yacy/data/Translator.java43
-rwxr-xr-xsource/net/yacy/utils/translation/GenerateMasterXliff.java6
-rw-r--r--source/net/yacy/utils/translation/GenerateSourceMasterXliff.java428
-rw-r--r--source/net/yacy/utils/translation/TranslationManager.java9
-rw-r--r--source/net/yacy/utils/translation/TranslatorXliff.java2
-rw-r--r--source/net/yacy/yacy.java1
6 files changed, 478 insertions, 11 deletions
diff --git a/source/net/yacy/data/Translator.java b/source/net/yacy/data/Translator.java
index 03e55c61b..448c24b1d 100644
--- a/source/net/yacy/data/Translator.java
+++ b/source/net/yacy/data/Translator.java
@@ -47,6 +47,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.regex.Pattern;
import net.yacy.cora.util.CommonPattern;
import net.yacy.cora.util.ConcurrentLog;
@@ -68,6 +69,10 @@ public class Translator {
public final static String LANG_FILENAME_FILTER = "^.*\\.lng$";
+ private static final Pattern TECHNICAL_TARGET_PATTERN = Pattern.compile(
+ "^(?:https?://.*|[A-Za-z0-9_./:%?#=&;,+~@!$'()\\[\\]-]+\\.(?:html|inc|json|xml|rss|css|js|pac)(?:[?#].*)?)$",
+ Pattern.CASE_INSENSITIVE);
+
/**
* Translate source using entries in translationTable
* @param source text to translate. Must be non null.
@@ -112,8 +117,12 @@ public class Translator {
}
if (boundary) { // boundary check ok -> translate
- builder.replace(index, index + key.length(), translation);
- index = builder.indexOf(key, index + translation.length());
+ if (isInsideTechnicalTarget(builder, index, key.length())) {
+ index = builder.indexOf(key, index + key.length());
+ } else {
+ builder.replace(index, index + key.length(), translation);
+ index = builder.indexOf(key, index + translation.length());
+ }
} else { // otherwise just skip to next occurence
index = builder.indexOf(key, index + key.length());
}
@@ -124,6 +133,35 @@ public class Translator {
return builder.toString();
}
+ private static boolean isInsideTechnicalTarget(final CharSequence content, final int index, final int length) {
+ int start = index;
+ while (start > 0 && isTechnicalTargetCharacter(content.charAt(start - 1))) {
+ start--;
+ }
+
+ int end = index + length;
+ while (end < content.length() && isTechnicalTargetCharacter(content.charAt(end))) {
+ end++;
+ }
+
+ if (start == index && end == index + length) {
+ return false;
+ }
+
+ final String token = content.subSequence(start, end).toString();
+ return TECHNICAL_TARGET_PATTERN.matcher(token).matches();
+ }
+
+ private static boolean isTechnicalTargetCharacter(final char c) {
+ return Character.isLetterOrDigit(c)
+ || c == '_' || c == '-' || c == '.' || c == '/'
+ || c == ':' || c == '%' || c == '?' || c == '#'
+ || c == '=' || c == '&' || c == ';' || c == ','
+ || c == '+' || c == '~' || c == '@' || c == '!'
+ || c == '$' || c == '\'' || c == '(' || c == ')'
+ || c == '[' || c == ']';
+ }
+
/**
* Load multiple translationLists from one File. Each List starts with #File: relative/path/to/file
* (within each file section translation is done in order of the language file entries, on conflicts
@@ -300,6 +338,7 @@ public class Translator {
final File destDir = new File(env.getDataPath("locale.translated_html", "DATA/LOCALE/htroot"), lang.substring(0, lang.length() - 4));// cut .lng
final File translationFile = new File(langPath, lang);
+ FileUtils.deletedelete(destDir);
if (translateFilesRecursive(sourceDir, destDir, translationFile, "html,template,inc", "locale")) {
env.setConfig("locale.language", lang.substring(0, lang.length() - 4));
Formatter.setLocale(env.getConfig("locale.language", "en"));
diff --git a/source/net/yacy/utils/translation/GenerateMasterXliff.java b/source/net/yacy/utils/translation/GenerateMasterXliff.java
index 9cc1b691c..8a9d3c1e0 100755
--- a/source/net/yacy/utils/translation/GenerateMasterXliff.java
+++ b/source/net/yacy/utils/translation/GenerateMasterXliff.java
@@ -39,8 +39,8 @@ public class GenerateMasterXliff {
/**
* Read all translation files in YaCy custom lng format from a given folder
* (locales/ as default), and convert them to a single master xliff file
- * containing entries to translate. When the ouput master file already exists,
- * it is updated with new entries from the lng files.
+ * containing entries to translate. When the output master file already exists,
+ * it is replaced with the currently valid entries from the lng files.
*
* @param args
* runtime optional arguments<br/>
@@ -74,7 +74,7 @@ public class GenerateMasterXliff {
masterXlf = new File("master.lng.xlf");
}
if (masterXlf.exists()) {
- System.out.println("Updating master xliff file at " + masterXlf.getAbsolutePath());
+ System.out.println("Replacing master xliff file at " + masterXlf.getAbsolutePath());
} else {
System.out.println("Generating a new master xliff file at " + masterXlf.getAbsolutePath());
}
diff --git a/source/net/yacy/utils/translation/GenerateSourceMasterXliff.java b/source/net/yacy/utils/translation/GenerateSourceMasterXliff.java
new file mode 100644
index 000000000..1832ad676
--- /dev/null
+++ b/source/net/yacy/utils/translation/GenerateSourceMasterXliff.java
@@ -0,0 +1,428 @@
+// GenerateSourceMasterXliff.java
+// ---------------------------
+// Copyright 2026 by YaCy contributors
+//
+// This is a part of YaCy, a peer-to-peer based web search engine
+//
+// LICENSE
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package net.yacy.utils.translation;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+import net.yacy.cora.util.ConcurrentLog;
+import net.yacy.document.SentenceReader;
+
+/**
+ * Generate a source-based XLIFF master candidate by extracting conservative UI
+ * text candidates directly from htroot source files.
+ *
+ * This tool does not use existing *.lng files as input. It is intended to build
+ * and review a source-of-truth candidate before checking individual languages
+ * for completeness.
+ */
+public class GenerateSourceMasterXliff {
+
+ private static final List<String> SOURCE_EXTENSIONS = Arrays.asList(".html", ".template", ".inc");
+
+ private static final Set<String> TEXT_ATTRIBUTES = new LinkedHashSet<>(
+ Arrays.asList("alt", "aria-label", "placeholder", "title", "value"));
+
+ private static final Pattern IGNORED_BLOCK_PATTERN = Pattern.compile(
+ "(?is)<(script|style|pre|code|textarea)\\b[^>]*>.*?</\\1>");
+
+ private static final Pattern HTML_COMMENT_PATTERN = Pattern.compile("(?s)<!--.*?-->");
+
+ private static final Pattern TAG_PATTERN = Pattern.compile("(?s)<[^>]+>");
+
+ private static final Pattern BLOCK_TAG_PATTERN = Pattern.compile(
+ "(?is)</?(?:html|head|body|title|div|p|table|thead|tbody|tfoot|tr|td|th|ul|ol|li|form|fieldset|legend|dl|dt|dd|select|option|button|input|h[1-6]|section|article|nav|header|footer|hr)\\b[^>]*>");
+
+ private static final Pattern LINK_TAG_PATTERN = Pattern.compile("(?is)</?a\\b[^>]*>");
+
+ private static final Pattern UNPRESERVED_TAG_PATTERN = Pattern.compile(
+ "(?is)<(?!/?(?:abbr|b|strong|em|i|br)\\b)[^>]+>");
+
+ private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile(
+ "(?i)\\b([a-zA-Z_:][-a-zA-Z0-9_:.]*)\\s*=\\s*(\"([^\"]*)\"|'([^']*)')");
+
+ private static final Pattern TEMPLATE_BOUNDARY_PATTERN = Pattern.compile(
+ "#\\([^)]*\\)#|#\\(/[^)]*\\)#|#\\{[^}]+\\}#|#\\{/[^}]+\\}#|#%[^%]+%#|::");
+
+ private static final Pattern HTML_SPACE_ENTITY_ONLY_PATTERN = Pattern.compile(
+ "(?i)(?:&nbsp;|&#160;|&ensp;|&emsp;|\\s)+");
+
+ private static final Pattern HTML_SPACE_ENTITY_EDGE_PATTERN = Pattern.compile(
+ "(?i)^(?:&nbsp;|&#160;|&ensp;|&emsp;|\\s)+|(?:&nbsp;|&#160;|&ensp;|&emsp;|\\s)+$");
+
+ private static final Pattern FORMAT_WRAPPER_PATTERN = Pattern.compile(
+ "(?is)^<(strong|b|em|i)>(.*)</\\1>(?:<br\\s*/?>)?$");
+
+ private static final Pattern HTML_LINE_BREAK_EDGE_PATTERN = Pattern.compile(
+ "(?is)^(?:<br\\s*/?>\\s*)+|(?:\\s*<br\\s*/?>)+$");
+
+ private static final Pattern HTML_TAG_ONLY_PATTERN = Pattern.compile("(?is)</?[a-z][^>]*>");
+
+ /**
+ * @param args runtime optional arguments<br/>
+ * <ul>
+ * <li>args[0] : source folder path (htroot/ as default)</li>
+ * <li>args[1] : output xliff master file path
+ * (source-master.lng.xlf as default)</li>
+ * </ul>
+ * @throws IOException when a read/write error occurred
+ */
+ public static void main(final String args[]) throws IOException {
+ try {
+ final File sourceFolder = args.length > 0 && args[0] != null ? new File(args[0]) : new File("htroot");
+ if (!sourceFolder.isDirectory()) {
+ System.err.println(sourceFolder.getPath() + " is not a directory");
+ return;
+ }
+
+ final File masterXlf = args.length > 1 && args[1] != null ? new File(args[1]) : new File("source-master.lng.xlf");
+
+ System.out.println("Extracting source text from folder " + sourceFolder.getAbsolutePath());
+ System.out.println((masterXlf.exists() ? "Replacing" : "Generating") + " source master xliff file at "
+ + masterXlf.getAbsolutePath());
+
+ final ExtractionResult extractionResult = extractSourceMaster(sourceFolder.toPath());
+ final Map<String, Map<String, String>> sourceMaster = extractionResult.sourceMaster;
+ final VerificationResult verificationResult = verifySourceMaster(sourceFolder.toPath(), sourceMaster);
+ if (!verificationResult.isOk()) {
+ throw new IOException("Source master verification failed: " + verificationResult);
+ }
+ new TranslatorXliff().saveAsXliff(null, masterXlf, sourceMaster);
+
+ int unitCount = 0;
+ for (Map<String, String> entries : sourceMaster.values()) {
+ unitCount += entries.size();
+ }
+ System.out.println("Extracted " + unitCount + " source text candidates from " + sourceMaster.size() + " files.");
+ System.out.println("Rejected " + extractionResult.rejectedCandidates
+ + " extracted candidates without a runtime boundary match.");
+ System.out.println("Verified " + verificationResult.verifiedKeys
+ + " source text candidates in " + verificationResult.verifiedFiles + " files.");
+ } finally {
+ ConcurrentLog.shutdown();
+ }
+ }
+
+ static ExtractionResult extractSourceMaster(final Path sourceRoot) throws IOException {
+ final ExtractionResult result = new ExtractionResult();
+ try (Stream<Path> files = Files.walk(sourceRoot)) {
+ files.filter(Files::isRegularFile)
+ .filter(GenerateSourceMasterXliff::hasSupportedExtension)
+ .forEach(path -> extractFile(sourceRoot, path, result.sourceMaster, result));
+ }
+ return result;
+ }
+
+ private static boolean hasSupportedExtension(final Path path) {
+ final String fileName = path.getFileName().toString().toLowerCase(Locale.ROOT);
+ for (String extension : SOURCE_EXTENSIONS) {
+ if (fileName.endsWith(extension)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static void extractFile(final Path sourceRoot, final Path sourceFile,
+ final Map<String, Map<String, String>> sourceMaster, final ExtractionResult result) {
+ try {
+ final String content = new String(Files.readAllBytes(sourceFile), StandardCharsets.UTF_8);
+ final LinkedHashSet<String> candidates = extractCandidates(relativeSourcePath(sourceRoot, sourceFile), content);
+ if (!candidates.isEmpty()) {
+ final String relativePath = relativeSourcePath(sourceRoot, sourceFile);
+ final Map<String, String> entries = new LinkedHashMap<>();
+ for (String candidate : candidates) {
+ if (hasRuntimeBoundaryMatch(content, candidate)) {
+ entries.put(candidate, null);
+ } else {
+ result.rejectedCandidates++;
+ }
+ }
+ if (!entries.isEmpty()) {
+ sourceMaster.put(relativePath, entries);
+ }
+ }
+ } catch (IOException e) {
+ ConcurrentLog.warn("TRANSLATOR", "Could not extract source text from " + sourceFile + ": " + e.getMessage());
+ }
+ }
+
+ private static String relativeSourcePath(final Path sourceRoot, final Path sourceFile) {
+ return sourceRoot.relativize(sourceFile).toString().replace(File.separatorChar, '/');
+ }
+
+ static VerificationResult verifySourceMaster(final Path sourceRoot,
+ final Map<String, Map<String, String>> sourceMaster) throws IOException {
+ final VerificationResult result = new VerificationResult();
+ for (Map.Entry<String, Map<String, String>> fileEntry : sourceMaster.entrySet()) {
+ result.verifiedFiles++;
+ final Path sourceFile = sourceRoot.resolve(fileEntry.getKey());
+ if (!Files.isRegularFile(sourceFile)) {
+ result.missingFiles++;
+ continue;
+ }
+
+ final String content = new String(Files.readAllBytes(sourceFile), StandardCharsets.UTF_8);
+ for (String key : fileEntry.getValue().keySet()) {
+ if (key == null || key.isEmpty()) {
+ result.emptyKeys++;
+ continue;
+ }
+ if (content.indexOf(key) < 0) {
+ result.missingRawMatches++;
+ continue;
+ }
+ if (!hasRuntimeBoundaryMatch(content, key)) {
+ result.missingRuntimeBoundaryMatches++;
+ continue;
+ }
+ result.verifiedKeys++;
+ }
+ }
+ return result;
+ }
+
+ private static boolean hasRuntimeBoundaryMatch(final String content, final String key) {
+ if (key == null || key.isEmpty()) {
+ return false;
+ }
+
+ int index = content.indexOf(key);
+ while (index >= 0) {
+ if (hasRuntimeBoundaries(content, key, index)) {
+ return true;
+ }
+ index = content.indexOf(key, index + key.length());
+ }
+ return false;
+ }
+
+ private static boolean hasRuntimeBoundaries(final String content, final String key, final int index) {
+ boolean boundary = index + key.length() >= content.length();
+
+ if (!boundary) {
+ final char c = content.charAt(index + key.length() - 1);
+ final char lc = content.charAt(index + key.length());
+ boundary |= SentenceReader.punctuation(c) || SentenceReader.invisible(c);
+ boundary |= SentenceReader.punctuation(lc) || SentenceReader.invisible(lc);
+ }
+
+ if (boundary && index > 0) {
+ final char c = content.charAt(index - 1);
+ boundary = SentenceReader.punctuation(c) || SentenceReader.invisible(c);
+ final char fc = content.charAt(index);
+ boundary |= SentenceReader.punctuation(fc) || SentenceReader.invisible(fc);
+ }
+ return boundary;
+ }
+
+ static LinkedHashSet<String> extractCandidates(final String content) {
+ return extractCandidates(null, content);
+ }
+
+ static LinkedHashSet<String> extractCandidates(final String sourcePath, final String content) {
+ final LinkedHashSet<String> candidates = new LinkedHashSet<>();
+ String withoutIgnoredBlocks = HTML_COMMENT_PATTERN.matcher(
+ IGNORED_BLOCK_PATTERN.matcher(content).replaceAll("")).replaceAll("");
+ if ("jslicense.html".equals(sourcePath)) {
+ withoutIgnoredBlocks = withoutIgnoredBlocks.replaceAll("(?is)<td\\b[^>]*>.*?</td>", "");
+ }
+
+ Matcher tagMatcher = TAG_PATTERN.matcher(withoutIgnoredBlocks);
+ while (tagMatcher.find()) {
+ addAttributeCandidates(tagMatcher.group(), candidates);
+ }
+
+ final String textSource = UNPRESERVED_TAG_PATTERN.matcher(
+ LINK_TAG_PATTERN.matcher(BLOCK_TAG_PATTERN.matcher(withoutIgnoredBlocks).replaceAll("\n")).replaceAll(""))
+ .replaceAll("\n");
+ addTextCandidates(textSource, candidates);
+ return candidates;
+ }
+
+ private static void addAttributeCandidates(final String tag, final Set<String> candidates) {
+ final String lowerTag = tag.toLowerCase(Locale.ROOT);
+ if (lowerTag.contains("type=\"hidden\"") || lowerTag.contains("type='hidden'")) {
+ return;
+ }
+
+ final Matcher attributeMatcher = ATTRIBUTE_PATTERN.matcher(tag);
+ while (attributeMatcher.find()) {
+ final String attributeName = attributeMatcher.group(1).toLowerCase(Locale.ROOT);
+ if (TEXT_ATTRIBUTES.contains(attributeName)) {
+ if ("value".equals(attributeName) && !isButtonValueAttribute(lowerTag)) {
+ continue;
+ }
+ final String value = attributeMatcher.group(3) != null ? attributeMatcher.group(3) : attributeMatcher.group(4);
+ final String quote = attributeMatcher.group(3) != null ? "\"" : "'";
+ addCandidate(quote + value + quote, candidates);
+ }
+ }
+ }
+
+ private static boolean isButtonValueAttribute(final String lowerTag) {
+ return lowerTag.startsWith("<button") || lowerTag.contains("type=\"submit\"") || lowerTag.contains("type='submit'")
+ || lowerTag.contains("type=\"button\"") || lowerTag.contains("type='button'")
+ || lowerTag.contains("type=\"reset\"") || lowerTag.contains("type='reset'");
+ }
+
+ private static void addTextCandidates(final String text, final Set<String> candidates) {
+ final String[] templateParts = TEMPLATE_BOUNDARY_PATTERN.split(text);
+ for (String templatePart : templateParts) {
+ final String[] lines = templatePart.split("\\r?\\n");
+ for (String line : lines) {
+ addCandidate(line, candidates);
+ }
+ }
+ }
+
+ private static void addCandidate(final String rawCandidate, final Set<String> candidates) {
+ if (rawCandidate == null) {
+ return;
+ }
+
+ final String candidate = normalizeFormattingWrapper(trimHtmlLineBreaks(
+ trimHtmlSpaceEntities(trimInvisible(rawCandidate))));
+ if (!isUsefulCandidate(candidate)) {
+ return;
+ }
+ candidates.add(candidate);
+ }
+
+ private static String trimInvisible(final String text) {
+ int start = 0;
+ int end = text.length();
+ while (start < end && Character.isWhitespace(text.charAt(start))) {
+ start++;
+ }
+ while (end > start && Character.isWhitespace(text.charAt(end - 1))) {
+ end--;
+ }
+ return text.substring(start, end);
+ }
+
+ private static String trimHtmlSpaceEntities(final String text) {
+ return HTML_SPACE_ENTITY_EDGE_PATTERN.matcher(text).replaceAll("");
+ }
+
+ private static String trimHtmlLineBreaks(final String text) {
+ return HTML_LINE_BREAK_EDGE_PATTERN.matcher(text).replaceAll("");
+ }
+
+ private static String normalizeFormattingWrapper(final String text) {
+ final Matcher matcher = FORMAT_WRAPPER_PATTERN.matcher(text);
+ if (matcher.matches()) {
+ return trimHtmlSpaceEntities(trimInvisible(matcher.group(2)));
+ }
+ return text;
+ }
+
+ private static boolean isUsefulCandidate(final String candidate) {
+ if (candidate.length() < 2) {
+ return false;
+ }
+ if (HTML_SPACE_ENTITY_ONLY_PATTERN.matcher(candidate).matches()) {
+ return false;
+ }
+ if (HTML_TAG_ONLY_PATTERN.matcher(candidate).matches()) {
+ return false;
+ }
+ if (candidate.indexOf("==") >= 0) {
+ return false;
+ }
+ if (candidate.endsWith("=")) {
+ return false;
+ }
+ if (candidate.startsWith("#")) {
+ return false;
+ }
+ if (candidate.startsWith("//") || candidate.startsWith("/*") || candidate.startsWith("*")) {
+ return false;
+ }
+ if (candidate.startsWith(":") || candidate.startsWith(".") || candidate.startsWith("-")) {
+ return false;
+ }
+ if (candidate.indexOf("#[") >= 0 || candidate.indexOf("#(") >= 0 || candidate.indexOf("#{") >= 0
+ || candidate.indexOf("#%") >= 0) {
+ return false;
+ }
+
+ boolean hasLetter = false;
+ for (int i = 0; i < candidate.length(); i++) {
+ if (Character.isLetter(candidate.charAt(i))) {
+ hasLetter = true;
+ break;
+ }
+ }
+ return hasLetter;
+ }
+
+ static final class ExtractionResult {
+
+ private final Map<String, Map<String, String>> sourceMaster = new TreeMap<>();
+
+ private int rejectedCandidates;
+ }
+
+ static final class VerificationResult {
+
+ private int verifiedFiles;
+
+ private int verifiedKeys;
+
+ private int missingFiles;
+
+ private int emptyKeys;
+
+ private int missingRawMatches;
+
+ private int missingRuntimeBoundaryMatches;
+
+ private boolean isOk() {
+ return this.missingFiles == 0 && this.emptyKeys == 0 && this.missingRawMatches == 0
+ && this.missingRuntimeBoundaryMatches == 0;
+ }
+
+ @Override
+ public String toString() {
+ return "verifiedFiles=" + this.verifiedFiles + ", verifiedKeys=" + this.verifiedKeys
+ + ", missingFiles=" + this.missingFiles + ", emptyKeys=" + this.emptyKeys
+ + ", missingRawMatches=" + this.missingRawMatches
+ + ", missingRuntimeBoundaryMatches=" + this.missingRuntimeBoundaryMatches;
+ }
+ }
+}
diff --git a/source/net/yacy/utils/translation/TranslationManager.java b/source/net/yacy/utils/translation/TranslationManager.java
index 1b1bbc42f..744dc9a3b 100644
--- a/source/net/yacy/utils/translation/TranslationManager.java
+++ b/source/net/yacy/utils/translation/TranslationManager.java
@@ -162,17 +162,16 @@ public class TranslationManager extends TranslatorXliff {
/**
* Create a master translation list by reading all translation files
- * If a masterOutputFile exists, content is preserved (loaded first)
+ * Existing masterOutputFile content is replaced. This keeps the generated
+ * master list in sync with strings that still occur in the current source
+ * files and avoids carrying stale source entries forward.
*
* @param localesFolder folder containing *.lng translation files
* @param masterOutputFile output file (xliff format). Must not be null.
* @throws IOException
*/
public void createMasterTranslationLists(final File localesFolder, final File masterOutputFile) throws IOException {
- if (masterOutputFile.exists()) // if file exists, conserve existing master content (may be updated by external tool)
- mainTransLists = loadTranslationsListsFromXliff(masterOutputFile);
- else
- mainTransLists = new TreeMap<String, Map<String, String>>();
+ mainTransLists = new TreeMap<String, Map<String, String>>();
List<String> lngFiles = Translator.langFiles(localesFolder);
for (String filename : lngFiles) {
diff --git a/source/net/yacy/utils/translation/TranslatorXliff.java b/source/net/yacy/utils/translation/TranslatorXliff.java
index d30009ae4..77cd7aeaa 100644
--- a/source/net/yacy/utils/translation/TranslatorXliff.java
+++ b/source/net/yacy/utils/translation/TranslatorXliff.java
@@ -227,7 +227,7 @@ public class TranslatorXliff extends Translator {
final OutputStreamWriter output = new OutputStreamWriter(fileOutStream, StandardCharsets.UTF_8.name());
) {
output.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
- output.write("<xliff version='1.2' xmlns='urn:oasis:names:tc:xliff:document:1.2'> \n");
+ output.write("<xliff version='1.2' xmlns='urn:oasis:names:tc:xliff:document:1.2'>\n");
for (String afilemap : lng.keySet()) {
output.write("<file original=\"" + afilemap + "\" " // original required in xliff 1.2
+ " source-language=\"" + sourceLanguage + "\" "); // required in xliff 1.2
diff --git a/source/net/yacy/yacy.java b/source/net/yacy/yacy.java
index 1a7166eb4..1a5babbc4 100644
--- a/source/net/yacy/yacy.java
+++ b/source/net/yacy/yacy.java
@@ -362,6 +362,7 @@ public final class yacy {
try { //is this another version?!
final File sourceDir = new File(sb.getConfig(SwitchboardConstants.HTROOT_PATH, SwitchboardConstants.HTROOT_PATH_DEFAULT));
final File destDir = new File(sb.getDataPath("locale.translated_html", "DATA/LOCALE/htroot"), tmplang);
+ FileUtils.deletedelete(destDir);
if (new TranslatorXliff().translateFilesRecursive(sourceDir, destDir, new File(locale_source, tmplang + ".lng"), "html,template,inc", "locale")) { //translate it
//write the new Versionnumber
final BufferedWriter bw = new BufferedWriter(new PrintWriter(new FileWriter(new File(destDir, "version"))));