/*: * @target MZ * @plugindesc LSDE i18n — Localization plugin for RPG Maker MZ by LepaSoft. * Loads translation and localized audio files generated by LSDE. * No changes to your events are required. * @author LepaSoft * @version 1.0.0 * @url https://lepasoft.com * * @param DefaultLocale * @text Default Locale * @type string * @default fr * @desc The locale of your data/ folder (your working language in RMMZ). * * @param AvailableLocales * @text Available Locales * @type string * @default fr,en * @desc Comma-separated locale codes (e.g. fr,en,es,de,ja). * * @param LocaleNames * @text Display Names * @type string * @default Francais,English * @desc Display names in the same order (e.g. Francais,English,Espanol). * * @param LocaleFolder * @text Translation Folder * @type string * @default i18n * @desc Subfolder inside data/ containing translation files (default: i18n). * * @param FallbackEmptyString * @text Empty String Fallback * @type boolean * @on Fallback to missing * @off Fallback to original * @default false * @desc What happens when a translation is an empty string "". * OFF: the original source text is displayed instead. * ON: the value of "Missing Translation Text" is used instead. * * @param MissingTranslationText * @text Missing Translation Text * @type string * @default * @desc Only used when "Empty String Fallback" is ON. * Set to "[MISSING]" to spot untranslated text during testing. * Leave blank to keep empty strings as intentional blanks. * * @param DebugMode * @text Debug Mode * @type boolean * @on Enabled * @off Disabled * @default false * @desc Logs loaded files, fallbacks, desync warnings and a coverage * report table to the console (F8). Open F8 BEFORE selecting a * language to capture the full debug output. * * @param AudioManifestPath * @text Audio Manifest Path * @type string * @default audio/se/audio-manifest.json * @desc Path to audio-manifest.json generated by LSDE (relative to * your project root). Leave blank to disable audio localization. * * @command ChangeLanguage * @text Change Language * @desc Shows the language selection screen. * * @help * ============================================================================ * LSDE i18n — Localization Plugin by LepaSoft * ============================================================================ * * This plugin works with LSDE (LepaSoft Dialogue Editor). * It loads translation and localized audio files from * data/{Translation Folder}/{locale}/ and applies them at runtime. * * --- How it works --- * * Your data/ folder contains your game in your working language. * LSDE generates translation files in data/{Translation Folder}/{locale}/. * This plugin loads those translations and applies them automatically. * * --- Expected folder structure --- * * data/i18n/en/Map001.json (English map dialogue) * data/i18n/en/Actors.json (English actor names) * data/i18n/en/Items.json (English item names) * data/i18n/en/System.json (English system terms) * data/i18n/en/Troops.json (English battle event text) * ... * * --- What gets translated --- * * - Show Text / Show Scrolling Text (codes 401, 405) * - Show Choices (code 102) * - Speaker names (code 101, name display) * - Actor names, nicknames, profiles * - Item / Weapon / Armor names and descriptions * - Skill names, descriptions and messages * - State names and messages * - Class / Enemy names * - System terms (menus, battle messages, params) * - Equipment / element / skill / weapon / armor type names * - Game title and currency unit * - Troop battle event text * * --- Behavior --- * * 1. On each launch, a language selection screen appears * 2. The choice is saved (pre-selected on next launch) * 3. If selected locale = default locale: zero processing * 4. If different locale: texts are replaced at load time * * --- Changing language in-game --- * * Plugin Command : "Change Language" (shows the selector) * Script Call : LSDE_i18n.showLanguageSelect(); * * --- Audio Localization --- * * LSDE can generate localized audio files (voice, SFX) with * locale-specific names like: filename;fr-FR.ogg * An audio-manifest.json lists all localized files per locale. * * Set "Audio Manifest Path" to the manifest location (e.g. * audio/voice/audio-manifest.json). The plugin will automatically * play the localized version of any audio file when available, * falling back to the original if no localized version exists. * * The audio file name delimiter (e.g. ";") is auto-detected * from the manifest configuration — no manual setup needed. * * --- Debug Mode --- * * Enable Debug Mode in the plugin parameters, then open the * developer console (F8) BEFORE choosing a language. * The console will show: * - Each translation file loaded or missing * - Each fallback (empty translation resolved to original) * - Each desync (source text changed since last export) * - A summary table with per-file translation coverage * - Audio manifest loading and override details * * --- Locale codes example (copy-paste ready) --- * * Without region: * fr,en,es,de,it,pt,ja,ko,zh,ru,ar,pl * Francais,English,Espanol,Deutsch,Italiano,Portugues, * 日本語,한국어,中文,Русский,العربية,Polski * * With region: * fr-CA,en-GB,es-ES,de-DE,it-IT,pt-BR,ja-JP,ko-KR, * zh-CN,ru-RU,ar-SA,pl-PL * Francais,English,Espanol,Deutsch,Italiano,Portugues, * 日本語,한국어,中文,Русский,العربية,Polski * * NOTE: you can mix both ISO 639-1 codes and ISO 639-1 + region codes * * --- Changelog --- * * v1.0.0 - Initial release * - Text, choices, speaker name localization * - Database localization (actors, items, skills, system, etc.) * - Audio localization via manifest + AudioManager override * - CommonEvents and Troops support * - Debug mode with coverage report * * ============================================================================ */ (()=>{"use strict";const e="LSDE_i18n",a=PluginManager.parameters(e),n=(a.DefaultLocale||"fr").trim(),t=(a.LocaleFolder||"i18n").trim(),o="true"===a.FallbackEmptyString,i=(a.MissingTranslationText||"").trim(),s="true"===a.DebugMode,r=(a.AudioManifestPath||"").trim();var l=";";// Default, auto-detected from manifest config.tokens const c=(a.AvailableLocales||"fr").split(",").map(function(e){return e.trim()}),f=(a.LocaleNames||"Francais").split(",").map(function(e){return e.trim()}); // ========================================================================= // ConfigManager — persistance du choix de langue // ========================================================================= ConfigManager.lsdeLocale="";const d=ConfigManager.makeData;ConfigManager.makeData=function(){const e=d.call(this);return e.lsdeLocale=ConfigManager.lsdeLocale,e};const g=ConfigManager.applyData;ConfigManager.applyData=function(e){g.call(this,e),ConfigManager.lsdeLocale=e.lsdeLocale||""}; // ========================================================================= // Chargement async des fichiers de traduction // ========================================================================= const p={};function m(e,a){return new Promise(function(n){var o=e+"/"+a;if(p[o])n(p[o]);else{var i=new XMLHttpRequest,r="data/"+t+"/"+e+"/"+a;i.open("GET",r),i.overrideMimeType("application/json"),i.onload=function(){if(i.status<400)try{var e=JSON.parse(i.responseText);p[o]=e,s&&console.log("[LSDE_i18n] loaded:",r),n(e)}catch(e){console.warn("[LSDE_i18n] invalid JSON: "+r),n(null)}else s&&console.log("[LSDE_i18n] not found (ok):",r),n(null)},i.onerror=function(){n(null)},i.send()}})} // ========================================================================= // Post-traitement des donnees // ========================================================================= const u={$dataActors:["name","nickname","profile"],$dataClasses:["name"],$dataSkills:["name","description","message1","message2"],$dataItems:["name","description"],$dataWeapons:["name","description"],$dataArmors:["name","description"],$dataEnemies:["name"],$dataStates:["name","message1","message2","message3","message4"]},v={$dataActors:"Actors.json",$dataClasses:"Classes.json",$dataSkills:"Skills.json",$dataItems:"Items.json",$dataWeapons:"Weapons.json",$dataArmors:"Armors.json",$dataEnemies:"Enemies.json",$dataStates:"States.json",$dataCommonEvents:"CommonEvents.json",$dataTroops:"Troops.json",$dataSystem:"System.json"};function h(e,a){var n=window[e];if(n){var t=a||{},o=u[e];if(o)for(var i=0;im){for(var y=[],S=m;S=0;M--)e.splice(c+m,0,y[M]);r+=u-m}else u=0&&this.select(t)},E.prototype.makeCommandList=function(){for(var e=0;e0&&(A=!1,x=null),O.call(this,e),ConfigManager.lsdeLocale&&ConfigManager.lsdeLocale!==n&&e>0){var a="Map"+String(e).padStart(3,"0")+".json";m(ConfigManager.lsdeLocale,a).then(function(e){ // Stocker les traductions — elles seront appliquées dans isReady // quand $dataMap est aussi prêt (évite la race condition) x=e?{data:e,label:a}:null,A=!0})}};const W=Scene_Map.prototype.isReady;Scene_Map.prototype.isReady=function(){return!!A&&( // Appliquer les traductions quand les deux sont prêts ($dataMap + i18n) x&&$dataMap&&(S={translated:0,fallback:0,desync:0},function(e,a){if(e&&a&&a.events){var n=a.events;for(var t in n){var o=parseInt(t,10),i=e.events[o];if(i){i.name=L(n[t].name,i.name||"");var s=n[t].pages;if(s)for(var r in s){var l=parseInt(r,10),c=i.pages[l];c&&c.list&&C(c.list,s[r])}}}}}($dataMap,x.data),$("Map "+x.label),x=null),W.call(this))}; // ========================================================================= // Audio localization — manifest + AudioManager override // ========================================================================= var I=null,z=null;// { files: { locale: { relativePath: { ... } } } } var H="";if(r){var N=r.lastIndexOf("/");H=N>=0?r.substring(0,N+1):""} // Override AudioManager.createBuffer to serve localized audio if(r){var P=AudioManager.createBuffer;AudioManager.createBuffer=function(e,a){if(z&&ConfigManager.lsdeLocale&&ConfigManager.lsdeLocale!==n){var t=ConfigManager.lsdeLocale,o=a,i=a.lastIndexOf(l); // Strip existing locale suffix if present (e.g., "Map001.1.0.t1;fr-FR" → "Map001.1.0.t1") // Matches locale patterns: "fr", "en", "fr-FR", "pt-BR", etc. if(i>=0){var r=a.substring(i+l.length);/^[a-z]{2}(-[A-Za-z]{2,})?$/.test(r)&&(o=a.substring(0,i))}for(var c=o+l+t,f=this.audioFileExt(),d=[t+"/"+c+f,c+f],g=0;g=0?u.substring(0,v+1):"",y=u.substring(v+1,u.length-f.length);return P.call(this,h,y)}}return P.call(this,e,a)}} // ========================================================================= // Plugin Command + API publique // ========================================================================= PluginManager.registerCommand(e,"ChangeLanguage",function(){D=!0,SceneManager.goto(Scene_Title)}),window.LSDE_i18n={getLocale:function(){return ConfigManager.lsdeLocale},getAudioManifest:function(){return I},showLanguageSelect:function(){D=!0,SceneManager.goto(Scene_Title)}}})();