базовые строки для локализации на русский

This commit is contained in:
2026-08-12 01:18:34 +04:00
parent 777c5ddf52
commit 5d82df8926
100 changed files with 6375 additions and 887 deletions
@@ -0,0 +1,8 @@
-- Bind the namespace-wide localization table after every AceLocale file has
-- had a chance to register the active client locale. Code loaded before this
-- file can still use bootstrap.lua's English-key fallback table.
local NE = DragonUI_NewEra
local locale = NE and NE.GetLocale and NE:GetLocale()
if locale then
NE.L = locale
end
@@ -13,4 +13,7 @@
<Script file="zhCN.lua"/>
<Script file="zhTW.lua"/>
<Script file="koKR.lua"/>
<!-- Expose the active AceLocale table as DragonUI_NewEra.L. -->
<Script file="Finalize.lua"/>
</Ui>
@@ -0,0 +1,33 @@
# DragonUI_NewEra: untranslated ruRU strings
Generated by `qa/audit-ruRU.ps1` from the files loaded by `DragonUI_NewEra.toc`.
- enUS keys: 868
- Missing ruRU keys: 0
- ruRU keys still falling back to English: 5
- NE.L usages missing from enUS: 0
- Direct hardcoded English UI strings: 0
- Indirect prose and fallback candidates: 0
- Total source locations requiring localization review: 0
The hardcoded section is the translation backlog. Move each user-facing literal to AceLocale before translating it. Generated spell/catalog data, embedded libraries, QA code, and modules not loaded by the MoonWell TOC are intentionally excluded.
## Missing or fallback locale keys
- English fallback: `Auctionator`
- English fallback: `DragonUI New Era`
- English fallback: `HandleBagEquip invSlot=`
- English fallback: `ID: `
- English fallback: `slotframe `
## Hardcoded English UI candidates
| Source | Current code |
|---|---|
## Indirect prose and fallback candidates
These literals are returned to UI code, passed through helper functions, used as English fallbacks, or emitted as runtime diagnostics. Review them while extracting the direct strings; some diagnostic-only entries may intentionally remain English.
| Source | Current code |
|---|---|
@@ -80,4 +80,816 @@ L["Equipment Manager"] = true
L["None"] = true
L["New Equipment Set"] = true
L["Edit"] = true
L["equipped"] = true
L["equipped"] = true
-- ============================================================================
-- GENERATED UI BACKLOG (qa/localize-hardcoded.ps1)
-- ============================================================================
L["A modern Dragonflight character window (custom frame; the Blizzard frame is hidden)."] = true
L["A ready sound plays when a COOLDOWN finishes.|nA tracked buff has none, so this one can|nnever play. Clearing it also clears the badge."] = true
L["A set with that name already exists."] = true
L["A spell can be on cooldown and buffing you at the same time. The glow says which icons "] = true
L["abilities and their levels come from your class trainer's own list, battlegrounds "] = true
L["Activate"] = true
L["ACTIVE EFFECTS"] = true
L["Add Friend"] = true
L["Adventure Guide"] = true
L["Adventure Guide (Encounter Journal)"] = true
L["Alert: %s (%s)"] = true
L["All Auctions"] = true
L["All Bids"] = true
L["always puts appearance back, whatever this says."] = true
L["always remembered per character; the appearance settings are shared unless you say otherwise "] = true
L["Ammo Pouch"] = true
L["and dungeons from the client's brackets. Visit a trainer once to fill it in; "] = true
L["and positions are not affected."] = true
L["and ready sounds — lives in the Cooldown Manager window itself (/cdm). Drag the viewers with "] = true
L["and touches nothing else, so your setup comes back exactly as you left it."] = true
L["another server"] = true
L["appearance either way, so this only decides what happens when one is applied. Revert "] = true
L["are buffed; the timer says how long."] = true
L["are not per-viewer: alerts, ready sounds, buff tracking, icon fit and the resets."] = true
L["Armor Enchantment"] = true
L["Attack Power"] = true
L["Attack Speed"] = true
L["Auction House"] = true
L["Auction Item"] = true
L["Auction query is throttled. Try again in a moment."] = true
L["Auctionator"] = true
L["Auctionator API not available for reagent scans."] = true
L["Auctionator scan started for recipe reagents."] = true
L["Aura "] = true
L["auras, trinket placement, alerts and sounds — to their defaults, and clears your "] = true
L["Auto-track buffs under %ds"] = true
L["Available"] = true
L["available in combat."] = true
L["Average wait: %s"] = true
L["Away"] = true
L["bag opening and replaces the per-window 'Retail bags' restyle. Reload (/reload) to apply."] = true
L["Bar Content"] = true
L["bar frame: %s, region %.0fpx wide vs %dpx art (%.2fx), caps %.1f/%.1f held"] = true
L["Bar Width"] = true
L["Bars only"] = true
L["Begins a Quest"] = true
L["Block Chance"] = true
L["Bonus Healing"] = true
L["boot failed: "] = true
L["Boss and loot browser. Requires a /reload to take effect (the micro button doesn't re-check this live)."] = true
L["Both of these are immediate and cannot be undone from here — Revert only covers layout "] = true
L["browse scan aborted: "] = true
L["Buff Bars"] = true
L["buff glow: %s, texture %s, lit on %d of %d shown tiles"] = true
L["Buff Icons"] = true
L["Buff tracking"] = true
L["buff viewer '"] = true
L["Buffed spells"] = true
L["Buffs you have not seen before are recorded and listed under Not Displayed on the "] = true
L["build: InsetRight not ready"] = true
L["buildEquipPane: InsetRight not ready"] = true
L["builder missing"] = true
L["buildInset: frame not built yet"] = true
L["buildInsetRight: frame not built yet"] = true
L["buildInsetRight: Inset not built yet"] = true
L["BuildJournals: shell insets not ready"] = true
L["buildSidebar: InsetRight not ready"] = true
L["buildSidebarTabs: InsetRight not ready"] = true
L["buildTabs: frame not built yet"] = true
L["Busy"] = true
L["Button Glow"] = true
L["Buy out this auction for %s?"] = true
L["Buyout Mode"] = true
L["Buyout Price"] = true
L["Cancel Auction"] = true
L["Cancel Extend"] = true
L["Can't do that in combat"] = true
L["Character Panel"] = true
L["character you are still setting up; in a raid it fills the viewers with other "] = true
L["character. On, each character can differ — until you change something here it still "] = true
L["Chat Channels"] = true
L["Check All"] = true
L["Choose search criteria and press \"Search\""] = true
L["class=%s spellbook built=%s names=%d"] = true
L["class's spell lists, tracked auras,|ntrinket placement, alerts and sounds."] = true
L["Clear All Alerts"] = true
L["Clear all alerts and sounds"] = true
L["Clear every configured alert and ready sound?\n\nSpell lists and frame positions are not affected."] = true
L["Clear Ready Sound"] = true
L["cleared stale auto-seeded spell lists; curated defaults restored"] = true
L["Click for this frame's settings."] = true
L["close button modernize failed: "] = true
L["Closes edit mode and opens the Cooldown Manager window, which carries the settings that "] = true
L["Collections"] = true
L["collectScanPage error: "] = true
L["Combined bag"] = true
L["Combined bag (all-in-one)"] = true
L["Condition of your equipment: %d of %d durability across every equipped item that has it."] = true
L["ContainerFrame_GenerateFrame missing; chrome will rely on OnShow hooks only"] = true
L["ContainerFrame_Update missing; item quality borders will not auto-refresh"] = true
L["Continue %s?"] = true
L["Convert to Raid"] = true
L["Cooldown Manager"] = true
L["Cooldown Manager layout string (Ctrl+C to copy):"] = true
L["Cooldown Manager layout: "] = true
L["Cooldown Manager Settings"] = true
L["cooldown widget: SetDrawEdge=%s GetDrawEdge=%s SetSwipeTexture=%s SetReverse=%s"] = true
L["Copy — Ctrl+A then Ctrl+C"] = true
L["Copy this build string (Ctrl+C). Talented & the WoWhead/wotlkdb calculators import it too:"] = true
L["corrupt layout string"] = true
L["could not read that string."] = true
L["could not save."] = true
L["Create Auction"] = true
L["CreateUIFrame failed for '"] = true
L["Currency pane host missing -- cannot build"] = true
L["CURRENCY: "] = true
L["Current Bid"] = true
L["Custom scale"] = true
L["Damage Per Second"] = true
L["default|r — it adds four viewers to the middle of your screen, so it waits to be asked. Every "] = true
L["Defensives, interrupts, CC and escapes."] = true
L["Delete loadout '%s'?"] = true
L["Delete the equipment set '%s'?"] = true
L["Delete the layout \"%s\"?"] = true
L["Diet: %s"] = true
L["Discipline layout and a Holy one do not overwrite each other. Where each viewer sits is "] = true
L["Dodge Chance"] = true
L["Drag onto Essential or Utility to track it."] = true
L["Drag to move."] = true
L["DragonUI editor API absent; '"] = true
L["DragonUI exposes neither ModuleRegistry nor RegisterModule; '"] = true
L["DragonUI New Era"] = true
L["DragonUI.db.profile not available at OnReady; newera settings not initialised."] = true
L["DragonUI's editor mode isn't available."] = true
L["DragonUI's editor mode to reposition them, and right-click one there for its own layout settings."] = true
L["Draw the countdown number on each icon."] = true
L["duplicate module registration: "] = true
L["Durability %d / %d"] = true
L["Each viewer's own settings — size, spacing, orientation, visibility, what its icons "] = true
L["Earn honorable kills in battlegrounds and world PvP to gain rank."] = true
L["edit mode with that viewer selected and its settings already up. Closes this window; not "] = true
L["Editor mode can't be opened during combat."] = true
L["editor mode couldn't be opened."] = true
L["Editor mode declined to open."] = true
L["Editor mode failed to close: "] = true
L["Editor mode failed to open: "] = true
L["editor mode isn't available in this DragonUI build. Type /dui edit to position frames."] = true
L["editor settings failed to open: "] = true
L["effect immediately either way, and nothing is forgotten — this switch stores one flag "] = true
L["Empty socket"] = true
L["empty talent string"] = true
L["Enable Cooldown Manager"] = true
L["Enable Level Up Display"] = true
L["Enabled"] = true
L["ENCHANTING BAG"] = true
L["Enchanting Bags"] = true
L["ENGINEERING BAG"] = true
L["Engineering Bags"] = true
L["Enter a note for %s:"] = true
L["Essential Cooldowns"] = true
L["Everything (no spec)"] = true
L["Everything recorded is displayed."] = true
L["Everything the Cooldown Manager can be told to do that is not about one viewer's layout. "] = true
L["Everything you have learned is displayed. Turn on Show Unlearned (cog) to see the rest."] = true
L["Export Layout"] = true
L["Extend"] = true
L["Extended"] = true
L["fallback highlight burst (flipbook atlas not registered)"] = true
L["Find Group"] = true
L["Fist Weapons"] = true
L["Flashes once, the moment the cooldown finishes.|nWorks for every spell."] = true
L["follows the shared setup, so nothing moves when you tick this, and unticking it "] = true
L["Food & Drink"] = true
L["Frame strength"] = true
L["Friends List"] = true
L["FX Style"] = true
L["Gap between icons. Retail offsets this by -4, so the low end overlaps slightly — that is "] = true
L["GEM BAG"] = true
L["Gem Bags"] = true
L["GetCurrencyListSize/GetCurrencyListInfo unavailable -- Currency pane degraded"] = true
L["GetNumFactions/GetFactionInfo unavailable — Reputation pane degraded"] = true
L["GetNumSkillLines/GetSkillLineInfo unavailable — Skills pane degraded"] = true
L["gives the shared setup back without losing what you changed."] = true
L["Glow active spells"] = true
L["Glow while buffed"] = true
L["Glows during the last %d%% of this buff's|nremaining time."] = true
L["Glows during the last %d%% of this spell's own|nbuff or debuff."] = true
L["Glows for as long as the spell is off cooldown|nand affordable."] = true
L["Glows for as long as this buff is on you.|n|nThe one that works for a"] = true
L["Glyph of "] = true
L["Glyph options"] = true
L["Glyph Socket"] = true
L["GLYPHS"] = true
L["Guild"] = true
L["Guild Control"] = true
L["Guild Information"] = true
L["Guild Message Of The Day"] = true
L["Halo the icon gold while the spell's buff (or, for a shaman, its totem) is up."] = true
L["HandleBagEquip invSlot="] = true
L["Haste rating %d (%.2f%% haste)"] = true
L["HERB BAG"] = true
L["Herb Bags"] = true
L["Hide Passives"] = true
L["Hide When Inactive"] = true
L["Highest Rank"] = true
L["Hit Rating"] = true
L["Honor pane host missing — cannot build"] = true
L["Honor Points"] = true
L["HONOR: "] = true
L["Honorable Kills"] = true
L["How many icons before the layout wraps. Vertical orientation reads this as icons per "] = true
L["Icon and Name"] = true
L["Icon Direction"] = true
L["Icon fit"] = true
L["Icon inset"] = true
L["Icon Limit"] = true
L["Icon Only"] = true
L["Icon Padding"] = true
L["Icon Size"] = true
L["Icons and bars"] = true
L["Icons only"] = true
L["ID: "] = true
L["Ignore Player"] = true
L["Import Layout"] = true
L["Import…"] = true
L["imported '"] = true
L["In Combat"] = true
L["Increases the speed that your spells cast by %.2f%%."] = true
L["Increases your damage with melee weapons by %.1f damage per second."] = true
L["Increases your damage with ranged weapons by %.1f damage per second."] = true
L["InnerBorder boot failed: "] = true
L["InnerBorder: Inset not built yet"] = true
L["INSCRIPTION BAG"] = true
L["inset build failed: "] = true
L["Inset shrinks the icon, which slides the shadow off it, so raise that one sparingly."] = true
L["installed DragonUI_NewEra includes core/Scale.lua AND its line in the .toc, then /reload."] = true
L["installed PutItemInBag wrapper"] = true
L["Instance"] = true
L["into the spec you are in."] = true
L["invalid string"] = true
L["Item Enhancement"] = true
L["Item Level"] = true
L["ItemBuy QueryAuctionItems error: "] = true
L["items missing"] = true
L["Join as Party"] = true
L["journal refresh"] = true
L["keeps running either way, so turning it back on costs nothing."] = true
L["Keeps this at the front of the list."] = true
L["Last Week"] = true
L["Lasts %s sec"] = true
L["Layout and position both live on the frame itself, in edit mode (/dui edit) — click a viewer "] = true
L["layout copied into per-spec buckets"] = true
L["Layout: Starter"] = true
L["Layouts"] = true
L["Layouts include appearance"] = true
L["Learn the selected talents? Spent points cannot be refunded without a respec."] = true
L["LEATHERWORKING BAG"] = true
L["Leave Queue"] = true
L["Level %d"] = true
L["Level %d %s %s"] = true
L["Level Range"] = true
L["Level Up Display"] = true
L["level, so this only adds a second copy on top of it. Turn it on if you "] = true
L["LevelText boot failed: "] = true
L["List Me"] = true
L["List My Group"] = true
L["Listed in the Raid Browser"] = true
L["Load the %s starter layout?\n\nEssential is set to that spec's spells. Everything else for "] = true
L["Loading results..."] = true
L["Loadouts"] = true
L["Locked"] = true
L["Locked socket"] = true
L["locked unless that window's mode is set to Custom."] = true
L["Looking for Group"] = true
L["Looking For Group (Dungeon/Raid Finder)"] = true
L["Lvl"] = true
L["Main Hand"] = true
L["MAJOR GLYPHS"] = true
L["Mana Regen"] = true
L["Marching Ants"] = true
L["Metal & Stone"] = true
L["MINING BAG"] = true
L["Mining Bags"] = true
L["MINOR GLYPHS"] = true
L["MISS atlas "] = true
L["MISS layout "] = true
L["MISS mask atlas "] = true
L["MISS path "] = true
L["ModelArea boot failed: "] = true
L["ModelControls boot failed: "] = true
L["Modern Communities-style guild window (Roster / Info / Chat)."] = true
L["Modern friends window (Friends / Ignore / Who) with a Guild tab."] = true
L["Modern visual shell for Buy/Sell/Auctions with optional Auctionator tab embedding."] = true
L["ModuleRegistry:Register failed"] = true
L["ModuleRegistry:Register failed for '"] = true
L["Move to "] = true
L["Movement Speed"] = true
L["Name Only"] = true
L["Name this imported loadout:"] = true
L["Name this layout:"] = true
L["Name this loadout (saves your current spec):"] = true
L["NE.itemgrid missing (core/ItemGrid.lua not loaded); combined bag unavailable"] = true
L["NE.menu unavailable; the settings cog has nothing to open"] = true
L["NE.modules.Register absent; character panel not booted"] = true
L["NE.modules.Register absent; collections window not booted"] = true
L["NE.modules.Register absent; combined bag not booted"] = true
L["NE.modules.Register absent; retail bags not booted"] = true
L["NE.modules.Register absent; spellbook window not booted"] = true
L["NE.modules.Register failed for '"] = true
L["New Era"] = true
L["New Layout"] = true
L["NewEra panels ported onto DragonUI. Toggle a panel below to enable or disable it. "] = true
L["nineslice art missing for "] = true
L["NO ACTIVE EFFECTS"] = true
L["No auctions found for this item."] = true
L["no class bg atlas for "] = true
L["no font object resolved from "] = true
L["No layout"] = true
L["No listings."] = true
L["no native engine; custom backend live"] = true
L["no native GetContainerItemQuestInfo on this client; returns empty (no quest item)"] = true
L["no race background for '"] = true
L["No reputations."] = true
L["No results match your filters."] = true
L["No results. Adjust filters and search again."] = true
L["No scaling"] = true
L["No skills."] = true
L["no uiMapID->map metadata on 3.3.5; returns nil"] = true
L["NO-LOCAL atlas "] = true
L["not a Cooldown Manager layout"] = true
L["not a Cooldown Manager layout string"] = true
L["Not Collected"] = true
L["Not Displayed"] = true
L["Not displayed on any viewer"] = true
L["Not yet collected"] = true
L["Not yet learned"] = true
L["Nothing here yet — drag a buff in to pin it."] = true
L["nothing pasted"] = true
L["nothing to export."] = true
L["Nothing to undo. It covers LAYOUTS, not the settings|non these tabs — a "] = true
L["of their own."] = true
L["off (Settings > Buffed spells)"] = true
L["Off by default. Turn on to show the four viewers; turn off to hide them again. Takes "] = true
L["Off Hand"] = true
L["Off, both specs share one set of lists. Turning it on copies the layout you have now "] = true
L["Off, loading or importing a layout changes only what you track — lists, tracked buffs, "] = true
L["Off, orientation, icons per row, size, padding and opacity are one setup for every "] = true
L["Offensive burst and damage cooldowns."] = true
L["Officer Note"] = true
L["On by default. Turn off to stop the banner appearing on level-up; the harvest "] = true
L["onBoot error in "] = true
L["One-Handed Axes"] = true
L["One-Handed Maces"] = true
L["One-Handed Swords"] = true
L["Opacity"] = true
L["Open Cooldown Manager"] = true
L["Open the Auction House first to run Auctionator scans."] = true
L["Opens a share string you can copy with Ctrl+C.|nIt covers this "] = true
L["Opens the Cooldown Manager window (/cdm) on its Spells tab. Needs the module on — "] = true
L["Options"] = true
L["options section '"] = true
L["options, under New Era > Cooldown Manager."] = true
L["OptionsPanel:RegisterTab failed: "] = true
L["Orientation"] = true
L["Our all-in-one bag window. Turn OFF to use the stock Blizzard bags instead. Reload (/reload) to apply."] = true
L["Overwrite the equipment set '%s' with your currently equipped items?"] = true
L["Page %d of %d (items %d-%d of %d, auctions: %d%s)"] = true
L[" -- partial scan"] = true
L["Page %d/%d"] = true
L["Page 1 of 1"] = true
L["Pandemic Border"] = true
L["pandemic ring: art %s, %d spell(s) set to Refresh, %d ring(s) up, %d pulsing"] = true
L["Panels appear here as their modules load."] = true
L["Parry Chance"] = true
L["Paste a Cooldown Manager layout string:"] = true
L["Paste a talent string or calculator URL (Talented / WoWhead / wotlkdb):"] = true
L["people's cooldowns, food and flasks."] = true
L["Per Item"] = true
L["Pet"] = true
L["Pet Journal"] = true
L["Pet pane host missing — cannot build"] = true
L["PET: "] = true
L["Picks a random favorite usable here (or any, if none are favorited)."] = true
L["Place a bid of %s?"] = true
L["PlaceAuctionBid(bid) error: "] = true
L["PlaceAuctionBid(buyout) error: "] = true
L["Play the level-up sound"] = true
L["%s points available"] = true
L["Points may land on the wrong talents."] = true
L["Points may not line up."] = true
L["Power Infusion"] = true
L["preview API unavailable"] = true
L["Professions"] = true
L["Promote"] = true
L["Promote to Assistant"] = true
L["Promote to Raid Leader"] = true
L["PutItemInBag missing; bag-swap disabled"] = true
L["PvP API unavailable — Honor pane degraded"] = true
L["quality borders. Disable to keep the stock Blizzard bags. Reload (/reload) to apply."] = true
L["QueryAuctionItems error: "] = true
L["Quest Items"] = true
L["Raid Browser"] = true
L["Raid Info"] = true
L["Random Dungeon"] = true
L["Random dungeon cooldown"] = true
L["Ranged Attack Power"] = true
L["Ready Check"] = true
L["ready flash: %s"] = true
L["Ready Sound"] = true
L["Ready sound: "] = true
L["rebuild failed for '"] = true
L["rebuild failed: "] = true
L["Reduces "] = true
L["Refresh Window"] = true
L["RefreshCurrency error: "] = true
L["refreshDetailRows error: "] = true
L["RefreshHonor error: "] = true
L["RefreshReputation error: "] = true
L["refreshResults error: "] = true
L["RefreshSkills error: "] = true
L["RefreshTitles error: "] = true
L["Register called without a name"] = true
L["RegisterEditableFrame failed for '"] = true
L["RegisterHUDFrame needs a name and frame; ignored."] = true
L["RegisterModule failed"] = true
L["RegisterModule failed for '"] = true
L["RegisterMover failed for '"] = true
L["RegisterPanel called without a spec.id; ignored."] = true
L["Reload (/reload) to apply."] = true
L["Remove every per-spell alert and ready sound. Spell lists and positions are not affected."] = true
L["Remove Favorite"] = true
L["Remove Favourite"] = true
L["Remove Friend"] = true
L["Remove this glyph?"] = true
L["rename failed (name taken?)."] = true
L["Rename loadout:"] = true
L["Rename specialization"] = true
L["Rename this specialization (letters only, max "] = true
L["Reputation pane host missing — cannot build"] = true
L["REPUTATION: "] = true
L["Requires higher level"] = true
L["Requires the "] = true
L["Requires the Auction House window to be open."] = true
L["Requires: "] = true
L["Reset "] = true
L["Reset spell and buff lists"] = true
L["Reset Spell Lists"] = true
L["Reset this class's Cooldown Manager spell and buff lists to their defaults?\n\nOther classes, alerts, sounds and frame positions are not affected."] = true
L["Reset to Default"] = true
L["Reset to the starter layout?\n\nThis reverts every Cooldown Manager edit — spells, tracked "] = true
L["Resets In"] = true
L["Restore the curated defaults and the auto-track window for THIS CLASS, clearing its "] = true
L["retail flipbook sprite (22 frames)"] = true
L["Retail's behaviour: while buffed, the icon counts down the BUFF. Off, it counts down the "] = true
L["Retail's Cooldown Manager, driven from curated per-class cooldown lists. |cffffcc55Off by "] = true
L["Retail's level-up banner. What it announces is read from |cffffcc55this server|r — "] = true
L["Retail-style crafting window for all professions."] = true
L["Revert"] = true
L["Revert Changes"] = true
L["right here."] = true
L["Right-click to dismiss"] = true
L["Right-click to summon"] = true
L["Role check in progress…"] = true
L["Save current spec…"] = true
L["Save, load, import and export the whole|nCooldown Manager setup for this class."] = true
L["saved '"] = true
L["Scale mode"] = true
L["Scaling controls need a newer DragonUI options panel (AddSlider/AddDropdown)."] = true
L["Scan AH"] = true
L["Scanning page %d of %d..."] = true
L["Scroll of "] = true
L["Search failed: "] = true
L["Searches Auctionator for the selected recipe and its reagents."] = true
L["Searching..."] = true
L["seeded the "] = true
L["Select All"] = true
L["SelectSidebar(3): ShowEquipManager absent; staying on stats"] = true
L["Send Message"] = true
L["Separate appearance per character"] = true
L["Separate layout per spec"] = true
L["Server uses custom talents"] = true
L["Set Favorite"] = true
L["Set Name"] = true
L["Set Name:"] = true
L["Set Note"] = true
L["setting — which spells and buffs are tracked, each viewer's layout, size and visibility, alerts "] = true
L["settings cog build failed: "] = true
L["Short-duration buffs and procs, as depleting bars."] = true
L["Short-duration buffs and procs, as icons."] = true
L["show — live on the frame, in edit mode, where you can see what you are changing. These open "] = true
L["Show a slot only while its aura is active."] = true
L["Show a tooltip when hovering an icon."] = true
L["Show All Ranks"] = true
L["Show Companions tab"] = true
L["Show every short buff the moment it lands, without assigning it first. Convenient on a "] = true
L["Show glyph effects"] = true
L["Show glyph names"] = true
L["Show Mounts tab"] = true
L["Show Offline"] = true
L["Show on Backpack"] = true
L["Show second page"] = true
L["Show single page"] = true
L["Show the buff's time, not the cooldown"] = true
L["Show them as"] = true
L["Show this viewer at all. The editor handle stays either way, so this is reversible from "] = true
L["Show Timer"] = true
L["Show Tooltips"] = true
L["Show Unlearned"] = true
L["sidebar build failed: "] = true
L["sidebar tabs build failed: "] = true
L["single stretch (bar too narrow)"] = true
L["size, padding and opacity the layout was saved with.|n|nLayouts always SAVE "] = true
L["Skills pane host missing — cannot build"] = true
L["SKILLS: "] = true
L["slotframe "] = true
L["SlotFrames boot failed: "] = true
L["Social"] = true
L["Social (Friends/Who/Guild/Chat/Raid)"] = true
L["Sort Per Item"] = true
L["SOUL BAG"] = true
L["Soul Bags"] = true
L["Source "] = true
L["Spec "] = true
L["Specific Dungeons"] = true
L["Spell "] = true
L["Spell Crit"] = true
L["Spell Hit"] = true
L["spell lists, aura assignments and trinket placement. Other classes, alerts, sounds "] = true
L["Spell Power"] = true
L["Spellbook"] = true
L["spell's cooldown and the glow alone marks it as buffed — which is clearer when the "] = true
L["Spent: %d"] = true
L["staged '"] = true
L["Starter Layout"] = true
L["starter reset.|n|nOne step, and only for this session."] = true
L["Starting Bid"] = true
L["sub-boot error in "] = true
L["Summon Random Favorite Companion"] = true
L["Summon Random Favorite Mount"] = true
L["Tab "] = true
L["tab build failed: "] = true
L["Tags exported builds with this realm so imports onto other layouts warn first."] = true
L["talent data unavailable"] = true
L["Talent specs"] = true
L["Talents"] = true
L["Talents Panel"] = true
L["that layout is for a %s"] = true
L["that viewer doesn't exist yet — it builds at login."] = true
L["The frame is a soft shadow that falls on the icon's outer edge, so it only shows where "] = true
L["The full curated list for your class, both specs' spells|nincluded. "] = true
L["The modern Dragonflight character window. Disable to keep the stock Blizzard frame."] = true
L["The modern Dragonflight Collections window (Mounts & Pet Journal). Opens on the companion keybind."] = true
L["The modern Dragonflight spellbook window. Disable to keep the stock Blizzard spellbook."] = true
L["The modern talents window. Turn off to use the standard Blizzard talent window."] = true
L["the stock look, not a bug."] = true
L["the window configures the viewers, so it goes away with them."] = true
L["There are no raids available for your level."] = true
L["there for its own settings, or use the buttons just below to go straight to one."] = true
L["there is icon underneath it. Strength draws it more than once to deepen it — that is also "] = true
L["This build was made for %s, which uses a different talent layout. "] = true
L["This feature becomes available at level %d."] = true
L["This is what the Cooldown Manager shipped with|nbefore per-spec starters."] = true
L["This looks like a standard WotLK build, but your server uses custom talents. "] = true
L["this server"] = true
L["This Week"] = true
L["Time in queue: %s"] = true
L["Time Left"] = true
L["TITLES: "] = true
L["to stock. Nothing else is affected, and it cannot be undone."] = true
L["To use this on an action bar: open Macros (ESC > Macros), find the unnamed icon-only macro matching this icon, and drag it onto your bar."] = true
L["Toggle Collections (Mounts & Pets)"] = true
L["Toggle Guild Window"] = true
L["Toggle slot name labels and the active-effects list."] = true
L["Toggle Social Window"] = true
L["total %d exceeds %d"] = true
L["Total Mounts"] = true
L["Total Price"] = true
L["Total: %d"] = true
L["Tracked auras, trinkets, alerts and frame positions are not affected."] = true
L["Tracked automatically. Drag it into a section to pin it there."] = true
L["Tracked Bars"] = true
L["Tracked Buffs"] = true
L["Tracked Buffs tab, where you can assign the ones you want. Nothing appears on screen until "] = true
L["Trade Goods"] = true
L["Training Points"] = true
L["Tree "] = true
L["trinkets, alerts and sounds. On, it also applies the orientation, icons per row, "] = true
L["two differ, as on Prayer of Mending."] = true
L["Two-Handed Axes"] = true
L["Two-Handed Maces"] = true
L["Two-Handed Swords"] = true
L["Type /cdm to open the Cooldown Manager window."] = true
L["Uncheck All"] = true
L["Undoes the last layout change — applying a layout,|nimporting one, or the "] = true
L["Unified retail-style Group Finder over the Dungeon Finder + Raid Browser."] = true
L["Unlist Me"] = true
L["Unlist My Group"] = true
L["Unlock Spec"] = true
L["unrecognised talent string"] = true
L["Usable Items Only"] = true
L["Use DragonUI's window in place of the Blizzard default. Changes take effect after a /reload."] = true
L["Use Starter Layout"] = true
L["Use UI scale"] = true
L["Utility Cooldowns"] = true
L["Very Long"] = true
L["View Log"] = true
L["viewer '"] = true
L["viewer art: all %d atlases registered"] = true
L["viewer art: MISSING %d of %d -- %s"] = true
L["viewer event '"] = true
L["Viewer layout"] = true
L["viewer's own size and position revert|nfrom its edit-mode panel instead."] = true
L["Visibility"] = true
L["want /nelevelup previews to make a sound, since those fire no game sound "] = true
L["Watch this currency at the bottom of your bags (up to 3)."] = true
L["Weapon Enchantment"] = true
L["what makes its rounded corners read, since the icons themselves cannot be rounded here. "] = true
L["When this viewer is on screen at all. Hidden still leaves the editor handle here."] = true
L["Which of the two buff viewers auto-tracked buffs land in."] = true
L["Which spells and buffs you track is remembered separately for each talent spec, so a "] = true
L["Window Scaling"] = true
L["wraps legacy GetPlayerMapPosition; ignores uiMapID arg, uses current map"] = true
L["You are listed in the Raid Browser."] = true
L["You are not saved to any instances."] = true
L["You can watch at most %d currencies on your backpack."] = true
L["You cannot list for raids while in the Dungeon Finder queue."] = true
L["You do not have a pet."] = true
L["you do."] = true
L["You have no auctions."] = true
L["You have no bids."] = true
L["You have not learned any companions."] = true
L["You have not learned any mounts."] = true
L["You have nothing equipped that can take durability damage."] = true
L["You have too many equipment sets."] = true
L["You haven't earned any titles."] = true
L["You may not list for this raid"] = true
L["You may not queue for this"] = true
L["You may not queue for this dungeon"] = true
L["You recently deserted a dungeon."] = true
L["Your pet's maximum health."] = true
L["Your running speed. 100%% is normal (%d yards/second); mounts and speed effects raise it,"] = true
-- ============================================================================
-- GENERATED UI BACKLOG (qa/localize-hardcoded.ps1)
-- ============================================================================
L["All Bags"] = true
L["Always"] = true
L["Armor"] = true
L["Arrow"] = true
L["Auto-empty old bag when swapping"] = true
L["Auto-sell junk at merchants"] = true
L["Bag Options"] = true
L["Bags"] = true
L["Bandage"] = true
L["Blue"] = true
L["Book"] = true
L["Bows"] = true
L["Bullet"] = true
L["Category (smart)"] = true
L["Cloth"] = true
L["Consumable"] = true
L["Container"] = true
L["Daggers"] = true
L["Devices"] = true
L["Elemental"] = true
L["Elixir"] = true
L["Explosives"] = true
L["Flask"] = true
L["Gems"] = true
L["Green"] = true
L["Guns"] = true
L["Herb"] = true
L["Hidden"] = true
L["Holiday"] = true
L["Horizontal"] = true
L["Idols"] = true
L["Junk"] = true
L["Keys"] = true
L["Leather"] = true
L["Left"] = true
L["Librams"] = true
L["Mail"] = true
L["Materials"] = true
L["Meat"] = true
L["Merchant"] = true
L["Meta"] = true
L["Miscellaneous"] = true
L["Name"] = true
L["New Talent Point"] = true
L["New Talent Points"] = true
L["Not enough free space to swap that bag."] = true
L["Orange"] = true
L["Other"] = true
L["Parts"] = true
L["Plate"] = true
L["Polearms"] = true
L["Potion"] = true
L["Prismatic"] = true
L["Projectile"] = true
L["Purple"] = true
L["Quality"] = true
L["Quest"] = true
L["Quiver"] = true
L["Reagent"] = true
L["Recipe"] = true
L["Red"] = true
L["Red-tint unusable items"] = true
L["Retail bags"] = true
L["Reverse sort order"] = true
L["Right"] = true
L["Scroll"] = true
L["Search"] = true
L["Separate specialty bags"] = true
L["Settings"] = true
L["Shields"] = true
L["Shift-right-click to stop watching"] = true
L["Show item level on items"] = true
L["Show keyring row"] = true
L["Sigils"] = true
L["Simple"] = true
L["Sold %d junk item(s)."] = true
L["Sort Bags"] = true
L["Sort by"] = true
L["Sorting…"] = true
L["Sparkles"] = true
L["Spells"] = true
L["Staves"] = true
L["Swapping bag…"] = true
L["The same setting as Enable Item Level in DragonUI's options (Enhancements > Item Level). Covers the character panel and every other frame too."] = true
L["Thrown"] = true
L["Totems"] = true
L["Turn on Item Level in DragonUI's options (Enhancements > Item Level) first."] = true
L["Vertical"] = true
L["Wand"] = true
L["Weapons"] = true
L["Yellow"] = true
L["You have reached"] = true
L["Background"] = true
L["Stone"] = true
L["Dark"] = true
L["Character panel settings"] = true
L[" (+%d more)"] = true
L["%d auctions"] = true
L["%d stacks of %d"] = true
L["< 1 min"] = true
L["12 Hours"] = true
L["24 Hours"] = true
L["48 Hours"] = true
L["All"] = true
L["Auctions"] = true
L["Back"] = true
L["Backfill"] = true
L["Bid"] = true
L["Bids"] = true
L["Browse"] = true
L["Buy"] = true
L["Buyout"] = true
L["Cancel"] = true
L["Class"] = true
L["Comment"] = true
L["Common"] = true
L["Damage"] = true
L["Demote"] = true
L["Deposit"] = true
L["Dungeons"] = true
L["Duration"] = true
L["Epic"] = true
L["Experience"] = true
L["Filter"] = true
L["Healer"] = true
L["Ignore"] = true
L["Invite"] = true
L["Level"] = true
L["Long"] = true
L["Max"] = true
L["Medium"] = true
L["Members"] = true
L["Money"] = true
L["No"] = true
L["Note"] = true
L["Offline"] = true
L["Poor"] = true
L["Price"] = true
L["Qty"] = true
L["Quantity"] = true
L["Queue"] = true
L["Raid"] = true
L["Raids"] = true
L["Rank"] = true
L["Rare"] = true
L["Rarity"] = true
L["Refresh"] = true
L["Remove"] = true
L["Rewards"] = true
L["Roster"] = true
L["Sell"] = true
L["Seller"] = true
L["Sellers"] = true
L["Short"] = true
L["Tank"] = true
L["Type"] = true
L["Uncommon"] = true
L["Whisper"] = true
L["Yes"] = true
L["Zone"] = true
-- ============================================================================
-- GENERATED UI BACKLOG (qa/localize-hardcoded.ps1)
-- ============================================================================
L["One movable window showing every bag slot in a Dragonflight-style grid. Takes over bag opening and replaces the per-window 'Retail bags' restyle. Reload (/reload) to apply."] = true
L["Restyle the bag windows with the Dragonflight metal frame, portrait, and item quality borders. Disable to keep the stock Blizzard bags. Reload (/reload) to apply."] = true
@@ -11,30 +11,30 @@ if not L then return end
-- ПРОФЕССИИ
-- ============================================================================
L["Alchemy"] = "Алхимия"
L["Blacksmithing"] = "Кузнечное дело"
L["Enchanting"] = "Наложение чар"
L["Engineering"] = "Инженерное дело"
L["Herbalism"] = "Травничество"
L["Leatherworking"] = "Кожевничество"
L["Mining"] = "Горное дело"
L["Smelting"] = "Выплавка металла"
L["Skinning"] = "Снятие шкур"
L["Tailoring"] = "Портняжное дело"
L["Inscription"] = "Начертание"
L["Jewelcrafting"] = "Ювелирное дело"
L["Prospecting"] = "Просеивание"
L["Cooking"] = "Кулинария"
L["First Aid"] = "Первая помощь"
L["Fishing"] = "Рыбная ловля"
L["Alchemy"] = "Алхимия"
L["Blacksmithing"] = "Кузнечное дело"
L["Enchanting"] = "Наложение чар"
L["Engineering"] = "Инженерное дело"
L["Herbalism"] = "Травничество"
L["Leatherworking"] = "Кожевничество"
L["Mining"] = "Горное дело"
L["Smelting"] = "Выплавка металла"
L["Skinning"] = "Снятие шкур"
L["Tailoring"] = "Портняжное дело"
L["Inscription"] = "Начертание"
L["Jewelcrafting"] = "Ювелирное дело"
L["Prospecting"] = "Просеивание"
L["Cooking"] = "Кулинария"
L["First Aid"] = "Первая помощь"
L["Fishing"] = "Рыбная ловля"
L["Select a recipe to craft"] = "Выберите рецепт для создания"
L["Hide item tooltips in list"] = "Скрывать подсказки в списке"
L["Hide item tooltips in list"] = "Скрывать подсказки в списке"
L["Colour names by skill difficulty"] = "Окрашивать названия по сложности"
L["Plain skill bar (no animation)"] = "Обычная полоса навыка (без анимации)"
L["Create"] = "Создать"
L["Plain skill bar (no animation)"] = "Обычная полоса навыка (без анимации)"
L["Create"] = "Создать"
L["Create All"] = "Создать все"
L["Show Learned"] = "Показывать изученные"
L["Has Skill Up"] = "Повышают навык"
L["Show Learned"] = "Показывать изученные"
L["Has Skill Up"] = "Повышают навык"
L["Have Materials"] = "Есть реагенты"
-- ============================================================================
@@ -45,7 +45,7 @@ L["Attributes"] = "Основные"
L["Spell"] = "Магия"
L["Resistances"] = "Сопротивление"
L["Maximum Health. If your health reaches zero, you will die."] = "Максимальный запас здоровья. Если ваше здоровье упадет до нуля, вы погибнете."
L["Your maximum %s."] = "Максимальное значение данного ресурса."
L["Your maximum %s."] = "Максимальный запас %s."
L["Increases attack power for some classes and the damage you block with a shield."] = "Увеличивает силу атаки некоторых классов и показатель блока щитом."
L["Increases critical strike chance, dodge chance, armor, and (for some classes) attack power."] = "Увеличивает шанс критического удара, уклонение, броню и (для некоторых классов) силу атаки."
L["Increases your maximum health."] = "Увеличивает максимальный запас здоровья."
@@ -54,7 +54,7 @@ L["Increases your health and mana regeneration while out of combat."] = "Уве
L["Seconds between melee swings."] = "Задержка между ударами ближнего боя (сек.)"
L["Improves your chance to hit by %.2f%%."] = "Увеличивает вероятность попадания по цели на %.2f%%."
L["Chance of melee attacks dealing extra damage."] = "Вероятность нанесения дополнительного урона при атаках ближнего боя."
L["Armor Penetration"] = "Рейт. пробивание брони"
L["Armor Penetration"] = "Рейтинг пробивания брони"
L["Reduces the target's armor by up to %.2f%%."] = "Снижает броню цели максимум на %.2f%%."
L["Reduces the chance your attacks are dodged or parried."] = "Снижает вероятность уклонения или парирования вашей атаки."
L["DPS"] = "Урон в сек."
@@ -64,7 +64,7 @@ L["Crit Chance"] = "Крит. Удар"
L["Chance of ranged attacks dealing extra damage."] = "Вероятность нанесения дополнительного урона при атаках дальнего боя."
L["Lowest bonus damage across schools. Hover the paper-doll stat for a breakdown."] = "Наименьший дополнительный урон среди всех школ магии. Наведите курсор на характеристику персонажа для подробностей."
L["Bonus power applied to healing spells."] = "Дополнительный эффект, применяемый к исцеляющим заклинаниям."
L["Not casting: %d / 5s\nWhile casting: %d / 5s"] = "Вне применения: %d / 5 сек.\nПри применении: %d / 5cек."
L["Not casting: %d / 5s\nWhile casting: %d / 5s"] = "Вне применения заклинаний: %d / 5 сек.\nВо время применения: %d / 5 сек."
L["Chance of spells dealing extra damage."] = "Вероятность нанесения дополнительного урона заклинаниями."
L["Haste Rating"] = "Рейт. скорости"
L["Reduces chance to be hit and crit; raises block/dodge/parry."] = "Снижает вероятность попадания и критического удара по вам; повышает шанс блока, уклонения и парирования."
@@ -80,4 +80,817 @@ L["Equipment Manager"] = "Управление экипировкой"
L["None"] = "Нет"
L["New Equipment Set"] = "Новый комплект"
L["Edit"] = "Редактировать"
L["equipped"] = "надето"
L["equipped"] = "надето"
-- ============================================================================
-- GENERATED UI BACKLOG (qa/localize-hardcoded.ps1)
-- ============================================================================
L["A modern Dragonflight character window (custom frame; the Blizzard frame is hidden)."] = "Современное окно персонажа в стиле Dragonflight (собственная рамка; стандартное окно Blizzard скрыто)."
L["A ready sound plays when a COOLDOWN finishes.|nA tracked buff has none, so this one can|nnever play. Clearing it also clears the badge."] = "Звук готовности воспроизводится, когда заканчивается ВРЕМЯ ВОССТАНОВЛЕНИЯ.|nУ отслеживаемого эффекта его нет, поэтому этот звук|nдля него никогда не сработает. Очистка также уберёт значок."
L["A set with that name already exists."] = "Комплект с таким названием уже существует."
L["A spell can be on cooldown and buffing you at the same time. The glow says which icons "] = "Заклинание может одновременно восстанавливаться и поддерживать на вас эффект. Свечение показывает, какие значки "
L["abilities and their levels come from your class trainer's own list, battlegrounds "] = "способности и их уровни берутся из списка вашего классового наставника, полей боя "
L["Activate"] = "Активировать"
L["ACTIVE EFFECTS"] = "АКТИВНЫЕ ЭФФЕКТЫ"
L["Add Friend"] = "Добавить в друзья"
L["Adventure Guide"] = "Путеводитель по приключениям"
L["Adventure Guide (Encounter Journal)"] = "Путеводитель по приключениям (Атлас подземелий)"
L["Alert: %s (%s)"] = "Оповещение: %s (%s)"
L["All Auctions"] = "Все аукционы"
L["All Bids"] = "Все ставки"
L["always puts appearance back, whatever this says."] = "всегда возвращает внешний вид, независимо от этой настройки."
L["always remembered per character; the appearance settings are shared unless you say otherwise "] = "всегда запоминаются отдельно для каждого персонажа; настройки внешнего вида общие, если не указано иное "
L["Ammo Pouch"] = "Подсумок"
L["and dungeons from the client's brackets. Visit a trainer once to fill it in; "] = "и подземелий из диапазонов уровней клиента. Один раз посетите наставника, чтобы заполнить список; "
L["and positions are not affected."] = "и расположение рамок не изменяются."
L["and ready sounds — lives in the Cooldown Manager window itself (/cdm). Drag the viewers with "] = "и звуки готовности — находятся в самом окне «Диспетчер времени восстановления» (/cdm). Перемещайте панели через "
L["and touches nothing else, so your setup comes back exactly as you left it."] = "и больше ничего не затрагивает, поэтому после возврата всё будет ровно так, как вы оставили."
L["another server"] = "другой сервер"
L["appearance either way, so this only decides what happens when one is applied. Revert "] = "внешний вид в любом случае, поэтому эта настройка определяет только, что происходит при применении. «Вернуть» "
L["are buffed; the timer says how long."] = "находятся под действием эффекта; таймер показывает, сколько он ещё продлится."
L["are not per-viewer: alerts, ready sounds, buff tracking, icon fit and the resets."] = "не относятся к отдельным панелям: оповещения, звуки готовности, отслеживание эффектов, подгонка значков и сбросы."
L["Armor Enchantment"] = "Чары для брони"
L["Attack Power"] = "Сила атаки"
L["Attack Speed"] = "Скорость атаки"
L["Auction House"] = "Аукционный дом"
L["Auction Item"] = "Аукционный лот"
L["Auction query is throttled. Try again in a moment."] = "Запросы к аукциону ограничены. Повторите попытку через несколько секунд."
L["Auctionator"] = "Auctionator"
L["Auctionator API not available for reagent scans."] = "API Auctionator недоступен для поиска реагентов."
L["Auctionator scan started for recipe reagents."] = "Запущено сканирование реагентов рецепта через Auctionator."
L["Aura "] = "Аура "
L["auras, trinket placement, alerts and sounds — to their defaults, and clears your "] = "ауры, размещение аксессуаров, оповещения и звуки — к значениям по умолчанию, а также очищает ваши "
L["Auto-track buffs under %ds"] = "Автоотслеживание эффектов короче %d сек."
L["Available"] = "Доступно"
L["available in combat."] = "доступно в бою."
L["Average wait: %s"] = "Среднее время ожидания: %s"
L["Away"] = "Нет на месте"
L["bag opening and replaces the per-window 'Retail bags' restyle. Reload (/reload) to apply."] = "открытие сумок и заменяет оформление отдельных окон «Сумки в стиле актуальной версии». Для применения выполните /reload."
L["Bar Content"] = "Содержимое полосы"
L["bar frame: %s, region %.0fpx wide vs %dpx art (%.2fx), caps %.1f/%.1f held"] = "рамка полосы: %s, область шириной %.0f пкс. против %d пкс. текстуры (%.2fx), края %.1f/%.1f сохранены"
L["Bar Width"] = "Ширина полосы"
L["Bars only"] = "Только полосы"
L["Begins a Quest"] = "Начинает задание"
L["Block Chance"] = "Шанс блока"
L["Bonus Healing"] = "Бонус к исцелению"
L["boot failed: "] = "ошибка запуска: "
L["Boss and loot browser. Requires a /reload to take effect (the micro button doesn't re-check this live)."] = "Просмотр боссов и добычи. Для применения требуется /reload (кнопка микроменю не проверяет настройку в реальном времени)."
L["Both of these are immediate and cannot be undone from here — Revert only covers layout "] = "Оба действия применяются сразу и не могут быть отменены отсюда — «Вернуть» относится только к расположению "
L["browse scan aborted: "] = "сканирование прервано: "
L["Buff Bars"] = "Полосы эффектов"
L["buff glow: %s, texture %s, lit on %d of %d shown tiles"] = "свечение эффекта: %s, текстура %s, подсвечено %d из %d отображаемых ячеек"
L["Buff Icons"] = "Значки эффектов"
L["Buff tracking"] = "Отслеживание эффектов"
L["buff viewer '"] = "панель эффектов '"
L["Buffed spells"] = "Заклинания с эффектами"
L["Buffs you have not seen before are recorded and listed under Not Displayed on the "] = "Эффекты, которые вы ещё не видели, записываются и перечисляются в разделе «Не отображаются» на вкладке "
L["build: InsetRight not ready"] = "build: InsetRight не готов"
L["buildEquipPane: InsetRight not ready"] = "buildEquipPane: InsetRight не готов"
L["builder missing"] = "сборщик отсутствует"
L["buildInset: frame not built yet"] = "buildInset: рамка ещё не создана"
L["buildInsetRight: frame not built yet"] = "buildInsetRight: рамка ещё не создана"
L["buildInsetRight: Inset not built yet"] = "buildInsetRight: Inset ещё не создан"
L["BuildJournals: shell insets not ready"] = "BuildJournals: внутренние области оболочки не готовы"
L["buildSidebar: InsetRight not ready"] = "buildSidebar: InsetRight не готов"
L["buildSidebarTabs: InsetRight not ready"] = "buildSidebarTabs: InsetRight не готов"
L["buildTabs: frame not built yet"] = "buildTabs: рамка ещё не создана"
L["Busy"] = "Занят"
L["Button Glow"] = "Свечение кнопки"
L["Buy out this auction for %s?"] = "Выкупить этот лот за %s?"
L["Buyout Mode"] = "Режим выкупа"
L["Buyout Price"] = "Выкупная цена"
L["Cancel Auction"] = "Отменить аукцион"
L["Cancel Extend"] = "Отменить продление"
L["Can't do that in combat"] = "Нельзя сделать это в бою"
L["Character Panel"] = "Окно персонажа"
L["character you are still setting up; in a raid it fills the viewers with other "] = "персонажа, которого вы ещё настраиваете; в рейде панели заполняются временем восстановления других "
L["character. On, each character can differ — until you change something here it still "] = "персонажа. Если включено, у каждого персонажа могут быть свои настройки — пока вы не измените их здесь, они всё равно "
L["Chat Channels"] = "Каналы чата"
L["Check All"] = "Отметить все"
L["Choose search criteria and press \"Search\""] = "Выберите критерии поиска и нажмите «Поиск»"
L["class=%s spellbook built=%s names=%d"] = "класс=%s книга заклинаний создана=%s названий=%d"
L["class's spell lists, tracked auras,|ntrinket placement, alerts and sounds."] = "списки заклинаний класса, отслеживаемые ауры,|nразмещение аксессуаров, оповещения и звуки."
L["Clear All Alerts"] = "Очистить все оповещения"
L["Clear all alerts and sounds"] = "Очистить все оповещения и звуки"
L["Clear every configured alert and ready sound?\n\nSpell lists and frame positions are not affected."] = "Удалить все настроенные оповещения и звуки готовности?\n\nСписки заклинаний и положение рамок не изменятся."
L["Clear Ready Sound"] = "Очистить звук готовности"
L["cleared stale auto-seeded spell lists; curated defaults restored"] = "устаревшие автоматически созданные списки заклинаний очищены; восстановлены подготовленные значения по умолчанию"
L["Click for this frame's settings."] = "Нажмите, чтобы открыть настройки этой рамки."
L["close button modernize failed: "] = "не удалось обновить кнопку закрытия: "
L["Closes edit mode and opens the Cooldown Manager window, which carries the settings that "] = "Закрывает режим редактирования и открывает окно «Диспетчер времени восстановления», где находятся настройки, которые "
L["Collections"] = "Коллекции"
L["collectScanPage error: "] = "ошибка collectScanPage: "
L["Combined bag"] = "Общая сумка"
L["Combined bag (all-in-one)"] = "Общая сумка (всё в одном)"
L["Condition of your equipment: %d of %d durability across every equipped item that has it."] = "Состояние экипировки: %d из %d прочности всех надетых предметов, у которых есть прочность."
L["ContainerFrame_GenerateFrame missing; chrome will rely on OnShow hooks only"] = "ContainerFrame_GenerateFrame отсутствует; оформление будет обновляться только через обработчики OnShow"
L["ContainerFrame_Update missing; item quality borders will not auto-refresh"] = "ContainerFrame_Update отсутствует; рамки качества предметов не будут обновляться автоматически"
L["Continue %s?"] = "Продолжить %s?"
L["Convert to Raid"] = "Преобразовать в рейд"
L["Cooldown Manager"] = "Диспетчер времени восстановления"
L["Cooldown Manager layout string (Ctrl+C to copy):"] = "Строка расположения «Диспетчера времени восстановления» (Ctrl+C для копирования):"
L["Cooldown Manager layout: "] = "Расположение «Диспетчера времени восстановления»: "
L["Cooldown Manager Settings"] = "Настройки диспетчера времени восстановления"
L["cooldown widget: SetDrawEdge=%s GetDrawEdge=%s SetSwipeTexture=%s SetReverse=%s"] = "виджет восстановления: SetDrawEdge=%s GetDrawEdge=%s SetSwipeTexture=%s SetReverse=%s"
L["Copy — Ctrl+A then Ctrl+C"] = "Копирование — Ctrl+A, затем Ctrl+C"
L["Copy this build string (Ctrl+C). Talented & the WoWhead/wotlkdb calculators import it too:"] = "Скопируйте строку раскладки талантов (Ctrl+C). Её также можно импортировать в Talented и калькуляторы WoWhead/wotlkdb:"
L["corrupt layout string"] = "повреждённая строка расположения"
L["could not read that string."] = "не удалось прочитать эту строку."
L["could not save."] = "не удалось сохранить."
L["Create Auction"] = "Создать аукцион"
L["CreateUIFrame failed for '"] = "не удалось создать CreateUIFrame для '"
L["Currency pane host missing -- cannot build"] = "область валюты отсутствует — создание невозможно"
L["CURRENCY: "] = "ВАЛЮТА: "
L["Current Bid"] = "Текущая ставка"
L["Custom scale"] = "Пользовательский масштаб"
L["Damage Per Second"] = "Урон в секунду"
L["default|r — it adds four viewers to the middle of your screen, so it waits to be asked. Every "] = "по умолчанию|r — он добавляет четыре панели в центр экрана, поэтому включается только по запросу. Каждая "
L["Defensives, interrupts, CC and escapes."] = "Защитные способности, прерывания, контроль и способы выхода из боя."
L["Delete loadout '%s'?"] = "Удалить набор «%s»?"
L["Delete the equipment set '%s'?"] = "Удалить комплект экипировки «%s»?"
L["Delete the layout \"%s\"?"] = "Удалить расположение «%s»?"
L["Diet: %s"] = "Рацион: %s"
L["Discipline layout and a Holy one do not overwrite each other. Where each viewer sits is "] = "Расположение для «Послушания» и «Света» не перезаписывают друг друга. Положение каждой панели "
L["Dodge Chance"] = "Шанс уклонения"
L["Drag onto Essential or Utility to track it."] = "Перетащите в «Основные» или «Вспомогательные», чтобы отслеживать."
L["Drag to move."] = "Перетащите для перемещения."
L["DragonUI editor API absent; '"] = "API редактора DragonUI отсутствует; '"
L["DragonUI exposes neither ModuleRegistry nor RegisterModule; '"] = "DragonUI не предоставляет ни ModuleRegistry, ни RegisterModule; '"
L["DragonUI New Era"] = "DragonUI New Era"
L["DragonUI.db.profile not available at OnReady; newera settings not initialised."] = "DragonUI.db.profile недоступен в OnReady; настройки newera не инициализированы."
L["DragonUI's editor mode isn't available."] = "Режим редактирования DragonUI недоступен."
L["DragonUI's editor mode to reposition them, and right-click one there for its own layout settings."] = "режим редактирования DragonUI, чтобы переместить их; там же щёлкните по панели правой кнопкой для настройки её расположения."
L["Draw the countdown number on each icon."] = "Показывать число обратного отсчёта на каждом значке."
L["duplicate module registration: "] = "повторная регистрация модуля: "
L["Durability %d / %d"] = "Прочность %d / %d"
L["Each viewer's own settings — size, spacing, orientation, visibility, what its icons "] = "Собственные настройки каждой панели — размер, интервалы, ориентация, видимость и то, какие значки "
L["Earn honorable kills in battlegrounds and world PvP to gain rank."] = "Совершайте почётные убийства на полях боя и в PvP в открытом мире, чтобы повышать звание."
L["edit mode with that viewer selected and its settings already up. Closes this window; not "] = "режим редактирования с уже выбранной панелью и открытыми её настройками. Это окно закроется; "
L["Editor mode can't be opened during combat."] = "Режим редактирования нельзя открыть во время боя."
L["editor mode couldn't be opened."] = "не удалось открыть режим редактирования."
L["Editor mode declined to open."] = "Режим редактирования не был открыт."
L["Editor mode failed to close: "] = "Не удалось закрыть режим редактирования: "
L["Editor mode failed to open: "] = "Не удалось открыть режим редактирования: "
L["editor mode isn't available in this DragonUI build. Type /dui edit to position frames."] = "Режим редактирования недоступен в этой сборке DragonUI. Введите /dui edit, чтобы перемещать рамки."
L["editor settings failed to open: "] = "не удалось открыть настройки режима редактирования: "
L["effect immediately either way, and nothing is forgotten — this switch stores one flag "] = "вступает в силу сразу в любом случае, и ничего не забывается — этот переключатель хранит один флаг "
L["Empty socket"] = "Пустое гнездо"
L["empty talent string"] = "пустая строка талантов"
L["Enable Cooldown Manager"] = "Включить диспетчер времени восстановления"
L["Enable Level Up Display"] = "Включить уведомление о новом уровне"
L["Enabled"] = "Включено"
L["ENCHANTING BAG"] = "СУМКА ЗАЧАРОВАТЕЛЯ"
L["Enchanting Bags"] = "Сумки зачарователя"
L["ENGINEERING BAG"] = "ИНЖЕНЕРНАЯ СУМКА"
L["Engineering Bags"] = "Инженерные сумки"
L["Enter a note for %s:"] = "Введите заметку для %s:"
L["Essential Cooldowns"] = "Основные способности"
L["Everything (no spec)"] = "Всё (без специализации)"
L["Everything recorded is displayed."] = "Отображаются все записанные данные."
L["Everything the Cooldown Manager can be told to do that is not about one viewer's layout. "] = "Все общие настройки диспетчера времени восстановления, не относящиеся к расположению отдельной панели. "
L["Everything you have learned is displayed. Turn on Show Unlearned (cog) to see the rest."] = "Отображается всё изученное. Включите «Показывать неизученные» (значок шестерёнки), чтобы увидеть остальное."
L["Export Layout"] = "Экспорт расположения"
L["Extend"] = "Продлить"
L["Extended"] = "Продлено"
L["fallback highlight burst (flipbook atlas not registered)"] = "резервная вспышка подсветки (атлас flipbook не зарегистрирован)"
L["Find Group"] = "Найти группу"
L["Fist Weapons"] = "Кистевое оружие"
L["Flashes once, the moment the cooldown finishes.|nWorks for every spell."] = "Однократно вспыхивает в момент окончания времени восстановления.|nРаботает для любого заклинания."
L["follows the shared setup, so nothing moves when you tick this, and unticking it "] = "следует общим настройкам, поэтому при включении ничего не перемещается, а отключение "
L["Food & Drink"] = "Еда и напитки"
L["Frame strength"] = "Интенсивность рамки"
L["Friends List"] = "Список друзей"
L["FX Style"] = "Стиль эффектов"
L["Gap between icons. Retail offsets this by -4, so the low end overlaps slightly — that is "] = "Расстояние между значками. В актуальной версии WoW используется смещение -4, поэтому при минимальном значении значки слегка перекрываются — это "
L["GEM BAG"] = "СУМКА ДЛЯ САМОЦВЕТОВ"
L["Gem Bags"] = "Сумки для самоцветов"
L["GetCurrencyListSize/GetCurrencyListInfo unavailable -- Currency pane degraded"] = "GetCurrencyListSize/GetCurrencyListInfo недоступны — раздел валют работает в ограниченном режиме"
L["GetNumFactions/GetFactionInfo unavailable — Reputation pane degraded"] = "GetNumFactions/GetFactionInfo недоступны — раздел репутации работает в ограниченном режиме"
L["GetNumSkillLines/GetSkillLineInfo unavailable — Skills pane degraded"] = "GetNumSkillLines/GetSkillLineInfo недоступны — раздел навыков работает в ограниченном режиме"
L["gives the shared setup back without losing what you changed."] = "возвращает общие настройки, не теряя внесённых вами изменений."
L["Glow active spells"] = "Подсвечивать активные заклинания"
L["Glow while buffed"] = "Подсвечивать во время действия эффекта"
L["Glows during the last %d%% of this buff's|nremaining time."] = "Светится в течение последних %d%%|nоставшегося времени этого эффекта."
L["Glows during the last %d%% of this spell's own|nbuff or debuff."] = "Светится в течение последних %d%% времени|nсобственного положительного или отрицательного эффекта заклинания."
L["Glows for as long as the spell is off cooldown|nand affordable."] = "Светится, пока заклинание не на восстановлении|nи у вас достаточно ресурсов для его применения."
L["Glows for as long as this buff is on you.|n|nThe one that works for a"] = "Светится, пока на вас действует этот эффект.|n|nПодходящий вариант для"
L["Glyph of "] = "Символ "
L["Glyph options"] = "Настройки символов"
L["Glyph Socket"] = "Гнездо символа"
L["GLYPHS"] = "СИМВОЛЫ"
L["Glyphs"] = "Символы"
L["Guild"] = "Гильдия"
L["Guild Control"] = "Управление гильдией"
L["Guild Information"] = "Информация о гильдии"
L["Guild Message Of The Day"] = "Сообщение дня гильдии"
L["Halo the icon gold while the spell's buff (or, for a shaman, its totem) is up."] = "Подсвечивать значок золотым ореолом, пока действует эффект заклинания (или тотем у шамана)."
L["HandleBagEquip invSlot="] = "HandleBagEquip invSlot="
L["Haste rating %d (%.2f%% haste)"] = "Рейтинг скорости %d (скорость %.2f%%)"
L["HERB BAG"] = "СУМКА ТРАВНИКА"
L["Herb Bags"] = "Сумки травника"
L["Hide Passives"] = "Скрывать пассивные"
L["Hide When Inactive"] = "Скрывать, когда неактивно"
L["Highest Rank"] = "Наивысшее звание"
L["Hit Rating"] = "Рейтинг меткости"
L["Honor pane host missing — cannot build"] = "область чести отсутствует — создание невозможно"
L["Honor Points"] = "Очки чести"
L["HONOR: "] = "ЧЕСТЬ: "
L["Honorable Kills"] = "Почётные победы"
L["How many icons before the layout wraps. Vertical orientation reads this as icons per "] = "Количество значков до переноса на следующий ряд. При вертикальной ориентации это число означает количество значков на "
L["Icon and Name"] = "Значок и название"
L["Icon Direction"] = "Направление значков"
L["Icon fit"] = "Подгонка значков"
L["Icon inset"] = "Отступ значка"
L["Icon Limit"] = "Лимит значков"
L["Icon Only"] = "Только значок"
L["Icon Padding"] = "Интервал между значками"
L["Icon Size"] = "Размер значка"
L["Icons and bars"] = "Значки и полосы"
L["Icons only"] = "Только значки"
L["ID: "] = "ID: "
L["Ignore Player"] = "Игнорировать игрока"
L["Import Layout"] = "Импорт расположения"
L["Import…"] = "Импорт…"
L["imported '"] = "импортировано '"
L["In Combat"] = "В бою"
L["Increases the speed that your spells cast by %.2f%%."] = "Увеличивает скорость произнесения заклинаний на %.2f%%."
L["Increases your damage with melee weapons by %.1f damage per second."] = "Увеличивает урон оружием ближнего боя на %.1f ед. в секунду."
L["Increases your damage with ranged weapons by %.1f damage per second."] = "Увеличивает урон оружием дальнего боя на %.1f ед. в секунду."
L["InnerBorder boot failed: "] = "ошибка запуска InnerBorder: "
L["InnerBorder: Inset not built yet"] = "InnerBorder: Inset ещё не создан"
L["INSCRIPTION BAG"] = "СУМКА НАЧЕРТАТЕЛЯ"
L["inset build failed: "] = "не удалось создать Inset: "
L["Inset shrinks the icon, which slides the shadow off it, so raise that one sparingly."] = "Отступ уменьшает значок, из-за чего тень смещается наружу, поэтому увеличивайте это значение умеренно."
L["installed DragonUI_NewEra includes core/Scale.lua AND its line in the .toc, then /reload."] = "в установленном DragonUI_NewEra должен присутствовать core/Scale.lua И соответствующая строка в .toc, затем выполните /reload."
L["installed PutItemInBag wrapper"] = "установлена обёртка PutItemInBag"
L["Instance"] = "Подземелье"
L["into the spec you are in."] = "в текущую специализацию."
L["invalid string"] = "недопустимая строка"
L["Item Enhancement"] = "Улучшение предмета"
L["Item Level"] = "Уровень предмета"
L["ItemBuy QueryAuctionItems error: "] = "ошибка ItemBuy QueryAuctionItems: "
L["items missing"] = "предметы отсутствуют"
L["Join as Party"] = "Присоединиться группой"
L["journal refresh"] = "обновление журнала"
L["keeps running either way, so turning it back on costs nothing."] = "продолжает работать в любом случае, поэтому повторное включение ничего не требует."
L["Keeps this at the front of the list."] = "Закрепляет этот элемент в начале списка."
L["Last Week"] = "На прошлой неделе"
L["Lasts %s sec"] = "Длительность: %s сек."
L["Layout and position both live on the frame itself, in edit mode (/dui edit) — click a viewer "] = "Расположение и положение панели настраиваются непосредственно на самой рамке в режиме редактирования (/dui edit) — выберите панель "
L["layout copied into per-spec buckets"] = "расположение скопировано в отдельные наборы специализаций"
L["Layout: Starter"] = "Расположение: начальное"
L["Layouts"] = "Расположения"
L["Layouts include appearance"] = "Расположения включают внешний вид"
L["Learn the selected talents? Spent points cannot be refunded without a respec."] = "Изучить выбранные таланты? Потраченные очки нельзя вернуть без сброса талантов."
L["LEATHERWORKING BAG"] = "СУМКА КОЖЕВНИКА"
L["Leave Queue"] = "Покинуть очередь"
L["Level %d"] = "Уровень %d"
L["Level %d %s %s"] = "Уровень %d %s %s"
L["Level Range"] = "Диапазон уровней"
L["Level Up Display"] = "Уведомление о новом уровне"
L["level, so this only adds a second copy on top of it. Turn it on if you "] = "уровня, поэтому эта настройка лишь добавляет поверх него вторую копию. Включите её, если "
L["LevelText boot failed: "] = "ошибка запуска LevelText: "
L["List Me"] = "Добавить меня"
L["List My Group"] = "Добавить мою группу"
L["Listed in the Raid Browser"] = "В списке поиска рейда"
L["Load the %s starter layout?\n\nEssential is set to that spec's spells. Everything else for "] = "Загрузить начальное расположение для %s?\n\nВ «Основные» будут добавлены заклинания этой специализации. Всё остальное для "
L["Loading results..."] = "Загрузка результатов..."
L["Loadouts"] = "Наборы"
L["Locked"] = "Заблокировано"
L["Locked socket"] = "Заблокированное гнездо"
L["locked unless that window's mode is set to Custom."] = "заблокировано, если режим этого окна не установлен в «Пользовательский»."
L["Looking for Group"] = "Поиск группы"
L["Looking For Group (Dungeon/Raid Finder)"] = "Поиск группы (подземелья/рейды)"
L["Lvl"] = "Ур."
L["Main Hand"] = "Правая рука"
L["MAJOR GLYPHS"] = "БОЛЬШИЕ СИМВОЛЫ"
L["Mana Regen"] = "Восполнение маны"
L["Marching Ants"] = "Бегущая пунктирная рамка"
L["Metal & Stone"] = "Металлы и камни"
L["MINING BAG"] = "СУМКА ШАХТЁРА"
L["Mining Bags"] = "Сумки шахтёра"
L["MINOR GLYPHS"] = "МАЛЫЕ СИМВОЛЫ"
L["MISS atlas "] = "ОТСУТСТВУЕТ атлас "
L["MISS layout "] = "ОТСУТСТВУЕТ расположение "
L["MISS mask atlas "] = "ОТСУТСТВУЕТ атлас маски "
L["MISS path "] = "ОТСУТСТВУЕТ путь "
L["ModelArea boot failed: "] = "ошибка запуска ModelArea: "
L["ModelControls boot failed: "] = "ошибка запуска ModelControls: "
L["Modern Communities-style guild window (Roster / Info / Chat)."] = "Современное окно гильдии в стиле «Сообществ» (состав / информация / чат)."
L["Modern friends window (Friends / Ignore / Who) with a Guild tab."] = "Современное окно общения (друзья / игнор / кто) со вкладкой гильдии."
L["Modern visual shell for Buy/Sell/Auctions with optional Auctionator tab embedding."] = "Современное оформление покупки, продажи и аукционов с возможностью встроить вкладку Auctionator."
L["ModuleRegistry:Register failed"] = "ошибка ModuleRegistry:Register"
L["ModuleRegistry:Register failed for '"] = "ошибка ModuleRegistry:Register для '"
L["Move to "] = "Переместить в "
L["Movement Speed"] = "Скорость передвижения"
L["Name Only"] = "Только название"
L["Name this imported loadout:"] = "Название импортированного набора:"
L["Name this layout:"] = "Название расположения:"
L["Name this loadout (saves your current spec):"] = "Название набора (сохраняет текущую специализацию):"
L["NE.itemgrid missing (core/ItemGrid.lua not loaded); combined bag unavailable"] = "NE.itemgrid отсутствует (core/ItemGrid.lua не загружен); общая сумка недоступна"
L["NE.menu unavailable; the settings cog has nothing to open"] = "NE.menu недоступно; кнопке настроек нечего открывать"
L["NE.modules.Register absent; character panel not booted"] = "NE.modules.Register отсутствует; окно персонажа не запущено"
L["NE.modules.Register absent; collections window not booted"] = "NE.modules.Register отсутствует; окно коллекций не запущено"
L["NE.modules.Register absent; combined bag not booted"] = "NE.modules.Register отсутствует; общая сумка не запущена"
L["NE.modules.Register absent; retail bags not booted"] = "NE.modules.Register отсутствует; сумки в стиле актуальной версии не запущены"
L["NE.modules.Register absent; spellbook window not booted"] = "NE.modules.Register отсутствует; книга заклинаний не запущена"
L["NE.modules.Register failed for '"] = "ошибка NE.modules.Register для '"
L["New Era"] = "Новая эра"
L["New Layout"] = "Новое расположение"
L["NewEra panels ported onto DragonUI. Toggle a panel below to enable or disable it. "] = "Панели NewEra перенесены в DragonUI. Включайте или отключайте их ниже. "
L["nineslice art missing for "] = "отсутствует текстура NineSlice для "
L["NO ACTIVE EFFECTS"] = "НЕТ АКТИВНЫХ ЭФФЕКТОВ"
L["No auctions found for this item."] = "Аукционы для этого предмета не найдены."
L["no class bg atlas for "] = "отсутствует атлас фона класса для "
L["no font object resolved from "] = "не удалось определить объект шрифта из "
L["No layout"] = "Нет расположения"
L["No listings."] = "Нет лотов."
L["no native engine; custom backend live"] = "родной движок отсутствует; используется пользовательский бэкенд"
L["no native GetContainerItemQuestInfo on this client; returns empty (no quest item)"] = "в этом клиенте нет встроенного GetContainerItemQuestInfo; возвращается пустое значение (не предмет задания)"
L["no race background for '"] = "отсутствует фон расы для '"
L["No reputations."] = "Нет данных о репутации."
L["No results match your filters."] = "Нет результатов, соответствующих фильтрам."
L["No results. Adjust filters and search again."] = "Нет результатов. Измените фильтры и повторите поиск."
L["No scaling"] = "Без масштабирования"
L["No skills."] = "Нет навыков."
L["no uiMapID->map metadata on 3.3.5; returns nil"] = "в 3.3.5 нет метаданных uiMapID->карта; возвращается nil"
L["NO-LOCAL atlas "] = "НЕТ ЛОКАЛЬНОГО атласа "
L["not a Cooldown Manager layout"] = "это не расположение диспетчера времени восстановления"
L["not a Cooldown Manager layout string"] = "это не строка расположения диспетчера времени восстановления"
L["Not Collected"] = "Не собрано"
L["Not Displayed"] = "Не отображается"
L["Not displayed on any viewer"] = "Не отображается ни на одной панели"
L["Not yet collected"] = "Ещё не собрано"
L["Not yet learned"] = "Ещё не изучено"
L["Nothing here yet — drag a buff in to pin it."] = "Здесь пока ничего нет — перетащите эффект, чтобы закрепить его."
L["nothing pasted"] = "ничего не вставлено"
L["nothing to export."] = "нечего экспортировать."
L["Nothing to undo. It covers LAYOUTS, not the settings|non these tabs — a "] = "Отменять нечего. Отмена относится к РАСПОЛОЖЕНИЯМ, а не к настройкам|nна этих вкладках — "
L["of their own."] = "своих собственных."
L["off (Settings > Buffed spells)"] = "выкл. (Настройки > Заклинания с эффектами)"
L["Off by default. Turn on to show the four viewers; turn off to hide them again. Takes "] = "По умолчанию выключено. Включите, чтобы показать четыре панели; выключите, чтобы снова скрыть их. "
L["Off Hand"] = "Левая рука"
L["Off, both specs share one set of lists. Turning it on copies the layout you have now "] = "Если выключено, обе специализации используют один набор списков. При включении текущее расположение копируется "
L["Off, loading or importing a layout changes only what you track — lists, tracked buffs, "] = "Если выключено, загрузка или импорт расположения меняет только отслеживание — списки, отслеживаемые эффекты, "
L["Off, orientation, icons per row, size, padding and opacity are one setup for every "] = "Если выключено, ориентация, число значков в ряду, размер, интервалы и прозрачность общие для каждой "
L["Offensive burst and damage cooldowns."] = "Атакующие усиления и способности с временем восстановления."
L["Officer Note"] = "Заметка офицера"
L["On by default. Turn off to stop the banner appearing on level-up; the harvest "] = "По умолчанию включено. Выключите, чтобы баннер повышения уровня не появлялся; сбор данных "
L["onBoot error in "] = "ошибка onBoot в "
L["One-Handed Axes"] = "Одноручные топоры"
L["One-Handed Maces"] = "Одноручное дробящее"
L["One-Handed Swords"] = "Одноручные мечи"
L["Opacity"] = "Прозрачность"
L["Open Cooldown Manager"] = "Открыть диспетчер времени восстановления"
L["Open the Auction House first to run Auctionator scans."] = "Сначала откройте аукционный дом, чтобы запустить сканирование Auctionator."
L["Opens a share string you can copy with Ctrl+C.|nIt covers this "] = "Открывает строку для обмена, которую можно скопировать через Ctrl+C.|nОна включает "
L["Opens the Cooldown Manager window (/cdm) on its Spells tab. Needs the module on — "] = "Открывает окно «Диспетчер времени восстановления» (/cdm) на вкладке «Заклинания». Модуль должен быть включён — "
L["Options"] = "Настройки"
L["options section '"] = "раздел настроек '"
L["options, under New Era > Cooldown Manager."] = "настройки в разделе Новая эра > Диспетчер времени восстановления."
L["OptionsPanel:RegisterTab failed: "] = "ошибка OptionsPanel:RegisterTab: "
L["Orientation"] = "Ориентация"
L["Our all-in-one bag window. Turn OFF to use the stock Blizzard bags instead. Reload (/reload) to apply."] = "Наше окно «все сумки в одной». Выключите, чтобы использовать стандартные сумки Blizzard. Для применения выполните /reload."
L["Overwrite the equipment set '%s' with your currently equipped items?"] = "Заменить комплект экипировки «%s» на предметы, надетые сейчас?"
L["Page %d of %d (items %d-%d of %d, auctions: %d%s)"] = "Страница %d из %d (предметы %d-%d из %d, аукционов: %d%s)"
L[" -- partial scan"] = " — неполное сканирование"
L["Page %d/%d"] = "Страница %d/%d"
L["Page 1 of 1"] = "Страница 1 из 1"
L["Pandemic Border"] = "Граница пандемии"
L["pandemic ring: art %s, %d spell(s) set to Refresh, %d ring(s) up, %d pulsing"] = "кольцо пандемии: текстура %s, %d заклин. в режиме «Обновить», колец активно: %d, пульсируют: %d"
L["Panels appear here as their modules load."] = "Панели появляются здесь по мере загрузки их модулей."
L["Parry Chance"] = "Шанс парирования"
L["Paste a Cooldown Manager layout string:"] = "Вставьте строку расположения диспетчера времени восстановления:"
L["Paste a talent string or calculator URL (Talented / WoWhead / wotlkdb):"] = "Вставьте строку талантов или ссылку на калькулятор (Talented / WoWhead / wotlkdb):"
L["people's cooldowns, food and flasks."] = "времени восстановления других игроков, еды и настоев."
L["Per Item"] = "За предмет"
L["Pet"] = "Питомец"
L["Pet Journal"] = "Журнал питомцев"
L["Pet pane host missing — cannot build"] = "область питомца отсутствует — создание невозможно"
L["PET: "] = "ПИТОМЕЦ: "
L["Picks a random favorite usable here (or any, if none are favorited)."] = "Выбирает случайного избранного, доступного здесь (или любого, если избранных нет)."
L["Place a bid of %s?"] = "Сделать ставку %s?"
L["PlaceAuctionBid(bid) error: "] = "ошибка PlaceAuctionBid(bid): "
L["PlaceAuctionBid(buyout) error: "] = "ошибка PlaceAuctionBid(buyout): "
L["Play the level-up sound"] = "Воспроизводить звук повышения уровня"
L["%s points available"] = "Доступно очков талантов: %s"
L["Points may land on the wrong talents."] = "Очки могут попасть не в те таланты."
L["Points may not line up."] = "Очки могут распределиться неверно."
L["Power Infusion"] = "Придание сил"
L["preview API unavailable"] = "API предпросмотра недоступен"
L["Professions"] = "Профессии"
L["Promote"] = "Повысить"
L["Promote to Assistant"] = "Назначить помощником"
L["Promote to Raid Leader"] = "Назначить лидером рейда"
L["PutItemInBag missing; bag-swap disabled"] = "PutItemInBag отсутствует; замена сумок отключена"
L["PvP API unavailable — Honor pane degraded"] = "API PvP недоступен — раздел чести работает в ограниченном режиме"
L["quality borders. Disable to keep the stock Blizzard bags. Reload (/reload) to apply."] = "рамки качества. Выключите, чтобы оставить стандартные сумки Blizzard. Для применения выполните /reload."
L["QueryAuctionItems error: "] = "ошибка QueryAuctionItems: "
L["Quest Items"] = "Предметы для заданий"
L["Raid Browser"] = "Поиск рейда"
L["Raid Info"] = "Информация о рейде"
L["Random Dungeon"] = "Случайное подземелье"
L["Random dungeon cooldown"] = "Время восстановления случайного подземелья"
L["Ranged Attack Power"] = "Сила атаки дальнего боя"
L["Ready Check"] = "Проверка готовности"
L["ready flash: %s"] = "вспышка готовности: %s"
L["Ready Sound"] = "Звук готовности"
L["Ready sound: "] = "Звук готовности: "
L["rebuild failed for '"] = "не удалось пересоздать для '"
L["rebuild failed: "] = "не удалось пересоздать: "
L["Reduces "] = "Снижает "
L["Refresh Window"] = "Обновить окно"
L["RefreshCurrency error: "] = "ошибка RefreshCurrency: "
L["refreshDetailRows error: "] = "ошибка refreshDetailRows: "
L["RefreshHonor error: "] = "ошибка RefreshHonor: "
L["RefreshReputation error: "] = "ошибка RefreshReputation: "
L["refreshResults error: "] = "ошибка refreshResults: "
L["RefreshSkills error: "] = "ошибка RefreshSkills: "
L["RefreshTitles error: "] = "ошибка RefreshTitles: "
L["Register called without a name"] = "Register вызван без имени"
L["RegisterEditableFrame failed for '"] = "ошибка RegisterEditableFrame для '"
L["RegisterHUDFrame needs a name and frame; ignored."] = "RegisterHUDFrame требует имя и рамку; вызов проигнорирован."
L["RegisterModule failed"] = "ошибка RegisterModule"
L["RegisterModule failed for '"] = "ошибка RegisterModule для '"
L["RegisterMover failed for '"] = "ошибка RegisterMover для '"
L["RegisterPanel called without a spec.id; ignored."] = "RegisterPanel вызван без spec.id; вызов проигнорирован."
L["Reload (/reload) to apply."] = "Для применения выполните /reload."
L["Remove every per-spell alert and ready sound. Spell lists and positions are not affected."] = "Удалить все оповещения и звуки готовности для отдельных заклинаний. Списки заклинаний и положение рамок не изменятся."
L["Remove Favorite"] = "Удалить из избранного"
L["Remove Favourite"] = "Удалить из избранного"
L["Remove Friend"] = "Удалить из друзей"
L["Remove this glyph?"] = "Удалить этот символ?"
L["rename failed (name taken?)."] = "не удалось переименовать (имя уже занято?)."
L["Rename loadout:"] = "Переименовать набор:"
L["Rename specialization"] = "Переименовать специализацию"
L["Rename this specialization (letters only, max "] = "Переименуйте эту специализацию (только буквы, максимум "
L["Reputation pane host missing — cannot build"] = "область репутации отсутствует — создание невозможно"
L["REPUTATION: "] = "РЕПУТАЦИЯ: "
L["Requires higher level"] = "Требуется более высокий уровень"
L["Requires the "] = "Требуется "
L["Requires the Auction House window to be open."] = "Требуется открыть окно аукционного дома."
L["Requires: "] = "Требуется: "
L["Reset "] = "Сбросить "
L["Reset spell and buff lists"] = "Сбросить списки заклинаний и эффектов"
L["Reset Spell Lists"] = "Сбросить списки заклинаний"
L["Reset this class's Cooldown Manager spell and buff lists to their defaults?\n\nOther classes, alerts, sounds and frame positions are not affected."] = "Сбросить списки заклинаний и эффектов диспетчера времени восстановления для этого класса?\n\nДругие классы, оповещения, звуки и положение рамок не изменятся."
L["Reset to Default"] = "Восстановить по умолчанию"
L["Reset to the starter layout?\n\nThis reverts every Cooldown Manager edit — spells, tracked "] = "Вернуться к начальному расположению?\n\nБудут отменены все изменения диспетчера времени восстановления — заклинания, отслеживаемые "
L["Resets In"] = "Сброс через"
L["Restore the curated defaults and the auto-track window for THIS CLASS, clearing its "] = "Восстановить подготовленные значения по умолчанию и окно автоотслеживания ДЛЯ ЭТОГО КЛАССА, очистив его "
L["retail flipbook sprite (22 frames)"] = "спрайт flipbook из актуальной версии (22 кадра)"
L["Retail's behaviour: while buffed, the icon counts down the BUFF. Off, it counts down the "] = "Поведение как в актуальной версии: пока действует эффект, значок отсчитывает время ЭФФЕКТА. Если выключено, отсчитывается "
L["Retail's Cooldown Manager, driven from curated per-class cooldown lists. |cffffcc55Off by "] = "Диспетчер времени восстановления как в актуальной версии, работающий на подготовленных списках способностей для каждого класса. |cffffcc55По умолчанию "
L["Retail's level-up banner. What it announces is read from |cffffcc55this server|r — "] = "Баннер повышения уровня как в актуальной версии. Его содержимое берётся с |cffffcc55этого сервера|r — "
L["Retail-style crafting window for all professions."] = "Окно ремесла в стиле актуальной версии для всех профессий."
L["Revert"] = "Вернуть"
L["Revert Changes"] = "Отменить изменения"
L["right here."] = "прямо здесь."
L["Right-click to dismiss"] = "Щёлкните правой кнопкой, чтобы закрыть"
L["Right-click to summon"] = "Щёлкните правой кнопкой, чтобы призвать"
L["Role check in progress…"] = "Идёт проверка ролей…"
L["Save current spec…"] = "Сохранить текущую специализацию…"
L["Save, load, import and export the whole|nCooldown Manager setup for this class."] = "Сохраняйте, загружайте, импортируйте и экспортируйте всю|nнастройку диспетчера времени восстановления для этого класса."
L["saved '"] = "сохранено '"
L["Scale mode"] = "Режим масштаба"
L["Scaling controls need a newer DragonUI options panel (AddSlider/AddDropdown)."] = "Для управления масштабом нужна более новая панель настроек DragonUI (AddSlider/AddDropdown)."
L["Scan AH"] = "Сканировать аукцион"
L["Scanning page %d of %d..."] = "Сканирование страницы %d из %d..."
L["Scroll of "] = "Свиток "
L["Search failed: "] = "Ошибка поиска: "
L["Searches Auctionator for the selected recipe and its reagents."] = "Ищет через Auctionator выбранный рецепт и необходимые для него реагенты."
L["Searching..."] = "Поиск..."
L["seeded the "] = "создано начальное содержимое "
L["Select All"] = "Выбрать всё"
L["SelectSidebar(3): ShowEquipManager absent; staying on stats"] = "SelectSidebar(3): ShowEquipManager отсутствует; остаёмся на характеристиках"
L["Send Message"] = "Отправить сообщение"
L["Separate appearance per character"] = "Отдельный внешний вид для каждого персонажа"
L["Separate layout per spec"] = "Отдельное расположение для каждой специализации"
L["Server uses custom talents"] = "На сервере используются нестандартные таланты"
L["Set Favorite"] = "Добавить в избранное"
L["Set Name"] = "Задать имя"
L["Set Name:"] = "Задать имя:"
L["Set Note"] = "Добавить заметку"
L["setting — which spells and buffs are tracked, each viewer's layout, size and visibility, alerts "] = "настройка — какие заклинания и эффекты отслеживаются, расположение, размер и видимость каждой панели, оповещения "
L["settings cog build failed: "] = "не удалось создать кнопку настроек: "
L["Short-duration buffs and procs, as depleting bars."] = "Кратковременные эффекты и срабатывания в виде убывающих полос."
L["Short-duration buffs and procs, as icons."] = "Кратковременные эффекты и срабатывания в виде значков."
L["show — live on the frame, in edit mode, where you can see what you are changing. These open "] = "отображаются непосредственно на рамке в режиме редактирования, где сразу видно изменения. Эти кнопки открывают "
L["Show a slot only while its aura is active."] = "Показывать ячейку только во время действия её ауры."
L["Show a tooltip when hovering an icon."] = "Показывать подсказку при наведении на значок."
L["Show All Ranks"] = "Показывать все ранги"
L["Show Companions tab"] = "Показывать вкладку спутников"
L["Show every short buff the moment it lands, without assigning it first. Convenient on a "] = "Сразу показывать каждый кратковременный эффект при его появлении, без предварительного назначения. Удобно для "
L["Show glyph effects"] = "Показывать эффекты символов"
L["Show glyph names"] = "Показывать названия символов"
L["Show Mounts tab"] = "Показывать вкладку транспорта"
L["Show Offline"] = "Показывать не в сети"
L["Show on Backpack"] = "Показывать на рюкзаке"
L["Show second page"] = "Показывать вторую страницу"
L["Show single page"] = "Показывать одну страницу"
L["Show the buff's time, not the cooldown"] = "Показывать время эффекта, а не время восстановления"
L["Show them as"] = "Отображать как"
L["Show this viewer at all. The editor handle stays either way, so this is reversible from "] = "Показывать эту панель вообще. Маркер редактора остаётся доступен в любом случае, поэтому настройку можно вернуть через "
L["Show Timer"] = "Показывать таймер"
L["Show Tooltips"] = "Показывать подсказки"
L["Show Unlearned"] = "Показывать неизученные"
L["sidebar build failed: "] = "не удалось создать боковую панель: "
L["sidebar tabs build failed: "] = "не удалось создать вкладки боковой панели: "
L["single stretch (bar too narrow)"] = "одно растяжение (полоса слишком узкая)"
L["size, padding and opacity the layout was saved with.|n|nLayouts always SAVE "] = "размером, интервалами и прозрачностью, с которыми расположение было сохранено.|n|nРасположения всегда СОХРАНЯЮТ "
L["Skills pane host missing — cannot build"] = "область навыков отсутствует — создание невозможно"
L["SKILLS: "] = "НАВЫКИ: "
L["slotframe "] = "slotframe "
L["SlotFrames boot failed: "] = "ошибка запуска SlotFrames: "
L["Social"] = "Общение"
L["Social (Friends/Who/Guild/Chat/Raid)"] = "Общение (друзья/кто/гильдия/чат/рейд)"
L["Sort Per Item"] = "Сортировать по цене за предмет"
L["SOUL BAG"] = "СУМКА ДУШ"
L["Soul Bags"] = "Сумки душ"
L["Source "] = "Источник "
L["Spec "] = "Специализация "
L["Specific Dungeons"] = "Выбранные подземелья"
L["Spell "] = "Заклинание "
L["Spell Crit"] = "Критический удар заклинаниями"
L["Spell Hit"] = "Меткость заклинаний"
L["spell lists, aura assignments and trinket placement. Other classes, alerts, sounds "] = "списки заклинаний, назначения аур и размещение аксессуаров. Другие классы, оповещения, звуки "
L["Spell Power"] = "Сила заклинаний"
L["Spellbook"] = "Книга заклинаний"
L["spell's cooldown and the glow alone marks it as buffed — which is clearer when the "] = "время восстановления заклинания, а свечение лишь показывает, что эффект активен — так понятнее, когда "
L["Spent: %d"] = "Потрачено: %d"
L["staged '"] = "подготовлено '"
L["Starter Layout"] = "Начальное расположение"
L["starter reset.|n|nOne step, and only for this session."] = "начального сброса.|n|nОдно действие, только для текущего сеанса."
L["Starting Bid"] = "Начальная ставка"
L["sub-boot error in "] = "ошибка подзапуска в "
L["Summon Random Favorite Companion"] = "Призвать случайного избранного спутника"
L["Summon Random Favorite Mount"] = "Призвать случайный избранный транспорт"
L["Tab "] = "Вкладка "
L["tab build failed: "] = "не удалось создать вкладку: "
L["Tags exported builds with this realm so imports onto other layouts warn first."] = "Помечает экспортированные раскладки этим игровым миром, чтобы при импорте в другие раскладки появлялось предупреждение."
L["talent data unavailable"] = "данные талантов недоступны"
L["Talent specs"] = "Специализации"
L["Talents"] = "Таланты"
L["Talents Panel"] = "Окно талантов"
L["that layout is for a %s"] = "это расположение предназначено для %s"
L["that viewer doesn't exist yet — it builds at login."] = "эта панель ещё не существует — она создаётся при входе в игру."
L["The frame is a soft shadow that falls on the icon's outer edge, so it only shows where "] = "Рамка представляет собой мягкую тень по внешнему краю значка, поэтому видна только там, где "
L["The full curated list for your class, both specs' spells|nincluded. "] = "Полный подготовленный список для вашего класса, включая|nзаклинания обеих специализаций. "
L["The modern Dragonflight character window. Disable to keep the stock Blizzard frame."] = "Современное окно персонажа в стиле Dragonflight. Выключите, чтобы оставить стандартное окно Blizzard."
L["The modern Dragonflight Collections window (Mounts & Pet Journal). Opens on the companion keybind."] = "Современное окно «Коллекции» в стиле Dragonflight (транспорт и журнал питомцев). Открывается клавишей спутников."
L["The modern Dragonflight spellbook window. Disable to keep the stock Blizzard spellbook."] = "Современное окно книги заклинаний в стиле Dragonflight. Выключите, чтобы оставить стандартную книгу заклинаний Blizzard."
L["The modern talents window. Turn off to use the standard Blizzard talent window."] = "Современное окно талантов. Выключите, чтобы использовать стандартное окно талантов Blizzard."
L["the stock look, not a bug."] = "это стандартный внешний вид, а не ошибка."
L["the window configures the viewers, so it goes away with them."] = "окно настраивает панели, поэтому при их отключении оно тоже исчезает."
L["There are no raids available for your level."] = "Для вашего уровня нет доступных рейдов."
L["there for its own settings, or use the buttons just below to go straight to one."] = "там для собственных настроек панели, либо воспользуйтесь кнопками ниже, чтобы сразу перейти к нужной."
L["there is icon underneath it. Strength draws it more than once to deepen it — that is also "] = "под ней есть значок. Параметр интенсивности рисует тень несколько раз, делая её темнее — это тоже "
L["This build was made for %s, which uses a different talent layout. "] = "Эта раскладка создана для %s, где используется другая схема талантов. "
L["This feature becomes available at level %d."] = "Эта функция становится доступна на %d-м уровне."
L["This is what the Cooldown Manager shipped with|nbefore per-spec starters."] = "Так выглядел диспетчер времени восстановления|nдо появления отдельных начальных раскладок для специализаций."
L["This looks like a standard WotLK build, but your server uses custom talents. "] = "Это похоже на стандартную раскладку WotLK, но на вашем сервере используются нестандартные таланты. "
L["this server"] = "этот сервер"
L["This Week"] = "На этой неделе"
L["Time in queue: %s"] = "Время в очереди: %s"
L["Time Left"] = "Осталось времени"
L["TITLES: "] = "ЗВАНИЯ: "
L["to stock. Nothing else is affected, and it cannot be undone."] = "к стандартному виду. Больше ничего не затрагивается, и отменить это действие нельзя."
L["To use this on an action bar: open Macros (ESC > Macros), find the unnamed icon-only macro matching this icon, and drag it onto your bar."] = "Чтобы использовать это на панели команд: откройте «Макросы» (ESC > Макросы), найдите безымянный макрос только со значком, совпадающим с этим значком, и перетащите его на панель команд."
L["Toggle Collections (Mounts & Pets)"] = "Открыть/закрыть коллекции (транспорт и питомцы)"
L["Toggle Guild Window"] = "Открыть/закрыть окно гильдии"
L["Toggle slot name labels and the active-effects list."] = "Показывать или скрывать подписи ячеек и список активных эффектов."
L["Toggle Social Window"] = "Открыть/закрыть окно общения"
L["total %d exceeds %d"] = "итого %d превышает %d"
L["Total Mounts"] = "Всего транспорта"
L["Total Price"] = "Общая цена"
L["Total: %d"] = "Всего: %d"
L["Tracked auras, trinkets, alerts and frame positions are not affected."] = "Отслеживаемые ауры, аксессуары, оповещения и положение рамок не изменяются."
L["Tracked automatically. Drag it into a section to pin it there."] = "Отслеживается автоматически. Перетащите в раздел, чтобы закрепить."
L["Tracked Bars"] = "Отслеживаемые полосы"
L["Tracked Buffs"] = "Отслеживаемые эффекты"
L["Tracked Buffs tab, where you can assign the ones you want. Nothing appears on screen until "] = "вкладке «Отслеживаемые эффекты», где можно назначить нужные. На экране ничего не появится, пока "
L["Trade Goods"] = "Хозяйственные товары"
L["Training Points"] = "Очки обучения"
L["Tree "] = "Ветка "
L["trinkets, alerts and sounds. On, it also applies the orientation, icons per row, "] = "аксессуары, оповещения и звуки. Если включено, также применяются ориентация, число значков в ряду, "
L["two differ, as on Prayer of Mending."] = "они различаются, как у «Молитвы восстановления»."
L["Two-Handed Axes"] = "Двуручные топоры"
L["Two-Handed Maces"] = "Двуручное дробящее"
L["Two-Handed Swords"] = "Двуручные мечи"
L["Type /cdm to open the Cooldown Manager window."] = "Введите /cdm, чтобы открыть диспетчер времени восстановления."
L["Uncheck All"] = "Снять все отметки"
L["Undoes the last layout change — applying a layout,|nimporting one, or the "] = "Отменяет последнее изменение расположения — применение расположения,|nего импорт или "
L["Unified retail-style Group Finder over the Dungeon Finder + Raid Browser."] = "Единый поиск группы в стиле актуальной версии поверх поиска подземелий и поиска рейда."
L["Unlist Me"] = "Убрать меня из списка"
L["Unlist My Group"] = "Убрать мою группу из списка"
L["Unlock Spec"] = "Разблокировать специализацию"
L["unrecognised talent string"] = "нераспознанная строка талантов"
L["Usable Items Only"] = "Только доступные предметы"
L["Use DragonUI's window in place of the Blizzard default. Changes take effect after a /reload."] = "Использовать окно DragonUI вместо стандартного окна Blizzard. Изменения вступят в силу после /reload."
L["Use Starter Layout"] = "Использовать начальное расположение"
L["Use UI scale"] = "Использовать масштаб интерфейса"
L["Utility Cooldowns"] = "Вспомогательные способности"
L["Very Long"] = "Очень долго"
L["View Log"] = "Показать журнал"
L["viewer '"] = "панель '"
L["viewer art: all %d atlases registered"] = "оформление панели: зарегистрированы все %d атласов"
L["viewer art: MISSING %d of %d -- %s"] = "оформление панели: ОТСУТСТВУЕТ %d из %d — %s"
L["viewer event '"] = "событие панели '"
L["Viewer layout"] = "Расположение панели"
L["viewer's own size and position revert|nfrom its edit-mode panel instead."] = "размер и положение самой панели вместо этого сбрасываются|nв её настройках режима редактирования."
L["Visibility"] = "Видимость"
L["want /nelevelup previews to make a sound, since those fire no game sound "] = "если вы хотите, чтобы предпросмотр /nelevelup сопровождался звуком, поскольку сам он не запускает игровой звук "
L["Watch this currency at the bottom of your bags (up to 3)."] = "Отслеживать эту валюту внизу сумок (не более 3)."
L["Weapon Enchantment"] = "Чары для оружия"
L["what makes its rounded corners read, since the icons themselves cannot be rounded here. "] = "именно это делает скруглённые углы визуально заметными, поскольку сами значки здесь нельзя скруглить. "
L["When this viewer is on screen at all. Hidden still leaves the editor handle here."] = "Определяет, когда эта панель вообще видна на экране. Даже в скрытом состоянии маркер редактора остаётся здесь."
L["Which of the two buff viewers auto-tracked buffs land in."] = "На какую из двух панелей эффектов попадут автоматически отслеживаемые эффекты."
L["Which spells and buffs you track is remembered separately for each talent spec, so a "] = "Какие заклинания и эффекты вы отслеживаете, запоминается отдельно для каждой специализации талантов, поэтому "
L["Window Scaling"] = "Масштаб окна"
L["wraps legacy GetPlayerMapPosition; ignores uiMapID arg, uses current map"] = "обёртка над устаревшим GetPlayerMapPosition; аргумент uiMapID игнорируется, используется текущая карта"
L["You are listed in the Raid Browser."] = "Вы добавлены в список поиска рейда."
L["You are not saved to any instances."] = "Вы не сохранены ни в одном подземелье."
L["You can watch at most %d currencies on your backpack."] = "На рюкзаке можно отслеживать не более %d валют."
L["You cannot list for raids while in the Dungeon Finder queue."] = "Нельзя добавляться в поиск рейда, находясь в очереди поиска подземелий."
L["You do not have a pet."] = "У вас нет питомца."
L["you do."] = "есть."
L["You have no auctions."] = "У вас нет аукционов."
L["You have no bids."] = "У вас нет ставок."
L["You have not learned any companions."] = "Вы не изучили ни одного спутника."
L["You have not learned any mounts."] = "Вы не изучили ни одного средства передвижения."
L["You have nothing equipped that can take durability damage."] = "На вас нет предметов, которые могут терять прочность."
L["You have too many equipment sets."] = "У вас слишком много комплектов экипировки."
L["You haven't earned any titles."] = "У вас нет полученных званий."
L["You may not list for this raid"] = "Вы не можете добавиться в список этого рейда"
L["You may not queue for this"] = "Вы не можете встать в эту очередь"
L["You may not queue for this dungeon"] = "Вы не можете встать в очередь этого подземелья"
L["You recently deserted a dungeon."] = "Недавно вы покинули подземелье до его завершения."
L["Your pet's maximum health."] = "Максимальный запас здоровья вашего питомца."
L["Your running speed. 100%% is normal (%d yards/second); mounts and speed effects raise it,"] = "Ваша скорость бега. 100%% — обычная скорость (%d ярдов/сек.); средства передвижения и эффекты скорости увеличивают её,"
-- ============================================================================
-- GENERATED UI BACKLOG (qa/localize-hardcoded.ps1)
-- ============================================================================
L["All Bags"] = "Все сумки"
L["Always"] = "Всегда"
L["Armor"] = "Броня"
L["Arrow"] = "Стрела"
L["Auto-empty old bag when swapping"] = "Автоматически освобождать старую сумку при замене"
L["Auto-sell junk at merchants"] = "Автоматически продавать хлам торговцам"
L["Bag Options"] = "Настройки сумок"
L["Bags"] = "Сумки"
L["Bandage"] = "Бинт"
L["Blue"] = "Синий"
L["Book"] = "Книга"
L["Bows"] = "Луки"
L["Bullet"] = "Пуля"
L["Category (smart)"] = "Категория (умная)"
L["Cloth"] = "Ткань"
L["Consumable"] = "Расходуемое"
L["Container"] = "Сумка"
L["Daggers"] = "Кинжалы"
L["Devices"] = "Устройства"
L["Elemental"] = "Стихии"
L["Elixir"] = "Эликсир"
L["Explosives"] = "Взрывчатка"
L["Flask"] = "Настой"
L["Gems"] = "Самоцветы"
L["Green"] = "Зелёный"
L["Guns"] = "Огнестрельное"
L["Herb"] = "Трава"
L["Hidden"] = "Скрыто"
L["Holiday"] = "Праздничное"
L["Horizontal"] = "Горизонтально"
L["Idols"] = "Идолы"
L["Junk"] = "Хлам"
L["Keys"] = "Ключи"
L["Leather"] = "Кожа"
L["Left"] = "Влево"
L["Librams"] = "Манускрипты"
L["Mail"] = "Кольчужные"
L["Materials"] = "Материалы"
L["Meat"] = "Мясо"
L["Merchant"] = "Торговец"
L["Meta"] = "Особый"
L["Miscellaneous"] = "Разное"
L["Name"] = "Название"
L["New Talent Point"] = "Новое очко талантов"
L["New Talent Points"] = "Новые очки талантов"
L["Not enough free space to swap that bag."] = "Недостаточно свободного места для замены этой сумки."
L["Orange"] = "Оранжевый"
L["Other"] = "Другое"
L["Parts"] = "Детали"
L["Plate"] = "Латы"
L["Polearms"] = "Древковое"
L["Potion"] = "Зелье"
L["Prismatic"] = "Радужный"
L["Projectile"] = "Боеприпасы"
L["Purple"] = "Фиолетовый"
L["Quality"] = "Качество"
L["Quest"] = "Задание"
L["Quiver"] = "Колчан"
L["Reagent"] = "Реагент"
L["Recipe"] = "Рецепт"
L["Red"] = "Красный"
L["Red-tint unusable items"] = "Подсвечивать красным недоступные предметы"
L["Retail bags"] = "Сумки в стиле актуальной версии"
L["Reverse sort order"] = "Обратный порядок сортировки"
L["Right"] = "Вправо"
L["Scroll"] = "Свиток"
L["Search"] = "Поиск"
L["Separate specialty bags"] = "Отделять специализированные сумки"
L["Settings"] = "Настройки"
L["Shields"] = "Щиты"
L["Shift-right-click to stop watching"] = "Shift+ПКМ — перестать отслеживать"
L["Show item level on items"] = "Показывать уровень предметов"
L["Show keyring row"] = "Показывать строку связки ключей"
L["Sigils"] = "Печати"
L["Simple"] = "Простой"
L["Sold %d junk item(s)."] = "Продано предметов-хлама: %d."
L["Sort Bags"] = "Сортировать сумки"
L["Sort by"] = "Сортировать по"
L["Sorting…"] = "Сортировка…"
L["Sparkles"] = "Искры"
L["Spells"] = "Заклинания"
L["Staves"] = "Посохи"
L["Swapping bag…"] = "Замена сумки…"
L["The same setting as Enable Item Level in DragonUI's options (Enhancements > Item Level). Covers the character panel and every other frame too."] = "Та же настройка, что и «Показывать уровень предметов» в параметрах DragonUI (Улучшения > Уровень предмета). Действует также на окно персонажа и все остальные рамки."
L["Thrown"] = "Метательное"
L["Totems"] = "Тотемы"
L["Turn on Item Level in DragonUI's options (Enhancements > Item Level) first."] = "Сначала включите «Показывать уровень предметов» в параметрах DragonUI (Улучшения > Уровень предмета)."
L["Vertical"] = "Вертикально"
L["Wand"] = "Жезлы"
L["Weapons"] = "Оружие"
L["Yellow"] = "Жёлтый"
L["You have reached"] = "Вы достигли"
L["Background"] = "Фон"
L["Stone"] = "Каменный"
L["Dark"] = "Тёмный"
L["Character panel settings"] = "Настройки окна персонажа"
L[" (+%d more)"] = " (+ещё %d)"
L["%d auctions"] = "Аукционов: %d"
L["%d stacks of %d"] = "%d стопок по %d"
L["< 1 min"] = "< 1 мин."
L["12 Hours"] = "12 часов"
L["24 Hours"] = "24 часа"
L["48 Hours"] = "48 часов"
L["All"] = "Все"
L["Auctions"] = "Аукционы"
L["Back"] = "Назад"
L["Backfill"] = "Добор"
L["Bid"] = "Ставка"
L["Bids"] = "Ставки"
L["Browse"] = "Просмотр"
L["Buy"] = "Покупка"
L["Buyout"] = "Выкуп"
L["Cancel"] = "Отмена"
L["Class"] = "Класс"
L["Comment"] = "Комментарий"
L["Common"] = "Обычный"
L["Damage"] = "Боец"
L["Demote"] = "Понизить"
L["Deposit"] = "Залог"
L["Dungeons"] = "Подземелья"
L["Duration"] = "Длительность"
L["Epic"] = "Эпический"
L["Experience"] = "Опыт"
L["Filter"] = "Фильтр"
L["Healer"] = "Лекарь"
L["Ignore"] = "Игнорировать"
L["Invite"] = "Пригласить"
L["Level"] = "Уровень"
L["Long"] = "Долгое"
L["Max"] = "Макс."
L["Medium"] = "Среднее"
L["Members"] = "Участники"
L["Money"] = "Деньги"
L["No"] = "Нет"
L["Note"] = "Заметка"
L["Offline"] = "Не в сети"
L["Poor"] = "Низкое"
L["Price"] = "Цена"
L["Qty"] = "Кол-во"
L["Quantity"] = "Количество"
L["Queue"] = "Очередь"
L["Raid"] = "Рейд"
L["Raids"] = "Рейды"
L["Rank"] = "Звание"
L["Rare"] = "Редкий"
L["Rarity"] = "Качество"
L["Refresh"] = "Обновить"
L["Remove"] = "Исключить"
L["Rewards"] = "Награды"
L["Roster"] = "Состав"
L["Sell"] = "Продажа"
L["Seller"] = "Продавец"
L["Sellers"] = "Продавцы"
L["Short"] = "Короткое"
L["Tank"] = "Танк"
L["Type"] = "Тип"
L["Uncommon"] = "Необычный"
L["Whisper"] = "Шепнуть"
L["Yes"] = "Да"
L["Zone"] = "Зона"
-- ============================================================================
-- GENERATED UI BACKLOG (qa/localize-hardcoded.ps1)
-- ============================================================================
L["One movable window showing every bag slot in a Dragonflight-style grid. Takes over bag opening and replaces the per-window 'Retail bags' restyle. Reload (/reload) to apply."] = "Одно перемещаемое окно со всеми ячейками сумок в сетке в стиле Dragonflight. Перехватывает открытие сумок и заменяет оформление отдельных окон «Сумки в стиле актуальной версии». Для применения выполните /reload."
L["Restyle the bag windows with the Dragonflight metal frame, portrait, and item quality borders. Disable to keep the stock Blizzard bags. Reload (/reload) to apply."] = "Оформляет окна сумок металлической рамкой Dragonflight, портретом и рамками качества предметов. Выключите, чтобы оставить стандартные сумки Blizzard. Для применения выполните /reload."
@@ -43,7 +43,7 @@ if not C.GetContainerItemQuestInfo then
return { isQuestItem = nil, questID = nil, questId = nil, isActive = nil }
end
NE.compat.RecordStub("C_Container.GetContainerItemQuestInfo",
"no native GetContainerItemQuestInfo on this client; returns empty (no quest item)")
NE.L["no native GetContainerItemQuestInfo on this client; returns empty (no quest item)"])
end
end
@@ -86,7 +86,7 @@ if not NATIVE then
}
NE.compat = NE.compat or {}
NE.compat.stubs = NE.compat.stubs or {}
NE.compat.stubs["C_EquipmentSet"] = "no native engine; custom backend live"
NE.compat.stubs["C_EquipmentSet"] = NE.L["no native engine; custom backend live"]
end
return
end
@@ -30,7 +30,7 @@ if not C.GetMapInfo then
return nil
end
compat.RecordStub("C_Map.GetMapInfo",
"no uiMapID->map metadata on 3.3.5; returns nil")
NE.L["no uiMapID->map metadata on 3.3.5; returns nil"])
end
-- GetPlayerMapPosition(uiMapID, unit) -> position table with :GetXY(). 3.3.5's
@@ -52,7 +52,7 @@ if not C.GetPlayerMapPosition then
return setmetatable({ x = x, y = y }, posMethods)
end
compat.RecordStub("C_Map.GetPlayerMapPosition",
"wraps legacy GetPlayerMapPosition; ignores uiMapID arg, uses current map")
NE.L["wraps legacy GetPlayerMapPosition; ignores uiMapID arg, uses current map"])
end
NE.compat.map = true
@@ -165,7 +165,7 @@ function NE.cd.ApplyNumbers(cooldown, opts)
-- Once per session, not once per icon: the whole chain missing is a single fact about the
-- client, and the text still renders in the FontString's inherited default.
NE.cd._warnedFont = true
NE.Log("CD", "no font object resolved from " .. tostring(tried))
NE.Log("CD", NE.L["no font object resolved from "] .. tostring(tried))
end
end
@@ -485,7 +485,7 @@ function NE.FrameUtil.CopyBox(opts)
local title = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
title:SetPoint("TOP", 0, -10)
title:SetText(opts.title or "Copy — Ctrl+A then Ctrl+C")
title:SetText(opts.title or NE.L["Copy — Ctrl+A then Ctrl+C"])
local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
close:SetPoint("TOPRIGHT", -4, -4)
@@ -505,7 +505,7 @@ function NE.FrameUtil.CopyBox(opts)
local selectAll = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
selectAll:SetSize(90, 20)
selectAll:SetPoint("BOTTOMLEFT", 12, 10)
selectAll:SetText("Select All")
selectAll:SetText(NE.L["Select All"])
selectAll:SetScript("OnClick", function() edit:SetFocus(); edit:HighlightText() end)
f.edit = edit
@@ -61,7 +61,7 @@ end
-- Quest-STARTER detection (the "!" bang) via a hidden scanning tooltip, cached per itemID.
local _questScanTip
local _questStarterCache = {}
local QUEST_STARTS_TEXT = (type(ITEM_STARTS_QUEST) == "string" and ITEM_STARTS_QUEST) or "Begins a Quest"
local QUEST_STARTS_TEXT = (type(ITEM_STARTS_QUEST) == "string" and ITEM_STARTS_QUEST) or NE.L["Begins a Quest"]
local function itemStartsQuest(bag, slot, itemID)
if itemID == nil then return false end
local cached = _questStarterCache[itemID]
@@ -197,14 +197,14 @@ dispatcher:SetScript("OnEvent", function(_, event, arg1, ...)
if opts and opts.onBoot and shouldBoot(name) then
Mods.booted[name] = true
local ok, err = pcall(opts.onBoot, event, arg1, ...)
if not ok and NE.Log then NE.Log("MODULES", "onBoot error in "..name..": "..tostring(err)) end
if not ok and NE.Log then NE.Log("MODULES", NE.L["onBoot error in "]..name..": "..tostring(err)) end
local subs = Mods.subBoots[name]
if subs then
for _, e in ipairs(subs) do
if e.every or not e.ran then
e.ran = true
local ok2, err2 = pcall(e.fn, event, arg1, ...)
if not ok2 and NE.Log then NE.Log("MODULES", "sub-boot error in "..name..": "..tostring(err2)) end
if not ok2 and NE.Log then NE.Log("MODULES", NE.L["sub-boot error in "]..name..": "..tostring(err2)) end
end
end
end
@@ -217,7 +217,7 @@ function Mods.OnBoot(family, fn, opts)
if Mods.booted[family] then
entry.ran = true
local ok, err = pcall(fn)
if not ok and NE.Log then NE.Log("MODULES", "sub-boot error in "..family..": "..tostring(err)) end
if not ok and NE.Log then NE.Log("MODULES", NE.L["sub-boot error in "]..family..": "..tostring(err)) end
if not entry.every then return end
end
local list = Mods.subBoots[family]
@@ -237,11 +237,11 @@ function Mods.Register(arg1, arg2)
opts = arg2 or {}
end
if not name then
if NE.Log then NE.Log("MODULES", "Register called without a name") end
if NE.Log then NE.Log("MODULES", NE.L["Register called without a name"]) end
return
end
if Mods.registry[name] then
if NE.Log then NE.Log("MODULES", "duplicate module registration: "..tostring(name)) end
if NE.Log then NE.Log("MODULES", NE.L["duplicate module registration: "]..tostring(name)) end
return
end
Mods.registry[name] = opts
@@ -106,7 +106,7 @@ function NE.nineslice.ApplyLayout(container, layoutName)
if not container then return false end
local layout = NE.nineslice.layouts[layoutName]
if not layout then
if NE.Log then NE.Log("NINESLICE", "MISS layout " .. tostring(layoutName)) end
if NE.Log then NE.Log("NINESLICE", NE.L["MISS layout "] .. tostring(layoutName)) end
return false
end
@@ -212,7 +212,7 @@ function PC.ApplyModernChrome(f, layout)
local applied = NE.nineslice and NE.nineslice.ApplyLayout and NE.nineslice.ApplyLayout(ns, layout)
if not applied then
applyFallbackBackdrop(f)
if NE.Log then NE.Log("CHROME", "nineslice art missing for "..tostring(layout)..", used fallback") end
if NE.Log then NE.Log("CHROME", NE.L["nineslice art missing for "]..tostring(layout)..", used fallback") end
end
-- TopTileStreaks under the title.
@@ -171,7 +171,7 @@ watcher:SetScript("OnEvent", function()
watcher._queued = false
local ok, err = pcall(SB.BuildRankTable)
if not ok then
if NE.Log then NE.Log("SPELLRANKS", "rebuild failed: " .. tostring(err)) end
if NE.Log then NE.Log("SPELLRANKS", NE.L["rebuild failed: "] .. tostring(err)) end
return
end
-- Consumers gate on this table (the cooldown viewers' learn-check, for one), so anything that
@@ -125,7 +125,7 @@ function NE.tex.SetAtlas(tex, name, useAtlasSize, filterMode)
if not (tex and name) then return false end
local entry = atlasEntry(name)
if not entry then
if NE.Log then NE.Log("ATLAS", "MISS atlas " .. tostring(name)) end
if NE.Log then NE.Log("ATLAS", NE.L["MISS atlas "] .. tostring(name)) end
return false
end
local source = entrySource(entry)
@@ -134,7 +134,7 @@ function NE.tex.SetAtlas(tex, name, useAtlasSize, filterMode)
-- setting it yields a BLANK texture while reporting success, which silently replaces native art
-- (e.g. blanked the stock close-button X). Report a miss so callers gracefully fall back.
if type(source) ~= "string" then
if NE.Log then NE.Log("ATLAS", "NO-LOCAL atlas " .. tostring(name) .. " (fdid " .. tostring(entry.file) .. " not shipped)") end
if NE.Log then NE.Log("ATLAS", NE.L["NO-LOCAL atlas "] .. tostring(name) .. " (fdid " .. tostring(entry.file) .. " not shipped)") end
return false
end
tex:SetTexture(source) -- DOWNPORT: no filterMode arg on 3.3.5a
@@ -196,7 +196,7 @@ function NE.tex.SetAtlasMask(maskTex, name)
if not (maskTex and name) then return false end
local entry = atlasEntry(name)
if not entry then
if NE.Log then NE.Log("ATLAS", "MISS mask atlas " .. tostring(name)) end
if NE.Log then NE.Log("ATLAS", NE.L["MISS mask atlas "] .. tostring(name)) end
return false
end
maskTex:SetTexture(entrySource(entry), "CLAMPTOBLACKADDITIVE", "CLAMPTOBLACKADDITIVE")
@@ -217,7 +217,7 @@ function NE.tex.ApplyAtlasMap(self, map, label)
NE.tex.SetAtlas(widget, entry.name, entry.useAtlasSize)
end
elseif label and NE.Log then
NE.Log("ATLAS", "MISS path " .. label .. "." .. path)
NE.Log("ATLAS", NE.L["MISS path "] .. label .. "." .. path)
end
end
end
@@ -278,7 +278,7 @@ function NE.tex.SetAtlasOnStatusBar(bar, name)
if not (bar and name) then return false end
local entry = atlasEntry(name)
if not entry then
if NE.Log then NE.Log("ATLAS", "MISS atlas " .. tostring(name)) end
if NE.Log then NE.Log("ATLAS", NE.L["MISS atlas "] .. tostring(name)) end
return false
end
local source = entrySource(entry)
@@ -14,7 +14,7 @@ if not NE then return end
local OPTIONS_ADDON = "DragonUI_Options"
local TAB_KEY = "newera"
local TAB_TITLE = "New Era"
local TAB_TITLE = NE.L["New Era"]
local TAB_ORDER = 16
-- The list the builder iterates is owned by Register.lua. Keep a safe handle.
@@ -51,8 +51,8 @@ local function builder(scroll)
C:AddHeading(scroll, TAB_TITLE)
C:AddDescription(scroll,
"NewEra panels ported onto DragonUI. Toggle a panel below to enable or disable it. "
.. "Panels appear here as their modules load.")
NE.L["NewEra panels ported onto DragonUI. Toggle a panel below to enable or disable it. "]
.. NE.L["Panels appear here as their modules load."])
if C.AddSpacer then C:AddSpacer(scroll) end
-- (Per-window enable toggles live in the "Windows" section below; we intentionally do NOT also
@@ -65,27 +65,27 @@ local function builder(scroll)
if NE.modules and NE.modules.IsEnabled then
C:AddHeading(scroll, "Windows")
C:AddDescription(scroll,
"Use DragonUI's window in place of the Blizzard default. Changes take effect after a /reload.")
NE.L["Use DragonUI's window in place of the Blizzard default. Changes take effect after a /reload."])
-- The per-window "Retail bags" restyle toggle stays hidden (the combined bag replaces it), but
-- the combined bag itself IS toggleable so players can turn our bag UI off entirely and fall
-- back to the stock Blizzard bags. To also re-expose the per-window restyle, add a row:
-- { id = "bags", label = "Retail bags" }
local WINDOWS = {
{ id = "character", label = "Character panel" },
{ id = "Spellbook", label = "Spellbook" },
{ id = "Talents", label = "Talents" },
{ id = "Professions", label = "Professions" },
{ id = "AuctionHouse", label = "Auction House" },
{ id = "Social", label = "Social (Friends/Who/Guild/Chat/Raid)" },
{ id = "LFG", label = "Looking For Group (Dungeon/Raid Finder)" },
{ id = "combinedbag", label = "Combined bag (all-in-one)",
desc = "Our all-in-one bag window. Turn OFF to use the stock Blizzard bags instead. Reload (/reload) to apply." },
{ id = "character", label = NE.L["Character panel"] },
{ id = "Spellbook", label = NE.L["Spellbook"] },
{ id = "Talents", label = NE.L["Talents"] },
{ id = "Professions", label = NE.L["Professions"] },
{ id = "AuctionHouse", label = NE.L["Auction House"] },
{ id = "Social", label = NE.L["Social (Friends/Who/Guild/Chat/Raid)"] },
{ id = "LFG", label = NE.L["Looking For Group (Dungeon/Raid Finder)"] },
{ id = "combinedbag", label = NE.L["Combined bag (all-in-one)"],
desc = NE.L["Our all-in-one bag window. Turn OFF to use the stock Blizzard bags instead. Reload (/reload) to apply."] },
}
for _, w in ipairs(WINDOWS) do
local id = w.id
C:AddToggle(scroll, {
label = w.label,
desc = w.desc or "Reload (/reload) to apply.",
desc = w.desc or NE.L["Reload (/reload) to apply."],
getFunc = function() return NE.modules.IsEnabled(id) and true or false end,
setFunc = function(v)
if NE.modules.SetEnabled then pcall(NE.modules.SetEnabled, id, v) end
@@ -103,8 +103,8 @@ local function builder(scroll)
-- configuration switch here.
if NE.ej then
C:AddToggle(scroll, {
label = "Adventure Guide (Encounter Journal)",
desc = "Boss and loot browser. Requires a /reload to take effect (the micro button doesn't re-check this live).",
label = NE.L["Adventure Guide (Encounter Journal)"],
desc = NE.L["Boss and loot browser. Requires a /reload to take effect (the micro button doesn't re-check this live)."],
getFunc = function()
local modules = dragon.db.profile.modules
local m = modules and modules["ne_EncounterJournal"]
@@ -126,38 +126,38 @@ local function builder(scroll)
-- Backed by NE.scale (NE.db.scale[window]); changes apply IMMEDIATELY to the live frame.
-- ----------------------------------------------------------------------------
if C.AddSpacer then C:AddSpacer(scroll) end
C:AddHeading(scroll, "Window Scaling")
C:AddHeading(scroll, NE.L["Window Scaling"])
local S = NE.scale
if not S then
-- core/Scale.lua isn't loaded — surface it instead of silently showing nothing.
C:AddDescription(scroll,
"Scaling controls are unavailable: the 'core\\Scale.lua' file isn't loaded. Make sure your "
.. "installed DragonUI_NewEra includes core/Scale.lua AND its line in the .toc, then /reload.")
.. NE.L["installed DragonUI_NewEra includes core/Scale.lua AND its line in the .toc, then /reload."])
elseif not (C.AddDropdown and C.AddSlider) then
C:AddDescription(scroll, "Scaling controls need a newer DragonUI options panel (AddSlider/AddDropdown).")
C:AddDescription(scroll, NE.L["Scaling controls need a newer DragonUI options panel (AddSlider/AddDropdown)."])
else
C:AddDescription(scroll,
"Each window's size: \"Use UI scale\" follows the game's UI Scale slider, \"No scaling\" "
.. "stays pixel-perfect, \"Custom\" uses its slider. The custom slider is greyed out and "
.. "locked unless that window's mode is set to Custom.")
.. NE.L["locked unless that window's mode is set to Custom."])
local AceGUI = LibStub and LibStub("AceGUI-3.0")
local MODES = { ui = "Use UI scale", none = "No scaling", custom = "Custom" }
local MODES = { ui = NE.L["Use UI scale"], none = NE.L["No scaling"], custom = "Custom" }
-- Keys are core/Scale.lua's window keys; every one of these frames registers itself with
-- NE.scale.SetFrame(key, frame) as it builds, so a change here lands on the live window.
-- A window that hasn't been opened yet this session simply has no frame registered — the
-- setting still persists and applies on its first build.
local WINDOWS = {
{ key = "character", label = "Character" },
{ key = "spellbook", label = "Spellbook" },
{ key = "talents", label = "Talents" },
{ key = "professions", label = "Professions" },
{ key = "social", label = "Social" },
{ key = "guild", label = "Guild" },
{ key = "lfg", label = "Looking For Group" },
{ key = "auctionhouse", label = "Auction House" },
{ key = "cooldownmanager", label = "Cooldown Manager" },
{ key = "encounterjournal", label = "Adventure Guide" },
{ key = "combinedbag", label = "Combined Bag" },
{ key = "character", label = NE.L["Character"] },
{ key = "spellbook", label = NE.L["Spellbook"] },
{ key = "talents", label = NE.L["Talents"] },
{ key = "professions", label = NE.L["Professions"] },
{ key = "social", label = NE.L["Social"] },
{ key = "guild", label = NE.L["Guild"] },
{ key = "lfg", label = NE.L["Looking For Group"] },
{ key = "auctionhouse", label = NE.L["Auction House"] },
{ key = "cooldownmanager", label = NE.L["Cooldown Manager"] },
{ key = "encounterjournal", label = NE.L["Adventure Guide"] },
{ key = "combinedbag", label = NE.L["Combined Bag"] },
}
-- One window's stacked controls: a centered column header, a mode dropdown, and a custom-scale
@@ -171,7 +171,7 @@ local function builder(scroll)
end
local slider
C:AddDropdown(parent, {
label = "Scale mode",
label = NE.L["Scale mode"],
values = MODES,
getFunc = function() local m = S.Get(key); return m end,
setFunc = function(v)
@@ -180,7 +180,7 @@ local function builder(scroll)
end,
})
slider = C:AddSlider(parent, {
label = "Custom scale",
label = NE.L["Custom scale"],
min = S.MIN or 0.5, max = S.MAX or 1.5, step = 0.05,
getFunc = function() local _, c = S.Get(key); return c end,
setFunc = function(v) S.SetCustom(key, v) end,
@@ -216,7 +216,7 @@ local function builder(scroll)
for _, section in ipairs(NE.optionSections) do
local ok, err = pcall(section.build, scroll, C)
if not ok and NE._warn then
NE._warn("options section '" .. tostring(section.id or "?") .. "' failed: " .. tostring(err))
NE._warn(NE.L["options section '"] .. tostring(section.id or "?") .. "' failed: " .. tostring(err))
end
end
end
@@ -239,7 +239,7 @@ local function tryRegister()
registered = true
return true
end
if NE._warn then NE._warn("OptionsPanel:RegisterTab failed: " .. tostring(err)) end
if NE._warn then NE._warn(NE.L["OptionsPanel:RegisterTab failed: "] .. tostring(err)) end
return false
end
@@ -72,7 +72,7 @@ end
function NE.OnReady()
local cfg = ensureProfile()
if not cfg then
warn("DragonUI.db.profile not available at OnReady; newera settings not initialised.")
warn(NE.L["DragonUI.db.profile not available at OnReady; newera settings not initialised."])
return
end
@@ -203,22 +203,22 @@ function NE.OpenFrameEditor(frame)
local dragon = NE.dragon
local EM = dragon and dragon.EditorMode
if not (EM and type(EM.Show) == "function" and type(EM.IsActive) == "function") then
return false, "DragonUI's editor mode isn't available."
return false, NE.L["DragonUI's editor mode isn't available."]
end
-- This check exists for the MESSAGE, not the behaviour: EditorMode:Show() already no-ops in
-- combat, and the IsActive re-check below would catch it either way — but "editor mode declined to
-- open" tells a player nothing, where "not during combat" tells them to try again in ten seconds.
if InCombatLockdown and InCombatLockdown() then
return false, "Editor mode can't be opened during combat."
return false, NE.L["Editor mode can't be opened during combat."]
end
if not EM:IsActive() then
local ok, err = pcall(EM.Show, EM)
if not ok then return false, "Editor mode failed to open: " .. tostring(err) end
if not ok then return false, NE.L["Editor mode failed to open: "] .. tostring(err) end
end
-- Re-check rather than trusting the call, per the header.
if not EM:IsActive() then
return false, "Editor mode declined to open."
return false, NE.L["Editor mode declined to open."]
end
local target = frame and (frame.editorAnchor or frame) or nil
@@ -239,10 +239,10 @@ function NE.CloseFrameEditor()
local dragon = NE.dragon
local EM = dragon and dragon.EditorMode
if not (EM and type(EM.Hide) == "function") then
return false, "DragonUI's editor mode isn't available."
return false, NE.L["DragonUI's editor mode isn't available."]
end
local ok, err = pcall(EM.Hide, EM)
if not ok then return false, "Editor mode failed to close: " .. tostring(err) end
if not ok then return false, NE.L["Editor mode failed to close: "] .. tostring(err) end
return true
end
@@ -286,7 +286,7 @@ function NE.OpenFrameEditorSettings(anchor)
end
local ok, err = pcall(open, anchor)
if not ok then
warn("editor settings failed to open: " .. tostring(err))
warn(NE.L["editor settings failed to open: "] .. tostring(err))
return false
end
return true
@@ -315,8 +315,8 @@ local function attachEditorSettings(anchor, spec)
if not NE.IsFrameEditorActive() then return end
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(spec.label or spec.name, 1, 1, 1)
GameTooltip:AddLine("Drag to move.", 0.8, 0.8, 0.8)
GameTooltip:AddLine("Click for this frame's settings.", 0.4, 1, 0.4)
GameTooltip:AddLine(NE.L["Drag to move."], 0.8, 0.8, 0.8)
GameTooltip:AddLine(NE.L["Click for this frame's settings."], 0.4, 1, 0.4)
GameTooltip:Show()
end)
anchor:SetScript("OnLeave", function() GameTooltip:Hide() end)
@@ -333,13 +333,13 @@ end
-- CastbarModule.anchor. We follow it.
function NE.RegisterHUDFrame(spec)
if type(spec) ~= "table" or not (spec.name and spec.frame) then
warn("RegisterHUDFrame needs a name and frame; ignored.")
warn(NE.L["RegisterHUDFrame needs a name and frame; ignored."])
return
end
local dragon = NE.dragon
if not (dragon and type(dragon.RegisterEditableFrame) == "function"
and type(dragon.CreateUIFrame) == "function") then
warn("DragonUI editor API absent; '" .. tostring(spec.name) .. "' won't be movable.")
warn(NE.L["DragonUI editor API absent; '"] .. tostring(spec.name) .. "' won't be movable.")
return
end
@@ -369,7 +369,7 @@ function NE.RegisterHUDFrame(spec)
local h = math.max(content:GetHeight() or 0, 32)
local okAnchor, anchor = pcall(dragon.CreateUIFrame, w, h, spec.name)
if not okAnchor or not anchor then
warn("CreateUIFrame failed for '" .. tostring(spec.name) .. "': " .. tostring(anchor))
warn(NE.L["CreateUIFrame failed for '"] .. tostring(spec.name) .. "': " .. tostring(anchor))
return
end
@@ -412,7 +412,7 @@ function NE.RegisterHUDFrame(spec)
end,
})
if not ok then
warn("RegisterEditableFrame failed for '" .. tostring(spec.name) .. "': " .. tostring(err))
warn(NE.L["RegisterEditableFrame failed for '"] .. tostring(spec.name) .. "': " .. tostring(err))
return
end
@@ -422,7 +422,7 @@ end
function NE.RegisterPanel(spec)
if type(spec) ~= "table" or not spec.id then
warn("RegisterPanel called without a spec.id; ignored.")
warn(NE.L["RegisterPanel called without a spec.id; ignored."])
return
end
@@ -466,7 +466,7 @@ function NE.RegisterPanel(spec)
}
end)
if not ok then
warn("NE.modules.Register failed for '" .. id .. "': " .. tostring(err))
warn(NE.L["NE.modules.Register failed for '"] .. id .. "': " .. tostring(err))
end
end
@@ -500,16 +500,16 @@ function NE.RegisterPanel(spec)
if ok then
registered = true
else
warn("ModuleRegistry:Register failed for '" .. id .. "': " .. tostring(err))
warn(NE.L["ModuleRegistry:Register failed for '"] .. id .. "': " .. tostring(err))
end
end
if not registered and type(dragon.RegisterModule) == "function" then
local ok, err = pcall(dragon.RegisterModule, dragon, "ne_" .. id, moduleTable, title, desc, spec.order)
if not ok then
warn("RegisterModule failed for '" .. id .. "': " .. tostring(err))
warn(NE.L["RegisterModule failed for '"] .. id .. "': " .. tostring(err))
end
elseif not registered and not mr then
warn("DragonUI exposes neither ModuleRegistry nor RegisterModule; '" .. id .. "' not in module list.")
warn(NE.L["DragonUI exposes neither ModuleRegistry nor RegisterModule; '"] .. id .. "' not in module list.")
end
end
@@ -544,7 +544,7 @@ function NE.RegisterPanel(spec)
}
end)
if not ok then
warn("RegisterMover failed for '" .. id .. "': " .. tostring(err))
warn(NE.L["RegisterMover failed for '"] .. id .. "': " .. tostring(err))
end
end
-- No frame yet (lazily-created panel): the panel re-calls RegisterPanel
@@ -51,10 +51,10 @@ end
-- "_DETAIL" globals ("Greater than 12 hours") -- the DETAIL variants are meant for tooltips and
-- are too wide for this column, wrapping onto a second line that bled into the Buyout column.
local function timeLeftText(code)
if code == 1 then return AUCTION_TIME_LEFT1 or "Short" end
if code == 2 then return AUCTION_TIME_LEFT2 or "Medium" end
if code == 3 then return AUCTION_TIME_LEFT3 or "Long" end
if code == 4 then return AUCTION_TIME_LEFT4 or "Very Long" end
if code == 1 then return AUCTION_TIME_LEFT1 or NE.L["Short"] end
if code == 2 then return AUCTION_TIME_LEFT2 or NE.L["Medium"] end
if code == 3 then return AUCTION_TIME_LEFT3 or NE.L["Long"] end
if code == 4 then return AUCTION_TIME_LEFT4 or NE.L["Very Long"] end
return "-"
end
@@ -74,8 +74,8 @@ function AH.BuildAuctionsPane(parent)
----------------------------------------------------------------------
local PILL_H = 23
local SUBTABS = {
{ id = 1, text = AUCTION_HOUSE_AUCTIONS_SUB_TAB or AUCTIONS or "Auctions" },
{ id = 2, text = AUCTION_HOUSE_BIDS_SUB_TAB or BIDS or "Bids" },
{ id = 1, text = AUCTION_HOUSE_AUCTIONS_SUB_TAB or AUCTIONS or NE.L["Auctions"] },
{ id = 2, text = AUCTION_HOUSE_BIDS_SUB_TAB or BIDS or NE.L["Bids"] },
}
-- Explicit SetWidth/SetHeight on btn FIRST, then anchor the art pieces off btn's own corners --
@@ -307,7 +307,7 @@ function AH.BuildAuctionsPane(parent)
setTabArt(SUBTABS[i].id, SUBTABS[i].id == id)
end
if pane.EmptyText then
pane.EmptyText:SetText((id == 1) and "You have no auctions." or "You have no bids.")
pane.EmptyText:SetText((id == 1) and NE.L["You have no auctions."] or NE.L["You have no bids."])
end
if cancelBtn then cancelBtn:SetShown(id == 1) end
requestActiveList()
@@ -425,8 +425,8 @@ function AH.BuildAuctionsPane(parent)
row.Text:ClearAllPoints()
row.Text:SetPoint("LEFT", row, "LEFT", 6, 0)
row.Text:SetPoint("RIGHT", row, "RIGHT", -4, 0)
row.Text:SetText((selectedSub == 1) and (AUCTION_HOUSE_ALL_AUCTIONS or "All Auctions")
or (AUCTION_HOUSE_ALL_BIDS or "All Bids"))
row.Text:SetText((selectedSub == 1) and (AUCTION_HOUSE_ALL_AUCTIONS or NE.L["All Auctions"])
or (AUCTION_HOUSE_ALL_BIDS or NE.L["All Bids"]))
else
row.Icon:Show()
row.Icon:SetTexture(e.texture or "Interface\\Icons\\INV_Misc_QuestionMark")
@@ -457,15 +457,15 @@ function AH.BuildAuctionsPane(parent)
----------------------------------------------------------------------
local h1 = right:CreateFontString(nil, "OVERLAY", "GameFontNormal")
h1:SetPoint("TOPLEFT", right, "TOPLEFT", 14, -8)
h1:SetText(NAME or "Name")
h1:SetText(NAME or NE.L["Name"])
local h2 = right:CreateFontString(nil, "OVERLAY", "GameFontNormal")
h2:SetPoint("TOPLEFT", right, "TOPLEFT", 238, -8)
h2:SetText(AUCTION_HOUSE_HEADER_CURRENT_BID or "Current Bid")
h2:SetText(AUCTION_HOUSE_HEADER_CURRENT_BID or NE.L["Current Bid"])
local h3 = right:CreateFontString(nil, "OVERLAY", "GameFontNormal")
h3:SetPoint("TOPLEFT", right, "TOPLEFT", 340, -8)
h3:SetText(AUCTION_BUYOUT_PRICE or BUYOUT or "Buyout")
h3:SetText(AUCTION_BUYOUT_PRICE or BUYOUT or NE.L["Buyout"])
-- Right-anchored + right-justified from the SAME frame ("right") and the SAME -30 (scrollbar
-- gutter) / -8 (row inset) offsets row.Time itself resolves to below -- guarantees this header
@@ -475,11 +475,11 @@ function AH.BuildAuctionsPane(parent)
h4:SetPoint("TOPRIGHT", right, "TOPRIGHT", -38, -8)
h4:SetWidth(115)
h4:SetJustifyH("RIGHT")
h4:SetText(TIME_LEFT or "Time Left")
h4:SetText(TIME_LEFT or NE.L["Time Left"])
local empty = right:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
empty:SetPoint("CENTER", right, "CENTER", 0, 44)
empty:SetText("You have no auctions.")
empty:SetText(NE.L["You have no auctions."])
empty:SetTextColor(1, 0.82, 0)
pane.EmptyText = empty
@@ -646,7 +646,7 @@ function AH.BuildAuctionsPane(parent)
cancelBtn = CreateFrame("Button", nil, pane, "UIPanelButtonTemplate")
cancelBtn:SetSize(150, 24)
cancelBtn:SetPoint("BOTTOMRIGHT", pane, "BOTTOMRIGHT", -27, 3)
cancelBtn:SetText(AUCTION_HOUSE_CANCEL_AUCTION_BUTTON or CANCEL_AUCTION or "Cancel Auction")
cancelBtn:SetText(AUCTION_HOUSE_CANCEL_AUCTION_BUTTON or CANCEL_AUCTION or NE.L["Cancel Auction"])
cancelBtn:Disable()
cancelBtn:SetScript("OnClick", function()
if selectedSub ~= 1 or not selectedOwnerIndex then return end
@@ -64,14 +64,14 @@ end
-- Aggregate browse row click drills into a per-item detail page (all individual auctions of that
-- item); the detail page's Bid/Buyout buttons act on the selected listing via these two confirms.
StaticPopupDialogs["NE_AH_BROWSE_BID"] = {
text = "Place a bid of %s?",
button1 = YES or "Yes",
button2 = NO or "No",
text = NE.L["Place a bid of %s?"],
button1 = YES or NE.L["Yes"],
button2 = NO or NE.L["No"],
OnAccept = function(_, data)
if not data or not data.index then return end
local ok, err = pcall(PlaceAuctionBid, "list", data.index, data.price)
if not ok and NE.Log then
NE.Log("AH", "PlaceAuctionBid(bid) error: " .. tostring(err))
NE.Log("AH", NE.L["PlaceAuctionBid(bid) error: "] .. tostring(err))
end
end,
timeout = 0,
@@ -80,14 +80,14 @@ StaticPopupDialogs["NE_AH_BROWSE_BID"] = {
showAlert = 1,
}
StaticPopupDialogs["NE_AH_BROWSE_BUYOUT"] = {
text = "Buy out this auction for %s?",
button1 = YES or "Yes",
button2 = NO or "No",
text = NE.L["Buy out this auction for %s?"],
button1 = YES or NE.L["Yes"],
button2 = NO or NE.L["No"],
OnAccept = function(_, data)
if not data or not data.index then return end
local ok, err = pcall(PlaceAuctionBid, "list", data.index, data.price)
if not ok and NE.Log then
NE.Log("AH", "PlaceAuctionBid(buyout) error: " .. tostring(err))
NE.Log("AH", NE.L["PlaceAuctionBid(buyout) error: "] .. tostring(err))
end
end,
timeout = 0,
@@ -97,17 +97,17 @@ StaticPopupDialogs["NE_AH_BROWSE_BUYOUT"] = {
}
local CATEGORY_TREE = {
{ name = "Weapons", children = { "One-Handed Axes", "Two-Handed Axes", "Bows", "Guns", "One-Handed Maces", "Two-Handed Maces", "Polearms", "One-Handed Swords", "Two-Handed Swords", "Staves", "Fist Weapons", "Daggers", "Thrown" } },
{ name = "Armor", children = { "Cloth", "Leather", "Mail", "Plate", "Shields", "Librams", "Idols", "Totems", "Sigils" } },
{ name = "Container", children = { "Bags", "Soul Bags", "Herb Bags", "Enchanting Bags", "Engineering Bags", "Gem Bags", "Mining Bags" } },
{ name = "Consumable", children = { "Food & Drink", "Potion", "Elixir", "Flask", "Bandage", "Scroll", "Item Enhancement" } },
{ name = "Trade Goods", children = { "Parts", "Explosives", "Devices", "Jewelcrafting", "Cloth", "Leather", "Metal & Stone", "Meat", "Herb", "Elemental", "Enchanting", "Materials", "Armor Enchantment", "Weapon Enchantment" } },
{ name = "Projectile", children = { "Wand", "Arrow", "Bullet" } },
{ name = "Quiver", children = { "Quiver", "Ammo Pouch" } },
{ name = "Recipe", children = { "Book", "Leatherworking", "Tailoring", "Engineering", "Blacksmithing", "Cooking", "Alchemy", "First Aid", "Enchanting", "Fishing", "Jewelcrafting", "Inscription" } },
{ name = "Gems", children = { "Red", "Blue", "Yellow", "Purple", "Green", "Orange", "Meta", "Simple", "Prismatic" } },
{ name = "Miscellaneous", children = { "Junk", "Reagent", "Pet", "Holiday", "Other" } },
{ name = "Quest Items", children = { "Quest" } },
{ name = NE.L["Weapons"], children = { NE.L["One-Handed Axes"], NE.L["Two-Handed Axes"], NE.L["Bows"], NE.L["Guns"], NE.L["One-Handed Maces"], NE.L["Two-Handed Maces"], NE.L["Polearms"], NE.L["One-Handed Swords"], NE.L["Two-Handed Swords"], NE.L["Staves"], NE.L["Fist Weapons"], NE.L["Daggers"], NE.L["Thrown"] } },
{ name = NE.L["Armor"], children = { NE.L["Cloth"], NE.L["Leather"], NE.L["Mail"], NE.L["Plate"], NE.L["Shields"], NE.L["Librams"], NE.L["Idols"], NE.L["Totems"], NE.L["Sigils"] } },
{ name = NE.L["Container"], children = { NE.L["Bags"], NE.L["Soul Bags"], NE.L["Herb Bags"], NE.L["Enchanting Bags"], NE.L["Engineering Bags"], NE.L["Gem Bags"], NE.L["Mining Bags"] } },
{ name = NE.L["Consumable"], children = { NE.L["Food & Drink"], NE.L["Potion"], NE.L["Elixir"], NE.L["Flask"], NE.L["Bandage"], NE.L["Scroll"], NE.L["Item Enhancement"] } },
{ name = NE.L["Trade Goods"], children = { NE.L["Parts"], NE.L["Explosives"], NE.L["Devices"], NE.L["Jewelcrafting"], NE.L["Cloth"], NE.L["Leather"], NE.L["Metal & Stone"], NE.L["Meat"], NE.L["Herb"], NE.L["Elemental"], NE.L["Enchanting"], NE.L["Materials"], NE.L["Armor Enchantment"], NE.L["Weapon Enchantment"] } },
{ name = NE.L["Projectile"], children = { NE.L["Wand"], NE.L["Arrow"], NE.L["Bullet"] } },
{ name = NE.L["Quiver"], children = { NE.L["Quiver"], NE.L["Ammo Pouch"] } },
{ name = NE.L["Recipe"], children = { NE.L["Book"], NE.L["Leatherworking"], NE.L["Tailoring"], NE.L["Engineering"], NE.L["Blacksmithing"], NE.L["Cooking"], NE.L["Alchemy"], NE.L["First Aid"], NE.L["Enchanting"], NE.L["Fishing"], NE.L["Jewelcrafting"], NE.L["Inscription"] } },
{ name = NE.L["Gems"], children = { NE.L["Red"], NE.L["Blue"], NE.L["Yellow"], NE.L["Purple"], NE.L["Green"], NE.L["Orange"], NE.L["Meta"], NE.L["Simple"], NE.L["Prismatic"] } },
{ name = NE.L["Miscellaneous"], children = { NE.L["Junk"], NE.L["Reagent"], NE.L["Pet"], NE.L["Holiday"], NE.L["Other"] } },
{ name = NE.L["Quest Items"], children = { NE.L["Quest"] } },
}
local function buildCategoryList(parent)
@@ -540,13 +540,13 @@ function AH.BuildBrowsePane(parent)
local searchButton = CreateFrame("Button", nil, pane, "UIPanelButtonTemplate")
searchButton:SetSize(120, 22)
searchButton:SetPoint("TOPRIGHT", pane, "TOPRIGHT", -8, -38)
searchButton:SetText(SEARCH or "Search")
searchButton:SetText(SEARCH or NE.L["Search"])
pane.SearchButton = searchButton
local filterButton = CreateFrame("Button", nil, pane, "UIPanelButtonTemplate")
filterButton:SetSize(90, 22)
filterButton:SetPoint("RIGHT", searchButton, "LEFT", -10, 0)
filterButton:SetText(AUCTION_FILTER_SUBCATEGORY_INVENTORY_TYPE or "Filter")
filterButton:SetText(AUCTION_FILTER_SUBCATEGORY_INVENTORY_TYPE or NE.L["Filter"])
pane.FilterButton = filterButton
-- Styled to match the professions crafting search box (modules/professions/RecipeList.lua) --
@@ -638,7 +638,7 @@ function AH.BuildBrowsePane(parent)
local lvlTitle = filterPanel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
lvlTitle:SetPoint("TOPLEFT", filterPanel, "TOPLEFT", 14, -14)
lvlTitle:SetText(AUCTION_HOUSE_FILTER_DROP_DOWN_LEVEL_RANGE or "Level Range")
lvlTitle:SetText(AUCTION_HOUSE_FILTER_DROP_DOWN_LEVEL_RANGE or NE.L["Level Range"])
lvlTitle:SetTextColor(1, 0.82, 0)
local function lvlBox()
@@ -681,7 +681,7 @@ function AH.BuildBrowsePane(parent)
usable:SetPoint("TOPLEFT", filterPanel, "TOPLEFT", 10, -72)
local usableLabel = filterPanel:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
usableLabel:SetPoint("LEFT", usable, "RIGHT", 2, 0)
usableLabel:SetText(AUCTION_HOUSE_FILTER_USABLE_ONLY or USABLE_ITEMS or "Usable Items Only")
usableLabel:SetText(AUCTION_HOUSE_FILTER_USABLE_ONLY or USABLE_ITEMS or NE.L["Usable Items Only"])
usable:SetScript("OnClick", function(self)
pane.Filter.usable = self:GetChecked() and true or false
end)
@@ -695,7 +695,7 @@ function AH.BuildBrowsePane(parent)
local rarityTitle = filterPanel:CreateFontString(nil, "OVERLAY", "GameFontNormal")
rarityTitle:SetPoint("TOPLEFT", filterPanel, "TOPLEFT", 14, -114)
rarityTitle:SetText(RARITY or "Rarity")
rarityTitle:SetText(RARITY or NE.L["Rarity"])
rarityTitle:SetTextColor(1, 0.82, 0)
-- Rarity radios (All + Poor..Epic). No dedicated radio-button XML template is trusted here --
@@ -721,12 +721,12 @@ function AH.BuildBrowsePane(parent)
rb:SetScript("OnClick", function() setRarity(val) end)
rarityBtns[#rarityBtns + 1] = rb
end
rarityRow(-134, ANY_QUALITY, ALL or "All")
rarityRow(-153, 0, NE.itembtn and NE.itembtn.WrapTextByQuality(_G.ITEM_QUALITY0_DESC or "Poor", 0) or (_G.ITEM_QUALITY0_DESC or "Poor"))
rarityRow(-172, 1, NE.itembtn and NE.itembtn.WrapTextByQuality(_G.ITEM_QUALITY1_DESC or "Common", 1) or (_G.ITEM_QUALITY1_DESC or "Common"))
rarityRow(-191, 2, NE.itembtn and NE.itembtn.WrapTextByQuality(_G.ITEM_QUALITY2_DESC or "Uncommon", 2) or (_G.ITEM_QUALITY2_DESC or "Uncommon"))
rarityRow(-210, 3, NE.itembtn and NE.itembtn.WrapTextByQuality(_G.ITEM_QUALITY3_DESC or "Rare", 3) or (_G.ITEM_QUALITY3_DESC or "Rare"))
rarityRow(-229, 4, NE.itembtn and NE.itembtn.WrapTextByQuality(_G.ITEM_QUALITY4_DESC or "Epic", 4) or (_G.ITEM_QUALITY4_DESC or "Epic"))
rarityRow(-134, ANY_QUALITY, ALL or NE.L["All"])
rarityRow(-153, 0, NE.itembtn and NE.itembtn.WrapTextByQuality(_G.ITEM_QUALITY0_DESC or NE.L["Poor"], 0) or (_G.ITEM_QUALITY0_DESC or NE.L["Poor"]))
rarityRow(-172, 1, NE.itembtn and NE.itembtn.WrapTextByQuality(_G.ITEM_QUALITY1_DESC or NE.L["Common"], 1) or (_G.ITEM_QUALITY1_DESC or NE.L["Common"]))
rarityRow(-191, 2, NE.itembtn and NE.itembtn.WrapTextByQuality(_G.ITEM_QUALITY2_DESC or NE.L["Uncommon"], 2) or (_G.ITEM_QUALITY2_DESC or NE.L["Uncommon"]))
rarityRow(-210, 3, NE.itembtn and NE.itembtn.WrapTextByQuality(_G.ITEM_QUALITY3_DESC or NE.L["Rare"], 3) or (_G.ITEM_QUALITY3_DESC or NE.L["Rare"]))
rarityRow(-229, 4, NE.itembtn and NE.itembtn.WrapTextByQuality(_G.ITEM_QUALITY4_DESC or NE.L["Epic"], 4) or (_G.ITEM_QUALITY4_DESC or NE.L["Epic"]))
setRarity(ANY_QUALITY)
filterButton:SetScript("OnClick", function() filterPanel:SetShown(not filterPanel:IsShown()) end)
@@ -796,13 +796,13 @@ function AH.BuildBrowsePane(parent)
-- -8 (or -96 for Lvl), so -38 / -126 here. ("Available" previously sat at -80, floating 42px left
-- of the numbers it labels -- with a Lvl column now next to it that gap reads as a misprint.)
local headers = {
{ text = AUCTION_HOUSE_BROWSE_HEADER_PRICE or "Price", x = 10, w = 120, just = "LEFT", key = "price" },
{ text = AUCTION_HOUSE_BROWSE_HEADER_NAME or NAME or "Name", x = 180, w = 260, just = "LEFT", key = "name" },
{ text = AUCTION_HOUSE_BROWSE_HEADER_PRICE or NE.L["Price"], x = 10, w = 120, just = "LEFT", key = "price" },
{ text = AUCTION_HOUSE_BROWSE_HEADER_NAME or NAME or NE.L["Name"], x = 180, w = 260, just = "LEFT", key = "name" },
-- "Lvl" (the stock AH's own name for the required-level column) rather than a spelled-out
-- "Req. Level": the same column has to fit the much tighter item-detail header strip below, and
-- one label across both views beats a wide one here and an abbreviation there.
{ text = "Lvl", x = -126, w = 64, just = "RIGHT", right = true, key = "level" },
{ text = AUCTION_HOUSE_BROWSE_HEADER_QUANTITY or "Available", x = -38, w = 72, just = "RIGHT", right = true, key = "qty" },
{ text = NE.L["Lvl"], x = -126, w = 64, just = "RIGHT", right = true, key = "level" },
{ text = AUCTION_HOUSE_BROWSE_HEADER_QUANTITY or NE.L["Available"], x = -38, w = 72, just = "RIGHT", right = true, key = "qty" },
}
for i = 1, #headers do
@@ -871,7 +871,7 @@ function AH.BuildBrowsePane(parent)
local empty = list:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
empty:SetPoint("CENTER", list, "CENTER", 0, 44)
empty:SetText("Choose search criteria and press \"Search\"")
empty:SetText(NE.L["Choose search criteria and press \"Search\""])
empty:SetTextColor(1, 0.82, 0)
pane.EmptyText = empty
@@ -1029,7 +1029,7 @@ function AH.BuildBrowsePane(parent)
local pageLabel = pane:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
pageLabel:SetPoint("LEFT", pageNext, "RIGHT", 8, 0)
pageLabel:SetText("Page 1 of 1")
pageLabel:SetText(NE.L["Page 1 of 1"])
local function totalDisplayPages()
return math.max(1, math.ceil(#displayRows / ITEMS_PER_PAGE))
@@ -1042,7 +1042,7 @@ function AH.BuildBrowsePane(parent)
-- Prev/Next stay disabled mid-scan: displayRows is still growing a page at a time, so the page
-- count it implies right now is not the final one.
if scanning then
pageLabel:SetText(string.format("Scanning page %d of %d...",
pageLabel:SetText(string.format(NE.L["Scanning page %d of %d..."],
math.min(scanPage + 1, scanPages), scanPages))
pagePrev:Disable()
pageNext:Disable()
@@ -1050,14 +1050,13 @@ function AH.BuildBrowsePane(parent)
end
if totalItems <= 0 then
pageLabel:SetText("Page 1 of 1")
pageLabel:SetText(NE.L["Page 1 of 1"])
else
local first = currentPage * ITEMS_PER_PAGE + 1
local last = math.min(first + ITEMS_PER_PAGE - 1, totalItems)
pageLabel:SetText(string.format("Page %d of %d (items %d-%d of %d, from %d auction%s%s)",
currentPage + 1, totalPages, first, last, totalItems,
listTotal, listTotal == 1 and "" or "s",
scanTruncated and " -- partial scan" or ""))
pageLabel:SetText(string.format(NE.L["Page %d of %d (items %d-%d of %d, auctions: %d%s)"],
currentPage + 1, totalPages, first, last, totalItems, listTotal,
scanTruncated and NE.L[" -- partial scan"] or ""))
end
if currentPage <= 0 then pagePrev:Disable() else pagePrev:Enable() end
@@ -1303,11 +1302,11 @@ function AH.BuildBrowsePane(parent)
else
empty:Show()
if scanning then
empty:SetText("Searching...")
empty:SetText(NE.L["Searching..."])
elseif listTotal > 0 then
empty:SetText("Loading results...")
empty:SetText(NE.L["Loading results..."])
else
empty:SetText("No results. Adjust filters and search again.")
empty:SetText(NE.L["No results. Adjust filters and search again."])
end
end
@@ -1372,7 +1371,7 @@ function AH.BuildBrowsePane(parent)
scanPending = false
scanTruncated = true
if #pageRows > 0 then
if NE.Log then NE.Log("AH", "browse scan aborted: " .. tostring(message)) end
if NE.Log then NE.Log("AH", NE.L["browse scan aborted: "] .. tostring(message)) end
else
scanTruncated = false
empty:SetText(message)
@@ -1434,7 +1433,7 @@ function AH.BuildBrowsePane(parent)
if scanRetries <= MAX_SCAN_RETRIES and C_Timer and C_Timer.After then
C_Timer.After(0.2, function() queryPage(page, token) end)
else
abortScan("Auction query is throttled. Try again in a moment.")
abortScan(NE.L["Auction query is throttled. Try again in a moment."])
end
return
end
@@ -1449,8 +1448,8 @@ function AH.BuildBrowsePane(parent)
if not ok then
-- Surface the real Lua error instead of a generic message -- if this still isn't the right
-- call shape, the exact "bad argument #N" text tells us which slot to fix next.
if NE.Log then NE.Log("AH", "QueryAuctionItems error: " .. tostring(err)) end
abortScan("Search failed: " .. tostring(err))
if NE.Log then NE.Log("AH", NE.L["QueryAuctionItems error: "] .. tostring(err)) end
abortScan(NE.L["Search failed: "] .. tostring(err))
return
end
scanPending = true
@@ -1631,7 +1630,7 @@ function AH.BuildBrowsePane(parent)
setBrowseResultsShown(true)
searchBox:SetText("")
searchBox:ClearFocus()
empty:SetText("Choose search criteria and press \"Search\"")
empty:SetText(NE.L["Choose search criteria and press \"Search\""])
refreshResults()
updatePageControls()
end
@@ -1702,7 +1701,7 @@ function AH.BuildBrowsePane(parent)
-- regression there leave the UI stuck showing stale text with no visible feedback.
local rrOk, rrErr = pcall(refreshResults)
if not rrOk and NE.Log then
NE.Log("AH", "refreshResults error: " .. tostring(rrErr))
NE.Log("AH", NE.L["refreshResults error: "] .. tostring(rrErr))
end
updatePageControls()
@@ -1735,7 +1734,7 @@ function AH.BuildBrowsePane(parent)
if refreshDetailRows and AH.OwnsListQuery("itembuy") then
local rdOk, rdErr = pcall(refreshDetailRows)
if not rdOk and NE.Log then
NE.Log("AH", "refreshDetailRows error: " .. tostring(rdErr))
NE.Log("AH", NE.L["refreshDetailRows error: "] .. tostring(rdErr))
end
end
return
@@ -1753,7 +1752,7 @@ function AH.BuildBrowsePane(parent)
pollAttempts = 0
local csOk, csErr = pcall(collectScanPage, scanToken)
if not csOk and NE.Log then
NE.Log("AH", "collectScanPage error: " .. tostring(csErr))
NE.Log("AH", NE.L["collectScanPage error: "] .. tostring(csErr))
end
end)
@@ -1786,7 +1785,7 @@ function AH.BuildBrowsePane(parent)
local detailBack = CreateFrame("Button", nil, detail, "UIPanelButtonTemplate")
detailBack:SetSize(90, 22)
detailBack:SetPoint("TOPLEFT", detail, "TOPLEFT", 8, -8)
detailBack:SetText(BACK or "Back")
detailBack:SetText(BACK or NE.L["Back"])
-- Refresh button: plain text, same UIPanelButtonTemplate every other button on this page uses.
-- Two prior icon attempts both failed on THIS client build -- a hand-rolled
@@ -1799,7 +1798,7 @@ function AH.BuildBrowsePane(parent)
local detailRefresh = CreateFrame("Button", nil, detail, "UIPanelButtonTemplate")
detailRefresh:SetSize(70, 20)
detailRefresh:SetPoint("TOPRIGHT", detail, "TOPRIGHT", -6, -10)
detailRefresh:SetText(REFRESH or "Refresh")
detailRefresh:SetText(REFRESH or NE.L["Refresh"])
local detailCount = detail:CreateFontString(nil, "OVERLAY", "GameFontNormal")
detailCount:SetPoint("RIGHT", detailRefresh, "LEFT", -8, 0)
@@ -1819,7 +1818,7 @@ function AH.BuildBrowsePane(parent)
detailPerItemCheck:SetChecked(true)
local detailPerItemLabel = detail:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
detailPerItemLabel:SetPoint("LEFT", detailPerItemCheck, "RIGHT", 2, 0)
detailPerItemLabel:SetText("Sort Per Item")
detailPerItemLabel:SetText(NE.L["Sort Per Item"])
detailPerItemCheck:SetScript("OnClick", function(self)
detailPerItem = self:GetChecked() and true or false
-- Re-read + re-sort the page the client already holds -- no fresh server query needed.
@@ -1917,12 +1916,12 @@ function AH.BuildBrowsePane(parent)
local detailBuyoutBtn = CreateFrame("Button", nil, detailBar, "UIPanelButtonTemplate")
detailBuyoutBtn:SetSize(110, 22)
detailBuyoutBtn:SetPoint("RIGHT", detailBar, "RIGHT", 0, 0)
detailBuyoutBtn:SetText(AUCTION_HOUSE_BUYOUT_BUTTON or BUYOUT or "Buyout")
detailBuyoutBtn:SetText(AUCTION_HOUSE_BUYOUT_BUTTON or BUYOUT or NE.L["Buyout"])
local detailBidBtn = CreateFrame("Button", nil, detailBar, "UIPanelButtonTemplate")
detailBidBtn:SetSize(110, 22)
detailBidBtn:SetPoint("RIGHT", detailBuyoutBtn, "LEFT", -6, 0)
detailBidBtn:SetText(AUCTION_HOUSE_BID_BUTTON or BID or "Bid")
detailBidBtn:SetText(AUCTION_HOUSE_BID_BUTTON or BID or NE.L["Bid"])
-- Per-auction list.
local detailList = CreateFrame("Frame", nil, detail)
@@ -1970,13 +1969,13 @@ function AH.BuildBrowsePane(parent)
-- Time Left's right anchor is -38 (rows are inset -30 from detailList, their text another -8),
-- not the -80 it previously used, which floated the label 42px clear of its own column.
local detailCols = {
{ text = "Per Item", x = 6, w = 92, just = "RIGHT" },
{ text = AUCTION_HOUSE_HEADER_BUYOUT or BUYOUT or "Buyout", x = 104, w = 92, just = "RIGHT" },
{ text = AUCTION_HOUSE_HEADER_CURRENT_BID or BID or "Current Bid", x = 202, w = 92, just = "RIGHT" },
{ text = "Lvl", x = 300, w = 36, just = "RIGHT" },
{ text = AUCTION_HOUSE_HEADER_QUANTITY or "Quantity", x = 344, w = 82, just = "LEFT" },
{ text = AUCTION_HOUSE_HEADER_SELLER or "Seller", x = 430, w = 76, just = "LEFT" },
{ text = AUCTION_HOUSE_HEADER_TIME_LEFT or "Time Left", x = -38, w = 62, just = "RIGHT", right = true },
{ text = NE.L["Per Item"], x = 6, w = 92, just = "RIGHT" },
{ text = AUCTION_HOUSE_HEADER_BUYOUT or BUYOUT or NE.L["Buyout"], x = 104, w = 92, just = "RIGHT" },
{ text = AUCTION_HOUSE_HEADER_CURRENT_BID or BID or NE.L["Current Bid"], x = 202, w = 92, just = "RIGHT" },
{ text = NE.L["Lvl"], x = 300, w = 36, just = "RIGHT" },
{ text = AUCTION_HOUSE_HEADER_QUANTITY or NE.L["Quantity"], x = 344, w = 82, just = "LEFT" },
{ text = AUCTION_HOUSE_HEADER_SELLER or NE.L["Seller"], x = 430, w = 76, just = "LEFT" },
{ text = AUCTION_HOUSE_HEADER_TIME_LEFT or NE.L["Time Left"], x = -38, w = 62, just = "RIGHT", right = true },
}
for i = 1, #detailCols do
local h = detailCols[i]
@@ -2041,8 +2040,8 @@ function AH.BuildBrowsePane(parent)
local function detailTimeLeftText(v)
if not v then return "" end
local TL = {
[1] = AUCTION_TIME_LEFT1 or "Short", [2] = AUCTION_TIME_LEFT2 or "Medium",
[3] = AUCTION_TIME_LEFT3 or "Long", [4] = AUCTION_TIME_LEFT4 or "Very Long",
[1] = AUCTION_TIME_LEFT1 or NE.L["Short"], [2] = AUCTION_TIME_LEFT2 or NE.L["Medium"],
[3] = AUCTION_TIME_LEFT3 or NE.L["Long"], [4] = AUCTION_TIME_LEFT4 or NE.L["Very Long"],
}
if v >= 1 and v <= 4 and TL[v] then return TL[v] end
return SecondsToTime and SecondsToTime(v) or tostring(v)
@@ -2144,7 +2143,7 @@ function AH.BuildBrowsePane(parent)
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:ClearLines()
if (d.num or 1) > 1 then
GameTooltip:AddLine(string.format("%d auctions", d.num), 1, 0.82, 0)
GameTooltip:AddLine(string.format(NE.L["%d auctions"], d.num), 1, 0.82, 0)
end
-- Unique sellers, capped so one mass-poster's wall of alts can't grow a giant tooltip.
-- owner can be nil on this client until the server fills it in; skip those.
@@ -2159,11 +2158,11 @@ function AH.BuildBrowsePane(parent)
end
if #uniq > 0 then
local MAX_SHOWN = 8
local label = (#uniq == 1) and (AUCTION_HOUSE_HEADER_SELLER or "Seller")
or ((AUCTION_HOUSE_HEADER_SELLER or "Seller") .. "s")
local label = (#uniq == 1) and (AUCTION_HOUSE_HEADER_SELLER or NE.L["Seller"])
or NE.L["Sellers"]
local names = table.concat(uniq, ", ", 1, math.min(#uniq, MAX_SHOWN))
if #uniq > MAX_SHOWN then
names = names .. string.format(" (+%d more)", #uniq - MAX_SHOWN)
names = names .. string.format(NE.L[" (+%d more)"], #uniq - MAX_SHOWN)
end
GameTooltip:AddLine(label .. ": " .. names, 1, 1, 1, true)
end
@@ -2325,7 +2324,7 @@ function AH.BuildBrowsePane(parent)
detailEmpty:Hide()
else
detailEmpty:Show()
detailEmpty:SetText("No listings.")
detailEmpty:SetText(NE.L["No listings."])
end
-- Total AUCTIONS, not visible rows -- condensing collapses identical listings into one row,
-- but this readout should keep meaning "how many listings exist".
@@ -2366,7 +2365,7 @@ function AH.BuildBrowsePane(parent)
row.Level:SetText(lvText)
row.Level:SetTextColor(lr, lg, lb)
if (data.num or 1) > 1 then
row.Qty:SetText(string.format("%d stacks of %d", data.num, data.count or 1))
row.Qty:SetText(string.format(NE.L["%d stacks of %d"], data.num, data.count or 1))
else
row.Qty:SetText(tostring(data.count or 1))
end
@@ -2457,14 +2456,14 @@ function AH.BuildBrowsePane(parent)
detailRing:Hide()
end
detailCount:SetText("")
detailEmpty:SetText("Searching...")
detailEmpty:SetText(NE.L["Searching..."])
detailEmpty:Show()
updateDetailActions()
setBrowseResultsShown(false)
detail:Show()
if not (CanSendAuctionQuery and CanSendAuctionQuery("list")) then
detailEmpty:SetText("Auction query is throttled. Try again in a moment.")
detailEmpty:SetText(NE.L["Auction query is throttled. Try again in a moment."])
return
end
-- Re-query scoped to this exact item name. THIS server's QueryAuctionItems signature (per its
@@ -2480,7 +2479,7 @@ function AH.BuildBrowsePane(parent)
AH.ClaimListQuery("itembuy")
local ok, err = pcall(QueryAuctionItems, data.name, 0, 0, 0, 0, 0, 0, false, ANY_QUALITY, false)
if not ok and NE.Log then
NE.Log("AH", "ItemBuy QueryAuctionItems error: " .. tostring(err))
NE.Log("AH", NE.L["ItemBuy QueryAuctionItems error: "] .. tostring(err))
end
end
@@ -27,9 +27,9 @@ NE.ah = NE.ah or {}
local AH = NE.ah
local DURATIONS = {
{ code = 1, label = AUCTION_DURATION_ONE or "12 Hours" },
{ code = 2, label = AUCTION_DURATION_TWO or "24 Hours" },
{ code = 3, label = AUCTION_DURATION_THREE or "48 Hours" },
{ code = 1, label = AUCTION_DURATION_ONE or NE.L["12 Hours"] },
{ code = 2, label = AUCTION_DURATION_TWO or NE.L["24 Hours"] },
{ code = 3, label = AUCTION_DURATION_THREE or NE.L["48 Hours"] },
}
-- Deposit lookup: this server's real signature takes (duration, minBid, buyoutPrice, stackSize,
@@ -234,7 +234,7 @@ function AH.BuildSellPane(parent)
if NE.tex and NE.tex.SetAtlas then NE.tex.SetAtlas(tabL, "auctionhouse-selltab-left", true) end
tabL:SetPoint("BOTTOMLEFT", form, "TOPLEFT", 42, -3)
local tabText = tabFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
tabText:SetText(AUCTION_CREATE_AUCTION or CREATE_AUCTION or "Create Auction")
tabText:SetText(AUCTION_CREATE_AUCTION or CREATE_AUCTION or NE.L["Create Auction"])
tabText:SetPoint("LEFT", tabL, "RIGHT", 12, 0)
local tabM = tabFrame:CreateTexture(nil, "ARTWORK")
if NE.tex and NE.tex.SetAtlas and NE.tex.SetAtlas(tabM, "auctionhouse-selltab-middle", false) then
@@ -304,7 +304,7 @@ function AH.BuildSellPane(parent)
if GetAuctionSellItemInfo and GetAuctionSellItemInfo() and GameTooltip.SetAuctionSellItem then
GameTooltip:SetAuctionSellItem()
else
GameTooltip:SetText(AUCTION_ITEM_TEXT or "Auction Item", 1, 1, 1)
GameTooltip:SetText(AUCTION_ITEM_TEXT or NE.L["Auction Item"], 1, 1, 1)
end
GameTooltip:Show()
-- Keep refreshing while hovered so pressing SHIFT mid-hover raises the comparison tooltip
@@ -351,7 +351,7 @@ function AH.BuildSellPane(parent)
end
-- Quantity (+ Max).
local qRow = alignedRow(nil, AUCTION_QUANTITY_LABEL or "Quantity")
local qRow = alignedRow(nil, AUCTION_QUANTITY_LABEL or NE.L["Quantity"])
local qty = makeInput(qRow, 134)
qty:SetNumeric(true)
qty:SetMaxLetters(5)
@@ -362,10 +362,10 @@ function AH.BuildSellPane(parent)
local maxBtn = CreateFrame("Button", nil, qRow, "UIPanelButtonTemplate")
maxBtn:SetSize(50, 20)
maxBtn:SetPoint("LEFT", qty, "RIGHT", 10, 0)
maxBtn:SetText(MAX or "Max")
maxBtn:SetText(MAX or NE.L["Max"])
-- Buyout price (per item; relabeled "Starting Bid" when Buyout Mode is off).
local pRow = alignedRow(qRow, AUCTION_BUYOUT_PRICE or "Buyout Price")
local pRow = alignedRow(qRow, AUCTION_BUYOUT_PRICE or NE.L["Buyout Price"])
local priceLabel = pRow.Label
local money = makeMoneyInput(pRow)
money:SetPoint("LEFT", pRow.Label, "RIGHT", 18, 0)
@@ -376,7 +376,7 @@ function AH.BuildSellPane(parent)
end
-- Duration: dropdown-look button in the same input skin; click cycles the three options.
local dRow = alignedRow(pRow, AUCTION_DURATION or "Duration")
local dRow = alignedRow(pRow, AUCTION_DURATION or NE.L["Duration"])
local duration = CreateFrame("Button", nil, dRow)
duration:SetSize(142, INPUT_H)
duration:SetPoint("LEFT", dRow.Label, "RIGHT", 18, 0)
@@ -402,12 +402,12 @@ function AH.BuildSellPane(parent)
pane.DurationButton = duration
-- Deposit / Total Price coin readouts.
local depRow = alignedRow(dRow, AUCTION_DEPOSIT_LABEL or DEPOSIT or "Deposit")
local depRow = alignedRow(dRow, AUCTION_DEPOSIT_LABEL or DEPOSIT or NE.L["Deposit"])
local dep = makeMoneyDisplay(depRow)
dep:SetPoint("LEFT", depRow.Label, "RIGHT", 18, 0)
pane.Deposit = dep
local totRow = alignedRow(depRow, AUCTION_TOTAL_VALUE or "Total Price")
local totRow = alignedRow(depRow, AUCTION_TOTAL_VALUE or NE.L["Total Price"])
local total = makeMoneyDisplay(totRow)
total:SetPoint("LEFT", totRow.Label, "RIGHT", 18, 0)
pane.Total = total
@@ -418,7 +418,7 @@ function AH.BuildSellPane(parent)
local createBtn = CreateFrame("Button", nil, form, "UIPanelButtonTemplate")
createBtn:SetSize(160, 22)
createBtn:SetPoint("TOP", totRow, "BOTTOM", 0, -16)
createBtn:SetText(AUCTION_CREATE_AUCTION or "Create Auction")
createBtn:SetText(AUCTION_CREATE_AUCTION or NE.L["Create Auction"])
createBtn:Disable()
pane.CreateButton = createBtn
@@ -428,7 +428,7 @@ function AH.BuildSellPane(parent)
buyoutMode:SetChecked(true)
local buyoutLabel = form:CreateFontString(nil, "OVERLAY", "GameFontNormal")
buyoutLabel:SetPoint("LEFT", buyoutMode, "RIGHT", 4, 0)
buyoutLabel:SetText(AUCTION_HOUSE_BUYOUT_MODE or "Buyout Mode")
buyoutLabel:SetText(AUCTION_HOUSE_BUYOUT_MODE or NE.L["Buyout Mode"])
----------------------------------------------------------------------
-- Market listings (right side): sortable header strip + refresh, rows on a
@@ -445,9 +445,9 @@ function AH.BuildSellPane(parent)
local refreshMarketRows
local headerDefs = {
{ key = "buyout", text = AUCTION_BUYOUT_PRICE or "Buyout", x = 8, w = 130 },
{ key = "qty", text = AUCTION_QUANTITY_LABEL or "Qty", x = 152, w = 48 },
{ key = "seller", text = AUCTION_SELLER or "Seller", x = 206, w = 110 },
{ key = "buyout", text = AUCTION_BUYOUT_PRICE or NE.L["Buyout"], x = 8, w = 130 },
{ key = "qty", text = AUCTION_QUANTITY_LABEL or NE.L["Qty"], x = 152, w = 48 },
{ key = "seller", text = AUCTION_SELLER or NE.L["Seller"], x = 206, w = 110 },
}
local headerBtns = {}
local function updateSortArrows()
@@ -495,7 +495,7 @@ function AH.BuildSellPane(parent)
local refreshBtn = CreateFrame("Button", nil, right, "UIPanelButtonTemplate")
refreshBtn:SetSize(64, 18)
refreshBtn:SetPoint("TOPRIGHT", right, "TOPRIGHT", -26, -2)
refreshBtn:SetText(REFRESH or "Refresh")
refreshBtn:SetText(REFRESH or NE.L["Refresh"])
pane.RefreshButton = refreshBtn
local emptyText = right:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
@@ -689,7 +689,7 @@ function AH.BuildSellPane(parent)
if AH.IsBrowseScanning and AH.IsBrowseScanning() then
SF._marketWaits = (SF._marketWaits or 0) + 1
if SF._marketWaits <= 20 and C_Timer and C_Timer.After then
emptyText:SetText(SEARCHING or "Searching...")
emptyText:SetText(SEARCHING or NE.L["Searching..."])
C_Timer.After(0.25, function() queryMarket(true) end)
return
end
@@ -698,10 +698,10 @@ function AH.BuildSellPane(parent)
SF.marketName = name
if not (CanSendAuctionQuery and CanSendAuctionQuery("list")) then
emptyText:SetText(AUCTION_HOUSE_THROTTLED or "Auction query is throttled. Try again in a moment.")
emptyText:SetText(AUCTION_HOUSE_THROTTLED or NE.L["Auction query is throttled. Try again in a moment."])
return
end
emptyText:SetText(SEARCHING or "Searching...")
emptyText:SetText(SEARCHING or NE.L["Searching..."])
SF.marketRows = {}
refreshMarketRows()
-- ISSUE #31: -1 ("any rarity"), not 0 -- QueryAuctionItems' quality argument is an exact match
@@ -785,7 +785,7 @@ function AH.BuildSellPane(parent)
buyoutMode:SetScript("OnClick", function(self)
SF.buyoutMode = self:GetChecked() and true or false
priceLabel:SetText(SF.buyoutMode and (AUCTION_BUYOUT_PRICE or "Buyout Price") or (AUCTION_STARTING_PRICE or "Starting Bid"))
priceLabel:SetText(SF.buyoutMode and (AUCTION_BUYOUT_PRICE or NE.L["Buyout Price"]) or (AUCTION_STARTING_PRICE or NE.L["Starting Bid"]))
recalcPrice()
end)
@@ -830,7 +830,7 @@ function AH.BuildSellPane(parent)
if AH.OwnsListQuery and not AH.OwnsListQuery("sellmarket") then return end
SF.marketRows = readMarket()
if #SF.marketRows == 0 then
emptyText:SetText(BROWSE_NO_RESULTS_TEXT or "No auctions found for this item.")
emptyText:SetText(BROWSE_NO_RESULTS_TEXT or NE.L["No auctions found for this item."])
end
applySort()
updateSortArrows()
@@ -72,7 +72,7 @@ AH.bridge.RegisterProvider("Auctionator", {
while _G["AuctionFrameTab" .. idx] do
local src = _G["AuctionFrameTab" .. idx]
local textObj = _G[src:GetName() .. "Text"]
local label = (textObj and textObj.GetText and textObj:GetText()) or (src.GetText and src:GetText()) or ("Tab " .. idx)
local label = (textObj and textObj.GetText and textObj:GetText()) or (src.GetText and src:GetText()) or (NE.L["Tab "] .. idx)
list[#list + 1] = {
key = "AuctionatorNative" .. idx,
text = label,
@@ -110,7 +110,7 @@ AH.bridge.RegisterProvider("Auctionator", {
if #list == 0 then
list[#list + 1] = {
key = "Auctionator",
text = "Auctionator",
text = NE.L["Auctionator"],
onSelect = function(ctx)
local NE = _G.DragonUI_NewEra
local ah = NE and NE.ah
@@ -15,7 +15,7 @@ local FRAME_NAME = "NE_AuctionHouseFrame"
-- Buy / Sell / Auctions. 3.3.5a has no AUCTION_HOUSE global; BUTTON_LAG_AUCTIONHOUSE is the only
-- GlobalStrings entry whose value is exactly "Auction House", and it IS localized per client, so
-- it beats hardcoding English. Literal fallback in case a locale ever drops it.
local AH_WINDOW_TITLE = BUTTON_LAG_AUCTIONHOUSE or "Auction House"
local AH_WINDOW_TITLE = BUTTON_LAG_AUCTIONHOUSE or NE.L["Auction House"]
-- Ctrl-click dress-up preview. In WoW 3.3.5a the dressup function is DressUpItemLink(link);
-- DressUpLink is the retail/modern name and does not exist in this client. The legacy
@@ -437,9 +437,9 @@ end
local function buildMainTabs(f)
local labels = {
AUCTION_HOUSE_BUY_TAB or "Buy",
AUCTION_HOUSE_SELL_TAB or "Sell",
AUCTION_HOUSE_AUCTIONS_SUB_TAB or "Auctions",
AUCTION_HOUSE_BUY_TAB or NE.L["Buy"],
AUCTION_HOUSE_SELL_TAB or NE.L["Sell"],
AUCTION_HOUSE_AUCTIONS_SUB_TAB or NE.L["Auctions"],
}
f._tabIds = {}
@@ -571,7 +571,7 @@ local function buildPanes(f)
local function safeBuild(builder, modeName)
if type(builder) ~= "function" then
return fallbackPane(modeName, "builder missing")
return fallbackPane(modeName, NE.L["builder missing"])
end
local ok, pane = pcall(builder, root)
if ok and pane then
@@ -725,8 +725,8 @@ local function onLogin()
if NE.RegisterPanel then
NE.RegisterPanel({
id = MODULE,
title = AUCTION_HOUSE or "Auction House",
desc = "Modern visual shell for Buy/Sell/Auctions with optional Auctionator tab embedding.",
title = AUCTION_HOUSE or NE.L["Auction House"],
desc = NE.L["Modern visual shell for Buy/Sell/Auctions with optional Auctionator tab embedding."],
frame = AH.frame,
openFn = AH.Show,
closeFn = AH.Hide,
@@ -190,13 +190,13 @@ local function installHooks()
styleItems(frame)
end)
else
log("ContainerFrame_GenerateFrame missing; chrome will rely on OnShow hooks only")
log(NE.L["ContainerFrame_GenerateFrame missing; chrome will rely on OnShow hooks only"])
end
if type(_G.ContainerFrame_Update) == "function" then
hooksecurefunc("ContainerFrame_Update", function(frame) styleItems(frame) end)
else
log("ContainerFrame_Update missing; item quality borders will not auto-refresh")
log(NE.L["ContainerFrame_Update missing; item quality borders will not auto-refresh"])
end
-- OnShow safety net: catches any frame shown without going through GenerateFrame.
@@ -225,14 +225,13 @@ if NE.modules and NE.modules.Register then
-- the per-window restyle can be re-enabled later; not booted by default.
label = NE.L["Retail bags"],
category = "Windows",
desc = NE.L["Restyle the bag windows with the Dragonflight metal frame, portrait, and item "
.. "quality borders. Disable to keep the stock Blizzard bags. Reload (/reload) to apply."],
desc = NE.L["Restyle the bag windows with the Dragonflight metal frame, portrait, and item quality borders. Disable to keep the stock Blizzard bags. Reload (/reload) to apply."],
events = { "PLAYER_LOGIN" },
onBoot = function() boot() end,
conflictsWith = NE.modules.RIVALS and NE.modules.RIVALS.BAGS or nil,
}
else
log("NE.modules.Register absent; retail bags not booted")
log(NE.L["NE.modules.Register absent; retail bags not booted"])
end
-- ----------------------------------------------------------------------------
@@ -143,7 +143,7 @@ if CB._autoEmptyBag == nil then CB._autoEmptyBag = true end
-- materials, recipes, then everything else.
local CAT_RANK = {
Weapon = 1, Armor = 2, Quest = 3, Consumable = 4,
["Trade Goods"] = 5, Gem = 5, Projectile = 5, Quiver = 5, Reagent = 5, Recipe = 6,
[NE.L["Trade Goods"]] = 5, Gem = 5, Projectile = 5, Quiver = 5, Reagent = 5, Recipe = 6,
}
-- Locale-independent category rank. GetItemInfo's itemType is LOCALISED, so keying CAT_RANK by the
-- English string only ranks correctly on enUS. GetAuctionItemClasses() returns the class names in a
@@ -403,7 +403,7 @@ end
-- ----------------------------------------------------------------------------
local function buildChrome()
if frame then return frame end
if not G then log("NE.itemgrid missing (core/ItemGrid.lua not loaded); combined bag unavailable"); return nil end
if not G then log(NE.L["NE.itemgrid missing (core/ItemGrid.lua not loaded); combined bag unavailable"]); return nil end
frame = CreateFrame("Frame", "NE_CombinedBagFrame", UIParent)
-- The red 3-slice is the addon's standard button; Watch keeps this window's panel buttons
@@ -690,10 +690,10 @@ end
-- ----------------------------------------------------------------------------
-- UPPERCASE fallback names per bag family bit (used only if the bag's localized subtype isn't cached).
local FAMILY_NAME = {
[1] = "QUIVER", [2] = "AMMO POUCH", [4] = "SOUL BAG",
[8] = "LEATHERWORKING BAG", [16] = "INSCRIPTION BAG", [32] = "HERB BAG",
[64] = "ENCHANTING BAG", [128] = "ENGINEERING BAG", [512] = "GEM BAG",
[1024] = "MINING BAG",
[1] = "QUIVER", [2] = NE.L["AMMO POUCH"], [4] = NE.L["SOUL BAG"],
[8] = NE.L["LEATHERWORKING BAG"], [16] = NE.L["INSCRIPTION BAG"], [32] = NE.L["HERB BAG"],
[64] = NE.L["ENCHANTING BAG"], [128] = NE.L["ENGINEERING BAG"], [512] = NE.L["GEM BAG"],
[1024] = NE.L["MINING BAG"],
}
-- Ordered list of the specialty families the player currently has a bag for.
local function activeSpecFamilies()
@@ -1511,7 +1511,7 @@ end
-- in that slot is still non-empty — run the managed swap; the new bag is back in inventory (by link).
function CB.HandleBagEquip(invSlot, link)
local container = (invSlot or 0) - CONTAINER_BAG_OFFSET
dbg("HandleBagEquip invSlot=", invSlot, "container=", container, "link=", link)
dbg(NE.L["HandleBagEquip invSlot="], invSlot, "container=", container, "link=", link)
if not CB._autoEmptyBag then dbg(" skip: autoEmptyBag off"); return end
if InCombatLockdown() or CB._bagSwapping or CB._sorting then dbg(" skip: busy/combat"); return end
if not (C_Container and C_Container.GetContainerNumSlots and PickupContainerItem and PutItemInBag and link) then
@@ -1553,7 +1553,7 @@ end
local function installBagEquipHooks()
if CB._bagHooksInstalled then return end
CB._bagHooksInstalled = true
if type(_G.PutItemInBag) ~= "function" then dbg("PutItemInBag missing; bag-swap disabled"); return end
if type(_G.PutItemInBag) ~= "function" then dbg(NE.L["PutItemInBag missing; bag-swap disabled"]); return end
local orig = _G.PutItemInBag
_G.PutItemInBag = function(invSlot)
local link
@@ -1565,7 +1565,7 @@ local function installBagEquipHooks()
orig(invSlot) -- perform the real equip (may succeed or fail)
if link then CB.HandleBagEquip(invSlot, link) end
end
dbg("installed PutItemInBag wrapper")
dbg(NE.L["installed PutItemInBag wrapper"])
end
CB.InstallBagEquipHooks = installBagEquipHooks
@@ -1778,20 +1778,19 @@ if NE.modules and NE.modules.Register then
-- When enabled it takes over bag opening and supersedes the per-window restyle.
label = NE.L["Combined bag (all-in-one)"],
category = "Windows",
desc = NE.L["One movable window showing every bag slot in a Dragonflight-style grid. Takes over "
.. "bag opening and replaces the per-window 'Retail bags' restyle. Reload (/reload) to apply."],
desc = NE.L["One movable window showing every bag slot in a Dragonflight-style grid. Takes over bag opening and replaces the per-window 'Retail bags' restyle. Reload (/reload) to apply."],
events = { "PLAYER_LOGIN" },
onBoot = function() boot() end,
conflictsWith = NE.modules.RIVALS and NE.modules.RIVALS.BAGS or nil,
}
else
log("NE.modules.Register absent; combined bag not booted")
log(NE.L["NE.modules.Register absent; combined bag not booted"])
end
if NE.qa then
NE.qa.modules = NE.qa.modules or {}
table.insert(NE.qa.modules, {
name = "Combined bag",
name = NE.L["Combined bag"],
frame = nil, -- built lazily; harness resolves NE_CombinedBagFrame on open
open = function() CB.Show() end,
close = function() CB.Hide() end,
@@ -720,13 +720,13 @@ if NE.modules and NE.modules.Register then
NE.modules.Register{
name = MODULE,
default = true,
label = "Character Panel",
desc = "The modern Dragonflight character window. Disable to keep the stock Blizzard frame.",
label = NE.L["Character Panel"],
desc = NE.L["The modern Dragonflight character window. Disable to keep the stock Blizzard frame."],
events = { "PLAYER_LOGIN", "PLAYER_ENTERING_WORLD" },
onBoot = function() boot() end,
}
else
log("NE.modules.Register absent; character panel not booted")
log(NE.L["NE.modules.Register absent; character panel not booted"])
end
-- ----------------------------------------------------------------------------
@@ -735,8 +735,8 @@ end
-- Each handshake is independently guarded (NE.dragon.* may be absent on a standalone load).
-- ----------------------------------------------------------------------------
local function registerWithDragon()
local title = "Character Panel"
local desc = "A modern Dragonflight character window (custom frame; the Blizzard frame is hidden)."
local title = NE.L["Character Panel"]
local desc = NE.L["A modern Dragonflight character window (custom frame; the Blizzard frame is hidden)."]
local moduleTable = {
ne_id = MODULE,
@@ -751,11 +751,11 @@ local function registerWithDragon()
if mr and type(mr.Register) == "function" then
local ok = pcall(mr.Register, mr, "ne_" .. MODULE, moduleTable, title, desc, 20)
registered = ok
if not ok then log("ModuleRegistry:Register failed") end
if not ok then log(NE.L["ModuleRegistry:Register failed"]) end
end
if not registered and type(dragon.RegisterModule) == "function" then
local ok = pcall(dragon.RegisterModule, dragon, "ne_" .. MODULE, moduleTable, title, desc, 20)
if not ok then log("RegisterModule failed") end
if not ok then log(NE.L["RegisterModule failed"]) end
end
end
@@ -40,5 +40,5 @@ boot:SetScript("OnEvent", function()
-- Ensure the frame (and thus its CloseButton) exists, then modernize.
if CP.BuildFrame then pcall(CP.BuildFrame) end
local ok, err = pcall(modernize)
if not ok and CP._log then CP._log("close button modernize failed: " .. tostring(err)) end
if not ok and CP._log then CP._log(NE.L["close button modernize failed: "] .. tostring(err)) end
end)
@@ -17,7 +17,7 @@ local CP = NE.charpanel
local ROW_HEIGHT = 24
local function log(msg)
if CP._log then CP._log("CURRENCY: " .. tostring(msg)); return end
if CP._log then CP._log(NE.L["CURRENCY: "] .. tostring(msg)); return end
if NE.Log then NE.Log("CURRENCY", msg); return end
if DEFAULT_CHAT_FRAME then
DEFAULT_CHAT_FRAME:AddMessage("|cffffcc55DragonUI_NewEra|r [currency]: " .. tostring(msg))
@@ -97,7 +97,7 @@ local function toggleWatch(info)
if want and numWatched() >= (MAX_WATCHED_TOKENS or 3) then
if UIErrorsFrame then
UIErrorsFrame:AddMessage(
(L("TOO_MANY_WATCHED_TOKENS", "You can watch at most %d currencies on your backpack.")):format(MAX_WATCHED_TOKENS or 3),
(L("TOO_MANY_WATCHED_TOKENS", NE.L["You can watch at most %d currencies on your backpack."])):format(MAX_WATCHED_TOKENS or 3),
1, 0.2, 0.2)
end
return
@@ -127,8 +127,8 @@ local function buildEntry(parent)
e._watch:SetHitRectInsets(2, 2, 2, 2)
e._watch:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_LEFT")
GameTooltip:SetText(L("TOKEN_OPTIONS_TRACK", "Show on Backpack"), 1, 1, 1)
GameTooltip:AddLine(L("TOKEN_OPTIONS_TRACK_HINT", "Watch this currency at the bottom of your bags (up to 3)."), nil, nil, nil, true)
GameTooltip:SetText(L("TOKEN_OPTIONS_TRACK", NE.L["Show on Backpack"]), 1, 1, 1)
GameTooltip:AddLine(L("TOKEN_OPTIONS_TRACK_HINT", NE.L["Watch this currency at the bottom of your bags (up to 3)."]), nil, nil, nil, true)
GameTooltip:Show()
end)
e._watch:SetScript("OnLeave", function() GameTooltip:Hide() end)
@@ -272,7 +272,7 @@ local function refresh()
flat = {}
if not (GetCurrencyListSize and GetCurrencyListInfo) then
log("GetCurrencyListSize/GetCurrencyListInfo unavailable -- Currency pane degraded")
log(NE.L["GetCurrencyListSize/GetCurrencyListInfo unavailable -- Currency pane degraded"])
updateScroll()
return
end
@@ -292,7 +292,7 @@ end
CP.RefreshCurrency = function()
local ok, err = pcall(refresh)
if not ok then log("RefreshCurrency error: " .. tostring(err)) end
if not ok then log(NE.L["RefreshCurrency error: "] .. tostring(err)) end
end
local function recomputeVisible()
@@ -303,7 +303,7 @@ end
local function build()
local host = getPane()
if not host then log("Currency pane host missing -- cannot build"); return false end
if not host then log(NE.L["Currency pane host missing -- cannot build"]); return false end
if scroll then return true end
local bg = host:CreateTexture(nil, "BACKGROUND")
@@ -117,7 +117,7 @@ end
-- requirement line (level / class / proficiency all render red when unmet). A
-- broken-item durability line is also red but still equippable, so skip it.
-- ----------------------------------------------------------------------------
local DUR_PREFIX = ((DURABILITY_TEMPLATE or "Durability %d / %d"):match("^(.-)%s*%%%d")) or "Durability"
local DUR_PREFIX = ((DURABILITY_TEMPLATE or NE.L["Durability %d / %d"]):match("^(.-)%s*%%%d")) or "Durability"
local scanTip
local function isUsable(bag, slot)
if not scanTip then
@@ -212,7 +212,7 @@ local function equipItem(btn)
local slotID = flyout and flyout._slotID
if not (data and slotID) then return end
if InCombatLockdown() then
if UIErrorsFrame then UIErrorsFrame:AddMessage(ERR_NOT_IN_COMBAT or "Can't do that in combat", 1, 0.1, 0.1, 1) end
if UIErrorsFrame then UIErrorsFrame:AddMessage(ERR_NOT_IN_COMBAT or NE.L["Can't do that in combat"], 1, 0.1, 0.1, 1) end
return
end
if CursorHasItem() or SpellIsTargeting() then return end
@@ -77,13 +77,13 @@ local GREEN = GREEN_FONT_COLOR or { r = 0.1, g = 1, b = 0.1 }
-- Never assign the StaticPopupDialogs global itself: writing it taints the global
-- for every reader, which blocks protected OnAccept handlers such as ReplaceEnchant.
StaticPopupDialogs["NE_CONFIRM_DELETE_EQUIPMENT_SET"] = {
text = L("CONFIRM_DELETE_EQUIPMENT_SET", "Delete the equipment set '%s'?"),
text = L("CONFIRM_DELETE_EQUIPMENT_SET", NE.L["Delete the equipment set '%s'?"]),
button1 = YES or "Yes", button2 = NO or "No",
OnAccept = function(_, data) if M then M.Delete(data) end; if CP.RefreshEquipPane then CP.RefreshEquipPane() end end,
timeout = 0, hideOnEscape = 1, whileDead = 1, showAlert = 1,
}
StaticPopupDialogs["NE_CONFIRM_SAVE_EQUIPMENT_SET"] = {
text = L("CONFIRM_SAVE_EQUIPMENT_SET", "Overwrite the equipment set '%s' with your currently equipped items?"),
text = L("CONFIRM_SAVE_EQUIPMENT_SET", NE.L["Overwrite the equipment set '%s' with your currently equipped items?"]),
button1 = YES or "Yes", button2 = NO or "No",
OnAccept = function(_, data)
if M and data then M.Save(data.setID, data.icon) end
@@ -177,7 +177,7 @@ local function buildPopup()
local header = p:CreateFontString(nil, "OVERLAY", "GameFontNormal")
header:SetPoint("TOP", p, "TOP", 0, -14)
header:SetText(L("GEARSETS_POPUP_TEXT", "Set Name:"))
header:SetText(L("GEARSETS_POPUP_TEXT", NE.L["Set Name:"]))
-- Name edit box.
local eb = CreateFrame("EditBox", "NE_GearManagerPopupEditBox", p, "InputBoxTemplate")
@@ -253,7 +253,7 @@ local function buildPopup()
if p.mode == "edit" then
if existingID and text ~= p.origName then
if UIErrorsFrame then UIErrorsFrame:AddMessage(L("EQUIPMENT_SETS_CANT_RENAME", "A set with that name already exists."), 1, 0.1, 0.1, 1) end
if UIErrorsFrame then UIErrorsFrame:AddMessage(L("EQUIPMENT_SETS_CANT_RENAME", NE.L["A set with that name already exists."]), 1, 0.1, 0.1, 1) end
return
end
M.Modify(p.setID, text, icon)
@@ -265,7 +265,7 @@ local function buildPopup()
CP._equipPane.selectedSetID = existingID
else
if M.GetNumSets() >= M.MAX_SETS then
if UIErrorsFrame then UIErrorsFrame:AddMessage(L("EQUIPMENT_SETS_TOO_MANY", "You have too many equipment sets."), 1, 0.1, 0.1, 1) end
if UIErrorsFrame then UIErrorsFrame:AddMessage(L("EQUIPMENT_SETS_TOO_MANY", NE.L["You have too many equipment sets."]), 1, 0.1, 0.1, 1) end
return
end
M.Create(text, icon)
@@ -438,7 +438,7 @@ local function makeGearButton(parent)
GameTooltip:AddLine(string.format("%d / %d %s", numEquipped or 0, numItems or 0,
L("EQUIPMENT_SETS_EQUIPPED", "equipped")), 0.8, 0.8, 0.8)
if (numLost or 0) > 0 then
GameTooltip:AddLine(string.format("%d %s", numLost, L("EQUIPMENT_SETS_MISSING", "items missing")), 1, 0.1, 0.1)
GameTooltip:AddLine(string.format("%d %s", numLost, L("EQUIPMENT_SETS_MISSING", NE.L["items missing"])), 1, 0.1, 0.1)
end
GameTooltip:Show()
end)
@@ -457,7 +457,7 @@ end
local function buildEquipPane()
if CP._equipPane then return CP._equipPane end
local ir = host()
if not ir then log("buildEquipPane: InsetRight not ready"); return nil end
if not ir then log(NE.L["buildEquipPane: InsetRight not ready"]); return nil end
local pane = CreateFrame("Frame", "NE_EquipmentManagerPane", ir)
pane:SetPoint("TOPLEFT", ir, "TOPLEFT", 3, -3)
@@ -587,7 +587,7 @@ local function refreshEquipPane()
if row.addSetButton then
b.setID = nil
b.text:SetText(L("PAPERDOLL_NEWEQUIPMENTSET", "New Equipment Set"))
b.text:SetText(L("PAPERDOLL_NEWEQUIPMENTSET", NE.L["New Equipment Set"]))
b.text:SetTextColor(GREEN.r, GREEN.g, GREEN.b)
-- DOWNPORT/REPORT: the "New Equipment Set" row has NO icon. ICON_NEW (Character-Plus) doesn't
-- exist on 3.3.5, so SetTexture left the pooled button showing the DELETED set's icon — clear +
@@ -688,7 +688,7 @@ local function equipByName(msg)
if not name then name = msg:gsub("^%s+", ""):gsub("%s+$", "") end
local chat = DEFAULT_CHAT_FRAME
if name == "" then
chat:AddMessage('|cffffcc55DragonUI|r Usage: /dnequip "Set Name"')
chat:AddMessage('|cffffcc55DragonUI|r Usage: /dnequip NE.L["Set Name"]')
return
end
if not M then chat:AddMessage("|cffff5555DragonUI|r Equipment manager not ready."); return end
@@ -416,7 +416,7 @@ function M.Use(setID)
if InCombatLockdown() then
pendingEquipSetID = setID
if UIErrorsFrame then
UIErrorsFrame:AddMessage(ERR_NOT_IN_COMBAT or "Can't do that in combat", 1.0, 0.1, 0.1, 1.0)
UIErrorsFrame:AddMessage(ERR_NOT_IN_COMBAT or NE.L["Can't do that in combat"], 1.0, 0.1, 0.1, 1.0)
end
return
end
@@ -28,7 +28,7 @@ NE.charpanel = NE.charpanel or {}
local CP = NE.charpanel
local function log(msg)
if CP._log then CP._log("HONOR: " .. tostring(msg)); return end
if CP._log then CP._log(NE.L["HONOR: "] .. tostring(msg)); return end
if NE.Log then NE.Log("HONOR", msg); return end
if DEFAULT_CHAT_FRAME then
DEFAULT_CHAT_FRAME:AddMessage("|cffffcc55DragonUI_NewEra|r [honor]: " .. tostring(msg))
@@ -65,15 +65,15 @@ local function disp(v)
end
-- Stat section schema (WotLK-adapted: no dishonorable kills).
local HK = L("HONORABLE_KILLS", "Honorable Kills")
local CONTRIB = L("CONTRIBUTION_POINTS", "Honor Points")
local HK = L("HONORABLE_KILLS", NE.L["Honorable Kills"])
local CONTRIB = L("CONTRIBUTION_POINTS", NE.L["Honor Points"])
local SECTIONS = {
{ title = L("HONOR_THIS_SESSION", "Today"), keys = { { HK, "sessionHK" }, { CONTRIB, "sessionCP" } } },
{ title = L("HONOR_YESTERDAY", "Yesterday"), keys = { { HK, "yHK" }, { CONTRIB, "yCP" } } },
{ title = L("HONOR_THISWEEK", "This Week"), keys = { { HK, "twHK" }, { CONTRIB, "twCP" } } },
{ title = L("HONOR_LASTWEEK", "Last Week"), keys = { { HK, "lwHK" }, { CONTRIB, "lwCP" } } },
{ title = L("HONOR_LIFETIME", "Lifetime"), keys = { { HK, "lHK" }, { L("HIGHEST_RANK", "Highest Rank"), "lRankName" } } },
{ title = L("HONOR_THISWEEK", NE.L["This Week"]), keys = { { HK, "twHK" }, { CONTRIB, "twCP" } } },
{ title = L("HONOR_LASTWEEK", NE.L["Last Week"]), keys = { { HK, "lwHK" }, { CONTRIB, "lwCP" } } },
{ title = L("HONOR_LIFETIME", "Lifetime"), keys = { { HK, "lHK" }, { L("HIGHEST_RANK", NE.L["Highest Rank"]), "lRankName" } } },
}
-- ---------------------------------------------------------------------------
@@ -153,7 +153,7 @@ local function buildRankBlock(parent)
r.hint:SetPoint("RIGHT", r, "RIGHT", -12, 0)
r.hint:SetJustifyH("LEFT")
r.hint:SetWordWrap(true)
r.hint:SetText("Earn honorable kills in battlegrounds and world PvP to gain rank.")
r.hint:SetText(NE.L["Earn honorable kills in battlegrounds and world PvP to gain rank."])
r.hint:Hide()
return r
end
@@ -359,7 +359,7 @@ local function refresh()
local apiOk = type(UnitPVPRank) == "function" and type(GetPVPSessionStats) == "function"
if not apiOk then
log("PvP API unavailable — Honor pane degraded")
log(NE.L["PvP API unavailable — Honor pane degraded"])
flat = {}
hideAllDynamic()
if unavailLabel then unavailLabel:Show() end
@@ -378,7 +378,7 @@ end
-- DOWNPORT/REPORT: surface (don't swallow) a refresh error so a broken Honor pane is diagnosable.
CP.RefreshHonor = function()
local ok, err = pcall(refresh)
if not ok then log("RefreshHonor error: " .. tostring(err)) end
if not ok then log(NE.L["RefreshHonor error: "] .. tostring(err)) end
end
local function recomputeVisible()
@@ -389,7 +389,7 @@ end
local function build()
local host = getPane()
if not host then log("Honor pane host missing — cannot build"); return false end
if not host then log(NE.L["Honor pane host missing — cannot build"]); return false end
if scroll then return true end
local bg = host:CreateTexture(nil, "BACKGROUND")
@@ -58,7 +58,7 @@ end
local function buildBorder()
local f = CP.frame
local inset = f and f.Inset
if not inset then log("InnerBorder: Inset not built yet"); return end
if not inset then log(NE.L["InnerBorder: Inset not built yet"]); return end
if inset._neInnerBorderBuilt then return end
inset._neInnerBorderBuilt = true
@@ -165,5 +165,5 @@ boot:SetScript("OnEvent", function()
buildBorder()
hookTabVisibility()
end)
if not ok then log("InnerBorder boot failed: " .. tostring(err)) end
if not ok then log(NE.L["InnerBorder boot failed: "] .. tostring(err)) end
end)
@@ -58,7 +58,7 @@ CP.DecorateInset = decorateInset
-- and never tracks the frame's width when the sidebar expands.
local function buildInset()
local f = CP.frame or (CP.BuildFrame and CP.BuildFrame())
if not f then log("buildInset: frame not built yet"); return nil end
if not f then log(NE.L["buildInset: frame not built yet"]); return nil end
if f.Inset then decorateInset(f.Inset); return f.Inset end
local inset = CreateFrame("Frame", "DragonUI_NewEra_CharacterInset", f)
@@ -76,10 +76,10 @@ end
-- NE.charpanel.SetSidebarExpanded (Wave 2 fills the body).
local function buildInsetRight()
local f = CP.frame
if not f then log("buildInsetRight: frame not built yet"); return nil end
if not f then log(NE.L["buildInsetRight: frame not built yet"]); return nil end
if f.InsetRight then decorateInset(f.InsetRight); return f.InsetRight end
local inset = f.Inset
if not inset then log("buildInsetRight: Inset not built yet"); return nil end
if not inset then log(NE.L["buildInsetRight: Inset not built yet"]); return nil end
local insetRight = CreateFrame("Frame", "DragonUI_NewEra_CharacterInsetRight", f)
-- DOWNPORT/REPORT: faithful NewEra — two anchors (auto-sizing): TOPLEFT = Inset.TOPRIGHT+(1,0),
@@ -105,5 +105,5 @@ boot:RegisterEvent("PLAYER_LOGIN")
boot:SetScript("OnEvent", function()
if NE.modules and NE.modules.IsEnabled and not NE.modules.IsEnabled("character") then return end
local ok, err = pcall(function() buildInset(); buildInsetRight() end)
if not ok then log("inset build failed: " .. tostring(err)) end
if not ok then log(NE.L["inset build failed: "] .. tostring(err)) end
end)
@@ -30,7 +30,7 @@ local function buildColoredLevelText()
classDisplay = classDisplay or ""
local coloredClass = (NE.color and NE.color.WrapClass)
and NE.color.WrapClass(classFile, classDisplay) or classDisplay
return string.format("Level %d %s %s", level, raceName, coloredClass)
return string.format(NE.L["Level %d %s %s"], level, raceName, coloredClass)
end
-- Our own guild string (GUILD_TITLE_TEMPLATE = "%s of <%s>"). Returns nil if not guilded.
@@ -131,5 +131,5 @@ boot:SetScript("OnEvent", function(_, event, unit)
if NE.modules and NE.modules.IsEnabled and not NE.modules.IsEnabled(MODULE) then return end
if event == "UNIT_LEVEL" and unit and unit ~= "player" then return end
local ok, err = pcall(function() installHooks(); applyLevelText() end)
if not ok then log("LevelText boot failed: " .. tostring(err)) end
if not ok then log(NE.L["LevelText boot failed: "] .. tostring(err)) end
end)
@@ -86,7 +86,7 @@ local function resolveRaceKey(raceFileName)
local seenKey = tostring(raceFileName)
if not LOGGED_UNKNOWN[seenKey] then
LOGGED_UNKNOWN[seenKey] = true
log("no race background for '" .. seenKey .. "', using " .. fallback)
log(NE.L["no race background for '"] .. seenKey .. "', using " .. fallback)
end
return fallback
end
@@ -192,5 +192,5 @@ boot:RegisterEvent("PLAYER_ENTERING_WORLD")
boot:SetScript("OnEvent", function()
if NE.modules and NE.modules.IsEnabled and not NE.modules.IsEnabled(MODULE) then return end
local ok, err = pcall(applyRaceBackground)
if not ok then log("ModelArea boot failed: " .. tostring(err)) end
if not ok then log(NE.L["ModelArea boot failed: "] .. tostring(err)) end
end)
@@ -310,5 +310,5 @@ boot:RegisterEvent("PLAYER_ENTERING_WORLD")
boot:SetScript("OnEvent", function()
if NE.modules and NE.modules.IsEnabled and not NE.modules.IsEnabled(MODULE) then return end
local ok, err = pcall(buildControlBar)
if not ok then log("ModelControls boot failed: " .. tostring(err)) end
if not ok then log(NE.L["ModelControls boot failed: "] .. tostring(err)) end
end)
@@ -45,7 +45,7 @@ local function applyClassBackground()
local _, classFile = UnitClass("player")
if not classFile then return end
local atlas = CLASS_BG_ATLAS[classFile]
if not atlas then log("no class bg atlas for " .. tostring(classFile)); return end
if not atlas then log(NE.L["no class bg atlas for "] .. tostring(classFile)); return end
local bg = pane._neClassBg
if not bg then
@@ -25,7 +25,7 @@ NE.charpanel = NE.charpanel or {}
local CP = NE.charpanel
local function log(msg)
if CP._log then CP._log("PET: " .. tostring(msg)); return end
if CP._log then CP._log(NE.L["PET: "] .. tostring(msg)); return end
if NE.Log then NE.Log("PET", msg); return end
if DEFAULT_CHAT_FRAME then
DEFAULT_CHAT_FRAME:AddMessage("|cffffcc55DragonUI_NewEra|r [pet]: " .. tostring(msg))
@@ -79,7 +79,7 @@ local function statResist(schoolIdx, schoolLabel)
local base, resistance = UnitResistance("pet", schoolIdx)
resistance = resistance or 0
return tostring(resistance), schoolLabel .. " " .. L("RESISTANCE", "Resistance") .. ": " .. resistance,
{ "Reduces " .. schoolLabel:lower() .. " damage taken." }
{ NE.L["Reduces "] .. schoolLabel:lower() .. " damage taken." }
end
end
@@ -94,12 +94,12 @@ local DEF_POWER = { label = L("MANA", "Mana"),
local _, t = UnitPowerType("pet")
return tostring(v), (_G[t or ""] or L("MANA", "Mana")) .. " " .. v
end }
local DEF_TRAINING = { label = L("PET_TRAIN_BUTTON", "Training Points"), compute = function()
local DEF_TRAINING = { label = L("PET_TRAIN_BUTTON", NE.L["Training Points"]), compute = function()
local total, spent = GetPetTrainingPoints()
total, spent = total or 0, spent or 0
local unspent = total - spent
return tostring(unspent), L("PET_TRAIN_BUTTON", "Training Points") .. ": " .. unspent,
{ string.format("Total: %d", total), string.format("Spent: %d", spent) }
return tostring(unspent), L("PET_TRAIN_BUTTON", NE.L["Training Points"]) .. ": " .. unspent,
{ string.format(NE.L["Total: %d"], total), string.format(NE.L["Spent: %d"], spent) }
end }
local DEF_DAMAGE = { label = L("DAMAGE", "Damage"), compute = function()
local minDmg, maxDmg = UnitDamage("pet")
@@ -108,15 +108,15 @@ local DEF_DAMAGE = { label = L("DAMAGE", "Damage"), compute = function()
local value = low .. " - " .. high
return value, L("DAMAGE", "Damage") .. " " .. value
end }
local DEF_AP = { label = L("ATTACK_POWER", "Attack Power"), compute = function()
local DEF_AP = { label = L("ATTACK_POWER", NE.L["Attack Power"]), compute = function()
local base, pos, neg = UnitAttackPower("pet")
local eff = (base or 0) + (pos or 0) + (neg or 0)
return tostring(eff), L("ATTACK_POWER", "Attack Power") .. ": " .. eff
return tostring(eff), L("ATTACK_POWER", NE.L["Attack Power"]) .. ": " .. eff
end }
local DEF_ATTACK_SPEED = { label = L("ATTACK_SPEED", "Attack Speed"), compute = function()
local DEF_ATTACK_SPEED = { label = L("ATTACK_SPEED", NE.L["Attack Speed"]), compute = function()
local speed = UnitAttackSpeed("pet") or 0
local value = string.format("%.2f", speed)
return value, L("ATTACK_SPEED", "Attack Speed") .. " " .. value
return value, L("ATTACK_SPEED", NE.L["Attack Speed"]) .. " " .. value
end }
local DEF_ARMOR = { label = L("ARMOR", "Armor"), compute = function()
local _, effective = UnitArmor("pet")
@@ -493,9 +493,9 @@ local function refresh()
local family = safe(UnitCreatureFamily, "pet")
local lvl = UnitLevel("pet") or 0
if family then
levelText:SetText(string.format(L("UNIT_LEVEL_TEMPLATE", "Level %d"), lvl) .. " " .. family)
levelText:SetText(string.format(L("UNIT_LEVEL_TEMPLATE", NE.L["Level %d"]), lvl) .. " " .. family)
else
levelText:SetText(string.format(L("UNIT_LEVEL_TEMPLATE", "Level %d"), lvl))
levelText:SetText(string.format(L("UNIT_LEVEL_TEMPLATE", NE.L["Level %d"]), lvl))
end
-- Loyalty / diet (hunter only).
@@ -503,7 +503,7 @@ local function refresh()
loyaltyText:SetText(loyalty or "")
local foods = { safe(GetPetFoodTypes) }
if foods[1] and BuildListString then
dietText:SetText((L("PET_DIET_TEMPLATE", "Diet: %s")):format(BuildListString(unpack(foods))))
dietText:SetText((L("PET_DIET_TEMPLATE", NE.L["Diet: %s"])):format(BuildListString(unpack(foods))))
else
dietText:SetText("")
end
@@ -543,7 +543,7 @@ end
local function build()
local host = getPane()
if not host then log("Pet pane host missing — cannot build"); return false end
if not host then log(NE.L["Pet pane host missing — cannot build"]); return false end
if scene then return true end
buildScene(host)
@@ -553,7 +553,7 @@ local function build()
noPetLabel = host:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
noPetLabel:SetPoint("CENTER", host, "CENTER", 0, 0)
noPetLabel:SetText(L("NO_PET", "You do not have a pet."))
noPetLabel:SetText(L("NO_PET", NE.L["You do not have a pet."]))
noPetLabel:Hide()
return true
end
@@ -28,7 +28,7 @@ local CP = NE.charpanel
-- Local logger (graceful — NE.Log may be absent on a standalone load).
-- ---------------------------------------------------------------------------
local function log(msg)
if CP._log then CP._log("REPUTATION: " .. tostring(msg)); return end
if CP._log then CP._log(NE.L["REPUTATION: "] .. tostring(msg)); return end
if NE.Log then NE.Log("REPUTATION", msg); return end
if DEFAULT_CHAT_FRAME then
DEFAULT_CHAT_FRAME:AddMessage("|cffffcc55DragonUI_NewEra|r [reputation]: " .. tostring(msg))
@@ -290,7 +290,7 @@ local function refresh()
flat = {}
if not (GetNumFactions and GetFactionInfo) then
log("GetNumFactions/GetFactionInfo unavailable — Reputation pane degraded")
log(NE.L["GetNumFactions/GetFactionInfo unavailable — Reputation pane degraded"])
updateScroll()
return
end
@@ -318,7 +318,7 @@ end
-- DOWNPORT/REPORT: surface (don't swallow) a refresh error so an empty Reputation pane is diagnosable.
CP.RefreshReputation = function()
local ok, err = pcall(refresh)
if not ok then log("RefreshReputation error: " .. tostring(err)) end
if not ok then log(NE.L["RefreshReputation error: "] .. tostring(err)) end
end
local function recomputeVisible()
@@ -329,7 +329,7 @@ end
local function build()
local host = getPane()
if not host then log("Reputation pane host missing — cannot build"); return false end
if not host then log(NE.L["Reputation pane host missing — cannot build"]); return false end
if scroll then return true end
-- Class-themed content background (matches the other secondary tabs); degrades silently.
@@ -375,7 +375,7 @@ local function build()
emptyLabel = host:CreateFontString(nil, "ARTWORK", "GameFontDisableSmall")
emptyLabel:SetPoint("TOP", host, "TOP", 0, -40)
emptyLabel:SetWidth(200)
emptyLabel:SetText(_G.REPUTATION and (_G.REPUTATION .. " unavailable") or "No reputations.")
emptyLabel:SetText(_G.REPUTATION and (_G.REPUTATION .. " unavailable") or NE.L["No reputations."])
emptyLabel:Hide()
recomputeVisible()
@@ -47,7 +47,7 @@ end
local function toggleMenu(cog)
if not (NE.menu and NE.menu.ToggleAnchored) then
log("NE.menu unavailable; the settings cog has nothing to open")
log(NE.L["NE.menu unavailable; the settings cog has nothing to open"])
return
end
NE.menu.ToggleAnchored(menuGenerator, cog,
@@ -110,5 +110,5 @@ boot:SetScript("OnEvent", function()
-- The frame owns the corner we anchor into; build it first (idempotent), same as CloseButton.lua.
if CP.BuildFrame then pcall(CP.BuildFrame) end
local ok, err = pcall(buildCog)
if not ok then log("settings cog build failed: " .. tostring(err)) end
if not ok then log(NE.L["settings cog build failed: "] .. tostring(err)) end
end)
@@ -138,7 +138,7 @@ end
-- Stock shows melee/ranged haste inside the Attack Speed tooltip (PaperDollFrame_SetAttackSpeed),
-- not as its own row. Mirror that: "Haste rating N (X% haste)".
local function hasteLine(cr)
return format(_G.CR_HASTE_RATING_TOOLTIP or "Haste rating %d (%.2f%% haste)", rating(cr), ratingBonus(cr))
return format(_G.CR_HASTE_RATING_TOOLTIP or NE.L["Haste rating %d (%.2f%% haste)"], rating(cr), ratingBonus(cr))
end
-- Self-contained melee hand maths (DUIC ComputeHand — replaces the retail combat dummy).
@@ -313,7 +313,7 @@ local SECTIONS = {
{ id = "health", name = L("HEALTH", "Health"), func = function()
local hp = UnitHealthMax("player") or 0
return tostring(hp), (L("HEALTH", "Health") .. " " .. hp),
L("MAX_HEALTH_TOOLTIP", "Maximum Health. If your health reaches zero, you will die.")
L("MAX_HEALTH_TOOLTIP", NE.L["Maximum Health. If your health reaches zero, you will die."])
end },
{ id = "power", name = (function()
local _, token = UnitPowerType("player")
@@ -325,12 +325,12 @@ local SECTIONS = {
return tostring(pmax), (plabel .. " " .. pmax),
format(L["Your maximum %s."], plabel)
end },
{ id = SPEED_ID, name = L("STAT_MOVEMENT_SPEED", "Movement Speed"), func = function()
local label = L("STAT_MOVEMENT_SPEED", "Movement Speed")
{ id = SPEED_ID, name = L("STAT_MOVEMENT_SPEED", NE.L["Movement Speed"]), func = function()
local label = L("STAT_MOVEMENT_SPEED", NE.L["Movement Speed"])
local pct, measured = movementSpeedPct()
local val = format("%d%%", floor(pct + 0.5))
return val, (label .. " " .. val), format(
"Your running speed. 100%% is normal (%d yards/second); mounts and speed effects raise it,"
NE.L["Your running speed. 100%% is normal (%d yards/second); mounts and speed effects raise it,"]
.. " slows lower it. Walking and backpedalling are ignored."
.. (measured and "" or "\nAssuming the 100%% baseline — move a step to measure it."),
BASE_SPEED)
@@ -339,11 +339,11 @@ local SECTIONS = {
local label = L("DURABILITY", "Durability")
local pct, cur, maxTotal, worst = equipmentDurability()
if not pct then
return "--", label, "You have nothing equipped that can take durability damage."
return "--", label, NE.L["You have nothing equipped that can take durability damage."]
end
local val = format("%d%%", floor(pct + 0.5))
return val, (label .. " " .. val), format(
"Condition of your equipment: %d of %d durability across every equipped item that has it."
NE.L["Condition of your equipment: %d of %d durability across every equipped item that has it."]
.. "\nWorst slot: %d%%."
.. "\nItems with no durability (rings, neck, trinkets, shirt, tabard) are not counted.",
cur, maxTotal, floor(worst + 0.5))
@@ -359,11 +359,11 @@ local SECTIONS = {
-- values we don't have here — passed through raw they printed literal "%d"/"%.2f%%". Use accurate
-- static descriptions instead so the tooltip body is correct with no unfilled placeholders.
local ATTR_DESC = {
"Increases attack power for some classes and the damage you block with a shield.",
"Increases critical strike chance, dodge chance, armor, and (for some classes) attack power.",
"Increases your maximum health.",
"Increases your maximum mana and your spell critical strike chance.",
"Increases your health and mana regeneration while out of combat.",
NE.L["Increases attack power for some classes and the damage you block with a shield."],
NE.L["Increases critical strike chance, dodge chance, armor, and (for some classes) attack power."],
NE.L["Increases your maximum health."],
NE.L["Increases your maximum mana and your spell critical strike chance."],
NE.L["Increases your health and mana regeneration while out of combat."],
}
local t = {}
for i = 1, 5 do
@@ -389,7 +389,7 @@ local SECTIONS = {
local minD, maxD, minO, maxO, physPos, physNeg, percent = UnitDamage("player")
local mhText, mhDps = computeHand(minD, maxD, physPos, physNeg, percent, speed)
local lines = {
{ left = L("INVTYPE_WEAPONMAINHAND", "Main Hand") },
{ left = L("INVTYPE_WEAPONMAINHAND", NE.L["Main Hand"]) },
{ left = L("ATTACK_SPEED_SECONDS", "Speed"), right = format("%.2f", speed or 0) },
{ left = L("DAMAGE", "Damage"), right = mhText },
{ left = L("DAMAGE_PER_SECOND", "DPS"), right = format("%.1f", mhDps) },
@@ -397,7 +397,7 @@ local SECTIONS = {
if offSpeed then
local ohText, ohDps = computeHand(minO, maxO, physPos, physNeg, percent, offSpeed)
lines[#lines + 1] = { left = " " }
lines[#lines + 1] = { left = L("INVTYPE_WEAPONOFFHAND", "Off Hand"), white = true }
lines[#lines + 1] = { left = L("INVTYPE_WEAPONOFFHAND", NE.L["Off Hand"]), white = true }
lines[#lines + 1] = { left = L("ATTACK_SPEED_SECONDS", "Speed"), right = format("%.2f", offSpeed) }
lines[#lines + 1] = { left = L("DAMAGE", "Damage"), right = ohText }
lines[#lines + 1] = { left = L("DAMAGE_PER_SECOND", "DPS"), right = format("%.1f", ohDps) }
@@ -409,38 +409,38 @@ local SECTIONS = {
local minD, maxD, minO, maxO, physPos, physNeg, percent = UnitDamage("player")
local _, mhDps = computeHand(minD, maxD, physPos, physNeg, percent, speed)
local frameText = format("%.1f", mhDps)
local lines = { { left = L("DAMAGE_PER_SECOND", "Damage Per Second") },
{ left = L("INVTYPE_WEAPONMAINHAND", "Main Hand"), right = frameText } }
local lines = { { left = L("DAMAGE_PER_SECOND", NE.L["Damage Per Second"]) },
{ left = L("INVTYPE_WEAPONMAINHAND", NE.L["Main Hand"]), right = frameText } }
if offSpeed then
local _, ohDps = computeHand(minO, maxO, physPos, physNeg, percent, offSpeed)
lines[#lines + 1] = { left = L("INVTYPE_WEAPONOFFHAND", "Off Hand"), right = format("%.1f", ohDps) }
lines[#lines + 1] = { left = L("INVTYPE_WEAPONOFFHAND", NE.L["Off Hand"]), right = format("%.1f", ohDps) }
end
return frameText, nil, nil, lines
end },
{ id = "ap", name = L("ATTACK_POWER", "Attack Power"), func = function()
{ id = "ap", name = L("ATTACK_POWER", NE.L["Attack Power"]), func = function()
local base, posBuff, negBuff = UnitAttackPower("player")
local frameText, tip = formatStat(L("MELEE_ATTACK_POWER", "Attack Power"), base, posBuff, negBuff)
local frameText, tip = formatStat(L("MELEE_ATTACK_POWER", NE.L["Attack Power"]), base, posBuff, negBuff)
local body = format(L("MELEE_ATTACK_POWER_TOOLTIP",
"Increases your damage with melee weapons by %.1f damage per second."),
NE.L["Increases your damage with melee weapons by %.1f damage per second."]),
max((base or 0) + (posBuff or 0) + (negBuff or 0), 0) / MAGIC_AP)
return frameText, tip, body
end },
{ id = "attackspeed", name = L("ATTACK_SPEED", "Attack Speed"), func = function()
{ id = "attackspeed", name = L("ATTACK_SPEED", NE.L["Attack Speed"]), func = function()
local speed, offSpeed = UnitAttackSpeed("player")
local frameText = format("%.2f", speed or 0)
if offSpeed then frameText = frameText .. " / " .. format("%.2f", offSpeed) end
return frameText, (L("ATTACK_SPEED", "Attack Speed") .. " " .. frameText),
L("Seconds between melee swings.", "Seconds between melee swings.") .. "\n" .. hasteLine(CR_HASTE_MELEE or 18)
return frameText, (L("ATTACK_SPEED", NE.L["Attack Speed"]) .. " " .. frameText),
L("Seconds between melee swings.", NE.L["Seconds between melee swings."]) .. "\n" .. hasteLine(CR_HASTE_MELEE or 18)
end },
{ id = "hit", name = L("COMBAT_RATING_NAME6", "Hit Rating"), func = function()
{ id = "hit", name = L("COMBAT_RATING_NAME6", NE.L["Hit Rating"]), func = function()
local r = rating(CR_HIT_MELEE)
return tostring(r), (L("COMBAT_RATING_NAME6", "Hit") .. " " .. r),
format(L["Improves your chance to hit by %.2f%%."], ratingBonus(CR_HIT_MELEE))
end },
{ id = "crit", name = L("MELEE_CRIT_CHANCE", "Crit Chance"), func = function()
{ id = "crit", name = L("MELEE_CRIT_CHANCE", NE.L["Crit Chance"]), func = function()
local crit = (GetCritChance and GetCritChance()) or 0
local str = format("%.2f%%", crit)
return str, (L("MELEE_CRIT_CHANCE", "Crit Chance") .. " " .. str),
return str, (L("MELEE_CRIT_CHANCE", NE.L["Crit Chance"]) .. " " .. str),
L["Chance of melee attacks dealing extra damage."]
end },
{ id = "arp", name = L["Armor Penetration"], func = function()
@@ -484,32 +484,32 @@ local SECTIONS = {
local dps = (speed and speed > 0) and (((lowD or 0) + (hiD or 0)) / 2 / speed) or 0
return format("%.1f", dps)
end },
{ id = "r_ap", name = L("ATTACK_POWER", "Attack Power"), func = function()
{ id = "r_ap", name = L("ATTACK_POWER", NE.L["Attack Power"]), func = function()
if not hasRanged() then return L("NOT_APPLICABLE", "N/A") end
if HasWandEquipped and HasWandEquipped() then return L("NOT_APPLICABLE", "N/A") end
local base, posBuff, negBuff = UnitRangedAttackPower("player")
local frameText, tip = formatStat(L("RANGED_ATTACK_POWER", "Ranged Attack Power"), base, posBuff, negBuff)
local frameText, tip = formatStat(L("RANGED_ATTACK_POWER", NE.L["Ranged Attack Power"]), base, posBuff, negBuff)
local body = format(L("RANGED_ATTACK_POWER_TOOLTIP",
"Increases your damage with ranged weapons by %.1f damage per second."),
NE.L["Increases your damage with ranged weapons by %.1f damage per second."]),
max((base or 0) + (posBuff or 0) + (negBuff or 0), 0) / MAGIC_AP)
return frameText, tip, body
end },
{ id = "r_speed", name = L("ATTACK_SPEED", "Attack Speed"), func = function()
{ id = "r_speed", name = L("ATTACK_SPEED", NE.L["Attack Speed"]), func = function()
if not hasRanged() then return L("NOT_APPLICABLE", "N/A") end
local speed = UnitRangedDamage("player")
local ft = format("%.2f", speed or 0)
return ft, (L("ATTACK_SPEED", "Attack Speed") .. " " .. ft),
return ft, (L("ATTACK_SPEED", NE.L["Attack Speed"]) .. " " .. ft),
hasteLine(CR_HASTE_RANGED or 19)
end },
{ id = "r_hit", name = L("STAT_HIT_CHANCE", "Hit Chance"), func = function()
{ id = "r_hit", name = L("STAT_HIT_CHANCE", NE.L["Hit Chance"]), func = function()
if not hasRanged() then return L("NOT_APPLICABLE", "N/A") end
local hit = (GetHitModifier and GetHitModifier()) or 0
return format("%.2f%%", hit), L("STAT_HIT_CHANCE", "Hit Chance"), L["Reduces your chance to miss."]
return format("%.2f%%", hit), L("STAT_HIT_CHANCE", NE.L["Hit Chance"]), L["Reduces your chance to miss."]
end },
{ id = "r_crit", name = L("STAT_CRITICAL_STRIKE", "Crit Chance"), func = function()
{ id = "r_crit", name = L("STAT_CRITICAL_STRIKE", NE.L["Crit Chance"]), func = function()
if not hasRanged() then return L("NOT_APPLICABLE", "N/A") end
local crit = (GetRangedCritChance and GetRangedCritChance()) or 0
return format("%.2f%%", crit), (L("CRIT_CHANCE", "Crit Chance") .. format(" %.2f%%", crit)),
return format("%.2f%%", crit), (L("CRIT_CHANCE", NE.L["Crit Chance"]) .. format(" %.2f%%", crit)),
L["Chance of ranged attacks dealing extra damage."]
end },
},
@@ -518,7 +518,7 @@ local SECTIONS = {
{
id = "spell", title = L("STAT_CATEGORY_SPELL", L("MAGIC_SCHOOLS", "Spell")),
stats = {
{ id = "s_damage", name = L("BONUS_DAMAGE", "Spell Power"), func = function()
{ id = "s_damage", name = L("BONUS_DAMAGE", NE.L["Spell Power"]), func = function()
local holy = 2
local minMod = (GetSpellBonusDamage and GetSpellBonusDamage(holy)) or 0
local maxSchool = MAX_SPELL_SCHOOLS or 7
@@ -526,15 +526,15 @@ local SECTIONS = {
local b = (GetSpellBonusDamage and GetSpellBonusDamage(i)) or 0
minMod = min(minMod, b)
end
return tostring(minMod), (L("BONUS_DAMAGE", "Spell Power") .. " " .. minMod),
return tostring(minMod), (L("BONUS_DAMAGE", NE.L["Spell Power"]) .. " " .. minMod),
L["Lowest bonus damage across schools. Hover the paper-doll stat for a breakdown."]
end },
{ id = "s_healing", name = L("BONUS_HEALING", "Bonus Healing"), func = function()
{ id = "s_healing", name = L("BONUS_HEALING", NE.L["Bonus Healing"]), func = function()
local heal = (GetSpellBonusHealing and GetSpellBonusHealing()) or 0
return format(" %d", heal), (L("BONUS_HEALING", "Bonus Healing") .. " " .. heal),
return format(" %d", heal), (L("BONUS_HEALING", NE.L["Bonus Healing"]) .. " " .. heal),
L["Bonus power applied to healing spells."]
end },
{ id = "s_hit", name = L("COMBAT_RATING_NAME8", "Spell Hit"), func = function()
{ id = "s_hit", name = L("COMBAT_RATING_NAME8", NE.L["Spell Hit"]), func = function()
local r = rating(CR_HIT_SPELL)
local pen = (GetSpellPenetration and GetSpellPenetration()) or 0
-- Faithful to the stock Spell Hit tooltip (PaperDollFrame.lua:342, issue #11): the spell
@@ -548,18 +548,18 @@ local SECTIONS = {
body = format("Improves your chance to hit with spells by %.2f%%.\n\nSpell Penetration %d (Reduces enemy resistances by %d)",
ratingBonus(CR_HIT_SPELL), pen, pen)
end
return tostring(r), (L("COMBAT_RATING_NAME8", "Spell Hit") .. " " .. r), body
return tostring(r), (L("COMBAT_RATING_NAME8", NE.L["Spell Hit"]) .. " " .. r), body
end },
{ id = "s_regen", name = L("MANA_REGEN", "Mana Regen"), func = function()
{ id = "s_regen", name = L("MANA_REGEN", NE.L["Mana Regen"]), func = function()
if not (UnitHasMana and UnitHasMana("player")) then return L("NOT_APPLICABLE", "N/A") end
local ok, base, casting = pcall(GetManaRegen)
if not ok then return "--" end
base = floor((base or 0) * 5.0)
casting = floor((casting or 0) * 5.0)
return tostring(base), (L("MANA_REGEN", "Mana Regen") .. " " .. base),
return tostring(base), (L("MANA_REGEN", NE.L["Mana Regen"]) .. " " .. base),
format(L["Not casting: %d / 5s\nWhile casting: %d / 5s"], base, casting)
end },
{ id = "s_crit", name = L("SPELL_CRIT_CHANCE", "Spell Crit"), func = function()
{ id = "s_crit", name = L("SPELL_CRIT_CHANCE", NE.L["Spell Crit"]), func = function()
local holy = 2
local minCrit = (GetSpellCritChance and GetSpellCritChance(holy)) or 0
local maxSchool = MAX_SPELL_SCHOOLS or 7
@@ -568,14 +568,14 @@ local SECTIONS = {
minCrit = min(minCrit, c)
end
local str = format("%.2f%%", minCrit)
return str, (L("SPELL_CRIT_CHANCE", "Spell Crit") .. " " .. str),
return str, (L("SPELL_CRIT_CHANCE", NE.L["Spell Crit"]) .. " " .. str),
L["Chance of spells dealing extra damage."]
end },
{ id = "s_haste", name = L["Haste Rating"], func = function()
local cr = CR_HASTE_SPELL or 20
local r = rating(cr)
return tostring(r), (L["Haste Rating"] .. " " .. r),
format(_G.SPELL_HASTE_TOOLTIP or "Increases the speed that your spells cast by %.2f%%.", ratingBonus(cr))
format(_G.SPELL_HASTE_TOOLTIP or NE.L["Increases the speed that your spells cast by %.2f%%."], ratingBonus(cr))
end },
},
},
@@ -605,18 +605,18 @@ local SECTIONS = {
{ id = "dodge", name = L("DODGE", "Dodge"), func = function()
local v = (GetDodgeChance and GetDodgeChance()) or 0
local str = format(" %.2f%%", v)
return str, (L("DODGE_CHANCE", "Dodge Chance") .. str), L["Chance to dodge enemy melee attacks."]
return str, (L("DODGE_CHANCE", NE.L["Dodge Chance"]) .. str), L["Chance to dodge enemy melee attacks."]
end },
{ id = "parry", name = L("PARRY", "Parry"), func = function()
local v = (GetParryChance and GetParryChance()) or 0
local str = format(" %.2f%%", v)
return str, (L("PARRY_CHANCE", "Parry Chance") .. str), L["Chance to parry. Requires a weapon."]
return str, (L("PARRY_CHANCE", NE.L["Parry Chance"]) .. str), L["Chance to parry. Requires a weapon."]
end },
{ id = "block", name = L("BLOCK", "Block"), func = function()
local v = (GetBlockChance and GetBlockChance()) or 0
local str = format(" %.2f%%", v)
local blockVal = (GetShieldBlock and GetShieldBlock()) or 0
return str, (L("BLOCK_CHANCE", "Block Chance") .. str),
return str, (L("BLOCK_CHANCE", NE.L["Block Chance"]) .. str),
format(L["Chance to block. Requires a shield.\nBlock Value: %d"], blockVal)
end },
{ id = "resilience", name = L["Resilience"], func = function()
@@ -659,7 +659,7 @@ local SECTIONS = {
if negative < 0 then tip = tip .. RED .. " " .. negative .. CLOSE end
tip = tip .. ")"
end
return frameText, tip, (L("RESISTANCE_TOOLTIP_DESC", "Reduces magic damage taken."))
return frameText, tip, (L("RESISTANCE_TOOLTIP_DESC", NE.L["Reduces magic damage taken."]))
end,
}
end
@@ -692,7 +692,7 @@ local PET_SECTIONS = {
local t = {
{ id = "pet_health", name = L("HEALTH", "Health"), func = function()
local v = UnitHealthMax("pet") or 0
return tostring(v), (L("HEALTH", "Health") .. " " .. v), "Your pet's maximum health."
return tostring(v), (L("HEALTH", "Health") .. " " .. v), NE.L["Your pet's maximum health."]
end },
{ id = "pet_power", name = L("MANA", "Mana"),
relabel = petPower,
@@ -704,14 +704,14 @@ local PET_SECTIONS = {
-- Hunter pets earn training points (warlock minions return nil → "--"). Built once, so we always
-- include the row and let the guarded getter degrade rather than gate on class at file-load time
-- (UnitClass is nil that early).
t[#t + 1] = { id = "pet_training", name = L("PET_TRAIN_BUTTON", "Training Points"),
t[#t + 1] = { id = "pet_training", name = L("PET_TRAIN_BUTTON", NE.L["Training Points"]),
func = function()
if not petIsHunter() then return L("NOT_APPLICABLE", "N/A") end
local total, spent = GetPetTrainingPoints()
total, spent = total or 0, spent or 0
local unspent = total - spent
return tostring(unspent),
(L("PET_TRAIN_BUTTON", "Training Points") .. ": " .. unspent),
(L("PET_TRAIN_BUTTON", NE.L["Training Points"]) .. ": " .. unspent),
format("Total: %d\nSpent: %d", total, spent)
end }
return t
@@ -742,15 +742,15 @@ local PET_SECTIONS = {
local v = low .. " - " .. high
return v, (L("DAMAGE", "Damage") .. " " .. v), nil
end },
{ id = "pet_ap", name = L("ATTACK_POWER", "Attack Power"), func = function()
{ id = "pet_ap", name = L("ATTACK_POWER", NE.L["Attack Power"]), func = function()
local base, pos, neg = UnitAttackPower("pet")
local eff = (base or 0) + (pos or 0) + (neg or 0)
return tostring(eff), (L("ATTACK_POWER", "Attack Power") .. " " .. eff), nil
return tostring(eff), (L("ATTACK_POWER", NE.L["Attack Power"]) .. " " .. eff), nil
end },
{ id = "pet_aspeed", name = L("ATTACK_SPEED", "Attack Speed"), func = function()
{ id = "pet_aspeed", name = L("ATTACK_SPEED", NE.L["Attack Speed"]), func = function()
local speed = UnitAttackSpeed("pet") or 0
local v = format("%.2f", speed)
return v, (L("ATTACK_SPEED", "Attack Speed") .. " " .. v), nil
return v, (L("ATTACK_SPEED", NE.L["Attack Speed"]) .. " " .. v), nil
end },
},
},
@@ -775,7 +775,7 @@ local PET_SECTIONS = {
local _, total = UnitResistance("pet", i)
total = total or 0
return tostring(total), (schoolName .. " " .. total),
("Reduces " .. schoolName:lower() .. " damage taken.")
(NE.L["Reduces "] .. schoolName:lower() .. " damage taken.")
end }
end
return t
@@ -1154,7 +1154,7 @@ local function createItemLevelRow(content)
local label = header:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
label:SetPoint("CENTER", bg, "CENTER", 0, 1)
label:SetText(L("ITEM_LEVEL_LABEL", "Item Level"))
label:SetText(L("ITEM_LEVEL_LABEL", NE.L["Item Level"]))
header:RegisterForClicks("LeftButtonUp")
header:SetScript("OnClick", function()
@@ -1346,7 +1346,7 @@ local function buildSidebar()
if CP._sidebar then return CP._sidebar end
local f = CP.frame
local insetRight = f and (f.InsetRight or CP.InsetRight)
if not insetRight then log("buildSidebar: InsetRight not ready"); return nil end
if not insetRight then log(NE.L["buildSidebar: InsetRight not ready"]); return nil end
-- Publish the contract-surface aliases (the brief names NE.charpanel.{Inset, InsetRight}); the
-- foundation only set them as frame fields. Non-destructive.
@@ -1439,7 +1439,7 @@ local function selectSidebar(i)
CP._activeSidebar = 1; i = 1
end
else
log("SelectSidebar(3): ShowEquipManager absent; staying on stats")
log(NE.L["SelectSidebar(3): ShowEquipManager absent; staying on stats"])
if pane then pane:Show(); refreshSidebar() end
CP._activeSidebar = 1; i = 1
end
@@ -1596,5 +1596,5 @@ boot:RegisterEvent("PLAYER_ENTERING_WORLD")
boot:SetScript("OnEvent", function()
if NE.modules and NE.modules.IsEnabled and not NE.modules.IsEnabled("character") then return end
local ok, err = pcall(buildSidebar)
if not ok then log("sidebar build failed: " .. tostring(err)) end
if not ok then log(NE.L["sidebar build failed: "] .. tostring(err)) end
end)
@@ -181,7 +181,7 @@ end
local function buildSidebarTabs()
local f = CP.frame
local insetRight = f and (f.InsetRight or CP.InsetRight)
if not insetRight then log("buildSidebarTabs: InsetRight not ready"); return end
if not insetRight then log(NE.L["buildSidebarTabs: InsetRight not ready"]); return end
if f._neSidebarTabs then return f._neSidebarTabs end
local strip = CreateFrame("Frame", "NE_PaperDollSidebarTabs", insetRight)
@@ -205,7 +205,7 @@ local function buildSidebarTabs()
-- Build right-to-left (NewEra chain anchors).
-- Tab3: Equipment Manager → SelectSidebar(3) (which routes to ShowEquipManager, guarded).
local tab3 = buildTab(3, strip, "NE_PaperDollSidebarTab3", "tex", TC.equipIcon,
{ title = L("PAPERDOLL_EQUIPMENTMANAGER", "Equipment Manager") }, true,
{ title = L("PAPERDOLL_EQUIPMENTMANAGER", NE.L["Equipment Manager"]) }, true,
function() if CP.SelectSidebar then CP.SelectSidebar(3) end end)
tab3:SetPoint("BOTTOMRIGHT", strip, "BOTTOMRIGHT", -30, 0)
strip.Tab3 = tab3
@@ -248,5 +248,5 @@ boot:RegisterEvent("PLAYER_ENTERING_WORLD")
boot:SetScript("OnEvent", function()
if NE.modules and NE.modules.IsEnabled and not NE.modules.IsEnabled("character") then return end
local ok, err = pcall(buildSidebarTabs)
if not ok then log("sidebar tabs build failed: " .. tostring(err)) end
if not ok then log(NE.L["sidebar tabs build failed: "] .. tostring(err)) end
end)
@@ -22,7 +22,7 @@ NE.charpanel = NE.charpanel or {}
local CP = NE.charpanel
local function log(msg)
if CP._log then CP._log("SKILLS: " .. tostring(msg)); return end
if CP._log then CP._log(NE.L["SKILLS: "] .. tostring(msg)); return end
if NE.Log then NE.Log("SKILLS", msg); return end
if DEFAULT_CHAT_FRAME then
DEFAULT_CHAT_FRAME:AddMessage("|cffffcc55DragonUI_NewEra|r [skills]: " .. tostring(msg))
@@ -244,7 +244,7 @@ local function refresh()
flat = {}
if not (GetNumSkillLines and GetSkillLineInfo) then
log("GetNumSkillLines/GetSkillLineInfo unavailable — Skills pane degraded")
log(NE.L["GetNumSkillLines/GetSkillLineInfo unavailable — Skills pane degraded"])
updateScroll()
return
end
@@ -269,7 +269,7 @@ end
-- DOWNPORT/REPORT: surface (don't swallow) a refresh error so an empty Skills pane is diagnosable.
CP.RefreshSkills = function()
local ok, err = pcall(refresh)
if not ok then log("RefreshSkills error: " .. tostring(err)) end
if not ok then log(NE.L["RefreshSkills error: "] .. tostring(err)) end
end
local function recomputeVisible()
@@ -280,7 +280,7 @@ end
local function build()
local host = getPane()
if not host then log("Skills pane host missing — cannot build"); return false end
if not host then log(NE.L["Skills pane host missing — cannot build"]); return false end
if scroll then return true end
local bg = host:CreateTexture(nil, "BACKGROUND")
@@ -324,7 +324,7 @@ local function build()
emptyLabel = host:CreateFontString(nil, "ARTWORK", "GameFontDisableSmall")
emptyLabel:SetPoint("TOP", host, "TOP", 0, -40)
emptyLabel:SetWidth(200)
emptyLabel:SetText(_G.SKILLS and (_G.SKILLS .. " unavailable") or "No skills.")
emptyLabel:SetText(_G.SKILLS and (_G.SKILLS .. " unavailable") or NE.L["No skills."])
emptyLabel:Hide()
recomputeVisible()
@@ -91,7 +91,7 @@ end
local function applyAll()
for name, spec in pairs(SLOT_SIDES) do
local ok, err = pcall(applyFrameToSlot, name, spec)
if not ok then log("slotframe " .. name .. " failed: " .. tostring(err)) end
if not ok then log(NE.L["slotframe "] .. name .. " failed: " .. tostring(err)) end
end
end
@@ -140,5 +140,5 @@ boot:RegisterEvent("PLAYER_ENTERING_WORLD")
boot:SetScript("OnEvent", function()
if NE.modules and NE.modules.IsEnabled and not NE.modules.IsEnabled(MODULE) then return end
local ok, err = pcall(applySlotFrames)
if not ok then log("SlotFrames boot failed: " .. tostring(err)) end
if not ok then log(NE.L["SlotFrames boot failed: "] .. tostring(err)) end
end)
@@ -123,7 +123,7 @@ CP._resizeTab = resizeTab
-- Build the DF metal tab row across the bottom of our frame.
local function buildTabs()
local f = CP.frame or (CP.BuildFrame and CP.BuildFrame())
if not f then log("buildTabs: frame not built yet"); return end
if not f then log(NE.L["buildTabs: frame not built yet"]); return end
for i, def in ipairs(TABS) do
local name = "DragonUI_NewEra_CharacterTab" .. i
@@ -357,5 +357,5 @@ boot:RegisterEvent("PLAYER_LOGIN")
boot:SetScript("OnEvent", function()
if NE.modules and NE.modules.IsEnabled and not NE.modules.IsEnabled("character") then return end
local ok, err = pcall(buildTabs)
if not ok then log("tab build failed: " .. tostring(err)) end
if not ok then log(NE.L["tab build failed: "] .. tostring(err)) end
end)
@@ -16,7 +16,7 @@ local CP = NE.charpanel
local ROW_H = 22
local function log(msg) if CP._log then CP._log("TITLES: " .. tostring(msg)) elseif NE.Log then NE.Log("TITLES", msg) end end
local function log(msg) if CP._log then CP._log(NE.L["TITLES: "] .. tostring(msg)) elseif NE.Log then NE.Log("TITLES", msg) end end
local function getLocaleString(global, fallback)
if type(global) == "string" and global ~= "" then
@@ -208,7 +208,7 @@ local function refresh()
end
CP.RefreshTitles = function()
local ok, err = pcall(refresh)
if not ok then log("RefreshTitles error: " .. tostring(err)) end
if not ok then log(NE.L["RefreshTitles error: "] .. tostring(err)) end
end
local function recomputeVisible()
@@ -222,7 +222,7 @@ local function build()
if not h then
-- Pane lives in InsetRight; create it lazily.
local ir = host()
if not ir then log("build: InsetRight not ready"); return false end
if not ir then log(NE.L["build: InsetRight not ready"]); return false end
pane = CreateFrame("Frame", "NE_TitlesPane", ir)
pane:SetPoint("TOPLEFT", ir, "TOPLEFT", 3, -3)
pane:SetPoint("BOTTOMRIGHT", ir, "BOTTOMRIGHT", -3, 2)
@@ -260,7 +260,7 @@ local function build()
emptyLabel = h:CreateFontString(nil, "ARTWORK", "GameFontDisableSmall")
emptyLabel:SetPoint("TOP", h, "TOP", 0, -40)
emptyLabel:SetWidth(180)
emptyLabel:SetText(L("NO_TITLES_TOOLTIP", "You haven't earned any titles."))
emptyLabel:SetText(L("NO_TITLES_TOOLTIP", NE.L["You haven't earned any titles."]))
emptyLabel:Hide()
recomputeVisible()
@@ -495,7 +495,7 @@ local function createJournal(kind)
-- (no creatureDisplayID is available for those — only the API's real companions can preview).
notCollectedHint = displayGroup:CreateFontString(nil, "OVERLAY", "GameFontDisable")
notCollectedHint:SetPoint("CENTER", model, "CENTER", 0, 0)
notCollectedHint:SetText(NOT_COLLECTED or "Not yet collected")
notCollectedHint:SetText(NOT_COLLECTED or NE.L["Not yet collected"])
notCollectedHint:Hide()
-- Info header (icon + name + source + lore), over the top of the backdrop.
@@ -525,7 +525,7 @@ local function createJournal(kind)
fav:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_LEFT")
GameTooltip:SetText(BATTLE_PET_FAVORITE or "Favourite", 1, 0.82, 0)
GameTooltip:AddLine("Keeps this at the front of the list.", 1, 1, 1, true)
GameTooltip:AddLine(NE.L["Keeps this at the front of the list."], 1, 1, 1, true)
GameTooltip:Show()
end)
fav:SetScript("OnLeave", function() GameTooltip:Hide() end)
@@ -553,8 +553,8 @@ local function createJournal(kind)
emptyLabel = displayGroup:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge")
emptyLabel:SetPoint("CENTER", displayGroup, "CENTER", 0, 0)
emptyLabel:SetText(isMount and (ERR_NO_RIDING_SKILL or "You have not learned any mounts.")
or "You have not learned any companions.")
emptyLabel:SetText(isMount and (ERR_NO_RIDING_SKILL or NE.L["You have not learned any mounts."])
or NE.L["You have not learned any companions."])
emptyLabel:Hide()
end
@@ -693,7 +693,7 @@ local function createJournal(kind)
slabel:SetPoint("RIGHT", summonBtn, "LEFT", -6, 0)
slabel:SetWidth(150)
slabel:SetJustifyH("RIGHT")
slabel:SetText(isMount and "Summon Random\nFavorite Mount" or "Summon Random\nFavorite Companion")
slabel:SetText(isMount and NE.L["Summon Random Favorite Mount"] or NE.L["Summon Random Favorite Companion"])
summonBtn:SetPushedTexture("Interface\\Buttons\\UI-Quickslot-Depress")
summonBtn:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square", "ADD")
-- Dragging this button itself onto an action bar never worked reliably (matches EZCollections'
@@ -711,9 +711,9 @@ local function createJournal(kind)
end)
summonBtn:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_LEFT")
GameTooltip:SetText(isMount and "Summon Random Favorite Mount" or "Summon Random Favorite Companion", 1, 1, 1)
GameTooltip:AddLine("Picks a random favorite usable here (or any, if none are favorited).", 0.82, 0.82, 0.82, true)
GameTooltip:AddLine("To use this on an action bar: open Macros (ESC > Macros), find the unnamed icon-only macro matching this icon, and drag it onto your bar.", 0.6, 0.8, 1, true)
GameTooltip:SetText(isMount and NE.L["Summon Random Favorite Mount"] or NE.L["Summon Random Favorite Companion"], 1, 1, 1)
GameTooltip:AddLine(NE.L["Picks a random favorite usable here (or any, if none are favorited)."], 0.82, 0.82, 0.82, true)
GameTooltip:AddLine(NE.L["To use this on an action bar: open Macros (ESC > Macros), find the unnamed icon-only macro matching this icon, and drag it onto your bar."], 0.6, 0.8, 1, true)
GameTooltip:Show()
end)
summonBtn:SetScript("OnLeave", function() GameTooltip:Hide() end)
@@ -811,9 +811,9 @@ local function createJournal(kind)
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(data.displayName or data.name or "", 1, 1, 1)
if data.index then
GameTooltip:AddLine(data.issummoned and "Right-click to dismiss" or "Right-click to summon", 1, 0.82, 0)
GameTooltip:AddLine(data.issummoned and NE.L["Right-click to dismiss"] or NE.L["Right-click to summon"], 1, 0.82, 0)
else
GameTooltip:AddLine(NOT_COLLECTED or "Not yet collected", 0.6, 0.6, 0.6)
GameTooltip:AddLine(NOT_COLLECTED or NE.L["Not yet collected"], 0.6, 0.6, 0.6)
end
GameTooltip:Show()
end)
@@ -894,7 +894,7 @@ local function createJournal(kind)
if countLabel then
-- The count pill is "how many you actually have", like retail — NOT the full catalog size
-- (J.all now also includes not-yet-learned placeholder rows).
countLabel:SetText(string.format("%s %d", isMount and (TOTAL_MOUNTS or "Total Mounts") or "Companions", J.collectedCount or 0))
countLabel:SetText(string.format("%s %d", isMount and (TOTAL_MOUNTS or NE.L["Total Mounts"]) or "Companions", J.collectedCount or 0))
if J._countBox then J._countBox:SetWidth(math.max(90, (countLabel:GetStringWidth() or 0) + 24)) end
end
end
@@ -914,7 +914,7 @@ local function createJournal(kind)
info.func = function() summonData(data) end
UIDropDownMenu_AddButton(info, level)
info.text = data._fav and (BATTLE_PET_UNFAVORITE or "Remove Favourite") or (BATTLE_PET_FAVORITE or "Favourite")
info.text = data._fav and (BATTLE_PET_UNFAVORITE or NE.L["Remove Favourite"]) or (BATTLE_PET_FAVORITE or "Favourite")
info.func = function() setFavorite(kind, data, not data._fav); J:RefreshView() end
UIDropDownMenu_AddButton(info, level)
@@ -950,7 +950,7 @@ local function createJournal(kind)
info.func = function(_, _, _, checked) J.showCollected = checked and true or false; J:RefreshView() end
UIDropDownMenu_AddButton(info, level)
info.text = NOT_COLLECTED or "Not Collected"
info.text = NOT_COLLECTED or NE.L["Not Collected"]
info.checked = function() return J.showNotCollected end
info.func = function(_, _, _, checked) J.showNotCollected = checked and true or false; J:RefreshView() end
UIDropDownMenu_AddButton(info, level)
@@ -1000,10 +1000,10 @@ local function createJournal(kind)
end
local all = UIDropDownMenu_CreateInfo()
all.notCheckable = true; all.keepShownOnClick = true
all.text = CHECK_ALL or "Check All"
all.text = CHECK_ALL or NE.L["Check All"]
all.func = function() J.sourceHidden = {}; J:RefreshView(); refreshSub() end
UIDropDownMenu_AddButton(all, level)
all.text = UNCHECK_ALL or "Uncheck All"
all.text = UNCHECK_ALL or NE.L["Uncheck All"]
all.func = function()
J.sourceHidden = {}
for _, idx in ipairs(presentSources()) do J.sourceHidden[idx] = true end
@@ -1014,7 +1014,7 @@ local function createJournal(kind)
for _, idx in ipairs(presentSources()) do
local i2 = UIDropDownMenu_CreateInfo()
i2.isNotRadio = true; i2.keepShownOnClick = true
i2.text = (C.SourceLabel and C.SourceLabel(idx)) or ("Source " .. idx)
i2.text = (C.SourceLabel and C.SourceLabel(idx)) or (NE.L["Source "] .. idx)
i2.checked = function() return not J.sourceHidden[idx] end
i2.func = function(_, _, _, checked) J.sourceHidden[idx] = (not checked) and true or nil; J:RefreshView() end
UIDropDownMenu_AddButton(i2, level)
@@ -1128,9 +1128,9 @@ local function createJournal(kind)
if emptyLabel then
if #J.list == 0 then
emptyLabel:SetText((J.collectedCount or 0) == 0
and (isMount and (ERR_NO_RIDING_SKILL or "You have not learned any mounts.")
or "You have not learned any companions.")
or "No results match your filters.")
and (isMount and (ERR_NO_RIDING_SKILL or NE.L["You have not learned any mounts."])
or NE.L["You have not learned any companions."])
or NE.L["No results match your filters."])
emptyLabel:Show()
else
emptyLabel:Hide()
@@ -1172,7 +1172,7 @@ end
function C.BuildJournals()
if C._journalsBuilt then return end
if not (C.LeftInset and C.RightInset and C.TopBand and C.BottomBand) then
log("BuildJournals: shell insets not ready"); return
log(NE.L["BuildJournals: shell insets not ready"]); return
end
C._journalsBuilt = true
C.RegisterJournal("MOUNT", createJournal("MOUNT"))
@@ -100,7 +100,7 @@ local function create()
end)
b:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_LEFT")
GameTooltip:SetText("Collections")
GameTooltip:SetText(NE.L["Collections"])
GameTooltip:Show()
end)
b:SetScript("OnLeave", function() GameTooltip:Hide() end)
@@ -13,8 +13,8 @@ local C = NE.collections
-- Binding labels for the Key Bindings UI. Bindings.xml declares the action; the settings UI reads
-- these globals for the header + row label. Never overwrite an existing global.
BINDING_HEADER_DRAGONUI_NEWERA = BINDING_HEADER_DRAGONUI_NEWERA or "DragonUI New Era"
BINDING_NAME_NEWERA_TOGGLECOLLECTIONS = BINDING_NAME_NEWERA_TOGGLECOLLECTIONS or "Toggle Collections (Mounts & Pets)"
BINDING_HEADER_DRAGONUI_NEWERA = BINDING_HEADER_DRAGONUI_NEWERA or NE.L["DragonUI New Era"]
BINDING_NAME_NEWERA_TOGGLECOLLECTIONS = BINDING_NAME_NEWERA_TOGGLECOLLECTIONS or NE.L["Toggle Collections (Mounts & Pets)"]
-- Default keybind: SHIFT-P opens the Collections window. The 3.3.5a Bindings.xml schema has no
-- `default` attribute, so the default is applied once via SetBinding, latched in the DB so we never
@@ -68,7 +68,7 @@ end
-- Tab order + labels + portrait icon. "MOUNT"/"CRITTER" are the GetCompanionInfo filter names.
local TABS = {
{ key = "MOUNT", label = _G.MOUNTS or "Mounts", portrait = C.tex and C.tex.mountPortrait },
{ key = "CRITTER", label = "Pet Journal", portrait = C.tex and C.tex.petPortrait },
{ key = "CRITTER", label = NE.L["Pet Journal"], portrait = C.tex and C.tex.petPortrait },
}
C.journals = C.journals or {} -- kind -> journal object (Show/Hide/Refresh), set by Journal.lua
@@ -218,7 +218,7 @@ function C.SelectTab(kind)
if k == kind then if j.Show then j:Show() end else if j.Hide then j:Hide() end end
end
local jr = C.journals[kind]
if jr and jr.Refresh then guard("journal refresh", function() jr:Refresh() end) end
if jr and jr.Refresh then guard(NE.L["journal refresh"], function() jr:Refresh() end) end
end
-- ---------------------------------------------------------------------------
@@ -365,14 +365,14 @@ C.Boot = boot
if NE.modules and NE.modules.Register then
NE.modules.Register(MODULE, {
default = true,
label = "Collections",
label = NE.L["Collections"],
category = "Windows",
desc = "The modern Dragonflight Collections window (Mounts & Pet Journal). Opens on the companion keybind.",
desc = NE.L["The modern Dragonflight Collections window (Mounts & Pet Journal). Opens on the companion keybind."],
events = {
"PLAYER_LOGIN", "COMPANION_LEARNED", "COMPANION_UPDATE", "COMPANION_UNLEARNED",
},
onBoot = function(event) boot(event) end,
})
else
log("NE.modules.Register absent; collections window not booted")
log(NE.L["NE.modules.Register absent; collections window not booted"])
end
@@ -81,10 +81,10 @@ local LCG = LibStub and LibStub("LibCustomGlow-1.0", true)
-- addon does not have. We define our own over the LibCustomGlow renderers, keeping 1 = ants as the
-- default so a stored upstream-shaped value still lands on the intended look.
AL.FX = {
{ id = 1, name = "Marching Ants" },
{ id = 2, name = "Button Glow" },
{ id = 3, name = "Sparkles" },
{ id = 4, name = "Pandemic Border" },
{ id = 1, name = NE.L["Marching Ants"] },
{ id = 2, name = NE.L["Button Glow"] },
{ id = 3, name = NE.L["Sparkles"] },
{ id = 4, name = NE.L["Pandemic Border"] },
}
local DEFAULT_FX = 1
@@ -222,7 +222,7 @@ end
local function safeRebuild(self)
local ok, err = pcall(self.Rebuild, self)
if not ok and NE.Log then
NE.Log("CDM", "buff viewer '" .. tostring(self.category) .. "' rebuild error: " .. tostring(err))
NE.Log("CDM", NE.L["buff viewer '"] .. tostring(self.category) .. "' rebuild error: " .. tostring(err))
end
end
@@ -284,7 +284,7 @@ function BuffIconMixin:GetStride()
end
local BUFFICON_PREVIEW = {
{ "Power Infusion", "Interface\\Icons\\Spell_Holy_PowerInfusion", 15, 11, 10060 },
{ NE.L["Power Infusion"], "Interface\\Icons\\Spell_Holy_PowerInfusion", 15, 11, 10060 },
{ "Berserking", "Interface\\Icons\\Spell_Nature_BloodLust", 20, 14, 20554 },
{ "Crusader", "Interface\\Icons\\Spell_Holy_HolySmite", 15, 6, 20007 },
{ "Recklessness", "Interface\\Icons\\Ability_CriticalStrike", 15, 9, 1719 },
@@ -181,7 +181,7 @@ function M.SpecNames()
local out = {}
if not (GetNumTalentTabs and GetTalentTabInfo) then return out end
for tab = 1, (GetNumTalentTabs() or 0) do
out[tab] = (GetTalentTabInfo(tab, false, false, M.ActiveTalentGroup())) or ("Tree " .. tab)
out[tab] = (GetTalentTabInfo(tab, false, false, M.ActiveTalentGroup())) or (NE.L["Tree "] .. tab)
end
return out
end
@@ -220,7 +220,7 @@ local function migrateLayout(cd)
cd.specLayouts[key] = bucket
end
for _, k in ipairs(LAYOUT_KEYS) do cd[k] = nil end
if NE.Log then NE.Log("CDM", "layout copied into per-spec buckets") end
if NE.Log then NE.Log("CDM", NE.L["layout copied into per-spec buckets"]) end
end
-- A bucket that does not exist yet inherits from "shared" — so turning the feature ON carries the
@@ -606,7 +606,7 @@ function M.MigrateStaleCustomLists()
end
M.InvalidateCuratedCache()
if had and NE.Log then
NE.Log("CDM", "cleared stale auto-seeded spell lists; curated defaults restored")
NE.Log("CDM", NE.L["cleared stale auto-seeded spell lists; curated defaults restored"])
end
return had
end
@@ -47,11 +47,11 @@ local FOOTER_H = 15 + BTN_H + 10 -- button row, divider above it
local SIDE_GAP = 12 -- clearance between the dialog and the frame it edits
-- Same value/label pairs, in the same order, as the /cdm Settings tab.
local ORIENTATION = { { "horizontal", "Horizontal" }, { "vertical", "Vertical" } }
local DIRECTION = { { "right", "Right" }, { "left", "Left" } }
local VISIBILITY = { { "always", "Always" }, { "incombat", "In Combat" }, { "hidden", "Hidden" } }
local BAR_CONTENT = { { "iconAndName", "Icon and Name" }, { "iconOnly", "Icon Only" },
{ "nameOnly", "Name Only" } }
local ORIENTATION = { { "horizontal", NE.L["Horizontal"] }, { "vertical", NE.L["Vertical"] } }
local DIRECTION = { { "right", NE.L["Right"] }, { "left", NE.L["Left"] } }
local VISIBILITY = { { "always", NE.L["Always"] }, { "incombat", NE.L["In Combat"] }, { "hidden", NE.L["Hidden"] } }
local BAR_CONTENT = { { "iconAndName", NE.L["Icon and Name"] }, { "iconOnly", NE.L["Icon Only"] },
{ "nameOnly", NE.L["Name Only"] } }
local function pct(v) return tostring(v) .. "%" end
local function px(v) return tostring(v) .. "px" end
@@ -208,9 +208,9 @@ function M.ShowConfirmReset(category)
local c = ensureConfirm(panel)
local spec = specFor(category)
c._category = category
c.Text:SetText("Reset " .. ((spec and spec.label) or category) ..
c.Text:SetText(NE.L["Reset "] .. ((spec and spec.label) or category) ..
" to its default layout?\n\nThis viewer's position, size, orientation and visibility all go back " ..
"to stock. Nothing else is affected, and it cannot be undone.")
NE.L["to stock. Nothing else is affected, and it cannot be undone."])
-- Sized to the wrapped text. The floor covers the offline harness, whose FontString cannot measure.
local textH = math.max(60, (c.Text.GetStringHeight and c.Text:GetStringHeight() or 0) + 4)
c:SetHeight(20 + textH + 18 + BTN_H + 16)
@@ -274,9 +274,9 @@ local function buildPage(category)
local function getter(key) return function() return get(key) end end
col:AddCheckbox({
label = "Enabled",
desc = "Show this viewer at all. The editor handle stays either way, so this is reversible from "
.. "right here.",
label = NE.L["Enabled"],
desc = NE.L["Show this viewer at all. The editor handle stays either way, so this is reversible from "]
.. NE.L["right here."],
get = function() return M.IsCategoryEnabled(category) end,
set = function(v)
M.SetCategoryEnabled(category, v)
@@ -285,36 +285,36 @@ local function buildPage(category)
})
col:AddDropdown({
label = "Orientation", values = ORIENTATION,
label = NE.L["Orientation"], values = ORIENTATION,
get = getter("orientation"), set = set("orientation"),
})
col:AddCompactSlider({
label = "Icon Limit", min = 1, max = 20, step = 1,
desc = "How many icons before the layout wraps. Vertical orientation reads this as icons per "
label = NE.L["Icon Limit"], min = 1, max = 20, step = 1,
desc = NE.L["How many icons before the layout wraps. Vertical orientation reads this as icons per "]
.. "column.",
get = getter("iconLimit"), set = set("iconLimit"),
})
col:AddDropdown({
label = "Icon Direction", values = DIRECTION,
label = NE.L["Icon Direction"], values = DIRECTION,
get = getter("iconDirection"), set = set("iconDirection"),
})
col:AddCompactSlider({
label = "Icon Size", min = 50, max = 200, step = 10, format = pct,
label = NE.L["Icon Size"], min = 50, max = 200, step = 10, format = pct,
get = getter("iconSize"), set = set("iconSize"),
})
col:AddCompactSlider({
label = "Icon Padding", min = 0, max = 14, step = 1, format = px,
desc = "Gap between icons. Retail offsets this by -4, so the low end overlaps slightly — that is "
.. "the stock look, not a bug.",
label = NE.L["Icon Padding"], min = 0, max = 14, step = 1, format = px,
desc = NE.L["Gap between icons. Retail offsets this by -4, so the low end overlaps slightly — that is "]
.. NE.L["the stock look, not a bug."],
get = getter("iconPadding"), set = set("iconPadding"),
})
col:AddCompactSlider({
label = "Opacity", min = 50, max = 100, step = 1, format = pct,
label = NE.L["Opacity"], min = 50, max = 100, step = 1, format = pct,
get = getter("opacity"), set = set("opacity"),
})
col:AddDropdown({
label = "Visibility", values = VISIBILITY,
desc = "When this viewer is on screen at all. Hidden still leaves the editor handle here.",
label = NE.L["Visibility"], values = VISIBILITY,
desc = NE.L["When this viewer is on screen at all. Hidden still leaves the editor handle here."],
get = getter("visibleSetting"), set = set("visibleSetting"),
})
@@ -323,22 +323,22 @@ local function buildPage(category)
-- control for the same reason, and a row that silently does nothing is worse than no row.
if get("allowHideWhenInactive") then
col:AddCheckbox({
label = "Hide When Inactive",
desc = "Show a slot only while its aura is active.",
label = NE.L["Hide When Inactive"],
desc = NE.L["Show a slot only while its aura is active."],
get = function() return get("hideWhenInactive") and true or false end,
set = set("hideWhenInactive"),
})
end
col:AddCheckbox({
label = "Show Timer",
desc = "Draw the countdown number on each icon.",
label = NE.L["Show Timer"],
desc = NE.L["Draw the countdown number on each icon."],
get = function() return get("showTimer") and true or false end,
set = set("showTimer"),
})
col:AddCheckbox({
label = "Show Tooltips",
desc = "Show a tooltip when hovering an icon.",
label = NE.L["Show Tooltips"],
desc = NE.L["Show a tooltip when hovering an icon."],
get = function() return get("showTooltips") and true or false end,
set = set("showTooltips"),
})
@@ -346,11 +346,11 @@ local function buildPage(category)
-- Bar-only, exactly as retail exposes them (the BuffBar system alone).
if spec and spec.bar then
col:AddDropdown({
label = "Bar Content", values = BAR_CONTENT,
label = NE.L["Bar Content"], values = BAR_CONTENT,
get = getter("barContent"), set = set("barContent"),
})
col:AddCompactSlider({
label = "Bar Width", min = 50, max = 200, step = 5, format = pct,
label = NE.L["Bar Width"], min = 50, max = 200, step = 5, format = pct,
get = getter("barWidthScale"), set = set("barWidthScale"),
})
end
@@ -358,10 +358,10 @@ local function buildPage(category)
-- Retail's AddExtraButtons puts a system's own extra actions at the END of the options stack,
-- above the Revert/Reset region — which is where NewEra's dialog carries this one too.
col:AddButton({
label = "Cooldown Manager Settings",
label = NE.L["Cooldown Manager Settings"],
width = ROW_W,
desc = "Closes edit mode and opens the Cooldown Manager window, which carries the settings that "
.. "are not per-viewer: alerts, ready sounds, buff tracking, icon fit and the resets.",
desc = NE.L["Closes edit mode and opens the Cooldown Manager window, which carries the settings that "]
.. NE.L["are not per-viewer: alerts, ready sounds, buff tracking, icon fit and the resets."],
onClick = function()
-- Leave the editor first: it covers the screen, and DragonUI saves every frame's position on
-- the way out, so nothing is lost by going this way.
@@ -440,7 +440,7 @@ local function ensurePanel()
f.buttonsDivider:SetSize(330, 16)
f.buttonsDivider:SetPoint("BOTTOM", 0, 15 + BTN_H + 1)
f.revertButton = footerButton("Revert Changes", BTN_W, function(category)
f.revertButton = footerButton(NE.L["Revert Changes"], BTN_W, function(category)
revert(category)
M.RefreshEditorPanel()
end)
@@ -450,7 +450,7 @@ local function ensurePanel()
-- when there is nothing to undo — but Reset throws away every layout choice ever made for this
-- viewer, and nothing in the addon can put it back. The two now sit a few pixels apart, so the one
-- that is not reversible asks.
f.resetButton = footerButton("Reset to Default", BTN_W, function(category)
f.resetButton = footerButton(NE.L["Reset to Default"], BTN_W, function(category)
M.ShowConfirmReset(category)
end)
f.resetButton:SetPoint("BOTTOMRIGHT", -CONTENT_X, 15)
@@ -22,14 +22,14 @@ local M = NE.cooldownviewer
-- All four retail viewers. essential/utility read spell cooldowns from the curated class lists;
-- buffIcon/buffBar read live auras (Phase 3) and need no class data.
local VIEWERS = {
{ category = "essential", id = "CooldownViewerEssential", label = "Essential Cooldowns",
desc = "Offensive burst and damage cooldowns.", order = 40 },
{ category = "utility", id = "CooldownViewerUtility", label = "Utility Cooldowns",
desc = "Defensives, interrupts, CC and escapes.", order = 41 },
{ category = "buffIcon", id = "CooldownViewerBuffIcon", label = "Buff Icons",
desc = "Short-duration buffs and procs, as icons.", order = 42, aura = true },
{ category = "buffBar", id = "CooldownViewerBuffBar", label = "Buff Bars",
desc = "Short-duration buffs and procs, as depleting bars.", order = 43,
{ category = "essential", id = "CooldownViewerEssential", label = NE.L["Essential Cooldowns"],
desc = NE.L["Offensive burst and damage cooldowns."], order = 40 },
{ category = "utility", id = "CooldownViewerUtility", label = NE.L["Utility Cooldowns"],
desc = NE.L["Defensives, interrupts, CC and escapes."], order = 41 },
{ category = "buffIcon", id = "CooldownViewerBuffIcon", label = NE.L["Buff Icons"],
desc = NE.L["Short-duration buffs and procs, as icons."], order = 42, aura = true },
{ category = "buffBar", id = "CooldownViewerBuffBar", label = NE.L["Buff Bars"],
desc = NE.L["Short-duration buffs and procs, as depleting bars."], order = 43,
aura = true, bar = true },
}
M.VIEWER_SPECS = VIEWERS
@@ -60,7 +60,7 @@ local function boot()
for _, spec in ipairs(VIEWERS) do
local frame = spec.aura and M.CreateBuffViewer(spec.category) or M.CreateViewer(spec.category)
if not frame then
if NE.Log then NE.Log("CDM", "viewer '" .. spec.category .. "' failed to build") end
if NE.Log then NE.Log("CDM", NE.L["viewer '"] .. spec.category .. "' failed to build") end
else
NE.RegisterHUDFrame({
@@ -157,7 +157,7 @@ SlashCmdList["NECDM"] = function()
local SB = NE.spellbook
local function say(msg) DEFAULT_CHAT_FRAME:AddMessage("|cff1784d1CDM|r " .. msg) end
say(("class=%s spellbook built=%s names=%d"):format(
say((NE.L["class=%s spellbook built=%s names=%d"]):format(
tostring(class),
tostring(SB and SB.built),
(function() local n = 0; for _ in pairs(SB and SB.KNOWN_NAMES or {}) do n = n + 1 end; return n end)()))
@@ -224,7 +224,7 @@ SlashCmdList["NECDM"] = function()
M._widgetProbe = probe
end
local function has(name) return probe[name] ~= nil and "yes" or "NO" end
say(("cooldown widget: SetDrawEdge=%s GetDrawEdge=%s SetSwipeTexture=%s SetReverse=%s"):format(
say((NE.L["cooldown widget: SetDrawEdge=%s GetDrawEdge=%s SetSwipeTexture=%s SetReverse=%s"]):format(
has("SetDrawEdge"), has("GetDrawEdge"), has("SetSwipeTexture"), has("SetReverse")))
-- Phase 8a. Six atlases were being set by name with nothing registered, which renders as an
@@ -242,14 +242,14 @@ SlashCmdList["NECDM"] = function()
if not NE.tex.HasAtlas(n) then missing[#missing + 1] = n end
end
if #missing == 0 then
say(("viewer art: all %d atlases registered"):format(#names))
say((NE.L["viewer art: all %d atlases registered"]):format(#names))
else
say(("viewer art: MISSING %d of %d -- %s"):format(#missing, #names,
say((NE.L["viewer art: MISSING %d of %d -- %s"]):format(#missing, #names,
table.concat(missing, ", ")))
end
local flip = NE.tex.HasAtlas("UI-HUD-ActionBar-GCD-Flipbook")
say(("ready flash: %s"):format(flip and "retail flipbook sprite (22 frames)"
or "fallback highlight burst (flipbook atlas not registered)"))
say((NE.L["ready flash: %s"]):format(flip and NE.L["retail flipbook sprite (22 frames)"]
or NE.L["fallback highlight burst (flipbook atlas not registered)"]))
end
-- Phase 8c. The glow has two independent ways to be invisible — the setting is off, or no tile is
@@ -266,8 +266,8 @@ SlashCmdList["NECDM"] = function()
end
end
end
say(("buff glow: %s, texture %s, lit on %d of %d shown tiles"):format(
M.IsBuffGlowEnabled() and "ON" or "off (Settings > Buffed spells)",
say((NE.L["buff glow: %s, texture %s, lit on %d of %d shown tiles"]):format(
M.IsBuffGlowEnabled() and "ON" or NE.L["off (Settings > Buffed spells)"],
tostring(M.BUFF_GLOW_TEXTURE), lit, withSpell))
-- Phase 8e. The pandemic ring has the same "three ways to be invisible" problem the buff glow had:
@@ -289,7 +289,7 @@ SlashCmdList["NECDM"] = function()
end
end
end
say(("pandemic ring: art %s, %d spell(s) set to Refresh, %d ring(s) up, %d pulsing"):format(
say((NE.L["pandemic ring: art %s, %d spell(s) set to Refresh, %d ring(s) up, %d pulsing"]):format(
(NE.tex and NE.tex.HasAtlas and NE.tex.HasAtlas("UI-CooldownManager-PandemicBorder"))
and "registered" or "MISSING", assigned, lit, pulsing))
end
@@ -302,8 +302,8 @@ SlashCmdList["NECDM"] = function()
local bg = bar and bar.BarBG
if bg and bg.Left then
local w = (bar:GetWidth() or 0) - M.BARBG_PAD_L + M.BARBG_PAD_R
say(("bar frame: %s, region %.0fpx wide vs %dpx art (%.2fx), caps %.1f/%.1f held"):format(
bg.Middle and bg.Middle:IsShown() and "3-slice" or "single stretch (bar too narrow)",
say((NE.L["bar frame: %s, region %.0fpx wide vs %dpx art (%.2fx), caps %.1f/%.1f held"]):format(
bg.Middle and bg.Middle:IsShown() and "3-slice" or NE.L["single stretch (bar too narrow)"],
w, M.BARBG_NATIVE_W, w / M.BARBG_NATIVE_W,
bg.Left:GetWidth() or 0, bg.Right and bg.Right:GetWidth() or 0))
end
@@ -313,7 +313,7 @@ local bootFrame = CreateFrame("Frame")
bootFrame:RegisterEvent("PLAYER_LOGIN")
bootFrame:SetScript("OnEvent", function()
local ok, err = pcall(boot)
if not ok and NE.Log then NE.Log("CDM", "boot failed: " .. tostring(err)) end
if not ok and NE.Log then NE.Log("CDM", NE.L["boot failed: "] .. tostring(err)) end
end)
-- ── Options ─────────────────────────────────────────────────────────────────────────────────────
@@ -338,33 +338,33 @@ NE.RegisterOptionSection({
order = 40,
build = function(scroll, C)
if C.AddSpacer then C:AddSpacer(scroll) end
C:AddHeading(scroll, "Cooldown Manager")
C:AddHeading(scroll, NE.L["Cooldown Manager"])
C:AddDescription(scroll,
"Retail's Cooldown Manager, driven from curated per-class cooldown lists. |cffffcc55Off by "
.. "default|r — it adds four viewers to the middle of your screen, so it waits to be asked. Every "
.. "setting — which spells and buffs are tracked, each viewer's layout, size and visibility, alerts "
.. "and ready sounds — lives in the Cooldown Manager window itself (/cdm). Drag the viewers with "
.. "DragonUI's editor mode to reposition them, and right-click one there for its own layout settings.")
NE.L["Retail's Cooldown Manager, driven from curated per-class cooldown lists. |cffffcc55Off by "]
.. NE.L["default|r — it adds four viewers to the middle of your screen, so it waits to be asked. Every "]
.. NE.L["setting — which spells and buffs are tracked, each viewer's layout, size and visibility, alerts "]
.. NE.L["and ready sounds — lives in the Cooldown Manager window itself (/cdm). Drag the viewers with "]
.. NE.L["DragonUI's editor mode to reposition them, and right-click one there for its own layout settings."])
C:AddToggle(scroll, {
label = "Enable Cooldown Manager",
desc = "Off by default. Turn on to show the four viewers; turn off to hide them again. Takes "
.. "effect immediately either way, and nothing is forgotten — this switch stores one flag "
.. "and touches nothing else, so your setup comes back exactly as you left it.",
label = NE.L["Enable Cooldown Manager"],
desc = NE.L["Off by default. Turn on to show the four viewers; turn off to hide them again. Takes "]
.. NE.L["effect immediately either way, and nothing is forgotten — this switch stores one flag "]
.. NE.L["and touches nothing else, so your setup comes back exactly as you left it."],
getFunc = function() return M.IsEnabled() end,
setFunc = function(v) M.SetEnabled(v) end,
})
if C.AddButton then
C:AddButton(scroll, {
label = "Open Cooldown Manager",
label = NE.L["Open Cooldown Manager"],
-- SPELLS, not Settings (owner's steer). This is the front door: the first thing anyone wants
-- after turning the module on is to see and change what it tracks, which is the Spells tab.
-- Settings is where you go once you already know what is in the viewers, and the tabs are right
-- there. (The link on the edit-mode dialog still opens on Settings — that one exists to reach
-- the non-per-viewer settings by name, so it means it.)
desc = "Opens the Cooldown Manager window (/cdm) on its Spells tab. Needs the module on — "
.. "the window configures the viewers, so it goes away with them.",
desc = NE.L["Opens the Cooldown Manager window (/cdm) on its Spells tab. Needs the module on — "]
.. NE.L["the window configures the viewers, so it goes away with them."],
callback = function()
if M.OpenSettingsPanel then M.OpenSettingsPanel("spells") end
end,
@@ -372,7 +372,7 @@ NE.RegisterOptionSection({
else
-- An older DragonUI_Options with no AddButton. Name the command rather than leaving this section
-- as an off switch and no way through.
C:AddDescription(scroll, "Type /cdm to open the Cooldown Manager window.")
C:AddDescription(scroll, NE.L["Type /cdm to open the Cooldown Manager window."])
end
end,
})
@@ -50,10 +50,10 @@ local CATS = {
essential = { mode = "spells", kind = "icon", label = GS("COOLDOWN_VIEWER_SETTINGS_CATEGORY_ESSENTIAL", "Essential"), list = "essential" },
utility = { mode = "spells", kind = "icon", label = GS("COOLDOWN_VIEWER_SETTINGS_CATEGORY_UTILITY", "Utility"), list = "utility" },
equipActive = { mode = "spells", kind = "icon", label = GS("COOLDOWN_VIEWER_SETTINGS_CATEGORY_EQUIP_ACTIVE", "Trinkets"), source = true },
hiddenSpell = { mode = "spells", kind = "icon", label = GS("COOLDOWN_VIEWER_SETTINGS_CATEGORY_NOT_IN_BAR", "Not Displayed") },
trackedBuff = { mode = "auras", kind = "icon", label = GS("COOLDOWN_VIEWER_SETTINGS_CATEGORY_TRACKED_BUFF", "Tracked Buffs"), aura = "icon" },
trackedBar = { mode = "auras", kind = "bar", label = GS("COOLDOWN_VIEWER_SETTINGS_CATEGORY_TRACKED_BARS", "Tracked Bars"), aura = "bar" },
hiddenAura = { mode = "auras", kind = "icon", label = GS("COOLDOWN_VIEWER_SETTINGS_CATEGORY_NOT_IN_BAR", "Not Displayed"), aura = "hidden" },
hiddenSpell = { mode = "spells", kind = "icon", label = GS("COOLDOWN_VIEWER_SETTINGS_CATEGORY_NOT_IN_BAR", NE.L["Not Displayed"]) },
trackedBuff = { mode = "auras", kind = "icon", label = GS("COOLDOWN_VIEWER_SETTINGS_CATEGORY_TRACKED_BUFF", NE.L["Tracked Buffs"]), aura = "icon" },
trackedBar = { mode = "auras", kind = "bar", label = GS("COOLDOWN_VIEWER_SETTINGS_CATEGORY_TRACKED_BARS", NE.L["Tracked Bars"]), aura = "bar" },
hiddenAura = { mode = "auras", kind = "icon", label = GS("COOLDOWN_VIEWER_SETTINGS_CATEGORY_NOT_IN_BAR", NE.L["Not Displayed"]), aura = "hidden" },
}
-- The source pool sits between the display categories and Not Displayed, which is upstream's order
@@ -88,10 +88,10 @@ function Adapter.Kind(catID) return CATS[catID] and CATS[catID].kind or "icon"
-- likely reporting is a LOW-LEVEL character with nothing left to list — and "(empty)" there reads as
-- a fault. Naming Show Unlearned turns the dead end into a signposted one.
local EMPTY_TEXT = {
trackedBuff = "Nothing here yet — drag a buff in to pin it.",
trackedBar = "Nothing here yet — drag a buff in to pin it.",
hiddenAura = "Everything recorded is displayed.",
hiddenSpell = "Everything you have learned is displayed. Turn on Show Unlearned (cog) to see the rest.",
trackedBuff = NE.L["Nothing here yet — drag a buff in to pin it."],
trackedBar = NE.L["Nothing here yet — drag a buff in to pin it."],
hiddenAura = NE.L["Everything recorded is displayed."],
hiddenSpell = NE.L["Everything you have learned is displayed. Turn on Show Unlearned (cog) to see the rest."],
}
function Adapter.EmptyText(catID) return EMPTY_TEXT[catID] or "(empty)" end
@@ -110,7 +110,7 @@ local function setAuraEntry(self, entry)
end
if self.Label then
self.Label:SetText(self.spellName ~= "" and self.spellName
or ("Aura " .. tostring(entry.spellID or "?")))
or (NE.L["Aura "] .. tostring(entry.spellID or "?")))
end
if CDS._applyAlertBadge then CDS._applyAlertBadge(self) end
end
@@ -135,23 +135,23 @@ local function itemOnEnter(self)
-- For an aura the gate is a talent, not a level, so say which one. "Not yet learned" on a proc
-- you cannot ever learn without respeccing tells the player nothing they can act on.
if self._aura and self._aura.talent then
GameTooltip:AddLine("Requires the " .. self._aura.talent .. " talent", 1, 0.3, 0.3)
GameTooltip:AddLine(NE.L["Requires the "] .. self._aura.talent .. " talent", 1, 0.3, 0.3)
else
GameTooltip:AddLine("Not yet learned", 1, 0.3, 0.3)
GameTooltip:AddLine(NE.L["Not yet learned"], 1, 0.3, 0.3)
end
end
if self._aura then
if self._aura.dur then
GameTooltip:AddLine(("Lasts %s sec"):format(tostring(self._aura.dur)), 0.7, 0.7, 0.7)
GameTooltip:AddLine((NE.L["Lasts %s sec"]):format(tostring(self._aura.dur)), 0.7, 0.7, 0.7)
end
if self._aura.auto then
GameTooltip:AddLine("Tracked automatically. Drag it into a section to pin it there.",
GameTooltip:AddLine(NE.L["Tracked automatically. Drag it into a section to pin it there."],
0.6, 0.8, 1)
end
end
if self.token then
GameTooltip:AddLine(self._equipHidden and "Not displayed on any viewer"
or "Drag onto Essential or Utility to track it.", 0.6, 0.8, 1)
GameTooltip:AddLine(self._equipHidden and NE.L["Not displayed on any viewer"]
or NE.L["Drag onto Essential or Utility to track it."], 0.6, 0.8, 1)
end
-- What the corner badge means for this tile. Provided by SettingsMenu (4b-3).
if CDS._itemTooltipExtra then CDS._itemTooltipExtra(self, GameTooltip) end
@@ -242,7 +242,7 @@ local function makeBarItem(parent)
local name, _, icon = GetSpellInfo(spellID)
self.spellName = name
self.Icon:SetTexture(icon or QUESTION_MARK)
self.Label:SetText(name or ("Spell " .. tostring(spellID)))
self.Label:SetText(name or (NE.L["Spell "] .. tostring(spellID)))
applyLearnedTint(self)
if CDS._applyAlertBadge then CDS._applyAlertBadge(self) end
end
@@ -74,11 +74,11 @@ function CDS._itemTooltipExtra(item, tooltip)
for _, fx in ipairs((AL and AL.FX) or {}) do
if fx.id == AL.GetFX(item.spellID) then fxName = fx.name end
end
tooltip:AddLine(("Alert: %s (%s)"):format(t, fxName or "?"), 0.4, 1, 0.4)
tooltip:AddLine((NE.L["Alert: %s (%s)"]):format(t, fxName or "?"), 0.4, 1, 0.4)
end
local kit = M.GetReadySoundKit and M.GetReadySoundKit(item.spellID)
if kit then
tooltip:AddLine("Ready sound: " .. (M.GetSoundKitName(kit) or tostring(kit)), 0.4, 0.8, 1)
tooltip:AddLine(NE.L["Ready sound: "] .. (M.GetSoundKitName(kit) or tostring(kit)), 0.4, 0.8, 1)
end
end
@@ -89,7 +89,7 @@ end
-- branch is here rather than hidden inside Assign, where a caller could not tell which happened.
local function addMoveEntries(root, item, class)
for _, target in ipairs(Adapter.GetValidTargets(item._catID)) do
root:CreateButton("Move to " .. Adapter.Label(target), function()
root:CreateButton(NE.L["Move to "] .. Adapter.Label(target), function()
if item.token then
Adapter.AssignEquip(item.token, item._catID, target)
else
@@ -127,16 +127,16 @@ local function addSoundEntries(root, item)
-- there lighting the badge with nothing behind it and no per-item way to take it off.
if M.GetReadySoundKit(item.spellID) == nil then return end
root:CreateDivider()
root:CreateButton("Clear Ready Sound", function()
root:CreateButton(NE.L["Clear Ready Sound"], function()
M.SetReadySoundKit(item.spellID, nil)
applyAlertBadge(item)
end):SetTooltip("Clear Ready Sound",
"A ready sound plays when a COOLDOWN finishes.|nA tracked buff has none, so this one can|nnever play. Clearing it also clears the badge.")
end):SetTooltip(NE.L["Clear Ready Sound"],
NE.L["A ready sound plays when a COOLDOWN finishes.|nA tracked buff has none, so this one can|nnever play. Clearing it also clears the badge."])
return
end
root:CreateDivider()
local soundRoot = root:CreateButton("Ready Sound")
local soundRoot = root:CreateButton(NE.L["Ready Sound"])
soundRoot:CreateRadio("None",
function() return M.GetReadySoundKit(item.spellID) == nil end,
@@ -204,7 +204,7 @@ local function addAlertEntries(root, item)
-- set back to None or moved onto Refresh.
if not isAuraRow(item) then
alertRoot:CreateRadio("Available", function() return isType("available") end, pick("available"))
:SetTooltip("Available", "Flashes once, the moment the cooldown finishes.|nWorks for every spell.")
:SetTooltip("Available", NE.L["Flashes once, the moment the cooldown finishes.|nWorks for every spell."])
end
-- Refresh is the one event that genuinely cannot fire for some spells, so it says what it needs.
@@ -214,9 +214,9 @@ local function addAlertEntries(root, item)
-- The caveat below is about a COOLDOWN that applies no aura, which cannot be the case here: the
-- row is the aura. This is the one trigger a tracked buff is built for, and saying so is the
-- point — it is also the one that was silently doing nothing until the ticker learned to look.
refreshText = ("Glows during the last %d%% of this buff's|nremaining time."):format(pct)
refreshText = (NE.L["Glows during the last %d%% of this buff's|nremaining time."]):format(pct)
else
refreshText = ("Glows during the last %d%% of this spell's own|nbuff or debuff."):format(pct)
refreshText = (NE.L["Glows during the last %d%% of this spell's own|nbuff or debuff."]):format(pct)
.. "|n|nA cooldown that applies no aura — Shadowfiend,|nPsychic Scream — can never trigger it."
end
refreshText = refreshText .. (auraSeen(item.spellName)
@@ -230,7 +230,7 @@ local function addAlertEntries(root, item)
-- aura-row triggers because for a tracked buff it is the obvious one.
if isAuraRow(item) then
alertRoot:CreateRadio("Active", function() return isType("active") end, pick("active"))
:SetTooltip("Active", "Glows for as long as this buff is on you.|n|nThe one that works for a"
:SetTooltip("Active", NE.L["Glows for as long as this buff is on you.|n|nThe one that works for a"]
.. " proc: it asks whether the|nbuff is up, not whether something is castable|nor off cooldown.")
end
@@ -244,7 +244,7 @@ local function addAlertEntries(root, item)
-- would hide the entry from anyone who opened the menu out of mana.
local usableCastable = (not isAuraRow(item)) or (item.spellName and GetSpellInfo(item.spellName) ~= nil)
if usableCastable then
local usableText = "Glows for as long as the spell is off cooldown|nand affordable."
local usableText = NE.L["Glows for as long as the spell is off cooldown|nand affordable."]
local threshold = M.alertdata and M.alertdata.ExecuteThreshold
and M.alertdata.ExecuteThreshold(item.spellID, item._rankCDIDs)
if threshold then
@@ -260,7 +260,7 @@ local function addAlertEntries(root, item)
alertRoot:CreateDivider()
-- Generated from AL.FX, not from upstream's hardcoded pair. See the header.
local fxSub = alertRoot:CreateButton("FX Style")
local fxSub = alertRoot:CreateButton(NE.L["FX Style"])
for _, fx in ipairs(AL.FX or {}) do
fxSub:CreateRadio(fx.name,
function() return AL.GetFX(item.spellID) == fx.id end,
@@ -270,7 +270,7 @@ local function addAlertEntries(root, item)
end)
end
local winSub = alertRoot:CreateButton("Refresh Window")
local winSub = alertRoot:CreateButton(NE.L["Refresh Window"])
for _, pct in ipairs(REFRESH_WINDOWS) do
winSub:CreateRadio(pct .. "%",
function() return math.abs((AL.GetWindow(item.spellID) * 100) - pct) < 0.5 end,
@@ -313,7 +313,7 @@ end
-- ── The settings cog ────────────────────────────────────────────────────────────────────────────
function CDS.SettingsMenuGenerator(_, root)
root:CreateCheckbox("Show Unlearned",
root:CreateCheckbox(NE.L["Show Unlearned"],
function() return M.GetShowUnlearned and M.GetShowUnlearned() end,
function()
M.SetShowUnlearned(not M.GetShowUnlearned())
@@ -321,8 +321,8 @@ function CDS.SettingsMenuGenerator(_, root)
end)
root:CreateDivider()
root:CreateButton("Reset Spell Lists", function() StaticPopup_Show("NE_CDM_RESET_TRACKING") end)
root:CreateButton("Clear All Alerts", function() StaticPopup_Show("NE_CDM_RESET_ALERTS") end)
root:CreateButton(NE.L["Reset Spell Lists"], function() StaticPopup_Show("NE_CDM_RESET_TRACKING") end)
root:CreateButton(NE.L["Clear All Alerts"], function() StaticPopup_Show("NE_CDM_RESET_ALERTS") end)
end
-- Both resets are destructive and irreversible (there is no undo store until 4b-5), so both confirm.
@@ -330,7 +330,7 @@ StaticPopupDialogs = StaticPopupDialogs or {}
StaticPopupDialogs["NE_CDM_RESET_TRACKING"] = {
-- Names the scope in the confirm too. This is the last thing read before an irreversible action,
-- and it used to say nothing about WHOSE lists — while the code behind it cleared every class.
text = "Reset this class's Cooldown Manager spell and buff lists to their defaults?\n\nOther classes, alerts, sounds and frame positions are not affected.",
text = NE.L["Reset this class's Cooldown Manager spell and buff lists to their defaults?\n\nOther classes, alerts, sounds and frame positions are not affected."],
button1 = YES or "Yes",
button2 = NO or "No",
OnAccept = function()
@@ -340,7 +340,7 @@ StaticPopupDialogs["NE_CDM_RESET_TRACKING"] = {
timeout = 0, whileDead = 1, hideOnEscape = 1,
}
StaticPopupDialogs["NE_CDM_RESET_ALERTS"] = {
text = "Clear every configured alert and ready sound?\n\nSpell lists and frame positions are not affected.",
text = NE.L["Clear every configured alert and ready sound?\n\nSpell lists and frame positions are not affected."],
button1 = YES or "Yes",
button2 = NO or "No",
OnAccept = function()
@@ -45,7 +45,7 @@ local PAGE_W = 330
-- Ordered, unlike the options tab's value->label maps: those go to AceGUI, which sorts them, and a
-- radio menu has to pick its own order. The per-viewer lists that used to sit here went with the
-- controls that read them; EditorPanel.lua carries its own copies.
local TRACK_DEST = { { "both", "Icons and bars" }, { "icon", "Icons only" }, { "bar", "Bars only" } }
local TRACK_DEST = { { "both", NE.L["Icons and bars"] }, { "icon", NE.L["Icons only"] }, { "bar", NE.L["Bars only"] } }
local function pct(v) return tostring(v) .. "%" end
@@ -68,7 +68,7 @@ end
local function positionViewer(category)
local frame = M.viewers and M.viewers[category]
if not frame then
say("that viewer doesn't exist yet — it builds at login.")
say(NE.L["that viewer doesn't exist yet — it builds at login."])
return
end
-- No master-enable check here on purpose, though a disabled Cooldown Manager registers no editor
@@ -77,12 +77,12 @@ local function positionViewer(category)
-- off, and M.SetEnabled closes one that is already up. A guard for it would be a comment describing
-- something that cannot happen, which is worse than no guard at all.
if not NE.OpenFrameEditor then
say("editor mode isn't available in this DragonUI build. Type /dui edit to position frames.")
say(NE.L["editor mode isn't available in this DragonUI build. Type /dui edit to position frames."])
return
end
local ok, why = NE.OpenFrameEditor(frame)
if not ok then
say(why or "editor mode couldn't be opened.")
say(why or NE.L["editor mode couldn't be opened."])
return
end
-- Editor mode covers the screen and the viewer being positioned may sit under this window.
@@ -104,11 +104,11 @@ CDS.PositionViewer = positionViewer -- test seam
-- frame it changes. Rendering them here as well is the exact duplication this file's header rules out.
local function buildViewerLinks(col)
col:AddSection("Viewer layout", true)
col:AddText("Each viewer's own settings — size, spacing, orientation, visibility, what its icons "
.. "show — live on the frame, in edit mode, where you can see what you are changing. These open "
.. "edit mode with that viewer selected and its settings already up. Closes this window; not "
.. "available in combat.")
col:AddSection(NE.L["Viewer layout"], true)
col:AddText(NE.L["Each viewer's own settings — size, spacing, orientation, visibility, what its icons "]
.. NE.L["show — live on the frame, in edit mode, where you can see what you are changing. These open "]
.. NE.L["edit mode with that viewer selected and its settings already up. Closes this window; not "]
.. NE.L["available in combat."])
for _, spec in ipairs(M.VIEWER_SPECS or {}) do
col:AddButton({
@@ -130,9 +130,9 @@ local function build(parent)
local c = Kit.New(parent, PAGE_W)
c:AddText("Everything the Cooldown Manager can be told to do that is not about one viewer's layout. "
.. "Layout and position both live on the frame itself, in edit mode (/dui edit) — click a viewer "
.. "there for its own settings, or use the buttons just below to go straight to one.",
c:AddText(NE.L["Everything the Cooldown Manager can be told to do that is not about one viewer's layout. "]
.. NE.L["Layout and position both live on the frame itself, in edit mode (/dui edit) — click a viewer "]
.. NE.L["there for its own settings, or use the buttons just below to go straight to one."],
{ font = "GameFontHighlightSmall" })
buildViewerLinks(c)
@@ -140,79 +140,79 @@ local function build(parent)
-- ── Buffed spells. Retail tints the swipe gold while a spell's own buff is on you; that setter is
-- WoD+, so ours haloes the icon instead (PORT_PLAN §H.2 8c) — which then frees the timer to show
-- whichever number is more useful, rather than being retail's only way to say "buffed".
c:AddSection("Buffed spells", false)
c:AddText("A spell can be on cooldown and buffing you at the same time. The glow says which icons "
.. "are buffed; the timer says how long.")
c:AddSection(NE.L["Buffed spells"], false)
c:AddText(NE.L["A spell can be on cooldown and buffing you at the same time. The glow says which icons "]
.. NE.L["are buffed; the timer says how long."])
c:AddCheckbox({
label = "Glow while buffed",
desc = "Halo the icon gold while the spell's buff (or, for a shaman, its totem) is up.",
label = NE.L["Glow while buffed"],
desc = NE.L["Halo the icon gold while the spell's buff (or, for a shaman, its totem) is up."],
get = function() return M.IsBuffGlowEnabled() end,
set = function(v) M.SetBuffGlowEnabled(v) end,
})
c:AddCheckbox({
label = "Show the buff's time, not the cooldown",
desc = "Retail's behaviour: while buffed, the icon counts down the BUFF. Off, it counts down the "
.. "spell's cooldown and the glow alone marks it as buffed — which is clearer when the "
.. "two differ, as on Prayer of Mending.",
label = NE.L["Show the buff's time, not the cooldown"],
desc = NE.L["Retail's behaviour: while buffed, the icon counts down the BUFF. Off, it counts down the "]
.. NE.L["spell's cooldown and the glow alone marks it as buffed — which is clearer when the "]
.. NE.L["two differ, as on Prayer of Mending."],
get = function() return M.BuffShowsAuraTime() end,
set = function(v) M.SetBuffShowsAuraTime(v) end,
})
-- ── Icon fit. Retail masks its icons, which both insets and rounds them; §C2 records that
-- MaskTexture has no polyfill on this client, so the inset is reproducible and the rounding is not.
c:AddSection("Icon fit", false)
c:AddText("The frame is a soft shadow that falls on the icon's outer edge, so it only shows where "
.. "there is icon underneath it. Strength draws it more than once to deepen it — that is also "
.. "what makes its rounded corners read, since the icons themselves cannot be rounded here. "
.. "Inset shrinks the icon, which slides the shadow off it, so raise that one sparingly.")
c:AddSection(NE.L["Icon fit"], false)
c:AddText(NE.L["The frame is a soft shadow that falls on the icon's outer edge, so it only shows where "]
.. NE.L["there is icon underneath it. Strength draws it more than once to deepen it — that is also "]
.. NE.L["what makes its rounded corners read, since the icons themselves cannot be rounded here. "]
.. NE.L["Inset shrinks the icon, which slides the shadow off it, so raise that one sparingly."])
c:AddSlider({
label = "Frame strength", min = 1, max = M.FRAME_STRENGTH_MAX, step = 1,
label = NE.L["Frame strength"], min = 1, max = M.FRAME_STRENGTH_MAX, step = 1,
get = function() return M.GetFrameStrength() end,
set = function(v) M.SetFrameStrength(v) end,
})
c:AddSlider({
label = "Icon inset", min = 0, max = M.ICON_INSET_EXTRA_MAX, step = 1, format = pct,
label = NE.L["Icon inset"], min = 0, max = M.ICON_INSET_EXTRA_MAX, step = 1, format = pct,
get = function() return M.GetIconInsetExtra() end,
set = function(v) M.SetIconInsetExtra(v) end,
})
-- ── Per-spec layout. Dual talent specialisation means one character genuinely wants two layouts.
c:AddSection("Talent specs", false)
c:AddText("Which spells and buffs you track is remembered separately for each talent spec, so a "
.. "Discipline layout and a Holy one do not overwrite each other. Where each viewer sits is "
.. "always remembered per character; the appearance settings are shared unless you say otherwise "
c:AddSection(NE.L["Talent specs"], false)
c:AddText(NE.L["Which spells and buffs you track is remembered separately for each talent spec, so a "]
.. NE.L["Discipline layout and a Holy one do not overwrite each other. Where each viewer sits is "]
.. NE.L["always remembered per character; the appearance settings are shared unless you say otherwise "]
.. "below.")
c:AddCheckbox({
label = "Separate layout per spec",
desc = "Off, both specs share one set of lists. Turning it on copies the layout you have now "
.. "into the spec you are in.",
label = NE.L["Separate layout per spec"],
desc = NE.L["Off, both specs share one set of lists. Turning it on copies the layout you have now "]
.. NE.L["into the spec you are in."],
get = function() return M.IsPerSpecLayout() end,
set = function(v) M.SetPerSpecLayout(v) end,
})
c:AddCheckbox({
label = "Separate appearance per character",
label = NE.L["Separate appearance per character"],
-- Says what happens on BOTH edges, because the reversibility is the reason it is safe to try:
-- a character that has changed nothing keeps reading the shared values, so nothing moves when
-- this is ticked, and unticking it simply stops consulting the per-character values rather than
-- discarding them.
desc = "Off, orientation, icons per row, size, padding and opacity are one setup for every "
.. "character. On, each character can differ — until you change something here it still "
.. "follows the shared setup, so nothing moves when you tick this, and unticking it "
.. "gives the shared setup back without losing what you changed.",
desc = NE.L["Off, orientation, icons per row, size, padding and opacity are one setup for every "]
.. NE.L["character. On, each character can differ — until you change something here it still "]
.. NE.L["follows the shared setup, so nothing moves when you tick this, and unticking it "]
.. NE.L["gives the shared setup back without losing what you changed."],
get = function() return M.IsPerCharacterFrames() end,
set = function(v) M.SetPerCharacterFrames(v) end,
})
c:AddCheckbox({
label = "Layouts include appearance",
label = NE.L["Layouts include appearance"],
-- Governs APPLYING, not saving. A share string always carries appearance, so this is the
-- recipient's decision rather than the author's — and it is off by default because an imported
-- layout that silently resizes and reorients four viewers is how "load a layout" stops being an
-- action anyone trusts.
desc = "Off, loading or importing a layout changes only what you track — lists, tracked buffs, "
.. "trinkets, alerts and sounds. On, it also applies the orientation, icons per row, "
.. "size, padding and opacity the layout was saved with.|n|nLayouts always SAVE "
.. "appearance either way, so this only decides what happens when one is applied. Revert "
.. "always puts appearance back, whatever this says.",
desc = NE.L["Off, loading or importing a layout changes only what you track — lists, tracked buffs, "]
.. NE.L["trinkets, alerts and sounds. On, it also applies the orientation, icons per row, "]
.. NE.L["size, padding and opacity the layout was saved with.|n|nLayouts always SAVE "]
.. NE.L["appearance either way, so this only decides what happens when one is applied. Revert "]
.. NE.L["always puts appearance back, whatever this says."],
get = function() return M.LayoutsIncludeAppearance() end,
set = function(v) M.SetLayoutsIncludeAppearance(v) end,
})
@@ -220,22 +220,22 @@ local function build(parent)
-- ── Buff tracking. The buff viewers have no curated list: any player buff shorter than the
-- auto-track window shows automatically, which is what makes trinket, potion and proc buffs work
-- without enumerating them in advance.
c:AddSection("Buff tracking", false)
c:AddText("Buffs you have not seen before are recorded and listed under Not Displayed on the "
.. "Tracked Buffs tab, where you can assign the ones you want. Nothing appears on screen until "
.. "you do.")
c:AddSection(NE.L["Buff tracking"], false)
c:AddText(NE.L["Buffs you have not seen before are recorded and listed under Not Displayed on the "]
.. NE.L["Tracked Buffs tab, where you can assign the ones you want. Nothing appears on screen until "]
.. NE.L["you do."])
c:AddCheckbox({
label = ("Auto-track buffs under %ds"):format(M.BUFF_TRACK_MAX_DURATION or 120),
desc = "Show every short buff the moment it lands, without assigning it first. Convenient on a "
.. "character you are still setting up; in a raid it fills the viewers with other "
.. "people's cooldowns, food and flasks.",
label = (NE.L["Auto-track buffs under %ds"]):format(M.BUFF_TRACK_MAX_DURATION or 120),
desc = NE.L["Show every short buff the moment it lands, without assigning it first. Convenient on a "]
.. NE.L["character you are still setting up; in a raid it fills the viewers with other "]
.. NE.L["people's cooldowns, food and flasks."],
get = function() return M.IsAutoTrackBuffs() end,
set = function(v) M.SetAutoTrackBuffs(v) end,
onChanged = function() if M.RefreshActiveViewer then M.RefreshActiveViewer() end end,
})
c:AddDropdown({
label = "Show them as", values = TRACK_DEST, width = 140,
desc = "Which of the two buff viewers auto-tracked buffs land in.",
label = NE.L["Show them as"], values = TRACK_DEST, width = 140,
desc = NE.L["Which of the two buff viewers auto-tracked buffs land in."],
get = function() return M.AutoTrackDest() end,
set = function(v) M.SetAutoTrackDest(v) end,
onChanged = function() if M.RefreshActiveViewer then M.RefreshActiveViewer() end end,
@@ -247,22 +247,22 @@ local function build(parent)
-- reset looks under Settings. What the §G.10 decision removes is a second view of the same VALUES in
-- a second window — not a second way to press a button.
c:AddSection("Reset", false)
c:AddText("Both of these are immediate and cannot be undone from here — Revert only covers layout "
c:AddText(NE.L["Both of these are immediate and cannot be undone from here — Revert only covers layout "]
.. "changes.")
c:AddButton({
label = "Reset spell and buff lists",
label = NE.L["Reset spell and buff lists"],
-- Says CLASS, not "character". The store is one shared DragonUI profile keyed by class, so this
-- is the honest scope: another class is untouched, and another character of the SAME class reads
-- the same lists and will see them reset too. Claiming "per-character" is what the old copy did,
-- while the code behind it reset every class at once.
desc = "Restore the curated defaults and the auto-track window for THIS CLASS, clearing its "
.. "spell lists, aura assignments and trinket placement. Other classes, alerts, sounds "
.. "and positions are not affected.",
desc = NE.L["Restore the curated defaults and the auto-track window for THIS CLASS, clearing its "]
.. NE.L["spell lists, aura assignments and trinket placement. Other classes, alerts, sounds "]
.. NE.L["and positions are not affected."],
onClick = function() StaticPopup_Show("NE_CDM_RESET_TRACKING") end,
})
c:AddButton({
label = "Clear all alerts and sounds",
desc = "Remove every per-spell alert and ready sound. Spell lists and positions are not affected.",
label = NE.L["Clear all alerts and sounds"],
desc = NE.L["Remove every per-spell alert and ready sound. Spell lists and positions are not affected."],
onClick = function() StaticPopup_Show("NE_CDM_RESET_ALERTS") end,
})
c:EndSection()
@@ -135,7 +135,7 @@ local function build()
-- retextures the streaks and sets the title styling. Same path modules/collections/Window.lua uses.
local PC = NE.panelchrome
if PC and PC.Apply then
PC.Apply(f, { layout = "PortraitFrameTemplate", title = "Cooldown Manager", noPortrait = true })
PC.Apply(f, { layout = "PortraitFrameTemplate", title = NE.L["Cooldown Manager"], noPortrait = true })
end
-- THE BODY: plain, full-brightness UI-Background-Rock — the same stone every other from-scratch
@@ -217,14 +217,14 @@ local function build()
-- TOPLEFT -> panel TOPRIGHT(+1,-28), next TOP -> prev BOTTOM(0,-3). Retail reuses one glyph for
-- both active and inactive states, so only activeAtlas is passed.
f.spellsTab = NE.tabs.MakeSideTab(f, {
activeAtlas = "icon_cooldownmanager", tooltip = "Spells", iconSize = 32,
activeAtlas = "icon_cooldownmanager", tooltip = NE.L["Spells"], iconSize = 32,
onClick = function() CDS.SetDisplayMode("spells") end,
})
f.spellsTab.displayMode = "spells"
f.spellsTab:SetPoint("TOPLEFT", f, "TOPRIGHT", 1, -28)
f.aurasTab = NE.tabs.MakeSideTab(f, {
activeAtlas = "icon_trackedbuffs", tooltip = "Tracked Buffs", iconSize = 32,
activeAtlas = "icon_trackedbuffs", tooltip = NE.L["Tracked Buffs"], iconSize = 32,
onClick = function() CDS.SetDisplayMode("auras") end,
})
f.aurasTab.displayMode = "auras"
@@ -236,7 +236,7 @@ local function build()
-- player already reads as "options in this window". At 18px it is a hair over its native 15x16, so
-- it stays crisp.
f.settingsTab = NE.tabs.MakeSideTab(f, {
activeAtlas = "questlog-icon-setting", tooltip = "Settings", iconSize = 18,
activeAtlas = "questlog-icon-setting", tooltip = NE.L["Settings"], iconSize = 18,
onClick = function() CDS.SetDisplayMode("settings") end,
})
f.settingsTab.displayMode = "settings"
@@ -280,7 +280,7 @@ local function build()
end)
cog:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText("Options")
GameTooltip:SetText(NE.L["Options"])
GameTooltip:Show()
end)
cog:SetScript("OnLeave", function() GameTooltip:Hide() end)
@@ -332,8 +332,8 @@ local function build()
end)
f.layoutButton:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText("Layouts")
GameTooltip:AddLine("Save, load, import and export the whole|nCooldown Manager setup for this class.",
GameTooltip:SetText(NE.L["Layouts"])
GameTooltip:AddLine(NE.L["Save, load, import and export the whole|nCooldown Manager setup for this class."],
1, 1, 1)
GameTooltip:Show()
end)
@@ -344,7 +344,7 @@ local function build()
f.revertButton = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
f.revertButton:SetSize(90, 22)
f.revertButton:SetPoint("LEFT", f.layoutButton, "RIGHT", 6, 0)
f.revertButton:SetText("Revert")
f.revertButton:SetText(NE.L["Revert"])
f.revertButton:SetScript("OnClick", function()
if CDS.Revert then CDS.Revert() end
end)
@@ -355,13 +355,13 @@ local function build()
f.revertButton:SetMotionScriptsWhileDisabled(true)
f.revertButton:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText("Revert")
GameTooltip:AddLine("Undoes the last layout change — applying a layout,|nimporting one, or the "
.. "starter reset.|n|nOne step, and only for this session.", 1, 1, 1)
GameTooltip:SetText(NE.L["Revert"])
GameTooltip:AddLine(NE.L["Undoes the last layout change — applying a layout,|nimporting one, or the "]
.. NE.L["starter reset.|n|nOne step, and only for this session."], 1, 1, 1)
if not (CDS.CanRevert and CDS.CanRevert()) then
GameTooltip:AddLine(" ")
GameTooltip:AddLine("Nothing to undo. It covers LAYOUTS, not the settings|non these tabs — a "
.. "viewer's own size and position revert|nfrom its edit-mode panel instead.", 1, 0.3, 0.3)
GameTooltip:AddLine(NE.L["Nothing to undo. It covers LAYOUTS, not the settings|non these tabs — a "]
.. NE.L["viewer's own size and position revert|nfrom its edit-mode panel instead."], 1, 0.3, 0.3)
end
GameTooltip:Show()
end)
@@ -431,7 +431,7 @@ function CDS.GetDisplayMode() return currentMode end
function CDS.RefreshLayoutDropdown()
if not (panel and panel.layoutButton) then return end
local cur = CDS.presets and CDS.presets.Current and CDS.presets.Current()
panel.layoutButton:SetText(cur or "Layout: Starter")
panel.layoutButton:SetText(cur or NE.L["Layout: Starter"])
end
function CDS.RefreshRevertState()
@@ -475,7 +475,7 @@ local function windowAvailable()
if M.IsEnabled() then return true end
if DEFAULT_CHAT_FRAME then
DEFAULT_CHAT_FRAME:AddMessage("|cff1784d1Cooldown Manager|r is turned off. Enable it in DragonUI's "
.. "options, under New Era > Cooldown Manager.")
.. NE.L["options, under New Era > Cooldown Manager."])
end
return false
end
@@ -354,7 +354,7 @@ function Presets.SeedStarterIfFresh()
lay.starterSeeded = tab
local ok = Presets.UseSpecStarter(tab)
if ok and NE.Log then
NE.Log("CDM", "seeded the " .. tostring((M.SpecNames and M.SpecNames()[tab]) or tab)
NE.Log("CDM", NE.L["seeded the "] .. tostring((M.SpecNames and M.SpecNames()[tab]) or tab)
.. " starter layout for a new spec")
end
return ok
@@ -469,20 +469,20 @@ end
-- Returns snapshot, or nil + a reason fit to show the player. Never errors.
function Presets.Decode(str)
if type(str) ~= "string" then return nil, "nothing pasted" end
if type(str) ~= "string" then return nil, NE.L["nothing pasted"] end
str = str:gsub("%s", "")
if str == "" then return nil, "nothing pasted" end
if str:sub(1, #MAGIC) ~= MAGIC then return nil, "not a Cooldown Manager layout string" end
if str == "" then return nil, NE.L["nothing pasted"] end
if str:sub(1, #MAGIC) ~= MAGIC then return nil, NE.L["not a Cooldown Manager layout string"] end
local payload = base64Decode(str:sub(#MAGIC + 1))
local ok, val = pcall(function() return (parseValue(payload, 1)) end)
if not ok or type(val) ~= "table" then return nil, "corrupt layout string" end
if type(val.class) ~= "string" then return nil, "not a Cooldown Manager layout" end
if not ok or type(val) ~= "table" then return nil, NE.L["corrupt layout string"] end
if type(val.class) ~= "string" then return nil, NE.L["not a Cooldown Manager layout"] end
-- The class gate upstream lacks. The spell lists are keyed by class, so applying another class's
-- layout writes into that class's slot and changes nothing visible — a silent no-op is a worse
-- outcome than a refusal, because the player has no way to tell it from a broken feature.
local _, class = UnitClass("player")
if class and val.class ~= class then
return nil, ("that layout is for a %s"):format(val.class)
return nil, (NE.L["that layout is for a %s"]):format(val.class)
end
return val
end
@@ -499,7 +499,7 @@ end
function Presets.Import(text)
local snap, err = Presets.Decode(text)
if not snap then
local msg = "Cooldown Manager layout: " .. (err or "invalid string")
local msg = NE.L["Cooldown Manager layout: "] .. (err or NE.L["invalid string"])
if UIErrorsFrame then UIErrorsFrame:AddMessage(msg, 1, 0.3, 0.3) end
if NE.Log then NE.Log(msg) end
return false
@@ -526,7 +526,7 @@ end
StaticPopupDialogs = StaticPopupDialogs or {}
StaticPopupDialogs["NE_CDM_LAYOUT_NAME"] = {
text = "Name this layout:",
text = NE.L["Name this layout:"],
button1 = SAVE or "Save", button2 = CANCEL or "Cancel",
hasEditBox = 1, timeout = 0, whileDead = 1, hideOnEscape = 1,
OnShow = function(self)
@@ -548,7 +548,7 @@ function CDS.PromptName(preset, cb)
end
StaticPopupDialogs["NE_CDM_LAYOUT_IMPORT"] = {
text = "Paste a Cooldown Manager layout string:",
text = NE.L["Paste a Cooldown Manager layout string:"],
button1 = "Import", button2 = CANCEL or "Cancel",
hasEditBox = 1, editBoxWidth = 260, timeout = 0, whileDead = 1, hideOnEscape = 1,
OnShow = function(self)
@@ -566,7 +566,7 @@ StaticPopupDialogs["NE_CDM_LAYOUT_IMPORT"] = {
}
StaticPopupDialogs["NE_CDM_LAYOUT_EXPORT"] = {
text = "Cooldown Manager layout string (Ctrl+C to copy):",
text = NE.L["Cooldown Manager layout string (Ctrl+C to copy):"],
button1 = OKAY or "Close",
hasEditBox = 1, editBoxWidth = 260, timeout = 0, whileDead = 1, hideOnEscape = 1,
OnShow = function(self)
@@ -580,7 +580,7 @@ StaticPopupDialogs["NE_CDM_LAYOUT_EXPORT"] = {
}
StaticPopupDialogs["NE_CDM_LAYOUT_DELETE"] = {
text = "Delete the layout \"%s\"?",
text = NE.L["Delete the layout \"%s\"?"],
button1 = YES or "Yes", button2 = NO or "No",
timeout = 0, whileDead = 1, hideOnEscape = 1,
OnAccept = function(self) Presets.Delete(self.data and self.data.name) end,
@@ -588,8 +588,8 @@ StaticPopupDialogs["NE_CDM_LAYOUT_DELETE"] = {
-- Starter confirms, and Revert does not: one is destructive, the other IS the undo.
StaticPopupDialogs["NE_CDM_LAYOUT_STARTER"] = {
text = "Reset to the starter layout?\n\nThis reverts every Cooldown Manager edit — spells, tracked "
.. "auras, trinket placement, alerts and sounds — to their defaults, and clears your "
text = NE.L["Reset to the starter layout?\n\nThis reverts every Cooldown Manager edit — spells, tracked "]
.. NE.L["auras, trinket placement, alerts and sounds — to their defaults, and clears your "]
.. "saved-layout selection.\n\nFrame positions are not affected.",
button1 = YES or "Yes", button2 = NO or "No",
timeout = 0, whileDead = 1, hideOnEscape = 1,
@@ -600,9 +600,9 @@ StaticPopupDialogs["NE_CDM_LAYOUT_STARTER"] = {
-- Narrower in what it says, because it is narrower in what it does — tracked auras, trinkets, alerts
-- and sounds are all left alone, and the off-spec spells are turned off rather than removed.
StaticPopupDialogs["NE_CDM_LAYOUT_SPEC_STARTER"] = {
text = "Load the %s starter layout?\n\nEssential is set to that spec's spells. Everything else for "
text = NE.L["Load the %s starter layout?\n\nEssential is set to that spec's spells. Everything else for "]
.. "your class moves to Not Displayed — nothing is deleted, and you can drag any of it back.\n\n"
.. "Tracked auras, trinkets, alerts and frame positions are not affected.",
.. NE.L["Tracked auras, trinkets, alerts and frame positions are not affected."],
button1 = YES or "Yes", button2 = NO or "No",
timeout = 0, whileDead = 1, hideOnEscape = 1,
OnAccept = function(self) Presets.UseSpecStarter(self.data and self.data.tab) end,
@@ -614,7 +614,7 @@ function CDS.BuildLayoutMenu(_, root)
local cur = getCurrent()
local names = Presets.Names()
root:CreateTitle(cur or "No layout")
root:CreateTitle(cur or NE.L["No layout"])
if #names > 0 then
for _, name in ipairs(names) do
@@ -625,7 +625,7 @@ function CDS.BuildLayoutMenu(_, root)
root:CreateDivider()
end
root:CreateButton("New Layout", function()
root:CreateButton(NE.L["New Layout"], function()
CDS.PromptName("", function(n) Presets.SaveAs(n) end)
end)
if cur then
@@ -650,7 +650,7 @@ function CDS.BuildLayoutMenu(_, root)
local names = M.SpecNames and M.SpecNames() or {}
local detected = M.DetectSpec and M.DetectSpec()
if #names > 0 then
local sub = root:CreateButton("Starter Layout")
local sub = root:CreateButton(NE.L["Starter Layout"])
for tab = 1, #names do
local label = names[tab]
if tab == detected then label = label .. " |cff44ff44(your spec)|r" end
@@ -659,17 +659,17 @@ function CDS.BuildLayoutMenu(_, root)
end)
end
sub:CreateDivider()
sub:CreateButton("Everything (no spec)", function() StaticPopup_Show("NE_CDM_LAYOUT_STARTER") end)
:SetTooltip("Everything", "The full curated list for your class, both specs' spells|nincluded. "
.. "This is what the Cooldown Manager shipped with|nbefore per-spec starters.")
sub:CreateButton(NE.L["Everything (no spec)"], function() StaticPopup_Show("NE_CDM_LAYOUT_STARTER") end)
:SetTooltip("Everything", NE.L["The full curated list for your class, both specs' spells|nincluded. "]
.. NE.L["This is what the Cooldown Manager shipped with|nbefore per-spec starters."])
else
root:CreateButton("Use Starter Layout", function() StaticPopup_Show("NE_CDM_LAYOUT_STARTER") end)
root:CreateButton(NE.L["Use Starter Layout"], function() StaticPopup_Show("NE_CDM_LAYOUT_STARTER") end)
end
root:CreateButton("Import Layout", function() StaticPopup_Show("NE_CDM_LAYOUT_IMPORT") end)
root:CreateButton("Export Layout", function() Presets.Export(cur) end)
:SetTooltip("Export Layout", "Opens a share string you can copy with Ctrl+C.|nIt covers this "
.. "class's spell lists, tracked auras,|ntrinket placement, alerts and sounds.")
root:CreateButton(NE.L["Import Layout"], function() StaticPopup_Show("NE_CDM_LAYOUT_IMPORT") end)
root:CreateButton(NE.L["Export Layout"], function() Presets.Export(cur) end)
:SetTooltip(NE.L["Export Layout"], NE.L["Opens a share string you can copy with Ctrl+C.|nIt covers this "]
.. NE.L["class's spell lists, tracked auras,|ntrinket placement, alerts and sounds."])
end
function CDS.ToggleLayoutMenu(anchor)
@@ -225,7 +225,7 @@ function BaseViewerMixin:OnEvent(event, ...)
if not M.IsEnabled() then return end
local ok, err = pcall(self.HandleEvent, self, event, ...)
if not ok and NE.Log then
NE.Log("CDM", "viewer event '" .. tostring(event) .. "' error: " .. tostring(err))
NE.Log("CDM", NE.L["viewer event '"] .. tostring(event) .. "' error: " .. tostring(err))
end
end
@@ -355,7 +355,7 @@ function BaseViewerMixin:Rebuild()
local ok, err = pcall(self.RebuildInner, self)
self._rebuilding = false
if not ok then
if NE.Log then NE.Log("CDM", "rebuild failed for '" .. tostring(self.category) .. "': " .. tostring(err)) end
if NE.Log then NE.Log("CDM", NE.L["rebuild failed for '"] .. tostring(self.category) .. "': " .. tostring(err)) end
end
end
@@ -68,7 +68,7 @@ local function buildWell(parent, name, labelText, anchorTop, height, commitFn, c
-- depends on. Guarded on enabled state so a read-only well still refuses focus.
scroll:EnableMouse(true)
scroll:SetScript("OnMouseDown", function()
if edit.IsEnabled and not edit:IsEnabled() then return end
if edit._newEraEditable == false then return end
edit:SetFocus()
end)
@@ -91,34 +91,31 @@ function G.SetupInfo(f)
-- Single full-width column. The reference frame puts "Guild News" in a right-hand column, but
-- guild news is a Cataclysm system — phantom on 3.3.5a — so it stays CUT (consistent with the
-- WotLK-real scope), and MOTD + Guild Information take the whole panel instead.
panel.MOTD = buildWell(panel, "NE_GuildMOTDScroll", GUILD_MOTD or "Guild Message Of The Day", -6, 70,
panel.MOTD = buildWell(panel, "NE_GuildMOTDScroll", GUILD_MOTD or NE.L["Guild Message Of The Day"], -6, 70,
function(text) if GuildSetMOTD then GuildSetMOTD(text) end end,
function() return CanEditMOTD and CanEditMOTD() end)
panel.Info = buildWell(panel, "NE_GuildInfoScroll", GUILD_INFORMATION or "Guild Information", -114, 300,
panel.Info = buildWell(panel, "NE_GuildInfoScroll", GUILD_INFORMATION or NE.L["Guild Information"], -114, 300,
function(text) if SetGuildInfoText then SetGuildInfoText(text) end end,
function() return CanEditGuildInfo and CanEditGuildInfo() end)
end
-- Apply edit permission to a well, but ONLY on an actual change of state.
--
-- EditBox:SetEnabled is a ClassicAPI shim (!!!ClassicAPI/Util/WidgetAPI.lua) and its Enable() path
-- ends in ClearFocus(). RefreshInfo runs on every GUILD_ROSTER_UPDATE — and this window requests a
-- fresh roster whenever it opens — so re-applying "enabled" unconditionally tore the cursor out of
-- the box mid-sentence and the well read as uneditable. Guarding on change means Enable() fires once,
-- before anyone is typing, instead of on every roster tick.
--
-- Enable() also swaps the font object to GameFontWhite, so the well's own font has to be re-applied
-- after it or the text silently changes size.
-- EditBox has no native Enable/Disable on this 3.3.5a client. Toggle mouse and
-- keyboard input directly instead of relying on the EncounterJournal's retail
-- SetEnabled compatibility method. RefreshInfo runs on every
-- GUILD_ROSTER_UPDATE, so only change state when the permission changes.
local function applyEditable(well, canEdit)
local want = canEdit and true or false
if well._editable == want then return end
well._editable = want
local edit = well.edit
if edit.SetEnabled then edit:SetEnabled(want)
elseif want then edit:Enable()
else edit:Disable() end
edit._newEraEditable = want
edit:EnableMouse(want)
if edit.EnableKeyboard then edit:EnableKeyboard(want) end
if not want and edit:HasFocus() then edit:ClearFocus() end
edit:SetFontObject("GameFontHighlightSmall")
edit:SetTextColor(1, 1, 1)
@@ -17,9 +17,9 @@ local G = NE.guild
-- Binding labels for the Key Bindings UI. Bindings.xml (addon root) declares the actions; the
-- settings UI reads these globals for the header + row labels. "never overwrite an existing
-- global" precedent, same as the reference's Core/Boot/Core.lua.
BINDING_HEADER_DRAGONUI_NEWERA = BINDING_HEADER_DRAGONUI_NEWERA or "DragonUI New Era"
BINDING_NAME_NEWERA_TOGGLEGUILD = BINDING_NAME_NEWERA_TOGGLEGUILD or "Toggle Guild Window"
BINDING_NAME_NEWERA_TOGGLESOCIAL = BINDING_NAME_NEWERA_TOGGLESOCIAL or "Toggle Social Window"
BINDING_HEADER_DRAGONUI_NEWERA = BINDING_HEADER_DRAGONUI_NEWERA or NE.L["DragonUI New Era"]
BINDING_NAME_NEWERA_TOGGLEGUILD = BINDING_NAME_NEWERA_TOGGLEGUILD or NE.L["Toggle Guild Window"]
BINDING_NAME_NEWERA_TOGGLESOCIAL = BINDING_NAME_NEWERA_TOGGLESOCIAL or NE.L["Toggle Social Window"]
-- Same as Window.lua's isModuleEnabled: gated entirely by the options panel's single "Social"
-- checkbox — Guild has no enable flag of its own (see Window.lua's comment for why a
@@ -28,12 +28,12 @@ local ROW_HEIGHT = 18
-- GuildColumn (owner 2026-07-17: column moved onto the Roster tab) rather than spanning the full
-- window; the fill-width Note column just gets a bit less room as a result.
local COLUMNS = {
{ key = "level", title = LEVEL_ABBR or "Lvl", x = 4, w = 40, justify = "CENTER" },
{ key = "class", title = CLASS or "Class", x = 46, w = 46, justify = "CENTER", icon = true },
{ key = "name", title = NAME or "Name", x = 94, w = 150, justify = "LEFT" },
{ key = "zone", title = ZONE or "Zone", x = 246, w = 150, justify = "LEFT" },
{ key = "rank", title = RANK or "Rank", x = 398, w = 120, justify = "LEFT" },
{ key = "note", title = LABEL_NOTE or "Note", x = 520, w = 0, justify = "LEFT" }, -- w 0 = fill
{ key = "level", title = LEVEL_ABBR or NE.L["Lvl"], x = 4, w = 40, justify = "CENTER" },
{ key = "class", title = CLASS or NE.L["Class"], x = 46, w = 46, justify = "CENTER", icon = true },
{ key = "name", title = NAME or NE.L["Name"], x = 94, w = 150, justify = "LEFT" },
{ key = "zone", title = ZONE or NE.L["Zone"], x = 246, w = 150, justify = "LEFT" },
{ key = "rank", title = RANK or NE.L["Rank"], x = 398, w = 120, justify = "LEFT" },
{ key = "note", title = LABEL_NOTE or NE.L["Note"], x = 520, w = 0, justify = "LEFT" }, -- w 0 = fill
}
-- Native 3.3.5a class-icon sheet + coords. CLASS_ICON_TCOORDS is a stock global (modules/talents
@@ -145,7 +145,7 @@ local function buildMemberDetail(parent)
-- Public note.
local nlabel = d:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
nlabel:SetPoint("TOPLEFT", d.Rank, "BOTTOMLEFT", 0, -10)
nlabel:SetText(LABEL_NOTE or "Note")
nlabel:SetText(LABEL_NOTE or NE.L["Note"])
local note = CreateFrame("EditBox", nil, d, "InputBoxTemplate")
note:SetSize(170, 20); note:SetAutoFocus(false)
note:SetPoint("TOPLEFT", nlabel, "BOTTOMLEFT", 6, -2)
@@ -159,7 +159,7 @@ local function buildMemberDetail(parent)
-- Officer note (shown only when viewable).
local olabel = d:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
olabel:SetPoint("TOPLEFT", nlabel, "BOTTOMLEFT", 0, -46)
olabel:SetText(GUILD_OFFICERNOTES_LABEL or "Officer Note")
olabel:SetText(GUILD_OFFICERNOTES_LABEL or NE.L["Officer Note"])
d.OfficerLabel = olabel
local onote = CreateFrame("EditBox", nil, d, "InputBoxTemplate")
onote:SetSize(170, 20); onote:SetAutoFocus(false)
@@ -248,11 +248,11 @@ local function buildMemberDetail(parent)
-- officer note once that block became the rank arrows (owner-reported 2026-08-09). Hanging the row
-- off the content also means it follows when the officer note is hidden for members who cannot view
-- it, instead of stranding the buttons further down.
d.Invite = actionButton(GROUP_INVITE or "Invite")
d.Invite = actionButton(GROUP_INVITE or NE.L["Invite"])
d.Invite:SetPoint("TOPLEFT", onote, "BOTTOMLEFT", -6, -18)
d.Invite:SetScript("OnClick", function() if d._name and InviteUnit then InviteUnit(d._name) end end)
d.Remove = actionButton(REMOVE or "Remove")
d.Remove = actionButton(REMOVE or NE.L["Remove"])
d.Remove:SetPoint("LEFT", d.Invite, "RIGHT", 4, 0)
d.Remove:SetScript("OnClick", function()
if d._name and GuildUninvite then GuildUninvite(d._name); d:Hide(); G.RefreshRoster() end
@@ -269,9 +269,9 @@ local function showMemberDetail(f, index)
if not name then d:Hide(); return end
d._index, d._name = index, name
d.Name:SetText(name); d.Name:SetTextColor(classColor(classFile))
d.Level:SetText((LEVEL or "Level") .. " " .. tostring(level or ""))
d.Level:SetText((LEVEL or NE.L["Level"]) .. " " .. tostring(level or ""))
d.Zone:SetText((online and (zone or "")) or lastOnlineText(index))
d.Rank:SetText((RANK or "Rank") .. ": " .. tostring(rank or ""))
d.Rank:SetText((RANK or NE.L["Rank"]) .. ": " .. tostring(rank or ""))
d.NoteEdit:SetText(note or "")
local canEditNote = CanEditPublicNote and CanEditPublicNote()
@@ -334,31 +334,31 @@ local function openRosterMenu(idx)
end
local menu = { { text = name, isTitle = true, notCheckable = true } }
if online then
menu[#menu + 1] = { text = WHISPER or "Whisper", notCheckable = true,
menu[#menu + 1] = { text = WHISPER or NE.L["Whisper"], notCheckable = true,
func = function() if ChatFrame_SendTell then ChatFrame_SendTell(name) end end }
menu[#menu + 1] = { text = GROUP_INVITE or "Invite", notCheckable = true,
menu[#menu + 1] = { text = GROUP_INVITE or NE.L["Invite"], notCheckable = true,
func = function() if InviteUnit then InviteUnit(name) end end }
end
-- Ignore (owner steer 2026-07-17: added to the right-click menu spec; Promote/Demote/Remove kept
-- as-is per owner confirmation rather than replaced).
menu[#menu + 1] = { text = IGNORE or "Ignore", notCheckable = true,
menu[#menu + 1] = { text = IGNORE or NE.L["Ignore"], notCheckable = true,
func = function() if AddIgnore then AddIgnore(name) end end }
if CanGuildPromote and CanGuildPromote() then
-- Not GUILD_PROMOTE: on this client that global reads "Promote to Guildmaster", which is not what
-- GuildPromote() does — it moves the member up ONE rank. Promoting to guild master is a separate
-- action (GuildSetLeader) that this module does not offer.
menu[#menu + 1] = { text = "Promote", notCheckable = true,
menu[#menu + 1] = { text = NE.L["Promote"], notCheckable = true,
func = function() if GuildPromote then GuildPromote(name) end; G.RefreshRoster() end }
end
if CanGuildDemote and CanGuildDemote() then
menu[#menu + 1] = { text = GUILD_DEMOTE or "Demote", notCheckable = true,
menu[#menu + 1] = { text = GUILD_DEMOTE or NE.L["Demote"], notCheckable = true,
func = function() if GuildDemote then GuildDemote(name) end; G.RefreshRoster() end }
end
if CanGuildRemove and CanGuildRemove() then
menu[#menu + 1] = { text = REMOVE or "Remove", notCheckable = true,
menu[#menu + 1] = { text = REMOVE or NE.L["Remove"], notCheckable = true,
func = function() if GuildUninvite then GuildUninvite(name) end; G.RefreshRoster() end }
end
menu[#menu + 1] = { text = CANCEL or "Cancel", notCheckable = true }
menu[#menu + 1] = { text = CANCEL or NE.L["Cancel"], notCheckable = true }
EasyMenu(menu, rosterMenuFrame, "cursor", 0, 0, "MENU")
end
@@ -384,7 +384,7 @@ function G.SetupRoster(f)
cb:SetPoint("TOPLEFT", panel, "TOPLEFT", 2, -2)
cb:SetScale(0.9)
local cbText = _G[cb:GetName() .. "Text"] or cb.Text
if cbText then cbText:SetText(COMMUNITIES_MEMBER_LIST_SHOW_OFFLINE or GUILD_MEMBERS_SHOW_OFFLINE or "Show Offline") end
if cbText then cbText:SetText(COMMUNITIES_MEMBER_LIST_SHOW_OFFLINE or GUILD_MEMBERS_SHOW_OFFLINE or NE.L["Show Offline"]) end
if GetGuildRosterShowOffline then cb:SetChecked(GetGuildRosterShowOffline()) end
cb:SetScript("OnClick", function(self)
if SetGuildRosterShowOffline then SetGuildRosterShowOffline(self:GetChecked() and true or false) end
@@ -569,7 +569,7 @@ function G.RefreshRoster()
row.cells[3]:SetTextColor(classColor(classFile))
row.cells[3]:SetAlpha(online and 1 or 0.5)
row.cells[4]:SetText((online and (zone or "")) or (GUILD_OFFLINE or "Offline"))
row.cells[4]:SetText((online and (zone or "")) or (GUILD_OFFLINE or NE.L["Offline"]))
row.cells[5]:SetText(rank or "")
row.cells[6]:SetText(note or "")
for c = 4, 6 do row.cells[c]:SetTextColor(dim, dim, dim) end
@@ -593,9 +593,9 @@ function G.RefreshRoster()
-- changes, not on every scroll; see recountOnline below).
if panel.MemberCount then
if G._onlineCount then
panel.MemberCount:SetText(string.format("%d/%d %s", G._onlineCount, total, GUILD_MEMBERS or "Members"))
panel.MemberCount:SetText(string.format("%d/%d %s", G._onlineCount, total, GUILD_MEMBERS or NE.L["Members"]))
else
panel.MemberCount:SetText(string.format("%d %s", total, GUILD_MEMBERS or "Members"))
panel.MemberCount:SetText(string.format("%d %s", total, GUILD_MEMBERS or NE.L["Members"]))
end
end
end
@@ -43,8 +43,8 @@ local ALL_PANELS = { "RosterFrame", "InfoFrame", "ChatFrame" }
-- Only 2 tabs now — Chat was folded into the Guild Info tab (see MODE above), so there's no
-- separate ChatTab side button anymore.
local TAB_DEFS = {
{ key = "RosterTab", mode = "ROSTER", icon = "Interface\\Icons\\INV_Misc_GroupLooking", tip = GUILD_ROSTER_TITLE or "Roster" },
{ key = "InfoTab", mode = "GUILD_INFO", icon = "Interface\\Icons\\INV_Scroll_03", tip = GUILD_INFORMATION or "Guild Information" },
{ key = "RosterTab", mode = "ROSTER", icon = "Interface\\Icons\\INV_Misc_GroupLooking", tip = GUILD_ROSTER_TITLE or NE.L["Roster"] },
{ key = "InfoTab", mode = "GUILD_INFO", icon = "Interface\\Icons\\INV_Scroll_03", tip = GUILD_INFORMATION or NE.L["Guild Information"] },
}
-- Gated entirely by the options panel's single "Social (Friends/Who/Guild/Chat/Raid)" checkbox
@@ -128,7 +128,7 @@ local function buildChrome(f)
titleStr:SetPoint("TOP", f, "TOP", 0, -6)
titleStr:SetPoint("LEFT", f, "LEFT", 58, 0)
titleStr:SetPoint("RIGHT", f, "RIGHT", -58, 0)
titleStr:SetText(GUILD or "Guild")
titleStr:SetText(GUILD or NE.L["Guild"])
f.TitleContainer = tc
f.TitleText = titleStr
f.Title = titleStr
@@ -360,18 +360,18 @@ local function buildGuildColumn(f)
name:SetPoint("LEFT", list, "LEFT", 8, 0)
name:SetPoint("RIGHT", list, "RIGHT", -8, 0)
name:SetJustifyH("CENTER")
name:SetText(GUILD or "Guild")
name:SetText(GUILD or NE.L["Guild"])
list.Name = name
list.MemberCount = list:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
list.MemberCount:SetPoint("TOP", name, "BOTTOM", 0, -6)
list.SetGuild = function(gname, online, total)
name:SetText(gname or (GUILD or "Guild"))
name:SetText(gname or (GUILD or NE.L["Guild"]))
if online and total then
list.MemberCount:SetText(string.format("%d/%d %s", online, total, GUILD_MEMBERS or "Members"))
list.MemberCount:SetText(string.format("%d/%d %s", online, total, GUILD_MEMBERS or NE.L["Members"]))
elseif total then
list.MemberCount:SetText(string.format("%d %s", total, GUILD_MEMBERS or "Members"))
list.MemberCount:SetText(string.format("%d %s", total, GUILD_MEMBERS or NE.L["Members"]))
else
list.MemberCount:SetText("")
end
@@ -420,7 +420,7 @@ local function buildControls(f)
local log = CreateFrame("Button", FRAME_NAME .. "LogButton", f, "UIPanelButtonTemplate")
log:SetSize(120, 22)
log:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 12, 6)
log:SetText(GUILD_EVENT_LOG or "View Log")
log:SetText(GUILD_EVENT_LOG or NE.L["View Log"])
-- 3.3.5a ships GuildEventLogFrame inside FriendsFrame (FriendsFrame.xml), so it exists but its
-- default anchor is the old GuildControlPopupFrame — off-screen from our window. We reparent it,
-- re-anchor it to the RIGHT of our guild frame, and skin it to match the NewEra chrome (once).
@@ -583,7 +583,7 @@ local function buildControls(f)
local control = CreateFrame("Button", FRAME_NAME .. "ControlButton", f, "UIPanelButtonTemplate")
control:SetSize(120, 22)
control:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -12, 6)
control:SetText(GUILDCONTROL or "Guild Control")
control:SetText(GUILDCONTROL or NE.L["Guild Control"])
-- The original handler here was ported verbatim from the retail NewEra source and targeted
-- GuildControlUI_LoadUI()/GuildControlUI — the Cataclysm-era Blizzard_GuildControlUI LoD addon.
-- That addon does not exist on 3.3.5a (no Blizzard_GuildControlUI folder in Interface\AddOns), so
@@ -1175,7 +1175,7 @@ local function buildControls(f)
local invite = CreateFrame("Button", FRAME_NAME .. "InviteButton", f, "UIPanelButtonTemplate")
invite:SetSize(120, 22)
invite:SetPoint("RIGHT", control, "LEFT", -4, 0)
invite:SetText(GUILD_INVITE_MEMBER or COMMUNITIES_INVITE_MEMBERS or "Invite")
invite:SetText(GUILD_INVITE_MEMBER or COMMUNITIES_INVITE_MEMBERS or NE.L["Invite"])
f.InviteButton = invite
-- Stock Blizzard invite dialog (owner steer 2026-07-17: "should trigger the default blizzard
@@ -1260,7 +1260,7 @@ local function createWindow()
f:HookScript("OnShow", function()
local gname = IsInGuild and IsInGuild() and GetGuildInfo("player")
if NE.panelchrome and NE.panelchrome.SetTitle then NE.panelchrome.SetTitle(f, gname or (GUILD or "Guild")) end
if NE.panelchrome and NE.panelchrome.SetTitle then NE.panelchrome.SetTitle(f, gname or (GUILD or NE.L["Guild"])) end
-- Best-effort immediate paint (cached count, may be stale) — GuildRoster()'s response below
-- fires GUILD_ROSTER_UPDATE, which re-syncs it for real via Roster.lua's recountOnline.
-- BUG FIX 2026-07-17: this used to omit `total`, so SetGuild's `online and total` check always
@@ -1335,8 +1335,8 @@ eventFrame:SetScript("OnEvent", function()
if NE.RegisterPanel then
NE.RegisterPanel({
id = MODULE,
title = GUILD or "Guild",
desc = "Modern Communities-style guild window (Roster / Info / Chat).",
title = GUILD or NE.L["Guild"],
desc = NE.L["Modern Communities-style guild window (Roster / Info / Chat)."],
frame = G.frame,
openFn = G.Show,
closeFn = G.Hide,
@@ -356,10 +356,9 @@ function M.Show(level)
f.index = 1
f.levelFrame.reachedText:SetText(NE.L["You have reached"])
-- Lower-case "level" deliberately, against retail's LEVEL_GAINED ("Level %d"). Morpheus renders
-- lower case as small caps, so a capital L came out as an oversized initial next to them; all
-- lower case gives an even small-caps line. Owner's call, 2026-08-01.
f.levelFrame.levelText:SetFormattedText(NE.L["level %d"], level or 0)
-- Use the registered locale key. Morpheus renders the label as small caps,
-- while the locale controls the actual language and word order.
f.levelFrame.levelText:SetFormattedText(NE.L["Level %d"], level or 0)
centreLevelText(f)
f.levelFrame:SetAlpha(0)
f.spellFrame:SetAlpha(0)
@@ -129,7 +129,7 @@ login:SetScript("OnEvent", function(self)
if NE.RegisterHUDFrame then
NE.RegisterHUDFrame({
name = "NE_LevelUpDisplay",
label = "Level Up Display",
label = NE.L["Level Up Display"],
frame = banner,
key = "levelup",
defaultPoint = M.DEFAULT_POINT,
@@ -144,16 +144,16 @@ login:SetScript("OnEvent", function(self)
order = 45,
build = function(scroll, C)
if C.AddSpacer then C:AddSpacer(scroll) end
C:AddHeading(scroll, "Level Up Display")
C:AddHeading(scroll, NE.L["Level Up Display"])
C:AddDescription(scroll,
"Retail's level-up banner. What it announces is read from |cffffcc55this server|r — "
.. "abilities and their levels come from your class trainer's own list, battlegrounds "
.. "and dungeons from the client's brackets. Visit a trainer once to fill it in; "
NE.L["Retail's level-up banner. What it announces is read from |cffffcc55this server|r — "]
.. NE.L["abilities and their levels come from your class trainer's own list, battlegrounds "]
.. NE.L["and dungeons from the client's brackets. Visit a trainer once to fill it in; "]
.. "|cffffcc55/nelevelup coverage|r shows what it knows.")
C:AddToggle(scroll, {
label = "Enable Level Up Display",
desc = "On by default. Turn off to stop the banner appearing on level-up; the harvest "
.. "keeps running either way, so turning it back on costs nothing.",
label = NE.L["Enable Level Up Display"],
desc = NE.L["On by default. Turn off to stop the banner appearing on level-up; the harvest "]
.. NE.L["keeps running either way, so turning it back on costs nothing."],
getFunc = function() return M.IsEnabled() end,
setFunc = function(v)
M.SetEnabled(v)
@@ -162,11 +162,11 @@ login:SetScript("OnEvent", function(self)
})
C:AddToggle(scroll, {
label = "Play the level-up sound",
label = NE.L["Play the level-up sound"],
desc = "|cffffcc55Off by default.|r The game already plays its own fanfare when you "
.. "level, so this only adds a second copy on top of it. Turn it on if you "
.. "want /nelevelup previews to make a sound, since those fire no game sound "
.. "of their own.",
.. NE.L["level, so this only adds a second copy on top of it. Turn it on if you "]
.. NE.L["want /nelevelup previews to make a sound, since those fire no game sound "]
.. NE.L["of their own."],
getFunc = function() return M.IsSoundEnabled() end,
setFunc = function(v) M.SetSoundEnabled(v) end,
})
@@ -177,7 +177,7 @@ login:SetScript("OnEvent", function(self)
if NE.qa then
NE.qa.modules = NE.qa.modules or {}
table.insert(NE.qa.modules, {
name = "Level Up Display",
name = NE.L["Level Up Display"],
frame = banner,
open = function() M.Show(UnitLevel("player") or 10) end,
close = function() M.Hide(); M.HideSide() end,
@@ -163,8 +163,7 @@ function M.ShowSide(level)
p.t = 0
p.reachedText:SetText(NE.L["You have reached"])
-- Lower case to match the banner; see Banner.lua's note at the same call.
p.levelText:SetFormattedText(NE.L["level %d"], level or 0)
p.levelText:SetFormattedText(NE.L["Level %d"], level or 0)
local count = #list
local rows = math.max(1, math.ceil(count / GRID_COLS))
@@ -59,7 +59,7 @@ end
local function typeDropDown_Initialize()
local info = UIDropDownMenu_CreateInfo()
info.text = SPECIFIC_DUNGEONS or "Specific Dungeons"
info.text = SPECIFIC_DUNGEONS or NE.L["Specific Dungeons"]
info.value = "specific"
info.func = typeDropDown_OnClick
info.checked = LFDQueueFrame and LFDQueueFrame.type == info.value
@@ -87,7 +87,7 @@ local function typeDropDown_Initialize()
info.checked = nil
info.tooltipWhileDisabled = 1
info.tooltipOnButton = 1
info.tooltipTitle = YOU_MAY_NOT_QUEUE_FOR_THIS or "You may not queue for this"
info.tooltipTitle = YOU_MAY_NOT_QUEUE_FOR_THIS or NE.L["You may not queue for this"]
info.tooltipText = LFDConstructDeclinedMessage and LFDConstructDeclinedMessage(id)
UIDropDownMenu_AddButton(info)
end
@@ -117,7 +117,7 @@ local function rowLock_OnEnter(self)
local dungeonID = self:GetParent().id
if LFGIsIDHeader(dungeonID) then return end
GameTooltip:SetOwner(self, "ANCHOR_TOP")
GameTooltip:AddLine(YOU_MAY_NOT_QUEUE_FOR_DUNGEON or "You may not queue for this dungeon", 1, 1, 1)
GameTooltip:AddLine(YOU_MAY_NOT_QUEUE_FOR_DUNGEON or NE.L["You may not queue for this dungeon"], 1, 1, 1)
for i = 1, GetLFDLockPlayerCount() do
local playerName, lockedReason = GetLFDLockInfo(dungeonID, i)
if lockedReason ~= 0 then
@@ -338,7 +338,7 @@ local function updateRandomPanel()
rp.description:SetText(dungeonDescription or "")
rp.rewardsDescription:SetText((doneToday and LFD_HOLIDAY_REWARD_EXPLANATION2 or LFD_HOLIDAY_REWARD_EXPLANATION1) or "")
else
rp.title:SetText(LFG_TYPE_RANDOM_DUNGEON or "Random Dungeon")
rp.title:SetText(LFG_TYPE_RANDOM_DUNGEON or NE.L["Random Dungeon"])
rp.description:SetText(LFD_RANDOM_EXPLANATION or "")
rp.rewardsDescription:SetText((doneToday and LFD_RANDOM_REWARD_EXPLANATION2 or LFD_RANDOM_REWARD_EXPLANATION1) or "")
end
@@ -381,7 +381,7 @@ local function updateRandomPanel()
else rp.rewardsLabel:Hide(); rp.rewardsDescription:Hide() end
if moneyAmount > 0 then
rp.moneyLine:SetText((REWARD_MONEY or "Money") .. ": " ..
rp.moneyLine:SetText((REWARD_MONEY or NE.L["Money"]) .. ": " ..
(NE.money and NE.money.Text and NE.money.Text(moneyAmount) or tostring(moneyAmount)))
rp.moneyLine:SetPoint("TOPLEFT", lastAnchor, "BOTTOMLEFT", 0, -8)
rp.moneyLine:Show()
@@ -390,7 +390,7 @@ local function updateRandomPanel()
rp.moneyLine:Hide()
end
if experienceGained > 0 then
rp.xpLine:SetText((REWARD_EXPERIENCE or "Experience") .. ": " .. experienceGained)
rp.xpLine:SetText((REWARD_EXPERIENCE or NE.L["Experience"]) .. ": " .. experienceGained)
rp.xpLine:SetPoint("TOPLEFT", lastAnchor, "BOTTOMLEFT", 0, -4)
rp.xpLine:Show()
else
@@ -406,13 +406,13 @@ local function updateJoinButton()
local btn = pane.joinButton
local mode = GetLFGMode()
if mode == "queued" or mode == "rolecheck" or mode == "proposal" then
btn:SetText(LEAVE_QUEUE or "Leave Queue")
btn:SetText(LEAVE_QUEUE or NE.L["Leave Queue"])
elseif mode == "listed" then
btn:SetText((GetNumPartyMembers() > 0 or GetNumRaidMembers() > 0) and (UNLIST_MY_GROUP or "Unlist My Group") or (UNLIST_ME or "Unlist Me"))
btn:SetText((GetNumPartyMembers() > 0 or GetNumRaidMembers() > 0) and (UNLIST_MY_GROUP or NE.L["Unlist My Group"]) or (UNLIST_ME or NE.L["Unlist Me"]))
elseif GetNumPartyMembers() > 0 or GetNumRaidMembers() > 0 then
btn:SetText(JOIN_AS_PARTY or "Join as Party")
btn:SetText(JOIN_AS_PARTY or NE.L["Join as Party"])
else
btn:SetText(FIND_A_GROUP or "Find Group")
btn:SetText(FIND_A_GROUP or NE.L["Find Group"])
end
-- Port of LFDQueueFrameFindGroupButton_Update's enable gate; the native "backfill panel
@@ -439,11 +439,11 @@ local function updateStatus()
local deserter = GetLFGDeserterExpiration and GetLFGDeserterExpiration()
local cooldown = not deserter and GetLFGRandomCooldownExpiration and GetLFGRandomCooldownExpiration()
if deserter and GetTime() < deserter then
fs:SetFormattedText("|cffff2020%s|r", (LFG_DESERTER_YOU or "You recently deserted a dungeon.") ..
fs:SetFormattedText("|cffff2020%s|r", (LFG_DESERTER_YOU or NE.L["You recently deserted a dungeon."]) ..
" (" .. SecondsToTime(math.ceil(deserter - GetTime())) .. ")")
return
elseif cooldown and GetTime() < cooldown then
fs:SetFormattedText("|cffff2020%s|r", (LFG_RANDOM_COOLDOWN_YOU or "Random dungeon cooldown") ..
fs:SetFormattedText("|cffff2020%s|r", (LFG_RANDOM_COOLDOWN_YOU or NE.L["Random dungeon cooldown"]) ..
" (" .. SecondsToTime(math.ceil(cooldown - GetTime())) .. ")")
return
end
@@ -453,24 +453,24 @@ local function updateStatus()
local lines = {}
if hasData and queuedTime then
local elapsed = GetTime() - queuedTime
lines[#lines + 1] = format(TIME_IN_QUEUE or "Time in queue: %s",
lines[#lines + 1] = format(TIME_IN_QUEUE or NE.L["Time in queue: %s"],
(elapsed >= 60) and SecondsToTime(elapsed) or (LESS_THAN_ONE_MINUTE or "< 1 min"))
if myWait and myWait ~= -1 then
lines[#lines + 1] = format(LFG_STATISTIC_AVERAGE_WAIT or "Average wait: %s", SecondsToTime(myWait, false, false, 1))
lines[#lines + 1] = format(LFG_STATISTIC_AVERAGE_WAIT or NE.L["Average wait: %s"], SecondsToTime(myWait, false, false, 1))
end
lines[#lines + 1] = format("%s |cffffffff%d|r %s |cffffffff%d|r %s |cffffffff%d|r",
TANK or "Tank", tankNeeds or 0, HEALER or "Healer", healerNeeds or 0, DAMAGER or "Damage", dpsNeeds or 0)
TANK or NE.L["Tank"], tankNeeds or 0, HEALER or NE.L["Healer"], healerNeeds or 0, DAMAGER or NE.L["Damage"], dpsNeeds or 0)
else
lines[#lines + 1] = format(TIME_IN_QUEUE or "Time in queue: %s", LESS_THAN_ONE_MINUTE or "< 1 min")
lines[#lines + 1] = format(TIME_IN_QUEUE or NE.L["Time in queue: %s"], LESS_THAN_ONE_MINUTE or NE.L["< 1 min"])
end
fs:SetText(table.concat(lines, "\n"))
elseif mode == "rolecheck" then
fs:SetText(ROLE_CHECK_IN_PROGRESS or "Role check in progress…")
fs:SetText(ROLE_CHECK_IN_PROGRESS or NE.L["Role check in progress…"])
elseif mode == "listed" then
fs:SetText(LOOKING_FOR_RAID or "Listed in the Raid Browser")
fs:SetText(LOOKING_FOR_RAID or NE.L["Listed in the Raid Browser"])
elseif CanPartyLFGBackfill and CanPartyLFGBackfill() then
local name = GetPartyLFGBackfillInfo and GetPartyLFGBackfillInfo()
fs:SetFormattedText(LFG_OFFER_CONTINUE or "Continue %s?", HIGHLIGHT_FONT_COLOR_CODE .. (name or "") .. "|r")
fs:SetFormattedText(LFG_OFFER_CONTINUE or NE.L["Continue %s?"], HIGHLIGHT_FONT_COLOR_CODE .. (name or "") .. "|r")
pane.backfillButton:Show()
return
else
@@ -497,7 +497,7 @@ function L.RefreshDungeons()
local ddText = _G[DROPDOWN_NAME .. "Text"]
if ddText then
if t == "specific" then
ddText:SetText(SPECIFIC_DUNGEONS or "Specific Dungeons")
ddText:SetText(SPECIFIC_DUNGEONS or NE.L["Specific Dungeons"])
elseif type(t) == "number" then
ddText:SetText((GetLFGDungeonInfo(t)) or "")
else
@@ -531,7 +531,7 @@ local function buildPane(host)
-- Header: type dropdown (left) + role row (right).
local ddLabel = pane:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
ddLabel:SetPoint("TOPLEFT", pane, "TOPLEFT", 4, -6)
ddLabel:SetText(TYPE or "Type")
ddLabel:SetText(TYPE or NE.L["Type"])
local dd = CreateFrame("Frame", DROPDOWN_NAME, pane, "UIDropDownMenuTemplate")
dd:SetPoint("TOPLEFT", pane, "TOPLEFT", -12, -18)
@@ -610,7 +610,7 @@ local function buildPane(host)
if rp.description.SetWordWrap then rp.description:SetWordWrap(true) end
rp.rewardsLabel = rp:CreateFontString(nil, "ARTWORK", "GameFontNormal")
rp.rewardsLabel:SetPoint("TOPLEFT", rp.description, "BOTTOMLEFT", 0, -12)
rp.rewardsLabel:SetText(REWARDS or "Rewards")
rp.rewardsLabel:SetText(REWARDS or NE.L["Rewards"])
rp.rewardsDescription = rp:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
rp.rewardsDescription:SetPoint("TOPLEFT", rp.rewardsLabel, "BOTTOMLEFT", 0, -4)
rp.rewardsDescription:SetJustifyH("LEFT")
@@ -640,7 +640,7 @@ local function buildPane(host)
local backfill = CreateFrame("Button", nil, pane, "UIPanelButtonTemplate")
backfill:SetSize(90, 22)
backfill:SetPoint("RIGHT", join, "LEFT", -6, 0)
backfill:SetText(LFG_OFFER_CONTINUE_BUTTON or YES or "Backfill")
backfill:SetText(LFG_OFFER_CONTINUE_BUTTON or YES or NE.L["Backfill"])
backfill:SetScript("OnClick", function()
if StaticPopup_Hide then StaticPopup_Hide("LFG_OFFER_CONTINUE") end
if PartyLFGStartBackfill then PartyLFGStartBackfill() end
@@ -101,7 +101,7 @@ local function makeRaidRow(parent)
local dungeonID = self:GetParent().id
if LFGIsIDHeader(dungeonID) then return end
GameTooltip:SetOwner(self, "ANCHOR_TOP")
GameTooltip:AddLine(YOU_MAY_NOT_QUEUE_FOR_DUNGEON or "You may not list for this raid", 1, 1, 1)
GameTooltip:AddLine(YOU_MAY_NOT_QUEUE_FOR_DUNGEON or NE.L["You may not list for this raid"], 1, 1, 1)
for i = 1, GetLFDLockPlayerCount() do
local playerName, lockedReason = GetLFDLockInfo(dungeonID, i)
if lockedReason ~= 0 then
@@ -270,9 +270,9 @@ local function updateListButton()
local mode = GetLFGMode()
local grouped = GetNumPartyMembers() > 0 or GetNumRaidMembers() > 0
if mode == "listed" then
btn:SetText(grouped and (UNLIST_MY_GROUP or "Unlist My Group") or (UNLIST_ME or "Unlist Me"))
btn:SetText(grouped and (UNLIST_MY_GROUP or NE.L["Unlist My Group"]) or (UNLIST_ME or NE.L["Unlist Me"]))
else
btn:SetText(grouped and (LIST_MY_GROUP or "List My Group") or (LIST_ME or "List Me"))
btn:SetText(grouped and (LIST_MY_GROUP or NE.L["List My Group"]) or (LIST_ME or NE.L["List Me"]))
end
if lfrEmpowered() and mode ~= "proposal" and mode ~= "queued" and mode ~= "rolecheck"
and (not LFRRaidList or LFRRaidList[1]) then
@@ -283,9 +283,9 @@ local function updateListButton()
local status = pane.queueStatus
if mode == "queued" or mode == "rolecheck" or mode == "proposal" then
status:SetText("|cffff2020" .. (NO_LFR_WHILE_LFD or "You cannot list for raids while in the Dungeon Finder queue.") .. "|r")
status:SetText("|cffff2020" .. (NO_LFR_WHILE_LFD or NE.L["You cannot list for raids while in the Dungeon Finder queue."]) .. "|r")
elseif mode == "listed" then
status:SetText(LFR_QUEUE_PENDING_MESSAGE or "You are listed in the Raid Browser.")
status:SetText(LFR_QUEUE_PENDING_MESSAGE or NE.L["You are listed in the Raid Browser."])
else
status:SetText("")
end
@@ -321,7 +321,7 @@ local function browseDropDown_Initialize(self, level)
local info = UIDropDownMenu_CreateInfo()
if not level or level == 1 then
info.text = NONE or "None"
info.text = NONE or NE.L["None"]
info.value = "none"
info.func = browseDropDown_OnClick
info.checked = activeSearching == info.value
@@ -498,7 +498,7 @@ local function updateBrowseList()
-- Direct label write — see native LFRFrame.lua:391 (SetSelectedValue's refresh only works
-- while the shared DropDownList still holds our buttons).
if joined == "none" then
ddText:SetText(NONE or "None")
ddText:SetText(NONE or NE.L["None"])
else
local info = LFGGetDungeonInfoByID and LFGGetDungeonInfoByID(joined)
ddText:SetText(info and info[LFG_RETURN_VALUES.name] or "")
@@ -594,12 +594,12 @@ local function buildPane(host)
pane:Hide()
-- Header: sub-tabs (left) + role row (right — LFR has no leader box, native parity).
local queueTab = makeSubTab(pane, LFR_QUEUE_TAB or QUEUE or "Queue")
local queueTab = makeSubTab(pane, LFR_QUEUE_TAB or QUEUE or NE.L["Queue"])
queueTab:SetPoint("TOPLEFT", pane, "TOPLEFT", 2, -10)
queueTab:SetScript("OnClick", function() PlaySound("igCharacterInfoTab"); setSubMode("QUEUE") end)
pane.queueTab = queueTab
local browseTab = makeSubTab(pane, LFR_BROWSE_TAB or BROWSE or "Browse")
local browseTab = makeSubTab(pane, LFR_BROWSE_TAB or BROWSE or NE.L["Browse"])
browseTab:SetPoint("LEFT", queueTab, "RIGHT", 4, 0)
browseTab:SetScript("OnClick", function() PlaySound("igCharacterInfoTab"); setSubMode("BROWSE") end)
pane.browseTab = browseTab
@@ -661,14 +661,14 @@ local function buildPane(host)
local noRaids = qp:CreateFontString(nil, "ARTWORK", "GameFontDisable")
noRaids:SetPoint("CENTER", qp, "CENTER", 0, 30)
noRaids:SetText(NO_RAIDS_AVAILABLE or "There are no raids available for your level.")
noRaids:SetText(NO_RAIDS_AVAILABLE or NE.L["There are no raids available for your level."])
noRaids:Hide()
pane.noRaids = noRaids
-- Comment box (mirrors into the native LFRQueueFrameComment — see syncCommentFromServer).
local commentLabel = qp:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
commentLabel:SetPoint("BOTTOMLEFT", qp, "BOTTOMLEFT", 8, 46)
commentLabel:SetText(LFG_COMMENT or COMMENT or "Comment")
commentLabel:SetText(LFG_COMMENT or COMMENT or NE.L["Comment"])
local commentBox = CreateFrame("Frame", nil, qp)
commentBox:SetPoint("BOTTOMLEFT", qp, "BOTTOMLEFT", 6, 6)
@@ -710,7 +710,7 @@ local function buildPane(host)
local ddLabel = bp:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
ddLabel:SetPoint("TOPLEFT", bp, "TOPLEFT", 10, -12)
ddLabel:SetText(RAID or "Raid")
ddLabel:SetText(RAID or NE.L["Raid"])
local bdd = CreateFrame("Frame", BROWSE_DROPDOWN, bp, "UIDropDownMenuTemplate")
bdd:SetPoint("TOPLEFT", bp, "TOPLEFT", 24, -4)
@@ -722,7 +722,7 @@ local function buildPane(host)
local refresh = CreateFrame("Button", nil, bp, "UIPanelButtonTemplate")
refresh:SetSize(80, 22)
refresh:SetPoint("TOPRIGHT", bp, "TOPRIGHT", -8, -8)
refresh:SetText(REFRESH or "Refresh")
refresh:SetText(REFRESH or NE.L["Refresh"])
refresh:SetScript("OnClick", function()
if RefreshLFGList then RefreshLFGList() end
end)
@@ -792,7 +792,7 @@ local function buildPane(host)
local message = CreateFrame("Button", nil, pane, "UIPanelButtonTemplate")
message:SetSize(110, 22)
message:SetPoint("BOTTOMLEFT", pane, "BOTTOMLEFT", 4, 6)
message:SetText(SEND_MESSAGE or "Send Message")
message:SetText(SEND_MESSAGE or NE.L["Send Message"])
message:SetScript("OnClick", function()
PlaySound("UChatScrollButton")
if LFRBrowseFrame and LFRBrowseFrame.selectedName and ChatFrame_SendTell then
@@ -806,7 +806,7 @@ local function buildPane(host)
local invite = CreateFrame("Button", nil, pane, "UIPanelButtonTemplate")
invite:SetSize(90, 22)
invite:SetPoint("LEFT", message, "RIGHT", 4, 0)
invite:SetText(INVITE or "Invite")
invite:SetText(INVITE or NE.L["Invite"])
invite:SetScript("OnClick", function()
PlaySound("UChatScrollButton")
if LFRBrowseFrame and LFRBrowseFrame.selectedName and InviteUnit then
@@ -65,7 +65,7 @@ local function categoryUnlocked(def)
end
local function lockedMessage(def)
return format(FEATURE_BECOMES_AVAILABLE_AT_LEVEL or "This feature becomes available at level %d.",
return format(FEATURE_BECOMES_AVAILABLE_AT_LEVEL or NE.L["This feature becomes available at level %d."],
def.minLevel)
end
@@ -116,9 +116,9 @@ local RAIL = {
-- (LFRFrame.xml's $parentIcon, file="Interface\LFGFrame\UI-LFR-PORTRAIT") — ships with the client,
-- so referencing the path directly is both correct AND needs no custom BLP copy/registration.
local CATEGORIES = {
{ key = "DUNGEONS", name = DUNGEONS or "Dungeons", icon = "Interface\\Icons\\INV_Helmet_08", default = true,
{ key = "DUNGEONS", name = DUNGEONS or NE.L["Dungeons"], icon = "Interface\\Icons\\INV_Helmet_08", default = true,
minLevel = DUNGEONS_MIN_LEVEL },
{ key = "RAIDS", name = RAIDS or "Raids", icon = "Interface\\LFGFrame\\UI-LFR-PORTRAIT" },
{ key = "RAIDS", name = RAIDS or NE.L["Raids"], icon = "Interface\\LFGFrame\\UI-LFR-PORTRAIT" },
}
local function railFile(code)
@@ -260,7 +260,7 @@ local function buildChrome(f)
titleStr:SetPoint("TOP", f, "TOP", 0, -6)
titleStr:SetPoint("LEFT", f, "LEFT", 58, 0)
titleStr:SetPoint("RIGHT", f, "RIGHT", -58, 0)
titleStr:SetText(LFG_TITLE or "Looking for Group")
titleStr:SetText(LFG_TITLE or NE.L["Looking for Group"])
f.TitleContainer = tc
f.TitleText = titleStr
f.Title = titleStr
@@ -696,8 +696,8 @@ eventFrame:SetScript("OnEvent", function()
if NE.RegisterPanel then
NE.RegisterPanel({
id = MODULE,
title = LFG_TITLE or "Looking for Group",
desc = "Unified retail-style Group Finder over the Dungeon Finder + Raid Browser.",
title = LFG_TITLE or NE.L["Looking for Group"],
desc = NE.L["Unified retail-style Group Finder over the Dungeon Finder + Raid Browser."],
frame = L.frame,
openFn = function() L.Show() end,
closeFn = L.Hide,
@@ -102,7 +102,7 @@ local PROF_MAP = {
["Inscription"] = { kit = "Inscription", icon = 4620676, fill = "skillbar_fill_flipbook_inscription" },
["Jewelcrafting"] = { kit = "Jewelcrafting", icon = 4620677, fill = "skillbar_fill_flipbook_jewelcrafting" },
["Prospecting"] = { kit = "Jewelcrafting", icon = 4620677, fill = "skillbar_fill_flipbook_jewelcrafting" },
["First Aid"] = { kit = nil, icon = "Interface\\Icons\\Spell_Holy_SealOfSacrifice", fill = "skillbar_fill_flipbook_skinning" },
[NE.L["First Aid"]] = { kit = nil, icon = "Interface\\Icons\\Spell_Holy_SealOfSacrifice", fill = "skillbar_fill_flipbook_skinning" },
}
local function infoFromName(name)
@@ -588,7 +588,7 @@ local function buildAuctionatorSearchPayload(r)
end
if shoppingListName and shoppingListName ~= "" and string.find(shoppingListName, "Enchant ", 1, true) then
shoppingListName = "Scroll of " .. shoppingListName
shoppingListName = NE.L["Scroll of "] .. shoppingListName
end
local items = {}
@@ -872,7 +872,7 @@ function C.buildSchematicForm(f)
fav:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
local on = C._selected and isFav(C._selected.name)
local line = on and (BATTLE_PET_UNFAVORITE or "Remove Favorite") or (BATTLE_PET_FAVORITE or "Set Favorite")
local line = on and (BATTLE_PET_UNFAVORITE or NE.L["Remove Favorite"]) or (BATTLE_PET_FAVORITE or NE.L["Set Favorite"])
if GameTooltip_AddHighlightLine then GameTooltip_AddHighlightLine(GameTooltip, line)
else GameTooltip:AddLine(line, 1, 1, 1) end
GameTooltip:Show()
@@ -894,7 +894,7 @@ function C.buildSchematicForm(f)
empty:SetPoint("CENTER", sf, "CENTER", 0, 40)
empty:SetVertexColor(0.5, 0.5, 0.5)
local emptyText = (L and L["Select a recipe to craft"]) or "Select a recipe to craft"
local emptyText = (L and L["Select a recipe to craft"]) or NE.L["Select a recipe to craft"]
empty:SetText(emptyText)
sf.EmptyText = empty
@@ -1073,7 +1073,7 @@ function C.buildCreateControls(f)
-- "Create All" button.
local createAll = CreateFrame("Button", "NE_ProfessionsCraftingCreateAll", f, "UIPanelButtonTemplate")
createAll:SetSize(125, 22)
createAll:SetText((L and L["Create All"]) or _G.TRADESKILL_CREATE_ALL or "Create All")
createAll:SetText((L and L["Create All"]) or _G.TRADESKILL_CREATE_ALL or NE.L["Create All"])
createAll:SetPoint("RIGHT", minus, "LEFT", -30, 0)
createAll:Disable()
f.CreateAllButton = createAll
@@ -1082,7 +1082,7 @@ function C.buildCreateControls(f)
if isAuctionatorLoaded() then
local scanAH = CreateFrame("Button", "NE_ProfessionsCraftingScanAH", f, "UIPanelButtonTemplate")
scanAH:SetSize(100, 22)
scanAH:SetText("Scan AH")
scanAH:SetText(NE.L["Scan AH"])
scanAH:SetPoint("RIGHT", createAll, "LEFT", -8, 0)
scanAH:Disable()
scanAH:SetScript("OnClick", function(self)
@@ -1096,20 +1096,20 @@ function C.buildCreateControls(f)
local started, reason = startAuctionatorScan(r)
if UIErrorsFrame and UIErrorsFrame.AddMessage then
if started then
UIErrorsFrame:AddMessage("Auctionator scan started for recipe reagents.", 0.2, 1.0, 0.2, 1)
UIErrorsFrame:AddMessage(NE.L["Auctionator scan started for recipe reagents."], 0.2, 1.0, 0.2, 1)
elseif reason == "AH_CLOSED" then
UIErrorsFrame:AddMessage("Open the Auction House first to run Auctionator scans.", 1.0, 0.2, 0.2, 1)
UIErrorsFrame:AddMessage(NE.L["Open the Auction House first to run Auctionator scans."], 1.0, 0.2, 0.2, 1)
else
UIErrorsFrame:AddMessage("Auctionator API not available for reagent scans.", 1.0, 0.2, 0.2, 1)
UIErrorsFrame:AddMessage(NE.L["Auctionator API not available for reagent scans."], 1.0, 0.2, 0.2, 1)
end
end
end)
scanAH:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_TOP")
GameTooltip:ClearLines()
GameTooltip:AddLine("Scan AH", 1, 1, 1)
GameTooltip:AddLine("Searches Auctionator for the selected recipe and its reagents.", 0.85, 0.85, 0.85, true)
GameTooltip:AddLine("Requires the Auction House window to be open.", 0.85, 0.85, 0.85, true)
GameTooltip:AddLine(NE.L["Scan AH"], 1, 1, 1)
GameTooltip:AddLine(NE.L["Searches Auctionator for the selected recipe and its reagents."], 0.85, 0.85, 0.85, true)
GameTooltip:AddLine(NE.L["Requires the Auction House window to be open."], 0.85, 0.85, 0.85, true)
GameTooltip:Show()
end)
scanAH:SetScript("OnLeave", function()
@@ -1627,7 +1627,7 @@ function C.UpdateRequires(r)
end
if #names > 0 then
req:SetText("Requires: " .. table.concat(names, ", "))
req:SetText(NE.L["Requires: "] .. table.concat(names, ", "))
if anyMissing then req:SetTextColor(1, 0.3, 0.3) else req:SetTextColor(0.82, 0.82, 0.82) end
req:Show()
else
@@ -1853,7 +1853,7 @@ function C.UpdateCreateButtons(r)
local f = C.frame; if not f then return end
-- Получаем перевод
local createAllText = (L and L["Create All"]) or _G.TRADESKILL_CREATE_ALL or "Create All"
local createAllText = (L and L["Create All"]) or _G.TRADESKILL_CREATE_ALL or NE.L["Create All"]
if f.ScanAHButton then
local reagentNames = getRecipeReagentNames(r)
@@ -417,8 +417,8 @@ local function initRecipeRow(btn, entry, rl)
end
UIDropDownMenu_Initialize(dd, function()
local info = UIDropDownMenu_CreateInfo()
info.text = isFav(r.name) and (BATTLE_PET_UNFAVORITE or "Remove Favorite")
or (BATTLE_PET_FAVORITE or "Set Favorite")
info.text = isFav(r.name) and (BATTLE_PET_UNFAVORITE or NE.L["Remove Favorite"])
or (BATTLE_PET_FAVORITE or NE.L["Set Favorite"])
info.notCheckable = true
info.func = function()
toggleFav(r.name); C.RefreshRecipes()
@@ -721,9 +721,9 @@ function C.buildRecipeList(f)
UIDropDownMenu_AddButton(info)
end
local showLearnedText = (L and L["Show Learned"]) or "Show Learned"
local hasSkillUpText = (L and L["Has Skill Up"]) or "Has Skill Up"
local haveMaterialsText = (L and L["Have Materials"]) or "Have Materials"
local showLearnedText = (L and L["Show Learned"]) or NE.L["Show Learned"]
local hasSkillUpText = (L and L["Has Skill Up"]) or NE.L["Has Skill Up"]
local haveMaterialsText = (L and L["Have Materials"]) or NE.L["Have Materials"]
addCheck(showLearnedText, "showLearned")
addCheck(hasSkillUpText, "skillUp")
@@ -390,18 +390,18 @@ local function buildCogMenu(f, cog)
return cb
end
menu.cbTip = checkRow((L and L["Hide item tooltips in list"]) or "Hide item tooltips in list",
menu.cbTip = checkRow((L and L["Hide item tooltips in list"]) or NE.L["Hide item tooltips in list"],
function() return C.opts.hideListTooltips end,
function(v) C.opts.hideListTooltips = v; saveOpts() end, -12)
menu.cbDiff = checkRow((L and L["Colour names by skill difficulty"]) or "Colour names by skill difficulty",
menu.cbDiff = checkRow((L and L["Colour names by skill difficulty"]) or NE.L["Colour names by skill difficulty"],
function() return C.opts.colorByDifficulty end,
function(v)
C.opts.colorByDifficulty = v; saveOpts()
if C.RefreshRecipes then C.RefreshRecipes() end
end, -40)
menu.cbBar = checkRow((L and L["Plain skill bar (no animation)"]) or "Plain skill bar (no animation)",
menu.cbBar = checkRow((L and L["Plain skill bar (no animation)"]) or NE.L["Plain skill bar (no animation)"],
function() return C.opts.genericBar end,
function(v)
C.opts.genericBar = v; saveOpts()
@@ -764,7 +764,7 @@ eventFrame:SetScript("OnEvent", function(_, event, ...)
NE.RegisterPanel({
id = MODULE,
title = _G.TRADE_SKILLS or "Professions",
desc = "Retail-style crafting window for all professions.",
desc = NE.L["Retail-style crafting window for all professions."],
frame = C.frame,
openFn = C.Show,
closeFn = C.Hide,
@@ -160,9 +160,9 @@ local function openStatusMenu(anchor)
statusMenuFrame = CreateFrame("Frame", "NE_SocialStatusMenu", UIParent, "UIDropDownMenuTemplate")
end
local menu = {
{ text = "Available", notCheckable = true, func = function() setOwnStatus("AVAILABLE") end },
{ text = "Away", notCheckable = true, func = function() setOwnStatus("AFK") end },
{ text = "Busy", notCheckable = true, func = function() setOwnStatus("DND") end },
{ text = NE.L["Available"], notCheckable = true, func = function() setOwnStatus("AVAILABLE") end },
{ text = NE.L["Away"], notCheckable = true, func = function() setOwnStatus("AFK") end },
{ text = NE.L["Busy"], notCheckable = true, func = function() setOwnStatus("DND") end },
}
EasyMenu(menu, statusMenuFrame, anchor, 0, 0, "MENU")
end
@@ -198,11 +198,11 @@ function SO.RefreshOwnStatus()
local isAFK = UnitIsAFK and UnitIsAFK("player")
local isDND = UnitIsDND and UnitIsDND("player")
if isDND then
btn.icon:SetTexture(STATUS_DND); btn.label:SetText("Busy")
btn.icon:SetTexture(STATUS_DND); btn.label:SetText(NE.L["Busy"])
elseif isAFK then
btn.icon:SetTexture(STATUS_AFK); btn.label:SetText("Away")
btn.icon:SetTexture(STATUS_AFK); btn.label:SetText(NE.L["Away"])
else
btn.icon:SetTexture(STATUS_ONLINE); btn.label:SetText("Available")
btn.icon:SetTexture(STATUS_ONLINE); btn.label:SetText(NE.L["Available"])
end
end
@@ -237,7 +237,7 @@ end
-- onto the same raw global, which looks index-suggestive but is just a shim mislabel.)
-- ---------------------------------------------------------------------------
StaticPopupDialogs["NE_SET_FRIEND_NOTE"] = {
text = "Enter a note for %s:",
text = NE.L["Enter a note for %s:"],
button1 = ACCEPT or "Accept",
button2 = CANCEL or "Cancel",
hasEditBox = true,
@@ -272,13 +272,13 @@ local function openFriendMenu(idx)
if name and InviteUnit then InviteUnit(name) end
end }
end
menu[#menu + 1] = { text = "Set Note", notCheckable = true, func = function()
menu[#menu + 1] = { text = NE.L["Set Note"], notCheckable = true, func = function()
if name then StaticPopup_Show("NE_SET_FRIEND_NOTE", name, nil, { name = name, note = note }) end
end }
menu[#menu + 1] = { text = IGNORE or "Ignore", notCheckable = true, func = function()
if name and AddIgnore then AddIgnore(name) end
end }
menu[#menu + 1] = { text = REMOVE_FRIEND or "Remove Friend", notCheckable = true, func = function()
menu[#menu + 1] = { text = REMOVE_FRIEND or NE.L["Remove Friend"], notCheckable = true, func = function()
if RemoveFriend then RemoveFriend(idx) end
SO.RefreshFriends()
end }
@@ -410,7 +410,7 @@ local function setupFriendsView(view)
end
local add = CreateFrame("Button", nil, view, "UIPanelButtonTemplate")
add:SetSize(116, 22); add:SetText(ADD_FRIEND or "Add Friend")
add:SetSize(116, 22); add:SetText(ADD_FRIEND or NE.L["Add Friend"])
-- Anchored to the PANEL (view's parent), not `view` itself (owner report 2026-07-17: button was
-- sitting inside the dark inset, overlapping the last row, instead of the outer grey frame below
-- it). Panel's own bottom edge sits 36px above the window's true bottom (Window.lua's buildPanels
@@ -551,7 +551,7 @@ local function setupIgnoreView(view)
end
local add = CreateFrame("Button", nil, view, "UIPanelButtonTemplate")
add:SetSize(116, 22); add:SetText(IGNORE_PLAYER or "Ignore Player")
add:SetSize(116, 22); add:SetText(IGNORE_PLAYER or NE.L["Ignore Player"])
-- Same outer-chrome anchor fix as the Friends tab's Add Friend button above (owner report
-- 2026-07-17): anchored to the panel (view's parent), not `view` itself, so it sits in the grey
-- band below the dark inset instead of overlapping the last row.
@@ -83,11 +83,11 @@ local function openRaidSlotMenu(slot)
local name = slot._name
local menu = { { text = name, isTitle = true, notCheckable = true } }
menu[#menu + 1] = { text = "Promote to Raid Leader", notCheckable = true, func = function()
menu[#menu + 1] = { text = NE.L["Promote to Raid Leader"], notCheckable = true, func = function()
if PromoteToLeader then PromoteToLeader(raidTarget(slot, name), true) end
SO.RefreshRaid()
end }
menu[#menu + 1] = { text = "Promote to Assistant", notCheckable = true, func = function()
menu[#menu + 1] = { text = NE.L["Promote to Assistant"], notCheckable = true, func = function()
if PromoteToAssistant then PromoteToAssistant(raidTarget(slot, name), true) end
SO.RefreshRaid()
end }
@@ -189,7 +189,7 @@ local function buildRaidInfoFrame(f, panel)
if NE.chrome and NE.chrome.Apply then
NE.chrome.Apply(frame, {
layout = "ButtonFrameTemplateNoPortrait",
title = RAID_INFO or "Raid Info",
title = RAID_INFO or NE.L["Raid Info"],
noPortrait = true,
})
end
@@ -236,12 +236,12 @@ local function buildRaidInfoFrame(f, panel)
-- RAID_INFO is the exception — it's a real 3.3.5a string ("Raid Info").
local hName = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
hName:SetPoint("TOPLEFT", frame, "TOPLEFT", 18, -30)
hName:SetText("Instance")
hName:SetText(NE.L["Instance"])
hName:SetTextColor(1, 0.82, 0)
local hReset = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
hReset:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -34, -30)
hReset:SetText("Resets In")
hReset:SetText(NE.L["Resets In"])
hReset:SetTextColor(1, 0.82, 0)
-- Dark recessed list well, same treatment as the tab panels themselves.
@@ -300,9 +300,9 @@ local function buildRaidInfoFrame(f, panel)
if not (self._entry and GameTooltip) then return end
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(instanceLabel(self._entry), 1, 0.82, 0)
if self._entry.id then GameTooltip:AddLine("ID: " .. tostring(self._entry.id), 1, 1, 1) end
if self._entry.id then GameTooltip:AddLine(NE.L["ID: "] .. tostring(self._entry.id), 1, 1, 1) end
GameTooltip:AddLine(formatReset(self._entry.reset), 1, 1, 1)
if self._entry.extended then GameTooltip:AddLine("Extended", 0.25, 1, 0.25) end
if self._entry.extended then GameTooltip:AddLine(NE.L["Extended"], 0.25, 1, 0.25) end
GameTooltip:Show()
end)
row:SetScript("OnLeave", function() if GameTooltip then GameTooltip:Hide() end end)
@@ -323,7 +323,7 @@ local function buildRaidInfoFrame(f, panel)
local empty = content:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall")
empty:SetPoint("TOP", content, "TOP", 0, -12)
empty:SetText("You are not saved to any instances.")
empty:SetText(NE.L["You are not saved to any instances."])
empty:Hide()
frame.Empty = empty
@@ -331,7 +331,7 @@ local function buildRaidInfoFrame(f, panel)
local ext = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate")
ext:SetSize(140, 22)
ext:SetPoint("BOTTOM", frame, "BOTTOM", 0, 12)
ext:SetText("Extend")
ext:SetText(NE.L["Extend"])
ext:Disable()
ext:SetScript("OnClick", function()
local e = frame._selectedEntry
@@ -393,10 +393,10 @@ function SO.RefreshSavedInstances()
if frame.Extend then
if selectedEntry then
frame.Extend:Enable()
frame.Extend:SetText(selectedEntry.extended and "Cancel Extend" or "Extend")
frame.Extend:SetText(selectedEntry.extended and NE.L["Cancel Extend"] or "Extend")
else
frame.Extend:Disable()
frame.Extend:SetText("Extend")
frame.Extend:SetText(NE.L["Extend"])
end
end
end
@@ -767,7 +767,7 @@ function SO.SetupRaid(f)
-- (110 + 6 + 100 = 216 wide; Ready Check's center lands at -216/2 + 110/2 = -53.)
local ready = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
ready:SetSize(110, 20)
ready:SetText(READY_CHECK or "Ready Check")
ready:SetText(READY_CHECK or NE.L["Ready Check"])
ready:SetPoint("TOP", f, "TOP", -53, -33)
ready:SetScript("OnClick", function()
if DoReadyCheck then DoReadyCheck() end
@@ -778,7 +778,7 @@ function SO.SetupRaid(f)
-- your own saved instances needs no group and no rank.
local info = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
info:SetSize(100, 20)
info:SetText(RAID_INFO or "Raid Info")
info:SetText(RAID_INFO or NE.L["Raid Info"])
info:SetPoint("LEFT", ready, "RIGHT", 6, 0)
info:SetScript("OnClick", function() SO.ToggleRaidInfo() end)
panel.RaidInfoButton = info
@@ -791,7 +791,7 @@ function SO.SetupRaid(f)
-- alongside Convert to Raid (owner steer 2026-07-17: the group grid replaced the old empty-state
-- blurb it used to live inside).
local browser = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
browser:SetSize(140, 22); browser:SetText(RAID_BROWSER_BUTTON or "Raid Browser")
browser:SetSize(140, 22); browser:SetText(RAID_BROWSER_BUTTON or NE.L["Raid Browser"])
browser:SetPoint("BOTTOMRIGHT", panel, "BOTTOMRIGHT", 0, -29)
browser:SetScript("OnClick", function()
if ToggleLFRParentFrame then ToggleLFRParentFrame() end
@@ -804,7 +804,7 @@ function SO.SetupRaid(f)
-- window's true bottom — +4 sat the button inside the dark inset instead of the outer grey
-- chrome band below it).
local convert = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
convert:SetSize(140, 22); convert:SetText(CONVERT_TO_RAID or "Convert to Raid")
convert:SetSize(140, 22); convert:SetText(CONVERT_TO_RAID or NE.L["Convert to Raid"])
convert:SetPoint("BOTTOMLEFT", panel, "BOTTOMLEFT", 0, -29)
convert:SetScript("OnClick", function()
if ConvertToRaid then ConvertToRaid() end
@@ -258,7 +258,7 @@ function SO.SetupWho(f)
-- bottom edge, and panel's bottom already sits 36px above the window's true bottom — so +4 sat
-- the buttons inside the dark inset instead of the outer grey chrome band below it).
local addf = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
addf:SetSize(110, 22); addf:SetText(ADD_FRIEND or "Add Friend")
addf:SetSize(110, 22); addf:SetText(ADD_FRIEND or NE.L["Add Friend"])
addf:SetPoint("BOTTOMLEFT", panel, "BOTTOMLEFT", 0, -29)
addf:SetScript("OnClick", function() local n = selectedName(); if n and AddFriend then AddFriend(n) end end)
@@ -74,7 +74,7 @@ local function buildChrome(f)
titleStr:SetPoint("TOP", f, "TOP", 0, -6)
titleStr:SetPoint("LEFT", f, "LEFT", 58, 0)
titleStr:SetPoint("RIGHT", f, "RIGHT", -58, 0)
titleStr:SetText(FRIENDS_LIST or "Friends List")
titleStr:SetText(FRIENDS_LIST or NE.L["Friends List"])
f.TitleContainer = tc
f.TitleText = titleStr
f.Title = titleStr
@@ -197,9 +197,9 @@ end
-- The stock window retitles itself per tab ("Friends List" / "Who List" / "Chat Channels" /
-- "Raid") rather than carrying one static title — mirror that.
local TITLE_BY_MODE = {
FRIENDS = FRIENDS_LIST or "Friends List",
FRIENDS = FRIENDS_LIST or NE.L["Friends List"],
WHO = WHO_LIST or ((WHO or "Who") .. " " .. (LIST_LABEL or "List")),
CHAT = CHAT_CHANNELS or "Chat Channels",
CHAT = CHAT_CHANNELS or NE.L["Chat Channels"],
RAID = RAID or "Raid",
}
@@ -385,7 +385,7 @@ eventFrame:SetScript("OnEvent", function(_, event)
NE.RegisterPanel({
id = MODULE,
title = SOCIALS or FRIENDS or "Social",
desc = "Modern friends window (Friends / Ignore / Who) with a Guild tab.",
desc = NE.L["Modern friends window (Friends / Ignore / Who) with a Guild tab."],
frame = SO.frame,
openFn = SO.Show,
closeFn = SO.Hide,
@@ -1182,7 +1182,7 @@ function SB.RenderCards()
for i = hi + 1, #SB.headers do SB.headers[i]:Hide() end
if SB.paging then
SB.paging.label:SetText(("Page %d/%d"):format(SB.page, SB.totalPages or 1))
SB.paging.label:SetText((NE.L["Page %d/%d"]):format(SB.page, SB.totalPages or 1))
if SB.paging.prev.SetEnabled then SB.paging.prev:SetEnabled(SB.page > 1)
elseif SB.page > 1 then SB.paging.prev:Enable() else SB.paging.prev:Disable() end
if SB.paging.next.SetEnabled then SB.paging.next:SetEnabled(SB.page < (SB.totalPages or 1))
@@ -1303,22 +1303,22 @@ local function buildCogMenu(cog)
return cb
end
menu.cbPassives = checkRow(SPELLBOOK_FILTER_PASSIVES or "Hide Passives",
menu.cbPassives = checkRow(SPELLBOOK_FILTER_PASSIVES or NE.L["Hide Passives"],
function() return SB.hidePassives end,
function(v) SB.hidePassives = v; saveOpts(); SB.page = 1; buildElements(); SB.RenderCards() end, -30)
menu.cbRanks = checkRow("Show All Ranks",
menu.cbRanks = checkRow(NE.L["Show All Ranks"],
function() return SB.showRanks end,
function(v) SB.showRanks = v; saveOpts(); SB.page = 1; buildElements(); SB.RenderCards() end, -54)
-- Toggling the Mounts tab changes the category list, so re-run the full Refresh (rebuilds the tab
-- row + cards). Reset the auto-pick so we don't get stuck on a now-removed tab.
menu.cbMounts = checkRow("Show Mounts tab",
menu.cbMounts = checkRow(NE.L["Show Mounts tab"],
function() return SB.showMounts end,
function(v)
SB.showMounts = v; saveOpts(); SB.userPickedCategory = false; SB.page = 1
if SB.Refresh then SB.Refresh() end
end, -78)
-- Companions tab (vanity pets) — same as Mounts: toggling changes the category list, so full Refresh.
menu.cbCompanions = checkRow("Show Companions tab",
menu.cbCompanions = checkRow(NE.L["Show Companions tab"],
function() return SB.showCompanions end,
function(v)
SB.showCompanions = v; saveOpts(); SB.userPickedCategory = false; SB.page = 1
@@ -1326,7 +1326,7 @@ local function buildCogMenu(cog)
end, -102)
-- Glow active spells (the WeakAuras ants on spells whose buff is currently up). Toggling re-evaluates
-- every visible card's overlay immediately (turning glows on/off without a full re-render).
menu.cbGlow = checkRow("Glow active spells",
menu.cbGlow = checkRow(NE.L["Glow active spells"],
function() return SB.showActiveGlow end,
function(v) SB.showActiveGlow = v; saveOpts(); if SB.UpdateActiveOverlays then SB.UpdateActiveOverlays() end end, -126)
@@ -213,7 +213,7 @@ local function buildMinimize(f)
b:SetScript("OnEnter", function(self)
if not GameTooltip then return end
GameTooltip:SetOwner(self, "ANCHOR_LEFT")
GameTooltip:SetText(SB.minimized and "Show second page" or "Show single page")
GameTooltip:SetText(SB.minimized and NE.L["Show second page"] or NE.L["Show single page"])
GameTooltip:Show()
end)
b:SetScript("OnLeave", function() if GameTooltip then GameTooltip:Hide() end end)
@@ -571,9 +571,9 @@ SB.Boot = boot
if NE.modules and NE.modules.Register then
NE.modules.Register(MODULE, {
default = true,
label = "Spellbook",
label = NE.L["Spellbook"],
category = "Windows",
desc = "The modern Dragonflight spellbook window. Disable to keep the stock Blizzard spellbook.",
desc = NE.L["The modern Dragonflight spellbook window. Disable to keep the stock Blizzard spellbook."],
events = {
"PLAYER_LOGIN", "SPELLS_CHANGED", "LEARNED_SPELL_IN_TAB", "ACTIONBAR_SLOT_CHANGED",
"PET_BAR_UPDATE", "UNIT_PET", "PLAYER_LEVEL_UP", "PLAYER_REGEN_ENABLED", "UPDATE_BINDINGS",
@@ -581,5 +581,5 @@ if NE.modules and NE.modules.Register then
onBoot = function(event) boot(event) end,
})
else
log("NE.modules.Register absent; spellbook window not booted")
log(NE.L["NE.modules.Register absent; spellbook window not booted"])
end
@@ -352,7 +352,7 @@ end
-- BOTTOM BAR SETUP
-- ----------------------------------------------------------------------------
StaticPopupDialogs["NE_TALENTS_LEARN"] = {
text = CONFIRM_LEARN_PREVIEW_TALENTS or "Learn the selected talents? Spent points cannot be refunded without a respec.",
text = CONFIRM_LEARN_PREVIEW_TALENTS or NE.L["Learn the selected talents? Spent points cannot be refunded without a respec."],
button1 = YES, button2 = NO,
OnAccept = function()
if LearnPreviewTalents then pcall(LearnPreviewTalents, T.PetViewActive and T.PetViewActive() or false) end
@@ -393,7 +393,7 @@ local function buildBottomBar(f)
local activate = CreateFrame("Button", "NE_TalentActivateButton", f, "UIPanelButtonTemplate")
activate:SetSize(160, 26)
activate:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -((T.FRAME.CHROME_R or 0) + 24), (T.FRAME.CHROME_B or 0) + 27)
activate:SetText("Activate")
activate:SetText(NE.L["Activate"])
activate:SetScript("OnClick", function()
if InCombatLockdown and InCombatLockdown() then return end
@@ -549,7 +549,7 @@ function T.Populate()
local name, icon, spent, _bg, prevSpent = GetTalentTabInfo(tabIdx, false, isPet, group)
if isPet and _bg then petBgName = _bg end
local tabPointsSpent = (spent or 0) + (preview and (prevSpent or 0) or 0)
tf.headerName:SetText(string.upper(name or ("Tree " .. tabIdx)))
tf.headerName:SetText(string.upper(name or (NE.L["Tree "] .. tabIdx)))
tf.headerPts:SetText(tostring(tabPointsSpent))
tf.headerPts:SetTextColor((tabPointsSpent > 0) and 0.1 or 0.5, (tabPointsSpent > 0) and 1.0 or 0.5, (tabPointsSpent > 0) and 0.1 or 0.5)
@@ -663,7 +663,8 @@ function T.Populate()
end
if f.pointsText then
f.pointsText:SetText(("|cffffffff%d|r points available"):format(math.max(0, available)))
local points = ("|cffffffff%d|r"):format(math.max(0, available))
f.pointsText:SetText((NE.L["%s points available"]):format(points))
end
local hasStaged = previewSpentAll > 0
if f.apply and GlowEmitterFactory and GlowEmitterMixin then
@@ -702,20 +703,20 @@ function T.Populate()
if isLocked then
if playerMoney < unlockCost then
f.activate:SetText("Locked")
f.activate:SetText(NE.L["Locked"])
f.activate:Disable()
if f.activate.SetAlpha then f.activate:SetAlpha(0.25) end
local btnText = f.activate:GetFontString()
if btnText then btnText:SetTextColor(0.4, 0.4, 0.4) end
else
f.activate:SetText("Unlock Spec")
f.activate:SetText(NE.L["Unlock Spec"])
f.activate:Enable()
if f.activate.SetAlpha then f.activate:SetAlpha(1.0) end
local btnText = f.activate:GetFontString()
if btnText then btnText:SetTextColor(1, 0.82, 0) end
end
else
f.activate:SetText("Activate")
f.activate:SetText(NE.L["Activate"])
f.activate:Enable()
if f.activate.SetAlpha then f.activate:SetAlpha(1.0) end
local btnText = f.activate:GetFontString()
@@ -724,7 +725,7 @@ function T.Populate()
-- ---------------------------------------------------------------------------------
else
-- Non-Triumvirate realms: plain Activate, no gold-unlock system to check against.
f.activate:SetText("Activate")
f.activate:SetText(NE.L["Activate"])
f.activate:Enable()
if f.activate.SetAlpha then f.activate:SetAlpha(1.0) end
local btnText = f.activate:GetFontString()
@@ -779,4 +780,4 @@ boot:SetScript("OnEvent", function()
if event == "ACTIVE_TALENT_GROUP_CHANGED" then playSound("spec") end
T.Refresh()
end)
end)
end)
@@ -11,7 +11,7 @@ local SOCKET_COUNT = 6
local GLYPH_DOT_SIZE = 4
local GLYPH_DOT_GAP = 9
local GLYPH_FLOW_SPEED = 16
local GLYPH_NAME_PREFIX = "Glyph of "
local GLYPH_NAME_PREFIX = NE.L["Glyph of "]
-- Socket ring: the gold circle from the talents sheet (talents-node-circle-yellow), isolated from a
-- 2x upscale for a crisper edge and shipped as a 128 TGA. White tint on the active spec (true gold),
-- dimmed when locked/inactive/off-spec.
@@ -167,7 +167,7 @@ end
if not StaticPopupDialogs["NE_GLYPH_REMOVE_CONFIRM"] then
StaticPopupDialogs["NE_GLYPH_REMOVE_CONFIRM"] = {
text = "Remove this glyph?",
text = NE.L["Remove this glyph?"],
button1 = ACCEPT,
button2 = CANCEL,
OnAccept = function(self, data)
@@ -503,7 +503,7 @@ local function tooltipForSocket(button)
shown = ok and true or false
end
if not shown then
GameTooltip:SetText(button._fallbackName or "Glyph Socket", 1, 1, 1)
GameTooltip:SetText(button._fallbackName or NE.L["Glyph Socket"], 1, 1, 1)
if button._fallbackState then
GameTooltip:AddLine(button._fallbackState, 0.8, 0.8, 0.8, true)
end
@@ -676,7 +676,7 @@ local function ensureGlyphOptionsMenu(anchor)
if level ~= 1 then return end
local info = UIDropDownMenu_CreateInfo()
info.text = "Show glyph names"
info.text = NE.L["Show glyph names"]
info.checked = glyphLabelNamesEnabled()
info.keepShownOnClick = true
info.isNotRadio = true
@@ -687,7 +687,7 @@ local function ensureGlyphOptionsMenu(anchor)
UIDropDownMenu_AddButton(info, level)
local infoEffects = UIDropDownMenu_CreateInfo()
infoEffects.text = "Show glyph effects"
infoEffects.text = NE.L["Show glyph effects"]
infoEffects.checked = glyphShowEffectsEnabled()
infoEffects.keepShownOnClick = true
infoEffects.isNotRadio = true
@@ -734,8 +734,8 @@ local function buildGlyphCog(parent)
end)
cog:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_LEFT")
GameTooltip:SetText("Glyph options", 1, 1, 1)
GameTooltip:AddLine("Toggle slot name labels and the active-effects list.", 0.85, 0.85, 0.85, true)
GameTooltip:SetText(NE.L["Glyph options"], 1, 1, 1)
GameTooltip:AddLine(NE.L["Toggle slot name labels and the active-effects list."], 0.85, 0.85, 0.85, true)
GameTooltip:Show()
end)
cog:SetScript("OnLeave", function()
@@ -763,7 +763,7 @@ local function layoutRoot()
if root.title.SetTextScale then root.title:SetTextScale(1.1) end
root.title:SetJustifyH("CENTER")
root.title:SetPoint("TOP", root, "TOP", 0, -28)
root.title:SetText("GLYPHS")
root.title:SetText(NE.L["GLYPHS"])
root.title:SetTextColor(1, 1, 1)
buildGlyphCog(root)
end
@@ -805,7 +805,7 @@ local function layoutRoot()
lf.title = lf.textLayer:CreateFontString(nil, "OVERLAY", "GameFontNormalHuge")
if _G.SystemFont_Shadow_Large2 then lf.title:SetFontObject(_G.SystemFont_Shadow_Large2) end
if lf.title.SetTextScale then lf.title:SetTextScale(0.72) end -- Matches left side spec header scaling
lf.title:SetText("ACTIVE EFFECTS")
lf.title:SetText(NE.L["ACTIVE EFFECTS"])
lf.title:SetPoint("TOPLEFT", lf, "TOPLEFT", 40, -66)
lf.title:SetTextColor(1, 1, 1)
@@ -948,7 +948,7 @@ local function updateSocket(button, info, activePane, wantMajor)
if button.IconTint then button.IconTint:Hide() end
button.Glow:Hide()
button.Plus:SetText("")
button._fallbackName = "Glyph Socket"
button._fallbackName = NE.L["Glyph Socket"]
button._fallbackState = "Unavailable"
if button.Name then
button.Name:SetText("")
@@ -986,8 +986,8 @@ local function updateSocket(button, info, activePane, wantMajor)
if button.IconTint then button.IconTint:Hide() end
button.Glow:Hide()
button.Plus:SetText("")
button._fallbackName = "Locked socket"
button._fallbackState = "Requires higher level"
button._fallbackName = NE.L["Locked socket"]
button._fallbackState = NE.L["Requires higher level"]
if button.Name then
button.Name:SetText("")
button.Name:Hide()
@@ -1058,7 +1058,7 @@ local function updateSocket(button, info, activePane, wantMajor)
button.Plus:SetText("")
local displayName = hasGlyph and glyphDisplayName(info, spellName) or nil
button._fallbackName = displayName or spellName or "Glyph"
button._fallbackState = hasGlyph and "Equipped" or "Empty socket"
button._fallbackState = hasGlyph and "Equipped" or NE.L["Empty socket"]
if button.Name then
if glyphLabelNamesEnabled() and hasGlyph and displayName then
button.Name:SetText(displayName)
@@ -1382,10 +1382,10 @@ function T.GlyphsRefresh()
if hasAny then
root.listFrame.title:SetText("")
root.listFrame.title:Hide()
addSection("MAJOR GLYPHS", majors)
addSection("MINOR GLYPHS", minors)
addSection(NE.L["MAJOR GLYPHS"], majors)
addSection(NE.L["MINOR GLYPHS"], minors)
else
root.listFrame.title:SetText("NO ACTIVE EFFECTS")
root.listFrame.title:SetText(NE.L["NO ACTIVE EFFECTS"])
root.listFrame.title:Show()
end
@@ -157,7 +157,7 @@ end
function T.LO_ServerLabel()
local realm = GetRealmName and GetRealmName()
if realm and realm ~= "" then return realm end
return "this server"
return NE.L["this server"]
end
-- ----------------------------------------------------------------------------
@@ -303,16 +303,16 @@ end
-- Decode any supported string into a build { class, ranks, fp, server }, or nil, err.
function T.LO_Decode(text)
local struct = buildStruct()
if not struct then return nil, "talent data unavailable" end
if not struct then return nil, NE.L["talent data unavailable"] end
local code, meta = splitMeta(text)
if not code or code == "" then return nil, "empty talent string" end
if not code or code == "" then return nil, NE.L["empty talent string"] end
local ranks, class
-- Try packed (both alphabets), then plain digits.
ranks, class = decodePacked(code, struct, MAP_URL)
if not ranks then ranks, class = decodePacked(code, struct, MAP_INTERNAL) end
if not ranks then ranks, class = decodeDigits(code, struct) end
if not ranks then return nil, "unrecognised talent string" end
if not ranks then return nil, NE.L["unrecognised talent string"] end
local fp, server = parseMeta(meta)
return { class = class or struct.class, ranks = ranks, fp = fp, server = server }
@@ -343,7 +343,7 @@ local function tabSpent(ranks, tab)
end
function T.LO_Validate(build)
local struct = buildStruct()
if not struct then return nil, { "talent data unavailable" } end
if not struct then return nil, { NE.L["talent data unavailable"] } end
local clean, issues, total = {}, {}, 0
for tab = 1, #struct.tabs do
local td, src, out = struct.tabs[tab], (build.ranks[tab] or {}), {}
@@ -360,7 +360,7 @@ function T.LO_Validate(build)
clean[tab] = out
end
if total > MAX_POINTS then
issues[#issues + 1] = ("total %d exceeds %d"):format(total, MAX_POINTS)
issues[#issues + 1] = (NE.L["total %d exceeds %d"]):format(total, MAX_POINTS)
end
return clean, issues, total
end
@@ -386,11 +386,11 @@ function T.LO_ImportString(text)
-- otherwise: layout mismatch (or unknown) onto custom trees
local reason
if build.fp then
reason = ("This build was made for %s, which uses a different talent layout. "):format(build.server or "another server")
.. "Points may land on the wrong talents."
reason = (NE.L["This build was made for %s, which uses a different talent layout. "]):format(build.server or NE.L["another server"])
.. NE.L["Points may land on the wrong talents."]
else
reason = "This looks like a standard WotLK build, but your server uses custom talents. "
.. "Points may not line up."
reason = NE.L["This looks like a standard WotLK build, but your server uses custom talents. "]
.. NE.L["Points may not line up."]
end
return build, { warn = true, reason = reason, server = build.server }
end
@@ -471,9 +471,9 @@ end
-- ----------------------------------------------------------------------------
function T.LO_StageBuild(build)
if not (AddPreviewTalentPoints and GetTalentInfo and GetNumTalentTabs and GetNumTalents) then
return false, { { name = "preview API unavailable" } }, 0
return false, { { name = NE.L["preview API unavailable"] } }, 0
end
if InCombatLockdown and InCombatLockdown() then return false, { { name = "in combat" } }, 0 end
if InCombatLockdown and InCombatLockdown() then return false, { { name = NE.L["in combat"] } }, 0 end
local clean = T.LO_Validate(build) or build.ranks
local group = (GetActiveTalentGroup and GetActiveTalentGroup()) or 1
@@ -22,13 +22,13 @@ local function refresh() if T.LO_RefreshPanel then T.LO_RefreshPanel() end end
local function msg(s) DEFAULT_CHAT_FRAME:AddMessage("|cff66ccffLoadouts|r: " .. tostring(s)) end
StaticPopupDialogs["NE_TALENT_LO_SAVE"] = {
text = "Name this loadout (saves your current spec):",
text = NE.L["Name this loadout (saves your current spec):"],
button1 = SAVE or "Save", button2 = CANCEL or "Cancel",
hasEditBox = 1, maxLetters = 40, timeout = 0, whileDead = 1, hideOnEscape = 1,
OnAccept = function(self)
local name = (self.editBox or self.EditBox):GetText()
if name and name ~= "" then
if T.LO_SaveCurrent(name) then msg("saved '" .. name .. "'.") else msg("could not save.") end
if T.LO_SaveCurrent(name) then msg(NE.L["saved '"] .. name .. "'.") else msg(NE.L["could not save."]) end
refresh()
end
end,
@@ -36,14 +36,14 @@ StaticPopupDialogs["NE_TALENT_LO_SAVE"] = {
}
StaticPopupDialogs["NE_TALENT_LO_RENAME"] = {
text = "Rename loadout:",
text = NE.L["Rename loadout:"],
button1 = OKAY or "OK", button2 = CANCEL or "Cancel",
hasEditBox = 1, maxLetters = 40, timeout = 0, whileDead = 1, hideOnEscape = 1,
OnShow = function(self) (self.editBox or self.EditBox):SetText(T._loSelected or "") end,
OnAccept = function(self)
local newName = (self.editBox or self.EditBox):GetText()
if T._loSelected and newName and newName ~= "" then
if T.LO_Rename(T._loSelected, newName) then T._loSelected = newName else msg("rename failed (name taken?).") end
if T.LO_Rename(T._loSelected, newName) then T._loSelected = newName else msg(NE.L["rename failed (name taken?)."]) end
refresh()
end
end,
@@ -51,14 +51,14 @@ StaticPopupDialogs["NE_TALENT_LO_RENAME"] = {
}
StaticPopupDialogs["NE_TALENT_LO_DELETE"] = {
text = "Delete loadout '%s'?",
text = NE.L["Delete loadout '%s'?"],
button1 = DELETE or "Delete", button2 = CANCEL or "Cancel",
timeout = 0, whileDead = 1, hideOnEscape = 1, showAlert = 1,
OnAccept = function() if T._loSelected then T.LO_Delete(T._loSelected); T._loSelected = nil; refresh() end end,
}
StaticPopupDialogs["NE_TALENT_LO_EXPORT"] = {
text = "Copy this build string (Ctrl+C). Talented & the WoWhead/wotlkdb calculators import it too:",
text = NE.L["Copy this build string (Ctrl+C). Talented & the WoWhead/wotlkdb calculators import it too:"],
button1 = CLOSE or "Close",
hasEditBox = 1, editBoxWidth = 260, timeout = 0, whileDead = 1, hideOnEscape = 1,
OnShow = function(self)
@@ -70,7 +70,7 @@ StaticPopupDialogs["NE_TALENT_LO_EXPORT"] = {
}
StaticPopupDialogs["NE_TALENT_LO_IMPORT"] = {
text = "Paste a talent string or calculator URL (Talented / WoWhead / wotlkdb):",
text = NE.L["Paste a talent string or calculator URL (Talented / WoWhead / wotlkdb):"],
button1 = OKAY or "OK", button2 = CANCEL or "Cancel",
hasEditBox = 1, editBoxWidth = 260, maxLetters = 400, timeout = 0, whileDead = 1, hideOnEscape = 1,
OnAccept = function(self) if T.LO_ImportFromText then T.LO_ImportFromText((self.editBox or self.EditBox):GetText()) end end,
@@ -85,14 +85,14 @@ StaticPopupDialogs["NE_TALENT_LO_IMPORT_WARN"] = {
}
StaticPopupDialogs["NE_TALENT_LO_IMPORT_NAME"] = {
text = "Name this imported loadout:",
text = NE.L["Name this imported loadout:"],
button1 = SAVE or "Save", button2 = CANCEL or "Cancel",
hasEditBox = 1, maxLetters = 40, timeout = 0, whileDead = 1, hideOnEscape = 1,
OnAccept = function(self)
local name = (self.editBox or self.EditBox):GetText()
if name and name ~= "" and T._loPendingImport then
T.LO_Save(name, T._loPendingImport); T._loPendingImport = nil; refresh()
msg("imported '" .. name .. "'.")
msg(NE.L["imported '"] .. name .. "'.")
end
end,
EditBoxOnEnterPressed = function(self) self:GetParent().button1:Click() end,
@@ -109,7 +109,7 @@ StaticPopupDialogs["NE_TALENT_LO_CONFLICT"] = {
-- ----------------------------------------------------------------------------
function T.LO_ImportFromText(text)
local build, verdict = T.LO_ImportString(text)
if not build then msg(verdict and verdict.error or "could not read that string."); return end
if not build then msg(verdict and verdict.error or NE.L["could not read that string."]); return end
T._loPendingImport = build
if verdict.warn then
StaticPopupDialogs["NE_TALENT_LO_IMPORT_WARN"].text = verdict.reason .. "\n\nImport anyway?"
@@ -123,7 +123,7 @@ local function exportText(name)
local code, url, tagged
if name then code, url, tagged = T.LO_Encode(T.LO_Get(name)) else code, url, tagged = T.LO_ExportCurrent() end
T._loExportText = tagged or code
if T._loExportText then StaticPopup_Show("NE_TALENT_LO_EXPORT") else msg("nothing to export.") end
if T._loExportText then StaticPopup_Show("NE_TALENT_LO_EXPORT") else msg(NE.L["nothing to export."]) end
end
local function loadBuild(name)
@@ -141,7 +141,7 @@ local function loadBuild(name)
.. "\n\nReset at a class trainer, then load again. (The rest has been staged — click Apply to learn it.)"
StaticPopup_Show("NE_TALENT_LO_CONFLICT")
else
msg("staged '" .. name .. "' — review the highlighted talents and click Apply to learn.")
msg(NE.L["staged '"] .. name .. "' — review the highlighted talents and click Apply to learn.")
end
end
@@ -174,18 +174,18 @@ local function buildPanel()
local title = panel:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
title:SetPoint("TOP", 0, -14)
title:SetText("Loadouts")
title:SetText(NE.L["Loadouts"])
local close = CreateFrame("Button", nil, panel, "UIPanelCloseButton")
close:SetPoint("TOPRIGHT", -4, -4)
-- Top actions: Save current, Import.
local save = styleButton(CreateFrame("Button", nil, panel, "UIPanelButtonTemplate"))
save:SetSize(150, 24); save:SetPoint("TOPLEFT", 16, -44); save:SetText("Save current spec…")
save:SetSize(150, 24); save:SetPoint("TOPLEFT", 16, -44); save:SetText(NE.L["Save current spec…"])
save:SetScript("OnClick", function() StaticPopup_Show("NE_TALENT_LO_SAVE") end)
local import = styleButton(CreateFrame("Button", nil, panel, "UIPanelButtonTemplate"))
import:SetSize(150, 24); import:SetPoint("TOPRIGHT", -16, -44); import:SetText("Import…")
import:SetSize(150, 24); import:SetPoint("TOPRIGHT", -16, -44); import:SetText(NE.L["Import…"])
import:SetScript("OnClick", function() StaticPopup_Show("NE_TALENT_LO_IMPORT") end)
-- Saved-build list (FauxScrollFrame — must be NAMED on 3.3.5a).
@@ -254,7 +254,7 @@ local function buildPanel()
cb:SetSize(24, 24)
local cbText = cb:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
cbText:SetPoint("LEFT", cb, "RIGHT", 2, 0)
cbText:SetText("Server uses custom talents")
cbText:SetText(NE.L["Server uses custom talents"])
cb:SetChecked(T.LO_IsCustomServer())
cb:SetScript("OnClick", function(self) T.LO_SetCustomServer(self:GetChecked() and true or false) end)
panel.cb = cb
@@ -263,7 +263,7 @@ local function buildPanel()
hint:SetPoint("BOTTOMLEFT", 16, 16)
hint:SetPoint("BOTTOMRIGHT", -16, 16)
hint:SetJustifyH("LEFT")
hint:SetText("Tags exported builds with this realm so imports onto other layouts warn first.")
hint:SetText(NE.L["Tags exported builds with this realm so imports onto other layouts warn first."])
return panel
end
@@ -316,7 +316,7 @@ local function ensureBarButton()
local b = CreateFrame("Button", "NE_TalentLoadoutsButton", f, "UIPanelButtonTemplate")
b:SetSize(120, 26)
b:SetPoint("BOTTOM", f, "BOTTOM", 0, ((T.FRAME and T.FRAME.CHROME_B) or 0) + 27)
b:SetText("Loadouts")
b:SetText(NE.L["Loadouts"])
b:SetScript("OnClick", function() T.LO_TogglePanel() end)
f._loBtn = b
end
@@ -49,13 +49,13 @@ local function defaultName(group)
elseif group == 3 then return "Tertiary" -- or "Third"
elseif group == 4 then return "Quaternary" -- or "Fourth"
end
return "Spec " .. group
return NE.L["Spec "] .. group
end
local function specName(group) return customName(group) or defaultName(group) end
-- ---- rename dialog (opened by the cog) ------------------------------------------------------------
StaticPopupDialogs["NE_TALENT_RENAME_SPEC"] = {
text = "Rename this specialization (letters only, max " .. MAX_NAME .. "):",
text = NE.L["Rename this specialization (letters only, max "] .. MAX_NAME .. "):",
button1 = ACCEPT or "Accept", button2 = CANCEL or "Cancel",
hasEditBox = 1, maxLetters = MAX_NAME,
OnShow = function(self)
@@ -221,7 +221,7 @@ local function buildCog()
end)
cog:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText("Rename specialization", 1, 1, 1)
GameTooltip:SetText(NE.L["Rename specialization"], 1, 1, 1)
GameTooltip:Show()
end)
cog:SetScript("OnLeave", function() GameTooltip:Hide() end)
@@ -260,7 +260,7 @@ function T.RefreshSpecTabs()
local tab = buildTab(1)
local txt = _G[TAB_NAMES[1] .. "Text"]
if needTalentsTab then
if txt then txt:SetText("Talents") elseif tab.SetText then tab:SetText("Talents") end
if txt then txt:SetText(NE.L["Talents"]) elseif tab.SetText then tab:SetText(NE.L["Talents"]) end
tab:Show()
tabsToSize[#tabsToSize + 1] = TAB_NAMES[1]
else
@@ -278,7 +278,7 @@ function T.RefreshSpecTabs()
if petAvail then
local ptab = buildPetTab()
local ptxt = _G[PET_TAB_NAME .. "Text"]
if ptxt then ptxt:SetText("Pet") elseif ptab.SetText then ptab:SetText("Pet") end
if ptxt then ptxt:SetText(NE.L["Pet"]) elseif ptab.SetText then ptab:SetText(NE.L["Pet"]) end
ptab:Show()
tabsToSize[#tabsToSize + 1] = PET_TAB_NAME
else
@@ -290,7 +290,7 @@ function T.RefreshSpecTabs()
if hasGlyph then
local gtab = buildGlyphTab()
local gtxt = _G[GLYPH_TAB_NAME .. "Text"]
if gtxt then gtxt:SetText("Glyphs") elseif gtab.SetText then gtab:SetText("Glyphs") end
if gtxt then gtxt:SetText(NE.L["Glyphs"]) elseif gtab.SetText then gtab:SetText(NE.L["Glyphs"]) end
gtab:Show()
tabsToSize[#tabsToSize + 1] = GLYPH_TAB_NAME
else
@@ -726,7 +726,7 @@ local function refuseIfLocked()
if T.IsUnlocked() then return false end
if UIErrorsFrame then
UIErrorsFrame:AddMessage(
format(FEATURE_BECOMES_AVAILABLE_AT_LEVEL or "This feature becomes available at level %d.",
format(FEATURE_BECOMES_AVAILABLE_AT_LEVEL or NE.L["This feature becomes available at level %d."],
TALENT_MIN_LEVEL), 1.0, 0.1, 0.1, 1.0)
end
return true
@@ -856,9 +856,9 @@ end
if NE.modules and NE.modules.Register then
NE.modules.Register("Talents", {
default = true,
label = "Talents Panel",
label = NE.L["Talents Panel"],
category = "Windows",
desc = "The modern talents window. Turn off to use the standard Blizzard talent window.",
desc = NE.L["The modern talents window. Turn off to use the standard Blizzard talent window."],
events = { "PLAYER_LOGIN", "ADDON_LOADED", "UI_SCALE_CHANGED", "DISPLAY_SIZE_CHANGED" },
onBoot = function(event, arg1) boot(event, arg1) end,
})
@@ -0,0 +1,276 @@
param(
[string]$ReportPath
)
$ErrorActionPreference = "Stop"
$addonRoot = Split-Path -Parent $PSScriptRoot
$tocPath = Join-Path $addonRoot "DragonUI_NewEra.toc"
$enPath = Join-Path $addonRoot "Locales\enUS.lua"
$ruPath = Join-Path $addonRoot "Locales\ruRU.lua"
if (-not $ReportPath) {
$ReportPath = Join-Path $addonRoot "Locales\UNTRANSLATED_ruRU.md"
}
function Get-LocaleEntries {
param([string]$Path)
$entries = @{}
foreach ($line in Get-Content -LiteralPath $Path -Encoding UTF8) {
if ($line -match '^\s*L\["((?:\\.|[^"\\])*)"\]\s*=\s*(.+?)\s*$') {
$entries[$matches[1]] = $matches[2]
}
}
return $entries
}
function Escape-MarkdownCell {
param([string]$Value)
return $Value.Replace("|", "\|").Replace("`t", " ").Trim()
}
function Remove-LuaLineComment {
param([string]$Line)
$quote = [char]0
$escaped = $false
for ($i = 0; $i -lt $Line.Length; $i++) {
$ch = $Line[$i]
if ($quote -ne [char]0) {
if ($escaped) {
$escaped = $false
} elseif ($ch -eq '\') {
$escaped = $true
} elseif ($ch -eq $quote) {
$quote = [char]0
}
continue
}
if ($ch -eq '"' -or $ch -eq "'") {
$quote = $ch
} elseif ($ch -eq '-' -and ($i + 1) -lt $Line.Length -and $Line[$i + 1] -eq '-') {
return $Line.Substring(0, $i)
}
}
return $Line
}
$english = Get-LocaleEntries -Path $enPath
$russian = Get-LocaleEntries -Path $ruPath
$missingKeys = @()
$fallbackKeys = @()
foreach ($key in ($english.GetEnumerator() | ForEach-Object { $_.Key } | Sort-Object)) {
if (-not $russian.ContainsKey($key)) {
$missingKeys += $key
continue
}
$value = $russian[$key]
if ($value -eq "true" -or $value -eq ('"' + $key + '"')) {
$fallbackKeys += $key
}
}
# Only scan Lua files that are actually loaded by the MoonWell NewEra TOC.
# Generated spell/catalog tables contain English development metadata but do
# not render those literals directly, so they are excluded from the UI audit.
$generatedFiles = @{
"AlertData.lua" = $true
"Assets.lua" = $true
"CdmArsenal.lua" = $true
"CdmAuraCatalog.lua" = $true
"CdmSeedWotLK.lua" = $true
"ClassData.lua" = $true
"Data.lua" = $true
"NodeData.lua" = $true
"SoundAlertData.lua" = $true
}
$sourceFiles = @()
foreach ($line in Get-Content -LiteralPath $tocPath -Encoding UTF8) {
$entry = $line.Trim()
if (-not $entry -or $entry.StartsWith("#") -or -not $entry.EndsWith(".lua")) {
continue
}
if ($entry -match '^(Locales|Libs|qa|Textures)\\') {
continue
}
$name = Split-Path -Leaf $entry
if ($generatedFiles.ContainsKey($name)) {
continue
}
$path = Join-Path $addonRoot $entry
if (Test-Path -LiteralPath $path) {
$sourceFiles += [PSCustomObject]@{
Relative = $entry.Replace("\", "/")
Path = $path
}
}
}
$hardcoded = @()
$hardcodedLocations = @{}
$usedLocaleKeys = @{}
foreach ($source in $sourceFiles) {
$lineNumber = 0
foreach ($line in Get-Content -LiteralPath $source.Path -Encoding UTF8) {
$lineNumber++
$trimmed = (Remove-LuaLineComment -Line $line).Trim()
if (-not $trimmed) {
continue
}
foreach ($match in [regex]::Matches($trimmed, '"(?:\\.|[^"\\])*"')) {
$before = $trimmed.Substring(0, $match.Index)
$rawValue = $match.Value.Substring(1, $match.Value.Length - 2)
if ($before -match 'NE\.L\[\s*$') {
$usedLocaleKeys[$rawValue] = $true
continue
}
if ($before -match '\bL\s*[\(\[]\s*$') {
continue
}
if ($before -match '(==|~=)\s*$' -or
$before -match '(string\.find|string\.match|:match|:gsub|:sub)\s*\([^\)]*$') {
continue
}
if ($rawValue -match '^[A-Za-z]' -and
($before -match '\b(SetText|SetFormattedText|AddLine|AddDoubleLine|AddMessage|print)\s*\(\s*$' -or
$before -match '(?i)\b(text|label|title|description|tooltipText)\s*=\s*$')) {
$hardcoded += [PSCustomObject]@{
File = $source.Relative
Line = $lineNumber
Source = $trimmed
}
$hardcodedLocations["$($source.Relative):$lineNumber"] = $true
break
}
}
}
}
# Catch prose that reaches the UI indirectly: return values, fallback text,
# format strings, option value tables, and messages passed through helpers.
# This section deliberately includes diagnostics so a human can decide whether
# they should be localized or kept as developer-only English.
$additional = @()
foreach ($source in $sourceFiles) {
$lineNumber = 0
foreach ($line in Get-Content -LiteralPath $source.Path -Encoding UTF8) {
$lineNumber++
$trimmed = (Remove-LuaLineComment -Line $line).Trim()
$location = "$($source.Relative):$lineNumber"
if (-not $trimmed -or $hardcodedLocations.ContainsKey($location)) {
continue
}
foreach ($match in [regex]::Matches($trimmed, '"(?:\\.|[^"\\])*"')) {
$before = $trimmed.Substring(0, $match.Index)
$rawValue = $match.Value.Substring(1, $match.Value.Length - 2)
if ($before -match '(?:NE\.)?L\s*\[\s*$' -or $before -match '\bL\s*\(\s*$') {
continue
}
if ($before -match '(==|~=)\s*$' -or
$before -match '(string\.find|string\.match|:match|:gsub|:sub)\s*\([^\)]*$') {
continue
}
if ($rawValue -notmatch '\\\\' -and $rawValue -match '^[A-Za-z][^"\\]*\s+[^"\\]*$') {
$additional += [PSCustomObject]@{
File = $source.Relative
Line = $lineNumber
Source = $trimmed
}
break
}
}
}
}
$unregisteredKeys = @()
foreach ($entry in $usedLocaleKeys.GetEnumerator()) {
if (-not $english.ContainsKey($entry.Key)) {
$unregisteredKeys += [string]$entry.Key
}
}
$unregisteredKeys = @($unregisteredKeys | Sort-Object)
$builder = New-Object System.Text.StringBuilder
[void]$builder.AppendLine("# DragonUI_NewEra: untranslated ruRU strings")
[void]$builder.AppendLine("")
[void]$builder.AppendLine("Generated by ``qa/audit-ruRU.ps1`` from the files loaded by ``DragonUI_NewEra.toc``.")
[void]$builder.AppendLine("")
[void]$builder.AppendLine("- enUS keys: $($english.Count)")
[void]$builder.AppendLine("- Missing ruRU keys: $($missingKeys.Count)")
[void]$builder.AppendLine("- ruRU keys still falling back to English: $($fallbackKeys.Count)")
[void]$builder.AppendLine("- NE.L usages missing from enUS: $($unregisteredKeys.Count)")
[void]$builder.AppendLine("- Direct hardcoded English UI strings: $($hardcoded.Count)")
[void]$builder.AppendLine("- Indirect prose and fallback candidates: $($additional.Count)")
[void]$builder.AppendLine("- Total source locations requiring localization review: $($hardcoded.Count + $additional.Count)")
[void]$builder.AppendLine("")
[void]$builder.AppendLine("The hardcoded section is the translation backlog. Move each user-facing literal to AceLocale before translating it. Generated spell/catalog data, embedded libraries, QA code, and modules not loaded by the MoonWell TOC are intentionally excluded.")
[void]$builder.AppendLine("")
[void]$builder.AppendLine("## Missing or fallback locale keys")
[void]$builder.AppendLine("")
if ($missingKeys.Count -eq 0 -and $fallbackKeys.Count -eq 0) {
[void]$builder.AppendLine("None. The existing enUS dictionary is fully covered by ruRU.")
} else {
foreach ($key in $missingKeys) {
[void]$builder.AppendLine("- Missing: ``$key``")
}
foreach ($key in $fallbackKeys) {
[void]$builder.AppendLine("- English fallback: ``$key``")
}
}
if ($unregisteredKeys.Count -gt 0) {
[void]$builder.AppendLine("")
[void]$builder.AppendLine("## NE.L usages missing from enUS")
[void]$builder.AppendLine("")
foreach ($key in $unregisteredKeys) {
[void]$builder.AppendLine("- Unregistered: ``$key``")
}
}
[void]$builder.AppendLine("")
[void]$builder.AppendLine("## Hardcoded English UI candidates")
[void]$builder.AppendLine("")
[void]$builder.AppendLine("| Source | Current code |")
[void]$builder.AppendLine("|---|---|")
foreach ($entry in $hardcoded) {
$location = "$($entry.File):$($entry.Line)"
$code = Escape-MarkdownCell -Value $entry.Source
[void]$builder.AppendLine("| ``$location`` | ``$code`` |")
}
[void]$builder.AppendLine("")
[void]$builder.AppendLine("## Indirect prose and fallback candidates")
[void]$builder.AppendLine("")
[void]$builder.AppendLine("These literals are returned to UI code, passed through helper functions, used as English fallbacks, or emitted as runtime diagnostics. Review them while extracting the direct strings; some diagnostic-only entries may intentionally remain English.")
[void]$builder.AppendLine("")
[void]$builder.AppendLine("| Source | Current code |")
[void]$builder.AppendLine("|---|---|")
foreach ($entry in $additional) {
$location = "$($entry.File):$($entry.Line)"
$code = Escape-MarkdownCell -Value $entry.Source
[void]$builder.AppendLine("| ``$location`` | ``$code`` |")
}
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($ReportPath, $builder.ToString(), $utf8NoBom)
Write-Output "Report: $ReportPath"
Write-Output "enUS keys: $($english.Count)"
Write-Output "Missing ruRU keys: $($missingKeys.Count)"
Write-Output "English fallback ruRU keys: $($fallbackKeys.Count)"
Write-Output "NE.L usages missing from enUS: $($unregisteredKeys.Count)"
Write-Output "Direct hardcoded English UI strings: $($hardcoded.Count)"
Write-Output "Indirect prose and fallback candidates: $($additional.Count)"
Write-Output "Total source locations requiring review: $($hardcoded.Count + $additional.Count)"
@@ -0,0 +1,208 @@
[CmdletBinding()]
param(
[switch]$Apply
)
$ErrorActionPreference = "Stop"
$addonRoot = Split-Path -Parent $PSScriptRoot
$tocPath = Join-Path $addonRoot "DragonUI_NewEra.toc"
$enPath = Join-Path $addonRoot "Locales\enUS.lua"
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
function Remove-LuaLineComment {
param([string]$Line)
$quote = [char]0
$escaped = $false
for ($i = 0; $i -lt $Line.Length; $i++) {
$ch = $Line[$i]
if ($quote -ne [char]0) {
if ($escaped) {
$escaped = $false
} elseif ($ch -eq '\') {
$escaped = $true
} elseif ($ch -eq $quote) {
$quote = [char]0
}
continue
}
if ($ch -eq '"' -or $ch -eq "'") {
$quote = $ch
} elseif ($ch -eq '-' -and ($i + 1) -lt $Line.Length -and $Line[$i + 1] -eq '-') {
return $Line.Substring(0, $i)
}
}
return $Line
}
function Should-LocalizeLiteral {
param(
[string]$Before,
[string]$RawValue
)
# Already localized, or a localized-global fallback handled by the local
# L(globalName, fallback) helper used by several character modules.
if ($Before -match '(?:NE\.)?L\s*\[\s*$' -or $Before -match '\bL\s*\(\s*$') {
return $false
}
# Comparison and parsing literals are data/protocol tokens, not labels.
# Translating them would change control flow or make a parser locale-bound.
if ($Before -match '(==|~=)\s*$' -or
$Before -match '(string\.find|string\.match|:match|:gsub|:sub)\s*\([^\)]*$') {
return $false
}
if ($RawValue -match '^[A-Za-z]' -and
$Before -match '\b(SetText|SetFormattedText|AddLine|AddDoubleLine|AddMessage|print)\s*\(\s*$') {
return $true
}
if ($RawValue -match '^[A-Za-z]' -and
$Before -match '(?i)\b(text|label|title|description|tooltipText)\s*=\s*$') {
return $true
}
# Human-readable prose, including format strings and concatenated sentence
# fragments. Texture/file paths are never locale keys.
if ($RawValue -notmatch '\\\\' -and $RawValue -match '^[A-Za-z][^"\\]*\s+[^"\\]*$') {
return $true
}
return $false
}
$generatedFiles = @{
"AlertData.lua" = $true
"Assets.lua" = $true
"CdmArsenal.lua" = $true
"CdmAuraCatalog.lua" = $true
"CdmSeedWotLK.lua" = $true
"ClassData.lua" = $true
"Data.lua" = $true
"NodeData.lua" = $true
"SoundAlertData.lua" = $true
}
$sourceFiles = @()
foreach ($line in Get-Content -LiteralPath $tocPath -Encoding UTF8) {
$entry = $line.Trim()
if (-not $entry -or $entry.StartsWith("#") -or -not $entry.EndsWith(".lua")) {
continue
}
if ($entry -match '^(Locales|Libs|qa|Textures)\\') {
continue
}
if ($generatedFiles.ContainsKey((Split-Path -Leaf $entry))) {
continue
}
$path = Join-Path $addonRoot $entry
if (Test-Path -LiteralPath $path) {
$sourceFiles += [PSCustomObject]@{ Relative = $entry; Path = $path }
}
}
$keys = @{}
$changedFiles = 0
$changedLiterals = 0
foreach ($source in $sourceFiles) {
$content = [System.IO.File]::ReadAllText($source.Path)
$newline = if ($content.Contains("`r`n")) { "`r`n" } else { "`n" }
$hadFinalNewline = $content.EndsWith("`n")
$lines = $content -split "`r?`n"
if ($hadFinalNewline -and $lines.Count -gt 0 -and $lines[$lines.Count - 1] -eq "") {
$lines = $lines[0..($lines.Count - 2)]
}
$fileChanged = $false
for ($lineIndex = 0; $lineIndex -lt $lines.Count; $lineIndex++) {
$line = $lines[$lineIndex]
$code = Remove-LuaLineComment -Line $line
$comment = $line.Substring($code.Length)
$matches = [regex]::Matches($code, '"(?:\\.|[^"\\])*"')
if ($matches.Count -eq 0) {
continue
}
$replacements = @()
foreach ($match in $matches) {
$before = $code.Substring(0, $match.Index)
$rawValue = $match.Value.Substring(1, $match.Value.Length - 2)
if (Should-LocalizeLiteral -Before $before -RawValue $rawValue) {
$replacements += [PSCustomObject]@{
Index = $match.Index
Length = $match.Length
Text = "NE.L[$($match.Value)]"
}
$keys[$rawValue] = $match.Value
}
}
foreach ($replacement in ($replacements | Sort-Object Index -Descending)) {
$code = $code.Remove($replacement.Index, $replacement.Length).Insert($replacement.Index, $replacement.Text)
$changedLiterals++
$fileChanged = $true
}
if ($fileChanged) {
$lines[$lineIndex] = $code + $comment
}
}
if ($fileChanged) {
$changedFiles++
if ($Apply) {
$updated = [string]::Join($newline, $lines)
if ($hadFinalNewline) { $updated += $newline }
[System.IO.File]::WriteAllText($source.Path, $updated, $utf8NoBom)
}
}
}
# Include manually localized NE.L usages (not only literals changed during this
# invocation) so their English keys are guaranteed to be registered as well.
foreach ($source in $sourceFiles) {
$content = [System.IO.File]::ReadAllText($source.Path)
foreach ($match in [regex]::Matches($content, 'NE\.L\["((?:\\.|[^"\\])*)"\]')) {
$rawValue = $match.Groups[1].Value
$keys[$rawValue] = '"' + $rawValue + '"'
}
}
$enContent = [System.IO.File]::ReadAllText($enPath)
$existingKeys = @{}
foreach ($match in [regex]::Matches($enContent, 'L\["((?:\\.|[^"\\])*)"\]\s*=')) {
$existingKeys[$match.Groups[1].Value] = $true
}
$newKeys = @()
foreach ($entry in $keys.GetEnumerator()) {
$key = [string]$entry.Key
if (-not $existingKeys.ContainsKey($key)) {
$newKeys += $key
}
}
$newKeys = @($newKeys | Sort-Object)
if ($Apply -and $newKeys.Count -gt 0) {
$section = New-Object System.Text.StringBuilder
[void]$section.AppendLine("")
[void]$section.AppendLine("-- ============================================================================")
[void]$section.AppendLine("-- GENERATED UI BACKLOG (qa/localize-hardcoded.ps1)")
[void]$section.AppendLine("-- ============================================================================")
foreach ($key in $newKeys) {
[void]$section.AppendLine("L[$($keys[$key])] = true")
}
[System.IO.File]::WriteAllText($enPath, $enContent.TrimEnd() + $section.ToString(), $utf8NoBom)
}
$mode = if ($Apply) { "APPLIED" } else { "DRY RUN" }
Write-Output "${mode}: localized literals: $changedLiterals"
Write-Output "${mode}: changed Lua files: $changedFiles"
Write-Output "${mode}: registered NE.L usages: $($keys.Count)"
Write-Output "${mode}: new enUS keys: $($newKeys.Count)"
if (-not $Apply -and $newKeys.Count -gt 0) {
Write-Output ("${mode}: pending enUS keys: " + ($newKeys -join ", "))
}
@@ -0,0 +1,16 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../luaparse/bin/luaparse" "$@"
else
exec node "$basedir/../luaparse/bin/luaparse" "$@"
fi
@@ -0,0 +1,17 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\luaparse\bin\luaparse" %*
@@ -0,0 +1,28 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../luaparse/bin/luaparse" $args
} else {
& "$basedir/node$exe" "$basedir/../luaparse/bin/luaparse" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../luaparse/bin/luaparse" $args
} else {
& "node$exe" "$basedir/../luaparse/bin/luaparse" $args
}
$ret=$LASTEXITCODE
}
exit $ret
@@ -0,0 +1,16 @@
{
"name": "offline",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/luaparse": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/luaparse/-/luaparse-0.3.1.tgz",
"integrity": "sha512-b21h2bFEbtGXmVqguHogbyrMAA0wOHyp9u/rx+w6Yc9pW1t9YjhGUsp87lYcp7pFRqSWN/PhFkrdIqKEUzRjjQ==",
"license": "MIT",
"bin": {
"luaparse": "bin/luaparse"
}
}
}
}
@@ -0,0 +1,20 @@
Copyright (c) Oskar Schöldström 2012-2014
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,308 @@
# luaparse
A Lua parser written in JavaScript, originally written by Oskar Schöldström for his bachelor's thesis at Arcada.
## Installation
Install through `npm install luaparse`.
## Usage
CommonJS
```js
var parser = require('luaparse');
var ast = parser.parse('i = 0');
console.log(JSON.stringify(ast));
```
AMD
```js
require(['luaparse'], function(parser) {
var ast = parser.parse('i = 0');
console.log(JSON.stringify(ast));
});
```
Browser
```html
<script src="luaparse.js"></script>
<script>
var ast = luaparse.parse('i = 0');
console.log(JSON.stringify(ast));
</script>
```
## Parser Interface
Basic usage:
```js
luaparse.parse(code, options);
```
The output of the parser is an Abstract Syntax Tree (AST) formatted in JSON.
The available options are:
- `wait: false` Explicitly tell the parser when the input ends.
- `comments: true` Store comments as an array in the chunk object.
- `scope: false` Track identifier scopes.
- `locations: false` Store location information on each syntax node.
- `ranges: false` Store the start and end character locations on each syntax
node.
- `onCreateNode: null` A callback which will be invoked when a syntax node
has been completed. The node which has been created will be passed as the
only parameter.
- `onCreateScope: null` A callback which will be invoked when a new scope is
created.
- `onDestroyScope: null` A callback which will be invoked when the current
scope is destroyed.
- `onLocalDeclaration: null` A callback which will be invoked when a local
variable is declared. The identifier will be passed as the only parameter.
- `luaVersion: '5.1'` The version of Lua the parser will target; supported
values are `'5.1'`, `'5.2'`, `'5.3'` and `'LuaJIT'`.
- `extendedIdentifiers: false` Whether to allow code points ≥ U+0080 in
identifiers, like LuaJIT does. **Note:** setting `luaVersion: 'LuaJIT'`
currently does *not* enable this option; this may change in the future.
- `encodingMode: 'none'` Defines the relation between code points ≥ U+0080
appearing in parser input and raw bytes in source code, and how Lua escape
sequences in JavaScript strings should be interpreted. See the
[Encoding modes](#encoding-modes) section below for more information.
The default options are also exposed through `luaparse.defaultOptions` where
they can be overriden globally.
There is a second interface which might be preferable when using the `wait`
option.
```js
var parser = luaparse.parse({ wait: true });
parser.write('foo = "');
parser.write('bar');
var ast = parser.end('"');
```
This would be identical to:
```js
var ast = luaparse.parse('foo = "bar"');
```
### AST format
If the following code is executed:
```js
luaparse.parse('foo = "bar"');
```
then the returned value will be:
```js
{
"type": "Chunk",
"body": [
{
"type": "AssignmentStatement",
"variables": [
{
"type": "Identifier",
"name": "foo"
}
],
"init": [
{
"type": "StringLiteral",
"value": "bar",
"raw": "\"bar\""
}
]
}
],
"comments": []
}
```
### Encoding modes
Unlike strings in JavaScript, Lua strings are not Unicode strings, but
bytestrings (sequences of 8-bit values); likewise, implementations of Lua
parse the source code as a sequence of octets. However, the input to this
parser is a JavaScript string, i.e. a sequence of 16-bit code units (not
necessarily well-formed UTF-16). This poses a problem of how those code
units should be interpreted, particularly if they are outside the Basic
Latin block ('ASCII').
The `encodingMode` option specifies how these issues should be handled.
Possible values are as follows:
- `'none'`: Source code characters all pass through as-is and string
literals are not interpreted at all; the string literal nodes contain
the value `null`. This is the default mode.
- `'x-user-defined'`: Source code has been decoded with the WHATWG
`x-user-defined` encoding; escapes of bytes in the range \[0x80, 0xff]
are mapped to the Unicode range \[U+F780, U+F7FF].
- `'pseudo-latin1'`: Source code has been decoded with the IANA
`iso-8859-1` encoding; escapes of bytes in the range \[0x80, 0xff]
are mapped to Unicode range \[U+0080, U+00FF]. Note that this is
**not** the same as how WHATWG standards define the `iso-8859-1`
encoding, which is to say, as a synonym of `windows-1252`.
### Custom AST
The default AST structure is somewhat inspired by the Mozilla Parser API but
can easily be overriden to customize the structure or to inject custom logic.
`luaparse.ast` is an object containing all functions used to create the AST, if
you for example wanted to trigger an event on node creations you could use the
following:
```js
var luaparse = require('luaparse'),
events = new (require('events').EventEmitter);
Object.keys(luaparse.ast).forEach(function(type) {
var original = luaparse.ast[type];
luaparse.ast[type] = function() {
var node = original.apply(null, arguments);
events.emit(node.type, node);
return node;
};
});
events.on('Identifier', function(node) { console.log(node); });
luaparse.parse('i = "foo"');
```
_this is only an example to illustrate what is possible and this particular
example might not suit your needs as the end location of the node has not been
determined yet. If you desire events you should use the `onCreateNode` callback
instead)._
### Lexer
The lexer used by luaparse can be used independently of the recursive descent
parser. The lex function is exposed as `luaparse.lex()` and it will return the
next token up until `EOF` is reached.
Each token consists of:
- `type` expressed as an enum flag which can be matched with `luaparse.tokenTypes`.
- `value`
- `line`, `lineStart`
- `range` can be used to slice out raw values, eg. `foo = "bar"` will return a
`StringLiteral` token with the value `bar`. Slicing out the range on the other
hand will return `"bar"`.
```js
var parser = luaparse.parse('foo = "bar"', { wait: true });
parser.lex(); // { type: 8, value: "foo", line: 1, lineStart: 0, range: [0, 3] }
parser.lex(); // { type: 32, value: "=", line: 1, lineStart: 0, range: [4, 5]}
parser.lex(); // { type: 2, value: "bar", line: 1, lineStart: 0, range: [6, 11] }
parser.lex(); // { type: 1, value: "<eof>", line: 1, lineStart: 0, range: [11 11] }
parser.lex(); // { type: 1, value: "<eof>", line: 1, lineStart: 0, range: [11 11] }
```
## Examples
Have a look in the [examples directory](https://github.com/fstirlitz/luaparse/tree/master/examples)
of the repository for some code examples or check them out [live](https://fstirlitz.github.io/luaparse/examples.html).
## luaparse(1)
The `luaparse` executable can be used in your shell by installing `luaparse` globally using npm:
```bash
$ npm install -g luaparse
$ luaparse --help
Usage: luaparse [option]... [file|code]...
Options:
-c|--code [code] parse code snippet
-f|--file [file] parse from file
-b|--beautify output an indenteted AST
--[no]-comments store comments. defaults to true
--[no]-scope store variable scope. defaults to false
--[no]-locations store location data on syntax nodes. defaults to false
--[no]-ranges store start and end character locations. defaults to false
-q|--quiet suppress output
-h|--help
-v|--version
--verbose
Examples:
luaparse --no-comments -c "locale foo = \"bar\""
luaparse foo.lua bar.lua
```
Example usage
```bash
$ luaparse "i = 0"
{"type":"Chunk","body":[{"type":"AssignmentStatement","variables":[{"type":"Identifier","name":"i"}],"init":[{"type":"NumericLiteral","value":0,"raw":"0"}]}],"comments":[]}
```
## Support
Has been tested in at least IE6+, Firefox 3+, Safari 4+, Chrome 10+, Opera 10+,
Node 0.4.0+, RingoJS 0.8-0.9, Rhino 1.7R4-1.7R5, Nashorn 1.8.0.
## Quality Assurance
_TL;DR simply run `make qa`. This will run all quality assurance scripts but
assumes you have it set up correctly._
Begin by cloning the repository and installing the development dependencies
with `npm install`.
The luaparse test suite uses [testem](https://github.com/airportyh/testem) as a
test runner, and because of this it's very easy to run the tests using
different javascript engines or even on locally installed browsers.
### Test runners
- `make test` uses node.
- `make testem-engines` uses node, ringo and rhino
1.7R5. This requires that you have the engines installed.
- `make test-node` uses a custom command line reporter to make the output
easier on the eyes while practicing TDD.
- By installing `testem` globally you can also run the tests in a locally
installed browser.
### Other quality assurance measures
- You can check the function complexity using [complexity-report](https://github.com/philbooth/complexityReport.js)
using `make complexity-analysis`
- Running `make coverage` will generate the [coverage report](https://fstirlitz.github.io/luaparse/coverage.html).
To simply check that all code has coverage you can run `make coverage-analysis`.
- `make lint`, `make benchmark`, `make profile`.
### Documentation
By running `make docs` all [documentation](https://fstirlitz.github.io/luaparse/)
will be generated.
## Projects using/extending luaparse
- [luamin](http://mths.be/luamin), a Lua minifier written by Mathias Bynens.
- [Ace](https://github.com/ajaxorg/ace), an online code editor.
## Acknowledgements
* Initial tests are scaffolded from [yueliang][yueliang] and then manually checked for error.
* Much of the code is based on [LuaMinify][luaminify], the [Lua][lua] source and [Esprima][esprima]. All awesome projects.
## License
MIT
[luaminify]: https://github.com/stravant/LuaMinify
[yueliang]: http://yueliang.luaforge.net/
[lua]: https://www.lua.org
[esprima]: http://esprima.org
[wtf8]: https://simonsapin.github.io/wtf-8/
@@ -0,0 +1,128 @@
#!/usr/bin/env node
/*jshint node:true*/
/*globals console:true */
'use strict';
var fs = require('fs')
, util = require('util')
, luaparse = require('../luaparse')
, args = process.argv.splice(2)
, stdin = process.stdin
, isTTY = stdin.isTTY
, beautify = false
, quiet = false
, verbose = false
, input = ''
, options = {}
, snippets = [];
function usage() {
console.log([
"Usage: luaparse [option]... [file|code]..."
, "\nOptions:"
, " -c|--code [code] parse code snippet"
, " -f|--file [file] parse from file"
, " -b|--beautify output an indenteted AST"
, " --[no]-comments store comments. defaults to true"
, " --[no]-scope store variable scope. defaults to false"
, " --[no]-locations store location data on syntax nodes. defaults to false"
, " --[no]-ranges store start and end character locations. defaults to false"
, " -q|--quiet suppress output"
, " -h|--help"
, " -v|--version"
, " --verbose"
, "\nExamples:"
, " luaparse --no-comments -c \"locale foo = \\\"bar\\\"\""
, " luaparse foo.lua bar.lua"
].join("\n"));
}
if (isTTY && !args.length) {
usage();
process.exit(0);
}
for (var i = 0, l = args.length; i < l; i++) {
var arg = args[i], match, flag, bool, snippet;
if (match = /^(?:-|--)(?:(no)-)?(\w+)$/.exec(arg)) {
bool = 'no' !== match[1];
flag = match[2];
switch (flag) {
case 'b': case 'beautify':
beautify = true;
continue;
case 'q': case 'quiet':
quiet = true;
continue;
case 'verbose':
verbose = true;
continue;
case 'c': case 'code':
snippets.push({ name: args[++i], code: args[i] });
continue;
case 'f': case 'file':
snippets.push({ name: args[++i], code: fs.readFileSync(args[i], 'utf-8') });
continue;
case 'comments': case 'scope': case 'locations': case 'ranges':
options[flag] = bool;
continue;
case 'L': case 'luaversion':
options.luaVersion = args[++i];
continue;
case 'X': case 'extendedidentifiers':
options.extendedIdentifiers = bool;
continue;
case 'v': case 'version':
console.log("luaparse v%s", luaparse.version);
process.exit(0);
break;
case 'h': case 'help':
usage();
process.exit(0);
break;
default:
if (!quiet) console.log("Unknown option: %s", match[0]);
process.exit(2);
}
}
// Default to autodetecting code or file.
snippet = fs.existsSync(arg) ?
{ name: arg, code: fs.readFileSync(arg, 'utf-8') } :
{ name: arg, code: arg };
snippets.push(snippet);
}
function done() {
var success = true;
snippets.forEach(function(snippet) {
var message, ast;
try {
ast = luaparse.parse(snippet.code, options);
message = beautify ? JSON.stringify(ast, null, ' ')
: JSON.stringify(ast);
} catch(e) {
if (!(e instanceof luaparse.SyntaxError))
throw e;
message = util.format("%s: %s", snippet.name, e.message);
if (verbose) console.log(e.stack);
success = false;
}
if (!quiet) console.log(message);
});
if (!success) process.exit(1);
}
if (stdin.isTTY) done();
else {
// @TODO doesn't work in node 0.9.4-0.9.11 because `end` is never emitted.
stdin.setEncoding('utf8');
stdin.on('data', function(chunk) { input += chunk; });
stdin.on('end', function() { snippets.unshift({ code: input }); done(); });
stdin.resume();
}
/* vim: set sw=2 ts=2 et tw=80 ft=javascript : */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,52 @@
{
"name": "luaparse",
"version": "0.3.1",
"description": "A Lua parser in JavaScript",
"keywords": [
"ast",
"lua",
"parser",
"parsing"
],
"homepage": "https://fstirlitz.github.io/luaparse/",
"bugs": "https://github.com/fstirlitz/luaparse/issues",
"license": "MIT",
"author": "Oskar Schöldström <public@oxy.fi> (http://www.oxy.fi/)",
"files": [
"README.md",
"LICENSE",
"luaparse.js",
"bin/luaparse"
],
"main": "luaparse.js",
"bin": {
"luaparse": "bin/luaparse"
},
"man": "./docs/luaparse.1",
"repository": {
"type": "git",
"url": "https://github.com/fstirlitz/luaparse.git"
},
"scripts": {
"test": "make qa",
"version": "make version-bump"
},
"devDependencies": {
"benchmark": "~1.0.0",
"complexity-report": "~0.10.5",
"gulp": "^4.0.0",
"gulp-add-src": "^1.0.0",
"gulp-filelog": "^0.4.1",
"gulp-header": "^2.0.9",
"gulp-jshint": "^2.1.0",
"gulp-rename": "^1.2.0",
"gulp-uglify": "^3.0.2",
"jshint": "^2.11.1",
"marked": "^0.8.2",
"nyc": "^15.1.0",
"plugin-error": "^1.0.1",
"spec": "1.0.1",
"testem": "^3.4.1",
"through2": "^4.0.2"
}
}
@@ -30,10 +30,25 @@ local function SetParentArray(self, arrayName)
end
local function SetEnabled(self, enabled)
if enabled then
self:Enable()
else
self:Disable()
-- Enable/Disable only exist on button-like widgets in 3.3.5a. Retail's
-- SetEnabled is also used on EditBox/Frame objects by the imported EJ.
if type(self.Enable) == "function" and type(self.Disable) == "function" then
if enabled then
self:Enable()
else
self:Disable()
end
return
end
if type(self.EnableMouse) == "function" then
self:EnableMouse(enabled and true or false)
end
if type(self.EnableKeyboard) == "function" then
self:EnableKeyboard(enabled and true or false)
end
if not enabled and type(self.ClearFocus) == "function" then
self:ClearFocus()
end
end