daggerheart/module/documents/item.mjs
codebytaki 477e2b78b6 Fix: Internationalize hardcoded UI strings (Issue #1787)
- Replace hardcoded 'Inventory Items', 'Character Items', 'Other' with i18n keys
- Internationalize 'New Effect' default name in active effects
- Fix hardcoded 'Roll Formula' and 'Formula Roll' labels in roll tables
- Add translation keys to lang/en.json for proper localization support
- Ensures all UI strings can be translated to other languages

Fixes #1787: I18n: Some UI strings still seem to be hardcoded
2026-04-14 17:39:55 +06:00

242 lines
8.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import ActionSelectionDialog from '../applications/dialogs/actionSelectionDialog.mjs';
/**
* Override and extend the basic Item implementation.
* @extends {foundry.documents.Item}
*/
export default class DHItem extends foundry.documents.Item {
/** @inheritDoc */
prepareEmbeddedDocuments() {
super.prepareEmbeddedDocuments();
for (const action of this.system.actions ?? []) action.prepareData();
}
/** @inheritDoc */
getEmbeddedDocument(embeddedName, id, options) {
let doc;
switch (embeddedName) {
case 'Action':
doc = this.system.actions?.get(id);
if (!doc && this.system.attack?.id === id) doc = this.system.attack;
break;
default:
return super.getEmbeddedDocument(embeddedName, id, options);
}
if (options?.strict && !doc) {
throw new Error(`The key ${id} does not exist in the ${embeddedName} Collection`);
}
return doc;
}
static async createDocuments(sources, operation) {
// Ensure that items being created are valid to the actor its being added to
const actor = operation.parent;
sources = actor?.system?.isItemValid ? sources.filter(s => actor.system.isItemValid(s)) : sources;
return super.createDocuments(sources, operation);
}
/* -------------------------------------------- */
/** @inheritDoc */
static migrateData(source) {
if (source.system?.attack && !source.system.attack.type) source.system.attack.type = 'attack';
return super.migrateData(source);
}
/**
* @inheritdoc
* @param {object} options - Options which modify the getRollData method.
* @returns
*/
getRollData(options = {}) {
let data;
if (this.system.getRollData) data = this.system.getRollData(options);
else {
const actorRollData = this.actor?.getRollData(options) ?? {};
data = { ...actorRollData, item: { ...this.system } };
}
if (data?.item) {
data.item.flags = { ...this.flags };
data.item.name = this.name;
}
return data;
}
/**
* Determine if this item is classified as an inventory item based on its metadata.
* @returns {boolean} Returns `true` if the item is an inventory item.
*/
get isInventoryItem() {
return this.system.metadata.isInventoryItem ?? false;
}
/** @inheritdoc */
static async createDialog(data = {}, createOptions = {}, options = {}) {
const { folders, types, template, context = {}, ...dialogOptions } = options;
if (types?.length === 0) {
throw new Error('The array of sub-types to restrict to must not be empty.');
}
const documentTypes = this.TYPES.filter(type => type !== 'base' && (!types || types.includes(type))).map(
type => {
const labelKey = CONFIG.Item?.typeLabels?.[type];
const label = labelKey && game.i18n.has(labelKey) ? game.i18n.localize(labelKey) : type;
const isInventoryItem = CONFIG.Item.dataModels[type]?.metadata?.isInventoryItem;
const group =
isInventoryItem === true
? game.i18n.localize('DAGGERHEART.ITEM_GROUPS.INVENTORY')
: isInventoryItem === false
? game.i18n.localize('DAGGERHEART.ITEM_GROUPS.CHARACTER')
: game.i18n.localize('DAGGERHEART.ITEM_GROUPS.OTHER');
return { value: type, label, group };
}
);
if (!documentTypes.length) {
throw new Error('No document types were permitted to be created.'); //TODO localize
}
const sortedTypes = documentTypes.sort((a, b) => a.label.localeCompare(b.label, game.i18n.lang));
return await super.createDialog(data, createOptions, {
folders,
types,
template,
context: { types: sortedTypes, ...context },
...dialogOptions
});
}
/* -------------------------------------------- */
/**
* Generate an array of localized tag.
* @returns {string[]} An array of localized tag strings.
*/
_getTags() {
const tags = [];
if (this.system._getTags) tags.push(...this.system._getTags());
return tags;
}
/**
* Generate a localized label array for this item.
* @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects.
*/
_getLabels() {
const labels = [];
if (this.system._getLabels) labels.push(...this.system._getLabels());
return labels;
}
/* -------------------------------------------- */
/**@inheritdoc */
static getDefaultArtwork(itemData) {
const { type } = itemData;
const Model = CONFIG.Item.dataModels[type];
const img = Model.DEFAULT_ICON ?? this.DEFAULT_ICON;
return { img };
}
/* -------------------------------------------- */
async use(event) {
/* DomainCard check. Can be expanded or made neater */
if (this.system.isDomainTouchedSuppressed) {
return ui.notifications.warn(
game.i18n.format('DAGGERHEART.UI.Notifications.domainTouchRequirement', {
nr: this.domainTouched,
domain: game.i18n.localize(CONFIG.DH.DOMAIN.allDomains()[this.domain].label)
})
);
}
const actions = new Set(this.system.actionsList);
if (actions?.size) {
let action = actions.first();
if (actions.size > 1 && !event?.shiftKey) {
// Actions Choice Dialog
action = await ActionSelectionDialog.create(this, event);
}
if (action) return action.use(event);
}
}
/**
* Create a new ChatMessage to display this documents data
* @param {String} origin - uuid of a document. TODO: This needs to be reviewed.
*/
async toChat(origin) {
/**@type {foundry.documents.ChatMessage} */
const cls = getDocumentClass('ChatMessage');
const item = await foundry.utils.fromUuid(origin);
const systemData = {
origin: origin,
img: this.img,
item: {
name: this.name,
img: this.img,
tags: this._getTags()
},
actions: item.system.actionsList,
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.system.description, {
relativeTo: this.parent,
rollData: this.parent?.getRollData() ?? {}
})
};
const msg = {
type: 'abilityUse',
user: game.user.id,
actor: item.parent,
speaker: cls.getSpeaker(),
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/ability-use.hbs',
systemData
),
flags: {
daggerheart: {
cssClass: 'dh-chat-message dh-style'
}
}
};
cls.create(msg);
}
deleteTriggers() {
const actions = Array.from(this.system.actions ?? []);
if (!actions.length) return;
const triggerKeys = actions.flatMap(action => action.triggers.map(x => x.trigger));
game.system.registeredTriggers.unregisterTriggers(triggerKeys, this.uuid);
if (this.actor && !(this.actor.parent instanceof game.system.api.documents.DhToken)) {
for (const token of this.actor.getActiveTokens()) {
game.system.registeredTriggers.unregisterTriggers(triggerKeys, `${token.document.uuid}.${this.uuid}`);
}
}
}
async _preDelete() {
this.deleteTriggers();
}
/** @inheritDoc */
static migrateData(source) {
const documentClass = game.system.api.data.items[`DH${source.type?.capitalize()}`];
if (documentClass?.migrateDocumentData) {
documentClass.migrateDocumentData(source);
}
return super.migrateData(source);
}
}