From 96d0d1d171aa95519be36e763a1708c9ff9e2431 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Fri, 6 Aug 2021 10:05:58 -0700 Subject: [PATCH] feat: add built-in translations (#200) * feat: add built-in translations * docs: fix docs * docs: fix docs --- .gitignore | 1 + README.md | 49 ++++++++++++++++++++++++++++++++----- bin/buildI18n.js | 34 +++++++++++++++++++++++++ package.json | 3 ++- src/picker/PickerElement.js | 2 +- src/picker/i18n/de.js | 34 +++++++++++++++++++++++++ src/picker/i18n/fr.js | 34 +++++++++++++++++++++++++ 7 files changed, 149 insertions(+), 8 deletions(-) create mode 100644 bin/buildI18n.js create mode 100644 src/picker/i18n/de.js create mode 100644 src/picker/i18n/fr.js diff --git a/.gitignore b/.gitignore index 093a462..8c69db7 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,4 @@ dist /bundle.js /test/benchmark/node_modules /test/benchmark/data.json +/i18n \ No newline at end of file diff --git a/README.md b/README.md index 9442395..e36c2cf 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ A lightweight emoji picker, distributed as a web component. + [Custom styling](#custom-styling) * [JavaScript API](#javascript-api) + [Picker](#picker) - - [i18n structure](#i18n-structure) + - [Internationalization](#internationalization) + * [Built-in translations](#built-in-translations) - [Custom category order](#custom-category-order) + [Database](#database) - [Constructors](#constructors) @@ -287,9 +288,9 @@ Some values can also be set as declarative attributes: Note that complex properties like `i18n` or `customEmoji` are not supported as attributes, because the DOM only supports string attributes, not complex objects. -#### i18n structure +#### Internationalization -Here is the default English `i18n` object (`"en"` locale): +The `i18n` parameter specifies translations for the picker interface. Here is the default English `i18n` object: @@ -332,8 +333,36 @@ Here is the default English `i18n` object (`"en"` locale): -Note that some of these strings are only visible to users of screen readers. -But you should still support them if you internationalize your app! +Note that some of these strings are only visible to users of screen readers. They are still important for accessibility! + +##### Built-in translations + +Community-provided translations for some languages [are available](https://github.com/nolanlawson/emoji-picker-element/tree/master/src/i18n). You can use them like so: + +```js +import fr from 'emoji-picker-element/i18n/fr'; +import de from 'emoji-picker-element/i18n/de'; + +// French +picker.i18n = fr; + +// German +picker.i18n = de; +``` + +Note that translations for the interface (`i18n`) are not the same as translations for the emoji data (`dataSource` and `locale`). To support both, you should do something like: + +```js +import fr from 'emoji-picker-element/i18n/fr'; + +const picker = new Picker({ + i18n: fr, + locale: 'fr', + dataSource: 'https://cdn.jsdelivr.net/npm/emoji-picker-element-data@^1/fr/emojibase/data.json', +}); +``` + +If a built-in translation for your target language is not available, you can also write your own translation and pass it in as `i18n`. Please feel free to contribute your translation [here](https://github.com/nolanlawson/emoji-picker-element/tree/master/src/i18n). #### Custom category order @@ -740,12 +769,20 @@ While this option can reduce your bundle size, note that it only works if your S ### Data source and JSON format -If you'd like to host the emoji JSON yourself, you can do: +If you'd like to host the emoji data (`dataSource`) yourself, you can do: npm install emoji-picker-element-data@^1 Then host `node_modules/emoji-picker-element-data/en/emojibase/data.json` (or other JSON files) on your web server. +```js +const picker = new Picker({ + dataSource: '/path/to/my/webserver/data.json' +}); +``` + +See [`emoji-picker-element-data`](https://www.npmjs.com/package/emoji-picker-element-data) for details. + ### Shortcodes There is no standard for shortcodes, so unlike other emoji data, there is some disagreement as to what a "shortcode" actually is. diff --git a/bin/buildI18n.js b/bin/buildI18n.js new file mode 100644 index 0000000..93f4ad6 --- /dev/null +++ b/bin/buildI18n.js @@ -0,0 +1,34 @@ +import path from 'path' +import { copyFile, readdir, writeFile } from './fs.js' +import mkdirp from 'mkdirp' +import rimraf from 'rimraf' +import { promisify } from 'util' + +const __dirname = path.dirname(new URL(import.meta.url).pathname) + +async function main () { + const targetDir = path.join(__dirname, '../i18n') + + await promisify(rimraf)(targetDir) + await mkdirp(targetDir) + + const sourceDir = path.join(__dirname, '../src/picker/i18n') + + const sourceFiles = await readdir(sourceDir) + + await Promise.all(sourceFiles.map(async sourceFile => { + await Promise.all([ + copyFile(path.join(sourceDir, sourceFile), path.join(targetDir, sourceFile)), + writeFile(path.join(targetDir, sourceFile.replace('.js', '.d.ts')), ` +import { I18n } from "../shared"; +declare const _default: I18n; +export default _default; + `.trim()) + ]) + })) +} + +main().catch(err => { + console.error(err) + process.exit(1) +}) diff --git a/package.json b/package.json index 4580aa2..d3d9ae4 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,12 @@ ], "scripts": { "prepare": "run-s build", - "build": "run-s build:rollup build:css-docs build:i18n-docs build:toc", + "build": "run-s build:rollup build:i18n build:css-docs build:i18n-docs build:toc", "build:rollup": "cross-env NODE_ENV=production rollup -c", "build:css-docs": "node ./bin/generateCssDocs", "build:i18n-docs": "node ./bin/generateI18nDocs", "build:toc": "node ./bin/generateTOC", + "build:i18n": "node ./bin/buildI18n", "benchmark:runtime": "cross-env PERF=1 run-s build:rollup && ./bin/run-benchmark.sh", "benchmark:bundlesize": "run-s build:rollup benchmark:bundle benchmark:run-bundlesize", "benchmark:bundle": "rollup -c ./test/bundlesize/rollup.config.js", diff --git a/src/picker/PickerElement.js b/src/picker/PickerElement.js index ce5d544..f2016f3 100644 --- a/src/picker/PickerElement.js +++ b/src/picker/PickerElement.js @@ -1,7 +1,7 @@ import SveltePicker from './components/Picker/Picker.svelte' import { DEFAULT_DATA_SOURCE, DEFAULT_LOCALE } from '../database/constants' import { DEFAULT_CATEGORY_SORTING, DEFAULT_SKIN_TONE_EMOJI } from './constants' -import enI18n from '../picker/i18n/en.js' +import enI18n from './i18n/en.js' import styles from 'emoji-picker-element-styles' import Database from './ImportedDatabase' diff --git a/src/picker/i18n/de.js b/src/picker/i18n/de.js new file mode 100644 index 0000000..4655fd9 --- /dev/null +++ b/src/picker/i18n/de.js @@ -0,0 +1,34 @@ +export default { + categoriesLabel: 'Kategorien', + emojiUnsupportedMessage: 'Dein Browser unterstützt keine farbigen Emojis.', + favoritesLabel: 'Favoriten', + loadingMessage: 'Wird geladen…', + networkErrorMessage: 'Konnte Emoji nicht laden. Versuche, die Seite neu zu laden.', + regionLabel: 'Emoji auswählen', + searchDescription: 'Wenn Suchergebnisse verfügbar sind, wähle sie mit Pfeil rauf und runter, dann Eingabetaste, aus.', + searchLabel: 'Suchen', + searchResultsLabel: 'Suchergebnisse', + skinToneDescription: 'Wenn angezeigt, nutze Pfeiltasten rauf und runter zum Auswählen, Eingabe zum Akzeptieren.', + skinToneLabel: 'Wähle einen Hautton (aktuell {skinTone})', + skinTonesLabel: 'Hauttöne', + skinTones: [ + 'Standard', + 'Hell', + 'Mittel-hell', + 'Mittel', + 'Mittel-dunkel', + 'Dunkel' + ], + categories: { + custom: 'Benutzerdefiniert', + 'smileys-emotion': 'Smileys und Emoticons', + 'people-body': 'Menschen und Körper', + 'animals-nature': 'Tiere und Natur', + 'food-drink': 'Essen und Trinken', + 'travel-places': 'Reisen und Orte', + activities: 'Aktivitäten', + objects: 'Objekte', + symbols: 'Symbole', + flags: 'Flaggen' + } +} diff --git a/src/picker/i18n/fr.js b/src/picker/i18n/fr.js new file mode 100644 index 0000000..f2df4db --- /dev/null +++ b/src/picker/i18n/fr.js @@ -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' + } +}