Skip to content

Commit 72d3983

Browse files
authored
ReportNode first i18n API (#3299)
* Add ResourceBundle in powsybl-commons and partly use it in UCTE * Add withLocaleMessageTemplate and deprecate withMessageTemplate * Add unit tests Signed-off-by: Florian Dupuy <[email protected]>
1 parent ac44e44 commit 72d3983

19 files changed

+164
-19
lines changed

commons/src/main/java/com/powsybl/commons/report/AbstractReportNodeAdderOrBuilder.java

+15
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import java.util.LinkedHashMap;
1111
import java.util.Map;
12+
import java.util.ResourceBundle;
1213
import java.util.Objects;
1314

1415
/**
@@ -21,6 +22,7 @@ public abstract class AbstractReportNodeAdderOrBuilder<T extends ReportNodeAdder
2122
protected String messageTemplate;
2223
protected boolean withTimestamp = false;
2324
protected String timestampPattern;
25+
protected String bundleBaseName;
2426

2527
@Override
2628
public T withMessageTemplate(String key, String messageTemplate) {
@@ -29,6 +31,13 @@ public T withMessageTemplate(String key, String messageTemplate) {
2931
return self();
3032
}
3133

34+
@Override
35+
public T withLocaleMessageTemplate(String key, String bundleBaseName) {
36+
this.key = key;
37+
this.bundleBaseName = bundleBaseName;
38+
return self();
39+
}
40+
3241
@Override
3342
public T withTypedValue(String key, String value, String type) {
3443
values.put(key, TypedValue.of(value, type));
@@ -127,5 +136,11 @@ public T withTimestamp(String pattern) {
127136
return self();
128137
}
129138

139+
protected String getMessageTemplate(TreeContext treeContext) {
140+
return bundleBaseName != null
141+
? ResourceBundle.getBundle(bundleBaseName, treeContext.getLocale()).getString(key)
142+
: messageTemplate;
143+
}
144+
130145
protected abstract T self();
131146
}

commons/src/main/java/com/powsybl/commons/report/ReportNodeAdderOrBuilder.java

+13
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,22 @@ public interface ReportNodeAdderOrBuilder<T extends ReportNodeAdderOrBuilder<T>>
1919
* the values mentioned being the values of corresponding {@link ReportNode} and the values of any
2020
* {@link ReportNode} ancestor of the created {@link ReportNode}
2121
* @return a reference to this object
22+
* @deprecated Use {@link #withLocaleMessageTemplate} instead, adding templates to a {@link java.util.ResourceBundle}
2223
*/
24+
@Deprecated(since = "6.7.0")
2325
T withMessageTemplate(String key, String messageTemplate);
2426

27+
/**
28+
* Provide the message template to build the {@link ReportNode} with, through the referred ResourcesBundle and the
29+
* key provided. The corresponding functional log may contain references to values using the <code>${key}</code>
30+
* syntax, the values mentioned being the values of corresponding {@link ReportNode} and the values of any
31+
* {@link ReportNode} ancestor of the created {@link ReportNode}.
32+
* @param key the key identifying the message template in the ResourcesBundle
33+
* @param bundleBaseName baseName for ResourcesBundle
34+
* @return a reference to this object
35+
*/
36+
T withLocaleMessageTemplate(String key, String bundleBaseName);
37+
2538
/**
2639
* Provide one typed string value to build the {@link ReportNode} with.
2740
* @param key the key for the typed string value

commons/src/main/java/com/powsybl/commons/report/ReportNodeChildAdderImpl.java

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public class ReportNodeChildAdderImpl extends AbstractReportNodeAdderOrBuilder<R
2424

2525
@Override
2626
public ReportNode add() {
27+
String messageTemplate = getMessageTemplate(parent.getTreeContext());
2728
ReportNodeImpl node = ReportNodeImpl.createChildReportNode(key, messageTemplate, values, withTimestamp, timestampPattern, parent);
2829
parent.addChild(node);
2930
return node;

commons/src/main/java/com/powsybl/commons/report/ReportNodeNoOp.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,12 @@ public ReportNode add() {
156156
}
157157

158158
@Override
159-
public ReportNodeAdder withMessageTemplate(String key, String messageTemplate) {
159+
public ReportNodeAdder withMessageTemplate(String key, String bundleBaseName) {
160+
return this;
161+
}
162+
163+
@Override
164+
public ReportNodeAdder withLocaleMessageTemplate(String key, String bundleBaseName) {
160165
return this;
161166
}
162167

commons/src/main/java/com/powsybl/commons/report/ReportNodeRootBuilderImpl.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ public ReportNodeBuilder withLocale(Locale locale) {
3333

3434
@Override
3535
public ReportNode build() {
36-
TreeContextImpl treeContext = new TreeContextImpl(locale, defaultTimestampPattern);
36+
Locale localeSetOrDefault = this.locale != null ? this.locale : ReportConstants.DEFAULT_LOCALE;
37+
TreeContextImpl treeContext = new TreeContextImpl(localeSetOrDefault, defaultTimestampPattern);
38+
String messageTemplate = getMessageTemplate(treeContext);
3739
return ReportNodeImpl.createRootReportNode(key, messageTemplate, values, withTimestamp, timestampPattern, treeContext);
3840
}
3941

commons/src/main/java/com/powsybl/commons/report/TreeContextNoOp.java

+8-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
*/
88
package com.powsybl.commons.report;
99

10+
import com.powsybl.commons.PowsyblException;
11+
1012
import java.time.format.DateTimeFormatter;
1113
import java.util.Locale;
1214
import java.util.Map;
@@ -27,12 +29,14 @@ public DateTimeFormatter getDefaultTimestampFormatter() {
2729
}
2830

2931
@Override
30-
public Locale getLocale() {
31-
return null;
32+
public void merge(TreeContext treeContext) {
33+
if (!(treeContext instanceof TreeContextNoOp)) {
34+
throw new PowsyblException("Cannot merge a TreeContextNoOp with non TreeContextNoOp");
35+
}
3236
}
3337

3438
@Override
35-
public void merge(TreeContext treeContext) {
36-
// no-op
39+
public Locale getLocale() {
40+
return Locale.getDefault();
3741
}
3842
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
negativeLineResistance=${lineId} - Real line resistance cannot be negative (${resistance} ohm)
2+
fixUcteRegulations=Fix UCTE regulations
3+
fixUcteTransformer=Fix UCTE transformers
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
negativeLineResistance=${lineId} - La résistance d'une ligne ne peut pas être négative (${resistance} ohm)
2+
fixUcteRegulations=Corrections règles UCTE
3+
fixUcteTransformer=Corrections transformateurs UCTE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fixUcteTransformer=Corrections transfo UCTE

commons/src/test/java/com/powsybl/commons/report/NoOpTest.java

+20-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
package com.powsybl.commons.report;
99

10+
import com.powsybl.commons.PowsyblException;
1011
import com.powsybl.commons.test.AbstractSerDeTest;
1112
import com.powsybl.commons.test.ComparisonUtils;
1213
import org.junit.jupiter.api.Test;
@@ -16,6 +17,7 @@
1617
import java.nio.file.Files;
1718
import java.nio.file.Path;
1819
import java.util.Collections;
20+
import java.util.Locale;
1921
import java.util.Optional;
2022

2123
import static org.junit.jupiter.api.Assertions.*;
@@ -73,12 +75,27 @@ void test() throws IOException {
7375
void testTreeContextNoOp() {
7476
assertEquals(0, TreeContextNoOp.NO_OP.getDictionary().size());
7577
assertNull(TreeContextNoOp.NO_OP.getDefaultTimestampFormatter());
76-
assertNull(TreeContextNoOp.NO_OP.getLocale());
78+
assertNotNull(TreeContextNoOp.NO_OP.getLocale());
7779

7880
TreeContextImpl treeContext = new TreeContextImpl();
7981
treeContext.addDictionaryEntry("key", "value");
80-
TreeContextNoOp.NO_OP.merge(treeContext);
81-
assertEquals(0, TreeContextNoOp.NO_OP.getDictionary().size());
82+
PowsyblException e = assertThrows(PowsyblException.class, () -> TreeContextNoOp.NO_OP.merge(treeContext));
83+
assertEquals("Cannot merge a TreeContextNoOp with non TreeContextNoOp", e.getMessage());
84+
85+
assertEquals(Locale.US, treeContext.getLocale());
86+
}
87+
88+
@Test
89+
void testTreeContextMerge() {
90+
TreeContextImpl treeContext = new TreeContextImpl();
91+
92+
assertEquals(0, treeContext.getDictionary().size());
93+
assertEquals(ReportConstants.DEFAULT_LOCALE, treeContext.getLocale());
94+
95+
TreeContextImpl treeContext2 = new TreeContextImpl();
96+
treeContext2.addDictionaryEntry("key", "value");
97+
treeContext.merge(treeContext2);
98+
assertEquals(1, treeContext.getDictionary().size());
8299
}
83100

84101
@Test

commons/src/test/java/com/powsybl/commons/report/ReportNodeTest.java

+34
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,40 @@ void testTimestamps() {
335335
assertHasTimeStamp(root4, noPatternAndLocaleFormatter);
336336
}
337337

338+
@Test
339+
void testLocaleAndi18n() {
340+
final String bundleName = "i18n.reports";
341+
342+
// Without giving a locale => default one is en_US
343+
ReportNode rootReportEnglish = ReportNode.newRootReportNode()
344+
.withLocaleMessageTemplate("rootWithValue", bundleName)
345+
.withUntypedValue("value", 4)
346+
.build();
347+
// translation should fall back to default properties as the key is not defined in the reports_en_US.properties
348+
assertEquals("Root message with value 4", rootReportEnglish.getMessage());
349+
assertEquals(Locale.US, rootReportEnglish.getTreeContext().getLocale());
350+
351+
// With french locale
352+
ReportNode rootReportFrench = ReportNode.newRootReportNode()
353+
.withLocale(Locale.FRENCH)
354+
.withLocaleMessageTemplate("rootWithValue", bundleName)
355+
.withUntypedValue("value", 4)
356+
.build();
357+
// translation should be from the reports_fr.properties file
358+
assertEquals("Message racine avec la valeur 4", rootReportFrench.getMessage());
359+
assertEquals(Locale.FRENCH, rootReportFrench.getTreeContext().getLocale());
360+
361+
// Test giving the specific France locale
362+
ReportNode rootReportFrance = ReportNode.newRootReportNode()
363+
.withLocale(Locale.FRANCE)
364+
.withLocaleMessageTemplate("rootWithValue", bundleName)
365+
.withUntypedValue("value", 4)
366+
.build();
367+
// translation should be from the reports_fr.properties file as the key is not defined in the reports_fr_FR.properties
368+
assertEquals("Message racine avec la valeur 4", rootReportFrance.getMessage());
369+
assertEquals(Locale.FRANCE, rootReportFrance.getTreeContext().getLocale());
370+
}
371+
338372
private static void assertHasNoTimeStamp(ReportNode root1) {
339373
assertFalse(root1.getValues().containsKey(ReportConstants.TIMESTAMP_KEY));
340374
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rootWithValue = Root message with value ${value}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rootWithValue = Message racine avec la valeur ${value}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

ucte/ucte-network/src/main/java/com/powsybl/ucte/network/UcteNetworkImpl.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
package com.powsybl.ucte.network;
1010

1111
import com.powsybl.commons.report.ReportNode;
12+
import com.powsybl.ucte.network.util.UcteReports;
1213

1314
import java.util.*;
1415

@@ -132,15 +133,14 @@ public void fix(ReportNode reportNode) {
132133
line.fix(linesReportNode);
133134
}
134135

135-
ReportNode transfoReportNode = reportNode.newReportNode().withMessageTemplate("fixUcteTransformer", "Fix UCTE transformers").add();
136+
ReportNode transfoReportNode = UcteReports.fixUcteTransformers(reportNode);
136137
for (UcteTransformer transfo : transformers.values()) {
137138
transfo.fix(transfoReportNode);
138139
}
139140

140-
ReportNode regulationsReportNode = reportNode.newReportNode().withMessageTemplate("fixUcteRegulations", "Fix UCTE regulations").add();
141+
ReportNode regulationsReportNode = UcteReports.fixUcteRegulations(reportNode);
141142
for (UcteRegulation regulation : regulations.values()) {
142143
regulation.fix(regulationsReportNode);
143144
}
144145
}
145-
146146
}

ucte/ucte-network/src/main/java/com/powsybl/ucte/network/UcteValidation.java

+3-7
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,21 @@
99

1010
import com.powsybl.commons.report.ReportNode;
1111
import com.powsybl.commons.report.TypedValue;
12+
import com.powsybl.ucte.network.util.UcteReports;
1213

1314
/**
1415
* @author Anne Tilloy {@literal <anne.tilloy at rte-france.com>}
1516
*/
1617
public final class UcteValidation {
1718

1819
private static final UcteLogger LOGGER = new UcteLogger();
20+
public static final String LINE_ID_KEY = "lineId";
1921

2022
public static final double ZERO_EPS = 1e-4;
2123
public static final double REACTANCE_EPS = 0.05;
2224
public static final double DU_LIMIT = 6;
2325
public static final double THETA_ABS_LIMIT = 180;
2426
public static final double N_LIMIT = 35;
25-
public static final String LINE_ID_KEY = "lineId";
2627

2728
private UcteValidation() {
2829
}
@@ -36,12 +37,7 @@ public static void checkValidLineCharacteristics(UcteLine line, ReportNode repor
3637
case REAL_ELEMENT_IN_OPERATION:
3738
case REAL_ELEMENT_OUT_OF_OPERATION:
3839
if (line.getResistance() < ZERO_EPS) {
39-
reportNode.newReportNode()
40-
.withMessageTemplate("negativeLineResistance", "${lineId} - Real line resistance cannot be negative (${resistance} ohm)")
41-
.withUntypedValue(LINE_ID_KEY, lineId)
42-
.withTypedValue("resistance", line.getResistance(), TypedValue.RESISTANCE)
43-
.withSeverity(TypedValue.ERROR_SEVERITY)
44-
.add();
40+
UcteReports.negativeLineResistance(line, reportNode, lineId);
4541
LOGGER.error(lineId, "Real line resistance cannot be negative", line.getResistance() + " ohm");
4642
}
4743
if (Math.abs(line.getReactance()) < REACTANCE_EPS) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Copyright (c) 2025, RTE (http://www.rte-france.com)
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
* SPDX-License-Identifier: MPL-2.0
7+
*/
8+
package com.powsybl.ucte.network.util;
9+
10+
import com.powsybl.commons.report.ReportNode;
11+
import com.powsybl.commons.report.TypedValue;
12+
import com.powsybl.ucte.network.UcteLine;
13+
14+
/**
15+
* @author Florian Dupuy {@literal <florian.dupuy at rte-france.com>}
16+
*/
17+
public final class UcteReports {
18+
19+
public static final String LINE_ID_KEY = "lineId";
20+
public static final String BUNDLE_BASE_NAME = "com.powsybl.commons.reports";
21+
22+
private UcteReports() {
23+
}
24+
25+
public static ReportNode fixUcteTransformers(ReportNode reportNode) {
26+
return reportNode.newReportNode()
27+
.withLocaleMessageTemplate("fixUcteTransformer", BUNDLE_BASE_NAME)
28+
.add();
29+
}
30+
31+
public static ReportNode fixUcteRegulations(ReportNode reportNode) {
32+
return reportNode.newReportNode()
33+
.withLocaleMessageTemplate("fixUcteRegulations", BUNDLE_BASE_NAME)
34+
.add();
35+
}
36+
37+
public static void negativeLineResistance(UcteLine line, ReportNode reportNode, String lineId) {
38+
reportNode.newReportNode()
39+
.withLocaleMessageTemplate("negativeLineResistance", BUNDLE_BASE_NAME)
40+
.withUntypedValue(LINE_ID_KEY, lineId)
41+
.withTypedValue("resistance", line.getResistance(), TypedValue.RESISTANCE)
42+
.withSeverity(TypedValue.ERROR_SEVERITY)
43+
.add();
44+
}
45+
46+
}

0 commit comments

Comments
 (0)