diff --git a/pygeoapi/api/maps.py b/pygeoapi/api/maps.py index 25ccd218b..360673f18 100644 --- a/pygeoapi/api/maps.py +++ b/pygeoapi/api/maps.py @@ -385,7 +385,6 @@ def get_oas_30(cfg: dict, locale: str) -> tuple[list[dict[str, str]], dict[str, 'operationId': 'getMap', 'parameters': [ {'$ref': '#/components/parameters/bbox'}, - {'$ref': f"{OPENAPI_YAML['oapif-1']}#/components/parameters/datetime"}, # noqa {'$ref': f"{OPENAPI_YAML['oamaps']}#/components/parameters/subset"}, # noqa { 'name': 'width', diff --git a/pygeoapi/openapi.py b/pygeoapi/openapi.py index 09cfee333..5c2f4ffc4 100644 --- a/pygeoapi/openapi.py +++ b/pygeoapi/openapi.py @@ -134,12 +134,13 @@ def gen_response_object(description: str, media_type: str, return response -def gen_contact(cfg: dict) -> dict: +def gen_contact(cfg: dict, locale_: str) -> dict: """ Generates an OpenAPI contact object with OGC extensions based on OGC API - Records contact :param cfg: `dict` of configuration + :param locale_: `str` of locale :returns: `dict` of OpenAPI contact object """ @@ -148,20 +149,22 @@ def gen_contact(cfg: dict) -> dict: has_phones = False contact = { - 'name': cfg['metadata']['provider']['name'] + 'name': l10n.translate(cfg['metadata']['provider']['name'], locale_) } for key in ['url', 'email']: if key in cfg['metadata']['provider']: - contact[key] = cfg['metadata']['provider'][key] + contact[key] = l10n.translate(cfg['metadata']['provider'][key], + locale_) contact['x-ogc-serviceContact'] = { - 'name': cfg['metadata']['contact']['name'], + 'name': l10n.translate(cfg['metadata']['contact']['name'], locale_), 'addresses': [] } if 'position' in cfg['metadata']['contact']: - contact['x-ogc-serviceContact']['position'] = cfg['metadata']['contact']['position'] # noqa + contact['x-ogc-serviceContact']['position'] = l10n.translate( + cfg['metadata']['contact']['position'], locale_) if any(address in ['address', 'city', 'stateorprovince', 'postalcode', 'country'] for address in cfg['metadata']['contact']): # noqa has_addresses = True @@ -169,19 +172,24 @@ def gen_contact(cfg: dict) -> dict: if has_addresses: address = {} if 'address' in cfg['metadata']['contact']: - address['deliveryPoint'] = [cfg['metadata']['contact']['address']] + address['deliveryPoint'] = [l10n.translate( + cfg['metadata']['contact']['address'], locale_)] if 'city' in cfg['metadata']['contact']: - address['city'] = cfg['metadata']['contact']['city'] + address['city'] = l10n.translate( + cfg['metadata']['contact']['city'], locale_) if 'stateorprovince' in cfg['metadata']['contact']: - address['administrativeArea'] = cfg['metadata']['contact']['stateorprovince'] # noqa + address['administrativeArea'] = l10n.translate( + cfg['metadata']['contact']['stateorprovince'], locale_) if 'postalCode' in cfg['metadata']['contact']: - address['administrativeArea'] = cfg['metadata']['contact']['postalCode'] # noqa + address['administrativeArea'] = l10n.translate( + cfg['metadata']['contact']['postalCode'], locale_) if 'country' in cfg['metadata']['contact']: - address['administrativeArea'] = cfg['metadata']['contact']['country'] # noqa + address['administrativeArea'] = l10n.translate( + cfg['metadata']['contact']['country'], locale_) contact['x-ogc-serviceContact']['addresses'].append(address) @@ -192,33 +200,39 @@ def gen_contact(cfg: dict) -> dict: if has_phones: if 'phone' in cfg['metadata']['contact']: contact['x-ogc-serviceContact']['phones'].append({ - 'type': 'main', 'value': cfg['metadata']['contact']['phone'] + 'type': 'main', 'value': l10n.translate( + cfg['metadata']['contact']['phone'], locale_) }) if 'fax' in cfg['metadata']['contact']: contact['x-ogc-serviceContact']['phones'].append({ - 'type': 'fax', 'value': cfg['metadata']['contact']['fax'] + 'type': 'fax', 'value': l10n.translate( + cfg['metadata']['contact']['fax'], locale_) }) if 'email' in cfg['metadata']['contact']: contact['x-ogc-serviceContact']['emails'] = [{ - 'value': cfg['metadata']['contact']['email'] + 'value': l10n.translate( + cfg['metadata']['contact']['email'], locale_) }] if 'url' in cfg['metadata']['contact']: contact['x-ogc-serviceContact']['links'] = [{ 'type': 'text/html', - 'href': cfg['metadata']['contact']['url'] + 'href': l10n.translate(cfg['metadata']['contact']['url'], locale_) }] if 'instructions' in cfg['metadata']['contact']: - contact['x-ogc-serviceContact']['contactInstructions'] = cfg['metadata']['contact']['instructions'] # noqa + contact['x-ogc-serviceContact']['contactInstructions'] = l10n.translate( # noqa + cfg['metadata']['contact']['instructions'], locale_) if 'hours' in cfg['metadata']['contact']: - contact['x-ogc-serviceContact']['hoursOfService'] = cfg['metadata']['contact']['hours'] # noqa + contact['x-ogc-serviceContact']['hoursOfService'] = l10n.translate( + cfg['metadata']['contact']['hours'], locale_) if 'role' in cfg['metadata']['contact']: - contact['x-ogc-serviceContact']['hoursOfService'] = cfg['metadata']['contact']['role'] # noqa + contact['x-ogc-serviceContact']['hoursOfService'] = l10n.translate( + cfg['metadata']['contact']['role'], locale_) return contact @@ -255,11 +269,11 @@ def get_oas_30(cfg: dict, fail_on_invalid_collection: bool = True) -> dict: 'description': l10n.translate(cfg['metadata']['identification']['description'], locale_), # noqa 'x-keywords': l10n.translate(cfg['metadata']['identification']['keywords'], locale_), # noqa 'termsOfService': - cfg['metadata']['identification']['terms_of_service'], - 'contact': gen_contact(cfg), + l10n.translate(cfg['metadata']['identification']['terms_of_service'], locale_), # noqa + 'contact': gen_contact(cfg, locale_), 'license': { - 'name': cfg['metadata']['license']['name'], - 'url': cfg['metadata']['license']['url'] + 'name': l10n.translate(cfg['metadata']['license']['name'], locale_), # noqa + 'url': l10n.translate(cfg['metadata']['license']['url'], locale_) }, 'version': api_rules.api_version } @@ -360,7 +374,7 @@ def get_oas_30(cfg: dict, fail_on_invalid_collection: bool = True) -> dict: 'description': l10n.translate(cfg['metadata']['identification']['description'], locale_), # noqa 'externalDocs': { 'description': 'information', - 'url': cfg['metadata']['identification']['url']} + 'url': l10n.translate(cfg['metadata']['identification']['url'], locale_)} # noqa } ) diff --git a/tests/other/test_openapi.py b/tests/other/test_openapi.py index 0832ec433..ea99791cf 100644 --- a/tests/other/test_openapi.py +++ b/tests/other/test_openapi.py @@ -58,13 +58,20 @@ def config_hidden_resources(): return yaml_load(fh) +@pytest.fixture() +def config_i18n(): + filename = 'pygeoapi-test-config-i18n.yml' + with open(get_test_file_path(filename)) as fh: + return yaml_load(fh) + + @pytest.fixture() def openapi(): with open(get_test_file_path('pygeoapi-test-openapi.yml')) as fh: return yaml_load(fh) -def test_str2bool(): +def test_get_ogc_schemas_location(): default = { 'url': 'http://localhost:5000' @@ -141,6 +148,13 @@ def test_hidden_resources(config_hidden_resources): assert '/collections/obs/items' not in openapi_doc['paths'] +def test_i18n(config_i18n): + openapi_doc = get_oas(config_i18n) + + assert isinstance(openapi_doc['info']['contact']['name'], str) + assert openapi_doc['info']['contact']['name'] == 'Organization Name' + + def test_admin_empty_resources(config_admin_empty_resources): openapi_doc = get_oas(config_admin_empty_resources) assert '/admin/config' in openapi_doc['paths'] diff --git a/tests/pygeoapi-test-config-i18n.yml b/tests/pygeoapi-test-config-i18n.yml new file mode 100644 index 000000000..9f9c3c1f4 --- /dev/null +++ b/tests/pygeoapi-test-config-i18n.yml @@ -0,0 +1,192 @@ +# ================================================================= +# +# Authors: Tom Kralidis +# +# Copyright (c) 2026 Tom Kralidis +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation +# files (the "Software"), to deal in the Software without +# restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# ================================================================= + +server: + bind: + host: 0.0.0.0 + port: 5000 + url: http://localhost:5000/ + mimetype: application/json; charset=UTF-8 + encoding: utf-8 + gzip: false + languages: + # First language is the default language + - en-US + - fr-CA + cors: true + pretty_print: true + limits: + default_items: 10 + max_items: 10 + # templates: /path/to/templates + map: + url: https://tile.openstreetmap.org/{z}/{x}/{y}.png + attribution: '© OpenStreetMap contributors' + manager: + name: TinyDB + connection: /tmp/pygeoapi-test-process-manager.db + output_dir: /tmp + +logging: + level: DEBUG + #logfile: /tmp/pygeoapi.log + +metadata: + identification: + title: + en: pygeoapi default instance + fr: instance par défaut de pygeoapi + description: + en: pygeoapi provides an API to geospatial data + fr: pygeoapi fournit une API aux données géospatiales + keywords: + en: + - geospatial + - data + - api + fr: + - géospatiale + - données + - api + keywords_type: theme + terms_of_service: https://creativecommons.org/licenses/by/4.0/ + url: http://example.org + license: + name: + en: CC-BY 4.0 license + fr: license CC-BY 4.0 license + url: + en: https://creativecommons.org/licenses/by/4.0/ + fr: https://creativecommons.org/licenses/by/4.0/ + provider: + name: + en: Organization Name + fr: nom d'organisation + url: + en: https://pygeoapi.io + fr: https://pygeoapi.io + contact: + name: + en: Lastname, Firstname + fr: nom de famille, nom + position: + en: Position Title + fr: titre du poste + address: + en: Mailing Address + fr: address postale + city: + en: City + fr: ville + stateorprovince: + en: Administrative Area + fr: zone administrative + postalcode: + en: Zip or Postal Code + fr: code postale + country: + en: Country + fr: pays + phone: + en: +xx-xxx-xxx-xxxx + fr: +xx-xxx-xxx-xxxx + fax: + en: +xx-xxx-xxx-xxxx + fr: +xx-xxx-xxx-xxxx + email: you@example.org + url: + en: Contact URL + fr: URL de contact + hours: + en: Hours of Service + fr: heures de service + instructions: + en: During hours of service. Off on weekends. + fr: pendant les heures de service. Fermé le week-end. + role: pointOfContact + +resources: + obs: + type: collection + title: + en: Observations + fr: Observations + description: + en: My cool observations + fr: Mes belles observations + keywords: + - observations + - monitoring + links: + - type: text/csv + rel: canonical + title: data + href: https://github.com/mapserver/mapserver/blob/branch-7-0/msautotest/wxs/data/obs.csv + hreflang: en-US + - type: text/csv + rel: alternate + title: data + href: https://raw.githubusercontent.com/mapserver/mapserver/branch-7-0/msautotest/wxs/data/obs.csv + hreflang: en-US + linked-data: + context: + - schema: https://schema.org/ + stn_id: + "@id": schema:identifier + "@type": schema:Text + datetime: + "@type": schema:DateTime + "@id": schema:observationDate + value: + "@type": schema:Number + "@id": schema:QuantitativeValue + extents: + spatial: + bbox: [-180,-90,180,90] + crs: http://www.opengis.net/def/crs/OGC/1.3/CRS84 + temporal: + begin: 2000-10-30T18:24:39Z + end: 2007-10-30T08:57:29Z + trs: http://www.opengis.net/def/uom/ISO-8601/0/Gregorian + resolution: P1D + default: 2000-10-30T18:24:39Z + providers: + - type: feature + name: CSV + data: tests/data/obs.csv + crs: + - http://www.opengis.net/def/crs/OGC/1.3/CRS84 + - http://www.opengis.net/def/crs/EPSG/0/4326 + - http://www.opengis.net/def/crs/EPSG/0/3857 + - http://www.opengis.net/def/crs/EPSG/0/28992 + storage_crs: http://www.opengis.net/def/crs/OGC/1.3/CRS84 + id_field: id + geometry: + x_field: long + y_field: lat