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.
- fiftyone_ipintelligence_translation-4.5.0/MANIFEST.in +3 -0
- fiftyone_ipintelligence_translation-4.5.0/PKG-INFO +70 -0
- fiftyone_ipintelligence_translation-4.5.0/pyproject.toml +3 -0
- fiftyone_ipintelligence_translation-4.5.0/readme.md +35 -0
- fiftyone_ipintelligence_translation-4.5.0/setup.cfg +4 -0
- fiftyone_ipintelligence_translation-4.5.0/setup.py +110 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/__init__.py +0 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/comparer.py +51 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/constants.py +34 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/countries_translation_engine.py +209 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/countries_translation_engine_builder.py +54 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/country_code_translation_engine.py +61 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/country_code_translation_engine_builder.py +52 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/resources.py +131 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.cs_CZ.yml +250 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.de_DE.yml +250 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.es_ES.yml +250 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.fr_FR.yml +250 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.hu_HU.yml +250 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.it_IT.yml +250 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.ja_JP.yml +250 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.nl_NL.yml +250 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.pl_PL.yml +250 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.pt_PT.yml +250 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.ro_RO.yml +250 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.sk_SK.yml +250 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.sv_SE.yml +250 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.tr_TR.yml +250 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.uk_UA.yml +250 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countries.vi_VN.yml +250 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/translations/countrycodes.en_GB.yml +250 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation.egg-info/PKG-INFO +70 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation.egg-info/SOURCES.txt +36 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation.egg-info/dependency_links.txt +1 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation.egg-info/requires.txt +3 -0
- fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation.egg-info/top_level.txt +1 -0
- fiftyone_ipintelligence_translation-4.5.0/tests/test_translation.py +452 -0
- fiftyone_ipintelligence_translation-4.5.0/version.txt +1 -0
|
@@ -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
|
+
 **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,35 @@
|
|
|
1
|
+
# IP Intelligence Country Translation
|
|
2
|
+
|
|
3
|
+
 **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,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
|
+
)
|
|
File without changes
|
|
@@ -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
|
fiftyone_ipintelligence_translation-4.5.0/src/fiftyone_ipintelligence_translation/constants.py
ADDED
|
@@ -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
|