django-lang 0.6.3__py3-none-any.whl
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.
- django_lang-0.6.3.data/data/share/django-lang/requirements/py310-django32.txt +30 -0
- django_lang-0.6.3.data/data/share/django-lang/requirements/py310-django42.txt +26 -0
- django_lang-0.6.3.data/data/share/django-lang/requirements/py310-django52.txt +26 -0
- django_lang-0.6.3.data/data/share/django-lang/requirements/py311-django42.txt +22 -0
- django_lang-0.6.3.data/data/share/django-lang/requirements/py311-django52.txt +22 -0
- django_lang-0.6.3.data/data/share/django-lang/requirements/py312-django42.txt +22 -0
- django_lang-0.6.3.data/data/share/django-lang/requirements/py312-django52.txt +22 -0
- django_lang-0.6.3.data/data/share/django-lang/requirements/py313-django52.txt +22 -0
- django_lang-0.6.3.data/data/share/django-lang/requirements/py314-django52.txt +22 -0
- django_lang-0.6.3.data/data/share/django-lang/requirements/py39-django42.txt +26 -0
- django_lang-0.6.3.dist-info/METADATA +207 -0
- django_lang-0.6.3.dist-info/RECORD +30 -0
- django_lang-0.6.3.dist-info/WHEEL +5 -0
- django_lang-0.6.3.dist-info/licenses/LICENSE +21 -0
- django_lang-0.6.3.dist-info/top_level.txt +1 -0
- lang/__init__.py +22 -0
- lang/conf.py +103 -0
- lang/context_processors.py +63 -0
- lang/defaults.py +53 -0
- lang/middleware.py +56 -0
- lang/static/lang/css/nav-link-standalone.css +27 -0
- lang/static/lang/css/nav-link.css +17 -0
- lang/static/lang/js/display-standalone-class.js +15 -0
- lang/templates/hreflang.html +8 -0
- lang/templates/lang/nav-link-standalone.html +6 -0
- lang/templates/lang/nav-link.html +14 -0
- lang/templatetags/__init__.py +0 -0
- lang/templatetags/languages_helpers.py +74 -0
- lang/templatetags/urls.py +83 -0
- lang/utils.py +35 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is autogenerated by pip-compile with Python 3.10
|
|
3
|
+
# by the following command:
|
|
4
|
+
#
|
|
5
|
+
# pip-compile --allow-unsafe --generate-hashes --output-file=requirements/py310-django32.txt requirements/runtime.in
|
|
6
|
+
#
|
|
7
|
+
asgiref==3.11.1 \
|
|
8
|
+
--hash=sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce \
|
|
9
|
+
--hash=sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133
|
|
10
|
+
# via django
|
|
11
|
+
django==3.2.25 \
|
|
12
|
+
--hash=sha256:7ca38a78654aee72378594d63e51636c04b8e28574f5505dff630895b5472777 \
|
|
13
|
+
--hash=sha256:a52ea7fcf280b16f7b739cec38fa6d3f8953a5456986944c3ca97e79882b4e38
|
|
14
|
+
# via -r requirements/runtime.in
|
|
15
|
+
emoji-country-flag==2.1.0 \
|
|
16
|
+
--hash=sha256:224b19583210901b950a624ab0b0896cdf0764b36f8bd1463868b15fb8c10f6a \
|
|
17
|
+
--hash=sha256:3b4df6a9d9961ab7716090689c2e96405baff68e46849ab2704335d904589f2a
|
|
18
|
+
# via -r requirements/runtime.in
|
|
19
|
+
pytz==2026.2 \
|
|
20
|
+
--hash=sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126 \
|
|
21
|
+
--hash=sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a
|
|
22
|
+
# via django
|
|
23
|
+
sqlparse==0.5.5 \
|
|
24
|
+
--hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \
|
|
25
|
+
--hash=sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e
|
|
26
|
+
# via django
|
|
27
|
+
typing-extensions==4.15.0 \
|
|
28
|
+
--hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
|
|
29
|
+
--hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
|
|
30
|
+
# via asgiref
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is autogenerated by pip-compile with Python 3.10
|
|
3
|
+
# by the following command:
|
|
4
|
+
#
|
|
5
|
+
# pip-compile --allow-unsafe --generate-hashes --output-file=requirements/py310-django42.txt requirements/runtime.in
|
|
6
|
+
#
|
|
7
|
+
asgiref==3.11.1 \
|
|
8
|
+
--hash=sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce \
|
|
9
|
+
--hash=sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133
|
|
10
|
+
# via django
|
|
11
|
+
django==4.2.30 \
|
|
12
|
+
--hash=sha256:4d07aaf1c62f9984842b67c2874ebbf7056a17be253860299b93ae1881faad65 \
|
|
13
|
+
--hash=sha256:4ebc7a434e3819db6cf4b399fb5b3f536310a30e8486f08b66886840be84b37c
|
|
14
|
+
# via -r requirements/runtime.in
|
|
15
|
+
emoji-country-flag==2.1.0 \
|
|
16
|
+
--hash=sha256:224b19583210901b950a624ab0b0896cdf0764b36f8bd1463868b15fb8c10f6a \
|
|
17
|
+
--hash=sha256:3b4df6a9d9961ab7716090689c2e96405baff68e46849ab2704335d904589f2a
|
|
18
|
+
# via -r requirements/runtime.in
|
|
19
|
+
sqlparse==0.5.5 \
|
|
20
|
+
--hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \
|
|
21
|
+
--hash=sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e
|
|
22
|
+
# via django
|
|
23
|
+
typing-extensions==4.15.0 \
|
|
24
|
+
--hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
|
|
25
|
+
--hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
|
|
26
|
+
# via asgiref
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is autogenerated by pip-compile with Python 3.10
|
|
3
|
+
# by the following command:
|
|
4
|
+
#
|
|
5
|
+
# pip-compile --allow-unsafe --generate-hashes --output-file=requirements/py310-django52.txt requirements/runtime.in
|
|
6
|
+
#
|
|
7
|
+
asgiref==3.11.1 \
|
|
8
|
+
--hash=sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce \
|
|
9
|
+
--hash=sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133
|
|
10
|
+
# via django
|
|
11
|
+
django==5.2.15 \
|
|
12
|
+
--hash=sha256:0eb4a9bb1853a35b0286dbc6d916bd352c8c2687195a7f2d6f80cefd840e4970 \
|
|
13
|
+
--hash=sha256:5154a9bf84ac01dde011e367f355c07dbb329532e06810dcf3ef2af269e236e7
|
|
14
|
+
# via -r requirements/runtime.in
|
|
15
|
+
emoji-country-flag==2.1.0 \
|
|
16
|
+
--hash=sha256:224b19583210901b950a624ab0b0896cdf0764b36f8bd1463868b15fb8c10f6a \
|
|
17
|
+
--hash=sha256:3b4df6a9d9961ab7716090689c2e96405baff68e46849ab2704335d904589f2a
|
|
18
|
+
# via -r requirements/runtime.in
|
|
19
|
+
sqlparse==0.5.5 \
|
|
20
|
+
--hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \
|
|
21
|
+
--hash=sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e
|
|
22
|
+
# via django
|
|
23
|
+
typing-extensions==4.15.0 \
|
|
24
|
+
--hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
|
|
25
|
+
--hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
|
|
26
|
+
# via asgiref
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is autogenerated by pip-compile with Python 3.11
|
|
3
|
+
# by the following command:
|
|
4
|
+
#
|
|
5
|
+
# pip-compile --allow-unsafe --generate-hashes --output-file=requirements/py311-django42.txt requirements/runtime.in
|
|
6
|
+
#
|
|
7
|
+
asgiref==3.11.1 \
|
|
8
|
+
--hash=sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce \
|
|
9
|
+
--hash=sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133
|
|
10
|
+
# via django
|
|
11
|
+
django==4.2.30 \
|
|
12
|
+
--hash=sha256:4d07aaf1c62f9984842b67c2874ebbf7056a17be253860299b93ae1881faad65 \
|
|
13
|
+
--hash=sha256:4ebc7a434e3819db6cf4b399fb5b3f536310a30e8486f08b66886840be84b37c
|
|
14
|
+
# via -r requirements/runtime.in
|
|
15
|
+
emoji-country-flag==2.1.0 \
|
|
16
|
+
--hash=sha256:224b19583210901b950a624ab0b0896cdf0764b36f8bd1463868b15fb8c10f6a \
|
|
17
|
+
--hash=sha256:3b4df6a9d9961ab7716090689c2e96405baff68e46849ab2704335d904589f2a
|
|
18
|
+
# via -r requirements/runtime.in
|
|
19
|
+
sqlparse==0.5.5 \
|
|
20
|
+
--hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \
|
|
21
|
+
--hash=sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e
|
|
22
|
+
# via django
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is autogenerated by pip-compile with Python 3.11
|
|
3
|
+
# by the following command:
|
|
4
|
+
#
|
|
5
|
+
# pip-compile --allow-unsafe --generate-hashes --output-file=requirements/py311-django52.txt requirements/runtime.in
|
|
6
|
+
#
|
|
7
|
+
asgiref==3.11.1 \
|
|
8
|
+
--hash=sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce \
|
|
9
|
+
--hash=sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133
|
|
10
|
+
# via django
|
|
11
|
+
django==5.2.15 \
|
|
12
|
+
--hash=sha256:0eb4a9bb1853a35b0286dbc6d916bd352c8c2687195a7f2d6f80cefd840e4970 \
|
|
13
|
+
--hash=sha256:5154a9bf84ac01dde011e367f355c07dbb329532e06810dcf3ef2af269e236e7
|
|
14
|
+
# via -r requirements/runtime.in
|
|
15
|
+
emoji-country-flag==2.1.0 \
|
|
16
|
+
--hash=sha256:224b19583210901b950a624ab0b0896cdf0764b36f8bd1463868b15fb8c10f6a \
|
|
17
|
+
--hash=sha256:3b4df6a9d9961ab7716090689c2e96405baff68e46849ab2704335d904589f2a
|
|
18
|
+
# via -r requirements/runtime.in
|
|
19
|
+
sqlparse==0.5.5 \
|
|
20
|
+
--hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \
|
|
21
|
+
--hash=sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e
|
|
22
|
+
# via django
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is autogenerated by pip-compile with Python 3.12
|
|
3
|
+
# by the following command:
|
|
4
|
+
#
|
|
5
|
+
# pip-compile --allow-unsafe --generate-hashes --output-file=requirements/py312-django42.txt requirements/runtime.in
|
|
6
|
+
#
|
|
7
|
+
asgiref==3.11.1 \
|
|
8
|
+
--hash=sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce \
|
|
9
|
+
--hash=sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133
|
|
10
|
+
# via django
|
|
11
|
+
django==4.2.30 \
|
|
12
|
+
--hash=sha256:4d07aaf1c62f9984842b67c2874ebbf7056a17be253860299b93ae1881faad65 \
|
|
13
|
+
--hash=sha256:4ebc7a434e3819db6cf4b399fb5b3f536310a30e8486f08b66886840be84b37c
|
|
14
|
+
# via -r requirements/runtime.in
|
|
15
|
+
emoji-country-flag==2.1.0 \
|
|
16
|
+
--hash=sha256:224b19583210901b950a624ab0b0896cdf0764b36f8bd1463868b15fb8c10f6a \
|
|
17
|
+
--hash=sha256:3b4df6a9d9961ab7716090689c2e96405baff68e46849ab2704335d904589f2a
|
|
18
|
+
# via -r requirements/runtime.in
|
|
19
|
+
sqlparse==0.5.5 \
|
|
20
|
+
--hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \
|
|
21
|
+
--hash=sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e
|
|
22
|
+
# via django
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is autogenerated by pip-compile with Python 3.12
|
|
3
|
+
# by the following command:
|
|
4
|
+
#
|
|
5
|
+
# pip-compile --allow-unsafe --generate-hashes --output-file=requirements/py312-django52.txt requirements/runtime.in
|
|
6
|
+
#
|
|
7
|
+
asgiref==3.11.1 \
|
|
8
|
+
--hash=sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce \
|
|
9
|
+
--hash=sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133
|
|
10
|
+
# via django
|
|
11
|
+
django==5.2.15 \
|
|
12
|
+
--hash=sha256:0eb4a9bb1853a35b0286dbc6d916bd352c8c2687195a7f2d6f80cefd840e4970 \
|
|
13
|
+
--hash=sha256:5154a9bf84ac01dde011e367f355c07dbb329532e06810dcf3ef2af269e236e7
|
|
14
|
+
# via -r requirements/runtime.in
|
|
15
|
+
emoji-country-flag==2.1.0 \
|
|
16
|
+
--hash=sha256:224b19583210901b950a624ab0b0896cdf0764b36f8bd1463868b15fb8c10f6a \
|
|
17
|
+
--hash=sha256:3b4df6a9d9961ab7716090689c2e96405baff68e46849ab2704335d904589f2a
|
|
18
|
+
# via -r requirements/runtime.in
|
|
19
|
+
sqlparse==0.5.5 \
|
|
20
|
+
--hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \
|
|
21
|
+
--hash=sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e
|
|
22
|
+
# via django
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is autogenerated by pip-compile with Python 3.13
|
|
3
|
+
# by the following command:
|
|
4
|
+
#
|
|
5
|
+
# pip-compile --allow-unsafe --generate-hashes --output-file=requirements/py313-django52.txt requirements/runtime.in
|
|
6
|
+
#
|
|
7
|
+
asgiref==3.11.1 \
|
|
8
|
+
--hash=sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce \
|
|
9
|
+
--hash=sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133
|
|
10
|
+
# via django
|
|
11
|
+
django==5.2.15 \
|
|
12
|
+
--hash=sha256:0eb4a9bb1853a35b0286dbc6d916bd352c8c2687195a7f2d6f80cefd840e4970 \
|
|
13
|
+
--hash=sha256:5154a9bf84ac01dde011e367f355c07dbb329532e06810dcf3ef2af269e236e7
|
|
14
|
+
# via -r requirements/runtime.in
|
|
15
|
+
emoji-country-flag==2.1.0 \
|
|
16
|
+
--hash=sha256:224b19583210901b950a624ab0b0896cdf0764b36f8bd1463868b15fb8c10f6a \
|
|
17
|
+
--hash=sha256:3b4df6a9d9961ab7716090689c2e96405baff68e46849ab2704335d904589f2a
|
|
18
|
+
# via -r requirements/runtime.in
|
|
19
|
+
sqlparse==0.5.5 \
|
|
20
|
+
--hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \
|
|
21
|
+
--hash=sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e
|
|
22
|
+
# via django
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is autogenerated by pip-compile with Python 3.14
|
|
3
|
+
# by the following command:
|
|
4
|
+
#
|
|
5
|
+
# pip-compile --allow-unsafe --generate-hashes --output-file=requirements/py314-django52.txt requirements/runtime.in
|
|
6
|
+
#
|
|
7
|
+
asgiref==3.11.1 \
|
|
8
|
+
--hash=sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce \
|
|
9
|
+
--hash=sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133
|
|
10
|
+
# via django
|
|
11
|
+
django==5.2.15 \
|
|
12
|
+
--hash=sha256:0eb4a9bb1853a35b0286dbc6d916bd352c8c2687195a7f2d6f80cefd840e4970 \
|
|
13
|
+
--hash=sha256:5154a9bf84ac01dde011e367f355c07dbb329532e06810dcf3ef2af269e236e7
|
|
14
|
+
# via -r requirements/runtime.in
|
|
15
|
+
emoji-country-flag==2.1.0 \
|
|
16
|
+
--hash=sha256:224b19583210901b950a624ab0b0896cdf0764b36f8bd1463868b15fb8c10f6a \
|
|
17
|
+
--hash=sha256:3b4df6a9d9961ab7716090689c2e96405baff68e46849ab2704335d904589f2a
|
|
18
|
+
# via -r requirements/runtime.in
|
|
19
|
+
sqlparse==0.5.5 \
|
|
20
|
+
--hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \
|
|
21
|
+
--hash=sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e
|
|
22
|
+
# via django
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#
|
|
2
|
+
# This file is autogenerated by pip-compile with Python 3.9
|
|
3
|
+
# by the following command:
|
|
4
|
+
#
|
|
5
|
+
# pip-compile --allow-unsafe --generate-hashes --output-file=requirements/py39-django42.txt requirements/runtime.in
|
|
6
|
+
#
|
|
7
|
+
asgiref==3.11.1 \
|
|
8
|
+
--hash=sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce \
|
|
9
|
+
--hash=sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133
|
|
10
|
+
# via django
|
|
11
|
+
django==4.2.30 \
|
|
12
|
+
--hash=sha256:4d07aaf1c62f9984842b67c2874ebbf7056a17be253860299b93ae1881faad65 \
|
|
13
|
+
--hash=sha256:4ebc7a434e3819db6cf4b399fb5b3f536310a30e8486f08b66886840be84b37c
|
|
14
|
+
# via -r requirements/runtime.in
|
|
15
|
+
emoji-country-flag==1.3.2 \
|
|
16
|
+
--hash=sha256:42d9fc1a8ec27b3ef2c18a39532dcdbf0527afbc95749be02a3afc1f1b165aa3 \
|
|
17
|
+
--hash=sha256:6ddcf8f3bc55b2f59b1d7d9dc830e90f9423f555dd37862c4c2e220d42726f73
|
|
18
|
+
# via -r requirements/runtime.in
|
|
19
|
+
sqlparse==0.5.5 \
|
|
20
|
+
--hash=sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba \
|
|
21
|
+
--hash=sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e
|
|
22
|
+
# via django
|
|
23
|
+
typing-extensions==4.15.0 \
|
|
24
|
+
--hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
|
|
25
|
+
--hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
|
|
26
|
+
# via asgiref
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-lang
|
|
3
|
+
Version: 0.6.3
|
|
4
|
+
Summary: Django application to provide useful utils and reusable parts of code for multi-languages sites.
|
|
5
|
+
Author-email: DLRSP <dlrsp.dev@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/DLRSP/django-lang
|
|
8
|
+
Project-URL: Documentation, https://github.com/DLRSP/django-lang/
|
|
9
|
+
Project-URL: Repository, https://github.com/DLRSP/django-lang
|
|
10
|
+
Project-URL: Issues, https://github.com/DLRSP/django-lang/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/DLRSP/django-lang/blob/main/CHANGELOG.rst
|
|
12
|
+
Keywords: django,i18n,multilingual,hreflang,language
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Natural Language :: Italian
|
|
15
|
+
Classifier: Natural Language :: English
|
|
16
|
+
Classifier: Environment :: Web Environment
|
|
17
|
+
Classifier: Framework :: Django
|
|
18
|
+
Classifier: Framework :: Django :: 3.2
|
|
19
|
+
Classifier: Framework :: Django :: 4.2
|
|
20
|
+
Classifier: Framework :: Django :: 5.2
|
|
21
|
+
Classifier: Intended Audience :: Developers
|
|
22
|
+
Classifier: Operating System :: OS Independent
|
|
23
|
+
Classifier: Programming Language :: Python
|
|
24
|
+
Classifier: Programming Language :: Python :: 3
|
|
25
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
28
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
29
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
32
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
33
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
34
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
|
|
35
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
36
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
37
|
+
Requires-Python: >=3.9
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
License-File: LICENSE
|
|
40
|
+
Requires-Dist: Django>=3.2
|
|
41
|
+
Requires-Dist: emoji-country-flag>=2.1.0; python_version >= "3.10"
|
|
42
|
+
Requires-Dist: emoji-country-flag<2,>=1.3.2; python_version < "3.10"
|
|
43
|
+
Provides-Extra: testing
|
|
44
|
+
Requires-Dist: coverage; extra == "testing"
|
|
45
|
+
Requires-Dist: codecov; extra == "testing"
|
|
46
|
+
Requires-Dist: pytest; extra == "testing"
|
|
47
|
+
Requires-Dist: pytest-django; extra == "testing"
|
|
48
|
+
Provides-Extra: docs
|
|
49
|
+
Requires-Dist: mkdocs>=1.5; extra == "docs"
|
|
50
|
+
Requires-Dist: mkdocs-material>=9.0; extra == "docs"
|
|
51
|
+
Requires-Dist: pymdown-extensions>=10.0; extra == "docs"
|
|
52
|
+
Requires-Dist: mkdocs-git-revision-date-plugin>=2.0; extra == "docs"
|
|
53
|
+
Provides-Extra: linting
|
|
54
|
+
Requires-Dist: flake8; extra == "linting"
|
|
55
|
+
Requires-Dist: flake8-pyproject; extra == "linting"
|
|
56
|
+
Requires-Dist: flake8-bugbear; extra == "linting"
|
|
57
|
+
Requires-Dist: flake8-comprehensions; extra == "linting"
|
|
58
|
+
Requires-Dist: flake8-tidy-imports; extra == "linting"
|
|
59
|
+
Requires-Dist: flake8-typing-imports; extra == "linting"
|
|
60
|
+
Requires-Dist: pylint; extra == "linting"
|
|
61
|
+
Dynamic: license-file
|
|
62
|
+
|
|
63
|
+
# django-lang [](https://pypi.python.org/pypi/django-lang)
|
|
64
|
+
|
|
65
|
+
[](https://pypi.python.org/pypi/django-lang)
|
|
66
|
+
[](https://pypi.python.org/pypi/django-lang)
|
|
67
|
+
[](https://pypi.python.org/pypi/django-lang)
|
|
68
|
+
[](https://pypi.python.org/pypi/django-lang)
|
|
69
|
+
[](https://pypi.python.org/pypi/django-lang)
|
|
70
|
+
[](https://pypi.python.org/pypi/django-lang)
|
|
71
|
+
|
|
72
|
+
## GitHub  
|
|
73
|
+
|
|
74
|
+
## Test [](https://codecov.io/github/DLRSP/django-lang?branch=main) [](https://results.pre-commit.ci/latest/github/DLRSP/django-lang/main) [](https://github.com/DLRSP/django-lang/actions/workflows/ci.yaml)
|
|
75
|
+
|
|
76
|
+
## Check Demo Project
|
|
77
|
+
* Check the demo repo on [GitHub](https://github.com/DLRSP/example/tree/django-lang)
|
|
78
|
+
|
|
79
|
+
## Requirements
|
|
80
|
+
- Python 3.9+ supported.
|
|
81
|
+
- Django 3.2+ supported.
|
|
82
|
+
|
|
83
|
+
## Setup
|
|
84
|
+
1. Install from **pip**:
|
|
85
|
+
```shell
|
|
86
|
+
pip install django-lang
|
|
87
|
+
```
|
|
88
|
+
2. Modify `settings.py` by adding the app to `INSTALLED_APPS`:
|
|
89
|
+
```python
|
|
90
|
+
INSTALLED_APPS = [
|
|
91
|
+
# ...
|
|
92
|
+
"lang",
|
|
93
|
+
# ...
|
|
94
|
+
]
|
|
95
|
+
```
|
|
96
|
+
3. Modify `settings.py` by adding the app to `INSTALLED_APPS`:
|
|
97
|
+
``` python title="settings.py" hl_lines="12"
|
|
98
|
+
TEMPLATES = [
|
|
99
|
+
{
|
|
100
|
+
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
101
|
+
"DIRS": [os.path.join(PROJECT_DIR, "templates")],
|
|
102
|
+
"APP_DIRS": True,
|
|
103
|
+
"OPTIONS": {
|
|
104
|
+
"context_processors": [
|
|
105
|
+
"django.template.context_processors.debug",
|
|
106
|
+
"django.template.context_processors.request",
|
|
107
|
+
"django.contrib.auth.context_processors.auth",
|
|
108
|
+
"django.contrib.messages.context_processors.messages",
|
|
109
|
+
"lang.context_processors.from_settings",
|
|
110
|
+
"lang.context_processors.seo_i18n",
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Optional: ``lang.context_processors.language_switcher_next`` (fills ``redirect_to`` for the packaged language form).
|
|
118
|
+
|
|
119
|
+
### Optional: ``SetLanguageNextPathMiddleware``
|
|
120
|
+
|
|
121
|
+
Only needed if you use **translated URL segments** (``gettext_lazy`` under ``i18n_patterns``) and see language switches that POST to ``set_language`` but stay on the wrong prefix. Short how-to: [docs/set_language_middleware.md](docs/set_language_middleware.md).
|
|
122
|
+
|
|
123
|
+
4. Modify your project's base template `base.html` to include language's switcher styles:
|
|
124
|
+
```html
|
|
125
|
+
<head>
|
|
126
|
+
...
|
|
127
|
+
<link rel="stylesheet" type="text/css" href="{% static 'lang/css/nav-link.css' %}">
|
|
128
|
+
...
|
|
129
|
+
</head>
|
|
130
|
+
```
|
|
131
|
+
5. Modify your project's base template `base.html` to include attributes using `translate_url` template's tag:
|
|
132
|
+
```html
|
|
133
|
+
<head>
|
|
134
|
+
...
|
|
135
|
+
<meta name="language" content="{{ LANGUAGE_CODE }}" />
|
|
136
|
+
{% include "hreflang.html" %}
|
|
137
|
+
...
|
|
138
|
+
</head>
|
|
139
|
+
```
|
|
140
|
+
6. Modify your project's nav template `nav.html` to include language's switcher:
|
|
141
|
+
```html
|
|
142
|
+
<nav class="navbar">
|
|
143
|
+
...
|
|
144
|
+
<ul class="nav navbar-nav">
|
|
145
|
+
{% include "lang/nav-link.html" %}
|
|
146
|
+
</ul>
|
|
147
|
+
...
|
|
148
|
+
</nav>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Configuration: ``lang.conf`` and ``APP_CONFIG``
|
|
152
|
+
|
|
153
|
+
Built-in maps live in ``lang.defaults``. At runtime, :mod:`lang.conf` resolves values **lazily**:
|
|
154
|
+
|
|
155
|
+
1. Top-level Django settings (``LANGUAGE_HREFLANG_MAP``, ``LANGUAGE_WIKIPEDIA_SAMEAS``, ``OG_LOCALE_BY_LANGUAGE``, ``HREFLANG_DEFAULT_LANGUAGE``, ``LANGUAGE_FLAG_MAP``), if set.
|
|
156
|
+
2. Partial dicts under ``settings.APP_CONFIG["lang"]`` merged onto the package defaults (for the three ``LANGUAGE_*`` / ``OG_*`` maps and flag overrides).
|
|
157
|
+
3. Otherwise the content of ``lang.defaults``.
|
|
158
|
+
|
|
159
|
+
You do **not** need to import ``lang.defaults`` from ``settings.py``. Typical site-only override for ``x-default``::
|
|
160
|
+
|
|
161
|
+
APP_CONFIG = {
|
|
162
|
+
"lang": {
|
|
163
|
+
"HREFLANG_DEFAULT_LANGUAGE": "it",
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
Or use the usual flat settings (full replacement for the dict keys, or ``HREFLANG_DEFAULT_LANGUAGE`` at top level) if you prefer.
|
|
168
|
+
|
|
169
|
+
### Optional: language control beside the hamburger on **small viewports**
|
|
170
|
+
|
|
171
|
+
``nav-link-standalone.css`` shows the extra switcher next to the menu toggle below the ``lg`` breakpoint (~992px) and hides the duplicate inside the collapsed drawer. Optional script ``display-standalone-class.js`` only adds class ``display-standalone`` on ``<html>`` for installed web apps; layout no longer depends on it.
|
|
172
|
+
|
|
173
|
+
1. In `settings.py`, add the optional context processor so `redirect_to` is filled for `set_language`’s `next` (unless you pass `redirect_to` from each view):
|
|
174
|
+
```python
|
|
175
|
+
"lang.context_processors.language_switcher_next",
|
|
176
|
+
```
|
|
177
|
+
2. In the base layout `<head>`, after `nav-link.css`, add:
|
|
178
|
+
```html
|
|
179
|
+
<link rel="stylesheet" href="{% static 'lang/css/nav-link-standalone.css' %}">
|
|
180
|
+
<script src="{% static 'lang/js/display-standalone-class.js' %}"></script>
|
|
181
|
+
```
|
|
182
|
+
3. Next to your mobile menu button, include:
|
|
183
|
+
```html
|
|
184
|
+
{% include "lang/nav-link-standalone.html" %}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Packaged templates:
|
|
188
|
+
|
|
189
|
+
- ``hreflang.html`` (app template root) — ``<link rel="alternate" hreflang="…">`` for the current view.
|
|
190
|
+
- ``lang/nav-link.html`` — language ``<select>`` (optional context: ``lang_switcher_id``, ``lang_switcher_extra_class``).
|
|
191
|
+
- ``lang/nav-link-standalone.html`` — duplicate switcher beside the mobile toggle (small viewports / PWA).
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
## Run Example Project
|
|
195
|
+
|
|
196
|
+
```shell
|
|
197
|
+
git clone --depth=50 --branch=django-lang https://github.com/DLRSP/example.git DLRSP/example
|
|
198
|
+
cd DLRSP/example
|
|
199
|
+
python manage.py runserver
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Now browser the app @ http://127.0.0.1:8000
|
|
203
|
+
|
|
204
|
+
## References
|
|
205
|
+
|
|
206
|
+
- [brainstorm.it](https://brainstorm.it/snippets/django-language-switching/) - Language's switching
|
|
207
|
+
- [hakibenita.com](https://hakibenita.com/django-multi-language-site-hreflang) - Url's translation for "hreflang" html's attributes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
django_lang-0.6.3.data/data/share/django-lang/requirements/py310-django32.txt,sha256=Is5a75mDJwwbiTi_y2Ew0SNFnD_wI_mJv3qOVFlK5-s,1490
|
|
2
|
+
django_lang-0.6.3.data/data/share/django-lang/requirements/py310-django42.txt,sha256=hYkTt5hE91FrZ-LJH0dMUembnnuTEcjbk6POfVU9bf4,1290
|
|
3
|
+
django_lang-0.6.3.data/data/share/django-lang/requirements/py310-django52.txt,sha256=TWCY2bckrkhdP578QYvtZoICIWQVPISOKstdDg2r1ZE,1290
|
|
4
|
+
django_lang-0.6.3.data/data/share/django-lang/requirements/py311-django42.txt,sha256=roRqDD4Bkx1nqaVZ_7Iisy1Htku1VMzPv-DS2LPuYWk,1076
|
|
5
|
+
django_lang-0.6.3.data/data/share/django-lang/requirements/py311-django52.txt,sha256=RQYIjE0lPtVgurhNzMcoPZf2qDzwhXd7vkTLrn3bqkc,1076
|
|
6
|
+
django_lang-0.6.3.data/data/share/django-lang/requirements/py312-django42.txt,sha256=dUK-xWhdYCNu3NXSZK_aXFisZREqfxk1P_zM2kCIPek,1076
|
|
7
|
+
django_lang-0.6.3.data/data/share/django-lang/requirements/py312-django52.txt,sha256=rtKLPk_UEC5dXzTw-SsD4PYgL_QFP7j11dPcfjcGKHU,1076
|
|
8
|
+
django_lang-0.6.3.data/data/share/django-lang/requirements/py313-django52.txt,sha256=cghaLwOzYis19pbLcDFUwYqw4woVbLXba1Ia64EHZ8g,1076
|
|
9
|
+
django_lang-0.6.3.data/data/share/django-lang/requirements/py314-django52.txt,sha256=R6gYVOJzUQjehM-TFQtgHdP1lM0F5z4Pokm-EidMuxs,1076
|
|
10
|
+
django_lang-0.6.3.data/data/share/django-lang/requirements/py39-django42.txt,sha256=hli_6tnHOeH5BSDGPnBGF1x6ot4w088o3J-sraCLp7w,1288
|
|
11
|
+
django_lang-0.6.3.dist-info/licenses/LICENSE,sha256=lea6YyyICc5SM-8jvboUSCWiCpPwBrUPJAb8cHxAP5A,1114
|
|
12
|
+
lang/__init__.py,sha256=IIk7gZN3QmKyv2GEWFOd5D3qY04cMPb8Rr-flYq8Mwk,462
|
|
13
|
+
lang/conf.py,sha256=G5teoWe7Vl6fwX3GVWiSWXvcSkvPMWoD1wfeCrff2UA,3054
|
|
14
|
+
lang/context_processors.py,sha256=zefDpet4tXYlnBELC1HCK_VkU7jgbEKDijZOkdUJvL4,2155
|
|
15
|
+
lang/defaults.py,sha256=idhm3btcU-IZBmliby0q3sGY-6qVyQsN01q7FUZ5IG8,1866
|
|
16
|
+
lang/middleware.py,sha256=UP7gdMMwMs9pWjh2ZKqabBsJCz6-ovRt9d-KXngy1kA,2313
|
|
17
|
+
lang/utils.py,sha256=K67OAzyC4WJLz-ZGCbTSRkHNgYelmMZ1uNZEYmuXy-c,978
|
|
18
|
+
lang/static/lang/css/nav-link-standalone.css,sha256=bipwrkyKNgR0_Kv-q7rXyWdzym4JAf1GbcdpZK2w0G4,811
|
|
19
|
+
lang/static/lang/css/nav-link.css,sha256=tFhfGH6FkWZDWUaH6H4VFWwo3afBmkDwX2qKzO1hQcI,426
|
|
20
|
+
lang/static/lang/js/display-standalone-class.js,sha256=TUhv5gpWZJUBQtTzuv12WSym8sKi3zlJVkKKYuq1BVs,643
|
|
21
|
+
lang/templates/hreflang.html,sha256=BdciMglJ2xETeXc92YZl9wvmpzDBAYf7aCIIkgzY16A,373
|
|
22
|
+
lang/templates/lang/nav-link-standalone.html,sha256=wuaGJ3PmT2RaQCnxn6UKJ-CsIHjPuA3G4YojQOYGzjM,307
|
|
23
|
+
lang/templates/lang/nav-link.html,sha256=swHsDd3lOw7yfJfiAyOWCFPqgSt1J2seX281luxnsO8,781
|
|
24
|
+
lang/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
+
lang/templatetags/languages_helpers.py,sha256=_zhbFZ5IH7QUum7wXQJeJ9Qw3hn5a20u5CQNVhv6SxA,2242
|
|
26
|
+
lang/templatetags/urls.py,sha256=4bYpWmhBfHKHEAyrAj0wod1YSok1JqS4oIcCJtossYc,2842
|
|
27
|
+
django_lang-0.6.3.dist-info/METADATA,sha256=OvZ6Qhg3x3k6yG61GHzmdxsXrIET3OV1iOuk0SvQOCI,9491
|
|
28
|
+
django_lang-0.6.3.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
29
|
+
django_lang-0.6.3.dist-info/top_level.txt,sha256=k1z7040BUci34YPBCxIsaMA0DGeL-qBF3vBS1z3Ro28,5
|
|
30
|
+
django_lang-0.6.3.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2010-present DLRSP (https://dlrsp.org) and other contributors.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
lang
|
lang/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
See PEP 386 (https://peps.python.org/pep-0386/)
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "0.6.3"
|
|
6
|
+
__version_info__ = tuple(
|
|
7
|
+
int(i) if i.isdigit() else i for i in __version__.split(".")
|
|
8
|
+
)
|
|
9
|
+
__license__ = "MIT"
|
|
10
|
+
__title__ = "lang"
|
|
11
|
+
|
|
12
|
+
__author__ = "DLRSP"
|
|
13
|
+
__copyright__ = "Copyright 2010-present DLRSP"
|
|
14
|
+
|
|
15
|
+
# Version synonym
|
|
16
|
+
VERSION = __version_info__
|
|
17
|
+
|
|
18
|
+
# Header encoding (see RFC5987)
|
|
19
|
+
HTTP_HEADER_ENCODING = "iso-8859-1"
|
|
20
|
+
|
|
21
|
+
# Default datetime input and output formats
|
|
22
|
+
ISO_8601 = "iso-8601"
|
lang/conf.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Resolved configuration for ``lang`` (lazy reads from ``django.conf.settings``).
|
|
3
|
+
|
|
4
|
+
Priority for each value:
|
|
5
|
+
|
|
6
|
+
1. Top-level Django setting (e.g. ``LANGUAGE_HREFLANG_MAP``), if set.
|
|
7
|
+
2. Partial override inside ``settings.APP_CONFIG["lang"]`` merged onto
|
|
8
|
+
:mod:`lang.defaults`.
|
|
9
|
+
3. Package defaults from :mod:`lang.defaults`.
|
|
10
|
+
|
|
11
|
+
Projects can rely on defaults only (no imports from ``lang.defaults`` in
|
|
12
|
+
``settings.py``) and optionally set::
|
|
13
|
+
|
|
14
|
+
APP_CONFIG = {
|
|
15
|
+
"lang": {
|
|
16
|
+
"HREFLANG_DEFAULT_LANGUAGE": "it",
|
|
17
|
+
"LANGUAGE_HREFLANG_MAP": {"en": "en-GB"},
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
from django.conf import settings
|
|
27
|
+
|
|
28
|
+
from lang import defaults
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _app_config_lang() -> dict[str, Any]:
|
|
32
|
+
cfg = getattr(settings, "APP_CONFIG", None) or {}
|
|
33
|
+
lang = cfg.get("lang")
|
|
34
|
+
return dict(lang) if lang else {}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _merged_lang_dict(
|
|
38
|
+
setting_name: str,
|
|
39
|
+
defaults_dict: dict[str, str],
|
|
40
|
+
) -> dict[str, str]:
|
|
41
|
+
"""
|
|
42
|
+
Full replacement if ``settings.<name>`` is set; else defaults merged with
|
|
43
|
+
``APP_CONFIG['lang'][<name>]`` (partial).
|
|
44
|
+
"""
|
|
45
|
+
explicit = getattr(settings, setting_name, None)
|
|
46
|
+
if explicit is not None:
|
|
47
|
+
return dict(explicit)
|
|
48
|
+
partial = _app_config_lang().get(setting_name)
|
|
49
|
+
if partial:
|
|
50
|
+
return {**defaults_dict, **dict(partial)}
|
|
51
|
+
return dict(defaults_dict)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_language_hreflang_map() -> dict[str, str]:
|
|
55
|
+
"""Django language code → BCP 47 ``hreflang`` attribute value."""
|
|
56
|
+
return _merged_lang_dict(
|
|
57
|
+
"LANGUAGE_HREFLANG_MAP",
|
|
58
|
+
defaults.LANGUAGE_HREFLANG_MAP,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_language_wikipedia_sameas() -> dict[str, str]:
|
|
63
|
+
"""Django language code → Wikipedia URL (e.g. JSON-LD ``sameAs``)."""
|
|
64
|
+
return _merged_lang_dict(
|
|
65
|
+
"LANGUAGE_WIKIPEDIA_SAMEAS",
|
|
66
|
+
defaults.LANGUAGE_WIKIPEDIA_SAMEAS,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_og_locale_by_language() -> dict[str, str]:
|
|
71
|
+
"""Django language code → Open Graph locale string."""
|
|
72
|
+
return _merged_lang_dict(
|
|
73
|
+
"OG_LOCALE_BY_LANGUAGE",
|
|
74
|
+
defaults.OG_LOCALE_BY_LANGUAGE,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_hreflang_default_language() -> str | None:
|
|
79
|
+
"""
|
|
80
|
+
Brand default for ``hreflang`` ``x-default`` (non-empty string or ``None``).
|
|
81
|
+
|
|
82
|
+
Reads ``HREFLANG_DEFAULT_LANGUAGE`` from top-level settings, then
|
|
83
|
+
``APP_CONFIG['lang']['HREFLANG_DEFAULT_LANGUAGE']``.
|
|
84
|
+
"""
|
|
85
|
+
top = getattr(settings, "HREFLANG_DEFAULT_LANGUAGE", None)
|
|
86
|
+
if top:
|
|
87
|
+
return str(top)
|
|
88
|
+
nested = _app_config_lang().get("HREFLANG_DEFAULT_LANGUAGE")
|
|
89
|
+
if nested:
|
|
90
|
+
return str(nested)
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def get_language_flag_map() -> dict[str, str]:
|
|
95
|
+
"""
|
|
96
|
+
ISO region codes for emoji flags: merge package defaults with
|
|
97
|
+
``settings.LANGUAGE_FLAG_MAP`` and ``APP_CONFIG['lang']['LANGUAGE_FLAG_MAP']``.
|
|
98
|
+
"""
|
|
99
|
+
from lang.utils import DEFAULT_LANGUAGE_FLAG_MAP
|
|
100
|
+
|
|
101
|
+
user = getattr(settings, "LANGUAGE_FLAG_MAP", None) or {}
|
|
102
|
+
user2 = _app_config_lang().get("LANGUAGE_FLAG_MAP") or {}
|
|
103
|
+
return {**DEFAULT_LANGUAGE_FLAG_MAP, **dict(user), **dict(user2)}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Context processors for django-lang app
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
from django.conf import settings
|
|
8
|
+
|
|
9
|
+
from lang.conf import (
|
|
10
|
+
get_hreflang_default_language,
|
|
11
|
+
get_language_wikipedia_sameas,
|
|
12
|
+
get_og_locale_by_language,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def from_settings(request) -> Dict[str, Any]:
|
|
17
|
+
return {
|
|
18
|
+
"DEFAULT_LANGUAGE_CODE": getattr(settings, "LANGUAGE_CODE", None),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def language_switcher_next(request) -> Dict[str, Any]:
|
|
23
|
+
"""
|
|
24
|
+
Optional: set ``redirect_to`` to the current path for packaged language forms
|
|
25
|
+
(``set_language`` POST field ``next``). Add to ``TEMPLATES`` context_processors
|
|
26
|
+
when you use ``lang/nav-link.html`` without
|
|
27
|
+
passing ``redirect_to`` from each view.
|
|
28
|
+
"""
|
|
29
|
+
if request is None:
|
|
30
|
+
return {}
|
|
31
|
+
return {"redirect_to": request.get_full_path()}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def seo_i18n(request) -> Dict[str, Any]:
|
|
35
|
+
"""
|
|
36
|
+
SEO helpers for multilingual templates (list **after**
|
|
37
|
+
``lang.context_processors.from_settings`` so it can override
|
|
38
|
+
``DEFAULT_LANGUAGE_CODE`` when needed).
|
|
39
|
+
|
|
40
|
+
- ``DEFAULT_LANGUAGE_CODE``: set from ``settings.HREFLANG_DEFAULT_LANGUAGE``
|
|
41
|
+
when it is a non-empty string (drives ``hreflang.html`` ``x-default``).
|
|
42
|
+
If unset or empty, ``from_settings`` keeps ``LANGUAGE_CODE``.
|
|
43
|
+
- ``LANGUAGE_WIKIPEDIA_SAMEAS``: optional ``dict`` mapping Django language
|
|
44
|
+
code → URL (e.g. JSON-LD ``sameAs`` for languages).
|
|
45
|
+
- ``OG_LOCALE_BY_LANGUAGE``: optional ``dict`` mapping Django language code
|
|
46
|
+
→ Open Graph locale string (e.g. ``it_IT``).
|
|
47
|
+
|
|
48
|
+
Configure on ``settings`` (all optional except the first, which is
|
|
49
|
+
opt-in via defining ``HREFLANG_DEFAULT_LANGUAGE``):
|
|
50
|
+
|
|
51
|
+
- ``HREFLANG_DEFAULT_LANGUAGE`` (top-level or ``APP_CONFIG['lang']``)
|
|
52
|
+
- ``LANGUAGE_WIKIPEDIA_SAMEAS`` / ``OG_LOCALE_BY_LANGUAGE`` (same)
|
|
53
|
+
|
|
54
|
+
See :mod:`lang.conf` for merge rules.
|
|
55
|
+
"""
|
|
56
|
+
ctx: Dict[str, Any] = {
|
|
57
|
+
"LANGUAGE_WIKIPEDIA_SAMEAS": get_language_wikipedia_sameas(),
|
|
58
|
+
"OG_LOCALE_BY_LANGUAGE": get_og_locale_by_language(),
|
|
59
|
+
}
|
|
60
|
+
href_default = get_hreflang_default_language()
|
|
61
|
+
if href_default:
|
|
62
|
+
ctx["DEFAULT_LANGUAGE_CODE"] = href_default
|
|
63
|
+
return ctx
|
lang/defaults.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Built-in defaults for i18n / SEO helpers.
|
|
3
|
+
|
|
4
|
+
Runtime resolution (settings + optional ``APP_CONFIG``) lives in
|
|
5
|
+
:mod:`lang.conf`; do **not** require projects to import this module from
|
|
6
|
+
``settings.py`` unless they prefer explicit re-exports.
|
|
7
|
+
|
|
8
|
+
``HREFLANG_DEFAULT_LANGUAGE`` is project-specific; set it via
|
|
9
|
+
``settings.HREFLANG_DEFAULT_LANGUAGE`` or ``APP_CONFIG['lang']`` (see
|
|
10
|
+
:mod:`lang.conf`).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
# Google-style BCP 47 in <link hreflang="…"> (Django code → attribute value).
|
|
16
|
+
LANGUAGE_HREFLANG_MAP: dict[str, str] = {
|
|
17
|
+
"zh-hans": "zh-Hans",
|
|
18
|
+
"zh-hant": "zh-Hant",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# JSON-LD ``sameAs`` (or similar) → Wikipedia articles per language.
|
|
22
|
+
LANGUAGE_WIKIPEDIA_SAMEAS: dict[str, str] = {
|
|
23
|
+
"it": "https://en.wikipedia.org/wiki/Italian_language",
|
|
24
|
+
"en": "https://en.wikipedia.org/wiki/English_language",
|
|
25
|
+
"de": "https://en.wikipedia.org/wiki/German_language",
|
|
26
|
+
"fr": "https://en.wikipedia.org/wiki/French_language",
|
|
27
|
+
"es": "https://en.wikipedia.org/wiki/Spanish_language",
|
|
28
|
+
"pt": "https://en.wikipedia.org/wiki/Portuguese_language",
|
|
29
|
+
"nl": "https://en.wikipedia.org/wiki/Dutch_language",
|
|
30
|
+
"pl": "https://en.wikipedia.org/wiki/Polish_language",
|
|
31
|
+
"ru": "https://en.wikipedia.org/wiki/Russian_language",
|
|
32
|
+
"zh-hans": "https://en.wikipedia.org/wiki/Chinese_language",
|
|
33
|
+
"ja": "https://en.wikipedia.org/wiki/Japanese_language",
|
|
34
|
+
"ko": "https://en.wikipedia.org/wiki/Korean_language",
|
|
35
|
+
"ar": "https://en.wikipedia.org/wiki/Arabic",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Open Graph ``og:locale``-style strings (underscore + region).
|
|
39
|
+
OG_LOCALE_BY_LANGUAGE: dict[str, str] = {
|
|
40
|
+
"it": "it_IT",
|
|
41
|
+
"en": "en_US",
|
|
42
|
+
"de": "de_DE",
|
|
43
|
+
"fr": "fr_FR",
|
|
44
|
+
"es": "es_ES",
|
|
45
|
+
"pt": "pt_PT",
|
|
46
|
+
"nl": "nl_NL",
|
|
47
|
+
"pl": "pl_PL",
|
|
48
|
+
"ru": "ru_RU",
|
|
49
|
+
"zh-hans": "zh_CN",
|
|
50
|
+
"ja": "ja_JP",
|
|
51
|
+
"ko": "ko_KR",
|
|
52
|
+
"ar": "ar_SA",
|
|
53
|
+
}
|
lang/middleware.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Optional middleware for Django's ``set_language`` view and gettext_lazy URL paths.
|
|
3
|
+
|
|
4
|
+
``django.views.i18n.set_language`` calls ``django.urls.translate_url``, which runs
|
|
5
|
+
``resolve()`` while the **active** translation language is still the one chosen by
|
|
6
|
+
``LocaleMiddleware`` for the current request. For POSTs to ``/i18n/setlang/`` there
|
|
7
|
+
is no language prefix in the URL, so the language often comes from the
|
|
8
|
+
``django_language`` cookie (e.g. ``LANGUAGE_CODE`` default ``en``). If the user is
|
|
9
|
+
leaving a page whose path uses a **different** prefix (e.g. ``/it/...``) and
|
|
10
|
+
translated segments from ``gettext_lazy``, ``resolve()`` can fail under the wrong
|
|
11
|
+
active language and the redirect keeps the old prefix — while the cookie updates,
|
|
12
|
+
``LocaleMiddleware`` still prefers the URL prefix, so the UI appears stuck.
|
|
13
|
+
|
|
14
|
+
This middleware activates the language parsed from the ``next`` URL path (when it
|
|
15
|
+
carries an i18n prefix) **before** ``set_language`` runs, so ``translate_url`` matches
|
|
16
|
+
the same patterns as for a normal request on that path.
|
|
17
|
+
|
|
18
|
+
Insert **immediately after** ``django.middleware.locale.LocaleMiddleware``::
|
|
19
|
+
|
|
20
|
+
MIDDLEWARE = [
|
|
21
|
+
...
|
|
22
|
+
"django.middleware.locale.LocaleMiddleware",
|
|
23
|
+
"lang.middleware.SetLanguageNextPathMiddleware",
|
|
24
|
+
...
|
|
25
|
+
]
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from urllib.parse import urlsplit
|
|
31
|
+
|
|
32
|
+
from django.utils import translation
|
|
33
|
+
from django.utils.deprecation import MiddlewareMixin
|
|
34
|
+
from django.utils.translation import get_language_from_path
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SetLanguageNextPathMiddleware(MiddlewareMixin):
|
|
38
|
+
"""Align active language with the i18n prefix in ``next`` for POST ``set_language``."""
|
|
39
|
+
|
|
40
|
+
def process_request(self, request):
|
|
41
|
+
if request.method != "POST":
|
|
42
|
+
return None
|
|
43
|
+
path_info = request.path_info or ""
|
|
44
|
+
if not path_info.startswith("/i18n/") or "setlang" not in path_info:
|
|
45
|
+
return None
|
|
46
|
+
next_url = request.POST.get("next") or request.GET.get("next")
|
|
47
|
+
if not next_url:
|
|
48
|
+
return None
|
|
49
|
+
path = urlsplit(next_url).path or "/"
|
|
50
|
+
path_lang = get_language_from_path(path)
|
|
51
|
+
if not path_lang:
|
|
52
|
+
return None
|
|
53
|
+
translation.activate(path_lang)
|
|
54
|
+
if hasattr(request, "LANGUAGE_CODE"):
|
|
55
|
+
request.LANGUAGE_CODE = path_lang
|
|
56
|
+
return None
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*-----------------------------------------------------------------------------------*/
|
|
2
|
+
/* Mobile / narrow navbar (< lg): language switch beside hamburger; hide duplicate */
|
|
3
|
+
/* in the collapsed drawer. (display-standalone-class.js is optional; not required.) */
|
|
4
|
+
/*-----------------------------------------------------------------------------------*/
|
|
5
|
+
|
|
6
|
+
.lang-switcher-standalone {
|
|
7
|
+
display: none !important;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@media (max-width: 991.98px) {
|
|
11
|
+
.lang-switcher-standalone {
|
|
12
|
+
display: flex !important;
|
|
13
|
+
align-items: center;
|
|
14
|
+
margin-right: 6px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#language-switcher-standalone select {
|
|
18
|
+
font-size: 24px;
|
|
19
|
+
line-height: 1;
|
|
20
|
+
cursor: pointer;
|
|
21
|
+
padding: 0 2px 0 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.navbar-collapse #language-switcher {
|
|
25
|
+
display: none !important;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* Menu Language (in-drawer + optional standalone bar id language-switcher-standalone) */
|
|
2
|
+
#language-switcher select,
|
|
3
|
+
#language-switcher-standalone select {
|
|
4
|
+
width: auto;
|
|
5
|
+
background-color: transparent;
|
|
6
|
+
font-size: 28px;
|
|
7
|
+
border: 0px solid;
|
|
8
|
+
|
|
9
|
+
/*remove arrow*/
|
|
10
|
+
-webkit-appearance: none;
|
|
11
|
+
-moz-appearance: none;
|
|
12
|
+
text-indent: 1px;
|
|
13
|
+
text-overflow: '';
|
|
14
|
+
|
|
15
|
+
/*remove focus border*/
|
|
16
|
+
outline: none;
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adds class ``display-standalone`` on <html> when the page runs in a standalone
|
|
3
|
+
* display surface (CSS ``display-mode: standalone``) or as an iOS home-screen
|
|
4
|
+
* web app (``navigator.standalone``). Optional hook for future CSS/JS; navbar
|
|
5
|
+
* language placement no longer depends on it (see nav-link-standalone.css).
|
|
6
|
+
*/
|
|
7
|
+
(function () {
|
|
8
|
+
try {
|
|
9
|
+
var dm = window.matchMedia && window.matchMedia("(display-mode: standalone)");
|
|
10
|
+
var ios = window.navigator.standalone === true;
|
|
11
|
+
if ((dm && dm.matches) || ios) {
|
|
12
|
+
document.documentElement.classList.add("display-standalone");
|
|
13
|
+
}
|
|
14
|
+
} catch (e) {}
|
|
15
|
+
})();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{% load i18n urls languages_helpers %}
|
|
2
|
+
|
|
3
|
+
<!-- hreflang -->
|
|
4
|
+
{% get_available_languages as LANGUAGES %}
|
|
5
|
+
{% for language_code, language_name in LANGUAGES %}
|
|
6
|
+
<link rel="alternate" hreflang="{{ language_code|hreflang_bcp47 }}" href="{% translate_url language_code %}" />
|
|
7
|
+
{% endfor %}
|
|
8
|
+
<link rel="alternate" hreflang="x-default" href="{% translate_url DEFAULT_LANGUAGE_CODE %}" />
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{# Beside the mobile menu toggle when (display-mode: standalone) or iOS web app. #}
|
|
2
|
+
{# Use with nav-link-standalone.css + display-standalone-class.js #}
|
|
3
|
+
{% load i18n %}
|
|
4
|
+
<div class="lang-switcher-standalone">
|
|
5
|
+
{% include "lang/nav-link.html" with lang_switcher_id="language-switcher-standalone" %}
|
|
6
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{% load i18n languages_helpers %}
|
|
2
|
+
<div id="{{ lang_switcher_id|default:'language-switcher' }}" class="nav-link{% if lang_switcher_extra_class %} {{ lang_switcher_extra_class }}{% endif %}">
|
|
3
|
+
<form action="{% url 'set_language' %}" method="post">
|
|
4
|
+
{% csrf_token %}
|
|
5
|
+
<input name="next" type="hidden" value="{{ redirect_to }}" />
|
|
6
|
+
<select name="language" onchange="this.form.submit()" aria-label="{% translate 'Language' %}">
|
|
7
|
+
{% for language in request|get_language_info_list_ex %}
|
|
8
|
+
<option value="{{ language.code }}"{% if language.is_current %} selected="selected"{% endif %}>
|
|
9
|
+
<span class="flag">{{ language.flag }}</span>
|
|
10
|
+
</option>
|
|
11
|
+
{% endfor %}
|
|
12
|
+
</select>
|
|
13
|
+
</form>
|
|
14
|
+
</div>
|
|
File without changes
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import flag
|
|
2
|
+
from django import template
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
from django.utils import translation
|
|
5
|
+
|
|
6
|
+
from lang.utils import build_language_flag_map, get_hreflang_code
|
|
7
|
+
|
|
8
|
+
register = template.Library()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@register.filter
|
|
12
|
+
def hreflang_bcp47(django_language_code: str) -> str:
|
|
13
|
+
"""Map Django language code to hreflang (BCP 47); see :mod:`lang.conf`."""
|
|
14
|
+
return get_hreflang_code(django_language_code)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@register.filter
|
|
18
|
+
def get_language_info_list_ex(request):
|
|
19
|
+
"""
|
|
20
|
+
Sample result:
|
|
21
|
+
|
|
22
|
+
[{'bidi': False,
|
|
23
|
+
'code': 'en',
|
|
24
|
+
'flag': '🇬🇧',
|
|
25
|
+
'is_current': False,
|
|
26
|
+
'name': 'English',
|
|
27
|
+
'name_local': 'English',
|
|
28
|
+
'name_translated': 'Inglese'},
|
|
29
|
+
{'bidi': False,
|
|
30
|
+
'code': 'it',
|
|
31
|
+
'flag': '🇮🇹',
|
|
32
|
+
'is_current': True,
|
|
33
|
+
'name': 'Italian',
|
|
34
|
+
'name_local': 'italiano',
|
|
35
|
+
'name_translated': 'Italiano'},
|
|
36
|
+
{'bidi': False,
|
|
37
|
+
'code': 'es',
|
|
38
|
+
'flag': '🇪🇸',
|
|
39
|
+
'is_current': False,
|
|
40
|
+
'name': 'Spanish',
|
|
41
|
+
'name_local': 'español',
|
|
42
|
+
'name_translated': 'Spagnolo'}]
|
|
43
|
+
"""
|
|
44
|
+
data = []
|
|
45
|
+
|
|
46
|
+
# From django.templatetags.i18n.GetLanguageInfoListNode
|
|
47
|
+
def get_language_info(language):
|
|
48
|
+
# ``language`` is either a language code string or a sequence
|
|
49
|
+
# with the language code as its first item
|
|
50
|
+
if len(language[0]) > 1:
|
|
51
|
+
return translation.get_language_info(language[0])
|
|
52
|
+
else:
|
|
53
|
+
return translation.get_language_info(str(language))
|
|
54
|
+
|
|
55
|
+
flag_map = build_language_flag_map()
|
|
56
|
+
|
|
57
|
+
# Es: 'es'
|
|
58
|
+
current_language = translation.get_language()
|
|
59
|
+
|
|
60
|
+
# Es: [('en', 'Inglés'), ('it', 'Italiano'), ('es', 'Español')]
|
|
61
|
+
# languages = [(k, translation.gettext(v)) for k, v in settings.LANGUAGES]
|
|
62
|
+
for language in settings.LANGUAGES:
|
|
63
|
+
# Es: {'bidi': False, 'code': 'es', 'name': 'Spanish',
|
|
64
|
+
# 'name_local': 'español', 'name_translated': 'Español'}
|
|
65
|
+
info = get_language_info(language)
|
|
66
|
+
|
|
67
|
+
code = info["code"]
|
|
68
|
+
info["is_current"] = code == current_language
|
|
69
|
+
|
|
70
|
+
# This requires emoji-country-flag Python package
|
|
71
|
+
info["flag"] = flag.flag(flag_map.get(code, code))
|
|
72
|
+
data.append(info)
|
|
73
|
+
|
|
74
|
+
return data
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
from urllib.parse import unquote, urlsplit, urlunsplit
|
|
4
|
+
|
|
5
|
+
from django import template, urls
|
|
6
|
+
from django.urls import Resolver404, resolve, reverse
|
|
7
|
+
from django.utils.translation import override
|
|
8
|
+
|
|
9
|
+
register = template.Library()
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@register.simple_tag(takes_context=True)
|
|
15
|
+
def translate_url(context: Dict[str, Any], language: Optional[str]) -> str:
|
|
16
|
+
"""
|
|
17
|
+
Given the context of the current page, try to get its translated version in
|
|
18
|
+
the `language` (either by i18n_patterns or by translated regex).
|
|
19
|
+
Return the original URL if no translated version is found.
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
{% translate_url 'en' %}
|
|
23
|
+
"""
|
|
24
|
+
request = context["request"]
|
|
25
|
+
iter_key = f"translated_iter_{language}"
|
|
26
|
+
url_key = f"translated_url_{language}"
|
|
27
|
+
session = request.session
|
|
28
|
+
|
|
29
|
+
# set session's iteration to avoid circular calls
|
|
30
|
+
try:
|
|
31
|
+
if session.get(iter_key, 0) > 0:
|
|
32
|
+
return session[url_key]
|
|
33
|
+
session[iter_key] = 1
|
|
34
|
+
except Exception as err:
|
|
35
|
+
raise Exception(f"Avoid circular execution! [{err}]")
|
|
36
|
+
|
|
37
|
+
if url_key in session:
|
|
38
|
+
logger.debug("session has %s: %s", url_key, session[url_key])
|
|
39
|
+
return session[url_key]
|
|
40
|
+
url = request.build_absolute_uri()
|
|
41
|
+
try:
|
|
42
|
+
parsed = urlsplit(url)
|
|
43
|
+
match = resolve(unquote(parsed.path))
|
|
44
|
+
except Resolver404:
|
|
45
|
+
pass
|
|
46
|
+
else:
|
|
47
|
+
with override(language):
|
|
48
|
+
try:
|
|
49
|
+
view = match.func(request, **match.kwargs)
|
|
50
|
+
if hasattr(view, "url"):
|
|
51
|
+
translated_url = urlunsplit(
|
|
52
|
+
(
|
|
53
|
+
parsed.scheme,
|
|
54
|
+
parsed.netloc,
|
|
55
|
+
view.url,
|
|
56
|
+
parsed.query,
|
|
57
|
+
parsed.fragment,
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
else:
|
|
61
|
+
view_url = urlunsplit(
|
|
62
|
+
(
|
|
63
|
+
parsed.scheme,
|
|
64
|
+
parsed.netloc,
|
|
65
|
+
reverse(
|
|
66
|
+
match.url_name,
|
|
67
|
+
args=match.args,
|
|
68
|
+
kwargs=match.kwargs,
|
|
69
|
+
),
|
|
70
|
+
parsed.query,
|
|
71
|
+
parsed.fragment,
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
translated_url = urls.translate_url(view_url, language)
|
|
75
|
+
session[url_key] = translated_url
|
|
76
|
+
return translated_url
|
|
77
|
+
except Exception as err:
|
|
78
|
+
logger.debug(f"Translate url exception: [{err}]")
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
translated_url = urls.translate_url(url, language)
|
|
82
|
+
session[url_key] = translated_url
|
|
83
|
+
return translated_url
|
lang/utils.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared helpers for language display (hreflang BCP 47, flag region codes).
|
|
3
|
+
|
|
4
|
+
Resolved values come from :mod:`lang.conf` (settings + ``APP_CONFIG`` + defaults).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
# Reasonable defaults for common Django language codes (ISO 3166-1 for flag.emoji).
|
|
10
|
+
DEFAULT_LANGUAGE_FLAG_MAP: dict[str, str] = {
|
|
11
|
+
"ar": "sa",
|
|
12
|
+
"en": "gb",
|
|
13
|
+
"ja": "jp",
|
|
14
|
+
"ko": "kr",
|
|
15
|
+
"pt": "pt",
|
|
16
|
+
"ru": "ru",
|
|
17
|
+
"zh-hans": "cn",
|
|
18
|
+
"zh-hant": "tw",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_hreflang_code(django_language_code: str) -> str:
|
|
23
|
+
"""Return the hreflang attribute value for a Django ``LANGUAGE_CODE``."""
|
|
24
|
+
from lang.conf import get_language_hreflang_map
|
|
25
|
+
|
|
26
|
+
return get_language_hreflang_map().get(
|
|
27
|
+
django_language_code, django_language_code
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def build_language_flag_map() -> dict[str, str]:
|
|
32
|
+
"""Merge built-in flag regions with project / ``APP_CONFIG`` overrides."""
|
|
33
|
+
from lang.conf import get_language_flag_map
|
|
34
|
+
|
|
35
|
+
return get_language_flag_map()
|