xrstf
xrstf

Reputation: 1162

URL Layout for multi-lingual CMS

So I'm trying to add a new, shiny REST webservice to our CMS. It should follow the REST "roules" pretty closely, so it shall use GET/POST/PUT/DELETE and proper, logical URLs. My design is heavily inspired by the Apigee Best Practices.

The CMS manages a tree of categories, where each category can contain a number of articles. For multi-lingual projects, the categories are duplicated for each language (so any article is uniquely identifier by its ID and the language ID). The structure is the same across all languages, only the positions can vary (so category X contains the categories Y and Z in every language, but Y can be before Z in language 1 and the other way around in language 2). Creating a new article or category always also creates copies in all languages. Deleting works the same way, so an article is always deleted in all languages.

FYI: Languages are identified by their numeric ID or their locale (like en_US).

(Please image a leading /v1 in front of all URIs; I've skipped it because it adds nothing to this question.)`

My current approach is having an URL scheme like this:

GET    /articles               :(  returns all articles in all languages
GET    /articles/:id-:langid   :)  returns a single article in a given language
POST   /articles               :)  creates a new article
PUT    /articles/:id-:langid   :)  update a single article in a given language
DELETE /articles/:id           :)  delete an article in all languages

But...

How to identify languages?

Currently, the locale each language is assigned does not need to be unique, so two languages can in rare cases have the same locale. Using the ID is guaranteed to be unique.

Using the locale (most likely forced to lowercase) would be nice, cause the URLs are more readable. But this would

I would like to end up with exactly one solution to this and tend towards using the locale. But is it worth risking overlapping locales?

(You can image langid=X to be interchangeable with locale=xx_xx in the following.)

Should the language be a hierarchy element?

In most cases, clients of the API will want to fetch the articles for a given language instead of all. I could solve this by allowing a GET parameter and offer GET /articles?langid=42. But since is such a common usecase, I would like to avoid the optional parameter and make it explicit.

This would lead to GET /articles/:langid, but this introduces the concept that the language is a hierarchy level inside the API. If I'm going to do so, I would like to make it consistent for the other verbs. The new URL layout would look like this:

GET    /articles/:langid       :)  returns all articles in a given language
GET    /articles/:langid/:id   :)  returns a single article in a given language
POST   /articles               :(  creates a new article in all languages
PUT    /articles/:langid/:id   :)  update a single article in a given language
DELETE /articles/:langid/:id   :(  delete an article in all languages

or

GET    /:langid/articles       :)  returns all articles in a given language
GET    /:langid/articles/:id   :)  returns a single article in a given language
POST   /articles               :(  creates a new article in all languages
PUT    /:langid/articles/:id   :)  update a single article in a given language
DELETE /:langid/articles/:id   :(  delete an article in all languages

This leads to...

What to do with global actions?

The above layout has the problem that POST and DELETE work on all languages, but the URL suggests that they work only on one language. So I could modify the layout:

GET    /articles/:langid       :)  returns all articles in a given language
GET    /articles/:langid/:id   :)  returns a single article in a given language
POST   /articles               :)  creates a new article in all languages
PUT    /articles/:langid/:id   :)  update a single article in a given language
DELETE /articles/:id           :)  delete an article in all languages

This makes for a somewhat confusing layout, as the language level is only sometimes present and one needs to know a lot about the system's interna. On the bright side, this matches the system very well and is very similar to the internal workings.

Sacrifices?

So where do I make sacrifies? Should I introduce alias URLs to have nice URL layouts but do maybe unintended stuff in the backgroud?

Upvotes: 2

Views: 167

Answers (2)

PetrosZ
PetrosZ

Reputation: 11

I had the exact same problem some time ago and I decided to put local in front of everything. While consuming content it makes much more sense to read URLs like:

/en/articles/1/

/en/authors/2/

/gr/articles/1/

/gr/authors/2/

than

/articles/en/1/

/authors/en/2/

/articles/gr/1/

/authors/gr/2/

In order to solve the "no specific locale" issue I would use a keyword referring to all available locales, or a default locale. So:

/global/articles/

or

/all-locales/articles/

or

/all/articles/

to be honest I like global and all because they make sense reading them.

DELETE /global/articles/:id :) delete an article in all languages

hope I helped

Upvotes: 1

gnz
gnz

Reputation: 394

My initial approach would have been to have language as an optional prefix in the URL, e.g. /en_us/articles, /en_us/articles/:id, but if you don't want to 'pollute' your URLs with language identifiers you can use the Accept-language header instead, in the same way that content negotiation is defined in RFC 2616. Microsoft's WebAPI is using this approach for format negotiation.

Upvotes: 0

Related Questions