Quantcast

Implemented localization.

F16Gaming [03-30-12 - 20:06]
Implemented localization.
Filename
AddonComm.lua
BattleNetTools.lua
ChatManager.lua
Command.lua
CommandManager.lua
EventHandler.lua
Events.lua
Events_Chat.lua
GroupTools.lua
LocaleLoader.xml
LocaleManager.lua
Logger.lua
LootManager.lua
PlayerManager.lua
QueueManager.lua
RollManager.lua
String.lua
Table.lua
load.xml
locales/enUS.lua
locales/svSE.lua
diff --git a/AddonComm.lua b/AddonComm.lua
index 21575f8..f7477e9 100644
--- a/AddonComm.lua
+++ b/AddonComm.lua
@@ -1,5 +1,5 @@
---[[
-	* Copyright (c) 2011 by Adam Hellberg.
+--[[
+	* Copyright (c) 2011-2012 by Adam Hellberg.
 	*
 	* This file is part of Command.
 	*
@@ -19,6 +19,10 @@

 local C = Command

+local function L(k)
+	return C.LocaleManager:GetActive()[k]
+end
+
 local GT = C.GroupTools
 local CES = C.Extensions.String
 local CET = C.Extensions.Table
@@ -65,7 +69,7 @@ local function GroupTimerUpdate(frame, elapsed)
 		frame.Time = 0
 		AC.GroupRunning = false
 		frame:SetScript("OnUpdate", nil)
-		log:Normal("No response from group, running updater...")
+		log:Normal(L("AC_GROUP_NORESP"))
 		AC:UpdateGroup()
 	end
 end
@@ -76,7 +80,7 @@ local function GuildTimerUpdate(frame, elapsed)
 		frame.Time = 0
 		AC.GuildRunning = false
 		frame:SetScript("OnUpdate", nil)
-		log:Normal("No response from guild, running updater...")
+		log:Normal(L("AC_GUILD_NORESP"))
 		AC:UpdateGuild()
 	end
 end
@@ -85,8 +89,8 @@ function AC:Init()
 	--self:LoadSavedVars()
 	for _,v in pairs(self.Type) do
 		if not RegisterAddonMessagePrefix(v) then
-			log:Error(("[FATAL] Failed to register Addon prefix %q. Maximum number of prefixes reached on client."):format(tostring(v)))
-			error(("[FATAL] Failed to register Addon prefix %q. Maximum number of prefixes reached on client."):format(tostring(v)))
+			log:Error(L("AC_ERR_PREFIX"):format(tostring(v)))
+			error(L("AC_ERR_PREFIX"):format(tostring(v)))
 		end
 	end
 end
@@ -115,7 +119,7 @@ function AC:Receive(msgType, msg, channel, sender)
 				table.insert(self.GroupMembers, v)
 			end
 		end
-		log:Debug("Updated group members, controller: " .. self.GroupMembers[1])
+		log:Debug(L("AC_GROUP_R_UPDATE"):format(self.GroupMembers[1]))
 		self:UpdateGroup()
 	elseif msgType == self.Type.GroupAdd then
 		if channel ~= "WHISPER" or not GT:IsGroup() then return end
@@ -142,7 +146,7 @@ function AC:Receive(msgType, msg, channel, sender)
 				table.insert(self.GuildMembers, v)
 			end
 		end
-		log:Debug("Updated guild members, controller: " .. self.GuildMembers[1])
+		log:Debug(L("AC_GUILD_R_UPDATE"):format(self.GuildMembers[1]))
 		self:UpdateGuild()
 	elseif msgType == self.Type.GuildAdd then
 		if channel ~= "WHISPER" then return end
@@ -165,7 +169,7 @@ function AC:Send(msgType, msg, channel, target)
 		channel = "PARTY"
 	end
 	if not CET:HasValue(self.Type, msgType) then
-		error("Invalid Message Type specified: " .. tostring(msgType))
+		error(L("AC_ERR_MSGTYPE"):format(tostring(msgType)))
 		return
 	end
 	SendAddonMessage(msgType, msg, channel, target)
@@ -177,7 +181,7 @@ end
 function AC:UpdateGroup()
 	if not GT:IsGroup() then
 		if self.InGroup then
-			log:Normal("Left group, resetting group variables...")
+			log:Normal(L("AC_GROUP_LEFT"))
 		end
 		self.InGroup = false
 		self.GroupChecked = false
@@ -189,7 +193,7 @@ function AC:UpdateGroup()
 		if not self.GroupChecked and not GT:IsGroupLeader() then
 			self.GroupChecked = true
 			self.GroupRunning = true
-			log:Normal("Waiting for group response...")
+			log:Normal(L("AC_GROUP_WAIT"))
 			GroupTimer:SetScript("OnUpdate", GroupTimerUpdate)
 			self:Send(self.Type.GroupRequest, UnitName("player"), "RAID")
 			return
@@ -228,7 +232,7 @@ function AC:UpdateGuild()
 		if not self.GuildChecked then
 			self.GuildChecked = true
 			self.GuildRunning = true
-			log:Normal("Waiting for guild response...")
+			log:Normal(L("AC_GUILD_WAIT"))
 			GuildTimer:SetScript("OnUpdate", GuildTimerUpdate)
 			return
 		end
@@ -268,7 +272,7 @@ end
 function AC:CheckGroupRoster()
 	for i,v in ipairs(self.GroupMembers) do
 		if not GT:IsInGroup(v) then
-			log:Normal("Detected that " .. v .. " is no longer in the group, removing and updating group members...")
+			log:Normal(L("AC_GROUP_REMOVE"):format(v))
 			table.remove(self.GroupMembers, i)
 			if self.GroupMembers[1] == UnitName("player") then
 				self.GroupMaster = true
@@ -298,7 +302,7 @@ function AC:SyncGroup()
 	if GT:IsInGroup() and GT:IsGroupLeader() then
 		if self.GroupMembers[1] ~= UnitName("player") or not self.GroupMaster then
 			-- Sync handler table
-			log:Normal("Detected group handlers out of date! Sending sync message...")
+			log:Normal(L("AC_GROUP_SYNC"))
 			wipe(self.GroupMembers)
 			self.GroupMembers[1] = UnitName("player")
 			self.GroupMaster = true
diff --git a/BattleNetTools.lua b/BattleNetTools.lua
index d3a0301..cf0d6d2 100644
--- a/BattleNetTools.lua
+++ b/BattleNetTools.lua
@@ -1,5 +1,5 @@
---[[
-	* Copyright (c) 2011 by Adam Hellberg.
+--[[
+	* Copyright (c) 2011-2012 by Adam Hellberg.
 	*
 	* This file is part of Command.
 	*
diff --git a/ChatManager.lua b/ChatManager.lua
index 3fc83dd..faeb7bc 100644
--- a/ChatManager.lua
+++ b/ChatManager.lua
@@ -1,5 +1,5 @@
---[[
-	* Copyright (c) 2011 by Adam Hellberg.
+--[[
+	* Copyright (c) 2011-2012 by Adam Hellberg.
 	*
 	* This file is part of Command.
 	*
@@ -55,6 +55,7 @@ C.ChatManager = {

 local CM = C.ChatManager
 local PM = C.PlayerManager
+local L = C.LocaleManager
 local GT = C.GroupTools
 local AC = C.AddonComm
 local CCM = C.CommandManager
@@ -153,11 +154,11 @@ end

 function CM:SetCmdChar(char)
 	if type(char) ~= "string" then
-		return false, "Command char has to be of type string."
+		return false, "CHAT_ERR_CMDCHAR"
 	end
 	char = char:lower() --:sub(1, 1)
 	self.Settings.CMD_CHAR = char
-	return "Successfully set the command char to: " .. char
+	return "CHAT_CMDCHAR_SUCCESS", {char}
 end

 --- Handle a chat message.
@@ -180,7 +181,7 @@ function CM:HandleMessage(msg, sender, channel, target, sourceChannel, isBN, pID
 	local cmd = self:ParseCommand(args[1])
 	if not CCM:HasCommand(cmd) then return end
 	if not AC:IsController(sourceChannel) then
-		C.Logger:Normal("Not controller instance for \124cff00FFFF" .. sourceChannel:lower() .. "\124r, aborting.")
+		C.Logger:Normal(L:GetActive()["CHAT_HANDLE_NOTCONTROLLER"]:format(sourceChannel:lower()))
 		return
 	end
 	local t = {}
@@ -190,11 +191,48 @@ function CM:HandleMessage(msg, sender, channel, target, sourceChannel, isBN, pID
 		end
 	end
 	local player = PM:GetOrCreatePlayer(sender)
-	local result, err = CCM:HandleCommand(cmd, t, true, player)
+	--local result, err = CCM:HandleCommand(cmd, t, true, player)
+	local result, arg, errArg = CCM:HandleCommand(cmd, t, sourceChannel, player)
 	if isBN then
 		target = pID
 		sender = pID
 	end
+	local l
+	if (channel == "WHISPER" or channel == "BNET") or not result then
+		l = L:GetLocale(player.Settings.Locale, true)
+	else
+		l = L:GetActive()
+	end
+	if result then
+		if type(result) == "table" then
+			for _,v in ipairs(result) do
+				if type(v) == "table" then
+					local s = v[1]
+					if type(v[2]) == "table" then
+						s = s:format(unpack(v[2]))
+					end
+					self:SendMessage(s, channel, target, isBN)
+				end
+			end
+		elseif result == "RAW_TABLE_OUTPUT" then
+			for _,v in ipairs(arg) do
+				self:SendMessage(tostring(v), channel, target, isBN)
+			end
+		else
+			local s = l[result]
+			if type(arg) == "table" then
+				s = s:format(unpack(arg))
+			end
+			self:SendMessage(s, channel, target, isBN)
+		end
+	else
+		local s = l[arg]
+		if type(errArg) == "table" then
+			s = s:format(unpack(errArg))
+		end
+		self:SendMessage(s, "WHISPER", sender, isBN)
+	end
+	--[[ PRE-Locale stuff
 	if result then
 		if type(result) == "table" then
 			for _,v in ipairs(result) do
@@ -206,4 +244,5 @@ function CM:HandleMessage(msg, sender, channel, target, sourceChannel, isBN, pID
 	else
 		self:SendMessage(tostring(err), "WHISPER", sender, isBN)
 	end
+	-- END PRE-Locale stuff ]]
 end
diff --git a/Command.lua b/Command.lua
index 7900732..04992a1 100644
--- a/Command.lua
+++ b/Command.lua
@@ -1,5 +1,5 @@
---[[
-	* Copyright (c) 2011 by Adam Hellberg.
+--[[
+	* Copyright (c) 2011-2012 by Adam Hellberg.
 	*
 	* This file is part of Command.
 	*
@@ -43,6 +43,8 @@ Command = {
 }

 local C = Command
+local L
+local GetL
 local Cmd
 local CM
 local PM
@@ -56,6 +58,7 @@ function C:Init()
 	if self.Version == "@" .. "project-version" .. "@" then
 		self.Version = "Dev"
 	end
+	L = self.LocaleManager
 	Cmd = self.CommandManager
 	CM = self.ChatManager
 	PM = self.PlayerManager
@@ -63,7 +66,8 @@ function C:Init()
 	AC = self.AddonComm
 	log = self.Logger
 	self:LoadSavedVars()
-	log:Normal("AddOn loaded! Use /cmd help or !help for help. !!NYI!!")
+	GetL = function(k) return L:GetActive()[k] end
+	log:Normal(GetL("ADDON_LOAD"))
 	self.Loaded = true
 end

@@ -75,7 +79,7 @@ function C:LoadSavedVars()
 		_G["COMMAND"] = {}
 	elseif type(_G["COMMAND"]["VERSION"]) == "number" then
 		if _G["COMMAND"]["VERSION"] < self.VarVersion then
-			log:Normal("Saved Variables out of date, resetting...")
+			log:Normal(GetL("SVARS_OUTDATED"))
 			wipe(_G["COMMAND"])
 			_G["COMMAND"] = {}
 		end
@@ -97,6 +101,7 @@ function C:LoadSavedVars()
 	if type(self.Settings.GROUP_INVITE_ANNOUNCE_DELAY) ~= "number" then
 		self.Settings.GROUP_INVITE_ANNOUNCE_DELAY = 0
 	end
+	L:Init()
 	CM:Init()
 	PM:Init()
 	RM:Init()
@@ -110,7 +115,7 @@ function C:CheckVersion(ver)
 	if self.VersionChecked then return end
 	ver = ver or 0
 	if ver > self.VersionNum then
-		log:Normal("\124cffFF0000A new version of \124cff00FFFF" .. self.Name .. "\124cffFF0000 is available! \124cffFFFF00Check the site you downloaded from for the updated version.")
+		log:Normal(GetL("NEWVERSION_NOTICE"):format(self.Name))
 		self.VersionChecked = true
 	end
 end
@@ -121,9 +126,9 @@ end
 function C:SetEnabled(enabled)
 	self.Settings.ENABLED = enabled
 	if self.Settings.ENABLED then
-		return "AddOn \124cff00FF00enabled\124r."
+		return "ENABLED"
 	end
-	return "AddOn \124cffFF0000disabled\124r."
+	return "DISABLED"
 end

 --- Enable AddOn.
@@ -151,9 +156,9 @@ function C:SetDebug(enabled)
 	self.Settings.DEBUG = enabled
 	log:SetDebug(enabled)
 	if self.Settings.DEBUG then
-		return "Debugging \124cff00FF00enabled\124r."
+		return "DEBUGENABLED"
 	end
-	return "Debugging \124cffFF0000disabled\124r."
+	return "DEBUGDISABLED"
 end

 --- Enable debugging.
@@ -177,9 +182,9 @@ end
 function C:SetGroupInvite(enabled)
 	self.Settings.GROUP_INVITE_ANNOUNCE = enabled
 	if self.Settings.GROUP_INVITE_ANNOUNCE then
-		return "Group Invite (Announce) enabled."
+		return "GI_ENABLED"
 	end
-	return "Group Invite (Announce) disabled."
+	return "GI_DISABLED"
 end

 function C:EnableGroupInvite()
@@ -196,17 +201,17 @@ end

 function C:SetGroupInviteDelay(time)
 	if type(time) ~= "number" then
-		return false, "Delay has to be a number."
+		return false, "GI_DELAY_NUM"
 	end
 	time = math.ceil(time)
 	if time > 50 then
-		return false, "Delay cannot be greater than 50 seconds."
+		return false, "GI_DELAY_MAX"
 	end
 	self.Settings.GROUP_INVITE_ANNOUNCE_DELAY = time
 	if self.Settings.GROUP_INVITE_ANNOUNCE_DELAY > 0 then
-		return "Group Invite (Announce) delay set to " .. self.Settings.GROUP_INVITE_ANNOUNCE_DELAY .. " seconds."
+		return "GI_DELAY_SET", {self.Settings.GROUP_INVITE_ANNOUNCE_DELAY}
 	end
-	return "Group Invite (Announce) delay disabled."
+	return "GI_DELAY_DISABLED"
 end

 function C:DisableGroupInviteDelay()
diff --git a/CommandManager.lua b/CommandManager.lua
index e172353..6282547 100644
--- a/CommandManager.lua
+++ b/CommandManager.lua
@@ -1,5 +1,5 @@
---[[
-	* Copyright (c) 2011 by Adam Hellberg.
+--[[
+	* Copyright (c) 2011-2012 by Adam Hellberg.
 	*
 	* This file is part of Command.
 	*
@@ -36,6 +36,7 @@ C.CommandManager = {

 local CM = C.CommandManager
 local PM = C.PlayerManager
+local L = C.LocaleManager
 local QM = C.QueueManager
 local RM = C.RollManager
 local LM = C.LootManager
@@ -57,7 +58,7 @@ end
 -- @param help Message describing how the command should be used.
 --
 function CM:Register(names, access, func, help)
-	help = help or "No help available."
+	help = help or "CM_NO_HELP"
 	if type(names) == "string" then
 		names = {names}
 	end
@@ -139,32 +140,33 @@ function CM:HandleCommand(command, args, isChat, player)
 	if cmd then
 		if isChat then
 			if not PM:IsCommandAllowed(cmd) and player.Info.Name ~= UnitName("player") then
-				return false, ("%s is not allowed to be used, %s."):format(cmd.Name, player.Info.Name)
+				return false, "CM_ERR_NOTALLOWED", {cmd.Name, player.Info.Name}
 			elseif not PM:HasAccess(player, cmd) then
-				return false, ("You do not have permission to use that command, %s. Required access level: %d. Your access level: %d."):format(player.Info.Name, cmd.Access, PM:GetAccess(player))
+				return false, "CM_ERR_NOACCESS", {player.Info.Name, cmd.Access, PM:GetAccess(player)}
 			end
 		end
 		return cmd.Call(args, player, isChat)
 	else
-		return false, ("%q is not a registered command."):format(tostring(command))
+		return false, "CM_ERR_NOTREGGED", {tostring(command)}
 	end
 end

 --- Prints all command names together with their help messages.
 function CM:AutoHelp()
+	local l = L:GetActive()
 	for k,v in pairs(self.Commands) do
 		C.Logger:Normal(("/%s %s"):format(self.Slash[1], k))
-		C.Logger:Normal(("      - %s"):format(v.Help))
+		C.Logger:Normal(("      - %s"):format(l[v.Help]))
 	end
 end

 CM:Register({"__DEFAULT__", "help", "h"}, PM.Access.Local, function(args, sender, isChat)
 	if isChat then
-		return "Type !commands for a listing of commands available."
+		return "CM_DEFAULT_CHAT"
 	end
 	CM:AutoHelp()
-	return "End of help message."
-end, "Prints this help message.")
+	return "CM_DEFAULT_END"
+end, "CM_DEFAULT_HELP")

 CM:Register({"commands", "cmds", "cmdlist", "listcmds", "listcommands", "commandlist"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
 	local all
@@ -172,27 +174,27 @@ CM:Register({"commands", "cmds", "cmdlist", "listcmds", "listcommands", "command
 		all = args[1] == "all"
 	end
 	local cmds = CM:GetCommands(all)
-	return CES:Fit(cmds, 240, ", ") -- Max length is 255, "[Command] " takes up 10. This leaves us with 5 characters grace.
-end, "Print all registered commands.")
+	return "RAW_TABLE_OUTPUT", CES:Fit(cmds, 240, ", ") -- Max length is 255, "[Command] " takes up 10. This leaves us with 5 characters grace.
+end, "CM_COMMANDS_HELP")

 CM:Register({"version", "ver", "v"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
-	return C.Version
-end, "Print the version of Command")
+	--return C.Version
+	return "CM_VERSION", {C.Version}
+end, "CM_VERSION_HELP")

 CM:Register({"set", "s"}, PM.Access.Groups.Admin.Level, function(args, sender, isChat)
-	local usage = "Usage: set cmdchar|groupinvite"
 	if #args <= 0 then
-		return false, usage
+		return false, "CM_SET_USAGE"
 	end
 	args[1] = args[1]:lower()
 	if args[1]:match("^c") then -- Command Char setting
 		if #args < 2 then
-			return false, "No command character specified."
+			return false, "CM_ERR_NOCMDCHAR"
 		end
 		return Chat:SetCmdChar(args[2])
 	elseif args[1]:match("^g") then -- Group invite (announce)
 		if #args < 2 then
-			return false, "Usage: set groupinvite enable|disable|<time>"
+			return false, "CM_SET_GROUPINVITE_USAGE"
 		end
 		args[2] = args[2]:lower()
 		local time = tonumber(args[2])
@@ -203,10 +205,46 @@ CM:Register({"set", "s"}, PM.Access.Groups.Admin.Level, function(args, sender, i
 		elseif args[2]:match("^[dn]") then -- Disable
 			return C:DisableGroupInvite()
 		end
-		return false, "Usage: set groupinvite enable|disable|<time>"
+		return false, "CM_SET_GROUPINVITE_USAGE"
+	elseif args[1]:match("^s.*l") then -- Set locale
+		if #args < 2 then
+			return false, "CM_SET_SETLOCALE_USAGE"
+		end
+		local locale = tostring(args[2]):lower()
+		return L:SetLocale(locale)
+	elseif args[1]:match("^l") then -- Other locale settings
+		if #args < 2 then
+			return false, "CM_SET_LOCALE_USAGE"
+		end
+		local sub = tostring(args[2]):lower()
+		if sub:match("^r") or sub:match("^u.*a") then -- Reset / Use active
+			return L:ResetLocale()
+		elseif sub:match("^u.*m") then -- Use master
+			return L:UseMasterLocale()
+		end
+		return false, "CM_SET_LOCALE_USAGE"
 	end
-	return false, usage
-end, "Control the settings of Command.")
+	return false, "CM_SET_USAGE"
+end, "CM_SET_HELP")
+
+CM:Register({"mylocale", "ml"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
+	if not isChat then
+		return false, "CM_ERR_CHATONLY"
+	end
+	if #args >= 1 then
+		local locale = args[1]:lower()
+		if L:LocaleLoaded(locale) then
+			sender.Settings.Locale = locale
+			PM:UpdatePlayer(sender)
+			return "CM_MYLOCALE_SET", {locale}
+		end
+		return false, "LOCALE_NOT_LOADED"
+	else -- Reset user locale
+		sender.Settings.Locale = L.Active
+		PM:UpdatePlayer(sender)
+		return "CM_MYLOCALE_SET", {L.Active}
+	end
+end, "CM_MYLOCALE_HELP")

 CM:Register({"lock", "lockdown"}, PM.Access.Groups.Admin.Level, function(args, sender, isChat)
 	if type(args[1]) == "string" then
@@ -214,7 +252,7 @@ CM:Register({"lock", "lockdown"}, PM.Access.Groups.Admin.Level, function(args, s
 	else
 		return PM:SetLocked(sender, true)
 	end
-end, "Lock a player.")
+end, "CM_LOCK_HELP")

 CM:Register({"unlock", "open"}, PM.Access.Groups.Admin.Level, function(args, sender, isChat)
 	if type(args[1]) == "string" then
@@ -222,21 +260,20 @@ CM:Register({"unlock", "open"}, PM.Access.Groups.Admin.Level, function(args, sen
 	else
 		return PM:SetLocked(sender, false)
 	end
-end, "Unlock a player.")
+end, "CM_UNLOCK_HELP")

 CM:Register({"getaccess"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
-	local msg = "%s's access is %d (%s)"
 	if type(args[1]) == "string" then
 		local player = PM:GetOrCreatePlayer(args[1])
-		return msg:format(player.Info.Name, PM.Access.Groups[player.Info.Group].Level, player.Info.Group)
+		return "CM_GETACCESS_STRING", {player.Info.Name, PM.Access.Groups[player.Info.Group].Level, player.Info.Group}
 	else
-		return msg:format(sender.Info.Name, PM.Access.Groups[sender.Info.Group].Level, sender.Info.Group)
+		return "CM_GETACCESS_STRING", {sender.Info.Name, PM.Access.Groups[sender.Info.Group].Level, sender.Info.Group}
 	end
-end, "Get the access level of a user.")
+end, "CM_GETACCESS_HELP")

 CM:Register({"setaccess"}, PM.Access.Local, function(args, sender, isChat)
 	if #args < 1 then
-		return false, "Too few arguments. Usage: setaccess [player] <group>"
+		return false, "CM_SETACCESS_USAGE"
 	end
 	if #args >= 2 then
 		local player = PM:GetOrCreatePlayer(args[1])
@@ -244,26 +281,26 @@ CM:Register({"setaccess"}, PM.Access.Local, function(args, sender, isChat)
 	else
 		return PM:SetAccessGroup(sender, args[1])
 	end
-end, "Set the access level of a user.")
+end, "CM_SETACCESS_HELP")

 CM:Register({"owner"}, PM.Access.Local, function(args, sender, isChat)
 	if isChat then
-		return false, "This command is not allowed to be used from the chat."
+		return false, "CM_ERR_NOCHAT"
 	end
 	local player = PM:GetOrCreatePlayer(UnitName("player"))
 	return PM:SetOwner(player)
-end, "Promote a player to owner rank.")
+end, "CM_OWNER_HELP")

 CM:Register({"admin"}, PM.Access.Groups.Owner.Level, function(args, sender, isChat)
 	if isChat then
-		return false, "This command is not allowed to be used from the chat."
+		return false, "CM_ERR_NOCHAT"
 	end
 	if #args <= 0 then
-		return false, "Missing argument: name"
+		return false, "CM_ADMIN_USAGE"
 	end
 	local player = PM:GetOrCreatePlayer(args[1])
 	return PM:SetAdmin(player)
-end, "Promote a player to admin rank.")
+end, "CM_ADMIN_HELP")

 CM:Register({"op"}, PM.Access.Groups.Admin.Level, function(args, sender, isChat)
 	if type(args[1]) == "string" then
@@ -272,7 +309,7 @@ CM:Register({"op"}, PM.Access.Groups.Admin.Level, function(args, sender, isChat)
 	else
 		return PM:SetOp(sender)
 	end
-end, "Promote a player to op rank.")
+end, "CM_OP_HELP")

 CM:Register({"user"}, PM.Access.Groups.Op.Level, function(args, sender, isChat)
 	if type(args[1]) == "string" then
@@ -281,25 +318,25 @@ CM:Register({"user"}, PM.Access.Groups.Op.Level, function(args, sender, isChat)
 	else
 		return PM:SetUser(sender)
 	end
-end, "Promote a player to user rank.")
+end, "CM_USER_HELP")

 CM:Register({"ban"}, PM.Access.Groups.Admin.Level, function(args, sender, isChat)
 	if #args <= 0 then
-		return false, "Missing argument: name"
+		return false, "CM_BAN_USAGE"
 	end
 	local player = PM:GetOrCreatePlayer(args[1])
 	return PM:BanUser(player)
-end, "Ban a player.")
+end, "CM_BAN_HELP")

 CM:Register({"acceptinvite", "acceptinv", "join", "joingroup"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
 	if not StaticPopup_Visible("PARTY_INVITE") then
-		return false, "No pending invites active."
+		return false, "CM_ACCEPTINVITE_NOTACTIVE"
 	elseif GT:IsInGroup() then
-		return false, "I am already in a group." -- This shouldn't happen
+		return false, "CM_ACCEPTINVITE_EXISTS" -- This shouldn't happen
 	end
 	AcceptGroup()
-	return "Accepted group invite!"
-end, "Accepts a pending group invite.")
+	return "CM_ACCEPTINVITE_SUCCESS"
+end, "CM_ACCEPTINVITE_HELP")

 CM:Register({"invite", "inv"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
 	if type(args[1]) == "string" then
@@ -308,85 +345,85 @@ CM:Register({"invite", "inv"}, PM.Access.Groups.User.Level, function(args, sende
 	else
 		return PM:Invite(sender, sender)
 	end
-end, "Invite a player to group.")
+end, "CM_INVITE_HELP")

 CM:Register({"inviteme", "invme"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
 	if not isChat then
-		return false, "This command can only be used from the chat."
+		return false, "CM_ERR_CHATONLY"
 	end
 	return PM:Invite(sender, sender)
-end, "Player who issued the command will be invited to group.")
+end, "CM_INVITEME_HELP")

-CM:Register({"denyinvite", "blockinvite", "denyinvites", "blockinvites"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
+CM:Register({"blockinvites", "blockinvite", "denyinvites", "denyinvite"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
 	if not isChat then
-		return false, "This command can only be used from the chat."
+		return false, "CM_ERR_CHATONLY"
 	end
-	return PM:DenyInvites(sender)
-end, "Player issuing this command will no longer be sent invites from this AddOn.")
+	return PM:DenyInvites(sender, isChat == "WHISPER" or isChat == "BNET")
+end, "CM_DENYINVITE_HELP")

-CM:Register({"allowinvite", "allowinvites"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
+CM:Register({"allowinvites", "allowinvite"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
 	if not isChat then
-		return false, "This command can only be used from the chat."
+		return false, "CM_ERR_CHATONLY"
 	end
-	return PM:AllowInvites(sender)
-end, "Player issuing this command will receive invites sent from this AddOn.")
+	return PM:AllowInvites(sender, isChat == "WHISPER" or isChat == "BNET")
+end, "CM_ALLOWINVITE_HELP")

 CM:Register({"kick"}, PM.Access.Groups.Op.Level, function(args, sender, isChat)
 	if #args <= 0 then
-		return false, "Usage: kick <player> [reason]"
+		return false, "CM_KICK_USAGE"
 	end
 	local player = PM:GetOrCreatePlayer(args[1])
 	return PM:Kick(player, sender, args[2])
-end, "Kick a player from group with optional reason (Requires confirmation).")
+end, "CM_KICK_HELP")

 CM:Register({"kingme", "givelead"}, PM.Access.Groups.Op.Level, function(args, sender, isChat)
 	if not isChat then
-		return false, "This command can only be used from the chat."
+		return false, "CM_ERR_CHATONLY"
 	end
 	return PM:PromoteToLeader(sender)
-end, "Player issuing this command will be promoted to group leader.")
+end, "CM_KINGME_HELP")

 CM:Register({"opme", "assistant", "assist"}, PM.Access.Groups.Op.Level, function(args, sender, isChat)
 	if not isChat then
-		return false, "This command can only be used from the chat."
+		return false, "CM_ERR_CHATONLY"
 	end
 	return PM:PromoteToAssistant(sender)
-end, "Player issuing this command will be promoted to raid assistant.")
+end, "CM_OPME_HELP")

 CM:Register({"deopme", "deassistant", "deassist"}, PM.Access.Groups.Op.Level, function(args, sender, isChat)
 	if not isChat then
-		return false, "This command can only be used from the chat."
+		return false, "CM_ERR_CHATONLY"
 	end
 	return PM:DemoteAssistant(sender)
-end, "Player issuing this command will be demoted from assistant status.")
+end, "CM_DEOPME_HELP")

 CM:Register({"leader", "lead"}, PM.Access.Groups.Op.Level, function(args, sender, isChat)
 	if #args <= 0 then
-		return false, "Missing argument: name"
+		return false, "CM_LEADER_USAGE"
 	end
 	local player = PM:GetOrCreatePlayer(args[1])
 	return PM:PromoteToLeader(player)
-end, "Promote a player to group leader.")
+end, "CM_LEADER_HELP")

 CM:Register({"promote"}, PM.Access.Groups.Op.Level, function(args, sender, isChat)
 	if #args <= 0 then
-		return false, "Missing argument: name"
+		return false, "CM_PROMOTE_USAGE"
 	end
 	local player = PM:GetOrCreatePlayer(args[1])
 	return PM:PromoteToAssistant(player)
-end, "Promote a player to raid assistant.")
+end, "CM_PROMOTE_HELP")

 CM:Register({"demote"}, PM.Access.Groups.Op.Level, function(args, sender, isChat)
 	if #args <= 0 then
-		return false, "Missing argument: name"
+		return false, "CM_DEMOTE_USAGE"
 	end
 	local player = PM:GetOrCreatePlayer(args[1])
 	return PM:DemoteAssistant(player)
-end, "Demote a player from assistant status.")
+end, "CM_DEMOTE_HELP")

 CM:Register({"queue", "q"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
 	if #args <= 0 then
-		return false, "Missing argument: LFD type"
+		return false, "CM_QUEUE_USAGE"
 	end
 	ClearAllLFGDungeons()
 	SetCVar("Sound_EnableSFX", 0)
@@ -400,135 +437,132 @@ CM:Register({"queue", "q"}, PM.Access.Groups.User.Level, function(args, sender,
 	if not index then
 		HideUIPanel(LFDParentFrame)
 		SetCVar("Sound_EnableSFX", 1)
-		return false, ("No such dungeon type: %q"):format(args[1])
+		return false, "CM_QUEUE_INVALID", {args[1]}
 	end
 	return QM:Queue(index)
-end, "Enter the LFG queue for the specified category.")
+end, "CM_QUEUE_HELP")

 CM:Register({"leavelfg", "cancellfg", "cancel", "leavelfd", "cancellfd"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
 	if not QM.QueuedByCommand then
-		return false, "Not queued by command, unable to cancel."
+		return false, "CM_LEAVELFG_FAIL"
 	end
 	return QM:Cancel()
-end, "Leave the LFG queue.")
+end, "CM_LEAVELFG_HELP")

 CM:Register({"acceptlfg", "accept", "acceptlfd", "joinlfg", "joinlfd"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
 	if not QM.QueuedByCommand then
-		return false, "Not currently queued by command."
+		return false, "CM_ACCEPTLFG_FAIL"
 	end
 	return QM:Accept()
-end, "Causes you to accept the LFG invite.")
+end, "CM_ACCEPTLFG_HELP")

 CM:Register({"convert", "conv"}, PM.Access.Groups.Op.Level, function(args, sender, isChat)
 	if GT:IsLFGGroup() then
-		return false, "LFG groups cannot be converted."
-	end
-	if not GT:IsGroup() then
-		return false, "Cannot convert if not in a group."
-	end
-	if not GT:IsGroupLeader() then
-		return false, "Cannot convert group, not leader."
-	end
-	if #args <= 0 then
-		return false, "Usage: convert party||raid."
+		return false, "CM_CONVERT_LFG"
+	elseif not GT:IsGroup() then
+		return false, "CM_CONVERT_NOGROUP"
+	elseif not GT:IsGroupLeader() then
+		return false, "CM_CONVERT_NOLEAD"
+	elseif #args <= 0 then
+		return false, "CM_CONVERT_USAGE"
 	end
 	args[1] = args[1]:lower()
 	if args[1]:match("^p") then
 		if GT:IsRaid() then
 			ConvertToParty()
-			return "Converted raid to party."
+			return "CM_CONVERT_PARTY"
 		else
-			return false, "Group is already a party."
+			return false, "CM_CONVERT_PARTYFAIL"
 		end
 	elseif args[1]:match("^r") then
 		if GT:IsRaid() then
-			return false, "Group is already a raid."
+			return false, "CM_CONVERT_RAIDFAIL"
 		else
 			ConvertToRaid()
-			return "Converted party to raid."
+			return "CM_CONVERT_RAID"
 		end
 	end
-	return false, "Invalid group type, only \"party\" or \"raid\" allowed."
-end, "Convert group to party or raid.")
+	return false, "CM_CONVERT_INVALID"
+end, "CM_CONVERT_HELP")

 CM:Register({"list"}, PM.Access.Groups.Admin.Level, function(args, sender, isChat)
 	if not args[1] then
-		return false, "Missing argument: command name"
+		return false, "CM_LIST_USAGE"
 	end
 	return PM:ListToggle(args[1]:lower())
-end, "Toggle status of a command on the blacklist/whitelist.")
+end, "CM_LIST_HELP")

 CM:Register({"listmode", "lm", "lmode"}, PM.Access.Groups.Admin.Level, function(args, sender, isChat)
 	return PM:ToggleListMode()
-end, "Toggle list between being a blacklist and being a whitelist.")
+end, "CM_LISTMODE_HELP")

 CM:Register({"groupallow", "gallow"}, PM.Access.Groups.Admin.Level, function(args, sender, isChat)
 	if #args <= 1 then
-		return false, "Usage: groupallow <groupname> <commandname>"
+		return false, "CM_GROUPALLOW_USAGE"
 	end
 	local group = args[1]:gsub("^%l", string.upper)
 	local cmd = args[2]:lower()
 	return PM:GroupAccess(group, cmd, true)
-end, "Allow a group to use a specific command.")
+end, "CM_GROUPALLOW_HELP")

 CM:Register({"groupdeny", "gdeny", "deny"}, PM.Access.Groups.Admin.Level, function(args, sender, isChat)
 	if #args <= 1 then
-		return false, "Usage: groupdeny <groupname> <commandname>"
+		return false, "CM_GROUPDENY_USAGE"
 	end
 	local group = args[1]:gsub("^%1", string.upper)
 	local cmd = args[2]:lower()
 	return PM:GroupAccess(group, cmd, false)
-end, "Deny a group to use a specific command.")
+end, "CM_GROUPDENY_HELP")

 CM:Register({"resetgroupaccess", "groupaccessreset", "removegroupaccess", "groupaccessremove", "rga", "gar"}, PM.Access.Groups.Admin.Level, function(args, sender, isChat)
 	if #args <= 1 then
-		return false, "Usage: resetgroupaccess <groupname> <commandname>"
+		return false, "CM_RESETGROUPACCESS_USAGE"
 	end
 	local group = args[1]:gsub("^%1", string.upper)
 	local cmd = args[2]:lower()
 	return PM:GroupAccessRemove(group, cmd)
-end, "Reset the group's access to a specific command.")
+end, "CM_RESETGROUPACCESS_HELP")

 CM:Register({"userallow", "uallow"}, PM.Access.Groups.Admin.Level, function(args, sender, isChat)
 	if #args <= 1 then
-		return false, "Usage: userallow <playername> <commandname>"
+		return false, "CM_USERALLOW_USAGE"
 	end
 	local player = PM:GetOrCreatePlayer(args[1])
 	local cmd = args[2]:lower()
 	return PM:PlayerAccess(player, cmd, true)
-end, "Allow a user to use a specific command.")
+end, "CM_USERALLOW_HELP")

 CM:Register({"userdeny", "udeny"}, PM.Access.Groups.Admin.Level, function(args, sender, isChat)
 	if #args <= 1 then
-		return false, "Usage: userdeny <playername> <commandname>"
+		return false, "CM_USERDENY_USAGE"
 	end
 	local player = PM:GetOrCreatePlayer(args[1])
 	local cmd = args[2]:lower()
 	return PM:PlayerAccess(player, cmd, false)
-end, "Deny a user to use a specific command.")
+end, "CM_USERDENY_HELP")

 CM:Register({"resetuseraccess", "useraccessreset", "removeuseraccess", "useraccessremove", "rua", "uar"}, PM.Access.Groups.Admin.Level, function(args, sender, isChat)
 	if #args <= 1 then
-		return false, "Usage: resetuseraccess <playername> <commandname>."
+		return false, "CM_RESETUSERACCESS_USAGE"
 	end
 	local player = PM:GetOrCreatePlayer(args[1])
 	local cmd = args[2]:lower()
 	return PM:PlayerAccessRemove(player, cmd)
-end, "Reset the user's access to a specific command.")
+end, "CM_RESETUSERACCESS_HELP")

 CM:Register({"toggle", "t"}, PM.Access.Local, function(args, sender, isChat)
 	if isChat then
-		return false, "This command is not allowed to be used from the chat."
+		return false, "CM_ERR_NOCHAT"
 	end
 	return C:Toggle()
-end, "Toggle AddOn on and off.")
+end, "CM_TOGGLE_HELP")

 CM:Register({"toggledebug", "td", "debug", "d"}, PM.Access.Local, function(args, sender, isChat)
 	if isChat then
-		return false, "This command is not allowed to be used from the chat."
+		return false, "CM_ERR_NOCHAT"
 	end
 	return C:ToggleDebug()
-end, "Toggle debugging mode on and off.")
+end, "CM_TOGGLEDEBUG_HELP")

 CM:Register({"readycheck", "rc"}, PM.Access.Groups.Op.Level, function(args, sender, isChat)
 	if #args <= 0 then
@@ -536,14 +570,14 @@ CM:Register({"readycheck", "rc"}, PM.Access.Groups.Op.Level, function(args, send
 			C.Data.ReadyCheckRunning = true
 			local name = tostring(sender.Info.Name)
 			DoReadyCheck()
-			return name .. " issued a ready check!"
+			return "CM_READYCHECK_ISSUED", {name}
 		else
-			return false, "Can't initiate ready check when not leader or assistant."
+			return false, "CM_READYCHECK_NOPRIV"
 		end
 	end
 	local status = GetReadyCheckStatus("player")
 	if (status ~= "waiting" and status ~= nil) or GetReadyCheckTimeLeft() <= 0 or not C.Data.ReadyCheckRunning then
-		return false, "Ready check not running or I have already responded."
+		return false, "CM_READYCHECK_INACTIVE"
 	end
 	local arg = tostring(args[1]):lower()
 	if arg:match("^[ay]") then -- Accept
@@ -553,7 +587,7 @@ CM:Register({"readycheck", "rc"}, PM.Access.Groups.Op.Level, function(args, send
 		end
 		ConfirmReadyCheck(true)
 		status = GetReadyCheckStatus("player")
-		return "Accepted ready check."
+		return "CM_READYCHECK_ACCEPTED"
 	elseif arg:match("^[dn]") then -- Decline
 		C.Data.ReadyCheckRunning = false
 		if ReadyCheckFrameNoButton then
@@ -561,36 +595,36 @@ CM:Register({"readycheck", "rc"}, PM.Access.Groups.Op.Level, function(args, send
 		end
 		ConfirmReadyCheck(false)
 		status = GetReadyCheckStatus("player")
-		return "Declined ready check."
+		return "CM_READYCHECK_DECLINED"
 	else
-		return false, "Invalid argument: " .. arg
+		return false, "CM_READYCHECK_INVALID", {tostring(arg)}
 	end
-	return false, "Failed to accept or decline ready check."
-end, "Respond to ready check or initate a new one.")
+	return false, "CM_READYCHECK_FAIL"
+end, "CM_READYCHECK_HELP")

 CM:Register({"loot", "l"}, PM.Access.Groups.Op.Level, function(args, sender, isChat)
 	if GT:IsLFGGroup() then
-		return false, "Cannot use loot command in LFG group."
+		return false, "CM_LOOT_LFG"
 	end
-	local usage = "Usage: loot <type||threshold||master||pass>"
+	local usage = "CM_LOOT_USAGE"
 	if #args <= 0 then
 		return false, usage
 	end
 	args[1] = args[1]:lower()
 	if args[1]:match("^ty") or args[1]:match("^me") or args[1] == "t" then
 		if #args < 2 then
-			return false, "No loot method specified."
+			return false, "CM_LOOT_NOMETHOD"
 		end
 		local method = args[2]:lower()
 		return LM:SetLootMethod(method, args[3])
 	elseif args[1]:match("^th") or args[1]:match("^l") then
 		if #args < 2 then
-			return false, "No loot threshold specified."
+			return false, "CM_LOOT_NOTHRESHOLD"
 		end
 		return LM:SetLootThreshold(args[2])
 	elseif args[1]:match("^m") then
 		if #args < 2 then
-			return false, "No master looter specified."
+			return false, "CM_LOOT_NOMASTER"
 		end
 		return LM:SetLootMaster(args[2])
 	elseif args[1]:match("^p") then
@@ -609,7 +643,7 @@ CM:Register({"loot", "l"}, PM.Access.Groups.Op.Level, function(args, sender, isC
 		return LM:SetLootPass(p)
 	end
 	return false, usage
-end, "Provides various loot functions.")
+end, "CM_LOOT_HELP")

 CM:Register({"roll", "r"}, PM.Access.Groups.Op.Level, function(args, sender, isChat)
 	if #args <= 0 then
@@ -618,7 +652,7 @@ CM:Register({"roll", "r"}, PM.Access.Groups.Op.Level, function(args, sender, isC
 	args[1] = args[1]:lower()
 	if args[1]:match("^sta") then
 		if #args < 2 then
-			return false, "Usage: roll start <[time] [item]>"
+			return false, "CM_LOOT_START_USAGE"
 		end
 		local time = tonumber(args[2])
 		local item
@@ -655,9 +689,9 @@ CM:Register({"roll", "r"}, PM.Access.Groups.Op.Level, function(args, sender, isC
 		else
 			return RM:DoRoll(min, max)
 		end
-	elseif args[1]:match("^se") then
+	elseif args[1]:match("^se") then -- Set
 		if #args < 3 then
-			return false, "Usage: roll set <min||max||time> <amount>"
+			return false, "CM_LOOT_SET_USAGE"
 		end
 		args[2] = args[2]:lower()
 		if args[2]:match("^mi") then
@@ -667,19 +701,19 @@ CM:Register({"roll", "r"}, PM.Access.Groups.Op.Level, function(args, sender, isC
 		elseif args[2]:match("^t") then
 			return RM:SetTime(tonumber(args[3]))
 		else
-			return false, "Usage: roll set <min||max||time> <amount>"
+			return false, "CM_LOOT_SET_USAGE"
 		end
 	end
-	return false, "Usage: roll [start||stop||pass||time||do||set]"
-end, "Provides tools for managing or starting/stopping rolls.")
+	return false, "CM_LOOT_USAGE"
+end, "CM_LOOT_HELP")

 CM:Register({"raidwarning", "rw", "raid_warning"}, PM.Access.Groups.User.Level, function(args, sender, isChat)
 	if not GT:IsRaid() then
-		return false, "Cannot send raid warning when not in a raid group."
+		return false, "CM_RAIDWARNING_NORAID"
 	elseif not GT:IsRaidLeaderOrAssistant() then
-		return false, "Cannot send raid warning: Not raid leader or assistant."
+		return false, "CM_RAIDWARNING_NOPRIV"
 	elseif #args <= 0 then
-		return false, "Usage: raidwarning <message>"
+		return false, "CM_RAIDWARNING_USAGE"
 	end
 	local msg = args[1]
 	if #args > 1 then
@@ -688,8 +722,8 @@ CM:Register({"raidwarning", "rw", "raid_warning"}, PM.Access.Groups.User.Level,
 		end
 	end
 	Chat:SendMessage(msg, "RAID_WARNING")
-	return "Sent raid warning."
-end, "Sends a raid warning.")
+	return "CM_RAIDWARNING_SENT"
+end, "CM_RAIDWARNING_HELP")

 for i,v in ipairs(CM.Slash) do
 	_G["SLASH_" .. C.Name:upper() .. i] = "/" .. v
@@ -705,7 +739,35 @@ SlashCmdList[C.Name:upper()] = function(msg, editBox)
 			table.insert(t, args[i])
 		end
 	end
-	local result, err = CM:HandleCommand(cmd, t, false, PM:GetOrCreatePlayer(UnitName("player")))
+	--local result, err = CM:HandleCommand(cmd, t, false, PM:GetOrCreatePlayer(UnitName("player")))
+	local result, arg, errArg = CM:HandleCommand(cmd, t, false, PM:GetOrCreatePlayer(UnitName("player")))
+	local l = L:GetActive()
+	if result then
+		if type(result) == "table" then
+			for _,v in ipairs(result) do
+				if type(v) == "table" then
+					local s = l[v[1]]
+					if type(v[2]) == "table" then
+						s = s:format(unpack(v[2]))
+					end
+					C.Logger:Normal(s)
+				end
+			end
+		elseif result == "RAW_TABLE_OUTPUT" then
+			for _,v in ipairs(arg) do
+				C.Logger:Normal(tostring(v))
+			end
+		else
+			local s = l[result]
+			if type(arg) == "table" then
+				s = s:format(unpack(arg))
+			end
+			C.Logger:Normal(s)
+		end
+	else
+		C.Logger:Error(tostring(err))
+	end
+	--[[ PRE-Locale code
 	if result then
 		if type(result) == "table" then
 			for _,v in ipairs(result) do
@@ -717,4 +779,5 @@ SlashCmdList[C.Name:upper()] = function(msg, editBox)
 	else
 		C.Logger:Error(tostring(err))
 	end
+	-- END PRE-Locale code ]]
 end
diff --git a/EventHandler.lua b/EventHandler.lua
index 2b2d2df..c88e81e 100644
--- a/EventHandler.lua
+++ b/EventHandler.lua
@@ -1,5 +1,5 @@
---[[
-	* Copyright (c) 2011 by Adam Hellberg.
+--[[
+	* Copyright (c) 2011-2012 by Adam Hellberg.
 	*
 	* This file is part of Command.
 	*
@@ -18,6 +18,7 @@
 --]]

 local C = Command
+local L = C.LocaleManager
 local CES = C.Extensions.String

 --- Handles events.
@@ -39,6 +40,5 @@ end
 C.Frame = CreateFrame("Frame")
 for k,_ in pairs(C.Events) do
 	C.Frame:RegisterEvent(k)
-	C.Logger:Debug(("%q registered."):format(k))
 end
 C.Frame:SetScript("OnEvent", function(frame, event, ...) C:OnEvent(frame, event, ...) end)
diff --git a/Events.lua b/Events.lua
index 555ddf8..60cc013 100644
--- a/Events.lua
+++ b/Events.lua
@@ -1,5 +1,5 @@
---[[
-	* Copyright (c) 2011 by Adam Hellberg.
+--[[
+	* Copyright (c) 2011-2012 by Adam Hellberg.
 	*
 	* This file is part of Command.
 	*
@@ -19,6 +19,7 @@

 local C = Command

+local L = function(k) return C.LocaleManager:GetActive()[k] end
 local CM = C.ChatManager
 local QM = C.QueueManager
 local AC = C.AddonComm
@@ -57,7 +58,7 @@ end
 --
 function C.Events.LFG_PROPOSAL_SHOW(self, ...)
 	if not QM.QueuedByCommand then return end
-	CM:SendMessage("Group has been found, type !accept to make me accept the invite.", "PARTY")
+	CM:SendMessage(L("E_LFGPROPOSAL"), "PARTY")
 end

 --- Event handler for LFG_PROPOSAL_FAILED
@@ -68,14 +69,14 @@ end
 function C.Events.LFG_PROPOSAL_FAILED(self, ...)
 	if not QM.QueuedByCommand then return end
 	QM.QueuedByCommand = false
-	CM:SendMessage("LFG failed, use !queue <type> to requeue.", "PARTY")
+	CM:SendMessage(L("E_LFGFAIL"), "PARTY")
 end

 function C.Events.READY_CHECK(self, ...)
 	if C.Data.ReadyCheckRunning then return end
 	C.Data.ReadyCheckRunning = true
 	local name = tostring(select(1, ...))
-	CM:SendMessage(name .. " issued a ready check, type !rc accept to make me accept it or !rc deny to deny it.", "SMART")
+	CM:SendMessage(L("E_READYCHECK"):format(name), "SMART")
 end

 function C.Events.READY_CHECK_FINISHED(self, ...)
@@ -90,7 +91,8 @@ end
 function C.Events.PARTY_INVITE_REQUEST(self, ...)
 	if not self.Settings.GROUP_INVITE_ANNOUNCE then return end
 	local sender = (select(1, ...))
-	local msg = "Type !acceptinvite to make me accept the group invite."
+	local locale = C.PlayerManager:GetOrCreatePlayer(sender).Settings.Locale
+	local msg = C.LocaleManager:GetLocale(locale, true)["E_GROUPINVITE"]
 	if self.Settings.GROUP_INVITE_ANNOUNCE_DELAY > 0 then
 		local f=CreateFrame("Frame")f.T=0;f.L=self.Settings.GROUP_INVITE_ANNOUNCE_DELAY;f.S=sender;f.M=msg
 		f:SetScript("OnUpdate",function(s,e)s.T=s.T+e;if(s.T>s.L)then s:SetScript("OnUpdate",nil)if(StaticPopup_Visible("PARTY_INVITE"))then CM:SendMessage(s.M,"WHISPER",s.S)end;end;end)
diff --git a/Events_Chat.lua b/Events_Chat.lua
index 9eb6ac7..eb554d0 100644
--- a/Events_Chat.lua
+++ b/Events_Chat.lua
@@ -1,5 +1,5 @@
---[[
-	* Copyright (c) 2011 by Adam Hellberg.
+--[[
+	* Copyright (c) 2011-2012 by Adam Hellberg.
 	*
 	* This file is part of Command.
 	*
diff --git a/GroupTools.lua b/GroupTools.lua
index 1e41ab5..2898cce 100644
--- a/GroupTools.lua
+++ b/GroupTools.lua
@@ -1,5 +1,5 @@
---[[
-	* Copyright (c) 2011 by Adam Hellberg.
+--[[
+	* Copyright (c) 2011-2012 by Adam Hellberg.
 	*
 	* This file is part of Command.
 	*
@@ -37,7 +37,7 @@ local GT = C.GroupTools
 -- @return True if player is in group, false otherwise.
 --
 function GT:IsGroup()
-	return UnitExists("party1") or GT:IsRaid() or self:IsLFGGroup()
+	return UnitExists("party1") or self:IsRaid() or self:IsLFGGroup()
 end

 --- Check if the player is in an LFG group.
@@ -86,14 +86,9 @@ end
 --
 function GT:IsGroupFull()
 	-- We need to add 1 to the number because it doesn't count the player.
-	local num = 0
+	local num = self:GetNumGroupMembers()
 	local max = self.RaidMax
-	if self:IsRaid() then
-		num = GetNumRaidMembers()
-	elseif self:IsGroup() then
-		num = GetNumPartyMembers() + 1
-		max = self.PartyMax
-	end
+	if self:IsGroup() then max = self.PartyMax end
 	if num >= max then return true end
 	return false
 end
diff --git a/LocaleLoader.xml b/LocaleLoader.xml
new file mode 100644
index 0000000..45ca14c
--- /dev/null
+++ b/LocaleLoader.xml
@@ -0,0 +1,25 @@
+<!--
+	* Copyright (c) 2011-2012 by Adam Hellberg.
+	*
+	* This file is part of Command.
+	*
+	* Command is free software: you can redistribute it and/or modify
+	* it under the terms of the GNU General Public License as published by
+	* the Free Software Foundation, either version 3 of the License, or
+	* (at your option) any later version.
+	*
+	* Command is distributed in the hope that it will be useful,
+	* but WITHOUT ANY WARRANTY; without even the implied warranty of
+	* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	* GNU General Public License for more details.
+	*
+	* You should have received a copy of the GNU General Public License
+	* along with Command. If not, see <http://www.gnu.org/licenses/>.
+-->
+
+<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
+..\FrameXML\UI.xsd">
+	<Script file="LocaleManager.lua" />
+	<Script file="locales\enUS.lua" />
+	<Script file="locales\svSE.lua" />
+</Ui>
diff --git a/LocaleManager.lua b/LocaleManager.lua
new file mode 100644
index 0000000..6bad1fe
--- /dev/null
+++ b/LocaleManager.lua
@@ -0,0 +1,143 @@
+--[[
+	* Copyright (c) 2011-2012 by Adam Hellberg.
+	*
+	* This file is part of Command.
+	*
+	* Command is free software: you can redistribute it and/or modify
+	* it under the terms of the GNU General Public License as published by
+	* the Free Software Foundation, either version 3 of the License, or
+	* (at your option) any later version.
+	*
+	* Command is distributed in the hope that it will be useful,
+	* but WITHOUT ANY WARRANTY; without even the implied warranty of
+	* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	* GNU General Public License for more details.
+	*
+	* You should have received a copy of the GNU General Public License
+	* along with Command. If not, see <http://www.gnu.org/licenses/>.
+--]]
+
+-- NOTE: All error() calls use constant strings in english for debugging reasons.
+-- NOTE: return after error() shouldn't be needed. Consider remove.
+
+local C = Command
+
+C.LocaleManager = {
+	Settings = {},
+	Master = "enUS", -- Fallback locale
+	Active = GetLocale(),
+	Locales = {}
+}
+
+local LM = C.LocaleManager
+
+local function l_index(t, k) -- Wait with metatable stuff til we know everything else works properly :P
+	local master = LM:GetMaster()[k]
+	if master then return master end
+	return ("%%%s%%"):format(k)
+end
+
+-- Initialize LocaleManager
+function LM:Init()
+	if self.Active == "enGB" then self.Active = "enUS" end
+	--setmetatable(self.Locales, {__index = function(t, k) error("FATAL: __index meta LocaleManager.Locales report to addon author.") end})
+	self:LoadSavedVars()
+end
+
+-- Load the saved variables
+function LM:LoadSavedVars()
+	if type(C.Settings.LOCALE_MANAGER) ~= "table" then
+		C.Settings.LOCALE_MANAGER = {}
+	end
+	self.Settings = C.Settings.LOCALE_MANAGER
+	if type(self.Settings.LOCALE) ~= "string" then
+		self.Settings.LOCALE = self.Active
+	end
+	if type(self.Settings.PLAYER_INDEPENDENT) ~= "boolean" then
+		self.Settings.PLAYER_INDEPENDENT = true
+	end
+end
+
+-- Register a new locale (might add a check against master to make sure no keys are missing)
+function LM:Register(locale, localeTable)
+	if type(locale) ~= "string" or type(localeTable) ~= "table" then
+		error("Invalid arguments for Register. Expected [string, table], got ["..type(locale)..", "..type(localeTable).."]!")
+		return
+	end
+	locale = locale:lower()
+	if self:LocaleLoaded(locale) then return end
+	self.Locales[locale] = {}
+	for k,v in pairs(localeTable) do
+		if type(v) == "string" then
+			self.Locales[locale][k] = v
+		end
+	end
+	--setmetatable(self.Locales[locale], {__index = l_index})
+end
+
+-- Check if a locale has been loaded/registered
+function LM:LocaleLoaded(locale)
+	locale = locale:lower()
+	if self.Locales[locale] then
+		return true
+	end
+	return false
+end
+
+-- Get the locale <locale>
+-- Will handle nonexistant locale
+-- However, if for some reason the client default or fallback locale is NOT loaded...
+-- ^NOTE^ Above should not happen, under any circumstance what-so-ever unless Locales table is modified.
+function LM:GetLocale(locale, isPlr)
+	if isPlr and not self.Settings.PLAYER_INDEPENDENT then
+		locale = self.Active
+	end
+	locale = locale or self.Settings.LOCALE
+	locale = locale:lower()
+	if locale == "engb" then locale = "enus" end
+	if not self:LocaleLoaded(locale) then
+		if locale ~= self.Settings.LOCALE:lower() then -- Prevent infinite loop...
+			return self:GetActive()
+		elseif locale ~= self.Master:lower() then
+			return self:GetMaster()
+		else
+			error("FATAL: GetLocale unable to resolve to working locale.")
+			return nil
+		end
+	end
+	return self.Locales[locale]
+end
+
+-- Returns client locale (or user setting if set)
+function LM:GetActive()
+	return self:GetLocale(self.Settings.LOCALE)
+end
+
+-- Returns master (fallback) locale
+function LM:GetMaster()
+	if not self:LocaleLoaded(self.Master) then
+		error("FATAL! Master locale not loaded, AddOn cannot function!")
+		return
+	end
+	return LM:GetLocale(self.Master)
+end
+
+-- Set the locale to use
+function LM:SetLocale(locale)
+	if locale:lower() == "engb" then locale = "enUS" end
+	if not self:LocaleLoaded(locale) then
+		return false, "LOCALE_NOT_LOADED"
+	end
+	self.Settings.LOCALE = locale
+	return "LOCALE_UPDATE", {self.Settings.LOCALE}
+end
+
+-- Reset locale to client default
+function LM:ResetLocale()
+	return self:SetLocale(self.Active)
+end
+
+-- Set locale to master (fallback) locale
+function LM:UseMasterLocale()
+	return self:SetLocale(self.Master)
+end
diff --git a/Logger.lua b/Logger.lua
index 7af0e5a..f2647f4 100644
--- a/Logger.lua
+++ b/Logger.lua
@@ -1 +1 @@
---[[
	* Copyright (c) 2011 by Adam Hellberg.
	* 
	* This file is part of Command.
	* 
	* Command is free software: you can redistribute it and/or modify
	* it under the terms of the GNU General Public License as published by
	* the Free Software Foundation, either version 3 of the License, or
	* (at your option) any later version.
	* 
	* Command is distributed in the hope that it will be useful,
	* but WITHOUT ANY WARRANTY; without even the implied warranty of
	* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	* GNU General Public License for more details.
	* 
	* You should have received a copy of the GNU General Public License
	* along with Command. If not, see <http://www.gnu.org/licenses/>.
--]]

local C = Command

--- Table containing all Logger methods.
-- This is referenced "Logger" in Logger.lua.
-- @name Command.Logger
-- @class table
-- @field Level Table containing all logger levels.
-- @field Settings Table containing all settings specific to Logger.
--
C.Logger = {
	Level = {
		Debug = 0,
		Normal = 1,
		Warning = 2,
		Error = 3
	},
	Settings = {
		Debug = false,
		Format = "%s%s: %s",
		Prefix = {
			Main = "\124cff00FF00[Command]\124r",
			Debug = " \124cffBBBBFFDebug\124r",
			Normal = "",
			Warning = " \124cffFFFF00Warning\124r",
			Error = " \124cffFF0000ERROR\124r"
		}
	}
}

local Logger = C.Logger

------------------------
-- MAIN LOGGER MODULE --
------------------------

--- Print a log message at the specified level.
-- @param msg Message to pring.
-- @param level One of the levels defined in Logger.Level.
--
function Logger:Print(msg, level)
	local prefix
	if level == self.Level.Debug then
		if not self.Settings.Debug then return end
		prefix = self.Settings.Prefix.Debug
	elseif level == self.Level.Normal then
		prefix = self.Settings.Prefix.Normal
	elseif level == self.Level.Warning then
		prefix = self.Settings.Prefix.Warning
	elseif level == self.Level.Error then
		prefix = self.Settings.Prefix.Error
	else
		error(("Undefined logger level passed (%q)"):format(tostring(level)))
		return
	end
	DEFAULT_CHAT_FRAME:AddMessage(self.Settings.Format:format(self.Settings.Prefix.Main, prefix, msg))
end

--- Print a debug message.
-- @param msg Message to print.
--
function Logger:Debug(msg)
	self:Print(msg, self.Level.Debug)
end

--- Print a normal message
-- @param msg Message to print.
--
function Logger:Normal(msg)
	self:Print(msg, self.Level.Normal)
end

--- Print a warning message.
-- @param msg Message to pring.
--
function Logger:Warning(msg)
	self:Print(msg, self.Level.Warning)
end

--- Print an error message.
-- @param msg Message to print.
--
function Logger:Error(msg)
	self:Print(msg, self.Level.Error)
end


--- Control the debug state.
-- Setting debugging to enabled will enable debug messages to be printed.
-- @param enabled Boolean indicating enabled or disabled state.
--
function Logger:SetDebug(enabled)
	self.Settings.Debug = enabled
end

--- Enable debugging.
--
function Logger:EnableDebug()
	self:SetDebug(true)
end

--- Disable debugging.
--
function Logger:DisableDebug()
	self:SetDebug(false)
end

--- Toggle debugging.
--
function Logger:ToggleDebug()
	self:SetDebug(not self.Settings.Debug)
end
\ No newline at end of file
+--[[
	* Copyright (c) 2011-2012 by Adam Hellberg.
	* 
	* This file is part of Command.
	* 
	* Command is free software: you can redistribute it and/or modify
	* it under the terms of the GNU General Public License as published by
	* the Free Software Foundation, either version 3 of the License, or
	* (at your option) any later version.
	* 
	* Command is distributed in the hope that it will be useful,
	* but WITHOUT ANY WARRANTY; without even the implied warranty of
	* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	* GNU General Public License for more details.
	* 
	* You should have received a copy of the GNU General Public License
	* along with Command. If not, see <http://www.gnu.org/licenses/>.
--]]

local C = Command

--- Table containing all Logger methods.
-- This is referenced "Logger" in Logger.lua.
-- @name Command.Logger
-- @class table
-- @field Level Table containing all logger levels.
-- @field Settings Table containing all settings specific to Logger.
--
C.Logger = {
	Level = {
		Debug = 0,
		Normal = 1,
		Warning = 2,
		Error = 3
	},
	Settings = {
		Debug = false,
		Format = "%s%s: %s",
	}
}

local Logger = C.Logger
local L = function(k) return C.LocaleManager:GetActive()[k] end

------------------------
-- MAIN LOGGER MODULE --
------------------------

--- Print a log message at the specified level.
-- @param msg Message to pring.
-- @param level One of the levels defined in Logger.Level.
--
function Logger:Print(msg, level)
	local prefix
	if level == self.Level.Debug then
		if not self.Settings.Debug then return end
		prefix = L("LOGGER_PREFIX_DEBUG")
	elseif level == self.Level.Normal then
		prefix = L("LOGGER_PREFIX_NORMAL")
	elseif level == self.Level.Warning then
		prefix = L("LOGGER_PREFIX_WARNING")
	elseif level == self.Level.Error then
		prefix = L("LOGGER_PREFIX_ERROR")
	else
		error(L("LOGGER_ERR_UNDEFINED"):format(tostring(level)))
		return
	end
	DEFAULT_CHAT_FRAME:AddMessage(self.Settings.Format:format(L("LOGGER_PREFIX_MAIN"):format(C.Name), prefix, msg))
end

--- Print a debug message.
-- @param msg Message to print.
--
function Logger:Debug(msg)
	self:Print(msg, self.Level.Debug)
end

--- Print a normal message
-- @param msg Message to print.
--
function Logger:Normal(msg)
	self:Print(msg, self.Level.Normal)
end

--- Print a warning message.
-- @param msg Message to pring.
--
function Logger:Warning(msg)
	self:Print(msg, self.Level.Warning)
end

--- Print an error message.
-- @param msg Message to print.
--
function Logger:Error(msg)
	self:Print(msg, self.Level.Error)
end


--- Control the debug state.
-- Setting debugging to enabled will enable debug messages to be printed.
-- @param enabled Boolean indicating enabled or disabled state.
--
function Logger:SetDebug(enabled)
	self.Settings.Debug = enabled
end

--- Enable debugging.
--
function Logger:EnableDebug()
	self:SetDebug(true)
end

--- Disable debugging.
--
function Logger:DisableDebug()
	self:SetDebug(false)
end

--- Toggle debugging.
--
function Logger:ToggleDebug()
	self:SetDebug(not self.Settings.Debug)
end
\ No newline at end of file
diff --git a/LootManager.lua b/LootManager.lua
index 2e06edb..1404b8b 100644
--- a/LootManager.lua
+++ b/LootManager.lua
@@ -1,5 +1,5 @@
---[[
-	* Copyright (c) 2011 by Adam Hellberg.
+--[[
+	* Copyright (c) 2011-2012 by Adam Hellberg.
 	*
 	* This file is part of Command.
 	*
@@ -27,6 +27,7 @@ local C = Command
 C.LootManager = {
 }

+local L = function(k) return C.LocaleManager:GetActive()[k] end
 local LM = C.LootManager
 local GT = C.GroupTools

@@ -47,6 +48,23 @@ local function ParseLootMethod(method)
 	return "group"
 end

+local function PrettyMethod(method)
+	if type(method) ~= "string" then return L("LOOT_METHOD_GROUP") end
+	method = method:lower()
+	if method:match("^f") then
+		return L("LOOT_METHOD_FFA")
+	elseif method:match("^g") then
+		return L("LOOT_METHOD_GROUP")
+	elseif method:match("^m") then
+		return L("LOOT_METHOD_MASTER")
+	elseif method:match("^n") then
+		return L("LOOT_METHOD_NEEDGREED")
+	elseif method:match("^r") then
+		return L("LOOT_METHOD_ROUNDROBIN")
+	end
+	return method
+end
+
 local function ParseThreshold(threshold)
 	if type(threshold) == "string" then
 		threshold = threshold:lower()
@@ -71,88 +89,86 @@ end

 local function PrettyThreshold(level)
 	if level == 2 then
-		return "Uncommon"
+		return L("LOOT_THRESHOLD_UNCOMMON")
 	elseif level == 3 then
-		return "Rare"
+		return L("LOOT_THRESHOLD_RARE")
 	elseif level == 4 then
-		return "Epic"
+		return L("LOOT_THRESHOLD_EPIC")
 	elseif level == 5 then
-		return "Legendary"
+		return L("LOOT_THRESHOLD_LEGENDARY")
 	elseif level == 6 then
-		return "Artifact"
+		return L("LOOT_THRESHOLD_ARTIFACT")
 	elseif level == 7 then
-		return "Heirloom"
+		return L("LOOT_THRESHOLD_HEIRLOOM")
 	end
-	return "Unknown"
+	return L("LOOT_THRESHOLD_UNKNOWN")
 end

 function LM:SetLootMethod(method, master)
 	if not GT:IsGroupLeader() then
-		return false, "Unable to change loot method, not group leader."
+		return false, "LOOT_SM_NOLEAD"
 	end
 	method = ParseLootMethod(method)
 	if method == GetLootMethod() then
-		return false, "The loot method is already set to " .. method .. "!"
+		return false, "LOOT_SM_DUPE", {PrettyMethod(method)}
 	elseif method == "master" then
 		if not master then
 			master = UnitName("player")
-			--return false, "A master looter must be specified when setting loot method to Master Loot."
 		elseif not GT:IsInGroup(master) then
-			return false, ("%q is not in the group and cannot be set as the master looter."):format(master)
+			return false, "LOOT_MASTER_NOEXIST", {master}
 		end
 		SetLootMethod(method, master)
-		return ("Successfully set the loot method to %s (%s)!"):format(method, master)
+		return "LOOT_SM_SUCCESSMASTER", {PrettyMethod(method), master}
 	end
 	SetLootMethod(method)
-	return ("Successfully set the loot method to %s!"):format(method)
+	return "LOOT_SM_SUCCESS", {PrettyMethod(method)}
 end

 function LM:SetLootMaster(master)
 	if not GT:IsGroupLeader() then
-		return false, "Unable to change master looter, not group leader."
+		return false, "LOOT_SLM_NOLEAD"
 	end
 	local method = GetLootMethod()
 	if method ~= "master" then
-		return false, "Cannot set master looter when loot method is set to " .. method
+		return false, "LOOT_SLM_METHOD", {method}
 	end
 	if not master then
-		return false, "Master looter not specified."
+		return false, "LOOT_SLM_SPECIFY"
 	elseif not GT:IsInGroup(master) then
-		return false, ("%q is not in the group and cannot be set as the master looter."):format(master)
+		return false, "LOOT_MASTER_NOEXIST", {master}
 	end
 	SetLootMethod("master", master)
-	return ("Successfully set %s as the master looter!"):format(master)
+	return "LOOT_SLM_SUCCESS", {master}
 end

 function LM:SetLootThreshold(threshold)
 	if not GT:IsGroupLeader() then
-		return false, "Unable to change loot threshold, not group leader."
+		return false, "LOOT_ST_NOLEAD"
 	end
 	threshold = ParseThreshold(threshold)
 	if threshold < 2 or threshold > 7 then
-		return false, "Invalid loot threshold specified, please specify a loot threshold between 2 and 7 (inclusive)."
+		return false, "LOOT_ST_INVALID"
 	end
 	SetLootThreshold(threshold)
-	return "Successfully set the loot threshold to " .. PrettyThreshold(threshold) .. "!"
+	return "LOOT_ST_SUCCESS", {PrettyThreshold(threshold)}
 end

 function LM:SetLootPass(pass)
-	local msg = UnitName("player") .. " is %s passing on loot."
+	local p = false
 	if type(pass) == "nil" then
 		local current = GetOptOutOfLoot()
 		SetOptOutOfLoot(not current)
-		if current then
-			msg = msg:format("not")
-		else
-			msg = msg:format("now")
+		if not current then
+			p = true
 		end
 	else
 		SetOptOutOfLoot(pass)
 		if pass then
-			msg = msg:format("now")
-		else
-			msg = msg:format("not")
+			p = true
 		end
 	end
-	return msg
+	if p then
+		return "LOOT_SP_PASS", {UnitName("player")}
+	end
+	return "LOOT_SP_ROLL", {UnitName("player")}
 end
diff --git a/PlayerManager.lua b/PlayerManager.lua
index 0d60a1a..da17f11 100644
--- a/PlayerManager.lua
+++ b/PlayerManager.lua
@@ -1,5 +1,5 @@
---[[
-	* Copyright (c) 2011 by Adam Hellberg.
+--[[
+	* Copyright (c) 2011-2012 by Adam Hellberg.
 	*
 	* This file is part of Command.
 	*
@@ -21,6 +21,8 @@ local MODE_BLACKLIST = 0
 local MODE_WHITELIST = 1

 local C = Command
+local L = C.LocaleManager
+local GetL = function(k) return L:GetActive()[k] end
 local CM
 local GT = C.GroupTools
 local BNT = C.BattleNetTools
@@ -72,7 +74,7 @@ C.PlayerManager = {
 				Deny = {}
 			}
 		}
-	},
+	}
 }

 local Players = {}
@@ -102,24 +104,44 @@ local function Kick(name, sender, reason)
 	UninviteUnit(name, reason)
 	if GT:IsGroup() then
 		if type(reason) == "string" then
-			CM:SendMessage(("%s has been kicked on %s's request. (Reason: %s)"):format(name, sender, reason), CM.LastChannel, CM.LastTarget)
+			local msg
+			if CM.LastChannel == "WHISPER" or CM.LastChannel == "BNET" then
+				msg = L:GetLocale(PM:GetOrCreatePlayer(sender).Settings.Locale, true)["PM_KICK_REASON"]:format(name, sender, reason)
+			else
+				msg = GetL("PM_KICK_REASON"):format(name, sender, reason)
+			end
+			CM:SendMessage(msg, CM.LastChannel, CM.LastTarget)
 		else
-			CM:SendMessage(("%s has been kicked on %s's request."):format(name, sender), CM.LastChannel, CM.LastTarget)
+			local msg
+			if CM.LastChannel == "WHISPER" or CM.LastChannel == "BNET" then
+				msg = L:GetLocale(PM:GetOrCreatePlayer(sender).Settings.Locale, true)["PM_KICK"]:format(name, sender)
+			else
+				msg = GetL("PM_KICK"):format(name, sender)
+			end
+			CM:SendMessage(msg, CM.LastChannel, CM.LastTarget)
 		end
 	else
-		CM:SendMessage(("%s was kicked on your request."):format(name), "WHISPER", sender)
+		local msg = L:GetLocale(PM:GetOrCreatePlayer(sender).Settings.Locale, true)["PM_KICK_NOTIFY"]:format(name)
+		CM:SendMessage(msg, "WHISPER", sender)
 	end
-	CM:SendMessage(("You have been kicked out of the group by %s."):format(sender), "WHISPER", name)
+	local msg = L:GetLocale(PM:GetOrCreatePlayer(name).Settings.Locale, true)["PM_KICK_TARGET"]:format(sender)
+	CM:SendMessage(msg, "WHISPER", name)
 end

 local function KickCancelled(name, sender)
-	CM:SendMessage(("%s's request to kick %s has been denied."):format(sender, name), CM.LastChannel, CM.LastTarget)
+	local msg
+	if CM.LastTarget and (CM.LastChannel == "WHISPER" or CM.LastChannel == "BNET") then
+		msg = L:GetLocale(PM:GetOrCreatePlayer(CM.LastTarget).Settings.Locale, true)["PM_KICK_DENIED"]:format(sender, name)
+	else
+		msg = GetL("PM_KICK_DENIED"):format(sender, name)
+	end
+	CM:SendMessage(msg, CM.LastChannel, CM.LastTarget)
 end

 StaticPopupDialogs["COMMAND_CONFIRMKICK"] = {
-	text = "%s wants to kick %s. Confirm?",
-	button1 = "Yes",
-	button2 = "No",
+	text = "PM_KICK_POPUP",
+	button1 = "YES",
+	button2 = "NO",
 	OnAccept = function() Kick(KickName, KickSender, KickReason) end,
 	OnCancel = function() KickCancelled(KickName, KickSender) end,
 	timeout = 10,
@@ -142,36 +164,37 @@ function PM:LoadSavedVars()
 	if type(C.Global["PLAYER_MANAGER"]) ~= "table" then
 		C.Global["PLAYER_MANAGER"] = {}
 	end
-	if type(C.Global["PLAYER_MANAGER"]["PLAYERS"]) ~= "table" then
-		C.Global["PLAYER_MANAGER"]["PLAYERS"] = {}
+	self.Data = C.Global["PLAYER_MANAGER"]
+	if type(self.Data.PLAYERS) ~= "table" then
+		self.Data.PLAYERS = {}
 	end
-	if type(C.Global["PLAYER_MANAGER"]["LIST_MODE"]) ~= "number" then
-		C.Global["PLAYER_MANAGER"]["LIST_MODE"] = MODE_BLACKLIST
+	if type(self.Data.LIST_MODE) ~= "number" then
+		self.Data.LIST_MODE = MODE_BLACKLIST
 	end
-	if type(C.Global["PLAYER_MANAGER"]["LIST"]) ~= "table" then
-		C.Global["PLAYER_MANAGER"]["LIST"] = {}
+	if type(self.Data.LIST) ~= "table" then
+		self.Data.LIST = {}
 	end
-	if type(C.Global["PLAYER_MANAGER"]["GROUP_PERMS"]) ~= "table" then
-		C.Global["PLAYER_MANAGER"]["GROUP_PERMS"] = {}
+	if type(self.Data.GROUP_PERMS) ~= "table" then
+		self.Data.GROUP_PERMS = {}
 		for k,v in pairs(self.Access.Groups) do
-			C.Global["PLAYER_MANAGER"]["GROUP_PERMS"][k] = {
+			self.Data.GROUP_PERMS[k] = {
 				Allow = {},
 				Deny = {}
 			}
 			for _,v in pairs(self.Access.Groups[k].Allow) do
-				table.insert(C.Global["PLAYER_MANAGER"]["GROUP_PERMS"][k].Allow, v)
+				table.insert(self.Data.GROUP_PERMS[k].Allow, v)
 			end
 			for _,v in pairs(self.Access.Groups[k].Deny) do
-				table.insert(C.Global["PLAYER_MANAGER"]["GROUP_PERMS"][k].Deny, v)
+				table.insert(self.Data.GROUP_PERMS[k].Deny, v)
 			end
 		end
 	end
-	for k,v in pairs(C.Global["PLAYER_MANAGER"]["GROUP_PERMS"]) do
+	for k,v in pairs(self.Data.GROUP_PERMS) do
 		self.Access.Groups[k].Allow = v.Allow
 		self.Access.Groups[k].Deny = v.Deny
 	end
-	Players = C.Global["PLAYER_MANAGER"]["PLAYERS"]
-	List = C.Global["PLAYER_MANAGER"]["LIST"]
+	Players = self.Data.PLAYERS
+	List = self.Data.LIST
 end

 --- Get or create a player.
@@ -191,7 +214,7 @@ function PM:GetOrCreatePlayer(name)
 			player.Info.Group = self.Access.Groups.User.Name
 		end
 		Players[player.Info.Name] = player
-		log:Normal(("Created player %q with default settings."):format(player.Info.Name))
+		log:Normal(GetL("PM_PLAYER_CREATE"):format(player.Info.Name))
 		return player
 	end
 end
@@ -201,7 +224,7 @@ end
 --
 function PM:UpdatePlayer(player)
 	Players[player.Info.Name] = player
-	log:Normal(("Updated player %q."):format(player.Info.Name))
+	log:Normal(GetL("PM_PLAYER_UPDATE"):format(player.Info.Name))
 end

 --- Completely remove a command from a group's access list.
@@ -213,14 +236,14 @@ end
 --
 function PM:GroupAccessRemove(group, command)
 	group = group:gsub("^%l", string.upper)
-	if not command then return false, "No Command specified" end
+	if not command then return false, "PM_ERR_NOCOMMAND" end
 	for i,v in pairs(self.Access.Groups[k].Allow) do
 		if v == command then table.remove(self.Access.Groups[k].Allow, i) end
 	end
 	for i,v in pairs(self.Access.Groups[k].Deny) do
 		if v == command then table.remove(self.Access.Groups[k].Deny, i) end
 	end
-	return ("%q removed from group %s."):format(command, group)
+	return "PM_GA_REMOVED", {command, group}
 end

 --- Modify the access of a command for a specific group.
@@ -231,8 +254,8 @@ end
 -- @return Error message if unsuccessful, otherwise nil.
 --
 function PM:GroupAccess(group, command, allow)
-	if not command then return false, "No command specified" end
-	local mode = "allowed"
+	if not command then return false, "PM_ERR_NOCOMMAND" end
+	local mode = true
 	if allow then
 		if CET:HasValue(self.Access.Groups[group].Deny, command) then
 			for i,v in ipairs(player.Access.Deny) do
@@ -240,10 +263,9 @@ function PM:GroupAccess(group, command, allow)
 			end
 		end
 		if CET:HasValue(self.Access.Groups[group].Allow, command) then
-			return false, ("%q already has that command on the allow list."):format(group)
+			return false, "PM_GA_EXISTSALLOW", {group}
 		end
 		table.insert(self.Access.Groups[group].Allow, command)
-		mode = "allowed"
 	else
 		if CET:HasValue(self.Access.Groups[group].Allow, command) then
 			for i,v in pairs(self.Access.Groups[group].Allow) do
@@ -251,12 +273,15 @@ function PM:GroupAccess(group, command, allow)
 			end
 		end
 		if CET:HasValue(self.Access.Groups[group].Deny, command) then
-			return false, ("%q already has that command on the deny list."):format(group)
+			return false, "PM_GA_EXISTSDENY", {group}
 		end
 		table.insert(self.Access.Groups[group].Deny, command)
-		mode = "denied"
+		mode = false
 	end
-	return ("%q is now %s for %s"):format(command, mode, group)
+	if mode then
+		return "PM_ACCESS_ALLOWED", {command, group}
+	end
+	return "PM_ACCESS_DENIED", {command, group}
 end

 --- Completely remove a command from a player's access list.
@@ -267,8 +292,8 @@ end
 -- @return Error message is unsuccessful, otherwise nil.
 --
 function PM:PlayerAccessRemove(player, command)
-	if not command then return false, "No command specified" end
-	if self:IsLocked(player) then return false, "Target player is locked and cannot be modified." end
+	if not command then return false, "PM_ERR_NOCOMMAND" end
+	if self:IsLocked(player) then return false, "PM_ERR_LOCKED" end
 	for i,v in pairs(player.Access.Allow) do
 		if v == command then table.remove(player.Access.Allow, i) end
 	end
@@ -276,7 +301,7 @@ function PM:PlayerAccessRemove(player, command)
 		if v == command then table.remove(player.Access.Deny, i) end
 	end
 	self:UpdatePlayer(player)
-	return ("%q removed from %s"):format(command, player.Info.Name)
+	return "PM_PA_REMOVED", {command, player.Info.Name}
 end

 --- Modify the access of a command for a specific player.
@@ -287,9 +312,9 @@ end
 -- @return Error message if unsuccessful, otherwise nil.
 --
 function PM:PlayerAccess(player, command, allow)
-	if not command then return false, "No command specified" end
-	if self:IsLocked(player) then return false, "Target player is locked and cannot be modified." end
-	local mode = "allowed"
+	if not command then return false, "PM_ERR_NOCOMMAND" end
+	if self:IsLocked(player) then return false, "PM_ERR_LOCKED" end
+	local mode = true
 	if allow then
 		if CET:HasValue(player.Access.Deny, command) then
 			for i,v in ipairs(player.Access.Deny) do
@@ -297,10 +322,9 @@ function PM:PlayerAccess(player, command, allow)
 			end
 		end
 		if CET:HasValue(player.Access.Allow, command) then
-			return false, ("%q already has that command on the allow list."):format(player.Info.Name)
+			return false, "PM_PA_EXISTSALLOW", {player.Info.Name}
 		end
 		table.insert(player.Access.Allow, command)
-		mode = "allowed"
 	else
 		if CET:HasValue(player.Access.Allow, command) then
 			for i,v in pairs(player.Access.Allow) do
@@ -308,13 +332,16 @@ function PM:PlayerAccess(player, command, allow)
 			end
 		end
 		if CET:HasValue(player.Access.Deny, command) then
-			return false, ("%q already has that command on the deny list."):format(player.Info.Name)
+			return false, "PM_PA_EXISTSDENY", {player.Info.Name}
 		end
 		table.insert(player.Access.Deny, command)
-		mode = "denied"
+		mode = false
 	end
 	self:UpdatePlayer(player)
-	return ("%q is now %s for %s"):format(command, mode, player.Info.Name)
+	if mode then
+		return "PM_ACCESS_ALLOWED", {command, player.Info.Name}
+	end
+	return "PM_ACCESS_DENIED", {command, player.Info.Name}
 end

 --- Check if provided player is locked.
@@ -342,9 +369,12 @@ function PM:SetLocked(player, locked)
 		player = self:GetOrCreatePlayer(tostring(player))
 	end
 	player.Settings.Locked = locked
-	local mode = "locked"
-	if not locked then mode = "unlocked" end
-	return ("Player %s has been %s."):format(player.Info.Name, mode)
+	local mode = true
+	if not locked then mode = false end
+	if mode then
+		return "PM_LOCKED", {player.Info.Name}
+	end
+	return "PM_UNLOCKED", {player.Info.Name}
 end

 --- Check if supplied player is on the player's friends list.
@@ -444,15 +474,15 @@ end
 function PM:SetAccessGroup(player, group)
 	group = group:gsub("^%l", string.upper)
 	if player.Info.Name == UnitName("player") then
-		return false, "Cannot modify my own access level."
+		return false, "PM_SAG_SELF"
 	end
 	if not CET:HasKey(self.Access.Groups, group) then
-		return false, ("No such access group: %q"):format(group)
+		return false, "PM_SAG_NOEXIST", {group}
 	end
-	if self:IsLocked(player) then return false, "Target player is locked and cannot be modified." end
+	if self:IsLocked(player) then return false, "PM_ERR_LOCKED" end
 	player.Info.Group = group
 	self:UpdatePlayer(player)
-	return ("Set the access level of %q to %d (%s)"):format(player.Info.Name, PM:GetAccess(player), player.Info.Group)
+	return "PM_SAG_SET", {player.Info.Name, PM:GetAccess(player), player.Info.Group}
 end

 --- Give player Owner access.
@@ -512,25 +542,26 @@ end
 function PM:Invite(player, sender)
 	if not sender then sender = self:GetOrCreatePlayer(UnitName("player")) end
 	if player.Info.Name == UnitName("player") then
-		return false, "Cannot invite myself to group."
+		return false, "PM_INVITE_SELF"
 	elseif GT:IsInGroup(player.Info.Name) then
-		return false, ("%s is already in the group."):format(player.Info.Name)
+		return false, "PM_INVITE_INGROUP", {player.Info.Name}
 	elseif GT:IsGroupFull() then
-		return false, "The group is already full."
+		return false, "PM_INVITE_FULL"
 	end
 	if GT:IsGroupLeader() or GT:IsRaidLeaderOrAssistant() or not GT:IsGroup() then
 		if player.Info.Name == sender.Info.Name then
 			InviteUnit(player.Info.Name)
-			return "Invited you to the group."
+			return "PM_INVITE_NOTIFYTARGET"
 		elseif player.Settings.Invite then
 			InviteUnit(player.Info.Name)
-			CM:SendMessage(("%s invited you to the group, %s. Whisper !denyinvite to block these invites."):format(sender.Info.Name, player.Info.Name), "WHISPER", player.Info.Name)
-			return ("Invited %s to group."):format(player.Info.Name)
+			local msg = L:GetLocale(player.Settings.Locale, true)["PM_INVITE_NOTIFY"]:format(sender.Info.Name, player.Info.Name)
+			CM:SendMessage(msg, "WHISPER", player.Info.Name)
+			return "PM_INVITE_SUCCESS", {player.Info.Name}
 		else
-			return false, ("%s does not wish to be invited."):format(player.Info.Name)
+			return false, "PM_INVITE_BLOCKED", {player.Info.Name}
 		end
 	end
-	return false, ("Unable to invite %s to group. Not group leader or assistant."):format(player.Info.Name)
+	return false, "PM_INVITE_NOPRIV", {player.Info.Name}
 end

 --- Stop sending Command invites to player.
@@ -538,14 +569,18 @@ end
 -- @return String stating the result of the operation, false if error.
 -- @return Error message if unsuccessful, nil otherwise.
 --
-function PM:DenyInvites(player)
+function PM:DenyInvites(player, isWhisper)
 	if player.Settings.Invite then
 		player.Settings.Invite = false
 		self:UpdatePlayer(player)
-		CM:SendMessage("You are now blocking invites, whisper !allowinvite to receive them.", "WHISPER", player.Info.Name)
-		return ("%s is no longer receiving invites."):format(player.Info.Name)
+		if isWhisper then
+			return "PM_DI_BLOCKING"
+		end
+		local msg = L:GetLocale(player.Settings.Locale, true)["PM_DI_BLOCKING"]
+		CM:SendMessage(msg, "WHISPER", player.Info.Name)
+		return "PM_DI_SUCCESS", {player.Info.Name}
 	end
-	return false, "You are already blocking invites."
+	return false, "PM_DI_FAIL"
 end

 --- Allow sending Command invites to player.
@@ -553,14 +588,18 @@ end
 -- @return String stating the result of the operation, false if error.
 -- @return Error message if unsuccessful, nil otherwise.
 --
-function PM:AllowInvites(player)
+function PM:AllowInvites(player, isWhisper)
 	if player.Settings.Invite then
-		return false, "You are already allowing invites."
+		return false, "PM_AI_FAIL"
 	end
 	player.Settings.Invite = true
 	self:UpdatePlayer(player)
-	CM:SendMessage("You are now allowing invites, whisper !blockinvite to block them.", "WHISPER", player.Info.Name)
-	return ("%s is now receiving invites."):format(player.Info.Name)
+	if isWhisper then
+		return "PM_AI_ALLOWING"
+	end
+	local msg = L:GetLocale(player.Settings.Locale, true)["PM_AI_ALLOWING"]
+	CM:SendMessage(msg, "WHISPER", player.Info.Name)
+	return "PM_AI_SUCCESS", {player.Info.Name}
 end

 --- Kick a player from the group.
@@ -571,20 +610,23 @@ end
 --
 function PM:Kick(player, sender, reason)
 	if player.Info.Name == UnitName("player") then
-		return false, "Cannot kick myself."
+		return false, "PM_KICK_SELF"
 	elseif self:IsFriend(player) or self:IsBNFriend(player) then
-		return false, "Cannot kick my friend."
+		return false, "PM_KICK_FRIEND"
 	elseif not GT:IsInGroup(player.Info.Name) then
-		return false, ("%s is not in the group."):format(player.Info.Name)
+		return false, "PM_ERR_NOTINGROUP", {player.Info.Name}
 	end
 	if GT:IsGroupLeader() or GT:IsRaidLeaderOrAssistant() then
 		KickName = player.Info.Name
 		KickSender = sender.Info.Name
-		KickReason = reason or ("%s used !kick command."):format(KickSender)
+		KickReason = reason or GetL("PM_KICK_DEFAULTREASON"):format(KickSender)
+		StaticPopupDialogs.COMMAND_CONFIRMKICK.text = GetL("PM_KICK_POPUP")
+		StaticPopupDialogs.COMMAND_CONFIRMKICK.button1 = GetL("YES")
+		StaticPopupDialogs.COMMAND_CONFIRMKICK.button2 = GetL("NO")
 		StaticPopup_Show("COMMAND_CONFIRMKICK", KickSender, KickName)
-		return ("Awaiting confirmation to kick %s..."):format(KickName)
+		return "PM_KICK_WAIT", {KickName}
 	end
-	return false, ("Unable to kick %s from group. Not group leader or assistant."):format(player.Info.Name)
+	return false, "PM_KICK_NOPRIV", {player.Info.Name}
 end

 --- Promote a player to group leader.
@@ -594,20 +636,19 @@ end
 --
 function PM:PromoteToLeader(player)
 	if player.Info.Name == UnitName("player") then
-		return false, "Cannot promote myself to leader."
+		return false, "PM_LEADER_SELF"
 	elseif GT:IsGroupLeader(player.Info.Name) then
-		return false, ("%s is already leader."):format(player.Info.Name)
+		return false, "PM_LEADER_DUPE", {player.Info.Name}
 	elseif not GT:IsInGroup(player.Info.Name) then
-		return false, ("%s is not in the group."):format(player.Info.Name)
+		return false, "PM_ERR_NOTINGROUP", {player.Info.Name}
 	end
 	if GT:IsGroupLeader() then
-		if self:IsLocked(player) then return false, "Target player is locked and cannot be modified." end
+		if self:IsLocked(player) then return false, "PM_ERR_LOCKED" end
 		PromoteToLeader(player.Info.Name)
-		return ("Promoted %s to group leader."):format(player.Info.Name)
+		return "PM_LEADER_SUCCESS", {player.Info.Name}
 	else
-		return false, ("Cannot promote %s to group leader, insufficient permissions."):format(player.Info.Name)
+		return false, "PM_LEADER_NOPRIV", {player.Info.Name}
 	end
-	return false, "Unknown error occurred."
 end

 --- Promote player to assistant.
@@ -617,42 +658,40 @@ end
 --
 function PM:PromoteToAssistant(player)
 	if player.Info.Name == UnitName("player") then
-		return false, "Cannot promote myself to assistant."
+		return false, "PM_ASSIST_SELF"
 	elseif GT:IsRaidAssistant(player.Info.Name) then
-		return false, ("%s is already assistant."):format(player.Info.Name)
+		return false, "PM_ASSIST_DUPE", {player.Info.Name}
 	elseif not GT:IsInGroup(player.Info.Name) then
-		return false, ("%s is not in the group."):format(player.Info.Name)
+		return false, "PM_ERR_NOTINGROUP", {player.Info.Name}
 	elseif not UnitInRaid("player") then
-		return false, "Cannot promote to assistant when not in a raid."
+		return false, "PM_ASSIST_NORAID"
 	end
 	if GT:IsGroupLeader() then
-		if self:IsLocked(player) then return false, "Target player is locked and cannot be modified." end
+		if self:IsLocked(player) then return false, "PM_ERR_LOCKED" end
 		PromoteToAssistant(player.Info.Name)
-		return ("Promoted %s to assistant."):format(player.Info.Name)
+		return "PM_ASSIST_SUCCESS", {player.Info.Name}
 	else
-		return false, ("Cannot promote %s to assistant, insufficient permissions."):format(player.Info.Name)
+		return false, "PM_ASSIST_NOPRIV", {player.Info.Name}
 	end
-	return false, "Unknown error occurred."
 end

 function PM:DemoteAssistant(player)
 	if player.Info.Name == UnitName("player") then
-		return false, "Cannot demote myself."
+		return false, "PM_DEMOTE_SELF"
 	elseif not GT:IsRaidAssistant(player.Info.Name) then
-		return false, ("%s is not an assistant, can only demote assistants."):format(player.Info.Name)
+		return false, "PM_DEMOTE_INVALID", {player.Info.Name}
 	elseif not GT:IsInGroup(player.Info.Name) then
-		return false, ("%s is not in the group."):format(player.Info.Name)
+		return false, "PM_ERR_NOTINGROUP", {player.Info.Name}
 	elseif not UnitInRaid("player") then
-		return false, "Cannot demote when not in a raid."
+		return false, "PM_DEMOTE_NORAID"
 	end
 	if GT:IsGroupLeader() then
-		if self:IsLocked(player) then return false, "Target player is locked and cannot be modified." end
+		if self:IsLocked(player) then return false, "PM_ERR_LOCKED" end
 		DemoteAssistant(player.Info.Name)
-		return ("Demoted %s."):format(player.Info.Name)
+		return "PM_DEMOTE_SUCCESS", {player.Info.Name}
 	else
-		return false, ("Cannot demote %s, insufficient permissions."):format(player.Info.Name)
+		return false, "PM_DEMOTE_NOPRIV", {player.Info.Name}
 	end
-	return false, "Unknown error occurred."
 end

 --- Check if a certain command is on the blacklist/whitelist.
@@ -670,23 +709,22 @@ end
 --
 function PM:List(command, list)
 	if not CCM:HasCommand(command) then
-		return false, ("%q is not a registered command."):format(command)
+		return false, "CM_ERR_NOTREGGED", {command}
 	end
 	command = CCM:GetRealName(command)
+	local mode = self:GetListMode()
 	if list then
 		List[command] = true
-		local mode = "blacklist"
-		if self:GetListMode() == MODE_WHITELIST then
-			mode = "whitelist"
+		if mode == MODE_WHITELIST then
+			return "PM_LIST_ADDWHITE", {command}
 		end
-		return ("Added %s to %s."):format(command, mode)
+		return "PM_LIST_ADDBLACK", {command}
 	end
 	List[command] = false
-	local mode = "blacklist"
-	if self:GetListMode() == MODE_WHITELIST then
-		mode = "whitelist"
+	if mode == MODE_WHITELIST then
+		return "PM_LIST_REMOVEWHITE", {command}
 	end
-	return ("Removed %s from %s."):format(command, mode)
+	return "PM_LIST_REMOVEBLACK", {command}
 end

 --- Dynamically add or remove an item from the list.
@@ -733,11 +771,11 @@ end
 --
 function PM:SetListMode(mode)
 	if mode == MODE_WHITELIST then
-		C.Global["PLAYER_MANAGER"]["LIST_MODE"] = MODE_WHITELIST
-		return "Now using list as whitelist."
+		self.Data.LIST_MODE = MODE_WHITELIST
+		return "PM_LIST_SETWHITE"
 	else
-		C.Global["PLAYER_MANAGER"]["LIST_MODE"] = MODE_BLACKLIST
-		return "Now using list as blacklist."
+		self.Data.LIST_MODE = MODE_BLACKLIST
+		return "PM_LIST_SETBLACK"
 	end
 end

@@ -745,5 +783,5 @@ end
 -- @return List mode, possible values: 0/1, as set by MODE_BLACKLIST and MODE_WHITELIST.
 --
 function PM:GetListMode()
-	return C.Global["PLAYER_MANAGER"]["LIST_MODE"]
+	return self.Data.LIST_MODE
 end
diff --git a/QueueManager.lua b/QueueManager.lua
index f84bcad..73e849b 100644
--- a/QueueManager.lua
+++ b/QueueManager.lua
@@ -1,5 +1,5 @@
---[[
-	* Copyright (c) 2011 by Adam Hellberg.
+--[[
+	* Copyright (c) 2011-2012 by Adam Hellberg.
 	*
 	* This file is part of Command.
 	*
@@ -38,6 +38,7 @@ C.QueueManager = {
 	Announced = false
 }

+local L = function(k) return C.LocaleManager:GetActive()[k] end
 local QM = C.QueueManager

 --- Contains information about various dungeon types.
@@ -225,7 +226,7 @@ function QM:Queue(index)
 	SetCVar("Sound_EnableSFX", 1)
 	self.Current = name
 	self.Announced = false
-	return ("Starting queue for %s, please select your role(s)... Type !cancel to cancel."):format(tostring(QM.Current))
+	return "QM_QUEUE_START", {tostring(QM.Current)}
 end

 --- Cancel the queueing/rolechecking.
@@ -235,7 +236,7 @@ function QM:Cancel()
 	self.QueuedByCommand = false
 	self.Announced = false
 	LeaveLFG()
-	return "Left the LFG queue."
+	return "QM_CANCEL"
 end

 --- Causes player to accept a pending LFG invite.
@@ -245,7 +246,7 @@ function QM:Accept()
 	self.QueuedByCommand = false
 	self.Announced = false
 	AcceptProposal()
-	return "Accepted LFG invite."
+	return "QM_ACCEPT"
 end

 --- Announce the current status of LFG to group.
@@ -258,12 +259,14 @@ function QM:Announce(_, elapsed)
 		local mode = (select(1, GetLFGMode()))
 		if mode ~= nil then self.LastMode = mode end
 		if mode == "queued" and not self.Announced then
-			Command.ChatManager:SendMessage(("Now queueing for %s, type !cancel to cancel."):format(QM.Current), "PARTY")
+			C.ChatManager:SendMessage(L("QM_ANNOUNCE_QUEUEING"):format(QM.Current), "SMART")
 			self.Announced = true
 		elseif not mode then
-			local current = "Role check"
-			if self.LastMode ~= "rolecheck" then current = "LFG" end
-			Command.ChatManager:SendMessage(current .. " cancelled.", "PARTY")
+			if self.LastMode ~= "rolecheck" then
+				C.ChatManager:SendMessage(L("QM_ANNOUNCE_LFGCANCEL"), "SMART")
+			else
+				C.ChatManager:SendMessage(L("QM_ANNOUNCE_ROLECANCEL"), "SMART")
+			end
 			self.QueuedByCommand = false
 			self.Announced = false
 		end
diff --git a/RollManager.lua b/RollManager.lua
index 1018b4d..f69b0ef 100644
--- a/RollManager.lua
+++ b/RollManager.lua
@@ -1,5 +1,5 @@
---[[
-	* Copyright (c) 2011 by Adam Hellberg.
+--[[
+	* Copyright (c) 2011-2012 by Adam Hellberg.
 	*
 	* This file is part of Command.
 	*
@@ -18,6 +18,7 @@
 --]]

 local C = Command
+local L = function(k) return C.LocaleManager:GetActive()[k] end
 local GT = C.GroupTools
 local CM
 local CES = C.Extensions.String
@@ -32,9 +33,6 @@ C.RollManager = {

 local RM = C.RollManager

-local RollFormat = "%s rolls %d (%d-%d)" -- Not Used
-local RollMatch = "(%w+) rolls (%d+) %((%d+)-(%d+)%)" -- Thanks to ITSBTH for the pattern string
-
 local Rollers = {}
 local RollCount = 0

@@ -54,7 +52,7 @@ local function RollTimerUpdate(_, elapsed)
 	local left = RollTimer.Time - RollTimer.Current
 	if not RollTimer.LastWarning then RollTimer.LastWarning = 0 end
 	if (left <= 10 and left > 0) and ceil(RollTimer.Current) - RollTimer.LastWarning >= 5 then
-		CM:SendMessage(ceil(left) .. " seconds left to roll!", "SMART")
+		CM:SendMessage(L("RM_UPDATE_TIMELEFT"):format(ceil(left)), "SMART")
 		RollTimer.LastWarning = ceil(RollTimer.Current)
 	end

@@ -78,62 +76,62 @@ function RM:LoadSavedVars()
 		C.Global["ROLL_MANAGER"] = {}
 	end
 	self.Settings = C.Global["ROLL_MANAGER"]
-	if type(self.Settings["MIN_ROLL"]) ~= "number" then
-		self.Settings["MIN_ROLL"] = 1
+	if type(self.Settings.MIN_ROLL) ~= "number" then
+		self.Settings.MIN_ROLL = 1
 	end
-	if type(self.Settings["MAX_ROLL"]) ~= "number" then
-		self.Settings["MAX_ROLL"] = 100
+	if type(self.Settings.MAX_ROLL) ~= "number" then
+		self.Settings.MAX_ROLL = 100
 	end
-	if type(self.Settings["DEFAULT_TIME"]) ~= "number" then
-		self.Settings["DEFAULT_TIME"] = 20
+	if type(self.Settings.DEFAULT_TIME) ~= "number" then
+		self.Settings.DEFAULT_TIME = 20
 	end
 end

 function RM:SetMin(amount)
 	if type(amount) ~= "number" then
-		return false, "Invalid amount passed: " .. tostring(amount)
+		return false, "RM_ERR_INVALIDAMOUNT", {tostring(amount)}
 	end
 	if amount > self.Settings.MAX_ROLL then
-		return false, "Minimum roll number cannot be higher than maximum roll number!"
+		return false, "RM_SET_MINFAIL"
 	end
 	self.Settings.MIN_ROLL = amount
-	return "Sucessfully set minimum roll number to " .. amount .. "!"
+	return "RM_SET_MINSUCCESS", {amount}
 end

 function RM:SetMax(amount)
 	if type(amount) ~= "number" then
-		return false, "Invalid amount passed: " .. tostring(amount)
+		return false, "PM_ERR_INVALIDAMOUNT", {tostring(amount)}
 	end
 	if amount < self.Settings.MIN_ROLL then
-		return false, "Maximum roll number cannot be higher than minimum roll number!"
+		return false, "PM_SET_MAXFAIL"
 	end
 	self.Settings.MAX_ROLL = amount
-	return "Sucessfully set maximum roll number to " .. amount .. "!"
+	return "PM_SET_MAXSUCCESS", {amount}
 end

 function RM:SetTime(amount)
 	if type(amount) ~= "number" then
-		return false, "Invalid amount passed: " .. tostring(amount)
+		return false, "RM_ERR_INVALIDAMOUNT", {tostring(amount)}
 	end
 	if amount <= 0 then
-		return false, "Amount must be larger than zero (0)."
+		return false, "RM_SET_TIMEFAIL"
 	end
 	self.Settings.DEFAULT_TIME = amount
-	return "Successfully set default roll time to " .. amount .. "!"
+	return "RM_SET_TIMESUCCESS", {amount}
 end

 function RM:StartRoll(sender, item, time)
 	if RM.Running then
-		return false, "A roll is already in progress, wait for it to complete or use roll stop."
+		return false, "RM_START_RUNNING"
 	end
 	time = tonumber(time) or self.Settings.DEFAULT_TIME
 	RollTimer.Time = time
 	if not sender then
-		return false, "Could not identify sender: " .. tostring(sender) .. ". Aborting roll..."
+		return false, "RM_START_SENDER", {tostring(sender)}
 	end
 	self.NumGroupMembers = GT:GetNumGroupMembers()
 	if self.NumGroupMembers <= 0 then
-		return false, "Could not start roll, not enough group members!"
+		return false, "RM_START_MEMBERS"
 	end
 	self.Running = true
 	self.Sender = sender
@@ -142,16 +140,11 @@ function RM:StartRoll(sender, item, time)
 	if item then
 		self.Item = item
 		RollTimer.Frame:SetScript("OnUpdate", RollTimerUpdate)
-		return ("%s started a roll for %s, ends in %d seconds! Type /roll %d %d. Type !roll pass to pass."):format(self.Sender, self.Item, time, self.Settings.MIN_ROLL, self.Settings.MAX_ROLL)
+		return "RM_START_SUCCESSITEM", {self.Sender, self.Item, time, self.Settings.MIN_ROLL, self.Settings.MAX_ROLL}
 	else
 		RollTimer.Frame:SetScript("OnUpdate", RollTimerUpdate)
-		return ("%s started a roll, ends in %d seconds! Type /roll %d %d. Type !roll pass to pass."):format(self.Sender, time, self.Settings.MIN_ROLL, self.Settings.MAX_ROLL)
+		return "RM_START_SUCCESS", {self.Sender, time, self.Settings.MIN_ROLL, self.Settings.MAX_ROLL}
 	end
-	-- We shouldn't reach this place
-	self.Running = false
-	self.Sender = nil
-	self.Item = nil
-	return false, "Unknown error occurred"
 end

 function RM:StopRoll(finished, expire)
@@ -159,61 +152,59 @@ function RM:StopRoll(finished, expire)
 		self:AnnounceResult(expire)
 	else
 		if not self.Running then
-			return false, "No roll is currently in progress!"
+			return false, "RM_ERR_NOTRUNNING"
 		end
 	end
 	self.Running = false
 	self.Sender = nil
 	self.Item = nil
 	wipe(Rollers)
-	return "Roll has been stopped."
+	return "RM_STOP_SUCCESS"
 end

 function RM:DoRoll(min, max)
 	min = min or self.Settings.MIN_ROLL
 	max = max or self.Settings.MAX_ROLL
 	RandomRoll(min, max)
-	return "Done! Executed RandomRoll(" .. min .. ", " .. max .. ")"
+	return "RM_DO_SUCCESS", {min, max}
 end

 function RM:AddRoll(name, roll)
 	if CET:HasKey(Rollers, name) then
-		CM:SendMessage(name .. " has already rolled! (" .. Rollers[name] .. ")", "SMART")
+		CM:SendMessage(L("RM_ROLLEXISTS"):format(name, Rollers[name]), "SMART")
 		return
 	end
 	Rollers[name] = tonumber(roll)
 	RollCount = RollCount + 1
-	CM:SendMessage(("%d/%d players have rolled!"):format(RollCount, self.NumGroupMembers), "SMART")
-	--if RollCount >= self.NumGroupMembers then RM:StopRoll(true) end
+	CM:SendMessage(L("RM_ROLLPROGRESS"):format(RollCount, self.NumGroupMembers), "SMART")
 end

 function RM:PassRoll(name)
 	name = name or UnitName("player")
 	if CET:HasKey(Rollers, name) then
-		return false, ("%s has already rolled (%d)"):format(name, Rollers[name])
+		return false, "RM_ROLLEXISTS", {name, Rollers[name]}
 	end
 	Rollers[name] = -1
 	RollCount = RollCount + 1
-	--if RollCount >= self.NumGroupMembers then RM:StopRoll(true) end
-	return ("%s has passed on the roll."):format(name)
+	return "RM_PASS_SUCCESS", {name}
 end

 function RM:GetTime()
 	if self.Running then
-		return ("%d seconds remaining!"):format(max(ceil(RollTimer.Time - RollTimer.Current), 0))
+		return "RM_TIME_LEFT", {max(ceil(RollTimer.Time - RollTimer.Current), 0)}
 	else
-		return false, "No roll is currently in progress!"
+		return false, "RM_ERR_NOTRUNNING"
 	end
 end

 function RM:AnnounceResult(expire)
 	if expire then
-		CM:SendMessage("Roll time expired! Results...", "SMART")
+		CM:SendMessage(L("RM_ANNOUNCE_EXPIRE"), "SMART")
 	else
-		CM:SendMessage("Everyone has rolled! Results...", "SMART")
+		CM:SendMessage(L("RM_ANNOUNCE_FINISH"), "SMART")
 	end
 	if RollCount <= 0 then
-		CM:SendMessage("Noone rolled, there is no winner!", "SMART")
+		CM:SendMessage(L("RM_ANNOUNCE_EMPTY"), "SMART")
 		return
 	end
 	local name
@@ -233,42 +224,42 @@ function RM:AnnounceResult(expire)
 	end
 	local msg
 	if roll == -1 then
-		msg = "Everyone passed on the roll! There is no winner"
 		if self.Item then
-			msg = msg .. " for " .. self.Item
+			msg = L("RM_ANNOUNCE_PASSITEM"):format(self.Item)
+		else
+			msg = L("RM_ANNOUNCE_PASS")
 		end
-		msg = msg .. "."
 		CM:SendMessage(msg, "SMART")
 	elseif numAdditional <= 0 then
-		msg = "The winner is: " .. name .. "! With a roll of " .. roll
 		if self.Item then
-			msg = msg .. " for " .. self.Item
+			msg = L("RM_ANNOUNCE_WINITEM"):format(name, roll, self.Item)
+		else
+			msg = L("RM_ANNOUNCE_WIN"):format(name, roll)
 		end
-		msg = msg .. "."
 		CM:SendMessage(msg, "SMART")
 	else
-		msg = "There are multiple winners"
 		if self.Item then
-			msg = msg .. " for " .. self.Item
+			msg = L("RM_ANNOUNCE_MULTIPLEITEM"):format(self.Item)
+		else
+			msg = L("RM_ANNOUNCE_MULTIPLE")
 		end
-		msg = msg .. ":"
 		CM:SendMessage(msg, "SMART")
-		CM:SendMessage(name .. " with a roll of " .. roll, "SMART")
+		CM:SendMessage(L("RM_ANNOUNCE_WINNER"):format(name, roll), "SMART")
 		for k,v in pairs(additional) do
-			CM:SendMessage(k .. " with a roll of " .. v, "SMART")
+			CM:SendMessage(L("RM_ANNOUNCE_WINNER"):format(k, v), "SMART")
 		end
 	end
 end

 function RM:ParseMessage(msg)
-	if not string.match(msg, RollMatch) then return end
-	local name, roll, minRoll, maxRoll = msg:match(RollMatch)
+	if not string.match(msg, L("RM_MATCH")) then return end
+	local name, roll, minRoll, maxRoll = msg:match(L("RM_MATCH"))
 	roll = tonumber(roll)
 	minRoll = tonumber(minRoll)
 	maxRoll = tonumber(maxRoll)
 	print(name, roll, minRoll, maxRoll)
 	if minRoll ~= self.Settings.MIN_ROLL or maxRoll ~= self.Settings.MAX_ROLL then
-		CM:SendMessage(name .. " specified too high or low roll region, not including their roll!", "SMART")
+		CM:SendMessage(L("RM_ERR_INVALIDROLL"):format(name), "SMART")
 		return
 	end
 	self:AddRoll(name, roll)
diff --git a/String.lua b/String.lua
index 63f6c90..a725727 100644
--- a/String.lua
+++ b/String.lua
@@ -1,5 +1,5 @@
---[[
-	* Copyright (c) 2011 by Adam Hellberg.
+--[[
+	* Copyright (c) 2011-2012 by Adam Hellberg.
 	*
 	* This file is part of Command.
 	*
diff --git a/Table.lua b/Table.lua
index aeb3d81..2bedc23 100644
--- a/Table.lua
+++ b/Table.lua
@@ -1,5 +1,5 @@
---[[
-	* Copyright (c) 2011 by Adam Hellberg.
+--[[
+	* Copyright (c) 2011-2012 by Adam Hellberg.
 	*
 	* This file is part of Command.
 	*
diff --git a/load.xml b/load.xml
index 19f2c8e..d1498bf 100644
--- a/load.xml
+++ b/load.xml
@@ -1,5 +1,5 @@
-<!--
-	* Copyright (c) 2011 by Adam Hellberg.
+<!--
+	* Copyright (c) 2011-2012 by Adam Hellberg.
 	*
 	* This file is part of Command.
 	*
@@ -22,6 +22,7 @@
 	<Script file="Command.lua" />
 	<Script file="Table.lua" />
 	<Script file="String.lua" />
+	<Include file="LocaleLoader.xml" />
 	<Script file="Logger.lua" />
 	<Script file="BattleNetTools.lua" />
 	<Script file="GroupTools.lua" />
diff --git a/locales/enUS.lua b/locales/enUS.lua
new file mode 100644
index 0000000..120a83e
--- /dev/null
+++ b/locales/enUS.lua
@@ -0,0 +1,446 @@
+--[[
+	* Copyright (c) 2011-2012 by Adam Hellberg.
+	*
+	* This file is part of Command.
+	*
+	* Command is free software: you can redistribute it and/or modify
+	* it under the terms of the GNU General Public License as published by
+	* the Free Software Foundation, either version 3 of the License, or
+	* (at your option) any later version.
+	*
+	* Command is distributed in the hope that it will be useful,
+	* but WITHOUT ANY WARRANTY; without even the implied warranty of
+	* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	* GNU General Public License for more details.
+	*
+	* You should have received a copy of the GNU General Public License
+	* along with Command. If not, see <http://www.gnu.org/licenses/>.
+--]]
+
+local L = {
+	-------------------
+	-- LocaleManager --
+	-------------------
+
+	LOCALE_NOT_LOADED = "The specified locale has not been loaded.",
+	LOCALE_UPDATE = "Set new locale to: %s",
+
+	-------------
+	-- General --
+	-------------
+
+	YES = "Yes",
+	NO = "No",
+
+	----------
+	-- Core --
+	----------
+
+	ADDON_LOAD = "AddOn loaded! Use /cmd help or !help for help.",
+	SVARS_OUTDATED = "Saved Variables out of date, resetting...",
+	NEWVERSION_NOTICE = "\124cffFF0000A new version of \124cff00FFFF%s\124cffFF0000 is available! \124cffFFFF00Check the site you downloaded from for the updated version.",
+	ENABLED = "AddOn \124cff00FF00enabled\124r.",
+	DISABLED = "AddOn \124cffFF0000disabled\124r.",
+	DEBUGENABLED = "Debugging \124cff00FF00enabled\124r.",
+	DEBUGDISABLED = "Debugging \124cffFF0000disabled\124r.",
+
+	---------------
+	-- AddonComm --
+	---------------
+
+	AC_ERR_PREFIX = "[FATAL] Failed to register AddOn prefix %q. Maximum number of prefixes reached on client.",
+	AC_ERR_MSGTYPE = "Invalid message type specified: %s",
+
+	AC_GROUP_NORESP = "No response from group, running updater...",
+	AC_GROUP_R_UPDATE = "Updated group members, controller: %s",
+	AC_GROUP_LEFT = "Left group, resetting group variables...",
+	AC_GROUP_WAIT = "Waiting for group response...",
+	AC_GROUP_REMOVE = "Detected that %s is no longer in the group, removing and updating group members...",
+	AC_GROUP_SYNC = "Detected group handlers out of date! Sending sync message...",
+
+	AC_GUILD_NORESP = "No response from guild, running updater...",
+	AC_GUILD_R_UPDATE = "Updated guild members, controller: %s",
+	AC_GUILD_WAIT = "Waiting for guild response...",
+
+	-----------------
+	-- ChatManager --
+	-----------------
+
+	CHAT_ERR_CMDCHAR = "Command char has to be of type string.",
+	CHAT_CMDCHAR_SUCCESS = "Successfully set the command char to: %s",
+	CHAT_HANDLE_NOTCONTROLLER = "Not controller instance for \124cff00FFFF%s\124r, aborting.",
+
+	--------------------
+	-- CommandManager --
+	--------------------
+
+	CM_ERR_NOTALLOWED = "%s is not allowed to be used, %s.",
+	CM_ERR_NOACCESS = "You do not have permission to use that command, %s. Required access level: %d. Your access level: %d.",
+	CM_ERR_NOTREGGED = "%q is not a registered command.",
+	CM_ERR_NOCMDCHAR = "No command character specified.",
+	CM_ERR_NOCHAT = "This command is not allowed to be used from the chat.",
+	CM_ERR_CHATONLY = "This command can only be used from the chat.",
+
+	CM_NO_HELP = "No help available.",
+
+	CM_DEFAULT_HELP = "Prints this help message.",
+	CM_DEFAULT_CHAT = "Type !commands for a listing of commands available.",
+	CM_DEFAULT_END = "End of help message.",
+
+	CM_COMMANDS_HELP = "Print all registered commands.",
+
+	CM_VERSION_HELP = "Print the version of Command.",
+	CM_VERSION = "%s",
+
+	CM_SET_HELP = "Control the settings of Command.",
+	CM_SET_USAGE = "Usage: set cmdchar|groupinvite|setlocale|locale",
+	CM_SET_GROUPINVITE_USAGE = "Usage: set groupinvite enable|disable|<time>",
+	CM_SET_SETLOCALE_USAGE = "Usage: set setlocale <locale>",
+	CM_SET_LOCALE_USAGE = "Usage: set locale reset|usemaster",
+
+	CM_MYLOCALE_HELP = "Let's users set their own locale.",
+	CM_MYLOCALE_SET = "Successfully set your locale to %s.",
+
+	CM_LOCK_HELP = "Lock a player.",
+	CM_UNLOCK_HELP = "Unlock a player.",
+
+	CM_GETACCESS_HELP = "Get the access level of a user.",
+	CM_GETACCESS_STRING = "%s's access is %d (%s)",
+
+	CM_SETACCESS_HELP = "Set the access level of a user.",
+	CM_SETACCESS_USAGE = "Usage: setaccess [player] <group>",
+
+	CM_OWNER_HELP = "Promote a player to owner rank.",
+
+	CM_ADMIN_HELP = "Promote a player to admin rank.",
+	CM_ADMIN_USAGE = "Usage: admin <name>",
+
+	CM_OP_HELP = "Promote a player to op rank.",
+
+	CM_USER_HELP = "Promote a player to user rank.",
+
+	CM_BAN_HELP = "Ban a player.",
+	CM_BAN_USAGE = "Usage: ban <name>",
+
+	CM_ACCEPTINVITE_HELP = "Accepts a pending group invite.",
+	CM_ACCEPTINVITE_NOTACTIVE = "No pending invites active.",
+	CM_ACCEPTINVITE_EXISTS = "I am already in a group.",
+	CM_ACCEPTINVITE_SUCCESS = "Accepted group invite!",
+
+	CM_INVITE_HELP = "Invite a player to group.",
+
+	CM_INVITEME_HELP = "Player who issued the command will be invited to group.",
+
+	CM_DENYINVITE_HELP = "Player issuing this command will no longer be sent invites from this AddOn.",
+
+	CM_ALLOWINVITE_HELP = "Player issuing this command will receive invites sent from this AddOn.",
+
+	CM_KICK_HELP = "Kick a player from group with optional reason (Requires confirmation).",
+	CM_KICK_USAGE = "Usage: kick <player> [reason]",
+
+	CM_KINGME_HELP = "Player issuing this command will be promoted to group leader.",
+
+	CM_OPME_HELP = "Player issuing this command will be promoted to raid assistant.",
+
+	CM_DEOPME_HELP = "Player issuing this command will be demoted from assistant status.",
+
+	CM_LEADER_HELP = "Promote a player to group leader.",
+	CM_LEADER_USAGE = "Usage: leader <name>",
+
+	CM_PROMOTE_HELP = "Promote a player to raid assistant.",
+	CM_PROMOTE_USAGE = "Usage: promote <name>",
+
+	CM_DEMOTE_HELP = "Demote a player from assistant status.",
+	CM_DEMOTE_USAGE = "Usage: demote <name>",
+
+	CM_QUEUE_HELP = "Enter the LFG queue for the specified category.",
+	CM_QUEUE_USAGE = "Usage: queue <type>",
+	CM_QUEUE_INVALID = "No such dungeon type: %q.",
+
+	CM_LEAVELFG_HELP = "Leave the LFG queue.",
+	CM_LEAVELFG_FAIL = "Not queued by command, unable to cancel.",
+
+	CM_ACCEPTLFG_HELP = "Causes you to accept the LFG invite.",
+	CM_ACCEPTLFG_FAIL = "Not currently queued by command.",
+
+	CM_CONVERT_HELP = "Convert group to party or raid.",
+	CM_CONVERT_USAGE = "Usage: convert party||raid",
+	CM_CONVERT_LFG = "LFG groups cannot be converted.",
+	CM_CONVERT_NOGROUP = "Cannot convert if not in a group.",
+	CM_CONVERT_NOLEAD = "Cannot convert group, not leader.",
+	CM_CONVERT_PARTY = "Converted raid to party.",
+	CM_CONVERT_PARTYFAIL = "Group is already a party.",
+	CM_CONVERT_RAID = "Converted party to raid.",
+	CM_CONVERT_RAIDFAIL = "Group is already a raid.",
+	CM_CONVERT_INVALID = "Invalid group type, only \"party\" or \"raid\" allowed.",
+
+	CM_LIST_HELP = "Toggle status of a command on the blacklist/whitelist.",
+	CM_LIST_USAGE = "Usage: list <command>",
+
+	CM_LISTMODE_HELP = "Toggle list between being a blacklist and being a whitelist.",
+
+	CM_GROUPALLOW_HELP = "Allow a group to use a specific command.",
+	CM_GROUPALLOW_USAGE = "Usage: groupallow <group> <command>",
+
+	CM_GROUPDENY_HELP = "Deny a group to use a specific command.",
+	CM_GROUPDENY_USAGE = "Usage: groupdeny <group> <command>",
+
+	CM_RESETGROUPACCESS_HELP = "Reset the group's access to a specific command.",
+	CM_RESETGROUPACCESS_USAGE = "Usage: resetgroupaccess <group> <command>",
+
+	CM_USERALLOW_HELP = "Allow a user to use a specific command.",
+	CM_USERALLOW_USAGE = "Usage: userallow <player> <command>",
+
+	CM_USERDENY_HELP = "Deny a user to use a specific command.",
+	CM_USERDENY_USAGE = "Usage: userdeny <player> <command>",
+
+	CM_RESETUSERACCESS_HELP = "Reset the user's access to a specific command.",
+	CM_RESETUSERACCESS_USAGE = "Usage: resetuseraccess <player> <command>",
+
+	CM_TOGGLE_HELP = "Toggle AddOn on and off.",
+
+	CM_TOGGLEDEBUG_HELP = "Toggle debugging mode on and off.",
+
+	CM_READYCHECK_HELP = "Respond to ready check or initiate a new one.",
+	CM_READYCHECK_ISSUED = "%s issued a ready check!",
+	CM_READYCHECK_NOPRIV = "Cannot initiate ready check when not leader or assistant.",
+	CM_READYCHECK_INACTIVE = "Ready check not running or I have already responded.",
+	CM_READYCHECK_ACCEPTED = "Accepted ready check.",
+	CM_READYCHECK_DECLINED = "Declined ready check.",
+	CM_READYCHECK_INVALID = "Invalid argument: %s",
+	CM_READYCHECK_FAIL = "Failed to accept or decline ready check.",
+
+	CM_LOOT_HELP = "Provides various loot functions.",
+	CM_LOOT_USAGE = "Usage: loot type||threshold||master||pass",
+	CM_LOOT_LFG = "Cannot use loot command in LFG group.",
+	CM_LOOT_NOMETHOD = "No loot method specified.",
+	CM_LOOT_NOTHRESHOLD = "No loot threshold specified.",
+	CM_LOOT_NOMASTER = "No master looter specified.",
+
+	CM_ROLL_HELP = "Provides tools for managing or starting/stopping rolls.",
+	CM_ROLL_USAGE = "Usage: roll [start||stop||pass||time||do||set]",
+	CM_ROLL_START_USAGE = "Usage: roll start <[time] [item]>",
+	CM_ROLL_SET_USAGE = "Usage: roll set min||max||time <amount>",
+
+	CM_RAIDWARNING_HELP = "Sends a raid warning.",
+	CM_RAIDWARNING_USAGE = "Usage: raidwarning <message>",
+	CM_RAIDWARNING_NORAID = "Cannot send raid warning when not in a raid group.",
+	CM_RAIDWARNING_NOPRIV = "Cannot send raid warning: Not raid leader or assistant.",
+	CM_RAIDWARNING_SENT = "Sent raid warning.",
+
+	------------
+	-- Events --
+	------------
+
+	E_LFGPROPOSAL = "Group has been found, type !accept to make me accept the invite.",
+	E_LFGFAIL = "LFG failed, use !queue <type> to requeue.",
+	E_READYCHECK = "%s issued a ready check, type !rc accept to make me accept it or !rc deny to deny it.",
+	E_GROUPINVITE = "Type !acceptinvite to make me accept the group invite.",
+
+	------------------
+	-- EventHandler --
+	------------------
+
+	EH_REGISTERED = "%q registered.",
+
+	----------------------------
+	-- GroupInvite (Core-Sub) --
+	----------------------------
+
+	GI_ENABLED = "Group Invite (Announce) enabled.",
+	GI_DISABLED = "Group Invite (Announce) disabled.",
+	GI_DELAY_NUM = "Delay has to be a number.",
+	GI_DELAY_MAX = "Delay cannot be greater than 50 seconds.",
+	GI_DELAY_SET = "Group Invite (Announce) delay set to %d seconds.",
+	GI_DELAY_DISABLED = "Group Invite (Announce) delay disabled.",
+
+	------------
+	-- Logger --
+	------------
+
+	LOGGER_ERR_UNDEFINED = "Undefined logger level passed (%q)",
+	LOGGER_PREFIX_MAIN = "\124cff00FF00[%s]\124r",
+	LOGGER_PREFIX_DEBUG = " \124cffBBBBFFDebug\124r",
+	LOGGER_PREFIX_NORMAL = "",
+	LOGGER_PREFIX_WARNING = " \124cffFFFF00Warning\124r",
+	LOGGER_PREFIX_ERROR = " \124cffFF0000ERROR\124r",
+
+	-----------------
+	-- LootManager --
+	-----------------
+
+	LOOT_METHOD_GROUP = "Group Loot",
+	LOOT_METHOD_FFA = "Free For All",
+	LOOT_METHOD_MASTER = "Master Looter",
+	LOOT_METHOD_NEEDGREED = "Need Before Greed",
+	LOOT_METHOD_ROUNDROBIN = "Round Robin",
+
+	LOOT_THRESHOLD_UNCOMMON = "Uncommon",
+	LOOT_THRESHOLD_RARE = "Rare",
+	LOOT_THRESHOLD_EPIC = "Epic",
+	LOOT_THRESHOLD_LEGENDARY = "Legendary",
+	LOOT_THRESHOLD_ARTIFACT = "Artifact",
+	LOOT_THRESHOLD_HEIRLOOM = "Heirloom",
+	LOOT_THRESHOLD_UNKNOWN = "Unknown",
+
+	LOOT_MASTER_NOEXIST = "%q is not in the group and cannot be set as the master looter.",
+
+	LOOT_SM_NOLEAD = "Unable to change loot method, not group leader.",
+	LOOT_SM_DUPE = "The loot method is already set to %s!",
+	LOOT_SM_SUCCESS = "Successfully set the loot method to %s!",
+	LOOT_SM_SUCCESSMASTER = "Successfully set the loot method to %s (%s)!",
+
+	LOOT_SLM_NOLOEAD = "Unable to change master looter, not group leader.",
+	LOOT_SLM_METHOD = "Cannot set master looter when loot method is set to %s.",
+	LOOT_SLM_SPECIFY = "Master looter not specified.",
+	LOOT_SLM_SUCCESS = "Successfully set %s as the master looter!",
+
+	LOOT_ST_NOLEAD = "Unable to change loot threshold, not group leader.",
+	LOOT_ST_INVALID = "Invalid loot threshold specified, please specify a loot threshold between 2 and 7 (inclusive).",
+	LOOT_ST_SUCCESS = "Successfully set the loot threshold to %s!",
+
+	LOOT_SP_PASS = "%s is now passing on loot.",
+	LOOT_SP_ROLL = "%s is not passing on loot.",
+
+	-------------------
+	-- PlayerManager --
+	-------------------
+
+	PM_ERR_NOCOMMAND = "No command specified.",
+	PM_ERR_LOCKED = "Target player is locked and cannot be modified.",
+	PM_ERR_NOTINGROUP = "%s is not in the group.",
+
+	PM_ACCESS_ALLOWED = "%q is now allowed for %s.",
+	PM_ACCESS_DENIED = "%q is now denied for %s.",
+
+	PM_KICK_REASON = "%s has been kicked on %s's request. (Reason: %s)",
+	PM_KICK = "%s has been kicked on %s's request.",
+	PM_KICK_NOTIFY = "%s was kicked on your request.",
+	PM_KICK_TARGET = "You have ben kicked out of the group by %s.",
+	PM_KICK_DENIED = "%s's request to kick %s has been denied.",
+	PM_KICK_POPUP = "%s wants to kick %s. Confirm?",
+	PM_KICK_SELF = "Cannot kick myself.",
+	PM_KICK_FRIEND = "Cannot kick my friend.",
+	PM_KICK_DEFAULTREASON = "%s used !kick command.",
+	PM_KICK_WAIT = "Awaiting confirmation to kick %s...",
+	PM_KICK_NOPRIV = "Unable to kick %s from group. Not group leader or assistant.",
+
+	PM_PLAYER_CREATE = "Created player %q with default settings.",
+	PM_PLAYER_UPDATE = "Updated player %q.",
+
+	PM_GA_REMOVED = "%q removed from group %s.",
+	PM_GA_EXISTSALLOW = "%q already has that command on the allow list.",
+	PM_GA_EXISTSDENY = "%q already has that command on the deny list.",
+
+	PM_PA_REMOVED = "%q removed from %s.",
+	PM_PA_EXISTSALLOW = "%s already has that command on the allow list.",
+	PM_PA_EXISTSDENY = "%s already has that command on the deny list.",
+
+	PM_LOCKED = "Player %s has been locked.",
+	PM_UNLOCKED = "Player %s has been unlocked",
+
+	PM_SAG_SELF = "Cannot modify my own access level.",
+	PM_SAG_NOEXIST = "No such access group: %q",
+	PM_SAG_SET = "Set the access level of %q to %d (%s).",
+
+	PM_INVITE_SELF = "Cannot invite myself to group.",
+	PM_INVITE_INGROUP = "%s is already in the group.",
+	PM_INVITE_FULL = "The group is already full.",
+	PM_INVITE_NOTIFYTARGET = "Invited you to the group.",
+	PM_INVITE_NOTIFY = "%s invited you to the group, %s. Whisper !blockinvites to block these invites.",
+	PM_INVITE_SUCCESS = "Invited %s to group.",
+	PM_INVITE_BLOCKED = "%s does not wish to be invited.",
+	PM_INVITE_NOPRIV = "Unable to invite %s to group. Not group leader or assistant.",
+
+	PM_DI_BLOCKING = "You are now blocking invites, whisper !allowinvites to receive them again.",
+	PM_DI_SUCCESS = "%s is no longer receiving invites.",
+	PM_DI_FAIL = "You are already blocking invites.",
+
+	PM_AI_ALLOWING = "You are now allowing invites, whisper !blockinvites to block them.",
+	PM_AI_SUCCESS = "%s is now receiving invites.",
+	PM_AI_FAIL = "You are already allowing invites.",
+
+	PM_LEADER_SELF = "Cannot promote myself to leader.",
+	PM_LEADER_DUPE = "%s is already leader.",
+	PM_LEADER_SUCCESS = "Promoted %s to group leader.",
+	PM_LEADER_NOPRIV = "Cannot promote %s to group leader, insufficient permissions.",
+
+	PM_ASSIST_SELF = "Cannot promote myself to assistant.",
+	PM_ASSIST_DUPE = "%s is already assistant.",
+	PM_ASSIST_NORAID = "Cannot promote to assistant when not in a raid.",
+	PM_ASSIST_SUCCESS = "Promoted %s to assistant.",
+	PM_ASSIST_NOPRIV = "Cannot promote %s to assistant, insufficient permissions.",
+
+	PM_DEMOTE_SELF = "Cannot demote myself.",
+	PM_DEMOTE_INVALID = "%s is not an assistant, can only demote assistants.",
+	PM_DEMOTE_NORAID = "Cannot demote when not in a raid.",
+	PM_DEMOTE_SUCCESS = "Demoted %s.",
+	PM_DEMOTE_NOPRIV = "Cannot demote %s, insufficient permissions.",
+
+	PM_LIST_ADDWHITE = "Added %s to whitelist.",
+	PM_LIST_ADDBLACK = "Added %s to blacklist.",
+	PM_LIST_REMOVEWHITE = "Removed %s from whitelist.",
+	PM_LIST_REMOVEBLACK = "Removed %s from blacklist.",
+	PM_LIST_SETWHITE = "Now using list as whitelist.",
+	PM_LIST_SETBLACK = "Now using list as blacklist.",
+
+	------------------
+	-- QueueManager --
+	------------------
+
+	QM_QUEUE_START = "Starting queue for %s, please select your role(s)... Type !cancel to cancel.",
+	QM_CANCEL = "Left the LFG queue.",
+	QM_ACCEPT = "Accepted LFG invite.",
+	QM_ANNOUNCE_QUEUEING = "Now queueing for %s, type !cancel to cancel.",
+	QM_ANNOUNCE_ROLECANCEL = "Role Check cancelled.",
+	QM_ANNOUNCE_LFGCANCEL = "LFG cancelled.",
+
+	-----------------
+	-- RollManager --
+	-----------------
+
+	RM_ERR_INVALIDAMOUNT = "Invalid amount passed: %s.",
+	RM_ERR_NOTRUNNING = "No roll is currently in progress.",
+	RM_ERR_INVALIDROLL = "%s specified too high or too low roll region, not including their roll.",
+
+	RM_MATCH = "(%w+) rolls (%d+) %((%d+)-(%d+)%)",
+
+	RM_ROLLEXISTS = "%s has already rolled! (%d)",
+	RM_ROLLPROGRESS = "%d/%d players have rolled!",
+
+	RM_UPDATE_TIMELEFT = "%d seconds left to roll!",
+
+	RM_SET_MINFAIL = "Minimum roll number cannot be higher than maximum roll number!",
+	RM_SET_MINSUCCESS = "Successfully set minimum roll number to %d!",
+	RM_SET_MAXFAIL = "Maximum roll number cannot be higher than minimum roll number!",
+	RM_SET_MAXSUCCESS = "Successfully set maximum roll number to %d!",
+	RM_SET_TIMEFAIL = "Amount must be larger than zero (0).",
+	RM_SET_TIMESUCCESS = "Successfully set default roll time to %d!",
+
+	RM_START_RUNNING = "A roll is already in progress, wait for it to complete or use roll stop.",
+	RM_START_SENDER = "Could not identify sender: %s. Aborting roll...",
+	RM_START_MEMBERS = "Could not start roll, not enough group members!",
+	RM_START_SUCCESS = "%s started a roll, ends in %d seconds! Type /roll %d %d. Type !roll pass to pass.",
+	RM_START_SUCCESSITEM = "%s started a roll for %s, ends in %d seconds! Type /roll %d %d. Type !roll pass to pass.",
+
+	RM_STOP_SUCCESS = "Roll has been stopped.",
+
+	RM_DO_SUCCESS = "Done! Executed RandomRoll(%d, %d)",
+
+	RM_PASS_SUCCESS = "%s has passed on the roll.",
+
+	RM_TIME_LEFT = "%d seconds remaining!",
+
+	RM_ANNOUNCE_EXPIRE = "Roll time expired! Results...",
+	RM_ANNOUNCE_FINISH = "Everyone has rolled! Results...",
+	RM_ANNOUNCE_EMPTY = "Noone rolled, there is no winner!",
+	RM_ANNOUNCE_PASS = "Everyone passed on the roll, there is no winner!",
+	RM_ANNOUNCE_PASSITEM = "Everyone passed on the roll, there is no winner for %s!",
+	RM_ANNOUNCE_WIN = "The winner is: %s! With a roll of %d.",
+	RM_ANNOUNCE_WINITEM = "The winner is: %s! With a roll of %d for %s.",
+	RM_ANNOUNCE_MULTIPLE = "There are multiple winners:",
+	RM_ANNOUNCE_MULTIPLEITEM = "There are multiple winners for %s:",
+	RM_ANNOUNCE_WINNER = "%s with a roll of %d."
+}
+
+Command.LocaleManager:Register("enUS", L)
diff --git a/locales/svSE.lua b/locales/svSE.lua
new file mode 100644
index 0000000..e54c1bc
--- /dev/null
+++ b/locales/svSE.lua
@@ -0,0 +1,448 @@
+--[[
+	* Copyright (c) 2011-2012 by Adam Hellberg.
+	*
+	* This file is part of Command.
+	*
+	* Command is free software: you can redistribute it and/or modify
+	* it under the terms of the GNU General Public License as published by
+	* the Free Software Foundation, either version 3 of the License, or
+	* (at your option) any later version.
+	*
+	* Command is distributed in the hope that it will be useful,
+	* but WITHOUT ANY WARRANTY; without even the implied warranty of
+	* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	* GNU General Public License for more details.
+	*
+	* You should have received a copy of the GNU General Public License
+	* along with Command. If not, see <http://www.gnu.org/licenses/>.
+--]]
+
+local LM = Command.LocaleManager
+
+local L = {
+	-------------------
+	-- LocaleManager --
+	-------------------
+
+	LOCALE_NOT_LOADED = "Det specifierade språket är inte initialiserat.",
+	LOCALE_UPDATE = "Nytt språk inställt till: %s",
+
+	-------------
+	-- General --
+	-------------
+
+	YES = "Ja",
+	NO = "Nej",
+
+	----------
+	-- Core --
+	----------
+
+	ADDON_LOAD = "AddOn initialiserad! Använd /cmd help eller !help för hjälp.",
+	SVARS_OUTDATED = "Sparade variabler inaktuella, återställer...",
+	NEWVERSION_NOTICE = "\124cffFF0000En ny version av \124cff00FFFF%s\124cffFF0000 är tillgänglig! \124cffFFFF00Gå till sidan där du laddade ned Command för att installera den.",
+	ENABLED = "AddOn \124cff00FF00aktiverad\124r.",
+	DISABLED = "AddOn \124cffFF0000avaktiverad\124r.",
+	DEBUGENABLED = "Debugging \124cff00FF00aktiverat\124r.",
+	DEBUGDISABLED = "Debugging \124cffFF0000avaktiverat\124r.",
+
+	---------------
+	-- AddonComm --
+	---------------
+
+	AC_ERR_PREFIX = "[KRITISK] Misslyckades med att registrera AddOn prefix %q. Maximalt antal prefix har nåtts på klienten.",
+	AC_ERR_MSGTYPE = "Ogiltig meddelandetyp: %s",
+
+	AC_GROUP_NORESP = "Inget svar från grupp, uppdaterar...",
+	AC_GROUP_R_UPDATE = "Gruppmedlemmar uppdaterade, kontrollerare: %s",
+	AC_GROUP_LEFT = "Grupp lämnad, återställer gruppvariabler...",
+	AC_GROUP_WAIT = "Väntar på gruppsvar...",
+	AC_GROUP_REMOVE = "Upptäckte att %s inte längre är i gruppen, tar bort och uppdaterar gruppmedlemmar...",
+	AC_GROUP_SYNC = "Grupphanterare inaktuella! Skickar synkmeddelande...",
+
+	AC_GUILD_NORESP = "Inget svar från guild, uppdaterar...",
+	AC_GUILD_R_UPDATE = "Guildmedlemmar uppdaterade, kontrollerare: %s",
+	AC_GUILD_WAIT = "Väntar på guildsvar...",
+
+	-----------------
+	-- ChatManager --
+	-----------------
+
+	CHAT_ERR_CMDCHAR = "Kommandotecken måste vara ett tecken.",
+	CHAT_CMDCHAR_SUCCESS = "Nytt kommandotecken inställt till: %s",
+	CHAT_HANDLE_NOTCONTROLLER = "Ej kontroller för \124cff00FFFF%s\124r, avbryter.",
+
+	--------------------
+	-- CommandManager --
+	--------------------
+
+	CM_ERR_NOTALLOWED = "%s är inte tillåtet att användas, %s.",
+	CM_ERR_NOACCESS = "Du har inte tillåtelse att använda det kommandot, %s. Behörighet som krävs: %d. Din behörighet: %d.",
+	CM_ERR_NOTREGGED = "%q är inte ett registrerat kommando.",
+	CM_ERR_NOCMDCHAR = "Inget kommandotecken angivet.",
+	CM_ERR_NOCHAT = "Det här kommandot kan inte användas från chatten.",
+	CM_ERR_CHATONLY = "Det här kommandot kan endast användas från chatten.",
+
+	CM_NO_HELP = "Ingen hjälp tillgänglig.",
+
+	CM_DEFAULT_HELP = "Visar det här hjälpmeddelandet.",
+	CM_DEFAULT_CHAT = "Skriv !commands för en lista över kommandon.",
+	CM_DEFAULT_END = "Slut på hjälpmeddelandet.",
+
+	CM_COMMANDS_HELP = "Visa alla registrerade kommandon.",
+
+	CM_VERSION_HELP = "Visa versionen av Command",
+	CM_VERSION = "%s",
+
+	CM_SET_HELP = "Ändra inställningarna i Command.",
+	CM_SET_USAGE = "Användning: set cmdchar|groupinvite|setlocale|locale",
+	CM_SET_GROUPINVITE_USAGE = "Användning: set groupinvite enable|disable|<tid>",
+	CM_SET_SETLOCALE_USAGE = "Användning: set setlocale <språk>",
+	CM_SET_LOCALE_USAGE = "Användning: set locale reset|usemaster",
+
+	CM_MYLOCALE_HELP = "Låter användare ställa in sitt eget språk.",
+	CM_MYLOCALE_SET = "Ditt språk är nu inställt till %s.",
+
+	CM_LOCK_HELP = "Lås en användare.",
+	CM_UNLOCK_HELP = "Lås upp en användare.",
+
+	CM_GETACCESS_HELP = "Get the access level of a user.",
+	CM_GETACCESS_STRING = "%s's access is %d (%s)",
+
+	CM_SETACCESS_HELP = "Set the access level of a user.",
+	CM_SETACCESS_USAGE = "Usage: setaccess [player] <group>",
+
+	CM_OWNER_HELP = "Promote a player to owner rank.",
+
+	CM_ADMIN_HELP = "Promote a player to admin rank.",
+	CM_ADMIN_USAGE = "Usage: admin <name>",
+
+	CM_OP_HELP = "Promote a player to op rank.",
+
+	CM_USER_HELP = "Promote a player to user rank.",
+
+	CM_BAN_HELP = "Ban a player.",
+	CM_BAN_USAGE = "Usage: ban <name>",
+
+	CM_ACCEPTINVITE_HELP = "Accepts a pending group invite.",
+	CM_ACCEPTINVITE_NOTACTIVE = "No pending invites active.",
+	CM_ACCEPTINVITE_EXISTS = "I am already in a group.",
+	CM_ACCEPTINVITE_SUCCESS = "Accepted group invite!",
+
+	CM_INVITE_HELP = "Invite a player to group.",
+
+	CM_INVITEME_HELP = "Player who issued the command will be invited to group.",
+
+	CM_DENYINVITE_HELP = "Player issuing this command will no longer be sent invites from this AddOn.",
+
+	CM_ALLOWINVITE_HELP = "Player issuing this command will receive invites sent from this AddOn.",
+
+	CM_KICK_HELP = "Kick a player from group with optional reason (Requires confirmation).",
+	CM_KICK_USAGE = "Usage: kick <player> [reason]",
+
+	CM_KINGME_HELP = "Player issuing this command will be promoted to group leader.",
+
+	CM_OPME_HELP = "Player issuing this command will be promoted to raid assistant.",
+
+	CM_DEOPME_HELP = "Player issuing this command will be demoted from assistant status.",
+
+	CM_LEADER_HELP = "Promote a player to group leader.",
+	CM_LEADER_USAGE = "Usage: leader <name>",
+
+	CM_PROMOTE_HELP = "Promote a player to raid assistant.",
+	CM_PROMOTE_USAGE = "Usage: promote <name>",
+
+	CM_DEMOTE_HELP = "Demote a player from assistant status.",
+	CM_DEMOTE_USAGE = "Usage: demote <name>",
+
+	CM_QUEUE_HELP = "Enter the LFG queue for the specified category.",
+	CM_QUEUE_USAGE = "Usage: queue <type>",
+	CM_QUEUE_INVALID = "No such dungeon type: %q.",
+
+	CM_LEAVELFG_HELP = "Leave the LFG queue.",
+	CM_LEAVELFG_FAIL = "Not queued by command, unable to cancel.",
+
+	CM_ACCEPTLFG_HELP = "Causes you to accept the LFG invite.",
+	CM_ACCEPTLFG_FAIL = "Not currently queued by command.",
+
+	CM_CONVERT_HELP = "Convert group to party or raid.",
+	CM_CONVERT_USAGE = "Usage: convert party||raid",
+	CM_CONVERT_LFG = "LFG groups cannot be converted.",
+	CM_CONVERT_NOGROUP = "Cannot convert if not in a group.",
+	CM_CONVERT_NOLEAD = "Cannot convert group, not leader.",
+	CM_CONVERT_PARTY = "Converted raid to party.",
+	CM_CONVERT_PARTYFAIL = "Group is already a party.",
+	CM_CONVERT_RAID = "Converted party to raid.",
+	CM_CONVERT_RAIDFAIL = "Group is already a raid.",
+	CM_CONVERT_INVALID = "Invalid group type, only \"party\" or \"raid\" allowed.",
+
+	CM_LIST_HELP = "Toggle status of a command on the blacklist/whitelist.",
+	CM_LIST_USAGE = "Usage: list <command>",
+
+	CM_LISTMODE_HELP = "Toggle list between being a blacklist and being a whitelist.",
+
+	CM_GROUPALLOW_HELP = "Allow a group to use a specific command.",
+	CM_GROUPALLOW_USAGE = "Usage: groupallow <group> <command>",
+
+	CM_GROUPDENY_HELP = "Deny a group to use a specific command.",
+	CM_GROUPDENY_USAGE = "Usage: groupdeny <group> <command>",
+
+	CM_RESETGROUPACCESS_HELP = "Reset the group's access to a specific command.",
+	CM_RESETGROUPACCESS_USAGE = "Usage: resetgroupaccess <group> <command>",
+
+	CM_USERALLOW_HELP = "Allow a user to use a specific command.",
+	CM_USERALLOW_USAGE = "Usage: userallow <player> <command>",
+
+	CM_USERDENY_HELP = "Deny a user to use a specific command.",
+	CM_USERDENY_USAGE = "Usage: userdeny <player> <command>",
+
+	CM_RESETUSERACCESS_HELP = "Reset the user's access to a specific command.",
+	CM_RESETUSERACCESS_USAGE = "Usage: resetuseraccess <player> <command>",
+
+	CM_TOGGLE_HELP = "Toggle AddOn on and off.",
+
+	CM_TOGGLEDEBUG_HELP = "Toggle debugging mode on and off.",
+
+	CM_READYCHECK_HELP = "Respond to ready check or initiate a new one.",
+	CM_READYCHECK_ISSUED = "%s issued a ready check!",
+	CM_READYCHECK_NOPRIV = "Cannot initiate ready check when not leader or assistant.",
+	CM_READYCHECK_INACTIVE = "Ready check not running or I have already responded.",
+	CM_READYCHECK_ACCEPTED = "Accepted ready check.",
+	CM_READYCHECK_DECLINED = "Declined ready check.",
+	CM_READYCHECK_INVALID = "Invalid argument: %s",
+	CM_READYCHECK_FAIL = "Failed to accept or decline ready check.",
+
+	CM_LOOT_HELP = "Provides various loot functions.",
+	CM_LOOT_USAGE = "Usage: loot type||threshold||master||pass",
+	CM_LOOT_LFG = "Cannot use loot command in LFG group.",
+	CM_LOOT_NOMETHOD = "No loot method specified.",
+	CM_LOOT_NOTHRESHOLD = "No loot threshold specified.",
+	CM_LOOT_NOMASTER = "No master looter specified.",
+
+	CM_ROLL_HELP = "Provides tools for managing or starting/stopping rolls.",
+	CM_ROLL_USAGE = "Usage: roll [start||stop||pass||time||do||set]",
+	CM_ROLL_START_USAGE = "Usage: roll start <[time] [item]>",
+	CM_ROLL_SET_USAGE = "Usage: roll set min||max||time <amount>",
+
+	CM_RAIDWARNING_HELP = "Sends a raid warning.",
+	CM_RAIDWARNING_USAGE = "Usage: raidwarning <message>",
+	CM_RAIDWARNING_NORAID = "Cannot send raid warning when not in a raid group.",
+	CM_RAIDWARNING_NOPRIV = "Cannot send raid warning: Not raid leader or assistant.",
+	CM_RAIDWARNING_SENT = "Sent raid warning.",
+
+	------------
+	-- Events --
+	------------
+
+	E_LFGPROPOSAL = "Group has been found, type !accept to make me accept the invite.",
+	E_LFGFAIL = "LFG failed, use !queue <type> to requeue.",
+	E_READYCHECK = "%s issued a ready check, type !rc accept to make me accept it or !rc deny to deny it.",
+	E_GROUPINVITE = "Type !acceptinvite to make me accept the group invite.",
+
+	------------------
+	-- EventHandler --
+	------------------
+
+	EH_REGISTERED = "%q registered.",
+
+	----------------------------
+	-- GroupInvite (Core-Sub) --
+	----------------------------
+
+	GI_ENABLED = "Group Invite (Announce) enabled.",
+	GI_DISABLED = "Group Invite (Announce) disabled.",
+	GI_DELAY_NUM = "Delay has to be a number.",
+	GI_DELAY_MAX = "Delay cannot be greater than 50 seconds.",
+	GI_DELAY_SET = "Group Invite (Announce) delay set to %d seconds.",
+	GI_DELAY_DISABLED = "Group Invite (Announce) delay disabled.",
+
+	------------
+	-- Logger --
+	------------
+
+	LOGGER_ERR_UNDEFINED = "Undefined logger level passed (%q)",
+	LOGGER_PREFIX_MAIN = "\124cff00FF00[%s]\124r",
+	LOGGER_PREFIX_DEBUG = " \124cffBBBBFFDebug\124r",
+	LOGGER_PREFIX_NORMAL = "",
+	LOGGER_PREFIX_WARNING = " \124cffFFFF00Warning\124r",
+	LOGGER_PREFIX_ERROR = " \124cffFF0000ERROR\124r",
+
+	-----------------
+	-- LootManager --
+	-----------------
+
+	LOOT_METHOD_GROUP = "Group Loot",
+	LOOT_METHOD_FFA = "Free For All",
+	LOOT_METHOD_MASTER = "Master Looter",
+	LOOT_METHOD_NEEDGREED = "Need Before Greed",
+	LOOT_METHOD_ROUNDROBIN = "Round Robin",
+
+	LOOT_THRESHOLD_UNCOMMON = "Uncommon",
+	LOOT_THRESHOLD_RARE = "Rare",
+	LOOT_THRESHOLD_EPIC = "Epic",
+	LOOT_THRESHOLD_LEGENDARY = "Legendary",
+	LOOT_THRESHOLD_ARTIFACT = "Artifact",
+	LOOT_THRESHOLD_HEIRLOOM = "Heirloom",
+	LOOT_THRESHOLD_UNKNOWN = "Unknown",
+
+	LOOT_MASTER_NOEXIST = "%q is not in the group and cannot be set as the master looter.",
+
+	LOOT_SM_NOLEAD = "Unable to change loot method, not group leader.",
+	LOOT_SM_DUPE = "The loot method is already set to %s!",
+	LOOT_SM_SUCCESS = "Successfully set the loot method to %s!",
+	LOOT_SM_SUCCESSMASTER = "Successfully set the loot method to %s (%s)!",
+
+	LOOT_SLM_NOLOEAD = "Unable to change master looter, not group leader.",
+	LOOT_SLM_METHOD = "Cannot set master looter when loot method is set to %s.",
+	LOOT_SLM_SPECIFY = "Master looter not specified.",
+	LOOT_SLM_SUCCESS = "Successfully set %s as the master looter!",
+
+	LOOT_ST_NOLEAD = "Unable to change loot threshold, not group leader.",
+	LOOT_ST_INVALID = "Invalid loot threshold specified, please specify a loot threshold between 2 and 7 (inclusive).",
+	LOOT_ST_SUCCESS = "Successfully set the loot threshold to %s!",
+
+	LOOT_SP_PASS = "%s is now passing on loot.",
+	LOOT_SP_ROLL = "%s is not passing on loot.",
+
+	-------------------
+	-- PlayerManager --
+	-------------------
+
+	PM_ERR_NOCOMMAND = "No command specified.",
+	PM_ERR_LOCKED = "Target player is locked and cannot be modified.",
+	PM_ERR_NOTINGROUP = "%s is not in the group.",
+
+	PM_ACCESS_ALLOWED = "%q is now allowed for %s.",
+	PM_ACCESS_DENIED = "%q is now denied for %s.",
+
+	PM_KICK_REASON = "%s has been kicked on %s's request. (Reason: %s)",
+	PM_KICK = "%s has been kicked on %s's request.",
+	PM_KICK_NOTIFY = "%s was kicked on your request.",
+	PM_KICK_TARGET = "You have ben kicked out of the group by %s.",
+	PM_KICK_DENIED = "%s's request to kick %s has been denied.",
+	PM_KICK_POPUP = "%s wants to kick %s. Confirm?",
+	PM_KICK_SELF = "Cannot kick myself.",
+	PM_KICK_FRIEND = "Cannot kick my friend.",
+	PM_KICK_DEFAULTREASON = "%s used !kick command.",
+	PM_KICK_WAIT = "Awaiting confirmation to kick %s...",
+	PM_KICK_NOPRIV = "Unable to kick %s from group. Not group leader or assistant.",
+
+	PM_PLAYER_CREATE = "Created player %q with default settings.",
+	PM_PLAYER_UPDATE = "Updated player %q.",
+
+	PM_GA_REMOVED = "%q removed from group %s.",
+	PM_GA_EXISTSALLOW = "%q already has that command on the allow list.",
+	PM_GA_EXISTSDENY = "%q already has that command on the deny list.",
+
+	PM_PA_REMOVED = "%q removed from %s.",
+	PM_PA_EXISTSALLOW = "%s already has that command on the allow list.",
+	PM_PA_EXISTSDENY = "%s already has that command on the deny list.",
+
+	PM_LOCKED = "Player %s has been locked.",
+	PM_UNLOCKED = "Player %s has been unlocked",
+
+	PM_SAG_SELF = "Cannot modify my own access level.",
+	PM_SAG_NOEXIST = "No such access group: %q",
+	PM_SAG_SET = "Set the access level of %q to %d (%s).",
+
+	PM_INVITE_SELF = "Cannot invite myself to group.",
+	PM_INVITE_INGROUP = "%s is already in the group.",
+	PM_INVITE_FULL = "The group is already full.",
+	PM_INVITE_NOTIFYTARGET = "Invited you to the group.",
+	PM_INVITE_NOTIFY = "%s invited you to the group, %s. Whisper !blockinvites to block these invites.",
+	PM_INVITE_SUCCESS = "Invited %s to group.",
+	PM_INVITE_BLOCKED = "%s does not wish to be invited.",
+	PM_INVITE_NOPRIV = "Unable to invite %s to group. Not group leader or assistant.",
+
+	PM_DI_BLOCKING = "You are now blocking invites, whisper !allowinvites to receive them again.",
+	PM_DI_SUCCESS = "%s is no longer receiving invites.",
+	PM_DI_FAIL = "You are already blocking invites.",
+
+	PM_AI_ALLOWING = "You are now allowing invites, whisper !blockinvites to block them.",
+	PM_AI_SUCCESS = "%s is now receiving invites.",
+	PM_AI_FAIL = "You are already allowing invites.",
+
+	PM_LEADER_SELF = "Cannot promote myself to leader.",
+	PM_LEADER_DUPE = "%s is already leader.",
+	PM_LEADER_SUCCESS = "Promoted %s to group leader.",
+	PM_LEADER_NOPRIV = "Cannot promote %s to group leader, insufficient permissions.",
+
+	PM_ASSIST_SELF = "Cannot promote myself to assistant.",
+	PM_ASSIST_DUPE = "%s is already assistant.",
+	PM_ASSIST_NORAID = "Cannot promote to assistant when not in a raid.",
+	PM_ASSIST_SUCCESS = "Promoted %s to assistant.",
+	PM_ASSIST_NOPRIV = "Cannot promote %s to assistant, insufficient permissions.",
+
+	PM_DEMOTE_SELF = "Cannot demote myself.",
+	PM_DEMOTE_INVALID = "%s is not an assistant, can only demote assistants.",
+	PM_DEMOTE_NORAID = "Cannot demote when not in a raid.",
+	PM_DEMOTE_SUCCESS = "Demoted %s.",
+	PM_DEMOTE_NOPRIV = "Cannot demote %s, insufficient permissions.",
+
+	PM_LIST_ADDWHITE = "Added %s to whitelist.",
+	PM_LIST_ADDBLACK = "Added %s to blacklist.",
+	PM_LIST_REMOVEWHITE = "Removed %s from whitelist.",
+	PM_LIST_REMOVEBLACK = "Removed %s from blacklist.",
+	PM_LIST_SETWHITE = "Now using list as whitelist.",
+	PM_LIST_SETBLACK = "Now using list as blacklist.",
+
+	------------------
+	-- QueueManager --
+	------------------
+
+	QM_QUEUE_START = "Starting queue for %s, please select your role(s)... Type !cancel to cancel.",
+	QM_CANCEL = "Left the LFG queue.",
+	QM_ACCEPT = "Accepted LFG invite.",
+	QM_ANNOUNCE_QUEUEING = "Now queueing for %s, type !cancel to cancel.",
+	QM_ANNOUNCE_ROLECANCEL = "Role Check cancelled.",
+	QM_ANNOUNCE_LFGCANCEL = "LFG cancelled.",
+
+	-----------------
+	-- RollManager --
+	-----------------
+
+	RM_ERR_INVALIDAMOUNT = "Invalid amount passed: %s.",
+	RM_ERR_NOTRUNNING = "No roll is currently in progress.",
+	RM_ERR_INVALIDROLL = "%s specified too high or too low roll region, not including their roll.",
+
+	RM_MATCH = "(%w+) rolls (%d+) %((%d+)-(%d+)%)",
+
+	RM_ROLLEXISTS = "%s has already rolled! (%d)",
+	RM_ROLLPROGRESS = "%d/%d players have rolled!",
+
+	RM_UPDATE_TIMELEFT = "%d seconds left to roll!",
+
+	RM_SET_MINFAIL = "Minimum roll number cannot be higher than maximum roll number!",
+	RM_SET_MINSUCCESS = "Successfully set minimum roll number to %d!",
+	RM_SET_MAXFAIL = "Maximum roll number cannot be higher than minimum roll number!",
+	RM_SET_MAXSUCCESS = "Successfully set maximum roll number to %d!",
+	RM_SET_TIMEFAIL = "Amount must be larger than zero (0).",
+	RM_SET_TIMESUCCESS = "Successfully set default roll time to %d!",
+
+	RM_START_RUNNING = "A roll is already in progress, wait for it to complete or use roll stop.",
+	RM_START_SENDER = "Could not identify sender: %s. Aborting roll...",
+	RM_START_MEMBERS = "Could not start roll, not enough group members!",
+	RM_START_SUCCESS = "%s started a roll, ends in %d seconds! Type /roll %d %d. Type !roll pass to pass.",
+	RM_START_SUCCESSITEM = "%s started a roll for %s, ends in %d seconds! Type /roll %d %d. Type !roll pass to pass.",
+
+	RM_STOP_SUCCESS = "Roll has been stopped.",
+
+	RM_DO_SUCCESS = "Done! Executed RandomRoll(%d, %d)",
+
+	RM_PASS_SUCCESS = "%s has passed on the roll.",
+
+	RM_TIME_LEFT = "%d seconds remaining!",
+
+	RM_ANNOUNCE_EXPIRE = "Roll time expired! Results...",
+	RM_ANNOUNCE_FINISH = "Everyone has rolled! Results...",
+	RM_ANNOUNCE_EMPTY = "Noone rolled, there is no winner!",
+	RM_ANNOUNCE_PASS = "Everyone passed on the roll, there is no winner!",
+	RM_ANNOUNCE_PASSITEM = "Everyone passed on the roll, there is no winner for %s!",
+	RM_ANNOUNCE_WIN = "The winner is: %s! With a roll of %d.",
+	RM_ANNOUNCE_WINITEM = "The winner is: %s! With a roll of %d for %s.",
+	RM_ANNOUNCE_MULTIPLE = "There are multiple winners:",
+	RM_ANNOUNCE_MULTIPLEITEM = "There are multiple winners for %s:",
+	RM_ANNOUNCE_WINNER = "%s with a roll of %d."
+}
+
+LM:Register("svSE", L)