add gs
This commit is contained in:
@@ -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
|
||||
+41
@@ -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
|
||||
+35
@@ -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
|
||||
+52
@@ -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
|
||||
+100
@@ -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
|
||||
+19
@@ -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
|
||||
+24
@@ -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
|
||||
+22
@@ -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
|
||||
+24
@@ -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
|
||||
+77
@@ -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
|
||||
+45
@@ -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
|
||||
+22
@@ -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
|
||||
+23
@@ -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
|
||||
+21
@@ -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
|
||||
+17
@@ -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
|
||||
+126
@@ -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
|
||||
+33
@@ -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
|
||||
+37
@@ -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
|
||||
+16
@@ -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
|
||||
+81
@@ -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
|
||||
+15
@@ -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
|
||||
+30
@@ -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
|
||||
+30
@@ -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
|
||||
+48
@@ -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
|
||||
+28
@@ -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
|
||||
+20
@@ -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
|
||||
+52
@@ -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
|
||||
+21
@@ -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
|
||||
+61
@@ -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
|
||||
+20
@@ -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
|
||||
+59
@@ -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
|
||||
+22
@@ -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
|
||||
+30
@@ -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
|
||||
+20
@@ -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
|
||||
+22
@@ -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
|
||||
+135
@@ -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
|
||||
+29
@@ -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
|
||||
+105
@@ -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
|
||||
+42
@@ -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
|
||||
+55
@@ -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
|
||||
+38
@@ -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
|
||||
+114
@@ -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
|
||||
+36
@@ -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
|
||||
+68
@@ -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
|
||||
+19
@@ -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
|
||||
+13
@@ -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"] = [[商品]],
|
||||
}
|
||||
+161
@@ -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
|
||||
+26
@@ -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
Reference in New Issue
Block a user