From 38038ca69d21817530594b961a3643481483f9d1 Mon Sep 17 00:00:00 2001 From: Xruptor Date: Mon, 1 Oct 2018 09:36:15 -0400 Subject: [PATCH] Working on adding new config screens to all my addons. Also added localization for the future. --- XanChat.lua | 119 +++++++++++++++-------------- XanChat.toc | 6 ++ config.lua | 66 ++++++++++++++++ libs/AceLocale-3.0/AceLocale-3.0.lua | 137 ++++++++++++++++++++++++++++++++++ libs/AceLocale-3.0/AceLocale-3.0.xml | 4 + libs/LibStub/LibStub.lua | 30 ++++++++ libs/LibStub/LibStub.toc | 13 ++++ libs/LibStub/tests/test.lua | 41 ++++++++++ libs/LibStub/tests/test2.lua | 27 +++++++ libs/LibStub/tests/test3.lua | 14 ++++ libs/LibStub/tests/test4.lua | 41 ++++++++++ locale/enUS.lua | 93 +++++++++++++++++++++++ 12 files changed, 535 insertions(+), 56 deletions(-) create mode 100644 config.lua create mode 100644 libs/AceLocale-3.0/AceLocale-3.0.lua create mode 100644 libs/AceLocale-3.0/AceLocale-3.0.xml create mode 100644 libs/LibStub/LibStub.lua create mode 100644 libs/LibStub/LibStub.toc create mode 100644 libs/LibStub/tests/test.lua create mode 100644 libs/LibStub/tests/test2.lua create mode 100644 libs/LibStub/tests/test3.lua create mode 100644 libs/LibStub/tests/test4.lua create mode 100644 locale/enUS.lua diff --git a/XanChat.lua b/XanChat.lua index 20a982a..0e0cc6b 100644 --- a/XanChat.lua +++ b/XanChat.lua @@ -1,7 +1,12 @@ --Some stupid custom Chat modifications for made for myself. --Sharing it with the world in case anybody wants to actually use this. -local eFrame = CreateFrame("frame","xanChatEvent_Frame",UIParent) +local ADDON_NAME, addon = ... +if not _G[ADDON_NAME] then _G[ADDON_NAME] = addon + +addon.eventFrame = CreateFrame("frame","xanChatEvent_Frame",UIParent) +local eFrame = addon.eventFrame + eFrame:SetScript("OnEvent", function(self, event, ...) if self[event] then return self[event](self, event, ...) end end) local debugf = tekDebug and tekDebug:GetFrame("xanChat") @@ -9,6 +14,8 @@ local function Debug(...) if debugf then debugf:AddMessage(string.join(", ", tostringall(...))) end end +local L = LibStub("AceLocale-3.0"):GetLocale("xanChat") + --[[------------------------ Scrolling and Chat Links --------------------------]] @@ -69,7 +76,7 @@ StaticPopupDialogs["COPYNAME"] = { } UnitPopupButtons["WHOPLAYER"] = { - text = "Who Player?", + text = L.WhoPlayer, func = function() local dropdownFrame = UIDROPDOWNMENU_INIT_MENU local name = dropdownFrame.name @@ -83,7 +90,7 @@ tinsert(UnitPopupMenus["FRIEND"], #UnitPopupMenus["FRIEND"] - 1, "WHOPLAYER") customPopups["WHOPLAYER"] = true UnitPopupButtons["GUILDINVITE"] = { - text = "Guild Invite", + text = L.GuildInvite, func = function() local dropdownFrame = UIDROPDOWNMENU_INIT_MENU local name = dropdownFrame.name @@ -97,7 +104,7 @@ tinsert(UnitPopupMenus["FRIEND"], #UnitPopupMenus["FRIEND"] - 1, "GUILDINVITE") customPopups["GUILDINVITE"] = true UnitPopupButtons["COPYNAME"] = { - text = "Copy Name", + text = L.CopyName, func = function() local dropdownFrame = UIDROPDOWNMENU_INIT_MENU local name = dropdownFrame.name @@ -163,7 +170,7 @@ function urlFilter(self, event, msg, author, ...) end StaticPopupDialogs["LINKME"] = { - text = "URL COPY", + text = L.URLCopy, button2 = CANCEL, hasEditBox = true, hasWideEditBox = true, @@ -222,9 +229,9 @@ local msgHooks = {} local HistoryDB StaticPopupDialogs["XANCHAT_APPLYCHANGES"] = { - text = "xanChat: Would you like to apply the changes now?", - button1 = "Yes", - button2 = "No", + text = L.ApplyChanges, + button1 = L.Yes, + button2 = L.No, OnAccept = function() ReloadUI() end, @@ -237,12 +244,12 @@ local AddMessage = function(frame, text, ...) if type(text) == "string" then local chatNum = string.match(text,"%d+") or "" if not tonumber(chatNum) then chatNum = "" else chatNum = chatNum..":" end - text = gsub(text, "%[%d+%. General.-%]", "["..chatNum.."GN]") - text = gsub(text, "%[%d+%. Trade.-%]", "["..chatNum.."TR]") - text = gsub(text, "%[%d+%. WorldDefense%]", "["..chatNum.."WD]") - text = gsub(text, "%[%d+%. LocalDefense.-%]", "["..chatNum.."LD]") - text = gsub(text, "%[%d+%. LookingForGroup%]", "["..chatNum.."LFG]") - text = gsub(text, "%[%d+%. GuildRecruitment.-%]", "["..chatNum.."GR]") + text = gsub(text, L.ChannelGeneral, "["..chatNum..L.ShortGeneral.."]") + text = gsub(text, L.ChannelTrade, "["..chatNum..L.ShortTrade.."]") + text = gsub(text, L.ChannelWorldDefense, "["..chatNum..L.ShortWorldDefense.."]") + text = gsub(text, L.ChannelLocalDefense, "["..chatNum..L.ShortLocalDefense.."]") + text = gsub(text, L.ChannelLookingForGroup, "["..chatNum..L.ShortLookingForGroup.."]") + text = gsub(text, L.ChannelGuildRecruitment, "["..chatNum..L.ShortGuildRecruitment.."]") end msgHooks[frame:GetName()].AddMessage(frame, text, ...) end @@ -677,20 +684,20 @@ function eFrame:PLAYER_LOGIN() --enable short channel names for globals if XCHT_DB.shortNames then - CHAT_WHISPER_GET = "[W] %s: " - CHAT_WHISPER_INFORM_GET = "[W2] %s: " - CHAT_YELL_GET = "|Hchannel:Yell|h[Y]|h %s: " - CHAT_SAY_GET = "|Hchannel:Say|h[S]|h %s: " - CHAT_BATTLEGROUND_GET = "|Hchannel:Battleground|h[BG]|h %s: " - CHAT_BATTLEGROUND_LEADER_GET = [[|Hchannel:Battleground|h[BG|TInterface\GroupFrame\UI-Group-LeaderIcon:0|t]|h %s: ]] - CHAT_GUILD_GET = "|Hchannel:Guild|h[G]|h %s: " - CHAT_OFFICER_GET = "|Hchannel:Officer|h[O]|h %s: " - CHAT_PARTY_GET = "|Hchannel:Party|h[P]|h %s: " - CHAT_PARTY_LEADER_GET = [[|Hchannel:Party|h[P|TInterface\GroupFrame\UI-Group-LeaderIcon:0|t]|h %s: ]] - CHAT_PARTY_GUIDE_GET = CHAT_PARTY_LEADER_GET - CHAT_RAID_GET = "|Hchannel:Raid|h[R]|h %s: " - CHAT_RAID_LEADER_GET = [[|Hchannel:Raid|h[R|TInterface\GroupFrame\UI-Group-LeaderIcon:0|t]|h %s: ]] - CHAT_RAID_WARNING_GET = [[|Hchannel:RaidWarning|h[RW|TInterface\GroupFrame\UI-GROUP-MAINASSISTICON:0|t]|h %s: ]] + CHAT_WHISPER_GET = L.CHAT_WHISPER_GET + CHAT_WHISPER_INFORM_GET = L.CHAT_WHISPER_INFORM_GET + CHAT_YELL_GET = L.CHAT_YELL_GET + CHAT_SAY_GET = L.CHAT_SAY_GET + CHAT_BATTLEGROUND_GET = L.CHAT_BATTLEGROUND_GET + CHAT_BATTLEGROUND_LEADER_GET = L.CHAT_BATTLEGROUND_LEADER_GET + CHAT_GUILD_GET = L.CHAT_GUILD_GET + CHAT_OFFICER_GET = L.CHAT_OFFICER_GET + CHAT_PARTY_GET = L.CHAT_PARTY_GET + CHAT_PARTY_LEADER_GET = L.CHAT_PARTY_LEADER_GET + CHAT_PARTY_GUIDE_GET = L.CHAT_PARTY_GUIDE_GET + CHAT_RAID_GET = L.CHAT_RAID_GET + CHAT_RAID_LEADER_GET = L.CHAT_RAID_LEADER_GET + CHAT_RAID_WARNING_GET = L.CHAT_RAID_WARNING_GET CHAT_MONSTER_PARTY_GET = CHAT_PARTY_GET CHAT_MONSTER_SAY_GET = CHAT_SAY_GET @@ -727,74 +734,74 @@ function eFrame:PLAYER_LOGIN() local a,b,c=strfind(msg, "(%S+)") if a and XCHT_DB then - if c and c:lower() == "social" then + if c and c:lower() == L.SlashSocial then if XCHT_DB.hideSocial then XCHT_DB.hideSocial = false - DEFAULT_CHAT_FRAME:AddMessage("xanChat: Social buttons are now [|cFF99CC33ON|r]") + DEFAULT_CHAT_FRAME:AddMessage(L.SlashSocialOn) else XCHT_DB.hideSocial = true - DEFAULT_CHAT_FRAME:AddMessage("XanDebuffTimers: Social buttons are now [|cFF99CC33OFF|r]") + DEFAULT_CHAT_FRAME:AddMessage(L.SlashSocialOff) end StaticPopup_Show("XANCHAT_APPLYCHANGES") return true - elseif c and c:lower() == "scroll" then + elseif c and c:lower() == L.SlashScroll then if XCHT_DB.hideScroll then XCHT_DB.hideScroll = false - DEFAULT_CHAT_FRAME:AddMessage("xanChat: Scroll buttons are now [|cFF99CC33ON|r]") + DEFAULT_CHAT_FRAME:AddMessage(L.SlashScrollOn) else XCHT_DB.hideScroll = true - DEFAULT_CHAT_FRAME:AddMessage("xanChat: Scroll buttons are now [|cFF99CC33OFF|r]") + DEFAULT_CHAT_FRAME:AddMessage(L.SlashScrollOff) end StaticPopup_Show("XANCHAT_APPLYCHANGES") return true - elseif c and c:lower() == "shortnames" then + elseif c and c:lower() == L.SlashShortNames then if XCHT_DB.shortNames then XCHT_DB.shortNames = false - DEFAULT_CHAT_FRAME:AddMessage("xanChat: Short channel names are now [|cFF99CC33OFF|r]") + DEFAULT_CHAT_FRAME:AddMessage(L.SlashShortNamesOff) else XCHT_DB.shortNames = true - DEFAULT_CHAT_FRAME:AddMessage("xanChat: Short channel names are now [|cFF99CC33ON|r]") + DEFAULT_CHAT_FRAME:AddMessage(L.SlashShortNamesOn) end StaticPopup_Show("XANCHAT_APPLYCHANGES") return true - elseif c and c:lower() == "editbox" then + elseif c and c:lower() == L.SlashEditBox then if XCHT_DB.editBoxTop then XCHT_DB.editBoxTop = false setEditBox() - DEFAULT_CHAT_FRAME:AddMessage("xanChat: The edit box is now at the [|cFF99CC33BOTTOM|r]") + DEFAULT_CHAT_FRAME:AddMessage(L.SlashEditBoxBottom) else XCHT_DB.editBoxTop = true setEditBox(true) - DEFAULT_CHAT_FRAME:AddMessage("xanChat: The edit box is now at the [|cFF99CC33TOP|r]") + DEFAULT_CHAT_FRAME:AddMessage(L.SlashEditBoxTop) end return true - elseif c and c:lower() == "tabs" then + elseif c and c:lower() == L.SlashTabs then if XCHT_DB.hideTabs then XCHT_DB.hideTabs = false - DEFAULT_CHAT_FRAME:AddMessage("xanChat: The chat tabs are now [|cFF99CC33ON|r]") + DEFAULT_CHAT_FRAME:AddMessage(L.SlashTabsOn) else XCHT_DB.hideTabs = true - DEFAULT_CHAT_FRAME:AddMessage("xanChat: The chat tabs are now [|cFF99CC33OFF|r]") + DEFAULT_CHAT_FRAME:AddMessage(L.SlashTabsOff) end StaticPopup_Show("XANCHAT_APPLYCHANGES") return true - elseif c and c:lower() == "shadow" then + elseif c and c:lower() == L.SlashShadow then if XCHT_DB.addFontShadow then XCHT_DB.addFontShadow = false - DEFAULT_CHAT_FRAME:AddMessage("xanChat: Chat font shadows are now [|cFF99CC33OFF|r]") + DEFAULT_CHAT_FRAME:AddMessage(L.SlashShadowOff) else XCHT_DB.addFontShadow = true - DEFAULT_CHAT_FRAME:AddMessage("xanChat: Chat font shadows are now [|cFF99CC33ON|r]") + DEFAULT_CHAT_FRAME:AddMessage(L.SlashShadowOn) end StaticPopup_Show("XANCHAT_APPLYCHANGES") return true - elseif c and c:lower() == "voice" then + elseif c and c:lower() == L.SlashVoice then if XCHT_DB.hideVoice then XCHT_DB.hideVoice = false - DEFAULT_CHAT_FRAME:AddMessage("xanChat: Voice chat buttons are now [|cFF99CC33ON|r]") + DEFAULT_CHAT_FRAME:AddMessage(L.SlashVoiceOn) else XCHT_DB.hideVoice = true - DEFAULT_CHAT_FRAME:AddMessage("xanChat: Voice chat buttons are now [|cFF99CC33OFF|r]") + DEFAULT_CHAT_FRAME:AddMessage(L.SlashVoiceOff) ChatFrameToggleVoiceDeafenButton:Hide() ChatFrameToggleVoiceMuteButton:Hide() ChatFrameChannelButton:Hide() @@ -805,13 +812,13 @@ function eFrame:PLAYER_LOGIN() end DEFAULT_CHAT_FRAME:AddMessage("xanChat") - DEFAULT_CHAT_FRAME:AddMessage("/xanchat social - toggles the chat social buttons") - DEFAULT_CHAT_FRAME:AddMessage("/xanchat scroll - toggles the chat scroll bars") - DEFAULT_CHAT_FRAME:AddMessage("/xanchat shortnames - toggles short channels names") - DEFAULT_CHAT_FRAME:AddMessage("/xanchat editbox - toggles editbox to show at the top or the bottom") - DEFAULT_CHAT_FRAME:AddMessage("/xanchat tabs - toggles the chat tabs on or off") - DEFAULT_CHAT_FRAME:AddMessage("/xanchat shadow - toggles text shadows for chat fonts on or off") - DEFAULT_CHAT_FRAME:AddMessage("/xanchat voice - toggles voice chat buttons on or off") + DEFAULT_CHAT_FRAME:AddMessage(L.SlashSocialInfo) + DEFAULT_CHAT_FRAME:AddMessage(L.SlashScrollInfo) + DEFAULT_CHAT_FRAME:AddMessage(L.SlashShortNamesInfo) + DEFAULT_CHAT_FRAME:AddMessage(L.SlashEditBoxInfo) + DEFAULT_CHAT_FRAME:AddMessage(L.SlashTabsInfo) + DEFAULT_CHAT_FRAME:AddMessage(L.SlashShadowInfo) + DEFAULT_CHAT_FRAME:AddMessage(L.SlashVoiceInfo) end local ver = GetAddOnMetadata("xanChat","Version") or '1.0' diff --git a/XanChat.toc b/XanChat.toc index 68c9bf2..0c8b9a7 100644 --- a/XanChat.toc +++ b/XanChat.toc @@ -6,4 +6,10 @@ ## OptionalDeps: tekDebug ## SavedVariables: XCHT_DB, XCHT_HISTORY +libs\LibStub\LibStub.lua +libs\AceLocale-3.0\AceLocale-3.0.xml + +locale\enUS.lua + xanChat.lua +config.lua diff --git a/config.lua b/config.lua new file mode 100644 index 0000000..7f4c246 --- /dev/null +++ b/config.lua @@ -0,0 +1,66 @@ +local ADDON_NAME, addon = ... +if not _G[ADDON_NAME] then _G[ADDON_NAME] = addon + +local chkBoxIndex = 1 + +function createCheckbutton(parentFrame, xPos, yPos, displayText) + chkBoxIndex = chkBoxIndex + 1 + + local checkbutton = CreateFrame("CheckButton", ADDON_NAME.."_config_chkbtn_" .. chkBoxIndex, parentFrame, "ChatConfigCheckButtonTemplate") + checkbutton:SetPoint("TOPLEFT", xPos, yPos) + getglobal(checkbutton:GetName() .. 'Text'):SetText(displayText) + + return checkbutton +end + +local function LoadAboutFrame() + + --Code inspired from tekKonfigAboutPanel + local about = CreateFrame("Frame", ADDON_NAME.."AboutPanel", InterfaceOptionsFramePanelContainer) + about.name = ADDON_NAME + about:Hide() + + local fields = {"Version", "Author"} + local notes = GetAddOnMetadata(ADDON_NAME, "Notes") + + local title = about:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge") + + title:SetPoint("TOPLEFT", 16, -16) + title:SetText(ADDON_NAME) + + local subtitle = about:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") + subtitle:SetHeight(32) + subtitle:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -8) + subtitle:SetPoint("RIGHT", about, -32, 0) + subtitle:SetNonSpaceWrap(true) + subtitle:SetJustifyH("LEFT") + subtitle:SetJustifyV("TOP") + subtitle:SetText(notes) + + local anchor + for _,field in pairs(fields) do + local val = GetAddOnMetadata(ADDON_NAME, field) + if val then + local title = about:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall") + title:SetWidth(75) + if not anchor then title:SetPoint("TOPLEFT", subtitle, "BOTTOMLEFT", -2, -8) + else title:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, -6) end + title:SetJustifyH("RIGHT") + title:SetText(field:gsub("X%-", "")) + + local detail = about:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") + detail:SetPoint("LEFT", title, "RIGHT", 4, 0) + detail:SetPoint("RIGHT", -16, 0) + detail:SetJustifyH("LEFT") + detail:SetText(val) + + anchor = title + end + end + + InterfaceOptions_AddCategory(about) + + return about +end + +addon.aboutPanel = LoadAboutFrame() \ No newline at end of file diff --git a/libs/AceLocale-3.0/AceLocale-3.0.lua b/libs/AceLocale-3.0/AceLocale-3.0.lua new file mode 100644 index 0000000..e133781 --- /dev/null +++ b/libs/AceLocale-3.0/AceLocale-3.0.lua @@ -0,0 +1,137 @@ +--- **AceLocale-3.0** manages localization in addons, allowing for multiple locale to be registered with fallback to the base locale for untranslated strings. +-- @class file +-- @name AceLocale-3.0 +-- @release $Id: AceLocale-3.0.lua 1035 2011-07-09 03:20:13Z kaelten $ +local MAJOR,MINOR = "AceLocale-3.0", 6 + +local AceLocale, oldminor = LibStub:NewLibrary(MAJOR, MINOR) + +if not AceLocale then return end -- no upgrade needed + +-- Lua APIs +local assert, tostring, error = assert, tostring, error +local getmetatable, setmetatable, rawset, rawget = getmetatable, setmetatable, rawset, rawget + +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: GAME_LOCALE, geterrorhandler + +local gameLocale = GetLocale() +if gameLocale == "enGB" then + gameLocale = "enUS" +end + +AceLocale.apps = AceLocale.apps or {} -- array of ["AppName"]=localetableref +AceLocale.appnames = AceLocale.appnames or {} -- array of [localetableref]="AppName" + +-- This metatable is used on all tables returned from GetLocale +local readmeta = { + __index = function(self, key) -- requesting totally unknown entries: fire off a nonbreaking error and return key + rawset(self, key, key) -- only need to see the warning once, really + geterrorhandler()(MAJOR..": "..tostring(AceLocale.appnames[self])..": Missing entry for '"..tostring(key).."'") + return key + end +} + +-- This metatable is used on all tables returned from GetLocale if the silent flag is true, it does not issue a warning on unknown keys +local readmetasilent = { + __index = function(self, key) -- requesting totally unknown entries: return key + rawset(self, key, key) -- only need to invoke this function once + return key + end +} + +-- Remember the locale table being registered right now (it gets set by :NewLocale()) +-- NOTE: Do never try to register 2 locale tables at once and mix their definition. +local registering + +-- local assert false function +local assertfalse = function() assert(false) end + +-- This metatable proxy is used when registering nondefault locales +local writeproxy = setmetatable({}, { + __newindex = function(self, key, value) + rawset(registering, key, value == true and key or value) -- assigning values: replace 'true' with key string + end, + __index = assertfalse +}) + +-- This metatable proxy is used when registering the default locale. +-- It refuses to overwrite existing values +-- Reason 1: Allows loading locales in any order +-- Reason 2: If 2 modules have the same string, but only the first one to be +-- loaded has a translation for the current locale, the translation +-- doesn't get overwritten. +-- +local writedefaultproxy = setmetatable({}, { + __newindex = function(self, key, value) + if not rawget(registering, key) then + rawset(registering, key, value == true and key or value) + end + end, + __index = assertfalse +}) + +--- Register a new locale (or extend an existing one) for the specified application. +-- :NewLocale will return a table you can fill your locale into, or nil if the locale isn't needed for the players +-- game locale. +-- @paramsig application, locale[, isDefault[, silent]] +-- @param application Unique name of addon / module +-- @param locale Name of the locale to register, e.g. "enUS", "deDE", etc. +-- @param isDefault If this is the default locale being registered (your addon is written in this language, generally enUS) +-- @param silent If true, the locale will not issue warnings for missing keys. Must be set on the first locale registered. If set to "raw", nils will be returned for unknown keys (no metatable used). +-- @usage +-- -- enUS.lua +-- local L = LibStub("AceLocale-3.0"):NewLocale("TestLocale", "enUS", true) +-- L["string1"] = true +-- +-- -- deDE.lua +-- local L = LibStub("AceLocale-3.0"):NewLocale("TestLocale", "deDE") +-- if not L then return end +-- L["string1"] = "Zeichenkette1" +-- @return Locale Table to add localizations to, or nil if the current locale is not required. +function AceLocale:NewLocale(application, locale, isDefault, silent) + + -- GAME_LOCALE allows translators to test translations of addons without having that wow client installed + local gameLocale = GAME_LOCALE or gameLocale + + local app = AceLocale.apps[application] + + if silent and app and getmetatable(app) ~= readmetasilent then + geterrorhandler()("Usage: NewLocale(application, locale[, isDefault[, silent]]): 'silent' must be specified for the first locale registered") + end + + if not app then + if silent=="raw" then + app = {} + else + app = setmetatable({}, silent and readmetasilent or readmeta) + end + AceLocale.apps[application] = app + AceLocale.appnames[app] = application + end + + if locale ~= gameLocale and not isDefault then + return -- nop, we don't need these translations + end + + registering = app -- remember globally for writeproxy and writedefaultproxy + + if isDefault then + return writedefaultproxy + end + + return writeproxy +end + +--- Returns localizations for the current locale (or default locale if translations are missing). +-- Errors if nothing is registered (spank developer, not just a missing translation) +-- @param application Unique name of addon / module +-- @param silent If true, the locale is optional, silently return nil if it's not found (defaults to false, optional) +-- @return The locale table for the current language. +function AceLocale:GetLocale(application, silent) + if not silent and not AceLocale.apps[application] then + error("Usage: GetLocale(application[, silent]): 'application' - No locales registered for '"..tostring(application).."'", 2) + end + return AceLocale.apps[application] +end diff --git a/libs/AceLocale-3.0/AceLocale-3.0.xml b/libs/AceLocale-3.0/AceLocale-3.0.xml new file mode 100644 index 0000000..bf023f0 --- /dev/null +++ b/libs/AceLocale-3.0/AceLocale-3.0.xml @@ -0,0 +1,4 @@ + +