Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.hl7.fhir.utilities.i18n.POGenerator Maven / Gradle / Ivy
package org.hl7.fhir.utilities.i18n;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hl7.fhir.utilities.StringPair;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.filesystem.DirectoryVisitor;
import org.hl7.fhir.utilities.filesystem.DirectoryVisitor.IDirectoryVisitorImplementation;
import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
/**
* This class checks that all the i18n constants and declarations are consistent,
* and then generates / updates the .po source files, and then updates the .properties files
*
* It needs to be run whenever
* (a) New constants are added to the java code
* (b) An implementer contributes translations in a .po source file
*
* It takes 3 parameters:
* * path to the local copy of the core repo
* * path to the local copy of the ig-publisher repo
* * path to the local copy of the fhirserver repo
*/
public class POGenerator {
public class PropertyValue extends StringPair {
private boolean used;
public PropertyValue(String name, String value) {
super(name, value);
// TODO Auto-generated constructor stub
}
public String getBaseName() {
String res = getName();
if (res.endsWith("_one")) {
res = res.substring(0, res.length()-4);
} else if (res.endsWith("_other")) {
res = res.substring(0, res.length()-6);
}
return res;
}
}
public class ConstantDefinition {
private String name;
private String sname;
private boolean defined;
private boolean used;
}
public class POObjectSorter implements Comparator {
@Override
public int compare(POObject o1, POObject o2) {
return o1.id.compareTo(o2.id);
}
}
private class POObject {
private boolean orphan = true;
private boolean duplicate;
private String id;
private String msgid;
private String oldMsgId;
private String msgidPlural;
private String comment;
private List msgstr = new ArrayList();
}
public static void main(String[] args) throws IOException {
new POGenerator().execute(args[0], args[1], args[2]);
}
private List prefixes = new ArrayList<>();
private int noTrans = 0;
private void execute(String core, String igpub, String pascal) throws IOException {
String source = Utilities.path(core, "/org.hl7.fhir.utilities/src/main/resources");
if (checkState(source, core, igpub, pascal)) {
generate(source, "rendering-phrases.properties", "rendering-phrases-en.po", null, 2);
generate(source, "rendering-phrases.properties", "rendering-phrases-de.po", "rendering-phrases_de.properties", 2);
generate(source, "rendering-phrases.properties", "rendering-phrases-es.po", "rendering-phrases_es.properties", 3);
generate(source, "rendering-phrases.properties", "rendering-phrases-ja.po", "rendering-phrases_ja.properties", 2);
generate(source, "rendering-phrases.properties", "rendering-phrases-nl.po", "rendering-phrases_nl.properties", 2);
generate(source, "rendering-phrases.properties", "rendering-phrases-pt_BR.po", "rendering-phrases_pt-BR.properties", 2);
generate(source, "Messages.properties", "validator-messages-en.po", null, 2);
generate(source, "Messages.properties", "validator-messages-de.po", "Messages_de.properties", 2);
generate(source, "Messages.properties", "validator-messages-es.po", "Messages_es.properties", 3);
generate(source, "Messages.properties", "validator-messages-ja.po", "Messages_ja.properties", 2);
generate(source, "Messages.properties", "validator-messages-nl.po", "Messages_nl.properties", 2);
generate(source, "Messages.properties", "validator-messages-pt_BR.po", "Messages_pt-BR.properties", 2);
System.out.println("Finished");
}
}
private boolean checkState(String source, String core, String igpub, String pascal) throws IOException {
System.out.println("Checking...");
List props = loadProperties(Utilities.path(source, "rendering-phrases.properties"), true);
List consts = loadConstants(Utilities.path(core, "/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/RenderingI18nContext.java"));
boolean ok = true;
for (ConstantDefinition cd : consts) {
boolean found = false;
for (PropertyValue p : props) {
String pn = p.getBaseName();
if (pn.equals(cd.sname) || p.getName().equals(cd.sname)) {
found = true;
p.used = true;
}
}
cd.defined = found;
}
scanJavaSource(core, consts, "RenderingI18nContext", "RenderingContext");
scanJavaSource(igpub, consts, "RenderingI18nContext", "RenderingContext");
scanPascalSource(pascal, props);
Set pns = new HashSet<>();
for (PropertyValue p : props) {
if (!p.used) {
ok = false;
System.out.println("Error: PV "+p.getName()+ " provided but not used");
}
if (!pns.contains(p.getName())) {
pns.add(p.getName());
} else {
System.out.println("Error: PV "+p.getName()+ " duplicated");
}
if (p.getValue().contains("\\n")) {
System.out.println("Error: PV "+p.getName()+ " has a \\n");
}
}
for (ConstantDefinition cd : consts) {
if (!cd.defined && !cd.used) {
System.out.println("Error: "+cd.name+ " not defined or used");
ok = false;
} else if (!cd.defined) {
ok = false;
System.out.println("Error: msg for "+cd.name+ " not found at "+cd.sname);
} else if (!cd.used) {
System.out.println("Warning: const "+cd.name+ " not used");
ok = false;
}
}
props = loadProperties(Utilities.path(source, "Messages.properties"), true);
consts = loadConstants(Utilities.path(core, "/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java"));
for (ConstantDefinition cd : consts) {
boolean found = false;
for (PropertyValue p : props) {
String pn = p.getBaseName();
if (pn.equals(cd.sname) || p.getName().equals(cd.sname)) {
found = true;
p.used = true;
}
}
cd.defined = found;
}
scanJavaSource(core, consts, "I18nConstants");
scanJavaSource(igpub, consts, "I18nConstants");
scanPascalSource(pascal, props);
pns = new HashSet<>();
for (PropertyValue p : props) {
if (!p.used) {
ok = false;
System.out.println("Error: PV "+p.getName()+ " provided but not used");
}
if (!pns.contains(p.getName())) {
pns.add(p.getName());
} else {
System.out.println("Error: PV "+p.getName()+ " duplicated");
}
if (p.getValue().contains("\\n")) {
System.out.println("Error: PV "+p.getName()+ " has a \\n");
}
}
for (ConstantDefinition cd : consts) {
if (!cd.defined && !cd.used) {
System.out.println("Error: "+cd.name+ " not defined or used");
ok = false;
} else if (!cd.defined) {
ok = false;
System.out.println("Error: msg for "+cd.name+ " not found @ "+cd.sname);
} else if (!cd.used) {
System.out.println("Warning: const "+cd.name+ " not used");
ok = false;
}
}
if (ok) {
System.out.println("No Errors Found");
} else {
System.out.println("Errors Found, so not continuing");
}
return ok;
}
private class JavaScanner implements IDirectoryVisitorImplementation {
List consts;
List names;
@Override
public boolean enterDirectory(File f) throws IOException {
return !Utilities.existsInList(f.getName(), "model", "formats");
}
@Override
public boolean visitFile(File file) throws IOException {
String source = TextFile.fileToString(file);
for (ConstantDefinition cd : consts) {
if (!cd.used) {
boolean found = false;
for (String n : names) {
if (source.contains(n+"."+cd.name+",")) {
found = true;
}
if (source.contains(n+"."+cd.name+")")) {
found = true;
}
if (source.contains(n+"."+cd.name+" :")) {
found = true;
}
if (source.contains(n+"."+cd.name+";")) {
found = true;
}
}
if (found) {
cd.used = true;
}
}
}
return true;
}
}
private void scanJavaSource(String path, List consts, String... names) throws FileNotFoundException, IOException {
JavaScanner scanner = new JavaScanner();
scanner.consts = consts;
scanner.names = new ArrayList();
for (String s : names) {
scanner.names.add(s);
}
DirectoryVisitor.visitDirectory(scanner, path, "java");
}
private class PascalScanner implements IDirectoryVisitorImplementation {
private List defs;
@Override
public boolean enterDirectory(File directory) throws IOException {
return true;
}
@Override
public boolean visitFile(File file) throws IOException {
String source = TextFile.fileToString(file);
for (PropertyValue pv : defs) {
if (!pv.used) {
boolean found = false;
String pn = pv.getBaseName();
if (source.contains("'"+pn+"'")) {
found = true;
}
if (found) {
pv.used = true;
}
}
}
return true;
}
}
private void scanPascalSource(String path, List defs) throws FileNotFoundException, IOException {
PascalScanner scanner = new PascalScanner();
scanner.defs = defs;
DirectoryVisitor.visitDirectory(scanner, path, "pas");
}
private List loadConstants(String path) throws FileNotFoundException, IOException {
List res = new ArrayList();
for (String line : TextFile.fileToLines(path)) {
if (line.contains("public static final String") && !line.trim().startsWith("//")) {
int i = line.indexOf("public static final String") + "public static final String".length();
String[] p = line.substring(i).split("\\=");
if (p.length == 2) {
String n = p[0].trim();
String v = p[1].trim().replace("\"", "").replace(";", "");
ConstantDefinition cd = new ConstantDefinition();
cd.name = n;
cd.sname = v;
res.add(cd);
}
}
}
return res;
}
private void generate(String source, String src, String dest, String tgt, int count) throws IOException {
// load the destination file
// load the source file
// update the destination object set for changes from the source file
// save the destination file
List objects = loadPOFile(Utilities.path(source, "source", dest));
List props = loadProperties(Utilities.path(source, src), false);
for (PropertyValue e : props) {
String name = e.getName();
int mode = 0;
if (name.endsWith("_one")) {
mode = 1;
name = name.substring(0, name.length() - 4);
} else if (name.endsWith("_other")) {
mode = 2;
name = name.substring(0, name.length() - 6);
}
POObject o = findObject(objects, name);
if (o == null) {
if (mode > 1) {
throw new Error("Not right");
}
o = new POObject();
o.id = name;
o.comment = name;
objects.add(o);
o.msgid = e.getValue();
o.orphan = false;
} else {
update(o, mode, e.getValue());
}
}
objects.removeIf(o -> o.orphan);
Collections.sort(objects, new POObjectSorter());
Map sources = new HashMap<>();
Set dups = new HashSet<>();
for (POObject o : objects) {
if (sources.containsKey(o.msgid)) {
Integer c = sources.get(o.msgid)+1;
sources.put(o.msgid, c);
dups.add(o.msgid);
// System.out.println("Duplicate in "+dest.substring(dest.lastIndexOf("/")+1)+": "+o.msgid+" on ("+CommaSeparatedStringBuilder.join(",", listIds(objects, o.msgid))+")");
} else {
sources.put(o.msgid, 1);
}
}
for (POObject o : objects) {
Integer c = sources.get(o.msgid);
if (c > 1) {
o.duplicate = true;
}
}
savePOFile(Utilities.path(source, "source", dest), objects, count, false);
if (tgt == null) {
savePOFile(Utilities.path(source, "source", "transifex", dest), objects, count, true);
} else {
savePOFile(Utilities.path(source, "source", "transifex", "translations", dest), objects, count, true);
}
if (tgt != null) {
savePropFile(Utilities.path(source, tgt), objects);
}
}
private Set listIds(List objects, String msgid) {
Set res = new HashSet<>();
for (POObject o : objects) {
if (o.msgid.equals(msgid)) {
res.add(o.id);
}
}
return res;
}
private void savePOFile(String dest, List objects, int count, boolean tfxMode) throws IOException {
prefixes.clear();
StringBuilder b = new StringBuilder();
for (String p : prefixes) {
b.append(p);
b.append("\r\n");
}
b.append("\r\n");
for (POObject o : objects) {
// for POEdit
if (o.oldMsgId != null) {
b.append("# "+o.comment+" (!!when last translated was: "+o.oldMsgId+")\r\n");
} else {
b.append("# "+o.comment+"\r\n");
}
b.append("#: "+o.id+"\r\n");
if (o.duplicate) {
b.append("msgctxt \""+o.id+"\"\r\n");
}
String m = tfxMode && Utilities.noString(o.msgid) ? "-- no content: do not translate #"+(++noTrans )+" --" : o.msgid;
b.append("msgid \""+wrapQuotes(m)+"\"\r\n");
if (o.msgidPlural != null) {
b.append("msgid_plural \""+wrapQuotes(o.msgidPlural)+"\"\r\n");
while (o.msgstr.size() < count) {
o.msgstr.add("");
}
for (int i = 0; i < o.msgstr.size(); i++) {
String s = o.msgstr.get(i);
// if (tfxMode && Utilities.noString(s)) {
// s = Utilities.noString(i == 0 ? o.msgid : o.msgidPlural) ? "-- no content: do not translate --" : "";
// }
b.append("msgstr["+i+"] \""+wrapQuotes(s)+"\"\r\n");
}
} else {
if (o.msgstr.size() == 0) {
b.append("msgstr \"\"\r\n");
} else {
b.append("msgstr \""+wrapQuotes(o.msgstr.get(0))+"\"\r\n");
}
}
b.append("\r\n");
}
TextFile.stringToFile(b.toString(), dest);
}
private String wrapQuotes(String s) {
return s.replace("\"", "\\\"");
}
private void update(POObject o, int mode, String value) {
o.orphan = false;
if (o.comment == null) {
o.comment = o.id;
}
if (mode == 0) {
if (!value.equals(o.msgid)) {
// the english string has changed, and the other language string is now out of date
if (o.oldMsgId != null && !o.msgstr.isEmpty()) {
o.oldMsgId = o.msgid;
}
o.msgid = value;
for (int i = 0; i < o.msgstr.size(); i++) {
if (!Utilities.noString(o.msgstr.get(i))) {
o.msgstr.set(i, "!!"+o.msgstr.get(i));
}
}
} else {
o.oldMsgId = null;
}
} else if (mode == 1) {
if (!value.equals(o.msgid)) {
// the english string has changed, and the other language string is now out of date
if (o.oldMsgId != null && !o.msgstr.isEmpty()) {
o.oldMsgId = o.msgid;
}
o.msgid = value;
if (o.msgstr.size() > 0 && !Utilities.noString(o.msgstr.get(0))) {
o.msgstr.set(0, "!!"+o.msgstr.get(0));
}
} else {
o.oldMsgId = null;
}
} else if (mode == 2) {
if (!value.equals(o.msgidPlural)) {
// the english string has changed, and the other language string is now out of date
// if (o.oldMsgId != null) {
// o.oldMsgId = o.msgid;
// }
o.msgidPlural = value;
if (o.msgstr.size() > 1 && !Utilities.noString(o.msgstr.get(1))) {
o.msgstr.set(1, "!!"+o.msgstr.get(1));
}
} else {
o.oldMsgId = null;
}
}
}
private POObject findObject(List objects, String name) {
for (POObject t : objects) {
if (t.id != null && t.id.equals(name)) {
return t;
}
}
return null;
}
private List loadPOFile(String dest) throws FileNotFoundException, IOException {
List list = new ArrayList();
POObject obj = null;
for (String line : TextFile.fileToLines(dest)) {
if (Utilities.noString(line)) {
// else
} else if (line.startsWith("#:")) {
if (obj == null || obj.id != null) {
obj = new POObject();
list.add(obj);
}
obj.id = line.substring(2).trim();
} else if (line.startsWith("# ")) {
if (obj == null || obj.comment != null) {
obj = new POObject();
list.add(obj);
}
obj.comment = line.substring(1).trim();
if (obj.comment.contains("!!when")) {
obj.oldMsgId = obj.comment.substring(obj.comment.indexOf("!!when"));
obj.comment = obj.comment.substring(0, obj.comment.indexOf("!!when")-1);
obj.oldMsgId = obj.oldMsgId.substring(obj.oldMsgId.indexOf(": ")+2).trim();
obj.oldMsgId = obj.oldMsgId.substring(0, obj.oldMsgId.length()-1);
}
} else if (obj == null) {
prefixes.add(line);
} else if (line.startsWith("#|")) {
// retired use of #| because it caused problems with the tools
obj.oldMsgId = line.substring(2).trim();
if (obj.oldMsgId.startsWith("msgid ")) {
obj.oldMsgId = trimQuotes(obj.oldMsgId.substring(6));
}
} else if (line.startsWith("msgid ")) {
obj.msgid = trimQuotes(line.substring(5).trim());
if (obj.msgid.endsWith("("+obj.id+")")) {
obj.msgid = obj.msgid.substring(0, obj.msgid.length() - (obj.id.length()+3));
}
} else if (line.startsWith("msgid_plural ")) {
obj.msgidPlural = trimQuotes(line.substring(12).trim());
} else if (line.startsWith("msgstr ")) {
obj.msgstr.add(trimQuotes(line.substring(6).trim()));
} else if (line.startsWith("msgctxt ")) {
// ignore this
} else if (line.startsWith("msgstr[")) {
String s = line.substring(7);
int i = s.indexOf("]");
int c = Integer.valueOf(s.substring(0, i));
s = trimQuotes(s.substring(i+1).trim());
while (s.startsWith("!!")) {
s = s.substring(2);
}
if (c != obj.msgstr.size()) {
System.out.println("index issue");
} else { // if (!Utilities.noString(s)) {
obj.msgstr.add(s);
}
} else {
System.out.println("unknown line: "+line);
}
}
return list;
}
private String trimQuotes(String s) {
s = s.trim();
if (s.startsWith("\"")) {
s = s.substring(1);
}
if (s.endsWith("\"")) {
s = s.substring(0, s.length()-1);
}
return s.trim().replace("\\\"", "\"");
}
private List loadProperties(String source, boolean checking) throws IOException {
List res = new ArrayList<>();
File src = ManagedFileAccess.file(source);
List lines = Files.readAllLines(src.toPath());
for (String line : lines) {
if (!line.startsWith("#") && line.contains("=")) {
String n = line.substring(0, line.indexOf("=")).trim();
String v = line.substring(line.indexOf("=")+1).trim();
if (checking || !(v.length() == 3 && v.startsWith("{") && v.endsWith("}"))) {
res.add(new PropertyValue(n, v));
}
}
}
return res;
}
private void savePropFile(String tgt, List objects) throws IOException {
String nameLine = TextFile.fileToLines(tgt)[0];
String[] parts = nameLine.substring(1).trim().split("\\=");
String[] names = parts[1].split("\\,");
StringBuilder b = new StringBuilder();
b.append(nameLine+"\r\n");
for (POObject o : objects) {
if (o.msgidPlural == null) {
String v= o.msgstr.size() > 0 ? o.msgstr.get(0) : "";
if (!Utilities.noString(v)) {
b.append(o.id+" = "+v+"\r\n");
}
} else {
for (int i = 0; i < names.length; i++) {
String v = (o.msgstr.size() > i ? o.msgstr.get(i) : "");
if (!Utilities.noString(v)) {
b.append(o.id+"_"+names[i].trim()+" = "+v+"\r\n");
}
}
}
}
TextFile.stringToFile(b.toString(), tgt);
}
}