Categories
Development MongoDB

How handle i18n(with fallback) on MongoDB when translation is in another Collection?

Given these excerpt collections:

Translation Collection

[
    {
      "_id": "id01_name",
      "text": "Item's Name"
    },
    {
      "_id": "id01_desc",
      "text": "Item's lore description"
    },
    {
      "_id": "sk_id",
      "text": "Item's skill description"
    },

  ]

Item Collection

[
    {
      "_id": "id01",
      "name": "id01_name",
      "lore_description": "id01_desc",
      "skill": {
        "description": "sk_id01",
      }
    }
  ]

Question:

Using only mongodb driver (NO Mongo ODM, like mongoose, iridium, etc), what is the best approach for (i18n) Internationalization (with fallback) on MongoDB when translation is in another Collection?


MongoDB’s aggregate Approach

Currently, I’m using aggregate with $lookup to do these kind of queries.

db.artifact.aggregate([
  { 
    $lookup: { // get the name's data from the english translation
      from: "text_en", 
      localField: "name",
      foreignField: "_id",
      as: "name"
    },

  },
  {
    $unwind: "$name" //unwind because the lookup made name become an array with _id and text
  },
  {
    $addFields: {
      name: "$name.text" //rewrite name (currently an obj) into the translation string
    }
  },

(...etc)

Problem is, I need these 3 steps on every single key I need to translate. On a big document, looks a bit too much, and every $lookup feels like the response time increases bit by bit.

This also doesn’t have fallback cases, in case key is not available in said language, e.g., try $lookup on id01_name in the Spanish collection but the collection doesn’t have it, so a fallback would be get from English collection.

Here a working example of the example above:
https://mongoplayground.net/p/umuPQYriFRe


Manual aggregation Approach

I also thought in doing in phases, that is,

  • item.find() to get item data;
  • translation_english.find({ $in: [ (...listofkeys) ]}) to get all necessary english keys
  • translation_{otherlang}.find({ $in: [ (...listofkeys) ]}) to get all necessary other language keys
  • manually transform the two translation arrays into two objects (with cursor.forEach()), merge with Object.assign({},eng,otherlang)
  • handpicking each key and assigning to the item object

This method covers the fallback, but it’s bloated/very verbose.


On a test with both attempts, lookup took 310ms and the manual took 500ms to complete, that for one lookup. But manual has fallback (aka queries 3 collections; item, lang_en, lang_{foreign}). If language key does not exist in foreign, it’ll pick the en (it’s assumed eng will never miss a key).
While lookup fails to return a document when the lookup on foreign lang fails.

Leave an answer

Your email address will not be published. Required fields are marked *