I use SHPS: I store my i18n in a normalized DB, then I use a toolset and pass the request headers (done automatically in template scripts, see example below) plus the group/string identifier to receive the string I need in the target language. I could also retrieve all strings in the target language as a group and send that group to the client (for dynamic content). It's really that easy for me. I usually do the latter via AJAX or websockets and cache the strings in the browser for reduced traffic.
The advantage of storing stuff in a DB like that is that it is sorted, relational and can be sent away as CSV file for translation to a third party. The translation can then be inserted into the DB just as easy. There are tons of office programs for manipulating CSV files (for instance Calc and Excel) and many DBs are able to export/import CSV files directly.
const d = require('promise-defer')();
lang.getString('my-group', 'string-id').then(str => {
d.resolve(`The string in your language: ${str}!`);
}, d.reject);
d.promise; // the result of this line is returned from the template script; SHPS handles all errors