Update en.json
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
dfa5d3ec8e
commit
e6d6ac6d44
4 changed files with 126 additions and 198 deletions
|
@ -1,16 +1,11 @@
|
||||||
import { Placement } from '@popperjs/core';
|
import { Placement } from '@popperjs/core';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
|
||||||
import { usePopper } from 'react-popper';
|
import { usePopper } from 'react-popper';
|
||||||
|
|
||||||
import { changeSetting } from 'soapbox/actions/settings';
|
|
||||||
import { Emoji, HStack, IconButton } from 'soapbox/components/ui';
|
import { Emoji, HStack, IconButton } from 'soapbox/components/ui';
|
||||||
import { getFrequentlyUsedEmojis, messages } from 'soapbox/features/emoji/components/emoji-picker-dropdown';
|
import EmojiPickerDropdown from 'soapbox/features/emoji/components/emoji-picker-dropdown';
|
||||||
import { EmojiPicker as EmojiPickerAsync } from 'soapbox/features/ui/util/async-components';
|
import { useSoapboxConfig } from 'soapbox/hooks';
|
||||||
import { useAppDispatch, useAppSelector, useSettings, useSoapboxConfig } from 'soapbox/hooks';
|
|
||||||
|
|
||||||
let EmojiPicker: any; // load asynchronously
|
|
||||||
|
|
||||||
interface IEmojiButton {
|
interface IEmojiButton {
|
||||||
/** Unicode emoji character. */
|
/** Unicode emoji character. */
|
||||||
|
@ -64,28 +59,15 @@ const EmojiSelector: React.FC<IEmojiSelector> = ({
|
||||||
offset = [-10, 0],
|
offset = [-10, 0],
|
||||||
all = true,
|
all = true,
|
||||||
}): JSX.Element => {
|
}): JSX.Element => {
|
||||||
const intl = useIntl();
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const frequentlyUsedEmojis = useAppSelector(state => getFrequentlyUsedEmojis(state));
|
|
||||||
const settings = useSettings();
|
|
||||||
const userTheme = settings.get('themeMode');
|
|
||||||
const theme = (userTheme === 'dark' || userTheme === 'light') ? userTheme : 'auto';
|
|
||||||
const soapboxConfig = useSoapboxConfig();
|
const soapboxConfig = useSoapboxConfig();
|
||||||
|
|
||||||
const title = intl.formatMessage(messages.emoji);
|
|
||||||
|
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
// `useRef` won't trigger a re-render, while `useState` does.
|
// `useRef` won't trigger a re-render, while `useState` does.
|
||||||
// https://popper.js.org/react-popper/v2/
|
// https://popper.js.org/react-popper/v2/
|
||||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const onSkinTone = (skinTone: string) => {
|
|
||||||
dispatch(changeSetting(['skinTone'], skinTone));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
if ([referenceElement, popperElement].some(el => el?.contains(event.target as Node))) {
|
if ([referenceElement, popperElement].some(el => el?.contains(event.target as Node))) {
|
||||||
return;
|
return;
|
||||||
|
@ -116,38 +98,6 @@ const EmojiSelector: React.FC<IEmojiSelector> = ({
|
||||||
setExpanded(true);
|
setExpanded(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getI18n = () => {
|
|
||||||
return {
|
|
||||||
search: intl.formatMessage(messages.emoji_search),
|
|
||||||
pick: intl.formatMessage(messages.emoji_pick),
|
|
||||||
search_no_results_1: intl.formatMessage(messages.emoji_oh_no),
|
|
||||||
search_no_results_2: intl.formatMessage(messages.emoji_not_found),
|
|
||||||
add_custom: intl.formatMessage(messages.emoji_add_custom),
|
|
||||||
categories: {
|
|
||||||
search: intl.formatMessage(messages.search_results),
|
|
||||||
frequent: intl.formatMessage(messages.recent),
|
|
||||||
people: intl.formatMessage(messages.people),
|
|
||||||
nature: intl.formatMessage(messages.nature),
|
|
||||||
foods: intl.formatMessage(messages.food),
|
|
||||||
activity: intl.formatMessage(messages.activity),
|
|
||||||
places: intl.formatMessage(messages.travel),
|
|
||||||
objects: intl.formatMessage(messages.objects),
|
|
||||||
symbols: intl.formatMessage(messages.symbols),
|
|
||||||
flags: intl.formatMessage(messages.flags),
|
|
||||||
custom: intl.formatMessage(messages.custom),
|
|
||||||
},
|
|
||||||
skins: {
|
|
||||||
choose: intl.formatMessage(messages.skins_choose),
|
|
||||||
1: intl.formatMessage(messages.skins_1),
|
|
||||||
2: intl.formatMessage(messages.skins_2),
|
|
||||||
3: intl.formatMessage(messages.skins_3),
|
|
||||||
4: intl.formatMessage(messages.skins_4),
|
|
||||||
5: intl.formatMessage(messages.skins_5),
|
|
||||||
6: intl.formatMessage(messages.skins_6),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => () => {
|
useEffect(() => () => {
|
||||||
document.body.style.overflow = '';
|
document.body.style.overflow = '';
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -176,26 +126,6 @@ const EmojiSelector: React.FC<IEmojiSelector> = ({
|
||||||
}
|
}
|
||||||
}, [expanded, update]);
|
}, [expanded, update]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// fix scrolling focus issue
|
|
||||||
if (visible && expanded) {
|
|
||||||
document.body.style.overflow = 'hidden';
|
|
||||||
} else {
|
|
||||||
document.body.style.overflow = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!EmojiPicker) {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
EmojiPickerAsync().then(EmojiMart => {
|
|
||||||
EmojiPicker = EmojiMart.Picker;
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
}).catch(() => {
|
|
||||||
setLoading(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [visible, expanded]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -207,17 +137,10 @@ const EmojiSelector: React.FC<IEmojiSelector> = ({
|
||||||
{...attributes.popper}
|
{...attributes.popper}
|
||||||
>
|
>
|
||||||
{expanded ? (
|
{expanded ? (
|
||||||
!loading && <EmojiPicker
|
<EmojiPickerDropdown
|
||||||
title={title}
|
visible={expanded}
|
||||||
onEmojiSelect={(emoji: any) => onReact(emoji.native)}
|
setVisible={setExpanded}
|
||||||
recent={frequentlyUsedEmojis}
|
update={update}
|
||||||
perLine={8}
|
|
||||||
skin={onSkinTone}
|
|
||||||
emojiSize={22}
|
|
||||||
emojiButtonSize={34}
|
|
||||||
set='twitter'
|
|
||||||
theme={theme}
|
|
||||||
i18n={getI18n()}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<HStack
|
<HStack
|
||||||
|
|
|
@ -1,20 +1,17 @@
|
||||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
|
||||||
import { Map as ImmutableMap } from 'immutable';
|
import { Map as ImmutableMap } from 'immutable';
|
||||||
import React, { useEffect, useState, useLayoutEffect } from 'react';
|
import React, { useEffect, useState, useLayoutEffect } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { usePopper } from 'react-popper';
|
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import { useEmoji } from 'soapbox/actions/emojis';
|
import { useEmoji } from 'soapbox/actions/emojis';
|
||||||
import { changeSetting } from 'soapbox/actions/settings';
|
import { changeSetting } from 'soapbox/actions/settings';
|
||||||
import { useAppDispatch, useAppSelector, useSettings } from 'soapbox/hooks';
|
import { useAppDispatch, useAppSelector, useSettings } from 'soapbox/hooks';
|
||||||
import { isMobile } from 'soapbox/is-mobile';
|
|
||||||
import { RootState } from 'soapbox/store';
|
import { RootState } from 'soapbox/store';
|
||||||
|
|
||||||
import { buildCustomEmojis } from '../../emoji';
|
import { buildCustomEmojis } from '../../emoji';
|
||||||
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
|
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
|
||||||
|
|
||||||
|
import type { State as PopperState } from '@popperjs/core';
|
||||||
import type { Emoji, CustomEmoji, NativeEmoji } from 'soapbox/features/emoji';
|
import type { Emoji, CustomEmoji, NativeEmoji } from 'soapbox/features/emoji';
|
||||||
|
|
||||||
let EmojiPicker: any; // load asynchronously
|
let EmojiPicker: any; // load asynchronously
|
||||||
|
@ -49,13 +46,9 @@ export const messages = defineMessages({
|
||||||
export interface IEmojiPickerDropdown {
|
export interface IEmojiPickerDropdown {
|
||||||
onPickEmoji?: (emoji: Emoji) => void
|
onPickEmoji?: (emoji: Emoji) => void
|
||||||
condensed?: boolean
|
condensed?: boolean
|
||||||
render: React.FC<{
|
visible: boolean
|
||||||
setPopperReference: React.Ref<HTMLButtonElement>
|
setVisible: (value: boolean) => void
|
||||||
title?: string
|
update: (() => Promise<Partial<PopperState>>) | null
|
||||||
visible?: boolean
|
|
||||||
loading?: boolean
|
|
||||||
handleToggle: (e: Event) => void
|
|
||||||
}>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const perLine = 8;
|
const perLine = 8;
|
||||||
|
@ -132,9 +125,9 @@ const RenderAfter = ({ children, update }: any) => {
|
||||||
return nextTick ? children : null;
|
return nextTick ? children : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({
|
||||||
|
onPickEmoji, condensed, visible, setVisible, update,
|
||||||
const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({ onPickEmoji, condensed, render: Render }) => {
|
}) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
|
@ -145,29 +138,8 @@ const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({ onPickEmoji, cond
|
||||||
const customEmojis = useAppSelector((state) => getCustomEmojis(state));
|
const customEmojis = useAppSelector((state) => getCustomEmojis(state));
|
||||||
const frequentlyUsedEmojis = useAppSelector((state) => getFrequentlyUsedEmojis(state));
|
const frequentlyUsedEmojis = useAppSelector((state) => getFrequentlyUsedEmojis(state));
|
||||||
|
|
||||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
|
||||||
const [popperReference, setPopperReference] = useState<HTMLButtonElement | null>(null);
|
|
||||||
const [containerElement, setContainerElement] = useState<HTMLDivElement | null>(null);
|
|
||||||
|
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const placement = condensed ? 'bottom-start' : 'top-start';
|
|
||||||
const { styles, attributes, update } = usePopper(popperReference, popperElement, {
|
|
||||||
placement: isMobile(window.innerWidth) ? 'auto' : placement,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleToggle = (e: Event) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setVisible(!visible);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDocClick = (e: any) => {
|
|
||||||
if (!containerElement?.contains(e.target) && !popperElement?.contains(e.target)) {
|
|
||||||
setVisible(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePick = (emoji: any) => {
|
const handlePick = (emoji: any) => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
|
|
||||||
|
@ -233,17 +205,6 @@ const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({ onPickEmoji, cond
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.addEventListener('click', handleDocClick, false);
|
|
||||||
document.addEventListener('touchend', handleDocClick, listenerOptions);
|
|
||||||
|
|
||||||
return function cleanup() {
|
|
||||||
document.removeEventListener('click', handleDocClick, false);
|
|
||||||
// @ts-ignore
|
|
||||||
document.removeEventListener('touchend', handleDocClick, listenerOptions);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// fix scrolling focus issue
|
// fix scrolling focus issue
|
||||||
if (visible) {
|
if (visible) {
|
||||||
|
@ -265,29 +226,8 @@ const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({ onPickEmoji, cond
|
||||||
}
|
}
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
// TODO: move to class
|
|
||||||
const style: React.CSSProperties = !isMobile(window.innerWidth) ? styles.popper : {
|
|
||||||
...styles.popper, width: '100%',
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='relative' ref={setContainerElement}>
|
visible ? (
|
||||||
<Render
|
|
||||||
handleToggle={handleToggle}
|
|
||||||
visible={visible}
|
|
||||||
loading={loading}
|
|
||||||
title={title}
|
|
||||||
setPopperReference={setPopperReference}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{createPortal(
|
|
||||||
<div
|
|
||||||
className='z-[101]'
|
|
||||||
ref={setPopperElement}
|
|
||||||
style={style}
|
|
||||||
{...attributes.popper}
|
|
||||||
>
|
|
||||||
{visible && (
|
|
||||||
<RenderAfter update={update}>
|
<RenderAfter update={update}>
|
||||||
{!loading && (
|
{!loading && (
|
||||||
<EmojiPicker
|
<EmojiPicker
|
||||||
|
@ -305,11 +245,7 @@ const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({ onPickEmoji, cond
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</RenderAfter>
|
</RenderAfter>
|
||||||
)}
|
) : null
|
||||||
</div>,
|
|
||||||
document.body,
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,70 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React from 'react';
|
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||||
|
import React, { KeyboardEvent, useEffect, useState } from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import { usePopper } from 'react-popper';
|
||||||
|
|
||||||
import { IconButton } from 'soapbox/components/ui';
|
import { IconButton } from 'soapbox/components/ui';
|
||||||
|
import { isMobile } from 'soapbox/is-mobile';
|
||||||
|
|
||||||
import EmojiPickerDropdown, { IEmojiPickerDropdown } from '../components/emoji-picker-dropdown';
|
import EmojiPickerDropdown, { IEmojiPickerDropdown } from '../components/emoji-picker-dropdown';
|
||||||
|
|
||||||
const EmojiPickerDropdownWrapper = (props: Omit<IEmojiPickerDropdown, 'render'>) => {
|
const listenerOptions = supportsPassiveEvents ? { passive: true } : false;
|
||||||
|
|
||||||
|
export const messages = defineMessages({
|
||||||
|
emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const EmojiPickerDropdownWrapper = (
|
||||||
|
props: Pick<IEmojiPickerDropdown, 'onPickEmoji' | 'condensed'>,
|
||||||
|
) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const title = intl.formatMessage(messages.emoji);
|
||||||
|
|
||||||
|
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||||
|
const [popperReference, setPopperReference] = useState<HTMLButtonElement | null>(null);
|
||||||
|
const [containerElement, setContainerElement] = useState<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
|
const placement = props.condensed ? 'bottom-start' : 'top-start';
|
||||||
|
const { styles, attributes, update } = usePopper(popperReference, popperElement, {
|
||||||
|
placement: isMobile(window.innerWidth) ? 'auto' : placement,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleDocClick = (e: any) => {
|
||||||
|
if (!containerElement?.contains(e.target) && !popperElement?.contains(e.target)) {
|
||||||
|
setVisible(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToggle = (e: MouseEvent | KeyboardEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setVisible(!visible);
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: move to class
|
||||||
|
const style: React.CSSProperties = !isMobile(window.innerWidth) ? styles.popper : {
|
||||||
|
...styles.popper, width: '100%',
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener('click', handleDocClick, false);
|
||||||
|
document.addEventListener('touchend', handleDocClick, listenerOptions);
|
||||||
|
|
||||||
|
return function cleanup() {
|
||||||
|
document.removeEventListener('click', handleDocClick, false);
|
||||||
|
// @ts-ignore
|
||||||
|
document.removeEventListener('touchend', handleDocClick, listenerOptions);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EmojiPickerDropdown
|
<div className='relative' ref={setContainerElement}>
|
||||||
render={
|
|
||||||
({ setPopperReference, title, visible, loading, handleToggle }: any) => (
|
|
||||||
<IconButton
|
<IconButton
|
||||||
className={clsx({
|
className={clsx({
|
||||||
'text-gray-400 hover:text-gray-600': true,
|
'text-gray-400 hover:text-gray-600': true,
|
||||||
'pulse-loading': visible && loading,
|
|
||||||
})}
|
})}
|
||||||
ref={setPopperReference}
|
ref={setPopperReference}
|
||||||
src={require('@tabler/icons/mood-happy.svg')}
|
src={require('@tabler/icons/mood-happy.svg')}
|
||||||
|
@ -21,15 +72,23 @@ const EmojiPickerDropdownWrapper = (props: Omit<IEmojiPickerDropdown, 'render'>)
|
||||||
aria-label={title}
|
aria-label={title}
|
||||||
aria-expanded={visible}
|
aria-expanded={visible}
|
||||||
role='button'
|
role='button'
|
||||||
onClick={handleToggle}
|
onClick={handleToggle as any}
|
||||||
onKeyDown={handleToggle}
|
onKeyDown={handleToggle as React.KeyboardEventHandler<HTMLButtonElement>}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
/>
|
/>
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
{...props}
|
{createPortal(
|
||||||
/>
|
<div
|
||||||
|
className='z-[101]'
|
||||||
|
ref={setPopperElement}
|
||||||
|
style={style}
|
||||||
|
{...attributes.popper}
|
||||||
|
>
|
||||||
|
<EmojiPickerDropdown visible={visible} setVisible={setVisible} update={update} {...props} />
|
||||||
|
</div>,
|
||||||
|
document.body,
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -620,6 +620,7 @@
|
||||||
"email_verifilcation.exists": "This email has already been taken.",
|
"email_verifilcation.exists": "This email has already been taken.",
|
||||||
"embed.instructions": "Embed this post on your website by copying the code below.",
|
"embed.instructions": "Embed this post on your website by copying the code below.",
|
||||||
"emoji_button.activity": "Activity",
|
"emoji_button.activity": "Activity",
|
||||||
|
"emoji_button.add_custom": "Add custom emoji",
|
||||||
"emoji_button.custom": "Custom",
|
"emoji_button.custom": "Custom",
|
||||||
"emoji_button.flags": "Flags",
|
"emoji_button.flags": "Flags",
|
||||||
"emoji_button.food": "Food & Drink",
|
"emoji_button.food": "Food & Drink",
|
||||||
|
@ -627,7 +628,16 @@
|
||||||
"emoji_button.nature": "Nature",
|
"emoji_button.nature": "Nature",
|
||||||
"emoji_button.not_found": "No emojis found.",
|
"emoji_button.not_found": "No emojis found.",
|
||||||
"emoji_button.objects": "Objects",
|
"emoji_button.objects": "Objects",
|
||||||
|
"emoji_button.oh_no": "Oh no!",
|
||||||
"emoji_button.people": "People",
|
"emoji_button.people": "People",
|
||||||
|
"emoji_button.pick": "Pick an emoji…",
|
||||||
|
"emoji_button.skins_1": "Default",
|
||||||
|
"emoji_button.skins_2": "Light",
|
||||||
|
"emoji_button.skins_3": "Medium-Light",
|
||||||
|
"emoji_button.skins_4": "Medium",
|
||||||
|
"emoji_button.skins_5": "Medium-Dark",
|
||||||
|
"emoji_button.skins_6": "Dark",
|
||||||
|
"emoji_button.skins_choose": "Choose default skin tone",
|
||||||
"emoji_button.recent": "Frequently used",
|
"emoji_button.recent": "Frequently used",
|
||||||
"emoji_button.search": "Search…",
|
"emoji_button.search": "Search…",
|
||||||
"emoji_button.search_results": "Search results",
|
"emoji_button.search_results": "Search results",
|
||||||
|
|
Loading…
Reference in a new issue