mirror of
https://github.com/misenhower/splatoon2.ink.git
synced 2026-03-21 17:24:37 -05:00
Retrieve localized string data from SplatNet
This commit is contained in:
parent
2a5f2ed684
commit
db6d018ef5
|
|
@ -33,6 +33,7 @@
|
|||
"html-webpack-plugin": "^2.30.1",
|
||||
"json-stable-stringify": "^1.0.1",
|
||||
"jsonpath": "^1.0.0",
|
||||
"lodash": "^4.17.4",
|
||||
"make-runnable": "^1.3.6",
|
||||
"mkdirp": "^0.5.1",
|
||||
"moment-timezone": "^0.5.13",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,20 @@ const splatoonRegions = [
|
|||
{ key: 'jp', name: 'Japan', demonym: 'Japanese' },
|
||||
];
|
||||
|
||||
const languages = [
|
||||
{ region: 'NA', language: 'en' },
|
||||
{ region: 'NA', language: 'es-MX' },
|
||||
{ region: 'NA', language: 'fr-CA' },
|
||||
{ region: 'EU', language: 'en' },
|
||||
{ region: 'EU', language: 'de' },
|
||||
{ region: 'EU', language: 'nl' },
|
||||
{ region: 'EU', language: 'fr' },
|
||||
{ region: 'EU', language: 'it' },
|
||||
{ region: 'EU', language: 'ru' },
|
||||
{ region: 'EU', language: 'es' },
|
||||
{ region: 'JP', language: 'ja' },
|
||||
];
|
||||
|
||||
function getRegionByKey(key) {
|
||||
return splatoonRegions.find(r => r.key == key);
|
||||
}
|
||||
|
|
@ -64,6 +78,7 @@ function detectSplatoonRegionFromLanguage(language) {
|
|||
|
||||
module.exports = {
|
||||
splatoonRegions,
|
||||
languages,
|
||||
getRegionByKey,
|
||||
detectSplatoonRegion,
|
||||
}
|
||||
|
|
|
|||
96
src/updater/LocalizationProcessor.js
Normal file
96
src/updater/LocalizationProcessor.js
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const mkdirp = require('mkdirp');
|
||||
const jsonpath = require('jsonpath');
|
||||
const _ = require('lodash');
|
||||
const { readJson, writeJson } = require('./utilities');
|
||||
|
||||
const localizationsPath = path.resolve('public/data/lang');
|
||||
|
||||
class LocalizationProcessor {
|
||||
constructor(ruleset, languageInfo) {
|
||||
this.ruleset = ruleset;
|
||||
this.languageInfo = languageInfo;
|
||||
|
||||
let entities = this.ruleset.entities;
|
||||
this.entityExpressions = (Array.isArray(entities)) ? entities : [entities];
|
||||
|
||||
let values = this.ruleset.values;
|
||||
this.valueExpressions = (Array.isArray(values)) ? values : [values];
|
||||
|
||||
this.readData();
|
||||
}
|
||||
|
||||
getFilename() {
|
||||
return `${localizationsPath}/${this.languageInfo.language}.json`;
|
||||
}
|
||||
|
||||
readData() {
|
||||
if (fs.existsSync(this.getFilename()))
|
||||
this.data = readJson(this.getFilename());
|
||||
else
|
||||
this.data = {};
|
||||
}
|
||||
|
||||
writeData() {
|
||||
mkdirp(path.dirname(this.getFilename()));
|
||||
writeJson(this.getFilename(), this.data);
|
||||
}
|
||||
|
||||
getExpression(id, valueKey) {
|
||||
return [this.ruleset.name, id, valueKey];
|
||||
}
|
||||
|
||||
readValue(id, valueKey) {
|
||||
return _.get(this.data, this.getExpression(id, valueKey));
|
||||
}
|
||||
|
||||
writeValue(id, valueKey, newValue) {
|
||||
return _.setWith(this.data, this.getExpression(id, valueKey), newValue, Object);
|
||||
}
|
||||
|
||||
eachEntity(data, callback) {
|
||||
for (let expression of this.entityExpressions) {
|
||||
let entities = jsonpath.query(data, expression);
|
||||
|
||||
for (let entity of entities) {
|
||||
let result = callback(entity);
|
||||
if (result === false)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateLocalizations(data) {
|
||||
this.eachEntity(data, entity => this.updateLocalizationsForEntity(entity));
|
||||
}
|
||||
|
||||
updateLocalizationsForEntity(entity) {
|
||||
for (let valueKey of this.valueExpressions) {
|
||||
let id = _.get(entity, this.ruleset.id);
|
||||
let value = _.get(entity, valueKey);
|
||||
|
||||
this.writeValue(id, valueKey, value);
|
||||
}
|
||||
|
||||
this.writeData();
|
||||
}
|
||||
|
||||
hasLocalizations(data) {
|
||||
let result = this.eachEntity(data, entity => this.hasLocalizationsForEntity(entity));
|
||||
return (result !== false);
|
||||
}
|
||||
|
||||
hasLocalizationsForEntity(entity) {
|
||||
for (let valueKey of this.valueExpressions) {
|
||||
let id = _.get(entity, this.ruleset.id);
|
||||
|
||||
if (this.readValue(id, valueKey) === undefined)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LocalizationProcessor;
|
||||
|
|
@ -2,47 +2,20 @@ require('./bootstrap');
|
|||
|
||||
const Updater = require('./updaters/Updater');
|
||||
const SchedulesUpdater = require('./updaters/SchedulesUpdater');
|
||||
const CoopSchedulesUpdater = require('./updaters/CoopSchedulesUpdater');
|
||||
const TimelineUpdater = require('./updaters/TimelineUpdater');
|
||||
const OriginalGearImageUpdater = require('./updaters/OriginalGearImageUpdater');
|
||||
const FestivalsUpdater = require('./updaters/FestivalsUpdater');
|
||||
const MerchandisesUpdater = require('./updaters/MerchandisesUpdater');
|
||||
|
||||
const updaters = [
|
||||
// Original gear images
|
||||
new OriginalGearImageUpdater(),
|
||||
|
||||
// Schedules
|
||||
new OriginalGearImageUpdater,
|
||||
new SchedulesUpdater,
|
||||
|
||||
// Co-op Schedules
|
||||
new Updater({
|
||||
name: 'Co-op Schedules',
|
||||
filename: 'coop-schedules.json',
|
||||
request: (splatnet) => splatnet.getCoopSchedules(),
|
||||
imagePaths: [
|
||||
'$..stage.image',
|
||||
'$..weapons[*].image',
|
||||
],
|
||||
}),
|
||||
|
||||
// Timeline
|
||||
new Updater({
|
||||
name: 'Timeline',
|
||||
filename: 'timeline.json',
|
||||
request: (splatnet) => splatnet.getTimeline(),
|
||||
rootKeys: ['coop', 'weapon_availability'],
|
||||
imagePaths: [
|
||||
'$.coop..gear.image',
|
||||
'$.coop..gear.brand.image',
|
||||
'$.weapon_availability..weapon.image',
|
||||
'$.weapon_availability..weapon.special.image_a',
|
||||
'$.weapon_availability..weapon.sub.image_a',
|
||||
],
|
||||
}),
|
||||
|
||||
// Festivals
|
||||
new FestivalsUpdater,
|
||||
|
||||
// Merchandises
|
||||
new CoopSchedulesUpdater,
|
||||
new TimelineUpdater,
|
||||
new FestivalsUpdater('NA'),
|
||||
new FestivalsUpdater('EU'),
|
||||
new FestivalsUpdater('JP'),
|
||||
new MerchandisesUpdater,
|
||||
];
|
||||
|
||||
|
|
|
|||
43
src/updater/updaters/CoopSchedulesUpdater.js
Normal file
43
src/updater/updaters/CoopSchedulesUpdater.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
const Updater = require('./Updater');
|
||||
|
||||
class CoopSchedulesUpdater extends Updater {
|
||||
constructor() {
|
||||
super({
|
||||
name: 'Co-op Schedules',
|
||||
filename: 'coop-schedules.json',
|
||||
request: (splatnet) => splatnet.getCoopSchedules(),
|
||||
imagePaths: [
|
||||
'$..stage.image',
|
||||
'$..weapons[*].image',
|
||||
],
|
||||
localization: [
|
||||
{
|
||||
name: 'coop_stages',
|
||||
entities: '$..stage',
|
||||
id: 'image', // Unfortunately these don't have an ID, so we'll just match them by image URL
|
||||
values: 'name',
|
||||
},
|
||||
{
|
||||
name: 'weapons',
|
||||
entities: '$..weapons[?(@.id)]',
|
||||
id: 'id',
|
||||
values: 'name',
|
||||
},
|
||||
{
|
||||
name: 'weapon_subs',
|
||||
entities: '$..weapons[?(@.id)].sub',
|
||||
id: 'id',
|
||||
values: 'name',
|
||||
},
|
||||
{
|
||||
name: 'weapon_specials',
|
||||
entities: '$..weapons[?(@.id)].special',
|
||||
id: 'id',
|
||||
values: 'name',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CoopSchedulesUpdater;
|
||||
|
|
@ -1,10 +1,15 @@
|
|||
const Updater = require('./Updater');
|
||||
const SplatNet = require('../splatnet');
|
||||
const mkdirp = require('mkdirp');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const _ = require('lodash');
|
||||
const { readJson, writeJson } = require('../utilities');
|
||||
const { languages } = require('../../js/regions');
|
||||
|
||||
class FestivalsUpdater extends Updater {
|
||||
constructor() {
|
||||
constructor(region) {
|
||||
super({
|
||||
name: 'Festivals',
|
||||
name: `Festivals ${region}`,
|
||||
filename: 'festivals.json',
|
||||
request: (splatnet) => splatnet.getCombinedFestivals(),
|
||||
imagePaths: [
|
||||
|
|
@ -13,15 +18,41 @@ class FestivalsUpdater extends Updater {
|
|||
'$..images.panel',
|
||||
'$..special_stage.image',
|
||||
],
|
||||
localization: [
|
||||
{
|
||||
name: 'festivals',
|
||||
entities: '$.festivals[*]',
|
||||
id: 'festival_id',
|
||||
values: 'names',
|
||||
},
|
||||
{
|
||||
name: 'stages',
|
||||
entities: '$..special_stage',
|
||||
id: 'id',
|
||||
values: 'name',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
this.region = region;
|
||||
}
|
||||
|
||||
async getData() {
|
||||
return {
|
||||
na: await this.options.request(new SplatNet('NA')),
|
||||
eu: await this.options.request(new SplatNet('EU')),
|
||||
jp: await this.options.request(new SplatNet('JP')),
|
||||
}
|
||||
writeFile(filename, regionData) {
|
||||
mkdirp(path.dirname(filename));
|
||||
|
||||
// Load existing data since we only need to modify this region's data
|
||||
let data = {};
|
||||
if (fs.existsSync(filename))
|
||||
data = readJson(filename);
|
||||
|
||||
let region = this.region.toLowerCase();
|
||||
data[region] = JSON.parse(regionData);
|
||||
writeJson(filename, data);
|
||||
}
|
||||
|
||||
getLanguages() {
|
||||
// Return all languages for this region
|
||||
return _.filter(languages, { region: this.region });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,32 @@ class MerchandisesUpdater extends Updater {
|
|||
'$..gear.brand.frequent_skill.image',
|
||||
'$..skill.image',
|
||||
],
|
||||
localization: [
|
||||
{
|
||||
name: 'gear',
|
||||
entities: '$..gear',
|
||||
id: 'id',
|
||||
values: 'name',
|
||||
},
|
||||
{
|
||||
name: 'brands',
|
||||
entities: '$..gear.brand',
|
||||
id: 'id',
|
||||
values: 'name',
|
||||
},
|
||||
{
|
||||
name: 'skills',
|
||||
entities: '$..gear.brand.frequent_skill',
|
||||
id: 'id',
|
||||
values: 'name',
|
||||
},
|
||||
{
|
||||
name: 'skills',
|
||||
entities: '$..skill',
|
||||
id: 'id',
|
||||
values: 'name',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,29 @@ class SchedulesUpdater extends Updater {
|
|||
'$..stage_a.image',
|
||||
'$..stage_b.image',
|
||||
],
|
||||
localization: [
|
||||
{
|
||||
name: 'stages',
|
||||
entities: [
|
||||
'$..stage_a',
|
||||
'$..stage_b',
|
||||
],
|
||||
id: 'id',
|
||||
values: 'name',
|
||||
},
|
||||
{
|
||||
name: 'game_modes',
|
||||
entities: '$..game_mode',
|
||||
id: 'key',
|
||||
values: 'name',
|
||||
},
|
||||
{
|
||||
name: 'rules',
|
||||
entities: '$..rule',
|
||||
id: 'key',
|
||||
values: ['name', 'multiline_name'],
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
53
src/updater/updaters/TimelineUpdater.js
Normal file
53
src/updater/updaters/TimelineUpdater.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
const Updater = require('./Updater');
|
||||
|
||||
class TimelineUpdater extends Updater {
|
||||
constructor() {
|
||||
super({
|
||||
name: 'Timeline',
|
||||
filename: 'timeline.json',
|
||||
request: (splatnet) => splatnet.getTimeline(),
|
||||
rootKeys: ['coop', 'weapon_availability'],
|
||||
imagePaths: [
|
||||
'$.coop..gear.image',
|
||||
'$.coop..gear.brand.image',
|
||||
'$.weapon_availability..weapon.image',
|
||||
'$.weapon_availability..weapon.special.image_a',
|
||||
'$.weapon_availability..weapon.sub.image_a',
|
||||
],
|
||||
localization: [
|
||||
{
|
||||
name: 'gear',
|
||||
entities: '$.coop..gear',
|
||||
id: 'id',
|
||||
values: 'name',
|
||||
},
|
||||
{
|
||||
name: 'brands',
|
||||
entities: '$.coop..gear.brand',
|
||||
id: 'id',
|
||||
values: 'name',
|
||||
},
|
||||
{
|
||||
name: 'weapons',
|
||||
entities: '$.weapon_availability..weapon',
|
||||
id: 'id',
|
||||
values: 'name',
|
||||
},
|
||||
{
|
||||
name: 'weapon_subs',
|
||||
entities: '$.weapon_availability..weapon.sub',
|
||||
id: 'id',
|
||||
values: 'name',
|
||||
},
|
||||
{
|
||||
name: 'weapon_specials',
|
||||
entities: '$.weapon_availability..weapon.special',
|
||||
id: 'id',
|
||||
values: 'name',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TimelineUpdater;
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const mkdirp = require('mkdirp');
|
||||
const _ = require('lodash');
|
||||
const jsonpath = require('jsonpath');
|
||||
const SplatNet = require('../splatnet');
|
||||
const raven = require('raven');
|
||||
const { languages } = require('../../js/regions');
|
||||
const LocalizationProcessor = require('../LocalizationProcessor');
|
||||
|
||||
const dataPath = path.resolve('public/data');
|
||||
const splatnetAssetPath = path.resolve('public/assets/splatnet');
|
||||
|
|
@ -16,12 +19,18 @@ class Updater {
|
|||
async update() {
|
||||
this.info('Updating data...');
|
||||
|
||||
// Use the first language as the default
|
||||
let languageInfo = this.getLanguages()[0];
|
||||
|
||||
// Retrieve the data
|
||||
let data = await this.handleRequest(this.getData());
|
||||
let data = await this.handleRequest(this.getData(languageInfo));
|
||||
|
||||
// Filter the root keys if necessary
|
||||
data = this.filterRootKeys(data);
|
||||
|
||||
// Update localizations
|
||||
data = await this.updateLocalizations(data, languageInfo);
|
||||
|
||||
// Apply any other processing
|
||||
data = await this.processData(data);
|
||||
|
||||
|
|
@ -38,8 +47,8 @@ class Updater {
|
|||
this.info('Done.');
|
||||
}
|
||||
|
||||
getData() {
|
||||
let splatnet = new SplatNet;
|
||||
getData({ region, language }) {
|
||||
let splatnet = new SplatNet(region, language);
|
||||
return this.options.request(splatnet);
|
||||
}
|
||||
|
||||
|
|
@ -78,6 +87,50 @@ class Updater {
|
|||
return data;
|
||||
}
|
||||
|
||||
getLanguages() {
|
||||
// Only return one entry per language
|
||||
// (i.e., only return "region: NA language: en" and not "region: EU language: en")
|
||||
return _.uniqBy(languages, 'language');
|
||||
}
|
||||
|
||||
forEachLanguage(callback) {
|
||||
for (let languageInfo of this.getLanguages())
|
||||
this.forEachRuleset(languageInfo, callback);
|
||||
}
|
||||
|
||||
forEachRuleset(languageInfo, callback) {
|
||||
for (let ruleset of (this.options.localization)) {
|
||||
let processor = new LocalizationProcessor(ruleset, languageInfo);
|
||||
callback(processor, languageInfo);
|
||||
}
|
||||
}
|
||||
|
||||
async updateLocalizations(data, initialLanguageInfo) {
|
||||
if (this.options.localization) {
|
||||
// Update localization data for the initial language
|
||||
this.forEachRuleset(initialLanguageInfo, processor => processor.updateLocalizations(data));
|
||||
|
||||
// Do we need to retrieve data for any other languages?
|
||||
let missingLanguages = [];
|
||||
this.forEachLanguage((processor, languageInfo) => {
|
||||
if (missingLanguages.indexOf(languageInfo) === -1) {
|
||||
if (!processor.hasLocalizations(data))
|
||||
missingLanguages.push(languageInfo);
|
||||
}
|
||||
});
|
||||
|
||||
// Retrieve data for missing languages
|
||||
for (let missingLanguageInfo of missingLanguages) {
|
||||
this.info(`Retrieving localized data for region: ${missingLanguageInfo.region}, language: ${missingLanguageInfo.language}`);
|
||||
let localData = await this.handleRequest(this.getData(missingLanguageInfo));
|
||||
localData = this.filterRootKeys(localData);
|
||||
this.forEachRuleset(missingLanguageInfo, processor => processor.updateLocalizations(localData));
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
shouldIncludeRootValue(value) {
|
||||
if (!value)
|
||||
return false;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user