diff --git a/module.json b/module.json index 929f375..76da68c 100644 --- a/module.json +++ b/module.json @@ -8,7 +8,7 @@ } ], "url": "https://gitea.palmenalzer.de/alzer/wfrp-4e-de", - "version": "8.6.13", + "version": "8.6.14", "esmodules": [ "modules/babele-register.js", "modules/addon-register.js", diff --git a/modules/babele-register.js b/modules/babele-register.js index 0252622..291caf5 100644 --- a/modules/babele-register.js +++ b/modules/babele-register.js @@ -3,7 +3,7 @@ import loadScripts from "./loadScripts.js"; import statParserFR from "./import-stat-2.js"; /************************************************************************************/ -var compmod = "wfrp4e-core"; +let compmod = "wfrp4e-core"; const vo_conditions = { "ablaze": "Ablaze", "bleeding": "Bleeding", @@ -22,10 +22,18 @@ const vo_conditions = { "defeated": "Defeated" } -const __SELECT_BONUS_PREFIX_D = { - "widerstand": 1, - "gewandheit": 1, - "geschicklichkeit": 1 +// Map English characteristic names (as they appear in spell formulas) to WFRP4e abbreviations +const __CHAR_EN_TO_ABBR = { + "weapon skill": "ws", + "ballistic skill": "bs", + "strength": "s", + "toughness": "t", + "initiative": "i", + "agility": "ag", + "dexterity": "dex", + "intelligence": "int", + "willpower": "wp", + "fellowship": "fel" } /************************************************************************************/ @@ -50,32 +58,34 @@ export class WFRP4FrTranslation { } static processSpellContent(value) { - //console.log("Spell duration/range/damage/target :", value); if (value == "") return ""; // Hop ! if (value == "Touch") return "Berührung"; // Hop ! - if (value == "You") return "Du"; // Hop ! + if (value == "You") return "Du"; // Hop ! if (value == "Instant") return "Sofort"; // Hop ! let translw = value; - let re = /(.*)\s+[Bb]onus\s*(\w*)/i; + let re = /(.*)\s+[Bb]onus\s*(.*)/i; // (.*) at end captures modifiers like "+4" let res = re.exec(value); - //console.log("RES1:", res); let unit = ""; - if (res) { // Test " Bonus " pattern - if (res[1]) { // We have char name, then convert it - translw = game.i18n.localize(res[1].trim()); - // let bonusPrefix = (translw.toLowerCase() in __SELECT_BONUS_PREFIX_D) ? "Bonus d'" : "Bonus de "; - // translw = bonusPrefix + translw - let bonusSufix = (translw.toLowerCase() in __SELECT_BONUS_PREFIX_D) ? "sbonus" : "bonus"; - translw = translw + "bonusSufix" + if (res) { // Test " Bonus " pattern + if (res[1]) { // We have char name, then convert it + const charEN = res[1].trim().toLowerCase(); + const abbr = __CHAR_EN_TO_ABBR[charEN]; + if (abbr && game.wfrp4e?.config?.characteristicsBonus?.[abbr]) { + // Use the localized German bonus name from config (already resolved by localizeConfig at i18nInit) + translw = game.wfrp4e.config.characteristicsBonus[abbr]; + } else { + translw = game.i18n.localize(res[1].trim()); + translw = translw + "bonus"; + } } - unit = res[2]; + unit = res[2]; // may be "+4", "-2", "2", "yards", etc. } else { re = /(\d+) (\w+)/i; res = re.exec(value); if (res) { // Test : " " pattern translw = res[1]; unit = res[2]; - } else { // Test + } else { // Test re = /(\w+) (\w+)/i; res = re.exec(value); if (res) { // Test : " " pattern @@ -92,6 +102,8 @@ export class WFRP4FrTranslation { if (unit == "Bonus") { // Another weird management console.log("Translating bonus", unit); translw = translw + " Bonus"; + } else if (unit && /^[+\-*\/]/.test(unit)) { + translw += unit; // No space before operators like "+4" } else { translw += " " + unit; } @@ -102,7 +114,7 @@ export class WFRP4FrTranslation { /************************************************************************************/ Hooks.once('init', () => { - // Check various settings in the installation + // Check various settings in the installation game.modules.forEach((module, id) => { if (id == "wfrp4e-core" && module.active) { compmod = "wfrp4e-core"; @@ -120,102 +132,6 @@ Hooks.once('init', () => { console.log("WFRP4E-DE | Loading Babele translation module ..."); loadScripts(); - /*---------------------------------------------------------------------*/ - /* DEPRECATED : game.wfrp4e.entities.ItemWfrp4e.prototype.computeSpellDamage = function (formula, isMagicMissile) { - try { - - formula = formula.toLowerCase(); - - if (isMagicMissile) // If it's a magic missile, damage includes willpower bonus - { - formula += "+ " + this.actor.characteristics["wp"].bonus - } - - // Specific case, to avoid wrong matching with "Force" - if (formula.includes("toughness bonus")) { - formula = formula.replace("toughness bonus", this.actor.characteristics["t"].bonus); - } - - // Specific case, to avoid wrong matching with "Force" - if (formula.includes("force mentale")) { - // Determine if it's looking for the bonus or the value - if (formula.includes('bonus')) { - formula = formula.replace("bonus de force mentale", this.actor.characteristics["wp"].bonus); - formula = formula.replace("force mentale bonus", this.actor.characteristics["wp"].bonus); - } else - formula = formula.replace("force mentale", this.actor.characteristics["wp"].value); - } - - // Iterate through characteristics - for (let ch in this.actor.characteristics) { - // If formula includes characteristic name - while (formula.includes(this.actor.characteristics[ch].label.toLowerCase())) { - // Determine if it's looking for the bonus or the value - if (formula.includes('bonus')) { - formula = formula.replace("bonus de " + game.wfrp4e.config.characteristics[ch].toLowerCase(), this.actor.characteristics[ch].bonus); - formula = formula.replace(game.wfrp4e.config.characteristics[ch].toLowerCase() + " bonus", this.actor.characteristics[ch].bonus); - } - else - formula = formula.replace(game.wfrp4e.config.characteristics[ch].toLowerCase(), this.actor.characteristics[ch].value); - } - } - - return eval(formula); - } - catch (e) { - throw ui.notifications.error("Error: could not parse spell damage. See console for details") - } - }*/ - - /*---------------------------------------------------------------------*/ - /* DEPRECATED : game.wfrp4e.entities.ItemWfrp4e.prototype.computeSpellPrayerFormula = function (type, aoe = false, formulaOverride) { - let formula = formulaOverride || this[type]?.value - if (Number.isNumeric(formula)) - return formula - - //console.log("Compute FR") - formula = formula.toLowerCase(); - - // Do not process these special values - if (formula != game.i18n.localize("You").toLowerCase() && formula != game.i18n.localize("Special").toLowerCase() && formula != game.i18n.localize("Instant").toLowerCase()) { - // Specific case, to avoid wrong matching with "Force" - if (formula.includes("force mentale")) { - // Determine if it's looking for the bonus or the value - if (formula.includes('bonus')) { - formula = formula.replace("bonus de force mentale", this.actor.characteristics["wp"].bonus); - formula = formula.replace("force mentale bonus", this.actor.characteristics["wp"].bonus); - } - else - formula = formula.replace("force mentale", this.actor.characteristics["wp"].value); - } - if (formula.includes("yard")) - formula = formula.replace('yard', "mètre"); - if (formula.includes("yds")) - formula = formula.replace('yds', "m."); - // Iterate through remaining characteristics - for (let ch in this.actor.characteristics) { - // If formula includes characteristic name - //console.log("Testing :", ch, WFRP4E.characteristics[ch].toLowerCase()); - if (formula.includes(game.wfrp4e.config.characteristics[ch].toLowerCase())) { - // Determine if it's looking for the bonus or the value - if (formula.includes('bonus')) { - formula = formula.replace("bonus de " + game.wfrp4e.config.characteristics[ch].toLowerCase(), this.actor.characteristics[ch].bonus); - formula = formula.replace(game.wfrp4e.config.characteristics[ch].toLowerCase() + " bonus", this.actor.characteristics[ch].bonus); - } - else - formula = formula.replace(game.wfrp4e.config.characteristics[ch].toLowerCase(), this.actor.characteristics[ch].value); - } - } - } - - // If AoE - wrap with AoE ( ) - if (aoe) - formula = "AoE (" + formula.capitalize() + ")"; - - //console.log("calculateSpellAttributes -> " + formula ); - return formula.capitalize(); - }*/ - /*---------------------------------------------------------------------*/ // Converters area if (game.babele !== 'undefined') { @@ -224,8 +140,7 @@ Hooks.once('init', () => { module: 'wh4-de-translation', lang: 'de', dir: 'compendium' - }, - ) + }) // Pre-define functions for cross-converter calls (Babele 2.8+ wraps converters // as objects, so game.babele.converters.X() no longer works as a plain call) @@ -242,27 +157,25 @@ Hooks.once('init', () => { let translItem = game.babele.translate(compData.metadata.id, { name: skills_list[i], type: "skill" }, true) let transl = translItem?.name || undefined if (!transl) transl = skills_list[i] - //console.log("List ...", skills_list[i], compData.metadata.id, translItem); if (transl == skills_list[i]) { let res = re.exec(skills_list[i]); if (res) { - //console.log("Matched/split:", res[1], res[2]); let subword = game.i18n.localize(res[2].trim()); let s1 = res[1].trim() + " ()"; translItem = game.babele.translate(compData.metadata.id, { name: s1, type: "skill" }, true) let translw = translItem?.name || undefined if (translw && translw != s1) { let res2 = re.exec(translw); - transl = res2[1] + "(" + subword + ")"; + transl = res2[1].trim() + " (" + subword + ")"; } else { s1 = res[1].trim() + " ( )"; translItem = game.babele.translate(compData.metadata.id, { name: s1, type: "skill" }, true) translw = translItem?.name || undefined if (translw) { let res2 = re.exec(translw); - transl = res2[1] + "(" + subword + ")"; + transl = res2[1].trim() + " (" + subword + ")"; } else { - transl = res[1] + " (" + subword + ")"; + transl = res[1].trim() + " (" + subword + ")"; } } } @@ -277,23 +190,16 @@ Hooks.once('init', () => { }; const career_careergroup_fn = (value) => { - // Manage exception - Dirty hack - if (value == 'Slayer') { - return "Slayer"; - } - if (value == 'Druidic Priest') { - return "Druide"; - } - //console.log("Carre groupe : ", value ) - // Per default - let validCompendiums = game.wfrp4e.tags.getPacksWithTag("career") + if (value == 'Slayer') return "Slayer"; + if (value == 'Druidic Priest') return "Druide"; + let validCompendiums = game.wfrp4e.tags.getPacksWithTag("career"); for (let compData of validCompendiums) { - let newName = game.babele.translate(compData.metadata.id, { name: value }).name - if (!newName) newName = value - return newName + let newName = game.babele.translate(compData.metadata.id, { name: value }).name; + if (!newName) newName = value; + return newName; } - ui.notifications.error("Das Übersetzen der Karriere " + value + " ist nicht möglich. Sie ist wahrscheinlich nicht übersetzt.", { permanent: true }) - return value + ui.notifications.error("Das Übersetzen der Karriere " + value + " ist nicht möglich. Sie ist wahrscheinlich nicht übersetzt.", { permanent: true }); + return value; }; game.babele.registerConverters({ @@ -310,7 +216,22 @@ Hooks.once('init', () => { // Foundry v13 requires name; migrate legacy data where only label was stored if (e.name == null) e.name = e.label || ""; let origName = e.name - e.name = tc_translations.name || game.i18n.localize(e.name) || e.label || "" + // Symptom effects have their own name (Fever, Malaise, etc.) — don't overwrite with the parent item name + if (e.flags?.wfrp4e?.symptom) { + let symName = e.name; + let gravity = ""; + if (symName.includes("(") && symName.includes(")")) { + let re = /(.*) +\((.*)\)/i; + let res = re.exec(symName); + if (res) { + symName = res[1].trim(); + gravity = " (" + game.i18n.localize(res[2].trim()) + ")"; + } + } + e.name = game.i18n.localize(symName) + gravity; + } else { + e.name = tc_translations.name || game.i18n.localize(e.name) || e.label || "" + } if (e.flags?.wfrp4e?.scriptData) { for (let script of e.flags.wfrp4e.scriptData) { if (script?.label) { @@ -342,17 +263,13 @@ Hooks.once('init', () => { } // Auto patch if (results[0].description.includes("wfrp4e-core.career-descriptions")) { - if (game.system.version.match("7.")) { - results[0].description = "wfrp4e-core.journals" - } else { - results[0].description = "wfrp4e-core.journal-entries" - } + results[0].description = "wfrp4e-core.journals" } if (results[0].description.includes("wfrp4e-core.journal")) { for (let data of results) { let career = data.description.match(/{(.*)}/) //console.log(">>>>>", career) - if (career && career[1]) { + if (career?.[1]) { let careerFR = career_careergroup_fn(career[1]) data.description = data.description.replace(career[1], careerFR) } @@ -390,6 +307,9 @@ Hooks.once('init', () => { if (talents_list[i] === "Trapper") { translItem = game.babele.translate(compData.metadata.id, { name: "a7v422EZcOUUC20X" }, true); } + if (talents_list[i] === "Inspiring") { + translItem = game.babele.translate(compData.metadata.id, { name: "WCXnFSV4WOSmzzc4" }, true); + } //console.log("Search talent name:", compData.metadata.id, talents_list[i], translItem); let transl = translItem?.name || undefined if (!transl) transl = talents_list[i] @@ -402,7 +322,7 @@ Hooks.once('init', () => { let translw = translItem?.name || undefined //console.log("Search talent name:", compData.metadata.id, s1, translw); if (translw && translw != s1) { - transl = translw + " (" + subword + ")"; + transl = translw.trim() + " (" + subword + ")"; } } } @@ -417,7 +337,7 @@ Hooks.once('init', () => { "npc_characteristics": (chars) => { // Auto-convert char names in the sheet for (let key in chars) { let char = chars[key]; - //console.log("Was here !", key, char ); + //console.log("Was here !", key, char ); let abrev = char["abrev"]; let toTransl = "CHAR." + abrev; if (game.i18n.localize(toTransl) != toTransl) { // Manages unknown language @@ -444,7 +364,7 @@ Hooks.once('init', () => { for (let trait_en of beast_traits) { let special = ""; let nbt = ""; - let name_en = trait_en.name.trim(); // strip \r in some traits name + let name_en = trait_en.name.replace(/\r/g, '').trim(); // strip \r (including internal) in some traits name if (!trait_en.name || trait_en.name.length == 0) { console.log("Wrong item name found!!!!") continue @@ -455,24 +375,29 @@ Hooks.once('init', () => { if (name_en.includes("Tentacles")) { // Process specific Tentacles case let re = /(.d*)x Tentacles/i; let res = re.exec(name_en); - if (res && res[1]) + if (res?.[1]) nbt = res[1] + "x "; name_en = "Tentacles"; } else if (name_en.includes("(") && name_en.includes(")")) { // Then process specific traits name with (xxxx) inside let re = /(.*) \((.*)\)/i; let res = re.exec(name_en); - name_en = res[1]; // Get the root traits name - special = " (" + game.i18n.localize(res[2].trim()) + ")"; // And the special keyword + if (!res) { console.warn("WFRP4E-DE | bestiary_traits: regex failed for trait:", name_en); } + else { + name_en = res[1]; // Get the root traits name + special = " (" + game.i18n.localize(res[2].trim()) + ")"; // And the special keyword + } } let validCompendiums = game.wfrp4e.tags.getPacksWithTag("trait") for (let compData of validCompendiums) { - let trait_fr = game.babele.translate(compData.metadata.id, { name: name_en }, true) - if (trait_fr?.system) { + let trait_fr = game.babele.translate(compData.metadata.id, { name: name_en, system: { description: { value: trait_en.system.description.value } } }, true) + if (trait_fr?.name && trait_fr?.name != name_en) { trait_fr.name = trait_fr.name || trait_en.name trait_en.name = nbt + trait_fr.name + special; - trait_en.system.description.value = trait_fr.system.description.value; - if (trait_en.system?.specification && isNaN(trait_en.system.specification.value)) { // This is a string, so translate it - //console.log("Translating : ", trait_en.system.specification.value); + if (trait_en.system?.description?.value && trait_fr.system?.description?.value) { + trait_en.system.description.value = trait_fr.system.description.value; + } + if (trait_en?.system?.specification && isNaN(trait_en.system.specification?.value)) { // This is a string, so translate it + //console.log("Translating : ", trait_en.system.specification.value); trait_en.system.specification.value = game.i18n.localize(trait_en.system.specification.value.trim()); } break // Translation has been found, skip other compendiums @@ -482,14 +407,17 @@ Hooks.once('init', () => { if (name_en.includes("(") && name_en.includes(")")) { // Then process specific skills name with (xxxx) inside let re = /(.*) +\((.*)\)/i; let res = re.exec(name_en); - name_en = res[1].trim(); // Get the root skill name - special = " (" + game.i18n.localize(res[2].trim()) + ")"; // And the special keyword + if (!res) { console.warn("WFRP4E-DE | bestiary_traits: regex failed for skill:", name_en); } + else { + name_en = res[1].trim(); // Get the root skill name + special = " (" + game.i18n.localize(res[2].trim()) + ")"; // And the special keyword + } } let validCompendiums = game.wfrp4e.tags.getPacksWithTag("skill") for (let compData of validCompendiums) { - let trait_fr = game.babele.translate(compData.metadata.id, { name: name_en }, true) - if (trait_fr?.system) { - //console.log(">>>>> Skill ?", name_en, special, trait_fr.name, trait_fr); + let trait_fr = game.babele.translate(compData.metadata.id, { name: name_en, system: { description: { value: trait_en.system.description.value } } }, true) + //console.log(">>>>> Skill ?", name_en, special, trait_fr.name, trait_fr); + if (trait_fr?.name && trait_fr?.name != name_en) { trait_fr.name = trait_fr.name || name_en trait_en.name = trait_fr.name + special; trait_en.system.description.value = trait_fr.system.description.value; @@ -499,8 +427,8 @@ Hooks.once('init', () => { } else if (trait_en.type == "prayer") { let validCompendiums = game.wfrp4e.tags.getPacksWithTag("prayer") for (let compData of validCompendiums) { - let trait_fr = game.babele.translate(compData.metadata.id, { name: name_en }, true) - if (trait_fr?.system) { + let trait_fr = game.babele.translate(compData.metadata.id, { name: name_en, system: { description: { value: trait_en.system.description.value } } }, true) + if (trait_fr?.name && trait_fr?.name != name_en) { //DEBUG : console.log(">>>>> Prayer ?", name_en, special, trait_fr.name ); WFRP4FrTranslation.parseSpellContent(trait_en) trait_fr.name = trait_fr.name || name_en @@ -514,8 +442,8 @@ Hooks.once('init', () => { } else if (trait_en.type == "spell") { let validCompendiums = game.wfrp4e.tags.getPacksWithTag("spell") for (let compData of validCompendiums) { - let trait_fr = game.babele.translate(compData.metadata.id, { name: name_en }, true) - if (trait_fr?.system) { + let trait_fr = game.babele.translate(compData.metadata.id, { name: name_en, system: { description: { value: trait_en.system.description.value } } }, true) + if (trait_fr?.name && trait_fr?.name != name_en) { //DEBUG : console.log(">>>>> Spell ?", name_en, special, trait_fr); WFRP4FrTranslation.parseSpellContent(trait_en) trait_fr.name = trait_fr.name || name_en @@ -530,17 +458,30 @@ Hooks.once('init', () => { if (name_en.includes("(") && name_en.includes(")")) { // Then process specific skills name with (xxxx) inside let re = /(.*) +\((.*)\)/i; let res = re.exec(name_en); - name_en = res[1].trim(); // Get the root talent name, no parenthesis this time... - special = " (" + game.i18n.localize(res[2].trim()) + ")"; // And the special keyword + if (!res) { console.warn("WFRP4E-DE | bestiary_traits: regex failed for talent:", name_en); } + else { + name_en = res[1].trim(); // Get the root talent name, no parenthesis this time... + special = " (" + game.i18n.localize(res[2].trim()) + ")"; // And the special keyword + } } let validCompendiums = game.wfrp4e.tags.getPacksWithTag("talent") for (let compData of validCompendiums) { - let trait_fr = game.babele.translate(compData.metadata.id, { name: name_en }, true) - if (trait_fr?.system) { + if (name_en === "Trapper") { + name_en = "a7v422EZcOUUC20X" + } + if (name_en === "Inspiring") { + name_en = "WCXnFSV4WOSmzzc4" + } + trait_en.name = name_en // Reset the name to the original one + let trait_fr = game.babele.translate(compData.metadata.id, trait_en, true) + if (trait_fr?.name && trait_fr?.name != name_en) { trait_fr.name = trait_fr.name || name_en // Security since babele v10 //console.log(">>>>> Talent ?", trait_fr, name_en, special, trait_fr.name); if (trait_fr.name && (trait_fr.name == "Sprinter" || trait_fr.name != name_en)) { // Talent translated! trait_en.name = trait_fr.name.trim() + special + if (trait_fr.system?.tests?.value) { // Why ??? + trait_en.system.tests.value = trait_fr.system.tests.value; + } if (trait_fr.system?.description?.value) { // Why ??? trait_en.system.description.value = trait_fr.system.description.value; } @@ -551,8 +492,8 @@ Hooks.once('init', () => { } else if (trait_en.type == "career") { let validCompendiums = game.wfrp4e.tags.getPacksWithTag("career") for (let compData of validCompendiums) { - let career_fr = game.babele.translate(compData.metadata.id, { name: name_en }, true); - if (career_fr?.system) { + let career_fr = game.babele.translate(compData.metadata.id, trait_en, true); + if (career_fr?.name && career_fr?.name != name_en) { trait_en.name = career_fr.name || trait_en.name // DEBG: console.log(">>>>> Career ?", career_fr.name ); trait_en.system = foundry.utils.duplicate(career_fr.system); @@ -563,7 +504,7 @@ Hooks.once('init', () => { let validCompendiums = game.wfrp4e.tags.getPacksWithTag("vehicleRole") for (let compData of validCompendiums) { let role_fr = game.babele.translate(compData.metadata.id, trait_en, true); - if (role_fr?.system) { + if (role_fr?.name && role_fr?.name != name_en) { trait_en.name = role_fr.name || trait_en.name // DEBG: console.log(">>>>> Role ?", role_fr.name ); trait_en.system = foundry.utils.duplicate(role_fr.system); @@ -573,8 +514,8 @@ Hooks.once('init', () => { } else if (trait_en.type == "trapping" || trait_en.type == "weapon" || trait_en.type == "armour" || trait_en.type == "container" || trait_en.type == "money") { let validCompendiums = game.wfrp4e.tags.getPacksWithTag(["trapping"], ["weapon", "armour", "container", "money"]) for (let compData of validCompendiums) { - let trapping_fr = game.babele.translate(compData.metadata.id, { name: name_en }, true); - if (trapping_fr?.system) { + let trapping_fr = game.babele.translate(compData.metadata.id, { name: name_en, system: { description: { value: trait_en.system.description.value } } }, true); + if (trapping_fr?.name && trapping_fr?.name != name_en) { //console.log(">>>>> Trapping ?", name_en, trapping_fr.name); trait_en.name = trapping_fr.name || trait_en.name if (trapping_fr.system?.description?.value) { @@ -630,7 +571,7 @@ Hooks.once('init', () => { if (!value) return; value = value.toLowerCase(); value = value.replace("gain a broken condition if you fail a test derived from ", "Du erhälst einen Demoralisiert-Zustand wenn dir ein Wurf misslingt in "); - value = value.replace("weapon skill" ,"Kampfgeschick"); + value = value.replace("weapon skill", "Kampfgeschick"); value = value.replace("ballistic skill", "Ballisitsche Fertigkeit"); value = value.replace("strength", "Stärke"); value = value.replace("toughness", "Widerstand"); @@ -706,4 +647,4 @@ Hooks.once('ready', () => { console.log("No stats available, giving up.") ) -}); \ No newline at end of file +});