Skip to content

Commit

Permalink
Add localization support for jobs ui module
Browse files Browse the repository at this point in the history
This doesn't localize jobs yet, but moves all strings and regular
expressions out to a separate lang-en.js file.  It seems plausible
to create additional lang-kr.js and lang-jp.js files if folks want
to take on the task of converting all the strings.
  • Loading branch information
quisquous committed Dec 23, 2017
1 parent 1b453b4 commit 2593b43
Show file tree
Hide file tree
Showing 7 changed files with 442 additions and 101 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ or to know the details of how the FFXIV ACT plugin writes things.
The set of extensions are:
- `\y{Float}`: Matches a floating-point number, accounting for locale-specific encodings.
- `\y{Name}`: Matches any character or ability name (including empty strings which the FFXIV ACT plugin can generate when unknown).
- `\y{ObjectId}`: Matches the 8 hex character object id in network log lines.
- `\y{AbilityCode}`: Matches the FFXIV ACT plugin's format for the number code of a spell or ability.
- `\y{TimeStamp}`: Matches the time stamp at the front of each log event such as `[10:23:34.123]`.
- `\y{LogType}`: Matches the FFXIV ACT plugin's format for the number code describing the type of log event, found near the front of each log event.
Expand Down
165 changes: 165 additions & 0 deletions resources/lang-en.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
"use strict";

class CactbotLanguageEn extends CactbotLanguage {
constructor(playerName) {
super('en', playerName);
}

InitStrings(playerName) {
this.kAbility = Object.freeze({
DragonKick: 'Dragon Kick',
TwinSnakes: 'Twin Snakes',
Demolish: 'Demolish',
Verstone: 'Verstone',
Verfire: 'Verfire',
Veraero: 'Veraero',
Verthunder: 'Verthunder',
Verholy: 'Verholy',
Verflare: 'Verflare',
Jolt2: 'Jolt II',
Jolt: 'Jolt',
Impact: 'Impact',
Scatter: 'Scatter',
Vercure: 'Vercure',
Verraise: 'Verraise',
Riposte: 'Riposte',
Zwerchhau: 'Zwerchhau',
Redoublement: 'Redoublement',
Moulinet: 'Moulinet',
EnchantedRiposte: 'Enchanted Riposte',
EnchantedZwerchhau: 'Enchanted Zwerchhau',
EnchantedRedoublement: 'Enchanted Redoublement',
EnchantedMoulinet: 'Enchanted Moulinet',
Tomahawk: 'Tomahawk',
Overpower: 'Overpower',
HeavySwing: 'Heavy Swing',
SkullSunder: 'Skull Sunder',
ButchersBlock: "Butcher's Block",
Maim: 'Maim',
StormsEye: "Storm's Eye",
StormsPath: "Storm's Path",
TrickAttack: 'Trick Attack',
Embolden: 'Embolden',
Aetherflow: 'Aetherflow',
ChainStrategem: 'Chain Strategem',
Hypercharge: 'Hypercharge',
});

this.kZone = Object.freeze({
O1S: /Deltascape V1\.0 \(Savage\)/,
O2S: /Deltascape V2\.0 \(Savage\)/,
O3S: /Deltascape V3\.0 \(Savage\)/,
O4S: /Deltascape V4\.0 \(Savage\)/,
UCU: /The Unending Coil Of Bahamut \(Ultimate\)/,
});

this.kEffect = Object.freeze({
BluntResistDown: 'Blunt Resistance Down',
VerstoneReady: 'Verstone Ready',
VerfireReady: 'Verfire Ready',
Impactful: 'Impactful',
FurtherRuin: 'Further Ruin',
Aetherflow: 'Aetherflow',
WellFed: 'Well Fed',
OpoOpoForm: 'Opo-Opo Form',
RaptorForm: 'Raptor Form',
CoeurlForm: 'Coeurl Form',
PerfectBalance: 'Perfect Balance',
Medicated: 'Medicated',
BattleLitany: 'Battle Litany',
Embolden: 'Embolden',
Balance: 'The Balance',
Hypercharge: 'Hypercharge',
LeftEye: 'Left Eye',
RightEye: 'Right Eye',
Brotherhood: 'Brotherhood',
Devotion: 'Devotion',
FoeRequiem: 'Foe Requiem',
});

this.kUIStrings = Object.freeze({
// jobs: text on the pull countdown.
Pull: 'Pull',
});

// Due to this bug: https://github.com/ravahn/FFXIV_ACT_Plugin/issues/100
// We can not look for log messages from FFXIV "You use X" here. Instead we
// look for the actual ability usage provided by the XIV plugin.
// Also, the networked parse info is given much quicker than the lines from the game.
this.youUseAbilityRegex = function() {
var ids = this.AbilitiesToIds.apply(this, arguments);
return Regexes.Parse(' 1[56]:\\y{ObjectId}:' + this.playerName + ':' + Regexes.AnyOf(ids) + ':');
};
this.youStartUsingRegex = function() {
var ids = this.AbilitiesToIds.apply(this, arguments);
return Regexes.Parse(' 14:' + Regexes.AnyOf(ids) + ':' + this.playerName + ' starts using ');
};
this.youGainEffectRegex = function() {
var effects = [];
for (var i = 0; i < arguments.length; ++i) {
var effect = arguments[i];
this.ValidateEffect(effect);
effects.push(effect);
}
return Regexes.Parse(' 1A:' + this.playerName + ' gains the effect of ' + Regexes.AnyOf(effects) + ' from .* for (\\y{Float}) Seconds\.');
};
this.youLoseEffectRegex = function() {
var effects = [];
for (var i = 0; i < arguments.length; ++i) {
var effect = arguments[i];
this.ValidateEffect(effect);
effects.push(effect);
}
return Regexes.Parse(' 1E:' + this.playerName + ' loses the effect of ' + Regexes.AnyOf(effects) + ' from .*\.');
};

this.abilityRegex = function(abilityName, attacker, target, flags) {
this.ValidateAbility(abilityName);
if (!attacker)
attacker = '[^:]*';
// type:attackerId:attackerName:abilityId:abilityName:targetId:targetName:flags:
var r = ' 1[56]:\\y{ObjectId}:' + attacker + ':' + this.kAbilNameToId[abilityName] + ':';
if (target || flags) {
if (!target)
target = '[^:]*';
if (!flags)
flags = '[^:]*';
r += '[^:]*:\\y{ObjectId}:' + target + ':' + flags + ':';
}
return Regexes.Parse(r);
};

this.gainsEffectRegex = function(effect, target, attacker) {
this.ValidateEffect(effect);
if (!target)
target = '[^:]*';
if (!attacker)
attacker = '[^:]*';
return Regexes.Parse(' 1A:' + target + ' gains the effect of ' + effect + ' from ' + attacker + ' for (\\y{Float}) Seconds\.');
};
this.losesEffectRegex = function(effect, target, attacker) {
this.ValidateEffect(effect);
if (!target)
target = '[^:]*';
if (!attacker)
attacker = '[^:]*';
return Regexes.Parse(' 1E:' + target + ' loses the effect of ' + effect + ' from ' + attacker + '.*\.');
};

this.countdownStartRegex = function() {
return Regexes.Parse(/Battle commencing in (\y{Float}) seconds!/);
};
this.countdownCancelRegex = function() {
return Regexes.Parse(/Countdown canceled by /);
};
}
}

document.addEventListener("onPlayerChangedEvent", function (e) {
if (Options && Options.Language == 'en') {
if (!gLang)
gLang = new CactbotLanguageEn();
if (gLang.playerName != e.detail.name)
gLang.OnPlayerNameChange(e.detail.name);
}
});
112 changes: 112 additions & 0 deletions resources/lang.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"use strict";

var gLang = null;

class CactbotLanguage {
constructor(lang) {
this.lang = lang;
this.playerName = null;
this.kAbilityId = Object.freeze({
DragonKick: '4A',
TwinSnakes: '3D',
Demolish: '42',
Verstone: '1D57',
Verfire: '1D56',
Veraero: '1D53',
Verthunder: '1D51',
Verholy: '1D66',
Verflare: '1D65',
Jolt2: '1D64',
Jolt: '1D4F',
Impact: '1D62',
Scatter: '1D55',
Vercure: '1D5A',
Verraise: '1D63',
Riposte: '1D50',
Zwerchhau: '1D58',
Redoublement: '1D5C',
Moulinet: '1D59',
EnchantedRiposte: '1D67',
EnchantedZwerchhau: '1D68',
EnchantedRedoublement: '1D69',
EnchantedMoulinet: '1D6A',
Tomahawk: '2E',
Overpower: '29',
HeavySwing: '1F',
SkullSunder: '23',
ButchersBlock: '2F',
Maim: '25',
StormsEye: '2D',
StormsPath: '2A',
TrickAttack: '8D2',
Embolden: '1D60',
Aetherflow: 'A6',
ChainStrategem: '1D0C',
Hypercharge: 'B45',
});
}

InitStrings(playerName) {
console.error('Derived language class must implement InitStrings');
}

OnPlayerNameChange(playerName) {
this.playerName = playerName;
this.InitStrings(playerName);
this.PostProcess();
}

AbilitiesToIds() {
// Allow passing in an array as args.
var array = arguments.length == 1 && Array.isArray(arguments[0]) ? arguments[0] : arguments;

var ids = [];
for (var i = 0; i < array.length; ++i) {
var abilityName = array[i];
this.ValidateAbility(abilityName);
ids.push(this.kAbilNameToId[abilityName]);
}
return ids;
}

ValidateEffect(effectName) {
var validEffects = Object.keys(this.kEffect).map((function(k){return this.kEffect[k]}).bind(this));
if (!effectName || validEffects.indexOf(effectName) < 0)
console.error('Invalid effect: ' + effectName);
}

ValidateAbility(abilityName) {
if (!abilityName || !(abilityName in this.kAbilNameToId))
console.error('Invalid ability: ' + abilityName);
}

PostProcess() {
var keys = Object.keys(this.kAbilityId);
var numAbilityNames = Object.keys(this.kAbility).length;
if (!this.kAbility)
console.error('Missing gLang.kAbility');
if (keys.length != numAbilityNames)
console.error('kAbilityId/kAbility length mismatch: ' + keys.length + ' vs ' + numAbilityNames);

this.kAbilIdToName = {};
this.kAbilNameToId = {};
for (var i = 0; i < keys.length; ++i) {
var key = keys[i];
if (!(key in this.kAbility))
console.error('Missing key ' + key + ' in kAbility');
// Id to name mapping must be bijective.
if (this.kAbilityId[key] in this.kAbilIdToName)
console.error('Duplicate ability id: ' + this.kAbilityId[key]);
if (!this.kAbilityId[key])
console.error(key + ' has an invalid ability id');
if (this.kAbility[key] in this.kAbilNameToId)
console.error('Duplicate ability name: ' + this.kAbility[key]);
this.kAbilIdToName[this.kAbilityId[key]] = this.kAbility[key];
this.kAbilNameToId[this.kAbility[key]] = this.kAbilityId[key];
}
if (Object.keys(this.kAbilIdToName).length != Object.keys(this.kAbilNameToId).length)
console.error('Id to name mapping must be the same size');
Object.freeze(this.kAbilNameToId);
Object.freeze(this.kAbilIdToName);
}
};
24 changes: 23 additions & 1 deletion resources/regexes.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions ui/jobs/jobs.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
<script src="../../resources/timerbox.js"></script>
<script src="../../resources/timericon.js"></script>
<script src="../../resources/widgetlist.js"></script>
<script src="../../resources/lang.js"></script>
<script src="../../resources/lang-en.js"></script>

<link rel="stylesheet" href="jobs.css">
<script src="jobs-icons.js"></script>
Expand Down

0 comments on commit 2593b43

Please sign in to comment.