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">
<FunStaticEmoji
role="img"
aria-label={emojiLocalizer(emojiVariantKey)}
aria-label={emojiLocalizer.getLocaleShortName(emojiVariantKey)}
size={28}
emoji={emojiVariant}
/>

View file

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

View file

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

View file

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

View file

@ -3,15 +3,7 @@
// Camelcase disabled due to emoji-datasource using snake_case
/* eslint-disable camelcase */
import {
compact,
flatMap,
groupBy,
keyBy,
map,
mapValues,
sortBy,
} from 'lodash';
import { groupBy, keyBy, mapValues, sortBy } from 'lodash';
import { getOwn } from '../../util/getOwn';
import {
EMOJI_SKIN_TONE_TO_KEY,
@ -152,15 +144,6 @@ export function getEmojiData(
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 {
return unified
.split('-')

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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