feat: intl support for emoji picker (#1910)
* feat: intl support for emoji picker Fixes #1908 * fix: update emoji-picker-element * fix: fix typo
This commit is contained in:
parent
2de875795b
commit
a028a7e880
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -8,7 +8,7 @@
|
||||||
/static/icons.svg
|
/static/icons.svg
|
||||||
/static/robots.txt
|
/static/robots.txt
|
||||||
/static/inline-script.js.map
|
/static/inline-script.js.map
|
||||||
/static/emoji-all-en.json
|
/static/emoji-*.json
|
||||||
/src/inline-script/checksum.js
|
/src/inline-script/checksum.js
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,48 @@
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
|
import { LOCALE } from '../src/routes/_static/intl'
|
||||||
|
|
||||||
const readFile = promisify(fs.readFile)
|
const readFile = promisify(fs.readFile)
|
||||||
const writeFile = promisify(fs.writeFile)
|
const writeFile = promisify(fs.writeFile)
|
||||||
|
|
||||||
|
// Try 'en-US' first, then 'en' if that doesn't exist
|
||||||
|
const PREFERRED_LOCALES = [LOCALE, LOCALE.split('-')[0]]
|
||||||
|
|
||||||
|
// emojibase seems like the most "neutral" shortcodes, but cldr is available in every language
|
||||||
|
const PREFERRED_SHORTCODES = ['emojibase', 'cldr']
|
||||||
|
|
||||||
|
async function getEmojiI18nFile (locale, shortcode) {
|
||||||
|
const filename = path.resolve(__dirname,
|
||||||
|
'../node_modules/emoji-picker-element-data',
|
||||||
|
locale,
|
||||||
|
shortcode,
|
||||||
|
'data.json')
|
||||||
|
try {
|
||||||
|
return JSON.parse(await readFile(filename, 'utf8'))
|
||||||
|
} catch (err) { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getFirstExistingEmojiI18nFile () {
|
||||||
|
for (const locale of PREFERRED_LOCALES) {
|
||||||
|
for (const shortcode of PREFERRED_SHORTCODES) {
|
||||||
|
const json = await getEmojiI18nFile(locale, shortcode)
|
||||||
|
if (json) {
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function main () {
|
async function main () {
|
||||||
const json = JSON.parse(await readFile(
|
const json = await getFirstExistingEmojiI18nFile()
|
||||||
path.resolve(__dirname, '../node_modules/emoji-picker-element-data/en/emojibase-legacy/data.json'),
|
|
||||||
'utf8')
|
if (!json) {
|
||||||
)
|
throw new Error(`Couldn't find i18n data for locale ${LOCALE}. Is it supported in emoji-picker-element-data?`)
|
||||||
|
}
|
||||||
|
|
||||||
await writeFile(
|
await writeFile(
|
||||||
path.resolve(__dirname, '../static/emoji-all-en.json'),
|
path.resolve(__dirname, `../static/emoji-${LOCALE}.json`),
|
||||||
JSON.stringify(json),
|
JSON.stringify(json),
|
||||||
'utf8'
|
'utf8'
|
||||||
)
|
)
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"cross-env": "^7.0.2",
|
"cross-env": "^7.0.2",
|
||||||
"css-dedoupe": "^0.1.1",
|
"css-dedoupe": "^0.1.1",
|
||||||
"emoji-picker-element": "^1.3.0",
|
"emoji-picker-element": "^1.3.1",
|
||||||
"emoji-picker-element-data": "^1.0.0",
|
"emoji-picker-element-data": "^1.0.0",
|
||||||
"emoji-regex": "^9.0.0",
|
"emoji-regex": "^9.0.0",
|
||||||
"encoding": "^0.1.13",
|
"encoding": "^0.1.13",
|
||||||
|
|
34
src/intl/emoji-picker/fr.js
Normal file
34
src/intl/emoji-picker/fr.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
export default {
|
||||||
|
categoriesLabel: 'Catégories',
|
||||||
|
emojiUnsupportedMessage: 'Votre navigateur ne soutient pas les emojis en couleur.',
|
||||||
|
favoritesLabel: 'Favoris',
|
||||||
|
loadingMessage: 'Chargement en cours…',
|
||||||
|
networkErrorMessage: 'Impossible de charger les emojis. Veuillez essayer de recharger.',
|
||||||
|
regionLabel: 'Choisir un emoji',
|
||||||
|
searchDescription: 'Quand les résultats sont disponisbles, appuyez la fleche vers le haut ou le bas et la touche entrée pour choisir.',
|
||||||
|
searchLabel: 'Rechercher',
|
||||||
|
searchResultsLabel: 'Résultats',
|
||||||
|
skinToneDescription: 'Quand disponible, appuyez la fleche vers le haut ou le bas et la touch entrée pour choisir.',
|
||||||
|
skinToneLabel: 'Choisir une couleur de peau (actuellement {skinTone})',
|
||||||
|
skinTonesLabel: 'Couleurs de peau',
|
||||||
|
skinTones: [
|
||||||
|
'Défaut',
|
||||||
|
'Clair',
|
||||||
|
'Moyennement clair',
|
||||||
|
'Moyen',
|
||||||
|
'Moyennement sombre',
|
||||||
|
'Sombre'
|
||||||
|
],
|
||||||
|
categories: {
|
||||||
|
custom: 'Customisé',
|
||||||
|
'smileys-emotion': 'Les smileyes et les émoticônes',
|
||||||
|
'people-body': 'Les gens et le corps',
|
||||||
|
'animals-nature': 'Les animaux et la nature',
|
||||||
|
'food-drink': 'La nourriture et les boissons',
|
||||||
|
'travel-places': 'Les voyages et les endroits',
|
||||||
|
activities: 'Les activités',
|
||||||
|
objects: 'Les objets',
|
||||||
|
symbols: 'Les symbols',
|
||||||
|
flags: 'Les drapeaux'
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,8 @@
|
||||||
<div class="emoji-container">
|
<div class="emoji-container">
|
||||||
<emoji-picker
|
<emoji-picker
|
||||||
ref:picker
|
ref:picker
|
||||||
data-source="/emoji-all-en.json"
|
locale={emojiPickerLocale}
|
||||||
|
data-source={emojiPickerDataSource}
|
||||||
class={darkMode ? 'dark' : 'light'}
|
class={darkMode ? 'dark' : 'light'}
|
||||||
on:emoji-click="onEmojiSelected(event)"
|
on:emoji-click="onEmojiSelected(event)"
|
||||||
on:keydown="onPickerKeydown(event)"
|
on:keydown="onPickerKeydown(event)"
|
||||||
|
@ -67,6 +68,7 @@
|
||||||
import { convertCustomEmojiToEmojiPickerFormat } from '../../../_utils/convertCustomEmojiToEmojiPickerFormat'
|
import { convertCustomEmojiToEmojiPickerFormat } from '../../../_utils/convertCustomEmojiToEmojiPickerFormat'
|
||||||
import { supportsFocusVisible } from '../../../_utils/supportsFocusVisible'
|
import { supportsFocusVisible } from '../../../_utils/supportsFocusVisible'
|
||||||
import { importFocusVisible } from '../../../_utils/asyncPolyfills'
|
import { importFocusVisible } from '../../../_utils/asyncPolyfills'
|
||||||
|
import { emojiPickerI18n, emojiPickerDataSource, emojiPickerLocale } from '../../../_static/emojiPickerIntl'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
async oncreate () {
|
async oncreate () {
|
||||||
|
@ -75,6 +77,9 @@
|
||||||
const { enableGrayscale, isUserTouching } = this.store.get()
|
const { enableGrayscale, isUserTouching } = this.store.get()
|
||||||
const { picker } = this.refs
|
const { picker } = this.refs
|
||||||
picker.customEmoji = customEmoji
|
picker.customEmoji = customEmoji
|
||||||
|
if (emojiPickerI18n) {
|
||||||
|
picker.i18n = emojiPickerI18n
|
||||||
|
}
|
||||||
// break into shadow DOM to fix grayscale in Wellness grayscale mode
|
// break into shadow DOM to fix grayscale in Wellness grayscale mode
|
||||||
if (enableGrayscale) {
|
if (enableGrayscale) {
|
||||||
const style = document.createElement('style')
|
const style = document.createElement('style')
|
||||||
|
@ -100,6 +105,10 @@
|
||||||
ModalDialog
|
ModalDialog
|
||||||
},
|
},
|
||||||
store: () => store,
|
store: () => store,
|
||||||
|
data: () => ({
|
||||||
|
emojiPickerLocale,
|
||||||
|
emojiPickerDataSource
|
||||||
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
darkMode: ({ $currentTheme }) => isDarkTheme($currentTheme),
|
darkMode: ({ $currentTheme }) => isDarkTheme($currentTheme),
|
||||||
customEmoji: ({ $currentCustomEmoji, $autoplayGifs }) => (
|
customEmoji: ({ $currentCustomEmoji, $autoplayGifs }) => (
|
||||||
|
|
11
src/routes/_static/emojiPickerIntl.js
Normal file
11
src/routes/_static/emojiPickerIntl.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { LOCALE } from './intl'
|
||||||
|
|
||||||
|
export const emojiPickerDataSource = `/emoji-${LOCALE}.json`
|
||||||
|
|
||||||
|
// this should be undefined for English; it's already bundled with emoji-picker-element
|
||||||
|
export const emojiPickerI18n = process.env.EMOJI_PICKER_I18N
|
||||||
|
|
||||||
|
// To avoid creating a new IDB database named emoji-picker-en-US, just
|
||||||
|
// reuse the existing default "en" one (otherwise people will end up with
|
||||||
|
// a stale database taking up useless space)
|
||||||
|
export const emojiPickerLocale = LOCALE === 'en-US' ? 'en' : LOCALE
|
|
@ -1,5 +1,6 @@
|
||||||
import Database from 'emoji-picker-element/database'
|
import Database from 'emoji-picker-element/database'
|
||||||
import { lifecycle } from './lifecycle'
|
import { lifecycle } from './lifecycle'
|
||||||
|
import { emojiPickerLocale, emojiPickerDataSource } from '../_static/emojiPickerIntl'
|
||||||
|
|
||||||
let database
|
let database
|
||||||
|
|
||||||
|
@ -23,7 +24,8 @@ function applySkinToneToEmoji (emoji, skinTone) {
|
||||||
export function init () {
|
export function init () {
|
||||||
if (!database) {
|
if (!database) {
|
||||||
database = new Database({
|
database = new Database({
|
||||||
dataSource: '/emoji-all-en.json'
|
locale: emojiPickerLocale,
|
||||||
|
dataSource: emojiPickerDataSource
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ const assets = __assets__
|
||||||
.filter(filename => filename !== '/robots.txt')
|
.filter(filename => filename !== '/robots.txt')
|
||||||
.filter(filename => !filename.includes('traineddata.gz')) // cache on-demand
|
.filter(filename => !filename.includes('traineddata.gz')) // cache on-demand
|
||||||
.filter(filename => !filename.endsWith('.webapp')) // KaiOS manifest
|
.filter(filename => !filename.endsWith('.webapp')) // KaiOS manifest
|
||||||
.filter(filename => !filename.includes('emoji-all-en.json')) // useless to cache; it already goes in IndexedDB
|
.filter(filename => !/emoji-.*?\.json$/.test(filename)) // useless to cache; it already goes in IndexedDB
|
||||||
|
|
||||||
// `shell` is an array of all the files generated by webpack
|
// `shell` is an array of all the files generated by webpack
|
||||||
// also contains '/index.html' for some reason
|
// also contains '/index.html' for some reason
|
||||||
|
|
|
@ -100,6 +100,16 @@ test('autosuggest handles works with regular emoji - clicking', async t => {
|
||||||
.expect(composeInput.value).eql('\ud83c\udf4d @quux ')
|
.expect(composeInput.value).eql('\ud83c\udf4d @quux ')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('autosuggest can suggest native emoji', async t => {
|
||||||
|
await loginAsFoobar(t)
|
||||||
|
await t
|
||||||
|
.hover(composeInput)
|
||||||
|
.typeText(composeInput, ':slight')
|
||||||
|
.expect(getNthAutosuggestionResult(1).innerText).contains(':slightly_smiling_face:', { timeout })
|
||||||
|
.click(getNthAutosuggestionResult(1))
|
||||||
|
.expect(composeInput.value).eql('\ud83d\ude42 ')
|
||||||
|
})
|
||||||
|
|
||||||
test('autosuggest only shows for one input', async t => {
|
test('autosuggest only shows for one input', async t => {
|
||||||
await loginAsFoobar(t)
|
await loginAsFoobar(t)
|
||||||
await t
|
await t
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { LOCALE } from '../src/routes/_static/intl'
|
import { DEFAULT_LOCALE, LOCALE } from '../src/routes/_static/intl'
|
||||||
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const webpack = require('webpack')
|
const webpack = require('webpack')
|
||||||
|
@ -19,6 +19,9 @@ const output = Object.assign(config.client.output(), {
|
||||||
chunkFilename: dev ? '[hash]/[id].js' : '[id].[contenthash].js'
|
chunkFilename: dev ? '[hash]/[id].js' : '[id].[contenthash].js'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const emojiPickerI18n = LOCALE !== DEFAULT_LOCALE &&
|
||||||
|
require(path.join(__dirname, '../src/intl/emoji-picker/', `${LOCALE}.js`)).default
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: config.client.entry(),
|
entry: config.client.entry(),
|
||||||
output,
|
output,
|
||||||
|
@ -101,7 +104,8 @@ module.exports = {
|
||||||
'process.env.INLINE_SVGS': JSON.stringify(inlineSvgs),
|
'process.env.INLINE_SVGS': JSON.stringify(inlineSvgs),
|
||||||
'process.env.ALL_SVGS': JSON.stringify(allSvgs),
|
'process.env.ALL_SVGS': JSON.stringify(allSvgs),
|
||||||
'process.env.URL_REGEX': urlRegex.toString(),
|
'process.env.URL_REGEX': urlRegex.toString(),
|
||||||
'process.env.LOCALE': JSON.stringify(LOCALE)
|
'process.env.LOCALE': JSON.stringify(LOCALE),
|
||||||
|
'process.env.EMOJI_PICKER_I18N': emojiPickerI18n ? JSON.stringify(emojiPickerI18n) : 'undefined'
|
||||||
}),
|
}),
|
||||||
new webpack.NormalModuleReplacementPlugin(
|
new webpack.NormalModuleReplacementPlugin(
|
||||||
/\/_database\/database\.js$/, // this version plays nicer with IDEs
|
/\/_database\/database\.js$/, // this version plays nicer with IDEs
|
||||||
|
|
|
@ -2904,10 +2904,10 @@ emoji-picker-element-data@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/emoji-picker-element-data/-/emoji-picker-element-data-1.0.0.tgz#1e9c4b399ce6e1858514df4c25b65284d981f92f"
|
resolved "https://registry.yarnpkg.com/emoji-picker-element-data/-/emoji-picker-element-data-1.0.0.tgz#1e9c4b399ce6e1858514df4c25b65284d981f92f"
|
||||||
integrity sha512-Ch6Ibuc2DJAh9MMyaH0hxsfkCoGAkYVWf9i1JC30PsaC4L9rmS7LMvu1iR396NHecdMYToJEQEOneatPVGe/IQ==
|
integrity sha512-Ch6Ibuc2DJAh9MMyaH0hxsfkCoGAkYVWf9i1JC30PsaC4L9rmS7LMvu1iR396NHecdMYToJEQEOneatPVGe/IQ==
|
||||||
|
|
||||||
emoji-picker-element@^1.3.0:
|
emoji-picker-element@^1.3.1:
|
||||||
version "1.3.0"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/emoji-picker-element/-/emoji-picker-element-1.3.0.tgz#d78deba0ebc4b87731bb2c16f7be00ec458d7647"
|
resolved "https://registry.yarnpkg.com/emoji-picker-element/-/emoji-picker-element-1.3.1.tgz#844e1ed261b6cda431423a1652da977202cb2090"
|
||||||
integrity sha512-Zg+8rtr3vXKuAgBXWpSBghHq+I6o7+35N+25MN3P07pUyk07GXJ6B+gKr8ttUo2LZrLDZVoqKOVMzowkNwwZIg==
|
integrity sha512-+WtNPw28snGd5ZXVS5mAAKsAJ1+hJVRdXNQ9ZCdWSLVCK1mtJjcHl2dMxtB1LxSpX6m79DAAHMmDJRjqbRA+5w==
|
||||||
|
|
||||||
emoji-regex@^7.0.1:
|
emoji-regex@^7.0.1:
|
||||||
version "7.0.3"
|
version "7.0.3"
|
||||||
|
|
Loading…
Reference in a new issue