How to manage i18n resource with API / JSON-LD / Hydra ?

Hi,

My name is Mikaël Labrut, I am actually working on an API using Hydra using
DunglasApiBundle.
This is great but, but one of my question have no "normalized" answer :
i18n.

Here is a copy of this topic (
https://github.com/dunglas/DunglasApiBundle/issues/127), we want to have a
lot of opinion about this topic before implementing a solution.
Feel free to say what do you think about that and what is the best way ??

Problem :
Imagine you have a "Country" (http://schema.org/Country) resource in your
API.
How to manage i18N on it ?

Here is a detailed solution, i have design based on experience, and the
JSON-LD spec.
Tell me if you think this is a good idea ?
If you think it's not ... tell me why ?

## How to get a specific locale on a resource ? (=GET)

### My solution

Add a GET specific parameter "locale"
Why ?
- Simple (this is the way used in FB graph api for example)
- One URL = One localized resource (like wikipedia, better for indexing or
caching)
- use of @language specified in JSON-LD spec
- use @container:@language

### Sample 1 : No locale specified

GET http://api.example.com/countries/1
```json
{
  "@context": {
    "@base" : "http://schema.org",
    "name" : {
      "@container": "@language"
    }
  },
  "@type": "Country",
  "@id": "/countries/1",
  "name": {
    "fr-FR" : "Angleterre",
    "en" : "England"
  }
}
```
=> Will return resource with all locale data

### Sample 2 : Use specific localization

GET http://api.example.com/countries/1?locale=fr-FR
```json
{
  "@context": "http://schema.org",
  "@type": "Country",
  "@id": "/countries/1",
  "@language": "fr-FR",
  "name": "Angleterre"
}
```
=> Will return resource with requested locale

Note we use locale (on request + response) in W3C format (IETF's BCP 47),
see http://www.w3.org/International/articles/language-tags/
It's the format used also with JSON-LD.

### Sample 3 : Error localization don't exist

GET http://api.example.com/countries/1?locale=es-CA
```json
{
  "@context": "/contexts/LocalizationError",
  "@type": "LocalizationError",
  "hydra:title": "An error occurred",
  "hydra:description": "no localization found for locale es-CA"
}
```
=> Will return code "404 not found", because es-CA localization don't exist
in my api.

## How to get a specific locale on a list of resource ? (=GET)

### My solution :
same as for one resource

### Sample 1 : No locale specified

GET http://api.example.com/countries
```json
{
  "@context": {
    "@base" : "http://schema.org",
    "name" : {
      "@container": "@language"
    }
  },
  "@id": "/countries",
  "@type": "hydra:PagedCollection",
  "hydra:totalItems": 1,
  "hydra:itemsPerPage": 3,
  "hydra:firstPage": "/countries",
  "hydra:lastPage": "/countries",
  "hydra:member": [
    {
      "@type": "Country",
      "@id": "/countries/1",
      "name": {
        "fr-FR" : "Angleterre",
        "en" : "England"
      }
    }
  ]
}
```
=> Will return resources with all available localization

### Sample 2 : Use specific localization

GET http://api.example.com/countries?locale=fr-FR
```json
{
  "@context": "http://schema.org",
  "@id": "/countries",
  "@type": "hydra:PagedCollection",
  "@language": "fr-FR",
  "hydra:totalItems": 1,
  "hydra:itemsPerPage": 3,
  "hydra:firstPage": "/countries",
  "hydra:lastPage": "/countries",
  "hydra:member": [
    {
      "@type": "Country",
      "@id": "/countries/1",
      "name": "Angleterre"
    }
  ]
}
```
=> Will return resource with requested locale

### Sample 3 : Error localization don't exist

GET http://api.example.com/countries?locale=es-CA
```json
{
  "@context": "http://schema.org",
  "@id": "/countries",
  "@type": "hydra:PagedCollection",
  "@language": "es-CA",
  "hydra:totalItems": 0,
  "hydra:itemsPerPage": 3,
  "hydra:firstPage": "/countries",
  "hydra:lastPage": "/countries",
  "hydra:member": []
}
```

## How to delete a localized resource ? (=DELETE)

### My solution :
same as usual but the localization are also deleted completely

DELETE http://api.example.com/countries/1
=> Will delete country and all associed localization

DELETE http://api.example.com/countries/1?locale=fr-FR
=> Will delete country localization fr-FR only !

## How to create a localized resource ? (POST)

### My solution :
create with a locale container or indicate the language in context

POST http://api.example.com/countries
```json
{
    "name": {
        "fr-FR" : "Angleterre",
        "en": "England"
    }
}
```
=> Will create country with two localized value

OR

POST http://api.example.com/countries
```json
{
    "@context": {
        "@language": "fr-FR"
    },
    "name": "Angleterre"
}
```
=> Will create country with one localized value

## How to add a new locale to a localized resource ? (PUT)

### My solution :
same as POST, you must specified @language or put all localized data

PUT http://api.example.com/countries/1
```json
{
    "@context" : {
        "@language": "it"
    },
    "name": "Inghilterra"
}
```
=> Will add or replace locale "it" name for the resource /countries/1

PUT http://api.example.com/countries/1
```json
{
    "name": {
        "fr" : "Angleterre"
    }
}
```
=> Will delete all localization for name and just have fr localization

## How to list all available locale for a resource ?

### My solution :
add a specific endpoint to api like :

GET http://api.example.com/countries/1/locales
```json
{
  "@context": "http://schema.org",
  "@id": "/http://api.example.com/countries/1/locales",
  "@type": "hydra:PagedCollection",
  "hydra:totalItems": 2,
  "hydra:itemsPerPage": 3,
  "hydra:firstPage": "/countries",
  "hydra:lastPage": "/countries",
  "hydra:member": [
    {
      "@type": "Locale",
      "@id": "/locale/fr"
    },
    {
      "@type": "Locale",
      "@id": "/locale/en"
    }
  ]
}
```
=> Will return all available locale for the resource

## Another way : separate localized content / resource

I study another possibility to provide the i18n support on a resource.
To add a collection of translation when needed.

For example :

GET http://api.example.com/countries/1?locale=fr-FR
```json
{
  "@context": "http://schema.org",
  "@type": "Country",
  "@id": "/countries/1",
  "@language": "fr-FR",
  "nonLocalizedAttribute": 10,
  "localization": {
    "@type": "CountryLocalization",
    "@id": "/country_localization/1/fr-FR",
    "name": "Angleterre"
  }
}
```

GET http://api.example.com/countries/1
```json
{
  "@context": "http://schema.org",
  "@type": "Country",
  "@id": "/countries/1",
  "nonLocalizedAttribute": 10,
  "localizations":{
     "en": {
         "@type": "CountryLocalization",
         "@id": "/country_localization/1/fr-FR",
         "name": "Angleterre"
      },
     "fr-FR": {
         "@type": "CountryLocalization",
         "@id": "/country_localization/1/fr-FR",
         "name": "England"
      }
}
}
```

This way he simpler for many reasons, but I think it's not very JSON-LD /
Hydra compliant.
It's simpler because :
* you cut the "localized" part of the resource into separate resource, wich
can be managed like other resources.
* "Like the DB storage"

It's not JSON-LD / Hydra compliant because :
* you change structure of object ( localization / localizations attribute
is dynamical ... ), you cannot use schema.org vocabulary because of that

What do you think about this ?

Best regards,

-- 
Mikaël Labrut - Team B2B
Github : MLKiiwy

Received on Friday, 19 June 2015 13:55:22 UTC