Translate emoji completions in composer

This commit is contained in:
Jamie Kyle
2025-04-23 16:03:35 -07:00
committed by GitHub
parent 6a20d91b71
commit e802ea0dc7
12 changed files with 216 additions and 273 deletions

View File

@@ -8,6 +8,7 @@ import {
getEmojiParentByKey,
getEmojiParentKeyByValue,
isEmojiParentValue,
normalizeShortNameCompletionQuery,
} from './data/emojis';
import type { LocaleEmojiListType } from '../../types/emoji';
import { useFunEmojiLocalization } from './FunEmojiLocalizationProvider';
@@ -23,10 +24,14 @@ export type FunEmojiSearchIndexEntry = Readonly<{
export type FunEmojiSearchIndex = ReadonlyArray<FunEmojiSearchIndexEntry>;
export type FunEmojiSearchResult = Readonly<{
parentKey: EmojiParentKey;
}>;
export type FunEmojiSearch = (
query: string,
limit?: number
) => ReadonlyArray<EmojiParentKey>;
) => ReadonlyArray<FunEmojiSearchResult>;
export function createFunEmojiSearchIndex(
localeEmojiList: LocaleEmojiListType
@@ -44,8 +49,10 @@ export function createFunEmojiSearchIndex(
results.push({
key: parentKey,
rank: localeEmoji.rank,
shortName: localeEmoji.shortName,
shortNames: localeEmoji.tags,
shortName: normalizeShortNameCompletionQuery(localeEmoji.shortName),
shortNames: localeEmoji.tags.map(tag => {
return normalizeShortNameCompletionQuery(tag);
}),
emoticon: emoji.emoticonDefault,
emoticons: emoji.emoticons,
});
@@ -67,6 +74,7 @@ const FuseFuzzyOptions: Fuse.IFuseOptions<FunEmojiSearchIndexEntry> = {
minMatchCharLength: 1,
keys: FuseKeys,
includeScore: true,
includeMatches: true,
};
const FuseExactOptions: Fuse.IFuseOptions<FunEmojiSearchIndexEntry> = {
@@ -75,49 +83,48 @@ const FuseExactOptions: Fuse.IFuseOptions<FunEmojiSearchIndexEntry> = {
minMatchCharLength: 1,
keys: FuseKeys,
includeScore: true,
includeMatches: true,
};
function createFunEmojiSearch(
/** @internal exported for tests */
export function _createFunEmojiSearch(
emojiSearchIndex: FunEmojiSearchIndex
): FunEmojiSearch {
const fuseIndex = Fuse.createIndex(FuseKeys, emojiSearchIndex);
const fuseFuzzy = new Fuse(emojiSearchIndex, FuseFuzzyOptions, fuseIndex);
const fuseExact = new Fuse(emojiSearchIndex, FuseExactOptions, fuseIndex);
return function emojiSearch(query, limit = 200) {
return function emojiSearch(rawQuery, limit = 200) {
const query = normalizeShortNameCompletionQuery(rawQuery);
// Prefer exact matches at 2 characters
const fuse = query.length < 2 ? fuseExact : fuseFuzzy;
const rawResults = fuse.search(query.substring(0, 32), {
limit: limit * 2,
});
const rawResults = fuse.search(query.substring(0, 32));
const rankedResults = rawResults.map(result => {
// Note: lodash's sortBy() only calls each iteratee once
const sortedResults = sortBy(rawResults, result => {
const rank = result.item.rank ?? 1e9;
const localizedQueryMatch =
result.item.shortNames.at(0) ?? result.item.shortName;
// Exact prefix matches in [0,1] range
if (result.item.shortName.startsWith(query)) {
return {
score: result.item.shortName.length / query.length,
item: result.item,
};
if (localizedQueryMatch.startsWith(query)) {
// Note: localizedQueryMatch will always be <= in length to the query
const matchRatio = query.length / localizedQueryMatch.length; // 1-0
return 1 - matchRatio;
}
const queryScore = result.score ?? 0; // 0-1
const rankScore = rank / emojiSearchIndex.length; // 0-1
// Other matches in [1,], ordered by score and rank
return {
score: 1 + (result.score ?? 0) + rank / emojiSearchIndex.length,
item: result.item,
};
return 1 + queryScore + rankScore;
});
const sortedResults = sortBy(rankedResults, result => {
return result.score;
});
const truncatedResults = sortedResults.slice(0, limit);
return truncatedResults.map(result => {
return result.item.key;
return sortedResults.slice(0, limit).map(result => {
return { parentKey: result.item.key };
});
};
}
@@ -125,7 +132,7 @@ function createFunEmojiSearch(
export function useFunEmojiSearch(): FunEmojiSearch {
const { emojiSearchIndex } = useFunEmojiLocalization();
const emojiSearch = useMemo(() => {
return createFunEmojiSearch(emojiSearchIndex);
return _createFunEmojiSearch(emojiSearchIndex);
}, [emojiSearchIndex]);
return emojiSearch;
}