Use webpack to load locale data

pull/1/head
cpsdqs 7 years ago committed by Ondřej Hruška
parent 795969842c
commit 8e36765c2e
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 1
      .gitignore
  2. 28
      dump_js_lang.php
  3. 5
      js/lang.js
  4. 12
      lang/keys.js
  5. 178
      lang/lang-loader.js
  6. 11
      webpack.config.js

1
.gitignore vendored

@ -7,4 +7,3 @@ node_modules/
.idea
.sass-cache
*.map
js/lang.js

@ -1,28 +0,0 @@
<?php
/* This script is run on demand to generate JS version of tr() */
require_once __DIR__ . '/base.php';
$selected = [
'wifi.connected_ip_is',
'wifi.not_conn',
'wifi.enter_passwd',
'term_nav.fullscreen',
'term_conn.connecting',
'term_conn.waiting_content',
'term_conn.disconnected',
'term_conn.waiting_server',
'term_conn.reconnecting'
];
$out = [];
foreach ($selected as $key) {
$out[$key] = tr($key);
}
file_put_contents(__DIR__. '/js/lang.js',
"// Generated from PHP locale file\n" .
'let _tr = ' . json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE) . ";\n\n" .
"module.exports = function tr (key) { return _tr[key] || '?' + key + '?' }\n"
);

@ -0,0 +1,5 @@
let data = require('locale-data')
module.exports = function localize (key) {
return data[key] || `?${key}?`
}

@ -0,0 +1,12 @@
// define language keys used by JS here
module.exports = [
'wifi.connected_ip_is',
'wifi.not_conn',
'wifi.enter_passwd',
'term_nav.fullscreen',
'term_conn.connecting',
'term_conn.waiting_content',
'term_conn.disconnected',
'term_conn.waiting_server',
'term_conn.reconnecting'
]

@ -0,0 +1,178 @@
/*
* This is a Webpack loader.
* Loads language PHP files by parsing the PHP syntax and returning a Javascript
* Object. This is because using JSON instead of PHP would not allow multiline
* strings, and CSON is unsupported by PHP, and because splitting the language
* files into PHP and JSON seems unwieldy.
*/
const selectedKeys = require('./keys')
const tokenizeRest = function (tokens, tokenize, ...args) {
let i = 0
while (i < tokens.length) {
if (typeof tokens[i] === 'string') {
tokens.splice(i, 1, ...tokenize(tokens[i], ...args))
}
i++
}
return tokens
}
const tokenizers = {
simpleType (code, regex, type, matchIndex = 1) {
let tokens = []
let match
while ((match = code.match(regex))) {
let before = code.substr(0, match.index)
code = code.substr(match.index + match[0].length)
if (before) tokens.push(before)
tokens.push({
type,
match,
content: match[matchIndex]
})
}
if (code) tokens.push(code)
return tokens
}
}
const unescapeString = function (token) {
if (token.match[1] === "'") {
// single-quoted string only has \\ and \'
return token.content.replace(/\\[\\']/g, match => match[1])
} else {
// double-quoted string
// \n -> 0x0A
// \r -> 0x0D
// \t -> 0x09
// \v -> 0x0B
// \e -> 0x1B
// \f -> 0x0C
// \\ -> \
// \$ -> $
// \" -> "
// \[0-7]{1,3} -> char
// \x[\da-f]{1,2} -> char
// \u{[\da-f]+} -> char
let content = token.content
content = content.replace(/\\[nrtvef\\$"]/g, match => match[1])
content = content.replace(/\\[0-7]{1,3}/g, match => {
return String.fromCodePoint(parseInt(match.substr(1), 8) % 0xFF)
})
content = content.replace(/\\x[\da-f]{1,2}/ig, match => {
return String.fromCodePoint(parseInt(match.substr(1), 16) % 0xFF)
})
content = content.replace(/\\u{[\da-f]+}/ig, match => {
return String.fromCodePoint(parseInt(match.substr(1), 16))
})
return content
}
}
module.exports = function (source) {
let originalSource = source
// remove PHP header
source = source.replace(/^\s*<\?(?:php)?\s*/, '')
// remove return statement
source = source.replace(/^return\s*/, '')
// remove trailing semicolon
source = source.replace(/;\s*$/, '')
// strings
let tokens = tokenizeRest([source], tokenizers.simpleType, /(['"])((?:\\.|[^\\\1])*?)\1/, 'string', 2)
// comments
tokenizeRest(tokens, tokenizers.simpleType, /\/\/(.*)/, 'comment')
// map delimiters
tokenizeRest(tokens, tokenizers.simpleType, /([[\]])/, 'map')
// arrows
tokenizeRest(tokens, tokenizers.simpleType, /(=>)/, 'arrow')
// commas
tokenizeRest(tokens, tokenizers.simpleType, /(,)/, 'comma')
// whitespace
tokenizeRest(tokens, tokenizers.simpleType, /(\s+)/, 'whitespace')
// remove whitespace tokens and comments
tokens = tokens.filter(token => !(['whitespace', 'comment'].includes(token.type)))
// split tokens into an array of items, separated by commas
let currentItem = []
let items = [currentItem]
for (let token of tokens) {
let { type } = token
if (type === 'map') continue
if (type === 'comma') items.push(currentItem = [])
else currentItem.push(token)
}
// remove null items
items = items.filter(item => item.length)
// assume that every item will contain [string, arrow, string] and create
// an object
let data = {}
for (let item of items) {
let key = item[0]
let value = item[2]
if (!key || !value) {
console.error('Item has no key or value!', item)
continue
}
data[unescapeString(key)] = unescapeString(value)
}
let result = {}
// put selected keys in result
for (let key of selectedKeys) {
result[key] = data[key]
}
// adapted from webpack/loader-utils
let remainingRequest = this.remainingRequest
if (!remainingRequest) {
remainingRequest = this.loaders.slice(this.loaderIndex + 1)
.map(obj => obj.request)
.concat([this.resource]).join('!')
}
let currentRequest = this.currentRequest
if (!currentRequest) {
remainingRequest = this.loaders.slice(this.loaderIndex)
.map(obj => obj.request)
.concat([this.resource]).join('!')
}
let map = {
version: 3,
file: currentRequest,
sourceRoot: '',
sources: [remainingRequest],
sourcesContent: [originalSource],
names: [],
mappings: 'AAAA;AAAA'
}
this.callback(null, `/* Generated language file */
module.exports=${JSON.stringify(result)}`, map)
}

@ -20,6 +20,13 @@ plugins.push(new webpack.optimize.UglifyJsPlugin({
sourceMap: devtool === 'source-map'
}))
// replace "locale-data" with path to locale data
let locale = process.env.LOCALE || 'en'
plugins.push(new webpack.NormalModuleReplacementPlugin(
/^locale-data$/,
path.resolve(`lang/${locale}.php`)
))
module.exports = {
entry: './js',
output: {
@ -34,6 +41,10 @@ module.exports = {
path.resolve(__dirname, 'node_modules')
],
loader: 'babel-loader'
},
{
test: /lang\/.+?\.php$/,
loader: './lang/lang-loader.js'
}
]
},

Loading…
Cancel
Save