diff --git a/.classpath b/.classpath
index 45ba4c8..0750fbc 100644
--- a/.classpath
+++ b/.classpath
@@ -41,17 +41,7 @@
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/README.md b/README.md
index 0f660e9..c71027f 100644
--- a/README.md
+++ b/README.md
@@ -49,9 +49,9 @@ Technical Support | Peer support at [Groups.io](https://groups.io/g/maxprograms
## Requirements
- JDK 21 or newer is required for compiling and building. Get it from [Adoptium](https://adoptium.net/).
-- Apache Ant 1.10.12 or newer. Get it from [https://ant.apache.org/](https://ant.apache.org/)
+- Apache Ant 1.10.14 or newer. Get it from [https://ant.apache.org/](https://ant.apache.org/)
- Node.js 20.16.0 LTS or newer. Get it from [https://nodejs.org/](https://nodejs.org/)
-- TypeScript 5.6.2 or newer, get it from [https://www.typescriptlang.org/](https://www.typescriptlang.org/)
+- TypeScript 5.6.3 or newer, get it from [https://www.typescriptlang.org/](https://www.typescriptlang.org/)
## Building
diff --git a/build.xml b/build.xml
index 7884797..6ed9b12 100644
--- a/build.xml
+++ b/build.xml
@@ -11,9 +11,7 @@
-
-
-
+
diff --git a/html/editProject.html b/html/editProject.html
new file mode 100644
index 0000000..f8d91a1
--- /dev/null
+++ b/html/editProject.html
@@ -0,0 +1,85 @@
+
+
+
+
+ Edit Project
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/html/messages.html b/html/messages.html
deleted file mode 100644
index d88ae49..0000000
--- a/html/messages.html
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/html/sortSegments.html b/html/sortSegments.html
index 4da0976..833fa18 100644
--- a/html/sortSegments.html
+++ b/html/sortSegments.html
@@ -11,9 +11,9 @@
-
+
-
+
@@ -23,13 +23,13 @@
-
+
-
+
@@ -41,7 +41,7 @@
-
+
diff --git a/jars/openxliff.jar b/jars/openxliff.jar
index d8726f1..26c7ec6 100644
Binary files a/jars/openxliff.jar and b/jars/openxliff.jar differ
diff --git a/jars/slf4j-api-2.0.13.jar b/jars/slf4j-api-2.0.13.jar
deleted file mode 100644
index a800cc2..0000000
Binary files a/jars/slf4j-api-2.0.13.jar and /dev/null differ
diff --git a/jars/slf4j-nop-2.0.13.jar b/jars/slf4j-nop-2.0.13.jar
deleted file mode 100644
index e2e1122..0000000
Binary files a/jars/slf4j-nop-2.0.13.jar and /dev/null differ
diff --git a/jars/sqlite-jdbc-3.45.3.0.jar b/jars/sqlite-jdbc-3.47.0.1-20241024.015404-1.jar
similarity index 60%
rename from jars/sqlite-jdbc-3.45.3.0.jar
rename to jars/sqlite-jdbc-3.47.0.1-20241024.015404-1.jar
index 4debbd4..8c3d91e 100644
Binary files a/jars/sqlite-jdbc-3.45.3.0.jar and b/jars/sqlite-jdbc-3.47.0.1-20241024.015404-1.jar differ
diff --git a/package.json b/package.json
index c14ce3c..9f6d5e7 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "swordfish",
"productName": "Swordfish",
- "version": "5.6.5",
+ "version": "5.7.0",
"description": "Swordfish Translation Editor",
"main": "js/Swordfish.js",
"scripts": {
@@ -20,7 +20,7 @@
"url": "https://github.com/rmraya/Swordfish.git"
},
"devDependencies": {
- "electron": "^32.2.0",
+ "electron": "^33.2.0",
"typescript": "^5.6.3"
},
"dependencies": {
diff --git a/src/com/maxprograms/swordfish/Constants.java b/src/com/maxprograms/swordfish/Constants.java
index a42b172..cd93aa5 100644
--- a/src/com/maxprograms/swordfish/Constants.java
+++ b/src/com/maxprograms/swordfish/Constants.java
@@ -19,8 +19,8 @@ private Constants() {
}
public static final String APPNAME = "Swordfish";
- public static final String VERSION = "5.6.4";
- public static final String BUILD = "20240921_0931";
+ public static final String VERSION = "5.7.0";
+ public static final String BUILD = "20241117_0847";
public static final String REASON = "reason";
public static final String STATUS = "status";
diff --git a/src/com/maxprograms/swordfish/ProjectsHandler.java b/src/com/maxprograms/swordfish/ProjectsHandler.java
index 5349130..1684add 100644
--- a/src/com/maxprograms/swordfish/ProjectsHandler.java
+++ b/src/com/maxprograms/swordfish/ProjectsHandler.java
@@ -104,6 +104,8 @@ private JSONObject processRequest(String url, String request) {
try {
if ("/projects/create".equals(url)) {
response = createProject(request);
+ } else if ("/projects/update".equals(url)) {
+ response = updateProject(request);
} else if ("/projects/list".equals(url)) {
response = listProjects();
} else if ("/projects/get".equals(url)) {
@@ -815,6 +817,40 @@ private JSONObject getSegmentsCount(String request) {
return result;
}
+ private JSONObject updateProject(String request) {
+ JSONObject result = new JSONObject();
+ JSONObject json = new JSONObject(request);
+ String projectId = json.getString("project");
+ if (!projectStores.containsKey(projectId)) {
+ try {
+ Map projects = getProjects();
+ Project prj = projects.get(projectId);
+ XliffStore store = new XliffStore(prj.getXliff(), prj.getSourceLang().getCode(),
+ prj.getTargetLang().getCode());
+ projectStores.put(projectId, store);
+ } catch (SAXException | IOException | ParserConfigurationException | URISyntaxException | SQLException e) {
+ logger.log(Level.ERROR, Messages.getString("ProjectsHandler.3"), e);
+ result.put(Constants.REASON, e.getMessage());
+ return result;
+ }
+ }
+ try {
+ projectStores.get(json.getString("project")).updateProject(json);
+ Map projects = getProjects();
+ Project project = projects.get(json.getString("project"));
+ project.setDescription(json.getString("description"));
+ project.setSourceLang(LanguageUtils.getLanguage(json.getString("srcLang")));
+ project.setTargetLang(LanguageUtils.getLanguage(json.getString("tgtLang")));
+ project.setClient(json.getString("client"));
+ project.setSubject(json.getString("subject"));
+ saveProjectsList(projects);
+ } catch (IOException | SAXException | ParserConfigurationException e) {
+ logger.log(Level.ERROR, e);
+ result.put(Constants.REASON, e.getMessage());
+ }
+ return result;
+ }
+
private JSONObject createProject(String request) {
JSONObject result = new JSONObject();
JSONObject json = new JSONObject(request);
@@ -1486,9 +1522,9 @@ private JSONObject generateStatistics(String request) {
try {
JSONObject json = new JSONObject(request);
String project = json.getString("project");
+ Map projects = getProjects();
if (!projectStores.containsKey(project)) {
try {
- Map projects = getProjects();
Project prj = projects.get(project);
XliffStore store = new XliffStore(prj.getXliff(), prj.getSourceLang().getCode(),
prj.getTargetLang().getCode());
@@ -1500,7 +1536,7 @@ private JSONObject generateStatistics(String request) {
return result;
}
}
- String analysis = projectStores.get(project).generateStatistics();
+ String analysis = projectStores.get(project).generateStatistics(projects.get(project).getDescription());
result.put("analysis", analysis);
} catch (SQLException | JSONException | SAXException | IOException | ParserConfigurationException
| URISyntaxException e) {
diff --git a/src/com/maxprograms/swordfish/models/Project.java b/src/com/maxprograms/swordfish/models/Project.java
index eca3fec..f61964c 100644
--- a/src/com/maxprograms/swordfish/models/Project.java
+++ b/src/com/maxprograms/swordfish/models/Project.java
@@ -200,10 +200,18 @@ public String getClient() {
return client;
}
+ public void setClient(String client) {
+ this.client = client;
+ }
+
public String getSubject() {
return subject;
}
+ public void setSubject(String subject) {
+ this.subject = subject;
+ }
+
@Override
public int compareTo(Project o) {
return creationDate.compareTo(o.getCreationDate());
diff --git a/src/com/maxprograms/swordfish/xliff/XliffStore.java b/src/com/maxprograms/swordfish/xliff/XliffStore.java
index 6aa0950..47888f3 100644
--- a/src/com/maxprograms/swordfish/xliff/XliffStore.java
+++ b/src/com/maxprograms/swordfish/xliff/XliffStore.java
@@ -12,9 +12,12 @@
package com.maxprograms.swordfish.xliff;
+import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.net.URISyntaxException;
@@ -191,6 +194,44 @@ protected void xFunc() throws SQLException {
createTables();
}
+ // check if chars column exists in segments table
+
+ String sql = "PRAGMA table_info(segments);";
+ boolean charsExists = false;
+ try (Statement st = conn.createStatement()) {
+ try (ResultSet rs = st.executeQuery(sql)) {
+ while (rs.next()) {
+ if ("chars".equals(rs.getString(2))) {
+ charsExists = true;
+ break;
+ }
+ }
+ }
+ }
+ if (!charsExists) {
+ sql = "ALTER TABLE segments ADD COLUMN chars INTEGER DEFAULT 0;";
+ try (Statement st = conn.createStatement()) {
+ st.execute(sql);
+ }
+ conn.commit();
+ sql = "UPDATE segments SET chars = ? WHERE sourceText = ?;";
+ try (PreparedStatement prep = conn.prepareStatement(sql)) {
+ sql = "SELECT sourceText FROM segments WHERE type='S';";
+ try (Statement st = conn.createStatement()) {
+ try (ResultSet rs = st.executeQuery(sql)) {
+ while (rs.next()) {
+ String sourceText = rs.getString(1);
+ int chars = sourceText.length() - spaces(sourceText);
+ prep.setInt(1, chars);
+ prep.setString(2, sourceText);
+ prep.execute();
+ }
+ }
+ }
+ }
+ conn.commit();
+ }
+
getUnitData = conn.prepareStatement("SELECT data, compressed FROM units WHERE file=? AND unitId=?");
getSource = conn.prepareStatement(
"SELECT source, sourceText, state, translate FROM segments WHERE file=? AND unitId=? AND segId=?");
@@ -307,7 +348,7 @@ private void parseDocument() throws SQLException, IOException {
insertFile = conn.prepareStatement("INSERT INTO files (id, name) VALUES (?,?)");
insertUnit = conn.prepareStatement("INSERT INTO units (file, unitId, data, compressed) VALUES (?,?,?,?)");
insertSegmentStmt = conn.prepareStatement(
- "INSERT INTO segments (file, unitId, segId, type, state, child, translate, tags, space, source, sourceText, target, targetText, words) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
+ "INSERT INTO segments (file, unitId, segId, type, state, child, translate, tags, space, source, sourceText, target, targetText, words, chars) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
insertNoteStmt = conn
.prepareStatement("INSERT INTO notes (file, unitId, segId, noteId, note) values (?,?,?,?,?)");
recurse(document.getRootElement());
@@ -500,9 +541,20 @@ private synchronized void insertSegment(String file, String unit, String segment
insertSegmentStmt.setString(12, (target != null ? target.toString() : ""));
insertSegmentStmt.setString(13, (target != null ? XliffUtils.pureText(target) : ""));
insertSegmentStmt.setInt(14, type.equals("S") ? RepetitionAnalysis.wordCount(pureSource, srcLang) : 0);
+ insertSegmentStmt.setInt(15, type.equals("S") ? (pureSource.length() - spaces(pureSource)) : 0);
insertSegmentStmt.execute();
}
+ private int spaces(String text) {
+ int count = 0;
+ for (int i = 0; i < text.length(); i++) {
+ if (Character.isWhitespace(text.charAt(i))) {
+ count++;
+ }
+ }
+ return count;
+ }
+
private void insertMatch(String file, String unit, Element match) throws SQLException {
Element originalData = match.getChild("originalData");
Element source = match.getChild("source");
@@ -1964,8 +2016,7 @@ public void exportMatches(String output, String description, String client, Stri
}
}
- public void exportTerms(String output, String description, String subject)
- throws SQLException, IOException, ParserConfigurationException {
+ public void exportTerms(String output, String description, String subject) throws SQLException, IOException {
Element descrip = null;
if (!subject.isBlank()) {
descrip = new Element("descrip");
@@ -2856,7 +2907,7 @@ public void acceptAll100Matches() throws SQLException, SAXException, IOException
}
}
- public String generateStatistics()
+ public String generateStatistics(String projectName)
throws SQLException, SAXException, IOException, ParserConfigurationException, URISyntaxException {
getPreferences();
updateXliff();
@@ -2865,15 +2916,17 @@ public String generateStatistics()
Map map = new HashMap<>();
Map> filesMap = new HashMap<>();
- String sql = "SELECT file, SUM(words), COUNT(*) FROM segments WHERE type='S' GROUP BY file";
+ String sql = "SELECT file, SUM(words), SUM(chars), COUNT(*) FROM segments WHERE type='S' GROUP BY file";
try (ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
String fileId = rs.getString(1);
JSONObject json = new JSONObject();
int words = rs.getInt(2);
- int segments = rs.getInt(3);
+ int chars = rs.getInt(3);
+ int segments = rs.getInt(4);
json.put("file", fileId);
json.put("words", words);
+ json.put("chars", chars);
json.put("segments", segments);
map.put(fileId, json);
}
@@ -2897,35 +2950,41 @@ public String generateStatistics()
}
}
- sql = "SELECT file, SUM(words), COUNT(*) FROM segments WHERE type = 'S' AND targettext = '' GROUP BY file";
+ sql = "SELECT file, SUM(words), SUM(chars), COUNT(*) FROM segments WHERE type = 'S' AND targettext = '' GROUP BY file";
try (ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
String fileId = rs.getString(1);
int untranslated = rs.getInt(2);
- int untranslatedSegments = rs.getInt(3);
+ int untranslatedChars = rs.getInt(3);
+ int untranslatedSegments = rs.getInt(4);
map.get(fileId).put("untranslated", untranslated);
+ map.get(fileId).put("untranslatedChars", untranslatedChars);
map.get(fileId).put("untranslatedSegments", untranslatedSegments);
}
}
- sql = "SELECT file, SUM(words), COUNT(*) FROM segments WHERE type = 'S' AND targettext <> '' GROUP BY file";
+ sql = "SELECT file, SUM(words), SUM(chars), COUNT(*) FROM segments WHERE type = 'S' AND targettext <> '' GROUP BY file";
try (ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
String fileId = rs.getString(1);
int translated = rs.getInt(2);
- int translatedSegments = rs.getInt(3);
+ int translatedChars = rs.getInt(3);
+ int translatedSegments = rs.getInt(4);
map.get(fileId).put("translated", translated);
+ map.get(fileId).put("translatedChars", translatedChars);
map.get(fileId).put("translatedSegments", translatedSegments);
}
}
- sql = "SELECT file, SUM(words), COUNT(*) FROM segments WHERE type = 'S' AND state = 'final' GROUP BY file";
+ sql = "SELECT file, SUM(words), SUM(chars), COUNT(*) FROM segments WHERE type = 'S' AND state = 'final' GROUP BY file";
try (ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
String fileId = rs.getString(1);
int confirmed = rs.getInt(2);
- int confirmedSegments = rs.getInt(3);
+ int confirmedChars = rs.getInt(3);
+ int confirmedSegments = rs.getInt(4);
map.get(fileId).put("confirmed", confirmed);
+ map.get(fileId).put("confirmedChars", confirmedChars);
map.get(fileId).put("confirmedSegments", confirmedSegments);
}
}
@@ -3020,13 +3079,15 @@ public String generateStatistics()
}
}
- sql = "SELECT file, SUM(words), COUNT(*) FROM segments WHERE type = 'S' AND translate = 'N' GROUP BY file";
+ sql = "SELECT file, SUM(words), SUM(chars), COUNT(*) FROM segments WHERE type = 'S' AND translate = 'N' GROUP BY file";
try (ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
String fileId = rs.getString(1);
- int confirmed = rs.getInt(2);
- int confirmedSegments = rs.getInt(3);
- map.get(fileId).put("locked", confirmed);
+ int locked = rs.getInt(2);
+ int lockedChars = rs.getInt(3);
+ int confirmedSegments = rs.getInt(4);
+ map.get(fileId).put("locked", locked);
+ map.get(fileId).put("lockedChars", lockedChars);
map.get(fileId).put("lockedSegments", confirmedSegments);
}
}
@@ -3044,10 +3105,18 @@ public String generateStatistics()
json.put("translated", 0);
map.put(key, json);
}
+ if (!json.has("translatedChars")) {
+ json.put("translatedChars", 0);
+ map.put(key, json);
+ }
if (!json.has("confirmed")) {
json.put("confirmed", 0);
map.put(key, json);
}
+ if (!json.has("confirmedChars")) {
+ json.put("confirmedChars", 0);
+ map.put(key, json);
+ }
if (!json.has("translatedSegments")) {
json.put("translatedSegments", 0);
map.put(key, json);
@@ -3056,6 +3125,10 @@ public String generateStatistics()
json.put("untranslatedSegments", 0);
map.put(key, json);
}
+ if (!json.has("untranslatedChars")) {
+ json.put("untranslatedChars", 0);
+ map.put(key, json);
+ }
if (!json.has("confirmedSegments")) {
json.put("confirmedSegments", 0);
map.put(key, json);
@@ -3064,12 +3137,33 @@ public String generateStatistics()
json.put("locked", 0);
map.put(key, json);
}
+ if (!json.has("lockedChars")) {
+ json.put("lockedChars", 0);
+ map.put(key, json);
+ }
if (!json.has("lockedSegments")) {
json.put("lockedSegments", 0);
map.put(key, json);
}
}
+ String css = "";
+ try (InputStream is = XliffStore.class.getResourceAsStream("styles.css")) {
+ StringBuffer sb = new StringBuffer();
+ try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {
+ try (BufferedReader br = new BufferedReader(reader)) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ if (!sb.isEmpty()) {
+ sb.append("\n");
+ }
+ sb.append(line);
+ }
+ }
+ }
+ css = sb.toString();
+ }
+
File log = new File(file.getAbsolutePath() + ".log.html");
try (FileOutputStream out = new FileOutputStream(log)) {
@@ -3079,49 +3173,12 @@ public String generateStatistics()
writeString(out, " \n");
writeString(out, " Project Statistics\n");
writeString(out, " \n");
writeString(out, "\n");
writeString(out, "\n");
- writeString(out, "
" + XMLUtils.cleanText(file.getName()) + "
\n");
+ writeString(out, "
" + XMLUtils.cleanText(projectName) + "
\n");
Set files = new TreeSet<>();
files.addAll(filesMap.keySet());
@@ -3144,7 +3201,7 @@ public String generateStatistics()
writeString(out, "
\n");
writeString(out, "
#
" + Messages.getString("XliffStore.8") + "
"
+ Messages.getString("XliffStore.9")
- + "
100%
95% - 99%
85% - 95%
75% - 84%
50% - 84%
"
+ + "
100%
95% - 99%
85% - 94%
75% - 84%
50% - 74%
"
+ Messages.getString("XliffStore.10") + "
" + Messages.getString("XliffStore.11")
+ "
" + Messages.getString("XliffStore.12") + "
\n");
while (it.hasNext()) {
@@ -3229,7 +3286,7 @@ public String generateStatistics()
writeString(out, "