mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-08-29 01:08:59 +00:00
FIX - complete phone locale fallback
Resolve configured and regional locales through a centralized English fallback for Lua and NUI consumers. Complete the German and English app translation keys, localize remaining picker and attachment copy, and add contract tests that detect missing locale entries.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import emojiData from 'emoji-picker-element-data/en/emojibase/data.json'
|
import deEmojiData from 'emoji-picker-element-data/de/cldr/data.json'
|
||||||
|
import enEmojiData from 'emoji-picker-element-data/en/cldr/data.json'
|
||||||
import { Search, X } from 'lucide-vue-next'
|
import { Search, X } from 'lucide-vue-next'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
@@ -25,10 +26,13 @@ const activeGroup = ref(0)
|
|||||||
const skinTone = ref(0)
|
const skinTone = ref(0)
|
||||||
const groups = ['😀', '👋', '🧑', '🐻', '🍔', '⚽', '🚗', '💡', '❤️', '🏳️']
|
const groups = ['😀', '👋', '🧑', '🐻', '🍔', '⚽', '🚗', '💡', '❤️', '🏳️']
|
||||||
const skinTones = ['✋', '✋🏻', '✋🏼', '✋🏽', '✋🏾', '✋🏿']
|
const skinTones = ['✋', '✋🏻', '✋🏼', '✋🏽', '✋🏾', '✋🏿']
|
||||||
|
const localizedEmojiData = computed(() =>
|
||||||
|
phone.lang.toLocaleLowerCase().startsWith('de') ? deEmojiData : enEmojiData,
|
||||||
|
)
|
||||||
|
|
||||||
const emojis = computed(() => {
|
const emojis = computed(() => {
|
||||||
const needle = query.value.trim().toLocaleLowerCase(phone.lang)
|
const needle = query.value.trim().toLocaleLowerCase(phone.lang)
|
||||||
return (emojiData as EmojiEntry[])
|
return (localizedEmojiData.value as EmojiEntry[])
|
||||||
.filter((entry) => {
|
.filter((entry) => {
|
||||||
if (!needle) return entry.group === activeGroup.value
|
if (!needle) return entry.group === activeGroup.value
|
||||||
return `${entry.annotation} ${entry.shortcodes?.join(' ') ?? ''} ${entry.tags?.join(' ') ?? ''}`
|
return `${entry.annotation} ${entry.shortcodes?.join(' ') ?? ''} ${entry.tags?.join(' ') ?? ''}`
|
||||||
@@ -40,17 +44,24 @@ const emojis = computed(() => {
|
|||||||
emoji:
|
emoji:
|
||||||
skinTone.value === 0
|
skinTone.value === 0
|
||||||
? entry.emoji
|
? entry.emoji
|
||||||
: entry.skins?.find((skin) => skin.tone === skinTone.value)?.emoji ??
|
: (entry.skins?.find((skin) => skin.tone === skinTone.value)?.emoji ??
|
||||||
entry.emoji,
|
entry.emoji),
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section class="messages-full-emoji-picker" aria-label="Emoji picker">
|
<section
|
||||||
|
class="messages-full-emoji-picker"
|
||||||
|
:aria-label="phone.t('Apps.messages.emojiPicker')"
|
||||||
|
>
|
||||||
<header>
|
<header>
|
||||||
<strong>{{ phone.t('Apps.messages.emoji') }}</strong>
|
<strong>{{ phone.t('Apps.messages.emoji') }}</strong>
|
||||||
<button type="button" :aria-label="phone.t('Common.done')" @click="emit('close')">
|
<button
|
||||||
|
type="button"
|
||||||
|
:aria-label="phone.t('Common.done')"
|
||||||
|
@click="emit('close')"
|
||||||
|
>
|
||||||
<X :size="18" />
|
<X :size="18" />
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
@@ -63,7 +74,7 @@ const emojis = computed(() => {
|
|||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<nav v-if="!query" aria-label="Emoji categories">
|
<nav v-if="!query" :aria-label="phone.t('Apps.messages.emojiCategories')">
|
||||||
<button
|
<button
|
||||||
v-for="(group, index) in groups"
|
v-for="(group, index) in groups"
|
||||||
:key="group"
|
:key="group"
|
||||||
@@ -79,7 +90,7 @@ const emojis = computed(() => {
|
|||||||
:key="tone"
|
:key="tone"
|
||||||
type="button"
|
type="button"
|
||||||
:class="{ active: skinTone === index }"
|
:class="{ active: skinTone === index }"
|
||||||
:title="`Skin tone ${index}`"
|
:title="phone.t('Apps.messages.skinTone', { number: String(index) })"
|
||||||
@click="skinTone = index"
|
@click="skinTone = index"
|
||||||
>
|
>
|
||||||
{{ tone }}
|
{{ tone }}
|
||||||
|
|||||||
@@ -32,12 +32,12 @@ const videoStyles: Record<string, string> = {
|
|||||||
'ocean-loop': 'linear-gradient(145deg, #0b132b, #26648e 55%, #67d5b5)',
|
'ocean-loop': 'linear-gradient(145deg, #0b132b, #26648e 55%, #67d5b5)',
|
||||||
'sunset-loop': 'linear-gradient(145deg, #141e30, #5f2c82 55%, #ff9a62)',
|
'sunset-loop': 'linear-gradient(145deg, #141e30, #5f2c82 55%, #ff9a62)',
|
||||||
}
|
}
|
||||||
const gifContent: Record<string, { emoji: string; label: string }> = {
|
const gifContent: Record<string, { emoji: string; key: string }> = {
|
||||||
celebrate: { emoji: '🎉', label: 'Celebrate!' },
|
celebrate: { emoji: '🎉', key: 'celebrate' },
|
||||||
hearts: { emoji: '💖', label: 'Love it' },
|
hearts: { emoji: '💖', key: 'hearts' },
|
||||||
party: { emoji: '🥳', label: 'Party time' },
|
party: { emoji: '🥳', key: 'party' },
|
||||||
thumbs_up: { emoji: '👍', label: 'Perfect' },
|
thumbs_up: { emoji: '👍', key: 'thumbsUp' },
|
||||||
wow: { emoji: '🤯', label: 'WOW' },
|
wow: { emoji: '🤯', key: 'wow' },
|
||||||
}
|
}
|
||||||
|
|
||||||
const background = computed(() =>
|
const background = computed(() =>
|
||||||
@@ -139,13 +139,13 @@ function durationLabel(milliseconds: number | null): string {
|
|||||||
<img
|
<img
|
||||||
v-if="mediaUrl"
|
v-if="mediaUrl"
|
||||||
:src="mediaUrl"
|
:src="mediaUrl"
|
||||||
alt="GIF"
|
:alt="phone.t('Apps.messages.gif')"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
/>
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span>{{ gif.emoji }}</span>
|
<span>{{ gif.emoji }}</span>
|
||||||
<strong>{{ gif.label }}</strong>
|
<strong>{{ phone.t(`Apps.messages.gifLabels.${gif.key}`) }}</strong>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="message.body?.trim()" class="messages-attachment-caption">
|
<p v-if="message.body?.trim()" class="messages-attachment-caption">
|
||||||
|
|||||||
@@ -0,0 +1,232 @@
|
|||||||
|
import { readdirSync, readFileSync } from 'node:fs'
|
||||||
|
import { join, relative } from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import ts from 'typescript'
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
type LuaToken = {
|
||||||
|
kind: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const frontendSourceDirectory = fileURLToPath(new URL('../', import.meta.url))
|
||||||
|
const englishLocaleSource = readFileSync(
|
||||||
|
new URL('../../../sky_phone/config/locales/en.lua', import.meta.url),
|
||||||
|
'utf8',
|
||||||
|
)
|
||||||
|
const germanLocaleSource = readFileSync(
|
||||||
|
new URL('../../../sky_phone/config/locales/de.lua', import.meta.url),
|
||||||
|
'utf8',
|
||||||
|
)
|
||||||
|
const phoneStoreSource = readFileSync(
|
||||||
|
new URL('./phone.ts', import.meta.url),
|
||||||
|
'utf8',
|
||||||
|
)
|
||||||
|
|
||||||
|
function tokenizeLua(source: string): LuaToken[] {
|
||||||
|
const tokens: LuaToken[] = []
|
||||||
|
let index = 0
|
||||||
|
while (index < source.length) {
|
||||||
|
const character = source[index]
|
||||||
|
if (/\s/.test(character)) {
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (source.startsWith('--', index)) {
|
||||||
|
const nextLine = source.indexOf('\n', index)
|
||||||
|
if (nextLine < 0) break
|
||||||
|
index = nextLine
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ('{}[]=,;'.includes(character)) {
|
||||||
|
tokens.push({ kind: character, value: character })
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (character === '"' || character === "'") {
|
||||||
|
const quote = character
|
||||||
|
let value = ''
|
||||||
|
index += 1
|
||||||
|
while (index < source.length && source[index] !== quote) {
|
||||||
|
if (source[index] === '\\' && index + 1 < source.length) {
|
||||||
|
value += source[index + 1]
|
||||||
|
index += 2
|
||||||
|
} else {
|
||||||
|
value += source[index]
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens.push({ kind: 'string', value })
|
||||||
|
index += 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const match = source
|
||||||
|
.slice(index)
|
||||||
|
.match(/^[A-Za-z_][A-Za-z0-9_]*|^-?\d+(?:\.\d+)?/)
|
||||||
|
if (match) {
|
||||||
|
tokens.push({ kind: 'word', value: match[0] })
|
||||||
|
index += match[0].length
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
return tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectLuaLocalePaths(source: string): Set<string> {
|
||||||
|
const tokens = tokenizeLua(source)
|
||||||
|
let position =
|
||||||
|
tokens.findIndex(
|
||||||
|
(token, index) => token.kind === '=' && tokens[index + 1]?.kind === '{',
|
||||||
|
) + 1
|
||||||
|
const paths = new Set<string>()
|
||||||
|
|
||||||
|
function parseValue(path: string[]): void {
|
||||||
|
if (tokens[position]?.kind === '{') {
|
||||||
|
parseTable(path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (path.length) paths.add(path.join('.'))
|
||||||
|
position += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseTable(path: string[]): void {
|
||||||
|
position += 1
|
||||||
|
while (position < tokens.length && tokens[position].kind !== '}') {
|
||||||
|
let key: string | null = null
|
||||||
|
if (
|
||||||
|
tokens[position].kind === 'word' &&
|
||||||
|
tokens[position + 1]?.kind === '='
|
||||||
|
) {
|
||||||
|
key = tokens[position].value
|
||||||
|
position += 2
|
||||||
|
} else if (
|
||||||
|
tokens[position].kind === '[' &&
|
||||||
|
tokens[position + 1]?.kind === 'string' &&
|
||||||
|
tokens[position + 2]?.kind === ']' &&
|
||||||
|
tokens[position + 3]?.kind === '='
|
||||||
|
) {
|
||||||
|
key = tokens[position + 1].value
|
||||||
|
position += 4
|
||||||
|
}
|
||||||
|
parseValue(key === null ? [] : [...path, key])
|
||||||
|
while (tokens[position]?.kind === ',' || tokens[position]?.kind === ';') {
|
||||||
|
position += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
position += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
parseTable([])
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectDefaultLocalePaths(source: string): Set<string> {
|
||||||
|
const ast = ts.createSourceFile(
|
||||||
|
'phone.ts',
|
||||||
|
source,
|
||||||
|
ts.ScriptTarget.Latest,
|
||||||
|
true,
|
||||||
|
ts.ScriptKind.TS,
|
||||||
|
)
|
||||||
|
const declarations = new Map<string, ts.Expression>()
|
||||||
|
const registerDeclarations = (node: ts.Node): void => {
|
||||||
|
if (
|
||||||
|
ts.isVariableDeclaration(node) &&
|
||||||
|
ts.isIdentifier(node.name) &&
|
||||||
|
node.initializer
|
||||||
|
) {
|
||||||
|
declarations.set(node.name.text, node.initializer)
|
||||||
|
}
|
||||||
|
ts.forEachChild(node, registerDeclarations)
|
||||||
|
}
|
||||||
|
registerDeclarations(ast)
|
||||||
|
|
||||||
|
const paths = new Set<string>()
|
||||||
|
const collect = (node: ts.Expression, path: string[]): void => {
|
||||||
|
if (
|
||||||
|
ts.isParenthesizedExpression(node) ||
|
||||||
|
ts.isAsExpression(node) ||
|
||||||
|
ts.isSatisfiesExpression(node)
|
||||||
|
) {
|
||||||
|
collect(node.expression, path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (ts.isIdentifier(node) && declarations.has(node.text)) {
|
||||||
|
collect(declarations.get(node.text)!, path)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!ts.isObjectLiteralExpression(node)) {
|
||||||
|
if (path.length) paths.add(path.join('.'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const property of node.properties) {
|
||||||
|
if (ts.isSpreadAssignment(property)) {
|
||||||
|
collect(property.expression, path)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!ts.isPropertyAssignment(property)) continue
|
||||||
|
const name = property.name
|
||||||
|
const key =
|
||||||
|
ts.isIdentifier(name) ||
|
||||||
|
ts.isStringLiteral(name) ||
|
||||||
|
ts.isNumericLiteral(name)
|
||||||
|
? name.text
|
||||||
|
: null
|
||||||
|
if (key !== null) collect(property.initializer, [...path, key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultLocales = declarations.get('defaultLocales')
|
||||||
|
expect(defaultLocales).toBeDefined()
|
||||||
|
collect(defaultLocales!, [])
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectFrontendFiles(directory: string): string[] {
|
||||||
|
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
||||||
|
const path = join(directory, entry.name)
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
return entry.name === 'development' ? [] : collectFrontendFiles(path)
|
||||||
|
}
|
||||||
|
return /\.(?:ts|vue)$/.test(entry.name) && !entry.name.includes('.test.')
|
||||||
|
? [path]
|
||||||
|
: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('phone locale contract', () => {
|
||||||
|
const englishPaths = collectLuaLocalePaths(englishLocaleSource)
|
||||||
|
|
||||||
|
it('keeps every bundled frontend fallback in en.lua', () => {
|
||||||
|
const missing = [...collectDefaultLocalePaths(phoneStoreSource)].filter(
|
||||||
|
(path) => !englishPaths.has(`Nui.${path}`),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(missing).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('defines every static frontend translation key in en.lua', () => {
|
||||||
|
const missing: string[] = []
|
||||||
|
for (const file of collectFrontendFiles(frontendSourceDirectory)) {
|
||||||
|
const source = readFileSync(file, 'utf8')
|
||||||
|
for (const match of source.matchAll(
|
||||||
|
/phone\.t\(\s*(['"])([^'"`]+)\1\s*[,)]/g,
|
||||||
|
)) {
|
||||||
|
if (!englishPaths.has(`Nui.${match[2]}`)) {
|
||||||
|
missing.push(
|
||||||
|
`${relative(frontendSourceDirectory, file)}: ${match[2]}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(missing).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('builds German on the complete English locale fallback', () => {
|
||||||
|
expect(germanLocaleSource).toContain(
|
||||||
|
'local german = translate_shared(clone(Locales["en"] or {}))',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -51,9 +51,7 @@ describe('phone locale fallback', () => {
|
|||||||
expect(phone.t('Apps.radio.providerFeatureUnavailable')).toBe(
|
expect(phone.t('Apps.radio.providerFeatureUnavailable')).toBe(
|
||||||
'Not supported by this voice service',
|
'Not supported by this voice service',
|
||||||
)
|
)
|
||||||
expect(phone.t('Apps.radio.displayNamePlaceholder')).toBe(
|
expect(phone.t('Apps.radio.displayNamePlaceholder')).toBe('Character name')
|
||||||
'Character name',
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('keeps Messages media controls translated with a partial server locale', () => {
|
it('keeps Messages media controls translated with a partial server locale', () => {
|
||||||
@@ -71,4 +69,21 @@ describe('phone locale fallback', () => {
|
|||||||
)
|
)
|
||||||
expect(phone.t('Apps.messages.seekAudio')).toBe('Seek Audio')
|
expect(phone.t('Apps.messages.seekAudio')).toBe('Seek Audio')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('uses the English Lua payload before the bundled emergency fallback', () => {
|
||||||
|
const phone = usePhoneStore()
|
||||||
|
phone.open({
|
||||||
|
fallbackLocales: {
|
||||||
|
Common: {
|
||||||
|
cancel: 'Cancel from en.lua',
|
||||||
|
save: 'Save from en.lua',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
locales: { Common: { save: 'Speichern' } },
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(phone.t('Common.save')).toBe('Speichern')
|
||||||
|
expect(phone.t('Common.cancel')).toBe('Cancel from en.lua')
|
||||||
|
expect(phone.t('Common.close')).toBe('Close')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export type PasscodeResponseData = {
|
|||||||
export type PhoneOpenPayload = {
|
export type PhoneOpenPayload = {
|
||||||
account?: DeviceBootstrap['account']
|
account?: DeviceBootstrap['account']
|
||||||
device?: PhoneDevice
|
device?: PhoneDevice
|
||||||
|
fallbackLocales?: LocaleTree
|
||||||
lang?: string
|
lang?: string
|
||||||
locales?: LocaleTree
|
locales?: LocaleTree
|
||||||
memos?: DeviceBootstrap['memos']
|
memos?: DeviceBootstrap['memos']
|
||||||
@@ -1887,6 +1888,16 @@ const defaultLocales: LocaleTree = {
|
|||||||
contactDeleteFailed: 'The contact could not be deleted.',
|
contactDeleteFailed: 'The contact could not be deleted.',
|
||||||
callFailed: 'The call could not be started.',
|
callFailed: 'The call could not be started.',
|
||||||
emoji: 'Emoji',
|
emoji: 'Emoji',
|
||||||
|
emojiPicker: 'Emoji picker',
|
||||||
|
emojiCategories: 'Emoji categories',
|
||||||
|
skinTone: 'Skin tone {number}',
|
||||||
|
gifLabels: {
|
||||||
|
celebrate: 'Celebrate!',
|
||||||
|
hearts: 'Love it',
|
||||||
|
party: 'Party time',
|
||||||
|
thumbsUp: 'Perfect',
|
||||||
|
wow: 'WOW',
|
||||||
|
},
|
||||||
voiceMessage: 'Audio Message',
|
voiceMessage: 'Audio Message',
|
||||||
recordVoice: 'Record Audio',
|
recordVoice: 'Record Audio',
|
||||||
playAudio: 'Play Audio',
|
playAudio: 'Play Audio',
|
||||||
@@ -2127,6 +2138,7 @@ const defaultLocales: LocaleTree = {
|
|||||||
takePhoto: 'Camera',
|
takePhoto: 'Camera',
|
||||||
removePhoto: 'Remove Photo',
|
removePhoto: 'Remove Photo',
|
||||||
call: 'Call',
|
call: 'Call',
|
||||||
|
callFailed: 'The call could not be started.',
|
||||||
message: 'Message',
|
message: 'Message',
|
||||||
video: 'Video',
|
video: 'Video',
|
||||||
mail: 'Mail',
|
mail: 'Mail',
|
||||||
@@ -4671,6 +4683,7 @@ export const usePhoneStore = defineStore('phone', {
|
|||||||
isOpen: false,
|
isOpen: false,
|
||||||
lang: 'en',
|
lang: 'en',
|
||||||
launchOrigin: null as AppLaunchOrigin | null,
|
launchOrigin: null as AppLaunchOrigin | null,
|
||||||
|
fallbackLocales: defaultLocales,
|
||||||
locales: defaultLocales,
|
locales: defaultLocales,
|
||||||
preferences: cloneJsonData(DEFAULT_PHONE_PREFERENCES),
|
preferences: cloneJsonData(DEFAULT_PHONE_PREFERENCES),
|
||||||
persistenceGeneration: 0,
|
persistenceGeneration: 0,
|
||||||
@@ -4709,7 +4722,8 @@ export const usePhoneStore = defineStore('phone', {
|
|||||||
}
|
}
|
||||||
this.deviceSessionToken = nextToken
|
this.deviceSessionToken = nextToken
|
||||||
this.lang = payload.lang ?? 'en'
|
this.lang = payload.lang ?? 'en'
|
||||||
this.locales = payload.locales ?? defaultLocales
|
this.fallbackLocales = payload.fallbackLocales ?? defaultLocales
|
||||||
|
this.locales = payload.locales ?? this.fallbackLocales
|
||||||
if (payload.device) this.hydrateDevice(payload.device)
|
if (payload.device) this.hydrateDevice(payload.device)
|
||||||
if (payload.player) this.player = payload.player
|
if (payload.player) this.player = payload.player
|
||||||
this.security = payload.security ?? {
|
this.security = payload.security ?? {
|
||||||
@@ -4941,13 +4955,16 @@ export const usePhoneStore = defineStore('phone', {
|
|||||||
},
|
},
|
||||||
t(path: string, replacements: Record<string, string> = {}): string {
|
t(path: string, replacements: Record<string, string> = {}): string {
|
||||||
const translated = getByPath(this.locales, path)
|
const translated = getByPath(this.locales, path)
|
||||||
const fallback = getByPath(defaultLocales, path)
|
const english = getByPath(this.fallbackLocales, path)
|
||||||
|
const emergencyFallback = getByPath(defaultLocales, path)
|
||||||
const value =
|
const value =
|
||||||
typeof translated === 'string'
|
typeof translated === 'string'
|
||||||
? translated
|
? translated
|
||||||
: typeof fallback === 'string'
|
: typeof english === 'string'
|
||||||
? fallback
|
? english
|
||||||
: path
|
: typeof emergencyFallback === 'string'
|
||||||
|
? emergencyFallback
|
||||||
|
: path
|
||||||
return Object.entries(replacements).reduce(
|
return Object.entries(replacements).reduce(
|
||||||
(result, [key, replacement]) =>
|
(result, [key, replacement]) =>
|
||||||
result.split(`{${key}}`).join(replacement),
|
result.split(`{${key}}`).join(replacement),
|
||||||
|
|||||||
@@ -1,3 +1,47 @@
|
|||||||
Config = Config or {}
|
Config = Config or {}
|
||||||
Locales = Locales or {}
|
Locales = Locales or {}
|
||||||
|
SkyPhoneLocales = SkyPhoneLocales or {}
|
||||||
|
|
||||||
|
local resolved_locales = {}
|
||||||
|
|
||||||
|
local function clone_locale(value)
|
||||||
|
if type(value) ~= "table" then
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
|
local copied = {}
|
||||||
|
for key, nested in pairs(value) do
|
||||||
|
copied[key] = clone_locale(nested)
|
||||||
|
end
|
||||||
|
return copied
|
||||||
|
end
|
||||||
|
|
||||||
|
local function merge_locale(target, values)
|
||||||
|
for key, value in pairs(values) do
|
||||||
|
if type(value) == "table" and type(target[key]) == "table" then
|
||||||
|
merge_locale(target[key], value)
|
||||||
|
else
|
||||||
|
target[key] = clone_locale(value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function SkyPhoneLocales.Resolve(requested_locale)
|
||||||
|
local normalized = type(requested_locale) == "string" and requested_locale:lower():gsub("_", "-") or "en"
|
||||||
|
local base = normalized:match("^([^-]+)") or "en"
|
||||||
|
local locale_name = Locales[normalized] and normalized or (Locales[base] and base or "en")
|
||||||
|
local selected = Locales[locale_name] or Locales.en or {}
|
||||||
|
local english = Locales.en or {}
|
||||||
|
|
||||||
|
if selected == english then
|
||||||
|
return english, "en"
|
||||||
|
end
|
||||||
|
|
||||||
|
if not resolved_locales[locale_name] then
|
||||||
|
resolved_locales[locale_name] = clone_locale(english)
|
||||||
|
merge_locale(resolved_locales[locale_name], selected)
|
||||||
|
end
|
||||||
|
|
||||||
|
return resolved_locales[locale_name], locale_name
|
||||||
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -187,6 +187,44 @@ local translations = {
|
|||||||
["No data available"] = "Keine Daten verfügbar",
|
["No data available"] = "Keine Daten verfügbar",
|
||||||
["Required"] = "Erforderlich",
|
["Required"] = "Erforderlich",
|
||||||
["Optional"] = "Optional",
|
["Optional"] = "Optional",
|
||||||
|
["Return to Call"] = "Zurück zum Anruf",
|
||||||
|
["This item is not available for EasyShare."] = "Dieses Element kann nicht über EasyShare geteilt werden.",
|
||||||
|
["The configured GIPHY API key is invalid."] = "Der konfigurierte GIPHY-API-Schlüssel ist ungültig.",
|
||||||
|
["GIPHY is busy. Try again in a moment."] = "GIPHY ist ausgelastet. Versuche es gleich erneut.",
|
||||||
|
["Search Calls"] = "Anrufe suchen",
|
||||||
|
["Contact Details"] = "Kontaktdetails",
|
||||||
|
["Unknown Caller"] = "Unbekannter Anrufer",
|
||||||
|
["Call History"] = "Anrufverlauf",
|
||||||
|
["No calls with this number yet."] = "Noch keine Anrufe mit dieser Nummer.",
|
||||||
|
["My Card"] = "Meine Karte",
|
||||||
|
["My Number"] = "Meine Nummer",
|
||||||
|
["Device"] = "Gerät",
|
||||||
|
["Contact Index"] = "Kontaktindex",
|
||||||
|
["The call could not be started."] = "Der Anruf konnte nicht gestartet werden.",
|
||||||
|
["Los Santos marketplace"] = "Marktplatz von Los Santos",
|
||||||
|
["Choose up to six photos from Photos or take new ones. Photos are optional and the first becomes the cover."] = "Wähle bis zu sechs Fotos aus Fotos oder nimm neue auf. Fotos sind optional; das erste wird zum Titelbild.",
|
||||||
|
["Use photos saved on this phone."] = "Verwende Fotos, die auf diesem Handy gespeichert sind.",
|
||||||
|
["Take photos"] = "Fotos aufnehmen",
|
||||||
|
["Take several new photos for this listing."] = "Nimm mehrere neue Fotos für dieses Angebot auf.",
|
||||||
|
["Selected photos"] = "Ausgewählte Fotos",
|
||||||
|
["Take as many photos as you need. Each shot is added to the listing automatically."] = "Nimm so viele Fotos auf, wie du benötigst. Jede Aufnahme wird automatisch zum Angebot hinzugefügt.",
|
||||||
|
["No photo available"] = "Kein Foto verfügbar",
|
||||||
|
["The seller did not add a photo to this listing."] = "Der Verkäufer hat diesem Angebot kein Foto hinzugefügt.",
|
||||||
|
["You can publish without a photo or add one from the gallery or camera."] = "Du kannst ohne Foto veröffentlichen oder eines aus der Galerie beziehungsweise Kamera hinzufügen.",
|
||||||
|
["Previous photo"] = "Vorheriges Foto",
|
||||||
|
["Next photo"] = "Nächstes Foto",
|
||||||
|
["Remove photo {number}"] = "Foto {number} entfernen",
|
||||||
|
["You can add up to six photos."] = "Du kannst bis zu sechs Fotos hinzufügen.",
|
||||||
|
["{current} / {maximum} characters"] = "{current} / {maximum} Zeichen",
|
||||||
|
["min. {minimum}"] = "mind. {minimum}",
|
||||||
|
["Emoji picker"] = "Emoji-Auswahl",
|
||||||
|
["Emoji categories"] = "Emoji-Kategorien",
|
||||||
|
["Skin tone {number}"] = "Hautton {number}",
|
||||||
|
["Celebrate!"] = "Feiern!",
|
||||||
|
["Love it"] = "Gefällt mir",
|
||||||
|
["Party time"] = "Partyzeit",
|
||||||
|
["Perfect"] = "Perfekt",
|
||||||
|
["WOW"] = "WOW",
|
||||||
}
|
}
|
||||||
|
|
||||||
local function translate_shared(value)
|
local function translate_shared(value)
|
||||||
|
|||||||
@@ -608,7 +608,7 @@ Locales["en"] = {
|
|||||||
voiceMessage = "Voice message", sending = "Sending", failed = "Not delivered", delivered = "Delivered", read = "Read", replying = "Replying to",
|
voiceMessage = "Voice message", sending = "Sending", failed = "Not delivered", delivered = "Delivered", read = "Read", replying = "Replying to",
|
||||||
messageDeleted = "Message deleted", securityUpdate = "Security settings updated", timerChanged = "Disappearing messages: {timer}",
|
messageDeleted = "Message deleted", securityUpdate = "Security settings updated", timerChanged = "Disappearing messages: {timer}",
|
||||||
timerOff = "Off", timerAfterRead = "After reading", timerMinute = "1 minute", timerFiveMinutes = "5 minutes", timerHour = "1 hour", timerDay = "24 hours", timerWeek = "7 days",
|
timerOff = "Off", timerAfterRead = "After reading", timerMinute = "1 minute", timerFiveMinutes = "5 minutes", timerHour = "1 hour", timerDay = "24 hours", timerWeek = "7 days",
|
||||||
copied = "Copied", reply = "Reply", copy = "Copy", deleteForMe = "Delete for me", deleteForBoth = "Delete for both", report = "Report",
|
copied = "Copied", reply = "Reply", copy = "Copy", deleteForMe = "Delete for me", deleteForBoth = "Delete for both", report = "Report", returnToCall = "Return to Call",
|
||||||
contactSecurity = "Contact & Security", chatSince = "Private chat since {date}", notifications = "Notifications", readReceipts = "Read receipts", disappearing = "Disappearing messages", contactAlias = "Contact alias", saveContact = "Save Contact", addContact = "Add Contact", contactSaved = "Contact saved", removeContact = "Remove Contact", block = "Block User", unblock = "Unblock User", clearChat = "Clear Chat", chatCleared = "Chat cleared",
|
contactSecurity = "Contact & Security", chatSince = "Private chat since {date}", notifications = "Notifications", readReceipts = "Read receipts", disappearing = "Disappearing messages", contactAlias = "Contact alias", saveContact = "Save Contact", addContact = "Add Contact", contactSaved = "Contact saved", removeContact = "Remove Contact", block = "Block User", unblock = "Unblock User", clearChat = "Clear Chat", chatCleared = "Chat cleared",
|
||||||
myIdentity = "My Dark Identity", alias = "Alias", notificationPrivacy = "Notification privacy", notificationFull = "Full · alias and message", notificationPrivate = "Private · generic message", notificationHidden = "Invisible · badge only", shareActivity = "Share activity status", inviteCode = "Private invitation code", copyInvite = "Copy Invite", shareInvite = "Share Invite", privacyDisclaimer = "DarkChat stores messages on the server and does not claim end-to-end encryption.",
|
myIdentity = "My Dark Identity", alias = "Alias", notificationPrivacy = "Notification privacy", notificationFull = "Full · alias and message", notificationPrivate = "Private · generic message", notificationHidden = "Invisible · badge only", shareActivity = "Share activity status", inviteCode = "Private invitation code", copyInvite = "Copy Invite", shareInvite = "Share Invite", privacyDisclaimer = "DarkChat stores messages on the server and does not claim end-to-end encryption.",
|
||||||
messageActions = "Message actions", chatSettings = "Chat Settings", contactActions = "Contact Actions", privacySettings = "Privacy Settings",
|
messageActions = "Message actions", chatSettings = "Chat Settings", contactActions = "Contact Actions", privacySettings = "Privacy Settings",
|
||||||
@@ -629,7 +629,7 @@ Locales["en"] = {
|
|||||||
photo = "Photo", gif = "GIF", video = "Video", contact = "Contact", attachPhoto = "Attach Photo", takePhoto = "Take Photo",
|
photo = "Photo", gif = "GIF", video = "Video", contact = "Contact", attachPhoto = "Attach Photo", takePhoto = "Take Photo",
|
||||||
attachGif = "Attach GIF", attachVideo = "Attach Video", photos = "Photos", gifs = "GIFs", videos = "Videos",
|
attachGif = "Attach GIF", attachVideo = "Attach Video", photos = "Photos", gifs = "GIFs", videos = "Videos",
|
||||||
contacts = "Contacts", shareContact = "Share Contact", noContactsToShare = "Save a contact first to share it here.", contactSaved = "Contact Saved",
|
contacts = "Contacts", shareContact = "Share Contact", noContactsToShare = "Save a contact first to share it here.", contactSaved = "Contact Saved",
|
||||||
noPhotos = "Take a photo first to attach it here.", noVideos = "Record a video in Camera first.", searchGifs = "Search GIPHY", loadMore = "Load More",
|
noPhotos = "Take a photo first to attach it here.", noVideos = "Record a video in Camera first.", searchGifs = "Search GIPHY", loadMore = "Load More", retryGifs = "Try Again",
|
||||||
moreActions = "More Actions", inboxActions = "Conversation Actions", sortLabel = "Conversation Sort", sortNewest = "Newest First", sortOldest = "Oldest First", unreadConversation = "{name}, {count} unread messages",
|
moreActions = "More Actions", inboxActions = "Conversation Actions", sortLabel = "Conversation Sort", sortNewest = "Newest First", sortOldest = "Oldest First", unreadConversation = "{name}, {count} unread messages",
|
||||||
attachmentPreview = "Selected attachments", attachmentLimit = "You can attach up to {count} photos or videos.", removeAttachment = "Remove attachment {number}",
|
attachmentPreview = "Selected attachments", attachmentLimit = "You can attach up to {count} photos or videos.", removeAttachment = "Remove attachment {number}",
|
||||||
contactDetails = "Contact Details", contactName = "Name", phoneNumber = "Phone Number", company = "Company", contactActions = "Contact Actions",
|
contactDetails = "Contact Details", contactName = "Name", phoneNumber = "Phone Number", company = "Company", contactActions = "Contact Actions",
|
||||||
@@ -638,7 +638,9 @@ Locales["en"] = {
|
|||||||
selectedCount = "{count} Selected", deleteSelected = "Delete",
|
selectedCount = "{count} Selected", deleteSelected = "Delete",
|
||||||
contactSaveFailed = "The contact could not be saved.", contactDeleteFailed = "The contact could not be deleted.",
|
contactSaveFailed = "The contact could not be saved.", contactDeleteFailed = "The contact could not be deleted.",
|
||||||
callFailed = "The call could not be started.",
|
callFailed = "The call could not be started.",
|
||||||
emoji = "Emoji", voiceMessage = "Audio Message", recordVoice = "Record Audio",
|
emoji = "Emoji", emojiPicker = "Emoji picker", emojiCategories = "Emoji categories", skinTone = "Skin tone {number}",
|
||||||
|
gifLabels = { celebrate = "Celebrate!", hearts = "Love it", party = "Party time", thumbsUp = "Perfect", wow = "WOW" },
|
||||||
|
voiceMessage = "Audio Message", recordVoice = "Record Audio",
|
||||||
playAudio = "Play Audio", pauseAudio = "Pause Audio", seekAudio = "Seek Audio",
|
playAudio = "Play Audio", pauseAudio = "Pause Audio", seekAudio = "Seek Audio",
|
||||||
recording = "Recording", stopAndSend = "Stop and Send", cancelRecording = "Cancel Recording",
|
recording = "Recording", stopAndSend = "Stop and Send", cancelRecording = "Cancel Recording",
|
||||||
sending = "Sending...", delivered = "Delivered", notDelivered = "Not Delivered",
|
sending = "Sending...", delivered = "Delivered", notDelivered = "Not Delivered",
|
||||||
@@ -654,7 +656,8 @@ Locales["en"] = {
|
|||||||
capture_provider_unavailable = "The screenshot resource is unavailable.", capture_failed = "The photo could not be captured.",
|
capture_provider_unavailable = "The screenshot resource is unavailable.", capture_failed = "The photo could not be captured.",
|
||||||
video_provider_unavailable = "Video capture requires the screencapture resource.", video_capture_failed = "The video could not be recorded.",
|
video_provider_unavailable = "Video capture requires the screencapture resource.", video_capture_failed = "The video could not be recorded.",
|
||||||
recording_in_progress = "A video is already being recorded.", recording_not_found = "No active video recording was found.",
|
recording_in_progress = "A video is already being recorded.", recording_not_found = "No active video recording was found.",
|
||||||
gif_provider_unconfigured = "GIF search is not configured.", gif_provider_failed = "GIF search is temporarily unavailable.",
|
gif_provider_unconfigured = "GIF search is not configured.", gif_provider_unauthorized = "The configured GIPHY API key is invalid.",
|
||||||
|
gif_provider_rate_limited = "GIPHY is busy. Try again in a moment.", gif_provider_failed = "GIF search is temporarily unavailable.",
|
||||||
self_message = "You cannot message your own number.", recipient_not_found = "That number is unavailable.", blocked = "This contact has blocked calls and messages from your SIM.",
|
self_message = "You cannot message your own number.", recipient_not_found = "That number is unavailable.", blocked = "This contact has blocked calls and messages from your SIM.",
|
||||||
messaging_unavailable = "This company contact does not accept messages.",
|
messaging_unavailable = "This company contact does not accept messages.",
|
||||||
no_sim = "This phone has no SIM card.", rate_limited = "Too many messages. Try again in a minute.",
|
no_sim = "This phone has no SIM card.", rate_limited = "Too many messages. Try again in a minute.",
|
||||||
@@ -971,7 +974,10 @@ Locales["en"] = {
|
|||||||
phone = {
|
phone = {
|
||||||
name = "Phone", recents = "Recents", contacts = "Contacts", keypad = "Keypad",
|
name = "Phone", recents = "Recents", contacts = "Contacts", keypad = "Keypad",
|
||||||
noSim = "No SIM", noSimBody = "Insert a SIM card in Settings to make calls.",
|
noSim = "No SIM", noSimBody = "Insert a SIM card in Settings to make calls.",
|
||||||
noRecents = "No Recent Calls", noContacts = "No Contacts", searchContacts = "Search Contacts", favorites = "Favorites",
|
noRecents = "No Recent Calls", allCalls = "All", missedCalls = "Missed", searchRecents = "Search Calls",
|
||||||
|
contactDetails = "Contact Details", unknownCaller = "Unknown Caller", callHistory = "Call History",
|
||||||
|
noCallHistory = "No calls with this number yet.", noContacts = "No Contacts", searchContacts = "Search Contacts", favorites = "Favorites",
|
||||||
|
myCard = "My Card", myNumber = "My Number", device = "Device", contactIndex = "Contact Index",
|
||||||
addContact = "New Contact", newContact = "New", editContact = "Edit Contact", contactName = "Name", firstName = "First Name", lastName = "Last Name", companyOrGroup = "Company or Group", phoneNumber = "Phone Number",
|
addContact = "New Contact", newContact = "New", editContact = "Edit Contact", contactName = "Name", firstName = "First Name", lastName = "Last Name", companyOrGroup = "Company or Group", phoneNumber = "Phone Number",
|
||||||
officialContact = "Official company contact",
|
officialContact = "Official company contact",
|
||||||
choosePhoto = "Choose Contact Photo", chooseGallery = "Photos", takePhoto = "Camera", removePhoto = "Remove Photo",
|
choosePhoto = "Choose Contact Photo", chooseGallery = "Photos", takePhoto = "Camera", removePhoto = "Remove Photo",
|
||||||
@@ -982,7 +988,7 @@ Locales["en"] = {
|
|||||||
block = "Block", blockCaller = "Block Caller", blockCallerTitle = "Block this caller?",
|
block = "Block", blockCaller = "Block Caller", blockCallerTitle = "Block this caller?",
|
||||||
blockCallerBody = "{number} will no longer be able to call this SIM.",
|
blockCallerBody = "{number} will no longer be able to call this SIM.",
|
||||||
faceTime = "FaceTime", hideKeypad = "Hide Keypad", more = "More", mute = "Mute", speaker = "Speaker", viewContact = "View Contact",
|
faceTime = "FaceTime", hideKeypad = "Hide Keypad", more = "More", mute = "Mute", speaker = "Speaker", viewContact = "View Contact",
|
||||||
call = "Call", calling = "calling...", incoming = "Incoming Call", incomingDirection = "Incoming", outgoingDirection = "Outgoing", connected = "Connected",
|
call = "Call", callFailed = "The call could not be started.", calling = "calling...", incoming = "Incoming Call", incomingDirection = "Incoming", outgoingDirection = "Outgoing", connected = "Connected",
|
||||||
missed = "Missed", declined = "Declined", busy = "Busy", unavailable = "Unavailable",
|
missed = "Missed", declined = "Declined", busy = "Busy", unavailable = "Unavailable",
|
||||||
noAnswer = "No Answer", cancelled = "Cancelled", disconnected = "Disconnected", sim_removed = "SIM Removed", completed = "Call Ended",
|
noAnswer = "No Answer", cancelled = "Cancelled", disconnected = "Disconnected", sim_removed = "SIM Removed", completed = "Call Ended",
|
||||||
answer = "Answer", decline = "Decline", hangup = "End", addToContacts = "Add to Contacts",
|
answer = "Answer", decline = "Decline", hangup = "End", addToContacts = "Add to Contacts",
|
||||||
@@ -1501,7 +1507,7 @@ Locales["en"] = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
citymarkt = {
|
citymarkt = {
|
||||||
name = "CityMarkt",
|
name = "CityMarkt", eyebrow = "Los Santos marketplace",
|
||||||
tabs = { discover = "Discover", search = "Search", sell = "Sell", inbox = "Inbox", profile = "Me" },
|
tabs = { discover = "Discover", search = "Search", sell = "Sell", inbox = "Inbox", profile = "Me" },
|
||||||
categories = {
|
categories = {
|
||||||
vehicles = "Vehicles", property = "Property", electronics = "Electronics", clothing = "Clothing",
|
vehicles = "Vehicles", property = "Property", electronics = "Electronics", clothing = "Clothing",
|
||||||
@@ -1544,8 +1550,13 @@ Locales["en"] = {
|
|||||||
signInToMessage = "Sign in to your Sky Cloud account to send a message.",
|
signInToMessage = "Sign in to your Sky Cloud account to send a message.",
|
||||||
edit = "Edit", makeActive = "Make active", markSold = "Mark sold", remove = "Remove",
|
edit = "Edit", makeActive = "Make active", markSold = "Mark sold", remove = "Remove",
|
||||||
createListing = "Create listing", editListing = "Edit listing", save = "Save", step = "Step {current} of {total}", next = "Next", previous = "Back", publish = "Publish",
|
createListing = "Create listing", editListing = "Edit listing", save = "Save", step = "Step {current} of {total}", next = "Next", previous = "Back", publish = "Publish",
|
||||||
addPhotos = "Add photos", addPhotosBody = "Choose up to six photos. The first becomes the cover.",
|
addPhotos = "Add photos", addPhotosBody = "Choose up to six photos from Photos or take new ones. Photos are optional and the first becomes the cover.",
|
||||||
|
chooseGalleryBody = "Use photos saved on this phone.", takePhotos = "Take photos", takePhotosBody = "Take several new photos for this listing.",
|
||||||
|
selectedPhotos = "Selected photos", gallery = "Photos", camera = "Camera", cameraHint = "Take as many photos as you need. Each shot is added to the listing automatically.",
|
||||||
|
noPhoto = "No photo available", noPhotoBody = "The seller did not add a photo to this listing.", noPhotoOptional = "You can publish without a photo or add one from the gallery or camera.",
|
||||||
|
previousPhoto = "Previous photo", nextPhoto = "Next photo", photo = "Photo", removePhoto = "Remove photo {number}", photoLimit = "You can add up to six photos.",
|
||||||
describeOffer = "Describe your offer", title = "Title", description = "Description", category = "Category", condition = "Condition",
|
describeOffer = "Describe your offer", title = "Title", description = "Description", category = "Category", condition = "Condition",
|
||||||
|
characterCount = "{current} / {maximum} characters", minimumCharacters = "min. {minimum}",
|
||||||
priceAndPlace = "Price and location", priceType = "Price type", price = "Price", district = "District",
|
priceAndPlace = "Price and location", priceType = "Price type", price = "Price", district = "District",
|
||||||
showPhone = "Show my current phone number", preview = "Preview", published = "Your listing is live.",
|
showPhone = "Show my current phone number", preview = "Preview", published = "Your listing is live.",
|
||||||
writeMessage = "Write a message", reserveForBuyer = "Reserve for this buyer", statusChanged = "Listing updated.",
|
writeMessage = "Write a message", reserveForBuyer = "Reserve for this buyer", statusChanged = "Listing updated.",
|
||||||
@@ -1741,7 +1752,7 @@ Locales["en"] = {
|
|||||||
easyShare = {
|
easyShare = {
|
||||||
name = "EasyShare", incoming = "Incoming Share", recentChats = "Contacts and Chats",
|
name = "EasyShare", incoming = "Incoming Share", recentChats = "Contacts and Chats",
|
||||||
destinations = "Share destinations", newMessage = "New Message", sentToChat = "Sent to chat.",
|
destinations = "Share destinations", newMessage = "New Message", sentToChat = "Sent to chat.",
|
||||||
shareProfile = "Share Profile", chooseConversation = "Choose a conversation",
|
shareProfile = "Share Profile", share = "Share", chooseConversation = "Choose a conversation",
|
||||||
kinds = {
|
kinds = {
|
||||||
contact = "Contact", document = "Document", link = "Link", location = "Location", media = "Media",
|
contact = "Contact", document = "Document", link = "Link", location = "Location", media = "Media",
|
||||||
note = "Note", photo = "Photo", playlist = "Playlist", post = "Post",
|
note = "Note", photo = "Photo", playlist = "Playlist", post = "Post",
|
||||||
@@ -1761,7 +1772,7 @@ Locales["en"] = {
|
|||||||
disabled = "EasyShare is disabled.", invalid_payload = "This item cannot be shared.",
|
disabled = "EasyShare is disabled.", invalid_payload = "This item cannot be shared.",
|
||||||
invalid_target = "Choose a valid recipient.", target_unavailable = "That player is no longer available.",
|
invalid_target = "Choose a valid recipient.", target_unavailable = "That player is no longer available.",
|
||||||
transfer_not_found = "That transfer is no longer available.", too_far = "The recipient is too far away.",
|
transfer_not_found = "That transfer is no longer available.", too_far = "The recipient is too far away.",
|
||||||
not_owned = "This item no longer belongs to this phone.", payload_too_large = "This item is too large to share.",
|
not_owned = "This item no longer belongs to this phone.", unsupported_payload = "This item is not available for EasyShare.", payload_too_large = "This item is too large to share.",
|
||||||
location_unavailable = "Your location is unavailable.", rate_limited = "Too many share requests. Try again shortly.",
|
location_unavailable = "Your location is unavailable.", rate_limited = "Too many share requests. Try again shortly.",
|
||||||
copy_failed = "Could not copy this item.", request_failed = "EasyShare is temporarily unavailable.",
|
copy_failed = "Could not copy this item.", request_failed = "EasyShare is temporarily unavailable.",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -45,10 +45,8 @@ local vehicle_mods = {
|
|||||||
modLivery = 48,
|
modLivery = 48,
|
||||||
}
|
}
|
||||||
|
|
||||||
local function garage_locale()
|
local phone_locale = SkyPhoneLocales.Resolve(Config.Bridge.Locale)
|
||||||
local locale = Locales[Config.Bridge.Locale] or Locales["en"]
|
local garage_locale = phone_locale.Nui.Apps.garage
|
||||||
return locale.Nui.Apps.garage
|
|
||||||
end
|
|
||||||
|
|
||||||
local function normalized_plate(value)
|
local function normalized_plate(value)
|
||||||
return tostring(value or ""):match("^%s*(.-)%s*$")
|
return tostring(value or ""):match("^%s*(.-)%s*$")
|
||||||
@@ -233,7 +231,7 @@ local function fail_valet(error_code, server_cancel)
|
|||||||
current_valet.can_cancel = false
|
current_valet.can_cancel = false
|
||||||
current_valet.error = error_code
|
current_valet.error = error_code
|
||||||
send_valet_state()
|
send_valet_state()
|
||||||
local locale = garage_locale()
|
local locale = garage_locale
|
||||||
Bridge.Framework.Notify(locale.name, locale.errors[error_code] or locale.errors.default, "error", 5000)
|
Bridge.Framework.Notify(locale.name, locale.errors[error_code] or locale.errors.default, "error", 5000)
|
||||||
SetTimeout(8000, function()
|
SetTimeout(8000, function()
|
||||||
if current_valet and current_valet.order_id == order_id then
|
if current_valet and current_valet.order_id == order_id then
|
||||||
@@ -429,7 +427,7 @@ local function run_valet_delivery(order)
|
|||||||
current_valet.status = "delivered"
|
current_valet.status = "delivered"
|
||||||
current_valet.can_cancel = false
|
current_valet.can_cancel = false
|
||||||
send_valet_state()
|
send_valet_state()
|
||||||
local locale = garage_locale()
|
local locale = garage_locale
|
||||||
Bridge.Framework.Notify(locale.name, locale.valetDelivered, "success", 5000)
|
Bridge.Framework.Notify(locale.name, locale.valetDelivered, "success", 5000)
|
||||||
local completed_order_id = current_valet.order_id
|
local completed_order_id = current_valet.order_id
|
||||||
SetTimeout(12000, function()
|
SetTimeout(12000, function()
|
||||||
|
|||||||
@@ -297,9 +297,7 @@ local server_callbacks = {
|
|||||||
"media:import:url",
|
"media:import:url",
|
||||||
}
|
}
|
||||||
|
|
||||||
local function get_locale()
|
local locale, locale_name = SkyPhoneLocales.Resolve(Config.Bridge.Locale)
|
||||||
return Locales[Config.Bridge.Locale] or Locales["en"]
|
|
||||||
end
|
|
||||||
|
|
||||||
local function update_nui_focus()
|
local function update_nui_focus()
|
||||||
local focus = SkyPhoneFocus.Resolve({
|
local focus = SkyPhoneFocus.Resolve({
|
||||||
@@ -375,8 +373,9 @@ local function send_open_message()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local payload = device_payload
|
local payload = device_payload
|
||||||
payload.lang = Config.Bridge.Locale
|
payload.lang = locale_name
|
||||||
payload.locales = get_locale().Nui
|
payload.locales = locale.Nui
|
||||||
|
payload.fallbackLocales = Locales.en.Nui
|
||||||
SkyPhoneApps.SendCatalog()
|
SkyPhoneApps.SendCatalog()
|
||||||
SendNUIMessage({
|
SendNUIMessage({
|
||||||
type = "app:open",
|
type = "app:open",
|
||||||
@@ -445,8 +444,8 @@ if Config.Phone.DevelopmentCommand then
|
|||||||
end
|
end
|
||||||
|
|
||||||
RegisterNetEvent("sky_phone:testdata:feedback", function(success, detail)
|
RegisterNetEvent("sky_phone:testdata:feedback", function(success, detail)
|
||||||
local locale = get_locale().TestData
|
local test_data_locale = locale.TestData
|
||||||
local message = success and locale.Success or locale.Failed
|
local message = success and test_data_locale.Success or test_data_locale.Failed
|
||||||
if success and type(detail) == "string" and detail ~= "" then
|
if success and type(detail) == "string" and detail ~= "" then
|
||||||
message = message:gsub("{email}", detail)
|
message = message:gsub("{email}", detail)
|
||||||
end
|
end
|
||||||
@@ -750,7 +749,7 @@ RegisterNetEvent("sky_phone:device:error", function(error_code)
|
|||||||
tostring(error_code),
|
tostring(error_code),
|
||||||
{ always = true }
|
{ always = true }
|
||||||
)
|
)
|
||||||
local message = get_locale().DeviceErrors[error_code] or get_locale().DeviceErrors.default
|
local message = locale.DeviceErrors[error_code] or locale.DeviceErrors.default
|
||||||
Bridge.Framework.Notify("iFruit", message, "error", 5000)
|
Bridge.Framework.Notify("iFruit", message, "error", 5000)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -767,7 +766,7 @@ RegisterNetEvent("sky_phone:gallery:changed", function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
RegisterNetEvent("sky_phone:mail:new", function(data)
|
RegisterNetEvent("sky_phone:mail:new", function(data)
|
||||||
local mail_locale = get_locale().Nui.Apps.mail
|
local mail_locale = locale.Nui.Apps.mail
|
||||||
data.title = mail_locale.name
|
data.title = mail_locale.name
|
||||||
data.text = mail_locale.newMessage:gsub("{sender}", tostring(data.sender))
|
data.text = mail_locale.newMessage:gsub("{sender}", tostring(data.sender))
|
||||||
SendNUIMessage({ type = "mail:new", data = data })
|
SendNUIMessage({ type = "mail:new", data = data })
|
||||||
@@ -782,7 +781,7 @@ RegisterNetEvent("sky_phone:companies:changed", function(data)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
RegisterNetEvent("sky_phone:companies:notification", function(data)
|
RegisterNetEvent("sky_phone:companies:notification", function(data)
|
||||||
local companies_locale = get_locale().Nui.Apps.companies
|
local companies_locale = locale.Nui.Apps.companies
|
||||||
data.title = companies_locale.name
|
data.title = companies_locale.name
|
||||||
data.text = companies_locale.notifications[data.kind]
|
data.text = companies_locale.notifications[data.kind]
|
||||||
or companies_locale.notifications.requestUpdated
|
or companies_locale.notifications.requestUpdated
|
||||||
@@ -798,7 +797,7 @@ RegisterNetEvent("sky_phone:fliptok:verification-changed", function(data)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
RegisterNetEvent("sky_phone:fliptok:new", function(data)
|
RegisterNetEvent("sky_phone:fliptok:new", function(data)
|
||||||
local fliptok_locale = get_locale().Nui.Apps.fliptok
|
local fliptok_locale = locale.Nui.Apps.fliptok
|
||||||
local notification_text = fliptok_locale.notifications[data.kind] or fliptok_locale.notifications.default
|
local notification_text = fliptok_locale.notifications[data.kind] or fliptok_locale.notifications.default
|
||||||
data.title = fliptok_locale.name
|
data.title = fliptok_locale.name
|
||||||
data.text = notification_text:gsub("{actor}", tostring(data.actor or ""))
|
data.text = notification_text:gsub("{actor}", tostring(data.actor or ""))
|
||||||
@@ -814,7 +813,7 @@ RegisterNetEvent("sky_phone:picstagram:verification-changed", function(data)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
RegisterNetEvent("sky_phone:picstagram:new", function(data)
|
RegisterNetEvent("sky_phone:picstagram:new", function(data)
|
||||||
local picstagram_locale = get_locale().Nui.Apps.picstagram
|
local picstagram_locale = locale.Nui.Apps.picstagram
|
||||||
local notification_text = picstagram_locale.notifications[data.kind] or picstagram_locale.notifications.default
|
local notification_text = picstagram_locale.notifications[data.kind] or picstagram_locale.notifications.default
|
||||||
data.title = picstagram_locale.name
|
data.title = picstagram_locale.name
|
||||||
data.text = notification_text:gsub("{actor}", tostring(data.actor or ""))
|
data.text = notification_text:gsub("{actor}", tostring(data.actor or ""))
|
||||||
@@ -822,7 +821,7 @@ RegisterNetEvent("sky_phone:picstagram:new", function(data)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
RegisterNetEvent("sky_phone:feather:new", function(data)
|
RegisterNetEvent("sky_phone:feather:new", function(data)
|
||||||
local feather_locale = get_locale().Nui.Apps.feather
|
local feather_locale = locale.Nui.Apps.feather
|
||||||
local notification_text = feather_locale.notifications[data.kind] or feather_locale.notifications.default
|
local notification_text = feather_locale.notifications[data.kind] or feather_locale.notifications.default
|
||||||
data.title = feather_locale.name
|
data.title = feather_locale.name
|
||||||
data.text = notification_text:gsub("{actor}", tostring(data.actor or ""))
|
data.text = notification_text:gsub("{actor}", tostring(data.actor or ""))
|
||||||
@@ -830,7 +829,7 @@ RegisterNetEvent("sky_phone:feather:new", function(data)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
RegisterNetEvent("sky_phone:marketplace:new-message", function(data)
|
RegisterNetEvent("sky_phone:marketplace:new-message", function(data)
|
||||||
local marketplace_locale = get_locale().Nui.Apps.citymarkt
|
local marketplace_locale = locale.Nui.Apps.citymarkt
|
||||||
data.title = marketplace_locale.name
|
data.title = marketplace_locale.name
|
||||||
if data.kind == "offer" then
|
if data.kind == "offer" then
|
||||||
data.text = marketplace_locale.newOffer
|
data.text = marketplace_locale.newOffer
|
||||||
@@ -851,21 +850,21 @@ RegisterNetEvent("sky_phone:marketplace:new-message", function(data)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
RegisterNetEvent("sky_phone:calendar:reminder", function(data)
|
RegisterNetEvent("sky_phone:calendar:reminder", function(data)
|
||||||
local calendar_locale = get_locale().Nui.Apps.calendar
|
local calendar_locale = locale.Nui.Apps.calendar
|
||||||
data.title = calendar_locale.name
|
data.title = calendar_locale.name
|
||||||
data.text = calendar_locale.reminder:gsub("{title}", tostring(data.eventTitle))
|
data.text = calendar_locale.reminder:gsub("{title}", tostring(data.eventTitle))
|
||||||
SendNUIMessage({ type = "calendar:reminder", data = data })
|
SendNUIMessage({ type = "calendar:reminder", data = data })
|
||||||
end)
|
end)
|
||||||
|
|
||||||
RegisterNetEvent("sky_phone:flare:match", function(data)
|
RegisterNetEvent("sky_phone:flare:match", function(data)
|
||||||
local flare_locale = get_locale().Nui.Apps.flare
|
local flare_locale = locale.Nui.Apps.flare
|
||||||
data.title = flare_locale.name
|
data.title = flare_locale.name
|
||||||
data.text = flare_locale.newMatchNotification:gsub("{sender}", tostring(data.sender))
|
data.text = flare_locale.newMatchNotification:gsub("{sender}", tostring(data.sender))
|
||||||
SendNUIMessage({ type = "flare:new-match", data = data })
|
SendNUIMessage({ type = "flare:new-match", data = data })
|
||||||
end)
|
end)
|
||||||
|
|
||||||
RegisterNetEvent("sky_phone:flare:message", function(data)
|
RegisterNetEvent("sky_phone:flare:message", function(data)
|
||||||
local flare_locale = get_locale().Nui.Apps.flare
|
local flare_locale = locale.Nui.Apps.flare
|
||||||
data.title = flare_locale.name
|
data.title = flare_locale.name
|
||||||
data.text = flare_locale.newMessageNotification:gsub("{sender}", tostring(data.sender))
|
data.text = flare_locale.newMessageNotification:gsub("{sender}", tostring(data.sender))
|
||||||
SendNUIMessage({ type = "flare:new-message", data = data })
|
SendNUIMessage({ type = "flare:new-message", data = data })
|
||||||
@@ -906,7 +905,7 @@ RegisterNetEvent("sky_phone:billing:changed", function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
RegisterNetEvent("sky_phone:billing:new", function(data)
|
RegisterNetEvent("sky_phone:billing:new", function(data)
|
||||||
local billing_locale = get_locale().Nui.Apps.billing
|
local billing_locale = locale.Nui.Apps.billing
|
||||||
data.title = billing_locale.name
|
data.title = billing_locale.name
|
||||||
data.text = billing_locale.notifications.newInvoice
|
data.text = billing_locale.notifications.newInvoice
|
||||||
:gsub("{issuer}", tostring(data.issuer))
|
:gsub("{issuer}", tostring(data.issuer))
|
||||||
@@ -919,7 +918,7 @@ RegisterNetEvent("sky_phone:crewlink:changed", function(data)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
RegisterNetEvent("sky_phone:crewlink:notification", function(data)
|
RegisterNetEvent("sky_phone:crewlink:notification", function(data)
|
||||||
local crewlink_locale = get_locale().Nui.Apps.crewlink
|
local crewlink_locale = locale.Nui.Apps.crewlink
|
||||||
local notification_text = crewlink_locale.notifications[data.kind]
|
local notification_text = crewlink_locale.notifications[data.kind]
|
||||||
or crewlink_locale.notifications.default
|
or crewlink_locale.notifications.default
|
||||||
data.title = crewlink_locale.name
|
data.title = crewlink_locale.name
|
||||||
@@ -935,7 +934,7 @@ RegisterNetEvent("sky_phone:messages:changed", function(data)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
RegisterNetEvent("sky_phone:messages:new", function(data)
|
RegisterNetEvent("sky_phone:messages:new", function(data)
|
||||||
local messages_locale = get_locale().Nui.Apps.messages
|
local messages_locale = locale.Nui.Apps.messages
|
||||||
data.title = messages_locale.name
|
data.title = messages_locale.name
|
||||||
data.text = messages_locale.newMessage:gsub("{sender}", tostring(data.sender))
|
data.text = messages_locale.newMessage:gsub("{sender}", tostring(data.sender))
|
||||||
SendNUIMessage({ type = "messages:new", data = data })
|
SendNUIMessage({ type = "messages:new", data = data })
|
||||||
@@ -946,7 +945,7 @@ RegisterNetEvent("sky_phone:darkchat:changed", function(data)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
RegisterNetEvent("sky_phone:darkchat:new", function(data)
|
RegisterNetEvent("sky_phone:darkchat:new", function(data)
|
||||||
local darkchat_locale = get_locale().Nui.Apps.darkchat
|
local darkchat_locale = locale.Nui.Apps.darkchat
|
||||||
data.title = darkchat_locale.name
|
data.title = darkchat_locale.name
|
||||||
if data.notificationMode == "private" then
|
if data.notificationMode == "private" then
|
||||||
data.sender = nil
|
data.sender = nil
|
||||||
@@ -1003,10 +1002,10 @@ end)
|
|||||||
|
|
||||||
CreateThread(function()
|
CreateThread(function()
|
||||||
if Config.Phone.DevelopmentCommand then
|
if Config.Phone.DevelopmentCommand then
|
||||||
TriggerEvent("chat:addSuggestion", "/" .. Config.Command, get_locale().CommandDescription)
|
TriggerEvent("chat:addSuggestion", "/" .. Config.Command, locale.CommandDescription)
|
||||||
end
|
end
|
||||||
if Config.TestData.Enabled then
|
if Config.TestData.Enabled then
|
||||||
TriggerEvent("chat:addSuggestion", "/" .. Config.TestData.Command, get_locale().TestData.CommandDescription)
|
TriggerEvent("chat:addSuggestion", "/" .. Config.TestData.Command, locale.TestData.CommandDescription)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|||||||
@@ -83,9 +83,7 @@ RegisterNetEvent("sky_phone:payphone:visual:stop", function(data)
|
|||||||
restore_remote_visual(data.id)
|
restore_remote_visual(data.id)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local function get_locale()
|
local locale = SkyPhoneLocales.Resolve(Config.Bridge.Locale)
|
||||||
return Locales[Config.Bridge.Locale] or Locales["en"]
|
|
||||||
end
|
|
||||||
|
|
||||||
local function load_model(model_hash)
|
local function load_model(model_hash)
|
||||||
if HasModelLoaded(model_hash) then
|
if HasModelLoaded(model_hash) then
|
||||||
@@ -366,7 +364,7 @@ local function payphone_open_payload()
|
|||||||
currency = Config.Payphones.Currency,
|
currency = Config.Payphones.Currency,
|
||||||
maxNumberLength = Config.Sim.NumberLength,
|
maxNumberLength = Config.Sim.NumberLength,
|
||||||
pricePerSecond = Config.Payphones.PricePerSecond,
|
pricePerSecond = Config.Payphones.PricePerSecond,
|
||||||
locales = get_locale().Nui.Payphone,
|
locales = locale.Nui.Payphone,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -400,16 +398,16 @@ local function current_call_elapsed_seconds()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function call_help_message()
|
local function call_help_message()
|
||||||
local locale = get_locale().Payphone
|
local payphone_locale = locale.Payphone
|
||||||
local message
|
local message
|
||||||
if active_call_state == "connected" then
|
if active_call_state == "connected" then
|
||||||
local elapsed_seconds = current_call_elapsed_seconds()
|
local elapsed_seconds = current_call_elapsed_seconds()
|
||||||
message = locale.ConnectedHelp
|
message = payphone_locale.ConnectedHelp
|
||||||
message = replace_placeholder(message, "duration", format_duration(elapsed_seconds))
|
message = replace_placeholder(message, "duration", format_duration(elapsed_seconds))
|
||||||
message = replace_placeholder(message, "currency", Config.Payphones.Currency)
|
message = replace_placeholder(message, "currency", Config.Payphones.Currency)
|
||||||
message = replace_placeholder(message, "cost", elapsed_seconds * (tonumber(Config.Payphones.PricePerSecond) or 0))
|
message = replace_placeholder(message, "cost", elapsed_seconds * (tonumber(Config.Payphones.PricePerSecond) or 0))
|
||||||
else
|
else
|
||||||
message = locale.RingingHelp
|
message = payphone_locale.RingingHelp
|
||||||
end
|
end
|
||||||
return replace_placeholder(message, "number", active_call_number or "")
|
return replace_placeholder(message, "number", active_call_number or "")
|
||||||
end
|
end
|
||||||
@@ -577,7 +575,7 @@ end)
|
|||||||
CreateThread(function()
|
CreateThread(function()
|
||||||
while true do
|
while true do
|
||||||
if nearest_payphone and nearest_payphone.distance <= Config.Payphones.InteractionDistance and not IsNuiFocused() then
|
if nearest_payphone and nearest_payphone.distance <= Config.Payphones.InteractionDistance and not IsNuiFocused() then
|
||||||
Bridge.Framework.ShowHelpNotification(get_locale().Payphone.Interact, "E")
|
Bridge.Framework.ShowHelpNotification(locale.Payphone.Interact, "E")
|
||||||
if IsControlJustReleased(0, 38) then
|
if IsControlJustReleased(0, 38) then
|
||||||
open_payphone(nearest_payphone)
|
open_payphone(nearest_payphone)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ RegisterNetEvent("sky_phone:radio:notification", function(data)
|
|||||||
if not radio_settings.notifications or current_primary <= 0 then
|
if not radio_settings.notifications or current_primary <= 0 then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local locale = (Locales[Config.Bridge.Locale] or Locales.en).Nui.Apps.radio
|
local locale = SkyPhoneLocales.Resolve(Config.Bridge.Locale).Nui.Apps.radio
|
||||||
local template = data.joined and locale.memberJoined or locale.memberLeft
|
local template = data.joined and locale.memberJoined or locale.memberLeft
|
||||||
SendNUIMessage({
|
SendNUIMessage({
|
||||||
type = "notification:show",
|
type = "notification:show",
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ local function canonical_crewlink_invite(device, invite_code)
|
|||||||
if not group then
|
if not group then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
local locale = (Locales[Config.Bridge.Locale] or Locales["en"]).Phone.Apps.crewlink
|
local locale = SkyPhoneLocales.Resolve(Config.Bridge.Locale).Nui.Apps.crewlink
|
||||||
local title = locale.shareInviteTitle:gsub("{group}", function() return group.name end)
|
local title = locale.shareInviteTitle:gsub("{group}", function() return group.name end)
|
||||||
local copy_text = locale.shareInviteBody
|
local copy_text = locale.shareInviteBody
|
||||||
:gsub("{code}", function() return group.invite_code end)
|
:gsub("{code}", function() return group.invite_code end)
|
||||||
|
|||||||
@@ -914,7 +914,7 @@ Bridge.Callbacks.Register("sky_phone:fliptok:delete", function(source, data)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
RegisterCommand(Config.FlipTok.VerifyCommand, function(source, arguments)
|
RegisterCommand(Config.FlipTok.VerifyCommand, function(source, arguments)
|
||||||
local command_locale = (Locales[Config.Bridge.Locale] or Locales["en"]).FlipTokCommand
|
local command_locale = SkyPhoneLocales.Resolve(Config.Bridge.Locale).FlipTokCommand
|
||||||
local function command_message(template, values)
|
local function command_message(template, values)
|
||||||
return template:gsub("{(%w+)}", function(key) return values[key] or "" end)
|
return template:gsub("{(%w+)}", function(key) return values[key] or "" end)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1435,7 +1435,7 @@ Bridge.Callbacks.Register("sky_phone:picstagram:admin-resolve-report", function(
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
RegisterCommand(Config.Picstagram.VerifyCommand, function(source, args)
|
RegisterCommand(Config.Picstagram.VerifyCommand, function(source, args)
|
||||||
local command_locale = (Locales[Config.Bridge.Locale] or Locales["en"]).PicstagramCommand
|
local command_locale = SkyPhoneLocales.Resolve(Config.Bridge.Locale).PicstagramCommand
|
||||||
local function command_message(template, values)
|
local function command_message(template, values)
|
||||||
return template:gsub("{(%w+)}", function(key)
|
return template:gsub("{(%w+)}", function(key)
|
||||||
return values[key] or ""
|
return values[key] or ""
|
||||||
|
|||||||
Reference in New Issue
Block a user