sokko-g/tests/helpers/data-validation.mjs
Shinuwa 0ac1a0974e
All checks were successful
Deploy Sokko G / deploy (push) Successful in 19s
Refocus responsive layouts on compact desktop use
2026-10-05 15:47:46 +02:00

1344 lines
65 KiB
JavaScript

// Rôle : centralise les règles de validation des JSON éditables du projet.
import assert from "node:assert/strict";
const SNAKE_CASE_RE = /^[a-z0-9]+(?:_[a-z0-9]+)*$/;
const PHYSICAL_HITZONE_KEYS = ["cut", "blunt", "ammo"];
const ELEMENTAL_HITZONE_KEYS = ["fire", "water", "thunder", "ice", "dragon"];
export function validateAniimoData(aniimos, dictionaries) {
assert.ok(Array.isArray(aniimos) && aniimos.length > 0, "Aniimo data must contain entries");
const ids = new Set();
const names = new Set();
function translated(key) {
assert.match(key, SNAKE_CASE_RE);
for (const dictionary of dictionaries) assertNonEmptyString(dictionary, key);
}
for (const aniimo of aniimos) {
assert.match(aniimo.id, /^\d+$/);
assert.ok(!ids.has(aniimo.id), `Duplicate Aniimo id: ${aniimo.id}`);
assert.ok(!names.has(aniimo.name), `Duplicate Aniimo name: ${aniimo.name}`);
ids.add(aniimo.id);
names.add(aniimo.name);
translated(aniimo.name);
assert.ok(Array.isArray(aniimo.variations) && aniimo.variations.length > 0);
const forms = new Set();
for (const variation of aniimo.variations) {
const stats = variation.attributes?.stats;
assert.deepEqual(Object.keys(stats || {}).sort(), ["atk", "break", "hp", "m_def", "p_def", "regen"], "Aniimo must have six stats");
assert.ok(Object.values(stats).every((value) => Number.isInteger(value) && value >= 0), "Stats must be nonnegative integers");
assert.equal(variation.attributes.total, Object.values(stats).reduce((sum, value) => sum + value, 0), "Stats total must equal their sum");
assert.ok(!forms.has(variation.form), `Duplicate form: ${aniimo.name}/${variation.form}`);
forms.add(variation.form);
translated(variation.form);
translated(variation.role);
assertStringArray(variation, "elements");
assert.equal(new Set(variation.elements).size, variation.elements.length);
variation.elements.forEach(translated);
assert.ok(Array.isArray(variation.homeland_abilities));
const types = new Set();
for (const ability of variation.homeland_abilities) {
translated(ability.type);
assert.ok(!types.has(ability.type), `Duplicate ability: ${ability.type}`);
types.add(ability.type);
assert.ok(Number.isInteger(ability.level) && ability.level >= 1 && ability.level <= 4, "Ability level must be an integer between 1 and 4");
}
}
if (forms.has("prismana_form")) assert.equal(aniimo.variations.at(-1).form, "prismana_form", `Prismana must be last: ${aniimo.name}`);
}
}
function valueAt(object, path) {
return path.split(".").reduce((value, key) => value?.[key], object);
}
function assertNonEmptyString(object, path) {
const value = valueAt(object, path);
assert.equal(typeof value, "string", `${path} must be a string`);
assert.ok(value.trim(), `${path} must not be empty`);
}
export function assertStringArray(object, path, minLength = 1) {
const value = valueAt(object, path);
assert.ok(Array.isArray(value), `${path} must be an array`);
assert.ok(value.length >= minLength, `${path} must contain at least ${minLength} item(s)`);
value.forEach((item, index) => {
assert.equal(typeof item, "string", `${path}[${index}] must be a string`);
assert.ok(item.trim(), `${path}[${index}] must not be empty`);
});
}
export function validateSiteContent(site) {
[
"brand.name",
"brand.homeAriaLabel",
"navigation.home",
"navigation.toolboxes",
"navigation.games",
"navigation.library",
"navigation.about",
"sidebar.badge",
"sidebar.note",
"topbar.dashboard",
"topbar.toolbox",
"topbar.library",
"topbar.games",
"topbar.gameViewLabel",
"topbar.guideView",
"topbar.toolboxView",
"gamesPage.eyebrow",
"gamesPage.title",
"gamesPage.description",
"gamesPage.emptyTitle",
"gamesPage.emptyText",
"guideChecklist.label",
"guideChecklist.selectLabel",
"guideChecklist.selectPlaceholder",
"guideChecklist.associate",
"guideChecklist.noMatches",
"guideChecklist.matches",
"guideChecklist.unlink",
"guideChecklist.hideCompleted",
"guideChecklist.completed",
"guideChecklist.checkCard",
"guideChecklist.uncheckCard",
"guideChecklist.resultsModalTitle",
"guideChecklist.resultsModalDescription",
"guideChecklist.resultsChecklistLabel",
"guideChecklist.resultsTargetLabel",
"guideChecklist.resultsNoToolbox",
"guideChecklist.resultsCopyLabel",
"guideChecklist.resultsCreateLabel",
"guideChecklist.resultsCreatingLabel",
"guideChecklist.resultsClose",
"guideChecklist.resultsCopied",
"guideChecklist.resultsCopyError",
"guideChecklist.resultsCreateError",
"aniimo.copyChecklist",
"aniimo.copyVariantsChecklist",
"aniimo.variantsChecklistTitle",
"aniimo.statsTitle",
"aniimo.statsTotal",
"aniimo.stats.hp",
"aniimo.stats.atk",
"aniimo.stats.p_def",
"aniimo.stats.m_def",
"aniimo.stats.break",
"aniimo.stats.regen",
"aniimo.title",
"aniimo.description",
"aniimo.homeDescription",
"aniimo.filters",
"aniimo.name",
"aniimo.searchPlaceholder",
"aniimo.prismanaOnly",
"aniimo.prismana",
"aniimo.elements",
"aniimo.roles",
"aniimo.role",
"aniimo.abilities",
"aniimo.abilityActions",
"aniimo.minimum",
"aniimo.exactLevel",
"aniimo.levelMode",
"aniimo.abilityDisabled",
"aniimo.level",
"aniimo.match",
"aniimo.and",
"aniimo.or",
"aniimo.reset",
"aniimo.groupVariants",
"aniimo.variants",
"aniimo.results",
"aniimo.emptyTitle",
"aniimo.emptyText",
"aniimo.loading",
"aniimo.error",
"aniimo.showFront",
"aniimo.showBack",
"library.eyebrow",
"library.title",
"library.description",
"library.summaryLabel",
"library.tocLabel",
"library.tocTitle",
"library.docTocTitle",
"library.tocDescription",
"library.toolsLabel",
"library.categoryLabel",
"library.featureHeading",
"library.advancedHeading",
"library.copyExampleLabel",
"library.copyExampleSuccess",
"library.controlLegendHeading",
"library.categories.notesTracking",
"library.categories.references",
"library.categories.calculationData",
"library.categories.timeRoutines",
"library.categories.buildsCommands",
"library.categoryDescriptions.notesTracking",
"library.categoryDescriptions.references",
"library.categoryDescriptions.calculationData",
"library.categoryDescriptions.timeRoutines",
"library.categoryDescriptions.buildsCommands",
"home.hero.eyebrow",
"home.hero.title",
"home.hero.description",
"home.hero.primaryAction",
"home.hero.secondaryAction",
"home.stats.toolboxSingular",
"home.stats.toolboxPlural",
"home.stats.toolSingular",
"home.stats.toolPlural",
"home.onboarding.eyebrow",
"home.onboarding.title",
"home.onboarding.description",
"home.onboarding.stepsTitle",
"home.onboarding.sliderTitle",
"home.onboarding.sliderLabel",
"home.onboarding.slideButtonLabel",
"home.origin.eyebrow",
"home.origin.title",
"home.origin.visualAlt",
"home.origin.dialogueAriaLabel",
"home.origin.caption",
"home.aboutLink",
"home.legal.copyright",
"home.legal.disclaimer",
"about.eyebrow",
"about.title",
"about.description",
"about.sectionsEyebrow",
"about.sectionsTitle",
"about.toolsTitle",
"about.toolsIntro",
"about.toolsLibraryLink",
"about.limitsTitle",
"about.contribute.eyebrow",
"about.contribute.title",
"about.contribute.image",
"about.contribute.imageAlt",
"about.contribute.description",
"about.contribute.exampleIntro",
"about.contribute.example",
"about.contribute.promptTitle",
"about.contribute.promptIntro",
"about.contribute.promptText",
"toolboxes.eyebrow",
"toolboxes.storageStatus.loadingTitle",
"toolboxes.storageStatus.mediaLoadingMessage",
"toolboxes.storageStatus.loadingMessage",
"toolboxes.storageStatus.errorTitle",
"toolboxes.storageStatus.errorMessage",
"toolboxes.storageStatus.retryLabel",
"toolboxes.storageStatus.writeErrorTitle",
"toolboxes.spaces.toolbarLabel",
"toolboxes.spaces.tabsLabel",
"toolboxes.spaces.addLabel",
"toolboxes.spaces.createTitle",
"toolboxes.spaces.renameTitle",
"toolboxes.spaces.renameLabel",
"toolboxes.spaces.deleteTitle",
"toolboxes.spaces.deleteLabel",
"toolboxes.spaces.deleteEmpty",
"toolboxes.spaces.deleteWithTools",
"toolboxes.spaces.nameLabel",
"toolboxes.spaces.cancelLabel",
"toolboxes.spaces.saveLabel",
"toolboxes.spaces.reorderLabel",
"toolboxes.spaces.moveLabel",
"toolboxes.spaces.moveSuccess",
"toolboxes.spaces.emptyText",
"toolboxes.spaces.limitReached",
"toolboxes.moduleConfiguration.buttonLabel",
"toolboxes.moduleConfiguration.panelLabel",
"toolboxes.moduleConfiguration.organizationTitle",
"toolboxes.moduleConfiguration.spaceLabel",
"toolboxes.moduleConfiguration.guideTitle",
"toolboxes.moduleConfiguration.dataTitle",
"toolboxes.moduleConfiguration.importButtonLabel",
"toolboxes.moduleConfiguration.exportButtonLabel",
"toolboxes.drawer.emptyText",
"toolboxes.drawer.createLabel",
"toolboxes.drawer.linkExistingTitle",
"toolboxes.drawer.selectLabel",
"toolboxes.drawer.selectPlaceholder",
"toolboxes.drawer.noAvailable",
"toolboxes.drawer.linkLabel",
"toolboxes.title",
"toolboxes.newButton",
"toolboxes.libraryLink",
"toolboxes.importAll",
"toolboxes.exportAll",
"toolboxes.importOne",
"toolboxes.preferencesLabel",
"toolboxes.toastPositionLabel",
"toolboxes.toastPositionLeft",
"toolboxes.toastPositionRight",
"toolboxes.autoBackup.eyebrow",
"toolboxes.autoBackup.title",
"toolboxes.autoBackup.description",
"toolboxes.autoBackup.compatibleLabel",
"toolboxes.autoBackup.permissionTitle",
"toolboxes.autoBackup.permissionDescription",
"toolboxes.autoBackup.reminderMessage",
"toolboxes.autoBackup.reminderAction",
"toolboxes.autoBackup.folderLabel",
"toolboxes.autoBackup.enabled",
"toolboxes.autoBackup.toggleHelp",
"toolboxes.autoBackup.chooseFolder",
"toolboxes.autoBackup.changeFolder",
"toolboxes.autoBackup.reauthorize",
"toolboxes.autoBackup.permissionRequired",
"toolboxes.autoBackup.saveNow",
"toolboxes.autoBackup.noFolder",
"toolboxes.autoBackup.never",
"toolboxes.autoBackup.lastBackup",
"toolboxes.autoBackup.retention",
"toolboxes.autoBackup.unsupported",
"toolboxes.autoBackup.close",
"toolboxes.summary",
"toolboxes.storageHelp.title",
"toolboxes.storageHelp.text",
"toolboxes.modules.notepad.placeholder",
"toolboxes.modules.notepad.pagesMenuLabel",
"toolboxes.modules.notepad.pagesToggleLabel",
"toolboxes.modules.notepad.pagesToggleText",
"toolboxes.modules.notepad.addPageLabel",
"toolboxes.modules.notepad.renamePageLabel",
"toolboxes.modules.notepad.deletePageLabel",
"toolboxes.modules.notepad.toolbarLabel",
"toolboxes.modules.notepad.textFormatLabel",
"toolboxes.modules.notepad.boldTitle",
"toolboxes.modules.notepad.italicTitle",
"toolboxes.modules.notepad.underlineTitle",
"toolboxes.modules.notepad.strikeTitle",
"toolboxes.modules.notepad.headingTitle",
"toolboxes.modules.notepad.listGroupLabel",
"toolboxes.modules.notepad.bulletListTitle",
"toolboxes.modules.notepad.numberListTitle",
"toolboxes.modules.notepad.colorGroupLabel",
"toolboxes.modules.notepad.textColorLabel",
"toolboxes.modules.notepad.highlightColorLabel",
"toolboxes.modules.notepad.drawingLabel",
"toolboxes.modules.notepad.drawTitle",
"toolboxes.modules.notepad.eraseTitle",
"toolboxes.modules.notepad.temporaryTitle",
"toolboxes.modules.notepad.permanentTitle",
"toolboxes.modules.notepad.undoDrawingTitle",
"toolboxes.modules.notepad.clearDrawingTitle",
"toolboxes.modules.notepad.drawingColorLabel",
"toolboxes.modules.notepad.drawingWidthLabel",
"toolboxes.modules.notepad.temporaryStatus",
"toolboxes.modules.notepad.permanentStatus",
"toolboxes.modules.notepad.updatedAtPrefix",
"toolboxes.modules.notepad.sessionStorageError",
"toolboxes.modules.notepad.importPlaceholder",
"toolboxes.modules.notepad.importOpenButton",
"toolboxes.modules.notepad.exportOpenButton",
"toolboxes.modules.notepad.importModalTitle",
"toolboxes.modules.notepad.exportModalTitle",
"toolboxes.modules.notepad.importButton",
"toolboxes.modules.notepad.exportButton",
"toolboxes.modules.notepad.importSuccess",
"toolboxes.modules.notepad.exportSuccess",
"toolboxes.modules.notepad.importError",
"toolboxes.modules.notepad.exportInfo",
"toolboxes.modules.memos.defaultText",
"toolboxes.modules.memos.placeholder",
"toolboxes.modules.memos.addButton",
"toolboxes.modules.memos.emptyLabel",
"toolboxes.modules.memos.editLabel",
"toolboxes.modules.memos.deleteTitle",
"toolboxes.modules.memos.colorLabel",
"toolboxes.modules.memos.reorderTitle",
"toolboxes.modules.memos.tickerLabel",
"toolboxes.modules.memos.hideTickerTitle",
"toolboxes.modules.memos.showTickerTitle",
"toolboxes.modules.memos.settingsTitle",
"toolboxes.modules.memos.plannerLinkLabel",
"toolboxes.modules.memos.plannerNoneLabel",
"toolboxes.modules.memos.plannerAllLabel",
"toolboxes.modules.memos.plannerDefaultLabel",
"toolboxes.modules.memos.taskAlertTemplate",
"toolboxes.modules.memos.importPlaceholder",
"toolboxes.modules.memos.importOpenButton",
"toolboxes.modules.memos.exportOpenButton",
"toolboxes.modules.memos.importModalTitle",
"toolboxes.modules.memos.exportModalTitle",
"toolboxes.modules.memos.importButton",
"toolboxes.modules.memos.exportButton",
"toolboxes.modules.memos.importSuccess",
"toolboxes.modules.memos.exportSuccess",
"toolboxes.modules.memos.importError",
"toolboxes.modules.memos.exportInfo",
"toolboxes.modules.checklist.itemPlaceholder",
"toolboxes.modules.checklist.sectionPlaceholder",
"toolboxes.modules.checklist.quantityLabel",
"toolboxes.modules.checklist.addButton",
"toolboxes.modules.checklist.emptyItems",
"toolboxes.modules.checklist.importPlaceholder",
"toolboxes.modules.checklist.importOpenButton",
"toolboxes.modules.checklist.importModalTitle",
"toolboxes.modules.checklist.importButton",
"toolboxes.modules.checklist.hideCompletedSectionsTitle",
"toolboxes.modules.checklist.hideCompletedSectionsFullyTitle",
"toolboxes.modules.checklist.hideCompletedItemsTitle",
"toolboxes.modules.checklist.completedSectionsModeTitle",
"toolboxes.modules.checklist.completedSectionsVisibleLabel",
"toolboxes.modules.checklist.completedSectionsReducedLabel",
"toolboxes.modules.checklist.completedSectionsHiddenLabel",
"toolboxes.modules.checklist.showCompletedSectionTitle",
"toolboxes.modules.checklist.hideCompletedSectionTitle",
"toolboxes.modules.checklist.decrementLabel",
"toolboxes.modules.checklist.incrementLabel",
"toolboxes.modules.checklist.currentQuantityLabel",
"toolboxes.modules.checklist.reorderTitle",
"toolboxes.modules.checklist.reorderSectionTitle",
"toolboxes.modules.checklist.sectionEditTitle",
"toolboxes.modules.checklist.itemEditLabel",
"toolboxes.modules.checklist.categoryEditLabel",
"toolboxes.modules.checklist.editTitle",
"toolboxes.modules.checklist.deleteTitle",
"toolboxes.modules.images.addImages",
"toolboxes.modules.images.emptyImages",
"toolboxes.modules.images.pastePlaceholder",
"toolboxes.modules.images.pasteAriaLabel",
"toolboxes.modules.images.imageAlt",
"toolboxes.modules.images.labelPlaceholder",
"toolboxes.modules.images.labelAriaLabel",
"toolboxes.modules.images.previewAriaLabel",
"toolboxes.modules.images.galleryButton",
"toolboxes.modules.images.galleryTitle",
"toolboxes.modules.images.galleryImageAriaLabel",
"toolboxes.modules.images.returnGalleryTitle",
"toolboxes.modules.images.galleryZoomLabel",
"toolboxes.modules.images.galleryZoomOutTitle",
"toolboxes.modules.images.galleryZoomInTitle",
"toolboxes.modules.images.galleryRatioLabel",
"toolboxes.modules.images.galleryRatioSquareTitle",
"toolboxes.modules.images.galleryRatioLandscapeTitle",
"toolboxes.modules.images.previousImageTitle",
"toolboxes.modules.images.nextImageTitle",
"toolboxes.modules.images.positionLabel",
"toolboxes.modules.images.annotateAriaLabel",
"toolboxes.modules.images.annotateTitle",
"toolboxes.modules.images.deleteAriaLabel",
"toolboxes.modules.images.deleteTitle",
"toolboxes.modules.links.titlePlaceholder",
"toolboxes.modules.links.urlPlaceholder",
"toolboxes.modules.links.addButton",
"toolboxes.modules.links.emptyLinks",
"toolboxes.modules.links.importPlaceholder",
"toolboxes.modules.links.importOpenButton",
"toolboxes.modules.links.importModalTitle",
"toolboxes.modules.links.importButton",
"toolboxes.modules.links.copyTitle",
"toolboxes.modules.links.copiedTitle",
"toolboxes.modules.links.deleteTitle",
"toolboxes.modules.counters.labelPlaceholder",
"toolboxes.modules.counters.addButton",
"toolboxes.modules.counters.emptyCounters",
"toolboxes.modules.counters.decrementLabel",
"toolboxes.modules.counters.incrementLabel",
"toolboxes.modules.counters.resetTitle",
"toolboxes.modules.counters.deleteTitle",
"toolboxes.modules.randomizer.diceTab",
"toolboxes.modules.randomizer.wheelTab",
"toolboxes.modules.randomizer.teamsTab",
"toolboxes.modules.randomizer.facesLabel",
"toolboxes.modules.randomizer.rollsLabel",
"toolboxes.modules.randomizer.rollButton",
"toolboxes.modules.randomizer.stopButton",
"toolboxes.modules.randomizer.totalLabel",
"toolboxes.modules.randomizer.drawsLabel",
"toolboxes.modules.randomizer.drawButton",
"toolboxes.modules.randomizer.valuesTitle",
"toolboxes.modules.randomizer.valuePlaceholder",
"toolboxes.modules.randomizer.savedValuesLabel",
"toolboxes.modules.randomizer.weightLabel",
"toolboxes.modules.randomizer.addButton",
"toolboxes.modules.randomizer.clearValuesButton",
"toolboxes.modules.randomizer.teamCountLabel",
"toolboxes.modules.randomizer.createTeamsButton",
"toolboxes.modules.randomizer.playerSelectionModeLabel",
"toolboxes.modules.randomizer.playerSelectionAll",
"toolboxes.modules.randomizer.playerSelectionCount",
"toolboxes.modules.randomizer.playerSelectionCountLabel",
"toolboxes.modules.randomizer.nameLabel",
"toolboxes.modules.randomizer.namesImportLabel",
"toolboxes.modules.randomizer.namesImportPlaceholder",
"toolboxes.modules.randomizer.importNamesButton",
"toolboxes.modules.randomizer.clearNamesButton",
"toolboxes.modules.randomizer.valuesImportLabel",
"toolboxes.modules.randomizer.valuesImportPlaceholder",
"toolboxes.modules.randomizer.scoreLabel",
"toolboxes.modules.randomizer.activeLabel",
"toolboxes.modules.randomizer.firstPlayerLabel",
"toolboxes.modules.randomizer.secondPlayerLabel",
"toolboxes.modules.randomizer.selectNamesButton",
"toolboxes.modules.randomizer.selectedNamesLabel",
"toolboxes.modules.randomizer.exclusionModeLabel",
"toolboxes.modules.randomizer.exclusionModeSource",
"toolboxes.modules.randomizer.exclusionModeGroup",
"toolboxes.modules.randomizer.addExclusionButton",
"toolboxes.modules.randomizer.exclusionsListLabel",
"toolboxes.modules.randomizer.teamsSingularLabel",
"toolboxes.modules.randomizer.teamsPluralLabel",
"toolboxes.modules.randomizer.teamScoreLabel",
"toolboxes.modules.randomizer.emptyTeams",
"toolboxes.modules.randomizer.historyTitle",
"toolboxes.modules.randomizer.emptyHistory",
"toolboxes.modules.randomizer.clearHistoryButton",
"toolboxes.modules.randomizer.deleteTitle",
"toolboxes.modules.randomizer.wheelInvalid",
"toolboxes.modules.randomizer.notEnoughPlayers",
"toolboxes.modules.randomizer.constraintsImpossible",
"toolboxes.modules.randomizer.importPlaceholder",
"toolboxes.modules.randomizer.importOpenButton",
"toolboxes.modules.randomizer.exportOpenButton",
"toolboxes.modules.randomizer.importModalTitle",
"toolboxes.modules.randomizer.exportModalTitle",
"toolboxes.modules.randomizer.importButton",
"toolboxes.modules.randomizer.exportButton",
"toolboxes.modules.randomizer.importSuccess",
"toolboxes.modules.randomizer.exportSuccess",
"toolboxes.modules.randomizer.importError",
"toolboxes.modules.randomizer.exportInfo",
"toolboxes.modules.rngSimulator.ratesTitle",
"toolboxes.modules.rngSimulator.addRateTitle",
"toolboxes.modules.rngSimulator.editRateTitle",
"toolboxes.modules.rngSimulator.emptyRates",
"toolboxes.modules.rngSimulator.labelLabel",
"toolboxes.modules.rngSimulator.labelPlaceholder",
"toolboxes.modules.rngSimulator.rateLabel",
"toolboxes.modules.rngSimulator.costPerDrawLabel",
"toolboxes.modules.rngSimulator.costPerDrawPlaceholder",
"toolboxes.modules.rngSimulator.rateLabelRequired",
"toolboxes.modules.rngSimulator.rateInvalid",
"toolboxes.modules.rngSimulator.addRateButton",
"toolboxes.modules.rngSimulator.saveRateButton",
"toolboxes.modules.rngSimulator.cancelButton",
"toolboxes.modules.rngSimulator.deleteTitle",
"toolboxes.modules.rngSimulator.noActiveRateTitle",
"toolboxes.modules.rngSimulator.activeRateLabel",
"toolboxes.modules.rngSimulator.commonSettingsTitle",
"toolboxes.modules.rngSimulator.effectiveRateLabel",
"toolboxes.modules.rngSimulator.forecastsTabLabel",
"toolboxes.modules.rngSimulator.simulationTabLabel",
"toolboxes.modules.rngSimulator.trackingTabLabel",
"toolboxes.modules.rngSimulator.simulationDrawsLabel",
"toolboxes.modules.rngSimulator.perHourLabel",
"toolboxes.modules.rngSimulator.perDayLabel",
"toolboxes.modules.rngSimulator.multiplierLabel",
"toolboxes.modules.rngSimulator.averageDrawsLabel",
"toolboxes.modules.rngSimulator.simulationChanceLabel",
"toolboxes.modules.rngSimulator.simulationChanceWithoutMultiplierLabel",
"toolboxes.modules.rngSimulator.averageCostLabel",
"toolboxes.modules.rngSimulator.averageTimeTitle",
"toolboxes.modules.rngSimulator.periodLabel",
"toolboxes.modules.rngSimulator.averageTimeLabel",
"toolboxes.modules.rngSimulator.occurrenceCountLabel",
"toolboxes.modules.rngSimulator.hourTimeLabel",
"toolboxes.modules.rngSimulator.dayTimeLabel",
"toolboxes.modules.rngSimulator.durationChancesTitle",
"toolboxes.modules.rngSimulator.durationChancePrefix",
"toolboxes.modules.rngSimulator.chanceTargetsTitle",
"toolboxes.modules.rngSimulator.chanceLabel",
"toolboxes.modules.rngSimulator.costLabel",
"toolboxes.modules.rngSimulator.drawsUnit",
"toolboxes.modules.rngSimulator.hoursUnit",
"toolboxes.modules.rngSimulator.daysUnit",
"toolboxes.modules.rngSimulator.emptySummary",
"toolboxes.modules.rngSimulator.simulationTitle",
"toolboxes.modules.rngSimulator.simulateButton",
"toolboxes.modules.rngSimulator.clearHistoryButton",
"toolboxes.modules.rngSimulator.emptyHistory",
"toolboxes.modules.rngSimulator.successesLabel",
"toolboxes.modules.rngSimulator.failsLabel",
"toolboxes.modules.rngSimulator.observedRateLabel",
"toolboxes.modules.rngSimulator.expectedSuccessesLabel",
"toolboxes.modules.rngSimulator.multiplierShortLabel",
"toolboxes.modules.rngSimulator.longestFailStreakLabel",
"toolboxes.modules.rngSimulator.longestSuccessStreakLabel",
"toolboxes.modules.rngSimulator.firstSuccessCostLabel",
"toolboxes.modules.rngSimulator.costPerSuccessLabel",
"toolboxes.modules.rngSimulator.luckySimulationLabel",
"toolboxes.modules.rngSimulator.neutralSimulationLabel",
"toolboxes.modules.rngSimulator.unluckySimulationLabel",
"toolboxes.modules.rngSimulator.realDrawsLabel",
"toolboxes.modules.rngSimulator.realCumulativeChanceLabel",
"toolboxes.modules.rngSimulator.afterDrawsPrefix",
"toolboxes.modules.rngSimulator.averagePositionLabel",
"toolboxes.modules.rngSimulator.averageStatusStartLabel",
"toolboxes.modules.rngSimulator.averageStatusFarBelowLabel",
"toolboxes.modules.rngSimulator.averageStatusBelowLabel",
"toolboxes.modules.rngSimulator.averageStatusAverageLabel",
"toolboxes.modules.rngSimulator.averageStatusSlightlyAboveLabel",
"toolboxes.modules.rngSimulator.averageStatusHighLabel",
"toolboxes.modules.rngSimulator.averageStatusCatastrophicLabel",
"toolboxes.modules.rngSimulator.drawsUntilAverageLabel",
"toolboxes.modules.rngSimulator.averageExceededLabel",
"toolboxes.modules.rngSimulator.averageCostDeltaLabel",
"toolboxes.modules.rngSimulator.trackingTargetsTitle",
"toolboxes.modules.rngSimulator.trackingProgressLabel",
"toolboxes.modules.rngSimulator.remainingDrawsLabel",
"toolboxes.modules.rngSimulator.remainingCostLabel",
"toolboxes.modules.rngSimulator.importPlaceholder",
"toolboxes.modules.rngSimulator.importOpenButton",
"toolboxes.modules.rngSimulator.exportOpenButton",
"toolboxes.modules.rngSimulator.importModalTitle",
"toolboxes.modules.rngSimulator.exportModalTitle",
"toolboxes.modules.rngSimulator.importButton",
"toolboxes.modules.rngSimulator.exportButton",
"toolboxes.modules.rngSimulator.importSuccess",
"toolboxes.modules.rngSimulator.exportSuccess",
"toolboxes.modules.rngSimulator.importError",
"toolboxes.modules.rngSimulator.exportInfo",
"toolboxes.modules.combos.deviceLabel",
"toolboxes.modules.combos.keyboardLayoutLabel",
"toolboxes.modules.combos.categoryPlaceholder",
"toolboxes.modules.combos.targetCategoryLabel",
"toolboxes.modules.combos.noCategoryLabel",
"toolboxes.modules.combos.comboNameLabel",
"toolboxes.modules.combos.comboNamePlaceholder",
"toolboxes.modules.combos.defaultComboName",
"toolboxes.modules.combos.emptyDraft",
"toolboxes.modules.combos.simultaneousModeTitle",
"toolboxes.modules.combos.successiveModeLabel",
"toolboxes.modules.combos.simultaneousModeLabel",
"toolboxes.modules.combos.holdLabel",
"toolboxes.modules.combos.holdToggleLabel",
"toolboxes.modules.combos.removeStepTitle",
"toolboxes.modules.combos.removeLastInputTitle",
"toolboxes.modules.combos.addComboButton",
"toolboxes.modules.combos.saveComboButton",
"toolboxes.modules.combos.cancelEditTitle",
"toolboxes.modules.combos.cancelEditButton",
"toolboxes.modules.combos.paletteLabel",
"toolboxes.modules.combos.customKeyPlaceholder",
"toolboxes.modules.combos.addCustomKeyButton",
"toolboxes.modules.combos.emptyCombos",
"toolboxes.modules.combos.emptySequence",
"toolboxes.modules.combos.showCategoryTitle",
"toolboxes.modules.combos.hideCategoryTitle",
"toolboxes.modules.combos.reorderCategoryTitle",
"toolboxes.modules.combos.reorderComboTitle",
"toolboxes.modules.combos.editComboTitle",
"toolboxes.modules.combos.deleteComboTitle",
"toolboxes.modules.calculator.expressionLabel",
"toolboxes.modules.calculator.expressionPlaceholder",
"toolboxes.modules.calculator.totalLabel",
"toolboxes.modules.calculator.currentLabel",
"toolboxes.modules.calculator.currentPlaceholder",
"toolboxes.modules.calculator.subtractCurrentTitle",
"toolboxes.modules.calculator.resultLabel",
"toolboxes.modules.calculator.labelLabel",
"toolboxes.modules.calculator.labelPlaceholder",
"toolboxes.modules.calculator.saveButton",
"toolboxes.modules.calculator.updateButton",
"toolboxes.modules.calculator.newButton",
"toolboxes.modules.calculator.rootButton",
"toolboxes.modules.calculator.resetTitle",
"toolboxes.modules.calculator.copyTitle",
"toolboxes.modules.calculator.copiedTitle",
"toolboxes.modules.calculator.scrollableTitle",
"toolboxes.modules.calculator.emptyResults",
"toolboxes.modules.calculator.baseTokenLabel",
"toolboxes.modules.calculator.insertBaseTitle",
"toolboxes.modules.calculator.editTitle",
"toolboxes.modules.calculator.addChildTitle",
"toolboxes.modules.calculator.deleteTitle",
"toolboxes.modules.goalTree.editorTitle",
"toolboxes.modules.goalTree.treeTitle",
"toolboxes.modules.goalTree.addRootButton",
"toolboxes.modules.goalTree.addRootSchemaButton",
"toolboxes.modules.goalTree.addChildButton",
"toolboxes.modules.goalTree.addChildTitle",
"toolboxes.modules.goalTree.addContentItemTitle",
"toolboxes.modules.goalTree.addContentItemTooltip",
"toolboxes.modules.goalTree.verticalViewTitle",
"toolboxes.modules.goalTree.horizontalViewTitle",
"toolboxes.modules.goalTree.cardActionsLabel",
"toolboxes.modules.goalTree.editTitle",
"toolboxes.modules.goalTree.baseTab",
"toolboxes.modules.goalTree.contentTab",
"toolboxes.modules.goalTree.advancedTab",
"toolboxes.modules.goalTree.resetTimeLabel",
"toolboxes.modules.goalTree.weeklyResetDayLabel",
"toolboxes.modules.goalTree.titleLabel",
"toolboxes.modules.goalTree.listTitleLabel",
"toolboxes.modules.goalTree.kindLabel",
"toolboxes.modules.goalTree.stepKind",
"toolboxes.modules.goalTree.textKind",
"toolboxes.modules.goalTree.checklistKind",
"toolboxes.modules.goalTree.iconLabel",
"toolboxes.modules.goalTree.iconColorLabel",
"toolboxes.modules.goalTree.targetQuantityLabel",
"toolboxes.modules.goalTree.quantitySectionTitle",
"toolboxes.modules.goalTree.progressSectionTitle",
"toolboxes.modules.goalTree.autoProgressLabel",
"toolboxes.modules.goalTree.manualProgressLabel",
"toolboxes.modules.goalTree.autoProgressTitle",
"toolboxes.modules.goalTree.manualProgressTitle",
"toolboxes.modules.goalTree.autoProgressHint",
"toolboxes.modules.goalTree.manualProgressHint",
"toolboxes.modules.goalTree.contentSectionTitle",
"toolboxes.modules.goalTree.itemLabelLabel",
"toolboxes.modules.goalTree.itemLabelPlaceholder",
"toolboxes.modules.goalTree.itemCheckedLabel",
"toolboxes.modules.goalTree.itemTotalLabel",
"toolboxes.modules.goalTree.itemTotalPlaceholder",
"toolboxes.modules.goalTree.itemTotalHelp",
"toolboxes.modules.goalTree.prerequisitesSectionTitle",
"toolboxes.modules.goalTree.contentTabHint",
"toolboxes.modules.goalTree.stepContentUnavailable",
"toolboxes.modules.goalTree.decrementItemTitle",
"toolboxes.modules.goalTree.incrementItemTitle",
"toolboxes.modules.goalTree.addContentItemButton",
"toolboxes.modules.goalTree.formulaPlaceholder",
"toolboxes.modules.goalTree.formulaHelp",
"toolboxes.modules.goalTree.baseTokenLabel",
"toolboxes.modules.goalTree.insertBaseTitle",
"toolboxes.modules.goalTree.resetTypeLabel",
"toolboxes.modules.goalTree.noneReset",
"toolboxes.modules.goalTree.dailyReset",
"toolboxes.modules.goalTree.weeklyReset",
"toolboxes.modules.goalTree.closeButton",
"toolboxes.modules.goalTree.deleteButton",
"toolboxes.modules.goalTree.deleteTitle",
"toolboxes.modules.goalTree.reorderTitle",
"toolboxes.modules.goalTree.showChildrenTitle",
"toolboxes.modules.goalTree.hideChildrenTitle",
"toolboxes.modules.goalTree.emptySelection",
"toolboxes.modules.goalTree.emptyTree",
"toolboxes.modules.table.toolbarLabel",
"toolboxes.modules.table.gridLabel",
"toolboxes.modules.table.cellLabel",
"toolboxes.modules.table.rowActionsLabel",
"toolboxes.modules.table.columnActionsLabel",
"toolboxes.modules.table.sizeLabel",
"toolboxes.modules.table.addRowTitle",
"toolboxes.modules.table.removeRowTitle",
"toolboxes.modules.table.addColumnTitle",
"toolboxes.modules.table.removeColumnTitle",
"toolboxes.modules.table.copyTitle",
"toolboxes.modules.table.copiedTitle",
"toolboxes.modules.table.errorTitle",
"toolboxes.modules.table.columnLabel",
"toolboxes.modules.table.rowLabel",
"toolboxes.modules.table.columnFormulaTitle",
"toolboxes.modules.table.rowFormulaTitle",
"toolboxes.modules.timer.stopwatchTab",
"toolboxes.modules.timer.countdownTab",
"toolboxes.modules.timer.stopwatchTitle",
"toolboxes.modules.timer.countdownTitle",
"toolboxes.modules.timer.startButton",
"toolboxes.modules.timer.pauseButton",
"toolboxes.modules.timer.lapButton",
"toolboxes.modules.timer.resetTitle",
"toolboxes.modules.timer.lapLabelAriaLabel",
"toolboxes.modules.timer.countdownLabel",
"toolboxes.modules.timer.countdownLabelPlaceholder",
"toolboxes.modules.timer.countdownTypeLabel",
"toolboxes.modules.timer.durationType",
"toolboxes.modules.timer.dailyTimeType",
"toolboxes.modules.timer.timePatternType",
"toolboxes.modules.timer.intervalType",
"toolboxes.modules.timer.durationLabel",
"toolboxes.modules.timer.dailyTimeLabel",
"toolboxes.modules.timer.timePatternLabel",
"toolboxes.modules.timer.intervalLabel",
"toolboxes.modules.timer.intervalStartNowOption",
"toolboxes.modules.timer.intervalStartTimeOption",
"toolboxes.modules.timer.intervalStartTimeLabel",
"toolboxes.modules.timer.addCountdownButton",
"toolboxes.modules.timer.requiredError",
"toolboxes.modules.timer.invalidError",
"toolboxes.modules.timer.defaultCountdownLabel",
"toolboxes.modules.timer.finishedLabel",
"toolboxes.modules.timer.targetLabel",
"toolboxes.modules.timer.stepLabel",
"toolboxes.modules.timer.stepsTitle",
"toolboxes.modules.timer.countdownsTitle",
"toolboxes.modules.timer.scrollableTitle",
"toolboxes.modules.timer.sortTitle",
"toolboxes.modules.timer.autoRefreshTitle",
"toolboxes.modules.timer.alertModeTitle",
"toolboxes.modules.timer.alertOffTitle",
"toolboxes.modules.timer.alertVisibleTitle",
"toolboxes.modules.timer.alertSiteTitle",
"toolboxes.modules.timer.alertTooSoonTitle",
"toolboxes.modules.timer.alertMessage",
"toolboxes.modules.timer.emptySteps",
"toolboxes.modules.timer.emptyCountdowns",
"toolboxes.modules.timer.renameTitle",
"toolboxes.modules.timer.deleteTitle",
"toolboxes.modules.calendar.init.title",
"toolboxes.modules.calendar.init.weekly",
"toolboxes.modules.calendar.init.weeklyDescription",
"toolboxes.modules.calendar.init.monthly",
"toolboxes.modules.calendar.init.monthlyDescription",
"toolboxes.modules.calendar.init.soon",
"toolboxes.modules.calendar.gridLabel",
"toolboxes.modules.calendar.monthGridLabel",
"toolboxes.modules.calendar.titleLabel",
"toolboxes.modules.calendar.titlePlaceholder",
"toolboxes.modules.calendar.dayLabel",
"toolboxes.modules.calendar.startDayLabel",
"toolboxes.modules.calendar.endDayLabel",
"toolboxes.modules.calendar.startLabel",
"toolboxes.modules.calendar.endLabel",
"toolboxes.modules.calendar.alertModeTitle",
"toolboxes.modules.calendar.alertTypeLabel",
"toolboxes.modules.calendar.alertOffTitle",
"toolboxes.modules.calendar.alertVisibleTitle",
"toolboxes.modules.calendar.alertSiteTitle",
"toolboxes.modules.calendar.alertOffsetLabel",
"toolboxes.modules.calendar.alertOffset10",
"toolboxes.modules.calendar.alertOffset5",
"toolboxes.modules.calendar.alertOffset0",
"toolboxes.modules.calendar.alertMessage",
"toolboxes.modules.calendar.resumeAlertMessage",
"toolboxes.modules.calendar.colorLabel",
"toolboxes.modules.calendar.colorViolet",
"toolboxes.modules.calendar.colorGold",
"toolboxes.modules.calendar.colorCyan",
"toolboxes.modules.calendar.colorPink",
"toolboxes.modules.calendar.colorGreen",
"toolboxes.modules.calendar.colorGray",
"toolboxes.modules.calendar.repeatableTitle",
"toolboxes.modules.calendar.repeatLabel",
"toolboxes.modules.calendar.repeatNone",
"toolboxes.modules.calendar.repeatWeekly",
"toolboxes.modules.calendar.repeatMonthly",
"toolboxes.modules.calendar.monthlyRepeatLabel",
"toolboxes.modules.calendar.monthlyRepeatMonthDay",
"toolboxes.modules.calendar.monthlyRepeatWeekday",
"toolboxes.modules.calendar.monthDatePickerLabel",
"toolboxes.modules.calendar.previousMonthTitle",
"toolboxes.modules.calendar.nextMonthTitle",
"toolboxes.modules.calendar.currentMonthTitle",
"toolboxes.modules.calendar.previousShortLabel",
"toolboxes.modules.calendar.nextShortLabel",
"toolboxes.modules.calendar.hideEmptyHoursLabel",
"toolboxes.modules.calendar.importPlaceholder",
"toolboxes.modules.calendar.importOpenButton",
"toolboxes.modules.calendar.exportOpenButton",
"toolboxes.modules.calendar.importModalTitle",
"toolboxes.modules.calendar.exportModalTitle",
"toolboxes.modules.calendar.importButton",
"toolboxes.modules.calendar.exportButton",
"toolboxes.modules.calendar.exportInfo",
"toolboxes.modules.calendar.importSuccess",
"toolboxes.modules.calendar.exportSuccess",
"toolboxes.modules.calendar.importError",
"toolboxes.modules.calendar.addButton",
"toolboxes.modules.calendar.saveButton",
"toolboxes.modules.calendar.cancelButton",
"toolboxes.modules.calendar.editTitle",
"toolboxes.modules.calendar.copyTitle",
"toolboxes.modules.calendar.copiedTitle",
"toolboxes.modules.calendar.pastedTitle",
"toolboxes.modules.calendar.deleteTitle",
"toolboxes.modules.calendar.deleteOccurrenceTitle",
"toolboxes.modules.calendar.deleteSeriesTitle",
"toolboxes.modules.calendar.requiredError",
"toolboxes.modules.calendar.invalidRangeError",
"toolboxes.modules.taskPlanner.settingsTitle",
"toolboxes.modules.taskPlanner.hideCompletedTitle",
"toolboxes.modules.taskPlanner.globalWeeklyResetDayLabel",
"toolboxes.modules.taskPlanner.resetTimeLabel",
"toolboxes.modules.taskPlanner.clearCompletedUniqueTitle",
"toolboxes.modules.taskPlanner.clearCompletedUniqueButton",
"toolboxes.modules.taskPlanner.titlePlaceholder",
"toolboxes.modules.taskPlanner.titleLabel",
"toolboxes.modules.taskPlanner.descriptionPlaceholder",
"toolboxes.modules.taskPlanner.descriptionLabel",
"toolboxes.modules.taskPlanner.typeLabel",
"toolboxes.modules.taskPlanner.uniqueType",
"toolboxes.modules.taskPlanner.dailyType",
"toolboxes.modules.taskPlanner.weeklyType",
"toolboxes.modules.taskPlanner.addButton",
"toolboxes.modules.taskPlanner.reorderTitle",
"toolboxes.modules.taskPlanner.checkTitle",
"toolboxes.modules.taskPlanner.uncheckTitle",
"toolboxes.modules.taskPlanner.deleteTitle",
"toolboxes.modules.taskPlanner.showDescriptionTitle",
"toolboxes.modules.taskPlanner.hideDescriptionTitle",
"toolboxes.modules.taskPlanner.taskSettingsTitle",
"toolboxes.modules.taskPlanner.missingPrerequisiteTitle",
"toolboxes.modules.taskPlanner.missingPrerequisiteBadge",
"toolboxes.modules.taskPlanner.missingPrerequisiteNotice",
"toolboxes.modules.taskPlanner.categoryLabel",
"toolboxes.modules.taskPlanner.categoryPlaceholder",
"toolboxes.modules.taskPlanner.categoryReorderTitle",
"toolboxes.modules.taskPlanner.uncategorizedDropTitle",
"toolboxes.modules.taskPlanner.showCategoryTitle",
"toolboxes.modules.taskPlanner.hideCategoryTitle",
"toolboxes.modules.taskPlanner.taskDailyResetTimeLabel",
"toolboxes.modules.taskPlanner.inheritResetTime",
"toolboxes.modules.taskPlanner.taskResetLabel",
"toolboxes.modules.taskPlanner.noTaskResetLabel",
"toolboxes.modules.taskPlanner.taskWeeklyResetDayLabel",
"toolboxes.modules.taskPlanner.inheritWeeklyResetDay",
"toolboxes.modules.taskPlanner.linksLabel",
"toolboxes.modules.taskPlanner.emptyLinks",
"toolboxes.modules.taskPlanner.addLinkLabel",
"toolboxes.modules.taskPlanner.prerequisiteCheckboxLabel",
"toolboxes.modules.taskPlanner.removeRelationTitle",
"toolboxes.modules.taskPlanner.emptyTasks",
"toolboxes.modules.equipmentPlanner.typePlaceholder",
"toolboxes.modules.equipmentPlanner.equipmentIconLabel",
"toolboxes.modules.equipmentPlanner.equipmentPlaceholder",
"toolboxes.modules.equipmentPlanner.addEquipmentButton",
"toolboxes.modules.equipmentPlanner.summaryTitle",
"toolboxes.modules.equipmentPlanner.activeEquipmentsSingular",
"toolboxes.modules.equipmentPlanner.activeEquipmentsPlural",
"toolboxes.modules.equipmentPlanner.numericTotalsTitle",
"toolboxes.modules.equipmentPlanner.equipmentTotalsTitle",
"toolboxes.modules.equipmentPlanner.socketTotalsTitle",
"toolboxes.modules.equipmentPlanner.mixedTotalsTitle",
"toolboxes.modules.equipmentPlanner.summaryTooltipTitle",
"toolboxes.modules.equipmentPlanner.textBonusesTitle",
"toolboxes.modules.equipmentPlanner.emptyTotals",
"toolboxes.modules.equipmentPlanner.emptyTextBonuses",
"toolboxes.modules.equipmentPlanner.emptyEquipments",
"toolboxes.modules.equipmentPlanner.activeLabel",
"toolboxes.modules.equipmentPlanner.includeInSummaryLabel",
"toolboxes.modules.equipmentPlanner.reorderTypeTitle",
"toolboxes.modules.equipmentPlanner.renameTypeTitle",
"toolboxes.modules.equipmentPlanner.showTypeTitle",
"toolboxes.modules.equipmentPlanner.hideTypeTitle",
"toolboxes.modules.equipmentPlanner.reorderEquipmentTitle",
"toolboxes.modules.equipmentPlanner.renameEquipmentTitle",
"toolboxes.modules.equipmentPlanner.showDetailsTitle",
"toolboxes.modules.equipmentPlanner.hideDetailsTitle",
"toolboxes.modules.equipmentPlanner.deleteEquipmentTitle",
"toolboxes.modules.equipmentPlanner.tabsLabel",
"toolboxes.modules.equipmentPlanner.statsTab",
"toolboxes.modules.equipmentPlanner.socketsTab",
"toolboxes.modules.equipmentPlanner.craftTab",
"toolboxes.modules.equipmentPlanner.displayStatsTitle",
"toolboxes.modules.equipmentPlanner.displaySocketTitle",
"toolboxes.modules.equipmentPlanner.editModeTitle",
"toolboxes.modules.equipmentPlanner.viewModeTitle",
"toolboxes.modules.equipmentPlanner.editModeButton",
"toolboxes.modules.equipmentPlanner.viewModeButton",
"toolboxes.modules.equipmentPlanner.obtainLabel",
"toolboxes.modules.equipmentPlanner.obtainPlaceholder",
"toolboxes.modules.equipmentPlanner.emptyObtain",
"toolboxes.modules.equipmentPlanner.characteristicsTitle",
"toolboxes.modules.equipmentPlanner.traitIconLabel",
"toolboxes.modules.equipmentPlanner.categoryPlaceholder",
"toolboxes.modules.equipmentPlanner.traitNamePlaceholder",
"toolboxes.modules.equipmentPlanner.traitValuePlaceholder",
"toolboxes.modules.equipmentPlanner.addTraitButton",
"toolboxes.modules.equipmentPlanner.reorderTraitTitle",
"toolboxes.modules.equipmentPlanner.reorderCategoryTitle",
"toolboxes.modules.equipmentPlanner.deleteTraitTitle",
"toolboxes.modules.equipmentPlanner.emptyCharacteristics",
"toolboxes.modules.equipmentPlanner.materialsTitle",
"toolboxes.modules.equipmentPlanner.materialNamePlaceholder",
"toolboxes.modules.equipmentPlanner.materialQtyLabel",
"toolboxes.modules.equipmentPlanner.addMaterialButton",
"toolboxes.modules.equipmentPlanner.deleteMaterialTitle",
"toolboxes.modules.equipmentPlanner.socketItemsTitle",
"toolboxes.modules.equipmentPlanner.emptySocketItems",
"toolboxes.modules.equipmentPlanner.socketNamePlaceholder",
"toolboxes.modules.equipmentPlanner.socketShapeLabel",
"toolboxes.modules.equipmentPlanner.socketColorLabel",
"toolboxes.modules.equipmentPlanner.addSocketButton",
"toolboxes.modules.equipmentPlanner.bonusNamePlaceholder",
"toolboxes.modules.equipmentPlanner.addBonusButton",
"toolboxes.modules.equipmentPlanner.deleteSocketTitle",
"toolboxes.modules.equipmentPlanner.deleteBonusTitle",
"toolboxes.modules.equipmentPlanner.addSocketLinkLabel",
"toolboxes.modules.equipmentPlanner.deleteSocketLinkTitle",
"toolboxes.modules.imageAnnotation.addImage",
"toolboxes.modules.imageAnnotation.replaceImage",
"toolboxes.modules.imageAnnotation.pastePlaceholder",
"toolboxes.modules.imageAnnotation.pasteAriaLabel",
"toolboxes.modules.imageAnnotation.stageAriaLabel",
"toolboxes.modules.imageAnnotation.addMarkerAriaLabel",
"toolboxes.modules.imageAnnotation.imageAlt",
"toolboxes.modules.imageAnnotation.previewTitle",
"toolboxes.modules.imageAnnotation.previewAriaLabel",
"toolboxes.modules.imageAnnotation.markerPrefix",
"toolboxes.modules.imageAnnotation.markersSingular",
"toolboxes.modules.imageAnnotation.markersPlural",
"toolboxes.modules.imageAnnotation.markerPlaceholder",
"toolboxes.modules.imageAnnotation.emptyImage",
"toolboxes.modules.imageAnnotation.emptyMarkers",
"toolboxes.modules.imageAnnotation.deleteImageTitle",
"toolboxes.modules.imageAnnotation.deleteMarkerTitle",
"toolboxes.modules.imageAnnotation.drawingLabel",
"toolboxes.modules.imageAnnotation.drawTitle",
"toolboxes.modules.imageAnnotation.eraseTitle",
"toolboxes.modules.imageAnnotation.undoDrawingTitle",
"toolboxes.modules.imageAnnotation.clearDrawingTitle",
"toolboxes.modules.imageAnnotation.drawingColorLabel",
"toolboxes.modules.imageAnnotation.drawingWidthLabel",
"toolboxes.modules.imageAnnotation.temporaryTitle",
"toolboxes.modules.imageAnnotation.permanentTitle",
"toolboxes.modules.imageAnnotation.sessionStorageError",
"toolboxes.emptyTitle",
"toolboxes.emptyText"
].forEach((path) => assertNonEmptyString(site, path));
assertStringArray(site, "toolboxes.storageHelp.items", 3);
assertStringArray(site, "toolboxes.autoBackup.browsers", 2);
assertStringArray(site, "toolboxes.autoBackup.permissionSteps", 3);
const librarySummaryItems = valueAt(site, "library.summaryItems");
assert.ok(Array.isArray(librarySummaryItems), "library.summaryItems must be an array");
assert.ok(librarySummaryItems.length >= 3, "library.summaryItems must contain at least 3 items");
librarySummaryItems.forEach((item, index) => {
assertNonEmptyString({ item }, `item.icon`);
assertNonEmptyString({ item }, `item.title`);
assertNonEmptyString({ item }, `item.text`);
assert.equal(typeof item, "object", `library.summaryItems[${index}] must be an object`);
});
const libraryToolDocs = valueAt(site, "library.toolDocs");
["memos", "notepad", "checklist", "counters", "randomizer", "rngSimulator", "images", "imageAnnotation", "links", "calculator", "table", "goalTree", "timer", "calendar", "taskPlanner", "combos", "equipmentPlanner"].forEach((toolType) => {
const docs = libraryToolDocs?.[toolType];
assert.equal(typeof docs, "object", `library.toolDocs.${toolType} must be an object`);
assert.ok(Array.isArray(docs.controls), `library.toolDocs.${toolType}.controls must be an array`);
assert.ok(docs.controls.length >= 1, `library.toolDocs.${toolType}.controls must not be empty`);
docs.controls.forEach((control, index) => {
assert.equal(typeof control, "object", `library.toolDocs.${toolType}.controls[${index}] must be an object`);
assertNonEmptyString({ control }, "control.group");
assertNonEmptyString({ control }, "control.icon");
assertNonEmptyString({ control }, "control.title");
if (control.text) assertNonEmptyString({ control }, "control.text");
if (control.preview) {
assert.equal(typeof control.preview, "object", `library.toolDocs.${toolType}.controls[${index}].preview must be an object`);
assertNonEmptyString({ preview: control.preview }, "preview.kind");
}
});
assert.ok(Array.isArray(docs.features), `library.toolDocs.${toolType}.features must be an array`);
assert.ok(docs.features.length >= 1, `library.toolDocs.${toolType}.features must not be empty`);
docs.features.forEach((feature, index) => {
assertNonEmptyString({ feature }, "feature.title");
assertNonEmptyString({ feature }, "feature.text");
assert.equal(typeof feature, "object", `library.toolDocs.${toolType}.features[${index}] must be an object`);
if (feature.details) {
assert.ok(Array.isArray(feature.details), `library.toolDocs.${toolType}.features[${index}].details must be an array`);
assert.ok(feature.details.length >= 1, `library.toolDocs.${toolType}.features[${index}].details must not be empty`);
feature.details.forEach((detail, detailIndex) => {
assert.equal(typeof detail, "string", `library.toolDocs.${toolType}.features[${index}].details[${detailIndex}] must be a string`);
assert.ok(detail.trim(), `library.toolDocs.${toolType}.features[${index}].details[${detailIndex}] must not be empty`);
});
}
});
if (docs.importFormat !== undefined) {
assert.equal(typeof docs.importFormat, "object", `library.toolDocs.${toolType}.importFormat must be an object`);
assertNonEmptyString({ importFormat: docs.importFormat }, "importFormat.text");
assertNonEmptyString({ importFormat: docs.importFormat }, "importFormat.example");
}
});
const aboutLimits = valueAt(site, "about.limits");
assert.ok(Array.isArray(aboutLimits), "about.limits must be an array");
assert.ok(aboutLimits.length >= 3, "about.limits must contain at least 3 items");
aboutLimits.forEach((limit, index) => {
if (typeof limit === "string") {
assert.ok(limit.trim(), `about.limits[${index}] must not be empty`);
return;
}
assert.equal(typeof limit, "object", `about.limits[${index}] must be a string or object`);
assertNonEmptyString({ limit }, "limit.icon");
assertNonEmptyString({ limit }, "limit.title");
assertNonEmptyString({ limit }, "limit.text");
});
const aboutSections = valueAt(site, "about.sections");
assert.ok(Array.isArray(aboutSections), "about.sections must be an array");
assert.ok(aboutSections.length >= 2, "about.sections must contain at least 2 items");
aboutSections.forEach((section) => {
assertNonEmptyString({ section }, "section.icon");
assertNonEmptyString({ section }, "section.image");
assertNonEmptyString({ section }, "section.title");
assertNonEmptyString({ section }, "section.text");
if (section.afterText) {
assertNonEmptyString({ section }, "section.afterText");
}
if (section.warning) {
assertNonEmptyString({ section }, "section.warning");
}
if (section.bullets) {
assert.ok(Array.isArray(section.bullets), "section.bullets must be an array");
assert.ok(section.bullets.length >= 1, "section.bullets must not be empty");
if (section.bulletsColumns) {
assert.ok([1, 2].includes(section.bulletsColumns), "section.bulletsColumns must be 1 or 2");
}
section.bullets.forEach((bullet, bulletIndex) => {
assert.equal(typeof bullet, "string", `section.bullets[${bulletIndex}] must be a string`);
assert.ok(bullet.trim(), `section.bullets[${bulletIndex}] must not be empty`);
});
}
});
const aboutToolGroups = valueAt(site, "about.toolGroups");
assert.ok(Array.isArray(aboutToolGroups), "about.toolGroups must be an array");
assert.ok(aboutToolGroups.length >= 3, "about.toolGroups must contain at least 3 items");
aboutToolGroups.forEach((group, index) => {
assertNonEmptyString({ group }, "group.icon");
assertNonEmptyString({ group }, "group.title");
assertNonEmptyString({ group }, "group.text");
assert.ok(Array.isArray(group.examples), `about.toolGroups[${index}].examples must be an array`);
assert.ok(group.examples.length >= 1, `about.toolGroups[${index}].examples must not be empty`);
group.examples.forEach((example, exampleIndex) => {
assert.equal(typeof example, "string", `about.toolGroups[${index}].examples[${exampleIndex}] must be a string`);
assert.ok(example.trim(), `about.toolGroups[${index}].examples[${exampleIndex}] must not be empty`);
});
});
const contributeBlocks = valueAt(site, "about.contribute.blocks");
assert.ok(Array.isArray(contributeBlocks), "about.contribute.blocks must be an array");
assert.equal(contributeBlocks.length, 3, "about.contribute.blocks must contain 3 items");
contributeBlocks.forEach((block, index) => {
assertNonEmptyString({ block }, "block.icon");
assertNonEmptyString({ block }, "block.title");
assertNonEmptyString({ block }, "block.text");
if (block.note) {
assertNonEmptyString({ block }, "block.note");
}
if (block.bullets) {
assert.ok(Array.isArray(block.bullets), `about.contribute.blocks[${index}].bullets must be an array`);
assert.ok(block.bullets.length >= 1, `about.contribute.blocks[${index}].bullets must not be empty`);
block.bullets.forEach((bullet, bulletIndex) => {
assert.equal(typeof bullet, "string", `about.contribute.blocks[${index}].bullets[${bulletIndex}] must be a string`);
assert.ok(bullet.trim(), `about.contribute.blocks[${index}].bullets[${bulletIndex}] must not be empty`);
});
}
});
assertNonEmptyString(site, "home.onboarding.primaryAction");
const onboardingCards = valueAt(site, "home.onboarding.cards");
assert.ok(Array.isArray(onboardingCards), "home.onboarding.cards must be an array");
assert.ok(onboardingCards.length >= 2, "home.onboarding.cards must contain at least 2 items");
onboardingCards.forEach((card, index) => {
assert.equal(typeof card, "object", `home.onboarding.cards[${index}] must be an object`);
assertNonEmptyString({ card }, "card.icon");
assertNonEmptyString({ card }, "card.image");
assertNonEmptyString({ card }, "card.title");
assertNonEmptyString({ card }, "card.text");
assertNonEmptyString({ card }, "card.action");
assertNonEmptyString({ card }, "card.href");
});
const onboardingSteps = valueAt(site, "home.onboarding.steps");
assert.ok(Array.isArray(onboardingSteps), "home.onboarding.steps must be an array");
assert.ok(onboardingSteps.length >= 1, "home.onboarding.steps must contain at least 1 item");
onboardingSteps.forEach((step, index) => {
assert.equal(typeof step, "object", `home.onboarding.steps[${index}] must be an object`);
assertNonEmptyString({ step }, "step.title");
assertNonEmptyString({ step }, "step.text");
});
const onboardingSlides = valueAt(site, "home.onboarding.slides");
assert.ok(Array.isArray(onboardingSlides), "home.onboarding.slides must be an array");
assert.equal(onboardingSlides.length, 4, "home.onboarding.slides must contain 4 items");
onboardingSlides.forEach((slide, index) => {
assert.equal(typeof slide, "object", `home.onboarding.slides[${index}] must be an object`);
assertNonEmptyString({ slide }, "slide.src");
assertNonEmptyString({ slide }, "slide.alt");
assertNonEmptyString({ slide }, "slide.title");
assertNonEmptyString({ slide }, "slide.text");
});
const toolboxCreationTemplate = valueAt(site, "toolboxes.creationTemplate");
assert.equal(typeof toolboxCreationTemplate, "object", "toolboxes.creationTemplate must be an object");
assertNonEmptyString({ toolboxCreationTemplate }, "toolboxCreationTemplate.memos.title");
assertNonEmptyString({ toolboxCreationTemplate }, "toolboxCreationTemplate.memos.text");
assertNonEmptyString({ toolboxCreationTemplate }, "toolboxCreationTemplate.links.moduleTitle");
assertNonEmptyString({ toolboxCreationTemplate }, "toolboxCreationTemplate.links.title");
const toolboxTutorial = valueAt(site, "toolboxes.tutorial");
assert.equal(typeof toolboxTutorial, "object", "toolboxes.tutorial must be an object");
assertNonEmptyString({ toolboxTutorial }, "toolboxTutorial.eyebrow");
assertNonEmptyString({ toolboxTutorial }, "toolboxTutorial.title");
assertNonEmptyString({ toolboxTutorial }, "toolboxTutorial.text");
assertNonEmptyString({ toolboxTutorial }, "toolboxTutorial.dismissLabel");
assertNonEmptyString({ toolboxTutorial }, "toolboxTutorial.nextLabel");
assertNonEmptyString({ toolboxTutorial }, "toolboxTutorial.doneLabel");
assert.ok(Array.isArray(toolboxTutorial.items), "toolboxes.tutorial.items must be an array");
assert.ok(toolboxTutorial.items.length >= 4, "toolboxes.tutorial.items must contain at least 4 items");
toolboxTutorial.items.forEach((item, index) => {
assertNonEmptyString({ item }, "item.target");
assertNonEmptyString({ item }, "item.icon");
assertNonEmptyString({ item }, "item.title");
assertNonEmptyString({ item }, "item.text");
if (item.placement) {
assert.ok(["above", "below", "right"].includes(item.placement), "item.placement must be above, below or right");
}
assert.ok(index < 6, "toolboxes.tutorial.items should stay compact");
});
const lines = valueAt(site, "home.origin.lines");
assert.ok(Array.isArray(lines), "home.origin.lines must be an array");
assert.ok(lines.length >= 2, "home.origin.lines must contain at least 2 items");
lines.forEach((line, index) => {
assert.ok(["user", "app"].includes(line?.speaker), `home.origin.lines[${index}].speaker must be "user" or "app"`);
assert.equal(typeof line?.text, "string", `home.origin.lines[${index}].text must be a string`);
assert.ok(line.text.trim(), `home.origin.lines[${index}].text must not be empty`);
});
}
export function validateLibraryData(payload) {
assert.equal(typeof payload?.toolbox, "object", "library.toolbox must be an object");
assert.equal(typeof payload?.modules, "object", "library.modules must be an object");
assertNonEmptyString(payload, "toolbox.name");
assert.ok(Array.isArray(payload.toolbox.modules), "library.toolbox.modules must be an array");
assert.ok(payload.toolbox.modules.length >= 1, "library.toolbox.modules must not be empty");
const moduleIds = new Set();
payload.toolbox.modules.forEach((module, index) => {
assertNonEmptyString({ module }, "module.id");
assertNonEmptyString({ module }, "module.type");
assert.ok(!moduleIds.has(module.id), `library.toolbox.modules[${index}].id must be unique`);
moduleIds.add(module.id);
assert.equal(typeof payload.modules[module.id], "object", `library.modules.${module.id} must be an object`);
});
assert.ok(Array.isArray(payload.toolbox.spaces), "library.toolbox.spaces must be an array");
assert.equal(payload.toolbox.spaces.length, 1, "library.toolbox must declare one default space");
const defaultSpace = payload.toolbox.spaces[0];
assert.equal(payload.toolbox.defaultSpaceId, defaultSpace.id, "library.toolbox.defaultSpaceId must target the default space");
assert.equal(payload.toolbox.activeSpaceId, defaultSpace.id, "library.toolbox.activeSpaceId must target the default space");
assert.deepEqual(defaultSpace.moduleOrder?.one, [...moduleIds], "library default space order must match toolbox modules");
assert.ok(Array.isArray(defaultSpace.moduleOrder?.two), "library default space two-column order must be an array");
assert.equal(defaultSpace.moduleOrder.two.length, 2, "library default space must contain 2 columns");
defaultSpace.moduleOrder.two.forEach((column, index) => {
assert.ok(Array.isArray(column), `library default space column ${index} must be an array`);
});
const twoColumnIds = defaultSpace.moduleOrder.two.flat();
assert.deepEqual(new Set(twoColumnIds), moduleIds, "library default space must contain every module exactly once");
assert.equal(twoColumnIds.length, moduleIds.size, "library default space must not contain duplicate modules");
["memos", "notepad", "checklist", "images", "links", "counters", "combos", "calculator", "table", "goalTree", "timer", "calendar", "taskPlanner", "equipmentPlanner", "imageAnnotation"].forEach((type) => {
assert.ok(payload.toolbox.modules.some((module) => module.type === type), `library must include a ${type} example`);
});
const imagesModule = payload.toolbox.modules.find((module) => module.type === "images");
const imagesData = payload.modules[imagesModule.id];
assert.ok(Array.isArray(imagesData.images), "library images example must contain images");
assert.ok(imagesData.images.length >= 1, "library images example must not be empty");
imagesData.images.forEach((image, index) => {
assertNonEmptyString({ image }, "image.id");
assertNonEmptyString({ image }, "image.dataUrl");
assert.match(image.dataUrl, /^data:image\//, `library images[${index}].dataUrl must be an inline image`);
});
const taskModule = payload.toolbox.modules.find((module) => module.type === "taskPlanner");
const taskData = payload.modules[taskModule.id];
assert.ok(Array.isArray(taskData.tasks), "library task planner example must contain tasks");
assert.ok(Array.isArray(taskData.relations), "library task planner example must contain relations");
assert.ok(taskData.relations.length >= 1, "library task planner example must include a parent/child relation");
const goalTreeModule = payload.toolbox.modules.find((module) => module.type === "goalTree");
const goalTreeData = payload.modules[goalTreeModule.id];
assert.ok(Array.isArray(goalTreeData.nodes), "library goal tree example must contain nodes");
assert.ok(goalTreeData.nodes.some((node) => node.parentId), "library goal tree example must include a parent/child node");
const equipmentModule = payload.toolbox.modules.find((module) => module.type === "equipmentPlanner");
const equipmentData = payload.modules[equipmentModule.id];
assert.ok(Array.isArray(equipmentData.types), "library equipment planner example must contain types");
assert.ok(Array.isArray(equipmentData.equipments), "library equipment planner example must contain equipments");
assert.ok(equipmentData.equipments.some((equipment) => Array.isArray(equipment.socketItems) && equipment.socketItems.length), "library equipment planner example must include socket items");
}
function assertSnakeCase(value, path) {
assert.equal(typeof value, "string", `${path} must be a string`);
assert.match(value, SNAKE_CASE_RE, `${path} must be snake_case`);
}
function validateConditionValues(items, path) {
assert.ok(Array.isArray(items), `${path} must be an array`);
items.forEach((item, index) => {
assertSnakeCase(item?.condition, `${path}[${index}].condition`);
assert.ok(Array.isArray(item?.values), `${path}[${index}].values must be an array`);
item.values.forEach((value, valueIndex) => assertSnakeCase(value, `${path}[${index}].values[${valueIndex}]`));
});
}
export function validateMonsterData(monsters) {
assert.ok(Array.isArray(monsters), "monsters must be an array");
const ids = new Set();
const names = new Set();
monsters.forEach((monster, monsterIndex) => {
const path = `monsters[${monsterIndex}]`;
assert.equal(typeof monster.id, "number", `${path}.id must be a number`);
assert.ok(Number.isInteger(monster.id), `${path}.id must be an integer`);
assert.ok(!ids.has(monster.id), `${path}.id must be unique`);
ids.add(monster.id);
assertSnakeCase(monster.name, `${path}.name`);
assert.ok(!names.has(monster.name), `${path}.name must be unique`);
names.add(monster.name);
assertSnakeCase(monster.type, `${path}.type`);
validateConditionValues(monster.ailments, `${path}.ailments`);
validateConditionValues(monster.weaknesses, `${path}.weaknesses`);
validateConditionValues(monster.weakpoints, `${path}.weakpoints`);
assert.equal(monster.damage, undefined, `${path}.damage must not be used`);
assert.ok(Array.isArray(monster.hitzones), `${path}.hitzones must be an array`);
assert.ok(monster.hitzones.length > 0, `${path}.hitzones must not be empty`);
monster.hitzones.forEach((hitzone, hitzoneIndex) => {
const hitzonePath = `${path}.hitzones[${hitzoneIndex}]`;
assertSnakeCase(hitzone.name, `${hitzonePath}.name`);
assert.equal(typeof hitzone.physical, "object", `${hitzonePath}.physical must be an object`);
assert.equal(typeof hitzone.elemental, "object", `${hitzonePath}.elemental must be an object`);
PHYSICAL_HITZONE_KEYS.forEach((key) => assert.equal(typeof hitzone.physical[key], "number", `${hitzonePath}.physical.${key} must be a number`));
ELEMENTAL_HITZONE_KEYS.forEach((key) => assert.equal(typeof hitzone.elemental[key], "number", `${hitzonePath}.elemental.${key} must be a number`));
});
});
}
export function validateEndemicData(endemicPayload) {
["endemicLife", "aquaticLife"].forEach((collection) => {
assert.ok(Array.isArray(endemicPayload[collection]), `${collection} must be an array`);
endemicPayload[collection].forEach((item, itemIndex) => {
const path = `${collection}[${itemIndex}]`;
assertSnakeCase(item.name, `${path}.name`);
if (item.description) assertSnakeCase(item.description, `${path}.description`);
validateConditionValues(item.locations, `${path}.locations`);
});
});
}
export function validateTranslationKeys(translations, path) {
Object.keys(translations).forEach((key) => {
assert.ok(!/\s/.test(key), `${path}.${key} must not contain spaces`);
});
}
export function validateDiablo4Affixes(payload) {
assert.ok(Array.isArray(payload.categories), "diablo4.categories must be an array");
assert.ok(payload.categories.length > 0, "diablo4.categories must not be empty");
assert.ok(Array.isArray(payload.affixes), "diablo4.affixes must be an array");
assert.ok(payload.affixes.length > 0, "diablo4.affixes must not be empty");
const ids = new Set();
const categoryIds = payload.categories.map((category, index) => {
assertSnakeCase(category.id, `diablo4.categories[${index}].id`);
assertNonEmptyString({ category }, "category.label");
assertSnakeCase(category.icon, `diablo4.categories[${index}].icon`);
assertSnakeCase(category.tone, `diablo4.categories[${index}].tone`);
return category.id;
});
payload.affixes.forEach((affix, index) => {
assertSnakeCase(affix.id, `diablo4.affixes[${index}].id`);
assert.ok(!ids.has(affix.id), `diablo4.affixes[${index}].id must be unique`);
ids.add(affix.id);
assertNonEmptyString({ affix }, "affix.label");
assert.ok(Array.isArray(affix.categories), `diablo4.affixes[${index}].categories must be an array`);
affix.categories.forEach((category, categoryIndex) => {
assertSnakeCase(category, `diablo4.affixes[${index}].categories[${categoryIndex}]`);
assert.ok(categoryIds.includes(category), `diablo4.affixes[${index}].categories[${categoryIndex}] must exist in diablo4.categories`);
});
});
}