Retrieve localized strings from SplatNet

This commit is contained in:
Matt Isenhower 2022-10-17 19:30:55 -07:00
parent d5560477a2
commit 47d0abb747
8 changed files with 221 additions and 6 deletions

View File

@ -1,3 +1,5 @@
import path from 'path';
export function getTopOfCurrentHour(date = null) {
date ??= new Date;
@ -15,3 +17,16 @@ export function getGearIcon(gear) {
default: return null;
}
}
export function deriveId(node) {
// Unfortunately, SplatNet doesn't return IDs for a lot of gear properties.
// Derive IDs from image URLs instead.
let url = new URL(node.image.url);
let id =path.basename(url.pathname, '.png');
return {
'__splatoon3ink_id': id,
...node,
};
}

View File

@ -0,0 +1,99 @@
import fs from 'fs/promises';
import path from 'path';
import mkdirp from 'mkdirp';
import jsonpath from 'jsonpath';
import get from 'lodash/get.js';
import set from 'lodash/set.js';
function makeArray(value) {
return Array.isArray(value) ? value : [value];
}
export class LocalizationProcessor {
outputDirectory = 'dist/data/locale';
constructor(locale, rulesets) {
this.locale = locale;
this.rulesets = rulesets;
}
get filename() {
return `${this.outputDirectory}/${this.locale.code}.json`;
}
*rulesetIterations() {
for (let ruleset of this.rulesets) {
for (let node of makeArray(ruleset.nodes)) {
for (let value of makeArray(ruleset.values)) {
yield {
key: ruleset.key,
node,
id: ruleset.id,
value,
};
}
}
}
}
*dataIterations(data) {
for (let ruleset of this.rulesetIterations()) {
for (let node of jsonpath.query(data, ruleset.node)) {
let id = get(node, ruleset.id);
yield {
ruleset,
node,
id,
value: get(node, ruleset.value),
path: `${ruleset.key}.${id}.${ruleset.value}`,
};
}
}
}
async updateLocalizations(data) {
let localizations = await this.readData();
for (let { path, value } of this.dataIterations(data)) {
set(localizations, path, value);
}
await this.writeData(localizations);
}
async hasMissingLocalizations(data) {
let localizations = await this.readData();
for (let { path } of this.dataIterations(data)) {
if (get(localizations, path) === undefined) {
return true;
}
}
return false;
}
async writeData(data) {
// If we're running in debug mode, format the JSON output so it's easier to read
let debug = !!process.env.DEBUG;
let space = debug ? 2 : undefined;
data = JSON.stringify(data, undefined, space);
await mkdirp(path.dirname(this.filename))
await fs.writeFile(this.filename, data);
}
async readData() {
try {
let result = await fs.readFile(this.filename);
return JSON.parse(result) || {};
} catch (e) {
//
}
return {};
}
}

View File

@ -1,3 +1,5 @@
import jsonpath from 'jsonpath';
import { deriveId } from "../../common/util.mjs";
import DataUpdater from "./DataUpdater.mjs";
export default class CoopUpdater extends DataUpdater
@ -9,7 +11,20 @@ export default class CoopUpdater extends DataUpdater
'$..image.url',
];
getData(locale) {
return this.splatnet(locale).getCoopHistoryData();
localizations = [
{
key: 'gear',
nodes: '$..monthlyGear',
id: '__splatoon3ink_id',
values: 'name',
},
];
async getData(locale) {
let data = await this.splatnet(locale).getCoopHistoryData();
jsonpath.apply(data, '$..monthlyGear', deriveId);
return data;
}
}

View File

@ -7,6 +7,7 @@ import SplatNet3Client from "../../splatnet/SplatNet3Client.mjs";
import ImageProcessor from '../ImageProcessor.mjs';
import NsoClient from '../../splatnet/NsoClient.mjs';
import { locales } from '../../../src/common/i18n.mjs';
import { LocalizationProcessor } from '../LocalizationProcessor.mjs';
export default class DataUpdater
{
@ -15,6 +16,7 @@ export default class DataUpdater
outputDirectory = 'dist/data';
imagePaths = [];
localizations = [];
constructor(region = null) {
this.nsoClient = NsoClient.make(region);
@ -52,6 +54,9 @@ export default class DataUpdater
// Retrieve the data
let data = await this.tryRequest(this.getData(this.defaultLocale));
// Update localizations
await this.updateLocalizations(this.defaultLocale, data);
// Download any new images
await this.downloadImages(data);
@ -79,6 +84,24 @@ export default class DataUpdater
// Processing
async updateLocalizations(initialLocale, data) {
// Save localizations for the initial locale
let processor = new LocalizationProcessor(initialLocale, this.localizations);
await processor.updateLocalizations(data);
// Retrieve data for missing languages
for (let locale of this.locales.filter(l => l !== initialLocale)) {
processor = new LocalizationProcessor(locale, this.localizations);
if (await processor.hasMissingLocalizations(data)) {
this.console.info(`Retrieving localized data for ${locale.code}`);
let regionalData = await this.getData(locale);
await processor.updateLocalizations(regionalData);
}
}
}
async downloadImages(data) {
for (let expression of this.imagePaths) {
// This JSONPath library is completely synchronous, so we have to

View File

@ -1,4 +1,6 @@
import DataUpdater from "./DataUpdater.mjs";
import jsonpath from 'jsonpath';
import { deriveId } from "../../common/util.mjs";
export default class GearUpdater extends DataUpdater
{
@ -9,7 +11,39 @@ export default class GearUpdater extends DataUpdater
'$..image.url',
];
getData(locale) {
return this.splatnet(locale).getGesotownData();
localizations = [
{
key: 'brands',
nodes: '$..brand',
id: 'id',
values: 'name',
},
{
key: 'gear',
nodes: '$..gear',
id: '__splatoon3ink_id',
values: 'name',
},
{
key: 'powers',
nodes: [
'$..usualGearPower',
'$..primaryGearPower',
'$..additionalGearPowers.*',
],
id: '__splatoon3ink_id',
values: 'name',
},
];
async getData(locale) {
let data = await this.splatnet(locale).getGesotownData();
jsonpath.apply(data, '$..gear', deriveId);
jsonpath.apply(data, '$..usualGearPower', deriveId);
jsonpath.apply(data, '$..primaryGearPower', deriveId);
jsonpath.apply(data, '$..additionalGearPowers.*', deriveId);
return data;
}
}

View File

@ -1,4 +1,6 @@
import DataUpdater from "./DataUpdater.mjs";
import jsonpath from 'jsonpath';
import { deriveId } from "../../common/util.mjs";
export default class StageScheduleUpdater extends DataUpdater
{
@ -11,7 +13,32 @@ export default class StageScheduleUpdater extends DataUpdater
'$..thumbnailImage.url',
];
getData(locale) {
return this.splatnet(locale).getStageScheduleData();
localizations = [
{
key: 'stages',
nodes: '$..vsStages.nodes.*',
id: 'id',
values: 'name',
},
{
key: 'rules',
nodes: '$..vsRule',
id: 'id',
values: 'name',
},
{
key: 'weapons',
nodes: '$..weapons.*',
id: '__splatoon3ink_id',
values: 'name',
},
];
async getData(locale) {
let data = await this.splatnet(locale).getStageScheduleData();
jsonpath.apply(data, '$..weapons.*', deriveId);
return data;
}
}

1
package-lock.json generated
View File

@ -14,6 +14,7 @@
"dotenv": "^16.0.2",
"ecstatic": "^4.1.4",
"jsonpath": "^1.1.1",
"lodash": "^4.17.21",
"mkdirp": "^1.0.4",
"nxapi": "^1.4.0",
"pinia": "^2.0.22",

View File

@ -19,6 +19,7 @@
"dotenv": "^16.0.2",
"ecstatic": "^4.1.4",
"jsonpath": "^1.1.1",
"lodash": "^4.17.21",
"mkdirp": "^1.0.4",
"nxapi": "^1.4.0",
"pinia": "^2.0.22",