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:
Leon.Schmidt
2026-08-17 15:01:57 +02:00
parent 9adc5a755f
commit e58f1b6c18
15 changed files with 438 additions and 75 deletions
+19 -8
View File
@@ -1,5 +1,6 @@
<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 { computed, ref } from 'vue'
@@ -25,10 +26,13 @@ const activeGroup = ref(0)
const skinTone = ref(0)
const groups = ['😀', '👋', '🧑', '🐻', '🍔', '⚽', '🚗', '💡', '❤️', '🏳️']
const skinTones = ['✋', '✋🏻', '✋🏼', '✋🏽', '✋🏾', '✋🏿']
const localizedEmojiData = computed(() =>
phone.lang.toLocaleLowerCase().startsWith('de') ? deEmojiData : enEmojiData,
)
const emojis = computed(() => {
const needle = query.value.trim().toLocaleLowerCase(phone.lang)
return (emojiData as EmojiEntry[])
return (localizedEmojiData.value as EmojiEntry[])
.filter((entry) => {
if (!needle) return entry.group === activeGroup.value
return `${entry.annotation} ${entry.shortcodes?.join(' ') ?? ''} ${entry.tags?.join(' ') ?? ''}`
@@ -40,17 +44,24 @@ const emojis = computed(() => {
emoji:
skinTone.value === 0
? entry.emoji
: entry.skins?.find((skin) => skin.tone === skinTone.value)?.emoji ??
entry.emoji,
: (entry.skins?.find((skin) => skin.tone === skinTone.value)?.emoji ??
entry.emoji),
}))
})
</script>
<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>
<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" />
</button>
</header>
@@ -63,7 +74,7 @@ const emojis = computed(() => {
autocomplete="off"
/>
</label>
<nav v-if="!query" aria-label="Emoji categories">
<nav v-if="!query" :aria-label="phone.t('Apps.messages.emojiCategories')">
<button
v-for="(group, index) in groups"
:key="group"
@@ -79,7 +90,7 @@ const emojis = computed(() => {
:key="tone"
type="button"
:class="{ active: skinTone === index }"
:title="`Skin tone ${index}`"
:title="phone.t('Apps.messages.skinTone', { number: String(index) })"
@click="skinTone = index"
>
{{ tone }}
@@ -32,12 +32,12 @@ const videoStyles: Record<string, string> = {
'ocean-loop': 'linear-gradient(145deg, #0b132b, #26648e 55%, #67d5b5)',
'sunset-loop': 'linear-gradient(145deg, #141e30, #5f2c82 55%, #ff9a62)',
}
const gifContent: Record<string, { emoji: string; label: string }> = {
celebrate: { emoji: '🎉', label: 'Celebrate!' },
hearts: { emoji: '💖', label: 'Love it' },
party: { emoji: '🥳', label: 'Party time' },
thumbs_up: { emoji: '👍', label: 'Perfect' },
wow: { emoji: '🤯', label: 'WOW' },
const gifContent: Record<string, { emoji: string; key: string }> = {
celebrate: { emoji: '🎉', key: 'celebrate' },
hearts: { emoji: '💖', key: 'hearts' },
party: { emoji: '🥳', key: 'party' },
thumbs_up: { emoji: '👍', key: 'thumbsUp' },
wow: { emoji: '🤯', key: 'wow' },
}
const background = computed(() =>
@@ -139,13 +139,13 @@ function durationLabel(milliseconds: number | null): string {
<img
v-if="mediaUrl"
:src="mediaUrl"
alt="GIF"
:alt="phone.t('Apps.messages.gif')"
loading="lazy"
referrerpolicy="no-referrer"
/>
<template v-else>
<span>{{ gif.emoji }}</span>
<strong>{{ gif.label }}</strong>
<strong>{{ phone.t(`Apps.messages.gifLabels.${gif.key}`) }}</strong>
</template>
</div>
<p v-if="message.body?.trim()" class="messages-attachment-caption">