Files
mazegame/engine/script/js/localizer.js
T

305 lines
8.0 KiB
JavaScript

//-----------------------------------------------------------------------------
// Get the first non empty character (not space or tab)
//=============================================================================
// DEFINES
//=============================================================================
//-- Node.js libraries
const fs = require('fs');
const path = require('path');
//-- MSXgl JS libraries
const util = require("./util.js");
const locUtil = require("./localize/localize_util.js");
const locArb = require("./localize/localize_arb.js");
const locCyr = require("./localize/localize_cyr.js");
const locLat = require("./localize/localize_lat.js");
const locJap = require("./localize/localize_jap.js");
const locKor = require("./localize/localize_kor.js");
//-----------------------------------------------------------------------------
// Constants
const version = "1.0.0";
const lineTypeSection = 1;
const lineTypeText = 2;
const lineTypeSpecial = 3;
//-----------------------------------------------------------------------------
// Variables
var bComment = true;
//=============================================================================
// FUNCTIONS
//=============================================================================
//-----------------------------------------------------------------------------
// Parse a line and return a descriptor according to line type
function getLineType(str)
{
var obj = {};
const chr = locUtil.getFirstChar(str);
if((chr == '#') || (chr == ';')) // Comment
return null;
else if(chr == '[') // Language code
{
const reg = /\[\s*(?<section>[\w]+)\s*\]/;
const res = str.match(reg);
if(res == null)
{
console.log(`Error: Invalid section! ${str}`);
return null;
}
obj['type'] = lineTypeSection;
obj['value'] = res.groups.section.toUpperCase();
}
else if(chr != 0) // Text entry
{
const reg = /\s*(?<key>[\w]+)\s*\=\s*"(?<value>[^"]*)"/;
const res = str.match(reg);
if(res == null)
{
console.log(`Error: Invalid text! ${str}`);
return null;
}
obj['type'] = (chr == '@') ? lineTypeSpecial : lineTypeText;
obj['key'] = res.groups.key.toUpperCase();
obj['value'] = res.groups.value;
}
else
return null;
return obj;
}
//-----------------------------------------------------------------------------
// Parse a localisation file
function parseLocFile(filename, locTable, langTable)
{
locUtil.log("Parsing " + filename + "...");
var lang = null;
const fileData = fs.readFileSync(filename, 'utf8');
const lines = fileData.split('\n');
for (var line = 0; line < lines.length; line++)
{
const obj = getLineType(lines[line]);
if(obj != null)
{
switch(obj.type)
{
// Start a new language section
case lineTypeSection:
if(obj.value != lang)
{
lang = obj.value;
}
locUtil.resetSpecial(); // Reset language parameters
break;
// Special character entry
case lineTypeSpecial:
if(langTable[lang] == null)
langTable[lang] = {};
langTable[lang][obj.key] = obj.value;
locUtil.handleSpecial(obj.key, obj.value);
break;
// Localization entry
case lineTypeText:
if(locTable[lang] == null)
locTable[lang] = [];
var str = "";
switch(lang)
{
case "INT": // International
case "ENG": // English
case "EN":
case "FRA": // French
case "FRE":
case "FR":
case "GER": // German
case "DEU":
case "DE":
case "SPA": // Spanish
case "ES":
case "DAN": // Danish
case "DA":
case "NO": // Norwegian
case "NOR":
case "NL": // Dutch
case "NLD":
case "DUT":
case "FI": // Finnish
case "FIN":
case "DE": // German
case "DEU":
case "GER":
case "IS": // Icelandic
case "ISL":
case "ICE":
case "GA": // Irish
case "GLE":
case "IT": // Italian
case "ITA":
case "PL": // Polish
case "POL":
case "PT": // Portuguese
case "POR":
case "ES": // Spanish, Castilian
case "SPA":
case "SK": // Slovak
case "SLK":
case "SLO":
case "SV": // Swedish
case "SWE":
case "TR": // Turkish
case "TUR":
str = locLat.convertToMSX(obj.value);
break;
case "JA": // Japanese
case "JPN":
str = locJap.convertToMSX(obj.value);
break;
case "KO": // Korean
case "KOR":
str = locKor.convertToMSX(obj.value);
break;
case "AR": // Arabic
case "ARA":
str = locArb.convertToMSX(obj.value);
break;
case "UK": // Ukrainian
case "UKR":
case "RU": // Russian
case "RUS":
str = locCyr.convertToMSX(obj.value);
break;
default:
str = locLat.convertToMSX(obj.value);
// str = obj.value;
break;
}
if(locTable[lang][obj.key] == null)
locTable[lang][obj.key] = str; // set
else
locTable[lang][obj.key] += "\\n" + str; // concatenate
break;
}
}
}
}
//=============================================================================
// Main programm
//=============================================================================
module.exports.generate = function (inputFiles, outFilename, structName, splitDef, verbose)
{
invalidChar = 0x1C; // 0x3F
bVerbose = verbose;
//-----------------------------------------------------------------------------
// Parse the input file
var locTable = [];
var langTable = [];
for(var i = 0; i < inputFiles.length; i++)
parseLocFile(inputFiles[i], locTable, langTable);
// locUtil.log("locTable:");
// locUtil.log(locTable);
// locUtil.log("langTable:");
// locUtil.log(langTable);
//-----------------------------------------------------------------------------
// Analyze localisation data
var langIDs = [];
var textIDs = [];
var langKeys = Object.keys(locTable);
for(var i = 0; i < langKeys.length; i++)
{
langIDs.push(langKeys[i]);
var textKeys = Object.keys(locTable[langKeys[i]]);
for(var j = 0; j < textKeys.length; j++)
if(!textIDs.includes(textKeys[j]))
textIDs.push(textKeys[j]);
}
// locUtil.log("langIDs:");
// locUtil.log(langIDs);
// locUtil.log("textIDs:");
// locUtil.log(textIDs);
var outDefFilename = "";
if (splitDef)
outDefFilename = path.parse(outFilename).dir + "/" + path.parse(outFilename).name + "_def.h";
//-----------------------------------------------------------------------------
// Generate header file
var i;
var headContent = `// Generated by MSXgl on ${util.getDateString()}\n`
headContent += `#pragma once\n#include "core.h"\n\n`;
headContent += "// Language definition\n";
for(i = 0; i < langIDs.length; i++)
headContent += `#define LANG_${langIDs[i]} ${i}\n`;
headContent += `#define LANG_MAX ${i}\n\n`;
headContent += "// Text ID definition\n";
for(i = 0; i < textIDs.length; i++)
headContent += `#define TEXT_${textIDs[i]} ${i}\n`;
headContent += `#define TEXT_MAX ${i}\n\n`;
// Handle definition split in separate file
if (splitDef)
{
headContent += "// Localization data table\n";
headContent += `extern const c8* const ${structName}[LANG_MAX][TEXT_MAX];\n`;
fs.writeFileSync(outDefFilename, headContent);
headContent = `// Generated by MSXgl on ${util.getDateString()}\n`
headContent += `#pragma once\n#include "core.h"\n#include "${outDefFilename}"\n\n`;
}
headContent += "// Localization data table\n";
headContent += `const c8* const ${structName}[LANG_MAX][TEXT_MAX] =\n{\n`;
var langKeys = Object.keys(locTable);
for(var i = 0; i < langKeys.length; i++)
{
headContent += `\t// ${langKeys[i]}\n\t{\n`;
for(var j = 0; j < textIDs.length; j++)
{
if(locTable[langKeys[i]][textIDs[j]] == null)
{
headContent += `\t\tNULL,`;
locUtil.log(`Warning: Text ID '${textIDs[j]}' not defined for language '${langKeys[i]}'`);
}
else
headContent += `\t\t"${locTable[langKeys[i]][textIDs[j]]}",`;
if(bComment)
headContent += ` // TEXT_${textIDs[j]}`;
headContent += "\n";
}
headContent += `\t},\n`;
}
headContent += `};`;
// locUtil.log("headContent:");
// locUtil.log(headContent);
fs.writeFileSync(outFilename, headContent);
}