This commit is contained in:
lx
2026-07-06 14:01:11 -04:00
commit c4f97d729d
16468 changed files with 935321 additions and 0 deletions
@@ -0,0 +1,3 @@
local makeActionCreator = require(script.Parent.Parent.makeActionCreator)
return makeActionCreator(script.Name, "accountInfo")
@@ -0,0 +1,3 @@
local makeActionCreator = require(script.Parent.Parent.makeActionCreator)
return makeActionCreator(script.Name, "purchaseError")
@@ -0,0 +1,3 @@
local makeActionCreator = require(script.Parent.Parent.makeActionCreator)
return makeActionCreator(script.Name)
@@ -0,0 +1,3 @@
local makeActionCreator = require(script.Parent.Parent.makeActionCreator)
return makeActionCreator(script.Name, "purchaseError")
@@ -0,0 +1,3 @@
local makeActionCreator = require(script.Parent.Parent.makeActionCreator)
return makeActionCreator(script.Name, "productInfo")
@@ -0,0 +1,3 @@
local makeActionCreator = require(script.Parent.Parent.makeActionCreator)
return makeActionCreator(script.Name, "robuxProductId", "robuxPurchaseAmount")
@@ -0,0 +1,3 @@
local makeActionCreator = require(script.Parent.Parent.makeActionCreator)
return makeActionCreator(script.Name, "enabled")
@@ -0,0 +1,3 @@
local makeActionCreator = require(script.Parent.Parent.makeActionCreator)
return makeActionCreator(script.Name, "id", "infoType", "equipIfPurchased")
@@ -0,0 +1,3 @@
local makeActionCreator = require(script.Parent.Parent.makeActionCreator)
return makeActionCreator(script.Name, "promptState")
@@ -0,0 +1,3 @@
local makeActionCreator = require(script.Parent.Parent.makeActionCreator)
return makeActionCreator(script.Name, "purchasingStartTime")
@@ -0,0 +1,106 @@
local UserInputService = game:GetService("UserInputService")
local join = require(script.Parent.join)
--[[
CLILUACORE-318: Revisit this approach and evaluate if it adequately
addresses existing or future scams
]]
local ClickScamDetector = {}
ClickScamDetector.__index = ClickScamDetector
--[[
Create a new scam detector with the specified options
This object will track any clicks or confirm button presses that occur
during its lifetime and attempt to determine whether the player is
being asked to spam clicks or button presses. When processing an action,
users of this object can abort their behavior if calling isClickValid
returns false.
]]
function ClickScamDetector.new(options)
--[[
Overwrite default values with provided options
]]
options = join({
-- The number of clicks within the window that is interpreted as a scam
clickSpeedThreshold = 3,
-- The window in which to measure clicks
clickTimeWindow = 1,
-- A delay to allow clicks to stack up and prevent immediate clicks
initialDelay = 1,
-- Allow a button input to be associated with this click detection
buttonInput = nil,
}, options)
local self = {
_inputConnection = nil,
_clickCount = 0,
_startTime = tick(),
_options = options,
}
setmetatable(self, ClickScamDetector)
self._inputConnection = UserInputService.InputBegan:Connect(function(input)
self:_onInput(input)
end)
return self
end
--[[
Track mouse inputs, counting the number that occurred in the last
CLICK_TIME_WINDOW duration
Includes touch inputs and pressing the A button on a gamepad
]]
function ClickScamDetector:_onInput(input)
local inputType = input.UserInputType
local isGamepad = self._options.buttonInput ~= nil and input.KeyCode == self._options.buttonInput
local isMouseOrTouch = inputType == Enum.UserInputType.MouseButton1
or inputType == Enum.UserInputType.Touch
if isGamepad or isMouseOrTouch then
self._clickCount = self._clickCount + 1
delay(self._options.clickTimeWindow, function()
self._clickCount = self._clickCount - 1
end)
end
end
--[[
Determine whether or not there's a possibility of a scam occurring
and return whether or not we believe the click to be valid
]]
function ClickScamDetector:isClickValid()
--[[
If the mouse behavior is locked by dev-facing APIs, clicks are not valid
]]
if UserInputService.MouseBehavior == Enum.MouseBehavior.LockCurrentPosition then
return false
end
--[[
Don't allow any clicks until the initial delay has passed;
]]
if tick() - self._startTime < self._options.initialDelay then
return false
end
return self._clickCount / self._options.clickTimeWindow < self._options.clickSpeedThreshold
end
--[[
Cleanup connection to InputService; should be called when
the UI element using this object is destroyed
]]
function ClickScamDetector:destroy()
self._inputConnection:Disconnect()
end
return ClickScamDetector
@@ -0,0 +1,33 @@
return function()
local UserInputService = game:GetService("UserInputService")
local ClickScamDetector = require(script.Parent.ClickScamDetector)
-- We need better ways to fake time passing, so that we can test further functionality;
-- May want to indirect the `tick` function in the ClickScamDetector and allow overriding
it("should always report a scam if the mouse is locked", function()
local clickScamDetector = ClickScamDetector.new()
-- No clicks have been fired
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
expect(clickScamDetector:isClickValid()).to.equal(false)
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
clickScamDetector:destroy()
end)
it("should report a scam if there are many clicks in quick succession", function()
local clickScamDetector = ClickScamDetector.new()
local fakeInput = {
UserInputType = Enum.UserInputType.MouseButton1,
}
clickScamDetector:_onInput(fakeInput)
clickScamDetector:_onInput(fakeInput)
clickScamDetector:_onInput(fakeInput)
expect(clickScamDetector:isClickValid()).to.equal(false)
clickScamDetector:destroy()
end)
end
@@ -0,0 +1,41 @@
--[[
Connects to GuiService's browser close callback to retry purchase after upsell
]]
local CorePackages = game:GetService("CorePackages")
local GuiService = game:GetService("GuiService")
local Roact = require(CorePackages.Roact)
local ExternalEventConnection = require(script.Parent.ExternalEventConnection)
local retryAfterUpsell = require(script.Parent.Parent.Parent.Thunks.retryAfterUpsell)
local connectToStore = require(script.Parent.Parent.Parent.connectToStore)
local function BrowserPurchaseFinishedConnector(props)
local onBrowserWindowClosed = props.onBrowserWindowClosed
--[[
CLILUACORE-309: The browser window closing is the ONLY
indication we have about when the user finished interacting
with the upsell flow on desktop.
]]
return Roact.createElement(ExternalEventConnection, {
event = GuiService.BrowserWindowClosed,
callback = onBrowserWindowClosed,
})
end
local function mapDispatchToProps(dispatch)
return {
onBrowserWindowClosed = function()
dispatch(retryAfterUpsell())
end,
}
end
BrowserPurchaseFinishedConnector = connectToStore(
nil,
mapDispatchToProps
)(BrowserPurchaseFinishedConnector)
return BrowserPurchaseFinishedConnector
@@ -0,0 +1,35 @@
--[[
Connects relevant Roblox engine events to the rodux store
]]
local CorePackages = game:GetService("CorePackages")
local UserInputService = game:GetService("UserInputService")
local Roact = require(CorePackages.Roact)
local UpsellFlow = require(script.Parent.Parent.Parent.UpsellFlow)
local MarketplaceServiceEventConnector = require(script.Parent.MarketplaceServiceEventConnector)
local GamepadInputConnector = require(script.Parent.GamepadInputConnector)
local BrowserPurchaseFinishedConnector = require(script.Parent.BrowserPurchaseFinishedConnector)
local NativePurchaseFinishedConnector = require(script.Parent.NativePurchaseFinishedConnector)
local getUpsellFlow = require(script.Parent.Parent.Parent.NativeUpsell.getUpsellFlow)
local function EventConnections()
local upsellConnector
local upsellFlow = getUpsellFlow(UserInputService:GetPlatform())
if upsellFlow == UpsellFlow.Web then
upsellConnector = Roact.createElement(BrowserPurchaseFinishedConnector)
elseif upsellFlow == UpsellFlow.Mobile then
upsellConnector = Roact.createElement(NativePurchaseFinishedConnector)
end
return Roact.createElement("Folder", {}, {
MarketPlaceServiceEventConnector = Roact.createElement(MarketplaceServiceEventConnector),
GamepadInputConnector = Roact.createElement(GamepadInputConnector),
UpsellFinishedConnector = upsellConnector,
})
end
return EventConnections
@@ -0,0 +1,52 @@
--[[
A component that establishes a connection to a Roblox event when it is rendered.
]]
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local ExternalEventConnection = Roact.Component:extend("ExternalEventConnection")
function ExternalEventConnection:init()
self.connection = nil
end
--[[
Render the child component so that ExternalEventConnections can be nested like so:
Roact.createElement(ExternalEventConnection, {
event = UserInputService.InputBegan,
callback = inputBeganCallback,
}, {
Roact.createElement(ExternalEventConnection, {
event = UserInputService.InputEnded,
callback = inputChangedCallback,
})
})
]]
function ExternalEventConnection:render()
return Roact.oneChild(self.props[Roact.Children])
end
function ExternalEventConnection:didMount()
local event = self.props.event
local callback = self.props.callback
self.connection = event:Connect(callback)
end
function ExternalEventConnection:didUpdate(oldProps)
if self.props.event ~= oldProps.event or self.props.callback ~= oldProps.callback then
self.connection:Disconnect()
self.connection = self.props.event:Connect(self.props.callback)
end
end
function ExternalEventConnection:willUnmount()
self.connection:Disconnect()
self.connection = nil
end
return ExternalEventConnection
@@ -0,0 +1,100 @@
--[[
Sets whether or not gamepad buttons should be shown, based on recently
received inputs
]]
local CorePackages = game:GetService("CorePackages")
local UserInputService = game:GetService("UserInputService")
local Roact = require(CorePackages.Roact)
local ExternalEventConnection = require(script.Parent.ExternalEventConnection)
local SetGamepadEnabled = require(script.Parent.Parent.Parent.Actions.SetGamepadEnabled)
local connectToStore = require(script.Parent.Parent.Parent.connectToStore)
local gamepadInputs = {
[Enum.UserInputType.Gamepad1] = true,
[Enum.UserInputType.Gamepad2] = true,
[Enum.UserInputType.Gamepad3] = true,
[Enum.UserInputType.Gamepad4] = true,
}
local thumbstickInputs = {
[Enum.KeyCode.Thumbstick1] = true,
[Enum.KeyCode.Thumbstick2] = true,
}
--[[
Returns whether or not input changed, and what its new value is
UserInputService.InputChanged event fires very frequently when moving
the mouse or thumbsticks: we try to early-out if nothing changed so that
we don't send hundreds of dispatches through the store
]]
local function didInputChange(isGamepadEnabled, inputObject)
local newEnabledStatus = nil
if gamepadInputs[inputObject.UserInputType] then
if thumbstickInputs[inputObject.KeyCode] then
local position = inputObject.Position
if math.abs(position.X) > 0.1 or math.abs(position.Z) > 0.1 or math.abs(position.Y) > 0.1 then
newEnabledStatus = true
end
else
newEnabledStatus = true
end
else
newEnabledStatus = false
end
if newEnabledStatus ~= nil and newEnabledStatus ~= isGamepadEnabled then
return true, newEnabledStatus
end
return false, isGamepadEnabled
end
local function GamepadInputConnector(props)
local gamepadEnabled = props.gamepadEnabled
local inputChanged = props.inputChanged
local dispatchOnChange = function (inputObject)
local didChange, newEnabledStatus = didInputChange(gamepadEnabled, inputObject)
if didChange then
inputChanged(newEnabledStatus)
end
end
return Roact.createElement(ExternalEventConnection, {
event = UserInputService.InputChanged,
callback = dispatchOnChange,
}, {
Roact.createElement(ExternalEventConnection, {
event = UserInputService.InputBegan,
callback = dispatchOnChange,
})
})
end
local function mapStateToProps(state)
return {
gamepadEnabled = state.gamepadEnabled,
}
end
local function mapDispatchToProps(dispatch)
return {
inputChanged = function(newEnabledStatus)
dispatch(SetGamepadEnabled(newEnabledStatus))
end,
}
end
GamepadInputConnector = connectToStore(
mapStateToProps,
mapDispatchToProps
)(GamepadInputConnector)
return GamepadInputConnector
@@ -0,0 +1,19 @@
--[[
LayoutValuesConsumer will extract the LayoutValues object
from context and pass it into the given render callback
]]
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local LayoutValuesKey = require(script.Parent.Parent.Parent.LayoutValuesKey)
local LayoutValuesConsumer = Roact.Component:extend("LayoutValuesConsumer")
function LayoutValuesConsumer:render()
local service = self._context[LayoutValuesKey]
return self.props.render(service)
end
return LayoutValuesConsumer
@@ -0,0 +1,24 @@
--[[
LayoutValuesProvider is a simple wrapper component that injects the
specified services into context
]]
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local LayoutValuesKey = require(script.Parent.Parent.Parent.LayoutValuesKey)
local LayoutValuesProvider = Roact.Component:extend("LayoutValuesProvider")
function LayoutValuesProvider:init(props)
assert(type(props.layoutValues) == "table", "Expected required prop 'layoutValues' to be a table")
assert(type(props.render) == "function", "Expected prop 'render' to be a function")
self._context[LayoutValuesKey] = props.layoutValues
end
function LayoutValuesProvider:render()
return self.props.render()
end
return LayoutValuesProvider
@@ -0,0 +1,22 @@
--[[
LocalizationContextConsumer will extract the localization context
object from Roact context and provide it to the given render callback
Used for components that need to perform localization using this
project's LocalizationService
]]
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local LocalizationContextKey = require(script.Parent.Parent.Parent.LocalizationContextKey)
local LocalizationContextConsumer = Roact.Component:extend("LocalizationContextConsumer")
function LocalizationContextConsumer:render()
local localizationContext = self._context[LocalizationContextKey]
return self.props.render(localizationContext)
end
return LocalizationContextConsumer
@@ -0,0 +1,24 @@
--[[
LocalizationContextProvider is a simple wrapper component that injects the
specified services into context
]]
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local LocalizationContextKey = require(script.Parent.Parent.Parent.LocalizationContextKey)
local LocalizationContextProvider = Roact.Component:extend("LocalizationContextProvider")
function LocalizationContextProvider:init(props)
assert(props.localizationContext, "Missing required prop 'localizationContext'")
assert(props.render, "Missing required prop 'render'")
self._context[LocalizationContextKey] = props.localizationContext
end
function LocalizationContextProvider:render()
return self.props.render(LocalizationContextKey)
end
return LocalizationContextProvider
@@ -0,0 +1,77 @@
--[[
Connects Rodux store to external MarketplaceService events
]]
local CorePackages = game:GetService("CorePackages")
local MarketplaceService = game:GetService("MarketplaceService")
local Roact = require(CorePackages.Roact)
local ExternalEventConnection = require(script.Parent.ExternalEventConnection)
local PurchaseError = require(script.Parent.Parent.Parent.PurchaseError)
local ErrorOccurred = require(script.Parent.Parent.Parent.Actions.ErrorOccurred)
local completePurchase = require(script.Parent.Parent.Parent.Thunks.completePurchase)
local initiatePurchase = require(script.Parent.Parent.Parent.Thunks.initiatePurchase)
local connectToStore = require(script.Parent.Parent.Parent.connectToStore)
local function MarketplaceServiceEventConnector(props)
local onPurchaseRequest = props.onPurchaseRequest
local onProductPurchaseRequest = props.onProductPurchaseRequest
local onPurchaseGamePassRequest = props.onPurchaseGamePassRequest
local onServerPurchaseVerification = props.onServerPurchaseVerification
return Roact.createElement(ExternalEventConnection, {
event = MarketplaceService.PromptPurchaseRequested,
callback = onPurchaseRequest,
}, {
Roact.createElement(ExternalEventConnection, {
event = MarketplaceService.PromptProductPurchaseRequested,
callback = onProductPurchaseRequest,
}, {
Roact.createElement(ExternalEventConnection, {
event = MarketplaceService.PromptGamePassPurchaseRequested,
callback = onPurchaseGamePassRequest,
}, {
Roact.createElement(ExternalEventConnection, {
event = MarketplaceService.ServerPurchaseVerification,
callback = onServerPurchaseVerification,
})
})
})
})
end
MarketplaceServiceEventConnector = connectToStore(nil,
function(dispatch)
local function onPurchaseRequest(player, assetId, equipIfPurchased, currencyType)
dispatch(initiatePurchase(assetId, Enum.InfoType.Asset, equipIfPurchased))
end
local function onProductPurchaseRequest(player, productId, equipIfPurchased, currencyType)
dispatch(initiatePurchase(productId, Enum.InfoType.Product, equipIfPurchased))
end
local function onPurchaseGamePassRequest(player, gamePassId)
dispatch(initiatePurchase(gamePassId, Enum.InfoType.GamePass, false))
end
-- Specific to purchasing dev products
local function onServerPurchaseVerification(serverResponseTable)
if not serverResponseTable then
dispatch(ErrorOccurred(PurchaseError.UnknownFailure))
else
dispatch(completePurchase())
end
end
return {
onPurchaseRequest = onPurchaseRequest,
onProductPurchaseRequest = onProductPurchaseRequest,
onPurchaseGamePassRequest = onPurchaseGamePassRequest,
onServerPurchaseVerification = onServerPurchaseVerification,
}
end)(MarketplaceServiceEventConnector)
return MarketplaceServiceEventConnector
@@ -0,0 +1,45 @@
--[[
Connects to MarketplaceService's callback for completing a native purchase, so that we can
retry after an upsell purchase was processed
]]
local CorePackages = game:GetService("CorePackages")
local MarketplaceService = game:GetService("MarketplaceService")
local Roact = require(CorePackages.Roact)
local ExternalEventConnection = require(script.Parent.ExternalEventConnection)
local ErrorOccurred = require(script.Parent.Parent.Parent.Actions.ErrorOccurred)
local PurchaseError = require(script.Parent.Parent.Parent.PurchaseError)
local retryAfterUpsell = require(script.Parent.Parent.Parent.Thunks.retryAfterUpsell)
local connectToStore = require(script.Parent.Parent.Parent.connectToStore)
local function NativePurchaseFinishedConnector(props)
local nativePurchaseFinished = props.nativePurchaseFinished
return Roact.createElement(ExternalEventConnection, {
event = MarketplaceService.NativePurchaseFinished,
callback = nativePurchaseFinished,
})
end
local function mapDispatchToProps(dispatch)
return {
nativePurchaseFinished = function(player, productId, wasPurchased)
if wasPurchased then
dispatch(retryAfterUpsell())
else
dispatch(ErrorOccurred(PurchaseError.InvalidFunds))
end
end,
}
end
NativePurchaseFinishedConnector = connectToStore(
nil,
mapDispatchToProps
)(NativePurchaseFinishedConnector)
return NativePurchaseFinishedConnector
@@ -0,0 +1,22 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local LocalizationService = require(script.Parent.Parent.Parent.Localization.LocalizationService)
local LocalizationContextConsumer = require(script.Parent.LocalizationContextConsumer)
local function NumberLocalizer(props)
local number = props.number
local render = props.render
assert(typeof(number) == "number", "prop 'number' must be provided")
assert(typeof(render) == "function", "Render prop must be a function")
return Roact.createElement(LocalizationContextConsumer, {
render = function(localizationContext)
return render(LocalizationService.formatNumber(localizationContext, number))
end,
})
end
return NumberLocalizer
@@ -0,0 +1,23 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local LocalizationService = require(script.Parent.Parent.Parent.Localization.LocalizationService)
local LocalizationContextConsumer = require(script.Parent.LocalizationContextConsumer)
local function TextLocalizer(props)
local key = props.key
local params = props.params
local render = props.render
assert(typeof(key) == "string", "String key must be provided")
assert(typeof(render) == "function", "Render prop must be a function")
return Roact.createElement(LocalizationContextConsumer, {
render = function(localizationContext)
return render(LocalizationService.getString(localizationContext, key, params))
end,
})
end
return TextLocalizer
@@ -0,0 +1,21 @@
--[[
Helper for supplying the current Roblox Locale to the Roact
tree via context using the LocalizationContextProvider
]]
local CorePackages = game:GetService("CorePackages")
local LocalizationService = game:GetService("LocalizationService")
local Roact = require(CorePackages.Roact)
local LocalizationContextProvider = require(script.Parent.LocalizationContextProvider)
local getLocalizationContext = require(script.Parent.Parent.Parent.Localization.getLocalizationContext)
local function provideRobloxLocale(renderFunc)
return Roact.createElement(LocalizationContextProvider, {
localizationContext = getLocalizationContext(LocalizationService.RobloxLocaleId),
render = renderFunc
})
end
return provideRobloxLocale
@@ -0,0 +1,17 @@
--[[
Helpful wrapper around LayoutValuesConsumer to make it a
little less verbose to use
]]
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local LayoutValuesConsumer = require(script.Parent.LayoutValuesConsumer)
local function withLayoutValues(renderFunc)
return Roact.createElement(LayoutValuesConsumer, {
render = renderFunc,
})
end
return withLayoutValues
@@ -0,0 +1,126 @@
local CorePackages = game:GetService("CorePackages")
local UserInputService = game:GetService("UserInputService")
local Roact = require(CorePackages.Roact)
local connectToStore = require(script.Parent.Parent.Parent.connectToStore)
local LocalizationService = require(script.Parent.Parent.Parent.Localization.LocalizationService)
local PromptState = require(script.Parent.Parent.Parent.PromptState)
local UpsellFlow = require(script.Parent.Parent.Parent.UpsellFlow)
local TextLocalizer = require(script.Parent.Parent.Connection.TextLocalizer)
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
local getUpsellFlow = require(script.Parent.Parent.Parent.NativeUpsell.getUpsellFlow)
local isMockingPurchases = require(script.Parent.Parent.Parent.isMockingPurchases)
local PURCHASE_DETAILS_KEY = "CoreScripts.PurchasePrompt.PurchaseDetails.%s"
local function AdditionalDetailLabel(props)
return withLayoutValues(function(values)
local layoutOrder = props.layoutOrder
local messageKey = props.messageKey
local messageParams = props.messageParams
if messageKey == nil then
-- We return an empty frame to preserve UIListLayout spacing
return Roact.createElement("Frame", {
LayoutOrder = layoutOrder,
Size = values.Size.AdditonalDetailsLabel,
BackgroundTransparency = 1,
BorderSizePixel = 0,
})
end
return Roact.createElement(TextLocalizer, {
key = messageKey,
params = messageParams,
render = function(localizedText)
return Roact.createElement("TextLabel", {
Text = localizedText,
LayoutOrder = layoutOrder,
Size = values.Size.AdditonalDetailsLabel,
BackgroundTransparency = 1,
BorderSizePixel = 0,
TextColor3 = Color3.new(1, 1, 1),
Font = Enum.Font.SourceSans,
TextSize = values.TextSize.AdditonalDetails,
TextYAlignment = Enum.TextYAlignment.Top,
TextScaled = true,
TextWrapped = true,
}, {
TextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
MaxTextSize = values.TextSize.AdditonalDetails,
})
})
end,
})
end)
end
local function mapStateToProps(state)
local promptState = state.promptState
local messageKey = nil
local messageParams = nil
local price = state.productInfo.price
local balance = state.accountInfo.balance
if promptState == PromptState.PromptPurchase then
if price == 0 then
messageKey = PURCHASE_DETAILS_KEY:format("BalanceUnaffected")
elseif isMockingPurchases() then
messageKey = PURCHASE_DETAILS_KEY:format("MockPurchase")
else
messageKey = PURCHASE_DETAILS_KEY:format("BalanceFuture")
messageParams = {
BALANCE_FUTURE = LocalizationService.numberParam(balance - price),
}
end
elseif promptState == PromptState.RobuxUpsell then
local upsellFlow = getUpsellFlow(UserInputService:GetPlatform())
if upsellFlow ~= UpsellFlow.Web then
local upsellRobux = state.nativeUpsell.robuxPurchaseAmount
local amountNeeded = price - balance
local amountRemaining = upsellRobux - amountNeeded
messageKey = PURCHASE_DETAILS_KEY:format("RemainingAfterUpsell")
messageParams = {
REMAINING_ROBUX = LocalizationService.numberParam(amountRemaining),
}
end
elseif promptState == PromptState.BuildersClubUpsell then
local bcLevelRequired = state.productInfo.bcLevelRequired
messageKey = PURCHASE_DETAILS_KEY:format("InvalidBuildersClub")
messageParams = {
BC_LEVEL = LocalizationService.nestedKeyParam(
LocalizationService.getBuildersClubLevelKey(bcLevelRequired)
)
}
elseif promptState == PromptState.PurchaseComplete then
if isMockingPurchases() then
messageKey = PURCHASE_DETAILS_KEY:format("MockPurchaseComplete")
else
messageKey = PURCHASE_DETAILS_KEY:format("BalanceNow")
messageParams = {
BALANCE_NOW = LocalizationService.numberParam(balance - price),
}
end
end
return {
messageKey = messageKey,
messageParams = messageParams,
}
end
AdditionalDetailLabel = connectToStore(
mapStateToProps
)(AdditionalDetailLabel)
return AdditionalDetailLabel
@@ -0,0 +1,33 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local UnitTestContainer = require(script.Parent.Parent.Parent.Test.UnitTestContainer)
local AdditionalDetailLabel = require(script.Parent.AdditionalDetailLabel)
AdditionalDetailLabel = AdditionalDetailLabel.getUnconnected()
it("should create and destroy without errors", function()
local element = Roact.createElement(UnitTestContainer, nil, {
Roact.createElement(AdditionalDetailLabel, {
layoutOrder = 1,
messageKey = "test",
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
it("should create and destroy without errors when showing no text", function()
local emptyMessageElement = Roact.createElement(UnitTestContainer, nil, {
Roact.createElement(AdditionalDetailLabel, {
layoutOrder = 1,
})
})
local instance = Roact.mount(emptyMessageElement)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,37 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local GRAY = Color3.fromRGB(184, 184, 184)
local BLUE = Color3.fromRGB(0, 162, 255)
local function Dot(props)
local layoutOrder = props.layoutOrder
local time = props.time
local size = 0.8
local color = GRAY
if time >= layoutOrder -1 and time <= layoutOrder then
local animationProgress = math.sin(math.pi * (time % 1))
size = size + (1 - size) * animationProgress
color = GRAY:lerp(BLUE, animationProgress)
end
return Roact.createElement("Frame", {
Size = UDim2.new(1/3, 0, 1, 0),
BackgroundTransparency = 1,
BorderSizePixel = 0,
LayoutOrder = layoutOrder,
}, {
Dot = Roact.createElement("Frame", {
Size = UDim2.new(0.7, 0, size, 0),
SizeConstraint = Enum.SizeConstraint.RelativeYY,
AnchorPoint = Vector2.new(0.5, 0.5),
Position = UDim2.new(0.5, 0, 0.5, 0),
BackgroundColor3 = color,
BorderSizePixel = 0,
})
})
end
return Dot
@@ -0,0 +1,16 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local AnimatedDot = require(script.Parent.AnimatedDot)
it("should create and destroy without errors", function()
local element = Roact.createElement(AnimatedDot, {
time = 1,
layoutOrder = 1,
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,81 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local join = require(script.Parent.Parent.Parent.join)
local AutoResizeList = Roact.Component:extend("AutoResizeList")
function AutoResizeList:init()
self.containerRef = Roact.createRef()
self.listRef = Roact.createRef()
self.contentSizeCallback = function()
if self.listRef.current and self.containerRef.current then
self:resizeContainer()
end
end
end
function AutoResizeList:didMount()
self:resizeContainer()
end
function AutoResizeList:resizeContainer()
self.containerRef.current.Size = UDim2.new(
0, self.listRef.current.AbsoluteContentSize.X,
0, self.listRef.current.AbsoluteContentSize.Y)
end
function AutoResizeList:render()
local layoutOrder = self.props.layoutOrder
local position = self.props.position
local anchorPoint = self.props.anchorPoint
local backgroundImage = self.props.backgroundImage
local sliceCenter = self.props.sliceCenter
local horizontalAlignment = self.props.horizontalAlignment
local verticalAlignment = self.props.verticalAlignment
local fillDirection = self.props.fillDirection
local listPadding = self.props.listPadding
local children = join(self.props[Roact.Children] or {}, {
["$ListLayout"] = Roact.createElement("UIListLayout", {
HorizontalAlignment = horizontalAlignment,
VerticalAlignment = verticalAlignment,
FillDirection = fillDirection,
Padding = listPadding,
SortOrder = Enum.SortOrder.LayoutOrder,
[Roact.Ref] = self.listRef,
[Roact.Change.AbsoluteContentSize] = self.contentSizeCallback,
})
})
if backgroundImage == nil then
return Roact.createElement("Frame", {
LayoutOrder = layoutOrder,
BackgroundTransparency = 1,
BorderSizePixel = 0,
Position = position,
AnchorPoint = anchorPoint,
[Roact.Ref] = self.containerRef,
}, children)
else
local scaleType = (sliceCenter ~= nil) and Enum.ScaleType.Slice or Enum.ScaleType.Stretch
return Roact.createElement("ImageLabel", {
Image = backgroundImage,
LayoutOrder = layoutOrder,
BackgroundTransparency = 1,
BorderSizePixel = 0,
ScaleType = scaleType,
SliceCenter = sliceCenter,
Position = position,
AnchorPoint = anchorPoint,
[Roact.Ref] = self.containerRef,
}, children)
end
end
return AutoResizeList
@@ -0,0 +1,15 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local AutoResizeList = require(script.Parent.AutoResizeList)
it("should create and destroy without errors", function()
local element = Roact.createElement(AutoResizeList, {
layoutOrder = 1,
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,30 @@
local CorePackages = game:GetService("CorePackages")
local TextService = game:GetService("TextService")
local Roact = require(CorePackages.Roact)
local join = require(script.Parent.Parent.Parent.join)
local function AutoSizedTextLabel(props)
assert(props.Text ~= nil, "Cannot autosize label with Text = nil")
local text = props.Text
local textSize = props.TextSize
local font = props.Font
local width = props.width
local totalTextSize
if text ~= nil then
totalTextSize = TextService:GetTextSize(text, textSize, font, Vector2.new(width, 10000))
else
totalTextSize = Vector2.new(0, 0)
end
local textLabelProps = join(props, {
width = Roact.None,
Size = UDim2.new(0, width, 0, totalTextSize.Y),
})
return Roact.createElement("TextLabel", textLabelProps)
end
return AutoSizedTextLabel
@@ -0,0 +1,30 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local AutoSizedTextLabel = require(script.Parent.AutoSizedTextLabel)
it("should create and destroy without errors", function()
local element = Roact.createElement(AutoSizedTextLabel, {
Text = "Hello",
TextSize = 10,
Font = Enum.Font.SourceSans,
width = 100,
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
it("should throw when no text is provided", function()
local element = Roact.createElement(AutoSizedTextLabel, {
TextSize = 10,
Font = Enum.Font.SourceSans,
width = 100,
})
expect(function()
Roact.mount(element)
end).to.throw()
end)
end
@@ -0,0 +1,149 @@
local CorePackages = game:GetService("CorePackages")
local ContextActionService = game:GetService("ContextActionService")
local Roact = require(CorePackages.Roact)
local TextLocalizer = require(script.Parent.Parent.Connection.TextLocalizer)
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
local connectToStore = require(script.Parent.Parent.Parent.connectToStore)
local Button = Roact.PureComponent:extend("Button")
function Button:init()
local onClick = self.props.onClick
local imageDown = self.props.imageDown
local imageUp = self.props.imageUp
self.state = {
currentImage = imageUp
}
self.inputBegan = function()
self:setState({
currentImage = imageDown
})
end
self.inputEnded = function()
self:setState({
currentImage = imageUp
})
end
self.activated = function()
onClick()
self:setState({
currentImage = imageUp
})
end
end
function Button:didMount()
-- Some buttons need to support additional button bindings
local bindings = self.props.additionalBindings or {}
table.insert(bindings, self.props.gamepadButton)
ContextActionService:BindCoreAction(
self.props.stringKey,
self.props.onClick,
false,
unpack(bindings)
)
end
function Button:willUnmount()
ContextActionService:UnbindCoreAction(self.props.stringKey)
end
function Button:render()
assert(typeof(self.props.gamepadButton) == "EnumItem"
and self.props.gamepadButton.EnumType == Enum.KeyCode,
"Prop 'gamepadButton' is required and must be of type Enum.KeyCode")
return withLayoutValues(function(values)
local stringKey = self.props.stringKey
local font = self.props.font
local size = self.props.size
local position = self.props.position
local gamepadEnabled = self.props.gamepadEnabled
local gamepadButton = self.props.gamepadButton
local imageData = values.Image[self.state.currentImage]
local buttonContents = {
ButtonLabel = Roact.createElement(TextLocalizer, {
key = stringKey,
render = function(localizedText)
return Roact.createElement("TextLabel", {
Text = localizedText,
Font = font,
Size = UDim2.new(0.6, 0, 0.8, 0),
Position = UDim2.new(0.2, 0, 0.1, 0),
BackgroundTransparency = 1,
BorderSizePixel = 0,
TextColor3 = Color3.new(1, 1, 1),
TextSize = values.TextSize.Button,
TextScaled = true,
TextWrapped = false,
LayoutOrder = 2,
}, {
TextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
MaxTextSize = values.TextSize.Button,
}),
})
end,
})
}
if gamepadEnabled then
-- Using a frame allows the icon to be left-aligned and still
-- be in a size-constrained section of the overall button
buttonContents.ButtonIcon = Roact.createElement("Frame", {
Size = UDim2.new(0.2, 0, 0.8, 0),
Position = UDim2.new(0, 0, 0.1, 0),
BackgroundTransparency = 1,
BorderSizePixel = 0,
}, {
Icon = Roact.createElement("ImageLabel", {
Size = UDim2.new(1, 0, 1, 0),
Position = UDim2.new(0, values.Size.ButtonIconPadding, 0, values.Size.ButtonIconYOffset),
SizeConstraint = Enum.SizeConstraint.RelativeYY,
ScaleType = Enum.ScaleType.Fit,
Image = values.Image[gamepadButton.Name].Path,
BackgroundTransparency = 1,
BorderSizePixel = 0,
})
})
end
return Roact.createElement("ImageButton", {
BackgroundTransparency = 1,
BorderSizePixel = 0,
AutoButtonColor = false,
Modal = true,
Size = size,
Position = position,
ScaleType = Enum.ScaleType.Slice,
Image = imageData.Path,
SliceCenter = imageData.SliceCenter,
[Roact.Event.InputBegan] = self.inputBegan,
[Roact.Event.InputEnded] = self.inputEnded,
[Roact.Event.Activated] = self.activated,
}, buttonContents)
end)
end
local function mapStateToProps(state)
return {
gamepadEnabled = state.gamepadEnabled
}
end
Button = connectToStore(mapStateToProps)(Button)
return Button
@@ -0,0 +1,48 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local UnitTestContainer = require(script.Parent.Parent.Parent.Test.UnitTestContainer)
local Button = require(script.Parent.Button)
Button = Button.getUnconnected()
it("should create and destroy without errors with gamepad disabled", function()
local element = Roact.createElement(UnitTestContainer, nil, {
Roact.createElement(Button, {
gamepadEnabled = false,
stringKey = "testing123",
gamepadButton = Enum.KeyCode.ButtonA,
font = Enum.Font.SourceSans,
imageUp = "ButtonUp",
imageDown = "ButtonDown",
onClick = function()
end,
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
it("should create and destroy without errors with gamepad enabled", function()
local element = Roact.createElement(UnitTestContainer, nil, {
Roact.createElement(Button, {
gamepadEnabled = true,
stringKey = "testing123",
gamepadButton = Enum.KeyCode.ButtonA,
font = Enum.Font.SourceSans,
imageUp = "ButtonUp",
imageDown = "ButtonDown",
onClick = function()
end,
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,28 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Button = require(script.Parent.Button)
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
local function CancelButton(props)
return withLayoutValues(function(values)
local onClick = props.onClick
return Roact.createElement(Button, {
font = Enum.Font.SourceSans,
imageUp = "ButtonUpRight",
imageDown = "ButtonDownRight",
gamepadButton = Enum.KeyCode.ButtonB,
stringKey = "CoreScripts.PurchasePrompt.CancelPurchase.Cancel",
size = UDim2.new(0.5, 0, 1, 0),
position = UDim2.new(0.5, 0, 0, 0),
onClick = onClick,
})
end)
end
return CancelButton
@@ -0,0 +1,20 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local UnitTestContainer = require(script.Parent.Parent.Parent.Test.UnitTestContainer)
local CancelButton = require(script.Parent.CancelButton)
it("should create and destroy without errors", function()
local element = Roact.createElement(UnitTestContainer, nil, {
Roact.createElement(CancelButton, {
onClick = function()
end,
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,52 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local ClickScamDetector = require(script.Parent.Parent.Parent.ClickScamDetector)
local Button = require(script.Parent.Button)
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
local CONFIRM_BUTTON = Enum.KeyCode.ButtonA
local ConfirmButton = Roact.Component:extend("ConfirmButton")
function ConfirmButton:init()
local onClick = self.props.onClick
self.activated = function()
if self.clickScamDetector:isClickValid() then
onClick()
end
end
self.clickScamDetector = ClickScamDetector.new({
buttonInput = CONFIRM_BUTTON,
initialDelay = 0.5,
})
end
function ConfirmButton:willUnmount()
self.clickScamDetector:destroy()
end
function ConfirmButton:render()
return withLayoutValues(function(values)
local stringKey = self.props.stringKey
return Roact.createElement(Button, {
font = Enum.Font.SourceSansBold,
imageUp = "ButtonUpLeft",
imageDown = "ButtonDownLeft",
gamepadButton = CONFIRM_BUTTON,
stringKey = stringKey,
size = UDim2.new(0.5, 0, 1, 0),
onClick = self.activated,
})
end)
end
return ConfirmButton
@@ -0,0 +1,21 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local UnitTestContainer = require(script.Parent.Parent.Parent.Test.UnitTestContainer)
local ConfirmButton = require(script.Parent.ConfirmButton)
it("should create and destroy without errors", function()
local element = Roact.createElement(UnitTestContainer, nil, {
Roact.createElement(ConfirmButton, {
stringKey = "testing123",
onClick = function()
end,
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,61 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local TextLocalizer = require(script.Parent.Parent.Connection.TextLocalizer)
local AutoSizedTextLabel = require(script.Parent.AutoSizedTextLabel)
local PurchasingAnimation = require(script.Parent.PurchasingAnimation)
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
local function InProgressContents(props)
return withLayoutValues(function(values)
return Roact.createElement("ImageLabel", {
Size = UDim2.new(1, 0, 1, 0),
ScaleType = Enum.ScaleType.Slice,
Image = values.Image.InProgressBackground.Path,
SliceCenter = values.Image.InProgressBackground.SliceCenter,
BackgroundTransparency = 1,
BorderSizePixel = 0,
}, {
ListLayout = Roact.createElement("UIListLayout", {
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Center,
FillDirection = Enum.FillDirection.Vertical,
SortOrder = Enum.SortOrder.LayoutOrder,
Padding = UDim.new(0, 20)
}),
PurchasingText = Roact.createElement(TextLocalizer, {
key = "CoreScripts.PurchasePrompt.Purchasing",
render = function(localizedText)
return Roact.createElement(AutoSizedTextLabel, {
width = values.Size.Dialog.X.Offset,
Text = localizedText,
BackgroundTransparency = 1,
BorderSizePixel = 0,
TextColor3 = Color3.new(1, 1, 1),
Font = Enum.Font.SourceSans,
TextSize = values.TextSize.Purchasing,
TextXAlignment = Enum.TextXAlignment.Center,
TextYAlignment = Enum.TextYAlignment.Center,
TextScaled = true,
TextWrapped = true,
LayoutOrder = 1,
}, {
TextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
MaxTextSize = values.TextSize.Purchasing,
}),
})
end,
}),
PurchasingAnimation = Roact.createElement(PurchasingAnimation, {
layoutOrder = 2,
}),
})
end)
end
return InProgressContents
@@ -0,0 +1,20 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local UnitTestContainer = require(script.Parent.Parent.Parent.Test.UnitTestContainer)
local InProgressContents = require(script.Parent.InProgressContents)
it("should create and destroy without errors", function()
local element = Roact.createElement(UnitTestContainer, nil, {
Roact.createElement(InProgressContents, {
anchorPoint = Vector2.new(0, 0),
position = UDim2.new(0, 0, 0, 0),
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,59 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local connectToStore = require(script.Parent.Parent.Parent.connectToStore)
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
local PromptState = require(script.Parent.Parent.Parent.PromptState)
local function ItemPreviewImage(props)
return withLayoutValues(function(values)
local layoutOrder = props.layoutOrder
local promptState = props.promptState
local productImageUrl = props.productImageUrl
if promptState == PromptState.Error then
productImageUrl = values.Image.ErrorIcon.Path
end
return Roact.createElement("Frame", {
Size = values.Size.ItemPreviewContainerFrame,
BackgroundTransparency = 1,
BorderSizePixel = 0,
LayoutOrder = layoutOrder,
}, {
ItemPreviewImageContainer = Roact.createElement("Frame", {
Size = values.Size.ItemPreviewWhiteFrame,
BackgroundTransparency = 0,
AnchorPoint = Vector2.new(0.5, 0.5),
Position = UDim2.new(0.5, 0, 0.5, 0),
BorderSizePixel = 0,
BackgroundColor3 = Color3.new(1, 1, 1),
}, {
ItemImage = Roact.createElement("ImageLabel", {
Size = values.Size.ItemPreview,
BackgroundTransparency = 1,
BorderSizePixel = 0,
AnchorPoint = Vector2.new(0.5, 0.5),
Position = UDim2.new(0.5, 0, 0.5, 0),
Image = productImageUrl,
})
})
})
end)
end
local function mapStateToProps(state)
return {
promptState = state.promptState,
productImageUrl = state.productInfo.imageUrl,
}
end
ItemPreviewImage = connectToStore(
mapStateToProps
)(ItemPreviewImage)
return ItemPreviewImage
@@ -0,0 +1,22 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local UnitTestContainer = require(script.Parent.Parent.Parent.Test.UnitTestContainer)
local ItemPreviewImage = require(script.Parent.ItemPreviewImage)
ItemPreviewImage = ItemPreviewImage.getUnconnected()
it("should create and destroy without errors", function()
local element = Roact.createElement(UnitTestContainer, nil, {
Roact.createElement(ItemPreviewImage, {
layoutOrder = 1,
imageUrl = "",
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,30 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Button = require(script.Parent.Button)
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
local function OkButton(props)
return withLayoutValues(function(values)
local onClick = props.onClick
return Roact.createElement(Button, {
font = Enum.Font.SourceSans,
imageUp = "ButtonUp",
imageDown = "ButtonDown",
gamepadButton = Enum.KeyCode.ButtonA,
additionalBindings = {
Enum.KeyCode.ButtonB,
},
stringKey = "CoreScripts.PurchasePrompt.Button.OK",
size = UDim2.new(1, 0, 0, values.Size.ButtonHeight-4),
onClick = onClick
})
end)
end
return OkButton
@@ -0,0 +1,20 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local UnitTestContainer = require(script.Parent.Parent.Parent.Test.UnitTestContainer)
local OkButton = require(script.Parent.OkButton)
it("should create and destroy without errors", function()
local element = Roact.createElement(UnitTestContainer, nil, {
Roact.createElement(OkButton, {
onClick = function()
end,
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,84 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local PromptState = require(script.Parent.Parent.Parent.PromptState)
local NumberLocalizer = require(script.Parent.Parent.Connection.NumberLocalizer)
local AutoResizeList = require(script.Parent.AutoResizeList)
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
local connectToStore = require(script.Parent.Parent.Parent.connectToStore)
local function Price(props)
return withLayoutValues(function(values)
local layoutOrder = props.layoutOrder
local showPrice = props.showPrice
local price = props.price
return showPrice and Roact.createElement(AutoResizeList, {
layoutOrder = layoutOrder,
horizontalAlignment = Enum.HorizontalAlignment.Left,
verticalAlignment = Enum.VerticalAlignment.Center,
fillDirection = Enum.FillDirection.Horizontal,
}, {
RobuxIconContainer = Roact.createElement("Frame", {
BorderSizePixel = 0,
BackgroundTransparency = 1,
Size = values.Size.RobuxIconContainerFrame,
LayoutOrder = 1,
}, {
RobuxIcon = Roact.createElement("ImageLabel", {
Size = values.Size.RobuxIcon,
BackgroundTransparency = 1,
BorderSizePixel = 0,
AnchorPoint = Vector2.new(0, 0.5),
Position = UDim2.new(0, 0, 0.5, 0),
Image = values.Image.RobuxIcon.Path,
}),
}),
PriceTextLabel = Roact.createElement(NumberLocalizer, {
number = price,
render = function(localizedNumber)
return Roact.createElement("TextLabel", {
Text = localizedNumber,
LayoutOrder = 2,
Size = values.Size.PriceTextLabel,
BackgroundTransparency = 1,
BorderSizePixel = 0,
TextColor3 = Color3.fromRGB(2, 183, 87),
Font = Enum.Font.SourceSansBold,
TextSize = values.TextSize.Default,
TextXAlignment = Enum.TextXAlignment.Left,
TextScaled = true,
TextWrapped = true,
}, {
TextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
MaxTextSize = values.TextSize.Default,
})
})
end,
})
})
end)
end
local function mapStateToProps(state)
local promptState = state.promptState
local canPurchase = promptState ~= PromptState.CannotPurchase
and promptState ~= PromptState.Error
local free = state.productInfo.price == 0
return {
showPrice = canPurchase and not free,
price = state.productInfo.price,
}
end
Price = connectToStore(
mapStateToProps
)(Price)
return Price
@@ -0,0 +1,22 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local UnitTestContainer = require(script.Parent.Parent.Parent.Test.UnitTestContainer)
local Price = require(script.Parent.Price)
Price = Price.getUnconnected()
it("should create and destroy without errors", function()
local element = Roact.createElement(UnitTestContainer, nil, {
Roact.createElement(Price, {
layoutOrder = 1,
imageUrl = "",
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,135 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local PromptState = require(script.Parent.Parent.Parent.PromptState)
local PurchaseError = require(script.Parent.Parent.Parent.PurchaseError)
local LocalizationService = require(script.Parent.Parent.Parent.Localization.LocalizationService)
local TextLocalizer = require(script.Parent.Parent.Connection.TextLocalizer)
local AutoSizedTextLabel = require(script.Parent.AutoSizedTextLabel)
local Price = require(script.Parent.Price)
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
local connectToStore = require(script.Parent.Parent.Parent.connectToStore)
local PURCHASE_MESSAGE_KEY = "CoreScripts.PurchasePrompt.PurchaseMessage.%s"
local function ProductDescription(props)
return withLayoutValues(function(values)
local layoutOrder = props.layoutOrder
local descriptionKey = props.descriptionKey
local descriptionParams = props.descriptionParams
return Roact.createElement("Frame", {
BorderSizePixel = 0,
Size = values.Size.ProductDescription,
BackgroundTransparency = 1,
LayoutOrder = layoutOrder,
}, {
ProductDescriptionPadding = Roact.createElement("UIPadding", {
PaddingTop = UDim.new(0, values.Size.ProductDescriptionPaddingTop),
}),
ProductDescriptionListLayout = Roact.createElement("UIListLayout", {
HorizontalAlignment = Enum.HorizontalAlignment.Left,
VerticalAlignment = Enum.VerticalAlignment.Top,
FillDirection = Enum.FillDirection.Vertical,
SortOrder = Enum.SortOrder.LayoutOrder,
}),
ProductDescriptionText = Roact.createElement(TextLocalizer, {
key = descriptionKey,
params = descriptionParams,
render = function(localizedText)
return Roact.createElement(AutoSizedTextLabel, {
width = values.Size.ProductDescription.X.Offset - values.Size.HorizontalPadding,
Text = localizedText,
BackgroundTransparency = 1,
BorderSizePixel = 0,
TextColor3 = Color3.new(1, 1, 1),
Font = Enum.Font.SourceSans,
TextSize = values.TextSize.ProductDescription,
TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Top,
TextScaled = true,
TextWrapped = true,
LayoutOrder = 1,
}, {
TextSizeConstraint = Roact.createElement("UITextSizeConstraint", {
MaxTextSize = values.TextSize.ProductDescription,
}),
})
end,
}),
Price = Roact.createElement(Price, {
layoutOrder = 2,
}),
})
end)
end
local function mapStateToProps(state)
local promptState = state.promptState
local isFree = state.productInfo.price == 0
local descriptionKey
local descriptionParams
if promptState == PromptState.PurchaseComplete then
descriptionKey = PURCHASE_MESSAGE_KEY:format("Succeeded")
descriptionParams = {
ITEM_NAME = state.productInfo.name,
NEEDED_AMOUNT = LocalizationService.numberParam(
state.productInfo.price - state.accountInfo.balance
),
ASSET_TYPE = LocalizationService.nestedKeyParam(
LocalizationService.getAssetTypeKey(state.productInfo.assetTypeId)
),
}
elseif promptState == PromptState.RobuxUpsell then
descriptionKey = PURCHASE_MESSAGE_KEY:format("NeedMoreRobux")
descriptionParams = {
ITEM_NAME = state.productInfo.name,
NEEDED_AMOUNT = LocalizationService.numberParam(
state.productInfo.price - state.accountInfo.balance
),
ASSET_TYPE = LocalizationService.nestedKeyParam(
LocalizationService.getAssetTypeKey(state.productInfo.assetTypeId)
),
}
elseif promptState == PromptState.CannotPurchase or promptState == PromptState.Error then
descriptionKey = LocalizationService.getErrorKey(state.purchaseError)
if state.purchaseError == PurchaseError.UnknownFailure then
descriptionParams = {
ITEM_NAME = state.productInfo.name
}
end
elseif promptState ~= PromptState.Hidden then
if isFree then
descriptionKey = PURCHASE_MESSAGE_KEY:format("Free")
descriptionParams = {
ITEM_NAME = state.productInfo.name,
}
else
descriptionKey = PURCHASE_MESSAGE_KEY:format("Purchase")
descriptionParams = {
ASSET_TYPE = LocalizationService.nestedKeyParam(
LocalizationService.getAssetTypeKey(state.productInfo.assetTypeId)
),
ITEM_NAME = state.productInfo.name,
}
end
end
return {
descriptionKey = descriptionKey,
descriptionParams = descriptionParams,
}
end
ProductDescription = connectToStore(
mapStateToProps
)(ProductDescription)
return ProductDescription
@@ -0,0 +1,29 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Rodux = require(CorePackages.Rodux)
local Reducer = require(script.Parent.Parent.Parent.Reducers.Reducer)
local UnitTestContainer = require(script.Parent.Parent.Parent.Test.UnitTestContainer)
local ProductDescription = require(script.Parent.ProductDescription)
ProductDescription = ProductDescription.getUnconnected()
it("should create and destroy without errors", function()
local element = Roact.createElement(UnitTestContainer, {
overrideStore = Rodux.Store.new(Reducer, {
productInfo = {
price = 10,
},
})
}, {
Roact.createElement(ProductDescription, {
descriptionKey = "CoreScripts.PurchasePrompt.PurchaseMessage.Purchase",
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,105 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local PromptState = require(script.Parent.Parent.Parent.PromptState)
local purchaseItem = require(script.Parent.Parent.Parent.Thunks.purchaseItem)
local launchRobuxUpsell = require(script.Parent.Parent.Parent.Thunks.launchRobuxUpsell)
local launchBuildersClubUpsell = require(script.Parent.Parent.Parent.Thunks.launchBuildersClubUpsell)
local ConfirmButton = require(script.Parent.ConfirmButton)
local CancelButton = require(script.Parent.CancelButton)
local OkButton = require(script.Parent.OkButton)
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
local connectToStore = require(script.Parent.Parent.Parent.connectToStore)
local CONFIRM_PURCHASE_KEY = "CoreScripts.PurchasePrompt.ConfirmPurchase.%s"
local PromptButtons = Roact.PureComponent:extend("PromptButtons")
function PromptButtons:render()
return withLayoutValues(function(values)
local layoutOrder = self.props.layoutOrder
local onClose = self.props.onClose
local promptState = self.props.promptState
local price = self.props.price
local onBuy = self.props.onBuy
local onRobuxUpsell = self.props.onRobuxUpsell
local onBuildersClubUpsell = self.props.onBuildersClubUpsell
local children
if promptState == PromptState.PurchaseComplete
or promptState == PromptState.CannotPurchase
or promptState == PromptState.Error
then
children = {
UIPadding = Roact.createElement("UIPadding", {
PaddingBottom = UDim.new(0, 4),
}),
OkButton = Roact.createElement(OkButton, {
onClick = onClose,
}),
}
else
local confirmButtonStringKey = CONFIRM_PURCHASE_KEY:format("BuyNow")
local leftButtonCallback = onBuy
if price == 0 then
confirmButtonStringKey = CONFIRM_PURCHASE_KEY:format("TakeFree")
elseif promptState == PromptState.RobuxUpsell then
confirmButtonStringKey = CONFIRM_PURCHASE_KEY:format("BuyRobux")
leftButtonCallback = onRobuxUpsell
elseif promptState == PromptState.BuildersClubUpsell then
confirmButtonStringKey = CONFIRM_PURCHASE_KEY:format("UpgradeBuildersClub")
leftButtonCallback = onBuildersClubUpsell
end
children = {
ConfirmButton = Roact.createElement(ConfirmButton, {
stringKey = confirmButtonStringKey,
onClick = leftButtonCallback,
}),
CancelButton = Roact.createElement(CancelButton, {
onClick = onClose,
}),
}
end
return Roact.createElement("Frame", {
LayoutOrder = layoutOrder,
BackgroundTransparency = 1,
BorderSizePixel = 0,
Size = UDim2.new(1, 0, 0, values.Size.ButtonHeight)
}, children)
end)
end
local function mapStateToProps(state)
return {
promptState = state.promptState,
price = state.productInfo.price,
}
end
local function mapDispatchToProps(dispatch)
return {
onBuy = function()
dispatch(purchaseItem())
end,
onRobuxUpsell = function()
dispatch(launchRobuxUpsell())
end,
onBuildersClubUpsell = function()
dispatch(launchBuildersClubUpsell())
end,
}
end
PromptButtons = connectToStore(
mapStateToProps,
mapDispatchToProps
)(PromptButtons)
return PromptButtons
@@ -0,0 +1,42 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local PromptState = require(script.Parent.Parent.Parent.PromptState)
local UnitTestContainer = require(script.Parent.Parent.Parent.Test.UnitTestContainer)
local PromptButtons = require(script.Parent.PromptButtons)
PromptButtons = PromptButtons.getUnconnected()
local function noop()
end
it("should create and destroy without errors with one button", function()
local element = Roact.createElement(UnitTestContainer, nil, {
Roact.createElement(PromptButtons, {
layoutOrder = 1,
onClose = noop,
promptState = PromptState.PurchaseComplete,
price = 1,
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
it("should create and destroy without errors with two buttons", function()
local element = Roact.createElement(UnitTestContainer, nil, {
Roact.createElement(PromptButtons, {
layoutOrder = 1,
onClose = noop,
promptState = PromptState.PromptPurchase,
price = 1,
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,55 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local AutoResizeList = require(script.Parent.AutoResizeList)
local ItemPreviewImage = require(script.Parent.ItemPreviewImage)
local ProductDescription = require(script.Parent.ProductDescription)
local PromptButtons = require(script.Parent.PromptButtons)
local AdditionalDetailLabel = require(script.Parent.AdditionalDetailLabel)
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
local function PromptContents(props)
return withLayoutValues(function(values)
local onClose = props.onClose
return Roact.createElement("Frame", {
Size = UDim2.new(1, 0, 1, 0),
BorderSizePixel = 0,
BackgroundTransparency = 1,
}, {
ListLayout = Roact.createElement("UIListLayout", {
FillDirection = Enum.FillDirection.Vertical,
SortOrder = Enum.SortOrder.LayoutOrder,
}),
PromptBody = Roact.createElement(AutoResizeList, {
layoutOrder = 1,
backgroundImage = values.Image.PromptBackground.Path,
sliceCenter = values.Image.PromptBackground.SliceCenter,
fillDirection = Enum.FillDirection.Vertical,
}, {
ProductInfo = Roact.createElement(AutoResizeList, {
layoutOrder = 1,
fillDirection = Enum.FillDirection.Horizontal,
}, {
ItemPreviewImage = Roact.createElement(ItemPreviewImage, {
layoutOrder = 1,
}),
ProductDescription = Roact.createElement(ProductDescription, {
layoutOrder = 2,
})
}),
AdditionalDetails = Roact.createElement(AdditionalDetailLabel, {
layoutOrder = 2,
}),
}),
PromptButtons = Roact.createElement(PromptButtons, {
layoutOrder = 2,
onClose = onClose,
}),
})
end)
end
return PromptContents
@@ -0,0 +1,38 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Rodux = require(CorePackages.Rodux)
local Reducer = require(script.Parent.Parent.Parent.Reducers.Reducer)
local PromptState = require(script.Parent.Parent.Parent.PromptState)
local UnitTestContainer = require(script.Parent.Parent.Parent.Test.UnitTestContainer)
local PromptContents = require(script.Parent.PromptContents)
it("should create and destroy without errors", function()
local element = Roact.createElement(UnitTestContainer, {
promptState = PromptState.PromptPurchase,
overrideStore = Rodux.Store.new(Reducer, {
promptState = PromptState.PromptPurchase,
accountInfo = {
balance = 100,
},
productInfo = {
assetTypeId = 2, -- T-shirt
price = 10,
},
})
}, {
Roact.createElement(PromptContents, {
layoutOrder = 1,
onClose = function()
end,
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,114 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Otter = require(CorePackages.Otter)
local PromptState = require(script.Parent.Parent.Parent.PromptState)
local HidePrompt = require(script.Parent.Parent.Parent.Actions.HidePrompt)
local PromptContents = require(script.Parent.PromptContents)
local InProgressContents = require(script.Parent.InProgressContents)
local connectToStore = require(script.Parent.Parent.Parent.connectToStore)
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
local PurchasePrompt = Roact.Component:extend(script.Name)
local SPRING_CONFIG = {
dampingRatio = 1,
frequency = 1.6,
}
function PurchasePrompt:init()
local setPromptHidden = self.props.setPromptHidden
self.motor = Otter.createSingleMotor(0)
self.motor:start()
self.ref = Roact.createRef()
self.motor:onStep(function(value)
local frame = self.ref.current
if frame then
frame.AnchorPoint = Vector2.new(0.5, 1 - 0.5 * value)
frame.Position = UDim2.new(0.5, 0, 0.5 * value, 0)
end
end)
self.onClose = function()
local onCompleteDisconnector
onCompleteDisconnector = self.motor:onComplete(function()
setPromptHidden()
onCompleteDisconnector()
self.motor:stop()
end)
self.motor:setGoal(Otter.spring(0, SPRING_CONFIG))
end
end
function PurchasePrompt:didUpdate(prevProps, prevState)
if prevProps.promptState ~= self.props.promptState then
local goal = self.props.promptState == PromptState.Hidden and 0 or 1
self.motor:setGoal(Otter.spring(goal, SPRING_CONFIG))
if prevProps.promptState == PromptState.Hidden then
self.motor:start()
end
end
end
function PurchasePrompt:render()
return withLayoutValues(function(values)
local promptState = self.props.promptState
local contents
if promptState == PromptState.Hidden then
--[[
When the prompt is hidden, we'd rather not keep unused Roblox
instances for it around, so we don't render them
]]
contents = nil
elseif promptState == PromptState.PurchaseInProgress or promptState == PromptState.UpsellInProgress then
contents = Roact.createElement(InProgressContents)
else
contents = Roact.createElement(PromptContents, {
onClose = self.onClose,
})
end
return Roact.createElement("Frame", {
Size = values.Size.Dialog,
BorderSizePixel = 0,
BackgroundTransparency = 1,
[Roact.Ref] = self.ref
}, {
PromptContents = contents,
})
end)
end
local function mapStateToProps(state)
return {
promptState = state.promptState
}
end
local function mapDispatchToProps(dispatch)
return {
setPromptHidden = function()
dispatch(HidePrompt())
end
}
end
PurchasePrompt = connectToStore(
mapStateToProps,
mapDispatchToProps
)(PurchasePrompt)
return PurchasePrompt
@@ -0,0 +1,36 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Rodux = require(CorePackages.Rodux)
local Reducer = require(script.Parent.Parent.Parent.Reducers.Reducer)
local PromptState = require(script.Parent.Parent.Parent.PromptState)
local UnitTestContainer = require(script.Parent.Parent.Parent.Test.UnitTestContainer)
local PurchasePrompt = require(script.Parent.PurchasePrompt)
PurchasePrompt = PurchasePrompt.getUnconnected()
it("should create and destroy without errors", function()
local element = Roact.createElement(UnitTestContainer, {
overrideStore = Rodux.Store.new(Reducer, {
promptState = PromptState.PromptPurchase,
accountInfo = {
balance = 100,
},
productInfo = {
assetTypeId = 2, -- T-shirt
price = 10,
},
})
}, {
Roact.createElement(PurchasePrompt, {
promptState = PromptState.PromptPurchase,
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,68 @@
local CorePackages = game:GetService("CorePackages")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local Roact = require(CorePackages.Roact)
local AnimatedDot = require(script.Parent.AnimatedDot)
local ExternalEventConnection = require(script.Parent.Parent.Connection.ExternalEventConnection)
local withLayoutValues = require(script.Parent.Parent.Connection.withLayoutValues)
local ANIMATION_SPEED_MULTIPLIER = 2.00
local PurchasingAnimation = Roact.Component:extend("PurchasingAnimation")
function PurchasingAnimation:init()
self.state = {
gameTime = 0,
}
self.onRenderStepped = function()
self:setState({
gameTime = Workspace.DistributedGameTime
})
end
end
function PurchasingAnimation:render()
return withLayoutValues(function(values)
local layoutOrder = self.props.layoutOrder
local gameTime = self.state.gameTime
local animationTime = (gameTime * ANIMATION_SPEED_MULTIPLIER) % 3
return Roact.createElement("Frame", {
Size = values.Size.PurchasingAnimation,
BackgroundTransparency = 1,
BorderSizePixel = 0,
LayoutOrder = layoutOrder,
}, {
RenderSteppedConnection = Roact.createElement(ExternalEventConnection, {
event = RunService.RenderStepped,
callback = self.onRenderStepped,
}),
ListLayout = Roact.createElement("UIListLayout", {
HorizontalAlignment = Enum.HorizontalAlignment.Center,
VerticalAlignment = Enum.VerticalAlignment.Center,
FillDirection = Enum.FillDirection.Horizontal,
SortOrder = Enum.SortOrder.LayoutOrder,
}),
AnimatedDot1 = Roact.createElement(AnimatedDot, {
layoutOrder = 1,
time = animationTime,
}),
AnimatedDot2 = Roact.createElement(AnimatedDot, {
layoutOrder = 2,
time = animationTime,
}),
AnimatedDot3 = Roact.createElement(AnimatedDot, {
layoutOrder = 3,
time = animationTime,
}),
})
end)
end
return PurchasingAnimation
@@ -0,0 +1,19 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local UnitTestContainer = require(script.Parent.Parent.Parent.Test.UnitTestContainer)
local PurchasingAnimation = require(script.Parent.PurchasingAnimation)
it("should create and destroy without errors", function()
local element = Roact.createElement(UnitTestContainer, nil, {
Roact.createElement(PurchasingAnimation, {
layoutOrder = 1,
})
})
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,73 @@
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local Rodux = require(CorePackages.Rodux)
local RoactRodux = require(CorePackages.RoactRodux)
local Thunk = require(script.Parent.Parent.Thunk)
local EventConnections = require(script.Parent.Connection.EventConnections)
local PurchasePrompt = require(script.Parent.Presentation.PurchasePrompt)
local LayoutValuesProvider = require(script.Parent.Connection.LayoutValuesProvider)
local provideRobloxLocale = require(script.Parent.Connection.provideRobloxLocale)
local Network = require(script.Parent.Parent.Services.Network)
local Analytics = require(script.Parent.Parent.Services.Analytics)
local PlatformInterface = require(script.Parent.Parent.Services.PlatformInterface)
local ExternalSettings = require(script.Parent.Parent.Services.ExternalSettings)
local LayoutValues = require(script.Parent.Parent.Services.LayoutValues)
local Reducer = require(script.Parent.Parent.Reducers.Reducer)
local preloadImageAssets = require(script.Parent.Parent.preloadImageAssets)
local PurchasePromptApp = Roact.Component:extend("PurchasePromptApp")
function PurchasePromptApp:init()
local initialState = {}
local network = Network.new()
local analytics = Analytics.new()
local platformInterface = PlatformInterface.new()
local externalSettings = ExternalSettings.new()
self.state = {
store = Rodux.Store.new(Reducer, initialState, {
Thunk.middleware({
[Network] = network,
[Analytics] = analytics,
[PlatformInterface] = platformInterface,
[ExternalSettings] = externalSettings,
}),
}),
layoutValues = LayoutValues.generate(externalSettings.isTenFootInterface()),
}
end
function PurchasePromptApp:didMount()
preloadImageAssets(self.state.layoutValues.Image)
end
function PurchasePromptApp:render()
return provideRobloxLocale(function()
return Roact.createElement(RoactRodux.StoreProvider, {
store = self.state.store,
}, {
PurchasePrompt = Roact.createElement(LayoutValuesProvider, {
layoutValues = self.state.layoutValues,
render = function()
return Roact.createElement("ScreenGui", {
AutoLocalize = false,
IgnoreGuiInset = true,
}, {
PurchasePromptUI = Roact.createElement(PurchasePrompt),
EventConnections = Roact.createElement(EventConnections),
})
end,
})
})
end)
end
return PurchasePromptApp
@@ -0,0 +1,13 @@
return function()
local CorePackages = game:GetService("CorePackages")
local Roact = require(CorePackages.Roact)
local PurchasePromptApp = require(script.Parent.PurchasePromptApp)
it("should create and destroy without errors", function()
local element = Roact.createElement(PurchasePromptApp)
local instance = Roact.mount(element)
Roact.unmount(instance)
end)
end
@@ -0,0 +1,5 @@
local Symbol = require(script.Parent.Symbol)
local LayoutValuesKey = Symbol.named("LayoutValuesKey")
return LayoutValuesKey
@@ -0,0 +1,88 @@
local PurchaseError = require(script.Parent.Parent.PurchaseError)
local KeyMappings = {}
local PURCHASE_FAILED_KEY = "CoreScripts.PurchasePrompt.PurchaseFailed.%s"
local ASSET_TYPE_KEY = "Common.AssetTypes.Label.%s"
local BUILDERS_CLUB_LEVEL_KEY = "Common.BuildersClub.Label.%s"
KeyMappings.AssetTypeById = {
--[[
This key is a special case; developer products only exist
within the context of a game, so they're localized with the
rest of the purchase prompt strings.
]]
["0"] = "CoreScripts.PurchasePrompt.ProductType.Product",
--[[
The rest of these are asset types associated with Roblox
assets that exist outside of games, mostly related to
avatar customization
]]
["2"] = ASSET_TYPE_KEY:format("TShirt"),
["3"] = ASSET_TYPE_KEY:format("Audio"),
["4"] = ASSET_TYPE_KEY:format("Mesh"),
["8"] = ASSET_TYPE_KEY:format("Hat"),
["9"] = ASSET_TYPE_KEY:format("Place"),
["10"] = ASSET_TYPE_KEY:format("Model"),
["11"] = ASSET_TYPE_KEY:format("Shirt"),
["12"] = ASSET_TYPE_KEY:format("Pants"),
["13"] = ASSET_TYPE_KEY:format("Decal"),
["17"] = ASSET_TYPE_KEY:format("Head"),
["18"] = ASSET_TYPE_KEY:format("Face"),
["19"] = ASSET_TYPE_KEY:format("Gear"),
["21"] = ASSET_TYPE_KEY:format("Badge"),
["24"] = ASSET_TYPE_KEY:format("Animation"),
["27"] = ASSET_TYPE_KEY:format("Torso"),
["28"] = ASSET_TYPE_KEY:format("RightArm"),
["29"] = ASSET_TYPE_KEY:format("LeftArm"),
["30"] = ASSET_TYPE_KEY:format("LeftLeg"),
["31"] = ASSET_TYPE_KEY:format("RightLeg"),
["32"] = ASSET_TYPE_KEY:format("Package"),
["34"] = ASSET_TYPE_KEY:format("GamePass"),
["38"] = ASSET_TYPE_KEY:format("Plugin"),
["40"] = ASSET_TYPE_KEY:format("MeshPart"),
["41"] = ASSET_TYPE_KEY:format("Hair"),
["42"] = ASSET_TYPE_KEY:format("Face"),
["43"] = ASSET_TYPE_KEY:format("Neck"),
["44"] = ASSET_TYPE_KEY:format("Shoulder"),
["45"] = ASSET_TYPE_KEY:format("Front"),
["46"] = ASSET_TYPE_KEY:format("Back"),
["47"] = ASSET_TYPE_KEY:format("Waist"),
["48"] = ASSET_TYPE_KEY:format("Climb"),
["49"] = ASSET_TYPE_KEY:format("Death"),
["50"] = ASSET_TYPE_KEY:format("Fall"),
["51"] = ASSET_TYPE_KEY:format("Idle"),
["52"] = ASSET_TYPE_KEY:format("Jump"),
["53"] = ASSET_TYPE_KEY:format("Run"),
["54"] = ASSET_TYPE_KEY:format("Swim"),
["55"] = ASSET_TYPE_KEY:format("Walk"),
["56"] = ASSET_TYPE_KEY:format("Pose"),
}
KeyMappings.BuildersClubLevelById = {
["1"] = BUILDERS_CLUB_LEVEL_KEY:format("BuildersClub"),
["2"] = BUILDERS_CLUB_LEVEL_KEY:format("TurboBuildersClub"),
["3"] = BUILDERS_CLUB_LEVEL_KEY:format("OutrageousBuildersClub"),
}
KeyMappings.PurchaseErrorKey = {
[PurchaseError.CannotGetBalance] = PURCHASE_FAILED_KEY:format("CannotGetBalance"),
[PurchaseError.CannotGetItemPrice] = PURCHASE_FAILED_KEY:format("CannotGetItemPrice"),
[PurchaseError.NotForSale] = PURCHASE_FAILED_KEY:format("NotForSale"),
[PurchaseError.AlreadyOwn] = PURCHASE_FAILED_KEY:format("AlreadyOwn"),
[PurchaseError.Under13] = PURCHASE_FAILED_KEY:format("Under13"),
[PurchaseError.Limited] = PURCHASE_FAILED_KEY:format("Limited"),
[PurchaseError.Guest] = PURCHASE_FAILED_KEY:format("PromptPurchaseOnGuest"),
[PurchaseError.ThirdPartyDisabled] = PURCHASE_FAILED_KEY:format("ThirdPartyDisabled"),
[PurchaseError.NotEnoughRobux] = PURCHASE_FAILED_KEY:format("NotEnoughRobux"),
[PurchaseError.NotEnoughRobuxXbox] = PURCHASE_FAILED_KEY:format("NotEnoughRobuxXbox"),
[PurchaseError.BuildersClubLevelTooLow] = PURCHASE_FAILED_KEY:format("BuildersClubLevelTooLow"),
[PurchaseError.UnknownFailure] = PURCHASE_FAILED_KEY:format("UnknownFailure"),
[PurchaseError.UnknownFailureNoItemName] = PURCHASE_FAILED_KEY:format("UnknownFailureNoItemName"),
[PurchaseError.PurchaseDisabled] = PURCHASE_FAILED_KEY:format("PurchaseDisabled"),
[PurchaseError.InvalidFunds] = PURCHASE_FAILED_KEY:format("InvalidFunds"),
[PurchaseError.BuildersClubUpsellFailure] = PURCHASE_FAILED_KEY:format("BuildersClubUpsellFailure"),
}
return KeyMappings
@@ -0,0 +1,119 @@
--[[----------------------------------------------------------------------------------------------------
<auto-generated>
This file was generated by: ClientIntegration/Tools/LuaStringsGenerator/GenerateAllLocales.py
Changes to this file should always follow:
Building an Internationalized Feature - Engineer's Guide:
https://confluence.roblox.com/display/IN/Building+an+Internationalized+Feature+-+Engineer%27s+Guide
Sync up with newly-updated translations:
https://confluence.roblox.com/display/MOBAPP/Sync+up+with+newly-updated+translations
</auto-generated>
--------------------------------------------------------------------------------------------------------]]
return{
["Common.AssetTypes.Label.Accessories"] = [[Accessoires]],
["Common.AssetTypes.Label.Hat"] = [[Hut]],
["Common.AssetTypes.Label.Hair"] = [[Haare]],
["Common.AssetTypes.Label.Face"] = [[Gesicht]],
["Common.AssetTypes.Label.Neck"] = [[Hals]],
["Common.AssetTypes.Label.Shoulder"] = [[Schulter]],
["Common.AssetTypes.Label.Front"] = [[Vorderseite]],
["Common.AssetTypes.Label.Back"] = [[Rückseite]],
["Common.AssetTypes.Label.Waist"] = [[Taille]],
["Common.AssetTypes.Label.Animations"] = [[Animationen]],
["Common.AssetTypes.Label.Audio"] = [[Audiodateien]],
["Common.AssetTypes.Label.AvatarAnimations"] = [[Avataranimationen]],
["Common.AssetTypes.Label.Badges"] = [[Abzeichen]],
["Common.AssetTypes.Label.Decals"] = [[Decals]],
["Common.AssetTypes.Label.Faces"] = [[Gesichter]],
["Common.AssetTypes.Label.GamePasses"] = [[Spielpässe]],
["Common.AssetTypes.Label.Gear"] = [[Ausrüstung]],
["Common.AssetTypes.Label.Heads"] = [[Köpfe]],
["Common.AssetTypes.Label.Meshes"] = [[Meshes]],
["Common.AssetTypes.Label.Models"] = [[Modelle]],
["Common.AssetTypes.Label.Packages"] = [[Pakete]],
["Common.AssetTypes.Label.Pants"] = [[Hosen]],
["Common.AssetTypes.Label.Places"] = [[Orte]],
["Common.AssetTypes.Label.Plugins"] = [[Plug-ins]],
["Common.AssetTypes.Label.Shirts"] = [[Hemden]],
["Common.AssetTypes.Label.TShirts"] = [[T-Shirts]],
["Common.AssetTypes.Label.VipServers"] = [[VIP-Server]],
["Common.AssetTypes.Label.Run"] = [[Laufen]],
["Common.AssetTypes.Label.Walk"] = [[Gehen]],
["Common.AssetTypes.Label.Fall"] = [[Fallen]],
["Common.AssetTypes.Label.Jump"] = [[Springen]],
["Common.AssetTypes.Label.Idle"] = [[Untätig]],
["Common.AssetTypes.Label.Swim"] = [[Schwimmen]],
["Common.AssetTypes.Label.Climb"] = [[Klettern]],
["Common.AssetTypes.Label.Hats"] = [[Hüte]],
["Common.AssetTypes.Label.Shoulders"] = [[Schultern]],
["Common.AssetTypes.Label.Death"] = [[Tod]],
["Common.AssetTypes.Label.Pose"] = [[Pose]],
["Common.AssetTypes.Label.Head"] = [[Kopf]],
["Common.AssetTypes.Label.TShirt"] = [[T-Shirt]],
["Common.AssetTypes.Label.Shirt"] = [[Hemd]],
["Common.AssetTypes.Label.Decal"] = [[Decal]],
["Common.AssetTypes.Label.Model"] = [[Modell]],
["Common.AssetTypes.Label.Plugin"] = [[Plug-in]],
["Common.AssetTypes.Label.MeshPart"] = [[Mesh-Teil]],
["Common.AssetTypes.Label.GamePass"] = [[Spielpass]],
["Common.AssetTypes.Label.Badge"] = [[Abzeichen]],
["Common.AssetTypes.Label.Package"] = [[Paket]],
["Common.AssetTypes.Label.Place"] = [[Ort]],
["Common.AssetTypes.Label.LeftArm"] = [[Linker Arm]],
["Common.AssetTypes.Label.LeftLeg"] = [[Linkes Bein]],
["Common.AssetTypes.Label.RightArm"] = [[Rechter Arm]],
["Common.AssetTypes.Label.RightLeg"] = [[Rechtes Bein]],
["Common.AssetTypes.Label.Torso"] = [[Torso]],
["Common.AssetTypes.Label.Animation"] = [[Animation]],
["Common.BuildersClub.Label.PlanFree"] = [[Gratis]],
["Common.BuildersClub.Label.PlanClassic"] = [[Klassisch]],
["Common.BuildersClub.Label.PlanTurbo"] = [[Turbo]],
["Common.BuildersClub.Label.PlanOutrageous"] = [[Outrageous]],
["Common.BuildersClub.Label.BuildersClub"] = [[Builders Club]],
["Common.BuildersClub.Label.BuildersClubMembership"] = [[„Builders Club“-Mitgliedschaft]],
["Common.BuildersClub.Label.BuildersClubMembershipTurbo"] = [[„Turbo Builders Club“-Mitgliedschaft]],
["Common.BuildersClub.Label.BuildersClubMembershipOutrageous"] = [[„Outrageous Builders Club“-Mitgliedschaft]],
["Common.BuildersClub.Label.TurboBuildersClub"] = [[Turbo Builders Club]],
["Common.BuildersClub.Label.OutrageousBuildersClub"] = [[Outrageous Builders Club]],
["Common.BuildersClub.Label.Yes"] = [[Ja]],
["Common.BuildersClub.Label.No"] = [[Nein]],
["Common.BuildersClub.Label.NeverUppercase"] = [[NIEMALS]],
["Common.BuildersClub.Label.Robux"] = [[Robux]],
["Common.BuildersClub.Label.ClassicBuildersClub"] = [[Classic Builders Club]],
["Common.BuildersClub.Label.Lifetime"] = [[Auf Lebenszeit]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.TakeFree"] = [[Gratis nehmen]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.UpgradeBuildersClub"] = [[Aufwerten]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.BuyNow"] = [[Jetzt kaufen]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.BuyRobux"] = [[R$ kaufen]],
["CoreScripts.PurchasePrompt.CancelPurchase.Cancel"] = [[Abbrechen]],
["CoreScripts.PurchasePrompt.Button.OK"] = [[Okay]],
["CoreScripts.PurchasePrompt.Purchasing"] = [[Wird gekauft ...]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceUnaffected"] = [[Das Guthaben deines Kontos wird durch diese Transaktion nicht beeinflusst.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.MockPurchase"] = [[Dies ist ein Testkauf. Dein Konto wird dadurch nicht belastet.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.MockPurchaseComplete"] = [[Dies war ein Testkauf.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.InvalidBuildersClub"] = [[Dieser Gegenstand erfordert {BC_LEVEL}. Klicke auf „Aufwerten“, um deinen Builders-Club-Status zu verbessern!]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceFuture"] = [[Nach dieser Transaktion wird dein Guthaben {BALANCE_FUTURE} R$ betragen.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceNow"] = [[Dein Guthaben beträgt nun {BALANCE_NOW}.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.RemainingAfterUpsell"] = [[Die verbleibenden {REMAINING_ROBUX} Robux werden deinem Guthaben gutgeschrieben.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.InvalidFunds"] = [[Dein Kauf ist fehlgeschlagen, da dein Konto nicht über genügend Robux verfügt. Dein Konto wurde nicht belastet.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.BuildersClubUpsellFailure"] = [[Dein Kauf ist fehlgeschlagen, da du ein Abonnement benötigst, um diesen Gegenstand zu kaufen. Dein Konto wurde nicht belastet.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotEnoughRobuxXbox"] = [[Dieser Gegenstand kostet mehr Robux, als dir zur Verfügung stehen. Bitte verlasse dieses Spiel, gehe zum Robux-Menü und kaufe dort mehr.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotEnoughRobux"] = [[Dieser Gegenstand kostet mehr Robux, als du kaufen kannst. Bitte besuche www.roblox.com, um mehr Robux zu kaufen.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.PurchaseDisabled"] = [[Dein Kauf ist fehlgeschlagen, da Käufe im Spiel derzeit deaktiviert sind. Dein Konto wurde nicht belastet. Bitte versuche es später erneut.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.UnknownFailureNoItemName"] = [[Dein Kauf ist fehlgeschlagen, da ein Problem aufgetreten ist. Dein Konto wurde nicht belastet. Bitte versuche es später erneut.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.UnknownFailure"] = [[Dein Kauf von „{ITEM_NAME}“ ist fehlgeschlagen, da ein Problem aufgetreten ist. Dein Konto wurde nicht belastet. Bitte versuche es später erneut.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.CannotGetBalance"] = [[Dein Guthaben kann derzeit nicht abgerufen werden. Dein Konto wurde nicht belastet. Bitte versuche es später erneut.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.CannotGetItemPrice"] = [[Der Preis des Gegenstands kann derzeit nicht abgerufen werden. Dein Konto wurde nicht belastet. Bitte versuche es später erneut.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.Limited"] = [[Von diesem limitierten Gegenstand gibt es keine weiteren Exemplare. Such auf www.roblox.com nach einem anderen Verkäufer. Dein Konto wurde nicht belastet.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotForSale"] = [[Dieser Gegenstand steht derzeit nicht zum Verkauf. Dein Konto wurde nicht belastet.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.PromptPurchaseOnGuest"] = [[Du musst ein Roblox-Konto erstellen, um Gegenstände zu kaufen. Auf www.roblox.com findest du weitere Infos.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.ThirdPartyDisabled"] = [[Gegenstände von Drittanbietern können an diesem Ort nicht verkauft werden. Dein Konto wurde nicht belastet.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.Under13"] = [[Dein Konto ist für Spieler unter 13 Jahren. Der Kauf dieses Gegenstands ist nicht gestattet. Dein Konto wurde nicht belastet.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.AlreadyOwn"] = [[Diesen Gegenstand besitzt du bereits. Dein Konto wurde nicht belastet.]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Free"] = [[Möchtest du „{ITEM_NAME}“ gerne GRATIS nehmen?]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Purchase"] = [[Möchtest du „{ITEM_NAME}“ ({ASSET_TYPE}) kaufen für]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Succeeded"] = [[Du hast „{ITEM_NAME}“ gekauft!]],
["CoreScripts.PurchasePrompt.PurchaseMessage.NeedMoreRobux"] = [[Du brauchst noch {NEEDED_AMOUNT} Robux, um „{ITEM_NAME}“ ({ASSET_TYPE}) zu kaufen. Möchtest du mehr Robux kaufen?]],
["CoreScripts.PurchasePrompt.ProductType.Product"] = [[Produkt]],
}
@@ -0,0 +1,119 @@
--[[----------------------------------------------------------------------------------------------------
<auto-generated>
This file was generated by: ClientIntegration/Tools/LuaStringsGenerator/GenerateAllLocales.py
Changes to this file should always follow:
Building an Internationalized Feature - Engineer's Guide:
https://confluence.roblox.com/display/IN/Building+an+Internationalized+Feature+-+Engineer%27s+Guide
Sync up with newly-updated translations:
https://confluence.roblox.com/display/MOBAPP/Sync+up+with+newly-updated+translations
</auto-generated>
--------------------------------------------------------------------------------------------------------]]
return{
["Common.AssetTypes.Label.Accessories"] = [[Accessories]],
["Common.AssetTypes.Label.Hat"] = [[Hat]],
["Common.AssetTypes.Label.Hair"] = [[Hair]],
["Common.AssetTypes.Label.Face"] = [[Face]],
["Common.AssetTypes.Label.Neck"] = [[Neck]],
["Common.AssetTypes.Label.Shoulder"] = [[Shoulder]],
["Common.AssetTypes.Label.Front"] = [[Front]],
["Common.AssetTypes.Label.Back"] = [[Back]],
["Common.AssetTypes.Label.Waist"] = [[Waist]],
["Common.AssetTypes.Label.Animations"] = [[Animations]],
["Common.AssetTypes.Label.Audio"] = [[Audio]],
["Common.AssetTypes.Label.AvatarAnimations"] = [[Avatar Animations]],
["Common.AssetTypes.Label.Badges"] = [[Badges]],
["Common.AssetTypes.Label.Decals"] = [[Decals]],
["Common.AssetTypes.Label.Faces"] = [[Faces]],
["Common.AssetTypes.Label.GamePasses"] = [[Game Passes]],
["Common.AssetTypes.Label.Gear"] = [[Gear]],
["Common.AssetTypes.Label.Heads"] = [[Heads]],
["Common.AssetTypes.Label.Meshes"] = [[Meshes]],
["Common.AssetTypes.Label.Models"] = [[Models]],
["Common.AssetTypes.Label.Packages"] = [[Packages]],
["Common.AssetTypes.Label.Pants"] = [[Pants]],
["Common.AssetTypes.Label.Places"] = [[Places]],
["Common.AssetTypes.Label.Plugins"] = [[Plugins]],
["Common.AssetTypes.Label.Shirts"] = [[Shirts]],
["Common.AssetTypes.Label.TShirts"] = [[T-Shirts]],
["Common.AssetTypes.Label.VipServers"] = [[VIP Servers]],
["Common.AssetTypes.Label.Run"] = [[Run]],
["Common.AssetTypes.Label.Walk"] = [[Walk]],
["Common.AssetTypes.Label.Fall"] = [[Fall]],
["Common.AssetTypes.Label.Jump"] = [[Jump]],
["Common.AssetTypes.Label.Idle"] = [[Idle]],
["Common.AssetTypes.Label.Swim"] = [[Swim]],
["Common.AssetTypes.Label.Climb"] = [[Climb]],
["Common.AssetTypes.Label.Hats"] = [[Hats]],
["Common.AssetTypes.Label.Shoulders"] = [[Shoulders]],
["Common.AssetTypes.Label.Death"] = [[Death]],
["Common.AssetTypes.Label.Pose"] = [[Pose]],
["Common.AssetTypes.Label.Head"] = [[Head]],
["Common.AssetTypes.Label.TShirt"] = [[T-Shirt]],
["Common.AssetTypes.Label.Shirt"] = [[Shirt]],
["Common.AssetTypes.Label.Decal"] = [[Decal]],
["Common.AssetTypes.Label.Model"] = [[Model]],
["Common.AssetTypes.Label.Plugin"] = [[Plugin]],
["Common.AssetTypes.Label.MeshPart"] = [[Mesh Part]],
["Common.AssetTypes.Label.GamePass"] = [[Game Pass]],
["Common.AssetTypes.Label.Badge"] = [[Badge]],
["Common.AssetTypes.Label.Package"] = [[Package]],
["Common.AssetTypes.Label.Place"] = [[Place]],
["Common.AssetTypes.Label.LeftArm"] = [[Left Arm]],
["Common.AssetTypes.Label.LeftLeg"] = [[Left Leg]],
["Common.AssetTypes.Label.RightArm"] = [[Right Arm]],
["Common.AssetTypes.Label.RightLeg"] = [[Right Leg]],
["Common.AssetTypes.Label.Torso"] = [[Torso]],
["Common.AssetTypes.Label.Animation"] = [[Animation]],
["Common.BuildersClub.Label.PlanFree"] = [[Free]],
["Common.BuildersClub.Label.PlanClassic"] = [[Classic]],
["Common.BuildersClub.Label.PlanTurbo"] = [[Turbo]],
["Common.BuildersClub.Label.PlanOutrageous"] = [[Outrageous]],
["Common.BuildersClub.Label.BuildersClub"] = [[Builders Club]],
["Common.BuildersClub.Label.BuildersClubMembership"] = [[Builders Club Membership]],
["Common.BuildersClub.Label.BuildersClubMembershipTurbo"] = [[Turbo Builders Club Membership]],
["Common.BuildersClub.Label.BuildersClubMembershipOutrageous"] = [[Outrageous Builders Club Membership]],
["Common.BuildersClub.Label.TurboBuildersClub"] = [[Turbo Builders Club]],
["Common.BuildersClub.Label.OutrageousBuildersClub"] = [[Outrageous Builders Club]],
["Common.BuildersClub.Label.Yes"] = [[Yes]],
["Common.BuildersClub.Label.No"] = [[No]],
["Common.BuildersClub.Label.NeverUppercase"] = [[NEVER]],
["Common.BuildersClub.Label.Robux"] = [[Robux]],
["Common.BuildersClub.Label.ClassicBuildersClub"] = [[Classic Builders Club]],
["Common.BuildersClub.Label.Lifetime"] = [[Lifetime]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.TakeFree"] = [[Take Free]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.UpgradeBuildersClub"] = [[Upgrade]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.BuyNow"] = [[Buy Now]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.BuyRobux"] = [[Buy R$]],
["CoreScripts.PurchasePrompt.CancelPurchase.Cancel"] = [[Cancel]],
["CoreScripts.PurchasePrompt.Button.OK"] = [[OK]],
["CoreScripts.PurchasePrompt.Purchasing"] = [[Purchasing]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceUnaffected"] = [[Your account balance will not be affected by this transaction.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.MockPurchase"] = [[This is a test purchase; your account will not be charged.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.MockPurchaseComplete"] = [[This was a test purchase.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.InvalidBuildersClub"] = [[This item requires {BC_LEVEL}. Click 'Upgrade' to upgrade your Builders Club!]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceFuture"] = [[Your balance after this transaction will be R${BALANCE_FUTURE}]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceNow"] = [[Your balance is now {BALANCE_NOW}.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.RemainingAfterUpsell"] = [[The remaining {REMAINING_ROBUX} Robux will be credited to your balance.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.InvalidFunds"] = [[Your purchase failed because your account does not have enough Robux. Your account has not been charged.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.BuildersClubUpsellFailure"] = [[Your purchase failed because you need a subscription to purchase this item. Your account has not been charged.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotEnoughRobuxXbox"] = [[This item cost more Robux than you have available. Please leave this game and go to the Robux screen to purchase more.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotEnoughRobux"] = [[This item cost more Robux than you can purchase. Please visit www.roblox.com to purchase more Robux.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.PurchaseDisabled"] = [[Your purchase failed because In-game purchases are temporarily disabled. Your account has not been charged. Please try again later.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.UnknownFailureNoItemName"] = [[Your purchase failed because something went wrong. Your account has not been charged. Please try again later.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.UnknownFailure"] = [[Your purchase of {ITEM_NAME} failed because something went wrong. Your account has not been charged. Please try again later.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.CannotGetBalance"] = [[Cannot retrieve your balance at this time. Your account has not been charged. Please try again later.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.CannotGetItemPrice"] = [[We couldn't retrieve the price of the item at this time. Your account has not been charged. Please try again later.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.Limited"] = [[This limited item has no more copies. Try buying from another user on www.roblox.com. Your account has not been charged.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotForSale"] = [[This item is not currently for sale. Your account has not been charged.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.PromptPurchaseOnGuest"] = [[You need to create a ROBLOX account to buy items, visit www.roblox.com for more info.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.ThirdPartyDisabled"] = [[Third-party item sales have been disabled for this place. Your account has not been charged.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.Under13"] = [[Your account is under 13. Purchase of this item is not allowed. Your account has not been charged.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.AlreadyOwn"] = [[You already own this item. Your account has not been charged.]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Free"] = [[Would you like to take {ITEM_NAME} for FREE?]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Purchase"] = [[Want to buy the {ASSET_TYPE} {ITEM_NAME} for]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Succeeded"] = [[Your purchase of {ITEM_NAME} succeeded!]],
["CoreScripts.PurchasePrompt.PurchaseMessage.NeedMoreRobux"] = [[You need {NEEDED_AMOUNT} more Robux to buy the {ASSET_TYPE} {ITEM_NAME}. Would you like to buy more Robux?]],
["CoreScripts.PurchasePrompt.ProductType.Product"] = [[Product]],
}
@@ -0,0 +1,119 @@
--[[----------------------------------------------------------------------------------------------------
<auto-generated>
This file was generated by: ClientIntegration/Tools/LuaStringsGenerator/GenerateAllLocales.py
Changes to this file should always follow:
Building an Internationalized Feature - Engineer's Guide:
https://confluence.roblox.com/display/IN/Building+an+Internationalized+Feature+-+Engineer%27s+Guide
Sync up with newly-updated translations:
https://confluence.roblox.com/display/MOBAPP/Sync+up+with+newly-updated+translations
</auto-generated>
--------------------------------------------------------------------------------------------------------]]
return{
["Common.AssetTypes.Label.Accessories"] = [[Accesorios]],
["Common.AssetTypes.Label.Hat"] = [[Sombrero]],
["Common.AssetTypes.Label.Hair"] = [[Pelo]],
["Common.AssetTypes.Label.Face"] = [[Cara]],
["Common.AssetTypes.Label.Neck"] = [[Cuello]],
["Common.AssetTypes.Label.Shoulder"] = [[Hombro]],
["Common.AssetTypes.Label.Front"] = [[Frontal]],
["Common.AssetTypes.Label.Back"] = [[Trasero]],
["Common.AssetTypes.Label.Waist"] = [[Cintura]],
["Common.AssetTypes.Label.Animations"] = [[Animaciones]],
["Common.AssetTypes.Label.Audio"] = [[Sonidos]],
["Common.AssetTypes.Label.AvatarAnimations"] = [[Animaciones de avatar]],
["Common.AssetTypes.Label.Badges"] = [[Emblemas]],
["Common.AssetTypes.Label.Decals"] = [[Adhesivos]],
["Common.AssetTypes.Label.Faces"] = [[Caras]],
["Common.AssetTypes.Label.GamePasses"] = [[Pases del juego]],
["Common.AssetTypes.Label.Gear"] = [[Equipamiento]],
["Common.AssetTypes.Label.Heads"] = [[Cabezas]],
["Common.AssetTypes.Label.Meshes"] = [[Mallas]],
["Common.AssetTypes.Label.Models"] = [[Modelos]],
["Common.AssetTypes.Label.Packages"] = [[Paquetes]],
["Common.AssetTypes.Label.Pants"] = [[Pantalones]],
["Common.AssetTypes.Label.Places"] = [[Lugares]],
["Common.AssetTypes.Label.Plugins"] = [[Complementos]],
["Common.AssetTypes.Label.Shirts"] = [[Camisas]],
["Common.AssetTypes.Label.TShirts"] = [[Camisetas]],
["Common.AssetTypes.Label.VipServers"] = [[Servidores VIP]],
["Common.AssetTypes.Label.Run"] = [[Carrera]],
["Common.AssetTypes.Label.Walk"] = [[Marcha]],
["Common.AssetTypes.Label.Fall"] = [[Caída]],
["Common.AssetTypes.Label.Jump"] = [[Salto]],
["Common.AssetTypes.Label.Idle"] = [[Inactividad]],
["Common.AssetTypes.Label.Swim"] = [[Nado]],
["Common.AssetTypes.Label.Climb"] = [[Escalada]],
["Common.AssetTypes.Label.Hats"] = [[Sombreros]],
["Common.AssetTypes.Label.Shoulders"] = [[Hombros]],
["Common.AssetTypes.Label.Death"] = [[La muerte]],
["Common.AssetTypes.Label.Pose"] = [[Pose]],
["Common.AssetTypes.Label.Head"] = [[Cabeza]],
["Common.AssetTypes.Label.TShirt"] = [[Camiseta]],
["Common.AssetTypes.Label.Shirt"] = [[Camisa]],
["Common.AssetTypes.Label.Decal"] = [[Adhesivo]],
["Common.AssetTypes.Label.Model"] = [[Modelo]],
["Common.AssetTypes.Label.Plugin"] = [[Complemento]],
["Common.AssetTypes.Label.MeshPart"] = [[Parte de la malla]],
["Common.AssetTypes.Label.GamePass"] = [[Pase del juego]],
["Common.AssetTypes.Label.Badge"] = [[Emblema]],
["Common.AssetTypes.Label.Package"] = [[Paquete]],
["Common.AssetTypes.Label.Place"] = [[Lugar]],
["Common.AssetTypes.Label.LeftArm"] = [[Brazo izquierdo]],
["Common.AssetTypes.Label.LeftLeg"] = [[Pierna izquierda]],
["Common.AssetTypes.Label.RightArm"] = [[Brazo derecho]],
["Common.AssetTypes.Label.RightLeg"] = [[Pierna derecha]],
["Common.AssetTypes.Label.Torso"] = [[Torso]],
["Common.AssetTypes.Label.Animation"] = [[Animación]],
["Common.BuildersClub.Label.PlanFree"] = [[Gratis]],
["Common.BuildersClub.Label.PlanClassic"] = [[Clásico]],
["Common.BuildersClub.Label.PlanTurbo"] = [[Turbo]],
["Common.BuildersClub.Label.PlanOutrageous"] = [[Outrageous]],
["Common.BuildersClub.Label.BuildersClub"] = [[Builders Club]],
["Common.BuildersClub.Label.BuildersClubMembership"] = [[Suscripción al Builders Club]],
["Common.BuildersClub.Label.BuildersClubMembershipTurbo"] = [[Suscripción al Turbo Builders Club]],
["Common.BuildersClub.Label.BuildersClubMembershipOutrageous"] = [[Suscripción al Outrageous Builders Club]],
["Common.BuildersClub.Label.TurboBuildersClub"] = [[Turbo Builders Club]],
["Common.BuildersClub.Label.OutrageousBuildersClub"] = [[Outrageous Builders Club]],
["Common.BuildersClub.Label.Yes"] = [[Sí]],
["Common.BuildersClub.Label.No"] = [[No]],
["Common.BuildersClub.Label.NeverUppercase"] = [[NUNCA]],
["Common.BuildersClub.Label.Robux"] = [[Robux]],
["Common.BuildersClub.Label.ClassicBuildersClub"] = [[Builders Club Clásico]],
["Common.BuildersClub.Label.Lifetime"] = [[Vitalicia]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.TakeFree"] = [[Llévatelo gratis]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.UpgradeBuildersClub"] = [[Mejorar]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.BuyNow"] = [[Comprar ahora]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.BuyRobux"] = [[Comprar R$]],
["CoreScripts.PurchasePrompt.CancelPurchase.Cancel"] = [[Cancelar]],
["CoreScripts.PurchasePrompt.Button.OK"] = [[Aceptar]],
["CoreScripts.PurchasePrompt.Purchasing"] = [[Comprando]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceUnaffected"] = [[Esta operación no afectará tu saldo.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.MockPurchase"] = [[Esta es una compra de prueba; no se te cobrará por esta operación.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.MockPurchaseComplete"] = [[Esta fue una compra de prueba.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.InvalidBuildersClub"] = [[Para comprar este objeto se requiere {BC_LEVEL}. Haz clic en Mejorar para pasar a un nivel de suscripción superior del Builders Club.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceFuture"] = [[Tu saldo después de esta transacción será de R${BALANCE_FUTURE}]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceNow"] = [[Tu saldo es de {BALANCE_NOW}.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.RemainingAfterUpsell"] = [[Los {REMAINING_ROBUX} Robux restantes se abonarán a tu saldo.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.InvalidFunds"] = [[La compra no se ha realizado porque la cuenta no tiene suficientes Robux. No se te ha cobrado.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.BuildersClubUpsellFailure"] = [[La compra no se ha realizado porque se necesita una suscripción para adquirir este objeto. No se ha cobrado.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotEnoughRobuxXbox"] = [[No tienes suficientes Robux para comprar este objeto. Sal del juego y dirígete a la pantalla de Robux para obtener más.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotEnoughRobux"] = [[No tienes suficientes Robux para comprar este objeto. Visita la página www.roblox.com para obtener más Robux.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.PurchaseDisabled"] = [[La compra no se ha realizado porque las compras dentro del juego están desactivadas temporalmente. No se te ha cobrado. Inténtalo de nuevo más tarde.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.UnknownFailureNoItemName"] = [[La compra no se ha realizado porque algo ha ido mal. No se te ha cobrado. Inténtalo de nuevo más tarde.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.UnknownFailure"] = [[La compra de {ITEM_NAME} no se ha realizado porque algo ha ido mal. No se te ha cobrado. Inténtalo de nuevo más tarde.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.CannotGetBalance"] = [[No se ha podido recuperar tu saldo en este momento. No se te ha cobrado. Inténtalo de nuevo más tarde.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.CannotGetItemPrice"] = [[No se ha podido recuperar el precio de este objeto en este momento. No se te ha cobrado. Inténtalo de nuevo más tarde.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.Limited"] = [[No hay más copias disponibles de este objeto de edición limitada. Intenta comprarlo de otro usuario en www.roblox.com. No se te ha cobrado.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotForSale"] = [[Este objeto no está en venta en este momento. No se te ha cobrado.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.PromptPurchaseOnGuest"] = [[Tienes que crear una cuenta de Roblox para comprar objetos. Visita www.roblox.com para más información.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.ThirdPartyDisabled"] = [[Se ha desactivado la venta de objetos de terceros para este lugar. No se te ha cobrado.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.Under13"] = [[Tu cuenta es para menores de 13 años. No se permite la compra de este objeto. No se te ha cobrado.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.AlreadyOwn"] = [[Ya tienes este objeto. No se te ha cobrado.]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Free"] = [[El objeto {ITEM_NAME} es gratuito. ¿Te lo quieres llevar?]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Purchase"] = [[¿Quieres comprar {ASSET_TYPE} {ITEM_NAME} por]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Succeeded"] = [[¡La compra de {ITEM_NAME} se ha realizado correctamente!]],
["CoreScripts.PurchasePrompt.PurchaseMessage.NeedMoreRobux"] = [[Se necesitan {NEEDED_AMOUNT} Robux más para comprar {ASSET_TYPE} {ITEM_NAME}. ¿Quieres obtener más Robux?]],
["CoreScripts.PurchasePrompt.ProductType.Product"] = [[Artículos]],
}
@@ -0,0 +1,119 @@
--[[----------------------------------------------------------------------------------------------------
<auto-generated>
This file was generated by: ClientIntegration/Tools/LuaStringsGenerator/GenerateAllLocales.py
Changes to this file should always follow:
Building an Internationalized Feature - Engineer's Guide:
https://confluence.roblox.com/display/IN/Building+an+Internationalized+Feature+-+Engineer%27s+Guide
Sync up with newly-updated translations:
https://confluence.roblox.com/display/MOBAPP/Sync+up+with+newly-updated+translations
</auto-generated>
--------------------------------------------------------------------------------------------------------]]
return{
["Common.AssetTypes.Label.Accessories"] = [[Accessoires]],
["Common.AssetTypes.Label.Hat"] = [[Chapeau]],
["Common.AssetTypes.Label.Hair"] = [[Cheveux]],
["Common.AssetTypes.Label.Face"] = [[Visage]],
["Common.AssetTypes.Label.Neck"] = [[Cou]],
["Common.AssetTypes.Label.Shoulder"] = [[Épaules]],
["Common.AssetTypes.Label.Front"] = [[Avant]],
["Common.AssetTypes.Label.Back"] = [[Retour]],
["Common.AssetTypes.Label.Waist"] = [[Taille]],
["Common.AssetTypes.Label.Animations"] = [[Animations]],
["Common.AssetTypes.Label.Audio"] = [[Audio]],
["Common.AssetTypes.Label.AvatarAnimations"] = [[Animations d'avatar]],
["Common.AssetTypes.Label.Badges"] = [[Badges]],
["Common.AssetTypes.Label.Decals"] = [[Insignes]],
["Common.AssetTypes.Label.Faces"] = [[Visages]],
["Common.AssetTypes.Label.GamePasses"] = [[Passes de jeu]],
["Common.AssetTypes.Label.Gear"] = [[Équipement]],
["Common.AssetTypes.Label.Heads"] = [[Têtes]],
["Common.AssetTypes.Label.Meshes"] = [[Maillages]],
["Common.AssetTypes.Label.Models"] = [[Modèles]],
["Common.AssetTypes.Label.Packages"] = [[Packs]],
["Common.AssetTypes.Label.Pants"] = [[Pantalons]],
["Common.AssetTypes.Label.Places"] = [[Emplacements]],
["Common.AssetTypes.Label.Plugins"] = [[Plugins]],
["Common.AssetTypes.Label.Shirts"] = [[Chemises]],
["Common.AssetTypes.Label.TShirts"] = [[Tee-shirts]],
["Common.AssetTypes.Label.VipServers"] = [[Serveurs VIP]],
["Common.AssetTypes.Label.Run"] = [[Course]],
["Common.AssetTypes.Label.Walk"] = [[Marche]],
["Common.AssetTypes.Label.Fall"] = [[Chute]],
["Common.AssetTypes.Label.Jump"] = [[Saut]],
["Common.AssetTypes.Label.Idle"] = [[Inaction]],
["Common.AssetTypes.Label.Swim"] = [[Nage]],
["Common.AssetTypes.Label.Climb"] = [[Escalade]],
["Common.AssetTypes.Label.Hats"] = [[Chapeaux]],
["Common.AssetTypes.Label.Shoulders"] = [[Épaules]],
["Common.AssetTypes.Label.Death"] = [[Mort]],
["Common.AssetTypes.Label.Pose"] = [[Pose]],
["Common.AssetTypes.Label.Head"] = [[Tête]],
["Common.AssetTypes.Label.TShirt"] = [[Tee-shirt]],
["Common.AssetTypes.Label.Shirt"] = [[Chemise]],
["Common.AssetTypes.Label.Decal"] = [[Insigne]],
["Common.AssetTypes.Label.Model"] = [[Modèle]],
["Common.AssetTypes.Label.Plugin"] = [[Plugin]],
["Common.AssetTypes.Label.MeshPart"] = [[Partie de maillage]],
["Common.AssetTypes.Label.GamePass"] = [[Passe de jeu]],
["Common.AssetTypes.Label.Badge"] = [[Badge]],
["Common.AssetTypes.Label.Package"] = [[Pack]],
["Common.AssetTypes.Label.Place"] = [[Emplacement]],
["Common.AssetTypes.Label.LeftArm"] = [[Bras gauche]],
["Common.AssetTypes.Label.LeftLeg"] = [[Jambe gauche]],
["Common.AssetTypes.Label.RightArm"] = [[Bras droit]],
["Common.AssetTypes.Label.RightLeg"] = [[Jambe droite]],
["Common.AssetTypes.Label.Torso"] = [[Torse]],
["Common.AssetTypes.Label.Animation"] = [[Animation]],
["Common.BuildersClub.Label.PlanFree"] = [[Gratuit]],
["Common.BuildersClub.Label.PlanClassic"] = [[Classique]],
["Common.BuildersClub.Label.PlanTurbo"] = [[Turbo]],
["Common.BuildersClub.Label.PlanOutrageous"] = [[Outrageous]],
["Common.BuildersClub.Label.BuildersClub"] = [[Builders Club]],
["Common.BuildersClub.Label.BuildersClubMembership"] = [[Abonnement au Builders Club]],
["Common.BuildersClub.Label.BuildersClubMembershipTurbo"] = [[Abonnement au Turbo Builders Club]],
["Common.BuildersClub.Label.BuildersClubMembershipOutrageous"] = [[Abonnement à l'Outrageous Builders Club]],
["Common.BuildersClub.Label.TurboBuildersClub"] = [[Turbo Builders Club]],
["Common.BuildersClub.Label.OutrageousBuildersClub"] = [[Outrageous Builders Club]],
["Common.BuildersClub.Label.Yes"] = [[Oui]],
["Common.BuildersClub.Label.No"] = [[Non]],
["Common.BuildersClub.Label.NeverUppercase"] = [[JAMAIS]],
["Common.BuildersClub.Label.Robux"] = [[Robux]],
["Common.BuildersClub.Label.ClassicBuildersClub"] = [[Classic Builders Club]],
["Common.BuildersClub.Label.Lifetime"] = [[À vie]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.TakeFree"] = [[Prendre gratuitement]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.UpgradeBuildersClub"] = [[Améliorer]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.BuyNow"] = [[Acheter maintenant]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.BuyRobux"] = [[Acheter des R$]],
["CoreScripts.PurchasePrompt.CancelPurchase.Cancel"] = [[Annuler]],
["CoreScripts.PurchasePrompt.Button.OK"] = [[OK]],
["CoreScripts.PurchasePrompt.Purchasing"] = [[Achat]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceUnaffected"] = [[Le solde de votre compte ne sera pas affecté par cette transaction.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.MockPurchase"] = [[Ceci est un achat test ; votre compte ne sera pas débité.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.MockPurchaseComplete"] = [[C'était un achat test.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.InvalidBuildersClub"] = [[Cet objet nécessite {BC_LEVEL}. Cliquez sur « Améliorer » pour améliorer votre Builders Club !]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceFuture"] = [[Votre solde après cette transaction sera de {BALANCE_FUTURE} R$]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceNow"] = [[Votre solde est désormais de {BALANCE_NOW}.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.RemainingAfterUpsell"] = [[Les {REMAINING_ROBUX} Robux restants seront portés à votre solde.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.InvalidFunds"] = [[Échec de la transaction. Motif : votre compte ne possède pas assez de Robux. Votre compte n'a pas été débité.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.BuildersClubUpsellFailure"] = [[Échec de la transaction. Motif : il vous faut un abonnement pour acheter cet objet. Votre compte n'a pas été débité.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotEnoughRobuxXbox"] = [[Cette objet coûte plus de Robux que vous n'en avez. Veuillez quitter le jeu et aller à l'écran Robux pour en acheter plus.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotEnoughRobux"] = [[Cet objet coûte trop de Robux pour que vous l'achetiez. Veuillez vous rendre sur www.roblox.com pour acheter plus de Robux.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.PurchaseDisabled"] = [[Échec de la transaction. Motif : les achats en jeu sont temporairement désactivés. Votre compte n'a pas été débité. Veuillez réessayer plus tard.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.UnknownFailureNoItemName"] = [[Échec de la transaction. Motif : un problème est survenu. Votre compte n'a pas été débité. Veuillez réessayer plus tard.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.UnknownFailure"] = [[Votre achat de {ITEM_NAME} a échoué. Motif : un problème est survenu. Votre compte n'a pas été débité. Veuillez réessayer plus tard.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.CannotGetBalance"] = [[Impossible d'obtenir votre solde pour l'instant. Votre compte n'a pas été débité. Veuillez réessayer plus tard.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.CannotGetItemPrice"] = [[Nous ne pouvons pas récupérer le prix de cet objet pour l'instant. Votre compte n'a pas été débité. Veuillez réessayer plus tard.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.Limited"] = [[Il n'y a plus d'exemplaires de cet objet en série limitée. Essayez de l'acheter à un autre utilisateur sur www.roblox.com. Votre compte n'a pas été débité.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotForSale"] = [[Cet objet n'est pas en vente pour l'instant. Votre compte n'a pas été débité.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.PromptPurchaseOnGuest"] = [[Vous devez créer un compte ROBLOX pour acheter des objets, rendez-vous sur www.roblox.com pour plus d'informations.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.ThirdPartyDisabled"] = [[Les ventes d'objets par des tiers ont été désactivées pour cet emplacement. Votre compte n'a pas été débité.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.Under13"] = [[Le propriétaire de ce compte a moins de 13 ans. L'achat de cet objet n'est pas permis. Votre compte n'a pas été débité.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.AlreadyOwn"] = [[Vous possédez déjà cet objet. Votre compte n'a pas été débité.]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Free"] = [[Souhaitez-vous prendre l'objet {ITEM_NAME} GRATUITEMENT ?]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Purchase"] = [[Vous voulez acheter le {ASSET_TYPE} {ITEM_NAME} pour]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Succeeded"] = [[Votre achat de {ITEM_NAME} a réussi !]],
["CoreScripts.PurchasePrompt.PurchaseMessage.NeedMoreRobux"] = [[Vous avez besoin de {NEEDED_AMOUNT} Robux de plus pour acheter le {ASSET_TYPE} {ITEM_NAME}. Souhaitez-vous acheter plus de Robux ?]],
["CoreScripts.PurchasePrompt.ProductType.Product"] = [[Produit]],
}
@@ -0,0 +1,14 @@
--[[----------------------------------------------------------------------------------------------------
<auto-generated>
This file was generated by: ClientIntegration/Tools/LuaStringsGenerator/GenerateAllLocales.py
Changes to this file should always follow:
Building an Internationalized Feature - Engineer's Guide:
https://confluence.roblox.com/display/IN/Building+an+Internationalized+Feature+-+Engineer%27s+Guide
Sync up with newly-updated translations:
https://confluence.roblox.com/display/MOBAPP/Sync+up+with+newly-updated+translations
</auto-generated>
--------------------------------------------------------------------------------------------------------]]
return{
}
@@ -0,0 +1,14 @@
--[[----------------------------------------------------------------------------------------------------
<auto-generated>
This file was generated by: ClientIntegration/Tools/LuaStringsGenerator/GenerateAllLocales.py
Changes to this file should always follow:
Building an Internationalized Feature - Engineer's Guide:
https://confluence.roblox.com/display/IN/Building+an+Internationalized+Feature+-+Engineer%27s+Guide
Sync up with newly-updated translations:
https://confluence.roblox.com/display/MOBAPP/Sync+up+with+newly-updated+translations
</auto-generated>
--------------------------------------------------------------------------------------------------------]]
return{
}
@@ -0,0 +1,14 @@
--[[----------------------------------------------------------------------------------------------------
<auto-generated>
This file was generated by: ClientIntegration/Tools/LuaStringsGenerator/GenerateAllLocales.py
Changes to this file should always follow:
Building an Internationalized Feature - Engineer's Guide:
https://confluence.roblox.com/display/IN/Building+an+Internationalized+Feature+-+Engineer%27s+Guide
Sync up with newly-updated translations:
https://confluence.roblox.com/display/MOBAPP/Sync+up+with+newly-updated+translations
</auto-generated>
--------------------------------------------------------------------------------------------------------]]
return{
}
@@ -0,0 +1,119 @@
--[[----------------------------------------------------------------------------------------------------
<auto-generated>
This file was generated by: ClientIntegration/Tools/LuaStringsGenerator/GenerateAllLocales.py
Changes to this file should always follow:
Building an Internationalized Feature - Engineer's Guide:
https://confluence.roblox.com/display/IN/Building+an+Internationalized+Feature+-+Engineer%27s+Guide
Sync up with newly-updated translations:
https://confluence.roblox.com/display/MOBAPP/Sync+up+with+newly-updated+translations
</auto-generated>
--------------------------------------------------------------------------------------------------------]]
return{
["Common.AssetTypes.Label.Accessories"] = [[액세서리]],
["Common.AssetTypes.Label.Hat"] = [[모자]],
["Common.AssetTypes.Label.Hair"] = [[헤어]],
["Common.AssetTypes.Label.Face"] = [[얼굴]],
["Common.AssetTypes.Label.Neck"] = [[목]],
["Common.AssetTypes.Label.Shoulder"] = [[어깨]],
["Common.AssetTypes.Label.Front"] = [[가슴]],
["Common.AssetTypes.Label.Back"] = [[등]],
["Common.AssetTypes.Label.Waist"] = [[허리]],
["Common.AssetTypes.Label.Animations"] = [[애니메이션]],
["Common.AssetTypes.Label.Audio"] = [[오디오]],
["Common.AssetTypes.Label.AvatarAnimations"] = [[아바타 애니메이션]],
["Common.AssetTypes.Label.Badges"] = [[배지]],
["Common.AssetTypes.Label.Decals"] = [[데칼]],
["Common.AssetTypes.Label.Faces"] = [[얼굴]],
["Common.AssetTypes.Label.GamePasses"] = [[게임패스]],
["Common.AssetTypes.Label.Gear"] = [[기어]],
["Common.AssetTypes.Label.Heads"] = [[머리]],
["Common.AssetTypes.Label.Meshes"] = [[메시]],
["Common.AssetTypes.Label.Models"] = [[모델]],
["Common.AssetTypes.Label.Packages"] = [[패키지]],
["Common.AssetTypes.Label.Pants"] = [[바지]],
["Common.AssetTypes.Label.Places"] = [[장소]],
["Common.AssetTypes.Label.Plugins"] = [[플러그인]],
["Common.AssetTypes.Label.Shirts"] = [[셔츠]],
["Common.AssetTypes.Label.TShirts"] = [[티셔츠]],
["Common.AssetTypes.Label.VipServers"] = [[VIP 서버]],
["Common.AssetTypes.Label.Run"] = [[달리기]],
["Common.AssetTypes.Label.Walk"] = [[걷기]],
["Common.AssetTypes.Label.Fall"] = [[낙하]],
["Common.AssetTypes.Label.Jump"] = [[점프]],
["Common.AssetTypes.Label.Idle"] = [[기본]],
["Common.AssetTypes.Label.Swim"] = [[수영]],
["Common.AssetTypes.Label.Climb"] = [[오르기]],
["Common.AssetTypes.Label.Hats"] = [[모자]],
["Common.AssetTypes.Label.Shoulders"] = [[어깨]],
["Common.AssetTypes.Label.Death"] = [[사망]],
["Common.AssetTypes.Label.Pose"] = [[포즈]],
["Common.AssetTypes.Label.Head"] = [[머리]],
["Common.AssetTypes.Label.TShirt"] = [[티셔츠]],
["Common.AssetTypes.Label.Shirt"] = [[셔츠]],
["Common.AssetTypes.Label.Decal"] = [[데칼]],
["Common.AssetTypes.Label.Model"] = [[모델]],
["Common.AssetTypes.Label.Plugin"] = [[플러그인]],
["Common.AssetTypes.Label.MeshPart"] = [[메쉬 파트]],
["Common.AssetTypes.Label.GamePass"] = [[게임패스]],
["Common.AssetTypes.Label.Badge"] = [[배지]],
["Common.AssetTypes.Label.Package"] = [[패키지]],
["Common.AssetTypes.Label.Place"] = [[장소]],
["Common.AssetTypes.Label.LeftArm"] = [[왼팔]],
["Common.AssetTypes.Label.LeftLeg"] = [[왼쪽 다리]],
["Common.AssetTypes.Label.RightArm"] = [[오른팔]],
["Common.AssetTypes.Label.RightLeg"] = [[오른쪽 다리]],
["Common.AssetTypes.Label.Torso"] = [[상체]],
["Common.AssetTypes.Label.Animation"] = [[애니메이션]],
["Common.BuildersClub.Label.PlanFree"] = [[무료]],
["Common.BuildersClub.Label.PlanClassic"] = [[클래식]],
["Common.BuildersClub.Label.PlanTurbo"] = [[터보]],
["Common.BuildersClub.Label.PlanOutrageous"] = [[얼티메이트]],
["Common.BuildersClub.Label.BuildersClub"] = [[빌더스 클럽]],
["Common.BuildersClub.Label.BuildersClubMembership"] = [[빌더스 클럽 멤버십]],
["Common.BuildersClub.Label.BuildersClubMembershipTurbo"] = [[터보 빌더스 클럽 멤버십]],
["Common.BuildersClub.Label.BuildersClubMembershipOutrageous"] = [[얼티메이트 빌더스 클럽 멤버십]],
["Common.BuildersClub.Label.TurboBuildersClub"] = [[터보 빌더스 클럽]],
["Common.BuildersClub.Label.OutrageousBuildersClub"] = [[얼티메이트 빌더스 클럽]],
["Common.BuildersClub.Label.Yes"] = [[예]],
["Common.BuildersClub.Label.No"] = [[아니요]],
["Common.BuildersClub.Label.NeverUppercase"] = [[안 함]],
["Common.BuildersClub.Label.Robux"] = [[Robux]],
["Common.BuildersClub.Label.ClassicBuildersClub"] = [[클래식 빌더스 클럽]],
["Common.BuildersClub.Label.Lifetime"] = [[평생]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.TakeFree"] = [[무료 획득]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.UpgradeBuildersClub"] = [[업그레이드]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.BuyNow"] = [[지금 구매]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.BuyRobux"] = [[R$ 구매]],
["CoreScripts.PurchasePrompt.CancelPurchase.Cancel"] = [[취소]],
["CoreScripts.PurchasePrompt.Button.OK"] = [[확인]],
["CoreScripts.PurchasePrompt.Purchasing"] = [[구매 중]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceUnaffected"] = [[계정 잔액은 이 거래의 영향을 받지 않아요.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.MockPurchase"] = [[테스트 구매입니다. 계정에 비용이 청구되지 않아요.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.MockPurchaseComplete"] = [[이 구매는 테스트용입니다.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.InvalidBuildersClub"] = [[이 아이템에는 {BC_LEVEL}이(가) 필요해요. '업그레이드'를 클릭해 빌더스 클럽을 업그레이드하세요!]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceFuture"] = [[이 거래 후의 예상 잔액은 R${BALANCE_FUTURE}이에요.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceNow"] = [[현재 잔액은 {BALANCE_NOW}이에요.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.RemainingAfterUpsell"] = [[남은 {REMAINING_ROBUX} Robux는 잔액에 추가돼요.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.InvalidFunds"] = [[계정에 Robux가 부족해 구매에 실패했어요. 계정에 비용이 청구되지 않아요.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.BuildersClubUpsellFailure"] = [[이 아이템을 구매하기 위한 구독이 필요하기 때문에 구매에 실패했어요. 계정에 비용이 청구되지 않아요.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotEnoughRobuxXbox"] = [[이 아이템의 가격은 소지한 Robux보다 비싸요. 더 많은 Robux를 구매하려면 이 게임에서 나간 후 Robux 화면으로 이동하세요.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotEnoughRobux"] = [[이 아이템 구매에는 더 많은 Robux가 필요해요. Robux를 더 구매하려면 www.roblox.com을 방문하세요.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.PurchaseDisabled"] = [[게임 내 구매가 일시적으로 비활성화되어 구매에 실패했어요. 계정에 비용이 청구되지 않아요. 나중에 다시 시도해 주세요.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.UnknownFailureNoItemName"] = [[오류가 발생해 구매에 실패했어요. 계정에 비용이 청구되지 않아요. 나중에 다시 시도해 주세요.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.UnknownFailure"] = [[오류가 발생해 {ITEM_NAME} 구매에 실패했어요. 계정에 비용이 청구되지 않아요. 나중에 다시 시도해 주세요.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.CannotGetBalance"] = [[현재 잔액을 불러올 수 없어요. 계정에 비용이 청구되지 않아요. 나중에 다시 시도해 주세요.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.CannotGetItemPrice"] = [[현재 아이템 가격을 불러올 수 없어요. 계정에 비용이 청구되지 않아요. 나중에 다시 시도해 주세요.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.Limited"] = [[이 한정 아이템은 더 이상 재고가 없어요. www.roblox.com에서 다른 사용자에게 구매해보세요. 계정에 비용이 청구되지 않아요.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotForSale"] = [[이 아이템은 현재 판매 중이 아니에요. 계정에 비용이 청구되지 않아요.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.PromptPurchaseOnGuest"] = [[아이템을 구매하려면 ROBLOX 계정을 만들어야 해요. 자세한 정보는 www.roblox.com을 방문하세요.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.ThirdPartyDisabled"] = [[여기서는 제삼자 아이템 판매를 이용할 수 없어요. 계정에 비용이 청구되지 않아요.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.Under13"] = [[만 13세 미만의 계정으로 이 아이템을 구매할 수 없어요. 계정에 비용이 청구되지 않아요.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.AlreadyOwn"] = [[이 아이템을 이미 가지고 있어요. 계정에 비용이 청구되지 않아요.]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Free"] = [[무료로 {ITEM_NAME} 받을까요?]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Purchase"] = [[다음 금액으로 {ASSET_TYPE} {ITEM_NAME} 구매할까요]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Succeeded"] = [[{ITEM_NAME} 구매 성공!]],
["CoreScripts.PurchasePrompt.PurchaseMessage.NeedMoreRobux"] = [[{ASSET_TYPE} {ITEM_NAME} 구매에는 {NEEDED_AMOUNT}개의 Robux가 더 필요해요. Robux를 더 구매할까요?]],
["CoreScripts.PurchasePrompt.ProductType.Product"] = [[제품]],
}
@@ -0,0 +1,119 @@
--[[----------------------------------------------------------------------------------------------------
<auto-generated>
This file was generated by: ClientIntegration/Tools/LuaStringsGenerator/GenerateAllLocales.py
Changes to this file should always follow:
Building an Internationalized Feature - Engineer's Guide:
https://confluence.roblox.com/display/IN/Building+an+Internationalized+Feature+-+Engineer%27s+Guide
Sync up with newly-updated translations:
https://confluence.roblox.com/display/MOBAPP/Sync+up+with+newly-updated+translations
</auto-generated>
--------------------------------------------------------------------------------------------------------]]
return{
["Common.AssetTypes.Label.Accessories"] = [[Acessórios]],
["Common.AssetTypes.Label.Hat"] = [[Chapéu]],
["Common.AssetTypes.Label.Hair"] = [[Cabelo]],
["Common.AssetTypes.Label.Face"] = [[Rosto]],
["Common.AssetTypes.Label.Neck"] = [[Pescoço]],
["Common.AssetTypes.Label.Shoulder"] = [[Ombro]],
["Common.AssetTypes.Label.Front"] = [[Frente]],
["Common.AssetTypes.Label.Back"] = [[Costas]],
["Common.AssetTypes.Label.Waist"] = [[Cintura]],
["Common.AssetTypes.Label.Animations"] = [[Animações]],
["Common.AssetTypes.Label.Audio"] = [[Áudio]],
["Common.AssetTypes.Label.AvatarAnimations"] = [[Animações de avatar]],
["Common.AssetTypes.Label.Badges"] = [[Emblemas]],
["Common.AssetTypes.Label.Decals"] = [[Adesivos]],
["Common.AssetTypes.Label.Faces"] = [[Rostos]],
["Common.AssetTypes.Label.GamePasses"] = [[Passes de jogo]],
["Common.AssetTypes.Label.Gear"] = [[Equipamentos]],
["Common.AssetTypes.Label.Heads"] = [[Cabeças]],
["Common.AssetTypes.Label.Meshes"] = [[Malhas]],
["Common.AssetTypes.Label.Models"] = [[Modelos]],
["Common.AssetTypes.Label.Packages"] = [[Pacotes]],
["Common.AssetTypes.Label.Pants"] = [[Calças]],
["Common.AssetTypes.Label.Places"] = [[Locais]],
["Common.AssetTypes.Label.Plugins"] = [[Plugins]],
["Common.AssetTypes.Label.Shirts"] = [[Camisas]],
["Common.AssetTypes.Label.TShirts"] = [[Camisetas]],
["Common.AssetTypes.Label.VipServers"] = [[Servidores VIP]],
["Common.AssetTypes.Label.Run"] = [[Correr]],
["Common.AssetTypes.Label.Walk"] = [[Andar]],
["Common.AssetTypes.Label.Fall"] = [[Cair]],
["Common.AssetTypes.Label.Jump"] = [[Pular]],
["Common.AssetTypes.Label.Idle"] = [[Inatividade]],
["Common.AssetTypes.Label.Swim"] = [[Nadar]],
["Common.AssetTypes.Label.Climb"] = [[Escalar]],
["Common.AssetTypes.Label.Hats"] = [[Chapéus]],
["Common.AssetTypes.Label.Shoulders"] = [[Ombros]],
["Common.AssetTypes.Label.Death"] = [[Morte]],
["Common.AssetTypes.Label.Pose"] = [[Pose]],
["Common.AssetTypes.Label.Head"] = [[Cabeça]],
["Common.AssetTypes.Label.TShirt"] = [[Camisetas]],
["Common.AssetTypes.Label.Shirt"] = [[Camisas]],
["Common.AssetTypes.Label.Decal"] = [[Adesivos]],
["Common.AssetTypes.Label.Model"] = [[Modelos]],
["Common.AssetTypes.Label.Plugin"] = [[Plugins]],
["Common.AssetTypes.Label.MeshPart"] = [[Parte da malha]],
["Common.AssetTypes.Label.GamePass"] = [[Passe de jogo]],
["Common.AssetTypes.Label.Badge"] = [[Emblemas]],
["Common.AssetTypes.Label.Package"] = [[Pacotes]],
["Common.AssetTypes.Label.Place"] = [[Locais]],
["Common.AssetTypes.Label.LeftArm"] = [[Braço esquerdo]],
["Common.AssetTypes.Label.LeftLeg"] = [[Perna esquerda]],
["Common.AssetTypes.Label.RightArm"] = [[Braço direito]],
["Common.AssetTypes.Label.RightLeg"] = [[Perna direita]],
["Common.AssetTypes.Label.Torso"] = [[Tronco]],
["Common.AssetTypes.Label.Animation"] = [[Animações]],
["Common.BuildersClub.Label.PlanFree"] = [[Grátis]],
["Common.BuildersClub.Label.PlanClassic"] = [[Clássico]],
["Common.BuildersClub.Label.PlanTurbo"] = [[Turbo]],
["Common.BuildersClub.Label.PlanOutrageous"] = [[Outrageous]],
["Common.BuildersClub.Label.BuildersClub"] = [[Builders Club]],
["Common.BuildersClub.Label.BuildersClubMembership"] = [[Assinatura do Builders Club]],
["Common.BuildersClub.Label.BuildersClubMembershipTurbo"] = [[Assinatura do Turbo Builders Club]],
["Common.BuildersClub.Label.BuildersClubMembershipOutrageous"] = [[Assinatura do Outrageous Builders Club]],
["Common.BuildersClub.Label.TurboBuildersClub"] = [[Turbo Builders Club]],
["Common.BuildersClub.Label.OutrageousBuildersClub"] = [[Outrageous Builders Club]],
["Common.BuildersClub.Label.Yes"] = [[Sim]],
["Common.BuildersClub.Label.No"] = [[Não]],
["Common.BuildersClub.Label.NeverUppercase"] = [[NUNCA]],
["Common.BuildersClub.Label.Robux"] = [[Robux]],
["Common.BuildersClub.Label.ClassicBuildersClub"] = [[Classic Builders Club]],
["Common.BuildersClub.Label.Lifetime"] = [[Vitalícia]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.TakeFree"] = [[Pegue de graça]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.UpgradeBuildersClub"] = [[Melhorar]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.BuyNow"] = [[Comprar agora]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.BuyRobux"] = [[Comprar R$]],
["CoreScripts.PurchasePrompt.CancelPurchase.Cancel"] = [[Cancelar]],
["CoreScripts.PurchasePrompt.Button.OK"] = [[OK]],
["CoreScripts.PurchasePrompt.Purchasing"] = [[Comprando]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceUnaffected"] = [[O saldo da sua conta não será afetado por esta transação.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.MockPurchase"] = [[Esta é uma compra de teste. Nada será cobrado da sua conta.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.MockPurchaseComplete"] = [[Esta foi uma compra de teste.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.InvalidBuildersClub"] = [[Este item requer {BC_LEVEL}. Clique em 'Melhorar' para melhorar seu Builders Club!]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceFuture"] = [[Seu saldo depois desta transação será de R$ {BALANCE_FUTURE}]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceNow"] = [[Seu saldo agora é {BALANCE_NOW}.]],
["CoreScripts.PurchasePrompt.PurchaseDetails.RemainingAfterUpsell"] = [[Os {REMAINING_ROBUX} Robux restantes serão somados ao seu saldo.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.InvalidFunds"] = [[Sua compra falhou porque sua conta não tem ROBUX suficientes. Nada foi cobrado da sua conta.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.BuildersClubUpsellFailure"] = [[Sua compra falhou porque você precisa de uma assinatura para comprar este item. Nada foi cobrado da sua conta.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotEnoughRobuxXbox"] = [[Este item custa mais Robux do que você possui disponível. Saia do jogo e vá para a tela de Robux para comprar mais.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotEnoughRobux"] = [[Este item custa mais Robux do que você pode comprar. Visite www.roblox.com para comprar mais Robux.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.PurchaseDisabled"] = [[Sua compra falhou porque as compras no jogo estão temporariamente desabilitadas. Nada foi cobrado da sua conta. Tente de novo mais tarde.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.UnknownFailureNoItemName"] = [[Sua compra falhou porque algo deu errado. Nada foi cobrado da sua conta. Tente de novo mais tarde.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.UnknownFailure"] = [[Sua compra do item {ITEM_NAME} falhou porque algo deu errado. Nada foi cobrado da sua conta. Tente de novo mais tarde.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.CannotGetBalance"] = [[Impossível obter seu saldo no momento. Nada foi cobrado da sua conta. Tente de novo mais tarde.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.CannotGetItemPrice"] = [[Não conseguimos obter o preço do item no momento. Nada foi cobrado da sua conta. Tente de novo mais tarde.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.Limited"] = [[Esgotaram-se as cópias deste item limitado. Tente comprar de outro usuário em www.roblox.com. Nada foi cobrado da sua conta.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotForSale"] = [[Este item não está à venda no momento. Nada foi cobrado da sua conta.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.PromptPurchaseOnGuest"] = [[Você precisa criar uma conta ROBLOX para comprar itens. Visite www.roblox.com para mais informações.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.ThirdPartyDisabled"] = [[Vendas de itens de terceiros foram desabilitadas para este local. Nada foi cobrado da sua conta.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.Under13"] = [[Sua conta é para menor de 13 anos. A compra deste item não é permitida. Nada foi cobrado da sua conta.]],
["CoreScripts.PurchasePrompt.PurchaseFailed.AlreadyOwn"] = [[Você já possui este item. Nada foi cobrado da sua conta.]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Free"] = [[Gostaria de obter {ITEM_NAME} GRÁTIS?]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Purchase"] = [[Deseja comprar o(a) {ASSET_TYPE} {ITEM_NAME} por]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Succeeded"] = [[Sua compra de {ITEM_NAME} foi bem-sucedida!]],
["CoreScripts.PurchasePrompt.PurchaseMessage.NeedMoreRobux"] = [[Você precisa de mais {NEEDED_AMOUNT} Robux para comprar o(a) {ASSET_TYPE} {ITEM_NAME}. Gostaria de comprar mais Robux?]],
["CoreScripts.PurchasePrompt.ProductType.Product"] = [[Produto]],
}
@@ -0,0 +1,14 @@
--[[----------------------------------------------------------------------------------------------------
<auto-generated>
This file was generated by: ClientIntegration/Tools/LuaStringsGenerator/GenerateAllLocales.py
Changes to this file should always follow:
Building an Internationalized Feature - Engineer's Guide:
https://confluence.roblox.com/display/IN/Building+an+Internationalized+Feature+-+Engineer%27s+Guide
Sync up with newly-updated translations:
https://confluence.roblox.com/display/MOBAPP/Sync+up+with+newly-updated+translations
</auto-generated>
--------------------------------------------------------------------------------------------------------]]
return{
}
@@ -0,0 +1,14 @@
--[[----------------------------------------------------------------------------------------------------
<auto-generated>
This file was generated by: ClientIntegration/Tools/LuaStringsGenerator/GenerateAllLocales.py
Changes to this file should always follow:
Building an Internationalized Feature - Engineer's Guide:
https://confluence.roblox.com/display/IN/Building+an+Internationalized+Feature+-+Engineer%27s+Guide
Sync up with newly-updated translations:
https://confluence.roblox.com/display/MOBAPP/Sync+up+with+newly-updated+translations
</auto-generated>
--------------------------------------------------------------------------------------------------------]]
return{
}
@@ -0,0 +1,14 @@
--[[----------------------------------------------------------------------------------------------------
<auto-generated>
This file was generated by: ClientIntegration/Tools/LuaStringsGenerator/GenerateAllLocales.py
Changes to this file should always follow:
Building an Internationalized Feature - Engineer's Guide:
https://confluence.roblox.com/display/IN/Building+an+Internationalized+Feature+-+Engineer%27s+Guide
Sync up with newly-updated translations:
https://confluence.roblox.com/display/MOBAPP/Sync+up+with+newly-updated+translations
</auto-generated>
--------------------------------------------------------------------------------------------------------]]
return{
}
@@ -0,0 +1,14 @@
--[[----------------------------------------------------------------------------------------------------
<auto-generated>
This file was generated by: ClientIntegration/Tools/LuaStringsGenerator/GenerateAllLocales.py
Changes to this file should always follow:
Building an Internationalized Feature - Engineer's Guide:
https://confluence.roblox.com/display/IN/Building+an+Internationalized+Feature+-+Engineer%27s+Guide
Sync up with newly-updated translations:
https://confluence.roblox.com/display/MOBAPP/Sync+up+with+newly-updated+translations
</auto-generated>
--------------------------------------------------------------------------------------------------------]]
return{
}
@@ -0,0 +1,119 @@
--[[----------------------------------------------------------------------------------------------------
<auto-generated>
This file was generated by: ClientIntegration/Tools/LuaStringsGenerator/GenerateAllLocales.py
Changes to this file should always follow:
Building an Internationalized Feature - Engineer's Guide:
https://confluence.roblox.com/display/IN/Building+an+Internationalized+Feature+-+Engineer%27s+Guide
Sync up with newly-updated translations:
https://confluence.roblox.com/display/MOBAPP/Sync+up+with+newly-updated+translations
</auto-generated>
--------------------------------------------------------------------------------------------------------]]
return{
["Common.AssetTypes.Label.Accessories"] = [[饰品]],
["Common.AssetTypes.Label.Hat"] = [[帽子]],
["Common.AssetTypes.Label.Hair"] = [[发型]],
["Common.AssetTypes.Label.Face"] = [[脸部]],
["Common.AssetTypes.Label.Neck"] = [[颈]],
["Common.AssetTypes.Label.Shoulder"] = [[肩]],
["Common.AssetTypes.Label.Front"] = [[正面]],
["Common.AssetTypes.Label.Back"] = [[背面]],
["Common.AssetTypes.Label.Waist"] = [[腰]],
["Common.AssetTypes.Label.Animations"] = [[动画]],
["Common.AssetTypes.Label.Audio"] = [[音频]],
["Common.AssetTypes.Label.AvatarAnimations"] = [[虚拟形象动画]],
["Common.AssetTypes.Label.Badges"] = [[徽章]],
["Common.AssetTypes.Label.Decals"] = [[贴花]],
["Common.AssetTypes.Label.Faces"] = [[脸部]],
["Common.AssetTypes.Label.GamePasses"] = [[游戏通行证]],
["Common.AssetTypes.Label.Gear"] = [[装备]],
["Common.AssetTypes.Label.Heads"] = [[头]],
["Common.AssetTypes.Label.Meshes"] = [[网格]],
["Common.AssetTypes.Label.Models"] = [[模型]],
["Common.AssetTypes.Label.Packages"] = [[套装]],
["Common.AssetTypes.Label.Pants"] = [[裤子]],
["Common.AssetTypes.Label.Places"] = [[地点]],
["Common.AssetTypes.Label.Plugins"] = [[插件]],
["Common.AssetTypes.Label.Shirts"] = [[衬衫]],
["Common.AssetTypes.Label.TShirts"] = [[T 恤]],
["Common.AssetTypes.Label.VipServers"] = [[VIP 服务器]],
["Common.AssetTypes.Label.Run"] = [[奔跑]],
["Common.AssetTypes.Label.Walk"] = [[步行]],
["Common.AssetTypes.Label.Fall"] = [[下落]],
["Common.AssetTypes.Label.Jump"] = [[跳跃]],
["Common.AssetTypes.Label.Idle"] = [[闲置]],
["Common.AssetTypes.Label.Swim"] = [[游泳]],
["Common.AssetTypes.Label.Climb"] = [[攀爬]],
["Common.AssetTypes.Label.Hats"] = [[帽子]],
["Common.AssetTypes.Label.Shoulders"] = [[肩]],
["Common.AssetTypes.Label.Death"] = [[死亡]],
["Common.AssetTypes.Label.Pose"] = [[姿势]],
["Common.AssetTypes.Label.Head"] = [[头部]],
["Common.AssetTypes.Label.TShirt"] = [[T 恤]],
["Common.AssetTypes.Label.Shirt"] = [[衬衫]],
["Common.AssetTypes.Label.Decal"] = [[贴花]],
["Common.AssetTypes.Label.Model"] = [[模型]],
["Common.AssetTypes.Label.Plugin"] = [[插件]],
["Common.AssetTypes.Label.MeshPart"] = [[网格组件]],
["Common.AssetTypes.Label.GamePass"] = [[游戏通行证]],
["Common.AssetTypes.Label.Badge"] = [[徽章]],
["Common.AssetTypes.Label.Package"] = [[套装]],
["Common.AssetTypes.Label.Place"] = [[地点]],
["Common.AssetTypes.Label.LeftArm"] = [[左臂]],
["Common.AssetTypes.Label.LeftLeg"] = [[左腿]],
["Common.AssetTypes.Label.RightArm"] = [[右臂]],
["Common.AssetTypes.Label.RightLeg"] = [[右腿]],
["Common.AssetTypes.Label.Torso"] = [[躯干]],
["Common.AssetTypes.Label.Animation"] = [[动画]],
["Common.BuildersClub.Label.PlanFree"] = [[免费]],
["Common.BuildersClub.Label.PlanClassic"] = [[经典]],
["Common.BuildersClub.Label.PlanTurbo"] = [[Turbo]],
["Common.BuildersClub.Label.PlanOutrageous"] = [[Outrageous]],
["Common.BuildersClub.Label.BuildersClub"] = [[Builders Club]],
["Common.BuildersClub.Label.BuildersClubMembership"] = [[建造者社团会员]],
["Common.BuildersClub.Label.BuildersClubMembershipTurbo"] = [[Turbo Builders Club 会员资格]],
["Common.BuildersClub.Label.BuildersClubMembershipOutrageous"] = [[Outrageous Builders Club 会员资格]],
["Common.BuildersClub.Label.TurboBuildersClub"] = [[涡轮建造者社团]],
["Common.BuildersClub.Label.OutrageousBuildersClub"] = [[顶尖建造者社团]],
["Common.BuildersClub.Label.Yes"] = [[是]],
["Common.BuildersClub.Label.No"] = [[否]],
["Common.BuildersClub.Label.NeverUppercase"] = [[从不]],
["Common.BuildersClub.Label.Robux"] = [[Robux]],
["Common.BuildersClub.Label.ClassicBuildersClub"] = [[经典建造者社团]],
["Common.BuildersClub.Label.Lifetime"] = [[永久]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.TakeFree"] = [[免费领取]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.UpgradeBuildersClub"] = [[升级]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.BuyNow"] = [[立即购买]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.BuyRobux"] = [[购买 R$]],
["CoreScripts.PurchasePrompt.CancelPurchase.Cancel"] = [[取消]],
["CoreScripts.PurchasePrompt.Button.OK"] = [[好]],
["CoreScripts.PurchasePrompt.Purchasing"] = [[正在购买]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceUnaffected"] = [[此次交易不会影响你的帐户余额。]],
["CoreScripts.PurchasePrompt.PurchaseDetails.MockPurchase"] = [[此为测试购买,你的帐户不会被收费。]],
["CoreScripts.PurchasePrompt.PurchaseDetails.MockPurchaseComplete"] = [[此为测试购买。]],
["CoreScripts.PurchasePrompt.PurchaseDetails.InvalidBuildersClub"] = [[此物品需要 {BC_LEVEL}。点击“升级”来升级你的建造者社团!]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceFuture"] = [[此次交易后,你的余额将为 R${BALANCE_FUTURE}]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceNow"] = [[你的当前余额为 {BALANCE_NOW}。]],
["CoreScripts.PurchasePrompt.PurchaseDetails.RemainingAfterUpsell"] = [[剩余的 {REMAINING_ROBUX} Robux 将会加进你的余额中。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.InvalidFunds"] = [[购买失败,帐户内 Robux 不足。系统并未向你的帐户收取费用。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.BuildersClubUpsellFailure"] = [[购买失败,你必须具备订阅会员资格才能购买此物品。系统并未向你的帐户收取费用。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotEnoughRobuxXbox"] = [[你拥有的 Robux 不足以购买此物品。请离开本游戏,然后前往 Robux 屏幕购买更多 Robux。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotEnoughRobux"] = [[此物品的价格超过你能购买的 Robux 数目。请访问 www.roblox.com 以购买更多 Robux。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.PurchaseDisabled"] = [[购买失败,因为游戏内购买功能已暂时遭到禁用。系统并未向你的帐户收取费用,请稍后重试。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.UnknownFailureNoItemName"] = [[购买失败,因为有地方出错了。系统并未向你的帐户收取费用,请稍后重试。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.UnknownFailure"] = [[购买 {ITEM_NAME} 失败,因为有地方出错了。系统并未向你的帐户收取费用,请稍后重试。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.CannotGetBalance"] = [[当前无法取回你的余额信息。系统并未向你的帐户收取费用,请稍后重试。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.CannotGetItemPrice"] = [[当前无法取回此物品的价格。系统并未向你的帐户收取费用,请稍后重试。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.Limited"] = [[此限量物品已售完。请至 www.roblox.com 尝试向其它用户购买。系统并未向你的帐户收取费用。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotForSale"] = [[此物品当前为非卖品。系统并未向你的帐户收取费用。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.PromptPurchaseOnGuest"] = [[你必须创建 Roblox 帐户才能购买物品,如需更多信息请访问 www.roblox.com。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.ThirdPartyDisabled"] = [[此位置已禁用第三方物品贩售。系统并未向你的帐户收取费用。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.Under13"] = [[因为你未满 13 岁,不允许购买此物品。系统并未向你的帐户收取费用。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.AlreadyOwn"] = [[你已拥有此物品。系统并未向你的帐户收取费用。]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Free"] = [[你是否要免费领取“{ITEM_NAME}”?]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Purchase"] = [[确定要购买 {ASSET_TYPE} {ITEM_NAME}(费用:]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Succeeded"] = [[{ITEM_NAME} 购买成功!]],
["CoreScripts.PurchasePrompt.PurchaseMessage.NeedMoreRobux"] = [[你还需要 {NEEDED_AMOUNT} Robux 才能购买 {ASSET_TYPE} {ITEM_NAME}。是否要购买更多 Robux]],
["CoreScripts.PurchasePrompt.ProductType.Product"] = [[产品]],
}
@@ -0,0 +1,119 @@
--[[----------------------------------------------------------------------------------------------------
<auto-generated>
This file was generated by: ClientIntegration/Tools/LuaStringsGenerator/GenerateAllLocales.py
Changes to this file should always follow:
Building an Internationalized Feature - Engineer's Guide:
https://confluence.roblox.com/display/IN/Building+an+Internationalized+Feature+-+Engineer%27s+Guide
Sync up with newly-updated translations:
https://confluence.roblox.com/display/MOBAPP/Sync+up+with+newly-updated+translations
</auto-generated>
--------------------------------------------------------------------------------------------------------]]
return{
["Common.AssetTypes.Label.Accessories"] = [[配件]],
["Common.AssetTypes.Label.Hat"] = [[帽子]],
["Common.AssetTypes.Label.Hair"] = [[頭髮]],
["Common.AssetTypes.Label.Face"] = [[臉]],
["Common.AssetTypes.Label.Neck"] = [[頸]],
["Common.AssetTypes.Label.Shoulder"] = [[肩]],
["Common.AssetTypes.Label.Front"] = [[正面]],
["Common.AssetTypes.Label.Back"] = [[背面]],
["Common.AssetTypes.Label.Waist"] = [[腰]],
["Common.AssetTypes.Label.Animations"] = [[動畫]],
["Common.AssetTypes.Label.Audio"] = [[音訊]],
["Common.AssetTypes.Label.AvatarAnimations"] = [[虛擬人偶動畫]],
["Common.AssetTypes.Label.Badges"] = [[徽章]],
["Common.AssetTypes.Label.Decals"] = [[貼花]],
["Common.AssetTypes.Label.Faces"] = [[臉]],
["Common.AssetTypes.Label.GamePasses"] = [[遊戲通行證]],
["Common.AssetTypes.Label.Gear"] = [[裝備]],
["Common.AssetTypes.Label.Heads"] = [[頭]],
["Common.AssetTypes.Label.Meshes"] = [[網格]],
["Common.AssetTypes.Label.Models"] = [[模型]],
["Common.AssetTypes.Label.Packages"] = [[套件]],
["Common.AssetTypes.Label.Pants"] = [[褲子]],
["Common.AssetTypes.Label.Places"] = [[地點]],
["Common.AssetTypes.Label.Plugins"] = [[插件]],
["Common.AssetTypes.Label.Shirts"] = [[上衣]],
["Common.AssetTypes.Label.TShirts"] = [[T恤]],
["Common.AssetTypes.Label.VipServers"] = [[VIP 伺服器]],
["Common.AssetTypes.Label.Run"] = [[奔跑]],
["Common.AssetTypes.Label.Walk"] = [[步行]],
["Common.AssetTypes.Label.Fall"] = [[下降]],
["Common.AssetTypes.Label.Jump"] = [[跳起]],
["Common.AssetTypes.Label.Idle"] = [[閒置]],
["Common.AssetTypes.Label.Swim"] = [[游泳]],
["Common.AssetTypes.Label.Climb"] = [[攀爬]],
["Common.AssetTypes.Label.Hats"] = [[帽子]],
["Common.AssetTypes.Label.Shoulders"] = [[肩]],
["Common.AssetTypes.Label.Death"] = [[死亡]],
["Common.AssetTypes.Label.Pose"] = [[姿勢]],
["Common.AssetTypes.Label.Head"] = [[頭]],
["Common.AssetTypes.Label.TShirt"] = [[T恤]],
["Common.AssetTypes.Label.Shirt"] = [[上衣]],
["Common.AssetTypes.Label.Decal"] = [[裝飾貼紙]],
["Common.AssetTypes.Label.Model"] = [[模型]],
["Common.AssetTypes.Label.Plugin"] = [[外掛程式]],
["Common.AssetTypes.Label.MeshPart"] = [[網格零件]],
["Common.AssetTypes.Label.GamePass"] = [[遊戲通行證]],
["Common.AssetTypes.Label.Badge"] = [[徽章]],
["Common.AssetTypes.Label.Package"] = [[套件]],
["Common.AssetTypes.Label.Place"] = [[地點]],
["Common.AssetTypes.Label.LeftArm"] = [[左臂]],
["Common.AssetTypes.Label.LeftLeg"] = [[左腿]],
["Common.AssetTypes.Label.RightArm"] = [[右臂]],
["Common.AssetTypes.Label.RightLeg"] = [[右腿]],
["Common.AssetTypes.Label.Torso"] = [[軀幹]],
["Common.AssetTypes.Label.Animation"] = [[動畫]],
["Common.BuildersClub.Label.PlanFree"] = [[免費]],
["Common.BuildersClub.Label.PlanClassic"] = [[經典]],
["Common.BuildersClub.Label.PlanTurbo"] = [[強化]],
["Common.BuildersClub.Label.PlanOutrageous"] = [[超狂]],
["Common.BuildersClub.Label.BuildersClub"] = [[建置者社團]],
["Common.BuildersClub.Label.BuildersClubMembership"] = [[建置者社團會員]],
["Common.BuildersClub.Label.BuildersClubMembershipTurbo"] = [[強化建置者社團會員]],
["Common.BuildersClub.Label.BuildersClubMembershipOutrageous"] = [[超狂建置者社團會員]],
["Common.BuildersClub.Label.TurboBuildersClub"] = [[強化建置者社團]],
["Common.BuildersClub.Label.OutrageousBuildersClub"] = [[超狂建置者社團]],
["Common.BuildersClub.Label.Yes"] = [[是]],
["Common.BuildersClub.Label.No"] = [[否]],
["Common.BuildersClub.Label.NeverUppercase"] = [[永不]],
["Common.BuildersClub.Label.Robux"] = [[Robux]],
["Common.BuildersClub.Label.ClassicBuildersClub"] = [[經典建置者社團]],
["Common.BuildersClub.Label.Lifetime"] = [[終身]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.TakeFree"] = [[免費收取]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.UpgradeBuildersClub"] = [[升級]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.BuyNow"] = [[立即購買]],
["CoreScripts.PurchasePrompt.ConfirmPurchase.BuyRobux"] = [[購買 R$]],
["CoreScripts.PurchasePrompt.CancelPurchase.Cancel"] = [[取消]],
["CoreScripts.PurchasePrompt.Button.OK"] = [[確定]],
["CoreScripts.PurchasePrompt.Purchasing"] = [[正在購買]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceUnaffected"] = [[您的帳戶餘額不受此交易影響。]],
["CoreScripts.PurchasePrompt.PurchaseDetails.MockPurchase"] = [[此為測試性購買;不會自您的帳戶收費。]],
["CoreScripts.PurchasePrompt.PurchaseDetails.MockPurchaseComplete"] = [[此為測試性購買。]],
["CoreScripts.PurchasePrompt.PurchaseDetails.InvalidBuildersClub"] = [[此項目需要{BC_LEVEL}。按一下「升級」即可升級您的建置者社團!]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceFuture"] = [[您此交易後的餘額將為 R${BALANCE_FUTURE}]],
["CoreScripts.PurchasePrompt.PurchaseDetails.BalanceNow"] = [[您的餘額現在為{BALANCE_NOW}。]],
["CoreScripts.PurchasePrompt.PurchaseDetails.RemainingAfterUpsell"] = [[剩餘的 {REMAINING_ROBUX} Robux 會計入您的餘額。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.InvalidFunds"] = [[無法購買,原因是您的帳號內 Robux 不足。未自您的帳戶收費。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.BuildersClubUpsellFailure"] = [[無法購買,原因是您必須訂閱才能購買此項目。未自您的帳戶收費。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotEnoughRobuxXbox"] = [[此項目花費的 Robux 超過您所能花用的數量。請離開此遊戲,前往 Robux 畫面加購。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotEnoughRobux"] = [[此項目花費的 Robux 超過您所能購買的數量。請瀏覽 www.roblox.com 加購 Robux。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.PurchaseDisabled"] = [[無法購買,原因是遊戲內購買功能暫時停用。未自您的帳戶收費。請稍後再試一次。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.UnknownFailureNoItemName"] = [[無法購買,原因是有地方出錯。未自您的帳戶收費。請稍後再試一次。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.UnknownFailure"] = [[無法購買 {ITEM_NAME},原因是有地方出錯。未自您的帳戶收費。請稍後再試一次。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.CannotGetBalance"] = [[此刻無法擷取您的餘額。未自您的帳戶收費。請稍後再試一次。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.CannotGetItemPrice"] = [[此刻無法擷取項目的價格。未自您的帳戶收費。請稍後再試一次。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.Limited"] = [[此限量項目已售完。請嘗試在 www.roblox.com 向其他使用者購買。未自您的帳戶收費。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.NotForSale"] = [[此項目目前為非賣品,未自您的帳戶收費。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.PromptPurchaseOnGuest"] = [[您需要建立 ROBLOX 帳戶以購買項目,更多資訊請瀏覽 www.roblox.com。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.ThirdPartyDisabled"] = [[此地點的第三方項目大拍賣已停用。並未自您的帳戶收費。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.Under13"] = [[您的帳戶未滿 13 歲,不允許購買此項目。未自您的帳戶收費。]],
["CoreScripts.PurchasePrompt.PurchaseFailed.AlreadyOwn"] = [[您已有此物,未自您的帳戶收費。]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Free"] = [[您是否要免費收取 {ITEM_NAME}]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Purchase"] = [[是否要購買 {ASSET_TYPE}{ITEM_NAME}?價格為]],
["CoreScripts.PurchasePrompt.PurchaseMessage.Succeeded"] = [[您成功購買 {ITEM_NAME}]],
["CoreScripts.PurchasePrompt.PurchaseMessage.NeedMoreRobux"] = [[您需要再多 {NEEDED_AMOUNT} Robux 才能購買 {ASSET_TYPE} {ITEM_NAME}。您是否要加購 Robux]],
["CoreScripts.PurchasePrompt.ProductType.Product"] = [[商品]],
}
@@ -0,0 +1,161 @@
local PurchaseError = require(script.Parent.Parent.PurchaseError)
local Symbol = require(script.Parent.Parent.Symbol)
local KeyMappings = require(script.Parent.KeyMappings)
local addGroupDelimiters = require(script.Parent.Parent.addGroupDelimiters)
local DEBUG_LOCALIZATION = false
--[[
Locale-specific group delimiters for displaying numbers. Used to
format values like 100000 to strings like "100,000". This table
does not provide any info regarding decimal separators
]]
local groupDelimiterByLocale = {
["en-us"] = ",",
["en-gb"] = ",",
["es-mx"] = ",",
["es-es"] = ".",
["fr-fr"] = " ",
["de-de"] = " ",
["pt-br"] = ".",
["zh-cn"] = ",",
["zh-tw"] = ",",
["ko-kr"] = ",",
["ja-jp"] = ",",
["it-it"] = " ",
["ru-ru"] = ".",
["id-id"] = ".",
["vi-vn"] = ".",
["th-th"] = ",",
["tr-tr"] = ".",
}
--[[
This is a marker used to indicate that a provided param needs a locale-aware
formatting pass. We need this for nested localization and number formatting
]]
local FormattedParamTag = Symbol.named("FormattedParam")
local function isFormattedParam(paramValue)
return typeof(paramValue) == "table" and paramValue[FormattedParamTag] == true
end
local function createFormattedParam(formatFunc)
return {
[FormattedParamTag] = true,
format = formatFunc,
}
end
--[[
Looks up the given key in the localization context's translation table
]]
local function getLocalizedString(localizationContext, key)
local translations = localizationContext.translations
if DEBUG_LOCALIZATION and translations[key] == nil then
warn(("Missing translation for %s in locale %s"):format(key, localizationContext.locale))
end
return translations[key]
end
local LocalizationService = {}
function LocalizationService.formatNumber(localizationContext, number)
local delimiter = groupDelimiterByLocale[localizationContext.locale]
return addGroupDelimiters(number, delimiter)
end
--[[
Generates a placeholder for a number param that needs locale-aware formatting
]]
function LocalizationService.numberParam(number)
return createFormattedParam(function(localizationContext)
return LocalizationService.formatNumber(localizationContext, number)
end)
end
--[[
Generates a placeholder for a param substitution that needs
its own localization pass
]]
function LocalizationService.nestedKeyParam(key)
return createFormattedParam(function(localizationContext)
return getLocalizedString(localizationContext, key)
end)
end
--[[
Utility function returns the localization key for a given asset id
]]
function LocalizationService.getAssetTypeKey(assetTypeId)
assert(typeof(assetTypeId) == "number" or typeof(assetTypeId) == "string",
"provided asset type must be a number or string")
local assetType = KeyMappings.AssetTypeById[tostring(assetTypeId)]
if DEBUG_LOCALIZATION and assetType == nil then
warn("Invalid Asset Type id " .. tostring(assetTypeId))
end
return assetType
end
--[[
Utility function returns the localization key for a given
builders club level
]]
function LocalizationService.getBuildersClubLevelKey(bcLevelId)
assert(typeof(bcLevelId) == "number" or typeof(bcLevelId) == "string",
"provided builders club level must be a number")
local bcLevel = KeyMappings.BuildersClubLevelById[tostring(bcLevelId)]
if DEBUG_LOCALIZATION and bcLevel == nil then
warn("Invalid Builders Club Level id " .. tostring(bcLevelId))
end
return bcLevel
end
--[[
Utility function to retrieve relevant localization key for various
types of errors that may be encountered
]]
function LocalizationService.getErrorKey(errorType)
assert(PurchaseError.isMember(errorType),
"provided value " .. tostring(errorType) .. " is not a member of PurchaseError enum")
return KeyMappings.PurchaseErrorKey[errorType]
end
--[[
The primary function of this object
Retrieves a localized string from the provided context with the
given key and performs parameter substitutions
]]
function LocalizationService.getString(localizationContext, key, params)
assert(localizationContext ~= nil, "Must provide valid localization context")
local localizedString = getLocalizedString(localizationContext, key)
if params ~= nil then
for param, value in pairs(params) do
local replacement = value
local paramPlaceholder = ("{%s}"):format(param)
if isFormattedParam(value) then
replacement = value.format(localizationContext)
end
localizedString = string.gsub(localizedString, paramPlaceholder, replacement)
end
end
return localizedString
end
return LocalizationService
@@ -0,0 +1,26 @@
local Locales = script.Parent.Locales
local FALLBACK_LOCALE = "en-us"
local function getLocalizationContext(locale)
local primary = Locales:FindFirstChild(locale)
if primary ~= nil then
return {
locale = locale,
translations = require(primary),
}
else
--[[
If the requested language is not available, fallback to
the default; for now, this will be American English.
]]
local fallback = Locales:FindFirstChild(FALLBACK_LOCALE)
return {
locale = FALLBACK_LOCALE,
translations = require(fallback),
}
end
end
return getLocalizationContext
@@ -0,0 +1,5 @@
local Symbol = require(script.Parent.Symbol)
local LocalizationContextKey = Symbol.named("LocalizationContextKey")
return LocalizationContextKey
@@ -0,0 +1,15 @@
local CoreGui = game:GetService("CoreGui")
local CorePackages = game:GetService("CorePackages")
local RunService = game:GetService("RunService")
local Roact = require(CorePackages.Roact)
if RunService:IsStudio() and not RunService:IsRunning() then
return nil
end
local PurchasePromptApp = require(script.Parent.Components.PurchasePromptApp)
Roact.mount(Roact.createElement(PurchasePromptApp), CoreGui, "PurchasePromptApp")
return nil
@@ -0,0 +1,108 @@
--[[
CLILUACORE-310: Retrieve these values via native platform code,
like we do with Xbox, or from some reasonable endpoint
]]
local NativeProducts = {
IOS = {
BC = {
{
robuxValue = 90,
productId = "com.roblox.robloxmobile.90RobuxBC"
}, {
robuxValue = 180,
productId = "com.roblox.robloxmobile.180RobuxBC",
}, {
robuxValue = 270,
productId = "com.roblox.robloxmobile.270RobuxBC",
}, {
robuxValue = 360,
productId = "com.roblox.robloxmobile.360RobuxBC",
}, {
robuxValue = 450,
productId = "com.roblox.robloxmobile.450RobuxBC",
}, {
robuxValue = 1000,
productId = "com.roblox.robloxmobile.1000RobuxBC",
}, {
robuxValue = 2750,
productId = "com.roblox.robloxmobile.2750RobuxBC",
},
},
NonBC = {
{
robuxValue = 80,
productId = "com.roblox.robloxmobile.80RobuxNonBC",
}, {
robuxValue = 160,
productId = "com.roblox.robloxmobile.160RobuxNonBC",
}, {
robuxValue = 240,
productId = "com.roblox.robloxmobile.240RobuxNonBC",
}, {
robuxValue = 320,
productId = "com.roblox.robloxmobile.320RobuxNonBC",
}, {
robuxValue = 400,
productId = "com.roblox.robloxmobile.400RobuxNonBC",
}, {
robuxValue = 800,
productId = "com.roblox.robloxmobile.800RobuxNonBC",
}, {
robuxValue = 2000,
productId = "com.roblox.robloxmobile.2000RobuxNonBC",
},
}
},
Standard = {
BC = {
{
robuxValue = 90,
productId = "com.roblox.client.robux90bc",
}, {
robuxValue = 180,
productId = "com.roblox.client.robux180bc",
}, {
robuxValue = 270,
productId = "com.roblox.client.robux270bc",
}, {
robuxValue = 360,
productId = "com.roblox.client.robux360bc",
}, {
robuxValue = 450,
productId = "com.roblox.client.robux450bc",
}, {
robuxValue = 1000,
productId = "com.roblox.client.robux1000bc",
}, {
robuxValue = 2750,
productId = "com.roblox.client.robux2750bc",
},
},
NonBC = {
{
robuxValue = 80,
productId = "com.roblox.client.robux80",
}, {
robuxValue = 160,
productId = "com.roblox.client.robux160",
}, {
robuxValue = 240,
productId = "com.roblox.client.robux240",
}, {
robuxValue = 320,
productId = "com.roblox.client.robux320",
}, {
robuxValue = 400,
productId = "com.roblox.client.robux400",
}, {
robuxValue = 800,
productId = "com.roblox.client.robux800",
}, {
robuxValue = 2000,
productId = "com.roblox.client.robux2000",
},
}
}
}
return NativeProducts
@@ -0,0 +1,49 @@
--[[
CLILUACORE-311: We need to find a proper way to encapsulate this;
conditionally depending on PlatformService is bad!
]]
local PlatformService = nil
pcall(function()
PlatformService = game:GetService("PlatformService")
end)
local Promise = require(script.Parent.Parent.Promise)
local function parseRobuxValue(productInfo)
local rawText = productInfo and productInfo.Name
local noJunk = string.gsub(rawText, ",", "")
noJunk = noJunk and string.match(noJunk, "[0-9]+") or nil
return noJunk and tonumber(noJunk) or 1000
end
local XboxCatalogData = {}
function XboxCatalogData.GetCatalogInfoAsync()
if PlatformService == nil then
error("PlatformService unavailable; are you on XboxOne?")
end
local promisified = Promise.promisify(function()
return PlatformService:BeginGetCatalogInfo()
end)
return promisified()
:andThen(function(catalogInfo)
local availableProducts = {}
for _, productInfo in pairs(catalogInfo) do
local product = {
robuxValue = parseRobuxValue(productInfo),
productId = productInfo.ProductId
}
table.insert(availableProducts, product)
end
return Promise.resolve(availableProducts)
end)
:catch(function(errorReason)
return Promise.reject(errorReason)
end)
end
return XboxCatalogData
@@ -0,0 +1,15 @@
local UpsellFlow = require(script.Parent.Parent.UpsellFlow)
local function getUpsellFlow(platform)
if platform == Enum.Platform.Windows or platform == Enum.Platform.OSX then
return UpsellFlow.Web
elseif platform == Enum.Platform.IOS or platform == Enum.Platform.Android or platform == Enum.Platform.UWP then
return UpsellFlow.Mobile
elseif platform == Enum.Platform.XBoxOne then
return UpsellFlow.Xbox
end
return UpsellFlow.None
end
return getUpsellFlow
@@ -0,0 +1,38 @@
local XboxCatalogData = require(script.Parent.XboxCatalogData)
local NativeProducts = require(script.Parent.NativeProducts)
local Promise = require(script.Parent.Parent.Promise)
local function sortAscending(a, b)
return a.robuxValue < b.robuxValue
end
local function selectProduct(price, availableProducts)
table.sort(availableProducts, sortAscending)
for _, product in ipairs(availableProducts) do
if product.robuxValue >= price then
return Promise.resolve(product)
end
end
return Promise.reject()
end
local function selectRobuxProduct(platform, price, isBuildersClubMember)
if platform == Enum.Platform.XBoxOne then
return XboxCatalogData.GetCatalogInfoAsync()
:andThen(function(availableProducts)
return selectProduct(price, availableProducts)
end)
elseif platform == Enum.Platform.IOS then
local productOptions = isBuildersClubMember and NativeProducts.IOS.BC or NativeProducts.IOS.NonBC
return selectProduct(price, productOptions)
else
-- This product format is standard for other supported platforms (Android and UWP)
local productOptions = isBuildersClubMember and NativeProducts.Standard.BC or NativeProducts.Standard.NonBC
return selectProduct(price, productOptions)
end
end
return selectRobuxProduct
@@ -0,0 +1,24 @@
local PurchaseError = require(script.Parent.Parent.PurchaseError)
local Promise = require(script.Parent.Parent.Promise)
local MAX_ROBUX = 2147483647
local function getAccountInfo(network, externalSettings)
return network.getAccountInfo()
:andThen(function(result)
--[[
In studio, we falsely report that users have the maximum amount
of robux, so that they can always test the normal purchase flow
]]
if externalSettings.isStudio() then
result.RobuxBalance = MAX_ROBUX
end
return Promise.resolve(result)
end)
:catch(function(failure)
return Promise.reject(PurchaseError.UnknownFailure)
end)
end
return getAccountInfo
@@ -0,0 +1,14 @@
local Players = game:GetService("Players")
local PurchaseError = require(script.Parent.Parent.PurchaseError)
local Promise = require(script.Parent.Parent.Promise)
local function getIsAlreadyOwned(network, id, infoType)
return network.getPlayerOwns(Players.LocalPlayer, id, infoType)
:catch(function(failure)
return Promise.reject(PurchaseError.UnknownFailure)
end)
end
return getIsAlreadyOwned
@@ -0,0 +1,11 @@
local PurchaseError = require(script.Parent.Parent.PurchaseError)
local Promise = require(script.Parent.Parent.Promise)
local function getProductInfo(network, id, infoType)
return network.getProductInfo(id, infoType)
:catch(function(failure)
return Promise.reject(PurchaseError.UnknownFailureNoItemName)
end)
end
return getProductInfo
@@ -0,0 +1,22 @@
local function getToolAsset(network, assetId)
return network.loadAssetForEquip(assetId)
:andThen(function(tool)
if tool:IsA("Tool") then
return tool
else
local children = tool:GetChildren()
for _, child in ipairs(children) do
if child:IsA("Tool") then
return child
end
end
end
end)
:catch(function(failure)
-- There isn't really much we can do here with error reporting,
-- since the failure is unrelated to purchasing itself
return nil
end)
end
return getToolAsset
@@ -0,0 +1,28 @@
local PurchaseError = require(script.Parent.Parent.PurchaseError)
local Promise = require(script.Parent.Parent.Promise)
local function performPurchase(network, infoType, productId, expectedPrice, requestId)
return network.performPurchase(infoType, productId, expectedPrice, requestId)
:andThen(function(result)
--[[
User might purchase the product through the web after having
opened the purchase prompt, so an AlreadyOwned status is
acceptable.
]]
if result.success or result.status == "AlreadyOwned" then
return Promise.resolve(result)
elseif infoType == Enum.InfoType.Product and not result.receipt then
return Promise.reject(PurchaseError.UnknownFailure)
else
if result.status == "EconomyDisabled" then
return Promise.reject(PurchaseError.PurchaseDisabled)
else
return Promise.reject(PurchaseError.UnknownFailure)
end
end
end, function(failure)
return Promise.reject(PurchaseError.UnknownFailure)
end)
end
return performPurchase
@@ -0,0 +1,422 @@
--[[
An implementation of Promises similar to Promise/A+.
]]
local PROMISE_DEBUG = false
--[[
Packs a number of arguments into a table and returns its length.
Used to cajole varargs without dropping sparse values.
]]
local function pack(...)
local len = select("#", ...)
return len, { ... }
end
--[[
wpcallPacked is a version of xpcall that:
* Returns the length of the result first
* Returns the result packed into a table
* Passes extra arguments through to the passed function, which xpcall does not
* Issues a warning if PROMISE_DEBUG is enabled
]]
local function wpcallPacked(f, ...)
local argsLength, args = pack(...)
local body = function()
return f(unpack(args, 1, argsLength))
end
local resultLength, result = pack(xpcall(body, debug.traceback))
-- If promise debugging is on, warn whenever a pcall fails.
-- This is useful for debugging issues within the Promise implementation
-- itself.
if PROMISE_DEBUG and not result[1] then
warn(result[2])
end
return resultLength, result
end
--[[
Creates a function that invokes a callback with correct error handling and
resolution mechanisms.
]]
local function createAdvancer(callback, resolve, reject)
return function(...)
local resultLength, result = wpcallPacked(callback, ...)
local ok = result[1]
if ok then
resolve(unpack(result, 2, resultLength))
else
reject(unpack(result, 2, resultLength))
end
end
end
local function isEmpty(t)
return next(t) == nil
end
local Promise = {}
Promise.__index = Promise
Promise.Status = {
Started = "Started",
Resolved = "Resolved",
Rejected = "Rejected",
}
--[[
Constructs a new Promise with the given initializing callback.
This is generally only called when directly wrapping a non-promise API into
a promise-based version.
The callback will receive 'resolve' and 'reject' methods, used to start
invoking the promise chain.
For example:
local function get(url)
return Promise.new(function(resolve, reject)
spawn(function()
resolve(HttpService:GetAsync(url))
end)
end)
end
get("https://google.com")
:andThen(function(stuff)
print("Got some stuff!", stuff)
end)
]]
function Promise.new(callback)
local promise = {
-- Used to locate where a promise was created
_source = debug.traceback(),
-- A tag to identify us as a promise
_type = "Promise",
_status = Promise.Status.Started,
-- A table containing a list of all results, whether success or failure.
-- Only valid if _status is set to something besides Started
_values = nil,
-- Lua doesn't like sparse arrays very much, so we explicitly store the
-- length of _values to handle middle nils.
_valuesLength = -1,
-- If an error occurs with no observers, this will be set.
_unhandledRejection = false,
-- Queues representing functions we should invoke when we update!
_queuedResolve = {},
_queuedReject = {},
}
setmetatable(promise, Promise)
local function resolve(...)
promise:_resolve(...)
end
local function reject(...)
promise:_reject(...)
end
local _, result = wpcallPacked(callback, resolve, reject)
local ok = result[1]
local err = result[2]
if not ok and promise._status == Promise.Status.Started then
reject(err)
end
return promise
end
--[[
Create a promise that represents the immediately resolved value.
]]
function Promise.resolve(value)
return Promise.new(function(resolve)
resolve(value)
end)
end
--[[
Create a promise that represents the immediately rejected value.
]]
function Promise.reject(value)
return Promise.new(function(_, reject)
reject(value)
end)
end
--[[
Returns a new promise that:
* is resolved when all input promises resolve
* is rejected if ANY input promises reject
]]
function Promise.all(...)
local promises = {...}
-- check if we've been given a list of promises, not just a variable number of promises
if type(promises[1]) == "table" and promises[1]._type ~= "Promise" then
-- we've been given a table of promises already
promises = promises[1]
end
return Promise.new(function(resolve, reject)
local isResolved = false
local results = {}
local totalCompleted = 0
local promiseCount = 0
-- If we're agnostic about whether the promises are a table
-- or a list, users can provide tables with useful keys if they like
for _ in pairs(promises) do
promiseCount = promiseCount + 1
end
local function promiseCompleted(key, result)
if isResolved then
return
end
results[key] = result
totalCompleted = totalCompleted + 1
if totalCompleted == promiseCount then
resolve(results)
isResolved = true
end
end
if promiseCount == 0 then
resolve(results)
isResolved = true
return
end
for key, promise in pairs(promises) do
-- if a promise isn't resolved yet, add listeners for when it does
if promise._status == Promise.Status.Started then
promise:andThen(function(result)
promiseCompleted(key, result)
end):catch(function(reason)
isResolved = true
reject(reason)
end)
-- if a promise is already resolved, move on
elseif promise._status == Promise.Status.Resolved then
promiseCompleted(key, unpack(promise._values))
-- if a promise is rejected, reject the whole chain
else --if promise._status == Promise.Status.Rejected then
-- We catch here to indicate that the intermediate rejection
-- has been handled and seen
promise:catch(function(reason)
isResolved = true
reject(unpack(promise._values))
end)
end
end
end)
end
--[[
Is the given object a Promise instance?
]]
function Promise.is(object)
if type(object) ~= "table" then
return false
end
return object._type == "Promise"
end
--[[
Construct a promise from a yielding function
]]
function Promise.promisify(callback)
return function(...)
local args = {...}
local argLength = select("#", ...)
return Promise.new(function(resolve, reject)
spawn(function()
local success, result = pcall(callback, unpack(args, 1, argLength))
if success then
resolve(result)
else
reject(result)
end
end)
end)
end
end
function Promise:getStatus()
return self._status
end
--[[
Creates a new promise that receives the result of this promise.
The given callbacks are invoked depending on that result.
]]
function Promise:andThen(successHandler, failureHandler)
self._unhandledRejection = false
-- Create a new promise to follow this part of the chain
return Promise.new(function(resolve, reject)
-- Our default callbacks just pass values onto the next promise.
-- This lets success and failure cascade correctly!
local successCallback = resolve
if successHandler then
successCallback = createAdvancer(successHandler, resolve, reject)
end
local failureCallback = reject
if failureHandler then
failureCallback = createAdvancer(failureHandler, resolve, reject)
end
if self._status == Promise.Status.Started then
-- If we haven't resolved yet, put ourselves into the queue
table.insert(self._queuedResolve, successCallback)
table.insert(self._queuedReject, failureCallback)
elseif self._status == Promise.Status.Resolved then
-- This promise has already resolved! Trigger success immediately.
successCallback(unpack(self._values, 1, self._valuesLength))
elseif self._status == Promise.Status.Rejected then
-- This promise died a terrible death! Trigger failure immediately.
failureCallback(unpack(self._values, 1, self._valuesLength))
end
end)
end
--[[
Used to catch any errors that may have occurred in the promise.
]]
function Promise:catch(failureCallback)
return self:andThen(nil, failureCallback)
end
--[[
Yield until the promise is completed.
This matches the execution model of normal Roblox functions.
]]
function Promise:await()
self._unhandledRejection = false
if self._status == Promise.Status.Started then
local result
local resultLength
local bindable = Instance.new("BindableEvent")
self:andThen(function(...)
result = {...}
resultLength = select("#", ...)
bindable:Fire(true)
end, function(...)
result = {...}
resultLength = select("#", ...)
bindable:Fire(false)
end)
local ok = bindable.Event:Wait()
bindable:Destroy()
return ok, unpack(result, 1, resultLength)
elseif self._status == Promise.Status.Resolved then
return true, unpack(self._values, 1, self._valuesLength)
elseif self._status == Promise.Status.Rejected then
return false, unpack(self._values, 1, self._valuesLength)
end
end
function Promise:_resolve(...)
if self._status ~= Promise.Status.Started then
return
end
local argLength = select("#", ...)
-- If the resolved value was a Promise, we chain onto it!
if Promise.is((...)) then
-- Without this warning, arguments sometimes mysteriously disappear
if argLength > 1 then
local message = (
"When returning a Promise from andThen, extra arguments are " ..
"discarded! See:\n\n%s"
):format(
self._source
)
warn(message)
end
(...):andThen(function(...)
self:_resolve(...)
end, function(...)
self:_reject(...)
end)
return
end
self._status = Promise.Status.Resolved
self._values = {...}
self._valuesLength = argLength
-- We assume that these callbacks will not throw errors.
for _, callback in ipairs(self._queuedResolve) do
callback(...)
end
end
function Promise:_reject(...)
if self._status ~= Promise.Status.Started then
return
end
self._status = Promise.Status.Rejected
self._values = {...}
self._valuesLength = select("#", ...)
-- If there are any rejection handlers, call those!
if not isEmpty(self._queuedReject) then
-- We assume that these callbacks will not throw errors.
for _, callback in ipairs(self._queuedReject) do
callback(...)
end
else
-- At this point, no one was able to observe the error.
-- An error handler might still be attached if the error occurred
-- synchronously. We'll wait one tick, and if there are still no
-- observers, then we should put a message in the console.
self._unhandledRejection = true
local err = tostring((...))
spawn(function()
-- Someone observed the error, hooray!
if not self._unhandledRejection then
return
end
-- Build a reasonable message
local message = ("Unhandled promise rejection:\n\n%s\n\n%s"):format(
err,
self._source
)
warn(message)
end)
end
end
return Promise
@@ -0,0 +1,21 @@
--[[
Enumerated state of the purchase prompt
]]
local createEnum = require(script.Parent.createEnum)
local PromptState = createEnum("PromptState", {
"Hidden",
"RobuxUpsell",
"BuildersClubUpsell",
"PromptPurchase",
"PurchaseInProgress",
"UpsellInProgress",
"PurchaseComplete",
"CannotPurchase",
"Error",
})
return PromptState
@@ -0,0 +1,32 @@
--[[
Enumeration of all possible error states
]]
local createEnum = require(script.Parent.createEnum)
local PurchaseError = createEnum("PurchaseError", {
-- Pre-purchase network failures
"CannotGetBalance",
"CannotGetItemPrice",
-- Item unvailable
"NotForSale",
"AlreadyOwn",
"Under13",
"Limited",
"Guest",
"ThirdPartyDisabled",
"NotEnoughRobux",
"NotEnoughRobuxXbox",
-- Upsell
"BuildersClubLevelTooLow",
-- Network-reported failures
"UnknownFailure",
"UnknownFailureNoItemName",
"PurchaseDisabled",
"InvalidFunds",
"BuildersClubUpsellFailure",
})
return PurchaseError
@@ -0,0 +1,18 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local AccountInfoReceived = require(script.Parent.Parent.Actions.AccountInfoReceived)
local ProductInfoReducer = Rodux.createReducer({}, {
[AccountInfoReceived.name] = function(state, action)
local accountInfo = action.accountInfo
return {
balance = accountInfo.RobuxBalance,
bcLevel = accountInfo.MembershipType,
}
end,
})
return ProductInfoReducer
@@ -0,0 +1,13 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local SetGamepadEnabled = require(script.Parent.Parent.Actions.SetGamepadEnabled)
local GamepadEnabledReducer = Rodux.createReducer(false, {
[SetGamepadEnabled.name] = function(state, action)
return action.enabled
end,
})
return GamepadEnabledReducer
@@ -0,0 +1,17 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local PromptNativeUpsell = require(script.Parent.Parent.Actions.PromptNativeUpsell)
local NativeUpsellReducer = Rodux.createReducer({}, {
[PromptNativeUpsell.name] = function(state, action)
return {
robuxProductId = action.robuxProductId,
robuxPurchaseAmount = action.robuxPurchaseAmount,
}
end,
})
return NativeUpsellReducer
@@ -0,0 +1,24 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local ProductInfoReceived = require(script.Parent.Parent.Actions.ProductInfoReceived)
local getPreviewImageUrl = require(script.Parent.Parent.getPreviewImageUrl)
local ProductInfoReducer = Rodux.createReducer({}, {
[ProductInfoReceived.name] = function(state, action)
local productInfo = action.productInfo
return {
name = productInfo.Name,
price = productInfo.PriceInRobux or 0,
imageUrl = getPreviewImageUrl(productInfo),
assetTypeId = productInfo.AssetTypeId,
productId = productInfo.ProductId,
bcLevelRequired = productInfo.MinimumMembershipLevel,
}
end,
})
return ProductInfoReducer
@@ -0,0 +1,22 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local SetProduct = require(script.Parent.Parent.Actions.SetProduct)
local HidePrompt = require(script.Parent.Parent.Actions.HidePrompt)
local ProductReducer = Rodux.createReducer({}, {
[SetProduct.name] = function(state, action)
return {
id = action.id,
infoType = action.infoType,
equipIfPurchased = action.equipIfPurchased,
}
end,
[HidePrompt.name] = function(state, action)
-- Clear product info when we hide the prompt
return {}
end,
})
return ProductReducer
@@ -0,0 +1,35 @@
local CorePackages = game:GetService("CorePackages")
local Rodux = require(CorePackages.Rodux)
local PromptState = require(script.Parent.Parent.PromptState)
local SetPromptState = require(script.Parent.Parent.Actions.SetPromptState)
local HidePrompt = require(script.Parent.Parent.Actions.HidePrompt)
local ErrorOccurred = require(script.Parent.Parent.Actions.ErrorOccurred)
local ItemCannotBePurchased = require(script.Parent.Parent.Actions.ItemCannotBePurchased)
local StartPurchase = require(script.Parent.Parent.Actions.StartPurchase)
local PromptNativeUpsell = require(script.Parent.Parent.Actions.PromptNativeUpsell)
local PromptStateReducer = Rodux.createReducer(PromptState.Hidden, {
[SetPromptState.name] = function(state, action)
return action.promptState
end,
[HidePrompt.name] = function(state, action)
return PromptState.Hidden
end,
[ErrorOccurred.name] = function(state, action)
return PromptState.Error
end,
[ItemCannotBePurchased.name] = function(state, action)
return PromptState.CannotPurchase
end,
[StartPurchase.name] = function(state, action)
return PromptState.PurchaseInProgress
end,
[PromptNativeUpsell.name] = function(state, action)
return PromptState.RobuxUpsell
end,
})
return PromptStateReducer
@@ -0,0 +1,16 @@
local ErrorOccurred = require(script.Parent.Parent.Actions.ErrorOccurred)
local ItemCannotBePurchased = require(script.Parent.Parent.Actions.ItemCannotBePurchased)
-- TODO: Switch to Rodux.createReducer once CorePackages.Rodux is upgraded
local PurchaseErrorReducer = function(state, action)
if action.type == ErrorOccurred.name then
return action.purchaseError
elseif action.type == ItemCannotBePurchased.name then
return action.purchaseError
end
return state
end
return PurchaseErrorReducer

Some files were not shown because too many files have changed in this diff Show More