Skip to content

Commit 7ede91b

Browse files
committed
#587 #591 interim support for single file i18n output.
1 parent d819d83 commit 7ede91b

11 files changed

Lines changed: 409 additions & 83 deletions

File tree

api/tcMenuJavaApi/src/main/java/com/thecoderscorner/menu/persist/PropertiesLocaleEnabledHandler.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,12 @@ public List<Locale> getEnabledLocales() {
9090
private Locale makeLocale(String s) {
9191
var localeStr = (s.startsWith("_")) ? s.substring(1) : s;
9292
if(localeStr.isEmpty() || localeStr.equals("==")) {
93-
return new Locale(localeStr);
93+
return Locale.of(localeStr);
9494
}
9595
if(localeStr.length() == 2) {
96-
return new Locale(localeStr);
96+
return Locale.of(localeStr);
9797
} else if(localeStr.matches("\\w\\w_\\w\\w")) {
98-
return new Locale(localeStr.substring(0, 2), localeStr.substring(3));
98+
return Locale.of(localeStr.substring(0, 2), localeStr.substring(3));
9999
} else throw new UnsupportedOperationException("Unsupported type of locale only language or languageCountry" + localeStr);
100100
}
101101

web-designer/tcmenu-code-generator/src/main/java/com/thecoderscorner/menu/editorui/generator/CodeGeneratorOptions.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public class CodeGeneratorOptions {
4040
private IoExpanderDefinitionCollection projectIoExpanders;
4141
boolean useDynamicMenus;
4242
private MenuInMenuCollection menuInMenuCollection;
43+
boolean i18nEnabled;
4344

4445
public CodeGeneratorOptions() {
4546
// for serialisation
@@ -51,12 +52,14 @@ public CodeGeneratorOptions(EmbeddedPlatform embeddedPlatform, String displayTyp
5152
EepromDefinition eepromDef, AuthenticatorDefinition authDef,
5253
IoExpanderDefinitionCollection projectIoExpanders,
5354
MenuInMenuCollection menuInMenuCollection, ProjectSaveLocation saveLocation,
54-
boolean namingRecursive, boolean useCppMain, EepromSaveMode eepromSaveMode, boolean useDynamicMenus) {
55+
boolean namingRecursive, boolean useCppMain, EepromSaveMode eepromSaveMode, boolean useDynamicMenus,
56+
boolean i18nEnabled) {
5557
this.embeddedPlatform = embeddedPlatform;
5658
this.useDynamicMenus = useDynamicMenus;
5759
this.lastDisplayUuid = displayTypeId;
5860
this.eepromSaveMode = eepromSaveMode;
5961
this.lastInputUuid = inputTypeId;
62+
this.i18nEnabled = i18nEnabled;
6063
if(remoteCapabilities != null && !remoteCapabilities.isEmpty()) {
6164
this.lastRemoteUuids = remoteCapabilities;
6265
// for backward compatibility as far as possible we save the first in the old format.

web-designer/tcmenu-code-generator/src/main/java/com/thecoderscorner/menu/editorui/generator/CodeGeneratorOptionsBuilder.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public class CodeGeneratorOptionsBuilder {
3838
private AuthenticatorDefinition authDef = new NoAuthenticatorDefinition();
3939
private MenuInMenuCollection menuInMenuDefinitions;
4040
private boolean useDynamicMenus = true;
41+
private boolean i18nEnabled = false;
4142

4243
public CodeGeneratorOptionsBuilder withExisting(CodeGeneratorOptions other) {
4344
embeddedPlatform = other.getEmbeddedPlatform();
@@ -57,13 +58,14 @@ public CodeGeneratorOptionsBuilder withExisting(CodeGeneratorOptions other) {
5758
menuInMenuDefinitions = other.getMenuInMenuCollection();
5859
eepromSaveMode = other.getEepromSaveMode();
5960
useDynamicMenus = other.isUseDynamicMenus();
61+
i18nEnabled = other.isI18nEnabled();
6062
return this;
6163
}
6264

6365
public CodeGeneratorOptions codeOptions() {
6466
return new CodeGeneratorOptions(embeddedPlatform, lastDisplayUuid, lastInputUuid, lastRemoteUuids, lastThemeUuid,
6567
lastProperties, applicationUUID, applicationName, eepromDef, authDef, expanderDefinitions,
66-
menuInMenuDefinitions, saveLocation, namingRecursive, useCppMain, eepromSaveMode, useDynamicMenus);
68+
menuInMenuDefinitions, saveLocation, namingRecursive, useCppMain, eepromSaveMode, useDynamicMenus, i18nEnabled);
6769
}
6870

6971
public CodeGeneratorOptionsBuilder withRecursiveNaming(Boolean recursive) {
@@ -149,4 +151,9 @@ public CodeGeneratorOptionsBuilder withDynamicMenus(boolean dynamicMenus) {
149151
this.useDynamicMenus = dynamicMenus;
150152
return this;
151153
}
154+
155+
public CodeGeneratorOptionsBuilder withI18nEnabled(boolean i18nEnabled) {
156+
this.i18nEnabled = i18nEnabled;
157+
return this;
158+
}
152159
}

web-designer/tcmenu-code-generator/src/main/java/com/thecoderscorner/menu/editorui/generator/core/CoreCodeGenerator.java

Lines changed: 10 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
import com.thecoderscorner.menu.editorui.generator.EepromSaveMode;
1616
import com.thecoderscorner.menu.editorui.generator.ProjectSaveLocation;
1717
import com.thecoderscorner.menu.editorui.generator.arduino.CallbackRequirement;
18+
import com.thecoderscorner.menu.editorui.generator.i18n.I18nCodeGenerator;
19+
import com.thecoderscorner.menu.editorui.generator.i18n.MultiFileI18nCodeGenerator;
20+
import com.thecoderscorner.menu.editorui.generator.i18n.SingleFileI18nCodeGenerator;
1821
import com.thecoderscorner.menu.editorui.generator.logger.GeneratedFile;
1922
import com.thecoderscorner.menu.editorui.generator.logger.UserFeedbackLogger;
2023
import com.thecoderscorner.menu.editorui.generator.parameters.CodeGeneratorCapable;
@@ -46,7 +49,6 @@
4649
import static java.time.format.DateTimeFormatter.ISO_INSTANT;
4750

4851
public abstract class CoreCodeGenerator implements CodeGenerator {
49-
public static final String GENERATED_LOCAL_HEADER = "// TcMenu Generated Locale File, do not edit this file.";
5052
public static final String LINE_BREAK = System.lineSeparator();
5153
public static final String TWO_LINES = LINE_BREAK + LINE_BREAK;
5254
public static final String NO_REMOTE_ID = "2c101fec-1f7d-4ff3-8d2b-992ad41e7fcb";
@@ -146,7 +148,13 @@ public boolean startConversion(Path directory, List<CodePluginItem> allPlugins,
146148
fileProcessor.dealWithRequiredPlugins(allPlugins, srcDir, directory, psl, previousPluginFiles);
147149

148150
if (localeHandler.isLocalSupportEnabled()) {
149-
processLocale(srcDir, genDir);
151+
I18nCodeGenerator localeCodeGen;
152+
if(options.getSaveLocation() == ONE_SINGLE_FILE_MENU_MAIN || options.getSaveLocation() == ONE_SINGLE_FILE) {
153+
localeCodeGen = new SingleFileI18nCodeGenerator(feedbackLogger, localeHandler);
154+
} else {
155+
localeCodeGen = new MultiFileI18nCodeGenerator(feedbackLogger, localeHandler);
156+
}
157+
localeCodeGen.processLocale(srcDir, genDir);
150158
}
151159

152160
internalConversion(directory, srcDir, callbackFunctions, projectName);
@@ -184,74 +192,6 @@ private MenuTreeCodeGenerator makeMenuCodeGeneratorForOptions(CodeGeneratorOptio
184192
return gen;
185193
}
186194

187-
private void processLocale(Path srcDir, boolean generated) throws IOException {
188-
feedbackLogger.debug("Starting locale processing");
189-
StringBuilder langSelectText = new StringBuilder(8192);
190-
langSelectText.append(GENERATED_LOCAL_HEADER).append(LINE_BREAK);
191-
langSelectText.append("// This is the header to include. Set TC_LOCAL_?? to a locale").append(LINE_BREAK);
192-
langSelectText.append("// or omit for the default language").append(TWO_LINES);
193-
194-
var previousLocale = localeHandler.getCurrentLocale();
195-
196-
try {
197-
String defaultLocaleFile = toSourceFile(srcDir, "_lang" + ".h", generated);
198-
localeHandler.changeLocale(PropertiesLocaleEnabledHandler.DEFAULT_LOCALE);
199-
var defaultLocaleMap = localeHandler.getUnderlyingMap();
200-
localeToCpp(defaultLocaleFile, PropertiesLocaleEnabledHandler.DEFAULT_LOCALE, defaultLocaleMap);
201-
boolean useElIf = false;
202-
203-
for (var locale : localeHandler.getEnabledLocales().stream().filter(l -> !l.getLanguage().isEmpty()).toList()) {
204-
String localeFile = toSourceFile(srcDir, "_lang_" + locale.toString() + ".h", generated);
205-
localeToCpp(localeFile, locale, defaultLocaleMap);
206-
207-
if (useElIf) {
208-
langSelectText.append("#elif");
209-
} else {
210-
langSelectText.append("#if");
211-
useElIf = true;
212-
}
213-
langSelectText.append(" defined(TC_LOCALE_").append(locale.toString().toUpperCase()).append(')')
214-
.append(LINE_BREAK);
215-
langSelectText.append("# include \"").append(Paths.get(localeFile).getFileName()).append("\"").append(LINE_BREAK);
216-
}
217-
218-
Path defPath = Paths.get(defaultLocaleFile);
219-
if (useElIf) {
220-
langSelectText.append("#else").append(LINE_BREAK).append("#include \"")
221-
.append(defPath.getFileName()).append("\"").append(LINE_BREAK)
222-
.append("#endif").append(LINE_BREAK);
223-
} else {
224-
langSelectText.append("#include \"").append(defPath.getFileName()).append("\"").append(LINE_BREAK);
225-
}
226-
227-
var selFile = Paths.get(toSourceFile(srcDir, "_langSelect" + ".h", generated));
228-
Files.writeString(selFile, langSelectText.toString(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
229-
feedbackLogger.fileModificiation(GeneratedFile.always(selFile, langSelectText.toString()));
230-
feedbackLogger.info("Wrote out the language selector - " + selFile.getFileName());
231-
feedbackLogger.debug("Finished locale processing");
232-
}
233-
finally {
234-
// put back the same locale as was selected before.
235-
localeHandler.changeLocale(previousLocale);
236-
}
237-
}
238-
239-
private void localeToCpp(String localeOutputFile, Locale locale, Map<String, String> defaultLocaleMap) throws IOException {
240-
StringBuilder sb = new StringBuilder(8192);
241-
sb.append(GENERATED_LOCAL_HEADER).append(" Locale ").append(locale).append(LINE_BREAK);
242-
sb.append("// Never include directly, always include the langSelect header").append(TWO_LINES);
243-
244-
localeHandler.changeLocale(locale);
245-
var allEntries = localeHandler.getUnderlyingMap();
246-
for(var entry : defaultLocaleMap.entrySet()) {
247-
var value = (allEntries.containsKey(entry.getKey())) ? allEntries.get(entry.getKey()) : entry.getValue();
248-
sb.append("#define ").append(asDefine(entry.getKey())).append(" \"").append(value).append("\"");
249-
sb.append(LINE_BREAK);
250-
}
251-
Path path = Path.of(localeOutputFile);
252-
Files.writeString(path, sb.toString(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
253-
feedbackLogger.fileModificiation(GeneratedFile.always(path, sb.toString()));
254-
}
255195

256196
public static String asDefine(String key) {
257197
var words = key.split("[^A-Za-z0-9]+");
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.thecoderscorner.menu.editorui.generator.i18n;
2+
3+
import com.thecoderscorner.menu.persist.LocaleMappingHandler;
4+
5+
import java.io.IOException;
6+
import java.io.OutputStreamWriter;
7+
import java.io.Writer;
8+
import java.nio.file.Path;
9+
import java.util.Comparator;
10+
import java.util.Locale;
11+
import java.util.Map;
12+
13+
import static com.thecoderscorner.menu.editorui.generator.core.CoreCodeGenerator.*;
14+
15+
public abstract class I18nCodeGenerator {
16+
public enum I18nConvertMode {
17+
LOCALE_PER_FILE,
18+
PROCESSING_DEFAULT_LOCALE,
19+
PROCESSING_FIRST_NON_DEFAULT,
20+
PROCESSING_NON_DEFAULT
21+
}
22+
23+
public static final String GENERATED_LOCAL_HEADER = "// TcMenu Generated Locale File, do not edit this file.";
24+
protected final LocaleMappingHandler localeHandler;
25+
26+
protected I18nCodeGenerator(LocaleMappingHandler localeHandler) {
27+
this.localeHandler = localeHandler;
28+
}
29+
30+
public abstract void processLocale(Path srcDir, boolean generatedDir) throws IOException;
31+
32+
protected void localeToCpp(OutputStreamWriter writer,
33+
Locale locale,
34+
Map<String, String> defaultLocaleMap,
35+
I18nConvertMode convertMode) throws IOException {
36+
if(convertMode == I18nConvertMode.LOCALE_PER_FILE) {
37+
writer.append(GENERATED_LOCAL_HEADER).append(" Locale ").append(locale.getLanguage()).append(LINE_BREAK);
38+
writer.append("// Never include directly, always include the langSelect header").append(TWO_LINES);
39+
} else if(convertMode == I18nConvertMode.PROCESSING_FIRST_NON_DEFAULT || convertMode == I18nConvertMode.PROCESSING_NON_DEFAULT) {
40+
var ifStatement = (convertMode == I18nConvertMode.PROCESSING_FIRST_NON_DEFAULT) ? "if" : "elif";
41+
writer.append(LINE_BREAK).append("// Definitions for locale ").append(locale.getLanguage()).append(LINE_BREAK);
42+
writer.append("#%s defined(TC_LOCALE_%s)".formatted(ifStatement, locale.getLanguage().toUpperCase()));
43+
writer.append(LINE_BREAK);
44+
} else if(convertMode == I18nConvertMode.PROCESSING_DEFAULT_LOCALE) {
45+
writer.append(LINE_BREAK).append("#else // default locale").append(LINE_BREAK);
46+
}
47+
var allEntries = localeHandler.getUnderlyingMap();
48+
for(var entry : defaultLocaleMap.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).toList()) {
49+
var value = (allEntries.containsKey(entry.getKey())) ? allEntries.get(entry.getKey()) : entry.getValue();
50+
writer.append("#define ").append(asDefine(entry.getKey())).append(" \"").append(value).append("\"");
51+
writer.append(LINE_BREAK);
52+
}
53+
if(convertMode == I18nConvertMode.PROCESSING_DEFAULT_LOCALE) {
54+
writer.append("#endif // locale definitions" ).append(locale.getLanguage().toUpperCase()).append(LINE_BREAK);
55+
}
56+
}
57+
58+
public void addTcLocaleStringMethod(Writer writer) throws IOException {
59+
writer.append(TWO_LINES);
60+
writer.append("// Its always better to use getTcLocaleString(string_id) method.").append(LINE_BREAK);
61+
writer.append("#define getTcLocaleString(x) (x)").append(LINE_BREAK);
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.thecoderscorner.menu.editorui.generator.i18n;
2+
3+
import com.thecoderscorner.menu.editorui.generator.logger.GeneratedFile;
4+
import com.thecoderscorner.menu.editorui.generator.logger.UserFeedbackLogger;
5+
import com.thecoderscorner.menu.persist.LocaleMappingHandler;
6+
import com.thecoderscorner.menu.persist.PropertiesLocaleEnabledHandler;
7+
8+
import java.io.ByteArrayOutputStream;
9+
import java.io.FileOutputStream;
10+
import java.io.IOException;
11+
import java.io.OutputStreamWriter;
12+
import java.nio.file.Files;
13+
import java.nio.file.Path;
14+
import java.nio.file.Paths;
15+
import java.nio.file.StandardOpenOption;
16+
import java.util.Locale;
17+
import java.util.Map;
18+
19+
import static com.thecoderscorner.menu.editorui.generator.core.CoreCodeGenerator.*;
20+
21+
public class MultiFileI18nCodeGenerator extends I18nCodeGenerator {
22+
private final UserFeedbackLogger feedbackLogger;
23+
24+
public MultiFileI18nCodeGenerator(UserFeedbackLogger feedbackLogger, LocaleMappingHandler localMapper) {
25+
super(localMapper);
26+
this.feedbackLogger = feedbackLogger;
27+
}
28+
29+
@Override
30+
public void processLocale(Path srcDir, boolean generatedDir) throws IOException {
31+
feedbackLogger.debug("Starting locale processing");
32+
var previousLocale = localeHandler.getCurrentLocale();
33+
34+
try(var baos = new ByteArrayOutputStream(8192); var langSelectWriter = new OutputStreamWriter(baos)) {
35+
langSelectWriter.append(GENERATED_LOCAL_HEADER).append(LINE_BREAK);
36+
langSelectWriter.append("// This is the header to include. Set TC_LOCAL_?? to a locale").append(LINE_BREAK);
37+
langSelectWriter.append("// or omit for the default language").append(TWO_LINES);
38+
String defaultLocaleFile = toSourceFile(srcDir, "_lang" + ".h", generatedDir);
39+
localeHandler.changeLocale(PropertiesLocaleEnabledHandler.DEFAULT_LOCALE);
40+
var defaultLocaleMap = localeHandler.getUnderlyingMap();
41+
writeOutFile(defaultLocaleFile, PropertiesLocaleEnabledHandler.DEFAULT_LOCALE, defaultLocaleMap);
42+
43+
boolean useElIf = false;
44+
45+
for (var locale : localeHandler.getEnabledLocales().stream().filter(l -> !l.getLanguage().isEmpty()).toList()) {
46+
String localeFile = toSourceFile(srcDir, "_lang_" + locale.toString() + ".h", generatedDir);
47+
localeHandler.changeLocale(locale);
48+
writeOutFile(localeFile, locale, defaultLocaleMap);
49+
50+
if (useElIf) {
51+
langSelectWriter.append("#elif");
52+
} else {
53+
langSelectWriter.append("#if");
54+
useElIf = true;
55+
}
56+
langSelectWriter.append(" defined(TC_LOCALE_").append(locale.toString().toUpperCase()).append(')')
57+
.append(LINE_BREAK);
58+
langSelectWriter.append("# include \"").append(Paths.get(localeFile).getFileName().toString())
59+
.append("\"").append(LINE_BREAK);
60+
}
61+
62+
Path defPath = Paths.get(defaultLocaleFile);
63+
if (useElIf) {
64+
langSelectWriter.append("#else").append(LINE_BREAK).append("#include \"")
65+
.append(defPath.getFileName().toString()).append("\"").append(LINE_BREAK)
66+
.append("#endif").append(LINE_BREAK);
67+
} else {
68+
langSelectWriter.append("#include \"").append(defPath.getFileName().toString()).append("\"")
69+
.append(LINE_BREAK);
70+
}
71+
addTcLocaleStringMethod(langSelectWriter);
72+
73+
langSelectWriter.flush();
74+
var selFile = Paths.get(toSourceFile(srcDir, "_langSelect" + ".h", generatedDir));
75+
Files.writeString(selFile, baos.toString(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
76+
feedbackLogger.fileModificiation(GeneratedFile.always(selFile, langSelectWriter.toString()));
77+
feedbackLogger.info("Finished processing locale, Wrote out language selector - " + selFile.getFileName());
78+
}
79+
finally {
80+
// put back the same locale as was selected before.
81+
localeHandler.changeLocale(previousLocale);
82+
}
83+
}
84+
85+
private void writeOutFile(String defaultLocaleFile, Locale defaultLocale, Map<String, String> defaultLocaleMap) {
86+
try(var writer = new OutputStreamWriter(new FileOutputStream(defaultLocaleFile))) {
87+
localeToCpp(writer, defaultLocale, defaultLocaleMap, I18nConvertMode.LOCALE_PER_FILE);
88+
}
89+
catch(IOException ex) {
90+
feedbackLogger.error("Failed to write out default locale file", ex);
91+
}
92+
}
93+
}

0 commit comments

Comments
 (0)