Skip to content

Commit 8ffe9dc

Browse files
Jacky97sJacky Tan
authored andcommitted
feat: add i18n infrastructure for multilingual output
- Add I18n utility class with UTF-8 ResourceBundle loading and locale parsing - Add English messages.properties as default resource bundle - Replace hardcoded strings in ConsoleRender, MarkdownRender, HtmlRender, AsciidocRender with I18n.getMessage() calls - Add CJK-aware display width calculation for correct console alignment - Add --lang CLI option to select output language - Set HTML lang attribute dynamically based on locale
1 parent 9048a73 commit 8ffe9dc

File tree

8 files changed

+543
-175
lines changed

8 files changed

+543
-175
lines changed

README.md

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
1-
# OpenAPI-diff
1+
# OpenAPI-diff
22

33
Compare two OpenAPI specifications (3.x) and render the difference to HTML plain text, Markdown files, or JSON files.
44

5-
[![Build](https://github.com/OpenAPITools/openapi-diff/workflows/Main%20Build/badge.svg)](https://github.com/OpenAPITools/openapi-diff/actions?query=branch%3Amaster+workflow%3A"Main+Build")
6-
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=OpenAPITools_openapi-diff&metric=alert_status)](https://sonarcloud.io/dashboard?id=OpenAPITools_openapi-diff)
5+
[Build](https://github.com/OpenAPITools/openapi-diff/actions?query=branch%3Amaster+workflow%3A"Main+Build")
6+
[Quality Gate Status](https://sonarcloud.io/dashboard?id=OpenAPITools_openapi-diff)
77

8-
[![Maven Central](https://img.shields.io/maven-central/v/org.openapitools.openapidiff/openapi-diff-core)](https://search.maven.org/artifact/org.openapitools.openapidiff/openapi-diff-core)
8+
[Maven Central](https://search.maven.org/artifact/org.openapitools.openapidiff/openapi-diff-core)
99

10-
[![Contribute with Gitpod](https://img.shields.io/badge/Contribute%20with-Gitpod-908a85?logo=gitpod)](https://gitpod.io/#https://github.com/OpenAPITools/openapi-diff)
11-
[![Join the Slack chat room](https://img.shields.io/badge/Slack-Join%20the%20chat%20room-orange)](https://join.slack.com/t/openapi-generator/shared_invite/zt-12jxxd7p2-XUeQM~4pzsU9x~eGLQqX2g)
10+
[Contribute with Gitpod](https://gitpod.io/#https://github.com/OpenAPITools/openapi-diff)
11+
[Join the Slack chat room](https://join.slack.com/t/openapi-generator/shared_invite/zt-12jxxd7p2-XUeQM~4pzsU9x~eGLQqX2g)
1212

13-
[![Docker Automated build](https://img.shields.io/docker/automated/openapitools/openapi-diff)](https://hub.docker.com/r/openapitools/openapi-diff)
14-
[![Docker Image Version](https://img.shields.io/docker/v/openapitools/openapi-diff?sort=semver)](https://hub.docker.com/r/openapitools/openapi-diff/tags)
13+
[Docker Automated build](https://hub.docker.com/r/openapitools/openapi-diff)
14+
[Docker Image Version](https://hub.docker.com/r/openapitools/openapi-diff/tags)
1515

1616
# Requirements
1717

18-
* Java 8
18+
- Java 8
1919

2020
# Feature
2121

22-
* Supports OpenAPI spec v3.0.
23-
* In-depth comparison of parameters, responses, endpoint, http method (GET,POST,PUT,DELETE...)
24-
* Supports swagger api Authorization
25-
* Render difference of property with Expression Language
26-
* HTML, Markdown, Asciidoc & JSON render
22+
- Supports OpenAPI spec v3.0.
23+
- In-depth comparison of parameters, responses, endpoint, http method (GET,POST,PUT,DELETE...)
24+
- Supports swagger api Authorization
25+
- Render difference of property with Expression Language
26+
- HTML, Markdown, Asciidoc & JSON render
2727

2828
# Maven
2929

@@ -38,11 +38,13 @@ Available on [Maven Central](https://search.maven.org/artifact/org.openapitools.
3838
```
3939

4040
# Homebrew
41+
4142
Available for Mac users on [brew](https://formulae.brew.sh/formula/openapi-diff)
4243

4344
```bash
4445
brew install openapi-diff
4546
```
47+
4648
Usage instructions in [Usage -> Command line](#command-line)
4749

4850
# Docker
@@ -211,6 +213,7 @@ ChangedOpenApi diff = OpenApiCompare.fromLocations(oldSpec, newSpec, null, optio
211213
### Render difference
212214
213215
---
216+
214217
#### HTML
215218
216219
```java
@@ -500,7 +503,8 @@ openapi-diff is released under the Apache License 2.0.
500503
501504
# Thanks
502505
503-
* Adarsh Sharma / [adarshsharma](https://github.com/adarshsharma)
504-
* Quentin Desramé / [quen2404](https://github.com/quen2404)
505-
* [Sayi](https://github.com/Sayi) for his project [swagger-diff](https://github.com/Sayi/swagger-diff)
506-
which was a source of inspiration for this tool
506+
- Adarsh Sharma / [adarshsharma](https://github.com/adarshsharma)
507+
- Quentin Desramé / [quen2404](https://github.com/quen2404)
508+
- [Sayi](https://github.com/Sayi) for his project [swagger-diff](https://github.com/Sayi/swagger-diff)
509+
which was a source of inspiration for this tool
510+

cli/src/main/java/org/openapitools/openapidiff/cli/Main.java

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.openapitools.openapidiff.core.output.AsciidocRender;
2323
import org.openapitools.openapidiff.core.output.ConsoleRender;
2424
import org.openapitools.openapidiff.core.output.HtmlRender;
25+
import org.openapitools.openapidiff.core.output.I18n;
2526
import org.openapitools.openapidiff.core.output.JsonRender;
2627
import org.openapitools.openapidiff.core.output.MarkdownRender;
2728
import org.slf4j.Logger;
@@ -143,6 +144,13 @@ public static void main(String... args) {
143144
.argName("file")
144145
.desc("export diff as json in given file")
145146
.build());
147+
options.addOption(
148+
Option.builder()
149+
.longOpt("lang")
150+
.hasArg()
151+
.argName("language")
152+
.desc("output language (en, zh-Hant, zh-CN). Default: en")
153+
.build());
146154

147155
// create the parser
148156
CommandLineParser parser = new DefaultParser();
@@ -155,9 +163,13 @@ public static void main(String... args) {
155163
}
156164
if (line.hasOption("version") || line.hasOption("v")) {
157165
String version = Main.class.getPackage().getImplementationVersion();
158-
System.out.println("openapi-diff version: " + (version != null ? version : "DEV"));
166+
System.out.println(
167+
I18n.getMessage("cli.version.prefix") + " " + (version != null ? version : "DEV"));
159168
System.exit(0);
160169
}
170+
if (line.hasOption("lang")) {
171+
I18n.setLocale(I18n.parseLocale(line.getOptionValue("lang")));
172+
}
161173
String logLevel = "ERROR";
162174
if (line.hasOption("off")) {
163175
logLevel = "OFF";
@@ -185,10 +197,7 @@ public static void main(String... args) {
185197
&& !logLevel.equalsIgnoreCase("WARN")
186198
&& !logLevel.equalsIgnoreCase("ERROR")
187199
&& !logLevel.equalsIgnoreCase("OFF")) {
188-
throw new ParseException(
189-
String.format(
190-
"Invalid log level. Expected: [TRACE, DEBUG, INFO, WARN, ERROR, OFF]. Given: %s",
191-
logLevel));
200+
throw new ParseException(I18n.getMessage("cli.invalid.log.level", logLevel));
192201
}
193202
}
194203
if (line.hasOption("state")) {
@@ -199,7 +208,7 @@ public static void main(String... args) {
199208
root.setLevel(Level.toLevel(logLevel));
200209

201210
if (line.getArgList().size() < 2) {
202-
throw new ParseException("Missing arguments");
211+
throw new ParseException(I18n.getMessage("cli.missing.arguments"));
203212
}
204213
String oldPath = line.getArgList().get(0);
205214
String newPath = line.getArgList().get(1);
@@ -223,7 +232,8 @@ public static void main(String... args) {
223232
for (String propKeyAndVal : configProps) {
224233
String[] split = propKeyAndVal.split(":");
225234
if (split.length != 2 || split[0].isEmpty() || split[1].isEmpty()) {
226-
throw new IllegalArgumentException("--config-prop unexpected format: " + propKeyAndVal);
235+
throw new IllegalArgumentException(
236+
I18n.getMessage("cli.config.prop.unexpected.format") + " " + propKeyAndVal);
227237
}
228238
optionBuilder.configProperty(split[0], split[1]);
229239
}
@@ -283,12 +293,13 @@ public static void main(String... args) {
283293
}
284294
} catch (ParseException e) {
285295
// oops, something went wrong
286-
System.err.println("Parsing failed. Reason: " + e.getMessage());
296+
System.err.println(I18n.getMessage("cli.parsing.failed") + " " + e.getMessage());
287297
printHelp(options);
288298
System.exit(2);
289299
} catch (Exception e) {
290300
System.err.println(
291-
"Unexpected exception. Reason: "
301+
I18n.getMessage("cli.unexpected.exception")
302+
+ " "
292303
+ e.getMessage()
293304
+ "\n"
294305
+ ExceptionUtils.getStackTrace(e));

core/src/main/java/org/openapitools/openapidiff/core/output/AsciidocRender.java

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public void render(ChangedOpenApi diff, OutputStreamWriter outputStreamWriter) {
4646
diff.getNewSpecOpenApi().getInfo().getVersion()));
4747
safelyAppend(outputStreamWriter, System.lineSeparator());
4848
safelyAppend(outputStreamWriter, System.lineSeparator());
49-
safelyAppend(outputStreamWriter, "NOTE: No differences. Specifications are equivalent");
49+
safelyAppend(outputStreamWriter, I18n.getMessage("note.no.differences"));
5050
} else {
5151
safelyAppend(
5252
outputStreamWriter,
@@ -63,13 +63,13 @@ public void render(ChangedOpenApi diff, OutputStreamWriter outputStreamWriter) {
6363
safelyAppend(outputStreamWriter, System.lineSeparator());
6464

6565
List<Endpoint> newEndpoints = diff.getNewEndpoints();
66-
listEndpoints(newEndpoints, "What's New", outputStreamWriter);
66+
listEndpoints(newEndpoints, I18n.getMessage("whats.new"), outputStreamWriter);
6767

6868
List<Endpoint> missingEndpoints = diff.getMissingEndpoints();
69-
listEndpoints(missingEndpoints, "What's Deleted", outputStreamWriter);
69+
listEndpoints(missingEndpoints, I18n.getMessage("whats.deleted"), outputStreamWriter);
7070

7171
List<Endpoint> deprecatedEndpoints = diff.getDeprecatedEndpoints();
72-
listEndpoints(deprecatedEndpoints, "What's Deprecated", outputStreamWriter);
72+
listEndpoints(deprecatedEndpoints, I18n.getMessage("whats.deprecated"), outputStreamWriter);
7373

7474
List<ChangedOperation> changedOperations = diff.getChangedOperations();
7575
ol_changed(changedOperations, outputStreamWriter);
@@ -78,8 +78,8 @@ public void render(ChangedOpenApi diff, OutputStreamWriter outputStreamWriter) {
7878
safelyAppend(
7979
outputStreamWriter,
8080
diff.isCompatible()
81-
? "NOTE: API changes are backward compatible"
82-
: "WARNING: API changes broke backward compatibility");
81+
? I18n.getMessage("note.backward.compatible")
82+
: I18n.getMessage("warning.broke.compatibility"));
8383
safelyAppend(outputStreamWriter, System.lineSeparator());
8484
}
8585
try {
@@ -94,7 +94,7 @@ private void ol_changed(
9494
if (null == operations || operations.isEmpty()) {
9595
return;
9696
}
97-
safelyAppend(outputStreamWriter, title("What's Changed", 2));
97+
safelyAppend(outputStreamWriter, title(I18n.getMessage("whats.changed"), 2));
9898
safelyAppend(outputStreamWriter, System.lineSeparator());
9999
for (ChangedOperation operation : operations) {
100100
String pathUrl = operation.getPathUrl();
@@ -105,30 +105,33 @@ private void ol_changed(
105105
safelyAppend(outputStreamWriter, itemEndpoint(method, pathUrl, desc));
106106
safelyAppend(outputStreamWriter, System.lineSeparator());
107107
if (result(operation.getOperationId()).isDifferent()) {
108-
safelyAppend(outputStreamWriter, "* Operation ID:");
108+
safelyAppend(outputStreamWriter, "* " + I18n.getMessage("operation.id") + ":");
109109
safelyAppend(outputStreamWriter, System.lineSeparator());
110110
safelyAppend(
111111
outputStreamWriter,
112112
String.format(
113-
"** Changed %s to %s",
114-
operation.getOperationId().getLeft(), operation.getOperationId().getRight()));
113+
"** %s %s %s %s",
114+
I18n.getMessage("action.changed"),
115+
operation.getOperationId().getLeft(),
116+
I18n.getMessage("to"),
117+
operation.getOperationId().getRight()));
115118
safelyAppend(outputStreamWriter, System.lineSeparator());
116119
}
117120
if (result(operation.getParameters()).isDifferent()) {
118-
safelyAppend(outputStreamWriter, "* Parameter:");
121+
safelyAppend(outputStreamWriter, "* " + I18n.getMessage("parameter") + ":");
119122
safelyAppend(outputStreamWriter, System.lineSeparator());
120123
safelyAppend(outputStreamWriter, ul_param(operation.getParameters()));
121124
safelyAppend(outputStreamWriter, System.lineSeparator());
122125
}
123126
if (operation.resultRequestBody().isDifferent()) {
124-
safelyAppend(outputStreamWriter, "* Request:");
127+
safelyAppend(outputStreamWriter, "* " + I18n.getMessage("request") + ":");
125128
safelyAppend(outputStreamWriter, System.lineSeparator());
126129
safelyAppend(
127130
outputStreamWriter, ul_content(operation.getRequestBody().getContent(), true, 2));
128131
safelyAppend(outputStreamWriter, System.lineSeparator());
129132
}
130133
if (operation.resultApiResponses().isDifferent()) {
131-
safelyAppend(outputStreamWriter, "* Return Type:");
134+
safelyAppend(outputStreamWriter, "* " + I18n.getMessage("return.type") + ":");
132135
safelyAppend(outputStreamWriter, System.lineSeparator());
133136
safelyAppend(outputStreamWriter, ul_response(operation.getApiResponses()));
134137
safelyAppend(outputStreamWriter, System.lineSeparator());
@@ -142,13 +145,15 @@ private String ul_response(ChangedApiResponse changedApiResponse) {
142145
Map<String, ChangedResponse> changedResponses = changedApiResponse.getChanged();
143146
StringBuilder sb = new StringBuilder();
144147
for (String propName : addResponses.keySet()) {
145-
sb.append(itemResponse("** Add ", propName));
148+
sb.append(itemResponse("** " + I18n.getMessage("action.add") + " ", propName));
146149
}
147150
for (String propName : delResponses.keySet()) {
148-
sb.append(itemResponse("** Deleted ", propName));
151+
sb.append(itemResponse("** " + I18n.getMessage("action.deleted") + " ", propName));
149152
}
150153
for (Entry<String, ChangedResponse> entry : changedResponses.entrySet()) {
151-
sb.append(itemChangedResponse("** Changed ", entry.getKey(), entry.getValue()));
154+
sb.append(
155+
itemChangedResponse(
156+
"** " + I18n.getMessage("action.changed") + " ", entry.getKey(), entry.getValue()));
152157
}
153158
return sb.toString();
154159
}
@@ -165,7 +170,9 @@ private String itemResponse(String title, String code) {
165170

166171
private String itemChangedResponse(String title, String contentType, ChangedResponse response) {
167172
return itemResponse(title, contentType)
168-
+ "** Media types:"
173+
+ "** "
174+
+ I18n.getMessage("media.types")
175+
+ ":"
169176
+ System.lineSeparator()
170177
+ ul_content(response.getContent(), false, 3);
171178
}
@@ -176,15 +183,19 @@ private String ul_content(ChangedContent changedContent, boolean isRequest, int
176183
return sb.toString();
177184
}
178185
for (String propName : changedContent.getIncreased().keySet()) {
179-
sb.append(itemContent("Added ", propName, indent));
186+
sb.append(itemContent(I18n.getMessage("action.added") + " ", propName, indent));
180187
}
181188
for (String propName : changedContent.getMissing().keySet()) {
182-
sb.append(itemContent("Deleted ", propName, indent));
189+
sb.append(itemContent(I18n.getMessage("action.deleted") + " ", propName, indent));
183190
}
184191
for (String propName : changedContent.getChanged().keySet()) {
185192
sb.append(
186193
itemContent(
187-
"Changed ", propName, indent, changedContent.getChanged().get(propName), isRequest));
194+
I18n.getMessage("action.changed") + " ",
195+
propName,
196+
indent,
197+
changedContent.getChanged().get(propName),
198+
isRequest));
188199
}
189200
return sb.toString();
190201
}
@@ -201,8 +212,11 @@ private String itemContent(
201212
boolean isRequest) {
202213
StringBuilder sb = new StringBuilder();
203214
sb.append(itemContent(title, contentType, indent))
204-
.append(itemContent("Schema:", "", indent))
205-
.append(changedMediaType.isCompatible() ? "Backward compatible" : "Broken compatibility")
215+
.append(itemContent(I18n.getMessage("schema") + ":", "", indent))
216+
.append(
217+
changedMediaType.isCompatible()
218+
? I18n.getMessage("backward.compatible")
219+
: I18n.getMessage("broken.compatibility"))
206220
.append(System.lineSeparator());
207221
if (!changedMediaType.isCompatible() && changedMediaType.getSchema() != null) {
208222
sb.append(incompatibilities(changedMediaType.getSchema()));
@@ -221,13 +235,17 @@ private String incompatibilities(String propName, final ChangedSchema schema) {
221235
}
222236
if (schema.isCoreChanged() == DiffResult.INCOMPATIBLE && schema.isChangedType()) {
223237
String type = type(schema.getOldSchema()) + " -> " + type(schema.getNewSchema());
224-
sb.append(property(propName, "Changed property type", type));
238+
sb.append(property(propName, I18n.getMessage("changed.property.type"), type));
225239
sb.append(System.lineSeparator());
226240
sb.append(System.lineSeparator());
227241
}
228242
String prefix = propName.isEmpty() ? "" : propName + ".";
229243
sb.append(
230-
properties(prefix, "Missing property", schema.getMissingProperties(), schema.getContext()));
244+
properties(
245+
prefix,
246+
I18n.getMessage("missing.property"),
247+
schema.getMissingProperties(),
248+
schema.getContext()));
231249
schema
232250
.getChangedProperties()
233251
.forEach((name, property) -> sb.append(incompatibilities(prefix + name, property)));
@@ -286,26 +304,34 @@ private String ul_param(ChangedParameters changedParameters) {
286304
List<ChangedParameter> changed = changedParameters.getChanged();
287305
StringBuilder sb = new StringBuilder();
288306
for (Parameter param : addParameters) {
289-
sb.append(itemParam("** Add ", param));
307+
sb.append(itemParam("** " + I18n.getMessage("action.add") + " ", param));
290308
}
291309
for (ChangedParameter param : changed) {
292310
sb.append(li_changedParam(param));
293311
}
294312
for (Parameter param : delParameters) {
295-
sb.append(itemParam("** Delete ", param));
313+
sb.append(itemParam("** " + I18n.getMessage("action.delete") + " ", param));
296314
}
297315
return sb.toString();
298316
}
299317

300318
private String itemParam(String title, Parameter param) {
301-
return title + param.getName() + " in " + param.getIn() + System.lineSeparator();
319+
return title
320+
+ param.getName()
321+
+ " "
322+
+ I18n.getMessage("in")
323+
+ " "
324+
+ param.getIn()
325+
+ System.lineSeparator();
302326
}
303327

304328
private String li_changedParam(ChangedParameter changeParam) {
305329
if (changeParam.isDeprecated()) {
306-
return itemParam("** Deprecated ", changeParam.getNewParameter());
330+
return itemParam(
331+
"** " + I18n.getMessage("action.deprecated") + " ", changeParam.getNewParameter());
307332
} else {
308-
return itemParam("** Changed ", changeParam.getNewParameter());
333+
return itemParam(
334+
"** " + I18n.getMessage("action.changed") + " ", changeParam.getNewParameter());
309335
}
310336
}
311337

0 commit comments

Comments
 (0)