mirror of
https://github.com/misenhower/splatoon3.ink.git
synced 2026-04-26 07:49:22 -05:00
Retrieve localized strings from SplatNet
This commit is contained in:
parent
d5560477a2
commit
47d0abb747
|
|
@ -1,3 +1,5 @@
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
export function getTopOfCurrentHour(date = null) {
|
export function getTopOfCurrentHour(date = null) {
|
||||||
date ??= new Date;
|
date ??= new Date;
|
||||||
|
|
||||||
|
|
@ -15,3 +17,16 @@ export function getGearIcon(gear) {
|
||||||
default: return null;
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import jsonpath from 'jsonpath';
|
||||||
|
import { deriveId } from "../../common/util.mjs";
|
||||||
import DataUpdater from "./DataUpdater.mjs";
|
import DataUpdater from "./DataUpdater.mjs";
|
||||||
|
|
||||||
export default class CoopUpdater extends DataUpdater
|
export default class CoopUpdater extends DataUpdater
|
||||||
|
|
@ -9,7 +11,20 @@ export default class CoopUpdater extends DataUpdater
|
||||||
'$..image.url',
|
'$..image.url',
|
||||||
];
|
];
|
||||||
|
|
||||||
getData(locale) {
|
localizations = [
|
||||||
return this.splatnet(locale).getCoopHistoryData();
|
{
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import SplatNet3Client from "../../splatnet/SplatNet3Client.mjs";
|
||||||
import ImageProcessor from '../ImageProcessor.mjs';
|
import ImageProcessor from '../ImageProcessor.mjs';
|
||||||
import NsoClient from '../../splatnet/NsoClient.mjs';
|
import NsoClient from '../../splatnet/NsoClient.mjs';
|
||||||
import { locales } from '../../../src/common/i18n.mjs';
|
import { locales } from '../../../src/common/i18n.mjs';
|
||||||
|
import { LocalizationProcessor } from '../LocalizationProcessor.mjs';
|
||||||
|
|
||||||
export default class DataUpdater
|
export default class DataUpdater
|
||||||
{
|
{
|
||||||
|
|
@ -15,6 +16,7 @@ export default class DataUpdater
|
||||||
outputDirectory = 'dist/data';
|
outputDirectory = 'dist/data';
|
||||||
|
|
||||||
imagePaths = [];
|
imagePaths = [];
|
||||||
|
localizations = [];
|
||||||
|
|
||||||
constructor(region = null) {
|
constructor(region = null) {
|
||||||
this.nsoClient = NsoClient.make(region);
|
this.nsoClient = NsoClient.make(region);
|
||||||
|
|
@ -52,6 +54,9 @@ export default class DataUpdater
|
||||||
// Retrieve the data
|
// Retrieve the data
|
||||||
let data = await this.tryRequest(this.getData(this.defaultLocale));
|
let data = await this.tryRequest(this.getData(this.defaultLocale));
|
||||||
|
|
||||||
|
// Update localizations
|
||||||
|
await this.updateLocalizations(this.defaultLocale, data);
|
||||||
|
|
||||||
// Download any new images
|
// Download any new images
|
||||||
await this.downloadImages(data);
|
await this.downloadImages(data);
|
||||||
|
|
||||||
|
|
@ -79,6 +84,24 @@ export default class DataUpdater
|
||||||
|
|
||||||
// Processing
|
// 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) {
|
async downloadImages(data) {
|
||||||
for (let expression of this.imagePaths) {
|
for (let expression of this.imagePaths) {
|
||||||
// This JSONPath library is completely synchronous, so we have to
|
// This JSONPath library is completely synchronous, so we have to
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import DataUpdater from "./DataUpdater.mjs";
|
import DataUpdater from "./DataUpdater.mjs";
|
||||||
|
import jsonpath from 'jsonpath';
|
||||||
|
import { deriveId } from "../../common/util.mjs";
|
||||||
|
|
||||||
export default class GearUpdater extends DataUpdater
|
export default class GearUpdater extends DataUpdater
|
||||||
{
|
{
|
||||||
|
|
@ -9,7 +11,39 @@ export default class GearUpdater extends DataUpdater
|
||||||
'$..image.url',
|
'$..image.url',
|
||||||
];
|
];
|
||||||
|
|
||||||
getData(locale) {
|
localizations = [
|
||||||
return this.splatnet(locale).getGesotownData();
|
{
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import DataUpdater from "./DataUpdater.mjs";
|
import DataUpdater from "./DataUpdater.mjs";
|
||||||
|
import jsonpath from 'jsonpath';
|
||||||
|
import { deriveId } from "../../common/util.mjs";
|
||||||
|
|
||||||
export default class StageScheduleUpdater extends DataUpdater
|
export default class StageScheduleUpdater extends DataUpdater
|
||||||
{
|
{
|
||||||
|
|
@ -11,7 +13,32 @@ export default class StageScheduleUpdater extends DataUpdater
|
||||||
'$..thumbnailImage.url',
|
'$..thumbnailImage.url',
|
||||||
];
|
];
|
||||||
|
|
||||||
getData(locale) {
|
localizations = [
|
||||||
return this.splatnet(locale).getStageScheduleData();
|
{
|
||||||
|
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
1
package-lock.json
generated
|
|
@ -14,6 +14,7 @@
|
||||||
"dotenv": "^16.0.2",
|
"dotenv": "^16.0.2",
|
||||||
"ecstatic": "^4.1.4",
|
"ecstatic": "^4.1.4",
|
||||||
"jsonpath": "^1.1.1",
|
"jsonpath": "^1.1.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"mkdirp": "^1.0.4",
|
"mkdirp": "^1.0.4",
|
||||||
"nxapi": "^1.4.0",
|
"nxapi": "^1.4.0",
|
||||||
"pinia": "^2.0.22",
|
"pinia": "^2.0.22",
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
"dotenv": "^16.0.2",
|
"dotenv": "^16.0.2",
|
||||||
"ecstatic": "^4.1.4",
|
"ecstatic": "^4.1.4",
|
||||||
"jsonpath": "^1.1.1",
|
"jsonpath": "^1.1.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"mkdirp": "^1.0.4",
|
"mkdirp": "^1.0.4",
|
||||||
"nxapi": "^1.4.0",
|
"nxapi": "^1.4.0",
|
||||||
"pinia": "^2.0.22",
|
"pinia": "^2.0.22",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user