Skip to content

WIP: Internationalization and localization #892

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions babel.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[extractors]
yaml = ohsome_quality_api.utils:pybabel_yaml_extractor

[python: **.py]

[yaml: **.yaml]
keys = name,description,red,yellow,green,undefined,result_description
encoding = utf-8
17 changes: 17 additions & 0 deletions docs/i18n.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Internationalization (I18N) and Localization (I10N)

## Babel

Add a new locale:
```bash
poetry run pybabel extract -F babel.cfg -o messages.pot .
pybabel init -i messages.pot -d ohsome_quality_api/locale -l de
pybabel compile -d ohsome_quality_api/locale
```

Update a existing locale:
```bash
poetry run pybabel extract -F babel.cfg -o messages.pot .
pybabel update -i messages.pot -d ohsome_quality_api/locale
pybabel compile -d ohsome_quality_api/locale
```
4 changes: 3 additions & 1 deletion ohsome_quality_api/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import os
from typing import Any, Union

from fastapi import FastAPI, HTTPException, Request, status
from fastapi import Depends, FastAPI, HTTPException, Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware
Expand All @@ -13,6 +13,7 @@
get_swagger_ui_oauth2_redirect_html,
)
from fastapi.responses import JSONResponse
from fastapi_i18n import i18n
from geojson import FeatureCollection
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.staticfiles import StaticFiles
Expand Down Expand Up @@ -114,6 +115,7 @@
openapi_tags=TAGS_METADATA,
docs_url=None,
redoc_url=None,
dependencies=[Depends(i18n)],
)

app.add_middleware(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,17 @@ def calculate(self) -> None:
if self.result.value >= self.threshold_yellow:
self.result.class_ = 5
self.result.description = (
self.description + self.templates.label_description["green"]
self.description + self.templates.label_description.green
)
elif self.threshold_yellow > self.result.value >= self.threshold_red:
self.result.class_ = 3
self.result.description = (
self.description + self.templates.label_description["yellow"]
self.description + self.templates.label_description.yellow
)
else:
self.result.class_ = 1
self.result.description = (
self.description + self.templates.label_description["red"]
self.description + self.templates.label_description.red
)

def create_description(self):
Expand Down
2 changes: 1 addition & 1 deletion ohsome_quality_api/indicators/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def __init__(
self.topic: Topic = topic
self.feature: Feature = feature
self.result: Result = Result(
description=self.templates.label_description["undefined"],
description=self.templates.label_description.undefined
)
self._get_default_figure()

Expand Down
12 changes: 9 additions & 3 deletions ohsome_quality_api/indicators/building_comparison/indicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,19 @@ def calculate(self) -> None:
self.result.class_ = 3
elif self.th_low > self.result.value >= 0:
self.result.class_ = 1
label_description = self.templates.label_description[self.result.label]
label_description = getattr(
self.templates.label_description, self.result.label
)
self.result.description = " ".join((label_description, result_description))
elif major_edge_case:
label_description = self.templates.label_description[self.result.label]
label_description = getattr(
self.templates.label_description, self.result.label
)
self.result.description = " ".join((label_description, result_description))
else:
label_description = self.templates.label_description[self.result.label]
label_description = getattr(
self.templates.label_description, self.result.label
)
edge_case = (
"OSM has substantivly more buildings than the reference datasets. The "
"reference dataset is likely to miss many buildings."
Expand Down
2 changes: 1 addition & 1 deletion ohsome_quality_api/indicators/currentness/indicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def calculate(self):
else:
self.result.class_ = 1

label_description = self.templates.label_description[self.result.label]
label_description = getattr(self.templates.label_description, self.result.label)
self.result.description += Template(
self.templates.result_description
).substitute(
Expand Down
6 changes: 3 additions & 3 deletions ohsome_quality_api/indicators/density/indicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,18 @@ def calculate(self) -> None:
if self.result.value >= self.threshold_yellow:
self.result.class_ = 5
self.result.description = (
description + self.templates.label_description["green"]
description + self.templates.label_description.green
)
else:
if self.result.value > self.threshold_red:
self.result.class_ = 3
self.result.description = (
description + self.templates.label_description["yellow"]
description + self.templates.label_description.yellow
)
else:
self.result.class_ = 1
self.result.description = (
description + self.templates.label_description["red"]
description + self.templates.label_description.red
)

def create_figure(self) -> None:
Expand Down
4 changes: 2 additions & 2 deletions ohsome_quality_api/indicators/mapping_saturation/indicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ def calculate(self) -> None: # noqa: C901
description = Template(self.templates.result_description).substitute(
saturation=round(self.result.value * 100, 2)
)
self.result.description = (
description + self.templates.label_description[self.result.label]
self.result.description = description + getattr(
self.templates.label_description, self.result.label
)

def create_figure(self) -> None:
Expand Down
4 changes: 1 addition & 3 deletions ohsome_quality_api/indicators/minimal/indicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ async def preprocess(self) -> None:
def calculate(self) -> None:
description = Template(self.templates.result_description).substitute()
self.result.value = 1.0
self.result.description = (
description + self.templates.label_description["green"]
)
self.result.description = description + self.templates.label_description.green

def create_figure(self) -> None:
# Do nothing ...
Expand Down
27 changes: 25 additions & 2 deletions ohsome_quality_api/indicators/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from datetime import datetime, timezone
from typing import Literal

from pydantic import BaseModel, ConfigDict, Field, computed_field
from fastapi_i18n import _
from pydantic import BaseModel, ConfigDict, Field, computed_field, field_validator

from ohsome_quality_api.projects.definitions import ProjectEnum
from ohsome_quality_api.quality_dimensions.definitions import QualityDimensionEnum
Expand All @@ -23,13 +24,35 @@ class IndicatorMetadata(BaseModel):
populate_by_name=True,
)

@field_validator("name", "description", mode="before")
@classmethod
def translate(cls, value: str) -> str:
return _(value)


class LabelDescription(BaseModel):
green: str
yellow: str
red: str
undefined: str

@field_validator("green", "yellow", "red", "undefined", mode="before")
@classmethod
def translate(cls, value: str) -> str:
return _(value)


class IndicatorTemplates(BaseModel):
"""Result text templates of an indicator as defined in the templates.yaml file."""

label_description: dict[str, str]
label_description: LabelDescription
result_description: str

@field_validator("result_description", mode="before")
@classmethod
def translate(cls, value: str) -> str:
return _(value)


class Result(BaseModel):
"""The result of the Indicator.
Expand Down
2 changes: 1 addition & 1 deletion ohsome_quality_api/indicators/road_comparison/indicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def calculate(self) -> None:
elif self.th_low > self.result.value >= 0:
self.result.class_ = 1

label_description = self.templates.label_description[self.result.label]
label_description = getattr(self.templates.label_description, self.result.label)
self.result.description += label_description
# remove double white spaces
self.result.description = " ".join(self.result.description.split())
Expand Down
Loading