Fix localized emoji auto-completions

This commit is contained in:
Jamie Kyle 2025-04-29 16:24:14 -07:00 committed by GitHub
commit 074fa8af4b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 66 additions and 49 deletions

View file

@ -1271,7 +1271,7 @@ function useReactionsToast(props: UseReactionsToastType): void {
<span className="CallingReactionsToasts__reaction"> <span className="CallingReactionsToasts__reaction">
<FunStaticEmoji <FunStaticEmoji
role="img" role="img"
aria-label={emojiLocalizer(emojiVariantKey)} aria-label={emojiLocalizer.getLocaleShortName(emojiVariantKey)}
size={28} size={28}
emoji={emojiVariant} emoji={emojiVariant}
/> />

View file

@ -166,7 +166,7 @@ function BioEmoji(props: { emoji: EmojiVariantKey }) {
return ( return (
<FunStaticEmoji <FunStaticEmoji
role="img" role="img"
aria-label={emojiLocalizer(props.emoji)} aria-label={emojiLocalizer.getLocaleShortName(props.emoji)}
emoji={emojiVariant} emoji={emojiVariant}
size={24} size={24}
/> />

View file

@ -54,7 +54,7 @@ export function Emojify({
// eslint-disable-next-line react/no-array-index-key // eslint-disable-next-line react/no-array-index-key
key={index} key={index}
role="img" role="img"
aria-label={emojiLocalizer(variantKey)} aria-label={emojiLocalizer.getLocaleShortName(variantKey)}
emoji={variant} emoji={variant}
size={fontSizeOverride} size={fontSizeOverride}
/> />

View file

@ -86,7 +86,7 @@ function ReactionViewerEmoji(props: {
return ( return (
<FunStaticEmoji <FunStaticEmoji
role="img" role="img"
aria-label={emojiLocalizer(emojiVariantKey)} aria-label={emojiLocalizer.getLocaleShortName(emojiVariantKey)}
size={18} size={18}
emoji={emojiVariant} emoji={emojiVariant}
/> />

View file

@ -3,15 +3,7 @@
// Camelcase disabled due to emoji-datasource using snake_case // Camelcase disabled due to emoji-datasource using snake_case
/* eslint-disable camelcase */ /* eslint-disable camelcase */
import { import { groupBy, keyBy, mapValues, sortBy } from 'lodash';
compact,
flatMap,
groupBy,
keyBy,
map,
mapValues,
sortBy,
} from 'lodash';
import { getOwn } from '../../util/getOwn'; import { getOwn } from '../../util/getOwn';
import { import {
EMOJI_SKIN_TONE_TO_KEY, EMOJI_SKIN_TONE_TO_KEY,
@ -152,15 +144,6 @@ export function getEmojiData(
return base; return base;
} }
const shortNames = new Set([
...map(data, 'short_name'),
...compact<string>(flatMap(data, 'short_names')),
]);
export function isShortName(name: string): boolean {
return shortNames.has(name);
}
export function unifiedToEmoji(unified: string): string { export function unifiedToEmoji(unified: string): string {
return unified return unified
.split('-') .split('-')

View file

@ -57,7 +57,7 @@ export function FunEmojiPickerButton(
<FunStaticEmoji <FunStaticEmoji
role="img" role="img"
size={20} size={20}
aria-label={emojiLocalizer(emojiVarant.key)} aria-label={emojiLocalizer.getLocaleShortName(emojiVarant.key)}
emoji={emojiVarant} emoji={emojiVarant}
/> />
) : ( ) : (

View file

@ -252,7 +252,10 @@ type EmojiIndex = Readonly<{
pickerCategories: Record<EmojiPickerCategory, Array<EmojiParentKey>>; pickerCategories: Record<EmojiPickerCategory, Array<EmojiParentKey>>;
defaultEnglishSearchIndex: Array<FunEmojiSearchIndexEntry>; defaultEnglishSearchIndex: Array<FunEmojiSearchIndexEntry>;
defaultEnglishLocalizerIndex: Map<EmojiParentKey, string>; defaultEnglishLocalizerIndex: {
parentKeyToLocaleShortName: Map<EmojiParentKey, string>;
localeShortNameToParentKey: Map<string, EmojiParentKey>;
};
}>; }>;
/** @internal */ /** @internal */
@ -288,7 +291,10 @@ const EMOJI_INDEX: EmojiIndex = {
[EmojiPickerCategory.Flags]: [], [EmojiPickerCategory.Flags]: [],
}, },
defaultEnglishSearchIndex: [], defaultEnglishSearchIndex: [],
defaultEnglishLocalizerIndex: new Map(), defaultEnglishLocalizerIndex: {
parentKeyToLocaleShortName: new Map(),
localeShortNameToParentKey: new Map(),
},
}; };
function addParent(parent: EmojiParentData, rank: number) { function addParent(parent: EmojiParentData, rank: number) {
@ -320,10 +326,14 @@ function addParent(parent: EmojiParentData, rank: number) {
emoticons: parent.emoticons, emoticons: parent.emoticons,
}); });
EMOJI_INDEX.defaultEnglishLocalizerIndex.set( EMOJI_INDEX.defaultEnglishLocalizerIndex.parentKeyToLocaleShortName.set(
parent.key, parent.key,
parent.englishShortNameDefault parent.englishShortNameDefault
); );
EMOJI_INDEX.defaultEnglishLocalizerIndex.localeShortNameToParentKey.set(
parent.englishShortNameDefault,
parent.key
);
} }
function addVariant(parentKey: EmojiParentKey, variant: EmojiVariantData) { function addVariant(parentKey: EmojiParentKey, variant: EmojiVariantData) {

View file

@ -641,7 +641,7 @@ const Cell = memo(function Cell(props: CellProps): JSX.Element {
); );
const emojiName = useMemo(() => { const emojiName = useMemo(() => {
return emojiLocalizer(emojiVariant.key); return emojiLocalizer.getLocaleShortName(emojiVariant.key);
}, [emojiVariant.key, emojiLocalizer]); }, [emojiVariant.key, emojiLocalizer]);
const emojiShortNameDisplay = useMemo(() => { const emojiShortNameDisplay = useMemo(() => {

View file

@ -12,13 +12,21 @@ import type { LocaleEmojiListType } from '../../types/emoji';
import { strictAssert } from '../../util/assert'; import { strictAssert } from '../../util/assert';
import { useFunEmojiLocalization } from './FunEmojiLocalizationProvider'; import { useFunEmojiLocalization } from './FunEmojiLocalizationProvider';
export type FunEmojiLocalizerIndex = ReadonlyMap<EmojiParentKey, string>; export type FunEmojiLocalizerIndex = Readonly<{
export type FunEmojiLocalizer = (key: EmojiVariantKey) => string; parentKeyToLocaleShortName: ReadonlyMap<EmojiParentKey, string>;
localeShortNameToParentKey: ReadonlyMap<string, EmojiParentKey>;
}>;
export type FunEmojiLocalizer = Readonly<{
getLocaleShortName: (key: EmojiVariantKey) => string;
getParentKeyForText: (text: string) => EmojiParentKey | null;
}>;
export function createFunEmojiLocalizerIndex( export function createFunEmojiLocalizerIndex(
localeEmojiList: LocaleEmojiListType localeEmojiList: LocaleEmojiListType
): FunEmojiLocalizerIndex { ): FunEmojiLocalizerIndex {
const index = new Map<EmojiParentKey, string>(); const parentKeyToLocaleShortName = new Map<EmojiParentKey, string>();
const localeShortNameToParentKey = new Map<string, EmojiParentKey>();
for (const entry of localeEmojiList) { for (const entry of localeEmojiList) {
strictAssert( strictAssert(
@ -29,26 +37,35 @@ export function createFunEmojiLocalizerIndex(
const variantKey = getEmojiVariantKeyByValue(entry.emoji); const variantKey = getEmojiVariantKeyByValue(entry.emoji);
const parentKey = getEmojiParentKeyByVariantKey(variantKey); const parentKey = getEmojiParentKeyByVariantKey(variantKey);
const localizedShortName = entry.tags.at(0) ?? entry.shortName; const localizedShortName = entry.tags.at(0) ?? entry.shortName;
index.set(parentKey, localizedShortName); parentKeyToLocaleShortName.set(parentKey, localizedShortName);
localeShortNameToParentKey.set(localizedShortName, parentKey);
} }
return index; return { parentKeyToLocaleShortName, localeShortNameToParentKey };
} }
/** @internal exported for tests */ /** @internal exported for tests */
export function _createFunEmojiLocalizer( export function _createFunEmojiLocalizer(
emojiLocalizerIndex: FunEmojiLocalizerIndex emojiLocalizerIndex: FunEmojiLocalizerIndex
): FunEmojiLocalizer { ): FunEmojiLocalizer {
return variantKey => { function getLocaleShortName(variantKey: EmojiVariantKey) {
const parentKey = getEmojiParentKeyByVariantKey(variantKey); const parentKey = getEmojiParentKeyByVariantKey(variantKey);
const localeShortName = emojiLocalizerIndex.get(parentKey); const localeShortName =
emojiLocalizerIndex.parentKeyToLocaleShortName.get(parentKey);
if (localeShortName != null) { if (localeShortName != null) {
return localeShortName; return localeShortName;
} }
// Fallback to english short name // Fallback to english short name
const parent = getEmojiParentByKey(parentKey); const parent = getEmojiParentByKey(parentKey);
return parent.englishShortNameDefault; return parent.englishShortNameDefault;
}; }
function getParentKeyForText(text: string): EmojiParentKey | null {
const parentKey = emojiLocalizerIndex.localeShortNameToParentKey.get(text);
return parentKey ?? null;
}
return { getLocaleShortName, getParentKeyForText };
} }
export function useFunEmojiLocalizer(): FunEmojiLocalizer { export function useFunEmojiLocalizer(): FunEmojiLocalizer {

View file

@ -6,12 +6,11 @@ import Emitter from '@signalapp/quill-cjs/core/emitter';
import React from 'react'; import React from 'react';
import _, { isNumber } from 'lodash'; import _, { isNumber } from 'lodash';
import type Quill from '@signalapp/quill-cjs'; import type Quill from '@signalapp/quill-cjs';
import { Popper } from 'react-popper'; import { Popper } from 'react-popper';
import classNames from 'classnames'; import classNames from 'classnames';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import type { VirtualElement } from '@popperjs/core'; import type { VirtualElement } from '@popperjs/core';
import { convertShortName, isShortName } from '../../components/emoji/lib'; import { convertShortName } from '../../components/emoji/lib';
import type { EmojiPickDataType } from '../../components/emoji/EmojiPicker'; import type { EmojiPickDataType } from '../../components/emoji/EmojiPicker';
import { getBlotTextPartitions, matchBlotTextPartitions } from '../util'; import { getBlotTextPartitions, matchBlotTextPartitions } from '../util';
import { handleOutsideClick } from '../../util/handleOutsideClick'; import { handleOutsideClick } from '../../util/handleOutsideClick';
@ -23,8 +22,10 @@ import {
getEmojiVariantByParentKeyAndSkinTone, getEmojiVariantByParentKeyAndSkinTone,
normalizeShortNameCompletionDisplay, normalizeShortNameCompletionDisplay,
} from '../../components/fun/data/emojis'; } from '../../components/fun/data/emojis';
import type { FunEmojiSearchResult } from '../../components/fun/useFunEmojiSearch'; import type {
import { type FunEmojiSearch } from '../../components/fun/useFunEmojiSearch'; FunEmojiSearchResult,
FunEmojiSearch,
} from '../../components/fun/useFunEmojiSearch';
import { type FunEmojiLocalizer } from '../../components/fun/useFunEmojiLocalizer'; import { type FunEmojiLocalizer } from '../../components/fun/useFunEmojiLocalizer';
export type EmojiCompletionOptions = { export type EmojiCompletionOptions = {
@ -164,11 +165,14 @@ export class EmojiCompletion {
const [, leftTokenText, isSelfClosing] = leftTokenTextMatch; const [, leftTokenText, isSelfClosing] = leftTokenTextMatch;
if (isSelfClosing || justPressedColon) { if (isSelfClosing || justPressedColon) {
if (isShortName(leftTokenText)) { const parentKey =
this.options.emojiLocalizer.getParentKeyForText(leftTokenText);
if (parentKey != null) {
const numberOfColons = isSelfClosing ? 2 : 1; const numberOfColons = isSelfClosing ? 2 : 1;
const emoji = getEmojiParentByKey(parentKey);
this.insertEmoji({ this.insertEmoji({
shortName: leftTokenText, shortName: emoji.englishShortNameDefault,
index: range.index - leftTokenText.length - numberOfColons, index: range.index - leftTokenText.length - numberOfColons,
range: leftTokenText.length + numberOfColons, range: leftTokenText.length + numberOfColons,
justPressedColon, justPressedColon,
@ -182,10 +186,13 @@ export class EmojiCompletion {
if (rightTokenTextMatch) { if (rightTokenTextMatch) {
const [, rightTokenText] = rightTokenTextMatch; const [, rightTokenText] = rightTokenTextMatch;
const tokenText = leftTokenText + rightTokenText; const tokenText = leftTokenText + rightTokenText;
const parentKey =
this.options.emojiLocalizer.getParentKeyForText(tokenText);
if (isShortName(tokenText)) { if (parentKey != null) {
const emoji = getEmojiParentByKey(parentKey);
this.insertEmoji({ this.insertEmoji({
shortName: tokenText, shortName: emoji.englishShortNameDefault,
index: range.index - leftTokenText.length - 1, index: range.index - leftTokenText.length - 1,
range: tokenText.length + 2, range: tokenText.length + 2,
justPressedColon, justPressedColon,
@ -377,9 +384,10 @@ export class EmojiCompletion {
this.options.emojiSkinToneDefault ?? EmojiSkinTone.None this.options.emojiSkinToneDefault ?? EmojiSkinTone.None
); );
const localeShortName = this.options.emojiLocalizer( const localeShortName =
emojiVariant.key this.options.emojiLocalizer.getLocaleShortName(
); emojiVariant.key
);
const normalized = const normalized =
normalizeShortNameCompletionDisplay(localeShortName); normalizeShortNameCompletionDisplay(localeShortName);

View file

@ -3,13 +3,12 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import type { StateType } from '../reducer'; import type { StateType } from '../reducer';
import { isShortName } from '../../components/emoji/lib'; import { isEmojiEnglishShortName } from '../../components/fun/data/emojis';
export const selectRecentEmojis = createSelector( export const selectRecentEmojis = createSelector(
({ emojis }: StateType) => emojis.recents, ({ emojis }: StateType) => emojis.recents,
recents => recents.filter(isShortName) recents => recents.filter(isEmojiEnglishShortName)
); );
export const useRecentEmojis = (): Array<string> => export const useRecentEmojis = (): Array<string> =>