diff options
Diffstat (limited to 'source/net')
| -rw-r--r-- | source/net/yacy/data/Translator.java | 43 | ||||
| -rwxr-xr-x | source/net/yacy/utils/translation/GenerateMasterXliff.java | 6 | ||||
| -rw-r--r-- | source/net/yacy/utils/translation/GenerateSourceMasterXliff.java | 428 | ||||
| -rw-r--r-- | source/net/yacy/utils/translation/TranslationManager.java | 9 | ||||
| -rw-r--r-- | source/net/yacy/utils/translation/TranslatorXliff.java | 2 | ||||
| -rw-r--r-- | source/net/yacy/yacy.java | 1 |
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)(?: | | | |\\s)+"); + + private static final Pattern HTML_SPACE_ENTITY_EDGE_PATTERN = Pattern.compile( + "(?i)^(?: | | | |\\s)+|(?: | | | |\\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"))));
|
