DEV Community

Discussion on: Speed-up your internationalization calls up to 5-1000 times

Collapse
 
qm3ster profile image
Mihail Malo • Edited
var p=a||{};return "Check my "+(p["pet"]=="cat"?"evil cat":p["pet"]=="dog"?"good boy":(p["pet"]||(p["pet"]=="0"?0:"")))+" :D"

With a little more work of the compiler, there are things that could make the function shorter:

var p=a||{},a=p.pet;return "Check my "+(a=="cat"?"evil cat":a=="dog"?"good boy":a||(a=="0"?0:""))+" :D"

Can you clarify to me what the (a||(a=="0"?0:"")) is doing?

Thread Thread
 
vince_tblt profile image
Vincent Thibault

Yeah some optimizations can definitively cleanup the generated function. I just wanted to avoid spending much time (and file size) just to prettify the output.

The var p=a||{}; for example can be removed in case of raw string (that's not actually the case).

About the (a||(a=="0"?0:"")), it's actually to avoid printing "undefined", "null" in a translation, but keep "0" working :

// 'test' : 'Value: {value} !'

t('test'); // 'Variable:  !'
t('test', { value: undefined ); // 'Value:  !'
t('test', { value: null ); // 'Value:  !'
t('test', { value: 'test' ); // 'Value: test !'
t('test', { value: 0 ); // 'Value: 0 !'
Thread Thread
 
qm3ster profile image
Mihail Malo

I'm not well-versed in i18n, is that the expected behavior of pet, select, other{{pet}}? Empty string for entirely missing key?

Thread Thread
 
qm3ster profile image
Mihail Malo • Edited

Can I suggest this way of inlining selects or will it hit performance?

var a=p&&p.pet;return "Check my "+{cat:"evil cat",dog:"good boy"}[a]||a||(a=="0"?0:"")+" :D"

If you do manage to get rid of new Function, you could do things like this though (n being a helper shared by all functions for all locales):

const n=x=>typeof x==="string"?x:"",
g=(c,x)=>c[x]||n(a)
// Elsewhere...
const c={cat:"evil cat",dog:"good boy"},
out=({pet:a}={},f)=>`Check my ${g(c,a)} :D`
Thread Thread
 
vince_tblt profile image
Vincent Thibault

Depend of the i18n libs, some are printing undefined, some others are keeping "{variable}" in translation.

As for me, I think it's a better user experience to have an empty string than a variable name (else the website seems broken).

Thread Thread
 
qm3ster profile image
Mihail Malo

Should probably report an error in prod(in addition to whatever behavior you suggest)/fail in dev?
Imo the key name is less bad than an empty string, I have been on broken sites/apps where I could complete my flow in part thanks to the variable names in the template. But that's a nitpick.

Thread Thread
 
vince_tblt profile image
Vincent Thibault

Ok, I note the suggestion, I'll try to implement it this week end, something like the onMissingKey is working :

frenchkiss.onMissingVariable((value, key, lang) => {
  // report it
  sendReport(`variable "${value}" in ${lang}->${key} is missing.`);

  // the value you want to use instead
  return `[${value}]`;
});
Thread Thread
 
vince_tblt profile image
Vincent Thibault • Edited

Just saw your comment about SELECT optimization.

I already did some tests with it working using an object mapping to values, but it doesn’t work well with nested expressions.

With nested expressions, you can’t really pre-cache objects and it will execute all the possible branches code before doing the resolution leading to performance issue.

Thread Thread
 
qm3ster profile image
Mihail Malo • Edited

With nested expressions you can have functions for some branches and strings for some. :v
Don't know about performance, but it is usually very compact, and you just need one helper:

const e = (table, x, p) => {
  const val = table[p]
  return typeof val === "function" ? val(x) : val
}
const a = { a: "A", b: "chungus" }
const b = { a: "B", b: x => `<${e(a, x, x.a)}>` }
const c = { a: "Cfunction b(x) {", b: x => `[${e(b, x, x.b)}]` }
const rule = x => `Yo, ${e(c, x, x.c)}`

const prop = { a: "b", b: "b", c: "b" }
console.log(rule(prop))
prop.a = "a"
console.log(rule(prop))
prop.b = "a"
console.log(rule(prop))
prop.c = "a"
console.log(rule(prop))
Thread Thread
 
vince_tblt profile image
Vincent Thibault

Not really so compact if you transpile it to ES5.
Here is an example of a complete solution (if I don't miss a thing ?).

The translation :

Updated: {minutes, plural,
  easteregg {never}
  =0 {just now}
  =1 {one minute ago}
  other {
    {minutes} minutes ago by {gender, select,
      male {male}
      female {female}
      other {other}
    }
  }
}

The global functions

// Global SELECT
function getBranchData(
  branch,
  prop,
  params,
  getPluralCategory,
  onMissingVariable,
  key,
  language
) {
  var data = branch.hasOwnProperty(prop) ? branch[prop] : branch.other; // fallback to 'others'

  return typeof data === "function"
    ? data(params, getPluralCategory, onMissingVariable, key, language)
    : data;
}

// Global PLURAL
function getBranchPluralData(
  branch,
  prop,
  params,
  getPluralCategory,
  onMissingVariable,
  key,
  language,
) {
  var category = getPluralCategory && getPluralCategory(params[prop]);
  var data;

  if (branch.hasOwnProperty(prop)) {
    // direct assignment
    data = branch[prop];
  } else if (category && branch.hasOwnProperty(category)) {
    // category check (easter egg)
    data = branch[category];
  } else {
    // default to other
    data = branch.other;
  }

  return typeof data === "function"
    ? data(params, getPluralCategory, onMissingVariable, key, language)
    : data;
}

// Global Interpolation
function handleInterpolation(params, prop, onMissingVariable, key, language) {
  return !params.hasOwnProperty(prop)
    ? onMissingVariable(prop, key, language)
    : typeof params[prop] === "number"
    ? params[prop]
    : params[prop] || "";
}

The generated function demo:

function functionGenerator() {
  // Closure to avoid re-defining branches at each call
  var branchA = {
    0: "just now",
    1: "one minute ago",
    other: function(params, getPluralCategory, onMissingVariable, key, language) {
      return (
        handleInterpolation(params, "minutes", onMissingVariable, key, language) +
        " minutes ago by " +
        getBranchData(
          branchB,
          params.gender,
          params,
          getPluralCategory,
          onMissingVariable,
          key,
          language
        )
      );
    }
  };

  var branchB = {
    male: "male",
    female: "female",
    other: "other"
  };

  return function(params, getPluralCategory, onMissingVariable, key, language) {
    return (
      "Updated: " +
      getBranchPluralData(
        branchA,
        params.minutes,
        params,
        getPluralCategory,
        onMissingVariable,
        key,
        language
      )
    );
  };
}

var fn = functionGenerator();
fn({
  minutes: 5,
  gender: "male"
});

I'll probably do a branch to see if it's a good candidate.

Thread Thread
 
qm3ster profile image
Mihail Malo
        params,
        getPluralCategory,
        onMissingVariable,
        key,
        language

should probably be an object, passed by reference instead of individually through arguments?