Skip to content

Commit d5f3150

Browse files
authored
Merged Pull Request '#136 feature/country-translation->main: Add the country translation engines (ip-intelligence.translation)'
Add a new ip-intelligence.translation module with the two country translation engines, ported from the .NET FiftyOne.IpIntelligence.Translation package. Engine 1 (CountryCodeTranslationEngine) reads the weighted ISO country codes from the IP Intelligence engine and translates them to English country names using countrycodes.en_GB.yml. Its source element data key is configurable and defaults to "ip". Engine 2 (CountriesTranslationEngine) translates those English names to the browser language using the countries.<locale>.yml files, then builds complete ordered lists: the weighted countries first (most probable by weight, ties broken by translated name), followed by every remaining country alphabetically. The code and name "All" lists are index aligned so a demo can render a country dropdown with the most probable country first. The translation YAML files are wired in at build time from the IP Intelligence data submodule rather than shipped as new data.
1 parent e74c679 commit d5f3150

17 files changed

Lines changed: 2196 additions & 0 deletions
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="http://maven.apache.org/POM/4.0.0"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>pipeline.ip-intelligence</artifactId>
7+
<groupId>com.51degrees</groupId>
8+
<version>4.4.21-SNAPSHOT</version>
9+
</parent>
10+
<modelVersion>4.0.0</modelVersion>
11+
12+
<artifactId>ip-intelligence.translation</artifactId>
13+
<name>51Degrees :: IP Intelligence :: Translation</name>
14+
<description>Translation engines that turn the weighted ISO country codes
15+
from 51Degrees IP Intelligence into localized, ordered country name and
16+
code lists.</description>
17+
<url>https://51degrees.com?utm_source=maven&amp;utm_medium=package&amp;utm_campaign=ip-intelligence-java&amp;utm_content=ip-intelligence.translation-pom.xml&amp;utm_term=url</url>
18+
19+
<dependencies>
20+
<dependency>
21+
<groupId>${project.groupId}</groupId>
22+
<artifactId>ip-intelligence.shared</artifactId>
23+
<version>${project.version}</version>
24+
</dependency>
25+
<dependency>
26+
<groupId>${project.groupId}</groupId>
27+
<artifactId>pipeline.translation</artifactId>
28+
<version>${pipeline.version}</version>
29+
</dependency>
30+
31+
<dependency>
32+
<groupId>${project.groupId}</groupId>
33+
<artifactId>ip-intelligence.shared</artifactId>
34+
<version>${project.version}</version>
35+
<type>test-jar</type>
36+
<scope>test</scope>
37+
</dependency>
38+
<!-- Used only by the integration test, which drives the real
39+
on-premise engine end to end. Skipped when no data file is
40+
present. -->
41+
<dependency>
42+
<groupId>${project.groupId}</groupId>
43+
<artifactId>ip-intelligence.engine.on-premise</artifactId>
44+
<version>${project.version}</version>
45+
<scope>test</scope>
46+
</dependency>
47+
48+
<dependency>
49+
<groupId>junit</groupId>
50+
<artifactId>junit</artifactId>
51+
<scope>test</scope>
52+
</dependency>
53+
<dependency>
54+
<groupId>org.mockito</groupId>
55+
<artifactId>mockito-core</artifactId>
56+
<scope>test</scope>
57+
</dependency>
58+
</dependencies>
59+
60+
<build>
61+
<plugins>
62+
<!-- The country translation YAML files are not shipped as new data;
63+
they are wired in at build time from the IP Intelligence data
64+
submodule (mirroring the .NET project, which links the same
65+
files from ip-intelligence-data/Translations). -->
66+
<plugin>
67+
<groupId>org.apache.maven.plugins</groupId>
68+
<artifactId>maven-resources-plugin</artifactId>
69+
<version>3.3.1</version>
70+
<executions>
71+
<execution>
72+
<id>copy-translation-data</id>
73+
<phase>generate-resources</phase>
74+
<goals>
75+
<goal>copy-resources</goal>
76+
</goals>
77+
<configuration>
78+
<outputDirectory>${project.build.outputDirectory}/fiftyone/ipintelligence/translation</outputDirectory>
79+
<resources>
80+
<resource>
81+
<directory>${project.basedir}/../ip-intelligence.engine.on-premise/src/main/cxx/ip-intelligence-cxx/ip-intelligence-data/Translations</directory>
82+
<includes>
83+
<include>countrycodes.en_GB.yml</include>
84+
</includes>
85+
</resource>
86+
<resource>
87+
<directory>${project.basedir}/../ip-intelligence.engine.on-premise/src/main/cxx/ip-intelligence-cxx/ip-intelligence-data/Translations/OSM</directory>
88+
<includes>
89+
<include>countries.*.yml</include>
90+
</includes>
91+
</resource>
92+
</resources>
93+
</configuration>
94+
</execution>
95+
</executions>
96+
</plugin>
97+
</plugins>
98+
</build>
99+
</project>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/* *********************************************************************
2+
* This Original Work is copyright of 51 Degrees Mobile Experts Limited.
3+
* Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
4+
* Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
5+
*
6+
* This Original Work is licensed under the European Union Public Licence
7+
* (EUPL) v.1.2 and is subject to its terms as set out below.
8+
*
9+
* If a copy of the EUPL was not distributed with this file, You can obtain
10+
* one at https://opensource.org/licenses/EUPL-1.2.
11+
*
12+
* The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
13+
* amended by the European Commission) shall be deemed incompatible for
14+
* the purposes of the Work and the provisions of the compatibility
15+
* clause in Article 5 of the EUPL shall not apply.
16+
*
17+
* If using the Work as, or as part of, a network application, by
18+
* including the attribution notice(s) required under Article 5 of the EUPL
19+
* in the end user terms of the application under an appropriate heading,
20+
* such notice(s) shall fulfill the requirements of that article.
21+
* ********************************************************************* */
22+
23+
package fiftyone.ipintelligence.translation;
24+
25+
/**
26+
* Constants used by the country translation flow elements.
27+
*/
28+
public class Constants {
29+
30+
private Constants() {
31+
}
32+
33+
/**
34+
* Element data key used by
35+
* {@link fiftyone.ipintelligence.translation.flowelements.CountryCodeTranslationEngine}.
36+
*/
37+
public static final String COUNTRY_NAMES_KEY = "countrynames";
38+
39+
/**
40+
* Element data key used by
41+
* {@link fiftyone.ipintelligence.translation.flowelements.CountriesTranslationEngine}.
42+
*/
43+
public static final String COUNTRY_NAMES_TRANSLATED_KEY =
44+
"countrynamestranslated";
45+
46+
/**
47+
* Element data key of the IP Intelligence engine, the source of the
48+
* weighted country codes.
49+
*/
50+
public static final String IP_INTELLIGENCE_KEY = "ip";
51+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/* *********************************************************************
2+
* This Original Work is copyright of 51 Degrees Mobile Experts Limited.
3+
* Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
4+
* Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
5+
*
6+
* This Original Work is licensed under the European Union Public Licence
7+
* (EUPL) v.1.2 and is subject to its terms as set out below.
8+
*
9+
* If a copy of the EUPL was not distributed with this file, You can obtain
10+
* one at https://opensource.org/licenses/EUPL-1.2.
11+
*
12+
* The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
13+
* amended by the European Commission) shall be deemed incompatible for
14+
* the purposes of the Work and the provisions of the compatibility
15+
* clause in Article 5 of the EUPL shall not apply.
16+
*
17+
* If using the Work as, or as part of, a network application, by
18+
* including the attribution notice(s) required under Article 5 of the EUPL
19+
* in the end user terms of the application under an appropriate heading,
20+
* such notice(s) shall fulfill the requirements of that article.
21+
* ********************************************************************* */
22+
23+
package fiftyone.ipintelligence.translation;
24+
25+
import java.io.ByteArrayOutputStream;
26+
import java.io.IOException;
27+
import java.io.InputStream;
28+
import java.nio.charset.StandardCharsets;
29+
import java.util.LinkedHashMap;
30+
import java.util.Map;
31+
32+
/**
33+
* Reads the translation YAML files that are wired in from the IP Intelligence
34+
* data submodule and copied onto the classpath under this package by the build
35+
* (see the module pom).
36+
* <p>
37+
* The set of country name locale files is known and stable, so the files are
38+
* loaded by name rather than by scanning the classpath, which keeps loading
39+
* reliable whether running from a directory or a packaged jar. A file that is
40+
* not present is skipped.
41+
*/
42+
public class Resources {
43+
44+
/**
45+
* The single country-code to English-name file. This is also the list of
46+
* all known countries.
47+
*/
48+
private static final String COUNTRY_CODE_FILE = "countrycodes.en_GB.yml";
49+
50+
/**
51+
* The country English-name to localized-name files, one per shipped
52+
* locale.
53+
*/
54+
private static final String[] COUNTRY_FILES = new String[]{
55+
"countries.de_DE.yml",
56+
"countries.es_ES.yml",
57+
"countries.fr_FR.yml",
58+
"countries.it_IT.yml",
59+
"countries.nl_NL.yml",
60+
"countries.pl_PL.yml",
61+
"countries.pt_PT.yml",
62+
"countries.sv_SE.yml",
63+
"countries.tr_TR.yml",
64+
"countries.uk_UA.yml"
65+
};
66+
67+
private Resources() {
68+
}
69+
70+
/**
71+
* Get the country name translation YAML files (English name to localized
72+
* name).
73+
* @return map of file contents keyed on file name
74+
*/
75+
public static Map<String, String> getCountryResources() {
76+
Map<String, String> result = new LinkedHashMap<>();
77+
for (String name : COUNTRY_FILES) {
78+
String content = read(name);
79+
if (content != null) {
80+
result.put(name, content);
81+
}
82+
}
83+
return result;
84+
}
85+
86+
/**
87+
* Get the country code translation YAML file (ISO code to English name).
88+
* @return map of file contents keyed on file name
89+
*/
90+
public static Map<String, String> getCountryCodeResources() {
91+
Map<String, String> result = new LinkedHashMap<>();
92+
String content = read(COUNTRY_CODE_FILE);
93+
if (content != null) {
94+
result.put(COUNTRY_CODE_FILE, content);
95+
}
96+
return result;
97+
}
98+
99+
/**
100+
* Read a resource from this package as a UTF-8 string, or null if it is
101+
* not present.
102+
*/
103+
private static String read(String name) {
104+
try (InputStream stream = Resources.class.getResourceAsStream(name)) {
105+
if (stream == null) {
106+
return null;
107+
}
108+
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
109+
byte[] chunk = new byte[4096];
110+
int read;
111+
while ((read = stream.read(chunk)) != -1) {
112+
buffer.write(chunk, 0, read);
113+
}
114+
return new String(buffer.toByteArray(), StandardCharsets.UTF_8);
115+
} catch (IOException e) {
116+
return null;
117+
}
118+
}
119+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/* *********************************************************************
2+
* This Original Work is copyright of 51 Degrees Mobile Experts Limited.
3+
* Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
4+
* Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
5+
*
6+
* This Original Work is licensed under the European Union Public Licence
7+
* (EUPL) v.1.2 and is subject to its terms as set out below.
8+
*
9+
* If a copy of the EUPL was not distributed with this file, You can obtain
10+
* one at https://opensource.org/licenses/EUPL-1.2.
11+
*
12+
* The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
13+
* amended by the European Commission) shall be deemed incompatible for
14+
* the purposes of the Work and the provisions of the compatibility
15+
* clause in Article 5 of the EUPL shall not apply.
16+
*
17+
* If using the Work as, or as part of, a network application, by
18+
* including the attribution notice(s) required under Article 5 of the EUPL
19+
* in the end user terms of the application under an appropriate heading,
20+
* such notice(s) shall fulfill the requirements of that article.
21+
* ********************************************************************* */
22+
23+
package fiftyone.ipintelligence.translation.data;
24+
25+
import fiftyone.pipeline.core.data.FlowData;
26+
import fiftyone.pipeline.core.data.IWeightedValue;
27+
import fiftyone.pipeline.engines.data.AspectPropertyValue;
28+
import fiftyone.pipeline.translation.data.TranslationData;
29+
import org.slf4j.Logger;
30+
31+
import java.util.List;
32+
33+
/**
34+
* Concrete implementation of {@link ICountriesTranslationData}.
35+
*/
36+
public class CountriesTranslationData
37+
extends TranslationData
38+
implements ICountriesTranslationData {
39+
40+
/**
41+
* Construct a new instance.
42+
* @param logger used for logging
43+
* @param flowData the {@link FlowData} the element data is added to
44+
*/
45+
public CountriesTranslationData(Logger logger, FlowData flowData) {
46+
super(logger, flowData);
47+
}
48+
49+
@SuppressWarnings("unchecked")
50+
@Override
51+
public AspectPropertyValue<List<IWeightedValue<String>>>
52+
getCountryNamesGeographicalTranslated() {
53+
return getAs("CountryNamesGeographicalTranslated",
54+
AspectPropertyValue.class, List.class, IWeightedValue.class,
55+
String.class);
56+
}
57+
58+
@SuppressWarnings("unchecked")
59+
@Override
60+
public AspectPropertyValue<List<IWeightedValue<String>>>
61+
getCountryNamesPopulationTranslated() {
62+
return getAs("CountryNamesPopulationTranslated",
63+
AspectPropertyValue.class, List.class, IWeightedValue.class,
64+
String.class);
65+
}
66+
67+
@SuppressWarnings("unchecked")
68+
@Override
69+
public AspectPropertyValue<List<String>>
70+
getCountryNamesGeographicalAllTranslated() {
71+
return getAs("CountryNamesGeographicalAllTranslated",
72+
AspectPropertyValue.class, List.class, String.class);
73+
}
74+
75+
@SuppressWarnings("unchecked")
76+
@Override
77+
public AspectPropertyValue<List<String>>
78+
getCountryNamesPopulationAllTranslated() {
79+
return getAs("CountryNamesPopulationAllTranslated",
80+
AspectPropertyValue.class, List.class, String.class);
81+
}
82+
83+
@SuppressWarnings("unchecked")
84+
@Override
85+
public AspectPropertyValue<List<String>> getCountryCodesGeographicalAll() {
86+
return getAs("CountryCodesGeographicalAll",
87+
AspectPropertyValue.class, List.class, String.class);
88+
}
89+
90+
@SuppressWarnings("unchecked")
91+
@Override
92+
public AspectPropertyValue<List<String>> getCountryCodesPopulationAll() {
93+
return getAs("CountryCodesPopulationAll",
94+
AspectPropertyValue.class, List.class, String.class);
95+
}
96+
97+
@Override
98+
public String getSortingCultureUsed() {
99+
return getAs("SortingCultureUsed", String.class);
100+
}
101+
}

0 commit comments

Comments
 (0)