fiftyone-ipintelligence-translation 4.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. fiftyone_ipintelligence_translation-4.5.0/MANIFEST.in +3 -0
  2. fiftyone_ipintelligence_translation-4.5.0/PKG-INFO +70 -0
  3. fiftyone_ipintelligence_translation-4.5.0/pyproject.toml +3 -0
  4. fiftyone_ipintelligence_translation-4.5.0/readme.md +35 -0
  5. fiftyone_ipintelligence_translation-4.5.0/setup.cfg +4 -0
  6. fiftyone_ipintelligence_translation-4.5.0/setup.py +110 -0
  7. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/__init__.py +0 -0
  8. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/comparer.py +51 -0
  9. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/constants.py +34 -0
  10. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/countries_translation_engine.py +209 -0
  11. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/countries_translation_engine_builder.py +54 -0
  12. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/country_code_translation_engine.py +61 -0
  13. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/country_code_translation_engine_builder.py +52 -0
  14. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/resources.py +131 -0
  15. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.cs_CZ.yml +250 -0
  16. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.de_DE.yml +250 -0
  17. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.es_ES.yml +250 -0
  18. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.fr_FR.yml +250 -0
  19. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.hu_HU.yml +250 -0
  20. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.it_IT.yml +250 -0
  21. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.ja_JP.yml +250 -0
  22. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.nl_NL.yml +250 -0
  23. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.pl_PL.yml +250 -0
  24. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.pt_PT.yml +250 -0
  25. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.ro_RO.yml +250 -0
  26. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.sk_SK.yml +250 -0
  27. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.sv_SE.yml +250 -0
  28. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.tr_TR.yml +250 -0
  29. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.uk_UA.yml +250 -0
  30. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.vi_VN.yml +250 -0
  31. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countrycodes.en_GB.yml +250 -0
  32. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation.egg-info/PKG-INFO +70 -0
  33. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation.egg-info/SOURCES.txt +36 -0
  34. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation.egg-info/dependency_links.txt +1 -0
  35. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation.egg-info/requires.txt +3 -0
  36. fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation.egg-info/top_level.txt +1 -0
  37. fiftyone_ipintelligence_translation-4.5.0/tests/test_translation.py +452 -0
  38. fiftyone_ipintelligence_translation-4.5.0/version.txt +1 -0
@@ -0,0 +1,3 @@
1
+ include version.txt
2
+ include readme.md
3
+ recursive-include src/fiftyone_ipintelligence_translation/translations *.yml
@@ -0,0 +1,70 @@
1
+ Metadata-Version: 2.4
2
+ Name: fiftyone_ipintelligence_translation
3
+ Version: 4.5.0
4
+ Summary: Country translation engines for 51Degrees on-premise IP Intelligence. Turns the weighted ISO country codes returned by the IP Intelligence engine into localized country names and complete ordered country lists.
5
+ Home-page: https://51degrees.com/?utm_source=pypi&utm_medium=package&utm_campaign=51degrees-python&utm_content=fiftyone_ipintelligence_translation-setup.py&utm_term=url
6
+ Author: 51Degrees Engineering
7
+ Author-email: engineering@51degrees.com
8
+ License: EUPL-1.2
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Programming Language :: Python
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2)
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: fiftyone_pipeline_core
23
+ Requires-Dist: fiftyone_pipeline_translation
24
+ Requires-Dist: PyYAML
25
+ Dynamic: author
26
+ Dynamic: author-email
27
+ Dynamic: classifier
28
+ Dynamic: description
29
+ Dynamic: description-content-type
30
+ Dynamic: home-page
31
+ Dynamic: license
32
+ Dynamic: requires-dist
33
+ Dynamic: requires-python
34
+ Dynamic: summary
35
+
36
+ # IP Intelligence Country Translation
37
+
38
+ ![51Degrees](https://51degrees.com/img/logo.png?utm_source=github&utm_medium=readme&utm_campaign=51degrees-python&utm_content=fiftyone_ipintelligence_translation-readme.md&utm_term=ip-intelligence-country-translation "Data rewards the curious") **Python IP Intelligence Country Translation**
39
+
40
+ [Developer Documentation](https://51degrees.com/documentation/index.html?utm_source=github&utm_medium=readme&utm_campaign=51degrees-python&utm_content=fiftyone_ipintelligence_translation-readme.md&utm_term=ip-intelligence-country-translation "Developer Documentation")
41
+
42
+ ## Introduction
43
+
44
+ This package adds two flow elements that build on the on-premise 51Degrees IP
45
+ Intelligence engine.
46
+
47
+ On-premise IP Intelligence returns country information as weighted lists of ISO
48
+ country codes. These engines turn those codes into localized country names and
49
+ produce complete ordered lists, so an application can render a dropdown of
50
+ countries with the most probable country at the top.
51
+
52
+ * `CountryCodeTranslationEngine` translates the weighted country codes into
53
+ English country names.
54
+ * `CountriesTranslationEngine` translates the English names into the browser
55
+ language (resolved from the request evidence) and builds complete ordered
56
+ "All" lists of every known country, most probable first.
57
+
58
+ These properties are only produced for the on-premise path. The cloud service
59
+ already returns them in their final form.
60
+
61
+ ## Requirements
62
+
63
+ * Python 3.8+
64
+
65
+ ## This package fiftyone_ipintelligence_translation
66
+
67
+ Built on top of:
68
+
69
+ * [fiftyone_pipeline_core](https://pypi.org/project/fiftyone-pipeline-core/)
70
+ * [fiftyone_pipeline_translation](https://pypi.org/project/fiftyone-pipeline-translation/)
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,35 @@
1
+ # IP Intelligence Country Translation
2
+
3
+ ![51Degrees](https://51degrees.com/img/logo.png?utm_source=github&utm_medium=readme&utm_campaign=51degrees-python&utm_content=fiftyone_ipintelligence_translation-readme.md&utm_term=ip-intelligence-country-translation "Data rewards the curious") **Python IP Intelligence Country Translation**
4
+
5
+ [Developer Documentation](https://51degrees.com/documentation/index.html?utm_source=github&utm_medium=readme&utm_campaign=51degrees-python&utm_content=fiftyone_ipintelligence_translation-readme.md&utm_term=ip-intelligence-country-translation "Developer Documentation")
6
+
7
+ ## Introduction
8
+
9
+ This package adds two flow elements that build on the on-premise 51Degrees IP
10
+ Intelligence engine.
11
+
12
+ On-premise IP Intelligence returns country information as weighted lists of ISO
13
+ country codes. These engines turn those codes into localized country names and
14
+ produce complete ordered lists, so an application can render a dropdown of
15
+ countries with the most probable country at the top.
16
+
17
+ * `CountryCodeTranslationEngine` translates the weighted country codes into
18
+ English country names.
19
+ * `CountriesTranslationEngine` translates the English names into the browser
20
+ language (resolved from the request evidence) and builds complete ordered
21
+ "All" lists of every known country, most probable first.
22
+
23
+ These properties are only produced for the on-premise path. The cloud service
24
+ already returns them in their final form.
25
+
26
+ ## Requirements
27
+
28
+ * Python 3.8+
29
+
30
+ ## This package fiftyone_ipintelligence_translation
31
+
32
+ Built on top of:
33
+
34
+ * [fiftyone_pipeline_core](https://pypi.org/project/fiftyone-pipeline-core/)
35
+ * [fiftyone_pipeline_translation](https://pypi.org/project/fiftyone-pipeline-translation/)
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,110 @@
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
+ import setuptools
24
+ import io
25
+ import os
26
+ import shutil
27
+
28
+
29
+ def read(file_name):
30
+
31
+ """Read a text file and return the content as a string."""
32
+ try:
33
+ with io.open(
34
+ os.path.join(os.path.dirname(__file__), file_name), encoding="utf-8"
35
+ ) as f:
36
+ return f.read().strip()
37
+ except Exception:
38
+ return "0.0.0"
39
+
40
+
41
+ def stage_translations():
42
+
43
+ """Copy the translation YAML files shipped in the ip-intelligence-data
44
+ submodule into the package so they are included in the built distribution.
45
+ This mirrors how the .NET project embeds the same files as resources. If the
46
+ submodule is not present the package falls back to reading it at run time,
47
+ so any failure here is ignored."""
48
+
49
+ here = os.path.dirname(os.path.abspath(__file__))
50
+ source = os.path.join(here, "..", "ip-intelligence-data", "Translations")
51
+ dest = os.path.join(
52
+ here, "src", "fiftyone_ipintelligence_translation", "translations")
53
+ if not os.path.isdir(source):
54
+ return
55
+ os.makedirs(dest, exist_ok=True)
56
+
57
+ def copy_yaml(folder):
58
+ for name in os.listdir(folder):
59
+ full = os.path.join(folder, name)
60
+ if os.path.isfile(full) and name.endswith((".yml", ".yaml")):
61
+ shutil.copyfile(full, os.path.join(dest, name))
62
+
63
+ copy_yaml(source)
64
+ osm = os.path.join(source, "OSM")
65
+ if os.path.isdir(osm):
66
+ copy_yaml(osm)
67
+
68
+
69
+ try:
70
+ stage_translations()
71
+ except Exception:
72
+ pass
73
+
74
+
75
+ setuptools.setup(
76
+ name="fiftyone_ipintelligence_translation",
77
+ version=read("version.txt"),
78
+ author="51Degrees Engineering",
79
+ author_email="engineering@51degrees.com",
80
+ url="https://51degrees.com/?utm_source=pypi&utm_medium=package&utm_campaign=51degrees-python&utm_content=fiftyone_ipintelligence_translation-setup.py&utm_term=url",
81
+ description=("Country translation engines for 51Degrees on-premise IP Intelligence. Turns the weighted ISO country codes returned by the IP Intelligence engine into localized country names and complete ordered country lists."),
82
+ long_description=read("readme.md"),
83
+ long_description_content_type='text/markdown',
84
+ python_requires=">=3.10",
85
+ packages=["fiftyone_ipintelligence_translation"],
86
+ package_dir={"": "src"},
87
+ package_data={
88
+ "fiftyone_ipintelligence_translation": ["translations/*.yml"]
89
+ },
90
+ install_requires=[
91
+ "fiftyone_pipeline_core",
92
+ "fiftyone_pipeline_translation",
93
+ "PyYAML"
94
+ ],
95
+ license="EUPL-1.2",
96
+ classifiers=[
97
+ "Development Status :: 5 - Production/Stable",
98
+ "Programming Language :: Python",
99
+ "Programming Language :: Python :: 3 :: Only",
100
+ "Programming Language :: Python :: 3.10",
101
+ "Programming Language :: Python :: 3.11",
102
+ "Programming Language :: Python :: 3.12",
103
+ "Programming Language :: Python :: 3.13",
104
+ "Programming Language :: Python :: 3.14",
105
+ "License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2)",
106
+ "Operating System :: OS Independent",
107
+ "Topic :: Software Development :: Libraries :: Python Modules"
108
+ ],
109
+ include_package_data=True
110
+ )
@@ -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
+ import unicodedata
24
+
25
+
26
+ def make_comparer(culture=None):
27
+
28
+ """!
29
+ Return a sort key function for ordering country names.
30
+
31
+ Python has no stable, dependency-free, process-local culture aware string
32
+ comparer equivalent to .NET's CultureInfo / StringComparer, so a Unicode
33
+ normalized, case-insensitive key is used instead: names are accent folded
34
+ (NFKD, dropping combining marks) and case folded. This gives a deterministic
35
+ alphabetical ordering that is stable across platforms. The same key function
36
+ is used by the engine and its tests so the ordering is self-consistent.
37
+
38
+ The culture argument is accepted for parity with the resolved locale (it is
39
+ also surfaced as the SortingCultureUsed property) but does not change the
40
+ ordering.
41
+ """
42
+
43
+ def key(name):
44
+ if name is None:
45
+ return ""
46
+ decomposed = unicodedata.normalize("NFKD", str(name))
47
+ without_marks = "".join(
48
+ char for char in decomposed if not unicodedata.combining(char))
49
+ return without_marks.casefold()
50
+
51
+ return key
@@ -0,0 +1,34 @@
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
+
24
+ class Constants:
25
+
26
+ """!
27
+ Constants used by the country translation flow elements.
28
+ """
29
+
30
+ # Element data key used by CountryCodeTranslationEngine.
31
+ COUNTRY_NAMES_KEY = "countrynames"
32
+
33
+ # Element data key used by CountriesTranslationEngine.
34
+ COUNTRY_NAMES_TRANSLATED_KEY = "countrynamestranslated"
@@ -0,0 +1,209 @@
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
+ from fiftyone_pipeline_core.aspectproperty_value import AspectPropertyValue
24
+ from fiftyone_pipeline_translation.translation_engine_base import (
25
+ TranslationEngineBase)
26
+ from fiftyone_pipeline_translation.missing_translation_behavior import (
27
+ MissingTranslationBehavior)
28
+
29
+ from .constants import Constants
30
+ from .comparer import make_comparer
31
+ from .resources import get_country_resources
32
+
33
+
34
+ class CountriesTranslationEngine(TranslationEngineBase):
35
+
36
+ """!
37
+ Engine which takes the English country name properties from the
38
+ CountryCodeTranslationEngine and the weighted country code properties from
39
+ the IP Intelligence engine, translates the country names to the browser
40
+ language, and produces complete ordered lists of all countries combining the
41
+ weighted results with all known countries.
42
+
43
+ The base class handles the weighted translations
44
+ (CountryNamesGeographical / Population to their Translated variants). This
45
+ subclass adds the "All" list logic on top. Results are stored under the
46
+ Constants.COUNTRY_NAMES_TRANSLATED_KEY element data key.
47
+ """
48
+
49
+ # Output properties added by this engine on top of the base translations.
50
+ _EXTRA_PROPERTIES = [
51
+ "CountryNamesGeographicalAllTranslated",
52
+ "CountryNamesPopulationAllTranslated",
53
+ "CountryCodesGeographicalAll",
54
+ "CountryCodesPopulationAll",
55
+ "SortingCultureUsed",
56
+ ]
57
+
58
+ def __init__(self, all_countries):
59
+
60
+ """!
61
+ Constructor.
62
+
63
+ @type all_countries: list
64
+ @param all_countries: Ordered list of (code, English name) pairs for
65
+ every known country, as loaded from countrycodes.en_GB.yml.
66
+ """
67
+
68
+ super().__init__(
69
+ # Source is the output of the CountryCodeTranslationEngine.
70
+ Constants.COUNTRY_NAMES_KEY,
71
+ [
72
+ ("CountryNamesGeographical",
73
+ "CountryNamesGeographicalTranslated"),
74
+ ("CountryNamesPopulation",
75
+ "CountryNamesPopulationTranslated"),
76
+ ],
77
+ get_country_resources(),
78
+ None,
79
+ MissingTranslationBehavior.ORIGINAL)
80
+
81
+ self.datakey = Constants.COUNTRY_NAMES_TRANSLATED_KEY
82
+
83
+ if all_countries is None:
84
+ raise ValueError("all_countries must not be None.")
85
+ self._all_countries = list(all_countries)
86
+ # The set of real country codes, used to drop the IP engine's no-match
87
+ # sentinel (e.g. "Unknown") from the "All" lists.
88
+ self._valid_codes = {
89
+ code.lower() for code, _name in self._all_countries}
90
+
91
+ for name in self._EXTRA_PROPERTIES:
92
+ self.properties[name] = {"type": "object"}
93
+
94
+ def process_internal(self, flowdata):
95
+
96
+ # Let the base class produce the weighted translated names.
97
+ super().process_internal(flowdata)
98
+
99
+ element_data = flowdata.get(self.datakey)
100
+
101
+ # Resolve the translator and locale used for the "All" lists.
102
+ translator, locale = self._resolve_translator(flowdata)
103
+ culture_used = (
104
+ locale.replace("_", "-")
105
+ if translator is not None and locale else "")
106
+ element_data.contents["sortingcultureused"] = culture_used
107
+ comparer = make_comparer(culture_used or None)
108
+
109
+ # Weighted codes come directly from the IP Intelligence element data.
110
+ ip_data = self._get_ip_data(flowdata)
111
+ geo_codes = self._weighted_list(ip_data, "CountryCodesGeographical")
112
+ pop_codes = self._weighted_list(ip_data, "CountryCodesPopulation")
113
+
114
+ # Weighted translated names come from the base class output.
115
+ geo_names = self._weighted_list(
116
+ element_data, "CountryNamesGeographicalTranslated")
117
+ pop_names = self._weighted_list(
118
+ element_data, "CountryNamesPopulationTranslated")
119
+
120
+ self._build_and_store(
121
+ element_data, geo_names, geo_codes, translator, comparer,
122
+ "CountryNamesGeographicalAllTranslated",
123
+ "CountryCodesGeographicalAll")
124
+ self._build_and_store(
125
+ element_data, pop_names, pop_codes, translator, comparer,
126
+ "CountryNamesPopulationAllTranslated",
127
+ "CountryCodesPopulationAll")
128
+
129
+ return True
130
+
131
+ @staticmethod
132
+ def _get_ip_data(flowdata):
133
+ try:
134
+ return flowdata.get("ip")
135
+ except Exception:
136
+ return None
137
+
138
+ @staticmethod
139
+ def _weighted_list(data, property_name):
140
+
141
+ """Return the list of weighted {"value", "weighting"} dictionaries for
142
+ a property, or an empty list when it is missing or has no value."""
143
+
144
+ if data is None:
145
+ return []
146
+ try:
147
+ value = data.get(property_name)
148
+ except Exception:
149
+ return []
150
+ if value is None:
151
+ return []
152
+ if isinstance(value, AspectPropertyValue):
153
+ return value.value() if value.has_value() else []
154
+ return value
155
+
156
+ def _build_and_store(
157
+ self, element_data, translated_names, weighted_codes, translator,
158
+ comparer, names_property, codes_property):
159
+
160
+ """Build the complete "All" lists for one dimension and store them in
161
+ the element data."""
162
+
163
+ # The weighted countries, most probable first. Keep only real country
164
+ # codes so the IP engine's no-match sentinel never leads the list.
165
+ weighted_tuples = [
166
+ tuple_
167
+ for tuple_ in self._build_weighted_tuples(
168
+ translated_names, weighted_codes, comparer)
169
+ if tuple_[0].lower() in self._valid_codes]
170
+
171
+ used_codes = {code.lower() for code, _name in weighted_tuples}
172
+
173
+ # Every remaining known country, alphabetical by translated name.
174
+ remaining = [
175
+ (code, english)
176
+ for code, english in self._all_countries
177
+ if code.lower() not in used_codes]
178
+ if translator is not None:
179
+ remaining = [
180
+ (code, translator.translate(english) or english)
181
+ for code, english in remaining]
182
+ remaining.sort(key=lambda pair: comparer(pair[1]))
183
+
184
+ all_tuples = weighted_tuples + remaining
185
+
186
+ element_data.contents[codes_property.lower()] = AspectPropertyValue(
187
+ value=[code for code, _name in all_tuples])
188
+ element_data.contents[names_property.lower()] = AspectPropertyValue(
189
+ value=[name for _code, name in all_tuples])
190
+
191
+ @staticmethod
192
+ def _build_weighted_tuples(translated_names, codes, comparer):
193
+
194
+ """Zip the weighted translated names with the weighted codes by index,
195
+ ordered by weighting descending then translated name ascending."""
196
+
197
+ if not translated_names or not codes:
198
+ return []
199
+ count = min(len(translated_names), len(codes))
200
+ tuples = []
201
+ for index in range(count):
202
+ name_item = translated_names[index]
203
+ code_item = codes[index]
204
+ tuples.append((
205
+ code_item["value"],
206
+ name_item["value"],
207
+ name_item.get("weighting", 0)))
208
+ tuples.sort(key=lambda item: (-item[2], comparer(item[1])))
209
+ return [(code, name) for code, name, _weighting in tuples]
@@ -0,0 +1,54 @@
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
+ from .countries_translation_engine import CountriesTranslationEngine
24
+ from .resources import get_all_countries
25
+
26
+
27
+ class CountriesTranslationEngineBuilder:
28
+
29
+ """!
30
+ Builder for the CountriesTranslationEngine element. Loads the country code
31
+ resource (countrycodes.en_GB.yml) which lists all known country codes and
32
+ their English names. The country name translation resources (countries.*)
33
+ are handled by the base translation engine.
34
+ """
35
+
36
+ def __init__(self, logger_factory=None):
37
+
38
+ """!
39
+ Constructor.
40
+
41
+ @param logger_factory: Accepted for parity with the other SDKs; the
42
+ Python pipeline performs logging through the pipeline the element is
43
+ added to, so it is not required here.
44
+ """
45
+
46
+ self._logger_factory = logger_factory
47
+
48
+ def build(self):
49
+
50
+ """!
51
+ Build a new instance of CountriesTranslationEngine.
52
+ """
53
+
54
+ return CountriesTranslationEngine(get_all_countries())
@@ -0,0 +1,61 @@
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
+ from fiftyone_pipeline_translation.translation_engine_base import (
24
+ TranslationEngineBase)
25
+ from fiftyone_pipeline_translation.missing_translation_behavior import (
26
+ MissingTranslationBehavior)
27
+
28
+ from .constants import Constants
29
+ from .resources import get_country_code_resources
30
+
31
+
32
+ class CountryCodeTranslationEngine(TranslationEngineBase):
33
+
34
+ """!
35
+ Engine which takes the weighted country code properties from the IP
36
+ Intelligence engine and translates them to country names. The country names
37
+ are always in English (the fixed en_GB resource). Results are stored under
38
+ the Constants.COUNTRY_NAMES_KEY element data key.
39
+
40
+ All translation files are loaded as package resources, so no configuration
41
+ is required.
42
+ """
43
+
44
+ def __init__(self):
45
+
46
+ """!
47
+ Constructor.
48
+ """
49
+
50
+ super().__init__(
51
+ # Source element data key of the IP Intelligence engine.
52
+ "ip",
53
+ [
54
+ ("CountryCodesGeographical", "CountryNamesGeographical"),
55
+ ("CountryCodesPopulation", "CountryNamesPopulation"),
56
+ ],
57
+ get_country_code_resources(),
58
+ "en_GB",
59
+ MissingTranslationBehavior.ORIGINAL)
60
+
61
+ self.datakey = Constants.COUNTRY_NAMES_KEY