Skip to content

Commit

Permalink
Retrieve localized strings from SplatNet
Browse files Browse the repository at this point in the history
  • Loading branch information
misenhower committed Oct 18, 2022
1 parent d556047 commit 47d0abb
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 6 deletions.
15 changes: 15 additions & 0 deletions app/common/util.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import path from 'path';

export function getTopOfCurrentHour(date = null) {
date ??= new Date;

Expand All @@ -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,
};
}
99 changes: 99 additions & 0 deletions app/data/LocalizationProcessor.mjs
Original file line number Diff line number Diff line change
@@ -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 {};
}
}
19 changes: 17 additions & 2 deletions app/data/updaters/CoopUpdater.mjs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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;
}
}
23 changes: 23 additions & 0 deletions app/data/updaters/DataUpdater.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -15,6 +16,7 @@ export default class DataUpdater
outputDirectory = 'dist/data';

imagePaths = [];
localizations = [];

constructor(region = null) {
this.nsoClient = NsoClient.make(region);
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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
Expand Down
38 changes: 36 additions & 2 deletions app/data/updaters/GearUpdater.mjs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -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;
}
}
31 changes: 29 additions & 2 deletions app/data/updaters/StageScheduleUpdater.mjs
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -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 change: 1 addition & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 47d0abb

Please sign in to comment.