diff --git a/app/soapbox/actions/compose.ts b/app/soapbox/actions/compose.ts index 9816135e5..1b0959a0f 100644 --- a/app/soapbox/actions/compose.ts +++ b/app/soapbox/actions/compose.ts @@ -4,7 +4,8 @@ import throttle from 'lodash/throttle'; import { defineMessages, IntlShape } from 'react-intl'; import api from 'soapbox/api'; -import { search as emojiSearch } from 'soapbox/features/emoji/emoji-mart-search-light'; +import { isNativeEmoji } from 'soapbox/features/emoji'; +import emojiSearch from 'soapbox/features/emoji/search'; import { tagHistory } from 'soapbox/settings'; import toast from 'soapbox/toast'; import { isLoggedIn } from 'soapbox/utils/auth'; @@ -20,8 +21,8 @@ import { getSettings } from './settings'; import { createStatus } from './statuses'; import type { EditorState } from 'lexical'; -import type { Emoji } from 'soapbox/components/autosuggest-emoji'; import type { AutoSuggestion } from 'soapbox/components/autosuggest-input'; +import type { Emoji } from 'soapbox/features/emoji'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { Account, APIEntity, Status, Tag } from 'soapbox/types/entities'; import type { History } from 'soapbox/types/history'; @@ -522,7 +523,9 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, composeId, }, 200, { leading: true, trailing: true }); const fetchComposeSuggestionsEmojis = (dispatch: AppDispatch, getState: () => RootState, composeId: string, token: string) => { - const results = emojiSearch(token.replace(':', ''), { maxResults: 5 } as any); + const state = getState(); + const results = emojiSearch(token.replace(':', ''), { maxResults: 5 }, state.custom_emojis); + dispatch(readyComposeSuggestionsEmojis(composeId, token, results)); }; @@ -567,7 +570,7 @@ const selectComposeSuggestion = (composeId: string, position: number, token: str let completion, startPosition; if (typeof suggestion === 'object' && suggestion.id) { - completion = suggestion.native || suggestion.colons; + completion = isNativeEmoji(suggestion) ? suggestion.native : suggestion.colons; startPosition = position - 1; dispatch(useEmoji(suggestion)); diff --git a/app/soapbox/actions/emojis.ts b/app/soapbox/actions/emojis.ts index 46f591b06..f3d33ac61 100644 --- a/app/soapbox/actions/emojis.ts +++ b/app/soapbox/actions/emojis.ts @@ -1,6 +1,6 @@ import { saveSettings } from './settings'; -import type { Emoji } from 'soapbox/components/autosuggest-emoji'; +import type { Emoji } from 'soapbox/features/emoji'; import type { AppDispatch } from 'soapbox/store'; const EMOJI_USE = 'EMOJI_USE'; diff --git a/app/soapbox/actions/events.ts b/app/soapbox/actions/events.ts index d4ec49491..44a9ae207 100644 --- a/app/soapbox/actions/events.ts +++ b/app/soapbox/actions/events.ts @@ -569,7 +569,7 @@ const rejectEventParticipationRequestFail = (id: string, accountId: string, erro }); const fetchEventIcs = (id: string) => - (dispatch: any, getState: () => RootState) => + (dispatch: AppDispatch, getState: () => RootState) => api(getState).get(`/api/v1/pleroma/events/${id}/ics`); const cancelEventCompose = () => ({ diff --git a/app/soapbox/components/announcements/emoji.tsx b/app/soapbox/components/announcements/emoji.tsx index ecc28fcf8..0059e02b7 100644 --- a/app/soapbox/components/announcements/emoji.tsx +++ b/app/soapbox/components/announcements/emoji.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import unicodeMapping from 'soapbox/features/emoji/emoji-unicode-mapping-light'; +import unicodeMapping from 'soapbox/features/emoji/mapping'; import { useSettings } from 'soapbox/hooks'; import { joinPublicPath } from 'soapbox/utils/static'; diff --git a/app/soapbox/components/announcements/reaction.tsx b/app/soapbox/components/announcements/reaction.tsx index c5ea60212..0d7bd973f 100644 --- a/app/soapbox/components/announcements/reaction.tsx +++ b/app/soapbox/components/announcements/reaction.tsx @@ -2,7 +2,7 @@ import clsx from 'clsx'; import React, { useState } from 'react'; import AnimatedNumber from 'soapbox/components/animated-number'; -import unicodeMapping from 'soapbox/features/emoji/emoji-unicode-mapping-light'; +import unicodeMapping from 'soapbox/features/emoji/mapping'; import Emoji from './emoji'; diff --git a/app/soapbox/components/announcements/reactions-bar.tsx b/app/soapbox/components/announcements/reactions-bar.tsx index ebe651056..55b72c59a 100644 --- a/app/soapbox/components/announcements/reactions-bar.tsx +++ b/app/soapbox/components/announcements/reactions-bar.tsx @@ -2,14 +2,13 @@ import clsx from 'clsx'; import React from 'react'; import { TransitionMotion, spring } from 'react-motion'; -import { Icon } from 'soapbox/components/ui'; -import EmojiPickerDropdown from 'soapbox/features/compose/components/emoji-picker/emoji-picker-dropdown'; +import EmojiPickerDropdown from 'soapbox/features/emoji/containers/emoji-picker-dropdown-container'; import { useSettings } from 'soapbox/hooks'; import Reaction from './reaction'; import type { List as ImmutableList, Map as ImmutableMap } from 'immutable'; -import type { Emoji } from 'soapbox/components/autosuggest-emoji'; +import type { Emoji, NativeEmoji } from 'soapbox/features/emoji'; import type { AnnouncementReaction } from 'soapbox/types/entities'; interface IReactionsBar { @@ -24,7 +23,7 @@ const ReactionsBar: React.FC = ({ announcementId, reactions, addR const reduceMotion = useSettings().get('reduceMotion'); const handleEmojiPick = (data: Emoji) => { - addReaction(announcementId, data.native.replace(/:/g, '')); + addReaction(announcementId, (data as NativeEmoji).native.replace(/:/g, '')); }; const willEnter = () => ({ scale: reduceMotion ? 1 : 0 }); @@ -55,7 +54,7 @@ const ReactionsBar: React.FC = ({ announcementId, reactions, addR /> ))} - {visibleReactions.size < 8 && } />} + {visibleReactions.size < 8 && } )} diff --git a/app/soapbox/components/autosuggest-emoji.tsx b/app/soapbox/components/autosuggest-emoji.tsx index 9893fa345..4f4471ecf 100644 --- a/app/soapbox/components/autosuggest-emoji.tsx +++ b/app/soapbox/components/autosuggest-emoji.tsx @@ -1,38 +1,30 @@ import React from 'react'; -import unicodeMapping from 'soapbox/features/emoji/emoji-unicode-mapping-light'; +import { isCustomEmoji } from 'soapbox/features/emoji'; +import unicodeMapping from 'soapbox/features/emoji/mapping'; import { joinPublicPath } from 'soapbox/utils/static'; -export type Emoji = { - id: string - custom: boolean - imageUrl: string - native: string - colons: string -} - -type UnicodeMapping = { - filename: string -} +import type { Emoji } from 'soapbox/features/emoji'; interface IAutosuggestEmoji { emoji: Emoji } const AutosuggestEmoji: React.FC = ({ emoji }) => { - let url; + let url, alt; - if (emoji.custom) { + if (isCustomEmoji(emoji)) { url = emoji.imageUrl; + alt = emoji.colons; } else { - // @ts-ignore - const mapping: UnicodeMapping = unicodeMapping[emoji.native] || unicodeMapping[emoji.native.replace(/\uFE0F$/, '')]; + const mapping = unicodeMapping[emoji.native] || unicodeMapping[emoji.native.replace(/\uFE0F$/, '')]; if (!mapping) { return null; } - url = joinPublicPath(`packs/emoji/${mapping.filename}.svg`); + url = joinPublicPath(`packs/emoji/${mapping.unified}.svg`); + alt = emoji.native; } return ( @@ -40,7 +32,7 @@ const AutosuggestEmoji: React.FC = ({ emoji }) => { {emoji.native {emoji.colons} diff --git a/app/soapbox/components/autosuggest-input.tsx b/app/soapbox/components/autosuggest-input.tsx index dba457ee8..074acfef4 100644 --- a/app/soapbox/components/autosuggest-input.tsx +++ b/app/soapbox/components/autosuggest-input.tsx @@ -3,7 +3,7 @@ import { List as ImmutableList } from 'immutable'; import React from 'react'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import AutosuggestEmoji, { Emoji } from 'soapbox/components/autosuggest-emoji'; +import AutosuggestEmoji from 'soapbox/components/autosuggest-emoji'; import Icon from 'soapbox/components/icon'; import { Input, Portal } from 'soapbox/components/ui'; import AutosuggestAccount from 'soapbox/features/compose/components/autosuggest-account'; @@ -12,6 +12,7 @@ import { textAtCursorMatchesToken } from 'soapbox/utils/suggestions'; import type { Menu, MenuItem } from 'soapbox/components/dropdown-menu'; import type { InputThemes } from 'soapbox/components/ui/input/input'; +import type { Emoji } from 'soapbox/features/emoji'; export type AutoSuggestion = string | Emoji; diff --git a/app/soapbox/components/autosuggest-textarea.tsx b/app/soapbox/components/autosuggest-textarea.tsx index e5ff7473f..762aee29a 100644 --- a/app/soapbox/components/autosuggest-textarea.tsx +++ b/app/soapbox/components/autosuggest-textarea.tsx @@ -4,14 +4,14 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import Textarea from 'react-textarea-autosize'; import { Portal } from 'soapbox/components/ui'; +import AutosuggestAccount from 'soapbox/features/compose/components/autosuggest-account'; +import { isRtl } from 'soapbox/rtl'; import { textAtCursorMatchesToken } from 'soapbox/utils/suggestions'; -import AutosuggestAccount from '../features/compose/components/autosuggest-account'; -import { isRtl } from '../rtl'; - -import AutosuggestEmoji, { Emoji } from './autosuggest-emoji'; +import AutosuggestEmoji from './autosuggest-emoji'; import type { List as ImmutableList } from 'immutable'; +import type { Emoji } from 'soapbox/features/emoji'; interface IAutosuggesteTextarea { id?: string diff --git a/app/soapbox/components/group-card.tsx b/app/soapbox/components/group-card.tsx index 15d8cf497..a977e1ef0 100644 --- a/app/soapbox/components/group-card.tsx +++ b/app/soapbox/components/group-card.tsx @@ -17,43 +17,53 @@ const GroupCard: React.FC = ({ group }) => { const intl = useIntl(); return ( -
- -
- {group.header && {intl.formatMessage(messages.groupHeader)}} -
- -
-
- - - - {group.relationship?.role === 'admin' ? ( - - - - - ) : group.relationship?.role === 'moderator' && ( - - - - - )} - {group.locked ? ( - - - - - ) : ( - - - - - )} - - + + {/* Group Cover Image */} + + {group.header && ( + {intl.formatMessage(messages.groupHeader)} + )} -
+ + {/* Group Avatar */} +
+ +
+ + {/* Group Info */} + + + + + {group.relationship?.role === 'admin' ? ( + + + + + ) : group.relationship?.role === 'moderator' && ( + + + + + )} + + {group.locked ? ( + + + + + ) : ( + + + + + )} + + + ); }; diff --git a/app/soapbox/components/sidebar-menu.tsx b/app/soapbox/components/sidebar-menu.tsx index ffe3a0e98..0c68b2aff 100644 --- a/app/soapbox/components/sidebar-menu.tsx +++ b/app/soapbox/components/sidebar-menu.tsx @@ -10,7 +10,7 @@ import { closeSidebar } from 'soapbox/actions/sidebar'; import Account from 'soapbox/components/account'; import { Stack } from 'soapbox/components/ui'; import ProfileStats from 'soapbox/features/ui/components/profile-stats'; -import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector, useGroupsPath, useFeatures } from 'soapbox/hooks'; import { makeGetAccount, makeGetOtherAccounts } from 'soapbox/selectors'; import { Divider, HStack, Icon, IconButton, Text } from './ui'; @@ -90,6 +90,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { const sidebarOpen = useAppSelector((state) => state.sidebar.sidebarOpen); const settings = useAppSelector((state) => getSettings(state)); const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count()); + const groupsPath = useGroupsPath(); const closeButtonRef = React.useRef(null); @@ -210,7 +211,7 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { {features.groups && ( { const features = useFeatures(); const settings = useSettings(); const account = useOwnAccount(); + const groupsPath = useGroupsPath(); + const notificationCount = useAppSelector((state) => state.notifications.unread); const followRequestsCount = useAppSelector((state) => state.user_lists.follow_requests.items.count()); const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count()); @@ -135,7 +137,7 @@ const SidebarNavigation = () => { {features.groups && ( } /> diff --git a/app/soapbox/components/status-reaction-wrapper.tsx b/app/soapbox/components/status-reaction-wrapper.tsx index 224da7cda..b95d98375 100644 --- a/app/soapbox/components/status-reaction-wrapper.tsx +++ b/app/soapbox/components/status-reaction-wrapper.tsx @@ -112,6 +112,7 @@ const StatusReactionWrapper: React.FC = ({ statusId, chi referenceElement={referenceElement} onReact={handleReact} visible={visible} + onClose={() => setVisible(false)} /> )} diff --git a/app/soapbox/components/ui/carousel/carousel.tsx b/app/soapbox/components/ui/carousel/carousel.tsx new file mode 100644 index 000000000..ddb10b37a --- /dev/null +++ b/app/soapbox/components/ui/carousel/carousel.tsx @@ -0,0 +1,109 @@ +import React, { useEffect, useState } from 'react'; + +import { useDimensions } from 'soapbox/hooks'; + +import HStack from '../hstack/hstack'; +import Icon from '../icon/icon'; + +interface ICarousel { + children: any + /** Optional height to force on controls */ + controlsHeight?: number + /** How many items in the carousel */ + itemCount: number + /** The minimum width per item */ + itemWidth: number +} + +/** + * Carousel + */ +const Carousel: React.FC = (props): JSX.Element => { + const { children, controlsHeight, itemCount, itemWidth } = props; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_ref, setContainerRef, { width: containerWidth }] = useDimensions(); + + const [pageSize, setPageSize] = useState(0); + const [currentPage, setCurrentPage] = useState(1); + + const numberOfPages = Math.ceil(itemCount / pageSize); + const width = containerWidth / (Math.floor(containerWidth / itemWidth)); + + const hasNextPage = currentPage < numberOfPages && numberOfPages > 1; + const hasPrevPage = currentPage > 1 && numberOfPages > 1; + + const handleNextPage = () => setCurrentPage((prevPage) => prevPage + 1); + const handlePrevPage = () => setCurrentPage((prevPage) => prevPage - 1); + + const renderChildren = () => { + if (typeof children === 'function') { + return children({ width: width || 'auto' }); + } + + return children; + }; + + useEffect(() => { + if (containerWidth) { + setPageSize(Math.round(containerWidth / width)); + } + }, [containerWidth, width]); + + return ( + +
+ +
+ +
+ + {renderChildren()} + +
+ +
+ +
+
+ ); +}; + +export default Carousel; \ No newline at end of file diff --git a/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx b/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx index 5c4fa8dd4..961155cb3 100644 --- a/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx +++ b/app/soapbox/components/ui/emoji-selector/emoji-selector.tsx @@ -3,10 +3,12 @@ import clsx from 'clsx'; import React, { useEffect, useState } from 'react'; import { usePopper } from 'react-popper'; -import { Emoji, HStack, IconButton } from 'soapbox/components/ui'; -import { Picker } from 'soapbox/features/emoji/emoji-picker'; +import { Emoji as EmojiComponent, HStack, IconButton } from 'soapbox/components/ui'; +import EmojiPickerDropdown from 'soapbox/features/emoji/components/emoji-picker-dropdown'; import { useSoapboxConfig } from 'soapbox/hooks'; +import type { Emoji, NativeEmoji } from 'soapbox/features/emoji'; + interface IEmojiButton { /** Unicode emoji character. */ emoji: string @@ -29,7 +31,7 @@ const EmojiButton: React.FC = ({ emoji, className, onClick, tabInd return ( ); }; @@ -68,10 +70,16 @@ const EmojiSelector: React.FC = ({ const [popperElement, setPopperElement] = useState(null); const handleClickOutside = (event: MouseEvent) => { - if (referenceElement?.contains(event.target as Node) || popperElement?.contains(event.target as Node)) { + if ([referenceElement, popperElement, document.querySelector('em-emoji-picker')].some(el => el?.contains(event.target as Node))) { return; } + if (document.querySelector('em-emoji-picker')) { + event.preventDefault(); + event.stopPropagation(); + return setExpanded(false); + } + if (onClose) { onClose(); } @@ -93,6 +101,14 @@ const EmojiSelector: React.FC = ({ setExpanded(true); }; + const handlePickEmoji = (emoji: Emoji) => { + onReact((emoji as NativeEmoji).native); + }; + + useEffect(() => () => { + document.body.style.overflow = ''; + }, []); + useEffect(() => { setExpanded(false); }, [visible]); @@ -103,7 +119,7 @@ const EmojiSelector: React.FC = ({ return () => { document.removeEventListener('mousedown', handleClickOutside); }; - }, [referenceElement]); + }, [referenceElement, popperElement]); useEffect(() => { if (visible && update) { @@ -117,6 +133,7 @@ const EmojiSelector: React.FC = ({ } }, [expanded, update]); + return (
= ({ {...attributes.popper} > {expanded ? ( - require('emoji-datasource/img/twitter/sheets/32.png')} - onClick={(emoji: any) => onReact(emoji.native)} + ) : ( = ({ attachment, onOpenMedia }) => { } else if (attachment.type === 'audio') { const remoteURL = attachment.remote_url || ''; const fileExtensionLastIndex = remoteURL.lastIndexOf('.'); - const fileExtension = remoteURL.substr(fileExtensionLastIndex + 1).toUpperCase(); + const fileExtension = remoteURL.slice(fileExtensionLastIndex + 1).toUpperCase(); thumbnail = (
diff --git a/app/soapbox/features/chats/components/chat-composer.tsx b/app/soapbox/features/chats/components/chat-composer.tsx index c56eb1efe..11b22ba5d 100644 --- a/app/soapbox/features/chats/components/chat-composer.tsx +++ b/app/soapbox/features/chats/components/chat-composer.tsx @@ -6,13 +6,15 @@ import { openModal } from 'soapbox/actions/modals'; import { Button, Combobox, ComboboxInput, ComboboxList, ComboboxOption, ComboboxPopover, HStack, IconButton, Stack, Text } from 'soapbox/components/ui'; import { useChatContext } from 'soapbox/contexts/chat-context'; import UploadButton from 'soapbox/features/compose/components/upload-button'; -import { search as emojiSearch } from 'soapbox/features/emoji/emoji-mart-search-light'; +import emojiSearch from 'soapbox/features/emoji/search'; import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; import { Attachment } from 'soapbox/types/entities'; import { textAtCursorMatchesToken } from 'soapbox/utils/suggestions'; import ChatTextarea from './chat-textarea'; +import type { Emoji, NativeEmoji } from 'soapbox/features/emoji'; + const messages = defineMessages({ placeholder: { id: 'chat.input.placeholder', defaultMessage: 'Type a message' }, send: { id: 'chat.actions.send', defaultMessage: 'Send' }, @@ -31,7 +33,7 @@ const initialSuggestionState = { }; interface Suggestion { - list: { native: string, colons: string }[] + list: Emoji[] tokenStart: number token: string } @@ -45,7 +47,7 @@ interface IChatComposer extends Pick void - isUploading?: boolean + uploadCount?: number uploadProgress?: number } @@ -63,7 +65,7 @@ const ChatComposer = React.forwardRef onPaste, attachments = [], onDeleteAttachment, - isUploading, + uploadCount = 0, uploadProgress, }, ref) => { const intl = useIntl(); @@ -80,6 +82,7 @@ const ChatComposer = React.forwardRef const [suggestions, setSuggestions] = useState(initialSuggestionState); const isSuggestionsAvailable = suggestions.list.length > 0; + const isUploading = uploadCount > 0; const hasAttachment = attachments.length > 0; const isOverCharacterLimit = maxCharacterCount && value?.length > maxCharacterCount; const isSubmitDisabled = disabled || isUploading || isOverCharacterLimit || (value.length === 0 && !hasAttachment); @@ -198,7 +201,7 @@ const ChatComposer = React.forwardRef disabled={disabled} attachments={attachments} onDeleteAttachment={onDeleteAttachment} - isUploading={isUploading} + uploadCount={uploadCount} uploadProgress={uploadProgress} /> {isSuggestionsAvailable ? ( @@ -209,7 +212,7 @@ const ChatComposer = React.forwardRef key={emojiSuggestion.colons} value={renderSuggestionValue(emojiSuggestion)} > - {emojiSuggestion.native} + {(emojiSuggestion as NativeEmoji).native} {emojiSuggestion.colons} diff --git a/app/soapbox/features/chats/components/chat-message-reaction.tsx b/app/soapbox/features/chats/components/chat-message-reaction.tsx index 76c9fa0f8..9f7cd3e4f 100644 --- a/app/soapbox/features/chats/components/chat-message-reaction.tsx +++ b/app/soapbox/features/chats/components/chat-message-reaction.tsx @@ -2,7 +2,7 @@ import clsx from 'clsx'; import React from 'react'; import { Text } from 'soapbox/components/ui'; -import emojify from 'soapbox/features/emoji/emoji'; +import emojify from 'soapbox/features/emoji'; import { EmojiReaction } from 'soapbox/types/entities'; interface IChatMessageReaction { @@ -42,4 +42,4 @@ const ChatMessageReaction = (props: IChatMessageReaction) => { ); }; -export default ChatMessageReaction; \ No newline at end of file +export default ChatMessageReaction; diff --git a/app/soapbox/features/chats/components/chat-message.tsx b/app/soapbox/features/chats/components/chat-message.tsx index eccdaa6d7..05602a9af 100644 --- a/app/soapbox/features/chats/components/chat-message.tsx +++ b/app/soapbox/features/chats/components/chat-message.tsx @@ -9,7 +9,7 @@ import { openModal } from 'soapbox/actions/modals'; import { initReport } from 'soapbox/actions/reports'; import DropdownMenu from 'soapbox/components/dropdown-menu'; import { HStack, Icon, Stack, Text } from 'soapbox/components/ui'; -import emojify from 'soapbox/features/emoji/emoji'; +import emojify from 'soapbox/features/emoji'; import Bundle from 'soapbox/features/ui/components/bundle'; import { MediaGallery } from 'soapbox/features/ui/util/async-components'; import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; @@ -390,4 +390,4 @@ const ChatMessage = (props: IChatMessage) => { ); }; -export default ChatMessage; \ No newline at end of file +export default ChatMessage; diff --git a/app/soapbox/features/chats/components/chat-textarea.tsx b/app/soapbox/features/chats/components/chat-textarea.tsx index 2b491ed69..111a4cdc9 100644 --- a/app/soapbox/features/chats/components/chat-textarea.tsx +++ b/app/soapbox/features/chats/components/chat-textarea.tsx @@ -9,7 +9,7 @@ import ChatUpload from './chat-upload'; interface IChatTextarea extends React.ComponentProps { attachments?: Attachment[] onDeleteAttachment?: (i: number) => void - isUploading?: boolean + uploadCount?: number uploadProgress?: number } @@ -17,10 +17,12 @@ interface IChatTextarea extends React.ComponentProps { const ChatTextarea: React.FC = ({ attachments, onDeleteAttachment, - isUploading = false, + uploadCount = 0, uploadProgress = 0, ...rest }) => { + const isUploading = uploadCount > 0; + const handleDeleteAttachment = (i: number) => { return () => { if (onDeleteAttachment) { @@ -54,11 +56,11 @@ const ChatTextarea: React.FC = ({
))} - {isUploading && ( + {Array.from(Array(uploadCount)).map(() => (
- )} + ))}
)} diff --git a/app/soapbox/features/chats/components/chat.tsx b/app/soapbox/features/chats/components/chat.tsx index 758d180ab..b12d01ebe 100644 --- a/app/soapbox/features/chats/components/chat.tsx +++ b/app/soapbox/features/chats/components/chat.tsx @@ -57,7 +57,7 @@ const Chat: React.FC = ({ chat, inputRef, className }) => { const [content, setContent] = useState(''); const [attachments, setAttachments] = useState([]); - const [isUploading, setIsUploading] = useState(false); + const [uploadCount, setUploadCount] = useState(0); const [uploadProgress, setUploadProgress] = useState(0); const [resetContentKey, setResetContentKey] = useState(fileKeyGen()); const [resetFileKey, setResetFileKey] = useState(fileKeyGen()); @@ -86,7 +86,7 @@ const Chat: React.FC = ({ chat, inputRef, className }) => { } setContent(''); setAttachments([]); - setIsUploading(false); + setUploadCount(0); setUploadProgress(0); setResetFileKey(fileKeyGen()); setResetContentKey(fileKeyGen()); @@ -151,17 +151,21 @@ const Chat: React.FC = ({ chat, inputRef, className }) => { return; } - setIsUploading(true); + setUploadCount(files.length); - const data = new FormData(); - data.append('file', files[0]); - - dispatch(uploadMedia(data, onUploadProgress)).then((response: any) => { - setAttachments([...attachments, normalizeAttachment(response.data)]); - setIsUploading(false); - }).catch(() => { - setIsUploading(false); + const promises = Array.from(files).map(async(file) => { + const data = new FormData(); + data.append('file', file); + const response = await dispatch(uploadMedia(data, onUploadProgress)); + return normalizeAttachment(response.data); }); + + return Promise.all(promises) + .then((newAttachments) => { + setAttachments([...attachments, ...newAttachments]); + setUploadCount(0); + }) + .catch(() => setUploadCount(0)); }; useEffect(() => { @@ -189,7 +193,7 @@ const Chat: React.FC = ({ chat, inputRef, className }) => { onPaste={handlePaste} attachments={attachments} onDeleteAttachment={handleRemoveFile} - isUploading={isUploading} + uploadCount={uploadCount} uploadProgress={uploadProgress} /> diff --git a/app/soapbox/features/compose/components/compose-form.tsx b/app/soapbox/features/compose/components/compose-form.tsx index b0dc22325..eff94b794 100644 --- a/app/soapbox/features/compose/components/compose-form.tsx +++ b/app/soapbox/features/compose/components/compose-form.tsx @@ -16,6 +16,7 @@ import { import AutosuggestInput, { AutoSuggestion } from 'soapbox/components/autosuggest-input'; import AutosuggestTextarea from 'soapbox/components/autosuggest-textarea'; import { Button, HStack, Stack } from 'soapbox/components/ui'; +import EmojiPickerDropdown from 'soapbox/features/emoji/containers/emoji-picker-dropdown-container'; import { useAppDispatch, useAppSelector, useCompose, useFeatures, useInstance, usePrevious } from 'soapbox/hooks'; import { isMobile } from 'soapbox/is-mobile'; @@ -27,7 +28,6 @@ import WarningContainer from '../containers/warning-container'; import ComposeEditor from '../editor'; import { countableText } from '../util/counter'; -import EmojiPickerDropdown from './emoji-picker/emoji-picker-dropdown'; import PollButton from './poll-button'; import PollForm from './polls/poll-form'; import PrivacyDropdown from './privacy-dropdown'; @@ -40,7 +40,7 @@ import UploadForm from './upload-form'; import VisualCharacterCounter from './visual-character-counter'; import Warning from './warning'; -import type { Emoji } from 'soapbox/components/autosuggest-emoji'; +import type { Emoji } from 'soapbox/features/emoji'; const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d'; @@ -117,7 +117,7 @@ const ComposeForm = ({ id, shouldCondense, autoFocus, clickab // FIXME: Make this less brittle getClickableArea(), document.querySelector('.privacy-dropdown__dropdown'), - document.querySelector('.emoji-picker-dropdown__menu'), + document.querySelector('em-emoji-picker'), document.getElementById('modal-overlay'), ].some(element => element?.contains(e.target as any)); }; @@ -181,7 +181,7 @@ const ComposeForm = ({ id, shouldCondense, autoFocus, clickab const handleEmojiPick = (data: Emoji) => { const position = autosuggestTextareaRef.current!.textarea!.selectionStart; - const needsSpace = data.custom && position > 0 && !allowedAroundShortCode.includes(text[position - 1]); + const needsSpace = !!data.custom && position > 0 && !allowedAroundShortCode.includes(text[position - 1]); dispatch(insertEmojiCompose(id, position, data, needsSpace)); }; @@ -228,7 +228,7 @@ const ComposeForm = ({ id, shouldCondense, autoFocus, clickab const renderButtons = useCallback(() => ( {features.media && } - + {features.polls && } {features.privacyScopes && !group && !groupId && } {features.scheduledStatuses && } diff --git a/app/soapbox/features/compose/components/emoji-picker/emoji-picker-dropdown.tsx b/app/soapbox/features/compose/components/emoji-picker/emoji-picker-dropdown.tsx deleted file mode 100644 index 0a6d1998f..000000000 --- a/app/soapbox/features/compose/components/emoji-picker/emoji-picker-dropdown.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import clsx from 'clsx'; -import { List as ImmutableList, Map as ImmutableMap } from 'immutable'; -import React, { useRef, useState } from 'react'; -import { defineMessages, useIntl } from 'react-intl'; -// @ts-ignore -import Overlay from 'react-overlays/lib/Overlay'; -import { createSelector } from 'reselect'; - -import { useEmoji } from 'soapbox/actions/emojis'; -import { getSettings, changeSetting } from 'soapbox/actions/settings'; -import { IconButton } from 'soapbox/components/ui'; -import { EmojiPicker as EmojiPickerAsync } from 'soapbox/features/ui/util/async-components'; -import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; - -import EmojiPickerMenu from './emoji-picker-menu'; - -import type { Emoji as EmojiType } from 'soapbox/components/autosuggest-emoji'; -import type { RootState } from 'soapbox/store'; - -let EmojiPicker: any, Emoji: any; // load asynchronously - -const perLine = 8; -const lines = 2; - -const DEFAULTS = [ - '+1', - 'grinning', - 'kissing_heart', - 'heart_eyes', - 'laughing', - 'stuck_out_tongue_winking_eye', - 'sweat_smile', - 'joy', - 'yum', - 'disappointed', - 'thinking_face', - 'weary', - 'sob', - 'sunglasses', - 'heart', - 'ok_hand', -]; - -const getFrequentlyUsedEmojis = createSelector([ - (state: RootState) => state.settings.get('frequentlyUsedEmojis', ImmutableMap()), -], emojiCounters => { - let emojis = emojiCounters - .keySeq() - .sort((a: number, b: number) => emojiCounters.get(a) - emojiCounters.get(b)) - .reverse() - .slice(0, perLine * lines) - .toArray(); - - if (emojis.length < DEFAULTS.length) { - const uniqueDefaults = DEFAULTS.filter(emoji => !emojis.includes(emoji)); - emojis = emojis.concat(uniqueDefaults.slice(0, DEFAULTS.length - emojis.length)); - } - - return emojis; -}); - -const getCustomEmojis = createSelector([ - (state: RootState) => state.custom_emojis as ImmutableList>, -], emojis => emojis.filter((e) => e.get('visible_in_picker')).sort((a, b) => { - const aShort = a.get('shortcode')!.toLowerCase(); - const bShort = b.get('shortcode')!.toLowerCase(); - - if (aShort < bShort) { - return -1; - } else if (aShort > bShort) { - return 1; - } else { - return 0; - } -}) as ImmutableList>); - -const messages = defineMessages({ - emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, - emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search…' }, - emoji_not_found: { id: 'emoji_button.not_found', defaultMessage: 'No emoji\'s found.' }, - custom: { id: 'emoji_button.custom', defaultMessage: 'Custom' }, - recent: { id: 'emoji_button.recent', defaultMessage: 'Frequently used' }, - search_results: { id: 'emoji_button.search_results', defaultMessage: 'Search results' }, - people: { id: 'emoji_button.people', defaultMessage: 'People' }, - nature: { id: 'emoji_button.nature', defaultMessage: 'Nature' }, - food: { id: 'emoji_button.food', defaultMessage: 'Food & Drink' }, - activity: { id: 'emoji_button.activity', defaultMessage: 'Activity' }, - travel: { id: 'emoji_button.travel', defaultMessage: 'Travel & Places' }, - objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' }, - symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' }, - flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' }, -}); - -interface IEmojiPickerDropdown { - onPickEmoji: (data: EmojiType) => void - button?: JSX.Element -} - -const EmojiPickerDropdown: React.FC = ({ onPickEmoji, button }) => { - const intl = useIntl(); - const dispatch = useAppDispatch(); - - const customEmojis = useAppSelector((state) => getCustomEmojis(state)); - const skinTone = useAppSelector((state) => getSettings(state).get('skinTone') as number); - const frequentlyUsedEmojis = useAppSelector((state) => getFrequentlyUsedEmojis(state)); - - const [active, setActive] = useState(false); - const [loading, setLoading] = useState(false); - const [placement, setPlacement] = useState<'bottom' | 'top'>(); - - const target = useRef(null); - - const onSkinTone = (skinTone: number) => { - dispatch(changeSetting(['skinTone'], skinTone)); - }; - - const handlePickEmoji = (emoji: EmojiType) => { - // eslint-disable-next-line react-hooks/rules-of-hooks - dispatch(useEmoji(emoji)); - - if (onPickEmoji) { - onPickEmoji(emoji); - } - }; - - const onShowDropdown: React.EventHandler = (e) => { - e.stopPropagation(); - - setActive(true); - - if (!EmojiPicker) { - setLoading(true); - - EmojiPickerAsync().then(EmojiMart => { - EmojiPicker = EmojiMart.Picker; - Emoji = EmojiMart.Emoji; - - setLoading(false); - }).catch(() => { - setLoading(false); - }); - } - - const { top } = (e.target as any).getBoundingClientRect(); - setPlacement(top * 2 < innerHeight ? 'bottom' : 'top'); - }; - - const onHideDropdown = () => { - setActive(false); - }; - - const onToggle: React.EventHandler = (e) => { - if (!loading && (!(e as React.KeyboardEvent).key || (e as React.KeyboardEvent).key === 'Enter')) { - if (active) { - onHideDropdown(); - } else { - onShowDropdown(e); - } - } - }; - - const handleKeyDown: React.KeyboardEventHandler = e => { - if (e.key === 'Escape') { - onHideDropdown(); - } - }; - - const title = intl.formatMessage(messages.emoji); - - return ( -
-
- {button || } -
- - - - -
- ); -}; - -export { EmojiPicker, Emoji }; - -export default EmojiPickerDropdown; diff --git a/app/soapbox/features/compose/components/emoji-picker/emoji-picker-menu.tsx b/app/soapbox/features/compose/components/emoji-picker/emoji-picker-menu.tsx deleted file mode 100644 index 56e1ebfc5..000000000 --- a/app/soapbox/features/compose/components/emoji-picker/emoji-picker-menu.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import clsx from 'clsx'; -import { supportsPassiveEvents } from 'detect-passive-events'; -import { List as ImmutableList, Map as ImmutableMap } from 'immutable'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { defineMessages, useIntl } from 'react-intl'; - -import { buildCustomEmojis, categoriesFromEmojis } from '../../../emoji/emoji'; - -import { EmojiPicker } from './emoji-picker-dropdown'; -import ModifierPicker from './modifier-picker'; - -import type { Emoji } from 'soapbox/components/autosuggest-emoji'; - -const backgroundImageFn = () => require('emoji-datasource/img/twitter/sheets/32.png'); -const listenerOptions = supportsPassiveEvents ? { passive: true } : false; - -const messages = defineMessages({ - emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, - emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search…' }, - emoji_not_found: { id: 'emoji_button.not_found', defaultMessage: 'No emoji\'s found.' }, - custom: { id: 'emoji_button.custom', defaultMessage: 'Custom' }, - recent: { id: 'emoji_button.recent', defaultMessage: 'Frequently used' }, - search_results: { id: 'emoji_button.search_results', defaultMessage: 'Search results' }, - people: { id: 'emoji_button.people', defaultMessage: 'People' }, - nature: { id: 'emoji_button.nature', defaultMessage: 'Nature' }, - food: { id: 'emoji_button.food', defaultMessage: 'Food & Drink' }, - activity: { id: 'emoji_button.activity', defaultMessage: 'Activity' }, - travel: { id: 'emoji_button.travel', defaultMessage: 'Travel & Places' }, - objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' }, - symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' }, - flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' }, -}); - -interface IEmojiPickerMenu { - customEmojis: ImmutableList> - loading?: boolean - onClose: () => void - onPick: (emoji: Emoji) => void - onSkinTone: (skinTone: number) => void - skinTone?: number - frequentlyUsedEmojis?: Array - style?: React.CSSProperties -} - -const EmojiPickerMenu: React.FC = ({ - customEmojis, - loading = true, - onClose, - onPick, - onSkinTone, - skinTone, - frequentlyUsedEmojis = [], - style = {}, -}) => { - const intl = useIntl(); - - const node = useRef(null); - - const [modifierOpen, setModifierOpen] = useState(false); - - const categoriesSort = [ - 'recent', - 'people', - 'nature', - 'foods', - 'activity', - 'places', - 'objects', - 'symbols', - 'flags', - ]; - - categoriesSort.splice(1, 0, ...Array.from(categoriesFromEmojis(customEmojis) as Set).sort()); - - const handleDocumentClick = useCallback((e: MouseEvent | TouchEvent) => { - if (node.current && !node.current.contains(e.target as Node)) { - onClose(); - } - }, []); - - const getI18n = () => { - return { - search: intl.formatMessage(messages.emoji_search), - notfound: intl.formatMessage(messages.emoji_not_found), - categories: { - search: intl.formatMessage(messages.search_results), - recent: 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), - }, - }; - }; - - const handleClick = (emoji: any) => { - if (!emoji.native) { - emoji.native = emoji.colons; - } - - onClose(); - onPick(emoji); - }; - - const handleModifierOpen = () => { - setModifierOpen(true); - }; - - const handleModifierClose = () => { - setModifierOpen(false); - }; - - const handleModifierChange = (modifier: number) => { - onSkinTone(modifier); - }; - - useEffect(() => { - document.addEventListener('click', handleDocumentClick, false); - document.addEventListener('touchend', handleDocumentClick, listenerOptions); - - return () => { - document.removeEventListener('click', handleDocumentClick, false); - document.removeEventListener('touchend', handleDocumentClick, listenerOptions as any); - }; - }, []); - - if (loading) { - return
; - } - - const title = intl.formatMessage(messages.emoji); - - return ( -
- - - -
- ); -}; - -export default EmojiPickerMenu; diff --git a/app/soapbox/features/compose/components/emoji-picker/modifier-picker-menu.tsx b/app/soapbox/features/compose/components/emoji-picker/modifier-picker-menu.tsx deleted file mode 100644 index f3679c65e..000000000 --- a/app/soapbox/features/compose/components/emoji-picker/modifier-picker-menu.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { supportsPassiveEvents } from 'detect-passive-events'; -import React, { useCallback, useEffect, useRef } from 'react'; - -import { Emoji } from './emoji-picker-dropdown'; - -const listenerOptions = supportsPassiveEvents ? { passive: true } : false; -const backgroundImageFn = () => require('emoji-datasource/img/twitter/sheets/32.png'); - -interface IModifierPickerMenu { - active: boolean - onSelect: (modifier: number) => void - onClose: () => void -} - -const ModifierPickerMenu: React.FC = ({ active, onSelect, onClose }) => { - const node = useRef(null); - - const handleClick: React.MouseEventHandler = e => { - onSelect(+e.currentTarget.getAttribute('data-index')! * 1); - }; - - const handleDocumentClick = useCallback(((e: MouseEvent | TouchEvent) => { - if (node.current && !node.current.contains(e.target as Node)) { - onClose(); - } - }), []); - - const attachListeners = () => { - document.addEventListener('click', handleDocumentClick, false); - document.addEventListener('touchend', handleDocumentClick, listenerOptions); - }; - - const removeListeners = () => { - document.removeEventListener('click', handleDocumentClick, false); - document.removeEventListener('touchend', handleDocumentClick, listenerOptions as any); - }; - - useEffect(() => { - return () => { - removeListeners(); - }; - }, []); - - useEffect(() => { - if (active) attachListeners(); - else removeListeners(); - }, [active]); - - return ( -
- - - - - - -
- ); -}; - -export default ModifierPickerMenu; diff --git a/app/soapbox/features/compose/components/emoji-picker/modifier-picker.tsx b/app/soapbox/features/compose/components/emoji-picker/modifier-picker.tsx deleted file mode 100644 index 03c5b24af..000000000 --- a/app/soapbox/features/compose/components/emoji-picker/modifier-picker.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; - -import { Emoji } from './emoji-picker-dropdown'; -import ModifierPickerMenu from './modifier-picker-menu'; - -const backgroundImageFn = () => require('emoji-datasource/img/twitter/sheets/32.png'); - -interface IModifierPicker { - active: boolean - modifier?: number - onOpen: () => void - onClose: () => void - onChange: (skinTone: number) => void -} - -const ModifierPicker: React.FC = ({ active, modifier, onOpen, onClose, onChange }) => { - const handleClick = () => { - if (active) { - onClose(); - } else { - onOpen(); - } - }; - - const handleSelect = (modifier: number) => { - onChange(modifier); - onClose(); - }; - - return ( -
- - -
- ); -}; - -export default ModifierPicker; diff --git a/app/soapbox/features/emoji/__tests__/emoji-index.test.ts b/app/soapbox/features/emoji/__tests__/emoji-index.test.ts index faefaa72c..59e2e9bef 100644 --- a/app/soapbox/features/emoji/__tests__/emoji-index.test.ts +++ b/app/soapbox/features/emoji/__tests__/emoji-index.test.ts @@ -1,8 +1,7 @@ -// @ts-ignore -import { emojiIndex } from 'emoji-mart'; +import { List, Map } from 'immutable'; import pick from 'lodash/pick'; -import { search } from '../emoji-mart-search-light'; +import search, { addCustomToPool } from '../search'; const trimEmojis = (emoji: any) => pick(emoji, ['id', 'unified', 'native', 'custom']); @@ -16,116 +15,83 @@ describe('emoji_index', () => { }, ]; expect(search('pineapple').map(trimEmojis)).toEqual(expected); - expect(emojiIndex.search('pineapple').map(trimEmojis)).toEqual(expected); }); it('orders search results correctly', () => { const expected = [ - { - id: 'apple', - unified: '1f34e', - native: '🍎', - }, { id: 'pineapple', unified: '1f34d', native: '🍍', }, + { + id: 'apple', + unified: '1f34e', + native: '🍎', + }, { id: 'green_apple', unified: '1f34f', native: '🍏', }, - { - id: 'iphone', - unified: '1f4f1', - native: '📱', - }, ]; expect(search('apple').map(trimEmojis)).toEqual(expected); - expect(emojiIndex.search('apple').map(trimEmojis)).toEqual(expected); }); - it('can include/exclude categories', () => { - expect(search('flag', { include: ['people'] } as any)).toEqual([]); - expect(emojiIndex.search('flag', { include: ['people'] })).toEqual([]); - }); - - it('(different behavior from emoji-mart) do not erases custom emoji if not passed again', () => { + it('handles custom emojis', () => { const custom = [ { id: 'mastodon', name: 'mastodon', - short_names: ['mastodon'], - text: '', - emoticons: [], keywords: ['mastodon'], - imageUrl: 'http://example.com', - custom: true, + skins: { src: 'http://example.com' }, }, ]; - search('', { custom } as any); - emojiIndex.search('', { custom }); + + const custom_emojis = List([ + Map({ static_url: 'http://example.com', shortcode: 'mastodon' }), + ]); + const lightExpected = [ { id: 'mastodon', custom: true, }, ]; - expect(search('masto').map(trimEmojis)).toEqual(lightExpected); - expect(emojiIndex.search('masto').map(trimEmojis)).toEqual([]); + + addCustomToPool(custom); + expect(search('masto', {}, custom_emojis).map(trimEmojis)).toEqual(lightExpected); }); - it('(different behavior from emoji-mart) erases custom emoji if another is passed', () => { + it('updates custom emoji if another is passed', () => { const custom = [ { id: 'mastodon', name: 'mastodon', - short_names: ['mastodon'], - text: '', - emoticons: [], keywords: ['mastodon'], - imageUrl: 'http://example.com', - custom: true, + skins: { src: 'http://example.com' }, }, ]; - search('', { custom } as any); - emojiIndex.search('', { custom }); - expect(search('masto', { custom: [] } as any).map(trimEmojis)).toEqual([]); - expect(emojiIndex.search('masto').map(trimEmojis)).toEqual([]); - }); - it('handles custom emoji', () => { - const custom = [ - { - id: 'mastodon', - name: 'mastodon', - short_names: ['mastodon'], - text: '', - emoticons: [], - keywords: ['mastodon'], - imageUrl: 'http://example.com', - custom: true, - }, - ]; - search('', { custom } as any); - emojiIndex.search('', { custom }); - const expected = [ - { - id: 'mastodon', - custom: true, - }, - ]; - expect(search('masto', { custom } as any).map(trimEmojis)).toEqual(expected); - expect(emojiIndex.search('masto', { custom }).map(trimEmojis)).toEqual(expected); - }); + addCustomToPool(custom); - it('should filter only emojis we care about, exclude pineapple', () => { - const emojisToShowFilter = (emoji: any) => emoji.unified !== '1F34D'; - expect(search('apple', { emojisToShowFilter } as any).map((obj: any) => obj.id)) - .not.toContain('pineapple'); - expect(emojiIndex.search('apple', { emojisToShowFilter }).map((obj: any) => obj.id)) - .not.toContain('pineapple'); + const custom2 = [ + { + id: 'pleroma', + name: 'pleroma', + keywords: ['pleroma'], + skins: { src: 'http://example.com' }, + }, + ]; + + addCustomToPool(custom2); + + const custom_emojis = List([ + Map({ static_url: 'http://example.com', shortcode: 'pleroma' }), + ]); + + const expected: any = []; + expect(search('masto', {}, custom_emojis).map(trimEmojis)).toEqual(expected); }); it('does an emoji whose unified name is irregular', () => { @@ -147,7 +113,6 @@ describe('emoji_index', () => { }, ]; expect(search('polo').map(trimEmojis)).toEqual(expected); - expect(emojiIndex.search('polo').map(trimEmojis)).toEqual(expected); }); it('can search for thinking_face', () => { @@ -159,7 +124,6 @@ describe('emoji_index', () => { }, ]; expect(search('thinking_fac').map(trimEmojis)).toEqual(expected); - expect(emojiIndex.search('thinking_fac').map(trimEmojis)).toEqual(expected); }); it('can search for woman-facepalming', () => { @@ -171,6 +135,5 @@ describe('emoji_index', () => { }, ]; expect(search('woman-facep').map(trimEmojis)).toEqual(expected); - expect(emojiIndex.search('woman-facep').map(trimEmojis)).toEqual(expected); }); }); diff --git a/app/soapbox/features/emoji/__tests__/emoji.test.ts b/app/soapbox/features/emoji/__tests__/emoji.test.ts index df6798ef7..cd849d0f9 100644 --- a/app/soapbox/features/emoji/__tests__/emoji.test.ts +++ b/app/soapbox/features/emoji/__tests__/emoji.test.ts @@ -1,4 +1,4 @@ -import emojify from '../emoji'; +import emojify from '../index'; describe('emoji', () => { describe('.emojify', () => { @@ -11,8 +11,8 @@ describe('emoji', () => { }); it('works with unclosed tags', () => { - expect(emojify('hello>')).toEqual('hello>'); - expect(emojify('')).toEqual('hello>'); + expect(emojify(' { @@ -22,23 +22,23 @@ describe('emoji', () => { it('does unicode', () => { expect(emojify('\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66')).toEqual( - '👩‍👩‍👦‍👦'); + '👩‍👩‍👦‍👦'); expect(emojify('👨‍👩‍👧‍👧')).toEqual( - '👨‍👩‍👧‍👧'); - expect(emojify('👩‍👩‍👦')).toEqual('👩‍👩‍👦'); + '👨‍👩‍👧‍👧'); + expect(emojify('👩‍👩‍👦')).toEqual('👩‍👩‍👦'); expect(emojify('\u2757')).toEqual( - '❗'); + '❗'); }); it('does multiple unicode', () => { expect(emojify('\u2757 #\uFE0F\u20E3')).toEqual( - '❗ #️⃣'); + '❗ #️⃣'); expect(emojify('\u2757#\uFE0F\u20E3')).toEqual( - '❗#️⃣'); + '❗#️⃣'); expect(emojify('\u2757 #\uFE0F\u20E3 \u2757')).toEqual( - '❗ #️⃣ ❗'); + '❗ #️⃣ ❗'); expect(emojify('foo \u2757 #\uFE0F\u20E3 bar')).toEqual( - 'foo ❗ #️⃣ bar'); + 'foo ❗ #️⃣ bar'); }); it('ignores unicode inside of tags', () => { @@ -46,52 +46,1869 @@ describe('emoji', () => { }); it('does multiple emoji properly (issue 5188)', () => { - expect(emojify('👌🌈💕')).toEqual('👌🌈💕'); - expect(emojify('👌 🌈 💕')).toEqual('👌 🌈 💕'); + expect(emojify('👌🌈💕')).toEqual('👌🌈💕'); + expect(emojify('👌 🌈 💕')).toEqual('👌 🌈 💕'); }); it('does an emoji that has no shortcode', () => { - expect(emojify('👁‍🗨')).toEqual('👁‍🗨'); - }); - - it('does an emoji whose filename is irregular', () => { - expect(emojify('↙️')).toEqual('↙️'); - }); - - it('avoid emojifying on invisible text', () => { - expect(emojify('example.com/te')) - .toEqual('example.com/te'); - expect(emojify('', { ':luigi:': { static_url: 'luigi.exe' } })) - .toEqual(''); - }); - - it('avoid emojifying on invisible text with nested tags', () => { - expect(emojify('😇')) - .toEqual('😇'); - expect(emojify('😇')) - .toEqual('😇'); - expect(emojify('😇')) - .toEqual('😇'); + expect(emojify('👁‍🗨')).toEqual('👁‍🗨'); }); it('skips the textual presentation VS15 character', () => { expect(emojify('✴︎')) // This is U+2734 EIGHT POINTED BLACK STAR then U+FE0E VARIATION SELECTOR-15 - .toEqual('✴'); + .toEqual('✴️'); }); - it('does an simple emoji properly', () => { - expect(emojify('♀♂')) - .toEqual('♀♂'); - }); - - it('does an emoji containing ZWJ properly', () => { - expect(emojify('💂‍♀️💂‍♂️')) - .toEqual('💂\u200D♀️💂\u200D♂️'); - }); - - it('keeps ordering as expected (issue fixed by PR 20677)', () => { - expect(emojify('

💕 #foo test: foo.

')) - .toEqual('

💕 #foo test: foo.

'); + it('full v14 unicode emoji map', () => { + expect(emojify('💯')).toEqual('💯'); + expect(emojify('🔢')).toEqual('🔢'); + expect(emojify('😀')).toEqual('😀'); + expect(emojify('😃')).toEqual('😃'); + expect(emojify('😄')).toEqual('😄'); + expect(emojify('😁')).toEqual('😁'); + expect(emojify('😆')).toEqual('😆'); + expect(emojify('😅')).toEqual('😅'); + expect(emojify('🤣')).toEqual('🤣'); + expect(emojify('😂')).toEqual('😂'); + expect(emojify('🙂')).toEqual('🙂'); + expect(emojify('🙃')).toEqual('🙃'); + expect(emojify('🫠')).toEqual('🫠'); + expect(emojify('😉')).toEqual('😉'); + expect(emojify('😊')).toEqual('😊'); + expect(emojify('😇')).toEqual('😇'); + expect(emojify('🥰')).toEqual('🥰'); + expect(emojify('😍')).toEqual('😍'); + expect(emojify('🤩')).toEqual('🤩'); + expect(emojify('😘')).toEqual('😘'); + expect(emojify('😗')).toEqual('😗'); + expect(emojify('☺️')).toEqual('☺️'); + expect(emojify('😚')).toEqual('😚'); + expect(emojify('😙')).toEqual('😙'); + expect(emojify('🥲')).toEqual('🥲'); + expect(emojify('😋')).toEqual('😋'); + expect(emojify('😛')).toEqual('😛'); + expect(emojify('😜')).toEqual('😜'); + expect(emojify('🤪')).toEqual('🤪'); + expect(emojify('😝')).toEqual('😝'); + expect(emojify('🤑')).toEqual('🤑'); + expect(emojify('🤗')).toEqual('🤗'); + expect(emojify('🤭')).toEqual('🤭'); + expect(emojify('🫢')).toEqual('🫢'); + expect(emojify('🫣')).toEqual('🫣'); + expect(emojify('🤫')).toEqual('🤫'); + expect(emojify('🤔')).toEqual('🤔'); + expect(emojify('🫡')).toEqual('🫡'); + expect(emojify('🤐')).toEqual('🤐'); + expect(emojify('🤨')).toEqual('🤨'); + expect(emojify('😐')).toEqual('😐'); + expect(emojify('😑')).toEqual('😑'); + expect(emojify('😶')).toEqual('😶'); + expect(emojify('🫥')).toEqual('🫥'); + expect(emojify('😶‍🌫️')).toEqual('😶‍🌫️'); + expect(emojify('😏')).toEqual('😏'); + expect(emojify('😒')).toEqual('😒'); + expect(emojify('🙄')).toEqual('🙄'); + expect(emojify('😬')).toEqual('😬'); + expect(emojify('😮‍💨')).toEqual('😮‍💨'); + expect(emojify('🤥')).toEqual('🤥'); + expect(emojify('😌')).toEqual('😌'); + expect(emojify('😔')).toEqual('😔'); + expect(emojify('😪')).toEqual('😪'); + expect(emojify('🤤')).toEqual('🤤'); + expect(emojify('😴')).toEqual('😴'); + expect(emojify('😷')).toEqual('😷'); + expect(emojify('🤒')).toEqual('🤒'); + expect(emojify('🤕')).toEqual('🤕'); + expect(emojify('🤢')).toEqual('🤢'); + expect(emojify('🤮')).toEqual('🤮'); + expect(emojify('🤧')).toEqual('🤧'); + expect(emojify('🥵')).toEqual('🥵'); + expect(emojify('🥶')).toEqual('🥶'); + expect(emojify('🥴')).toEqual('🥴'); + expect(emojify('😵')).toEqual('😵'); + expect(emojify('😵‍💫')).toEqual('😵‍💫'); + expect(emojify('🤯')).toEqual('🤯'); + expect(emojify('🤠')).toEqual('🤠'); + expect(emojify('🥳')).toEqual('🥳'); + expect(emojify('🥸')).toEqual('🥸'); + expect(emojify('😎')).toEqual('😎'); + expect(emojify('🤓')).toEqual('🤓'); + expect(emojify('🧐')).toEqual('🧐'); + expect(emojify('😕')).toEqual('😕'); + expect(emojify('🫤')).toEqual('🫤'); + expect(emojify('😟')).toEqual('😟'); + expect(emojify('🙁')).toEqual('🙁'); + expect(emojify('☹️')).toEqual('☹️'); + expect(emojify('😮')).toEqual('😮'); + expect(emojify('😯')).toEqual('😯'); + expect(emojify('😲')).toEqual('😲'); + expect(emojify('😳')).toEqual('😳'); + expect(emojify('🥺')).toEqual('🥺'); + expect(emojify('🥹')).toEqual('🥹'); + expect(emojify('😦')).toEqual('😦'); + expect(emojify('😧')).toEqual('😧'); + expect(emojify('😨')).toEqual('😨'); + expect(emojify('😰')).toEqual('😰'); + expect(emojify('😥')).toEqual('😥'); + expect(emojify('😢')).toEqual('😢'); + expect(emojify('😭')).toEqual('😭'); + expect(emojify('😱')).toEqual('😱'); + expect(emojify('😖')).toEqual('😖'); + expect(emojify('😣')).toEqual('😣'); + expect(emojify('😞')).toEqual('😞'); + expect(emojify('😓')).toEqual('😓'); + expect(emojify('😩')).toEqual('😩'); + expect(emojify('😫')).toEqual('😫'); + expect(emojify('🥱')).toEqual('🥱'); + expect(emojify('😤')).toEqual('😤'); + expect(emojify('😡')).toEqual('😡'); + expect(emojify('😠')).toEqual('😠'); + expect(emojify('🤬')).toEqual('🤬'); + expect(emojify('😈')).toEqual('😈'); + expect(emojify('👿')).toEqual('👿'); + expect(emojify('💀')).toEqual('💀'); + expect(emojify('☠️')).toEqual('☠️'); + expect(emojify('💩')).toEqual('💩'); + expect(emojify('🤡')).toEqual('🤡'); + expect(emojify('👹')).toEqual('👹'); + expect(emojify('👺')).toEqual('👺'); + expect(emojify('👻')).toEqual('👻'); + expect(emojify('👽')).toEqual('👽'); + expect(emojify('👾')).toEqual('👾'); + expect(emojify('🤖')).toEqual('🤖'); + expect(emojify('😺')).toEqual('😺'); + expect(emojify('😸')).toEqual('😸'); + expect(emojify('😹')).toEqual('😹'); + expect(emojify('😻')).toEqual('😻'); + expect(emojify('😼')).toEqual('😼'); + expect(emojify('😽')).toEqual('😽'); + expect(emojify('🙀')).toEqual('🙀'); + expect(emojify('😿')).toEqual('😿'); + expect(emojify('😾')).toEqual('😾'); + expect(emojify('🙈')).toEqual('🙈'); + expect(emojify('🙉')).toEqual('🙉'); + expect(emojify('🙊')).toEqual('🙊'); + expect(emojify('💋')).toEqual('💋'); + expect(emojify('💌')).toEqual('💌'); + expect(emojify('💘')).toEqual('💘'); + expect(emojify('💝')).toEqual('💝'); + expect(emojify('💖')).toEqual('💖'); + expect(emojify('💗')).toEqual('💗'); + expect(emojify('💓')).toEqual('💓'); + expect(emojify('💞')).toEqual('💞'); + expect(emojify('💕')).toEqual('💕'); + expect(emojify('💟')).toEqual('💟'); + expect(emojify('❣️')).toEqual('❣️'); + expect(emojify('💔')).toEqual('💔'); + expect(emojify('❤️‍🔥')).toEqual('❤️‍🔥'); + expect(emojify('❤️‍🩹')).toEqual('❤️‍🩹'); + expect(emojify('❤️')).toEqual('❤️'); + expect(emojify('🧡')).toEqual('🧡'); + expect(emojify('💛')).toEqual('💛'); + expect(emojify('💚')).toEqual('💚'); + expect(emojify('💙')).toEqual('💙'); + expect(emojify('💜')).toEqual('💜'); + expect(emojify('🤎')).toEqual('🤎'); + expect(emojify('🖤')).toEqual('🖤'); + expect(emojify('🤍')).toEqual('🤍'); + expect(emojify('💢')).toEqual('💢'); + expect(emojify('💥')).toEqual('💥'); + expect(emojify('💫')).toEqual('💫'); + expect(emojify('💦')).toEqual('💦'); + expect(emojify('💨')).toEqual('💨'); + expect(emojify('🕳️')).toEqual('🕳️'); + expect(emojify('💣')).toEqual('💣'); + expect(emojify('💬')).toEqual('💬'); + expect(emojify('👁️‍🗨️')).toEqual('👁️‍🗨️'); + expect(emojify('🗨️')).toEqual('🗨️'); + expect(emojify('🗯️')).toEqual('🗯️'); + expect(emojify('💭')).toEqual('💭'); + expect(emojify('💤')).toEqual('💤'); + expect(emojify('👋')).toEqual('👋'); + expect(emojify('🤚')).toEqual('🤚'); + expect(emojify('🖐️')).toEqual('🖐️'); + expect(emojify('✋')).toEqual('✋'); + expect(emojify('🖖')).toEqual('🖖'); + expect(emojify('🫱')).toEqual('🫱'); + expect(emojify('🫲')).toEqual('🫲'); + expect(emojify('🫳')).toEqual('🫳'); + expect(emojify('🫴')).toEqual('🫴'); + expect(emojify('👌')).toEqual('👌'); + expect(emojify('🤌')).toEqual('🤌'); + expect(emojify('🤏')).toEqual('🤏'); + expect(emojify('✌️')).toEqual('✌️'); + expect(emojify('🤞')).toEqual('🤞'); + expect(emojify('🫰')).toEqual('🫰'); + expect(emojify('🤟')).toEqual('🤟'); + expect(emojify('🤘')).toEqual('🤘'); + expect(emojify('🤙')).toEqual('🤙'); + expect(emojify('👈')).toEqual('👈'); + expect(emojify('👉')).toEqual('👉'); + expect(emojify('👆')).toEqual('👆'); + expect(emojify('🖕')).toEqual('🖕'); + expect(emojify('👇')).toEqual('👇'); + expect(emojify('☝️')).toEqual('☝️'); + expect(emojify('🫵')).toEqual('🫵'); + expect(emojify('👍')).toEqual('👍'); + expect(emojify('👎')).toEqual('👎'); + expect(emojify('✊')).toEqual('✊'); + expect(emojify('👊')).toEqual('👊'); + expect(emojify('🤛')).toEqual('🤛'); + expect(emojify('🤜')).toEqual('🤜'); + expect(emojify('👏')).toEqual('👏'); + expect(emojify('🙌')).toEqual('🙌'); + expect(emojify('🫶')).toEqual('🫶'); + expect(emojify('👐')).toEqual('👐'); + expect(emojify('🤲')).toEqual('🤲'); + expect(emojify('🤝')).toEqual('🤝'); + expect(emojify('🙏')).toEqual('🙏'); + expect(emojify('✍️')).toEqual('✍️'); + expect(emojify('💅')).toEqual('💅'); + expect(emojify('🤳')).toEqual('🤳'); + expect(emojify('💪')).toEqual('💪'); + expect(emojify('🦾')).toEqual('🦾'); + expect(emojify('🦿')).toEqual('🦿'); + expect(emojify('🦵')).toEqual('🦵'); + expect(emojify('🦶')).toEqual('🦶'); + expect(emojify('👂')).toEqual('👂'); + expect(emojify('🦻')).toEqual('🦻'); + expect(emojify('👃')).toEqual('👃'); + expect(emojify('🧠')).toEqual('🧠'); + expect(emojify('🫀')).toEqual('🫀'); + expect(emojify('🫁')).toEqual('🫁'); + expect(emojify('🦷')).toEqual('🦷'); + expect(emojify('🦴')).toEqual('🦴'); + expect(emojify('👀')).toEqual('👀'); + expect(emojify('👁️')).toEqual('👁️'); + expect(emojify('👅')).toEqual('👅'); + expect(emojify('👄')).toEqual('👄'); + expect(emojify('🫦')).toEqual('🫦'); + expect(emojify('👶')).toEqual('👶'); + expect(emojify('🧒')).toEqual('🧒'); + expect(emojify('👦')).toEqual('👦'); + expect(emojify('👧')).toEqual('👧'); + expect(emojify('🧑')).toEqual('🧑'); + expect(emojify('👱')).toEqual('👱'); + expect(emojify('👨')).toEqual('👨'); + expect(emojify('🧔')).toEqual('🧔'); + expect(emojify('🧔‍♂️')).toEqual('🧔‍♂️'); + expect(emojify('🧔‍♀️')).toEqual('🧔‍♀️'); + expect(emojify('👨‍🦰')).toEqual('👨‍🦰'); + expect(emojify('👨‍🦱')).toEqual('👨‍🦱'); + expect(emojify('👨‍🦳')).toEqual('👨‍🦳'); + expect(emojify('👨‍🦲')).toEqual('👨‍🦲'); + expect(emojify('👩')).toEqual('👩'); + expect(emojify('👩‍🦰')).toEqual('👩‍🦰'); + expect(emojify('🧑‍🦰')).toEqual('🧑‍🦰'); + expect(emojify('👩‍🦱')).toEqual('👩‍🦱'); + expect(emojify('🧑‍🦱')).toEqual('🧑‍🦱'); + expect(emojify('👩‍🦳')).toEqual('👩‍🦳'); + expect(emojify('🧑‍🦳')).toEqual('🧑‍🦳'); + expect(emojify('👩‍🦲')).toEqual('👩‍🦲'); + expect(emojify('🧑‍🦲')).toEqual('🧑‍🦲'); + expect(emojify('👱‍♀️')).toEqual('👱‍♀️'); + expect(emojify('👱‍♂️')).toEqual('👱‍♂️'); + expect(emojify('🧓')).toEqual('🧓'); + expect(emojify('👴')).toEqual('👴'); + expect(emojify('👵')).toEqual('👵'); + expect(emojify('🙍')).toEqual('🙍'); + expect(emojify('🙍‍♂️')).toEqual('🙍‍♂️'); + expect(emojify('🙍‍♀️')).toEqual('🙍‍♀️'); + expect(emojify('🙎')).toEqual('🙎'); + expect(emojify('🙎‍♂️')).toEqual('🙎‍♂️'); + expect(emojify('🙎‍♀️')).toEqual('🙎‍♀️'); + expect(emojify('🙅')).toEqual('🙅'); + expect(emojify('🙅‍♂️')).toEqual('🙅‍♂️'); + expect(emojify('🙅‍♀️')).toEqual('🙅‍♀️'); + expect(emojify('🙆')).toEqual('🙆'); + expect(emojify('🙆‍♂️')).toEqual('🙆‍♂️'); + expect(emojify('🙆‍♀️')).toEqual('🙆‍♀️'); + expect(emojify('💁')).toEqual('💁'); + expect(emojify('💁‍♂️')).toEqual('💁‍♂️'); + expect(emojify('💁‍♀️')).toEqual('💁‍♀️'); + expect(emojify('🙋')).toEqual('🙋'); + expect(emojify('🙋‍♂️')).toEqual('🙋‍♂️'); + expect(emojify('🙋‍♀️')).toEqual('🙋‍♀️'); + expect(emojify('🧏')).toEqual('🧏'); + expect(emojify('🧏‍♂️')).toEqual('🧏‍♂️'); + expect(emojify('🧏‍♀️')).toEqual('🧏‍♀️'); + expect(emojify('🙇')).toEqual('🙇'); + expect(emojify('🙇‍♂️')).toEqual('🙇‍♂️'); + expect(emojify('🙇‍♀️')).toEqual('🙇‍♀️'); + expect(emojify('🤦')).toEqual('🤦'); + expect(emojify('🤦‍♂️')).toEqual('🤦‍♂️'); + expect(emojify('🤦‍♀️')).toEqual('🤦‍♀️'); + expect(emojify('🤷')).toEqual('🤷'); + expect(emojify('🤷‍♂️')).toEqual('🤷‍♂️'); + expect(emojify('🤷‍♀️')).toEqual('🤷‍♀️'); + expect(emojify('🧑‍⚕️')).toEqual('🧑‍⚕️'); + expect(emojify('👨‍⚕️')).toEqual('👨‍⚕️'); + expect(emojify('👩‍⚕️')).toEqual('👩‍⚕️'); + expect(emojify('🧑‍🎓')).toEqual('🧑‍🎓'); + expect(emojify('👨‍🎓')).toEqual('👨‍🎓'); + expect(emojify('👩‍🎓')).toEqual('👩‍🎓'); + expect(emojify('🧑‍🏫')).toEqual('🧑‍🏫'); + expect(emojify('👨‍🏫')).toEqual('👨‍🏫'); + expect(emojify('👩‍🏫')).toEqual('👩‍🏫'); + expect(emojify('🧑‍⚖️')).toEqual('🧑‍⚖️'); + expect(emojify('👨‍⚖️')).toEqual('👨‍⚖️'); + expect(emojify('👩‍⚖️')).toEqual('👩‍⚖️'); + expect(emojify('🧑‍🌾')).toEqual('🧑‍🌾'); + expect(emojify('👨‍🌾')).toEqual('👨‍🌾'); + expect(emojify('👩‍🌾')).toEqual('👩‍🌾'); + expect(emojify('🧑‍🍳')).toEqual('🧑‍🍳'); + expect(emojify('👨‍🍳')).toEqual('👨‍🍳'); + expect(emojify('👩‍🍳')).toEqual('👩‍🍳'); + expect(emojify('🧑‍🔧')).toEqual('🧑‍🔧'); + expect(emojify('👨‍🔧')).toEqual('👨‍🔧'); + expect(emojify('👩‍🔧')).toEqual('👩‍🔧'); + expect(emojify('🧑‍🏭')).toEqual('🧑‍🏭'); + expect(emojify('👨‍🏭')).toEqual('👨‍🏭'); + expect(emojify('👩‍🏭')).toEqual('👩‍🏭'); + expect(emojify('🧑‍💼')).toEqual('🧑‍💼'); + expect(emojify('👨‍💼')).toEqual('👨‍💼'); + expect(emojify('👩‍💼')).toEqual('👩‍💼'); + expect(emojify('🧑‍🔬')).toEqual('🧑‍🔬'); + expect(emojify('👨‍🔬')).toEqual('👨‍🔬'); + expect(emojify('👩‍🔬')).toEqual('👩‍🔬'); + expect(emojify('🧑‍💻')).toEqual('🧑‍💻'); + expect(emojify('👨‍💻')).toEqual('👨‍💻'); + expect(emojify('👩‍💻')).toEqual('👩‍💻'); + expect(emojify('🧑‍🎤')).toEqual('🧑‍🎤'); + expect(emojify('👨‍🎤')).toEqual('👨‍🎤'); + expect(emojify('👩‍🎤')).toEqual('👩‍🎤'); + expect(emojify('🧑‍🎨')).toEqual('🧑‍🎨'); + expect(emojify('👨‍🎨')).toEqual('👨‍🎨'); + expect(emojify('👩‍🎨')).toEqual('👩‍🎨'); + expect(emojify('🧑‍✈️')).toEqual('🧑‍✈️'); + expect(emojify('👨‍✈️')).toEqual('👨‍✈️'); + expect(emojify('👩‍✈️')).toEqual('👩‍✈️'); + expect(emojify('🧑‍🚀')).toEqual('🧑‍🚀'); + expect(emojify('👨‍🚀')).toEqual('👨‍🚀'); + expect(emojify('👩‍🚀')).toEqual('👩‍🚀'); + expect(emojify('🧑‍🚒')).toEqual('🧑‍🚒'); + expect(emojify('👨‍🚒')).toEqual('👨‍🚒'); + expect(emojify('👩‍🚒')).toEqual('👩‍🚒'); + expect(emojify('👮')).toEqual('👮'); + expect(emojify('👮‍♂️')).toEqual('👮‍♂️'); + expect(emojify('👮‍♀️')).toEqual('👮‍♀️'); + expect(emojify('🕵️')).toEqual('🕵️'); + expect(emojify('🕵️‍♂️')).toEqual('🕵️‍♂️'); + expect(emojify('🕵️‍♀️')).toEqual('🕵️‍♀️'); + expect(emojify('💂')).toEqual('💂'); + expect(emojify('💂‍♂️')).toEqual('💂‍♂️'); + expect(emojify('💂‍♀️')).toEqual('💂‍♀️'); + expect(emojify('🥷')).toEqual('🥷'); + expect(emojify('👷')).toEqual('👷'); + expect(emojify('👷‍♂️')).toEqual('👷‍♂️'); + expect(emojify('👷‍♀️')).toEqual('👷‍♀️'); + expect(emojify('🫅')).toEqual('🫅'); + expect(emojify('🤴')).toEqual('🤴'); + expect(emojify('👸')).toEqual('👸'); + expect(emojify('👳')).toEqual('👳'); + expect(emojify('👳‍♂️')).toEqual('👳‍♂️'); + expect(emojify('👳‍♀️')).toEqual('👳‍♀️'); + expect(emojify('👲')).toEqual('👲'); + expect(emojify('🧕')).toEqual('🧕'); + expect(emojify('🤵')).toEqual('🤵'); + expect(emojify('🤵‍♂️')).toEqual('🤵‍♂️'); + expect(emojify('🤵‍♀️')).toEqual('🤵‍♀️'); + expect(emojify('👰')).toEqual('👰'); + expect(emojify('👰‍♂️')).toEqual('👰‍♂️'); + expect(emojify('👰‍♀️')).toEqual('👰‍♀️'); + expect(emojify('🤰')).toEqual('🤰'); + expect(emojify('🫃')).toEqual('🫃'); + expect(emojify('🫄')).toEqual('🫄'); + expect(emojify('🤱')).toEqual('🤱'); + expect(emojify('👩‍🍼')).toEqual('👩‍🍼'); + expect(emojify('👨‍🍼')).toEqual('👨‍🍼'); + expect(emojify('🧑‍🍼')).toEqual('🧑‍🍼'); + expect(emojify('👼')).toEqual('👼'); + expect(emojify('🎅')).toEqual('🎅'); + expect(emojify('🤶')).toEqual('🤶'); + expect(emojify('🧑‍🎄')).toEqual('🧑‍🎄'); + expect(emojify('🦸')).toEqual('🦸'); + expect(emojify('🦸‍♂️')).toEqual('🦸‍♂️'); + expect(emojify('🦸‍♀️')).toEqual('🦸‍♀️'); + expect(emojify('🦹')).toEqual('🦹'); + expect(emojify('🦹‍♂️')).toEqual('🦹‍♂️'); + expect(emojify('🦹‍♀️')).toEqual('🦹‍♀️'); + expect(emojify('🧙')).toEqual('🧙'); + expect(emojify('🧙‍♂️')).toEqual('🧙‍♂️'); + expect(emojify('🧙‍♀️')).toEqual('🧙‍♀️'); + expect(emojify('🧚')).toEqual('🧚'); + expect(emojify('🧚‍♂️')).toEqual('🧚‍♂️'); + expect(emojify('🧚‍♀️')).toEqual('🧚‍♀️'); + expect(emojify('🧛')).toEqual('🧛'); + expect(emojify('🧛‍♂️')).toEqual('🧛‍♂️'); + expect(emojify('🧛‍♀️')).toEqual('🧛‍♀️'); + expect(emojify('🧜')).toEqual('🧜'); + expect(emojify('🧜‍♂️')).toEqual('🧜‍♂️'); + expect(emojify('🧜‍♀️')).toEqual('🧜‍♀️'); + expect(emojify('🧝')).toEqual('🧝'); + expect(emojify('🧝‍♂️')).toEqual('🧝‍♂️'); + expect(emojify('🧝‍♀️')).toEqual('🧝‍♀️'); + expect(emojify('🧞')).toEqual('🧞'); + expect(emojify('🧞‍♂️')).toEqual('🧞‍♂️'); + expect(emojify('🧞‍♀️')).toEqual('🧞‍♀️'); + expect(emojify('🧟')).toEqual('🧟'); + expect(emojify('🧟‍♂️')).toEqual('🧟‍♂️'); + expect(emojify('🧟‍♀️')).toEqual('🧟‍♀️'); + expect(emojify('🧌')).toEqual('🧌'); + expect(emojify('💆')).toEqual('💆'); + expect(emojify('💆‍♂️')).toEqual('💆‍♂️'); + expect(emojify('💆‍♀️')).toEqual('💆‍♀️'); + expect(emojify('💇')).toEqual('💇'); + expect(emojify('💇‍♂️')).toEqual('💇‍♂️'); + expect(emojify('💇‍♀️')).toEqual('💇‍♀️'); + expect(emojify('🚶')).toEqual('🚶'); + expect(emojify('🚶‍♂️')).toEqual('🚶‍♂️'); + expect(emojify('🚶‍♀️')).toEqual('🚶‍♀️'); + expect(emojify('🧍')).toEqual('🧍'); + expect(emojify('🧍‍♂️')).toEqual('🧍‍♂️'); + expect(emojify('🧍‍♀️')).toEqual('🧍‍♀️'); + expect(emojify('🧎')).toEqual('🧎'); + expect(emojify('🧎‍♂️')).toEqual('🧎‍♂️'); + expect(emojify('🧎‍♀️')).toEqual('🧎‍♀️'); + expect(emojify('🧑‍🦯')).toEqual('🧑‍🦯'); + expect(emojify('👨‍🦯')).toEqual('👨‍🦯'); + expect(emojify('👩‍🦯')).toEqual('👩‍🦯'); + expect(emojify('🧑‍🦼')).toEqual('🧑‍🦼'); + expect(emojify('👨‍🦼')).toEqual('👨‍🦼'); + expect(emojify('👩‍🦼')).toEqual('👩‍🦼'); + expect(emojify('🧑‍🦽')).toEqual('🧑‍🦽'); + expect(emojify('👨‍🦽')).toEqual('👨‍🦽'); + expect(emojify('👩‍🦽')).toEqual('👩‍🦽'); + expect(emojify('🏃')).toEqual('🏃'); + expect(emojify('🏃‍♂️')).toEqual('🏃‍♂️'); + expect(emojify('🏃‍♀️')).toEqual('🏃‍♀️'); + expect(emojify('💃')).toEqual('💃'); + expect(emojify('🕺')).toEqual('🕺'); + expect(emojify('🕴️')).toEqual('🕴️'); + expect(emojify('👯')).toEqual('👯'); + expect(emojify('👯‍♂️')).toEqual('👯‍♂️'); + expect(emojify('👯‍♀️')).toEqual('👯‍♀️'); + expect(emojify('🧖')).toEqual('🧖'); + expect(emojify('🧖‍♂️')).toEqual('🧖‍♂️'); + expect(emojify('🧖‍♀️')).toEqual('🧖‍♀️'); + expect(emojify('🧗')).toEqual('🧗'); + expect(emojify('🧗‍♂️')).toEqual('🧗‍♂️'); + expect(emojify('🧗‍♀️')).toEqual('🧗‍♀️'); + expect(emojify('🤺')).toEqual('🤺'); + expect(emojify('🏇')).toEqual('🏇'); + expect(emojify('⛷️')).toEqual('⛷️'); + expect(emojify('🏂')).toEqual('🏂'); + expect(emojify('🏌️')).toEqual('🏌️'); + expect(emojify('🏌️‍♂️')).toEqual('🏌️‍♂️'); + expect(emojify('🏌️‍♀️')).toEqual('🏌️‍♀️'); + expect(emojify('🏄')).toEqual('🏄'); + expect(emojify('🏄‍♂️')).toEqual('🏄‍♂️'); + expect(emojify('🏄‍♀️')).toEqual('🏄‍♀️'); + expect(emojify('🚣')).toEqual('🚣'); + expect(emojify('🚣‍♂️')).toEqual('🚣‍♂️'); + expect(emojify('🚣‍♀️')).toEqual('🚣‍♀️'); + expect(emojify('🏊')).toEqual('🏊'); + expect(emojify('🏊‍♂️')).toEqual('🏊‍♂️'); + expect(emojify('🏊‍♀️')).toEqual('🏊‍♀️'); + expect(emojify('⛹️')).toEqual('⛹️'); + expect(emojify('⛹️‍♂️')).toEqual('⛹️‍♂️'); + expect(emojify('⛹️‍♀️')).toEqual('⛹️‍♀️'); + expect(emojify('🏋️')).toEqual('🏋️'); + expect(emojify('🏋️‍♂️')).toEqual('🏋️‍♂️'); + expect(emojify('🏋️‍♀️')).toEqual('🏋️‍♀️'); + expect(emojify('🚴')).toEqual('🚴'); + expect(emojify('🚴‍♂️')).toEqual('🚴‍♂️'); + expect(emojify('🚴‍♀️')).toEqual('🚴‍♀️'); + expect(emojify('🚵')).toEqual('🚵'); + expect(emojify('🚵‍♂️')).toEqual('🚵‍♂️'); + expect(emojify('🚵‍♀️')).toEqual('🚵‍♀️'); + expect(emojify('🤸')).toEqual('🤸'); + expect(emojify('🤸‍♂️')).toEqual('🤸‍♂️'); + expect(emojify('🤸‍♀️')).toEqual('🤸‍♀️'); + expect(emojify('🤼')).toEqual('🤼'); + expect(emojify('🤼‍♂️')).toEqual('🤼‍♂️'); + expect(emojify('🤼‍♀️')).toEqual('🤼‍♀️'); + expect(emojify('🤽')).toEqual('🤽'); + expect(emojify('🤽‍♂️')).toEqual('🤽‍♂️'); + expect(emojify('🤽‍♀️')).toEqual('🤽‍♀️'); + expect(emojify('🤾')).toEqual('🤾'); + expect(emojify('🤾‍♂️')).toEqual('🤾‍♂️'); + expect(emojify('🤾‍♀️')).toEqual('🤾‍♀️'); + expect(emojify('🤹')).toEqual('🤹'); + expect(emojify('🤹‍♂️')).toEqual('🤹‍♂️'); + expect(emojify('🤹‍♀️')).toEqual('🤹‍♀️'); + expect(emojify('🧘')).toEqual('🧘'); + expect(emojify('🧘‍♂️')).toEqual('🧘‍♂️'); + expect(emojify('🧘‍♀️')).toEqual('🧘‍♀️'); + expect(emojify('🛀')).toEqual('🛀'); + expect(emojify('🛌')).toEqual('🛌'); + expect(emojify('🧑‍🤝‍🧑')).toEqual('🧑‍🤝‍🧑'); + expect(emojify('👭')).toEqual('👭'); + expect(emojify('👫')).toEqual('👫'); + expect(emojify('👬')).toEqual('👬'); + expect(emojify('💏')).toEqual('💏'); + expect(emojify('👩‍❤️‍💋‍👨')).toEqual('👩‍❤️‍💋‍👨'); + expect(emojify('👨‍❤️‍💋‍👨')).toEqual('👨‍❤️‍💋‍👨'); + expect(emojify('👩‍❤️‍💋‍👩')).toEqual('👩‍❤️‍💋‍👩'); + expect(emojify('💑')).toEqual('💑'); + expect(emojify('👩‍❤️‍👨')).toEqual('👩‍❤️‍👨'); + expect(emojify('👨‍❤️‍👨')).toEqual('👨‍❤️‍👨'); + expect(emojify('👩‍❤️‍👩')).toEqual('👩‍❤️‍👩'); + expect(emojify('👪')).toEqual('👪'); + expect(emojify('👨‍👩‍👦')).toEqual('👨‍👩‍👦'); + expect(emojify('👨‍👩‍👧')).toEqual('👨‍👩‍👧'); + expect(emojify('👨‍👩‍👧‍👦')).toEqual('👨‍👩‍👧‍👦'); + expect(emojify('👨‍👩‍👦‍👦')).toEqual('👨‍👩‍👦‍👦'); + expect(emojify('👨‍👩‍👧‍👧')).toEqual('👨‍👩‍👧‍👧'); + expect(emojify('👨‍👨‍👦')).toEqual('👨‍👨‍👦'); + expect(emojify('👨‍👨‍👧')).toEqual('👨‍👨‍👧'); + expect(emojify('👨‍👨‍👧‍👦')).toEqual('👨‍👨‍👧‍👦'); + expect(emojify('👨‍👨‍👦‍👦')).toEqual('👨‍👨‍👦‍👦'); + expect(emojify('👨‍👨‍👧‍👧')).toEqual('👨‍👨‍👧‍👧'); + expect(emojify('👩‍👩‍👦')).toEqual('👩‍👩‍👦'); + expect(emojify('👩‍👩‍👧')).toEqual('👩‍👩‍👧'); + expect(emojify('👩‍👩‍👧‍👦')).toEqual('👩‍👩‍👧‍👦'); + expect(emojify('👩‍👩‍👦‍👦')).toEqual('👩‍👩‍👦‍👦'); + expect(emojify('👩‍👩‍👧‍👧')).toEqual('👩‍👩‍👧‍👧'); + expect(emojify('👨‍👦')).toEqual('👨‍👦'); + expect(emojify('👨‍👦‍👦')).toEqual('👨‍👦‍👦'); + expect(emojify('👨‍👧')).toEqual('👨‍👧'); + expect(emojify('👨‍👧‍👦')).toEqual('👨‍👧‍👦'); + expect(emojify('👨‍👧‍👧')).toEqual('👨‍👧‍👧'); + expect(emojify('👩‍👦')).toEqual('👩‍👦'); + expect(emojify('👩‍👦‍👦')).toEqual('👩‍👦‍👦'); + expect(emojify('👩‍👧')).toEqual('👩‍👧'); + expect(emojify('👩‍👧‍👦')).toEqual('👩‍👧‍👦'); + expect(emojify('👩‍👧‍👧')).toEqual('👩‍👧‍👧'); + expect(emojify('🗣️')).toEqual('🗣️'); + expect(emojify('👤')).toEqual('👤'); + expect(emojify('👥')).toEqual('👥'); + expect(emojify('🫂')).toEqual('🫂'); + expect(emojify('👣')).toEqual('👣'); + expect(emojify('🐵')).toEqual('🐵'); + expect(emojify('🐒')).toEqual('🐒'); + expect(emojify('🦍')).toEqual('🦍'); + expect(emojify('🦧')).toEqual('🦧'); + expect(emojify('🐶')).toEqual('🐶'); + expect(emojify('🐕')).toEqual('🐕'); + expect(emojify('🦮')).toEqual('🦮'); + expect(emojify('🐕‍🦺')).toEqual('🐕‍🦺'); + expect(emojify('🐩')).toEqual('🐩'); + expect(emojify('🐺')).toEqual('🐺'); + expect(emojify('🦊')).toEqual('🦊'); + expect(emojify('🦝')).toEqual('🦝'); + expect(emojify('🐱')).toEqual('🐱'); + expect(emojify('🐈')).toEqual('🐈'); + expect(emojify('🐈‍⬛')).toEqual('🐈‍⬛'); + expect(emojify('🦁')).toEqual('🦁'); + expect(emojify('🐯')).toEqual('🐯'); + expect(emojify('🐅')).toEqual('🐅'); + expect(emojify('🐆')).toEqual('🐆'); + expect(emojify('🐴')).toEqual('🐴'); + expect(emojify('🐎')).toEqual('🐎'); + expect(emojify('🦄')).toEqual('🦄'); + expect(emojify('🦓')).toEqual('🦓'); + expect(emojify('🦌')).toEqual('🦌'); + expect(emojify('🦬')).toEqual('🦬'); + expect(emojify('🐮')).toEqual('🐮'); + expect(emojify('🐂')).toEqual('🐂'); + expect(emojify('🐃')).toEqual('🐃'); + expect(emojify('🐄')).toEqual('🐄'); + expect(emojify('🐷')).toEqual('🐷'); + expect(emojify('🐖')).toEqual('🐖'); + expect(emojify('🐗')).toEqual('🐗'); + expect(emojify('🐽')).toEqual('🐽'); + expect(emojify('🐏')).toEqual('🐏'); + expect(emojify('🐑')).toEqual('🐑'); + expect(emojify('🐐')).toEqual('🐐'); + expect(emojify('🐪')).toEqual('🐪'); + expect(emojify('🐫')).toEqual('🐫'); + expect(emojify('🦙')).toEqual('🦙'); + expect(emojify('🦒')).toEqual('🦒'); + expect(emojify('🐘')).toEqual('🐘'); + expect(emojify('🦣')).toEqual('🦣'); + expect(emojify('🦏')).toEqual('🦏'); + expect(emojify('🦛')).toEqual('🦛'); + expect(emojify('🐭')).toEqual('🐭'); + expect(emojify('🐁')).toEqual('🐁'); + expect(emojify('🐀')).toEqual('🐀'); + expect(emojify('🐹')).toEqual('🐹'); + expect(emojify('🐰')).toEqual('🐰'); + expect(emojify('🐇')).toEqual('🐇'); + expect(emojify('🐿️')).toEqual('🐿️'); + expect(emojify('🦫')).toEqual('🦫'); + expect(emojify('🦔')).toEqual('🦔'); + expect(emojify('🦇')).toEqual('🦇'); + expect(emojify('🐻')).toEqual('🐻'); + expect(emojify('🐻‍❄️')).toEqual('🐻‍❄️'); + expect(emojify('🐨')).toEqual('🐨'); + expect(emojify('🐼')).toEqual('🐼'); + expect(emojify('🦥')).toEqual('🦥'); + expect(emojify('🦦')).toEqual('🦦'); + expect(emojify('🦨')).toEqual('🦨'); + expect(emojify('🦘')).toEqual('🦘'); + expect(emojify('🦡')).toEqual('🦡'); + expect(emojify('🐾')).toEqual('🐾'); + expect(emojify('🦃')).toEqual('🦃'); + expect(emojify('🐔')).toEqual('🐔'); + expect(emojify('🐓')).toEqual('🐓'); + expect(emojify('🐣')).toEqual('🐣'); + expect(emojify('🐤')).toEqual('🐤'); + expect(emojify('🐥')).toEqual('🐥'); + expect(emojify('🐦')).toEqual('🐦'); + expect(emojify('🐧')).toEqual('🐧'); + expect(emojify('🕊️')).toEqual('🕊️'); + expect(emojify('🦅')).toEqual('🦅'); + expect(emojify('🦆')).toEqual('🦆'); + expect(emojify('🦢')).toEqual('🦢'); + expect(emojify('🦉')).toEqual('🦉'); + expect(emojify('🦤')).toEqual('🦤'); + expect(emojify('🪶')).toEqual('🪶'); + expect(emojify('🦩')).toEqual('🦩'); + expect(emojify('🦚')).toEqual('🦚'); + expect(emojify('🦜')).toEqual('🦜'); + expect(emojify('🐸')).toEqual('🐸'); + expect(emojify('🐊')).toEqual('🐊'); + expect(emojify('🐢')).toEqual('🐢'); + expect(emojify('🦎')).toEqual('🦎'); + expect(emojify('🐍')).toEqual('🐍'); + expect(emojify('🐲')).toEqual('🐲'); + expect(emojify('🐉')).toEqual('🐉'); + expect(emojify('🦕')).toEqual('🦕'); + expect(emojify('🦖')).toEqual('🦖'); + expect(emojify('🐳')).toEqual('🐳'); + expect(emojify('🐋')).toEqual('🐋'); + expect(emojify('🐬')).toEqual('🐬'); + expect(emojify('🦭')).toEqual('🦭'); + expect(emojify('🐟')).toEqual('🐟'); + expect(emojify('🐠')).toEqual('🐠'); + expect(emojify('🐡')).toEqual('🐡'); + expect(emojify('🦈')).toEqual('🦈'); + expect(emojify('🐙')).toEqual('🐙'); + expect(emojify('🐚')).toEqual('🐚'); + expect(emojify('🪸')).toEqual('🪸'); + expect(emojify('🐌')).toEqual('🐌'); + expect(emojify('🦋')).toEqual('🦋'); + expect(emojify('🐛')).toEqual('🐛'); + expect(emojify('🐜')).toEqual('🐜'); + expect(emojify('🐝')).toEqual('🐝'); + expect(emojify('🪲')).toEqual('🪲'); + expect(emojify('🐞')).toEqual('🐞'); + expect(emojify('🦗')).toEqual('🦗'); + expect(emojify('🪳')).toEqual('🪳'); + expect(emojify('🕷️')).toEqual('🕷️'); + expect(emojify('🕸️')).toEqual('🕸️'); + expect(emojify('🦂')).toEqual('🦂'); + expect(emojify('🦟')).toEqual('🦟'); + expect(emojify('🪰')).toEqual('🪰'); + expect(emojify('🪱')).toEqual('🪱'); + expect(emojify('🦠')).toEqual('🦠'); + expect(emojify('💐')).toEqual('💐'); + expect(emojify('🌸')).toEqual('🌸'); + expect(emojify('💮')).toEqual('💮'); + expect(emojify('🪷')).toEqual('🪷'); + expect(emojify('🏵️')).toEqual('🏵️'); + expect(emojify('🌹')).toEqual('🌹'); + expect(emojify('🥀')).toEqual('🥀'); + expect(emojify('🌺')).toEqual('🌺'); + expect(emojify('🌻')).toEqual('🌻'); + expect(emojify('🌼')).toEqual('🌼'); + expect(emojify('🌷')).toEqual('🌷'); + expect(emojify('🌱')).toEqual('🌱'); + expect(emojify('🪴')).toEqual('🪴'); + expect(emojify('🌲')).toEqual('🌲'); + expect(emojify('🌳')).toEqual('🌳'); + expect(emojify('🌴')).toEqual('🌴'); + expect(emojify('🌵')).toEqual('🌵'); + expect(emojify('🌾')).toEqual('🌾'); + expect(emojify('🌿')).toEqual('🌿'); + expect(emojify('☘️')).toEqual('☘️'); + expect(emojify('🍀')).toEqual('🍀'); + expect(emojify('🍁')).toEqual('🍁'); + expect(emojify('🍂')).toEqual('🍂'); + expect(emojify('🍃')).toEqual('🍃'); + expect(emojify('🪹')).toEqual('🪹'); + expect(emojify('🪺')).toEqual('🪺'); + expect(emojify('🍇')).toEqual('🍇'); + expect(emojify('🍈')).toEqual('🍈'); + expect(emojify('🍉')).toEqual('🍉'); + expect(emojify('🍊')).toEqual('🍊'); + expect(emojify('🍋')).toEqual('🍋'); + expect(emojify('🍌')).toEqual('🍌'); + expect(emojify('🍍')).toEqual('🍍'); + expect(emojify('🥭')).toEqual('🥭'); + expect(emojify('🍎')).toEqual('🍎'); + expect(emojify('🍏')).toEqual('🍏'); + expect(emojify('🍐')).toEqual('🍐'); + expect(emojify('🍑')).toEqual('🍑'); + expect(emojify('🍒')).toEqual('🍒'); + expect(emojify('🍓')).toEqual('🍓'); + expect(emojify('🫐')).toEqual('🫐'); + expect(emojify('🥝')).toEqual('🥝'); + expect(emojify('🍅')).toEqual('🍅'); + expect(emojify('🫒')).toEqual('🫒'); + expect(emojify('🥥')).toEqual('🥥'); + expect(emojify('🥑')).toEqual('🥑'); + expect(emojify('🍆')).toEqual('🍆'); + expect(emojify('🥔')).toEqual('🥔'); + expect(emojify('🥕')).toEqual('🥕'); + expect(emojify('🌽')).toEqual('🌽'); + expect(emojify('🌶️')).toEqual('🌶️'); + expect(emojify('🫑')).toEqual('🫑'); + expect(emojify('🥒')).toEqual('🥒'); + expect(emojify('🥬')).toEqual('🥬'); + expect(emojify('🥦')).toEqual('🥦'); + expect(emojify('🧄')).toEqual('🧄'); + expect(emojify('🧅')).toEqual('🧅'); + expect(emojify('🍄')).toEqual('🍄'); + expect(emojify('🥜')).toEqual('🥜'); + expect(emojify('🫘')).toEqual('🫘'); + expect(emojify('🌰')).toEqual('🌰'); + expect(emojify('🍞')).toEqual('🍞'); + expect(emojify('🥐')).toEqual('🥐'); + expect(emojify('🥖')).toEqual('🥖'); + expect(emojify('🫓')).toEqual('🫓'); + expect(emojify('🥨')).toEqual('🥨'); + expect(emojify('🥯')).toEqual('🥯'); + expect(emojify('🥞')).toEqual('🥞'); + expect(emojify('🧇')).toEqual('🧇'); + expect(emojify('🧀')).toEqual('🧀'); + expect(emojify('🍖')).toEqual('🍖'); + expect(emojify('🍗')).toEqual('🍗'); + expect(emojify('🥩')).toEqual('🥩'); + expect(emojify('🥓')).toEqual('🥓'); + expect(emojify('🍔')).toEqual('🍔'); + expect(emojify('🍟')).toEqual('🍟'); + expect(emojify('🍕')).toEqual('🍕'); + expect(emojify('🌭')).toEqual('🌭'); + expect(emojify('🥪')).toEqual('🥪'); + expect(emojify('🌮')).toEqual('🌮'); + expect(emojify('🌯')).toEqual('🌯'); + expect(emojify('🫔')).toEqual('🫔'); + expect(emojify('🥙')).toEqual('🥙'); + expect(emojify('🧆')).toEqual('🧆'); + expect(emojify('🥚')).toEqual('🥚'); + expect(emojify('🍳')).toEqual('🍳'); + expect(emojify('🥘')).toEqual('🥘'); + expect(emojify('🍲')).toEqual('🍲'); + expect(emojify('🫕')).toEqual('🫕'); + expect(emojify('🥣')).toEqual('🥣'); + expect(emojify('🥗')).toEqual('🥗'); + expect(emojify('🍿')).toEqual('🍿'); + expect(emojify('🧈')).toEqual('🧈'); + expect(emojify('🧂')).toEqual('🧂'); + expect(emojify('🥫')).toEqual('🥫'); + expect(emojify('🍱')).toEqual('🍱'); + expect(emojify('🍘')).toEqual('🍘'); + expect(emojify('🍙')).toEqual('🍙'); + expect(emojify('🍚')).toEqual('🍚'); + expect(emojify('🍛')).toEqual('🍛'); + expect(emojify('🍜')).toEqual('🍜'); + expect(emojify('🍝')).toEqual('🍝'); + expect(emojify('🍠')).toEqual('🍠'); + expect(emojify('🍢')).toEqual('🍢'); + expect(emojify('🍣')).toEqual('🍣'); + expect(emojify('🍤')).toEqual('🍤'); + expect(emojify('🍥')).toEqual('🍥'); + expect(emojify('🥮')).toEqual('🥮'); + expect(emojify('🍡')).toEqual('🍡'); + expect(emojify('🥟')).toEqual('🥟'); + expect(emojify('🥠')).toEqual('🥠'); + expect(emojify('🥡')).toEqual('🥡'); + expect(emojify('🦀')).toEqual('🦀'); + expect(emojify('🦞')).toEqual('🦞'); + expect(emojify('🦐')).toEqual('🦐'); + expect(emojify('🦑')).toEqual('🦑'); + expect(emojify('🦪')).toEqual('🦪'); + expect(emojify('🍦')).toEqual('🍦'); + expect(emojify('🍧')).toEqual('🍧'); + expect(emojify('🍨')).toEqual('🍨'); + expect(emojify('🍩')).toEqual('🍩'); + expect(emojify('🍪')).toEqual('🍪'); + expect(emojify('🎂')).toEqual('🎂'); + expect(emojify('🍰')).toEqual('🍰'); + expect(emojify('🧁')).toEqual('🧁'); + expect(emojify('🥧')).toEqual('🥧'); + expect(emojify('🍫')).toEqual('🍫'); + expect(emojify('🍬')).toEqual('🍬'); + expect(emojify('🍭')).toEqual('🍭'); + expect(emojify('🍮')).toEqual('🍮'); + expect(emojify('🍯')).toEqual('🍯'); + expect(emojify('🍼')).toEqual('🍼'); + expect(emojify('🥛')).toEqual('🥛'); + expect(emojify('☕')).toEqual('☕'); + expect(emojify('🫖')).toEqual('🫖'); + expect(emojify('🍵')).toEqual('🍵'); + expect(emojify('🍶')).toEqual('🍶'); + expect(emojify('🍾')).toEqual('🍾'); + expect(emojify('🍷')).toEqual('🍷'); + expect(emojify('🍸')).toEqual('🍸'); + expect(emojify('🍹')).toEqual('🍹'); + expect(emojify('🍺')).toEqual('🍺'); + expect(emojify('🍻')).toEqual('🍻'); + expect(emojify('🥂')).toEqual('🥂'); + expect(emojify('🥃')).toEqual('🥃'); + expect(emojify('🫗')).toEqual('🫗'); + expect(emojify('🥤')).toEqual('🥤'); + expect(emojify('🧋')).toEqual('🧋'); + expect(emojify('🧃')).toEqual('🧃'); + expect(emojify('🧉')).toEqual('🧉'); + expect(emojify('🧊')).toEqual('🧊'); + expect(emojify('🥢')).toEqual('🥢'); + expect(emojify('🍽️')).toEqual('🍽️'); + expect(emojify('🍴')).toEqual('🍴'); + expect(emojify('🥄')).toEqual('🥄'); + expect(emojify('🔪')).toEqual('🔪'); + expect(emojify('🫙')).toEqual('🫙'); + expect(emojify('🏺')).toEqual('🏺'); + expect(emojify('🌍')).toEqual('🌍'); + expect(emojify('🌎')).toEqual('🌎'); + expect(emojify('🌏')).toEqual('🌏'); + expect(emojify('🌐')).toEqual('🌐'); + expect(emojify('🗺️')).toEqual('🗺️'); + expect(emojify('🗾')).toEqual('🗾'); + expect(emojify('🧭')).toEqual('🧭'); + expect(emojify('🏔️')).toEqual('🏔️'); + expect(emojify('⛰️')).toEqual('⛰️'); + expect(emojify('🌋')).toEqual('🌋'); + expect(emojify('🗻')).toEqual('🗻'); + expect(emojify('🏕️')).toEqual('🏕️'); + expect(emojify('🏖️')).toEqual('🏖️'); + expect(emojify('🏜️')).toEqual('🏜️'); + expect(emojify('🏝️')).toEqual('🏝️'); + expect(emojify('🏞️')).toEqual('🏞️'); + expect(emojify('🏟️')).toEqual('🏟️'); + expect(emojify('🏛️')).toEqual('🏛️'); + expect(emojify('🏗️')).toEqual('🏗️'); + expect(emojify('🧱')).toEqual('🧱'); + expect(emojify('🪨')).toEqual('🪨'); + expect(emojify('🪵')).toEqual('🪵'); + expect(emojify('🛖')).toEqual('🛖'); + expect(emojify('🏘️')).toEqual('🏘️'); + expect(emojify('🏚️')).toEqual('🏚️'); + expect(emojify('🏠')).toEqual('🏠'); + expect(emojify('🏡')).toEqual('🏡'); + expect(emojify('🏢')).toEqual('🏢'); + expect(emojify('🏣')).toEqual('🏣'); + expect(emojify('🏤')).toEqual('🏤'); + expect(emojify('🏥')).toEqual('🏥'); + expect(emojify('🏦')).toEqual('🏦'); + expect(emojify('🏨')).toEqual('🏨'); + expect(emojify('🏩')).toEqual('🏩'); + expect(emojify('🏪')).toEqual('🏪'); + expect(emojify('🏫')).toEqual('🏫'); + expect(emojify('🏬')).toEqual('🏬'); + expect(emojify('🏭')).toEqual('🏭'); + expect(emojify('🏯')).toEqual('🏯'); + expect(emojify('🏰')).toEqual('🏰'); + expect(emojify('💒')).toEqual('💒'); + expect(emojify('🗼')).toEqual('🗼'); + expect(emojify('🗽')).toEqual('🗽'); + expect(emojify('⛪')).toEqual('⛪'); + expect(emojify('🕌')).toEqual('🕌'); + expect(emojify('🛕')).toEqual('🛕'); + expect(emojify('🕍')).toEqual('🕍'); + expect(emojify('⛩️')).toEqual('⛩️'); + expect(emojify('🕋')).toEqual('🕋'); + expect(emojify('⛲')).toEqual('⛲'); + expect(emojify('⛺')).toEqual('⛺'); + expect(emojify('🌁')).toEqual('🌁'); + expect(emojify('🌃')).toEqual('🌃'); + expect(emojify('🏙️')).toEqual('🏙️'); + expect(emojify('🌄')).toEqual('🌄'); + expect(emojify('🌅')).toEqual('🌅'); + expect(emojify('🌆')).toEqual('🌆'); + expect(emojify('🌇')).toEqual('🌇'); + expect(emojify('🌉')).toEqual('🌉'); + expect(emojify('♨️')).toEqual('♨️'); + expect(emojify('🎠')).toEqual('🎠'); + expect(emojify('🛝')).toEqual('🛝'); + expect(emojify('🎡')).toEqual('🎡'); + expect(emojify('🎢')).toEqual('🎢'); + expect(emojify('💈')).toEqual('💈'); + expect(emojify('🎪')).toEqual('🎪'); + expect(emojify('🚂')).toEqual('🚂'); + expect(emojify('🚃')).toEqual('🚃'); + expect(emojify('🚄')).toEqual('🚄'); + expect(emojify('🚅')).toEqual('🚅'); + expect(emojify('🚆')).toEqual('🚆'); + expect(emojify('🚇')).toEqual('🚇'); + expect(emojify('🚈')).toEqual('🚈'); + expect(emojify('🚉')).toEqual('🚉'); + expect(emojify('🚊')).toEqual('🚊'); + expect(emojify('🚝')).toEqual('🚝'); + expect(emojify('🚞')).toEqual('🚞'); + expect(emojify('🚋')).toEqual('🚋'); + expect(emojify('🚌')).toEqual('🚌'); + expect(emojify('🚍')).toEqual('🚍'); + expect(emojify('🚎')).toEqual('🚎'); + expect(emojify('🚐')).toEqual('🚐'); + expect(emojify('🚑')).toEqual('🚑'); + expect(emojify('🚒')).toEqual('🚒'); + expect(emojify('🚓')).toEqual('🚓'); + expect(emojify('🚔')).toEqual('🚔'); + expect(emojify('🚕')).toEqual('🚕'); + expect(emojify('🚖')).toEqual('🚖'); + expect(emojify('🚗')).toEqual('🚗'); + expect(emojify('🚘')).toEqual('🚘'); + expect(emojify('🚙')).toEqual('🚙'); + expect(emojify('🛻')).toEqual('🛻'); + expect(emojify('🚚')).toEqual('🚚'); + expect(emojify('🚛')).toEqual('🚛'); + expect(emojify('🚜')).toEqual('🚜'); + expect(emojify('🏎️')).toEqual('🏎️'); + expect(emojify('🏍️')).toEqual('🏍️'); + expect(emojify('🛵')).toEqual('🛵'); + expect(emojify('🦽')).toEqual('🦽'); + expect(emojify('🦼')).toEqual('🦼'); + expect(emojify('🛺')).toEqual('🛺'); + expect(emojify('🚲')).toEqual('🚲'); + expect(emojify('🛴')).toEqual('🛴'); + expect(emojify('🛹')).toEqual('🛹'); + expect(emojify('🛼')).toEqual('🛼'); + expect(emojify('🚏')).toEqual('🚏'); + expect(emojify('🛣️')).toEqual('🛣️'); + expect(emojify('🛤️')).toEqual('🛤️'); + expect(emojify('🛢️')).toEqual('🛢️'); + expect(emojify('⛽')).toEqual('⛽'); + expect(emojify('🛞')).toEqual('🛞'); + expect(emojify('🚨')).toEqual('🚨'); + expect(emojify('🚥')).toEqual('🚥'); + expect(emojify('🚦')).toEqual('🚦'); + expect(emojify('🛑')).toEqual('🛑'); + expect(emojify('🚧')).toEqual('🚧'); + expect(emojify('⚓')).toEqual('⚓'); + expect(emojify('🛟')).toEqual('🛟'); + expect(emojify('⛵')).toEqual('⛵'); + expect(emojify('🛶')).toEqual('🛶'); + expect(emojify('🚤')).toEqual('🚤'); + expect(emojify('🛳️')).toEqual('🛳️'); + expect(emojify('⛴️')).toEqual('⛴️'); + expect(emojify('🛥️')).toEqual('🛥️'); + expect(emojify('🚢')).toEqual('🚢'); + expect(emojify('✈️')).toEqual('✈️'); + expect(emojify('🛩️')).toEqual('🛩️'); + expect(emojify('🛫')).toEqual('🛫'); + expect(emojify('🛬')).toEqual('🛬'); + expect(emojify('🪂')).toEqual('🪂'); + expect(emojify('💺')).toEqual('💺'); + expect(emojify('🚁')).toEqual('🚁'); + expect(emojify('🚟')).toEqual('🚟'); + expect(emojify('🚠')).toEqual('🚠'); + expect(emojify('🚡')).toEqual('🚡'); + expect(emojify('🛰️')).toEqual('🛰️'); + expect(emojify('🚀')).toEqual('🚀'); + expect(emojify('🛸')).toEqual('🛸'); + expect(emojify('🛎️')).toEqual('🛎️'); + expect(emojify('🧳')).toEqual('🧳'); + expect(emojify('⌛')).toEqual('⌛'); + expect(emojify('⏳')).toEqual('⏳'); + expect(emojify('⌚')).toEqual('⌚'); + expect(emojify('⏰')).toEqual('⏰'); + expect(emojify('⏱️')).toEqual('⏱️'); + expect(emojify('⏲️')).toEqual('⏲️'); + expect(emojify('🕰️')).toEqual('🕰️'); + expect(emojify('🕛')).toEqual('🕛'); + expect(emojify('🕧')).toEqual('🕧'); + expect(emojify('🕐')).toEqual('🕐'); + expect(emojify('🕜')).toEqual('🕜'); + expect(emojify('🕑')).toEqual('🕑'); + expect(emojify('🕝')).toEqual('🕝'); + expect(emojify('🕒')).toEqual('🕒'); + expect(emojify('🕞')).toEqual('🕞'); + expect(emojify('🕓')).toEqual('🕓'); + expect(emojify('🕟')).toEqual('🕟'); + expect(emojify('🕔')).toEqual('🕔'); + expect(emojify('🕠')).toEqual('🕠'); + expect(emojify('🕕')).toEqual('🕕'); + expect(emojify('🕡')).toEqual('🕡'); + expect(emojify('🕖')).toEqual('🕖'); + expect(emojify('🕢')).toEqual('🕢'); + expect(emojify('🕗')).toEqual('🕗'); + expect(emojify('🕣')).toEqual('🕣'); + expect(emojify('🕘')).toEqual('🕘'); + expect(emojify('🕤')).toEqual('🕤'); + expect(emojify('🕙')).toEqual('🕙'); + expect(emojify('🕥')).toEqual('🕥'); + expect(emojify('🕚')).toEqual('🕚'); + expect(emojify('🕦')).toEqual('🕦'); + expect(emojify('🌑')).toEqual('🌑'); + expect(emojify('🌒')).toEqual('🌒'); + expect(emojify('🌓')).toEqual('🌓'); + expect(emojify('🌔')).toEqual('🌔'); + expect(emojify('🌕')).toEqual('🌕'); + expect(emojify('🌖')).toEqual('🌖'); + expect(emojify('🌗')).toEqual('🌗'); + expect(emojify('🌘')).toEqual('🌘'); + expect(emojify('🌙')).toEqual('🌙'); + expect(emojify('🌚')).toEqual('🌚'); + expect(emojify('🌛')).toEqual('🌛'); + expect(emojify('🌜')).toEqual('🌜'); + expect(emojify('🌡️')).toEqual('🌡️'); + expect(emojify('☀️')).toEqual('☀️'); + expect(emojify('🌝')).toEqual('🌝'); + expect(emojify('🌞')).toEqual('🌞'); + expect(emojify('🪐')).toEqual('🪐'); + expect(emojify('⭐')).toEqual('⭐'); + expect(emojify('🌟')).toEqual('🌟'); + expect(emojify('🌠')).toEqual('🌠'); + expect(emojify('🌌')).toEqual('🌌'); + expect(emojify('☁️')).toEqual('☁️'); + expect(emojify('⛅')).toEqual('⛅'); + expect(emojify('⛈️')).toEqual('⛈️'); + expect(emojify('🌤️')).toEqual('🌤️'); + expect(emojify('🌥️')).toEqual('🌥️'); + expect(emojify('🌦️')).toEqual('🌦️'); + expect(emojify('🌧️')).toEqual('🌧️'); + expect(emojify('🌨️')).toEqual('🌨️'); + expect(emojify('🌩️')).toEqual('🌩️'); + expect(emojify('🌪️')).toEqual('🌪️'); + expect(emojify('🌫️')).toEqual('🌫️'); + expect(emojify('🌬️')).toEqual('🌬️'); + expect(emojify('🌀')).toEqual('🌀'); + expect(emojify('🌈')).toEqual('🌈'); + expect(emojify('🌂')).toEqual('🌂'); + expect(emojify('☂️')).toEqual('☂️'); + expect(emojify('☔')).toEqual('☔'); + expect(emojify('⛱️')).toEqual('⛱️'); + expect(emojify('⚡')).toEqual('⚡'); + expect(emojify('❄️')).toEqual('❄️'); + expect(emojify('☃️')).toEqual('☃️'); + expect(emojify('⛄')).toEqual('⛄'); + expect(emojify('☄️')).toEqual('☄️'); + expect(emojify('🔥')).toEqual('🔥'); + expect(emojify('💧')).toEqual('💧'); + expect(emojify('🌊')).toEqual('🌊'); + expect(emojify('🎃')).toEqual('🎃'); + expect(emojify('🎄')).toEqual('🎄'); + expect(emojify('🎆')).toEqual('🎆'); + expect(emojify('🎇')).toEqual('🎇'); + expect(emojify('🧨')).toEqual('🧨'); + expect(emojify('✨')).toEqual('✨'); + expect(emojify('🎈')).toEqual('🎈'); + expect(emojify('🎉')).toEqual('🎉'); + expect(emojify('🎊')).toEqual('🎊'); + expect(emojify('🎋')).toEqual('🎋'); + expect(emojify('🎍')).toEqual('🎍'); + expect(emojify('🎎')).toEqual('🎎'); + expect(emojify('🎏')).toEqual('🎏'); + expect(emojify('🎐')).toEqual('🎐'); + expect(emojify('🎑')).toEqual('🎑'); + expect(emojify('🧧')).toEqual('🧧'); + expect(emojify('🎀')).toEqual('🎀'); + expect(emojify('🎁')).toEqual('🎁'); + expect(emojify('🎗️')).toEqual('🎗️'); + expect(emojify('🎟️')).toEqual('🎟️'); + expect(emojify('🎫')).toEqual('🎫'); + expect(emojify('🎖️')).toEqual('🎖️'); + expect(emojify('🏆')).toEqual('🏆'); + expect(emojify('🏅')).toEqual('🏅'); + expect(emojify('🥇')).toEqual('🥇'); + expect(emojify('🥈')).toEqual('🥈'); + expect(emojify('🥉')).toEqual('🥉'); + expect(emojify('⚽')).toEqual('⚽'); + expect(emojify('⚾')).toEqual('⚾'); + expect(emojify('🥎')).toEqual('🥎'); + expect(emojify('🏀')).toEqual('🏀'); + expect(emojify('🏐')).toEqual('🏐'); + expect(emojify('🏈')).toEqual('🏈'); + expect(emojify('🏉')).toEqual('🏉'); + expect(emojify('🎾')).toEqual('🎾'); + expect(emojify('🥏')).toEqual('🥏'); + expect(emojify('🎳')).toEqual('🎳'); + expect(emojify('🏏')).toEqual('🏏'); + expect(emojify('🏑')).toEqual('🏑'); + expect(emojify('🏒')).toEqual('🏒'); + expect(emojify('🥍')).toEqual('🥍'); + expect(emojify('🏓')).toEqual('🏓'); + expect(emojify('🏸')).toEqual('🏸'); + expect(emojify('🥊')).toEqual('🥊'); + expect(emojify('🥋')).toEqual('🥋'); + expect(emojify('🥅')).toEqual('🥅'); + expect(emojify('⛳')).toEqual('⛳'); + expect(emojify('⛸️')).toEqual('⛸️'); + expect(emojify('🎣')).toEqual('🎣'); + expect(emojify('🤿')).toEqual('🤿'); + expect(emojify('🎽')).toEqual('🎽'); + expect(emojify('🎿')).toEqual('🎿'); + expect(emojify('🛷')).toEqual('🛷'); + expect(emojify('🥌')).toEqual('🥌'); + expect(emojify('🎯')).toEqual('🎯'); + expect(emojify('🪀')).toEqual('🪀'); + expect(emojify('🪁')).toEqual('🪁'); + expect(emojify('🎱')).toEqual('🎱'); + expect(emojify('🔮')).toEqual('🔮'); + expect(emojify('🪄')).toEqual('🪄'); + expect(emojify('🧿')).toEqual('🧿'); + expect(emojify('🪬')).toEqual('🪬'); + expect(emojify('🎮')).toEqual('🎮'); + expect(emojify('🕹️')).toEqual('🕹️'); + expect(emojify('🎰')).toEqual('🎰'); + expect(emojify('🎲')).toEqual('🎲'); + expect(emojify('🧩')).toEqual('🧩'); + expect(emojify('🧸')).toEqual('🧸'); + expect(emojify('🪅')).toEqual('🪅'); + expect(emojify('🪩')).toEqual('🪩'); + expect(emojify('🪆')).toEqual('🪆'); + expect(emojify('♠️')).toEqual('♠️'); + expect(emojify('♥️')).toEqual('♥️'); + expect(emojify('♦️')).toEqual('♦️'); + expect(emojify('♣️')).toEqual('♣️'); + expect(emojify('♟️')).toEqual('♟️'); + expect(emojify('🃏')).toEqual('🃏'); + expect(emojify('🀄')).toEqual('🀄'); + expect(emojify('🎴')).toEqual('🎴'); + expect(emojify('🎭')).toEqual('🎭'); + expect(emojify('🖼️')).toEqual('🖼️'); + expect(emojify('🎨')).toEqual('🎨'); + expect(emojify('🧵')).toEqual('🧵'); + expect(emojify('🪡')).toEqual('🪡'); + expect(emojify('🧶')).toEqual('🧶'); + expect(emojify('🪢')).toEqual('🪢'); + expect(emojify('👓')).toEqual('👓'); + expect(emojify('🕶️')).toEqual('🕶️'); + expect(emojify('🥽')).toEqual('🥽'); + expect(emojify('🥼')).toEqual('🥼'); + expect(emojify('🦺')).toEqual('🦺'); + expect(emojify('👔')).toEqual('👔'); + expect(emojify('👕')).toEqual('👕'); + expect(emojify('👖')).toEqual('👖'); + expect(emojify('🧣')).toEqual('🧣'); + expect(emojify('🧤')).toEqual('🧤'); + expect(emojify('🧥')).toEqual('🧥'); + expect(emojify('🧦')).toEqual('🧦'); + expect(emojify('👗')).toEqual('👗'); + expect(emojify('👘')).toEqual('👘'); + expect(emojify('🥻')).toEqual('🥻'); + expect(emojify('🩱')).toEqual('🩱'); + expect(emojify('🩲')).toEqual('🩲'); + expect(emojify('🩳')).toEqual('🩳'); + expect(emojify('👙')).toEqual('👙'); + expect(emojify('👚')).toEqual('👚'); + expect(emojify('👛')).toEqual('👛'); + expect(emojify('👜')).toEqual('👜'); + expect(emojify('👝')).toEqual('👝'); + expect(emojify('🛍️')).toEqual('🛍️'); + expect(emojify('🎒')).toEqual('🎒'); + expect(emojify('🩴')).toEqual('🩴'); + expect(emojify('👞')).toEqual('👞'); + expect(emojify('👟')).toEqual('👟'); + expect(emojify('🥾')).toEqual('🥾'); + expect(emojify('🥿')).toEqual('🥿'); + expect(emojify('👠')).toEqual('👠'); + expect(emojify('👡')).toEqual('👡'); + expect(emojify('🩰')).toEqual('🩰'); + expect(emojify('👢')).toEqual('👢'); + expect(emojify('👑')).toEqual('👑'); + expect(emojify('👒')).toEqual('👒'); + expect(emojify('🎩')).toEqual('🎩'); + expect(emojify('🎓')).toEqual('🎓'); + expect(emojify('🧢')).toEqual('🧢'); + expect(emojify('🪖')).toEqual('🪖'); + expect(emojify('⛑️')).toEqual('⛑️'); + expect(emojify('📿')).toEqual('📿'); + expect(emojify('💄')).toEqual('💄'); + expect(emojify('💍')).toEqual('💍'); + expect(emojify('💎')).toEqual('💎'); + expect(emojify('🔇')).toEqual('🔇'); + expect(emojify('🔈')).toEqual('🔈'); + expect(emojify('🔉')).toEqual('🔉'); + expect(emojify('🔊')).toEqual('🔊'); + expect(emojify('📢')).toEqual('📢'); + expect(emojify('📣')).toEqual('📣'); + expect(emojify('📯')).toEqual('📯'); + expect(emojify('🔔')).toEqual('🔔'); + expect(emojify('🔕')).toEqual('🔕'); + expect(emojify('🎼')).toEqual('🎼'); + expect(emojify('🎵')).toEqual('🎵'); + expect(emojify('🎶')).toEqual('🎶'); + expect(emojify('🎙️')).toEqual('🎙️'); + expect(emojify('🎚️')).toEqual('🎚️'); + expect(emojify('🎛️')).toEqual('🎛️'); + expect(emojify('🎤')).toEqual('🎤'); + expect(emojify('🎧')).toEqual('🎧'); + expect(emojify('📻')).toEqual('📻'); + expect(emojify('🎷')).toEqual('🎷'); + expect(emojify('🪗')).toEqual('🪗'); + expect(emojify('🎸')).toEqual('🎸'); + expect(emojify('🎹')).toEqual('🎹'); + expect(emojify('🎺')).toEqual('🎺'); + expect(emojify('🎻')).toEqual('🎻'); + expect(emojify('🪕')).toEqual('🪕'); + expect(emojify('🥁')).toEqual('🥁'); + expect(emojify('🪘')).toEqual('🪘'); + expect(emojify('📱')).toEqual('📱'); + expect(emojify('📲')).toEqual('📲'); + expect(emojify('☎️')).toEqual('☎️'); + expect(emojify('📞')).toEqual('📞'); + expect(emojify('📟')).toEqual('📟'); + expect(emojify('📠')).toEqual('📠'); + expect(emojify('🔋')).toEqual('🔋'); + expect(emojify('🪫')).toEqual('🪫'); + expect(emojify('🔌')).toEqual('🔌'); + expect(emojify('💻')).toEqual('💻'); + expect(emojify('🖥️')).toEqual('🖥️'); + expect(emojify('🖨️')).toEqual('🖨️'); + expect(emojify('⌨️')).toEqual('⌨️'); + expect(emojify('🖱️')).toEqual('🖱️'); + expect(emojify('🖲️')).toEqual('🖲️'); + expect(emojify('💽')).toEqual('💽'); + expect(emojify('💾')).toEqual('💾'); + expect(emojify('💿')).toEqual('💿'); + expect(emojify('📀')).toEqual('📀'); + expect(emojify('🧮')).toEqual('🧮'); + expect(emojify('🎥')).toEqual('🎥'); + expect(emojify('🎞️')).toEqual('🎞️'); + expect(emojify('📽️')).toEqual('📽️'); + expect(emojify('🎬')).toEqual('🎬'); + expect(emojify('📺')).toEqual('📺'); + expect(emojify('📷')).toEqual('📷'); + expect(emojify('📸')).toEqual('📸'); + expect(emojify('📹')).toEqual('📹'); + expect(emojify('📼')).toEqual('📼'); + expect(emojify('🔍')).toEqual('🔍'); + expect(emojify('🔎')).toEqual('🔎'); + expect(emojify('🕯️')).toEqual('🕯️'); + expect(emojify('💡')).toEqual('💡'); + expect(emojify('🔦')).toEqual('🔦'); + expect(emojify('🏮')).toEqual('🏮'); + expect(emojify('🪔')).toEqual('🪔'); + expect(emojify('📔')).toEqual('📔'); + expect(emojify('📕')).toEqual('📕'); + expect(emojify('📖')).toEqual('📖'); + expect(emojify('📗')).toEqual('📗'); + expect(emojify('📘')).toEqual('📘'); + expect(emojify('📙')).toEqual('📙'); + expect(emojify('📚')).toEqual('📚'); + expect(emojify('📓')).toEqual('📓'); + expect(emojify('📒')).toEqual('📒'); + expect(emojify('📃')).toEqual('📃'); + expect(emojify('📜')).toEqual('📜'); + expect(emojify('📄')).toEqual('📄'); + expect(emojify('📰')).toEqual('📰'); + expect(emojify('🗞️')).toEqual('🗞️'); + expect(emojify('📑')).toEqual('📑'); + expect(emojify('🔖')).toEqual('🔖'); + expect(emojify('🏷️')).toEqual('🏷️'); + expect(emojify('💰')).toEqual('💰'); + expect(emojify('🪙')).toEqual('🪙'); + expect(emojify('💴')).toEqual('💴'); + expect(emojify('💵')).toEqual('💵'); + expect(emojify('💶')).toEqual('💶'); + expect(emojify('💷')).toEqual('💷'); + expect(emojify('💸')).toEqual('💸'); + expect(emojify('💳')).toEqual('💳'); + expect(emojify('🧾')).toEqual('🧾'); + expect(emojify('💹')).toEqual('💹'); + expect(emojify('✉️')).toEqual('✉️'); + expect(emojify('📧')).toEqual('📧'); + expect(emojify('📨')).toEqual('📨'); + expect(emojify('📩')).toEqual('📩'); + expect(emojify('📤')).toEqual('📤'); + expect(emojify('📥')).toEqual('📥'); + expect(emojify('📦')).toEqual('📦'); + expect(emojify('📫')).toEqual('📫'); + expect(emojify('📪')).toEqual('📪'); + expect(emojify('📬')).toEqual('📬'); + expect(emojify('📭')).toEqual('📭'); + expect(emojify('📮')).toEqual('📮'); + expect(emojify('🗳️')).toEqual('🗳️'); + expect(emojify('✏️')).toEqual('✏️'); + expect(emojify('✒️')).toEqual('✒️'); + expect(emojify('🖋️')).toEqual('🖋️'); + expect(emojify('🖊️')).toEqual('🖊️'); + expect(emojify('🖌️')).toEqual('🖌️'); + expect(emojify('🖍️')).toEqual('🖍️'); + expect(emojify('📝')).toEqual('📝'); + expect(emojify('💼')).toEqual('💼'); + expect(emojify('📁')).toEqual('📁'); + expect(emojify('📂')).toEqual('📂'); + expect(emojify('🗂️')).toEqual('🗂️'); + expect(emojify('📅')).toEqual('📅'); + expect(emojify('📆')).toEqual('📆'); + expect(emojify('🗒️')).toEqual('🗒️'); + expect(emojify('🗓️')).toEqual('🗓️'); + expect(emojify('📇')).toEqual('📇'); + expect(emojify('📈')).toEqual('📈'); + expect(emojify('📉')).toEqual('📉'); + expect(emojify('📊')).toEqual('📊'); + expect(emojify('📋')).toEqual('📋'); + expect(emojify('📌')).toEqual('📌'); + expect(emojify('📍')).toEqual('📍'); + expect(emojify('📎')).toEqual('📎'); + expect(emojify('🖇️')).toEqual('🖇️'); + expect(emojify('📏')).toEqual('📏'); + expect(emojify('📐')).toEqual('📐'); + expect(emojify('✂️')).toEqual('✂️'); + expect(emojify('🗃️')).toEqual('🗃️'); + expect(emojify('🗄️')).toEqual('🗄️'); + expect(emojify('🗑️')).toEqual('🗑️'); + expect(emojify('🔒')).toEqual('🔒'); + expect(emojify('🔓')).toEqual('🔓'); + expect(emojify('🔏')).toEqual('🔏'); + expect(emojify('🔐')).toEqual('🔐'); + expect(emojify('🔑')).toEqual('🔑'); + expect(emojify('🗝️')).toEqual('🗝️'); + expect(emojify('🔨')).toEqual('🔨'); + expect(emojify('🪓')).toEqual('🪓'); + expect(emojify('⛏️')).toEqual('⛏️'); + expect(emojify('⚒️')).toEqual('⚒️'); + expect(emojify('🛠️')).toEqual('🛠️'); + expect(emojify('🗡️')).toEqual('🗡️'); + expect(emojify('⚔️')).toEqual('⚔️'); + expect(emojify('🔫')).toEqual('🔫'); + expect(emojify('🪃')).toEqual('🪃'); + expect(emojify('🏹')).toEqual('🏹'); + expect(emojify('🛡️')).toEqual('🛡️'); + expect(emojify('🪚')).toEqual('🪚'); + expect(emojify('🔧')).toEqual('🔧'); + expect(emojify('🪛')).toEqual('🪛'); + expect(emojify('🔩')).toEqual('🔩'); + expect(emojify('⚙️')).toEqual('⚙️'); + expect(emojify('🗜️')).toEqual('🗜️'); + expect(emojify('⚖️')).toEqual('⚖️'); + expect(emojify('🦯')).toEqual('🦯'); + expect(emojify('🔗')).toEqual('🔗'); + expect(emojify('⛓️')).toEqual('⛓️'); + expect(emojify('🪝')).toEqual('🪝'); + expect(emojify('🧰')).toEqual('🧰'); + expect(emojify('🧲')).toEqual('🧲'); + expect(emojify('🪜')).toEqual('🪜'); + expect(emojify('⚗️')).toEqual('⚗️'); + expect(emojify('🧪')).toEqual('🧪'); + expect(emojify('🧫')).toEqual('🧫'); + expect(emojify('🧬')).toEqual('🧬'); + expect(emojify('🔬')).toEqual('🔬'); + expect(emojify('🔭')).toEqual('🔭'); + expect(emojify('📡')).toEqual('📡'); + expect(emojify('💉')).toEqual('💉'); + expect(emojify('🩸')).toEqual('🩸'); + expect(emojify('💊')).toEqual('💊'); + expect(emojify('🩹')).toEqual('🩹'); + expect(emojify('🩼')).toEqual('🩼'); + expect(emojify('🩺')).toEqual('🩺'); + expect(emojify('🩻')).toEqual('🩻'); + expect(emojify('🚪')).toEqual('🚪'); + expect(emojify('🛗')).toEqual('🛗'); + expect(emojify('🪞')).toEqual('🪞'); + expect(emojify('🪟')).toEqual('🪟'); + expect(emojify('🛏️')).toEqual('🛏️'); + expect(emojify('🛋️')).toEqual('🛋️'); + expect(emojify('🪑')).toEqual('🪑'); + expect(emojify('🚽')).toEqual('🚽'); + expect(emojify('🪠')).toEqual('🪠'); + expect(emojify('🚿')).toEqual('🚿'); + expect(emojify('🛁')).toEqual('🛁'); + expect(emojify('🪤')).toEqual('🪤'); + expect(emojify('🪒')).toEqual('🪒'); + expect(emojify('🧴')).toEqual('🧴'); + expect(emojify('🧷')).toEqual('🧷'); + expect(emojify('🧹')).toEqual('🧹'); + expect(emojify('🧺')).toEqual('🧺'); + expect(emojify('🧻')).toEqual('🧻'); + expect(emojify('🪣')).toEqual('🪣'); + expect(emojify('🧼')).toEqual('🧼'); + expect(emojify('🫧')).toEqual('🫧'); + expect(emojify('🪥')).toEqual('🪥'); + expect(emojify('🧽')).toEqual('🧽'); + expect(emojify('🧯')).toEqual('🧯'); + expect(emojify('🛒')).toEqual('🛒'); + expect(emojify('🚬')).toEqual('🚬'); + expect(emojify('⚰️')).toEqual('⚰️'); + expect(emojify('🪦')).toEqual('🪦'); + expect(emojify('⚱️')).toEqual('⚱️'); + expect(emojify('🗿')).toEqual('🗿'); + expect(emojify('🪧')).toEqual('🪧'); + expect(emojify('🪪')).toEqual('🪪'); + expect(emojify('🏧')).toEqual('🏧'); + expect(emojify('🚮')).toEqual('🚮'); + expect(emojify('🚰')).toEqual('🚰'); + expect(emojify('♿')).toEqual('♿'); + expect(emojify('🚹')).toEqual('🚹'); + expect(emojify('🚺')).toEqual('🚺'); + expect(emojify('🚻')).toEqual('🚻'); + expect(emojify('🚼')).toEqual('🚼'); + expect(emojify('🚾')).toEqual('🚾'); + expect(emojify('🛂')).toEqual('🛂'); + expect(emojify('🛃')).toEqual('🛃'); + expect(emojify('🛄')).toEqual('🛄'); + expect(emojify('🛅')).toEqual('🛅'); + expect(emojify('⚠️')).toEqual('⚠️'); + expect(emojify('🚸')).toEqual('🚸'); + expect(emojify('⛔')).toEqual('⛔'); + expect(emojify('🚫')).toEqual('🚫'); + expect(emojify('🚳')).toEqual('🚳'); + expect(emojify('🚭')).toEqual('🚭'); + expect(emojify('🚯')).toEqual('🚯'); + expect(emojify('🚱')).toEqual('🚱'); + expect(emojify('🚷')).toEqual('🚷'); + expect(emojify('📵')).toEqual('📵'); + expect(emojify('🔞')).toEqual('🔞'); + expect(emojify('☢️')).toEqual('☢️'); + expect(emojify('☣️')).toEqual('☣️'); + expect(emojify('⬆️')).toEqual('⬆️'); + expect(emojify('↗️')).toEqual('↗️'); + expect(emojify('➡️')).toEqual('➡️'); + expect(emojify('↘️')).toEqual('↘️'); + expect(emojify('⬇️')).toEqual('⬇️'); + expect(emojify('↙️')).toEqual('↙️'); + expect(emojify('⬅️')).toEqual('⬅️'); + expect(emojify('↖️')).toEqual('↖️'); + expect(emojify('↕️')).toEqual('↕️'); + expect(emojify('↔️')).toEqual('↔️'); + expect(emojify('↩️')).toEqual('↩️'); + expect(emojify('↪️')).toEqual('↪️'); + expect(emojify('⤴️')).toEqual('⤴️'); + expect(emojify('⤵️')).toEqual('⤵️'); + expect(emojify('🔃')).toEqual('🔃'); + expect(emojify('🔄')).toEqual('🔄'); + expect(emojify('🔙')).toEqual('🔙'); + expect(emojify('🔚')).toEqual('🔚'); + expect(emojify('🔛')).toEqual('🔛'); + expect(emojify('🔜')).toEqual('🔜'); + expect(emojify('🔝')).toEqual('🔝'); + expect(emojify('🛐')).toEqual('🛐'); + expect(emojify('⚛️')).toEqual('⚛️'); + expect(emojify('🕉️')).toEqual('🕉️'); + expect(emojify('✡️')).toEqual('✡️'); + expect(emojify('☸️')).toEqual('☸️'); + expect(emojify('☯️')).toEqual('☯️'); + expect(emojify('✝️')).toEqual('✝️'); + expect(emojify('☦️')).toEqual('☦️'); + expect(emojify('☪️')).toEqual('☪️'); + expect(emojify('☮️')).toEqual('☮️'); + expect(emojify('🕎')).toEqual('🕎'); + expect(emojify('🔯')).toEqual('🔯'); + expect(emojify('♈')).toEqual('♈'); + expect(emojify('♉')).toEqual('♉'); + expect(emojify('♊')).toEqual('♊'); + expect(emojify('♋')).toEqual('♋'); + expect(emojify('♌')).toEqual('♌'); + expect(emojify('♍')).toEqual('♍'); + expect(emojify('♎')).toEqual('♎'); + expect(emojify('♏')).toEqual('♏'); + expect(emojify('♐')).toEqual('♐'); + expect(emojify('♑')).toEqual('♑'); + expect(emojify('♒')).toEqual('♒'); + expect(emojify('♓')).toEqual('♓'); + expect(emojify('⛎')).toEqual('⛎'); + expect(emojify('🔀')).toEqual('🔀'); + expect(emojify('🔁')).toEqual('🔁'); + expect(emojify('🔂')).toEqual('🔂'); + expect(emojify('▶️')).toEqual('▶️'); + expect(emojify('⏩')).toEqual('⏩'); + expect(emojify('⏭️')).toEqual('⏭️'); + expect(emojify('⏯️')).toEqual('⏯️'); + expect(emojify('◀️')).toEqual('◀️'); + expect(emojify('⏪')).toEqual('⏪'); + expect(emojify('⏮️')).toEqual('⏮️'); + expect(emojify('🔼')).toEqual('🔼'); + expect(emojify('⏫')).toEqual('⏫'); + expect(emojify('🔽')).toEqual('🔽'); + expect(emojify('⏬')).toEqual('⏬'); + expect(emojify('⏸️')).toEqual('⏸️'); + expect(emojify('⏹️')).toEqual('⏹️'); + expect(emojify('⏺️')).toEqual('⏺️'); + expect(emojify('⏏️')).toEqual('⏏️'); + expect(emojify('🎦')).toEqual('🎦'); + expect(emojify('🔅')).toEqual('🔅'); + expect(emojify('🔆')).toEqual('🔆'); + expect(emojify('📶')).toEqual('📶'); + expect(emojify('📳')).toEqual('📳'); + expect(emojify('📴')).toEqual('📴'); + expect(emojify('♀️')).toEqual('♀️'); + expect(emojify('♂️')).toEqual('♂️'); + expect(emojify('⚧️')).toEqual('⚧️'); + expect(emojify('✖️')).toEqual('✖️'); + expect(emojify('➕')).toEqual('➕'); + expect(emojify('➖')).toEqual('➖'); + expect(emojify('➗')).toEqual('➗'); + expect(emojify('🟰')).toEqual('🟰'); + expect(emojify('♾️')).toEqual('♾️'); + expect(emojify('‼️')).toEqual('‼️'); + expect(emojify('⁉️')).toEqual('⁉️'); + expect(emojify('❓')).toEqual('❓'); + expect(emojify('❔')).toEqual('❔'); + expect(emojify('❕')).toEqual('❕'); + expect(emojify('❗')).toEqual('❗'); + expect(emojify('〰️')).toEqual('〰️'); + expect(emojify('💱')).toEqual('💱'); + expect(emojify('💲')).toEqual('💲'); + expect(emojify('⚕️')).toEqual('⚕️'); + expect(emojify('♻️')).toEqual('♻️'); + expect(emojify('⚜️')).toEqual('⚜️'); + expect(emojify('🔱')).toEqual('🔱'); + expect(emojify('📛')).toEqual('📛'); + expect(emojify('🔰')).toEqual('🔰'); + expect(emojify('⭕')).toEqual('⭕'); + expect(emojify('✅')).toEqual('✅'); + expect(emojify('☑️')).toEqual('☑️'); + expect(emojify('✔️')).toEqual('✔️'); + expect(emojify('❌')).toEqual('❌'); + expect(emojify('❎')).toEqual('❎'); + expect(emojify('➰')).toEqual('➰'); + expect(emojify('➿')).toEqual('➿'); + expect(emojify('〽️')).toEqual('〽️'); + expect(emojify('✳️')).toEqual('✳️'); + expect(emojify('✴️')).toEqual('✴️'); + expect(emojify('❇️')).toEqual('❇️'); + expect(emojify('©️')).toEqual('©️'); + expect(emojify('®️')).toEqual('®️'); + expect(emojify('™️')).toEqual('™️'); + expect(emojify('#️⃣')).toEqual('#️⃣'); + expect(emojify('*️⃣')).toEqual('*️⃣'); + expect(emojify('0️⃣')).toEqual('0️⃣'); + expect(emojify('1️⃣')).toEqual('1️⃣'); + expect(emojify('2️⃣')).toEqual('2️⃣'); + expect(emojify('3️⃣')).toEqual('3️⃣'); + expect(emojify('4️⃣')).toEqual('4️⃣'); + expect(emojify('5️⃣')).toEqual('5️⃣'); + expect(emojify('6️⃣')).toEqual('6️⃣'); + expect(emojify('7️⃣')).toEqual('7️⃣'); + expect(emojify('8️⃣')).toEqual('8️⃣'); + expect(emojify('9️⃣')).toEqual('9️⃣'); + expect(emojify('🔟')).toEqual('🔟'); + expect(emojify('🔠')).toEqual('🔠'); + expect(emojify('🔡')).toEqual('🔡'); + expect(emojify('🔣')).toEqual('🔣'); + expect(emojify('🔤')).toEqual('🔤'); + expect(emojify('🅰️')).toEqual('🅰️'); + expect(emojify('🆎')).toEqual('🆎'); + expect(emojify('🅱️')).toEqual('🅱️'); + expect(emojify('🆑')).toEqual('🆑'); + expect(emojify('🆒')).toEqual('🆒'); + expect(emojify('🆓')).toEqual('🆓'); + expect(emojify('ℹ️')).toEqual('ℹ️'); + expect(emojify('🆔')).toEqual('🆔'); + expect(emojify('Ⓜ️')).toEqual('Ⓜ️'); + expect(emojify('🆕')).toEqual('🆕'); + expect(emojify('🆖')).toEqual('🆖'); + expect(emojify('🅾️')).toEqual('🅾️'); + expect(emojify('🆗')).toEqual('🆗'); + expect(emojify('🅿️')).toEqual('🅿️'); + expect(emojify('🆘')).toEqual('🆘'); + expect(emojify('🆙')).toEqual('🆙'); + expect(emojify('🆚')).toEqual('🆚'); + expect(emojify('🈁')).toEqual('🈁'); + expect(emojify('🈂️')).toEqual('🈂️'); + expect(emojify('🈷️')).toEqual('🈷️'); + expect(emojify('🈶')).toEqual('🈶'); + expect(emojify('🈯')).toEqual('🈯'); + expect(emojify('🉐')).toEqual('🉐'); + expect(emojify('🈹')).toEqual('🈹'); + expect(emojify('🈚')).toEqual('🈚'); + expect(emojify('🈲')).toEqual('🈲'); + expect(emojify('🉑')).toEqual('🉑'); + expect(emojify('🈸')).toEqual('🈸'); + expect(emojify('🈴')).toEqual('🈴'); + expect(emojify('🈳')).toEqual('🈳'); + expect(emojify('㊗️')).toEqual('㊗️'); + expect(emojify('㊙️')).toEqual('㊙️'); + expect(emojify('🈺')).toEqual('🈺'); + expect(emojify('🈵')).toEqual('🈵'); + expect(emojify('🔴')).toEqual('🔴'); + expect(emojify('🟠')).toEqual('🟠'); + expect(emojify('🟡')).toEqual('🟡'); + expect(emojify('🟢')).toEqual('🟢'); + expect(emojify('🔵')).toEqual('🔵'); + expect(emojify('🟣')).toEqual('🟣'); + expect(emojify('🟤')).toEqual('🟤'); + expect(emojify('⚫')).toEqual('⚫'); + expect(emojify('⚪')).toEqual('⚪'); + expect(emojify('🟥')).toEqual('🟥'); + expect(emojify('🟧')).toEqual('🟧'); + expect(emojify('🟨')).toEqual('🟨'); + expect(emojify('🟩')).toEqual('🟩'); + expect(emojify('🟦')).toEqual('🟦'); + expect(emojify('🟪')).toEqual('🟪'); + expect(emojify('🟫')).toEqual('🟫'); + expect(emojify('⬛')).toEqual('⬛'); + expect(emojify('⬜')).toEqual('⬜'); + expect(emojify('◼️')).toEqual('◼️'); + expect(emojify('◻️')).toEqual('◻️'); + expect(emojify('◾')).toEqual('◾'); + expect(emojify('◽')).toEqual('◽'); + expect(emojify('▪️')).toEqual('▪️'); + expect(emojify('▫️')).toEqual('▫️'); + expect(emojify('🔶')).toEqual('🔶'); + expect(emojify('🔷')).toEqual('🔷'); + expect(emojify('🔸')).toEqual('🔸'); + expect(emojify('🔹')).toEqual('🔹'); + expect(emojify('🔺')).toEqual('🔺'); + expect(emojify('🔻')).toEqual('🔻'); + expect(emojify('💠')).toEqual('💠'); + expect(emojify('🔘')).toEqual('🔘'); + expect(emojify('🔳')).toEqual('🔳'); + expect(emojify('🔲')).toEqual('🔲'); + expect(emojify('🏁')).toEqual('🏁'); + expect(emojify('🚩')).toEqual('🚩'); + expect(emojify('🎌')).toEqual('🎌'); + expect(emojify('🏴')).toEqual('🏴'); + expect(emojify('🏳️')).toEqual('🏳️'); + expect(emojify('🏳️‍🌈')).toEqual('🏳️‍🌈'); + expect(emojify('🏳️‍⚧️')).toEqual('🏳️‍⚧️'); + expect(emojify('🏴‍☠️')).toEqual('🏴‍☠️'); + expect(emojify('🇦🇨')).toEqual('🇦🇨'); + expect(emojify('🇦🇩')).toEqual('🇦🇩'); + expect(emojify('🇦🇪')).toEqual('🇦🇪'); + expect(emojify('🇦🇫')).toEqual('🇦🇫'); + expect(emojify('🇦🇬')).toEqual('🇦🇬'); + expect(emojify('🇦🇮')).toEqual('🇦🇮'); + expect(emojify('🇦🇱')).toEqual('🇦🇱'); + expect(emojify('🇦🇲')).toEqual('🇦🇲'); + expect(emojify('🇦🇴')).toEqual('🇦🇴'); + expect(emojify('🇦🇶')).toEqual('🇦🇶'); + expect(emojify('🇦🇷')).toEqual('🇦🇷'); + expect(emojify('🇦🇸')).toEqual('🇦🇸'); + expect(emojify('🇦🇹')).toEqual('🇦🇹'); + expect(emojify('🇦🇺')).toEqual('🇦🇺'); + expect(emojify('🇦🇼')).toEqual('🇦🇼'); + expect(emojify('🇦🇽')).toEqual('🇦🇽'); + expect(emojify('🇦🇿')).toEqual('🇦🇿'); + expect(emojify('🇧🇦')).toEqual('🇧🇦'); + expect(emojify('🇧🇧')).toEqual('🇧🇧'); + expect(emojify('🇧🇩')).toEqual('🇧🇩'); + expect(emojify('🇧🇪')).toEqual('🇧🇪'); + expect(emojify('🇧🇫')).toEqual('🇧🇫'); + expect(emojify('🇧🇬')).toEqual('🇧🇬'); + expect(emojify('🇧🇭')).toEqual('🇧🇭'); + expect(emojify('🇧🇮')).toEqual('🇧🇮'); + expect(emojify('🇧🇯')).toEqual('🇧🇯'); + expect(emojify('🇧🇱')).toEqual('🇧🇱'); + expect(emojify('🇧🇲')).toEqual('🇧🇲'); + expect(emojify('🇧🇳')).toEqual('🇧🇳'); + expect(emojify('🇧🇴')).toEqual('🇧🇴'); + expect(emojify('🇧🇶')).toEqual('🇧🇶'); + expect(emojify('🇧🇷')).toEqual('🇧🇷'); + expect(emojify('🇧🇸')).toEqual('🇧🇸'); + expect(emojify('🇧🇹')).toEqual('🇧🇹'); + expect(emojify('🇧🇻')).toEqual('🇧🇻'); + expect(emojify('🇧🇼')).toEqual('🇧🇼'); + expect(emojify('🇧🇾')).toEqual('🇧🇾'); + expect(emojify('🇧🇿')).toEqual('🇧🇿'); + expect(emojify('🇨🇦')).toEqual('🇨🇦'); + expect(emojify('🇨🇨')).toEqual('🇨🇨'); + expect(emojify('🇨🇩')).toEqual('🇨🇩'); + expect(emojify('🇨🇫')).toEqual('🇨🇫'); + expect(emojify('🇨🇬')).toEqual('🇨🇬'); + expect(emojify('🇨🇭')).toEqual('🇨🇭'); + expect(emojify('🇨🇮')).toEqual('🇨🇮'); + expect(emojify('🇨🇰')).toEqual('🇨🇰'); + expect(emojify('🇨🇱')).toEqual('🇨🇱'); + expect(emojify('🇨🇲')).toEqual('🇨🇲'); + expect(emojify('🇨🇳')).toEqual('🇨🇳'); + expect(emojify('🇨🇴')).toEqual('🇨🇴'); + expect(emojify('🇨🇵')).toEqual('🇨🇵'); + expect(emojify('🇨🇷')).toEqual('🇨🇷'); + expect(emojify('🇨🇺')).toEqual('🇨🇺'); + expect(emojify('🇨🇻')).toEqual('🇨🇻'); + expect(emojify('🇨🇼')).toEqual('🇨🇼'); + expect(emojify('🇨🇽')).toEqual('🇨🇽'); + expect(emojify('🇨🇾')).toEqual('🇨🇾'); + expect(emojify('🇨🇿')).toEqual('🇨🇿'); + expect(emojify('🇩🇪')).toEqual('🇩🇪'); + expect(emojify('🇩🇬')).toEqual('🇩🇬'); + expect(emojify('🇩🇯')).toEqual('🇩🇯'); + expect(emojify('🇩🇰')).toEqual('🇩🇰'); + expect(emojify('🇩🇲')).toEqual('🇩🇲'); + expect(emojify('🇩🇴')).toEqual('🇩🇴'); + expect(emojify('🇩🇿')).toEqual('🇩🇿'); + expect(emojify('🇪🇦')).toEqual('🇪🇦'); + expect(emojify('🇪🇨')).toEqual('🇪🇨'); + expect(emojify('🇪🇪')).toEqual('🇪🇪'); + expect(emojify('🇪🇬')).toEqual('🇪🇬'); + expect(emojify('🇪🇭')).toEqual('🇪🇭'); + expect(emojify('🇪🇷')).toEqual('🇪🇷'); + expect(emojify('🇪🇸')).toEqual('🇪🇸'); + expect(emojify('🇪🇹')).toEqual('🇪🇹'); + expect(emojify('🇪🇺')).toEqual('🇪🇺'); + expect(emojify('🇫🇮')).toEqual('🇫🇮'); + expect(emojify('🇫🇯')).toEqual('🇫🇯'); + expect(emojify('🇫🇰')).toEqual('🇫🇰'); + expect(emojify('🇫🇲')).toEqual('🇫🇲'); + expect(emojify('🇫🇴')).toEqual('🇫🇴'); + expect(emojify('🇫🇷')).toEqual('🇫🇷'); + expect(emojify('🇬🇦')).toEqual('🇬🇦'); + expect(emojify('🇬🇧')).toEqual('🇬🇧'); + expect(emojify('🇬🇩')).toEqual('🇬🇩'); + expect(emojify('🇬🇪')).toEqual('🇬🇪'); + expect(emojify('🇬🇫')).toEqual('🇬🇫'); + expect(emojify('🇬🇬')).toEqual('🇬🇬'); + expect(emojify('🇬🇭')).toEqual('🇬🇭'); + expect(emojify('🇬🇮')).toEqual('🇬🇮'); + expect(emojify('🇬🇱')).toEqual('🇬🇱'); + expect(emojify('🇬🇲')).toEqual('🇬🇲'); + expect(emojify('🇬🇳')).toEqual('🇬🇳'); + expect(emojify('🇬🇵')).toEqual('🇬🇵'); + expect(emojify('🇬🇶')).toEqual('🇬🇶'); + expect(emojify('🇬🇷')).toEqual('🇬🇷'); + expect(emojify('🇬🇸')).toEqual('🇬🇸'); + expect(emojify('🇬🇹')).toEqual('🇬🇹'); + expect(emojify('🇬🇺')).toEqual('🇬🇺'); + expect(emojify('🇬🇼')).toEqual('🇬🇼'); + expect(emojify('🇬🇾')).toEqual('🇬🇾'); + expect(emojify('🇭🇰')).toEqual('🇭🇰'); + expect(emojify('🇭🇲')).toEqual('🇭🇲'); + expect(emojify('🇭🇳')).toEqual('🇭🇳'); + expect(emojify('🇭🇷')).toEqual('🇭🇷'); + expect(emojify('🇭🇹')).toEqual('🇭🇹'); + expect(emojify('🇭🇺')).toEqual('🇭🇺'); + expect(emojify('🇮🇨')).toEqual('🇮🇨'); + expect(emojify('🇮🇩')).toEqual('🇮🇩'); + expect(emojify('🇮🇪')).toEqual('🇮🇪'); + expect(emojify('🇮🇱')).toEqual('🇮🇱'); + expect(emojify('🇮🇲')).toEqual('🇮🇲'); + expect(emojify('🇮🇳')).toEqual('🇮🇳'); + expect(emojify('🇮🇴')).toEqual('🇮🇴'); + expect(emojify('🇮🇶')).toEqual('🇮🇶'); + expect(emojify('🇮🇷')).toEqual('🇮🇷'); + expect(emojify('🇮🇸')).toEqual('🇮🇸'); + expect(emojify('🇮🇹')).toEqual('🇮🇹'); + expect(emojify('🇯🇪')).toEqual('🇯🇪'); + expect(emojify('🇯🇲')).toEqual('🇯🇲'); + expect(emojify('🇯🇴')).toEqual('🇯🇴'); + expect(emojify('🇯🇵')).toEqual('🇯🇵'); + expect(emojify('🇰🇪')).toEqual('🇰🇪'); + expect(emojify('🇰🇬')).toEqual('🇰🇬'); + expect(emojify('🇰🇭')).toEqual('🇰🇭'); + expect(emojify('🇰🇮')).toEqual('🇰🇮'); + expect(emojify('🇰🇲')).toEqual('🇰🇲'); + expect(emojify('🇰🇳')).toEqual('🇰🇳'); + expect(emojify('🇰🇵')).toEqual('🇰🇵'); + expect(emojify('🇰🇷')).toEqual('🇰🇷'); + expect(emojify('🇰🇼')).toEqual('🇰🇼'); + expect(emojify('🇰🇾')).toEqual('🇰🇾'); + expect(emojify('🇰🇿')).toEqual('🇰🇿'); + expect(emojify('🇱🇦')).toEqual('🇱🇦'); + expect(emojify('🇱🇧')).toEqual('🇱🇧'); + expect(emojify('🇱🇨')).toEqual('🇱🇨'); + expect(emojify('🇱🇮')).toEqual('🇱🇮'); + expect(emojify('🇱🇰')).toEqual('🇱🇰'); + expect(emojify('🇱🇷')).toEqual('🇱🇷'); + expect(emojify('🇱🇸')).toEqual('🇱🇸'); + expect(emojify('🇱🇹')).toEqual('🇱🇹'); + expect(emojify('🇱🇺')).toEqual('🇱🇺'); + expect(emojify('🇱🇻')).toEqual('🇱🇻'); + expect(emojify('🇱🇾')).toEqual('🇱🇾'); + expect(emojify('🇲🇦')).toEqual('🇲🇦'); + expect(emojify('🇲🇨')).toEqual('🇲🇨'); + expect(emojify('🇲🇩')).toEqual('🇲🇩'); + expect(emojify('🇲🇪')).toEqual('🇲🇪'); + expect(emojify('🇲🇫')).toEqual('🇲🇫'); + expect(emojify('🇲🇬')).toEqual('🇲🇬'); + expect(emojify('🇲🇭')).toEqual('🇲🇭'); + expect(emojify('🇲🇰')).toEqual('🇲🇰'); + expect(emojify('🇲🇱')).toEqual('🇲🇱'); + expect(emojify('🇲🇲')).toEqual('🇲🇲'); + expect(emojify('🇲🇳')).toEqual('🇲🇳'); + expect(emojify('🇲🇴')).toEqual('🇲🇴'); + expect(emojify('🇲🇵')).toEqual('🇲🇵'); + expect(emojify('🇲🇶')).toEqual('🇲🇶'); + expect(emojify('🇲🇷')).toEqual('🇲🇷'); + expect(emojify('🇲🇸')).toEqual('🇲🇸'); + expect(emojify('🇲🇹')).toEqual('🇲🇹'); + expect(emojify('🇲🇺')).toEqual('🇲🇺'); + expect(emojify('🇲🇻')).toEqual('🇲🇻'); + expect(emojify('🇲🇼')).toEqual('🇲🇼'); + expect(emojify('🇲🇽')).toEqual('🇲🇽'); + expect(emojify('🇲🇾')).toEqual('🇲🇾'); + expect(emojify('🇲🇿')).toEqual('🇲🇿'); + expect(emojify('🇳🇦')).toEqual('🇳🇦'); + expect(emojify('🇳🇨')).toEqual('🇳🇨'); + expect(emojify('🇳🇪')).toEqual('🇳🇪'); + expect(emojify('🇳🇫')).toEqual('🇳🇫'); + expect(emojify('🇳🇬')).toEqual('🇳🇬'); + expect(emojify('🇳🇮')).toEqual('🇳🇮'); + expect(emojify('🇳🇱')).toEqual('🇳🇱'); + expect(emojify('🇳🇴')).toEqual('🇳🇴'); + expect(emojify('🇳🇵')).toEqual('🇳🇵'); + expect(emojify('🇳🇷')).toEqual('🇳🇷'); + expect(emojify('🇳🇺')).toEqual('🇳🇺'); + expect(emojify('🇳🇿')).toEqual('🇳🇿'); + expect(emojify('🇴🇲')).toEqual('🇴🇲'); + expect(emojify('🇵🇦')).toEqual('🇵🇦'); + expect(emojify('🇵🇪')).toEqual('🇵🇪'); + expect(emojify('🇵🇫')).toEqual('🇵🇫'); + expect(emojify('🇵🇬')).toEqual('🇵🇬'); + expect(emojify('🇵🇭')).toEqual('🇵🇭'); + expect(emojify('🇵🇰')).toEqual('🇵🇰'); + expect(emojify('🇵🇱')).toEqual('🇵🇱'); + expect(emojify('🇵🇲')).toEqual('🇵🇲'); + expect(emojify('🇵🇳')).toEqual('🇵🇳'); + expect(emojify('🇵🇷')).toEqual('🇵🇷'); + expect(emojify('🇵🇸')).toEqual('🇵🇸'); + expect(emojify('🇵🇹')).toEqual('🇵🇹'); + expect(emojify('🇵🇼')).toEqual('🇵🇼'); + expect(emojify('🇵🇾')).toEqual('🇵🇾'); + expect(emojify('🇶🇦')).toEqual('🇶🇦'); + expect(emojify('🇷🇪')).toEqual('🇷🇪'); + expect(emojify('🇷🇴')).toEqual('🇷🇴'); + expect(emojify('🇷🇸')).toEqual('🇷🇸'); + expect(emojify('🇷🇺')).toEqual('🇷🇺'); + expect(emojify('🇷🇼')).toEqual('🇷🇼'); + expect(emojify('🇸🇦')).toEqual('🇸🇦'); + expect(emojify('🇸🇧')).toEqual('🇸🇧'); + expect(emojify('🇸🇨')).toEqual('🇸🇨'); + expect(emojify('🇸🇩')).toEqual('🇸🇩'); + expect(emojify('🇸🇪')).toEqual('🇸🇪'); + expect(emojify('🇸🇬')).toEqual('🇸🇬'); + expect(emojify('🇸🇭')).toEqual('🇸🇭'); + expect(emojify('🇸🇮')).toEqual('🇸🇮'); + expect(emojify('🇸🇯')).toEqual('🇸🇯'); + expect(emojify('🇸🇰')).toEqual('🇸🇰'); + expect(emojify('🇸🇱')).toEqual('🇸🇱'); + expect(emojify('🇸🇲')).toEqual('🇸🇲'); + expect(emojify('🇸🇳')).toEqual('🇸🇳'); + expect(emojify('🇸🇴')).toEqual('🇸🇴'); + expect(emojify('🇸🇷')).toEqual('🇸🇷'); + expect(emojify('🇸🇸')).toEqual('🇸🇸'); + expect(emojify('🇸🇹')).toEqual('🇸🇹'); + expect(emojify('🇸🇻')).toEqual('🇸🇻'); + expect(emojify('🇸🇽')).toEqual('🇸🇽'); + expect(emojify('🇸🇾')).toEqual('🇸🇾'); + expect(emojify('🇸🇿')).toEqual('🇸🇿'); + expect(emojify('🇹🇦')).toEqual('🇹🇦'); + expect(emojify('🇹🇨')).toEqual('🇹🇨'); + expect(emojify('🇹🇩')).toEqual('🇹🇩'); + expect(emojify('🇹🇫')).toEqual('🇹🇫'); + expect(emojify('🇹🇬')).toEqual('🇹🇬'); + expect(emojify('🇹🇭')).toEqual('🇹🇭'); + expect(emojify('🇹🇯')).toEqual('🇹🇯'); + expect(emojify('🇹🇰')).toEqual('🇹🇰'); + expect(emojify('🇹🇱')).toEqual('🇹🇱'); + expect(emojify('🇹🇲')).toEqual('🇹🇲'); + expect(emojify('🇹🇳')).toEqual('🇹🇳'); + expect(emojify('🇹🇴')).toEqual('🇹🇴'); + expect(emojify('🇹🇷')).toEqual('🇹🇷'); + expect(emojify('🇹🇹')).toEqual('🇹🇹'); + expect(emojify('🇹🇻')).toEqual('🇹🇻'); + expect(emojify('🇹🇼')).toEqual('🇹🇼'); + expect(emojify('🇹🇿')).toEqual('🇹🇿'); + expect(emojify('🇺🇦')).toEqual('🇺🇦'); + expect(emojify('🇺🇬')).toEqual('🇺🇬'); + expect(emojify('🇺🇲')).toEqual('🇺🇲'); + expect(emojify('🇺🇳')).toEqual('🇺🇳'); + expect(emojify('🇺🇸')).toEqual('🇺🇸'); + expect(emojify('🇺🇾')).toEqual('🇺🇾'); + expect(emojify('🇺🇿')).toEqual('🇺🇿'); + expect(emojify('🇻🇦')).toEqual('🇻🇦'); + expect(emojify('🇻🇨')).toEqual('🇻🇨'); + expect(emojify('🇻🇪')).toEqual('🇻🇪'); + expect(emojify('🇻🇬')).toEqual('🇻🇬'); + expect(emojify('🇻🇮')).toEqual('🇻🇮'); + expect(emojify('🇻🇳')).toEqual('🇻🇳'); + expect(emojify('🇻🇺')).toEqual('🇻🇺'); + expect(emojify('🇼🇫')).toEqual('🇼🇫'); + expect(emojify('🇼🇸')).toEqual('🇼🇸'); + expect(emojify('🇽🇰')).toEqual('🇽🇰'); + expect(emojify('🇾🇪')).toEqual('🇾🇪'); + expect(emojify('🇾🇹')).toEqual('🇾🇹'); + expect(emojify('🇿🇦')).toEqual('🇿🇦'); + expect(emojify('🇿🇲')).toEqual('🇿🇲'); + expect(emojify('🇿🇼')).toEqual('🇿🇼'); + expect(emojify('🏴󠁧󠁢󠁥󠁮󠁧󠁿')).toEqual('🏴󠁧󠁢󠁥󠁮󠁧󠁿'); + expect(emojify('🏴󠁧󠁢󠁳󠁣󠁴󠁿')).toEqual('🏴󠁧󠁢󠁳󠁣󠁴󠁿'); + expect(emojify('🏴󠁧󠁢󠁷󠁬󠁳󠁿')).toEqual('🏴󠁧󠁢󠁷󠁬󠁳󠁿'); }); }); }); diff --git a/app/soapbox/features/emoji/components/emoji-picker-dropdown.tsx b/app/soapbox/features/emoji/components/emoji-picker-dropdown.tsx new file mode 100644 index 000000000..7e6bd2414 --- /dev/null +++ b/app/soapbox/features/emoji/components/emoji-picker-dropdown.tsx @@ -0,0 +1,259 @@ +import { Map as ImmutableMap } from 'immutable'; +import React, { useEffect, useState, useLayoutEffect } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; +import { createSelector } from 'reselect'; + +import { useEmoji } from 'soapbox/actions/emojis'; +import { changeSetting } from 'soapbox/actions/settings'; +import { useAppDispatch, useAppSelector, useSettings } from 'soapbox/hooks'; +import { RootState } from 'soapbox/store'; + +import { buildCustomEmojis } from '../../emoji'; +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'; + +let EmojiPicker: any; // load asynchronously + +export const messages = defineMessages({ + emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, + emoji_pick: { id: 'emoji_button.pick', defaultMessage: 'Pick an emoji…' }, + emoji_oh_no: { id: 'emoji_button.oh_no', defaultMessage: 'Oh no!' }, + emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search…' }, + emoji_not_found: { id: 'emoji_button.not_found', defaultMessage: 'No emoji\'s found.' }, + emoji_add_custom: { id: 'emoji_button.add_custom', defaultMessage: 'Add custom emoji' }, + custom: { id: 'emoji_button.custom', defaultMessage: 'Custom' }, + recent: { id: 'emoji_button.recent', defaultMessage: 'Frequently used' }, + search_results: { id: 'emoji_button.search_results', defaultMessage: 'Search results' }, + people: { id: 'emoji_button.people', defaultMessage: 'People' }, + nature: { id: 'emoji_button.nature', defaultMessage: 'Nature' }, + food: { id: 'emoji_button.food', defaultMessage: 'Food & Drink' }, + activity: { id: 'emoji_button.activity', defaultMessage: 'Activity' }, + travel: { id: 'emoji_button.travel', defaultMessage: 'Travel & Places' }, + objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' }, + symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' }, + flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' }, + skins_choose: { id: 'emoji_button.skins_choose', defaultMessage: 'Choose default skin tone' }, + skins_1: { id: 'emoji_button.skins_1', defaultMessage: 'Default' }, + skins_2: { id: 'emoji_button.skins_2', defaultMessage: 'Light' }, + skins_3: { id: 'emoji_button.skins_3', defaultMessage: 'Medium-Light' }, + skins_4: { id: 'emoji_button.skins_4', defaultMessage: 'Medium' }, + skins_5: { id: 'emoji_button.skins_5', defaultMessage: 'Medium-Dark' }, + skins_6: { id: 'emoji_button.skins_6', defaultMessage: 'Dark' }, +}); + +export interface IEmojiPickerDropdown { + onPickEmoji?: (emoji: Emoji) => void + condensed?: boolean + withCustom?: boolean + visible: boolean + setVisible: (value: boolean) => void + update: (() => Promise>) | null +} + +const perLine = 8; +const lines = 2; + +const DEFAULTS = [ + '+1', + 'grinning', + 'kissing_heart', + 'heart_eyes', + 'laughing', + 'stuck_out_tongue_winking_eye', + 'sweat_smile', + 'joy', + 'yum', + 'disappointed', + 'thinking_face', + 'weary', + 'sob', + 'sunglasses', + 'heart', + 'ok_hand', +]; + +export const getFrequentlyUsedEmojis = createSelector([ + (state: RootState) => state.settings.get('frequentlyUsedEmojis', ImmutableMap()), +], (emojiCounters: ImmutableMap) => { + let emojis = emojiCounters + .keySeq() + .sort((a, b) => emojiCounters.get(a)! - emojiCounters.get(b)!) + .reverse() + .slice(0, perLine * lines) + .toArray(); + + if (emojis.length < DEFAULTS.length) { + const uniqueDefaults = DEFAULTS.filter(emoji => !emojis.includes(emoji)); + emojis = emojis.concat(uniqueDefaults.slice(0, DEFAULTS.length - emojis.length)); + } + + return emojis; +}); + +const getCustomEmojis = createSelector([ + (state: RootState) => state.custom_emojis, +], emojis => emojis.filter(e => e.get('visible_in_picker')).sort((a, b) => { + const aShort = a.get('shortcode')!.toLowerCase(); + const bShort = b.get('shortcode')!.toLowerCase(); + + if (aShort < bShort) { + return -1; + } else if (aShort > bShort) { + return 1; + } else { + return 0; + } +})); + +// Fixes render bug where popover has a delayed position update +const RenderAfter = ({ children, update }: any) => { + const [nextTick, setNextTick] = useState(false); + + useEffect(() => { + setTimeout(() => { + setNextTick(true); + }, 0); + }, []); + + useLayoutEffect(() => { + if (nextTick) { + update(); + } + }, [nextTick, update]); + + return nextTick ? children : null; +}; + +const EmojiPickerDropdown: React.FC = ({ + onPickEmoji, visible, setVisible, update, withCustom = true, +}) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + const settings = useSettings(); + const title = intl.formatMessage(messages.emoji); + const userTheme = settings.get('themeMode'); + const theme = (userTheme === 'dark' || userTheme === 'light') ? userTheme : 'auto'; + + const customEmojis = useAppSelector((state) => getCustomEmojis(state)); + const frequentlyUsedEmojis = useAppSelector((state) => getFrequentlyUsedEmojis(state)); + + const [loading, setLoading] = useState(false); + + const handlePick = (emoji: any) => { + setVisible(false); + + let pickedEmoji: Emoji; + + if (emoji.native) { + pickedEmoji = { + id: emoji.id, + colons: emoji.shortcodes, + custom: false, + native: emoji.native, + unified: emoji.unified, + } as NativeEmoji; + } else { + pickedEmoji = { + id: emoji.id, + colons: emoji.shortcodes, + custom: true, + imageUrl: emoji.src, + } as CustomEmoji; + } + + dispatch(useEmoji(pickedEmoji)); // eslint-disable-line react-hooks/rules-of-hooks + + if (onPickEmoji) { + onPickEmoji(pickedEmoji); + } + }; + + const handleSkinTone = (skinTone: string) => { + dispatch(changeSetting(['skinTone'], skinTone)); + }; + + 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(() => { + // fix scrolling focus issue + if (visible) { + 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]); + + useEffect(() => () => { + document.body.style.overflow = ''; + }, []); + + return ( + visible ? ( + + {!loading && ( + + )} + + ) : null + ); +}; + +export default EmojiPickerDropdown; diff --git a/app/soapbox/features/emoji/components/emoji-picker.tsx b/app/soapbox/features/emoji/components/emoji-picker.tsx new file mode 100644 index 000000000..695fae2cc --- /dev/null +++ b/app/soapbox/features/emoji/components/emoji-picker.tsx @@ -0,0 +1,30 @@ +import { Picker as EmojiPicker } from 'emoji-mart'; +import React, { useRef, useEffect } from 'react'; + +import { joinPublicPath } from 'soapbox/utils/static'; + +import data from '../data'; + +const getSpritesheetURL = (set: string) => { + return require('emoji-datasource/img/twitter/sheets/32.png'); +}; + +const getImageURL = (set: string, name: string) => { + return joinPublicPath(`/packs/emoji/${name}.svg`); +}; + +const Picker = (props: any) => { + const ref = useRef(null); + + useEffect(() => { + const input = { ...props, data, ref, getImageURL, getSpritesheetURL }; + + new EmojiPicker(input); + }, []); + + return
; +}; + +export { + Picker, +}; diff --git a/app/soapbox/features/emoji/containers/emoji-picker-dropdown-container.tsx b/app/soapbox/features/emoji/containers/emoji-picker-dropdown-container.tsx new file mode 100644 index 000000000..47dea8d6f --- /dev/null +++ b/app/soapbox/features/emoji/containers/emoji-picker-dropdown-container.tsx @@ -0,0 +1,96 @@ +import clsx from 'clsx'; +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 { isMobile } from 'soapbox/is-mobile'; + +import EmojiPickerDropdown, { IEmojiPickerDropdown } from '../components/emoji-picker-dropdown'; + +const listenerOptions = supportsPassiveEvents ? { passive: true } : false; + +export const messages = defineMessages({ + emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, +}); + +const EmojiPickerDropdownContainer = ( + props: Pick, +) => { + const intl = useIntl(); + const title = intl.formatMessage(messages.emoji); + + const [popperElement, setPopperElement] = useState(null); + const [popperReference, setPopperReference] = useState(null); + const [containerElement, setContainerElement] = useState(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 () => { + document.removeEventListener('click', handleDocClick, false); + // @ts-ignore + document.removeEventListener('touchend', handleDocClick, listenerOptions); + }; + }); + + return ( +
+ } + tabIndex={0} + /> + + {createPortal( +
+ +
, + document.body, + )} +
+ ); +}; + + +export default EmojiPickerDropdownContainer; diff --git a/app/soapbox/features/emoji/data.ts b/app/soapbox/features/emoji/data.ts new file mode 100644 index 000000000..2d74822f3 --- /dev/null +++ b/app/soapbox/features/emoji/data.ts @@ -0,0 +1,52 @@ +import data from '@emoji-mart/data/sets/14/twitter.json'; + +export interface NativeEmoji { + unified: string + native: string + x: number + y: number +} + +export interface CustomEmoji { + src: string +} + +export interface Emoji { + id: string + name: string + keywords: string[] + skins: T[] + version?: number +} + +export interface EmojiCategory { + id: string + emojis: string[] +} + +export interface EmojiMap { + [s: string]: Emoji +} + +export interface EmojiAlias { + [s: string]: string +} + +export interface EmojiSheet { + cols: number + rows: number +} + +export interface EmojiData { + categories: EmojiCategory[] + emojis: EmojiMap + aliases: EmojiAlias + sheet: EmojiSheet +} + +const emojiData = data as EmojiData; +const { categories, emojis, aliases, sheet } = emojiData; + +export { categories, emojis, aliases, sheet }; + +export default emojiData; diff --git a/app/soapbox/features/emoji/emoji-compressed.js b/app/soapbox/features/emoji/emoji-compressed.js deleted file mode 100644 index adea912b9..000000000 --- a/app/soapbox/features/emoji/emoji-compressed.js +++ /dev/null @@ -1,124 +0,0 @@ -// @preval -// http://www.unicode.org/Public/emoji/5.0/emoji-test.txt -// This file contains the compressed version of the emoji data from -// both emoji-map.json and from emoji-mart's emojiIndex and data objects. -// It's designed to be emitted in an array format to take up less space -// over the wire. - -const { emojiIndex } = require('emoji-mart'); -let data = require('emoji-mart/data/all.json'); -const { uncompress: emojiMartUncompress } = require('emoji-mart/dist/utils/data'); - -const emojiMap = require('./emoji-map.json'); -const { unicodeToFilename } = require('./unicode-to-filename'); -const { unicodeToUnifiedName } = require('./unicode-to-unified-name'); - -if (data.compressed) { - data = emojiMartUncompress(data); -} - -const emojiMartData = data; - -const excluded = ['®', '©', '™']; -const skinTones = ['🏻', '🏼', '🏽', '🏾', '🏿']; -const shortcodeMap = {}; - -const shortCodesToEmojiData = {}; -const emojisWithoutShortCodes = []; - -Object.keys(emojiIndex.emojis).forEach(key => { - let emoji = emojiIndex.emojis[key]; - - // Emojis with skin tone modifiers are stored like this - if (Object.prototype.hasOwnProperty.call(emoji, '1')) { - emoji = emoji['1']; - } - - shortcodeMap[emoji.native] = emoji.id; -}); - -const stripModifiers = unicode => { - skinTones.forEach(tone => { - unicode = unicode.replace(tone, ''); - }); - - return unicode; -}; - -Object.keys(emojiMap).forEach(key => { - if (excluded.includes(key)) { - delete emojiMap[key]; - return; - } - - const normalizedKey = stripModifiers(key); - let shortcode = shortcodeMap[normalizedKey]; - - if (!shortcode) { - shortcode = shortcodeMap[normalizedKey + '\uFE0F']; - } - - const filename = emojiMap[key]; - - const filenameData = [key]; - - if (unicodeToFilename(key) !== filename) { - // filename can't be derived using unicodeToFilename - filenameData.push(filename); - } - - if (typeof shortcode === 'undefined') { - emojisWithoutShortCodes.push(filenameData); - } else { - if (!Array.isArray(shortCodesToEmojiData[shortcode])) { - shortCodesToEmojiData[shortcode] = [[]]; - } - - shortCodesToEmojiData[shortcode][0].push(filenameData); - } -}); - -Object.keys(emojiIndex.emojis).forEach(key => { - let emoji = emojiIndex.emojis[key]; - - // Emojis with skin tone modifiers are stored like this - if (Object.prototype.hasOwnProperty.call(emoji, '1')) { - emoji = emoji['1']; - } - - const { native } = emoji; - const { short_names, search, unified } = emojiMartData.emojis[key]; - - if (short_names[0] !== key) { - throw new Error('The compresser expects the first short_code to be the ' + - 'key. It may need to be rewritten if the emoji change such that this ' + - 'is no longer the case.'); - } - - const searchData = [ - native, - short_names.slice(1), // first short name can be inferred from the key - search, - ]; - - if (unicodeToUnifiedName(native) !== unified) { - // unified name can't be derived from unicodeToUnifiedName - searchData.push(unified); - } - - if (!Array.isArray(shortCodesToEmojiData[key])) { - shortCodesToEmojiData[key] = [[]]; - } - - shortCodesToEmojiData[key].push(searchData); -}); - -// JSON.parse/stringify is to emulate what @preval is doing and avoid any -// inconsistent behavior in dev mode -module.exports = JSON.parse(JSON.stringify([ - shortCodesToEmojiData, - emojiMartData.skins, - emojiMartData.categories, - emojiMartData.aliases, - emojisWithoutShortCodes, -])); diff --git a/app/soapbox/features/emoji/emoji-map.json b/app/soapbox/features/emoji/emoji-map.json deleted file mode 100644 index 91e339865..000000000 --- a/app/soapbox/features/emoji/emoji-map.json +++ /dev/null @@ -1 +0,0 @@ -{"😀":"1f600","😃":"1f603","😄":"1f604","😁":"1f601","😆":"1f606","😅":"1f605","🤣":"1f923","😂":"1f602","🙂":"1f642","🙃":"1f643","😉":"1f609","😊":"1f60a","😇":"1f607","🥰":"1f970","😍":"1f60d","🤩":"1f929","😘":"1f618","😗":"1f617","☺":"263a","😚":"1f61a","😙":"1f619","🥲":"1f972","😋":"1f60b","😛":"1f61b","😜":"1f61c","🤪":"1f92a","😝":"1f61d","🤑":"1f911","🤗":"1f917","🤭":"1f92d","🤫":"1f92b","🤔":"1f914","🤐":"1f910","🤨":"1f928","😐":"1f610","😑":"1f611","😶":"1f636","😏":"1f60f","😒":"1f612","🙄":"1f644","😬":"1f62c","🤥":"1f925","😌":"1f60c","😔":"1f614","😪":"1f62a","🤤":"1f924","😴":"1f634","😷":"1f637","🤒":"1f912","🤕":"1f915","🤢":"1f922","🤮":"1f92e","🤧":"1f927","🥵":"1f975","🥶":"1f976","🥴":"1f974","😵":"1f635","🤯":"1f92f","🤠":"1f920","🥳":"1f973","🥸":"1f978","😎":"1f60e","🤓":"1f913","🧐":"1f9d0","😕":"1f615","😟":"1f61f","🙁":"1f641","☹":"2639","😮":"1f62e","😯":"1f62f","😲":"1f632","😳":"1f633","🥺":"1f97a","😦":"1f626","😧":"1f627","😨":"1f628","😰":"1f630","😥":"1f625","😢":"1f622","😭":"1f62d","😱":"1f631","😖":"1f616","😣":"1f623","😞":"1f61e","😓":"1f613","😩":"1f629","😫":"1f62b","🥱":"1f971","😤":"1f624","😡":"1f621","😠":"1f620","🤬":"1f92c","😈":"1f608","👿":"1f47f","💀":"1f480","☠":"2620","💩":"1f4a9","🤡":"1f921","👹":"1f479","👺":"1f47a","👻":"1f47b","👽":"1f47d","👾":"1f47e","🤖":"1f916","😺":"1f63a","😸":"1f638","😹":"1f639","😻":"1f63b","😼":"1f63c","😽":"1f63d","🙀":"1f640","😿":"1f63f","😾":"1f63e","🙈":"1f648","🙉":"1f649","🙊":"1f64a","💋":"1f48b","💌":"1f48c","💘":"1f498","💝":"1f49d","💖":"1f496","💗":"1f497","💓":"1f493","💞":"1f49e","💕":"1f495","💟":"1f49f","❣":"2763","💔":"1f494","❤":"2764","🧡":"1f9e1","💛":"1f49b","💚":"1f49a","💙":"1f499","💜":"1f49c","🤎":"1f90e","🖤":"1f5a4","🤍":"1f90d","💯":"1f4af","💢":"1f4a2","💥":"1f4a5","💫":"1f4ab","💦":"1f4a6","💨":"1f4a8","🕳":"1f573","💣":"1f4a3","💬":"1f4ac","🗨":"1f5e8","🗯":"1f5ef","💭":"1f4ad","💤":"1f4a4","👋":"1f44b","🤚":"1f91a","🖐":"1f590","✋":"270b","🖖":"1f596","👌":"1f44c","🤌":"1f90c","🤏":"1f90f","✌":"270c","🤞":"1f91e","🤟":"1f91f","🤘":"1f918","🤙":"1f919","👈":"1f448","👉":"1f449","👆":"1f446","🖕":"1f595","👇":"1f447","☝":"261d","👍":"1f44d","👎":"1f44e","✊":"270a","👊":"1f44a","🤛":"1f91b","🤜":"1f91c","👏":"1f44f","🙌":"1f64c","👐":"1f450","🤲":"1f932","🤝":"1f91d","🙏":"1f64f","✍":"270d","💅":"1f485","🤳":"1f933","💪":"1f4aa","🦾":"1f9be","🦿":"1f9bf","🦵":"1f9b5","🦶":"1f9b6","👂":"1f442","🦻":"1f9bb","👃":"1f443","🧠":"1f9e0","🫀":"1fac0","🫁":"1fac1","🦷":"1f9b7","🦴":"1f9b4","👀":"1f440","👁":"1f441","👅":"1f445","👄":"1f444","👶":"1f476","🧒":"1f9d2","👦":"1f466","👧":"1f467","🧑":"1f9d1","👱":"1f471","👨":"1f468","🧔":"1f9d4","👩":"1f469","🧓":"1f9d3","👴":"1f474","👵":"1f475","🙍":"1f64d","🙎":"1f64e","🙅":"1f645","🙆":"1f646","💁":"1f481","🙋":"1f64b","🧏":"1f9cf","🙇":"1f647","🤦":"1f926","🤷":"1f937","👮":"1f46e","🕵":"1f575","💂":"1f482","🥷":"1f977","👷":"1f477","🤴":"1f934","👸":"1f478","👳":"1f473","👲":"1f472","🧕":"1f9d5","🤵":"1f935","👰":"1f470","🤰":"1f930","🤱":"1f931","👼":"1f47c","🎅":"1f385","🤶":"1f936","🦸":"1f9b8","🦹":"1f9b9","🧙":"1f9d9","🧚":"1f9da","🧛":"1f9db","🧜":"1f9dc","🧝":"1f9dd","🧞":"1f9de","🧟":"1f9df","💆":"1f486","💇":"1f487","🚶":"1f6b6","🧍":"1f9cd","🧎":"1f9ce","🏃":"1f3c3","💃":"1f483","🕺":"1f57a","🕴":"1f574","👯":"1f46f","🧖":"1f9d6","🧗":"1f9d7","🤺":"1f93a","🏇":"1f3c7","⛷":"26f7","🏂":"1f3c2","🏌":"1f3cc","🏄":"1f3c4","🚣":"1f6a3","🏊":"1f3ca","⛹":"26f9","🏋":"1f3cb","🚴":"1f6b4","🚵":"1f6b5","🤸":"1f938","🤼":"1f93c","🤽":"1f93d","🤾":"1f93e","🤹":"1f939","🧘":"1f9d8","🛀":"1f6c0","🛌":"1f6cc","👭":"1f46d","👫":"1f46b","👬":"1f46c","💏":"1f48f","💑":"1f491","👪":"1f46a","🗣":"1f5e3","👤":"1f464","👥":"1f465","🫂":"1fac2","👣":"1f463","🏻":"1f463","🏼":"1f463","🏽":"1f463","🏾":"1f463","🏿":"1f463","🦰":"1f463","🦱":"1f463","🦳":"1f463","🦲":"1f463","🐵":"1f435","🐒":"1f412","🦍":"1f98d","🦧":"1f9a7","🐶":"1f436","🐕":"1f415","🦮":"1f9ae","🐩":"1f429","🐺":"1f43a","🦊":"1f98a","🦝":"1f99d","🐱":"1f431","🐈":"1f408","🦁":"1f981","🐯":"1f42f","🐅":"1f405","🐆":"1f406","🐴":"1f434","🐎":"1f40e","🦄":"1f984","🦓":"1f993","🦌":"1f98c","🦬":"1f9ac","🐮":"1f42e","🐂":"1f402","🐃":"1f403","🐄":"1f404","🐷":"1f437","🐖":"1f416","🐗":"1f417","🐽":"1f43d","🐏":"1f40f","🐑":"1f411","🐐":"1f410","🐪":"1f42a","🐫":"1f42b","🦙":"1f999","🦒":"1f992","🐘":"1f418","🦣":"1f9a3","🦏":"1f98f","🦛":"1f99b","🐭":"1f42d","🐁":"1f401","🐀":"1f400","🐹":"1f439","🐰":"1f430","🐇":"1f407","🐿":"1f43f","🦫":"1f9ab","🦔":"1f994","🦇":"1f987","🐻":"1f43b","🐨":"1f428","🐼":"1f43c","🦥":"1f9a5","🦦":"1f9a6","🦨":"1f9a8","🦘":"1f998","🦡":"1f9a1","🐾":"1f43e","🦃":"1f983","🐔":"1f414","🐓":"1f413","🐣":"1f423","🐤":"1f424","🐥":"1f425","🐦":"1f426","🐧":"1f427","🕊":"1f54a","🦅":"1f985","🦆":"1f986","🦢":"1f9a2","🦉":"1f989","🦤":"1f9a4","🪶":"1fab6","🦩":"1f9a9","🦚":"1f99a","🦜":"1f99c","🐸":"1f438","🐊":"1f40a","🐢":"1f422","🦎":"1f98e","🐍":"1f40d","🐲":"1f432","🐉":"1f409","🦕":"1f995","🦖":"1f996","🐳":"1f433","🐋":"1f40b","🐬":"1f42c","🦭":"1f9ad","🐟":"1f41f","🐠":"1f420","🐡":"1f421","🦈":"1f988","🐙":"1f419","🐚":"1f41a","🐌":"1f40c","🦋":"1f98b","🐛":"1f41b","🐜":"1f41c","🐝":"1f41d","🪲":"1fab2","🐞":"1f41e","🦗":"1f997","🪳":"1fab3","🕷":"1f577","🕸":"1f578","🦂":"1f982","🦟":"1f99f","🪰":"1fab0","🪱":"1fab1","🦠":"1f9a0","💐":"1f490","🌸":"1f338","💮":"1f4ae","🏵":"1f3f5","🌹":"1f339","🥀":"1f940","🌺":"1f33a","🌻":"1f33b","🌼":"1f33c","🌷":"1f337","🌱":"1f331","🪴":"1fab4","🌲":"1f332","🌳":"1f333","🌴":"1f334","🌵":"1f335","🌾":"1f33e","🌿":"1f33f","☘":"2618","🍀":"1f340","🍁":"1f341","🍂":"1f342","🍃":"1f343","🍇":"1f347","🍈":"1f348","🍉":"1f349","🍊":"1f34a","🍋":"1f34b","🍌":"1f34c","🍍":"1f34d","🥭":"1f96d","🍎":"1f34e","🍏":"1f34f","🍐":"1f350","🍑":"1f351","🍒":"1f352","🍓":"1f353","🫐":"1fad0","🥝":"1f95d","🍅":"1f345","🫒":"1fad2","🥥":"1f965","🥑":"1f951","🍆":"1f346","🥔":"1f954","🥕":"1f955","🌽":"1f33d","🌶":"1f336","🫑":"1fad1","🥒":"1f952","🥬":"1f96c","🥦":"1f966","🧄":"1f9c4","🧅":"1f9c5","🍄":"1f344","🥜":"1f95c","🌰":"1f330","🍞":"1f35e","🥐":"1f950","🥖":"1f956","🫓":"1fad3","🥨":"1f968","🥯":"1f96f","🥞":"1f95e","🧇":"1f9c7","🧀":"1f9c0","🍖":"1f356","🍗":"1f357","🥩":"1f969","🥓":"1f953","🍔":"1f354","🍟":"1f35f","🍕":"1f355","🌭":"1f32d","🥪":"1f96a","🌮":"1f32e","🌯":"1f32f","🫔":"1fad4","🥙":"1f959","🧆":"1f9c6","🥚":"1f95a","🍳":"1f373","🥘":"1f958","🍲":"1f372","🫕":"1fad5","🥣":"1f963","🥗":"1f957","🍿":"1f37f","🧈":"1f9c8","🧂":"1f9c2","🥫":"1f96b","🍱":"1f371","🍘":"1f358","🍙":"1f359","🍚":"1f35a","🍛":"1f35b","🍜":"1f35c","🍝":"1f35d","🍠":"1f360","🍢":"1f362","🍣":"1f363","🍤":"1f364","🍥":"1f365","🥮":"1f96e","🍡":"1f361","🥟":"1f95f","🥠":"1f960","🥡":"1f961","🦀":"1f980","🦞":"1f99e","🦐":"1f990","🦑":"1f991","🦪":"1f9aa","🍦":"1f366","🍧":"1f367","🍨":"1f368","🍩":"1f369","🍪":"1f36a","🎂":"1f382","🍰":"1f370","🧁":"1f9c1","🥧":"1f967","🍫":"1f36b","🍬":"1f36c","🍭":"1f36d","🍮":"1f36e","🍯":"1f36f","🍼":"1f37c","🥛":"1f95b","☕":"2615","🫖":"1fad6","🍵":"1f375","🍶":"1f376","🍾":"1f37e","🍷":"1f377","🍸":"1f378","🍹":"1f379","🍺":"1f37a","🍻":"1f37b","🥂":"1f942","🥃":"1f943","🥤":"1f964","🧋":"1f9cb","🧃":"1f9c3","🧉":"1f9c9","🧊":"1f9ca","🥢":"1f962","🍽":"1f37d","🍴":"1f374","🥄":"1f944","🔪":"1f52a","🏺":"1f3fa","🌍":"1f30d","🌎":"1f30e","🌏":"1f30f","🌐":"1f310","🗺":"1f5fa","🗾":"1f5fe","🧭":"1f9ed","🏔":"1f3d4","⛰":"26f0","🌋":"1f30b","🗻":"1f5fb","🏕":"1f3d5","🏖":"1f3d6","🏜":"1f3dc","🏝":"1f3dd","🏞":"1f3de","🏟":"1f3df","🏛":"1f3db","🏗":"1f3d7","🧱":"1f9f1","🪨":"1faa8","🪵":"1fab5","🛖":"1f6d6","🏘":"1f3d8","🏚":"1f3da","🏠":"1f3e0","🏡":"1f3e1","🏢":"1f3e2","🏣":"1f3e3","🏤":"1f3e4","🏥":"1f3e5","🏦":"1f3e6","🏨":"1f3e8","🏩":"1f3e9","🏪":"1f3ea","🏫":"1f3eb","🏬":"1f3ec","🏭":"1f3ed","🏯":"1f3ef","🏰":"1f3f0","💒":"1f492","🗼":"1f5fc","🗽":"1f5fd","⛪":"26ea","🕌":"1f54c","🛕":"1f6d5","🕍":"1f54d","⛩":"26e9","🕋":"1f54b","⛲":"26f2","⛺":"26fa","🌁":"1f301","🌃":"1f303","🏙":"1f3d9","🌄":"1f304","🌅":"1f305","🌆":"1f306","🌇":"1f307","🌉":"1f309","♨":"2668","🎠":"1f3a0","🎡":"1f3a1","🎢":"1f3a2","💈":"1f488","🎪":"1f3aa","🚂":"1f682","🚃":"1f683","🚄":"1f684","🚅":"1f685","🚆":"1f686","🚇":"1f687","🚈":"1f688","🚉":"1f689","🚊":"1f68a","🚝":"1f69d","🚞":"1f69e","🚋":"1f68b","🚌":"1f68c","🚍":"1f68d","🚎":"1f68e","🚐":"1f690","🚑":"1f691","🚒":"1f692","🚓":"1f693","🚔":"1f694","🚕":"1f695","🚖":"1f696","🚗":"1f697","🚘":"1f698","🚙":"1f699","🛻":"1f6fb","🚚":"1f69a","🚛":"1f69b","🚜":"1f69c","🏎":"1f3ce","🏍":"1f3cd","🛵":"1f6f5","🦽":"1f9bd","🦼":"1f9bc","🛺":"1f6fa","🚲":"1f6b2","🛴":"1f6f4","🛹":"1f6f9","🛼":"1f6fc","🚏":"1f68f","🛣":"1f6e3","🛤":"1f6e4","🛢":"1f6e2","⛽":"26fd","🚨":"1f6a8","🚥":"1f6a5","🚦":"1f6a6","🛑":"1f6d1","🚧":"1f6a7","⚓":"2693","⛵":"26f5","🛶":"1f6f6","🚤":"1f6a4","🛳":"1f6f3","⛴":"26f4","🛥":"1f6e5","🚢":"1f6a2","✈":"2708","🛩":"1f6e9","🛫":"1f6eb","🛬":"1f6ec","🪂":"1fa82","💺":"1f4ba","🚁":"1f681","🚟":"1f69f","🚠":"1f6a0","🚡":"1f6a1","🛰":"1f6f0","🚀":"1f680","🛸":"1f6f8","🛎":"1f6ce","🧳":"1f9f3","⌛":"231b","⏳":"23f3","⌚":"231a","⏰":"23f0","⏱":"23f1","⏲":"23f2","🕰":"1f570","🕛":"1f55b","🕧":"1f567","🕐":"1f550","🕜":"1f55c","🕑":"1f551","🕝":"1f55d","🕒":"1f552","🕞":"1f55e","🕓":"1f553","🕟":"1f55f","🕔":"1f554","🕠":"1f560","🕕":"1f555","🕡":"1f561","🕖":"1f556","🕢":"1f562","🕗":"1f557","🕣":"1f563","🕘":"1f558","🕤":"1f564","🕙":"1f559","🕥":"1f565","🕚":"1f55a","🕦":"1f566","🌑":"1f311","🌒":"1f312","🌓":"1f313","🌔":"1f314","🌕":"1f315","🌖":"1f316","🌗":"1f317","🌘":"1f318","🌙":"1f319","🌚":"1f31a","🌛":"1f31b","🌜":"1f31c","🌡":"1f321","☀":"2600","🌝":"1f31d","🌞":"1f31e","🪐":"1fa90","⭐":"2b50","🌟":"1f31f","🌠":"1f320","🌌":"1f30c","☁":"2601","⛅":"26c5","⛈":"26c8","🌤":"1f324","🌥":"1f325","🌦":"1f326","🌧":"1f327","🌨":"1f328","🌩":"1f329","🌪":"1f32a","🌫":"1f32b","🌬":"1f32c","🌀":"1f300","🌈":"1f308","🌂":"1f302","☂":"2602","☔":"2614","⛱":"26f1","⚡":"26a1","❄":"2744","☃":"2603","⛄":"26c4","☄":"2604","🔥":"1f525","💧":"1f4a7","🌊":"1f30a","🎃":"1f383","🎄":"1f384","🎆":"1f386","🎇":"1f387","🧨":"1f9e8","✨":"2728","🎈":"1f388","🎉":"1f389","🎊":"1f38a","🎋":"1f38b","🎍":"1f38d","🎎":"1f38e","🎏":"1f38f","🎐":"1f390","🎑":"1f391","🧧":"1f9e7","🎀":"1f380","🎁":"1f381","🎗":"1f397","🎟":"1f39f","🎫":"1f3ab","🎖":"1f396","🏆":"1f3c6","🏅":"1f3c5","🥇":"1f947","🥈":"1f948","🥉":"1f949","⚽":"26bd","⚾":"26be","🥎":"1f94e","🏀":"1f3c0","🏐":"1f3d0","🏈":"1f3c8","🏉":"1f3c9","🎾":"1f3be","🥏":"1f94f","🎳":"1f3b3","🏏":"1f3cf","🏑":"1f3d1","🏒":"1f3d2","🥍":"1f94d","🏓":"1f3d3","🏸":"1f3f8","🥊":"1f94a","🥋":"1f94b","🥅":"1f945","⛳":"26f3","⛸":"26f8","🎣":"1f3a3","🤿":"1f93f","🎽":"1f3bd","🎿":"1f3bf","🛷":"1f6f7","🥌":"1f94c","🎯":"1f3af","🪀":"1fa80","🪁":"1fa81","🎱":"1f3b1","🔮":"1f52e","🪄":"1fa84","🧿":"1f9ff","🎮":"1f3ae","🕹":"1f579","🎰":"1f3b0","🎲":"1f3b2","🧩":"1f9e9","🧸":"1f9f8","🪅":"1fa85","🪆":"1fa86","♠":"2660","♥":"2665","♦":"2666","♣":"2663","♟":"265f","🃏":"1f0cf","🀄":"1f004","🎴":"1f3b4","🎭":"1f3ad","🖼":"1f5bc","🎨":"1f3a8","🧵":"1f9f5","🪡":"1faa1","🧶":"1f9f6","🪢":"1faa2","👓":"1f453","🕶":"1f576","🥽":"1f97d","🥼":"1f97c","🦺":"1f9ba","👔":"1f454","👕":"1f455","👖":"1f456","🧣":"1f9e3","🧤":"1f9e4","🧥":"1f9e5","🧦":"1f9e6","👗":"1f457","👘":"1f458","🥻":"1f97b","🩱":"1fa71","🩲":"1fa72","🩳":"1fa73","👙":"1f459","👚":"1f45a","👛":"1f45b","👜":"1f45c","👝":"1f45d","🛍":"1f6cd","🎒":"1f392","🩴":"1fa74","👞":"1f45e","👟":"1f45f","🥾":"1f97e","🥿":"1f97f","👠":"1f460","👡":"1f461","🩰":"1fa70","👢":"1f462","👑":"1f451","👒":"1f452","🎩":"1f3a9","🎓":"1f393","🧢":"1f9e2","🪖":"1fa96","⛑":"26d1","📿":"1f4ff","💄":"1f484","💍":"1f48d","💎":"1f48e","🔇":"1f507","🔈":"1f508","🔉":"1f509","🔊":"1f50a","📢":"1f4e2","📣":"1f4e3","📯":"1f4ef","🔔":"1f514","🔕":"1f515","🎼":"1f3bc","🎵":"1f3b5","🎶":"1f3b6","🎙":"1f399","🎚":"1f39a","🎛":"1f39b","🎤":"1f3a4","🎧":"1f3a7","📻":"1f4fb","🎷":"1f3b7","🪗":"1fa97","🎸":"1f3b8","🎹":"1f3b9","🎺":"1f3ba","🎻":"1f3bb","🪕":"1fa95","🥁":"1f941","🪘":"1fa98","📱":"1f4f1","📲":"1f4f2","☎":"260e","📞":"1f4de","📟":"1f4df","📠":"1f4e0","🔋":"1f50b","🔌":"1f50c","💻":"1f4bb","🖥":"1f5a5","🖨":"1f5a8","⌨":"2328","🖱":"1f5b1","🖲":"1f5b2","💽":"1f4bd","💾":"1f4be","💿":"1f4bf","📀":"1f4c0","🧮":"1f9ee","🎥":"1f3a5","🎞":"1f39e","📽":"1f4fd","🎬":"1f3ac","📺":"1f4fa","📷":"1f4f7","📸":"1f4f8","📹":"1f4f9","📼":"1f4fc","🔍":"1f50d","🔎":"1f50e","🕯":"1f56f","💡":"1f4a1","🔦":"1f526","🏮":"1f3ee","🪔":"1fa94","📔":"1f4d4","📕":"1f4d5","📖":"1f4d6","📗":"1f4d7","📘":"1f4d8","📙":"1f4d9","📚":"1f4da","📓":"1f4d3","📒":"1f4d2","📃":"1f4c3","📜":"1f4dc","📄":"1f4c4","📰":"1f4f0","🗞":"1f5de","📑":"1f4d1","🔖":"1f516","🏷":"1f3f7","💰":"1f4b0","🪙":"1fa99","💴":"1f4b4","💵":"1f4b5","💶":"1f4b6","💷":"1f4b7","💸":"1f4b8","💳":"1f4b3","🧾":"1f9fe","💹":"1f4b9","✉":"2709","📧":"1f4e7","📨":"1f4e8","📩":"1f4e9","📤":"1f4e4","📥":"1f4e5","📦":"1f4e6","📫":"1f4eb","📪":"1f4ea","📬":"1f4ec","📭":"1f4ed","📮":"1f4ee","🗳":"1f5f3","✏":"270f","✒":"2712","🖋":"1f58b","🖊":"1f58a","🖌":"1f58c","🖍":"1f58d","📝":"1f4dd","💼":"1f4bc","📁":"1f4c1","📂":"1f4c2","🗂":"1f5c2","📅":"1f4c5","📆":"1f4c6","🗒":"1f5d2","🗓":"1f5d3","📇":"1f4c7","📈":"1f4c8","📉":"1f4c9","📊":"1f4ca","📋":"1f4cb","📌":"1f4cc","📍":"1f4cd","📎":"1f4ce","🖇":"1f587","📏":"1f4cf","📐":"1f4d0","✂":"2702","🗃":"1f5c3","🗄":"1f5c4","🗑":"1f5d1","🔒":"1f512","🔓":"1f513","🔏":"1f50f","🔐":"1f510","🔑":"1f511","🗝":"1f5dd","🔨":"1f528","🪓":"1fa93","⛏":"26cf","⚒":"2692","🛠":"1f6e0","🗡":"1f5e1","⚔":"2694","🔫":"1f52b","🪃":"1fa83","🏹":"1f3f9","🛡":"1f6e1","🪚":"1fa9a","🔧":"1f527","🪛":"1fa9b","🔩":"1f529","⚙":"2699","🗜":"1f5dc","⚖":"2696","🦯":"1f9af","🔗":"1f517","⛓":"26d3","🪝":"1fa9d","🧰":"1f9f0","🧲":"1f9f2","🪜":"1fa9c","⚗":"2697","🧪":"1f9ea","🧫":"1f9eb","🧬":"1f9ec","🔬":"1f52c","🔭":"1f52d","📡":"1f4e1","💉":"1f489","🩸":"1fa78","💊":"1f48a","🩹":"1fa79","🩺":"1fa7a","🚪":"1f6aa","🛗":"1f6d7","🪞":"1fa9e","🪟":"1fa9f","🛏":"1f6cf","🛋":"1f6cb","🪑":"1fa91","🚽":"1f6bd","🪠":"1faa0","🚿":"1f6bf","🛁":"1f6c1","🪤":"1faa4","🪒":"1fa92","🧴":"1f9f4","🧷":"1f9f7","🧹":"1f9f9","🧺":"1f9fa","🧻":"1f9fb","🪣":"1faa3","🧼":"1f9fc","🪥":"1faa5","🧽":"1f9fd","🧯":"1f9ef","🛒":"1f6d2","🚬":"1f6ac","⚰":"26b0","🪦":"1faa6","⚱":"26b1","🗿":"1f5ff","🪧":"1faa7","🏧":"1f3e7","🚮":"1f6ae","🚰":"1f6b0","♿":"267f","🚹":"1f6b9","🚺":"1f6ba","🚻":"1f6bb","🚼":"1f6bc","🚾":"1f6be","🛂":"1f6c2","🛃":"1f6c3","🛄":"1f6c4","🛅":"1f6c5","⚠":"26a0","🚸":"1f6b8","⛔":"26d4","🚫":"1f6ab","🚳":"1f6b3","🚭":"1f6ad","🚯":"1f6af","🚱":"1f6b1","🚷":"1f6b7","📵":"1f4f5","🔞":"1f51e","☢":"2622","☣":"2623","⬆":"2b06","↗":"2197","➡":"27a1","↘":"2198","⬇":"2b07","↙":"2199","⬅":"2b05","↖":"2196","↕":"2195","↔":"2194","↩":"21a9","↪":"21aa","⤴":"2934","⤵":"2935","🔃":"1f503","🔄":"1f504","🔙":"1f519","🔚":"1f51a","🔛":"1f51b","🔜":"1f51c","🔝":"1f51d","🛐":"1f6d0","⚛":"269b","🕉":"1f549","✡":"2721","☸":"2638","☯":"262f","✝":"271d","☦":"2626","☪":"262a","☮":"262e","🕎":"1f54e","🔯":"1f52f","♈":"2648","♉":"2649","♊":"264a","♋":"264b","♌":"264c","♍":"264d","♎":"264e","♏":"264f","♐":"2650","♑":"2651","♒":"2652","♓":"2653","⛎":"26ce","🔀":"1f500","🔁":"1f501","🔂":"1f502","▶":"25b6","⏩":"23e9","⏭":"23ed","⏯":"23ef","◀":"25c0","⏪":"23ea","⏮":"23ee","🔼":"1f53c","⏫":"23eb","🔽":"1f53d","⏬":"23ec","⏸":"23f8","⏹":"23f9","⏺":"23fa","⏏":"23cf","🎦":"1f3a6","🔅":"1f505","🔆":"1f506","📶":"1f4f6","📳":"1f4f3","📴":"1f4f4","♀":"2640","♂":"2642","⚧":"26a7","✖":"2716","➕":"2795","➖":"2796","➗":"2797","♾":"267e","‼":"203c","⁉":"2049","❓":"2753","❔":"2754","❕":"2755","❗":"2757","〰":"3030","💱":"1f4b1","💲":"1f4b2","⚕":"2695","♻":"267b","⚜":"269c","🔱":"1f531","📛":"1f4db","🔰":"1f530","⭕":"2b55","✅":"2705","☑":"2611","✔":"2714","❌":"274c","❎":"274e","➰":"27b0","➿":"27bf","〽":"303d","✳":"2733","✴":"2734","❇":"2747","©":"a9","®":"ae","™":"2122","🔟":"1f51f","🔠":"1f520","🔡":"1f521","🔢":"1f522","🔣":"1f523","🔤":"1f524","🅰":"1f170","🆎":"1f18e","🅱":"1f171","🆑":"1f191","🆒":"1f192","🆓":"1f193","ℹ":"2139","🆔":"1f194","Ⓜ":"24c2","🆕":"1f195","🆖":"1f196","🅾":"1f17e","🆗":"1f197","🅿":"1f17f","🆘":"1f198","🆙":"1f199","🆚":"1f19a","🈁":"1f201","🈂":"1f202","🈷":"1f237","🈶":"1f236","🈯":"1f22f","🉐":"1f250","🈹":"1f239","🈚":"1f21a","🈲":"1f232","🉑":"1f251","🈸":"1f238","🈴":"1f234","🈳":"1f233","㊗":"3297","㊙":"3299","🈺":"1f23a","🈵":"1f235","🔴":"1f534","🟠":"1f7e0","🟡":"1f7e1","🟢":"1f7e2","🔵":"1f535","🟣":"1f7e3","🟤":"1f7e4","⚫":"26ab","⚪":"26aa","🟥":"1f7e5","🟧":"1f7e7","🟨":"1f7e8","🟩":"1f7e9","🟦":"1f7e6","🟪":"1f7ea","🟫":"1f7eb","⬛":"2b1b","⬜":"2b1c","◼":"25fc","◻":"25fb","◾":"25fe","◽":"25fd","▪":"25aa","▫":"25ab","🔶":"1f536","🔷":"1f537","🔸":"1f538","🔹":"1f539","🔺":"1f53a","🔻":"1f53b","💠":"1f4a0","🔘":"1f518","🔳":"1f533","🔲":"1f532","🏁":"1f3c1","🚩":"1f6a9","🎌":"1f38c","🏴":"1f3f4","🏳":"1f3f3","☺️":"263a","☹️":"2639","☠️":"2620","❣️":"2763","❤️":"2764","🕳️":"1f573","🗨️":"1f5e8","🗯️":"1f5ef","👋🏻":"1f44b-1f3fb","👋🏼":"1f44b-1f3fc","👋🏽":"1f44b-1f3fd","👋🏾":"1f44b-1f3fe","👋🏿":"1f44b-1f3ff","🤚🏻":"1f91a-1f3fb","🤚🏼":"1f91a-1f3fc","🤚🏽":"1f91a-1f3fd","🤚🏾":"1f91a-1f3fe","🤚🏿":"1f91a-1f3ff","🖐️":"1f590","🖐🏻":"1f590-1f3fb","🖐🏼":"1f590-1f3fc","🖐🏽":"1f590-1f3fd","🖐🏾":"1f590-1f3fe","🖐🏿":"1f590-1f3ff","✋🏻":"270b-1f3fb","✋🏼":"270b-1f3fc","✋🏽":"270b-1f3fd","✋🏾":"270b-1f3fe","✋🏿":"270b-1f3ff","🖖🏻":"1f596-1f3fb","🖖🏼":"1f596-1f3fc","🖖🏽":"1f596-1f3fd","🖖🏾":"1f596-1f3fe","🖖🏿":"1f596-1f3ff","👌🏻":"1f44c-1f3fb","👌🏼":"1f44c-1f3fc","👌🏽":"1f44c-1f3fd","👌🏾":"1f44c-1f3fe","👌🏿":"1f44c-1f3ff","🤌🏻":"1f90c-1f3fb","🤌🏼":"1f90c-1f3fc","🤌🏽":"1f90c-1f3fd","🤌🏾":"1f90c-1f3fe","🤌🏿":"1f90c-1f3ff","🤏🏻":"1f90f-1f3fb","🤏🏼":"1f90f-1f3fc","🤏🏽":"1f90f-1f3fd","🤏🏾":"1f90f-1f3fe","🤏🏿":"1f90f-1f3ff","✌️":"270c","✌🏻":"270c-1f3fb","✌🏼":"270c-1f3fc","✌🏽":"270c-1f3fd","✌🏾":"270c-1f3fe","✌🏿":"270c-1f3ff","🤞🏻":"1f91e-1f3fb","🤞🏼":"1f91e-1f3fc","🤞🏽":"1f91e-1f3fd","🤞🏾":"1f91e-1f3fe","🤞🏿":"1f91e-1f3ff","🤟🏻":"1f91f-1f3fb","🤟🏼":"1f91f-1f3fc","🤟🏽":"1f91f-1f3fd","🤟🏾":"1f91f-1f3fe","🤟🏿":"1f91f-1f3ff","🤘🏻":"1f918-1f3fb","🤘🏼":"1f918-1f3fc","🤘🏽":"1f918-1f3fd","🤘🏾":"1f918-1f3fe","🤘🏿":"1f918-1f3ff","🤙🏻":"1f919-1f3fb","🤙🏼":"1f919-1f3fc","🤙🏽":"1f919-1f3fd","🤙🏾":"1f919-1f3fe","🤙🏿":"1f919-1f3ff","👈🏻":"1f448-1f3fb","👈🏼":"1f448-1f3fc","👈🏽":"1f448-1f3fd","👈🏾":"1f448-1f3fe","👈🏿":"1f448-1f3ff","👉🏻":"1f449-1f3fb","👉🏼":"1f449-1f3fc","👉🏽":"1f449-1f3fd","👉🏾":"1f449-1f3fe","👉🏿":"1f449-1f3ff","👆🏻":"1f446-1f3fb","👆🏼":"1f446-1f3fc","👆🏽":"1f446-1f3fd","👆🏾":"1f446-1f3fe","👆🏿":"1f446-1f3ff","🖕🏻":"1f595-1f3fb","🖕🏼":"1f595-1f3fc","🖕🏽":"1f595-1f3fd","🖕🏾":"1f595-1f3fe","🖕🏿":"1f595-1f3ff","👇🏻":"1f447-1f3fb","👇🏼":"1f447-1f3fc","👇🏽":"1f447-1f3fd","👇🏾":"1f447-1f3fe","👇🏿":"1f447-1f3ff","☝️":"261d","☝🏻":"261d-1f3fb","☝🏼":"261d-1f3fc","☝🏽":"261d-1f3fd","☝🏾":"261d-1f3fe","☝🏿":"261d-1f3ff","👍🏻":"1f44d-1f3fb","👍🏼":"1f44d-1f3fc","👍🏽":"1f44d-1f3fd","👍🏾":"1f44d-1f3fe","👍🏿":"1f44d-1f3ff","👎🏻":"1f44e-1f3fb","👎🏼":"1f44e-1f3fc","👎🏽":"1f44e-1f3fd","👎🏾":"1f44e-1f3fe","👎🏿":"1f44e-1f3ff","✊🏻":"270a-1f3fb","✊🏼":"270a-1f3fc","✊🏽":"270a-1f3fd","✊🏾":"270a-1f3fe","✊🏿":"270a-1f3ff","👊🏻":"1f44a-1f3fb","👊🏼":"1f44a-1f3fc","👊🏽":"1f44a-1f3fd","👊🏾":"1f44a-1f3fe","👊🏿":"1f44a-1f3ff","🤛🏻":"1f91b-1f3fb","🤛🏼":"1f91b-1f3fc","🤛🏽":"1f91b-1f3fd","🤛🏾":"1f91b-1f3fe","🤛🏿":"1f91b-1f3ff","🤜🏻":"1f91c-1f3fb","🤜🏼":"1f91c-1f3fc","🤜🏽":"1f91c-1f3fd","🤜🏾":"1f91c-1f3fe","🤜🏿":"1f91c-1f3ff","👏🏻":"1f44f-1f3fb","👏🏼":"1f44f-1f3fc","👏🏽":"1f44f-1f3fd","👏🏾":"1f44f-1f3fe","👏🏿":"1f44f-1f3ff","🙌🏻":"1f64c-1f3fb","🙌🏼":"1f64c-1f3fc","🙌🏽":"1f64c-1f3fd","🙌🏾":"1f64c-1f3fe","🙌🏿":"1f64c-1f3ff","👐🏻":"1f450-1f3fb","👐🏼":"1f450-1f3fc","👐🏽":"1f450-1f3fd","👐🏾":"1f450-1f3fe","👐🏿":"1f450-1f3ff","🤲🏻":"1f932-1f3fb","🤲🏼":"1f932-1f3fc","🤲🏽":"1f932-1f3fd","🤲🏾":"1f932-1f3fe","🤲🏿":"1f932-1f3ff","🙏🏻":"1f64f-1f3fb","🙏🏼":"1f64f-1f3fc","🙏🏽":"1f64f-1f3fd","🙏🏾":"1f64f-1f3fe","🙏🏿":"1f64f-1f3ff","✍️":"270d","✍🏻":"270d-1f3fb","✍🏼":"270d-1f3fc","✍🏽":"270d-1f3fd","✍🏾":"270d-1f3fe","✍🏿":"270d-1f3ff","💅🏻":"1f485-1f3fb","💅🏼":"1f485-1f3fc","💅🏽":"1f485-1f3fd","💅🏾":"1f485-1f3fe","💅🏿":"1f485-1f3ff","🤳🏻":"1f933-1f3fb","🤳🏼":"1f933-1f3fc","🤳🏽":"1f933-1f3fd","🤳🏾":"1f933-1f3fe","🤳🏿":"1f933-1f3ff","💪🏻":"1f4aa-1f3fb","💪🏼":"1f4aa-1f3fc","💪🏽":"1f4aa-1f3fd","💪🏾":"1f4aa-1f3fe","💪🏿":"1f4aa-1f3ff","🦵🏻":"1f9b5-1f3fb","🦵🏼":"1f9b5-1f3fc","🦵🏽":"1f9b5-1f3fd","🦵🏾":"1f9b5-1f3fe","🦵🏿":"1f9b5-1f3ff","🦶🏻":"1f9b6-1f3fb","🦶🏼":"1f9b6-1f3fc","🦶🏽":"1f9b6-1f3fd","🦶🏾":"1f9b6-1f3fe","🦶🏿":"1f9b6-1f3ff","👂🏻":"1f442-1f3fb","👂🏼":"1f442-1f3fc","👂🏽":"1f442-1f3fd","👂🏾":"1f442-1f3fe","👂🏿":"1f442-1f3ff","🦻🏻":"1f9bb-1f3fb","🦻🏼":"1f9bb-1f3fc","🦻🏽":"1f9bb-1f3fd","🦻🏾":"1f9bb-1f3fe","🦻🏿":"1f9bb-1f3ff","👃🏻":"1f443-1f3fb","👃🏼":"1f443-1f3fc","👃🏽":"1f443-1f3fd","👃🏾":"1f443-1f3fe","👃🏿":"1f443-1f3ff","👁️":"1f441","👶🏻":"1f476-1f3fb","👶🏼":"1f476-1f3fc","👶🏽":"1f476-1f3fd","👶🏾":"1f476-1f3fe","👶🏿":"1f476-1f3ff","🧒🏻":"1f9d2-1f3fb","🧒🏼":"1f9d2-1f3fc","🧒🏽":"1f9d2-1f3fd","🧒🏾":"1f9d2-1f3fe","🧒🏿":"1f9d2-1f3ff","👦🏻":"1f466-1f3fb","👦🏼":"1f466-1f3fc","👦🏽":"1f466-1f3fd","👦🏾":"1f466-1f3fe","👦🏿":"1f466-1f3ff","👧🏻":"1f467-1f3fb","👧🏼":"1f467-1f3fc","👧🏽":"1f467-1f3fd","👧🏾":"1f467-1f3fe","👧🏿":"1f467-1f3ff","🧑🏻":"1f9d1-1f3fb","🧑🏼":"1f9d1-1f3fc","🧑🏽":"1f9d1-1f3fd","🧑🏾":"1f9d1-1f3fe","🧑🏿":"1f9d1-1f3ff","👱🏻":"1f471-1f3fb","👱🏼":"1f471-1f3fc","👱🏽":"1f471-1f3fd","👱🏾":"1f471-1f3fe","👱🏿":"1f471-1f3ff","👨🏻":"1f468-1f3fb","👨🏼":"1f468-1f3fc","👨🏽":"1f468-1f3fd","👨🏾":"1f468-1f3fe","👨🏿":"1f468-1f3ff","🧔🏻":"1f9d4-1f3fb","🧔🏼":"1f9d4-1f3fc","🧔🏽":"1f9d4-1f3fd","🧔🏾":"1f9d4-1f3fe","🧔🏿":"1f9d4-1f3ff","👩🏻":"1f469-1f3fb","👩🏼":"1f469-1f3fc","👩🏽":"1f469-1f3fd","👩🏾":"1f469-1f3fe","👩🏿":"1f469-1f3ff","🧓🏻":"1f9d3-1f3fb","🧓🏼":"1f9d3-1f3fc","🧓🏽":"1f9d3-1f3fd","🧓🏾":"1f9d3-1f3fe","🧓🏿":"1f9d3-1f3ff","👴🏻":"1f474-1f3fb","👴🏼":"1f474-1f3fc","👴🏽":"1f474-1f3fd","👴🏾":"1f474-1f3fe","👴🏿":"1f474-1f3ff","👵🏻":"1f475-1f3fb","👵🏼":"1f475-1f3fc","👵🏽":"1f475-1f3fd","👵🏾":"1f475-1f3fe","👵🏿":"1f475-1f3ff","🙍🏻":"1f64d-1f3fb","🙍🏼":"1f64d-1f3fc","🙍🏽":"1f64d-1f3fd","🙍🏾":"1f64d-1f3fe","🙍🏿":"1f64d-1f3ff","🙎🏻":"1f64e-1f3fb","🙎🏼":"1f64e-1f3fc","🙎🏽":"1f64e-1f3fd","🙎🏾":"1f64e-1f3fe","🙎🏿":"1f64e-1f3ff","🙅🏻":"1f645-1f3fb","🙅🏼":"1f645-1f3fc","🙅🏽":"1f645-1f3fd","🙅🏾":"1f645-1f3fe","🙅🏿":"1f645-1f3ff","🙆🏻":"1f646-1f3fb","🙆🏼":"1f646-1f3fc","🙆🏽":"1f646-1f3fd","🙆🏾":"1f646-1f3fe","🙆🏿":"1f646-1f3ff","💁🏻":"1f481-1f3fb","💁🏼":"1f481-1f3fc","💁🏽":"1f481-1f3fd","💁🏾":"1f481-1f3fe","💁🏿":"1f481-1f3ff","🙋🏻":"1f64b-1f3fb","🙋🏼":"1f64b-1f3fc","🙋🏽":"1f64b-1f3fd","🙋🏾":"1f64b-1f3fe","🙋🏿":"1f64b-1f3ff","🧏🏻":"1f9cf-1f3fb","🧏🏼":"1f9cf-1f3fc","🧏🏽":"1f9cf-1f3fd","🧏🏾":"1f9cf-1f3fe","🧏🏿":"1f9cf-1f3ff","🙇🏻":"1f647-1f3fb","🙇🏼":"1f647-1f3fc","🙇🏽":"1f647-1f3fd","🙇🏾":"1f647-1f3fe","🙇🏿":"1f647-1f3ff","🤦🏻":"1f926-1f3fb","🤦🏼":"1f926-1f3fc","🤦🏽":"1f926-1f3fd","🤦🏾":"1f926-1f3fe","🤦🏿":"1f926-1f3ff","🤷🏻":"1f937-1f3fb","🤷🏼":"1f937-1f3fc","🤷🏽":"1f937-1f3fd","🤷🏾":"1f937-1f3fe","🤷🏿":"1f937-1f3ff","👮🏻":"1f46e-1f3fb","👮🏼":"1f46e-1f3fc","👮🏽":"1f46e-1f3fd","👮🏾":"1f46e-1f3fe","👮🏿":"1f46e-1f3ff","🕵️":"1f575","🕵🏻":"1f575-1f3fb","🕵🏼":"1f575-1f3fc","🕵🏽":"1f575-1f3fd","🕵🏾":"1f575-1f3fe","🕵🏿":"1f575-1f3ff","💂🏻":"1f482-1f3fb","💂🏼":"1f482-1f3fc","💂🏽":"1f482-1f3fd","💂🏾":"1f482-1f3fe","💂🏿":"1f482-1f3ff","🥷🏻":"1f977-1f3fb","🥷🏼":"1f977-1f3fc","🥷🏽":"1f977-1f3fd","🥷🏾":"1f977-1f3fe","🥷🏿":"1f977-1f3ff","👷🏻":"1f477-1f3fb","👷🏼":"1f477-1f3fc","👷🏽":"1f477-1f3fd","👷🏾":"1f477-1f3fe","👷🏿":"1f477-1f3ff","🤴🏻":"1f934-1f3fb","🤴🏼":"1f934-1f3fc","🤴🏽":"1f934-1f3fd","🤴🏾":"1f934-1f3fe","🤴🏿":"1f934-1f3ff","👸🏻":"1f478-1f3fb","👸🏼":"1f478-1f3fc","👸🏽":"1f478-1f3fd","👸🏾":"1f478-1f3fe","👸🏿":"1f478-1f3ff","👳🏻":"1f473-1f3fb","👳🏼":"1f473-1f3fc","👳🏽":"1f473-1f3fd","👳🏾":"1f473-1f3fe","👳🏿":"1f473-1f3ff","👲🏻":"1f472-1f3fb","👲🏼":"1f472-1f3fc","👲🏽":"1f472-1f3fd","👲🏾":"1f472-1f3fe","👲🏿":"1f472-1f3ff","🧕🏻":"1f9d5-1f3fb","🧕🏼":"1f9d5-1f3fc","🧕🏽":"1f9d5-1f3fd","🧕🏾":"1f9d5-1f3fe","🧕🏿":"1f9d5-1f3ff","🤵🏻":"1f935-1f3fb","🤵🏼":"1f935-1f3fc","🤵🏽":"1f935-1f3fd","🤵🏾":"1f935-1f3fe","🤵🏿":"1f935-1f3ff","👰🏻":"1f470-1f3fb","👰🏼":"1f470-1f3fc","👰🏽":"1f470-1f3fd","👰🏾":"1f470-1f3fe","👰🏿":"1f470-1f3ff","🤰🏻":"1f930-1f3fb","🤰🏼":"1f930-1f3fc","🤰🏽":"1f930-1f3fd","🤰🏾":"1f930-1f3fe","🤰🏿":"1f930-1f3ff","🤱🏻":"1f931-1f3fb","🤱🏼":"1f931-1f3fc","🤱🏽":"1f931-1f3fd","🤱🏾":"1f931-1f3fe","🤱🏿":"1f931-1f3ff","👼🏻":"1f47c-1f3fb","👼🏼":"1f47c-1f3fc","👼🏽":"1f47c-1f3fd","👼🏾":"1f47c-1f3fe","👼🏿":"1f47c-1f3ff","🎅🏻":"1f385-1f3fb","🎅🏼":"1f385-1f3fc","🎅🏽":"1f385-1f3fd","🎅🏾":"1f385-1f3fe","🎅🏿":"1f385-1f3ff","🤶🏻":"1f936-1f3fb","🤶🏼":"1f936-1f3fc","🤶🏽":"1f936-1f3fd","🤶🏾":"1f936-1f3fe","🤶🏿":"1f936-1f3ff","🦸🏻":"1f9b8-1f3fb","🦸🏼":"1f9b8-1f3fc","🦸🏽":"1f9b8-1f3fd","🦸🏾":"1f9b8-1f3fe","🦸🏿":"1f9b8-1f3ff","🦹🏻":"1f9b9-1f3fb","🦹🏼":"1f9b9-1f3fc","🦹🏽":"1f9b9-1f3fd","🦹🏾":"1f9b9-1f3fe","🦹🏿":"1f9b9-1f3ff","🧙🏻":"1f9d9-1f3fb","🧙🏼":"1f9d9-1f3fc","🧙🏽":"1f9d9-1f3fd","🧙🏾":"1f9d9-1f3fe","🧙🏿":"1f9d9-1f3ff","🧚🏻":"1f9da-1f3fb","🧚🏼":"1f9da-1f3fc","🧚🏽":"1f9da-1f3fd","🧚🏾":"1f9da-1f3fe","🧚🏿":"1f9da-1f3ff","🧛🏻":"1f9db-1f3fb","🧛🏼":"1f9db-1f3fc","🧛🏽":"1f9db-1f3fd","🧛🏾":"1f9db-1f3fe","🧛🏿":"1f9db-1f3ff","🧜🏻":"1f9dc-1f3fb","🧜🏼":"1f9dc-1f3fc","🧜🏽":"1f9dc-1f3fd","🧜🏾":"1f9dc-1f3fe","🧜🏿":"1f9dc-1f3ff","🧝🏻":"1f9dd-1f3fb","🧝🏼":"1f9dd-1f3fc","🧝🏽":"1f9dd-1f3fd","🧝🏾":"1f9dd-1f3fe","🧝🏿":"1f9dd-1f3ff","💆🏻":"1f486-1f3fb","💆🏼":"1f486-1f3fc","💆🏽":"1f486-1f3fd","💆🏾":"1f486-1f3fe","💆🏿":"1f486-1f3ff","💇🏻":"1f487-1f3fb","💇🏼":"1f487-1f3fc","💇🏽":"1f487-1f3fd","💇🏾":"1f487-1f3fe","💇🏿":"1f487-1f3ff","🚶🏻":"1f6b6-1f3fb","🚶🏼":"1f6b6-1f3fc","🚶🏽":"1f6b6-1f3fd","🚶🏾":"1f6b6-1f3fe","🚶🏿":"1f6b6-1f3ff","🧍🏻":"1f9cd-1f3fb","🧍🏼":"1f9cd-1f3fc","🧍🏽":"1f9cd-1f3fd","🧍🏾":"1f9cd-1f3fe","🧍🏿":"1f9cd-1f3ff","🧎🏻":"1f9ce-1f3fb","🧎🏼":"1f9ce-1f3fc","🧎🏽":"1f9ce-1f3fd","🧎🏾":"1f9ce-1f3fe","🧎🏿":"1f9ce-1f3ff","🏃🏻":"1f3c3-1f3fb","🏃🏼":"1f3c3-1f3fc","🏃🏽":"1f3c3-1f3fd","🏃🏾":"1f3c3-1f3fe","🏃🏿":"1f3c3-1f3ff","💃🏻":"1f483-1f3fb","💃🏼":"1f483-1f3fc","💃🏽":"1f483-1f3fd","💃🏾":"1f483-1f3fe","💃🏿":"1f483-1f3ff","🕺🏻":"1f57a-1f3fb","🕺🏼":"1f57a-1f3fc","🕺🏽":"1f57a-1f3fd","🕺🏾":"1f57a-1f3fe","🕺🏿":"1f57a-1f3ff","🕴️":"1f574","🕴🏻":"1f574-1f3fb","🕴🏼":"1f574-1f3fc","🕴🏽":"1f574-1f3fd","🕴🏾":"1f574-1f3fe","🕴🏿":"1f574-1f3ff","🧖🏻":"1f9d6-1f3fb","🧖🏼":"1f9d6-1f3fc","🧖🏽":"1f9d6-1f3fd","🧖🏾":"1f9d6-1f3fe","🧖🏿":"1f9d6-1f3ff","🧗🏻":"1f9d7-1f3fb","🧗🏼":"1f9d7-1f3fc","🧗🏽":"1f9d7-1f3fd","🧗🏾":"1f9d7-1f3fe","🧗🏿":"1f9d7-1f3ff","🏇🏻":"1f3c7-1f3fb","🏇🏼":"1f3c7-1f3fc","🏇🏽":"1f3c7-1f3fd","🏇🏾":"1f3c7-1f3fe","🏇🏿":"1f3c7-1f3ff","⛷️":"26f7","🏂🏻":"1f3c2-1f3fb","🏂🏼":"1f3c2-1f3fc","🏂🏽":"1f3c2-1f3fd","🏂🏾":"1f3c2-1f3fe","🏂🏿":"1f3c2-1f3ff","🏌️":"1f3cc","🏌🏻":"1f3cc-1f3fb","🏌🏼":"1f3cc-1f3fc","🏌🏽":"1f3cc-1f3fd","🏌🏾":"1f3cc-1f3fe","🏌🏿":"1f3cc-1f3ff","🏄🏻":"1f3c4-1f3fb","🏄🏼":"1f3c4-1f3fc","🏄🏽":"1f3c4-1f3fd","🏄🏾":"1f3c4-1f3fe","🏄🏿":"1f3c4-1f3ff","🚣🏻":"1f6a3-1f3fb","🚣🏼":"1f6a3-1f3fc","🚣🏽":"1f6a3-1f3fd","🚣🏾":"1f6a3-1f3fe","🚣🏿":"1f6a3-1f3ff","🏊🏻":"1f3ca-1f3fb","🏊🏼":"1f3ca-1f3fc","🏊🏽":"1f3ca-1f3fd","🏊🏾":"1f3ca-1f3fe","🏊🏿":"1f3ca-1f3ff","⛹️":"26f9","⛹🏻":"26f9-1f3fb","⛹🏼":"26f9-1f3fc","⛹🏽":"26f9-1f3fd","⛹🏾":"26f9-1f3fe","⛹🏿":"26f9-1f3ff","🏋️":"1f3cb","🏋🏻":"1f3cb-1f3fb","🏋🏼":"1f3cb-1f3fc","🏋🏽":"1f3cb-1f3fd","🏋🏾":"1f3cb-1f3fe","🏋🏿":"1f3cb-1f3ff","🚴🏻":"1f6b4-1f3fb","🚴🏼":"1f6b4-1f3fc","🚴🏽":"1f6b4-1f3fd","🚴🏾":"1f6b4-1f3fe","🚴🏿":"1f6b4-1f3ff","🚵🏻":"1f6b5-1f3fb","🚵🏼":"1f6b5-1f3fc","🚵🏽":"1f6b5-1f3fd","🚵🏾":"1f6b5-1f3fe","🚵🏿":"1f6b5-1f3ff","🤸🏻":"1f938-1f3fb","🤸🏼":"1f938-1f3fc","🤸🏽":"1f938-1f3fd","🤸🏾":"1f938-1f3fe","🤸🏿":"1f938-1f3ff","🤽🏻":"1f93d-1f3fb","🤽🏼":"1f93d-1f3fc","🤽🏽":"1f93d-1f3fd","🤽🏾":"1f93d-1f3fe","🤽🏿":"1f93d-1f3ff","🤾🏻":"1f93e-1f3fb","🤾🏼":"1f93e-1f3fc","🤾🏽":"1f93e-1f3fd","🤾🏾":"1f93e-1f3fe","🤾🏿":"1f93e-1f3ff","🤹🏻":"1f939-1f3fb","🤹🏼":"1f939-1f3fc","🤹🏽":"1f939-1f3fd","🤹🏾":"1f939-1f3fe","🤹🏿":"1f939-1f3ff","🧘🏻":"1f9d8-1f3fb","🧘🏼":"1f9d8-1f3fc","🧘🏽":"1f9d8-1f3fd","🧘🏾":"1f9d8-1f3fe","🧘🏿":"1f9d8-1f3ff","🛀🏻":"1f6c0-1f3fb","🛀🏼":"1f6c0-1f3fc","🛀🏽":"1f6c0-1f3fd","🛀🏾":"1f6c0-1f3fe","🛀🏿":"1f6c0-1f3ff","🛌🏻":"1f6cc-1f3fb","🛌🏼":"1f6cc-1f3fc","🛌🏽":"1f6cc-1f3fd","🛌🏾":"1f6cc-1f3fe","🛌🏿":"1f6cc-1f3ff","👭🏻":"1f46d-1f3fb","👭🏼":"1f46d-1f3fc","👭🏽":"1f46d-1f3fd","👭🏾":"1f46d-1f3fe","👭🏿":"1f46d-1f3ff","👫🏻":"1f46b-1f3fb","👫🏼":"1f46b-1f3fc","👫🏽":"1f46b-1f3fd","👫🏾":"1f46b-1f3fe","👫🏿":"1f46b-1f3ff","👬🏻":"1f46c-1f3fb","👬🏼":"1f46c-1f3fc","👬🏽":"1f46c-1f3fd","👬🏾":"1f46c-1f3fe","👬🏿":"1f46c-1f3ff","💏🏻":"1f48f-1f3fb","💏🏼":"1f48f-1f3fc","💏🏽":"1f48f-1f3fd","💏🏾":"1f48f-1f3fe","💏🏿":"1f48f-1f3ff","💑🏻":"1f491-1f3fb","💑🏼":"1f491-1f3fc","💑🏽":"1f491-1f3fd","💑🏾":"1f491-1f3fe","💑🏿":"1f491-1f3ff","🗣️":"1f5e3","🐿️":"1f43f","🕊️":"1f54a","🕷️":"1f577","🕸️":"1f578","🏵️":"1f3f5","☘️":"2618","🌶️":"1f336","🍽️":"1f37d","🗺️":"1f5fa","🏔️":"1f3d4","⛰️":"26f0","🏕️":"1f3d5","🏖️":"1f3d6","🏜️":"1f3dc","🏝️":"1f3dd","🏞️":"1f3de","🏟️":"1f3df","🏛️":"1f3db","🏗️":"1f3d7","🏘️":"1f3d8","🏚️":"1f3da","⛩️":"26e9","🏙️":"1f3d9","♨️":"2668","🏎️":"1f3ce","🏍️":"1f3cd","🛣️":"1f6e3","🛤️":"1f6e4","🛢️":"1f6e2","🛳️":"1f6f3","⛴️":"26f4","🛥️":"1f6e5","✈️":"2708","🛩️":"1f6e9","🛰️":"1f6f0","🛎️":"1f6ce","⏱️":"23f1","⏲️":"23f2","🕰️":"1f570","🌡️":"1f321","☀️":"2600","☁️":"2601","⛈️":"26c8","🌤️":"1f324","🌥️":"1f325","🌦️":"1f326","🌧️":"1f327","🌨️":"1f328","🌩️":"1f329","🌪️":"1f32a","🌫️":"1f32b","🌬️":"1f32c","☂️":"2602","⛱️":"26f1","❄️":"2744","☃️":"2603","☄️":"2604","🎗️":"1f397","🎟️":"1f39f","🎖️":"1f396","⛸️":"26f8","🕹️":"1f579","♠️":"2660","♥️":"2665","♦️":"2666","♣️":"2663","♟️":"265f","🖼️":"1f5bc","🕶️":"1f576","🛍️":"1f6cd","⛑️":"26d1","🎙️":"1f399","🎚️":"1f39a","🎛️":"1f39b","☎️":"260e","🖥️":"1f5a5","🖨️":"1f5a8","⌨️":"2328","🖱️":"1f5b1","🖲️":"1f5b2","🎞️":"1f39e","📽️":"1f4fd","🕯️":"1f56f","🗞️":"1f5de","🏷️":"1f3f7","✉️":"2709","🗳️":"1f5f3","✏️":"270f","✒️":"2712","🖋️":"1f58b","🖊️":"1f58a","🖌️":"1f58c","🖍️":"1f58d","🗂️":"1f5c2","🗒️":"1f5d2","🗓️":"1f5d3","🖇️":"1f587","✂️":"2702","🗃️":"1f5c3","🗄️":"1f5c4","🗑️":"1f5d1","🗝️":"1f5dd","⛏️":"26cf","⚒️":"2692","🛠️":"1f6e0","🗡️":"1f5e1","⚔️":"2694","🛡️":"1f6e1","⚙️":"2699","🗜️":"1f5dc","⚖️":"2696","⛓️":"26d3","⚗️":"2697","🛏️":"1f6cf","🛋️":"1f6cb","⚰️":"26b0","⚱️":"26b1","⚠️":"26a0","☢️":"2622","☣️":"2623","⬆️":"2b06","↗️":"2197","➡️":"27a1","↘️":"2198","⬇️":"2b07","↙️":"2199","⬅️":"2b05","↖️":"2196","↕️":"2195","↔️":"2194","↩️":"21a9","↪️":"21aa","⤴️":"2934","⤵️":"2935","⚛️":"269b","🕉️":"1f549","✡️":"2721","☸️":"2638","☯️":"262f","✝️":"271d","☦️":"2626","☪️":"262a","☮️":"262e","▶️":"25b6","⏭️":"23ed","⏯️":"23ef","◀️":"25c0","⏮️":"23ee","⏸️":"23f8","⏹️":"23f9","⏺️":"23fa","⏏️":"23cf","♀️":"2640","♂️":"2642","⚧️":"26a7","✖️":"2716","♾️":"267e","‼️":"203c","⁉️":"2049","〰️":"3030","⚕️":"2695","♻️":"267b","⚜️":"269c","☑️":"2611","✔️":"2714","〽️":"303d","✳️":"2733","✴️":"2734","❇️":"2747","©️":"a9","®️":"ae","™️":"2122","#⃣":"23-20e3","*⃣":"2a-20e3","0⃣":"30-20e3","1⃣":"31-20e3","2⃣":"32-20e3","3⃣":"33-20e3","4⃣":"34-20e3","5⃣":"35-20e3","6⃣":"36-20e3","7⃣":"37-20e3","8⃣":"38-20e3","9⃣":"39-20e3","🅰️":"1f170","🅱️":"1f171","ℹ️":"2139","Ⓜ️":"24c2","🅾️":"1f17e","🅿️":"1f17f","🈂️":"1f202","🈷️":"1f237","㊗️":"3297","㊙️":"3299","◼️":"25fc","◻️":"25fb","▪️":"25aa","▫️":"25ab","🏳️":"1f3f3","🇦🇨":"1f1e6-1f1e8","🇦🇩":"1f1e6-1f1e9","🇦🇪":"1f1e6-1f1ea","🇦🇫":"1f1e6-1f1eb","🇦🇬":"1f1e6-1f1ec","🇦🇮":"1f1e6-1f1ee","🇦🇱":"1f1e6-1f1f1","🇦🇲":"1f1e6-1f1f2","🇦🇴":"1f1e6-1f1f4","🇦🇶":"1f1e6-1f1f6","🇦🇷":"1f1e6-1f1f7","🇦🇸":"1f1e6-1f1f8","🇦🇹":"1f1e6-1f1f9","🇦🇺":"1f1e6-1f1fa","🇦🇼":"1f1e6-1f1fc","🇦🇽":"1f1e6-1f1fd","🇦🇿":"1f1e6-1f1ff","🇧🇦":"1f1e7-1f1e6","🇧🇧":"1f1e7-1f1e7","🇧🇩":"1f1e7-1f1e9","🇧🇪":"1f1e7-1f1ea","🇧🇫":"1f1e7-1f1eb","🇧🇬":"1f1e7-1f1ec","🇧🇭":"1f1e7-1f1ed","🇧🇮":"1f1e7-1f1ee","🇧🇯":"1f1e7-1f1ef","🇧🇱":"1f1e7-1f1f1","🇧🇲":"1f1e7-1f1f2","🇧🇳":"1f1e7-1f1f3","🇧🇴":"1f1e7-1f1f4","🇧🇶":"1f1e7-1f1f6","🇧🇷":"1f1e7-1f1f7","🇧🇸":"1f1e7-1f1f8","🇧🇹":"1f1e7-1f1f9","🇧🇻":"1f1e7-1f1fb","🇧🇼":"1f1e7-1f1fc","🇧🇾":"1f1e7-1f1fe","🇧🇿":"1f1e7-1f1ff","🇨🇦":"1f1e8-1f1e6","🇨🇨":"1f1e8-1f1e8","🇨🇩":"1f1e8-1f1e9","🇨🇫":"1f1e8-1f1eb","🇨🇬":"1f1e8-1f1ec","🇨🇭":"1f1e8-1f1ed","🇨🇮":"1f1e8-1f1ee","🇨🇰":"1f1e8-1f1f0","🇨🇱":"1f1e8-1f1f1","🇨🇲":"1f1e8-1f1f2","🇨🇳":"1f1e8-1f1f3","🇨🇴":"1f1e8-1f1f4","🇨🇵":"1f1e8-1f1f5","🇨🇷":"1f1e8-1f1f7","🇨🇺":"1f1e8-1f1fa","🇨🇻":"1f1e8-1f1fb","🇨🇼":"1f1e8-1f1fc","🇨🇽":"1f1e8-1f1fd","🇨🇾":"1f1e8-1f1fe","🇨🇿":"1f1e8-1f1ff","🇩🇪":"1f1e9-1f1ea","🇩🇬":"1f1e9-1f1ec","🇩🇯":"1f1e9-1f1ef","🇩🇰":"1f1e9-1f1f0","🇩🇲":"1f1e9-1f1f2","🇩🇴":"1f1e9-1f1f4","🇩🇿":"1f1e9-1f1ff","🇪🇦":"1f1ea-1f1e6","🇪🇨":"1f1ea-1f1e8","🇪🇪":"1f1ea-1f1ea","🇪🇬":"1f1ea-1f1ec","🇪🇭":"1f1ea-1f1ed","🇪🇷":"1f1ea-1f1f7","🇪🇸":"1f1ea-1f1f8","🇪🇹":"1f1ea-1f1f9","🇪🇺":"1f1ea-1f1fa","🇫🇮":"1f1eb-1f1ee","🇫🇯":"1f1eb-1f1ef","🇫🇰":"1f1eb-1f1f0","🇫🇲":"1f1eb-1f1f2","🇫🇴":"1f1eb-1f1f4","🇫🇷":"1f1eb-1f1f7","🇬🇦":"1f1ec-1f1e6","🇬🇧":"1f1ec-1f1e7","🇬🇩":"1f1ec-1f1e9","🇬🇪":"1f1ec-1f1ea","🇬🇫":"1f1ec-1f1eb","🇬🇬":"1f1ec-1f1ec","🇬🇭":"1f1ec-1f1ed","🇬🇮":"1f1ec-1f1ee","🇬🇱":"1f1ec-1f1f1","🇬🇲":"1f1ec-1f1f2","🇬🇳":"1f1ec-1f1f3","🇬🇵":"1f1ec-1f1f5","🇬🇶":"1f1ec-1f1f6","🇬🇷":"1f1ec-1f1f7","🇬🇸":"1f1ec-1f1f8","🇬🇹":"1f1ec-1f1f9","🇬🇺":"1f1ec-1f1fa","🇬🇼":"1f1ec-1f1fc","🇬🇾":"1f1ec-1f1fe","🇭🇰":"1f1ed-1f1f0","🇭🇲":"1f1ed-1f1f2","🇭🇳":"1f1ed-1f1f3","🇭🇷":"1f1ed-1f1f7","🇭🇹":"1f1ed-1f1f9","🇭🇺":"1f1ed-1f1fa","🇮🇨":"1f1ee-1f1e8","🇮🇩":"1f1ee-1f1e9","🇮🇪":"1f1ee-1f1ea","🇮🇱":"1f1ee-1f1f1","🇮🇲":"1f1ee-1f1f2","🇮🇳":"1f1ee-1f1f3","🇮🇴":"1f1ee-1f1f4","🇮🇶":"1f1ee-1f1f6","🇮🇷":"1f1ee-1f1f7","🇮🇸":"1f1ee-1f1f8","🇮🇹":"1f1ee-1f1f9","🇯🇪":"1f1ef-1f1ea","🇯🇲":"1f1ef-1f1f2","🇯🇴":"1f1ef-1f1f4","🇯🇵":"1f1ef-1f1f5","🇰🇪":"1f1f0-1f1ea","🇰🇬":"1f1f0-1f1ec","🇰🇭":"1f1f0-1f1ed","🇰🇮":"1f1f0-1f1ee","🇰🇲":"1f1f0-1f1f2","🇰🇳":"1f1f0-1f1f3","🇰🇵":"1f1f0-1f1f5","🇰🇷":"1f1f0-1f1f7","🇰🇼":"1f1f0-1f1fc","🇰🇾":"1f1f0-1f1fe","🇰🇿":"1f1f0-1f1ff","🇱🇦":"1f1f1-1f1e6","🇱🇧":"1f1f1-1f1e7","🇱🇨":"1f1f1-1f1e8","🇱🇮":"1f1f1-1f1ee","🇱🇰":"1f1f1-1f1f0","🇱🇷":"1f1f1-1f1f7","🇱🇸":"1f1f1-1f1f8","🇱🇹":"1f1f1-1f1f9","🇱🇺":"1f1f1-1f1fa","🇱🇻":"1f1f1-1f1fb","🇱🇾":"1f1f1-1f1fe","🇲🇦":"1f1f2-1f1e6","🇲🇨":"1f1f2-1f1e8","🇲🇩":"1f1f2-1f1e9","🇲🇪":"1f1f2-1f1ea","🇲🇫":"1f1f2-1f1eb","🇲🇬":"1f1f2-1f1ec","🇲🇭":"1f1f2-1f1ed","🇲🇰":"1f1f2-1f1f0","🇲🇱":"1f1f2-1f1f1","🇲🇲":"1f1f2-1f1f2","🇲🇳":"1f1f2-1f1f3","🇲🇴":"1f1f2-1f1f4","🇲🇵":"1f1f2-1f1f5","🇲🇶":"1f1f2-1f1f6","🇲🇷":"1f1f2-1f1f7","🇲🇸":"1f1f2-1f1f8","🇲🇹":"1f1f2-1f1f9","🇲🇺":"1f1f2-1f1fa","🇲🇻":"1f1f2-1f1fb","🇲🇼":"1f1f2-1f1fc","🇲🇽":"1f1f2-1f1fd","🇲🇾":"1f1f2-1f1fe","🇲🇿":"1f1f2-1f1ff","🇳🇦":"1f1f3-1f1e6","🇳🇨":"1f1f3-1f1e8","🇳🇪":"1f1f3-1f1ea","🇳🇫":"1f1f3-1f1eb","🇳🇬":"1f1f3-1f1ec","🇳🇮":"1f1f3-1f1ee","🇳🇱":"1f1f3-1f1f1","🇳🇴":"1f1f3-1f1f4","🇳🇵":"1f1f3-1f1f5","🇳🇷":"1f1f3-1f1f7","🇳🇺":"1f1f3-1f1fa","🇳🇿":"1f1f3-1f1ff","🇴🇲":"1f1f4-1f1f2","🇵🇦":"1f1f5-1f1e6","🇵🇪":"1f1f5-1f1ea","🇵🇫":"1f1f5-1f1eb","🇵🇬":"1f1f5-1f1ec","🇵🇭":"1f1f5-1f1ed","🇵🇰":"1f1f5-1f1f0","🇵🇱":"1f1f5-1f1f1","🇵🇲":"1f1f5-1f1f2","🇵🇳":"1f1f5-1f1f3","🇵🇷":"1f1f5-1f1f7","🇵🇸":"1f1f5-1f1f8","🇵🇹":"1f1f5-1f1f9","🇵🇼":"1f1f5-1f1fc","🇵🇾":"1f1f5-1f1fe","🇶🇦":"1f1f6-1f1e6","🇷🇪":"1f1f7-1f1ea","🇷🇴":"1f1f7-1f1f4","🇷🇸":"1f1f7-1f1f8","🇷🇺":"1f1f7-1f1fa","🇷🇼":"1f1f7-1f1fc","🇸🇦":"1f1f8-1f1e6","🇸🇧":"1f1f8-1f1e7","🇸🇨":"1f1f8-1f1e8","🇸🇩":"1f1f8-1f1e9","🇸🇪":"1f1f8-1f1ea","🇸🇬":"1f1f8-1f1ec","🇸🇭":"1f1f8-1f1ed","🇸🇮":"1f1f8-1f1ee","🇸🇯":"1f1f8-1f1ef","🇸🇰":"1f1f8-1f1f0","🇸🇱":"1f1f8-1f1f1","🇸🇲":"1f1f8-1f1f2","🇸🇳":"1f1f8-1f1f3","🇸🇴":"1f1f8-1f1f4","🇸🇷":"1f1f8-1f1f7","🇸🇸":"1f1f8-1f1f8","🇸🇹":"1f1f8-1f1f9","🇸🇻":"1f1f8-1f1fb","🇸🇽":"1f1f8-1f1fd","🇸🇾":"1f1f8-1f1fe","🇸🇿":"1f1f8-1f1ff","🇹🇦":"1f1f9-1f1e6","🇹🇨":"1f1f9-1f1e8","🇹🇩":"1f1f9-1f1e9","🇹🇫":"1f1f9-1f1eb","🇹🇬":"1f1f9-1f1ec","🇹🇭":"1f1f9-1f1ed","🇹🇯":"1f1f9-1f1ef","🇹🇰":"1f1f9-1f1f0","🇹🇱":"1f1f9-1f1f1","🇹🇲":"1f1f9-1f1f2","🇹🇳":"1f1f9-1f1f3","🇹🇴":"1f1f9-1f1f4","🇹🇷":"1f1f9-1f1f7","🇹🇹":"1f1f9-1f1f9","🇹🇻":"1f1f9-1f1fb","🇹🇼":"1f1f9-1f1fc","🇹🇿":"1f1f9-1f1ff","🇺🇦":"1f1fa-1f1e6","🇺🇬":"1f1fa-1f1ec","🇺🇲":"1f1fa-1f1f2","🇺🇳":"1f1fa-1f1f3","🇺🇸":"1f1fa-1f1f8","🇺🇾":"1f1fa-1f1fe","🇺🇿":"1f1fa-1f1ff","🇻🇦":"1f1fb-1f1e6","🇻🇨":"1f1fb-1f1e8","🇻🇪":"1f1fb-1f1ea","🇻🇬":"1f1fb-1f1ec","🇻🇮":"1f1fb-1f1ee","🇻🇳":"1f1fb-1f1f3","🇻🇺":"1f1fb-1f1fa","🇼🇫":"1f1fc-1f1eb","🇼🇸":"1f1fc-1f1f8","🇽🇰":"1f1fd-1f1f0","🇾🇪":"1f1fe-1f1ea","🇾🇹":"1f1fe-1f1f9","🇿🇦":"1f1ff-1f1e6","🇿🇲":"1f1ff-1f1f2","🇿🇼":"1f1ff-1f1fc","😶‍🌫":"1f636-200d-1f32b-fe0f","😮‍💨":"1f62e-200d-1f4a8","😵‍💫":"1f635-200d-1f4ab","❤‍🔥":"2764-fe0f-200d-1f525","❤‍🩹":"2764-fe0f-200d-1fa79","👁‍🗨":"1f441-200d-1f5e8","🧔‍♂":"1f9d4-200d-2642-fe0f","🧔‍♀":"1f9d4-200d-2640-fe0f","👨‍🦰":"1f468-200d-1f9b0","👨‍🦱":"1f468-200d-1f9b1","👨‍🦳":"1f468-200d-1f9b3","👨‍🦲":"1f468-200d-1f9b2","👩‍🦰":"1f469-200d-1f9b0","🧑‍🦰":"1f9d1-200d-1f9b0","👩‍🦱":"1f469-200d-1f9b1","🧑‍🦱":"1f9d1-200d-1f9b1","👩‍🦳":"1f469-200d-1f9b3","🧑‍🦳":"1f9d1-200d-1f9b3","👩‍🦲":"1f469-200d-1f9b2","🧑‍🦲":"1f9d1-200d-1f9b2","👱‍♀":"1f471-200d-2640-fe0f","👱‍♂":"1f471-200d-2642-fe0f","🙍‍♂":"1f64d-200d-2642-fe0f","🙍‍♀":"1f64d-200d-2640-fe0f","🙎‍♂":"1f64e-200d-2642-fe0f","🙎‍♀":"1f64e-200d-2640-fe0f","🙅‍♂":"1f645-200d-2642-fe0f","🙅‍♀":"1f645-200d-2640-fe0f","🙆‍♂":"1f646-200d-2642-fe0f","🙆‍♀":"1f646-200d-2640-fe0f","💁‍♂":"1f481-200d-2642-fe0f","💁‍♀":"1f481-200d-2640-fe0f","🙋‍♂":"1f64b-200d-2642-fe0f","🙋‍♀":"1f64b-200d-2640-fe0f","🧏‍♂":"1f9cf-200d-2642-fe0f","🧏‍♀":"1f9cf-200d-2640-fe0f","🙇‍♂":"1f647-200d-2642-fe0f","🙇‍♀":"1f647-200d-2640-fe0f","🤦‍♂":"1f926-200d-2642-fe0f","🤦‍♀":"1f926-200d-2640-fe0f","🤷‍♂":"1f937-200d-2642-fe0f","🤷‍♀":"1f937-200d-2640-fe0f","🧑‍⚕":"1f9d1-200d-2695-fe0f","👨‍⚕":"1f468-200d-2695-fe0f","👩‍⚕":"1f469-200d-2695-fe0f","🧑‍🎓":"1f9d1-200d-1f393","👨‍🎓":"1f468-200d-1f393","👩‍🎓":"1f469-200d-1f393","🧑‍🏫":"1f9d1-200d-1f3eb","👨‍🏫":"1f468-200d-1f3eb","👩‍🏫":"1f469-200d-1f3eb","🧑‍⚖":"1f9d1-200d-2696-fe0f","👨‍⚖":"1f468-200d-2696-fe0f","👩‍⚖":"1f469-200d-2696-fe0f","🧑‍🌾":"1f9d1-200d-1f33e","👨‍🌾":"1f468-200d-1f33e","👩‍🌾":"1f469-200d-1f33e","🧑‍🍳":"1f9d1-200d-1f373","👨‍🍳":"1f468-200d-1f373","👩‍🍳":"1f469-200d-1f373","🧑‍🔧":"1f9d1-200d-1f527","👨‍🔧":"1f468-200d-1f527","👩‍🔧":"1f469-200d-1f527","🧑‍🏭":"1f9d1-200d-1f3ed","👨‍🏭":"1f468-200d-1f3ed","👩‍🏭":"1f469-200d-1f3ed","🧑‍💼":"1f9d1-200d-1f4bc","👨‍💼":"1f468-200d-1f4bc","👩‍💼":"1f469-200d-1f4bc","🧑‍🔬":"1f9d1-200d-1f52c","👨‍🔬":"1f468-200d-1f52c","👩‍🔬":"1f469-200d-1f52c","🧑‍💻":"1f9d1-200d-1f4bb","👨‍💻":"1f468-200d-1f4bb","👩‍💻":"1f469-200d-1f4bb","🧑‍🎤":"1f9d1-200d-1f3a4","👨‍🎤":"1f468-200d-1f3a4","👩‍🎤":"1f469-200d-1f3a4","🧑‍🎨":"1f9d1-200d-1f3a8","👨‍🎨":"1f468-200d-1f3a8","👩‍🎨":"1f469-200d-1f3a8","🧑‍✈":"1f9d1-200d-2708-fe0f","👨‍✈":"1f468-200d-2708-fe0f","👩‍✈":"1f469-200d-2708-fe0f","🧑‍🚀":"1f9d1-200d-1f680","👨‍🚀":"1f468-200d-1f680","👩‍🚀":"1f469-200d-1f680","🧑‍🚒":"1f9d1-200d-1f692","👨‍🚒":"1f468-200d-1f692","👩‍🚒":"1f469-200d-1f692","👮‍♂":"1f46e-200d-2642-fe0f","👮‍♀":"1f46e-200d-2640-fe0f","🕵‍♂":"1f575-fe0f-200d-2642-fe0f","🕵‍♀":"1f575-fe0f-200d-2640-fe0f","💂‍♂":"1f482-200d-2642-fe0f","💂‍♀":"1f482-200d-2640-fe0f","👷‍♂":"1f477-200d-2642-fe0f","👷‍♀":"1f477-200d-2640-fe0f","👳‍♂":"1f473-200d-2642-fe0f","👳‍♀":"1f473-200d-2640-fe0f","🤵‍♂":"1f935-200d-2642-fe0f","🤵‍♀":"1f935-200d-2640-fe0f","👰‍♂":"1f470-200d-2642-fe0f","👰‍♀":"1f470-200d-2640-fe0f","👩‍🍼":"1f469-200d-1f37c","👨‍🍼":"1f468-200d-1f37c","🧑‍🍼":"1f9d1-200d-1f37c","🧑‍🎄":"1f9d1-200d-1f384","🦸‍♂":"1f9b8-200d-2642-fe0f","🦸‍♀":"1f9b8-200d-2640-fe0f","🦹‍♂":"1f9b9-200d-2642-fe0f","🦹‍♀":"1f9b9-200d-2640-fe0f","🧙‍♂":"1f9d9-200d-2642-fe0f","🧙‍♀":"1f9d9-200d-2640-fe0f","🧚‍♂":"1f9da-200d-2642-fe0f","🧚‍♀":"1f9da-200d-2640-fe0f","🧛‍♂":"1f9db-200d-2642-fe0f","🧛‍♀":"1f9db-200d-2640-fe0f","🧜‍♂":"1f9dc-200d-2642-fe0f","🧜‍♀":"1f9dc-200d-2640-fe0f","🧝‍♂":"1f9dd-200d-2642-fe0f","🧝‍♀":"1f9dd-200d-2640-fe0f","🧞‍♂":"1f9de-200d-2642-fe0f","🧞‍♀":"1f9de-200d-2640-fe0f","🧟‍♂":"1f9df-200d-2642-fe0f","🧟‍♀":"1f9df-200d-2640-fe0f","💆‍♂":"1f486-200d-2642-fe0f","💆‍♀":"1f486-200d-2640-fe0f","💇‍♂":"1f487-200d-2642-fe0f","💇‍♀":"1f487-200d-2640-fe0f","🚶‍♂":"1f6b6-200d-2642-fe0f","🚶‍♀":"1f6b6-200d-2640-fe0f","🧍‍♂":"1f9cd-200d-2642-fe0f","🧍‍♀":"1f9cd-200d-2640-fe0f","🧎‍♂":"1f9ce-200d-2642-fe0f","🧎‍♀":"1f9ce-200d-2640-fe0f","🧑‍🦯":"1f9d1-200d-1f9af","👨‍🦯":"1f468-200d-1f9af","👩‍🦯":"1f469-200d-1f9af","🧑‍🦼":"1f9d1-200d-1f9bc","👨‍🦼":"1f468-200d-1f9bc","👩‍🦼":"1f469-200d-1f9bc","🧑‍🦽":"1f9d1-200d-1f9bd","👨‍🦽":"1f468-200d-1f9bd","👩‍🦽":"1f469-200d-1f9bd","🏃‍♂":"1f3c3-200d-2642-fe0f","🏃‍♀":"1f3c3-200d-2640-fe0f","👯‍♂":"1f46f-200d-2642-fe0f","👯‍♀":"1f46f-200d-2640-fe0f","🧖‍♂":"1f9d6-200d-2642-fe0f","🧖‍♀":"1f9d6-200d-2640-fe0f","🧗‍♂":"1f9d7-200d-2642-fe0f","🧗‍♀":"1f9d7-200d-2640-fe0f","🏌‍♂":"1f3cc-fe0f-200d-2642-fe0f","🏌‍♀":"1f3cc-fe0f-200d-2640-fe0f","🏄‍♂":"1f3c4-200d-2642-fe0f","🏄‍♀":"1f3c4-200d-2640-fe0f","🚣‍♂":"1f6a3-200d-2642-fe0f","🚣‍♀":"1f6a3-200d-2640-fe0f","🏊‍♂":"1f3ca-200d-2642-fe0f","🏊‍♀":"1f3ca-200d-2640-fe0f","⛹‍♂":"26f9-fe0f-200d-2642-fe0f","⛹‍♀":"26f9-fe0f-200d-2640-fe0f","🏋‍♂":"1f3cb-fe0f-200d-2642-fe0f","🏋‍♀":"1f3cb-fe0f-200d-2640-fe0f","🚴‍♂":"1f6b4-200d-2642-fe0f","🚴‍♀":"1f6b4-200d-2640-fe0f","🚵‍♂":"1f6b5-200d-2642-fe0f","🚵‍♀":"1f6b5-200d-2640-fe0f","🤸‍♂":"1f938-200d-2642-fe0f","🤸‍♀":"1f938-200d-2640-fe0f","🤼‍♂":"1f93c-200d-2642-fe0f","🤼‍♀":"1f93c-200d-2640-fe0f","🤽‍♂":"1f93d-200d-2642-fe0f","🤽‍♀":"1f93d-200d-2640-fe0f","🤾‍♂":"1f93e-200d-2642-fe0f","🤾‍♀":"1f93e-200d-2640-fe0f","🤹‍♂":"1f939-200d-2642-fe0f","🤹‍♀":"1f939-200d-2640-fe0f","🧘‍♂":"1f9d8-200d-2642-fe0f","🧘‍♀":"1f9d8-200d-2640-fe0f","👨‍👦":"1f468-200d-1f466","👨‍👧":"1f468-200d-1f467","👩‍👦":"1f469-200d-1f466","👩‍👧":"1f469-200d-1f467","🐕‍🦺":"1f415-200d-1f9ba","🐈‍⬛":"1f408-200d-2b1b","🐻‍❄":"1f43b-200d-2744-fe0f","#️⃣":"23-20e3","*️⃣":"2a-20e3","0️⃣":"30-20e3","1️⃣":"31-20e3","2️⃣":"32-20e3","3️⃣":"33-20e3","4️⃣":"34-20e3","5️⃣":"35-20e3","6️⃣":"36-20e3","7️⃣":"37-20e3","8️⃣":"38-20e3","9️⃣":"39-20e3","🏳‍🌈":"1f3f3-fe0f-200d-1f308","🏳‍⚧":"1f3f3-fe0f-200d-26a7-fe0f","🏴‍☠":"1f3f4-200d-2620-fe0f","😶‍🌫️":"1f636-200d-1f32b-fe0f","❤️‍🔥":"2764-fe0f-200d-1f525","❤️‍🩹":"2764-fe0f-200d-1fa79","👁‍🗨️":"1f441-200d-1f5e8","👁️‍🗨":"1f441-200d-1f5e8","🧔‍♂️":"1f9d4-200d-2642-fe0f","🧔🏻‍♂":"1f9d4-1f3fb-200d-2642-fe0f","🧔🏼‍♂":"1f9d4-1f3fc-200d-2642-fe0f","🧔🏽‍♂":"1f9d4-1f3fd-200d-2642-fe0f","🧔🏾‍♂":"1f9d4-1f3fe-200d-2642-fe0f","🧔🏿‍♂":"1f9d4-1f3ff-200d-2642-fe0f","🧔‍♀️":"1f9d4-200d-2640-fe0f","🧔🏻‍♀":"1f9d4-1f3fb-200d-2640-fe0f","🧔🏼‍♀":"1f9d4-1f3fc-200d-2640-fe0f","🧔🏽‍♀":"1f9d4-1f3fd-200d-2640-fe0f","🧔🏾‍♀":"1f9d4-1f3fe-200d-2640-fe0f","🧔🏿‍♀":"1f9d4-1f3ff-200d-2640-fe0f","👨🏻‍🦰":"1f468-1f3fb-200d-1f9b0","👨🏼‍🦰":"1f468-1f3fc-200d-1f9b0","👨🏽‍🦰":"1f468-1f3fd-200d-1f9b0","👨🏾‍🦰":"1f468-1f3fe-200d-1f9b0","👨🏿‍🦰":"1f468-1f3ff-200d-1f9b0","👨🏻‍🦱":"1f468-1f3fb-200d-1f9b1","👨🏼‍🦱":"1f468-1f3fc-200d-1f9b1","👨🏽‍🦱":"1f468-1f3fd-200d-1f9b1","👨🏾‍🦱":"1f468-1f3fe-200d-1f9b1","👨🏿‍🦱":"1f468-1f3ff-200d-1f9b1","👨🏻‍🦳":"1f468-1f3fb-200d-1f9b3","👨🏼‍🦳":"1f468-1f3fc-200d-1f9b3","👨🏽‍🦳":"1f468-1f3fd-200d-1f9b3","👨🏾‍🦳":"1f468-1f3fe-200d-1f9b3","👨🏿‍🦳":"1f468-1f3ff-200d-1f9b3","👨🏻‍🦲":"1f468-1f3fb-200d-1f9b2","👨🏼‍🦲":"1f468-1f3fc-200d-1f9b2","👨🏽‍🦲":"1f468-1f3fd-200d-1f9b2","👨🏾‍🦲":"1f468-1f3fe-200d-1f9b2","👨🏿‍🦲":"1f468-1f3ff-200d-1f9b2","👩🏻‍🦰":"1f469-1f3fb-200d-1f9b0","👩🏼‍🦰":"1f469-1f3fc-200d-1f9b0","👩🏽‍🦰":"1f469-1f3fd-200d-1f9b0","👩🏾‍🦰":"1f469-1f3fe-200d-1f9b0","👩🏿‍🦰":"1f469-1f3ff-200d-1f9b0","🧑🏻‍🦰":"1f9d1-1f3fb-200d-1f9b0","🧑🏼‍🦰":"1f9d1-1f3fc-200d-1f9b0","🧑🏽‍🦰":"1f9d1-1f3fd-200d-1f9b0","🧑🏾‍🦰":"1f9d1-1f3fe-200d-1f9b0","🧑🏿‍🦰":"1f9d1-1f3ff-200d-1f9b0","👩🏻‍🦱":"1f469-1f3fb-200d-1f9b1","👩🏼‍🦱":"1f469-1f3fc-200d-1f9b1","👩🏽‍🦱":"1f469-1f3fd-200d-1f9b1","👩🏾‍🦱":"1f469-1f3fe-200d-1f9b1","👩🏿‍🦱":"1f469-1f3ff-200d-1f9b1","🧑🏻‍🦱":"1f9d1-1f3fb-200d-1f9b1","🧑🏼‍🦱":"1f9d1-1f3fc-200d-1f9b1","🧑🏽‍🦱":"1f9d1-1f3fd-200d-1f9b1","🧑🏾‍🦱":"1f9d1-1f3fe-200d-1f9b1","🧑🏿‍🦱":"1f9d1-1f3ff-200d-1f9b1","👩🏻‍🦳":"1f469-1f3fb-200d-1f9b3","👩🏼‍🦳":"1f469-1f3fc-200d-1f9b3","👩🏽‍🦳":"1f469-1f3fd-200d-1f9b3","👩🏾‍🦳":"1f469-1f3fe-200d-1f9b3","👩🏿‍🦳":"1f469-1f3ff-200d-1f9b3","🧑🏻‍🦳":"1f9d1-1f3fb-200d-1f9b3","🧑🏼‍🦳":"1f9d1-1f3fc-200d-1f9b3","🧑🏽‍🦳":"1f9d1-1f3fd-200d-1f9b3","🧑🏾‍🦳":"1f9d1-1f3fe-200d-1f9b3","🧑🏿‍🦳":"1f9d1-1f3ff-200d-1f9b3","👩🏻‍🦲":"1f469-1f3fb-200d-1f9b2","👩🏼‍🦲":"1f469-1f3fc-200d-1f9b2","👩🏽‍🦲":"1f469-1f3fd-200d-1f9b2","👩🏾‍🦲":"1f469-1f3fe-200d-1f9b2","👩🏿‍🦲":"1f469-1f3ff-200d-1f9b2","🧑🏻‍🦲":"1f9d1-1f3fb-200d-1f9b2","🧑🏼‍🦲":"1f9d1-1f3fc-200d-1f9b2","🧑🏽‍🦲":"1f9d1-1f3fd-200d-1f9b2","🧑🏾‍🦲":"1f9d1-1f3fe-200d-1f9b2","🧑🏿‍🦲":"1f9d1-1f3ff-200d-1f9b2","👱‍♀️":"1f471-200d-2640-fe0f","👱🏻‍♀":"1f471-1f3fb-200d-2640-fe0f","👱🏼‍♀":"1f471-1f3fc-200d-2640-fe0f","👱🏽‍♀":"1f471-1f3fd-200d-2640-fe0f","👱🏾‍♀":"1f471-1f3fe-200d-2640-fe0f","👱🏿‍♀":"1f471-1f3ff-200d-2640-fe0f","👱‍♂️":"1f471-200d-2642-fe0f","👱🏻‍♂":"1f471-1f3fb-200d-2642-fe0f","👱🏼‍♂":"1f471-1f3fc-200d-2642-fe0f","👱🏽‍♂":"1f471-1f3fd-200d-2642-fe0f","👱🏾‍♂":"1f471-1f3fe-200d-2642-fe0f","👱🏿‍♂":"1f471-1f3ff-200d-2642-fe0f","🙍‍♂️":"1f64d-200d-2642-fe0f","🙍🏻‍♂":"1f64d-1f3fb-200d-2642-fe0f","🙍🏼‍♂":"1f64d-1f3fc-200d-2642-fe0f","🙍🏽‍♂":"1f64d-1f3fd-200d-2642-fe0f","🙍🏾‍♂":"1f64d-1f3fe-200d-2642-fe0f","🙍🏿‍♂":"1f64d-1f3ff-200d-2642-fe0f","🙍‍♀️":"1f64d-200d-2640-fe0f","🙍🏻‍♀":"1f64d-1f3fb-200d-2640-fe0f","🙍🏼‍♀":"1f64d-1f3fc-200d-2640-fe0f","🙍🏽‍♀":"1f64d-1f3fd-200d-2640-fe0f","🙍🏾‍♀":"1f64d-1f3fe-200d-2640-fe0f","🙍🏿‍♀":"1f64d-1f3ff-200d-2640-fe0f","🙎‍♂️":"1f64e-200d-2642-fe0f","🙎🏻‍♂":"1f64e-1f3fb-200d-2642-fe0f","🙎🏼‍♂":"1f64e-1f3fc-200d-2642-fe0f","🙎🏽‍♂":"1f64e-1f3fd-200d-2642-fe0f","🙎🏾‍♂":"1f64e-1f3fe-200d-2642-fe0f","🙎🏿‍♂":"1f64e-1f3ff-200d-2642-fe0f","🙎‍♀️":"1f64e-200d-2640-fe0f","🙎🏻‍♀":"1f64e-1f3fb-200d-2640-fe0f","🙎🏼‍♀":"1f64e-1f3fc-200d-2640-fe0f","🙎🏽‍♀":"1f64e-1f3fd-200d-2640-fe0f","🙎🏾‍♀":"1f64e-1f3fe-200d-2640-fe0f","🙎🏿‍♀":"1f64e-1f3ff-200d-2640-fe0f","🙅‍♂️":"1f645-200d-2642-fe0f","🙅🏻‍♂":"1f645-1f3fb-200d-2642-fe0f","🙅🏼‍♂":"1f645-1f3fc-200d-2642-fe0f","🙅🏽‍♂":"1f645-1f3fd-200d-2642-fe0f","🙅🏾‍♂":"1f645-1f3fe-200d-2642-fe0f","🙅🏿‍♂":"1f645-1f3ff-200d-2642-fe0f","🙅‍♀️":"1f645-200d-2640-fe0f","🙅🏻‍♀":"1f645-1f3fb-200d-2640-fe0f","🙅🏼‍♀":"1f645-1f3fc-200d-2640-fe0f","🙅🏽‍♀":"1f645-1f3fd-200d-2640-fe0f","🙅🏾‍♀":"1f645-1f3fe-200d-2640-fe0f","🙅🏿‍♀":"1f645-1f3ff-200d-2640-fe0f","🙆‍♂️":"1f646-200d-2642-fe0f","🙆🏻‍♂":"1f646-1f3fb-200d-2642-fe0f","🙆🏼‍♂":"1f646-1f3fc-200d-2642-fe0f","🙆🏽‍♂":"1f646-1f3fd-200d-2642-fe0f","🙆🏾‍♂":"1f646-1f3fe-200d-2642-fe0f","🙆🏿‍♂":"1f646-1f3ff-200d-2642-fe0f","🙆‍♀️":"1f646-200d-2640-fe0f","🙆🏻‍♀":"1f646-1f3fb-200d-2640-fe0f","🙆🏼‍♀":"1f646-1f3fc-200d-2640-fe0f","🙆🏽‍♀":"1f646-1f3fd-200d-2640-fe0f","🙆🏾‍♀":"1f646-1f3fe-200d-2640-fe0f","🙆🏿‍♀":"1f646-1f3ff-200d-2640-fe0f","💁‍♂️":"1f481-200d-2642-fe0f","💁🏻‍♂":"1f481-1f3fb-200d-2642-fe0f","💁🏼‍♂":"1f481-1f3fc-200d-2642-fe0f","💁🏽‍♂":"1f481-1f3fd-200d-2642-fe0f","💁🏾‍♂":"1f481-1f3fe-200d-2642-fe0f","💁🏿‍♂":"1f481-1f3ff-200d-2642-fe0f","💁‍♀️":"1f481-200d-2640-fe0f","💁🏻‍♀":"1f481-1f3fb-200d-2640-fe0f","💁🏼‍♀":"1f481-1f3fc-200d-2640-fe0f","💁🏽‍♀":"1f481-1f3fd-200d-2640-fe0f","💁🏾‍♀":"1f481-1f3fe-200d-2640-fe0f","💁🏿‍♀":"1f481-1f3ff-200d-2640-fe0f","🙋‍♂️":"1f64b-200d-2642-fe0f","🙋🏻‍♂":"1f64b-1f3fb-200d-2642-fe0f","🙋🏼‍♂":"1f64b-1f3fc-200d-2642-fe0f","🙋🏽‍♂":"1f64b-1f3fd-200d-2642-fe0f","🙋🏾‍♂":"1f64b-1f3fe-200d-2642-fe0f","🙋🏿‍♂":"1f64b-1f3ff-200d-2642-fe0f","🙋‍♀️":"1f64b-200d-2640-fe0f","🙋🏻‍♀":"1f64b-1f3fb-200d-2640-fe0f","🙋🏼‍♀":"1f64b-1f3fc-200d-2640-fe0f","🙋🏽‍♀":"1f64b-1f3fd-200d-2640-fe0f","🙋🏾‍♀":"1f64b-1f3fe-200d-2640-fe0f","🙋🏿‍♀":"1f64b-1f3ff-200d-2640-fe0f","🧏‍♂️":"1f9cf-200d-2642-fe0f","🧏🏻‍♂":"1f9cf-1f3fb-200d-2642-fe0f","🧏🏼‍♂":"1f9cf-1f3fc-200d-2642-fe0f","🧏🏽‍♂":"1f9cf-1f3fd-200d-2642-fe0f","🧏🏾‍♂":"1f9cf-1f3fe-200d-2642-fe0f","🧏🏿‍♂":"1f9cf-1f3ff-200d-2642-fe0f","🧏‍♀️":"1f9cf-200d-2640-fe0f","🧏🏻‍♀":"1f9cf-1f3fb-200d-2640-fe0f","🧏🏼‍♀":"1f9cf-1f3fc-200d-2640-fe0f","🧏🏽‍♀":"1f9cf-1f3fd-200d-2640-fe0f","🧏🏾‍♀":"1f9cf-1f3fe-200d-2640-fe0f","🧏🏿‍♀":"1f9cf-1f3ff-200d-2640-fe0f","🙇‍♂️":"1f647-200d-2642-fe0f","🙇🏻‍♂":"1f647-1f3fb-200d-2642-fe0f","🙇🏼‍♂":"1f647-1f3fc-200d-2642-fe0f","🙇🏽‍♂":"1f647-1f3fd-200d-2642-fe0f","🙇🏾‍♂":"1f647-1f3fe-200d-2642-fe0f","🙇🏿‍♂":"1f647-1f3ff-200d-2642-fe0f","🙇‍♀️":"1f647-200d-2640-fe0f","🙇🏻‍♀":"1f647-1f3fb-200d-2640-fe0f","🙇🏼‍♀":"1f647-1f3fc-200d-2640-fe0f","🙇🏽‍♀":"1f647-1f3fd-200d-2640-fe0f","🙇🏾‍♀":"1f647-1f3fe-200d-2640-fe0f","🙇🏿‍♀":"1f647-1f3ff-200d-2640-fe0f","🤦‍♂️":"1f926-200d-2642-fe0f","🤦🏻‍♂":"1f926-1f3fb-200d-2642-fe0f","🤦🏼‍♂":"1f926-1f3fc-200d-2642-fe0f","🤦🏽‍♂":"1f926-1f3fd-200d-2642-fe0f","🤦🏾‍♂":"1f926-1f3fe-200d-2642-fe0f","🤦🏿‍♂":"1f926-1f3ff-200d-2642-fe0f","🤦‍♀️":"1f926-200d-2640-fe0f","🤦🏻‍♀":"1f926-1f3fb-200d-2640-fe0f","🤦🏼‍♀":"1f926-1f3fc-200d-2640-fe0f","🤦🏽‍♀":"1f926-1f3fd-200d-2640-fe0f","🤦🏾‍♀":"1f926-1f3fe-200d-2640-fe0f","🤦🏿‍♀":"1f926-1f3ff-200d-2640-fe0f","🤷‍♂️":"1f937-200d-2642-fe0f","🤷🏻‍♂":"1f937-1f3fb-200d-2642-fe0f","🤷🏼‍♂":"1f937-1f3fc-200d-2642-fe0f","🤷🏽‍♂":"1f937-1f3fd-200d-2642-fe0f","🤷🏾‍♂":"1f937-1f3fe-200d-2642-fe0f","🤷🏿‍♂":"1f937-1f3ff-200d-2642-fe0f","🤷‍♀️":"1f937-200d-2640-fe0f","🤷🏻‍♀":"1f937-1f3fb-200d-2640-fe0f","🤷🏼‍♀":"1f937-1f3fc-200d-2640-fe0f","🤷🏽‍♀":"1f937-1f3fd-200d-2640-fe0f","🤷🏾‍♀":"1f937-1f3fe-200d-2640-fe0f","🤷🏿‍♀":"1f937-1f3ff-200d-2640-fe0f","🧑‍⚕️":"1f9d1-200d-2695-fe0f","🧑🏻‍⚕":"1f9d1-1f3fb-200d-2695-fe0f","🧑🏼‍⚕":"1f9d1-1f3fc-200d-2695-fe0f","🧑🏽‍⚕":"1f9d1-1f3fd-200d-2695-fe0f","🧑🏾‍⚕":"1f9d1-1f3fe-200d-2695-fe0f","🧑🏿‍⚕":"1f9d1-1f3ff-200d-2695-fe0f","👨‍⚕️":"1f468-200d-2695-fe0f","👨🏻‍⚕":"1f468-1f3fb-200d-2695-fe0f","👨🏼‍⚕":"1f468-1f3fc-200d-2695-fe0f","👨🏽‍⚕":"1f468-1f3fd-200d-2695-fe0f","👨🏾‍⚕":"1f468-1f3fe-200d-2695-fe0f","👨🏿‍⚕":"1f468-1f3ff-200d-2695-fe0f","👩‍⚕️":"1f469-200d-2695-fe0f","👩🏻‍⚕":"1f469-1f3fb-200d-2695-fe0f","👩🏼‍⚕":"1f469-1f3fc-200d-2695-fe0f","👩🏽‍⚕":"1f469-1f3fd-200d-2695-fe0f","👩🏾‍⚕":"1f469-1f3fe-200d-2695-fe0f","👩🏿‍⚕":"1f469-1f3ff-200d-2695-fe0f","🧑🏻‍🎓":"1f9d1-1f3fb-200d-1f393","🧑🏼‍🎓":"1f9d1-1f3fc-200d-1f393","🧑🏽‍🎓":"1f9d1-1f3fd-200d-1f393","🧑🏾‍🎓":"1f9d1-1f3fe-200d-1f393","🧑🏿‍🎓":"1f9d1-1f3ff-200d-1f393","👨🏻‍🎓":"1f468-1f3fb-200d-1f393","👨🏼‍🎓":"1f468-1f3fc-200d-1f393","👨🏽‍🎓":"1f468-1f3fd-200d-1f393","👨🏾‍🎓":"1f468-1f3fe-200d-1f393","👨🏿‍🎓":"1f468-1f3ff-200d-1f393","👩🏻‍🎓":"1f469-1f3fb-200d-1f393","👩🏼‍🎓":"1f469-1f3fc-200d-1f393","👩🏽‍🎓":"1f469-1f3fd-200d-1f393","👩🏾‍🎓":"1f469-1f3fe-200d-1f393","👩🏿‍🎓":"1f469-1f3ff-200d-1f393","🧑🏻‍🏫":"1f9d1-1f3fb-200d-1f3eb","🧑🏼‍🏫":"1f9d1-1f3fc-200d-1f3eb","🧑🏽‍🏫":"1f9d1-1f3fd-200d-1f3eb","🧑🏾‍🏫":"1f9d1-1f3fe-200d-1f3eb","🧑🏿‍🏫":"1f9d1-1f3ff-200d-1f3eb","👨🏻‍🏫":"1f468-1f3fb-200d-1f3eb","👨🏼‍🏫":"1f468-1f3fc-200d-1f3eb","👨🏽‍🏫":"1f468-1f3fd-200d-1f3eb","👨🏾‍🏫":"1f468-1f3fe-200d-1f3eb","👨🏿‍🏫":"1f468-1f3ff-200d-1f3eb","👩🏻‍🏫":"1f469-1f3fb-200d-1f3eb","👩🏼‍🏫":"1f469-1f3fc-200d-1f3eb","👩🏽‍🏫":"1f469-1f3fd-200d-1f3eb","👩🏾‍🏫":"1f469-1f3fe-200d-1f3eb","👩🏿‍🏫":"1f469-1f3ff-200d-1f3eb","🧑‍⚖️":"1f9d1-200d-2696-fe0f","🧑🏻‍⚖":"1f9d1-1f3fb-200d-2696-fe0f","🧑🏼‍⚖":"1f9d1-1f3fc-200d-2696-fe0f","🧑🏽‍⚖":"1f9d1-1f3fd-200d-2696-fe0f","🧑🏾‍⚖":"1f9d1-1f3fe-200d-2696-fe0f","🧑🏿‍⚖":"1f9d1-1f3ff-200d-2696-fe0f","👨‍⚖️":"1f468-200d-2696-fe0f","👨🏻‍⚖":"1f468-1f3fb-200d-2696-fe0f","👨🏼‍⚖":"1f468-1f3fc-200d-2696-fe0f","👨🏽‍⚖":"1f468-1f3fd-200d-2696-fe0f","👨🏾‍⚖":"1f468-1f3fe-200d-2696-fe0f","👨🏿‍⚖":"1f468-1f3ff-200d-2696-fe0f","👩‍⚖️":"1f469-200d-2696-fe0f","👩🏻‍⚖":"1f469-1f3fb-200d-2696-fe0f","👩🏼‍⚖":"1f469-1f3fc-200d-2696-fe0f","👩🏽‍⚖":"1f469-1f3fd-200d-2696-fe0f","👩🏾‍⚖":"1f469-1f3fe-200d-2696-fe0f","👩🏿‍⚖":"1f469-1f3ff-200d-2696-fe0f","🧑🏻‍🌾":"1f9d1-1f3fb-200d-1f33e","🧑🏼‍🌾":"1f9d1-1f3fc-200d-1f33e","🧑🏽‍🌾":"1f9d1-1f3fd-200d-1f33e","🧑🏾‍🌾":"1f9d1-1f3fe-200d-1f33e","🧑🏿‍🌾":"1f9d1-1f3ff-200d-1f33e","👨🏻‍🌾":"1f468-1f3fb-200d-1f33e","👨🏼‍🌾":"1f468-1f3fc-200d-1f33e","👨🏽‍🌾":"1f468-1f3fd-200d-1f33e","👨🏾‍🌾":"1f468-1f3fe-200d-1f33e","👨🏿‍🌾":"1f468-1f3ff-200d-1f33e","👩🏻‍🌾":"1f469-1f3fb-200d-1f33e","👩🏼‍🌾":"1f469-1f3fc-200d-1f33e","👩🏽‍🌾":"1f469-1f3fd-200d-1f33e","👩🏾‍🌾":"1f469-1f3fe-200d-1f33e","👩🏿‍🌾":"1f469-1f3ff-200d-1f33e","🧑🏻‍🍳":"1f9d1-1f3fb-200d-1f373","🧑🏼‍🍳":"1f9d1-1f3fc-200d-1f373","🧑🏽‍🍳":"1f9d1-1f3fd-200d-1f373","🧑🏾‍🍳":"1f9d1-1f3fe-200d-1f373","🧑🏿‍🍳":"1f9d1-1f3ff-200d-1f373","👨🏻‍🍳":"1f468-1f3fb-200d-1f373","👨🏼‍🍳":"1f468-1f3fc-200d-1f373","👨🏽‍🍳":"1f468-1f3fd-200d-1f373","👨🏾‍🍳":"1f468-1f3fe-200d-1f373","👨🏿‍🍳":"1f468-1f3ff-200d-1f373","👩🏻‍🍳":"1f469-1f3fb-200d-1f373","👩🏼‍🍳":"1f469-1f3fc-200d-1f373","👩🏽‍🍳":"1f469-1f3fd-200d-1f373","👩🏾‍🍳":"1f469-1f3fe-200d-1f373","👩🏿‍🍳":"1f469-1f3ff-200d-1f373","🧑🏻‍🔧":"1f9d1-1f3fb-200d-1f527","🧑🏼‍🔧":"1f9d1-1f3fc-200d-1f527","🧑🏽‍🔧":"1f9d1-1f3fd-200d-1f527","🧑🏾‍🔧":"1f9d1-1f3fe-200d-1f527","🧑🏿‍🔧":"1f9d1-1f3ff-200d-1f527","👨🏻‍🔧":"1f468-1f3fb-200d-1f527","👨🏼‍🔧":"1f468-1f3fc-200d-1f527","👨🏽‍🔧":"1f468-1f3fd-200d-1f527","👨🏾‍🔧":"1f468-1f3fe-200d-1f527","👨🏿‍🔧":"1f468-1f3ff-200d-1f527","👩🏻‍🔧":"1f469-1f3fb-200d-1f527","👩🏼‍🔧":"1f469-1f3fc-200d-1f527","👩🏽‍🔧":"1f469-1f3fd-200d-1f527","👩🏾‍🔧":"1f469-1f3fe-200d-1f527","👩🏿‍🔧":"1f469-1f3ff-200d-1f527","🧑🏻‍🏭":"1f9d1-1f3fb-200d-1f3ed","🧑🏼‍🏭":"1f9d1-1f3fc-200d-1f3ed","🧑🏽‍🏭":"1f9d1-1f3fd-200d-1f3ed","🧑🏾‍🏭":"1f9d1-1f3fe-200d-1f3ed","🧑🏿‍🏭":"1f9d1-1f3ff-200d-1f3ed","👨🏻‍🏭":"1f468-1f3fb-200d-1f3ed","👨🏼‍🏭":"1f468-1f3fc-200d-1f3ed","👨🏽‍🏭":"1f468-1f3fd-200d-1f3ed","👨🏾‍🏭":"1f468-1f3fe-200d-1f3ed","👨🏿‍🏭":"1f468-1f3ff-200d-1f3ed","👩🏻‍🏭":"1f469-1f3fb-200d-1f3ed","👩🏼‍🏭":"1f469-1f3fc-200d-1f3ed","👩🏽‍🏭":"1f469-1f3fd-200d-1f3ed","👩🏾‍🏭":"1f469-1f3fe-200d-1f3ed","👩🏿‍🏭":"1f469-1f3ff-200d-1f3ed","🧑🏻‍💼":"1f9d1-1f3fb-200d-1f4bc","🧑🏼‍💼":"1f9d1-1f3fc-200d-1f4bc","🧑🏽‍💼":"1f9d1-1f3fd-200d-1f4bc","🧑🏾‍💼":"1f9d1-1f3fe-200d-1f4bc","🧑🏿‍💼":"1f9d1-1f3ff-200d-1f4bc","👨🏻‍💼":"1f468-1f3fb-200d-1f4bc","👨🏼‍💼":"1f468-1f3fc-200d-1f4bc","👨🏽‍💼":"1f468-1f3fd-200d-1f4bc","👨🏾‍💼":"1f468-1f3fe-200d-1f4bc","👨🏿‍💼":"1f468-1f3ff-200d-1f4bc","👩🏻‍💼":"1f469-1f3fb-200d-1f4bc","👩🏼‍💼":"1f469-1f3fc-200d-1f4bc","👩🏽‍💼":"1f469-1f3fd-200d-1f4bc","👩🏾‍💼":"1f469-1f3fe-200d-1f4bc","👩🏿‍💼":"1f469-1f3ff-200d-1f4bc","🧑🏻‍🔬":"1f9d1-1f3fb-200d-1f52c","🧑🏼‍🔬":"1f9d1-1f3fc-200d-1f52c","🧑🏽‍🔬":"1f9d1-1f3fd-200d-1f52c","🧑🏾‍🔬":"1f9d1-1f3fe-200d-1f52c","🧑🏿‍🔬":"1f9d1-1f3ff-200d-1f52c","👨🏻‍🔬":"1f468-1f3fb-200d-1f52c","👨🏼‍🔬":"1f468-1f3fc-200d-1f52c","👨🏽‍🔬":"1f468-1f3fd-200d-1f52c","👨🏾‍🔬":"1f468-1f3fe-200d-1f52c","👨🏿‍🔬":"1f468-1f3ff-200d-1f52c","👩🏻‍🔬":"1f469-1f3fb-200d-1f52c","👩🏼‍🔬":"1f469-1f3fc-200d-1f52c","👩🏽‍🔬":"1f469-1f3fd-200d-1f52c","👩🏾‍🔬":"1f469-1f3fe-200d-1f52c","👩🏿‍🔬":"1f469-1f3ff-200d-1f52c","🧑🏻‍💻":"1f9d1-1f3fb-200d-1f4bb","🧑🏼‍💻":"1f9d1-1f3fc-200d-1f4bb","🧑🏽‍💻":"1f9d1-1f3fd-200d-1f4bb","🧑🏾‍💻":"1f9d1-1f3fe-200d-1f4bb","🧑🏿‍💻":"1f9d1-1f3ff-200d-1f4bb","👨🏻‍💻":"1f468-1f3fb-200d-1f4bb","👨🏼‍💻":"1f468-1f3fc-200d-1f4bb","👨🏽‍💻":"1f468-1f3fd-200d-1f4bb","👨🏾‍💻":"1f468-1f3fe-200d-1f4bb","👨🏿‍💻":"1f468-1f3ff-200d-1f4bb","👩🏻‍💻":"1f469-1f3fb-200d-1f4bb","👩🏼‍💻":"1f469-1f3fc-200d-1f4bb","👩🏽‍💻":"1f469-1f3fd-200d-1f4bb","👩🏾‍💻":"1f469-1f3fe-200d-1f4bb","👩🏿‍💻":"1f469-1f3ff-200d-1f4bb","🧑🏻‍🎤":"1f9d1-1f3fb-200d-1f3a4","🧑🏼‍🎤":"1f9d1-1f3fc-200d-1f3a4","🧑🏽‍🎤":"1f9d1-1f3fd-200d-1f3a4","🧑🏾‍🎤":"1f9d1-1f3fe-200d-1f3a4","🧑🏿‍🎤":"1f9d1-1f3ff-200d-1f3a4","👨🏻‍🎤":"1f468-1f3fb-200d-1f3a4","👨🏼‍🎤":"1f468-1f3fc-200d-1f3a4","👨🏽‍🎤":"1f468-1f3fd-200d-1f3a4","👨🏾‍🎤":"1f468-1f3fe-200d-1f3a4","👨🏿‍🎤":"1f468-1f3ff-200d-1f3a4","👩🏻‍🎤":"1f469-1f3fb-200d-1f3a4","👩🏼‍🎤":"1f469-1f3fc-200d-1f3a4","👩🏽‍🎤":"1f469-1f3fd-200d-1f3a4","👩🏾‍🎤":"1f469-1f3fe-200d-1f3a4","👩🏿‍🎤":"1f469-1f3ff-200d-1f3a4","🧑🏻‍🎨":"1f9d1-1f3fb-200d-1f3a8","🧑🏼‍🎨":"1f9d1-1f3fc-200d-1f3a8","🧑🏽‍🎨":"1f9d1-1f3fd-200d-1f3a8","🧑🏾‍🎨":"1f9d1-1f3fe-200d-1f3a8","🧑🏿‍🎨":"1f9d1-1f3ff-200d-1f3a8","👨🏻‍🎨":"1f468-1f3fb-200d-1f3a8","👨🏼‍🎨":"1f468-1f3fc-200d-1f3a8","👨🏽‍🎨":"1f468-1f3fd-200d-1f3a8","👨🏾‍🎨":"1f468-1f3fe-200d-1f3a8","👨🏿‍🎨":"1f468-1f3ff-200d-1f3a8","👩🏻‍🎨":"1f469-1f3fb-200d-1f3a8","👩🏼‍🎨":"1f469-1f3fc-200d-1f3a8","👩🏽‍🎨":"1f469-1f3fd-200d-1f3a8","👩🏾‍🎨":"1f469-1f3fe-200d-1f3a8","👩🏿‍🎨":"1f469-1f3ff-200d-1f3a8","🧑‍✈️":"1f9d1-200d-2708-fe0f","🧑🏻‍✈":"1f9d1-1f3fb-200d-2708-fe0f","🧑🏼‍✈":"1f9d1-1f3fc-200d-2708-fe0f","🧑🏽‍✈":"1f9d1-1f3fd-200d-2708-fe0f","🧑🏾‍✈":"1f9d1-1f3fe-200d-2708-fe0f","🧑🏿‍✈":"1f9d1-1f3ff-200d-2708-fe0f","👨‍✈️":"1f468-200d-2708-fe0f","👨🏻‍✈":"1f468-1f3fb-200d-2708-fe0f","👨🏼‍✈":"1f468-1f3fc-200d-2708-fe0f","👨🏽‍✈":"1f468-1f3fd-200d-2708-fe0f","👨🏾‍✈":"1f468-1f3fe-200d-2708-fe0f","👨🏿‍✈":"1f468-1f3ff-200d-2708-fe0f","👩‍✈️":"1f469-200d-2708-fe0f","👩🏻‍✈":"1f469-1f3fb-200d-2708-fe0f","👩🏼‍✈":"1f469-1f3fc-200d-2708-fe0f","👩🏽‍✈":"1f469-1f3fd-200d-2708-fe0f","👩🏾‍✈":"1f469-1f3fe-200d-2708-fe0f","👩🏿‍✈":"1f469-1f3ff-200d-2708-fe0f","🧑🏻‍🚀":"1f9d1-1f3fb-200d-1f680","🧑🏼‍🚀":"1f9d1-1f3fc-200d-1f680","🧑🏽‍🚀":"1f9d1-1f3fd-200d-1f680","🧑🏾‍🚀":"1f9d1-1f3fe-200d-1f680","🧑🏿‍🚀":"1f9d1-1f3ff-200d-1f680","👨🏻‍🚀":"1f468-1f3fb-200d-1f680","👨🏼‍🚀":"1f468-1f3fc-200d-1f680","👨🏽‍🚀":"1f468-1f3fd-200d-1f680","👨🏾‍🚀":"1f468-1f3fe-200d-1f680","👨🏿‍🚀":"1f468-1f3ff-200d-1f680","👩🏻‍🚀":"1f469-1f3fb-200d-1f680","👩🏼‍🚀":"1f469-1f3fc-200d-1f680","👩🏽‍🚀":"1f469-1f3fd-200d-1f680","👩🏾‍🚀":"1f469-1f3fe-200d-1f680","👩🏿‍🚀":"1f469-1f3ff-200d-1f680","🧑🏻‍🚒":"1f9d1-1f3fb-200d-1f692","🧑🏼‍🚒":"1f9d1-1f3fc-200d-1f692","🧑🏽‍🚒":"1f9d1-1f3fd-200d-1f692","🧑🏾‍🚒":"1f9d1-1f3fe-200d-1f692","🧑🏿‍🚒":"1f9d1-1f3ff-200d-1f692","👨🏻‍🚒":"1f468-1f3fb-200d-1f692","👨🏼‍🚒":"1f468-1f3fc-200d-1f692","👨🏽‍🚒":"1f468-1f3fd-200d-1f692","👨🏾‍🚒":"1f468-1f3fe-200d-1f692","👨🏿‍🚒":"1f468-1f3ff-200d-1f692","👩🏻‍🚒":"1f469-1f3fb-200d-1f692","👩🏼‍🚒":"1f469-1f3fc-200d-1f692","👩🏽‍🚒":"1f469-1f3fd-200d-1f692","👩🏾‍🚒":"1f469-1f3fe-200d-1f692","👩🏿‍🚒":"1f469-1f3ff-200d-1f692","👮‍♂️":"1f46e-200d-2642-fe0f","👮🏻‍♂":"1f46e-1f3fb-200d-2642-fe0f","👮🏼‍♂":"1f46e-1f3fc-200d-2642-fe0f","👮🏽‍♂":"1f46e-1f3fd-200d-2642-fe0f","👮🏾‍♂":"1f46e-1f3fe-200d-2642-fe0f","👮🏿‍♂":"1f46e-1f3ff-200d-2642-fe0f","👮‍♀️":"1f46e-200d-2640-fe0f","👮🏻‍♀":"1f46e-1f3fb-200d-2640-fe0f","👮🏼‍♀":"1f46e-1f3fc-200d-2640-fe0f","👮🏽‍♀":"1f46e-1f3fd-200d-2640-fe0f","👮🏾‍♀":"1f46e-1f3fe-200d-2640-fe0f","👮🏿‍♀":"1f46e-1f3ff-200d-2640-fe0f","🕵‍♂️":"1f575-fe0f-200d-2642-fe0f","🕵️‍♂":"1f575-fe0f-200d-2642-fe0f","🕵🏻‍♂":"1f575-1f3fb-200d-2642-fe0f","🕵🏼‍♂":"1f575-1f3fc-200d-2642-fe0f","🕵🏽‍♂":"1f575-1f3fd-200d-2642-fe0f","🕵🏾‍♂":"1f575-1f3fe-200d-2642-fe0f","🕵🏿‍♂":"1f575-1f3ff-200d-2642-fe0f","🕵‍♀️":"1f575-fe0f-200d-2640-fe0f","🕵️‍♀":"1f575-fe0f-200d-2640-fe0f","🕵🏻‍♀":"1f575-1f3fb-200d-2640-fe0f","🕵🏼‍♀":"1f575-1f3fc-200d-2640-fe0f","🕵🏽‍♀":"1f575-1f3fd-200d-2640-fe0f","🕵🏾‍♀":"1f575-1f3fe-200d-2640-fe0f","🕵🏿‍♀":"1f575-1f3ff-200d-2640-fe0f","💂‍♂️":"1f482-200d-2642-fe0f","💂🏻‍♂":"1f482-1f3fb-200d-2642-fe0f","💂🏼‍♂":"1f482-1f3fc-200d-2642-fe0f","💂🏽‍♂":"1f482-1f3fd-200d-2642-fe0f","💂🏾‍♂":"1f482-1f3fe-200d-2642-fe0f","💂🏿‍♂":"1f482-1f3ff-200d-2642-fe0f","💂‍♀️":"1f482-200d-2640-fe0f","💂🏻‍♀":"1f482-1f3fb-200d-2640-fe0f","💂🏼‍♀":"1f482-1f3fc-200d-2640-fe0f","💂🏽‍♀":"1f482-1f3fd-200d-2640-fe0f","💂🏾‍♀":"1f482-1f3fe-200d-2640-fe0f","💂🏿‍♀":"1f482-1f3ff-200d-2640-fe0f","👷‍♂️":"1f477-200d-2642-fe0f","👷🏻‍♂":"1f477-1f3fb-200d-2642-fe0f","👷🏼‍♂":"1f477-1f3fc-200d-2642-fe0f","👷🏽‍♂":"1f477-1f3fd-200d-2642-fe0f","👷🏾‍♂":"1f477-1f3fe-200d-2642-fe0f","👷🏿‍♂":"1f477-1f3ff-200d-2642-fe0f","👷‍♀️":"1f477-200d-2640-fe0f","👷🏻‍♀":"1f477-1f3fb-200d-2640-fe0f","👷🏼‍♀":"1f477-1f3fc-200d-2640-fe0f","👷🏽‍♀":"1f477-1f3fd-200d-2640-fe0f","👷🏾‍♀":"1f477-1f3fe-200d-2640-fe0f","👷🏿‍♀":"1f477-1f3ff-200d-2640-fe0f","👳‍♂️":"1f473-200d-2642-fe0f","👳🏻‍♂":"1f473-1f3fb-200d-2642-fe0f","👳🏼‍♂":"1f473-1f3fc-200d-2642-fe0f","👳🏽‍♂":"1f473-1f3fd-200d-2642-fe0f","👳🏾‍♂":"1f473-1f3fe-200d-2642-fe0f","👳🏿‍♂":"1f473-1f3ff-200d-2642-fe0f","👳‍♀️":"1f473-200d-2640-fe0f","👳🏻‍♀":"1f473-1f3fb-200d-2640-fe0f","👳🏼‍♀":"1f473-1f3fc-200d-2640-fe0f","👳🏽‍♀":"1f473-1f3fd-200d-2640-fe0f","👳🏾‍♀":"1f473-1f3fe-200d-2640-fe0f","👳🏿‍♀":"1f473-1f3ff-200d-2640-fe0f","🤵‍♂️":"1f935-200d-2642-fe0f","🤵🏻‍♂":"1f935-1f3fb-200d-2642-fe0f","🤵🏼‍♂":"1f935-1f3fc-200d-2642-fe0f","🤵🏽‍♂":"1f935-1f3fd-200d-2642-fe0f","🤵🏾‍♂":"1f935-1f3fe-200d-2642-fe0f","🤵🏿‍♂":"1f935-1f3ff-200d-2642-fe0f","🤵‍♀️":"1f935-200d-2640-fe0f","🤵🏻‍♀":"1f935-1f3fb-200d-2640-fe0f","🤵🏼‍♀":"1f935-1f3fc-200d-2640-fe0f","🤵🏽‍♀":"1f935-1f3fd-200d-2640-fe0f","🤵🏾‍♀":"1f935-1f3fe-200d-2640-fe0f","🤵🏿‍♀":"1f935-1f3ff-200d-2640-fe0f","👰‍♂️":"1f470-200d-2642-fe0f","👰🏻‍♂":"1f470-1f3fb-200d-2642-fe0f","👰🏼‍♂":"1f470-1f3fc-200d-2642-fe0f","👰🏽‍♂":"1f470-1f3fd-200d-2642-fe0f","👰🏾‍♂":"1f470-1f3fe-200d-2642-fe0f","👰🏿‍♂":"1f470-1f3ff-200d-2642-fe0f","👰‍♀️":"1f470-200d-2640-fe0f","👰🏻‍♀":"1f470-1f3fb-200d-2640-fe0f","👰🏼‍♀":"1f470-1f3fc-200d-2640-fe0f","👰🏽‍♀":"1f470-1f3fd-200d-2640-fe0f","👰🏾‍♀":"1f470-1f3fe-200d-2640-fe0f","👰🏿‍♀":"1f470-1f3ff-200d-2640-fe0f","👩🏻‍🍼":"1f469-1f3fb-200d-1f37c","👩🏼‍🍼":"1f469-1f3fc-200d-1f37c","👩🏽‍🍼":"1f469-1f3fd-200d-1f37c","👩🏾‍🍼":"1f469-1f3fe-200d-1f37c","👩🏿‍🍼":"1f469-1f3ff-200d-1f37c","👨🏻‍🍼":"1f468-1f3fb-200d-1f37c","👨🏼‍🍼":"1f468-1f3fc-200d-1f37c","👨🏽‍🍼":"1f468-1f3fd-200d-1f37c","👨🏾‍🍼":"1f468-1f3fe-200d-1f37c","👨🏿‍🍼":"1f468-1f3ff-200d-1f37c","🧑🏻‍🍼":"1f9d1-1f3fb-200d-1f37c","🧑🏼‍🍼":"1f9d1-1f3fc-200d-1f37c","🧑🏽‍🍼":"1f9d1-1f3fd-200d-1f37c","🧑🏾‍🍼":"1f9d1-1f3fe-200d-1f37c","🧑🏿‍🍼":"1f9d1-1f3ff-200d-1f37c","🧑🏻‍🎄":"1f9d1-1f3fb-200d-1f384","🧑🏼‍🎄":"1f9d1-1f3fc-200d-1f384","🧑🏽‍🎄":"1f9d1-1f3fd-200d-1f384","🧑🏾‍🎄":"1f9d1-1f3fe-200d-1f384","🧑🏿‍🎄":"1f9d1-1f3ff-200d-1f384","🦸‍♂️":"1f9b8-200d-2642-fe0f","🦸🏻‍♂":"1f9b8-1f3fb-200d-2642-fe0f","🦸🏼‍♂":"1f9b8-1f3fc-200d-2642-fe0f","🦸🏽‍♂":"1f9b8-1f3fd-200d-2642-fe0f","🦸🏾‍♂":"1f9b8-1f3fe-200d-2642-fe0f","🦸🏿‍♂":"1f9b8-1f3ff-200d-2642-fe0f","🦸‍♀️":"1f9b8-200d-2640-fe0f","🦸🏻‍♀":"1f9b8-1f3fb-200d-2640-fe0f","🦸🏼‍♀":"1f9b8-1f3fc-200d-2640-fe0f","🦸🏽‍♀":"1f9b8-1f3fd-200d-2640-fe0f","🦸🏾‍♀":"1f9b8-1f3fe-200d-2640-fe0f","🦸🏿‍♀":"1f9b8-1f3ff-200d-2640-fe0f","🦹‍♂️":"1f9b9-200d-2642-fe0f","🦹🏻‍♂":"1f9b9-1f3fb-200d-2642-fe0f","🦹🏼‍♂":"1f9b9-1f3fc-200d-2642-fe0f","🦹🏽‍♂":"1f9b9-1f3fd-200d-2642-fe0f","🦹🏾‍♂":"1f9b9-1f3fe-200d-2642-fe0f","🦹🏿‍♂":"1f9b9-1f3ff-200d-2642-fe0f","🦹‍♀️":"1f9b9-200d-2640-fe0f","🦹🏻‍♀":"1f9b9-1f3fb-200d-2640-fe0f","🦹🏼‍♀":"1f9b9-1f3fc-200d-2640-fe0f","🦹🏽‍♀":"1f9b9-1f3fd-200d-2640-fe0f","🦹🏾‍♀":"1f9b9-1f3fe-200d-2640-fe0f","🦹🏿‍♀":"1f9b9-1f3ff-200d-2640-fe0f","🧙‍♂️":"1f9d9-200d-2642-fe0f","🧙🏻‍♂":"1f9d9-1f3fb-200d-2642-fe0f","🧙🏼‍♂":"1f9d9-1f3fc-200d-2642-fe0f","🧙🏽‍♂":"1f9d9-1f3fd-200d-2642-fe0f","🧙🏾‍♂":"1f9d9-1f3fe-200d-2642-fe0f","🧙🏿‍♂":"1f9d9-1f3ff-200d-2642-fe0f","🧙‍♀️":"1f9d9-200d-2640-fe0f","🧙🏻‍♀":"1f9d9-1f3fb-200d-2640-fe0f","🧙🏼‍♀":"1f9d9-1f3fc-200d-2640-fe0f","🧙🏽‍♀":"1f9d9-1f3fd-200d-2640-fe0f","🧙🏾‍♀":"1f9d9-1f3fe-200d-2640-fe0f","🧙🏿‍♀":"1f9d9-1f3ff-200d-2640-fe0f","🧚‍♂️":"1f9da-200d-2642-fe0f","🧚🏻‍♂":"1f9da-1f3fb-200d-2642-fe0f","🧚🏼‍♂":"1f9da-1f3fc-200d-2642-fe0f","🧚🏽‍♂":"1f9da-1f3fd-200d-2642-fe0f","🧚🏾‍♂":"1f9da-1f3fe-200d-2642-fe0f","🧚🏿‍♂":"1f9da-1f3ff-200d-2642-fe0f","🧚‍♀️":"1f9da-200d-2640-fe0f","🧚🏻‍♀":"1f9da-1f3fb-200d-2640-fe0f","🧚🏼‍♀":"1f9da-1f3fc-200d-2640-fe0f","🧚🏽‍♀":"1f9da-1f3fd-200d-2640-fe0f","🧚🏾‍♀":"1f9da-1f3fe-200d-2640-fe0f","🧚🏿‍♀":"1f9da-1f3ff-200d-2640-fe0f","🧛‍♂️":"1f9db-200d-2642-fe0f","🧛🏻‍♂":"1f9db-1f3fb-200d-2642-fe0f","🧛🏼‍♂":"1f9db-1f3fc-200d-2642-fe0f","🧛🏽‍♂":"1f9db-1f3fd-200d-2642-fe0f","🧛🏾‍♂":"1f9db-1f3fe-200d-2642-fe0f","🧛🏿‍♂":"1f9db-1f3ff-200d-2642-fe0f","🧛‍♀️":"1f9db-200d-2640-fe0f","🧛🏻‍♀":"1f9db-1f3fb-200d-2640-fe0f","🧛🏼‍♀":"1f9db-1f3fc-200d-2640-fe0f","🧛🏽‍♀":"1f9db-1f3fd-200d-2640-fe0f","🧛🏾‍♀":"1f9db-1f3fe-200d-2640-fe0f","🧛🏿‍♀":"1f9db-1f3ff-200d-2640-fe0f","🧜‍♂️":"1f9dc-200d-2642-fe0f","🧜🏻‍♂":"1f9dc-1f3fb-200d-2642-fe0f","🧜🏼‍♂":"1f9dc-1f3fc-200d-2642-fe0f","🧜🏽‍♂":"1f9dc-1f3fd-200d-2642-fe0f","🧜🏾‍♂":"1f9dc-1f3fe-200d-2642-fe0f","🧜🏿‍♂":"1f9dc-1f3ff-200d-2642-fe0f","🧜‍♀️":"1f9dc-200d-2640-fe0f","🧜🏻‍♀":"1f9dc-1f3fb-200d-2640-fe0f","🧜🏼‍♀":"1f9dc-1f3fc-200d-2640-fe0f","🧜🏽‍♀":"1f9dc-1f3fd-200d-2640-fe0f","🧜🏾‍♀":"1f9dc-1f3fe-200d-2640-fe0f","🧜🏿‍♀":"1f9dc-1f3ff-200d-2640-fe0f","🧝‍♂️":"1f9dd-200d-2642-fe0f","🧝🏻‍♂":"1f9dd-1f3fb-200d-2642-fe0f","🧝🏼‍♂":"1f9dd-1f3fc-200d-2642-fe0f","🧝🏽‍♂":"1f9dd-1f3fd-200d-2642-fe0f","🧝🏾‍♂":"1f9dd-1f3fe-200d-2642-fe0f","🧝🏿‍♂":"1f9dd-1f3ff-200d-2642-fe0f","🧝‍♀️":"1f9dd-200d-2640-fe0f","🧝🏻‍♀":"1f9dd-1f3fb-200d-2640-fe0f","🧝🏼‍♀":"1f9dd-1f3fc-200d-2640-fe0f","🧝🏽‍♀":"1f9dd-1f3fd-200d-2640-fe0f","🧝🏾‍♀":"1f9dd-1f3fe-200d-2640-fe0f","🧝🏿‍♀":"1f9dd-1f3ff-200d-2640-fe0f","🧞‍♂️":"1f9de-200d-2642-fe0f","🧞‍♀️":"1f9de-200d-2640-fe0f","🧟‍♂️":"1f9df-200d-2642-fe0f","🧟‍♀️":"1f9df-200d-2640-fe0f","💆‍♂️":"1f486-200d-2642-fe0f","💆🏻‍♂":"1f486-1f3fb-200d-2642-fe0f","💆🏼‍♂":"1f486-1f3fc-200d-2642-fe0f","💆🏽‍♂":"1f486-1f3fd-200d-2642-fe0f","💆🏾‍♂":"1f486-1f3fe-200d-2642-fe0f","💆🏿‍♂":"1f486-1f3ff-200d-2642-fe0f","💆‍♀️":"1f486-200d-2640-fe0f","💆🏻‍♀":"1f486-1f3fb-200d-2640-fe0f","💆🏼‍♀":"1f486-1f3fc-200d-2640-fe0f","💆🏽‍♀":"1f486-1f3fd-200d-2640-fe0f","💆🏾‍♀":"1f486-1f3fe-200d-2640-fe0f","💆🏿‍♀":"1f486-1f3ff-200d-2640-fe0f","💇‍♂️":"1f487-200d-2642-fe0f","💇🏻‍♂":"1f487-1f3fb-200d-2642-fe0f","💇🏼‍♂":"1f487-1f3fc-200d-2642-fe0f","💇🏽‍♂":"1f487-1f3fd-200d-2642-fe0f","💇🏾‍♂":"1f487-1f3fe-200d-2642-fe0f","💇🏿‍♂":"1f487-1f3ff-200d-2642-fe0f","💇‍♀️":"1f487-200d-2640-fe0f","💇🏻‍♀":"1f487-1f3fb-200d-2640-fe0f","💇🏼‍♀":"1f487-1f3fc-200d-2640-fe0f","💇🏽‍♀":"1f487-1f3fd-200d-2640-fe0f","💇🏾‍♀":"1f487-1f3fe-200d-2640-fe0f","💇🏿‍♀":"1f487-1f3ff-200d-2640-fe0f","🚶‍♂️":"1f6b6-200d-2642-fe0f","🚶🏻‍♂":"1f6b6-1f3fb-200d-2642-fe0f","🚶🏼‍♂":"1f6b6-1f3fc-200d-2642-fe0f","🚶🏽‍♂":"1f6b6-1f3fd-200d-2642-fe0f","🚶🏾‍♂":"1f6b6-1f3fe-200d-2642-fe0f","🚶🏿‍♂":"1f6b6-1f3ff-200d-2642-fe0f","🚶‍♀️":"1f6b6-200d-2640-fe0f","🚶🏻‍♀":"1f6b6-1f3fb-200d-2640-fe0f","🚶🏼‍♀":"1f6b6-1f3fc-200d-2640-fe0f","🚶🏽‍♀":"1f6b6-1f3fd-200d-2640-fe0f","🚶🏾‍♀":"1f6b6-1f3fe-200d-2640-fe0f","🚶🏿‍♀":"1f6b6-1f3ff-200d-2640-fe0f","🧍‍♂️":"1f9cd-200d-2642-fe0f","🧍🏻‍♂":"1f9cd-1f3fb-200d-2642-fe0f","🧍🏼‍♂":"1f9cd-1f3fc-200d-2642-fe0f","🧍🏽‍♂":"1f9cd-1f3fd-200d-2642-fe0f","🧍🏾‍♂":"1f9cd-1f3fe-200d-2642-fe0f","🧍🏿‍♂":"1f9cd-1f3ff-200d-2642-fe0f","🧍‍♀️":"1f9cd-200d-2640-fe0f","🧍🏻‍♀":"1f9cd-1f3fb-200d-2640-fe0f","🧍🏼‍♀":"1f9cd-1f3fc-200d-2640-fe0f","🧍🏽‍♀":"1f9cd-1f3fd-200d-2640-fe0f","🧍🏾‍♀":"1f9cd-1f3fe-200d-2640-fe0f","🧍🏿‍♀":"1f9cd-1f3ff-200d-2640-fe0f","🧎‍♂️":"1f9ce-200d-2642-fe0f","🧎🏻‍♂":"1f9ce-1f3fb-200d-2642-fe0f","🧎🏼‍♂":"1f9ce-1f3fc-200d-2642-fe0f","🧎🏽‍♂":"1f9ce-1f3fd-200d-2642-fe0f","🧎🏾‍♂":"1f9ce-1f3fe-200d-2642-fe0f","🧎🏿‍♂":"1f9ce-1f3ff-200d-2642-fe0f","🧎‍♀️":"1f9ce-200d-2640-fe0f","🧎🏻‍♀":"1f9ce-1f3fb-200d-2640-fe0f","🧎🏼‍♀":"1f9ce-1f3fc-200d-2640-fe0f","🧎🏽‍♀":"1f9ce-1f3fd-200d-2640-fe0f","🧎🏾‍♀":"1f9ce-1f3fe-200d-2640-fe0f","🧎🏿‍♀":"1f9ce-1f3ff-200d-2640-fe0f","🧑🏻‍🦯":"1f9d1-1f3fb-200d-1f9af","🧑🏼‍🦯":"1f9d1-1f3fc-200d-1f9af","🧑🏽‍🦯":"1f9d1-1f3fd-200d-1f9af","🧑🏾‍🦯":"1f9d1-1f3fe-200d-1f9af","🧑🏿‍🦯":"1f9d1-1f3ff-200d-1f9af","👨🏻‍🦯":"1f468-1f3fb-200d-1f9af","👨🏼‍🦯":"1f468-1f3fc-200d-1f9af","👨🏽‍🦯":"1f468-1f3fd-200d-1f9af","👨🏾‍🦯":"1f468-1f3fe-200d-1f9af","👨🏿‍🦯":"1f468-1f3ff-200d-1f9af","👩🏻‍🦯":"1f469-1f3fb-200d-1f9af","👩🏼‍🦯":"1f469-1f3fc-200d-1f9af","👩🏽‍🦯":"1f469-1f3fd-200d-1f9af","👩🏾‍🦯":"1f469-1f3fe-200d-1f9af","👩🏿‍🦯":"1f469-1f3ff-200d-1f9af","🧑🏻‍🦼":"1f9d1-1f3fb-200d-1f9bc","🧑🏼‍🦼":"1f9d1-1f3fc-200d-1f9bc","🧑🏽‍🦼":"1f9d1-1f3fd-200d-1f9bc","🧑🏾‍🦼":"1f9d1-1f3fe-200d-1f9bc","🧑🏿‍🦼":"1f9d1-1f3ff-200d-1f9bc","👨🏻‍🦼":"1f468-1f3fb-200d-1f9bc","👨🏼‍🦼":"1f468-1f3fc-200d-1f9bc","👨🏽‍🦼":"1f468-1f3fd-200d-1f9bc","👨🏾‍🦼":"1f468-1f3fe-200d-1f9bc","👨🏿‍🦼":"1f468-1f3ff-200d-1f9bc","👩🏻‍🦼":"1f469-1f3fb-200d-1f9bc","👩🏼‍🦼":"1f469-1f3fc-200d-1f9bc","👩🏽‍🦼":"1f469-1f3fd-200d-1f9bc","👩🏾‍🦼":"1f469-1f3fe-200d-1f9bc","👩🏿‍🦼":"1f469-1f3ff-200d-1f9bc","🧑🏻‍🦽":"1f9d1-1f3fb-200d-1f9bd","🧑🏼‍🦽":"1f9d1-1f3fc-200d-1f9bd","🧑🏽‍🦽":"1f9d1-1f3fd-200d-1f9bd","🧑🏾‍🦽":"1f9d1-1f3fe-200d-1f9bd","🧑🏿‍🦽":"1f9d1-1f3ff-200d-1f9bd","👨🏻‍🦽":"1f468-1f3fb-200d-1f9bd","👨🏼‍🦽":"1f468-1f3fc-200d-1f9bd","👨🏽‍🦽":"1f468-1f3fd-200d-1f9bd","👨🏾‍🦽":"1f468-1f3fe-200d-1f9bd","👨🏿‍🦽":"1f468-1f3ff-200d-1f9bd","👩🏻‍🦽":"1f469-1f3fb-200d-1f9bd","👩🏼‍🦽":"1f469-1f3fc-200d-1f9bd","👩🏽‍🦽":"1f469-1f3fd-200d-1f9bd","👩🏾‍🦽":"1f469-1f3fe-200d-1f9bd","👩🏿‍🦽":"1f469-1f3ff-200d-1f9bd","🏃‍♂️":"1f3c3-200d-2642-fe0f","🏃🏻‍♂":"1f3c3-1f3fb-200d-2642-fe0f","🏃🏼‍♂":"1f3c3-1f3fc-200d-2642-fe0f","🏃🏽‍♂":"1f3c3-1f3fd-200d-2642-fe0f","🏃🏾‍♂":"1f3c3-1f3fe-200d-2642-fe0f","🏃🏿‍♂":"1f3c3-1f3ff-200d-2642-fe0f","🏃‍♀️":"1f3c3-200d-2640-fe0f","🏃🏻‍♀":"1f3c3-1f3fb-200d-2640-fe0f","🏃🏼‍♀":"1f3c3-1f3fc-200d-2640-fe0f","🏃🏽‍♀":"1f3c3-1f3fd-200d-2640-fe0f","🏃🏾‍♀":"1f3c3-1f3fe-200d-2640-fe0f","🏃🏿‍♀":"1f3c3-1f3ff-200d-2640-fe0f","👯‍♂️":"1f46f-200d-2642-fe0f","👯‍♀️":"1f46f-200d-2640-fe0f","🧖‍♂️":"1f9d6-200d-2642-fe0f","🧖🏻‍♂":"1f9d6-1f3fb-200d-2642-fe0f","🧖🏼‍♂":"1f9d6-1f3fc-200d-2642-fe0f","🧖🏽‍♂":"1f9d6-1f3fd-200d-2642-fe0f","🧖🏾‍♂":"1f9d6-1f3fe-200d-2642-fe0f","🧖🏿‍♂":"1f9d6-1f3ff-200d-2642-fe0f","🧖‍♀️":"1f9d6-200d-2640-fe0f","🧖🏻‍♀":"1f9d6-1f3fb-200d-2640-fe0f","🧖🏼‍♀":"1f9d6-1f3fc-200d-2640-fe0f","🧖🏽‍♀":"1f9d6-1f3fd-200d-2640-fe0f","🧖🏾‍♀":"1f9d6-1f3fe-200d-2640-fe0f","🧖🏿‍♀":"1f9d6-1f3ff-200d-2640-fe0f","🧗‍♂️":"1f9d7-200d-2642-fe0f","🧗🏻‍♂":"1f9d7-1f3fb-200d-2642-fe0f","🧗🏼‍♂":"1f9d7-1f3fc-200d-2642-fe0f","🧗🏽‍♂":"1f9d7-1f3fd-200d-2642-fe0f","🧗🏾‍♂":"1f9d7-1f3fe-200d-2642-fe0f","🧗🏿‍♂":"1f9d7-1f3ff-200d-2642-fe0f","🧗‍♀️":"1f9d7-200d-2640-fe0f","🧗🏻‍♀":"1f9d7-1f3fb-200d-2640-fe0f","🧗🏼‍♀":"1f9d7-1f3fc-200d-2640-fe0f","🧗🏽‍♀":"1f9d7-1f3fd-200d-2640-fe0f","🧗🏾‍♀":"1f9d7-1f3fe-200d-2640-fe0f","🧗🏿‍♀":"1f9d7-1f3ff-200d-2640-fe0f","🏌‍♂️":"1f3cc-fe0f-200d-2642-fe0f","🏌️‍♂":"1f3cc-fe0f-200d-2642-fe0f","🏌🏻‍♂":"1f3cc-1f3fb-200d-2642-fe0f","🏌🏼‍♂":"1f3cc-1f3fc-200d-2642-fe0f","🏌🏽‍♂":"1f3cc-1f3fd-200d-2642-fe0f","🏌🏾‍♂":"1f3cc-1f3fe-200d-2642-fe0f","🏌🏿‍♂":"1f3cc-1f3ff-200d-2642-fe0f","🏌‍♀️":"1f3cc-fe0f-200d-2640-fe0f","🏌️‍♀":"1f3cc-fe0f-200d-2640-fe0f","🏌🏻‍♀":"1f3cc-1f3fb-200d-2640-fe0f","🏌🏼‍♀":"1f3cc-1f3fc-200d-2640-fe0f","🏌🏽‍♀":"1f3cc-1f3fd-200d-2640-fe0f","🏌🏾‍♀":"1f3cc-1f3fe-200d-2640-fe0f","🏌🏿‍♀":"1f3cc-1f3ff-200d-2640-fe0f","🏄‍♂️":"1f3c4-200d-2642-fe0f","🏄🏻‍♂":"1f3c4-1f3fb-200d-2642-fe0f","🏄🏼‍♂":"1f3c4-1f3fc-200d-2642-fe0f","🏄🏽‍♂":"1f3c4-1f3fd-200d-2642-fe0f","🏄🏾‍♂":"1f3c4-1f3fe-200d-2642-fe0f","🏄🏿‍♂":"1f3c4-1f3ff-200d-2642-fe0f","🏄‍♀️":"1f3c4-200d-2640-fe0f","🏄🏻‍♀":"1f3c4-1f3fb-200d-2640-fe0f","🏄🏼‍♀":"1f3c4-1f3fc-200d-2640-fe0f","🏄🏽‍♀":"1f3c4-1f3fd-200d-2640-fe0f","🏄🏾‍♀":"1f3c4-1f3fe-200d-2640-fe0f","🏄🏿‍♀":"1f3c4-1f3ff-200d-2640-fe0f","🚣‍♂️":"1f6a3-200d-2642-fe0f","🚣🏻‍♂":"1f6a3-1f3fb-200d-2642-fe0f","🚣🏼‍♂":"1f6a3-1f3fc-200d-2642-fe0f","🚣🏽‍♂":"1f6a3-1f3fd-200d-2642-fe0f","🚣🏾‍♂":"1f6a3-1f3fe-200d-2642-fe0f","🚣🏿‍♂":"1f6a3-1f3ff-200d-2642-fe0f","🚣‍♀️":"1f6a3-200d-2640-fe0f","🚣🏻‍♀":"1f6a3-1f3fb-200d-2640-fe0f","🚣🏼‍♀":"1f6a3-1f3fc-200d-2640-fe0f","🚣🏽‍♀":"1f6a3-1f3fd-200d-2640-fe0f","🚣🏾‍♀":"1f6a3-1f3fe-200d-2640-fe0f","🚣🏿‍♀":"1f6a3-1f3ff-200d-2640-fe0f","🏊‍♂️":"1f3ca-200d-2642-fe0f","🏊🏻‍♂":"1f3ca-1f3fb-200d-2642-fe0f","🏊🏼‍♂":"1f3ca-1f3fc-200d-2642-fe0f","🏊🏽‍♂":"1f3ca-1f3fd-200d-2642-fe0f","🏊🏾‍♂":"1f3ca-1f3fe-200d-2642-fe0f","🏊🏿‍♂":"1f3ca-1f3ff-200d-2642-fe0f","🏊‍♀️":"1f3ca-200d-2640-fe0f","🏊🏻‍♀":"1f3ca-1f3fb-200d-2640-fe0f","🏊🏼‍♀":"1f3ca-1f3fc-200d-2640-fe0f","🏊🏽‍♀":"1f3ca-1f3fd-200d-2640-fe0f","🏊🏾‍♀":"1f3ca-1f3fe-200d-2640-fe0f","🏊🏿‍♀":"1f3ca-1f3ff-200d-2640-fe0f","⛹‍♂️":"26f9-fe0f-200d-2642-fe0f","⛹️‍♂":"26f9-fe0f-200d-2642-fe0f","⛹🏻‍♂":"26f9-1f3fb-200d-2642-fe0f","⛹🏼‍♂":"26f9-1f3fc-200d-2642-fe0f","⛹🏽‍♂":"26f9-1f3fd-200d-2642-fe0f","⛹🏾‍♂":"26f9-1f3fe-200d-2642-fe0f","⛹🏿‍♂":"26f9-1f3ff-200d-2642-fe0f","⛹‍♀️":"26f9-fe0f-200d-2640-fe0f","⛹️‍♀":"26f9-fe0f-200d-2640-fe0f","⛹🏻‍♀":"26f9-1f3fb-200d-2640-fe0f","⛹🏼‍♀":"26f9-1f3fc-200d-2640-fe0f","⛹🏽‍♀":"26f9-1f3fd-200d-2640-fe0f","⛹🏾‍♀":"26f9-1f3fe-200d-2640-fe0f","⛹🏿‍♀":"26f9-1f3ff-200d-2640-fe0f","🏋‍♂️":"1f3cb-fe0f-200d-2642-fe0f","🏋️‍♂":"1f3cb-fe0f-200d-2642-fe0f","🏋🏻‍♂":"1f3cb-1f3fb-200d-2642-fe0f","🏋🏼‍♂":"1f3cb-1f3fc-200d-2642-fe0f","🏋🏽‍♂":"1f3cb-1f3fd-200d-2642-fe0f","🏋🏾‍♂":"1f3cb-1f3fe-200d-2642-fe0f","🏋🏿‍♂":"1f3cb-1f3ff-200d-2642-fe0f","🏋‍♀️":"1f3cb-fe0f-200d-2640-fe0f","🏋️‍♀":"1f3cb-fe0f-200d-2640-fe0f","🏋🏻‍♀":"1f3cb-1f3fb-200d-2640-fe0f","🏋🏼‍♀":"1f3cb-1f3fc-200d-2640-fe0f","🏋🏽‍♀":"1f3cb-1f3fd-200d-2640-fe0f","🏋🏾‍♀":"1f3cb-1f3fe-200d-2640-fe0f","🏋🏿‍♀":"1f3cb-1f3ff-200d-2640-fe0f","🚴‍♂️":"1f6b4-200d-2642-fe0f","🚴🏻‍♂":"1f6b4-1f3fb-200d-2642-fe0f","🚴🏼‍♂":"1f6b4-1f3fc-200d-2642-fe0f","🚴🏽‍♂":"1f6b4-1f3fd-200d-2642-fe0f","🚴🏾‍♂":"1f6b4-1f3fe-200d-2642-fe0f","🚴🏿‍♂":"1f6b4-1f3ff-200d-2642-fe0f","🚴‍♀️":"1f6b4-200d-2640-fe0f","🚴🏻‍♀":"1f6b4-1f3fb-200d-2640-fe0f","🚴🏼‍♀":"1f6b4-1f3fc-200d-2640-fe0f","🚴🏽‍♀":"1f6b4-1f3fd-200d-2640-fe0f","🚴🏾‍♀":"1f6b4-1f3fe-200d-2640-fe0f","🚴🏿‍♀":"1f6b4-1f3ff-200d-2640-fe0f","🚵‍♂️":"1f6b5-200d-2642-fe0f","🚵🏻‍♂":"1f6b5-1f3fb-200d-2642-fe0f","🚵🏼‍♂":"1f6b5-1f3fc-200d-2642-fe0f","🚵🏽‍♂":"1f6b5-1f3fd-200d-2642-fe0f","🚵🏾‍♂":"1f6b5-1f3fe-200d-2642-fe0f","🚵🏿‍♂":"1f6b5-1f3ff-200d-2642-fe0f","🚵‍♀️":"1f6b5-200d-2640-fe0f","🚵🏻‍♀":"1f6b5-1f3fb-200d-2640-fe0f","🚵🏼‍♀":"1f6b5-1f3fc-200d-2640-fe0f","🚵🏽‍♀":"1f6b5-1f3fd-200d-2640-fe0f","🚵🏾‍♀":"1f6b5-1f3fe-200d-2640-fe0f","🚵🏿‍♀":"1f6b5-1f3ff-200d-2640-fe0f","🤸‍♂️":"1f938-200d-2642-fe0f","🤸🏻‍♂":"1f938-1f3fb-200d-2642-fe0f","🤸🏼‍♂":"1f938-1f3fc-200d-2642-fe0f","🤸🏽‍♂":"1f938-1f3fd-200d-2642-fe0f","🤸🏾‍♂":"1f938-1f3fe-200d-2642-fe0f","🤸🏿‍♂":"1f938-1f3ff-200d-2642-fe0f","🤸‍♀️":"1f938-200d-2640-fe0f","🤸🏻‍♀":"1f938-1f3fb-200d-2640-fe0f","🤸🏼‍♀":"1f938-1f3fc-200d-2640-fe0f","🤸🏽‍♀":"1f938-1f3fd-200d-2640-fe0f","🤸🏾‍♀":"1f938-1f3fe-200d-2640-fe0f","🤸🏿‍♀":"1f938-1f3ff-200d-2640-fe0f","🤼‍♂️":"1f93c-200d-2642-fe0f","🤼‍♀️":"1f93c-200d-2640-fe0f","🤽‍♂️":"1f93d-200d-2642-fe0f","🤽🏻‍♂":"1f93d-1f3fb-200d-2642-fe0f","🤽🏼‍♂":"1f93d-1f3fc-200d-2642-fe0f","🤽🏽‍♂":"1f93d-1f3fd-200d-2642-fe0f","🤽🏾‍♂":"1f93d-1f3fe-200d-2642-fe0f","🤽🏿‍♂":"1f93d-1f3ff-200d-2642-fe0f","🤽‍♀️":"1f93d-200d-2640-fe0f","🤽🏻‍♀":"1f93d-1f3fb-200d-2640-fe0f","🤽🏼‍♀":"1f93d-1f3fc-200d-2640-fe0f","🤽🏽‍♀":"1f93d-1f3fd-200d-2640-fe0f","🤽🏾‍♀":"1f93d-1f3fe-200d-2640-fe0f","🤽🏿‍♀":"1f93d-1f3ff-200d-2640-fe0f","🤾‍♂️":"1f93e-200d-2642-fe0f","🤾🏻‍♂":"1f93e-1f3fb-200d-2642-fe0f","🤾🏼‍♂":"1f93e-1f3fc-200d-2642-fe0f","🤾🏽‍♂":"1f93e-1f3fd-200d-2642-fe0f","🤾🏾‍♂":"1f93e-1f3fe-200d-2642-fe0f","🤾🏿‍♂":"1f93e-1f3ff-200d-2642-fe0f","🤾‍♀️":"1f93e-200d-2640-fe0f","🤾🏻‍♀":"1f93e-1f3fb-200d-2640-fe0f","🤾🏼‍♀":"1f93e-1f3fc-200d-2640-fe0f","🤾🏽‍♀":"1f93e-1f3fd-200d-2640-fe0f","🤾🏾‍♀":"1f93e-1f3fe-200d-2640-fe0f","🤾🏿‍♀":"1f93e-1f3ff-200d-2640-fe0f","🤹‍♂️":"1f939-200d-2642-fe0f","🤹🏻‍♂":"1f939-1f3fb-200d-2642-fe0f","🤹🏼‍♂":"1f939-1f3fc-200d-2642-fe0f","🤹🏽‍♂":"1f939-1f3fd-200d-2642-fe0f","🤹🏾‍♂":"1f939-1f3fe-200d-2642-fe0f","🤹🏿‍♂":"1f939-1f3ff-200d-2642-fe0f","🤹‍♀️":"1f939-200d-2640-fe0f","🤹🏻‍♀":"1f939-1f3fb-200d-2640-fe0f","🤹🏼‍♀":"1f939-1f3fc-200d-2640-fe0f","🤹🏽‍♀":"1f939-1f3fd-200d-2640-fe0f","🤹🏾‍♀":"1f939-1f3fe-200d-2640-fe0f","🤹🏿‍♀":"1f939-1f3ff-200d-2640-fe0f","🧘‍♂️":"1f9d8-200d-2642-fe0f","🧘🏻‍♂":"1f9d8-1f3fb-200d-2642-fe0f","🧘🏼‍♂":"1f9d8-1f3fc-200d-2642-fe0f","🧘🏽‍♂":"1f9d8-1f3fd-200d-2642-fe0f","🧘🏾‍♂":"1f9d8-1f3fe-200d-2642-fe0f","🧘🏿‍♂":"1f9d8-1f3ff-200d-2642-fe0f","🧘‍♀️":"1f9d8-200d-2640-fe0f","🧘🏻‍♀":"1f9d8-1f3fb-200d-2640-fe0f","🧘🏼‍♀":"1f9d8-1f3fc-200d-2640-fe0f","🧘🏽‍♀":"1f9d8-1f3fd-200d-2640-fe0f","🧘🏾‍♀":"1f9d8-1f3fe-200d-2640-fe0f","🧘🏿‍♀":"1f9d8-1f3ff-200d-2640-fe0f","🐻‍❄️":"1f43b-200d-2744-fe0f","🏳️‍🌈":"1f3f3-fe0f-200d-1f308","🏳‍⚧️":"1f3f3-fe0f-200d-26a7-fe0f","🏳️‍⚧":"1f3f3-fe0f-200d-26a7-fe0f","🏴‍☠️":"1f3f4-200d-2620-fe0f","👁️‍🗨️":"1f441-200d-1f5e8","🧔🏻‍♂️":"1f9d4-1f3fb-200d-2642-fe0f","🧔🏼‍♂️":"1f9d4-1f3fc-200d-2642-fe0f","🧔🏽‍♂️":"1f9d4-1f3fd-200d-2642-fe0f","🧔🏾‍♂️":"1f9d4-1f3fe-200d-2642-fe0f","🧔🏿‍♂️":"1f9d4-1f3ff-200d-2642-fe0f","🧔🏻‍♀️":"1f9d4-1f3fb-200d-2640-fe0f","🧔🏼‍♀️":"1f9d4-1f3fc-200d-2640-fe0f","🧔🏽‍♀️":"1f9d4-1f3fd-200d-2640-fe0f","🧔🏾‍♀️":"1f9d4-1f3fe-200d-2640-fe0f","🧔🏿‍♀️":"1f9d4-1f3ff-200d-2640-fe0f","👱🏻‍♀️":"1f471-1f3fb-200d-2640-fe0f","👱🏼‍♀️":"1f471-1f3fc-200d-2640-fe0f","👱🏽‍♀️":"1f471-1f3fd-200d-2640-fe0f","👱🏾‍♀️":"1f471-1f3fe-200d-2640-fe0f","👱🏿‍♀️":"1f471-1f3ff-200d-2640-fe0f","👱🏻‍♂️":"1f471-1f3fb-200d-2642-fe0f","👱🏼‍♂️":"1f471-1f3fc-200d-2642-fe0f","👱🏽‍♂️":"1f471-1f3fd-200d-2642-fe0f","👱🏾‍♂️":"1f471-1f3fe-200d-2642-fe0f","👱🏿‍♂️":"1f471-1f3ff-200d-2642-fe0f","🙍🏻‍♂️":"1f64d-1f3fb-200d-2642-fe0f","🙍🏼‍♂️":"1f64d-1f3fc-200d-2642-fe0f","🙍🏽‍♂️":"1f64d-1f3fd-200d-2642-fe0f","🙍🏾‍♂️":"1f64d-1f3fe-200d-2642-fe0f","🙍🏿‍♂️":"1f64d-1f3ff-200d-2642-fe0f","🙍🏻‍♀️":"1f64d-1f3fb-200d-2640-fe0f","🙍🏼‍♀️":"1f64d-1f3fc-200d-2640-fe0f","🙍🏽‍♀️":"1f64d-1f3fd-200d-2640-fe0f","🙍🏾‍♀️":"1f64d-1f3fe-200d-2640-fe0f","🙍🏿‍♀️":"1f64d-1f3ff-200d-2640-fe0f","🙎🏻‍♂️":"1f64e-1f3fb-200d-2642-fe0f","🙎🏼‍♂️":"1f64e-1f3fc-200d-2642-fe0f","🙎🏽‍♂️":"1f64e-1f3fd-200d-2642-fe0f","🙎🏾‍♂️":"1f64e-1f3fe-200d-2642-fe0f","🙎🏿‍♂️":"1f64e-1f3ff-200d-2642-fe0f","🙎🏻‍♀️":"1f64e-1f3fb-200d-2640-fe0f","🙎🏼‍♀️":"1f64e-1f3fc-200d-2640-fe0f","🙎🏽‍♀️":"1f64e-1f3fd-200d-2640-fe0f","🙎🏾‍♀️":"1f64e-1f3fe-200d-2640-fe0f","🙎🏿‍♀️":"1f64e-1f3ff-200d-2640-fe0f","🙅🏻‍♂️":"1f645-1f3fb-200d-2642-fe0f","🙅🏼‍♂️":"1f645-1f3fc-200d-2642-fe0f","🙅🏽‍♂️":"1f645-1f3fd-200d-2642-fe0f","🙅🏾‍♂️":"1f645-1f3fe-200d-2642-fe0f","🙅🏿‍♂️":"1f645-1f3ff-200d-2642-fe0f","🙅🏻‍♀️":"1f645-1f3fb-200d-2640-fe0f","🙅🏼‍♀️":"1f645-1f3fc-200d-2640-fe0f","🙅🏽‍♀️":"1f645-1f3fd-200d-2640-fe0f","🙅🏾‍♀️":"1f645-1f3fe-200d-2640-fe0f","🙅🏿‍♀️":"1f645-1f3ff-200d-2640-fe0f","🙆🏻‍♂️":"1f646-1f3fb-200d-2642-fe0f","🙆🏼‍♂️":"1f646-1f3fc-200d-2642-fe0f","🙆🏽‍♂️":"1f646-1f3fd-200d-2642-fe0f","🙆🏾‍♂️":"1f646-1f3fe-200d-2642-fe0f","🙆🏿‍♂️":"1f646-1f3ff-200d-2642-fe0f","🙆🏻‍♀️":"1f646-1f3fb-200d-2640-fe0f","🙆🏼‍♀️":"1f646-1f3fc-200d-2640-fe0f","🙆🏽‍♀️":"1f646-1f3fd-200d-2640-fe0f","🙆🏾‍♀️":"1f646-1f3fe-200d-2640-fe0f","🙆🏿‍♀️":"1f646-1f3ff-200d-2640-fe0f","💁🏻‍♂️":"1f481-1f3fb-200d-2642-fe0f","💁🏼‍♂️":"1f481-1f3fc-200d-2642-fe0f","💁🏽‍♂️":"1f481-1f3fd-200d-2642-fe0f","💁🏾‍♂️":"1f481-1f3fe-200d-2642-fe0f","💁🏿‍♂️":"1f481-1f3ff-200d-2642-fe0f","💁🏻‍♀️":"1f481-1f3fb-200d-2640-fe0f","💁🏼‍♀️":"1f481-1f3fc-200d-2640-fe0f","💁🏽‍♀️":"1f481-1f3fd-200d-2640-fe0f","💁🏾‍♀️":"1f481-1f3fe-200d-2640-fe0f","💁🏿‍♀️":"1f481-1f3ff-200d-2640-fe0f","🙋🏻‍♂️":"1f64b-1f3fb-200d-2642-fe0f","🙋🏼‍♂️":"1f64b-1f3fc-200d-2642-fe0f","🙋🏽‍♂️":"1f64b-1f3fd-200d-2642-fe0f","🙋🏾‍♂️":"1f64b-1f3fe-200d-2642-fe0f","🙋🏿‍♂️":"1f64b-1f3ff-200d-2642-fe0f","🙋🏻‍♀️":"1f64b-1f3fb-200d-2640-fe0f","🙋🏼‍♀️":"1f64b-1f3fc-200d-2640-fe0f","🙋🏽‍♀️":"1f64b-1f3fd-200d-2640-fe0f","🙋🏾‍♀️":"1f64b-1f3fe-200d-2640-fe0f","🙋🏿‍♀️":"1f64b-1f3ff-200d-2640-fe0f","🧏🏻‍♂️":"1f9cf-1f3fb-200d-2642-fe0f","🧏🏼‍♂️":"1f9cf-1f3fc-200d-2642-fe0f","🧏🏽‍♂️":"1f9cf-1f3fd-200d-2642-fe0f","🧏🏾‍♂️":"1f9cf-1f3fe-200d-2642-fe0f","🧏🏿‍♂️":"1f9cf-1f3ff-200d-2642-fe0f","🧏🏻‍♀️":"1f9cf-1f3fb-200d-2640-fe0f","🧏🏼‍♀️":"1f9cf-1f3fc-200d-2640-fe0f","🧏🏽‍♀️":"1f9cf-1f3fd-200d-2640-fe0f","🧏🏾‍♀️":"1f9cf-1f3fe-200d-2640-fe0f","🧏🏿‍♀️":"1f9cf-1f3ff-200d-2640-fe0f","🙇🏻‍♂️":"1f647-1f3fb-200d-2642-fe0f","🙇🏼‍♂️":"1f647-1f3fc-200d-2642-fe0f","🙇🏽‍♂️":"1f647-1f3fd-200d-2642-fe0f","🙇🏾‍♂️":"1f647-1f3fe-200d-2642-fe0f","🙇🏿‍♂️":"1f647-1f3ff-200d-2642-fe0f","🙇🏻‍♀️":"1f647-1f3fb-200d-2640-fe0f","🙇🏼‍♀️":"1f647-1f3fc-200d-2640-fe0f","🙇🏽‍♀️":"1f647-1f3fd-200d-2640-fe0f","🙇🏾‍♀️":"1f647-1f3fe-200d-2640-fe0f","🙇🏿‍♀️":"1f647-1f3ff-200d-2640-fe0f","🤦🏻‍♂️":"1f926-1f3fb-200d-2642-fe0f","🤦🏼‍♂️":"1f926-1f3fc-200d-2642-fe0f","🤦🏽‍♂️":"1f926-1f3fd-200d-2642-fe0f","🤦🏾‍♂️":"1f926-1f3fe-200d-2642-fe0f","🤦🏿‍♂️":"1f926-1f3ff-200d-2642-fe0f","🤦🏻‍♀️":"1f926-1f3fb-200d-2640-fe0f","🤦🏼‍♀️":"1f926-1f3fc-200d-2640-fe0f","🤦🏽‍♀️":"1f926-1f3fd-200d-2640-fe0f","🤦🏾‍♀️":"1f926-1f3fe-200d-2640-fe0f","🤦🏿‍♀️":"1f926-1f3ff-200d-2640-fe0f","🤷🏻‍♂️":"1f937-1f3fb-200d-2642-fe0f","🤷🏼‍♂️":"1f937-1f3fc-200d-2642-fe0f","🤷🏽‍♂️":"1f937-1f3fd-200d-2642-fe0f","🤷🏾‍♂️":"1f937-1f3fe-200d-2642-fe0f","🤷🏿‍♂️":"1f937-1f3ff-200d-2642-fe0f","🤷🏻‍♀️":"1f937-1f3fb-200d-2640-fe0f","🤷🏼‍♀️":"1f937-1f3fc-200d-2640-fe0f","🤷🏽‍♀️":"1f937-1f3fd-200d-2640-fe0f","🤷🏾‍♀️":"1f937-1f3fe-200d-2640-fe0f","🤷🏿‍♀️":"1f937-1f3ff-200d-2640-fe0f","🧑🏻‍⚕️":"1f9d1-1f3fb-200d-2695-fe0f","🧑🏼‍⚕️":"1f9d1-1f3fc-200d-2695-fe0f","🧑🏽‍⚕️":"1f9d1-1f3fd-200d-2695-fe0f","🧑🏾‍⚕️":"1f9d1-1f3fe-200d-2695-fe0f","🧑🏿‍⚕️":"1f9d1-1f3ff-200d-2695-fe0f","👨🏻‍⚕️":"1f468-1f3fb-200d-2695-fe0f","👨🏼‍⚕️":"1f468-1f3fc-200d-2695-fe0f","👨🏽‍⚕️":"1f468-1f3fd-200d-2695-fe0f","👨🏾‍⚕️":"1f468-1f3fe-200d-2695-fe0f","👨🏿‍⚕️":"1f468-1f3ff-200d-2695-fe0f","👩🏻‍⚕️":"1f469-1f3fb-200d-2695-fe0f","👩🏼‍⚕️":"1f469-1f3fc-200d-2695-fe0f","👩🏽‍⚕️":"1f469-1f3fd-200d-2695-fe0f","👩🏾‍⚕️":"1f469-1f3fe-200d-2695-fe0f","👩🏿‍⚕️":"1f469-1f3ff-200d-2695-fe0f","🧑🏻‍⚖️":"1f9d1-1f3fb-200d-2696-fe0f","🧑🏼‍⚖️":"1f9d1-1f3fc-200d-2696-fe0f","🧑🏽‍⚖️":"1f9d1-1f3fd-200d-2696-fe0f","🧑🏾‍⚖️":"1f9d1-1f3fe-200d-2696-fe0f","🧑🏿‍⚖️":"1f9d1-1f3ff-200d-2696-fe0f","👨🏻‍⚖️":"1f468-1f3fb-200d-2696-fe0f","👨🏼‍⚖️":"1f468-1f3fc-200d-2696-fe0f","👨🏽‍⚖️":"1f468-1f3fd-200d-2696-fe0f","👨🏾‍⚖️":"1f468-1f3fe-200d-2696-fe0f","👨🏿‍⚖️":"1f468-1f3ff-200d-2696-fe0f","👩🏻‍⚖️":"1f469-1f3fb-200d-2696-fe0f","👩🏼‍⚖️":"1f469-1f3fc-200d-2696-fe0f","👩🏽‍⚖️":"1f469-1f3fd-200d-2696-fe0f","👩🏾‍⚖️":"1f469-1f3fe-200d-2696-fe0f","👩🏿‍⚖️":"1f469-1f3ff-200d-2696-fe0f","🧑🏻‍✈️":"1f9d1-1f3fb-200d-2708-fe0f","🧑🏼‍✈️":"1f9d1-1f3fc-200d-2708-fe0f","🧑🏽‍✈️":"1f9d1-1f3fd-200d-2708-fe0f","🧑🏾‍✈️":"1f9d1-1f3fe-200d-2708-fe0f","🧑🏿‍✈️":"1f9d1-1f3ff-200d-2708-fe0f","👨🏻‍✈️":"1f468-1f3fb-200d-2708-fe0f","👨🏼‍✈️":"1f468-1f3fc-200d-2708-fe0f","👨🏽‍✈️":"1f468-1f3fd-200d-2708-fe0f","👨🏾‍✈️":"1f468-1f3fe-200d-2708-fe0f","👨🏿‍✈️":"1f468-1f3ff-200d-2708-fe0f","👩🏻‍✈️":"1f469-1f3fb-200d-2708-fe0f","👩🏼‍✈️":"1f469-1f3fc-200d-2708-fe0f","👩🏽‍✈️":"1f469-1f3fd-200d-2708-fe0f","👩🏾‍✈️":"1f469-1f3fe-200d-2708-fe0f","👩🏿‍✈️":"1f469-1f3ff-200d-2708-fe0f","👮🏻‍♂️":"1f46e-1f3fb-200d-2642-fe0f","👮🏼‍♂️":"1f46e-1f3fc-200d-2642-fe0f","👮🏽‍♂️":"1f46e-1f3fd-200d-2642-fe0f","👮🏾‍♂️":"1f46e-1f3fe-200d-2642-fe0f","👮🏿‍♂️":"1f46e-1f3ff-200d-2642-fe0f","👮🏻‍♀️":"1f46e-1f3fb-200d-2640-fe0f","👮🏼‍♀️":"1f46e-1f3fc-200d-2640-fe0f","👮🏽‍♀️":"1f46e-1f3fd-200d-2640-fe0f","👮🏾‍♀️":"1f46e-1f3fe-200d-2640-fe0f","👮🏿‍♀️":"1f46e-1f3ff-200d-2640-fe0f","🕵️‍♂️":"1f575-fe0f-200d-2642-fe0f","🕵🏻‍♂️":"1f575-1f3fb-200d-2642-fe0f","🕵🏼‍♂️":"1f575-1f3fc-200d-2642-fe0f","🕵🏽‍♂️":"1f575-1f3fd-200d-2642-fe0f","🕵🏾‍♂️":"1f575-1f3fe-200d-2642-fe0f","🕵🏿‍♂️":"1f575-1f3ff-200d-2642-fe0f","🕵️‍♀️":"1f575-fe0f-200d-2640-fe0f","🕵🏻‍♀️":"1f575-1f3fb-200d-2640-fe0f","🕵🏼‍♀️":"1f575-1f3fc-200d-2640-fe0f","🕵🏽‍♀️":"1f575-1f3fd-200d-2640-fe0f","🕵🏾‍♀️":"1f575-1f3fe-200d-2640-fe0f","🕵🏿‍♀️":"1f575-1f3ff-200d-2640-fe0f","💂🏻‍♂️":"1f482-1f3fb-200d-2642-fe0f","💂🏼‍♂️":"1f482-1f3fc-200d-2642-fe0f","💂🏽‍♂️":"1f482-1f3fd-200d-2642-fe0f","💂🏾‍♂️":"1f482-1f3fe-200d-2642-fe0f","💂🏿‍♂️":"1f482-1f3ff-200d-2642-fe0f","💂🏻‍♀️":"1f482-1f3fb-200d-2640-fe0f","💂🏼‍♀️":"1f482-1f3fc-200d-2640-fe0f","💂🏽‍♀️":"1f482-1f3fd-200d-2640-fe0f","💂🏾‍♀️":"1f482-1f3fe-200d-2640-fe0f","💂🏿‍♀️":"1f482-1f3ff-200d-2640-fe0f","👷🏻‍♂️":"1f477-1f3fb-200d-2642-fe0f","👷🏼‍♂️":"1f477-1f3fc-200d-2642-fe0f","👷🏽‍♂️":"1f477-1f3fd-200d-2642-fe0f","👷🏾‍♂️":"1f477-1f3fe-200d-2642-fe0f","👷🏿‍♂️":"1f477-1f3ff-200d-2642-fe0f","👷🏻‍♀️":"1f477-1f3fb-200d-2640-fe0f","👷🏼‍♀️":"1f477-1f3fc-200d-2640-fe0f","👷🏽‍♀️":"1f477-1f3fd-200d-2640-fe0f","👷🏾‍♀️":"1f477-1f3fe-200d-2640-fe0f","👷🏿‍♀️":"1f477-1f3ff-200d-2640-fe0f","👳🏻‍♂️":"1f473-1f3fb-200d-2642-fe0f","👳🏼‍♂️":"1f473-1f3fc-200d-2642-fe0f","👳🏽‍♂️":"1f473-1f3fd-200d-2642-fe0f","👳🏾‍♂️":"1f473-1f3fe-200d-2642-fe0f","👳🏿‍♂️":"1f473-1f3ff-200d-2642-fe0f","👳🏻‍♀️":"1f473-1f3fb-200d-2640-fe0f","👳🏼‍♀️":"1f473-1f3fc-200d-2640-fe0f","👳🏽‍♀️":"1f473-1f3fd-200d-2640-fe0f","👳🏾‍♀️":"1f473-1f3fe-200d-2640-fe0f","👳🏿‍♀️":"1f473-1f3ff-200d-2640-fe0f","🤵🏻‍♂️":"1f935-1f3fb-200d-2642-fe0f","🤵🏼‍♂️":"1f935-1f3fc-200d-2642-fe0f","🤵🏽‍♂️":"1f935-1f3fd-200d-2642-fe0f","🤵🏾‍♂️":"1f935-1f3fe-200d-2642-fe0f","🤵🏿‍♂️":"1f935-1f3ff-200d-2642-fe0f","🤵🏻‍♀️":"1f935-1f3fb-200d-2640-fe0f","🤵🏼‍♀️":"1f935-1f3fc-200d-2640-fe0f","🤵🏽‍♀️":"1f935-1f3fd-200d-2640-fe0f","🤵🏾‍♀️":"1f935-1f3fe-200d-2640-fe0f","🤵🏿‍♀️":"1f935-1f3ff-200d-2640-fe0f","👰🏻‍♂️":"1f470-1f3fb-200d-2642-fe0f","👰🏼‍♂️":"1f470-1f3fc-200d-2642-fe0f","👰🏽‍♂️":"1f470-1f3fd-200d-2642-fe0f","👰🏾‍♂️":"1f470-1f3fe-200d-2642-fe0f","👰🏿‍♂️":"1f470-1f3ff-200d-2642-fe0f","👰🏻‍♀️":"1f470-1f3fb-200d-2640-fe0f","👰🏼‍♀️":"1f470-1f3fc-200d-2640-fe0f","👰🏽‍♀️":"1f470-1f3fd-200d-2640-fe0f","👰🏾‍♀️":"1f470-1f3fe-200d-2640-fe0f","👰🏿‍♀️":"1f470-1f3ff-200d-2640-fe0f","🦸🏻‍♂️":"1f9b8-1f3fb-200d-2642-fe0f","🦸🏼‍♂️":"1f9b8-1f3fc-200d-2642-fe0f","🦸🏽‍♂️":"1f9b8-1f3fd-200d-2642-fe0f","🦸🏾‍♂️":"1f9b8-1f3fe-200d-2642-fe0f","🦸🏿‍♂️":"1f9b8-1f3ff-200d-2642-fe0f","🦸🏻‍♀️":"1f9b8-1f3fb-200d-2640-fe0f","🦸🏼‍♀️":"1f9b8-1f3fc-200d-2640-fe0f","🦸🏽‍♀️":"1f9b8-1f3fd-200d-2640-fe0f","🦸🏾‍♀️":"1f9b8-1f3fe-200d-2640-fe0f","🦸🏿‍♀️":"1f9b8-1f3ff-200d-2640-fe0f","🦹🏻‍♂️":"1f9b9-1f3fb-200d-2642-fe0f","🦹🏼‍♂️":"1f9b9-1f3fc-200d-2642-fe0f","🦹🏽‍♂️":"1f9b9-1f3fd-200d-2642-fe0f","🦹🏾‍♂️":"1f9b9-1f3fe-200d-2642-fe0f","🦹🏿‍♂️":"1f9b9-1f3ff-200d-2642-fe0f","🦹🏻‍♀️":"1f9b9-1f3fb-200d-2640-fe0f","🦹🏼‍♀️":"1f9b9-1f3fc-200d-2640-fe0f","🦹🏽‍♀️":"1f9b9-1f3fd-200d-2640-fe0f","🦹🏾‍♀️":"1f9b9-1f3fe-200d-2640-fe0f","🦹🏿‍♀️":"1f9b9-1f3ff-200d-2640-fe0f","🧙🏻‍♂️":"1f9d9-1f3fb-200d-2642-fe0f","🧙🏼‍♂️":"1f9d9-1f3fc-200d-2642-fe0f","🧙🏽‍♂️":"1f9d9-1f3fd-200d-2642-fe0f","🧙🏾‍♂️":"1f9d9-1f3fe-200d-2642-fe0f","🧙🏿‍♂️":"1f9d9-1f3ff-200d-2642-fe0f","🧙🏻‍♀️":"1f9d9-1f3fb-200d-2640-fe0f","🧙🏼‍♀️":"1f9d9-1f3fc-200d-2640-fe0f","🧙🏽‍♀️":"1f9d9-1f3fd-200d-2640-fe0f","🧙🏾‍♀️":"1f9d9-1f3fe-200d-2640-fe0f","🧙🏿‍♀️":"1f9d9-1f3ff-200d-2640-fe0f","🧚🏻‍♂️":"1f9da-1f3fb-200d-2642-fe0f","🧚🏼‍♂️":"1f9da-1f3fc-200d-2642-fe0f","🧚🏽‍♂️":"1f9da-1f3fd-200d-2642-fe0f","🧚🏾‍♂️":"1f9da-1f3fe-200d-2642-fe0f","🧚🏿‍♂️":"1f9da-1f3ff-200d-2642-fe0f","🧚🏻‍♀️":"1f9da-1f3fb-200d-2640-fe0f","🧚🏼‍♀️":"1f9da-1f3fc-200d-2640-fe0f","🧚🏽‍♀️":"1f9da-1f3fd-200d-2640-fe0f","🧚🏾‍♀️":"1f9da-1f3fe-200d-2640-fe0f","🧚🏿‍♀️":"1f9da-1f3ff-200d-2640-fe0f","🧛🏻‍♂️":"1f9db-1f3fb-200d-2642-fe0f","🧛🏼‍♂️":"1f9db-1f3fc-200d-2642-fe0f","🧛🏽‍♂️":"1f9db-1f3fd-200d-2642-fe0f","🧛🏾‍♂️":"1f9db-1f3fe-200d-2642-fe0f","🧛🏿‍♂️":"1f9db-1f3ff-200d-2642-fe0f","🧛🏻‍♀️":"1f9db-1f3fb-200d-2640-fe0f","🧛🏼‍♀️":"1f9db-1f3fc-200d-2640-fe0f","🧛🏽‍♀️":"1f9db-1f3fd-200d-2640-fe0f","🧛🏾‍♀️":"1f9db-1f3fe-200d-2640-fe0f","🧛🏿‍♀️":"1f9db-1f3ff-200d-2640-fe0f","🧜🏻‍♂️":"1f9dc-1f3fb-200d-2642-fe0f","🧜🏼‍♂️":"1f9dc-1f3fc-200d-2642-fe0f","🧜🏽‍♂️":"1f9dc-1f3fd-200d-2642-fe0f","🧜🏾‍♂️":"1f9dc-1f3fe-200d-2642-fe0f","🧜🏿‍♂️":"1f9dc-1f3ff-200d-2642-fe0f","🧜🏻‍♀️":"1f9dc-1f3fb-200d-2640-fe0f","🧜🏼‍♀️":"1f9dc-1f3fc-200d-2640-fe0f","🧜🏽‍♀️":"1f9dc-1f3fd-200d-2640-fe0f","🧜🏾‍♀️":"1f9dc-1f3fe-200d-2640-fe0f","🧜🏿‍♀️":"1f9dc-1f3ff-200d-2640-fe0f","🧝🏻‍♂️":"1f9dd-1f3fb-200d-2642-fe0f","🧝🏼‍♂️":"1f9dd-1f3fc-200d-2642-fe0f","🧝🏽‍♂️":"1f9dd-1f3fd-200d-2642-fe0f","🧝🏾‍♂️":"1f9dd-1f3fe-200d-2642-fe0f","🧝🏿‍♂️":"1f9dd-1f3ff-200d-2642-fe0f","🧝🏻‍♀️":"1f9dd-1f3fb-200d-2640-fe0f","🧝🏼‍♀️":"1f9dd-1f3fc-200d-2640-fe0f","🧝🏽‍♀️":"1f9dd-1f3fd-200d-2640-fe0f","🧝🏾‍♀️":"1f9dd-1f3fe-200d-2640-fe0f","🧝🏿‍♀️":"1f9dd-1f3ff-200d-2640-fe0f","💆🏻‍♂️":"1f486-1f3fb-200d-2642-fe0f","💆🏼‍♂️":"1f486-1f3fc-200d-2642-fe0f","💆🏽‍♂️":"1f486-1f3fd-200d-2642-fe0f","💆🏾‍♂️":"1f486-1f3fe-200d-2642-fe0f","💆🏿‍♂️":"1f486-1f3ff-200d-2642-fe0f","💆🏻‍♀️":"1f486-1f3fb-200d-2640-fe0f","💆🏼‍♀️":"1f486-1f3fc-200d-2640-fe0f","💆🏽‍♀️":"1f486-1f3fd-200d-2640-fe0f","💆🏾‍♀️":"1f486-1f3fe-200d-2640-fe0f","💆🏿‍♀️":"1f486-1f3ff-200d-2640-fe0f","💇🏻‍♂️":"1f487-1f3fb-200d-2642-fe0f","💇🏼‍♂️":"1f487-1f3fc-200d-2642-fe0f","💇🏽‍♂️":"1f487-1f3fd-200d-2642-fe0f","💇🏾‍♂️":"1f487-1f3fe-200d-2642-fe0f","💇🏿‍♂️":"1f487-1f3ff-200d-2642-fe0f","💇🏻‍♀️":"1f487-1f3fb-200d-2640-fe0f","💇🏼‍♀️":"1f487-1f3fc-200d-2640-fe0f","💇🏽‍♀️":"1f487-1f3fd-200d-2640-fe0f","💇🏾‍♀️":"1f487-1f3fe-200d-2640-fe0f","💇🏿‍♀️":"1f487-1f3ff-200d-2640-fe0f","🚶🏻‍♂️":"1f6b6-1f3fb-200d-2642-fe0f","🚶🏼‍♂️":"1f6b6-1f3fc-200d-2642-fe0f","🚶🏽‍♂️":"1f6b6-1f3fd-200d-2642-fe0f","🚶🏾‍♂️":"1f6b6-1f3fe-200d-2642-fe0f","🚶🏿‍♂️":"1f6b6-1f3ff-200d-2642-fe0f","🚶🏻‍♀️":"1f6b6-1f3fb-200d-2640-fe0f","🚶🏼‍♀️":"1f6b6-1f3fc-200d-2640-fe0f","🚶🏽‍♀️":"1f6b6-1f3fd-200d-2640-fe0f","🚶🏾‍♀️":"1f6b6-1f3fe-200d-2640-fe0f","🚶🏿‍♀️":"1f6b6-1f3ff-200d-2640-fe0f","🧍🏻‍♂️":"1f9cd-1f3fb-200d-2642-fe0f","🧍🏼‍♂️":"1f9cd-1f3fc-200d-2642-fe0f","🧍🏽‍♂️":"1f9cd-1f3fd-200d-2642-fe0f","🧍🏾‍♂️":"1f9cd-1f3fe-200d-2642-fe0f","🧍🏿‍♂️":"1f9cd-1f3ff-200d-2642-fe0f","🧍🏻‍♀️":"1f9cd-1f3fb-200d-2640-fe0f","🧍🏼‍♀️":"1f9cd-1f3fc-200d-2640-fe0f","🧍🏽‍♀️":"1f9cd-1f3fd-200d-2640-fe0f","🧍🏾‍♀️":"1f9cd-1f3fe-200d-2640-fe0f","🧍🏿‍♀️":"1f9cd-1f3ff-200d-2640-fe0f","🧎🏻‍♂️":"1f9ce-1f3fb-200d-2642-fe0f","🧎🏼‍♂️":"1f9ce-1f3fc-200d-2642-fe0f","🧎🏽‍♂️":"1f9ce-1f3fd-200d-2642-fe0f","🧎🏾‍♂️":"1f9ce-1f3fe-200d-2642-fe0f","🧎🏿‍♂️":"1f9ce-1f3ff-200d-2642-fe0f","🧎🏻‍♀️":"1f9ce-1f3fb-200d-2640-fe0f","🧎🏼‍♀️":"1f9ce-1f3fc-200d-2640-fe0f","🧎🏽‍♀️":"1f9ce-1f3fd-200d-2640-fe0f","🧎🏾‍♀️":"1f9ce-1f3fe-200d-2640-fe0f","🧎🏿‍♀️":"1f9ce-1f3ff-200d-2640-fe0f","🏃🏻‍♂️":"1f3c3-1f3fb-200d-2642-fe0f","🏃🏼‍♂️":"1f3c3-1f3fc-200d-2642-fe0f","🏃🏽‍♂️":"1f3c3-1f3fd-200d-2642-fe0f","🏃🏾‍♂️":"1f3c3-1f3fe-200d-2642-fe0f","🏃🏿‍♂️":"1f3c3-1f3ff-200d-2642-fe0f","🏃🏻‍♀️":"1f3c3-1f3fb-200d-2640-fe0f","🏃🏼‍♀️":"1f3c3-1f3fc-200d-2640-fe0f","🏃🏽‍♀️":"1f3c3-1f3fd-200d-2640-fe0f","🏃🏾‍♀️":"1f3c3-1f3fe-200d-2640-fe0f","🏃🏿‍♀️":"1f3c3-1f3ff-200d-2640-fe0f","🧖🏻‍♂️":"1f9d6-1f3fb-200d-2642-fe0f","🧖🏼‍♂️":"1f9d6-1f3fc-200d-2642-fe0f","🧖🏽‍♂️":"1f9d6-1f3fd-200d-2642-fe0f","🧖🏾‍♂️":"1f9d6-1f3fe-200d-2642-fe0f","🧖🏿‍♂️":"1f9d6-1f3ff-200d-2642-fe0f","🧖🏻‍♀️":"1f9d6-1f3fb-200d-2640-fe0f","🧖🏼‍♀️":"1f9d6-1f3fc-200d-2640-fe0f","🧖🏽‍♀️":"1f9d6-1f3fd-200d-2640-fe0f","🧖🏾‍♀️":"1f9d6-1f3fe-200d-2640-fe0f","🧖🏿‍♀️":"1f9d6-1f3ff-200d-2640-fe0f","🧗🏻‍♂️":"1f9d7-1f3fb-200d-2642-fe0f","🧗🏼‍♂️":"1f9d7-1f3fc-200d-2642-fe0f","🧗🏽‍♂️":"1f9d7-1f3fd-200d-2642-fe0f","🧗🏾‍♂️":"1f9d7-1f3fe-200d-2642-fe0f","🧗🏿‍♂️":"1f9d7-1f3ff-200d-2642-fe0f","🧗🏻‍♀️":"1f9d7-1f3fb-200d-2640-fe0f","🧗🏼‍♀️":"1f9d7-1f3fc-200d-2640-fe0f","🧗🏽‍♀️":"1f9d7-1f3fd-200d-2640-fe0f","🧗🏾‍♀️":"1f9d7-1f3fe-200d-2640-fe0f","🧗🏿‍♀️":"1f9d7-1f3ff-200d-2640-fe0f","🏌️‍♂️":"1f3cc-fe0f-200d-2642-fe0f","🏌🏻‍♂️":"1f3cc-1f3fb-200d-2642-fe0f","🏌🏼‍♂️":"1f3cc-1f3fc-200d-2642-fe0f","🏌🏽‍♂️":"1f3cc-1f3fd-200d-2642-fe0f","🏌🏾‍♂️":"1f3cc-1f3fe-200d-2642-fe0f","🏌🏿‍♂️":"1f3cc-1f3ff-200d-2642-fe0f","🏌️‍♀️":"1f3cc-fe0f-200d-2640-fe0f","🏌🏻‍♀️":"1f3cc-1f3fb-200d-2640-fe0f","🏌🏼‍♀️":"1f3cc-1f3fc-200d-2640-fe0f","🏌🏽‍♀️":"1f3cc-1f3fd-200d-2640-fe0f","🏌🏾‍♀️":"1f3cc-1f3fe-200d-2640-fe0f","🏌🏿‍♀️":"1f3cc-1f3ff-200d-2640-fe0f","🏄🏻‍♂️":"1f3c4-1f3fb-200d-2642-fe0f","🏄🏼‍♂️":"1f3c4-1f3fc-200d-2642-fe0f","🏄🏽‍♂️":"1f3c4-1f3fd-200d-2642-fe0f","🏄🏾‍♂️":"1f3c4-1f3fe-200d-2642-fe0f","🏄🏿‍♂️":"1f3c4-1f3ff-200d-2642-fe0f","🏄🏻‍♀️":"1f3c4-1f3fb-200d-2640-fe0f","🏄🏼‍♀️":"1f3c4-1f3fc-200d-2640-fe0f","🏄🏽‍♀️":"1f3c4-1f3fd-200d-2640-fe0f","🏄🏾‍♀️":"1f3c4-1f3fe-200d-2640-fe0f","🏄🏿‍♀️":"1f3c4-1f3ff-200d-2640-fe0f","🚣🏻‍♂️":"1f6a3-1f3fb-200d-2642-fe0f","🚣🏼‍♂️":"1f6a3-1f3fc-200d-2642-fe0f","🚣🏽‍♂️":"1f6a3-1f3fd-200d-2642-fe0f","🚣🏾‍♂️":"1f6a3-1f3fe-200d-2642-fe0f","🚣🏿‍♂️":"1f6a3-1f3ff-200d-2642-fe0f","🚣🏻‍♀️":"1f6a3-1f3fb-200d-2640-fe0f","🚣🏼‍♀️":"1f6a3-1f3fc-200d-2640-fe0f","🚣🏽‍♀️":"1f6a3-1f3fd-200d-2640-fe0f","🚣🏾‍♀️":"1f6a3-1f3fe-200d-2640-fe0f","🚣🏿‍♀️":"1f6a3-1f3ff-200d-2640-fe0f","🏊🏻‍♂️":"1f3ca-1f3fb-200d-2642-fe0f","🏊🏼‍♂️":"1f3ca-1f3fc-200d-2642-fe0f","🏊🏽‍♂️":"1f3ca-1f3fd-200d-2642-fe0f","🏊🏾‍♂️":"1f3ca-1f3fe-200d-2642-fe0f","🏊🏿‍♂️":"1f3ca-1f3ff-200d-2642-fe0f","🏊🏻‍♀️":"1f3ca-1f3fb-200d-2640-fe0f","🏊🏼‍♀️":"1f3ca-1f3fc-200d-2640-fe0f","🏊🏽‍♀️":"1f3ca-1f3fd-200d-2640-fe0f","🏊🏾‍♀️":"1f3ca-1f3fe-200d-2640-fe0f","🏊🏿‍♀️":"1f3ca-1f3ff-200d-2640-fe0f","⛹️‍♂️":"26f9-fe0f-200d-2642-fe0f","⛹🏻‍♂️":"26f9-1f3fb-200d-2642-fe0f","⛹🏼‍♂️":"26f9-1f3fc-200d-2642-fe0f","⛹🏽‍♂️":"26f9-1f3fd-200d-2642-fe0f","⛹🏾‍♂️":"26f9-1f3fe-200d-2642-fe0f","⛹🏿‍♂️":"26f9-1f3ff-200d-2642-fe0f","⛹️‍♀️":"26f9-fe0f-200d-2640-fe0f","⛹🏻‍♀️":"26f9-1f3fb-200d-2640-fe0f","⛹🏼‍♀️":"26f9-1f3fc-200d-2640-fe0f","⛹🏽‍♀️":"26f9-1f3fd-200d-2640-fe0f","⛹🏾‍♀️":"26f9-1f3fe-200d-2640-fe0f","⛹🏿‍♀️":"26f9-1f3ff-200d-2640-fe0f","🏋️‍♂️":"1f3cb-fe0f-200d-2642-fe0f","🏋🏻‍♂️":"1f3cb-1f3fb-200d-2642-fe0f","🏋🏼‍♂️":"1f3cb-1f3fc-200d-2642-fe0f","🏋🏽‍♂️":"1f3cb-1f3fd-200d-2642-fe0f","🏋🏾‍♂️":"1f3cb-1f3fe-200d-2642-fe0f","🏋🏿‍♂️":"1f3cb-1f3ff-200d-2642-fe0f","🏋️‍♀️":"1f3cb-fe0f-200d-2640-fe0f","🏋🏻‍♀️":"1f3cb-1f3fb-200d-2640-fe0f","🏋🏼‍♀️":"1f3cb-1f3fc-200d-2640-fe0f","🏋🏽‍♀️":"1f3cb-1f3fd-200d-2640-fe0f","🏋🏾‍♀️":"1f3cb-1f3fe-200d-2640-fe0f","🏋🏿‍♀️":"1f3cb-1f3ff-200d-2640-fe0f","🚴🏻‍♂️":"1f6b4-1f3fb-200d-2642-fe0f","🚴🏼‍♂️":"1f6b4-1f3fc-200d-2642-fe0f","🚴🏽‍♂️":"1f6b4-1f3fd-200d-2642-fe0f","🚴🏾‍♂️":"1f6b4-1f3fe-200d-2642-fe0f","🚴🏿‍♂️":"1f6b4-1f3ff-200d-2642-fe0f","🚴🏻‍♀️":"1f6b4-1f3fb-200d-2640-fe0f","🚴🏼‍♀️":"1f6b4-1f3fc-200d-2640-fe0f","🚴🏽‍♀️":"1f6b4-1f3fd-200d-2640-fe0f","🚴🏾‍♀️":"1f6b4-1f3fe-200d-2640-fe0f","🚴🏿‍♀️":"1f6b4-1f3ff-200d-2640-fe0f","🚵🏻‍♂️":"1f6b5-1f3fb-200d-2642-fe0f","🚵🏼‍♂️":"1f6b5-1f3fc-200d-2642-fe0f","🚵🏽‍♂️":"1f6b5-1f3fd-200d-2642-fe0f","🚵🏾‍♂️":"1f6b5-1f3fe-200d-2642-fe0f","🚵🏿‍♂️":"1f6b5-1f3ff-200d-2642-fe0f","🚵🏻‍♀️":"1f6b5-1f3fb-200d-2640-fe0f","🚵🏼‍♀️":"1f6b5-1f3fc-200d-2640-fe0f","🚵🏽‍♀️":"1f6b5-1f3fd-200d-2640-fe0f","🚵🏾‍♀️":"1f6b5-1f3fe-200d-2640-fe0f","🚵🏿‍♀️":"1f6b5-1f3ff-200d-2640-fe0f","🤸🏻‍♂️":"1f938-1f3fb-200d-2642-fe0f","🤸🏼‍♂️":"1f938-1f3fc-200d-2642-fe0f","🤸🏽‍♂️":"1f938-1f3fd-200d-2642-fe0f","🤸🏾‍♂️":"1f938-1f3fe-200d-2642-fe0f","🤸🏿‍♂️":"1f938-1f3ff-200d-2642-fe0f","🤸🏻‍♀️":"1f938-1f3fb-200d-2640-fe0f","🤸🏼‍♀️":"1f938-1f3fc-200d-2640-fe0f","🤸🏽‍♀️":"1f938-1f3fd-200d-2640-fe0f","🤸🏾‍♀️":"1f938-1f3fe-200d-2640-fe0f","🤸🏿‍♀️":"1f938-1f3ff-200d-2640-fe0f","🤽🏻‍♂️":"1f93d-1f3fb-200d-2642-fe0f","🤽🏼‍♂️":"1f93d-1f3fc-200d-2642-fe0f","🤽🏽‍♂️":"1f93d-1f3fd-200d-2642-fe0f","🤽🏾‍♂️":"1f93d-1f3fe-200d-2642-fe0f","🤽🏿‍♂️":"1f93d-1f3ff-200d-2642-fe0f","🤽🏻‍♀️":"1f93d-1f3fb-200d-2640-fe0f","🤽🏼‍♀️":"1f93d-1f3fc-200d-2640-fe0f","🤽🏽‍♀️":"1f93d-1f3fd-200d-2640-fe0f","🤽🏾‍♀️":"1f93d-1f3fe-200d-2640-fe0f","🤽🏿‍♀️":"1f93d-1f3ff-200d-2640-fe0f","🤾🏻‍♂️":"1f93e-1f3fb-200d-2642-fe0f","🤾🏼‍♂️":"1f93e-1f3fc-200d-2642-fe0f","🤾🏽‍♂️":"1f93e-1f3fd-200d-2642-fe0f","🤾🏾‍♂️":"1f93e-1f3fe-200d-2642-fe0f","🤾🏿‍♂️":"1f93e-1f3ff-200d-2642-fe0f","🤾🏻‍♀️":"1f93e-1f3fb-200d-2640-fe0f","🤾🏼‍♀️":"1f93e-1f3fc-200d-2640-fe0f","🤾🏽‍♀️":"1f93e-1f3fd-200d-2640-fe0f","🤾🏾‍♀️":"1f93e-1f3fe-200d-2640-fe0f","🤾🏿‍♀️":"1f93e-1f3ff-200d-2640-fe0f","🤹🏻‍♂️":"1f939-1f3fb-200d-2642-fe0f","🤹🏼‍♂️":"1f939-1f3fc-200d-2642-fe0f","🤹🏽‍♂️":"1f939-1f3fd-200d-2642-fe0f","🤹🏾‍♂️":"1f939-1f3fe-200d-2642-fe0f","🤹🏿‍♂️":"1f939-1f3ff-200d-2642-fe0f","🤹🏻‍♀️":"1f939-1f3fb-200d-2640-fe0f","🤹🏼‍♀️":"1f939-1f3fc-200d-2640-fe0f","🤹🏽‍♀️":"1f939-1f3fd-200d-2640-fe0f","🤹🏾‍♀️":"1f939-1f3fe-200d-2640-fe0f","🤹🏿‍♀️":"1f939-1f3ff-200d-2640-fe0f","🧘🏻‍♂️":"1f9d8-1f3fb-200d-2642-fe0f","🧘🏼‍♂️":"1f9d8-1f3fc-200d-2642-fe0f","🧘🏽‍♂️":"1f9d8-1f3fd-200d-2642-fe0f","🧘🏾‍♂️":"1f9d8-1f3fe-200d-2642-fe0f","🧘🏿‍♂️":"1f9d8-1f3ff-200d-2642-fe0f","🧘🏻‍♀️":"1f9d8-1f3fb-200d-2640-fe0f","🧘🏼‍♀️":"1f9d8-1f3fc-200d-2640-fe0f","🧘🏽‍♀️":"1f9d8-1f3fd-200d-2640-fe0f","🧘🏾‍♀️":"1f9d8-1f3fe-200d-2640-fe0f","🧘🏿‍♀️":"1f9d8-1f3ff-200d-2640-fe0f","🧑‍🤝‍🧑":"1f9d1-200d-1f91d-200d-1f9d1","👩‍❤‍👨":"1f469-200d-2764-fe0f-200d-1f468","👨‍❤‍👨":"1f468-200d-2764-fe0f-200d-1f468","👩‍❤‍👩":"1f469-200d-2764-fe0f-200d-1f469","👨‍👩‍👦":"1f468-200d-1f469-200d-1f466","👨‍👩‍👧":"1f468-200d-1f469-200d-1f467","👨‍👨‍👦":"1f468-200d-1f468-200d-1f466","👨‍👨‍👧":"1f468-200d-1f468-200d-1f467","👩‍👩‍👦":"1f469-200d-1f469-200d-1f466","👩‍👩‍👧":"1f469-200d-1f469-200d-1f467","👨‍👦‍👦":"1f468-200d-1f466-200d-1f466","👨‍👧‍👦":"1f468-200d-1f467-200d-1f466","👨‍👧‍👧":"1f468-200d-1f467-200d-1f467","👩‍👦‍👦":"1f469-200d-1f466-200d-1f466","👩‍👧‍👦":"1f469-200d-1f467-200d-1f466","👩‍👧‍👧":"1f469-200d-1f467-200d-1f467","🏳️‍⚧️":"1f3f3-fe0f-200d-26a7-fe0f","👩‍❤️‍👨":"1f469-200d-2764-fe0f-200d-1f468","👨‍❤️‍👨":"1f468-200d-2764-fe0f-200d-1f468","👩‍❤️‍👩":"1f469-200d-2764-fe0f-200d-1f469","🧑🏻‍🤝‍🧑🏻":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fb","🧑🏻‍🤝‍🧑🏼":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fc","🧑🏻‍🤝‍🧑🏽":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fd","🧑🏻‍🤝‍🧑🏾":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3fe","🧑🏻‍🤝‍🧑🏿":"1f9d1-1f3fb-200d-1f91d-200d-1f9d1-1f3ff","🧑🏼‍🤝‍🧑🏻":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fb","🧑🏼‍🤝‍🧑🏼":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fc","🧑🏼‍🤝‍🧑🏽":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fd","🧑🏼‍🤝‍🧑🏾":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3fe","🧑🏼‍🤝‍🧑🏿":"1f9d1-1f3fc-200d-1f91d-200d-1f9d1-1f3ff","🧑🏽‍🤝‍🧑🏻":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fb","🧑🏽‍🤝‍🧑🏼":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fc","🧑🏽‍🤝‍🧑🏽":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fd","🧑🏽‍🤝‍🧑🏾":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3fe","🧑🏽‍🤝‍🧑🏿":"1f9d1-1f3fd-200d-1f91d-200d-1f9d1-1f3ff","🧑🏾‍🤝‍🧑🏻":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fb","🧑🏾‍🤝‍🧑🏼":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fc","🧑🏾‍🤝‍🧑🏽":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fd","🧑🏾‍🤝‍🧑🏾":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3fe","🧑🏾‍🤝‍🧑🏿":"1f9d1-1f3fe-200d-1f91d-200d-1f9d1-1f3ff","🧑🏿‍🤝‍🧑🏻":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fb","🧑🏿‍🤝‍🧑🏼":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fc","🧑🏿‍🤝‍🧑🏽":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fd","🧑🏿‍🤝‍🧑🏾":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3fe","🧑🏿‍🤝‍🧑🏿":"1f9d1-1f3ff-200d-1f91d-200d-1f9d1-1f3ff","👩🏻‍🤝‍👩🏼":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fc","👩🏻‍🤝‍👩🏽":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fd","👩🏻‍🤝‍👩🏾":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3fe","👩🏻‍🤝‍👩🏿":"1f469-1f3fb-200d-1f91d-200d-1f469-1f3ff","👩🏼‍🤝‍👩🏻":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fb","👩🏼‍🤝‍👩🏽":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fd","👩🏼‍🤝‍👩🏾":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3fe","👩🏼‍🤝‍👩🏿":"1f469-1f3fc-200d-1f91d-200d-1f469-1f3ff","👩🏽‍🤝‍👩🏻":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fb","👩🏽‍🤝‍👩🏼":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fc","👩🏽‍🤝‍👩🏾":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3fe","👩🏽‍🤝‍👩🏿":"1f469-1f3fd-200d-1f91d-200d-1f469-1f3ff","👩🏾‍🤝‍👩🏻":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fb","👩🏾‍🤝‍👩🏼":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fc","👩🏾‍🤝‍👩🏽":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3fd","👩🏾‍🤝‍👩🏿":"1f469-1f3fe-200d-1f91d-200d-1f469-1f3ff","👩🏿‍🤝‍👩🏻":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fb","👩🏿‍🤝‍👩🏼":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fc","👩🏿‍🤝‍👩🏽":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fd","👩🏿‍🤝‍👩🏾":"1f469-1f3ff-200d-1f91d-200d-1f469-1f3fe","👩🏻‍🤝‍👨🏼":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fc","👩🏻‍🤝‍👨🏽":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fd","👩🏻‍🤝‍👨🏾":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3fe","👩🏻‍🤝‍👨🏿":"1f469-1f3fb-200d-1f91d-200d-1f468-1f3ff","👩🏼‍🤝‍👨🏻":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fb","👩🏼‍🤝‍👨🏽":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fd","👩🏼‍🤝‍👨🏾":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3fe","👩🏼‍🤝‍👨🏿":"1f469-1f3fc-200d-1f91d-200d-1f468-1f3ff","👩🏽‍🤝‍👨🏻":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fb","👩🏽‍🤝‍👨🏼":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fc","👩🏽‍🤝‍👨🏾":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3fe","👩🏽‍🤝‍👨🏿":"1f469-1f3fd-200d-1f91d-200d-1f468-1f3ff","👩🏾‍🤝‍👨🏻":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fb","👩🏾‍🤝‍👨🏼":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fc","👩🏾‍🤝‍👨🏽":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3fd","👩🏾‍🤝‍👨🏿":"1f469-1f3fe-200d-1f91d-200d-1f468-1f3ff","👩🏿‍🤝‍👨🏻":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fb","👩🏿‍🤝‍👨🏼":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fc","👩🏿‍🤝‍👨🏽":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fd","👩🏿‍🤝‍👨🏾":"1f469-1f3ff-200d-1f91d-200d-1f468-1f3fe","👨🏻‍🤝‍👨🏼":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fc","👨🏻‍🤝‍👨🏽":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fd","👨🏻‍🤝‍👨🏾":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3fe","👨🏻‍🤝‍👨🏿":"1f468-1f3fb-200d-1f91d-200d-1f468-1f3ff","👨🏼‍🤝‍👨🏻":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fb","👨🏼‍🤝‍👨🏽":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fd","👨🏼‍🤝‍👨🏾":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3fe","👨🏼‍🤝‍👨🏿":"1f468-1f3fc-200d-1f91d-200d-1f468-1f3ff","👨🏽‍🤝‍👨🏻":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fb","👨🏽‍🤝‍👨🏼":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fc","👨🏽‍🤝‍👨🏾":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3fe","👨🏽‍🤝‍👨🏿":"1f468-1f3fd-200d-1f91d-200d-1f468-1f3ff","👨🏾‍🤝‍👨🏻":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fb","👨🏾‍🤝‍👨🏼":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fc","👨🏾‍🤝‍👨🏽":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3fd","👨🏾‍🤝‍👨🏿":"1f468-1f3fe-200d-1f91d-200d-1f468-1f3ff","👨🏿‍🤝‍👨🏻":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fb","👨🏿‍🤝‍👨🏼":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fc","👨🏿‍🤝‍👨🏽":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fd","👨🏿‍🤝‍👨🏾":"1f468-1f3ff-200d-1f91d-200d-1f468-1f3fe","👩‍❤‍💋‍👨":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f468","👨‍❤‍💋‍👨":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468","👩‍❤‍💋‍👩":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469","🧑🏻‍❤‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏻‍❤‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏻‍❤‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏻‍❤‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏼‍❤‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏼‍❤‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏼‍❤‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏼‍❤‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏽‍❤‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏽‍❤‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏽‍❤‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏽‍❤‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏾‍❤‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏾‍❤‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏾‍❤‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏾‍❤‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏿‍❤‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏿‍❤‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏿‍❤‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏿‍❤‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fe","👩🏻‍❤‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","👩🏻‍❤‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","👩🏻‍❤‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👩🏻‍❤‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","👩🏻‍❤‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👩🏼‍❤‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👩🏼‍❤‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","👩🏼‍❤‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","👩🏼‍❤‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👩🏼‍❤‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👩🏽‍❤‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","👩🏽‍❤‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","👩🏽‍❤‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","👩🏽‍❤‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👩🏽‍❤‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👩🏾‍❤‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👩🏾‍❤‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👩🏾‍❤‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","👩🏾‍❤‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👩🏾‍❤‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","👩🏿‍❤‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👩🏿‍❤‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","👩🏿‍❤‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","👩🏿‍❤‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👩🏿‍❤‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👨🏻‍❤‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","👨🏻‍❤‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","👨🏻‍❤‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👨🏻‍❤‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","👨🏻‍❤‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👨🏼‍❤‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👨🏼‍❤‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","👨🏼‍❤‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","👨🏼‍❤‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👨🏼‍❤‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👨🏽‍❤‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","👨🏽‍❤‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","👨🏽‍❤‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","👨🏽‍❤‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👨🏽‍❤‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👨🏾‍❤‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👨🏾‍❤‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👨🏾‍❤‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","👨🏾‍❤‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👨🏾‍❤‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","👨🏿‍❤‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👨🏿‍❤‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","👨🏿‍❤‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","👨🏿‍❤‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👨🏿‍❤‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👩🏻‍❤‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fb","👩🏻‍❤‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fc","👩🏻‍❤‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fd","👩🏻‍❤‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fe","👩🏻‍❤‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3ff","👩🏼‍❤‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fb","👩🏼‍❤‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fc","👩🏼‍❤‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fd","👩🏼‍❤‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fe","👩🏼‍❤‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3ff","👩🏽‍❤‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fb","👩🏽‍❤‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fc","👩🏽‍❤‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fd","👩🏽‍❤‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fe","👩🏽‍❤‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3ff","👩🏾‍❤‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fb","👩🏾‍❤‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fc","👩🏾‍❤‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fd","👩🏾‍❤‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fe","👩🏾‍❤‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3ff","👩🏿‍❤‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fb","👩🏿‍❤‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fc","👩🏿‍❤‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fd","👩🏿‍❤‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fe","👩🏿‍❤‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3ff","👨‍👩‍👧‍👦":"1f468-200d-1f469-200d-1f467-200d-1f466","👨‍👩‍👦‍👦":"1f468-200d-1f469-200d-1f466-200d-1f466","👨‍👩‍👧‍👧":"1f468-200d-1f469-200d-1f467-200d-1f467","👨‍👨‍👧‍👦":"1f468-200d-1f468-200d-1f467-200d-1f466","👨‍👨‍👦‍👦":"1f468-200d-1f468-200d-1f466-200d-1f466","👨‍👨‍👧‍👧":"1f468-200d-1f468-200d-1f467-200d-1f467","👩‍👩‍👧‍👦":"1f469-200d-1f469-200d-1f467-200d-1f466","👩‍👩‍👦‍👦":"1f469-200d-1f469-200d-1f466-200d-1f466","👩‍👩‍👧‍👧":"1f469-200d-1f469-200d-1f467-200d-1f467","🏴󠁧󠁢󠁥󠁮󠁧󠁿":"1f3f4-e0067-e0062-e0065-e006e-e0067-e007f","🏴󠁧󠁢󠁳󠁣󠁴󠁿":"1f3f4-e0067-e0062-e0073-e0063-e0074-e007f","🏴󠁧󠁢󠁷󠁬󠁳󠁿":"1f3f4-e0067-e0062-e0077-e006c-e0073-e007f","👩‍❤️‍💋‍👨":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f468","👨‍❤️‍💋‍👨":"1f468-200d-2764-fe0f-200d-1f48b-200d-1f468","👩‍❤️‍💋‍👩":"1f469-200d-2764-fe0f-200d-1f48b-200d-1f469","🧑🏻‍❤️‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏻‍❤️‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏻‍❤️‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏻‍❤️‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏼‍❤️‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏼‍❤️‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏼‍❤️‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏼‍❤️‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏽‍❤️‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏽‍❤️‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏽‍❤️‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3fe","🧑🏽‍❤️‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏾‍❤️‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏾‍❤️‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏾‍❤️‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏾‍❤️‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f9d1-1f3ff","🧑🏿‍❤️‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fb","🧑🏿‍❤️‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fc","🧑🏿‍❤️‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fd","🧑🏿‍❤️‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f9d1-1f3fe","👩🏻‍❤️‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","👩🏻‍❤️‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","👩🏻‍❤️‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👩🏻‍❤️‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","👩🏻‍❤️‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👩🏼‍❤️‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👩🏼‍❤️‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","👩🏼‍❤️‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","👩🏼‍❤️‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👩🏼‍❤️‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👩🏽‍❤️‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","👩🏽‍❤️‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","👩🏽‍❤️‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","👩🏽‍❤️‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👩🏽‍❤️‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👩🏾‍❤️‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👩🏾‍❤️‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👩🏾‍❤️‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","👩🏾‍❤️‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👩🏾‍❤️‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","👩🏿‍❤️‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👩🏿‍❤️‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","👩🏿‍❤️‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","👩🏿‍❤️‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👩🏿‍❤️‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👨🏻‍❤️‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fb","👨🏻‍❤️‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fc","👨🏻‍❤️‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fd","👨🏻‍❤️‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3fe","👨🏻‍❤️‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f468-1f3ff","👨🏼‍❤️‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fb","👨🏼‍❤️‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fc","👨🏼‍❤️‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fd","👨🏼‍❤️‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3fe","👨🏼‍❤️‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f468-1f3ff","👨🏽‍❤️‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fb","👨🏽‍❤️‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fc","👨🏽‍❤️‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fd","👨🏽‍❤️‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3fe","👨🏽‍❤️‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f468-1f3ff","👨🏾‍❤️‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fb","👨🏾‍❤️‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fc","👨🏾‍❤️‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fd","👨🏾‍❤️‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3fe","👨🏾‍❤️‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f468-1f3ff","👨🏿‍❤️‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fb","👨🏿‍❤️‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fc","👨🏿‍❤️‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fd","👨🏿‍❤️‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3fe","👨🏿‍❤️‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f468-1f3ff","👩🏻‍❤️‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fb","👩🏻‍❤️‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fc","👩🏻‍❤️‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fd","👩🏻‍❤️‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3fe","👩🏻‍❤️‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f469-1f3ff","👩🏼‍❤️‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fb","👩🏼‍❤️‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fc","👩🏼‍❤️‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fd","👩🏼‍❤️‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3fe","👩🏼‍❤️‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f469-1f3ff","👩🏽‍❤️‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fb","👩🏽‍❤️‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fc","👩🏽‍❤️‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fd","👩🏽‍❤️‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3fe","👩🏽‍❤️‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f469-1f3ff","👩🏾‍❤️‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fb","👩🏾‍❤️‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fc","👩🏾‍❤️‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fd","👩🏾‍❤️‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3fe","👩🏾‍❤️‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f469-1f3ff","👩🏿‍❤️‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fb","👩🏿‍❤️‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fc","👩🏿‍❤️‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fd","👩🏿‍❤️‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3fe","👩🏿‍❤️‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f469-1f3ff","🧑🏻‍❤‍💋‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏻‍❤‍💋‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏻‍❤‍💋‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏻‍❤‍💋‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏼‍❤‍💋‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏼‍❤‍💋‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏼‍❤‍💋‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏼‍❤‍💋‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏽‍❤‍💋‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏽‍❤‍💋‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏽‍❤‍💋‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏽‍❤‍💋‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏾‍❤‍💋‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏾‍❤‍💋‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏾‍❤‍💋‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏾‍❤‍💋‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏿‍❤‍💋‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏿‍❤‍💋‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏿‍❤‍💋‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏿‍❤‍💋‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","👩🏻‍❤‍💋‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏻‍❤‍💋‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏻‍❤‍💋‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏻‍❤‍💋‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏻‍❤‍💋‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏼‍❤‍💋‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏼‍❤‍💋‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏼‍❤‍💋‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏼‍❤‍💋‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏼‍❤‍💋‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏽‍❤‍💋‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏽‍❤‍💋‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏽‍❤‍💋‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏽‍❤‍💋‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏽‍❤‍💋‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏾‍❤‍💋‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏾‍❤‍💋‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏾‍❤‍💋‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏾‍❤‍💋‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏾‍❤‍💋‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏿‍❤‍💋‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏿‍❤‍💋‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏿‍❤‍💋‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏿‍❤‍💋‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏿‍❤‍💋‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏻‍❤‍💋‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏻‍❤‍💋‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏻‍❤‍💋‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏻‍❤‍💋‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏻‍❤‍💋‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏼‍❤‍💋‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏼‍❤‍💋‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏼‍❤‍💋‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏼‍❤‍💋‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏼‍❤‍💋‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏽‍❤‍💋‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏽‍❤‍💋‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏽‍❤‍💋‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏽‍❤‍💋‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏽‍❤‍💋‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏾‍❤‍💋‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏾‍❤‍💋‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏾‍❤‍💋‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏾‍❤‍💋‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏾‍❤‍💋‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏿‍❤‍💋‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏿‍❤‍💋‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏿‍❤‍💋‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏿‍❤‍💋‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏿‍❤‍💋‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏻‍❤‍💋‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏻‍❤‍💋‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏻‍❤‍💋‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏻‍❤‍💋‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏻‍❤‍💋‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏼‍❤‍💋‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏼‍❤‍💋‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏼‍❤‍💋‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏼‍❤‍💋‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏼‍❤‍💋‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏽‍❤‍💋‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏽‍❤‍💋‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏽‍❤‍💋‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏽‍❤‍💋‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏽‍❤‍💋‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏾‍❤‍💋‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏾‍❤‍💋‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏾‍❤‍💋‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏾‍❤‍💋‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏾‍❤‍💋‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏿‍❤‍💋‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏿‍❤‍💋‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏿‍❤‍💋‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏿‍❤‍💋‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏿‍❤‍💋‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","🧑🏻‍❤️‍💋‍🧑🏼":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏻‍❤️‍💋‍🧑🏽":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏻‍❤️‍💋‍🧑🏾":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏻‍❤️‍💋‍🧑🏿":"1f9d1-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏼‍❤️‍💋‍🧑🏻":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏼‍❤️‍💋‍🧑🏽":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏼‍❤️‍💋‍🧑🏾":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏼‍❤️‍💋‍🧑🏿":"1f9d1-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏽‍❤️‍💋‍🧑🏻":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏽‍❤️‍💋‍🧑🏼":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏽‍❤️‍💋‍🧑🏾":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","🧑🏽‍❤️‍💋‍🧑🏿":"1f9d1-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏾‍❤️‍💋‍🧑🏻":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏾‍❤️‍💋‍🧑🏼":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏾‍❤️‍💋‍🧑🏽":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏾‍❤️‍💋‍🧑🏿":"1f9d1-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3ff","🧑🏿‍❤️‍💋‍🧑🏻":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fb","🧑🏿‍❤️‍💋‍🧑🏼":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fc","🧑🏿‍❤️‍💋‍🧑🏽":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fd","🧑🏿‍❤️‍💋‍🧑🏾":"1f9d1-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f9d1-1f3fe","👩🏻‍❤️‍💋‍👨🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏻‍❤️‍💋‍👨🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏻‍❤️‍💋‍👨🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏻‍❤️‍💋‍👨🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏻‍❤️‍💋‍👨🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏼‍❤️‍💋‍👨🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏼‍❤️‍💋‍👨🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏼‍❤️‍💋‍👨🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏼‍❤️‍💋‍👨🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏼‍❤️‍💋‍👨🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏽‍❤️‍💋‍👨🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏽‍❤️‍💋‍👨🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏽‍❤️‍💋‍👨🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏽‍❤️‍💋‍👨🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏽‍❤️‍💋‍👨🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏾‍❤️‍💋‍👨🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏾‍❤️‍💋‍👨🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏾‍❤️‍💋‍👨🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏾‍❤️‍💋‍👨🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏾‍❤️‍💋‍👨🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏿‍❤️‍💋‍👨🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👩🏿‍❤️‍💋‍👨🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👩🏿‍❤️‍💋‍👨🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👩🏿‍❤️‍💋‍👨🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👩🏿‍❤️‍💋‍👨🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏻‍❤️‍💋‍👨🏻":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏻‍❤️‍💋‍👨🏼":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏻‍❤️‍💋‍👨🏽":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏻‍❤️‍💋‍👨🏾":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏻‍❤️‍💋‍👨🏿":"1f468-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏼‍❤️‍💋‍👨🏻":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏼‍❤️‍💋‍👨🏼":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏼‍❤️‍💋‍👨🏽":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏼‍❤️‍💋‍👨🏾":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏼‍❤️‍💋‍👨🏿":"1f468-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏽‍❤️‍💋‍👨🏻":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏽‍❤️‍💋‍👨🏼":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏽‍❤️‍💋‍👨🏽":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏽‍❤️‍💋‍👨🏾":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏽‍❤️‍💋‍👨🏿":"1f468-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏾‍❤️‍💋‍👨🏻":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏾‍❤️‍💋‍👨🏼":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏾‍❤️‍💋‍👨🏽":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏾‍❤️‍💋‍👨🏾":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏾‍❤️‍💋‍👨🏿":"1f468-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👨🏿‍❤️‍💋‍👨🏻":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fb","👨🏿‍❤️‍💋‍👨🏼":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fc","👨🏿‍❤️‍💋‍👨🏽":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fd","👨🏿‍❤️‍💋‍👨🏾":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3fe","👨🏿‍❤️‍💋‍👨🏿":"1f468-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f468-1f3ff","👩🏻‍❤️‍💋‍👩🏻":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏻‍❤️‍💋‍👩🏼":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏻‍❤️‍💋‍👩🏽":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏻‍❤️‍💋‍👩🏾":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏻‍❤️‍💋‍👩🏿":"1f469-1f3fb-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏼‍❤️‍💋‍👩🏻":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏼‍❤️‍💋‍👩🏼":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏼‍❤️‍💋‍👩🏽":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏼‍❤️‍💋‍👩🏾":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏼‍❤️‍💋‍👩🏿":"1f469-1f3fc-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏽‍❤️‍💋‍👩🏻":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏽‍❤️‍💋‍👩🏼":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏽‍❤️‍💋‍👩🏽":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏽‍❤️‍💋‍👩🏾":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏽‍❤️‍💋‍👩🏿":"1f469-1f3fd-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏾‍❤️‍💋‍👩🏻":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏾‍❤️‍💋‍👩🏼":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏾‍❤️‍💋‍👩🏽":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏾‍❤️‍💋‍👩🏾":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏾‍❤️‍💋‍👩🏿":"1f469-1f3fe-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff","👩🏿‍❤️‍💋‍👩🏻":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fb","👩🏿‍❤️‍💋‍👩🏼":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fc","👩🏿‍❤️‍💋‍👩🏽":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fd","👩🏿‍❤️‍💋‍👩🏾":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3fe","👩🏿‍❤️‍💋‍👩🏿":"1f469-1f3ff-200d-2764-fe0f-200d-1f48b-200d-1f469-1f3ff"} diff --git a/app/soapbox/features/emoji/emoji-mart-data-light.ts b/app/soapbox/features/emoji/emoji-mart-data-light.ts deleted file mode 100644 index efdce96fa..000000000 --- a/app/soapbox/features/emoji/emoji-mart-data-light.ts +++ /dev/null @@ -1,44 +0,0 @@ -// The output of this module is designed to mimic emoji-mart's -// "data" object, such that we can use it for a light version of emoji-mart's -// emojiIndex.search functionality. -import emojiCompressed from './emoji-compressed'; -import { unicodeToUnifiedName } from './unicode-to-unified-name'; - -const [ shortCodesToEmojiData, skins, categories, short_names ] = emojiCompressed; - -const emojis: Record = {}; - -// decompress -Object.keys(shortCodesToEmojiData).forEach((shortCode) => { - const [ - _filenameData, // eslint-disable-line @typescript-eslint/no-unused-vars - searchData, - ] = shortCodesToEmojiData[shortCode]; - const [ - native, - short_names, - search, - unified, - ] = searchData; - - emojis[shortCode] = { - native, - search, - short_names: [shortCode].concat(short_names), - unified: unified || unicodeToUnifiedName(native), - }; -}); - -export { - emojis, - skins, - categories, - short_names, -}; - -export default { - emojis, - skins, - categories, - short_names, -}; diff --git a/app/soapbox/features/emoji/emoji-mart-search-light.js b/app/soapbox/features/emoji/emoji-mart-search-light.js deleted file mode 100644 index fcf7a6e59..000000000 --- a/app/soapbox/features/emoji/emoji-mart-search-light.js +++ /dev/null @@ -1,183 +0,0 @@ -// This code is largely borrowed from: -// https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/emoji-index.js - -import data from './emoji-mart-data-light'; -import { getData, getSanitizedData, uniq, intersect } from './emoji-utils'; - -const originalPool = {}; -let index = {}; -const emojisList = {}; -const emoticonsList = {}; -let customEmojisList = []; - -for (const emoji in data.emojis) { - const emojiData = data.emojis[emoji]; - const { short_names, emoticons } = emojiData; - const id = short_names[0]; - - if (emoticons) { - emoticons.forEach(emoticon => { - if (emoticonsList[emoticon]) { - return; - } - - emoticonsList[emoticon] = id; - }); - } - - emojisList[id] = getSanitizedData(id); - originalPool[id] = emojiData; -} - -function clearCustomEmojis(pool) { - customEmojisList.forEach((emoji) => { - const emojiId = emoji.id || emoji.short_names[0]; - - delete pool[emojiId]; - delete emojisList[emojiId]; - }); -} - -export function addCustomToPool(custom, pool = originalPool) { - if (customEmojisList.length) clearCustomEmojis(pool); - - custom.forEach((emoji) => { - const emojiId = emoji.id || emoji.short_names[0]; - - if (emojiId && !pool[emojiId]) { - pool[emojiId] = getData(emoji); - emojisList[emojiId] = getSanitizedData(emoji); - } - }); - - customEmojisList = custom; - index = {}; -} - -export function search(value, { emojisToShowFilter, maxResults, include, exclude, custom } = {}) { - if (custom !== undefined) { - if (customEmojisList !== custom) - addCustomToPool(custom, originalPool); - } else { - custom = []; - } - - maxResults = maxResults || 75; - include = include || []; - exclude = exclude || []; - - let results = null, - pool = originalPool; - - if (value.length) { - if (value === '-' || value === '-1') { - return [emojisList['-1']]; - } - - let values = value.toLowerCase().split(/[\s|,|\-|_]+/), - allResults = []; - - if (values.length > 2) { - values = [values[0], values[1]]; - } - - if (include.length || exclude.length) { - pool = {}; - - data.categories.forEach(category => { - const isIncluded = include && include.length ? include.includes(category.name.toLowerCase()) : true; - const isExcluded = exclude && exclude.length ? exclude.includes(category.name.toLowerCase()) : false; - if (!isIncluded || isExcluded) { - return; - } - - category.emojis.forEach(emojiId => pool[emojiId] = data.emojis[emojiId]); - }); - - if (custom.length) { - const customIsIncluded = include && include.length ? include.includes('custom') : true; - const customIsExcluded = exclude && exclude.length ? exclude.includes('custom') : false; - if (customIsIncluded && !customIsExcluded) { - addCustomToPool(custom, pool); - } - } - } - - const searchValue = (value) => { - let aPool = pool, - aIndex = index, - length = 0; - - for (let charIndex = 0; charIndex < value.length; charIndex++) { - const char = value[charIndex]; - length++; - - aIndex[char] = aIndex[char] || {}; - aIndex = aIndex[char]; - - if (!aIndex.results) { - const scores = {}; - - aIndex.results = []; - aIndex.pool = {}; - - for (const id in aPool) { - const emoji = aPool[id], - { search } = emoji, - sub = value.substr(0, length), - subIndex = search.indexOf(sub); - - if (subIndex !== -1) { - let score = subIndex + 1; - if (sub === id) score = 0; - - aIndex.results.push(emojisList[id]); - aIndex.pool[id] = emoji; - - scores[id] = score; - } - } - - aIndex.results.sort((a, b) => { - const aScore = scores[a.id], - bScore = scores[b.id]; - - return aScore - bScore; - }); - } - - aPool = aIndex.pool; - } - - return aIndex.results; - }; - - if (values.length > 1) { - results = searchValue(value); - } else { - results = []; - } - - allResults = values.map(searchValue).filter(a => a); - - if (allResults.length > 1) { - allResults = intersect.apply(null, allResults); - } else if (allResults.length) { - allResults = allResults[0]; - } - - results = uniq(results.concat(allResults)); - } - - if (results) { - if (emojisToShowFilter) { - results = results.filter((result) => emojisToShowFilter(data.emojis[result.id])); - } - - if (results && results.length > maxResults) { - results = results.slice(0, maxResults); - } - } - - return results; -} diff --git a/app/soapbox/features/emoji/emoji-picker.ts b/app/soapbox/features/emoji/emoji-picker.ts deleted file mode 100644 index f4dd98a51..000000000 --- a/app/soapbox/features/emoji/emoji-picker.ts +++ /dev/null @@ -1,9 +0,0 @@ -// @ts-ignore no types -import Emoji from 'emoji-mart/dist-es/components/emoji/emoji'; -// @ts-ignore no types -import Picker from 'emoji-mart/dist-es/components/picker/picker'; - -export { - Picker, - Emoji, -}; diff --git a/app/soapbox/features/emoji/emoji-unicode-mapping-light.js b/app/soapbox/features/emoji/emoji-unicode-mapping-light.js deleted file mode 100644 index aa1233460..000000000 --- a/app/soapbox/features/emoji/emoji-unicode-mapping-light.js +++ /dev/null @@ -1,32 +0,0 @@ -// A mapping of unicode strings to an object containing the filename -// (i.e. the svg filename) and a shortCode intended to be shown -// as a "title" attribute in an HTML element (aka tooltip). - -const [ - shortCodesToEmojiData, - skins, // eslint-disable-line @typescript-eslint/no-unused-vars - categories, // eslint-disable-line @typescript-eslint/no-unused-vars - short_names, // eslint-disable-line @typescript-eslint/no-unused-vars - emojisWithoutShortCodes, -] = require('./emoji-compressed'); -const { unicodeToFilename } = require('./unicode-to-filename'); - -// decompress -const unicodeMapping = {}; - -function processEmojiMapData(emojiMapData, shortCode) { - const [ native, filename ] = emojiMapData; - - unicodeMapping[native] = { - shortCode, - filename: filename || unicodeToFilename(native), - }; -} - -Object.keys(shortCodesToEmojiData).forEach((shortCode) => { - const [ filenameData ] = shortCodesToEmojiData[shortCode]; - filenameData.forEach(emojiMapData => processEmojiMapData(emojiMapData, shortCode)); -}); -emojisWithoutShortCodes.forEach(emojiMapData => processEmojiMapData(emojiMapData)); - -module.exports = unicodeMapping; diff --git a/app/soapbox/features/emoji/emoji-utils.js b/app/soapbox/features/emoji/emoji-utils.js deleted file mode 100644 index 43fdfa89c..000000000 --- a/app/soapbox/features/emoji/emoji-utils.js +++ /dev/null @@ -1,253 +0,0 @@ -// This code is largely borrowed from: -// https://github.com/missive/emoji-mart/blob/5f2ffcc/src/utils/index.js - -import data from './emoji-mart-data-light'; - -const buildSearch = (data) => { - const search = []; - - const addToSearch = (strings, split) => { - if (!strings) { - return; - } - - (Array.isArray(strings) ? strings : [strings]).forEach((string) => { - (split ? string.split(/[-|_|\s]+/) : [string]).forEach((s) => { - s = s.toLowerCase(); - - if (!search.includes(s)) { - search.push(s); - } - }); - }); - }; - - addToSearch(data.short_names, true); - addToSearch(data.name, true); - addToSearch(data.keywords, false); - addToSearch(data.emoticons, false); - - return search.join(','); -}; - -const _String = String; - -const stringFromCodePoint = _String.fromCodePoint || function() { - const MAX_SIZE = 0x4000; - const codeUnits = []; - let highSurrogate; - let lowSurrogate; - let index = -1; - const length = arguments.length; - if (!length) { - return ''; - } - let result = ''; - while (++index < length) { - let codePoint = Number(arguments[index]); - if ( - !isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity` - codePoint < 0 || // not a valid Unicode code point - codePoint > 0x10FFFF || // not a valid Unicode code point - Math.floor(codePoint) !== codePoint // not an integer - ) { - throw RangeError('Invalid code point: ' + codePoint); - } - if (codePoint <= 0xFFFF) { // BMP code point - codeUnits.push(codePoint); - } else { // Astral code point; split in surrogate halves - // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae - codePoint -= 0x10000; - highSurrogate = (codePoint >> 10) + 0xD800; - lowSurrogate = (codePoint % 0x400) + 0xDC00; - codeUnits.push(highSurrogate, lowSurrogate); - } - if (index + 1 === length || codeUnits.length > MAX_SIZE) { - result += String.fromCharCode.apply(null, codeUnits); - codeUnits.length = 0; - } - } - return result; -}; - -const _JSON = JSON; - -const COLONS_REGEX = /^(?:\:([^\:]+)\:)(?:\:skin-tone-(\d)\:)?$/; -const SKINS = [ - '1F3FA', '1F3FB', '1F3FC', - '1F3FD', '1F3FE', '1F3FF', -]; - -function unifiedToNative(unified) { - const unicodes = unified.split('-'), - codePoints = unicodes.map((u) => `0x${u}`); - - return stringFromCodePoint.apply(null, codePoints); -} - -function sanitize(emoji) { - const { name, short_names, skin_tone, skin_variations, emoticons, unified, custom, imageUrl } = emoji; - const id = emoji.id || short_names[0]; - const colons = `:${id}:`; - - if (custom) { - return { - id, - name, - colons, - emoticons, - custom, - imageUrl, - }; - } - - return { - id, - name, - colons: skin_tone ? `${colons}:skin-tone-${skin_tone}:` : colons, - emoticons, - unified: unified.toLowerCase(), - skin: skin_tone || (skin_variations ? 1 : null), - native: unifiedToNative(unified), - }; -} - -function getSanitizedData() { - return sanitize(getData(...arguments)); -} - -function getData(emoji, skin, set) { - let emojiData = {}; - - if (typeof emoji === 'string') { - const matches = emoji.match(COLONS_REGEX); - - if (matches) { - emoji = matches[1]; - - if (matches[2]) { - skin = parseInt(matches[2]); - } - } - - if (Object.prototype.hasOwnProperty.call(data.short_names, emoji)) { - emoji = data.short_names[emoji]; - } - - if (Object.prototype.hasOwnProperty.call(data.emojis, emoji)) { - emojiData = data.emojis[emoji]; - } - } else if (emoji.id) { - if (Object.prototype.hasOwnProperty.call(data.short_names, emoji.id)) { - emoji.id = data.short_names[emoji.id]; - } - - if (Object.prototype.hasOwnProperty.call(data.emojis, emoji.id)) { - emojiData = data.emojis[emoji.id]; - skin = skin || emoji.skin; - } - } - - if (!Object.keys(emojiData).length) { - emojiData = emoji; - emojiData.custom = true; - - if (!emojiData.search) { - emojiData.search = buildSearch(emoji); - } - } - - emojiData.emoticons = emojiData.emoticons || []; - emojiData.variations = emojiData.variations || []; - - if (emojiData.skin_variations && skin > 1 && set) { - emojiData = JSON.parse(_JSON.stringify(emojiData)); - - const skinKey = SKINS[skin - 1], - variationData = emojiData.skin_variations[skinKey]; - - if (!variationData.variations && emojiData.variations) { - delete emojiData.variations; - } - - if (variationData[`has_img_${set}`]) { - emojiData.skin_tone = skin; - - for (const k in variationData) { - const v = variationData[k]; - emojiData[k] = v; - } - } - } - - if (emojiData.variations && emojiData.variations.length) { - emojiData = JSON.parse(_JSON.stringify(emojiData)); - emojiData.unified = emojiData.variations.shift(); - } - - return emojiData; -} - -function uniq(arr) { - return arr.reduce((acc, item) => { - if (!acc.includes(item)) { - acc.push(item); - } - return acc; - }, []); -} - -function intersect(a, b) { - const uniqA = uniq(a); - const uniqB = uniq(b); - - return uniqA.filter(item => uniqB.includes(item)); -} - -function deepMerge(a, b) { - const o = {}; - - for (const key in a) { - const originalValue = a[key]; - let value = originalValue; - - if (Object.prototype.hasOwnProperty.call(b, key)) { - value = b[key]; - } - - if (typeof value === 'object') { - value = deepMerge(originalValue, value); - } - - o[key] = value; - } - - return o; -} - -// https://github.com/sonicdoe/measure-scrollbar -function measureScrollbar() { - const div = document.createElement('div'); - - div.style.width = '100px'; - div.style.height = '100px'; - div.style.overflow = 'scroll'; - div.style.position = 'absolute'; - div.style.top = '-9999px'; - - document.body.appendChild(div); - const scrollbarWidth = div.offsetWidth - div.clientWidth; - document.body.removeChild(div); - - return scrollbarWidth; -} - -export { - getData, - getSanitizedData, - uniq, - intersect, - deepMerge, - unifiedToNative, - measureScrollbar, -}; diff --git a/app/soapbox/features/emoji/emoji.js b/app/soapbox/features/emoji/emoji.js deleted file mode 100644 index e9b4caa35..000000000 --- a/app/soapbox/features/emoji/emoji.js +++ /dev/null @@ -1,132 +0,0 @@ -import Trie from 'substring-trie'; - -import { joinPublicPath } from 'soapbox/utils/static'; - -import unicodeMapping from './emoji-unicode-mapping-light'; - -const trie = new Trie(Object.keys(unicodeMapping)); - -const emojifyTextNode = (node, customEmojis, autoPlayGif = false) => { - let str = node.textContent; - - const fragment = new DocumentFragment(); - - for (;;) { - let match, i = 0; - - if (customEmojis === null) { - while (i < str.length && !(match = trie.search(str.slice(i)))) { - i += str.codePointAt(i) < 65536 ? 1 : 2; - } - } else { - while (i < str.length && str[i] !== ':' && !(match = trie.search(str.slice(i)))) { - i += str.codePointAt(i) < 65536 ? 1 : 2; - } - } - - let rend, replacement = null; - if (i === str.length) { - break; - } else if (str[i] === ':') { - // eslint-disable-next-line no-loop-func - if (!(() => { - rend = str.indexOf(':', i + 1) + 1; - if (!rend) return false; // no pair of ':' - const shortname = str.slice(i, rend); - // now got a replacee as ':shortname:' - // if you want additional emoji handler, add statements below which set replacement and return true. - if (shortname in customEmojis) { - const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url; - replacement = document.createElement('img'); - replacement.setAttribute('draggable', false); - replacement.setAttribute('class', 'emojione custom-emoji'); - replacement.setAttribute('alt', shortname); - replacement.setAttribute('title', shortname); - replacement.setAttribute('src', filename); - replacement.setAttribute('data-original', customEmojis[shortname].url); - replacement.setAttribute('data-static', customEmojis[shortname].static_url); - return true; - } - return false; - })()) rend = ++i; - } else { // matched to unicode emoji - const { filename, shortCode } = unicodeMapping[match]; - const title = shortCode ? `:${shortCode}:` : ''; - replacement = document.createElement('img'); - replacement.setAttribute('draggable', false); - replacement.setAttribute('class', 'emojione'); - replacement.setAttribute('alt', match); - replacement.setAttribute('title', title); - replacement.setAttribute('src', joinPublicPath(`packs/emoji/${filename}.svg`)); - rend = i + match.length; - // If the matched character was followed by VS15 (for selecting text presentation), skip it. - if (str.codePointAt(rend) === 65038) { - rend += 1; - } - } - - fragment.append(document.createTextNode(str.slice(0, i))); - if (replacement) { - fragment.append(replacement); - } - node.textContent = str.slice(0, i); - str = str.slice(rend); - } - - fragment.append(document.createTextNode(str)); - node.parentElement.replaceChild(fragment, node); -}; - -const emojifyNode = (node, customEmojis, autoPlayGif = false) => { - for (const child of node.childNodes) { - switch (child.nodeType) { - case Node.TEXT_NODE: - emojifyTextNode(child, customEmojis, autoPlayGif); - break; - case Node.ELEMENT_NODE: - if (!child.classList.contains('invisible')) - emojifyNode(child, customEmojis, autoPlayGif); - break; - } - } -}; - -const emojify = (str, customEmojis = {}, autoPlayGif = false) => { - const wrapper = document.createElement('div'); - wrapper.innerHTML = str; - - if (!Object.keys(customEmojis).length) - customEmojis = null; - - emojifyNode(wrapper, customEmojis, autoPlayGif); - - return wrapper.innerHTML; -}; - -export default emojify; - -export const buildCustomEmojis = (customEmojis, autoPlayGif = false) => { - const emojis = []; - - customEmojis.forEach(emoji => { - const shortcode = emoji.get('shortcode'); - const url = autoPlayGif ? emoji.get('url') : emoji.get('static_url'); - const name = shortcode.replace(':', ''); - - emojis.push({ - id: name, - name, - short_names: [name], - text: '', - emoticons: [], - keywords: [name], - imageUrl: url, - custom: true, - customCategory: emoji.get('category'), - }); - }); - - return emojis; -}; - -export const categoriesFromEmojis = customEmojis => customEmojis.reduce((set, emoji) => set.add(emoji.get('category') ? `custom-${emoji.get('category')}` : 'custom'), new Set(['custom'])); \ No newline at end of file diff --git a/app/soapbox/features/emoji/index.ts b/app/soapbox/features/emoji/index.ts new file mode 100644 index 000000000..8fa279dad --- /dev/null +++ b/app/soapbox/features/emoji/index.ts @@ -0,0 +1,228 @@ +import split from 'graphemesplit'; + +import unicodeMapping from './mapping'; + +import type { Emoji as EmojiMart, CustomEmoji as EmojiMartCustom } from 'soapbox/features/emoji/data'; + +/* + * TODO: Consolate emoji object types + * + * There are five different emoji objects currently + * - emoji-mart's "onPickEmoji" handler + * - emoji-mart's custom emoji types + * - an Emoji type that is either NativeEmoji or CustomEmoji + * - a type inside redux's `store.custom_emoji` immutablejs + * + * there needs to be one type for the picker handler callback + * and one type for the emoji-mart data + * and one type that is used everywhere that the above two are converted into + */ + +export interface CustomEmoji { + id: string + colons: string + custom: true + imageUrl: string +} + +export interface NativeEmoji { + id: string + colons: string + custom?: boolean + unified: string + native: string +} + +export type Emoji = CustomEmoji | NativeEmoji; + +export function isCustomEmoji(emoji: Emoji): emoji is CustomEmoji { + return (emoji as CustomEmoji).imageUrl !== undefined; +} + +export function isNativeEmoji(emoji: Emoji): emoji is NativeEmoji { + return (emoji as NativeEmoji).native !== undefined; +} + +const isAlphaNumeric = (c: string) => { + const code = c.charCodeAt(0); + + if (!(code > 47 && code < 58) && // numeric (0-9) + !(code > 64 && code < 91) && // upper alpha (A-Z) + !(code > 96 && code < 123)) { // lower alpha (a-z) + return false; + } else { + return true; + } +}; + +const validEmojiChar = (c: string) => { + return isAlphaNumeric(c) + || c === '_' + || c === '-' + || c === '.'; +}; + +const convertCustom = (shortname: string, filename: string) => { + return `${shortname}`; +}; + +const convertUnicode = (c: string) => { + const { unified, shortcode } = unicodeMapping[c]; + + return `${c}`; +}; + +const convertEmoji = (str: string, customEmojis: any) => { + if (str.length < 3) return str; + if (str in customEmojis) { + const emoji = customEmojis[str]; + const filename = emoji.static_url; + + if (filename?.length > 0) { + return convertCustom(str, filename); + } + } + + return str; +}; + +export const emojifyText = (str: string, customEmojis = {}) => { + let buf = ''; + let stack = ''; + let open = false; + + const clearStack = () => { + buf += stack; + open = false; + stack = ''; + }; + + for (let c of split(str)) { + // convert FE0E selector to FE0F so it can be found in unimap + if (c.codePointAt(c.length - 1) === 65038) { + c = c.slice(0, -1) + String.fromCodePoint(65039); + } + + // unqualified emojis aren't in emoji-mart's mappings so we just add FEOF + const unqualified = c + String.fromCodePoint(65039); + + if (c in unicodeMapping) { + if (open) { // unicode emoji inside colon + clearStack(); + } + + buf += convertUnicode(c); + } else if (unqualified in unicodeMapping) { + if (open) { // unicode emoji inside colon + clearStack(); + } + + buf += convertUnicode(unqualified); + } else if (c === ':') { + stack += ':'; + + // we see another : we convert it and clear the stack buffer + if (open) { + buf += convertEmoji(stack, customEmojis); + stack = ''; + } + + open = !open; + } else { + if (open) { + stack += c; + + // if the stack is non-null and we see invalid chars it's a string not emoji + // so we push it to the return result and clear it + if (!validEmojiChar(c)) { + clearStack(); + } + } else { + buf += c; + } + } + } + + // never found a closing colon so it's just a raw string + if (open) { + buf += stack; + } + + return buf; +}; + +export const parseHTML = (str: string): { text: boolean, data: string }[] => { + const tokens = []; + let buf = ''; + let stack = ''; + let open = false; + + for (const c of str) { + if (c === '<') { + if (open) { + tokens.push({ text: true, data: stack }); + stack = '<'; + } else { + tokens.push({ text: true, data: buf }); + stack = '<'; + open = true; + } + } else if (c === '>') { + if (open) { + open = false; + tokens.push({ text: false, data: stack + '>' }); + stack = ''; + buf = ''; + } else { + buf += '>'; + } + + } else { + if (open) { + stack += c; + } else { + buf += c; + } + } + } + + if (open) { + tokens.push({ text: true, data: buf + stack }); + } else if (buf !== '') { + tokens.push({ text: true, data: buf }); + } + + return tokens; +}; + +const emojify = (str: string, customEmojis = {}) => { + return parseHTML(str) + .map(({ text, data }) => { + if (!text) return data; + if (data.length === 0 || data === ' ') return data; + + return emojifyText(data, customEmojis); + }) + .join(''); +}; + +export default emojify; + +export const buildCustomEmojis = (customEmojis: any) => { + const emojis: EmojiMart[] = []; + + customEmojis.forEach((emoji: any) => { + const shortcode = emoji.get('shortcode'); + const url = emoji.get('static_url'); + const name = shortcode.replace(':', ''); + + emojis.push({ + id: name, + name, + keywords: [name], + skins: [{ src: url }], + }); + }); + + return emojis; +}; diff --git a/app/soapbox/features/emoji/mapping.ts b/app/soapbox/features/emoji/mapping.ts new file mode 100644 index 000000000..5259c2dc2 --- /dev/null +++ b/app/soapbox/features/emoji/mapping.ts @@ -0,0 +1,111 @@ +import data, { EmojiData } from './data'; + +const stripLeadingZeros = /^0+/; + +function replaceAll(str: string, find: string, replace: string) { + return str.replace(new RegExp(find, 'g'), replace); +} + +interface UnicodeMap { + [s: string]: { + unified: string + shortcode: string + } +} + +/* + * Twemoji strips their hex codes from unicode codepoints to make it look "pretty" + * - leading 0s are removed + * - fe0f is removed unless it has 200d + * - fe0f is NOT removed for 1f441-fe0f-200d-1f5e8-fe0f even though it has a 200d + * + * this is all wrong + */ + +const blacklist = { + '1f441-fe0f-200d-1f5e8-fe0f': true, +}; + +const tweaks = { + '#⃣': ['23-20e3', 'hash'], + '*⃣': ['2a-20e3', 'keycap_star'], + '0⃣': ['30-20e3', 'zero'], + '1⃣': ['31-20e3', 'one'], + '2⃣': ['32-20e3', 'two'], + '3⃣': ['33-20e3', 'three'], + '4⃣': ['34-20e3', 'four'], + '5⃣': ['35-20e3', 'five'], + '6⃣': ['36-20e3', 'six'], + '7⃣': ['37-20e3', 'seven'], + '8⃣': ['38-20e3', 'eight'], + '9⃣': ['39-20e3', 'nine'], + '❤‍🔥': ['2764-fe0f-200d-1f525', 'heart_on_fire'], + '❤‍🩹': ['2764-fe0f-200d-1fa79', 'mending_heart'], + '👁‍🗨️': ['1f441-fe0f-200d-1f5e8-fe0f', 'eye-in-speech-bubble'], + '👁️‍🗨': ['1f441-fe0f-200d-1f5e8-fe0f', 'eye-in-speech-bubble'], + '👁‍🗨': ['1f441-fe0f-200d-1f5e8-fe0f', 'eye-in-speech-bubble'], + '🕵‍♂️': ['1f575-fe0f-200d-2642-fe0f', 'male-detective'], + '🕵️‍♂': ['1f575-fe0f-200d-2642-fe0f', 'male-detective'], + '🕵‍♂': ['1f575-fe0f-200d-2642-fe0f', 'male-detective'], + '🕵‍♀️': ['1f575-fe0f-200d-2640-fe0f', 'female-detective'], + '🕵️‍♀': ['1f575-fe0f-200d-2640-fe0f', 'female-detective'], + '🕵‍♀': ['1f575-fe0f-200d-2640-fe0f', 'female-detective'], + '🏌‍♂️': ['1f3cc-fe0f-200d-2642-fe0f', 'man-golfing'], + '🏌️‍♂': ['1f3cc-fe0f-200d-2642-fe0f', 'man-golfing'], + '🏌‍♂': ['1f3cc-fe0f-200d-2642-fe0f', 'man-golfing'], + '🏌‍♀️': ['1f3cc-fe0f-200d-2640-fe0f', 'woman-golfing'], + '🏌️‍♀': ['1f3cc-fe0f-200d-2640-fe0f', 'woman-golfing'], + '🏌‍♀': ['1f3cc-fe0f-200d-2640-fe0f', 'woman-golfing'], + '⛹‍♂️': ['26f9-fe0f-200d-2642-fe0f', 'man-bouncing-ball'], + '⛹️‍♂': ['26f9-fe0f-200d-2642-fe0f', 'man-bouncing-ball'], + '⛹‍♂': ['26f9-fe0f-200d-2642-fe0f', 'man-bouncing-ball'], + '⛹‍♀️': ['26f9-fe0f-200d-2640-fe0f', 'woman-bouncing-ball'], + '⛹️‍♀': ['26f9-fe0f-200d-2640-fe0f', 'woman-bouncing-ball'], + '⛹‍♀': ['26f9-fe0f-200d-2640-fe0f', 'woman-bouncing-ball'], + '🏋‍♂️': ['1f3cb-fe0f-200d-2642-fe0f', 'man-lifting-weights'], + '🏋️‍♂': ['1f3cb-fe0f-200d-2642-fe0f', 'man-lifting-weights'], + '🏋‍♂': ['1f3cb-fe0f-200d-2642-fe0f', 'man-lifting-weights'], + '🏋‍♀️': ['1f3cb-fe0f-200d-2640-fe0f', 'woman-lifting-weights'], + '🏋️‍♀': ['1f3cb-fe0f-200d-2640-fe0f', 'woman-lifting-weights'], + '🏋‍♀': ['1f3cb-fe0f-200d-2640-fe0f', 'woman-lifting-weights'], + '🏳‍🌈': ['1f3f3-fe0f-200d-1f308', 'rainbow_flag'], + '🏳‍⚧️': ['1f3f3-fe0f-200d-26a7-fe0f', 'transgender_flag'], + '🏳️‍⚧': ['1f3f3-fe0f-200d-26a7-fe0f', 'transgender_flag'], + '🏳‍⚧': ['1f3f3-fe0f-200d-26a7-fe0f', 'transgender_flag'], +}; + +const stripcodes = (unified: string, native: string) => { + const stripped = unified.replace(stripLeadingZeros, ''); + + if (unified.includes('200d') && !(unified in blacklist)) { + return stripped; + } else { + return replaceAll(stripped, '-fe0f', ''); + } +}; + +export const generateMappings = (data: EmojiData): UnicodeMap => { + const result: UnicodeMap = {}; + const emojis = Object.values(data.emojis ?? {}); + + for (const value of emojis) { + for (const item of value.skins) { + const { unified, native } = item; + const stripped = stripcodes(unified, native); + + result[native] = { unified: stripped, shortcode: value.id }; + } + } + + for (const [native, [unified, shortcode]] of Object.entries(tweaks)) { + const stripped = stripcodes(unified, native); + + result[native] = { unified: stripped, shortcode }; + } + + return result; +}; + +const unicodeMapping = generateMappings(data); + +export default unicodeMapping; diff --git a/app/soapbox/features/emoji/search.ts b/app/soapbox/features/emoji/search.ts new file mode 100644 index 000000000..119636a29 --- /dev/null +++ b/app/soapbox/features/emoji/search.ts @@ -0,0 +1,65 @@ +import { Index } from 'flexsearch'; + +import data from './data'; + +import type { Emoji } from './index'; +// import type { Emoji as EmojiMart, CustomEmoji } from 'emoji-mart'; + +// @ts-ignore +const index = new Index({ + tokenize: 'full', + optimize: true, + context: true, +}); + +for (const [key, emoji] of Object.entries(data.emojis)) { + index.add('n' + key, emoji.name); +} + +export interface searchOptions { + maxResults?: number + custom?: any +} + +export const addCustomToPool = (customEmojis: any[]) => { + // @ts-ignore + for (const key in index.register) { + if (key[0] === 'c') { + index.remove(key); // remove old custom emojis + } + } + + let i = 0; + + for (const emoji of customEmojis) { + index.add('c' + i++, emoji.id); + } +}; + +// we can share an index by prefixing custom emojis with 'c' and native with 'n' +const search = (str: string, { maxResults = 5, custom }: searchOptions = {}, custom_emojis?: any): Emoji[] => { + return index.search(str, maxResults) + .flatMap((id: string) => { + if (id[0] === 'c') { + const { shortcode, static_url } = custom_emojis.get((id as string).slice(1)).toJS(); + + return { + id: shortcode, + colons: ':' + shortcode + ':', + custom: true, + imageUrl: static_url, + }; + } + + const { skins } = data.emojis[(id as string).slice(1)]; + + return { + id: (id as string).slice(1), + colons: ':' + id.slice(1) + ':', + unified: skins[0].unified, + native: skins[0].native, + }; + }); +}; + +export default search; diff --git a/app/soapbox/features/emoji/unicode-to-filename.js b/app/soapbox/features/emoji/unicode-to-filename.js deleted file mode 100644 index c75c4cd7d..000000000 --- a/app/soapbox/features/emoji/unicode-to-filename.js +++ /dev/null @@ -1,26 +0,0 @@ -// taken from: -// https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866 -exports.unicodeToFilename = (str) => { - let result = ''; - let charCode = 0; - let p = 0; - let i = 0; - while (i < str.length) { - charCode = str.charCodeAt(i++); - if (p) { - if (result.length > 0) { - result += '-'; - } - result += (0x10000 + ((p - 0xD800) << 10) + (charCode - 0xDC00)).toString(16); - p = 0; - } else if (0xD800 <= charCode && charCode <= 0xDBFF) { - p = charCode; - } else { - if (result.length > 0) { - result += '-'; - } - result += charCode.toString(16); - } - } - return result; -}; diff --git a/app/soapbox/features/emoji/unicode-to-unified-name.js b/app/soapbox/features/emoji/unicode-to-unified-name.js deleted file mode 100644 index d29550f12..000000000 --- a/app/soapbox/features/emoji/unicode-to-unified-name.js +++ /dev/null @@ -1,21 +0,0 @@ -function padLeft(str, num) { - while (str.length < num) { - str = '0' + str; - } - - return str; -} - -exports.unicodeToUnifiedName = (str) => { - let output = ''; - - for (let i = 0; i < str.length; i += 2) { - if (i > 0) { - output += '-'; - } - - output += padLeft(str.codePointAt(i).toString(16).toUpperCase(), 4); - } - - return output; -}; diff --git a/app/soapbox/features/group/components/__tests__/group-action-button.test.tsx b/app/soapbox/features/group/components/__tests__/group-action-button.test.tsx new file mode 100644 index 000000000..eb2cf670b --- /dev/null +++ b/app/soapbox/features/group/components/__tests__/group-action-button.test.tsx @@ -0,0 +1,130 @@ +import React from 'react'; + +import { render, screen } from 'soapbox/jest/test-helpers'; +import { normalizeGroup, normalizeGroupRelationship } from 'soapbox/normalizers'; +import { Group } from 'soapbox/types/entities'; + +import GroupActionButton from '../group-action-button'; + +let group: Group; + +describe('', () => { + describe('with no group relationship', () => { + beforeEach(() => { + group = normalizeGroup({ + relationship: null, + }); + }); + + describe('with a private group', () => { + beforeEach(() => { + group = group.set('locked', true); + }); + + it('should render the Request Access button', () => { + render(); + + expect(screen.getByRole('button')).toHaveTextContent('Request Access'); + }); + }); + + describe('with a public group', () => { + beforeEach(() => { + group = group.set('locked', false); + }); + + it('should render the Join Group button', () => { + render(); + + expect(screen.getByRole('button')).toHaveTextContent('Join Group'); + }); + }); + }); + + describe('with no group relationship member', () => { + beforeEach(() => { + group = normalizeGroup({ + relationship: normalizeGroupRelationship({ + member: null, + }), + }); + }); + + describe('with a private group', () => { + beforeEach(() => { + group = group.set('locked', true); + }); + + it('should render the Request Access button', () => { + render(); + + expect(screen.getByRole('button')).toHaveTextContent('Request Access'); + }); + }); + + describe('with a public group', () => { + beforeEach(() => { + group = group.set('locked', false); + }); + + it('should render the Join Group button', () => { + render(); + + expect(screen.getByRole('button')).toHaveTextContent('Join Group'); + }); + }); + }); + + describe('when the user has requested to join', () => { + beforeEach(() => { + group = normalizeGroup({ + relationship: normalizeGroupRelationship({ + requested: true, + member: true, + }), + }); + }); + + it('should render the Cancel Request button', () => { + render(); + + expect(screen.getByRole('button')).toHaveTextContent('Cancel Request'); + }); + }); + + describe('when the user is an Admin', () => { + beforeEach(() => { + group = normalizeGroup({ + relationship: normalizeGroupRelationship({ + requested: false, + member: true, + role: 'admin', + }), + }); + }); + + it('should render the Manage Group button', () => { + render(); + + expect(screen.getByRole('button')).toHaveTextContent('Manage Group'); + }); + }); + + describe('when the user is just a member', () => { + beforeEach(() => { + group = normalizeGroup({ + relationship: normalizeGroupRelationship({ + requested: false, + member: true, + role: 'user', + }), + }); + }); + + it('should render the Leave Group button', () => { + render(); + + expect(screen.getByRole('button')).toHaveTextContent('Leave Group'); + }); + }); +}); \ No newline at end of file diff --git a/app/soapbox/features/group/components/__tests__/group-member-count.test.tsx b/app/soapbox/features/group/components/__tests__/group-member-count.test.tsx new file mode 100644 index 000000000..86e9baac8 --- /dev/null +++ b/app/soapbox/features/group/components/__tests__/group-member-count.test.tsx @@ -0,0 +1,69 @@ +import React from 'react'; + +import { render, screen } from 'soapbox/jest/test-helpers'; +import { normalizeGroup } from 'soapbox/normalizers'; +import { Group } from 'soapbox/types/entities'; + +import GroupMemberCount from '../group-member-count'; + +let group: Group; + +describe('', () => { + describe('without support for "members_count"', () => { + beforeEach(() => { + group = normalizeGroup({ + members_count: undefined, + }); + }); + + it('should return null', () => { + render(); + + expect(screen.queryAllByTestId('group-member-count')).toHaveLength(0); + }); + }); + + describe('with support for "members_count"', () => { + describe('with 1 member', () => { + beforeEach(() => { + group = normalizeGroup({ + members_count: 1, + }); + }); + + it('should render correctly', () => { + render(); + + expect(screen.getByTestId('group-member-count').textContent).toEqual('1 member'); + }); + }); + + describe('with 2 members', () => { + beforeEach(() => { + group = normalizeGroup({ + members_count: 2, + }); + }); + + it('should render correctly', () => { + render(); + + expect(screen.getByTestId('group-member-count').textContent).toEqual('2 members'); + }); + }); + + describe('with 1000 members', () => { + beforeEach(() => { + group = normalizeGroup({ + members_count: 1000, + }); + }); + + it('should render correctly', () => { + render(); + + expect(screen.getByTestId('group-member-count').textContent).toEqual('1k members'); + }); + }); + }); +}); \ No newline at end of file diff --git a/app/soapbox/features/group/components/__tests__/group-privacy.test.tsx b/app/soapbox/features/group/components/__tests__/group-privacy.test.tsx new file mode 100644 index 000000000..72e4454e7 --- /dev/null +++ b/app/soapbox/features/group/components/__tests__/group-privacy.test.tsx @@ -0,0 +1,39 @@ +import React from 'react'; + +import { render, screen } from 'soapbox/jest/test-helpers'; +import { normalizeGroup } from 'soapbox/normalizers'; +import { Group } from 'soapbox/types/entities'; + +import GroupPrivacy from '../group-privacy'; + +let group: Group; + +describe('', () => { + describe('with a Private group', () => { + beforeEach(() => { + group = normalizeGroup({ + locked: true, + }); + }); + + it('should render the correct text', () => { + render(); + + expect(screen.getByTestId('group-privacy')).toHaveTextContent('Private'); + }); + }); + + describe('with a Public group', () => { + beforeEach(() => { + group = normalizeGroup({ + locked: false, + }); + }); + + it('should render the correct text', () => { + render(); + + expect(screen.getByTestId('group-privacy')).toHaveTextContent('Public'); + }); + }); +}); \ No newline at end of file diff --git a/app/soapbox/features/group/components/group-action-button.tsx b/app/soapbox/features/group/components/group-action-button.tsx new file mode 100644 index 000000000..53f27f709 --- /dev/null +++ b/app/soapbox/features/group/components/group-action-button.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import { joinGroup, leaveGroup } from 'soapbox/actions/groups'; +import { openModal } from 'soapbox/actions/modals'; +import { Button } from 'soapbox/components/ui'; +import { useAppDispatch } from 'soapbox/hooks'; +import { Group } from 'soapbox/types/entities'; + +interface IGroupActionButton { + group: Group +} + +const messages = defineMessages({ + confirmationHeading: { id: 'confirmations.leave_group.heading', defaultMessage: 'Leave group' }, + confirmationMessage: { id: 'confirmations.leave_group.message', defaultMessage: 'You are about to leave the group. Do you want to continue?' }, + confirmationConfirm: { id: 'confirmations.leave_group.confirm', defaultMessage: 'Leave' }, +}); + +const GroupActionButton = ({ group }: IGroupActionButton) => { + const dispatch = useAppDispatch(); + const intl = useIntl(); + + const isNonMember = !group.relationship || !group.relationship.member; + const isRequested = group.relationship?.requested; + const isAdmin = group.relationship?.role === 'admin'; + + const onJoinGroup = () => dispatch(joinGroup(group.id)); + + const onLeaveGroup = () => + dispatch(openModal('CONFIRM', { + heading: intl.formatMessage(messages.confirmationHeading), + message: intl.formatMessage(messages.confirmationMessage), + confirm: intl.formatMessage(messages.confirmationConfirm), + onConfirm: () => dispatch(leaveGroup(group.id)), + })); + + if (isNonMember) { + return ( + + ); + } + + if (isRequested) { + return ( + + ); + } + + if (isAdmin) { + return ( + + ); + } + + return ( + + ); +}; + +export default GroupActionButton; \ No newline at end of file diff --git a/app/soapbox/features/group/components/group-header.tsx b/app/soapbox/features/group/components/group-header.tsx index e341a28f2..7b532ddcc 100644 --- a/app/soapbox/features/group/components/group-header.tsx +++ b/app/soapbox/features/group/components/group-header.tsx @@ -1,22 +1,23 @@ import { List as ImmutableList } from 'immutable'; import React from 'react'; -import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; +import { defineMessages, useIntl } from 'react-intl'; -import { joinGroup, leaveGroup } from 'soapbox/actions/groups'; import { openModal } from 'soapbox/actions/modals'; import StillImage from 'soapbox/components/still-image'; -import { Avatar, Button, HStack, Icon, Stack, Text } from 'soapbox/components/ui'; +import { Avatar, HStack, Stack, Text } from 'soapbox/components/ui'; import { useAppDispatch } from 'soapbox/hooks'; import { normalizeAttachment } from 'soapbox/normalizers'; import { isDefaultHeader } from 'soapbox/utils/accounts'; +import GroupActionButton from './group-action-button'; +import GroupMemberCount from './group-member-count'; +import GroupPrivacy from './group-privacy'; +import GroupRelationship from './group-relationship'; + import type { Group } from 'soapbox/types/entities'; const messages = defineMessages({ header: { id: 'group.header.alt', defaultMessage: 'Group header' }, - confirmationHeading: { id: 'confirmations.leave_group.heading', defaultMessage: 'Leave group' }, - confirmationMessage: { id: 'confirmations.leave_group.message', defaultMessage: 'You are about to leave the group. Do you want to continue?' }, - confirmationConfirm: { id: 'confirmations.leave_group.confirm', defaultMessage: 'Leave' }, }); interface IGroupHeader { @@ -47,16 +48,6 @@ const GroupHeader: React.FC = ({ group }) => { ); } - const onJoinGroup = () => dispatch(joinGroup(group.id)); - - const onLeaveGroup = () => - dispatch(openModal('CONFIRM', { - heading: intl.formatMessage(messages.confirmationHeading), - message: intl.formatMessage(messages.confirmationMessage), - confirm: intl.formatMessage(messages.confirmationConfirm), - onConfirm: () => dispatch(leaveGroup(group.id)), - })); - const onAvatarClick = () => { const avatar = normalizeAttachment({ type: 'image', @@ -95,6 +86,7 @@ const GroupHeader: React.FC = ({ group }) => { ); @@ -110,93 +102,40 @@ const GroupHeader: React.FC = ({ group }) => { return header; }; - const makeActionButton = () => { - if (!group.relationship || !group.relationship.member) { - return ( - - ); - } - - if (group.relationship.requested) { - return ( - - ); - } - - if (group.relationship?.role === 'admin') { - return ( - - ); - } - - return ( - - ); - }; - - const actionButton = makeActionButton(); - return (
-
- {renderHeader()} -
+ {renderHeader()} +
- - - - {group.relationship?.role === 'admin' ? ( - - - - - ) : group.relationship?.role === 'moderator' && ( - - - - - )} - {group.locked ? ( - - - - - ) : ( - - - - - )} - - - {actionButton} + + + + + + + + + + + + + +
); diff --git a/app/soapbox/features/group/components/group-member-count.tsx b/app/soapbox/features/group/components/group-member-count.tsx new file mode 100644 index 000000000..e4dd33e54 --- /dev/null +++ b/app/soapbox/features/group/components/group-member-count.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { Text } from 'soapbox/components/ui'; +import { Group } from 'soapbox/types/entities'; +import { shortNumberFormat } from 'soapbox/utils/numbers'; + +interface IGroupMemberCount { + group: Group +} + +const GroupMemberCount = ({ group }: IGroupMemberCount) => { + if (typeof group.members_count === 'undefined') { + return null; + } + + return ( + + {shortNumberFormat(group.members_count)} + {' '} + + + ); +}; + +export default GroupMemberCount; \ No newline at end of file diff --git a/app/soapbox/features/group/components/group-privacy.tsx b/app/soapbox/features/group/components/group-privacy.tsx new file mode 100644 index 000000000..fdbbe2977 --- /dev/null +++ b/app/soapbox/features/group/components/group-privacy.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { HStack, Icon, Text } from 'soapbox/components/ui'; +import { Group } from 'soapbox/types/entities'; + +interface IGroupPolicy { + group: Group +} + +const GroupPrivacy = ({ group }: IGroupPolicy) => ( + + + + + {group.locked ? ( + + ) : ( + + )} + + +); + +export default GroupPrivacy; \ No newline at end of file diff --git a/app/soapbox/features/group/components/group-relationship.tsx b/app/soapbox/features/group/components/group-relationship.tsx new file mode 100644 index 000000000..6b79ecda5 --- /dev/null +++ b/app/soapbox/features/group/components/group-relationship.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { HStack, Icon, Text } from 'soapbox/components/ui'; +import { Group } from 'soapbox/types/entities'; + +interface IGroupRelationship { + group: Group +} + +const GroupRelationship = ({ group }: IGroupRelationship) => { + const isAdmin = group.relationship?.role === 'admin'; + const isModerator = group.relationship?.role === 'moderator'; + + if (!isAdmin || !isModerator) { + return null; + } + + return ( + + + + + {isAdmin + ? + : } + + + ); +}; + +export default GroupRelationship; \ No newline at end of file diff --git a/app/soapbox/features/group/group-timeline.tsx b/app/soapbox/features/group/group-timeline.tsx index f4cf2f574..f80343c1e 100644 --- a/app/soapbox/features/group/group-timeline.tsx +++ b/app/soapbox/features/group/group-timeline.tsx @@ -3,7 +3,6 @@ import { FormattedMessage } from 'react-intl'; import { Link } from 'react-router-dom'; import { groupCompose } from 'soapbox/actions/compose'; -import { fetchGroup } from 'soapbox/actions/groups'; import { connectGroupStream } from 'soapbox/actions/streaming'; import { expandGroupTimeline } from 'soapbox/actions/timelines'; import { Avatar, HStack, Stack } from 'soapbox/components/ui'; @@ -31,7 +30,6 @@ const GroupTimeline: React.FC = (props) => { }; useEffect(() => { - dispatch(fetchGroup(groupId)); dispatch(expandGroupTimeline(groupId)); dispatch(groupCompose(`group:${groupId}`, groupId)); diff --git a/app/soapbox/features/groups/components/discover/group.tsx b/app/soapbox/features/groups/components/discover/group.tsx new file mode 100644 index 000000000..a596f95f2 --- /dev/null +++ b/app/soapbox/features/groups/components/discover/group.tsx @@ -0,0 +1,81 @@ +import React, { forwardRef } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router-dom'; + +import { Avatar, Button, HStack, Stack, Text } from 'soapbox/components/ui'; +import GroupMemberCount from 'soapbox/features/group/components/group-member-count'; +import GroupPrivacy from 'soapbox/features/group/components/group-privacy'; +import { Group as GroupEntity } from 'soapbox/types/entities'; + +interface IGroup { + group: GroupEntity + width?: number +} + +const Group = forwardRef((props: IGroup, ref: React.ForwardedRef) => { + const { group, width = 'auto' } = props; + + return ( +
+ + + {group.header && ( + Group cover + )} + + + + + + + + + + + + + + + +
+ + + + +
+ ); +}); + +export default Group; \ No newline at end of file diff --git a/app/soapbox/features/groups/components/discover/popular-groups.tsx b/app/soapbox/features/groups/components/discover/popular-groups.tsx new file mode 100644 index 000000000..8ac7d7387 --- /dev/null +++ b/app/soapbox/features/groups/components/discover/popular-groups.tsx @@ -0,0 +1,54 @@ +import React, { useState } from 'react'; + +import { Carousel, Stack, Text } from 'soapbox/components/ui'; +import PlaceholderGroupDiscover from 'soapbox/features/placeholder/components/placeholder-group-discover'; +import { usePopularGroups } from 'soapbox/queries/groups'; + +import Group from './group'; + +const PopularGroups = () => { + const { groups, isFetching } = usePopularGroups(); + + const [groupCover, setGroupCover] = useState(null); + + return ( + + + Popular Groups + + + + {({ width }: { width: number }) => ( + <> + {isFetching ? ( + new Array(20).fill(0).map((_, idx) => ( +
+ +
+ )) + ) : ( + groups.map((group) => ( + + )) + )} + + )} +
+
+ ); +}; + +export default PopularGroups; \ No newline at end of file diff --git a/app/soapbox/features/groups/components/discover/search/__tests__/recent-searches.test.tsx b/app/soapbox/features/groups/components/discover/search/__tests__/recent-searches.test.tsx new file mode 100644 index 000000000..8c0e54262 --- /dev/null +++ b/app/soapbox/features/groups/components/discover/search/__tests__/recent-searches.test.tsx @@ -0,0 +1,79 @@ +import userEvent from '@testing-library/user-event'; +import { Map as ImmutableMap } from 'immutable'; +import React from 'react'; +import { VirtuosoMockContext } from 'react-virtuoso'; + +import { render, screen, waitFor } from 'soapbox/jest/test-helpers'; +import { normalizeAccount } from 'soapbox/normalizers'; +import { groupSearchHistory } from 'soapbox/settings'; +import { clearRecentGroupSearches, saveGroupSearch } from 'soapbox/utils/groups'; + +import RecentSearches from '../recent-searches'; + +const userId = '1'; +const store = { + me: userId, + accounts: ImmutableMap({ + [userId]: normalizeAccount({ + id: userId, + acct: 'justin-username', + display_name: 'Justin L', + avatar: 'test.jpg', + chats_onboarded: false, + }), + }), +}; + +const renderApp = (children: React.ReactNode) => ( + render( + + {children} + , + undefined, + store, + ) +); + +describe('', () => { + describe('with recent searches', () => { + beforeEach(() => { + saveGroupSearch(userId, 'foobar'); + }); + + afterEach(() => { + clearRecentGroupSearches(userId); + }); + + it('should render the recent searches', async () => { + renderApp(); + + await waitFor(() => { + expect(screen.getByTestId('recent-search')).toBeInTheDocument(); + }); + }); + + it('should support clearing recent searches', async () => { + renderApp(); + + expect(groupSearchHistory.get(userId)).toHaveLength(1); + await userEvent.click(screen.getByTestId('clear-recent-searches')); + expect(groupSearchHistory.get(userId)).toBeNull(); + }); + + it('should support click events on the results', async () => { + const handler = jest.fn(); + renderApp(); + expect(handler.mock.calls.length).toEqual(0); + await userEvent.click(screen.getByTestId('recent-search-result')); + expect(handler.mock.calls.length).toEqual(1); + }); + }); + + describe('without recent searches', () => { + it('should render the blankslate', async () => { + renderApp(); + + expect(screen.getByTestId('recent-searches-blankslate')).toBeInTheDocument(); + }); + }); +}); \ No newline at end of file diff --git a/app/soapbox/features/groups/components/discover/search/__tests__/search.test.tsx b/app/soapbox/features/groups/components/discover/search/__tests__/search.test.tsx new file mode 100644 index 000000000..698608363 --- /dev/null +++ b/app/soapbox/features/groups/components/discover/search/__tests__/search.test.tsx @@ -0,0 +1,62 @@ +import React from 'react'; + +import { __stub } from 'soapbox/api'; +import { render, screen, waitFor } from 'soapbox/jest/test-helpers'; +import { normalizeGroup, normalizeInstance } from 'soapbox/normalizers'; + +import Search from '../search'; + +const store = { + instance: normalizeInstance({ + version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)', + }), +}; + +const renderApp = (children: React.ReactElement) => render(children, undefined, store); + +describe('', () => { + describe('with no results', () => { + beforeEach(() => { + __stub((mock) => { + mock.onGet('/api/v1/groups/search').reply(200, []); + }); + }); + + it('should render the blankslate', async () => { + renderApp(); + + await waitFor(() => { + expect(screen.getByTestId('no-results')).toBeInTheDocument(); + }); + }); + }); + + describe('with results', () => { + beforeEach(() => { + __stub((mock) => { + mock.onGet('/api/v1/groups/search').reply(200, [ + normalizeGroup({ + display_name: 'Group', + id: '1', + }), + ]); + }); + }); + + it('should render the results', async () => { + renderApp(); + + await waitFor(() => { + expect(screen.getByTestId('results')).toBeInTheDocument(); + }); + }); + }); + + describe('before starting a search', () => { + it('should render the RecentSearches component', () => { + renderApp(); + + expect(screen.getByTestId('recent-searches')).toBeInTheDocument(); + }); + }); +}); \ No newline at end of file diff --git a/app/soapbox/features/groups/components/discover/search/no-results-blankslate.tsx b/app/soapbox/features/groups/components/discover/search/no-results-blankslate.tsx new file mode 100644 index 000000000..171348846 --- /dev/null +++ b/app/soapbox/features/groups/components/discover/search/no-results-blankslate.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { Stack, Text } from 'soapbox/components/ui'; + +export default () => ( + + + + + + + + + +); \ No newline at end of file diff --git a/app/soapbox/features/groups/components/discover/search/recent-searches.tsx b/app/soapbox/features/groups/components/discover/search/recent-searches.tsx new file mode 100644 index 000000000..1fe5e1d2f --- /dev/null +++ b/app/soapbox/features/groups/components/discover/search/recent-searches.tsx @@ -0,0 +1,90 @@ +import React, { useState } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Virtuoso } from 'react-virtuoso'; + +import { HStack, Icon, Stack, Text } from 'soapbox/components/ui'; +import { useOwnAccount } from 'soapbox/hooks'; +import { groupSearchHistory } from 'soapbox/settings'; +import { clearRecentGroupSearches } from 'soapbox/utils/groups'; + +interface Props { + onSelect(value: string): void +} + +export default (props: Props) => { + const { onSelect } = props; + + const me = useOwnAccount(); + + const [recentSearches, setRecentSearches] = useState(groupSearchHistory.get(me?.id as string) || []); + + const onClearRecentSearches = () => { + clearRecentGroupSearches(me?.id as string); + setRecentSearches([]); + }; + + return ( + + {recentSearches.length > 0 ? ( + <> + + + + + + + + + ( +
+ +
+ )} + /> + + ) : ( + + + + + + + + + + )} +
+ ); +}; \ No newline at end of file diff --git a/app/soapbox/features/groups/components/discover/search/results.tsx b/app/soapbox/features/groups/components/discover/search/results.tsx new file mode 100644 index 000000000..cfbc74039 --- /dev/null +++ b/app/soapbox/features/groups/components/discover/search/results.tsx @@ -0,0 +1,169 @@ +import clsx from 'clsx'; +import React, { useCallback, useState } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Components, Virtuoso, VirtuosoGrid } from 'react-virtuoso'; + +import { Avatar, Button, HStack, Icon, Stack, Text } from 'soapbox/components/ui'; +import { useGroupSearch } from 'soapbox/queries/groups/search'; +import { Group } from 'soapbox/types/entities'; +import { shortNumberFormat } from 'soapbox/utils/numbers'; + +import GroupComp from '../group'; + +interface Props { + groupSearchResult: ReturnType +} + +enum Layout { + LIST = 'LIST', + GRID = 'GRID' +} + +const GridList: Components['List'] = React.forwardRef((props, ref) => { + const { context, ...rest } = props; + return
; +}); + +export default (props: Props) => { + const { groupSearchResult } = props; + + const [layout, setLayout] = useState(Layout.LIST); + + const { groups, hasNextPage, isFetching, fetchNextPage } = groupSearchResult; + + const handleLoadMore = () => { + if (hasNextPage && !isFetching) { + fetchNextPage(); + } + }; + + const renderGroupList = useCallback((group: Group, index: number) => ( + + + + + + + + + + + + {group.locked ? ( + + ) : ( + + )} + + + {typeof group.members_count !== 'undefined' && ( + <> + + + {shortNumberFormat(group.members_count)} + {' '} + + + + )} + + + + + + + ), []); + + const renderGroupGrid = useCallback((group: Group, index: number) => ( +
+ +
+ ), []); + + return ( + + + + + + + + + + + + + + {layout === Layout.LIST ? ( + renderGroupList(group, index)} + endReached={handleLoadMore} + /> + ) : ( + renderGroupGrid(group, index)} + components={{ + Item: (props) => ( +
+ ), + List: GridList, + }} + endReached={handleLoadMore} + /> + )} + + ); +}; \ No newline at end of file diff --git a/app/soapbox/features/groups/components/discover/search/search.tsx b/app/soapbox/features/groups/components/discover/search/search.tsx new file mode 100644 index 000000000..083dab8d5 --- /dev/null +++ b/app/soapbox/features/groups/components/discover/search/search.tsx @@ -0,0 +1,64 @@ +import React, { useEffect } from 'react'; + +import { Stack } from 'soapbox/components/ui'; +import PlaceholderGroupSearch from 'soapbox/features/placeholder/components/placeholder-group-search'; +import { useDebounce, useOwnAccount } from 'soapbox/hooks'; +import { useGroupSearch } from 'soapbox/queries/groups/search'; +import { saveGroupSearch } from 'soapbox/utils/groups'; + +import NoResultsBlankslate from './no-results-blankslate'; +import RecentSearches from './recent-searches'; +import Results from './results'; + +interface Props { + onSelect(value: string): void + searchValue: string +} + +export default (props: Props) => { + const { onSelect, searchValue } = props; + + const me = useOwnAccount(); + const debounce = useDebounce; + + const debouncedValue = debounce(searchValue as string, 300); + const debouncedValueToSave = debounce(searchValue as string, 1000); + + const groupSearchResult = useGroupSearch(debouncedValue); + const { groups, isFetching, isFetched } = groupSearchResult; + + const hasSearchResults = isFetched && groups.length > 0; + const hasNoSearchResults = isFetched && groups.length === 0; + + useEffect(() => { + if (debouncedValueToSave && debouncedValueToSave.length >= 0) { + saveGroupSearch(me?.id as string, debouncedValueToSave); + } + }, [debouncedValueToSave]); + + if (isFetching) { + return ( + + + + + + ); + } + + if (hasNoSearchResults) { + return ; + } + + if (hasSearchResults) { + return ( + + ); + } + + return ( + + ); +}; \ No newline at end of file diff --git a/app/soapbox/features/groups/components/discover/suggested-groups.tsx b/app/soapbox/features/groups/components/discover/suggested-groups.tsx new file mode 100644 index 000000000..bf441a0ae --- /dev/null +++ b/app/soapbox/features/groups/components/discover/suggested-groups.tsx @@ -0,0 +1,54 @@ +import React, { useState } from 'react'; + +import { Carousel, Stack, Text } from 'soapbox/components/ui'; +import PlaceholderGroupDiscover from 'soapbox/features/placeholder/components/placeholder-group-discover'; +import { useSuggestedGroups } from 'soapbox/queries/groups'; + +import Group from './group'; + +const SuggestedGroups = () => { + const { groups, isFetching } = useSuggestedGroups(); + + const [groupCover, setGroupCover] = useState(null); + + return ( + + + Suggested For You + + + + {({ width }: { width: number }) => ( + <> + {isFetching ? ( + new Array(20).fill(0).map((_, idx) => ( +
+ +
+ )) + ) : ( + groups.map((group) => ( + + )) + )} + + )} +
+
+ ); +}; + +export default SuggestedGroups; \ No newline at end of file diff --git a/app/soapbox/features/groups/components/tab-bar.tsx b/app/soapbox/features/groups/components/tab-bar.tsx new file mode 100644 index 000000000..7a342bfc8 --- /dev/null +++ b/app/soapbox/features/groups/components/tab-bar.tsx @@ -0,0 +1,41 @@ +import React, { useMemo } from 'react'; +import { useHistory } from 'react-router-dom'; + +import { Tabs } from 'soapbox/components/ui'; + +import type { Item } from 'soapbox/components/ui/tabs/tabs'; + +export enum TabItems { + MY_GROUPS = 'MY_GROUPS', + FIND_GROUPS = 'FIND_GROUPS' +} + +interface ITabBar { + activeTab: TabItems +} + +const TabBar = ({ activeTab }: ITabBar) => { + const history = useHistory(); + + const tabItems: Item[] = useMemo(() => ([ + { + text: 'My Groups', + action: () => history.push('/groups'), + name: TabItems.MY_GROUPS, + }, + { + text: 'Find Groups', + action: () => history.push('/groups/discover'), + name: TabItems.FIND_GROUPS, + }, + ]), []); + + return ( + + ); +}; + +export default TabBar; \ No newline at end of file diff --git a/app/soapbox/features/groups/discover.tsx b/app/soapbox/features/groups/discover.tsx new file mode 100644 index 000000000..4e0c0c70a --- /dev/null +++ b/app/soapbox/features/groups/discover.tsx @@ -0,0 +1,81 @@ +import React, { useState } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; + +import { HStack, Icon, IconButton, Input, Stack } from 'soapbox/components/ui'; + +import PopularGroups from './components/discover/popular-groups'; +import Search from './components/discover/search/search'; +import SuggestedGroups from './components/discover/suggested-groups'; +import TabBar, { TabItems } from './components/tab-bar'; + +const messages = defineMessages({ + placeholder: { id: 'groups.discover.search.placeholder', defaultMessage: 'Search' }, +}); + +const Discover: React.FC = () => { + const intl = useIntl(); + + const [isSearching, setIsSearching] = useState(false); + const [value, setValue] = useState(''); + + const hasSearchValue = value && value.length > 0; + + const cancelSearch = () => { + clearValue(); + setIsSearching(false); + }; + + const clearValue = () => setValue(''); + + return ( + + + + + + {isSearching ? ( + + ) : null} + + setValue(event.target.value)} + onFocus={() => setIsSearching(true)} + outerClassName='mt-0 w-full' + theme='search' + append={ + + } + /> + + + {isSearching ? ( + setValue(newValue)} + /> + ) : ( + <> + + + + )} + + + ); +}; + +export default Discover; diff --git a/app/soapbox/features/groups/index.tsx b/app/soapbox/features/groups/index.tsx index 85a092e0b..f48bd1520 100644 --- a/app/soapbox/features/groups/index.tsx +++ b/app/soapbox/features/groups/index.tsx @@ -1,78 +1,65 @@ -import React, { useEffect } from 'react'; +import React from 'react'; import { FormattedMessage } from 'react-intl'; import { Link } from 'react-router-dom'; -import { createSelector } from 'reselect'; -import { fetchGroups } from 'soapbox/actions/groups'; import { openModal } from 'soapbox/actions/modals'; import GroupCard from 'soapbox/components/group-card'; import ScrollableList from 'soapbox/components/scrollable-list'; -import { Button, Column, Spinner, Stack, Text } from 'soapbox/components/ui'; -import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; +import { Button, Stack, Text } from 'soapbox/components/ui'; +import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; +import { useGroups } from 'soapbox/queries/groups'; import { PERMISSION_CREATE_GROUPS, hasPermission } from 'soapbox/utils/permissions'; import PlaceholderGroupCard from '../placeholder/components/placeholder-group-card'; -import type { List as ImmutableList } from 'immutable'; -import type { RootState } from 'soapbox/store'; +import TabBar, { TabItems } from './components/tab-bar'; + import type { Group as GroupEntity } from 'soapbox/types/entities'; -const getOrderedGroups = createSelector([ - (state: RootState) => state.groups.items, - (state: RootState) => state.groups.isLoading, - (state: RootState) => state.group_relationships, -], (groups, isLoading, group_relationships) => ({ - groups: (groups.toList().filter((item: GroupEntity | false) => !!item) as ImmutableList) - .map((item) => item.set('relationship', group_relationships.get(item.id) || null)) - .filter((item) => item.relationship?.member) - .sort((a, b) => a.display_name.localeCompare(b.display_name)), - isLoading, -})); +// const getOrderedGroups = createSelector([ +// (state: RootState) => state.groups.items, +// (state: RootState) => state.group_relationships, +// ], (groups, group_relationships) => ({ +// groups: (groups.toList().filter((item: GroupEntity | false) => !!item) as ImmutableList) +// .map((item) => item.set('relationship', group_relationships.get(item.id) || null)) +// .filter((item) => item.relationship?.member) +// .sort((a, b) => a.display_name.localeCompare(b.display_name)), +// })); + +const EmptyMessage = () => ( + + + + + + + + + + + +); const Groups: React.FC = () => { const dispatch = useAppDispatch(); + const features = useFeatures(); - const { groups, isLoading } = useAppSelector((state) => getOrderedGroups(state)); const canCreateGroup = useAppSelector((state) => hasPermission(state, PERMISSION_CREATE_GROUPS)); - useEffect(() => { - dispatch(fetchGroups()); - }, []); + const { groups, isLoading } = useGroups(); const createGroup = () => { dispatch(openModal('MANAGE_GROUP')); }; - if (!groups) { - return ( - - - - ); - } - - const emptyMessage = ( - - - - - - - - - - - - ); - return ( - + {canCreateGroup && ( )} + + {features.groupsDiscovery && ( + + )} + } itemClassName='py-3 first:pt-0 last:pb-0' isLoading={isLoading} - showLoading={isLoading && !groups.count()} + showLoading={isLoading && groups.length === 0} placeholderComponent={PlaceholderGroupCard} placeholderCount={3} > diff --git a/app/soapbox/features/placeholder/components/placeholder-group-card.tsx b/app/soapbox/features/placeholder/components/placeholder-group-card.tsx index 44ece7320..f07012f8f 100644 --- a/app/soapbox/features/placeholder/components/placeholder-group-card.tsx +++ b/app/soapbox/features/placeholder/components/placeholder-group-card.tsx @@ -5,23 +5,26 @@ import { HStack, Stack, Text } from 'soapbox/components/ui'; import { generateText, randomIntFromInterval } from '../utils'; const PlaceholderGroupCard = () => { - const groupNameLength = randomIntFromInterval(5, 25); - const roleLength = randomIntFromInterval(5, 15); - const privacyLength = randomIntFromInterval(5, 15); + const groupNameLength = randomIntFromInterval(12, 20); return ( -
- -
-
-
-
+
+ + {/* Group Cover Image */} +
+ + {/* Group Avatar */} +
+
- - {generateText(groupNameLength)} - - {generateText(roleLength)} - {generateText(privacyLength)} + + {/* Group Info */} + + {generateText(groupNameLength)} + + + {generateText(6)} + {generateText(6)} diff --git a/app/soapbox/features/placeholder/components/placeholder-group-discover.tsx b/app/soapbox/features/placeholder/components/placeholder-group-discover.tsx new file mode 100644 index 000000000..767dd5e0b --- /dev/null +++ b/app/soapbox/features/placeholder/components/placeholder-group-discover.tsx @@ -0,0 +1,38 @@ +import React from 'react'; + +import { HStack, Stack, Text } from 'soapbox/components/ui'; + +import { generateText, randomIntFromInterval } from '../utils'; + +const PlaceholderGroupDiscover = () => { + const groupNameLength = randomIntFromInterval(12, 20); + + return ( + + + {/* Group Cover Image */} +
+ + + {/* Group Avatar */} +
+ + {/* Group Info */} + + {generateText(groupNameLength)} + + + {generateText(6)} + {generateText(6)} + + + + + + {/* Join Group Button */} +
+ + ); +}; + +export default PlaceholderGroupDiscover; diff --git a/app/soapbox/features/placeholder/components/placeholder-group-search.tsx b/app/soapbox/features/placeholder/components/placeholder-group-search.tsx new file mode 100644 index 000000000..b2e2dc6f8 --- /dev/null +++ b/app/soapbox/features/placeholder/components/placeholder-group-search.tsx @@ -0,0 +1,43 @@ +import React from 'react'; + +import { HStack, Stack, Text } from 'soapbox/components/ui'; + +import { generateText, randomIntFromInterval } from '../utils'; + +export default () => { + const groupNameLength = randomIntFromInterval(12, 20); + + return ( + + + {/* Group Avatar */} +
+ + + + {generateText(groupNameLength)} + + + + + {generateText(6)} + + + + + + {generateText(6)} + + + + + + {/* Join Group Button */} +
+ + ); +}; diff --git a/app/soapbox/features/soapbox-config/components/icon-picker-dropdown.tsx b/app/soapbox/features/soapbox-config/components/icon-picker-dropdown.tsx index 950afdc08..4abde9e5a 100644 --- a/app/soapbox/features/soapbox-config/components/icon-picker-dropdown.tsx +++ b/app/soapbox/features/soapbox-config/components/icon-picker-dropdown.tsx @@ -13,10 +13,10 @@ const messages = defineMessages({ interface IIconPickerDropdown { value: string - onPickEmoji: React.ChangeEventHandler + onPickIcon: (icon: string) => void } -const IconPickerDropdown: React.FC = ({ value, onPickEmoji }) => { +const IconPickerDropdown: React.FC = ({ value, onPickIcon }) => { const intl = useIntl(); const [active, setActive] = useState(false); @@ -73,9 +73,9 @@ const IconPickerDropdown: React.FC = ({ value, onPickEmoji
diff --git a/app/soapbox/features/soapbox-config/components/icon-picker-menu.tsx b/app/soapbox/features/soapbox-config/components/icon-picker-menu.tsx index 46744e63e..765d282c2 100644 --- a/app/soapbox/features/soapbox-config/components/icon-picker-menu.tsx +++ b/app/soapbox/features/soapbox-config/components/icon-picker-menu.tsx @@ -1,31 +1,25 @@ import clsx from 'clsx'; import { supportsPassiveEvents } from 'detect-passive-events'; -// @ts-ignore -import Picker from 'emoji-mart/dist-es/components/picker/picker'; import React, { useCallback, useEffect, useRef } from 'react'; -import { defineMessages, useIntl } from 'react-intl'; +import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; + +import { Text } from 'soapbox/components/ui'; const messages = defineMessages({ emoji: { id: 'icon_button.label', defaultMessage: 'Select icon' }, - emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search…' }, - emoji_not_found: { id: 'icon_button.not_found', defaultMessage: 'No icons!! (╯°□°)╯︵ ┻━┻' }, - custom: { id: 'icon_button.icons', defaultMessage: 'Icons' }, - search_results: { id: 'emoji_button.search_results', defaultMessage: 'Search results' }, }); -const backgroundImageFn = () => ''; const listenerOptions = supportsPassiveEvents ? { passive: true } : false; -const categoriesSort = ['custom']; interface IIconPickerMenu { - customEmojis: Record> + icons: Record> onClose: () => void - onPick: any + onPick: (icon: string) => void style?: React.CSSProperties } -const IconPickerMenu: React.FC = ({ customEmojis, onClose, onPick, style }) => { +const IconPickerMenu: React.FC = ({ icons, onClose, onPick, style }) => { const intl = useIntl(); const node = useRef(null); @@ -60,70 +54,42 @@ const IconPickerMenu: React.FC = ({ customEmojis, onClose, onPi }); }; - const getI18n = () => { - - return { - search: intl.formatMessage(messages.emoji_search), - notfound: intl.formatMessage(messages.emoji_not_found), - categories: { - search: intl.formatMessage(messages.search_results), - custom: intl.formatMessage(messages.custom), - }, - }; - }; - - const handleClick = (emoji: Record) => { - emoji.native = emoji.colons; - + const handleClick = (icon: string) => { onClose(); - onPick(emoji); + onPick(icon); }; - const buildIcons = () => { - const emojis: Record = []; + const renderIcon = (icon: string) => { + const name = icon.replace('fa fa-', ''); - Object.values(customEmojis).forEach((category) => { - category.forEach((icon) => { - const name = icon.replace('fa fa-', ''); - if (icon !== 'email' && icon !== 'memo') { - emojis.push({ - id: name, - name, - short_names: [name], - emoticons: [], - keywords: [name], - imageUrl: '', - }); - } - }); - }); - - return emojis; + return ( +
  • + +
  • + ); }; - const data = { compressed: true, categories: [], aliases: [], emojis: [] }; const title = intl.formatMessage(messages.emoji); return ( -
    - +
    +
    + +
      + {Object.values(icons).flat().map(icon => renderIcon(icon))} +
    +
    ); }; diff --git a/app/soapbox/features/soapbox-config/components/icon-picker.tsx b/app/soapbox/features/soapbox-config/components/icon-picker.tsx index 475f87b6a..e4b9d6c5e 100644 --- a/app/soapbox/features/soapbox-config/components/icon-picker.tsx +++ b/app/soapbox/features/soapbox-config/components/icon-picker.tsx @@ -4,15 +4,13 @@ import IconPickerDropdown from './icon-picker-dropdown'; interface IIconPicker { value: string - onChange: React.ChangeEventHandler + onChange: (icon: string) => void } -const IconPicker: React.FC = ({ value, onChange }) => { - return ( -
    - -
    - ); -}; +const IconPicker: React.FC = ({ value, onChange }) => ( +
    + +
    +); export default IconPicker; diff --git a/app/soapbox/features/soapbox-config/components/promo-panel-input.tsx b/app/soapbox/features/soapbox-config/components/promo-panel-input.tsx index 93e32b0e3..0dd530c39 100644 --- a/app/soapbox/features/soapbox-config/components/promo-panel-input.tsx +++ b/app/soapbox/features/soapbox-config/components/promo-panel-input.tsx @@ -17,8 +17,8 @@ const messages = defineMessages({ const PromoPanelInput: StreamfieldComponent = ({ value, onChange }) => { const intl = useIntl(); - const handleIconChange = (icon: any) => { - onChange(value.set('icon', icon.id)); + const handleIconChange = (icon: string) => { + onChange(value.set('icon', icon)); }; const handleChange = (key: 'text' | 'url'): React.ChangeEventHandler => { diff --git a/app/soapbox/features/ui/components/link-footer.tsx b/app/soapbox/features/ui/components/link-footer.tsx index df15b7c5a..c8dd93588 100644 --- a/app/soapbox/features/ui/components/link-footer.tsx +++ b/app/soapbox/features/ui/components/link-footer.tsx @@ -5,7 +5,7 @@ import { Link } from 'react-router-dom'; import { logOut } from 'soapbox/actions/auth'; import { Text } from 'soapbox/components/ui'; -import emojify from 'soapbox/features/emoji/emoji'; +import emojify from 'soapbox/features/emoji'; import { useSoapboxConfig, useOwnAccount, useFeatures, useAppDispatch } from 'soapbox/hooks'; import sourceCode from 'soapbox/utils/code'; diff --git a/app/soapbox/features/ui/components/panels/new-group-panel.tsx b/app/soapbox/features/ui/components/panels/new-group-panel.tsx index 9710eeb82..9eba9c7e0 100644 --- a/app/soapbox/features/ui/components/panels/new-group-panel.tsx +++ b/app/soapbox/features/ui/components/panels/new-group-panel.tsx @@ -21,7 +21,7 @@ const NewGroupPanel = () => { - + @@ -30,12 +30,11 @@ const NewGroupPanel = () => { ); diff --git a/app/soapbox/features/ui/index.tsx b/app/soapbox/features/ui/index.tsx index 931dda451..d8068fe9a 100644 --- a/app/soapbox/features/ui/index.tsx +++ b/app/soapbox/features/ui/index.tsx @@ -115,6 +115,7 @@ import { EventDiscussion, Events, Groups, + GroupsDiscover, GroupMembers, GroupTimeline, ManageGroup, @@ -282,6 +283,7 @@ const SwitchingColumnsArea: React.FC = ({ children }) => {features.groups && } + {features.groupsDiscovery && } {features.groups && } {features.groups && } {features.groups && } @@ -572,7 +574,7 @@ const UI: React.FC = ({ children }) => { // @ts-ignore hotkeys.current.__mousetrap__.stopCallback = (_e, element) => { - return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName); + return ['TEXTAREA', 'SELECT', 'INPUT', 'EM-EMOJI-PICKER'].includes(element.tagName); }; }; diff --git a/app/soapbox/features/ui/util/async-components.ts b/app/soapbox/features/ui/util/async-components.ts index 6e18f5771..8f55da6bc 100644 --- a/app/soapbox/features/ui/util/async-components.ts +++ b/app/soapbox/features/ui/util/async-components.ts @@ -1,5 +1,5 @@ export function EmojiPicker() { - return import(/* webpackChunkName: "emoji_picker" */'../../emoji/emoji-picker'); + return import(/* webpackChunkName: "emoji_picker" */'../../emoji/components/emoji-picker'); } export function Notifications() { @@ -542,6 +542,10 @@ export function Groups() { return import(/* webpackChunkName: "features/groups" */'../../groups'); } +export function GroupsDiscover() { + return import(/* webpackChunkName: "features/groups/discover" */'../../groups/discover'); +} + export function GroupMembers() { return import(/* webpackChunkName: "features/groups" */'../../group/group-members'); } diff --git a/app/soapbox/hooks/__tests__/useGroupsPath.test.ts b/app/soapbox/hooks/__tests__/useGroupsPath.test.ts new file mode 100644 index 000000000..d2ffb2452 --- /dev/null +++ b/app/soapbox/hooks/__tests__/useGroupsPath.test.ts @@ -0,0 +1,73 @@ +import { Map as ImmutableMap } from 'immutable'; + +import { __stub } from 'soapbox/api'; +import { renderHook, waitFor } from 'soapbox/jest/test-helpers'; +import { normalizeAccount, normalizeGroup, normalizeInstance } from 'soapbox/normalizers'; + +import { useGroupsPath } from '../useGroupsPath'; + +describe('useGroupsPath()', () => { + test('without the groupsDiscovery feature', () => { + const store = { + instance: normalizeInstance({ + version: '2.7.2 (compatible; Pleroma 2.3.0)', + }), + }; + + const { result } = renderHook(useGroupsPath, undefined, store); + + expect(result.current).toEqual('/groups'); + }); + + describe('with the "groupsDiscovery" feature', () => { + let store: any; + + beforeEach(() => { + const userId = '1'; + store = { + instance: normalizeInstance({ + version: '3.4.1 (compatible; TruthSocial 1.0.0+unreleased)', + }), + me: userId, + accounts: ImmutableMap({ + [userId]: normalizeAccount({ + id: userId, + acct: 'justin-username', + display_name: 'Justin L', + avatar: 'test.jpg', + chats_onboarded: false, + }), + }), + }; + }); + + describe('when the user has no groups', () => { + test('should default to the discovery page', () => { + const { result } = renderHook(useGroupsPath, undefined, store); + + expect(result.current).toEqual('/groups/discover'); + }); + }); + + describe('when the user has groups', () => { + beforeEach(() => { + __stub((mock) => { + mock.onGet('/api/v1/groups').reply(200, [ + normalizeGroup({ + display_name: 'Group', + id: '1', + }), + ]); + }); + }); + + test('should default to the discovery page', async () => { + const { result } = renderHook(useGroupsPath, undefined, store); + + await waitFor(() => { + expect(result.current).toEqual('/groups'); + }); + }); + }); + }); +}); diff --git a/app/soapbox/hooks/index.ts b/app/soapbox/hooks/index.ts index 6460938b6..afefefd72 100644 --- a/app/soapbox/hooks/index.ts +++ b/app/soapbox/hooks/index.ts @@ -5,6 +5,7 @@ export { useAppSelector } from './useAppSelector'; export { useClickOutside } from './useClickOutside'; export { useCompose } from './useCompose'; export { useDebounce } from './useDebounce'; +export { useGroupsPath } from './useGroupsPath'; export { useDimensions } from './useDimensions'; export { useFeatures } from './useFeatures'; export { useInstance } from './useInstance'; diff --git a/app/soapbox/hooks/useGroupsPath.ts b/app/soapbox/hooks/useGroupsPath.ts new file mode 100644 index 000000000..8a4759f32 --- /dev/null +++ b/app/soapbox/hooks/useGroupsPath.ts @@ -0,0 +1,23 @@ +import { useGroups } from 'soapbox/queries/groups'; + +import { useFeatures } from './useFeatures'; + +/** + * Determine the correct URL to use for /groups. + * If the user does not have any Groups, let's default to the discovery tab. + * Otherwise, let's default to My Groups. + * + * @returns String (as link) + */ +const useGroupsPath = () => { + const features = useFeatures(); + const { groups } = useGroups(); + + if (!features.groupsDiscovery) { + return '/groups'; + } + + return groups.length > 0 ? '/groups' : '/groups/discover'; +}; + +export { useGroupsPath }; \ No newline at end of file diff --git a/app/soapbox/locales/ar.json b/app/soapbox/locales/ar.json index 2a4c06f10..3c523f14f 100644 --- a/app/soapbox/locales/ar.json +++ b/app/soapbox/locales/ar.json @@ -3,14 +3,14 @@ "accordion.collapse": "طيّ", "accordion.expand": "توسيع", "account.add_or_remove_from_list": "إضافة أو إزالة من القوائم", - "account.badges.bot": "بوت", + "account.badges.bot": "آلي", "account.birthday": "ولد في {date}", "account.birthday_today": "اليوم يوم ميلاد صاحب الحساب!", "account.block": "حظر @{name}", "account.block_domain": "إخفاء كل ما يتعلق بالنطاق {domain}", "account.blocked": "محظور", "account.chat": "دردشة مع @{name}", - "account.deactivated": "تم تعطيله", + "account.deactivated": "عُطِّلَ", "account.direct": "رسالة خاصة إلى @{name}", "account.domain_blocked": "النطاق مخفي", "account.edit_profile": "تعديل الملف الشخصي", @@ -18,7 +18,7 @@ "account.endorse.success": "أنت الآن تقوم بالتوصية بـ @{acct} في ملفك الشخصي", "account.familiar_followers": "يُتابعه {accounts}", "account.familiar_followers.empty": "لا أحد تعرفه يتابع {name}.", - "account.familiar_followers.more": "{count} {count, plural, one {other} other {others}} أنت تتباعهم", + "account.familiar_followers.more": "{count، plural، واحد {# other} آخر {# others}} تتبعه", "account.follow": "متابعة", "account.followers": "متابعون", "account.followers.empty": "لا يوجد متابعين لهذا الحساب.", @@ -26,7 +26,7 @@ "account.follows.empty": "هذا الحساب لا يُتابع أي شخص.", "account.follows_you": "يتابعك", "account.header.alt": "ترويسة الحساب", - "account.hide_reblogs": "إخفاء المناشير المعاد نشرها من @{name}", + "account.hide_reblogs": "إخفاء المنشورات المعاد نشرها من @{name}", "account.last_status": "آخر تواجد", "account.link_verified_on": "أُكِّدت ملكية الحساب في {date}", "account.locked_info": "هذا الحساب خاص. يجب الموافقة على طلب المتابعة من قِبل صاحب الحساب.", @@ -35,30 +35,30 @@ "account.member_since": "إنضم بتاريخ {date}", "account.mention": "إشارة", "account.mute": "كتم @{name}", - "account.muted": "تم كتمه", + "account.muted": "كُتِمَ", "account.never_active": "أبدًا", "account.posts": "منشورات", "account.posts_with_replies": "المنشورات والردود", "account.profile": "الملف الشخصي", "account.profile_external": "عرض الملف الشخصي في {domain}", - "account.register": "التسجيل", + "account.register": "إنشاء حساب", "account.remote_follow": "متابعة على خادم خارجي", "account.remove_from_followers": "حذف هذا المتابع", "account.report": "ابلِغ عن @{name}", "account.requested": "في انتظار الموافقة. اضغط لإلغاء طلب المتابعة", "account.requested_small": "في انتظار الموافقة", - "account.rss_feed": "الاشتراك في تغذية RSS", - "account.search": "البحث انطلاقًا من @{name}", + "account.rss_feed": "اشترك في موجز RSS", + "account.search": "البحث في منشورات @{name}", "account.search_self": "البحث في منشوراتك", "account.share": "مشاركة حساب @{name}", "account.show_reblogs": "عرض المنشورات المُعاد نشرها من @{name}", "account.subscribe": "متابعة الإشعارات من طرف @{name}", "account.subscribe.failure": "حدث خلل أثناء الاشتراك بالإشعارات من هذا الحساب.", - "account.subscribe.success": "لقد اشتركت بالإشعارات من هذا الحساب", + "account.subscribe.success": "لقد اشتركت في هذا الحساب.", "account.unblock": "إلغاء الحظر عن @{name}", "account.unblock_domain": "إلغاء إخفاء {domain}", "account.unendorse": "الإزالة من ملفك الشخصي", - "account.unendorse.success": "تمت إزالة @{acct} من ملفك الشخصي", + "account.unendorse.success": "أُزيل @{acct} من ملفك الشخصي", "account.unfollow": "إلغاء المتابعة", "account.unmute": "إلغاء كتم @{name}", "account.unsubscribe": "إلغاء متابعة الإشعارات من طرف @{name}", @@ -78,17 +78,23 @@ "account_moderation_modal.roles.moderator": "مشرف", "account_moderation_modal.roles.user": "مستخدم", "account_moderation_modal.title": "الإشراف على @{acct}", - "account_note.hint": "يمكنك الاحتفاظ بملاحظات حول هذا المستخدم لنفسك (لن تتم مشاركتها معهم):", + "account_note.hint": "يمكنك الاحتفاظ بملاحظات حول هذا المستخدم لنفسك (لن تُشارَكَ معهم):", "account_note.placeholder": "لا توجد تعليقات", "account_note.save": "حِفط", "account_note.target": "ملاحظة لـ @{target}", - "account_search.placeholder": "ابحث عن حساب", + "account_search.placeholder": "البحث عن حساب", "actualStatus.edited": "تم تعديله بتاريخ {date}", "actualStatuses.quote_tombstone": "المنشور غير متوفر", + "admin.announcements.action": "إنشاء إعلان", + "admin.announcements.all_day": "طوال اليوم", + "admin.announcements.delete": "إزالة", + "admin.announcements.edit": "تعديل", + "admin.announcements.ends_at": "ينتهي عند:", + "admin.announcements.starts_at": "يبدأ عند:", "admin.awaiting_approval.approved_message": "تمت الموافقة على {acct}!", "admin.awaiting_approval.empty_message": "ليس هناك حسابات جديدة للموافقة عليها. عندما يقوم شخص ما بالتسجيل، ستتمكن من مراجعة الحساب هنا.", - "admin.awaiting_approval.rejected_message": "تم رفض {acct}!", - "admin.dashboard.registration_mode.approval_hint": "يمكن للمستخدمين إنشاء الحسابات، لكن يتم تفعيل حساباتهم عند قبولها من طرف المدير.", + "admin.awaiting_approval.rejected_message": "رُفِضَ {acct}!", + "admin.dashboard.registration_mode.approval_hint": "يمكن للمستخدمين إنشاء الحسابات، لكن تُفعَّل حساباتهم عند قبولها من طرف المدير.", "admin.dashboard.registration_mode.approval_label": "يتطلب الموافقة", "admin.dashboard.registration_mode.closed_hint": "لا يمكن لأحد إنشاء حساب جديد، ولكن مازال بإمكانك دعوتهم.", "admin.dashboard.registration_mode.closed_label": "مُغلق", @@ -103,6 +109,18 @@ "admin.dashcounters.user_count_label": "إجمالي الأعضاء", "admin.dashwidgets.email_list_header": "قائمة البريد الإلكتروني", "admin.dashwidgets.software_header": "النظام", + "admin.edit_announcement.created": "تم إنشاء الإعلان", + "admin.edit_announcement.deleted": "حُذِفَ الإعلان", + "admin.edit_announcement.fields.all_day_hint": "عند التحديد ، سيتم عرض تواريخ النطاق الزمني فقط", + "admin.edit_announcement.fields.all_day_label": "حدث طوال اليوم", + "admin.edit_announcement.fields.content_label": "المحتوى", + "admin.edit_announcement.fields.content_placeholder": "محتوى الإعلان", + "admin.edit_announcement.fields.end_time_label": "تاريخ الانتهاء", + "admin.edit_announcement.fields.end_time_placeholder": "الإعلان ينتهي في:", + "admin.edit_announcement.fields.start_time_label": "تاريخ البَدْء", + "admin.edit_announcement.fields.start_time_placeholder": "الإعلان يبدأ في:", + "admin.edit_announcement.save": "حفظ", + "admin.edit_announcement.updated": "عُدِّلَ الإعلان", "admin.latest_accounts_panel.more": "إضغط لعرض {count} {count, plural, one {حساب} other {حسابات}}", "admin.latest_accounts_panel.title": "أحدث الحسابات", "admin.moderation_log.empty_message": "لم تنفِّذ أيّ عملية إشرافٍ بعد. عندما تفعل سيظهر سجلٌّ لها هنا.", @@ -140,7 +158,7 @@ "admin_nav.awaiting_approval": "في انتظار الموافقة", "admin_nav.dashboard": "لوحة التحكم", "admin_nav.reports": "البلاغات", - "age_verification.body": "منصة {siteTitle} تستوجب أن تبلغ في الأقل {ageMinimum} عامًا للتسجيل واستخدام المنصة. أي شخص يبلغ أقل من {ageMinimum} عام لا يُسمح له بالتسجيل.", + "age_verification.body": "يتطلب {siteTitle} أن يكون عمر المستخدمين على الأقل {ageMinimum، plural،one {# year} آخر {# years}} عام للوصول إلى النظام الأساسي الخاص به. لا يمكن لأي شخص يقل عمره عن {ageMinimum، plural، one {# year} other {# years}} الوصول إلى هذا النظام الأساسي.", "age_verification.fail": "يجب أن تبلغ {ageMinimum, plural, one {سنةً} two {سنتين} few {# سنواتٍ} many {# سنةً} other {# سنةً}} أو أكثر.", "age_verification.header": "رجاءً أدخل تاريخ ميلادك", "alert.unexpected.body": "نأسف للمقاطعة. إذا استمرت هذه المشكلة، يرجى التواصل مع فريق الدعم لدينا. يمكنك أيضًا محاولة {clearCookies} (سيؤدي هذا إلى تسجيل خروجك).", @@ -170,6 +188,7 @@ "app_create.scopes_placeholder": "مثلاً: (قراءة كتابة متابعة)", "app_create.submit": "إنشاء تطبيق", "app_create.website_label": "الموقع", + "auth.awaiting_approval": "حسابك ينتظر الموافقة", "auth.invalid_credentials": "اسم المستخدم أو كلمة المرور خاطئة", "auth.logged_out": "سُجِّل الخروج.", "auth_layout.register": "إنشاء حساب", @@ -190,77 +209,85 @@ "bundle_modal_error.retry": "إعادة المحاولة", "card.back.label": "العودة", "chat.actions.send": "إرسال", - "chat.failed_to_send": "الرسائل التي فشل إرسالها.", - "chat.input.placeholder": "أكتب رسالة", + "chat.failed_to_send": "فشل إرسال الرسالة.", + "chat.input.placeholder": "اكتب رسالة", "chat.new_message.title": "رسالة جديدة", - "chat.page_settings.accepting_messages.label": "السماح للمستخدمين بمراسلتك", - "chat.page_settings.play_sounds.label": "تشغيل صوت عند وصول رسالة جديدة", + "chat.page_settings.accepting_messages.label": "السماح للمستخدمين ببدء محادثة جديدة معك", + "chat.page_settings.play_sounds.label": "تفعيل إشعار صوتي عند وصول رسالة جديدة", "chat.page_settings.preferences": "التفضيلات", "chat.page_settings.privacy": "الخصوصية", "chat.page_settings.submit": "حفظ", "chat.page_settings.title": "إعدادات الرسائل", - "chat.retry": "المحاولة مجدداً؟", - "chat.welcome.accepting_messages.label": "السماح للمستخدمين بمراسلتك", + "chat.retry": "إعادة المحاولة؟", + "chat.welcome.accepting_messages.label": "السماح للمستخدمين ببدء محادثة جديدة معك", "chat.welcome.notice": "يمكنك تعديل هذه الإعدادات لاحقاً.", - "chat.welcome.submit": "الحفظ والمتابعة", + "chat.welcome.submit": "حفظ ومتابعة", "chat.welcome.subtitle": "تبادل الرسائل الخاصة مع الأعضاء الآخرين.", "chat.welcome.title": "مرحباً بك في مراسلات {br}!", - "chat_composer.unblock": "فك الحظر", - "chat_list_item.blocked_you": "قام هذا المستخدم بحظرك", - "chat_list_item.blocking": "لقد قمت بحظر هذا المستخدم", - "chat_message_list.blocked": "لقد قمت بحظر هذا المستخدم", + "chat_composer.unblock": "رفع الحظر", + "chat_list_item.blocked_you": "حظرك هذا المستخدم", + "chat_list_item.blocking": "لقد حَظَرْتَ هذا المستخدم", + "chat_message_list.blocked": "لقد حَظَرْتَ هذا المستخدم", "chat_message_list.blockedBy": "أنت محظور من قبل", "chat_message_list.network_failure.action": "حاول مجدداً", "chat_message_list.network_failure.subtitle": "هناك عطل في الشبكة.", "chat_message_list.network_failure.title": "المعذرة!", "chat_message_list_intro.actions.accept": "قبول", "chat_message_list_intro.actions.leave_chat": "الخروج من الدردشة", - "chat_message_list_intro.actions.message_lifespan": "يتم حذف الرسائل الأقدم من {day} يوم.", + "chat_message_list_intro.actions.message_lifespan": "تُحْذَفُ الرسائل الأقدم من {day، plural، one {# day} other {# days}}.", "chat_message_list_intro.actions.report": "تبليغ", - "chat_message_list_intro.intro": "يرغب في مراسلتك", + "chat_message_list_intro.intro": "يريد مراسلتك", "chat_message_list_intro.leave_chat.confirm": "الخروج من الدردشة", "chat_message_list_intro.leave_chat.heading": "الخروج من الدردشة", - "chat_message_list_intro.leave_chat.message": "متأكد من رغبتك في الخروج من هذه الدردشة؟ سيتم حذف الرسائل من حسابك وإزالة الدردشة من قائمة الرسائل.", + "chat_message_list_intro.leave_chat.message": "أمتأكد من رغبتك في الخروج من هذه الدردشة؟ سيتم حذف الرسائل من حسابك وإزالة الدردشة من قائمة الرسائل.", "chat_search.blankslate.body": "البحث عن عضو للدردشة معه.", - "chat_search.blankslate.title": "بدء دردشة", - "chat_search.empty_results_blankslate.action": "قم بمراسلة أحدهم", - "chat_search.empty_results_blankslate.body": "حاول أن تبحث عن شخص آخر.", - "chat_search.empty_results_blankslate.title": "لم يتم العثور على نتائج", + "chat_search.blankslate.title": "بَدْء دردشة", + "chat_search.empty_results_blankslate.action": "رَاسِلْ أحدهم", + "chat_search.empty_results_blankslate.body": "حاول البحث عن اسم آخر.", + "chat_search.empty_results_blankslate.title": "لم يُعْثَرْ على نتائج", "chat_search.placeholder": "أدخل اسم", "chat_search.title": "المراسلات", - "chat_settings.auto_delete.14days": "14 يوم", + "chat_settings.auto_delete.14days": "14 يوماً", "chat_settings.auto_delete.2minutes": "دقيقتين", - "chat_settings.auto_delete.30days": "30 يوم", + "chat_settings.auto_delete.30days": "30 يوماً", "chat_settings.auto_delete.7days": "7 أيام", - "chat_settings.auto_delete.90days": "90 يوم", - "chat_settings.auto_delete.days": "{day} يوم", - "chat_settings.auto_delete.hint": "سيتم حذف الرسائل المرسلة بشكل تلقائي بعد انقضاء الفترة المحددة", + "chat_settings.auto_delete.90days": "90 يوماً", + "chat_settings.auto_delete.days": "{number, plural, one {# يوم} other {# أيام}}", + "chat_settings.auto_delete.hint": "سَتُحْذَفُ الرسائل المرسلة بشكل تلقائي بعد انقضاء الفترة المحددة", "chat_settings.auto_delete.label": "الحذف التلقائي للرسائل", "chat_settings.block.confirm": "حظر", "chat_settings.block.heading": "حظر @{acct}", "chat_settings.block.message": "حظر الحساب سيتسبب بمنعه من مشاهدة محتواك ومراسلتك. يمكنك فك الحظر لاحقاً.", "chat_settings.leave.confirm": "الخروج من الدردشة", "chat_settings.leave.heading": "الخروج من الدردشة", - "chat_settings.leave.message": "متأكد من رغبتك في الخروج من هذه الدردشة؟ سيتم حذف الرسائل من حسابك وإزالة الدردشة من قائمة الرسائل.", + "chat_settings.leave.message": "أمتأكد من رغبتك في الخروج من هذه الدردشة؟ سيتم حذف الرسائل من حسابك وإزالة الدردشة من قائمة الرسائل.", "chat_settings.options.block_user": "حظر @{acct}", "chat_settings.options.leave_chat": "الخروج من الدردشة", "chat_settings.options.report_user": "التبليغ عن @{acct}", - "chat_settings.options.unblock_user": "فك الحظر عن @{acct}", + "chat_settings.options.unblock_user": "رفع الحظر عن @{acct}", "chat_settings.title": "تفاصيل المحادثة", - "chat_settings.unblock.confirm": "فك الحظر", - "chat_settings.unblock.heading": "فك الحظر عن @{acct}", + "chat_settings.unblock.confirm": "رفع الحظر", + "chat_settings.unblock.heading": "رفع الحظر عن @{acct}", + "chat_settings.unblock.message": "سيسمح إلغاء الحظر لهذا المِلَفّ الشخصي بتوجيه رسالة إليك وعرض المحتوى الخاص بك.", + "chat_window.auto_delete_label": "حذف تلقائي بعد {day، plural، one {# يوم} other {# أيام}}", + "chat_window.auto_delete_tooltip": "ضُبِطَتْ رسائل الدردشة على الحذف التلقائي بعد {day، plural، one {# يوم} other {# أيام}} عند الإرسال.", "chats.actions.copy": "نسخ", "chats.actions.delete": "حذف الرسالة", - "chats.actions.deleteForMe": "الحذف لي", + "chats.actions.deleteForMe": "الحذف عندي", "chats.actions.more": "المزيد", "chats.actions.report": "الإبلاغ عن المستخدم", "chats.dividers.today": "اليوم", - "chats.main.blankslate.new_chat": "مراسلة شخص ما", + "chats.main.blankslate.new_chat": "رَاسِلْ أحدهم", "chats.main.blankslate.subtitle": "البحث عن عضو للدردشة معه", - "chats.main.blankslate.title": "لا يوجد رسائل", + "chats.main.blankslate.title": "لا توجد رسائل حتى الآن", + "chats.main.blankslate_with_chats.subtitle": "اختر من إحدى الدردشات المفتوحة أو أنشئ رسالة جديدة.", + "chats.main.blankslate_with_chats.title": "اختيار دردشة", "chats.search_placeholder": "بَدْء دردشة مع…", + "column.admin.announcements": "الإعلانات", "column.admin.awaiting_approval": "في انتظار الموافقة", + "column.admin.create_announcement": "إنشاء إعلان", "column.admin.dashboard": "لوحة تحكم", + "column.admin.edit_announcement": "تعديل الإعلان", "column.admin.moderation_log": "سجل الإشراف", "column.admin.reports": "التبليغات", "column.admin.reports.menu.moderation_log": "سجل الإشراف", @@ -314,10 +341,14 @@ "column.follow_requests": "طلبات المتابعة", "column.followers": "المتابعين", "column.following": "يتابع", + "column.group_blocked_members": "الأعضاء المحظورين", + "column.group_pending_requests": "الطلبات المعلقة", + "column.groups": "مجموعات", "column.home": "الرئيسية", "column.import_data": "استيراد البيانات", "column.info": "معلومات عن الخادم", "column.lists": "القوائم", + "column.manage_group": "إدارة المجموعة", "column.mentions": "الإشارات", "column.mfa": "المصادقة المتعددة", "column.mfa_cancel": "الغاء", @@ -349,7 +380,9 @@ "compose.submit_success": "تم إرسال المنشور", "compose_event.create": "إنشاء", "compose_event.edit_success": "تم تعديل الحدث", + "compose_event.fields.approval_required": "أرغب في الموافقة على طلبات المشاركة يدويًا", "compose_event.fields.banner_label": "صورة الحدث", + "compose_event.fields.description_hint": "صيغة ماركداون مدعومة", "compose_event.fields.description_label": "وصف الحدث", "compose_event.fields.description_placeholder": "الوصف", "compose_event.fields.end_time_label": "الحدث والتاريخ", @@ -369,7 +402,9 @@ "compose_event.tabs.edit": "تعديل التفاصيل", "compose_event.tabs.pending": "إدارة الطلبات", "compose_event.update": "تحديث", + "compose_event.upload_banner": "تحميل لافتة الحدث", "compose_form.direct_message_warning": "لن يظهر منشورك إلا للمستخدمين المذكورين.", + "compose_form.event_placeholder": "أضف إلى هذا الحدث", "compose_form.hashtag_warning": "هذا المنشور لن يُدرَج تحت أي وسم كان، بما أنه غير مُدرَج. لا يُسمح بالبحث إلّا عن المنشورات العمومية عن طريق وسومها.", "compose_form.lock_disclaimer": "حسابك ليس {locked}. يمكن لأي شخص رؤية منشوراتك الخاصة", "compose_form.lock_disclaimer.lock": "مقفل", @@ -402,6 +437,9 @@ "confirmations.admin.deactivate_user.confirm": "تعطيل @{name}", "confirmations.admin.deactivate_user.heading": "تعطيل @{acct}", "confirmations.admin.deactivate_user.message": "أنت على وشك تعطيل @{acct}. يمكنك تفعيله في أي وقت.", + "confirmations.admin.delete_announcement.confirm": "حذف", + "confirmations.admin.delete_announcement.heading": "حذف الإعلان", + "confirmations.admin.delete_announcement.message": "هل أنت متأكد أنك تريد حذف الإعلان؟", "confirmations.admin.delete_local_user.checkbox": "أنا على وعي أنني سأقوم بحذف حساب محلي.", "confirmations.admin.delete_status.confirm": "حذف المنشور", "confirmations.admin.delete_status.heading": "حذف المنشور", @@ -422,26 +460,47 @@ "confirmations.block.confirm": "حجب", "confirmations.block.heading": "حجب @{name}", "confirmations.block.message": "هل تود حقًا حظر {name}؟", + "confirmations.block_from_group.confirm": "حظر", + "confirmations.block_from_group.heading": "حظر عضو المجموعة", + "confirmations.block_from_group.message": "هل أنت متأكد من أنك تريد منع @{name} من التفاعل مع هذه المجموعة؟", + "confirmations.cancel.confirm": "تجاهل", + "confirmations.cancel.heading": "تجاهل المنشور", + "confirmations.cancel.message": "هل أنت متأكد أنك تريد إلغاء إنشاء هذا المنشور؟", "confirmations.cancel_editing.confirm": "إلغاء التعديل", "confirmations.cancel_editing.heading": "إلغاء تعديل المنشور", "confirmations.cancel_editing.message": "هل أنت متأكد من رغبتك في إلغاء التعديل؟ ستخسر جميع التغييرات.", "confirmations.cancel_event_editing.heading": "إلغاء تعديل الحدث", + "confirmations.cancel_event_editing.message": "هل أنت متأكد من رغبتك في إلغاء التعديل؟ ستخسر جميع التغييرات.", "confirmations.delete.confirm": "إزالة", "confirmations.delete.heading": "حذف المنشور", "confirmations.delete.message": "هل تود حقًا حذف هذا المنشور؟", "confirmations.delete_event.confirm": "حذف", "confirmations.delete_event.heading": "حذف الحدث", "confirmations.delete_event.message": "متأكد من رغبتك في حذف هذا الحدث؟", + "confirmations.delete_from_group.heading": "حذف من المجموعة", + "confirmations.delete_from_group.message": "هل أنت متأكد من أنك تريد حذف منشور @{name}؟", + "confirmations.delete_group.confirm": "إزالة", + "confirmations.delete_group.heading": "حذف المجموعة", + "confirmations.delete_group.message": "هل أنت متأكد أنك تريد حذف هذه المجموعة؟ هذا إجراء دائم لا يمكن التراجع عنه.", "confirmations.delete_list.confirm": "حذف", "confirmations.delete_list.heading": "إزالة القائمة", "confirmations.delete_list.message": "هل تود حقا حذف هذه القائمة ؟", "confirmations.domain_block.confirm": "حجب النطاق", "confirmations.domain_block.heading": "حجب {domain}", "confirmations.domain_block.message": "هل تود حظر النطاق {domain} بالكامل؟ في غالب الأحيان يُستَحسَن كتم أو حظر بعض الحسابات بدلا من حظر نطاق بالكامل.\nلن تتمكن مِن رؤية محتوى هذا النطاق لا على خيوطك العمومية و لا في إشعاراتك. سيُزيل ذلك كافة متابعيك المنتمين إلى هذا النطاق.", + "confirmations.kick_from_group.confirm": "طرد", + "confirmations.kick_from_group.heading": "طرد عضو المجموعة", + "confirmations.kick_from_group.message": "هل أنت متأكد أنك تريد طرد @ {name} من هذه المجموعة؟", "confirmations.leave_event.confirm": "الخروج من الحدث", + "confirmations.leave_event.message": "إذا كنت تريد إعادة الانضمام إلى الحدث ، فستتم مراجعة الطلب يدويًا مرة أخرى. هل انت متأكد انك تريد المتابعة؟", + "confirmations.leave_group.confirm": "ترك", + "confirmations.leave_group.heading": "مغادرة المجموعة", + "confirmations.leave_group.message": "أنت على وشك مغادرة المجموعة هل تريد الاستمرار؟?", "confirmations.mute.confirm": "كتم", "confirmations.mute.heading": "كتم @{name}", "confirmations.mute.message": "هل تود حقًا حجب {name}؟", + "confirmations.promote_in_group.confirm": "ترقية", + "confirmations.promote_in_group.message": "هل أنت متأكد أنك تريد الترقية لـ @ {name}؟ لن تكون قادرًا على تخفيض رتبتهم.", "confirmations.redraft.confirm": "إزالة و إعادة الصياغة", "confirmations.redraft.heading": "إزالة وإعادة الصياغة", "confirmations.redraft.message": "هل تود حقًّا حذف المنشور وإعادة صياغته؟ ستفقد التفاعلات والمشاركات المتعلّقة به وستظهر الردود كمنشورات منفصلة. ", @@ -486,6 +545,7 @@ "developers.navigation.network_error_label": "خطأ في الشبكة", "developers.navigation.service_worker_label": "عامل الخدمة", "developers.navigation.settings_store_label": "مجمع الاعدادات", + "developers.navigation.show_toast": "الإخطارات العاجلة", "developers.navigation.test_timeline_label": "تجربة الخط الزمني", "developers.settings_store.advanced": "إعدادات متقدمة", "developers.settings_store.hint": "يمكنك تعديل إعدادت المستخدم من هنا. لكن كن حذرا! تعديل هذا القسم قد يتسبب في تعطيل الحساب، وستتمكن فقط من استرجاع البيانات عن طريق الـ API.", @@ -577,6 +637,7 @@ "empty_column.account_favourited_statuses": "هذا المستخدم لم يحصل على أي إعجاب على منشوراته حتى الآن.", "empty_column.account_timeline": "ليس هناك منشورات!", "empty_column.account_unavailable": "الملف الشخصي غير متوفر", + "empty_column.admin.announcements": "لا توجد إعلانات حتى الآن.", "empty_column.aliases": "لم تقم بإنشاء أية أسماء مستعارة لحسابك حتى الآن.", "empty_column.aliases.suggestions": "لا توجد حسابات مُقترحة للكلمة المُدخلة.", "empty_column.blocks": "لم تقم بحظر أي مستخدِم بعد.", @@ -584,11 +645,16 @@ "empty_column.community": " لا توجد منشورات في بسّام بعد. أكتب شيئا ما للعامة كبداية!", "empty_column.direct": "لم تتلقَ أي رسالة خاصة مباشرة بعد. ستعرض الرسائل المباشرة هنا في حال أرسلت أو تلقيت بعضها.", "empty_column.domain_blocks": "ليس هناك نطاقات مخفية بعد.", + "empty_column.event_participant_requests": "لا توجد طلبات معلقة للمشاركة في الحدث.", + "empty_column.event_participants": "لم ينضم أحد إلى هذا الحدث حتى الآن. عندما يفعل شخص ما ، سوف يظهر هنا.", "empty_column.favourited_statuses": "لم تقم بالإعجاب بأي منشور بعد. عندما تقوم بالإعجاب بواحد، سيظهر هنا.", "empty_column.favourites": "لم يتفاعل أحد مع هذا المنشور. عندما يتفاعل أحد ما سيظهر هنا.", "empty_column.filters": "لم ترشِّح أيّ كلمة بعد.", "empty_column.follow_recommendations": "يبدو أنه لا يمكن إنشاء اقتراحات مناسبة لك. يمكنك استخدام خانة البحث للعثور على أشخاص قد تعرفهم أو استكشاف المواضيع الشائعة عن طريق الوُسوم.", "empty_column.follow_requests": "ليس لديك أي طلب للمتابعة بعد. سوف تظهر الطلبات هنا عندما تتلقى البعض منها.", + "empty_column.group": "لا توجد مشاركات في هذه المجموعة حتى الآن.", + "empty_column.group_blocks": "لم تحظر المجموعة أي مستخدمين حتى الآن.", + "empty_column.group_membership_requests": "لا توجد طلبات عضوية معلقة لهذه المجموعة.", "empty_column.hashtag": "لا يوجد محتوى له عَلاقة بهذا الوسم", "empty_column.home": "لا توجد منشورات على صفحتك الرئسية. قم بزيارة {public} أو استخدم مربع البحث لتكتشف مستخدمين جدد.", "empty_column.home.local_tab": "قم بزيارة الخيط المحلي ل{site_title}", @@ -600,12 +666,16 @@ "empty_column.notifications": "لم تتلق أي إشعار بعدُ. تفاعل مع المستخدمين الآخرين لإنشاء محادثة.", "empty_column.notifications_filtered": "لم تتلقَ أيّ إشعارٍ من هذا النوع بعد.", "empty_column.public": "لا يوجد أي شيء هنا! قم بنشر شيء ما، أو اتبع المستخدمين الآخرين لملء الخط الزمني", + "empty_column.quotes": "لم يتم اقتباس هذا المنشور بعد.", "empty_column.remote": "لا يوجد أي شيء هنا، قم بمتابعة أحد المستخدمين من {instance} لملئ الفراغ.", "empty_column.scheduled_statuses": "ليس لديك أي حالات مجدولة حتى الآن. ستظهر هنا عندما تضيفها.", "empty_column.search.accounts": "لم يتم العثور على تطابق مع {term}", + "empty_column.search.groups": "لم يُعْثَرْ على منشورات لـ \"{term}\"", "empty_column.search.hashtags": "لم يتم العثور على وُسوم لـ \"{term}\"", "empty_column.search.statuses": "لم يتم العثور على منشورات لـ \"{term}\"", "empty_column.test": "الخط الزمني للاختبار فارغ.", + "event.banner": "لافتة الأحداث", + "event.copy": "نسخ الرابط إلى الحدث", "event.date": "التاريخ", "event.description": "الوصف", "event.discussion.empty": "ليس هناك تعليقات. عندما يقوم أحدهم بالتعليق، سيظهر التعليق هنا.", @@ -613,16 +683,23 @@ "event.external": "العرض في {domain}", "event.join_state.accept": "ذاهب", "event.join_state.empty": "اشتراك", + "event.join_state.pending": "معلقة", "event.join_state.rejected": "ذاهب", "event.location": "الموقع", "event.manage": "إدارة", "event.organized_by": "قام {name} بتنسيق الحدث", + "event.participants": "{count} {rawCount, plural, one {person} آخرون {people}} يتحدثون", + "event.quote": "اقتباس الحدث", + "event.reblog": "إعادة نشر الحدث", "event.show_on_map": "العرض على الخريطة", + "event.unreblog": "حدث لم يُعَدْ نشره", "event.website": "روابط خارجية", + "event_map.navigate": "التنقل", "events.create_event": "إنشاء حدث", "events.joined_events": "الأحداث المشترك بها", "events.joined_events.empty": "لم تشترك في أي حدث بعد.", "events.recent_events": "الأحداث الأخيرة", + "events.recent_events.empty": "لا توجد أحداث عامة حتى الآن.", "export_data.actions.export": "تصدير", "export_data.actions.export_blocks": "تصدير قائمة الحظر ", "export_data.actions.export_follows": "تصدير المتابعين", @@ -667,6 +744,40 @@ "gdpr.message": "يستخدم {siteTitle} ملفات الكوكيز لدعم الجلسات وهي تعتبر حيوية لكي يعمل الموقع بشكل صحيح.", "gdpr.title": "موقع {siteTitle} يستخدم الكوكيز", "getting_started.open_source_notice": "{code_name} هو برنامَج مفتوح المصدر. يمكنك المساهمة أو الإبلاغ عن الأخطاء على {code_link} (الإصدار {code_version}).", + "group.admin_subheading": "مسؤولي المجموعة", + "group.cancel_request": "إلغاء الطلب", + "group.group_mod_authorize": "قبول", + "group.group_mod_authorize.success": "قُبِلَ @ {name} في المجموعة", + "group.group_mod_block": "حظر @{name} من المجموعة", + "group.group_mod_block.success": "حُظِرَ @{name} من المجموعة", + "group.group_mod_demote": "خفض الرتبة", + "group.group_mod_demote.success": "خفض رتبة @{name} إلى مستخدم المجموعة", + "group.group_mod_kick": "طرد @{name} من المجموعة", + "group.group_mod_kick.success": "طُرِدَ @{name} من المجموعة", + "group.group_mod_promote_admin": "ترقية @{name} إلى مسؤول المجموعة", + "group.group_mod_promote_admin.success": "تمت ترقية @{name} إلى مسؤول المجموعة", + "group.group_mod_promote_mod": "ترقية @{name} إلى مشرف المجموعة", + "group.group_mod_promote_mod.success": "تمت ترقية @{name} إلى مشرف المجموعة", + "group.group_mod_reject": "رفض", + "group.group_mod_reject.success": "مرفوض @{name} من المجموعة", + "group.group_mod_unblock": "رفع الحظر", + "group.group_mod_unblock.success": "أُلْغِيَّ الحظر على @ {name} من المجموعة", + "group.header.alt": "غلاف المجموعة", + "group.join.request_success": "طلب الانضمام للمجموعة", + "group.join.success": "الإنضمام إلى المجموعة", + "group.leave": "غادر المجموعة", + "group.leave.success": "غادر المجموعة", + "group.manage": "إدارة المجموعة", + "group.moderator_subheading": "مشرفو المجموعة", + "group.privacy.locked": "خاص", + "group.privacy.public": "عام", + "group.role.admin": "مسؤول", + "group.role.moderator": "مشرف", + "group.tabs.all": "الكل", + "group.tabs.members": "الأعضاء", + "group.user_subheading": "المستخدمون", + "groups.empty.subtitle": "ابدأ في اكتشاف مجموعات للانضمام إليها أو إنشاء مجموعاتك الخاصة.", + "groups.empty.title": "لا توجد مجموعات حتى الآن", "hashtag.column_header.tag_mode.all": "و {additional}", "hashtag.column_header.tag_mode.any": "أو {additional}", "hashtag.column_header.tag_mode.none": "بدون {additional}", @@ -681,7 +792,6 @@ "home.column_settings.show_replies": "عرض الردود", "icon_button.icons": "الأيقونات", "icon_button.label": "تحديد أيقونة", - "icon_button.not_found": "لا يوجد أيقونات (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "إستيراد", "import_data.actions.import_blocks": "استيراد قائمة الحظر", "import_data.actions.import_follows": "استيراد قائمة المتابعين", @@ -701,6 +811,7 @@ "intervals.full.days": "{number, plural, one {# يوم} other {# أيام}}", "intervals.full.hours": "{number, plural, one {# ساعة} other {# ساعات}}", "intervals.full.minutes": "{number, plural, one {# دقيقة} other {# دقائق}}", + "join_event.hint": "يمكنك إخبار المنظم لماذا تريد المشاركة في هذا الحدث:", "join_event.join": "طلب الانضمام", "join_event.placeholder": "مراسلة منسّق الحدث", "join_event.request_success": "طلب الانضمام للحدث", @@ -769,6 +880,27 @@ "login_external.errors.instance_fail": "حدث خلل ما.", "login_external.errors.network_fail": "فشل الاتصال، ربما هناك إضافة في متصفحك تقوم بحظر الاتصال؟", "login_form.header": "تسجيل الدخول", + "manage_group.blocked_members": "الأعضاء المحظورين", + "manage_group.create": "إنشاء", + "manage_group.delete_group": "حذف المجموعة", + "manage_group.edit_group": "تحرير المجموعة", + "manage_group.edit_success": "تم تحرير المجموعة", + "manage_group.fields.description_label": "الوصف", + "manage_group.fields.description_placeholder": "الوصف", + "manage_group.fields.name_label": "اسم المجموعة (مطلوبة)", + "manage_group.fields.name_placeholder": "اسم المجموعة", + "manage_group.get_started": "لنبدأ!", + "manage_group.next": "التالي", + "manage_group.pending_requests": "الطلبات المعلقة", + "manage_group.privacy.hint": "لا يمكن تغيير هذه الإعدادات لاحقا.", + "manage_group.privacy.label": "إعدادات الخصوصية", + "manage_group.privacy.private.hint": "قابل للاكتشاف. يمكن للمستخدمين الانضمام بعد الموافقة على طلبهم.", + "manage_group.privacy.private.label": "خاص (مطلوب موافقة المالك)", + "manage_group.privacy.public.hint": "قابل للاكتشاف. يمكن لأي شخص الانضمام.", + "manage_group.privacy.public.label": "عام", + "manage_group.submit_success": "تم إنشاء المجموعة", + "manage_group.tagline": "تربطك المجموعات بالآخرين على أساس الاهتمامات المشتركة.", + "manage_group.update": "تحديث", "media_panel.empty_message": "لم يُعثر على أيّة وسائط.", "media_panel.title": "الوسائط", "mfa.confirm.success_message": "تم تأكيد إعدادات المصادقة المتعددة", @@ -804,6 +936,7 @@ "missing_indicator.label": "غير موجود", "missing_indicator.sublabel": "العنصر المطلوب غير موجود", "modals.policy.submit": "القبول والمتابعة", + "modals.policy.updateTitle": "لقد حصلت على أحدث إصدار من {siteTitle}! يُرجى تخصيص بعض الوقت لمراجعة الأشياء الجديدة والمثيرة التي كنا نعمل عليها.", "moderation_overlay.contact": "جهات الاتصال", "moderation_overlay.hide": "إخفاء المحتوى", "moderation_overlay.show": "عرض المحتوى", @@ -834,7 +967,9 @@ "navigation_bar.compose_quote": "اقتباس المنشور", "navigation_bar.compose_reply": "الرد على المنشور", "navigation_bar.create_event": "إنشاء حدث جديد", + "navigation_bar.create_group": "إنشاء مجموعة", "navigation_bar.domain_blocks": "النطاقات المخفية", + "navigation_bar.edit_group": "تحرير المجموعة", "navigation_bar.favourites": "المفضلة", "navigation_bar.filters": "المُرشِّحات", "navigation_bar.follow_requests": "طلبات المتابعة", @@ -846,6 +981,12 @@ "navigation_bar.preferences": "التفضيلات", "navigation_bar.profile_directory": "قائمة الحسابات", "navigation_bar.soapbox_config": "إعدادات Soapbox", + "new_event_panel.action": "انشاء حدث", + "new_event_panel.subtitle": "ألا تستطيع إيجاد ما تبحث عنه؟ جدولة الحدث الخاص بك.", + "new_event_panel.title": "إنشاء حدث جديد", + "new_group_panel.action": "إنشاء مجموعة", + "new_group_panel.subtitle": "ألا تستطيع إيجاد ما تبحث عنه؟ ابدأ مجموعتك الخاصة أو العامة.", + "new_group_panel.title": "إنشاء مجموعة جديدة", "notification.favourite": "أُعجِب {name} بمنشورك", "notification.follow": "قام {name} بمتابعتك", "notification.follow_request": "طلب {name} متابعتك", @@ -856,7 +997,9 @@ "notification.others": "+ {count} {count, plural, one {other} other {others}}", "notification.pleroma:chat_mention": "{name} أرسل لك رسالة", "notification.pleroma:emoji_reaction": "تفاعل {name} مع منشورك", + "notification.pleroma:event_reminder": "يبدأ الحدث الذي تشارك فيه قريبًا", "notification.pleroma:participation_accepted": "تمت الموافقة على طلب انضمامك للحدث", + "notification.pleroma:participation_request": "{name} يريد الانضمام إلى الحدث الخاص بك", "notification.poll": "لقد انتهى استفتاء شاركت فيه", "notification.reblog": "قام {name} بمشاركة منشورك", "notification.status": "{name} نشر للتو", @@ -930,6 +1073,7 @@ "preferences.fields.content_type_label": "صيغة المنشور الافتراضية", "preferences.fields.delete_modal_label": "أظهار إشعار لتأكيد حذف المنشور قبل التنفيذ", "preferences.fields.demetricator_label": "إخفاء إحصاءات المنشورات", + "preferences.fields.demo_hint": "استخدم شعار Soapbox ونظام الألوان الافتراضي. مناسب لأخذ لقطات الشاشة.", "preferences.fields.demo_label": "وضع العرض", "preferences.fields.display_media.default": "اخف الوسائط المصنفة بحساس", "preferences.fields.display_media.hide_all": "اخف جميع الوسائط", @@ -1018,6 +1162,7 @@ "remote_interaction.account_placeholder": "أدخل اسم المستخدم@عنوان الخادم الذي تريد التفاعل منه", "remote_interaction.divider": "أو", "remote_interaction.event_join": "المتابعة في الانضمام", + "remote_interaction.event_join_title": "الانضمام إلى حدث عن بعد", "remote_interaction.favourite": "المتابعة في الإعجاب", "remote_interaction.favourite_title": "الإعجاب بمنشور غير محلّي", "remote_interaction.follow": "المضي قدما في المتابعة", @@ -1039,6 +1184,7 @@ "reply_mentions.reply_empty": "الرد على منشور", "report.block": "حظر {target}", "report.block_hint": "أتريد حظر هذا الحساب أيضًا؟", + "report.chatMessage.context": "عند الإبلاغ عن رسالة مستخدم، سيتم تمرير الرسائل الخمس السابقة والخمس التي تليها إلى فريق الإشراف لدينا لمعرفة السياق.", "report.chatMessage.title": "التبليغ عن الرسالة", "report.confirmation.content": "في حال وجدنا أن هذا الحساب ينتهك {link}، سوف نتخذ أجراءات أخرى. ", "report.confirmation.title": "شكرًا لتقديمك للبلاغ.", @@ -1071,6 +1217,7 @@ "search.placeholder": "بحث", "search_results.accounts": "أشخاص", "search_results.filter_message": "أنت تبحث في @{acct} عن منشورات ", + "search_results.groups": "المجموعات", "search_results.hashtags": "الوسوم", "search_results.statuses": "المنشورات", "security.codes.fail": "فشك تحميل رموز النسخ الإحتياطي", @@ -1161,20 +1308,28 @@ "soapbox_config.hints.promo_panel_icons.link": "قائمة أيقونات بسّام", "soapbox_config.home_footer.meta_fields.label_placeholder": "خانة", "soapbox_config.home_footer.meta_fields.url_placeholder": "الرابط", + "soapbox_config.media_preview_hint": "توفر بعض الخلفيات نسخة محسّنة من الوسائط لعرضها في الجداول الزمنية. ومع ذلك ، قد تكون صور المعاينة هذه صغيرة جدًا بدون تكوين إضافي.", + "soapbox_config.media_preview_label": "تفضيل معاينة الوسائط للصور المصغرة", "soapbox_config.promo_panel.meta_fields.icon_placeholder": "أيقونة", "soapbox_config.promo_panel.meta_fields.label_placeholder": "خانة", "soapbox_config.promo_panel.meta_fields.url_placeholder": "الرابط", "soapbox_config.raw_json_hint": "تحرير ملف JSON بشكل مباشر. التعديلات التي ستقوم بها هناك سوف تطغى وتستبدل الحقول في الأعلى. قم بالضغط على حفظ لتطبيق التغييرات.", "soapbox_config.raw_json_invalid": "غير صالح", "soapbox_config.raw_json_label": "متقدم: تحرير ملف JSON", + "soapbox_config.redirect_root_no_login_hint": "مسار لإعادة توجيه الصفحة الرئيسية عندما لا يقوم المستخدم بتسجيل الدخول.", + "soapbox_config.redirect_root_no_login_label": "إعادة توجيه الصفحة الرئيسية", "soapbox_config.save": "حفظ", "soapbox_config.saved": "تم حفظ إعدادات Soapbox", + "soapbox_config.tile_server_attribution_label": "إسناد مربعات الخرائط", + "soapbox_config.tile_server_label": "خادم نظام الخرائط", "soapbox_config.verified_can_edit_name_label": "السماح للحسابات الموثّقة بتغيير أسمائهم.", "sponsored.info.message": "{siteTitle} يعرض اعلانات لتمويل الخدمة", "sponsored.info.title": "لماذا أرى هذا الإعلان؟", "sponsored.subtitle": "منشور ترويجي", "status.admin_account": "افتح الواجهة الإدارية لـ @{name}", "status.admin_status": "افتح هذا المنشور في واجهة الإشراف", + "status.approval.pending": "بانتظار الموافقة", + "status.approval.rejected": "مرفوض", "status.bookmark": "المحفوظات", "status.bookmarked": "تمت الإضافة للمحفوظات.", "status.cancel_reblog_private": "إلغاء المشاركة", @@ -1184,12 +1339,18 @@ "status.delete": "حذف", "status.detailed_status": "عرض مُفصّل للمحادثة", "status.direct": "رسالة خاصة إلى @{name}", + "status.disabled_replies.group_membership": "يمكن لأعضاء المجموعة فقط الرد", "status.edit": "تحرير", "status.embed": "تضمين", "status.external": "العرض على {domain}", "status.favourite": "تفاعل مع المنشور", "status.filtered": "رُشِّح", + "status.group": "نُشِرَ في {مجموعة}", + "status.group_mod_block": "حظر @{name} من المجموعة", + "status.group_mod_delete": "حذف المشاركة من المجموعة", + "status.group_mod_kick": "طرد @{name} من المجموعة", "status.interactions.favourites": "{count, plural, one {إعجاب واحد} other {إعجاب}}", + "status.interactions.quotes": "{count, plural, one {# صوت} other {# أصوات}}", "status.interactions.reblogs": "{count, plural, one {مشاركة} other {مشاركات}}", "status.load_more": "تحميل المزيد", "status.mention": "ذِكر @{name}", @@ -1248,6 +1409,7 @@ "tabs_bar.all": "الكل", "tabs_bar.dashboard": "لوحة التحكم", "tabs_bar.fediverse": "الكون الفيدرالي الإجتماعي", + "tabs_bar.groups": "المجموعات", "tabs_bar.home": "الرئيسية", "tabs_bar.local": "الخط المحلي", "tabs_bar.more": "المزيد", @@ -1256,7 +1418,13 @@ "tabs_bar.search": "البحث", "tabs_bar.settings": "الإعدادات", "theme_editor.Reset": "إعادة تعيين", - "theme_toggle.dark": "غامق", + "theme_editor.export": "تصدير السمة", + "theme_editor.import": "استيراد السمة", + "theme_editor.import_success": "تم استيراد السمة بنجاح!", + "theme_editor.restore": "استعادة السمة الافتراضية", + "theme_editor.save": "حفظ السمة", + "theme_editor.saved": "تم تحديث السمة!", + "theme_toggle.dark": "داكن", "theme_toggle.light": "فاتح", "theme_toggle.system": "النظام", "thread_login.login": "تسجيل الدخول", @@ -1284,7 +1452,7 @@ "upload_form.description": "وصف لمن لديهم إعاقة بصرية", "upload_form.preview": "مراجعة", "upload_form.undo": "حذف", - "upload_progress.label": "جارِ التحميل...", + "upload_progress.label": "جارِ الرفع…", "video.close": "إغلاق المقطع", "video.download": "تنزيل الملف", "video.exit_fullscreen": "الخروج من وضع ملئ الشاشة", @@ -1292,10 +1460,10 @@ "video.fullscreen": "ملئ الشاشة", "video.hide": "إخفاء المقطع", "video.mute": "كتم الصوت", - "video.pause": "ايقاف مؤقت", + "video.pause": "إيقاف مؤقت", "video.play": "تشغيل", "video.unmute": "تفعيل الصوت", - "waitlist.actions.verify_number": "توثيق رقم الهاتف", - "waitlist.body": "مرحباً بعودتك {title}! لقد كنت على قائمة الانتظار. يرجى تأكيد رقم الهاتف الخاص بك للحصول على وصول فوري لحسابك!", + "waitlist.actions.verify_number": "توثيق رَقْم الهاتف", + "waitlist.body": "مرحباً بعودتك {title}! لقد كنت على قائمة الانتظار. يرجى تأكيد رَقْم هاتفك للوصول الفوري لحسابك!", "who_to_follow.title": "حسابات مقترحة" } diff --git a/app/soapbox/locales/ca.json b/app/soapbox/locales/ca.json index 45cabe805..0fbcbdf26 100644 --- a/app/soapbox/locales/ca.json +++ b/app/soapbox/locales/ca.json @@ -294,7 +294,6 @@ "home.column_settings.show_replies": "Mostrar respostes", "icon_button.icons": "Icones", "icon_button.label": "Selecciona la icona", - "icon_button.not_found": "Sense icones!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Importació", "import_data.actions.import_blocks": "Importa blocs", "import_data.actions.import_follows": "Importa seguits", diff --git a/app/soapbox/locales/cy.json b/app/soapbox/locales/cy.json index b0f265d0f..20f885a78 100644 --- a/app/soapbox/locales/cy.json +++ b/app/soapbox/locales/cy.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Dangos ymatebion", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/de.json b/app/soapbox/locales/de.json index 7ca8d5846..bca4c22f4 100644 --- a/app/soapbox/locales/de.json +++ b/app/soapbox/locales/de.json @@ -40,7 +40,7 @@ "account.posts": "Beiträge", "account.posts_with_replies": "Beiträge und Antworten", "account.profile": "Profil", - "account.profile_external": "Auf Heimdomäne", + "account.profile_external": "Auf Originalseite öffnen", "account.register": "Registrieren", "account.remote_follow": "Von anderer Instanz folgen", "account.remove_from_followers": "Follower entfernen", @@ -737,7 +737,7 @@ "group.group_mod_unblock": "Entblocken", "group.group_mod_unblock.success": "@{name} in der Gruppe entblockt", "group.header.alt": "Gruppentitel", - "group.join": "Gruppe beitreten", + "group.join.public": "Gruppe beitreten", "group.join.request_success": "Mitgliedschaft in der Gruppe angefragt", "group.join.success": "Gruppe beigetreten", "group.leave": "Gruppe verlassen", @@ -746,7 +746,7 @@ "group.moderator_subheading": "Moderator:innen der Gruppe", "group.privacy.locked": "Privat", "group.privacy.public": "Öffentlich", - "group.request_join": "Mitgliedschaft in der Gruppe anfragen", + "group.join.private": "Mitgliedschaft in der Gruppe anfragen", "group.role.admin": "Administrator:in", "group.role.moderator": "Moderator:in", "group.tabs.all": "Alle", @@ -768,7 +768,6 @@ "home.column_settings.show_replies": "Antworten anzeigen", "icon_button.icons": "Icons", "icon_button.label": "Icons auswählen", - "icon_button.not_found": "Keine Icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Importieren", "import_data.actions.import_blocks": "Blockliste importieren", "import_data.actions.import_follows": "Nutzer, denen du folgst, importieren", diff --git a/app/soapbox/locales/el.json b/app/soapbox/locales/el.json index caa29ab89..3019d551a 100644 --- a/app/soapbox/locales/el.json +++ b/app/soapbox/locales/el.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Εμφάνιση απαντήσεων", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/en-Shaw.json b/app/soapbox/locales/en-Shaw.json index 269d0d3b6..e5b2237a4 100644 --- a/app/soapbox/locales/en-Shaw.json +++ b/app/soapbox/locales/en-Shaw.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "𐑖𐑴 𐑮𐑦𐑐𐑤𐑲𐑟", "icon_button.icons": "𐑲𐑒𐑪𐑯𐑟", "icon_button.label": "𐑕𐑦𐑤𐑧𐑒𐑑 𐑲𐑒𐑪𐑯", - "icon_button.not_found": "𐑯𐑴 𐑲𐑒𐑪𐑯𐑟!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "𐑦𐑥𐑐𐑹𐑑", "import_data.actions.import_blocks": "𐑦𐑥𐑐𐑹𐑑 𐑚𐑤𐑪𐑒𐑕", "import_data.actions.import_follows": "𐑦𐑥𐑐𐑹𐑑 𐑓𐑪𐑤𐑴𐑟", diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index dc55b1f83..657ab8d19 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -620,6 +620,7 @@ "email_verifilcation.exists": "This email has already been taken.", "embed.instructions": "Embed this post on your website by copying the code below.", "emoji_button.activity": "Activity", + "emoji_button.add_custom": "Add custom emoji", "emoji_button.custom": "Custom", "emoji_button.flags": "Flags", "emoji_button.food": "Food & Drink", @@ -627,10 +628,19 @@ "emoji_button.nature": "Nature", "emoji_button.not_found": "No emojis found.", "emoji_button.objects": "Objects", + "emoji_button.oh_no": "Oh no!", "emoji_button.people": "People", + "emoji_button.pick": "Pick an emoji…", "emoji_button.recent": "Frequently used", "emoji_button.search": "Search…", "emoji_button.search_results": "Search results", + "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.symbols": "Symbols", "emoji_button.travel": "Travel & Places", "empty_column.account_blocked": "You are blocked by @{accountUsername}.", @@ -745,7 +755,7 @@ "gdpr.title": "{siteTitle} uses cookies", "getting_started.open_source_notice": "{code_name} is open source software. You can contribute or report issues at {code_link} (v{code_version}).", "group.admin_subheading": "Group administrators", - "group.cancel_request": "Cancel request", + "group.cancel_request": "Cancel Request", "group.group_mod_authorize": "Accept", "group.group_mod_authorize.success": "Accepted @{name} to group", "group.group_mod_block": "Block @{name} from group", @@ -763,21 +773,30 @@ "group.group_mod_unblock": "Unblock", "group.group_mod_unblock.success": "Unblocked @{name} from group", "group.header.alt": "Group header", - "group.join": "Join group", + "group.join.private": "Request Access", + "group.join.public": "Join Group", "group.join.request_success": "Requested to join the group", "group.join.success": "Joined the group", - "group.leave": "Leave group", + "group.leave": "Leave Group", "group.leave.success": "Left the group", - "group.manage": "Manage group", + "group.manage": "Manage Group", "group.moderator_subheading": "Group moderators", "group.privacy.locked": "Private", "group.privacy.public": "Public", - "group.request_join": "Request to join group", "group.role.admin": "Admin", "group.role.moderator": "Moderator", "group.tabs.all": "All", "group.tabs.members": "Members", "group.user_subheading": "Users", + "groups.discover.search.no_results.subtitle": "Try searching for another group.", + "groups.discover.search.no_results.title": "No matches found", + "groups.discover.search.placeholder": "Search", + "groups.discover.search.recent_searches.blankslate.subtitle": "Search group names, topics or keywords", + "groups.discover.search.recent_searches.blankslate.title": "No recent searches", + "groups.discover.search.recent_searches.clear_all": "Clear all", + "groups.discover.search.recent_searches.title": "Recent searches", + "groups.discover.search.results.groups": "Groups", + "groups.discover.search.results.member_count": "{members, plural, one {member} other {members}}", "groups.empty.subtitle": "Start discovering groups to join or create your own.", "groups.empty.title": "No Groups yet", "hashtag.column_header.tag_mode.all": "and {additional}", @@ -794,7 +813,6 @@ "home.column_settings.show_replies": "Show replies", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", @@ -987,9 +1005,9 @@ "new_event_panel.action": "Create event", "new_event_panel.subtitle": "Can't find what you're looking for? Schedule your own event.", "new_event_panel.title": "Create New Event", - "new_group_panel.action": "Create group", + "new_group_panel.action": "Create Group", "new_group_panel.subtitle": "Can't find what you're looking for? Start your own private or public group.", - "new_group_panel.title": "Create New Group", + "new_group_panel.title": "Create Group", "notification.favourite": "{name} liked your post", "notification.follow": "{name} followed you", "notification.follow_request": "{name} has requested to follow you", diff --git a/app/soapbox/locales/es-AR.json b/app/soapbox/locales/es-AR.json index 01db53d44..4b8c123ec 100644 --- a/app/soapbox/locales/es-AR.json +++ b/app/soapbox/locales/es-AR.json @@ -614,7 +614,6 @@ "home.column_settings.show_replies": "Mostrar respuestas", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/es.json b/app/soapbox/locales/es.json index 703c3117e..331605539 100644 --- a/app/soapbox/locales/es.json +++ b/app/soapbox/locales/es.json @@ -85,6 +85,12 @@ "account_search.placeholder": "Buscar una cuenta", "actualStatus.edited": "Editado {date}", "actualStatuses.quote_tombstone": "La publicación no está disponible.", + "admin.announcements.action": "Crear anuncio", + "admin.announcements.all_day": "Todos los días", + "admin.announcements.delete": "Borrar", + "admin.announcements.edit": "Editar", + "admin.announcements.ends_at": "Acaba en:", + "admin.announcements.starts_at": "Empieza en:", "admin.awaiting_approval.approved_message": "¡{acct} ha sido aprobado!", "admin.awaiting_approval.empty_message": "No hay nadie esperando aprobación. Cuando se registre un nuevo usuario, puedes revisarlo aquí.", "admin.awaiting_approval.rejected_message": "{acct} fue rechazado.", @@ -103,6 +109,18 @@ "admin.dashcounters.user_count_label": "usuarios totales", "admin.dashwidgets.email_list_header": "Email list", "admin.dashwidgets.software_header": "Software", + "admin.edit_announcement.created": "Anuncio creado", + "admin.edit_announcement.deleted": "Anuncio borrado", + "admin.edit_announcement.fields.all_day_hint": "Si está marcada, sólo se mostrarán las fechas del intervalo de tiempo", + "admin.edit_announcement.fields.all_day_label": "Todo el día", + "admin.edit_announcement.fields.content_label": "Contenido", + "admin.edit_announcement.fields.content_placeholder": "Contenido del anuncio", + "admin.edit_announcement.fields.end_time_label": "Fecha límite", + "admin.edit_announcement.fields.end_time_placeholder": "El anuncio termina el:", + "admin.edit_announcement.fields.start_time_label": "Fecha de inicio", + "admin.edit_announcement.fields.start_time_placeholder": "El anuncio empieza el:", + "admin.edit_announcement.save": "Guardar", + "admin.edit_announcement.updated": "Anuncio editado", "admin.latest_accounts_panel.more": "Haga clic para ver {contar, plural, una {# cuenta} other {# cuentas}}", "admin.latest_accounts_panel.title": "Cuentas más recientes", "admin.moderation_log.empty_message": "Aún no has realizado ninguna acción de moderación. Cuando lo hagas, se mostrará un historial aquí.", @@ -265,8 +283,11 @@ "chats.main.blankslate_with_chats.subtitle": "Selecciona uno de tus chats abiertos o escribe un mensaje nuevo.", "chats.main.blankslate_with_chats.title": "Seleccionar un chat", "chats.search_placeholder": "Start a chat with…", + "column.admin.announcements": "Anuncios", "column.admin.awaiting_approval": "En espera de aprobación", + "column.admin.create_announcement": "Crear un anuncio", "column.admin.dashboard": "Dashboard", + "column.admin.edit_announcement": "Editar el anuncio", "column.admin.moderation_log": "Registro de moderación", "column.admin.reports": "Reports", "column.admin.reports.menu.moderation_log": "Registro de moderación", @@ -416,6 +437,9 @@ "confirmations.admin.deactivate_user.confirm": "Deactivate @{name}", "confirmations.admin.deactivate_user.heading": "Deactivate @{acct}", "confirmations.admin.deactivate_user.message": "You are about to deactivate @{acct}. Deactivating a user is a reversible action.", + "confirmations.admin.delete_announcement.confirm": "Borrar", + "confirmations.admin.delete_announcement.heading": "Borrar el anuncio", + "confirmations.admin.delete_announcement.message": "¿Seguro que quieres borrar el anuncio?", "confirmations.admin.delete_local_user.checkbox": "I understand that I am about to delete a local user.", "confirmations.admin.delete_status.confirm": "Delete post", "confirmations.admin.delete_status.heading": "Delete post", @@ -596,6 +620,7 @@ "email_verifilcation.exists": "This email has already been taken.", "embed.instructions": "Añade este toot a tu sitio web con el siguiente código.", "emoji_button.activity": "Actividad", + "emoji_button.add_custom": "Añade un emoji personalizado", "emoji_button.custom": "Personalizado", "emoji_button.flags": "Marcas", "emoji_button.food": "Comida y bebida", @@ -603,16 +628,26 @@ "emoji_button.nature": "Naturaleza", "emoji_button.not_found": "No se encontraron emojis :( .", "emoji_button.objects": "Objetos", + "emoji_button.oh_no": "¡Ay, no!", "emoji_button.people": "Gente", + "emoji_button.pick": "Escoge un emoji…", "emoji_button.recent": "Usados frecuentemente", "emoji_button.search": "Buscar…", "emoji_button.search_results": "Resultados de búsqueda", + "emoji_button.skins_1": "Por defecto", + "emoji_button.skins_2": "Claro", + "emoji_button.skins_3": "Claro Medio", + "emoji_button.skins_4": "Medio", + "emoji_button.skins_5": "Oscuro Medio", + "emoji_button.skins_6": "Negro", + "emoji_button.skins_choose": "Elige el tono de la skin predeterminado", "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viajes y lugares", "empty_column.account_blocked": "You are blocked by @{accountUsername}.", "empty_column.account_favourited_statuses": "This user doesn't have any liked posts yet.", "empty_column.account_timeline": "¡No hay toots aquí!", "empty_column.account_unavailable": "Perfil no disponible", + "empty_column.admin.announcements": "Todavía no hay anuncios.", "empty_column.aliases": "You haven't created any account alias yet.", "empty_column.aliases.suggestions": "There are no account suggestions available for the provided term.", "empty_column.blocks": "Aún no has bloqueado a ningún usuario.", @@ -720,7 +755,7 @@ "gdpr.title": "{siteTitle} uses cookies", "getting_started.open_source_notice": "{code_name} es software libre. Puedes contribuir o reportar errores en {code_link} (v{code_version}).", "group.admin_subheading": "Administradores del grupo", - "group.cancel_request": "Cancelar petición", + "group.cancel_request": "Cancelar solicitud", "group.group_mod_authorize": "Aceptar", "group.group_mod_authorize.success": "Aceptado @{name} al grupo", "group.group_mod_block": "Bloquear a @{name} del grupo", @@ -738,21 +773,30 @@ "group.group_mod_unblock": "Desbloquear", "group.group_mod_unblock.success": "Desbloquear a @{name} del grupo", "group.header.alt": "Encabezado del grupo", - "group.join": "Unirse al grupo", + "group.join.private": "Solicitar Acceso", + "group.join.public": "Únete al grupo", "group.join.request_success": "Solicitud de unión al grupo", "group.join.success": "Se unió al grupo", - "group.leave": "Dejar el grupo", + "group.leave": "Dejar el Grupo", "group.leave.success": "Abandonó el grupo", - "group.manage": "Gestionar el grupo", + "group.manage": "Gestionar el Grupo", "group.moderator_subheading": "Moderadores del grupo", "group.privacy.locked": "Privado", "group.privacy.public": "Público", - "group.request_join": "Solicitud de ingreso en el grupo", "group.role.admin": "Administrador", "group.role.moderator": "Moderador", "group.tabs.all": "Todos", "group.tabs.members": "Miembros", "group.user_subheading": "Usuarios", + "groups.discover.search.no_results.subtitle": "Intenta buscar otro grupo.", + "groups.discover.search.no_results.title": "Sin coincidencias", + "groups.discover.search.placeholder": "Buscar", + "groups.discover.search.recent_searches.blankslate.subtitle": "Buscar los nombres de los grupos, temas o palabras clave", + "groups.discover.search.recent_searches.blankslate.title": "Sin búsquedas recientes", + "groups.discover.search.recent_searches.clear_all": "Borrar todo", + "groups.discover.search.recent_searches.title": "Últimas búsquedas", + "groups.discover.search.results.groups": "Grupos", + "groups.discover.search.results.member_count": "{miembros, plural, un {miembro} otro {miembros}}", "groups.empty.subtitle": "Empieza a descubrir los grupos a los que unirte o crea el tuyo propio.", "groups.empty.title": "Aún no hay grupos", "hashtag.column_header.tag_mode.all": "y {additional}", @@ -769,7 +813,6 @@ "home.column_settings.show_replies": "Mostrar respuestas", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", @@ -962,9 +1005,9 @@ "new_event_panel.action": "Crear un evento", "new_event_panel.subtitle": "¿No encuentra lo que busca? Programe su propio evento.", "new_event_panel.title": "Crear un nuevo evento", - "new_group_panel.action": "Crear un grupo", + "new_group_panel.action": "Crear un Grupo", "new_group_panel.subtitle": "¿No encuentra lo que busca? Crea tu propio grupo privado o público.", - "new_group_panel.title": "Crear un nuevo grupo", + "new_group_panel.title": "Crear un Grupo", "notification.favourite": "{name} marcó tu estado como favorito", "notification.follow": "{name} te empezó a seguir", "notification.follow_request": "{name} has requested to follow you", @@ -1442,6 +1485,6 @@ "video.play": "Reproducir", "video.unmute": "Dejar de silenciar sonido", "waitlist.actions.verify_number": "Verify phone number", - "waitlist.body": "Welcome back to {title}! You were previously placed on our waitlist. Please verify your phone number to receive immediate access to your account!", + "waitlist.body": "¡Bienvenido de nuevo a {title}! Usted fue colocado previamente en nuestra lista de espera. ¡Por favor, verifique su número de teléfono para recibir acceso inmediato a su cuenta!", "who_to_follow.title": "Who To Follow" } diff --git a/app/soapbox/locales/fa.json b/app/soapbox/locales/fa.json index c924d9066..0209e1832 100644 --- a/app/soapbox/locales/fa.json +++ b/app/soapbox/locales/fa.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "نمایش پاسخ‌ها", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/fr.json b/app/soapbox/locales/fr.json index aeb89af8d..2a2f8c876 100644 --- a/app/soapbox/locales/fr.json +++ b/app/soapbox/locales/fr.json @@ -705,7 +705,6 @@ "home.column_settings.show_replies": "Afficher les réponses", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "Pas d'icônes ! ! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/ga.json b/app/soapbox/locales/ga.json index 42443e98d..594baa6e9 100644 --- a/app/soapbox/locales/ga.json +++ b/app/soapbox/locales/ga.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Show replies", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/he.json b/app/soapbox/locales/he.json index 5c02f9a4e..a92744cef 100644 --- a/app/soapbox/locales/he.json +++ b/app/soapbox/locales/he.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "הצגת תגובות", "icon_button.icons": "סמלים", "icon_button.label": "בחר סמל", - "icon_button.not_found": "אין סימנים!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "יבא", "import_data.actions.import_blocks": "יבא חסימות", "import_data.actions.import_follows": "יבא מעקבים", diff --git a/app/soapbox/locales/hi.json b/app/soapbox/locales/hi.json index 762142053..a9d6cdc67 100644 --- a/app/soapbox/locales/hi.json +++ b/app/soapbox/locales/hi.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Show replies", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/hr.json b/app/soapbox/locales/hr.json index faaf40012..8ea8e5ebf 100644 --- a/app/soapbox/locales/hr.json +++ b/app/soapbox/locales/hr.json @@ -702,7 +702,6 @@ "home.column_settings.show_replies": "Prikaži odgovore", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Uvezi", "import_data.actions.import_blocks": "Uvezi blokirane korisnike", "import_data.actions.import_follows": "Uvezi korisnike koje pratiš", diff --git a/app/soapbox/locales/hu.json b/app/soapbox/locales/hu.json index 3bd0a1e94..00c2da0c6 100644 --- a/app/soapbox/locales/hu.json +++ b/app/soapbox/locales/hu.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Válaszok mutatása", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/id.json b/app/soapbox/locales/id.json index 13540af6a..b76779427 100644 --- a/app/soapbox/locales/id.json +++ b/app/soapbox/locales/id.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Tampilkan balasan", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/io.json b/app/soapbox/locales/io.json index d86c97a20..27ca818eb 100644 --- a/app/soapbox/locales/io.json +++ b/app/soapbox/locales/io.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Montrar respondi", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/is.json b/app/soapbox/locales/is.json index ffa60f513..c5d0c2a3b 100644 --- a/app/soapbox/locales/is.json +++ b/app/soapbox/locales/is.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Sýna svör", "icon_button.icons": "Táknmyndir", "icon_button.label": "Velja táknmyndir", - "icon_button.not_found": "Engar táknmyndir!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Flytja inn", "import_data.actions.import_blocks": "Flytja inn útilokaða notendur", "import_data.actions.import_follows": "Flytja inn notendur í fylgi", diff --git a/app/soapbox/locales/it.json b/app/soapbox/locales/it.json index e46960f5a..2a851507a 100644 --- a/app/soapbox/locales/it.json +++ b/app/soapbox/locales/it.json @@ -85,12 +85,18 @@ "account_search.placeholder": "Cerca un profilo", "actualStatus.edited": "Modificato: {date}", "actualStatuses.quote_tombstone": "Pubblicazione non disponibile.", + "admin.announcements.action": "Crea un annuncio", + "admin.announcements.all_day": "Tutto il giorno", + "admin.announcements.delete": "Elimina", + "admin.announcements.edit": "Modifica", + "admin.announcements.ends_at": "Termina alle:", + "admin.announcements.starts_at": "Inizia alle:", "admin.awaiting_approval.approved_message": "Approvazione per {acct}!", "admin.awaiting_approval.empty_message": "Nessuno aspetta l'approvazione. Quando nuove persone si iscrivono, puoi approvarle da qui.", "admin.awaiting_approval.rejected_message": "Rifiuto per {acct}.", "admin.dashboard.registration_mode.approval_hint": "Iscrizioni aperte ma l'attivazione previa approvazione dagli amministratori.", "admin.dashboard.registration_mode.approval_label": "Richiesta di approvazione", - "admin.dashboard.registration_mode.closed_hint": "Iscrizioni chiuse. Puoi comunque invitare le persone.", + "admin.dashboard.registration_mode.closed_hint": "Iscrizioni chiuse. Puoi comunque invitare persone.", "admin.dashboard.registration_mode.closed_label": "Chiuse", "admin.dashboard.registration_mode.open_hint": "Può iscriversi chiunque.", "admin.dashboard.registration_mode.open_label": "Aperte", @@ -103,6 +109,18 @@ "admin.dashcounters.user_count_label": "persone totali", "admin.dashwidgets.email_list_header": "Email list", "admin.dashwidgets.software_header": "Software", + "admin.edit_announcement.created": "Hai creato l'annuncio", + "admin.edit_announcement.deleted": "Hai eliminato l'annuncio", + "admin.edit_announcement.fields.all_day_hint": "Mostra solamente le date del settore temporale selezionato", + "admin.edit_announcement.fields.all_day_label": "Durata giornaliera", + "admin.edit_announcement.fields.content_label": "Contenuto", + "admin.edit_announcement.fields.content_placeholder": "Contenuto dell'annuncio", + "admin.edit_announcement.fields.end_time_label": "Data finale", + "admin.edit_announcement.fields.end_time_placeholder": "L'annuncio finisce il:", + "admin.edit_announcement.fields.start_time_label": "Data di inizio", + "admin.edit_announcement.fields.start_time_placeholder": "L'annuncio inizia il:", + "admin.edit_announcement.save": "Salva", + "admin.edit_announcement.updated": "Hai modificato l'annuncio", "admin.latest_accounts_panel.more": "Clicca per vedere {count, plural, one {# profilo} other {# profili}}", "admin.latest_accounts_panel.title": "Ultimi profili", "admin.moderation_log.empty_message": "Non hai ancora moderato nessun profilo. In futuro, qui comparirà lo storico delle moderazioni.", @@ -265,8 +283,11 @@ "chats.main.blankslate_with_chats.subtitle": "Seleziona da una delle chat aperte, oppure crea un nuovo messaggio.", "chats.main.blankslate_with_chats.title": "Seleziona chat", "chats.search_placeholder": "Inizia a chattare con…", + "column.admin.announcements": "Annunci", "column.admin.awaiting_approval": "Attesa approvazione", + "column.admin.create_announcement": "Creazione annunci", "column.admin.dashboard": "Cruscotto", + "column.admin.edit_announcement": "Modifica annuncio", "column.admin.moderation_log": "Eventi di moderazione", "column.admin.reports": "Segnalazioni", "column.admin.reports.menu.moderation_log": "Eventi di moderazione", @@ -280,7 +301,7 @@ "column.app_create": "Creazione App", "column.backups": "Copie di sicurezza", "column.birthdays": "Compleanni", - "column.blocks": "Persone bloccate", + "column.blocks": "Profili bloccati", "column.bookmarks": "Segnalibri", "column.chats": "Chat", "column.community": "Timeline locale", @@ -416,6 +437,9 @@ "confirmations.admin.deactivate_user.confirm": "Disattivare @{name}", "confirmations.admin.deactivate_user.heading": "Disattivazione di @{acct}", "confirmations.admin.deactivate_user.message": "Vuoi davvero disattivare il profilo di @{acct}? Si tratta di una azione irreversibile.", + "confirmations.admin.delete_announcement.confirm": "Elimina", + "confirmations.admin.delete_announcement.heading": "Eliminazione annuncio", + "confirmations.admin.delete_announcement.message": "Vuoi davvero eliminare questo annuncio?", "confirmations.admin.delete_local_user.checkbox": "Sto eliminando un profilo locale.", "confirmations.admin.delete_status.confirm": "Conferma eliminazione pubblicazione", "confirmations.admin.delete_status.heading": "Elimina pubblicazione", @@ -613,6 +637,7 @@ "empty_column.account_favourited_statuses": "Questo profilo non ha ancora preferito alcuna pubblicazione.", "empty_column.account_timeline": "Qui non ci sono pubblicazioni!", "empty_column.account_unavailable": "Profilo non disponibile", + "empty_column.admin.announcements": "Attualmente, non ci sono annunci.", "empty_column.aliases": "Non hai ancora creato alcun alias.", "empty_column.aliases.suggestions": "Non ci sono profili suggeriti per il termine immesso.", "empty_column.blocks": "Non hai ancora bloccato nessuna persona.", @@ -738,7 +763,7 @@ "group.group_mod_unblock": "Sblocca", "group.group_mod_unblock.success": "Hai sbloccato @{name} dal gruppo", "group.header.alt": "Testata del gruppo", - "group.join": "Entra nel gruppo", + "group.join.public": "Entra nel gruppo", "group.join.request_success": "Richiesta di partecipazione", "group.join.success": "Partecipazione nel gruppo", "group.leave": "Abbandona il gruppo", @@ -747,7 +772,7 @@ "group.moderator_subheading": "Moderazione del gruppo", "group.privacy.locked": "Privato", "group.privacy.public": "Pubblico", - "group.request_join": "Richiesta di partecipazione", + "group.join.private": "Richiesta di partecipazione", "group.role.admin": "Amministrazione", "group.role.moderator": "Moderazione", "group.tabs.all": "Tutto", @@ -769,7 +794,6 @@ "home.column_settings.show_replies": "Visualizza le risposte", "icon_button.icons": "Icone", "icon_button.label": "Seleziona icona", - "icon_button.not_found": "Nessuna icona?! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Importazione", "import_data.actions.import_blocks": "Importazione persone bloccate", "import_data.actions.import_follows": "Importazione delle follow", @@ -980,7 +1004,7 @@ "notification.pleroma:participation_request": "{name} vorrebbe partecipare al tuo evento", "notification.poll": "Un sondaggio in cui hai votato è terminato", "notification.reblog": "{name} ha condiviso la tua pubblicazione", - "notification.status": "{name} ha appena scritto", + "notification.status": "{name} ha pubblicato", "notification.update": "{name} ha modificato una pubblicazione a cui hai interagito", "notification.user_approved": "Eccoci su {instance}!", "notifications.filter.all": "Tutte", @@ -1361,7 +1385,7 @@ "status.share": "Condividi", "status.show_less_all": "Mostra meno per tutti", "status.show_more_all": "Mostra di più per tutti", - "status.show_original": "Tradotto", + "status.show_original": "Originale", "status.title": "Pubblicazioni", "status.title_direct": "Messaggio diretto", "status.translate": "Traduzione", diff --git a/app/soapbox/locales/ja.json b/app/soapbox/locales/ja.json index 63dab6402..af935181c 100644 --- a/app/soapbox/locales/ja.json +++ b/app/soapbox/locales/ja.json @@ -36,7 +36,7 @@ "account.mention": "さんに投稿", "account.mute": "@{name}さんをミュート", "account.muted": "ミュートしています", - "account.never_active": "Never", + "account.never_active": "活動なし", "account.posts": "投稿", "account.posts_with_replies": "投稿と返信", "account.profile": "プロフィール", @@ -64,13 +64,13 @@ "account.unsubscribe": "Unsubscribe to notifications from @{name}", "account.unsubscribe.failure": "An error occurred trying to unsubscribe to this account.", "account.unsubscribe.success": "You have unsubscribed from this account.", - "account.verified": "Verified Account", + "account.verified": "認証済みアカウント", "account_gallery.none": "表示するメディアがありません。", "account_moderation_modal.admin_fe": "Open in AdminFE", "account_moderation_modal.fields.account_role": "Staff level", "account_moderation_modal.fields.badges": "Custom badges", - "account_moderation_modal.fields.deactivate": "Deactivate account", - "account_moderation_modal.fields.delete": "Delete account", + "account_moderation_modal.fields.deactivate": "アカウントを無効化", + "account_moderation_modal.fields.delete": "アカウントを削除", "account_moderation_modal.fields.suggested": "Suggested in people to follow", "account_moderation_modal.fields.verified": "Verified account", "account_moderation_modal.info.id": "ID: {id}", @@ -79,11 +79,11 @@ "account_moderation_modal.roles.user": "User", "account_moderation_modal.title": "Moderate @{acct}", "account_note.hint": "You can keep notes about this user for yourself (this will not be shared with them):", - "account_note.placeholder": "No comment provided", - "account_note.save": "Save", + "account_note.placeholder": "コメントはありません", + "account_note.save": "保存", "account_note.target": "Note for @{target}", - "account_search.placeholder": "Search for an account", - "actualStatus.edited": "Edited {date}", + "account_search.placeholder": "アカウントを検索", + "actualStatus.edited": "{date}に編集済", "actualStatuses.quote_tombstone": "Post is unavailable.", "admin.awaiting_approval.approved_message": "{acct} was approved!", "admin.awaiting_approval.empty_message": "There is nobody waiting for approval. When a new user signs up, you can review them here.", @@ -249,7 +249,7 @@ "chats.actions.report": "ユーザを通報", "chats.dividers.today": "Today", "chats.search_placeholder": "Start a chat with…", - "column.admin.awaiting_approval": "Awaiting Approval", + "column.admin.awaiting_approval": "承認待ち", "column.admin.dashboard": "Dashboard", "column.admin.moderation_log": "Moderation Log", "column.admin.reports": "Reports", @@ -314,25 +314,25 @@ "column.migration": "Account migration", "column.mutes": "ミュートしたユーザー", "column.notifications": "通知", - "column.pins": "Pinned posts", + "column.pins": "固定した投稿", "column.preferences": "環境設定", "column.public": "接続しているすべてのネットワーク", "column.reactions": "Reactions", "column.reblogs": "Reposts", "column.scheduled_statuses": "Scheduled Posts", - "column.search": "Search", + "column.search": "検索", "column.settings_store": "Settings store", "column.soapbox_config": "Soapbox設定", "column.test": "Test timeline", - "column_forbidden.body": "You do not have permission to access this page.", + "column_forbidden.body": "このページにアクセスする権限がありません。", "column_forbidden.title": "Forbidden", "common.cancel": "Cancel", "common.error": "Something isn't right. Try reloading the page.", "compare_history_modal.header": "Edit history", "compose.character_counter.title": "Used {chars} out of {maxChars} characters", - "compose.edit_success": "Your post was edited", - "compose.invalid_schedule": "You must schedule a post at least 5 minutes out.", - "compose.submit_success": "Your post was sent", + "compose.edit_success": "あなたの投稿は編集されました", + "compose.invalid_schedule": "投稿を予約するには5分以上空けてください。", + "compose.submit_success": "送信されました!", "compose_form.direct_message_warning": "この投稿はメンションされた人にのみ送信されます。", "compose_form.hashtag_warning": "この投稿は公開設定ではないのでハッシュタグの一覧に表示されません。公開投稿だけがハッシュタグで検索できます。", "compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。", @@ -342,12 +342,12 @@ "compose_form.message": "Message", "compose_form.placeholder": "今なにしてる?", "compose_form.poll.add_option": "追加", - "compose_form.poll.duration": "アンケート期間", - "compose_form.poll.multiselect": "Multi-Select", - "compose_form.poll.multiselect_detail": "Allow users to select multiple answers", - "compose_form.poll.option_placeholder": "項目 {number}", - "compose_form.poll.remove": "Remove poll", - "compose_form.poll.remove_option": "この項目を削除", + "compose_form.poll.duration": "投票期間", + "compose_form.poll.multiselect": "複数選択", + "compose_form.poll.multiselect_detail": "ユーザーが複数の回答を選択できるようにする", + "compose_form.poll.option_placeholder": "回答 {number}", + "compose_form.poll.remove": "アンケートを削除", + "compose_form.poll.remove_option": "この回答を削除", "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices", "compose_form.poll.switch_to_single": "Change poll to allow for a single choice", "compose_form.poll_placeholder": "Add a poll topic...", @@ -382,7 +382,7 @@ "confirmations.admin.reject_user.confirm": "Reject @{name}", "confirmations.admin.reject_user.heading": "Reject @{acct}", "confirmations.admin.reject_user.message": "You are about to reject @{acct} registration request. This action cannot be undone.", - "confirmations.block.block_and_report": "ブロックし通報", + "confirmations.block.block_and_report": "ブロックして通報", "confirmations.block.confirm": "ブロック", "confirmations.block.heading": "Block @{name}", "confirmations.block.message": "本当に{name}さんをブロックしますか?", @@ -404,7 +404,7 @@ "confirmations.redraft.confirm": "削除して下書きに戻す", "confirmations.redraft.heading": "Delete & redraft", "confirmations.redraft.message": "本当にこの投稿を削除して下書きに戻しますか? この投稿へのお気に入り登録やリピートは失われ、返信は孤立することになります。", - "confirmations.register.needs_approval": "Your account will be manually approved by an admin. Please be patient while we review your details.", + "confirmations.register.needs_approval": "あなたのアカウントは管理者によって手動で承認されます。詳細を確認するまでしばらくお待ちください。", "confirmations.register.needs_approval.header": "Approval needed", "confirmations.register.needs_confirmation": "Please check your inbox at {email} for confirmation instructions. You will need to verify your email address to continue.", "confirmations.register.needs_confirmation.header": "Confirmation needed", @@ -449,10 +449,10 @@ "developers.settings_store.advanced": "Advanced settings", "developers.settings_store.hint": "It is possible to directly edit your user settings here. BE CAREFUL! Editing this section can break your account, and you will only be able to recover through the API.", "direct.search_placeholder": "Send a message to…", - "directory.federated": "From known fediverse", - "directory.local": "From {domain} only", - "directory.new_arrivals": "New arrivals", - "directory.recently_active": "Recently active", + "directory.federated": "既知の連合", + "directory.local": "{domain}のみ", + "directory.new_arrivals": "新着", + "directory.recently_active": "最近の活動", "edit_email.header": "Change Email", "edit_email.placeholder": "me@example.com", "edit_federation.followers_only": "Hide posts except to followers", @@ -468,7 +468,7 @@ "edit_profile.fields.avatar_label": "アバター画像", "edit_profile.fields.bio_label": "自己紹介", "edit_profile.fields.bio_placeholder": "Tell us about yourself.", - "edit_profile.fields.birthday_label": "Birthday", + "edit_profile.fields.birthday_label": "誕生日", "edit_profile.fields.birthday_placeholder": "Your birthday", "edit_profile.fields.bot_label": "これはBOTアカウントです", "edit_profile.fields.discoverable_label": "Allow account discovery", @@ -524,7 +524,7 @@ "emoji_button.food": "食べ物", "emoji_button.label": "絵文字を追加", "emoji_button.nature": "自然", - "emoji_button.not_found": "絵文字がなーい!! (╯°□°)╯︵ ┻━┻", + "emoji_button.not_found": "絵文字がありません。", "emoji_button.objects": "物", "emoji_button.people": "人々", "emoji_button.recent": "よく使う絵文字", @@ -532,7 +532,7 @@ "emoji_button.search_results": "検索結果", "emoji_button.symbols": "記号", "emoji_button.travel": "旅行と場所", - "empty_column.account_blocked": "You are blocked by @{accountUsername}.", + "empty_column.account_blocked": "あなたは@{accountUsername}にブロックされています。", "empty_column.account_favourited_statuses": "This user doesn't have any liked posts yet.", "empty_column.account_timeline": "投稿がありません!", "empty_column.account_unavailable": "プロフィールは利用できません", @@ -623,7 +623,6 @@ "home.column_settings.show_replies": "返信表示", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "インポート", "import_data.actions.import_blocks": "ブロックをインポート", "import_data.actions.import_follows": "フォローをインポート", @@ -855,7 +854,7 @@ "poll_button.add_poll": "アンケートを追加", "poll_button.remove_poll": "アンケートを削除", "preferences.fields.auto_play_gif_label": "アニメGIFを自動再生する", - "preferences.fields.auto_play_video_label": "Auto-play videos", + "preferences.fields.auto_play_video_label": "動画を自動再生する", "preferences.fields.autoload_more_label": "Automatically load more items when scrolled to the bottom of the page", "preferences.fields.autoload_timelines_label": "Automatically load new posts when scrolled to the top of the page", "preferences.fields.boost_modal_label": "リピートする前に確認ダイアログを表示する", diff --git a/app/soapbox/locales/kk.json b/app/soapbox/locales/kk.json index 39ba506b2..e8de9619a 100644 --- a/app/soapbox/locales/kk.json +++ b/app/soapbox/locales/kk.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Жауаптарды көрсету", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/ko.json b/app/soapbox/locales/ko.json index 047f2a2ef..6d276a180 100644 --- a/app/soapbox/locales/ko.json +++ b/app/soapbox/locales/ko.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "답글 표시", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/lt.json b/app/soapbox/locales/lt.json index aab1a63bd..17337f628 100644 --- a/app/soapbox/locales/lt.json +++ b/app/soapbox/locales/lt.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Show replies", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/lv.json b/app/soapbox/locales/lv.json index 772d6c592..aefe70479 100644 --- a/app/soapbox/locales/lv.json +++ b/app/soapbox/locales/lv.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Show replies", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/mk.json b/app/soapbox/locales/mk.json index 01996c641..e530680dd 100644 --- a/app/soapbox/locales/mk.json +++ b/app/soapbox/locales/mk.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Show replies", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/ms.json b/app/soapbox/locales/ms.json index bbf8b0a49..c8a659c39 100644 --- a/app/soapbox/locales/ms.json +++ b/app/soapbox/locales/ms.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Show replies", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/nn.json b/app/soapbox/locales/nn.json index b6da84206..1fbf905bb 100644 --- a/app/soapbox/locales/nn.json +++ b/app/soapbox/locales/nn.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Vis svar", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/no.json b/app/soapbox/locales/no.json index 8ad8eaacf..a9b1e5679 100644 --- a/app/soapbox/locales/no.json +++ b/app/soapbox/locales/no.json @@ -1,200 +1,296 @@ { - "about.also_available": "Available in:", - "accordion.collapse": "Collapse", - "accordion.expand": "Expand", - "account.add_or_remove_from_list": "Add or Remove from lists", - "account.badges.bot": "Bot", - "account.birthday": "Born {date}", - "account.birthday_today": "Birthday is today!", - "account.block": "Blokkér @{name}", + "about.also_available": "Tilgjengelig i:", + "accordion.collapse": "Kollapse", + "accordion.expand": "Utivd", + "account.add_or_remove_from_list": "Legg til eller fjern fra lister", + "account.badges.bot": "Data", + "account.birthday": "Født {date}", + "account.birthday_today": "Bursdag idag!", + "account.block": "Blokker @{name}", "account.block_domain": "Skjul alt fra {domain}", - "account.blocked": "Blocked", - "account.chat": "Chat with @{name}", - "account.deactivated": "Deactivated", - "account.direct": "Direct Message @{name}", - "account.domain_blocked": "Domain hidden", + "account.blocked": "Blokkert", + "account.chat": "Start samtale med @{name}", + "account.deactivated": "Deaktivert", + "account.direct": "Direktemelding @{name}", + "account.domain_blocked": "Domene skjult", "account.edit_profile": "Rediger profil", - "account.endorse": "Feature on profile", + "account.endorse": "Funksjon på profil", "account.endorse.success": "You are now featuring @{acct} on your profile", "account.familiar_followers": "Fulgt av {accounts}", "account.familiar_followers.empty": "Ingen av dere følger {name}.", - "account.familiar_followers.more": "{count} {count, plural, one {other} other {others}} you follow", + "account.familiar_followers.more": "{count} {count, plural, one {other} other {others}} du følger", "account.follow": "Følg", "account.followers": "Følgere", "account.followers.empty": "Ingen følger denne brukeren.", "account.follows": "Følger", "account.follows.empty": "Denne brukeren følger ingen enda.", "account.follows_you": "Følger deg", - "account.header.alt": "Profile header", - "account.hide_reblogs": "Skjul fremhevinger fra @{name}", + "account.header.alt": "Profiloverskrift", + "account.hide_reblogs": "Skjul reposteringer fra @{navn}", "account.last_status": "Sist aktiv", - "account.link_verified_on": "Ownership of this link was checked on {date}", - "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.", - "account.login": "Log in", + "account.link_verified_on": "Eierskapet til denne lenken ble kontrollert den {dato}", + "account.locked_info": "Denne kontoens personvernstatus er satt til låst. Eieren vurderer manuelt hvem som kan følge dem.", + "account.login": "Logg inn", "account.media": "Media", - "account.member_since": "Joined {date}", + "account.member_since": "Ble med {date}", "account.mention": "Nevn", "account.mute": "Demp @{name}", - "account.muted": "Muted", - "account.never_active": "Never", + "account.muted": "Stille", + "account.never_active": "Aldri", "account.posts": "Innlegg", - "account.posts_with_replies": "Toots with replies", - "account.profile": "Profile", - "account.profile_external": "View profile on {domain}", - "account.register": "Sign up", - "account.remote_follow": "Remote follow", - "account.remove_from_followers": "Remove this follower", - "account.report": "Rapportér @{name}", - "account.requested": "Venter på godkjennelse", - "account.requested_small": "Awaiting approval", - "account.search": "Search from @{name}", - "account.search_self": "Search your posts", - "account.share": "Del @{name}s profil", + "account.posts_with_replies": "Innlegg & svar", + "account.profile": "Profil", + "account.profile_external": "Vis profilen på {domain}", + "account.register": "Registrer deg", + "account.remote_follow": "Fjernfølg", + "account.remove_from_followers": "Fjern denne følgeren", + "account.report": "Rapporter @{name}", + "account.requested": "Venter på godkjennelse. Trykk for å avbryte forespørselen", + "account.requested_small": "Venter på godkjennelse", + "account.rss_feed": "Abonner på RSS strøm", + "account.search": "Søk fra @{name}", + "account.search_self": "Søk i dine innlegg", + "account.share": "Del @{name}'s profil", "account.show_reblogs": "Vis reposts fra @{name}", - "account.subscribe": "Subscribe to notifications from @{name}", - "account.subscribe.failure": "An error occurred trying to subscribe to this account.", - "account.subscribe.success": "You have subscribed to this account.", + "account.subscribe": "Abonner på varsler fra @{name}", + "account.subscribe.failure": "En feil oppsto med å abonnere på denne kontoen.", + "account.subscribe.success": "Du har abonnert på denne kontoen.", "account.unblock": "Avblokker @{name}", "account.unblock_domain": "Vis {domain}", - "account.unendorse": "Don't feature on profile", - "account.unendorse.success": "You are no longer featuring @{acct}", + "account.unendorse": "Ikke fremhev på profilen", + "account.unendorse.success": "Du fremhever ikke lengre @{acct}", "account.unfollow": "Avfølg", "account.unmute": "Avdemp @{name}", - "account.unsubscribe": "Unsubscribe to notifications from @{name}", - "account.unsubscribe.failure": "An error occurred trying to unsubscribe to this account.", - "account.unsubscribe.success": "You have unsubscribed from this account.", + "account.unsubscribe": "Avslutt abonnementet på varsler fra @{name}", + "account.unsubscribe.failure": "Det oppstod en feil da du prøvde å avslutte abonnementet på denne kontoen.", + "account.unsubscribe.success": "Du har avsluttet abonnementet på denne kontoen.", "account.verified": "Bekreftet konto", - "account_gallery.none": "No media to show.", - "account_moderation_modal.admin_fe": "Open in AdminFE", - "account_moderation_modal.fields.account_role": "Staff level", - "account_moderation_modal.fields.badges": "Custom badges", - "account_moderation_modal.fields.deactivate": "Deactivate account", - "account_moderation_modal.fields.delete": "Delete account", - "account_moderation_modal.fields.suggested": "Suggested in people to follow", + "account_gallery.none": "Ingen media å vise.", + "account_moderation_modal.admin_fe": "Åpne i AdminFE", + "account_moderation_modal.fields.account_role": "Personalnivå", + "account_moderation_modal.fields.badges": "Egendefinerte merker", + "account_moderation_modal.fields.deactivate": "Deaktiver konto", + "account_moderation_modal.fields.delete": "Slett konto", + "account_moderation_modal.fields.suggested": "Foreslått hos personer å følge", "account_moderation_modal.fields.verified": "Bekreftet konto", "account_moderation_modal.info.id": "ID: {id}", "account_moderation_modal.roles.admin": "Admin", "account_moderation_modal.roles.moderator": "Moderator", - "account_moderation_modal.roles.user": "User", + "account_moderation_modal.roles.user": "Bruker", "account_moderation_modal.title": "Moderer @{acct}", - "account_note.hint": "You can keep notes about this user for yourself (this will not be shared with them):", - "account_note.placeholder": "No comment provided", - "account_note.save": "Save", - "account_note.target": "Note for @{target}", - "account_search.placeholder": "Search for an account", - "actualStatus.edited": "Edited {date}", - "actualStatuses.quote_tombstone": "Utilgjengelig post.", - "admin.awaiting_approval.approved_message": "{acct} ble godkjent.", - "admin.awaiting_approval.empty_message": "There is nobody waiting for approval. When a new user signs up, you can review them here.", + "account_note.hint": "Du kan holde notater om denne brukeren for deg selv (dette vil ikke bli delt med dem):", + "account_note.placeholder": "Ingen kommentarer gitt", + "account_note.save": "Lagre", + "account_note.target": "Notat for @{target}", + "account_search.placeholder": "Søk etter en konto", + "actualStatus.edited": "Redigert {date}", + "actualStatuses.quote_tombstone": "Utilgjengelig innlegg.", + "admin.announcements.action": "Opprett kunngjøring", + "admin.announcements.all_day": "Hele dagen", + "admin.announcements.delete": "Slett", + "admin.announcements.edit": "Rediger", + "admin.announcements.ends_at": "Slutter på:", + "admin.announcements.starts_at": "Starter på:", + "admin.awaiting_approval.approved_message": "{acct} ble godkjent!", + "admin.awaiting_approval.empty_message": "Det er ingen som venter på godkjenning. Når en ny bruker registrerer seg, kan du se dem her.", "admin.awaiting_approval.rejected_message": "{acct} ble avslått.", - "admin.dashboard.registration_mode.approval_hint": "Users can sign up, but their account only gets activated when an admin approves it.", + "admin.dashboard.registration_mode.approval_hint": "Brukere kan registrere seg, men kontoen deres blir bare aktivert når en administrator godkjenner den.", "admin.dashboard.registration_mode.approval_label": "Godkjenning kreves", - "admin.dashboard.registration_mode.closed_hint": "Nobody can sign up. You can still invite people.", + "admin.dashboard.registration_mode.closed_hint": "Ingen kan registrere seg. Men du kan invitere personer.", "admin.dashboard.registration_mode.closed_label": "Lukket", "admin.dashboard.registration_mode.open_hint": "Alle kan ta del.", - "admin.dashboard.registration_mode.open_label": "Open", + "admin.dashboard.registration_mode.open_label": "Åpne", "admin.dashboard.registration_mode_label": "Registreringer", - "admin.dashboard.settings_saved": "Settings saved!", - "admin.dashcounters.domain_count_label": "peers", - "admin.dashcounters.mau_label": "monthly active users", - "admin.dashcounters.retention_label": "user retention", - "admin.dashcounters.status_count_label": "posts", - "admin.dashcounters.user_count_label": "total users", - "admin.dashwidgets.email_list_header": "Email list", - "admin.dashwidgets.software_header": "Software", - "admin.latest_accounts_panel.more": "Click to see {count} {count, plural, one {account} other {accounts}}", - "admin.latest_accounts_panel.title": "Latest Accounts", - "admin.moderation_log.empty_message": "You have not performed any moderation actions yet. When you do, a history will be shown here.", - "admin.reports.actions.close": "Close", - "admin.reports.actions.view_status": "View post", - "admin.reports.empty_message": "There are no open reports. If a user gets reported, they will show up here.", - "admin.reports.report_closed_message": "Report on @{name} was closed", - "admin.reports.report_title": "Report on {acct}", - "admin.statuses.actions.delete_status": "Delete post", - "admin.statuses.actions.mark_status_not_sensitive": "Mark post not sensitive", - "admin.statuses.actions.mark_status_sensitive": "Mark post sensitive", - "admin.statuses.status_deleted_message": "Post by @{acct} was deleted", - "admin.statuses.status_marked_message_not_sensitive": "Post by @{acct} was marked not sensitive", - "admin.statuses.status_marked_message_sensitive": "Post by @{acct} was marked sensitive", + "admin.dashboard.settings_saved": "Innstillinger lagret!", + "admin.dashcounters.domain_count_label": "likemenn", + "admin.dashcounters.mau_label": "månedlig aktive brukere", + "admin.dashcounters.retention_label": "oppbevaring av brukere", + "admin.dashcounters.status_count_label": "innlegg", + "admin.dashcounters.user_count_label": "totalt antall brukere", + "admin.dashwidgets.email_list_header": "E-postliste", + "admin.dashwidgets.software_header": "Programvare", + "admin.edit_announcement.created": "Kunngjøring opprettet", + "admin.edit_announcement.deleted": "Kunngjøring slettet", + "admin.edit_announcement.fields.all_day_hint": "Når det er merket av, vises bare datoene for tidsperioden", + "admin.edit_announcement.fields.all_day_label": "Heldagsarrangement", + "admin.edit_announcement.fields.content_label": "Innhold", + "admin.edit_announcement.fields.content_placeholder": "Kunngjøringens innhold", + "admin.edit_announcement.fields.end_time_label": "Sluttdato", + "admin.edit_announcement.fields.end_time_placeholder": "Kunngjøringen avsluttes den:", + "admin.edit_announcement.fields.start_time_label": "Startdato", + "admin.edit_announcement.fields.start_time_placeholder": "Kunngjøringen starter den:", + "admin.edit_announcement.save": "Lagre", + "admin.edit_announcement.updated": "Kunngjøring redigert", + "admin.latest_accounts_panel.more": "Klikk for å se {count} {count, plural, one {account} other {accounts}}", + "admin.latest_accounts_panel.title": "Siste kontoer", + "admin.moderation_log.empty_message": "Du har ikke utført noen modereringshandlinger ennå. Når du gjør det, vises en historikk her.", + "admin.reports.actions.close": "Lukk", + "admin.reports.actions.view_status": "Se innlegget", + "admin.reports.empty_message": "Det er ingen åpne rapporter. Hvis en bruker blir rapportert, vil de dukke opp her.", + "admin.reports.report_closed_message": "Rapport om @{navn} ble avsluttet", + "admin.reports.report_title": "Rapport om {acct}", + "admin.software.backend": "Baksiden", + "admin.software.frontend": "Framsiden", + "admin.statuses.actions.delete_status": "Slett innlegg", + "admin.statuses.actions.mark_status_not_sensitive": "Merk innlegget som ikke sensitivt", + "admin.statuses.actions.mark_status_sensitive": "Merk innlegget som sensitivt", + "admin.statuses.status_deleted_message": "Innlegg av @{acct} ble slettet", + "admin.statuses.status_marked_message_not_sensitive": "Innlegg av @{acct} ble merket som ikke sensitivt", + "admin.statuses.status_marked_message_sensitive": "Innlegg av @{acct} ble slettet", + "admin.theme.title": "Tema", "admin.user_index.empty": "Fant ingen brukere.", "admin.user_index.search_input_placeholder": "Hvem ser du etter?", "admin.users.actions.deactivate_user": "Deaktiver @{name}", "admin.users.actions.delete_user": "Delete @{name}", - "admin.users.actions.demote_to_moderator_message": "@{acct} was demoted to a moderator", - "admin.users.actions.demote_to_user_message": "@{acct} was demoted to a regular user", - "admin.users.actions.promote_to_admin_message": "@{acct} was promoted to an admin", - "admin.users.actions.promote_to_moderator_message": "@{acct} was promoted to a moderator", - "admin.users.badges_saved_message": "Custom badges updated.", - "admin.users.remove_donor_message": "@{acct} was removed as a donor", - "admin.users.set_donor_message": "@{acct} was set as a donor", - "admin.users.user_deactivated_message": "@{acct} was deactivated", - "admin.users.user_deleted_message": "@{acct} was deleted", - "admin.users.user_suggested_message": "@{acct} was suggested", - "admin.users.user_unsuggested_message": "@{acct} was unsuggested", - "admin.users.user_unverified_message": "@{acct} was unverified", - "admin.users.user_verified_message": "@{acct} was verified", - "admin_nav.awaiting_approval": "Awaiting Approval", + "admin.users.actions.demote_to_moderator_message": "@{acct} ble degradert til moderator", + "admin.users.actions.demote_to_user_message": "@{acct} ble degradert til en vanlig bruker", + "admin.users.actions.promote_to_admin_message": "@{acct} ble forfremmet til administrator", + "admin.users.actions.promote_to_moderator_message": "@{acct} ble forfremmet til moderator", + "admin.users.badges_saved_message": "Egendefinerte merker oppdatert.", + "admin.users.remove_donor_message": "@{acct} ble fjernet som donor", + "admin.users.set_donor_message": "@{acct} ble satt som donor", + "admin.users.user_deactivated_message": "@{acct} ble deaktivert", + "admin.users.user_deleted_message": "@{acct} ble slettet", + "admin.users.user_suggested_message": "@{acct} ble foreslått", + "admin.users.user_unsuggested_message": "@{acct} ble ikke foreslått", + "admin.users.user_unverified_message": "@{acct} ble ubekreftet", + "admin.users.user_verified_message": "@{acct} ble bekreftet", + "admin_nav.awaiting_approval": "Venteliste", "admin_nav.dashboard": "Dashboard", - "admin_nav.reports": "Reports", - "age_verification.body": "{siteTitle} requires users to be at least {ageMinimum} years old to access its platform. Anyone under the age of {ageMinimum} years old cannot access this platform.", - "age_verification.fail": "You must be {ageMinimum, plural, one {# year} other {# years}} old or older.", - "age_verification.header": "Enter your birth date", - "alert.unexpected.body": "We're sorry for the interruption. If the problem persists, please reach out to our support team. You may also try to {clearCookies} (this will log you out).", - "alert.unexpected.browser": "Browser", - "alert.unexpected.clear_cookies": "clear cookies and browser data", - "alert.unexpected.links.help": "Help Center", + "admin_nav.reports": "Rapporter", + "age_verification.body": "{siteTitle} krever at brukerne er minst {ageMinimum, plural, one {# year} other {# years}} år for å få tilgang til plattformen. Alle under en alder av {ageMinimum, plural, one {# year} other {# years}} gammel kan ikke få tilgang til denne plattformen.", + "age_verification.fail": "Du må være {ageMinimum, plural, one {# year} other {# years}} gammel eller eldre.", + "age_verification.header": "Skriv inn fødselsdatoen din", + "alert.unexpected.body": "Vi beklager avbruddet. Hvis problemet vedvarer, vennligst ta kontakt med vårt kundestøtteteam. Du kan også prøve å {clearCookies} (dette vil logge deg ut).", + "alert.unexpected.browser": "Nettleser", + "alert.unexpected.clear_cookies": "slett informasjonskapsler og nettleserdata", + "alert.unexpected.links.help": "Hjelpesenter", "alert.unexpected.links.status": "Status", - "alert.unexpected.links.support": "Support", - "alert.unexpected.message": "An unexpected error occurred.", - "alert.unexpected.return_home": "Return Home", + "alert.unexpected.links.support": "Støtte", + "alert.unexpected.message": "Noe gikk galt.", + "alert.unexpected.return_home": "Gå Hjem", "aliases.account.add": "Opprett alias", - "aliases.account_label": "Old account:", - "aliases.aliases_list_delete": "Unlink alias", - "aliases.search": "Search your old account", - "aliases.success.add": "Account alias created successfully", - "aliases.success.remove": "Account alias removed successfully", - "announcements.title": "Announcements", - "app_create.name_label": "Programnavn", - "app_create.name_placeholder": "e.g. 'Soapbox'", - "app_create.redirect_uri_label": "Redirect URIs", - "app_create.restart": "Create another", - "app_create.results.app_label": "Program", - "app_create.results.explanation_text": "You created a new app and token! Please copy the credentials somewhere; you will not see them again after navigating away from this page.", - "app_create.results.explanation_title": "App created successfully", + "aliases.account_label": "Gammel konto:", + "aliases.aliases_list_delete": "Koble fra alias", + "aliases.search": "Søk i din gamle konto", + "aliases.success.add": "Kontoalias opprettet med suksess", + "aliases.success.remove": "Kontoalias fjernet med suksess", + "announcements.title": "Kunngjøringer", + "app_create.name_label": "Appnavn", + "app_create.name_placeholder": "eks. 'Soapbox'", + "app_create.redirect_uri_label": "Omdiriger URIer", + "app_create.restart": "Opprett en annen", + "app_create.results.app_label": "Applikasjon", + "app_create.results.explanation_text": "Du har opprettet en ny app og token! Vennligst kopier påloggingsinformasjonen et sted; du vil ikke se den igjen etter at du har navigert bort fra denne siden.", + "app_create.results.explanation_title": "Applikasjon opprettet med suksess", "app_create.results.token_label": "OAuth token", - "app_create.scopes_label": "Scopes", - "app_create.scopes_placeholder": "e.g. 'read write follow'", - "app_create.submit": "Create app", - "app_create.website_label": "Website", - "auth.invalid_credentials": "Wrong username or password", - "auth.logged_out": "Logged out.", + "app_create.scopes_label": "Omfang", + "app_create.scopes_placeholder": "f.eks. 'lese skrive følge'", + "app_create.submit": "Opprett applikasjon", + "app_create.website_label": "Nettsted", + "auth.awaiting_approval": "Kontoen din venter på godkjenning", + "auth.invalid_credentials": "Feil brukernavn eller passord", + "auth.logged_out": "Logget ut.", "auth_layout.register": "Opprett en konto", "backups.actions.create": "Opprett sikkerhetskopi", - "backups.empty_message": "No backups found. {action}", - "backups.empty_message.action": "Create one now?", - "backups.pending": "Pending", - "badge_input.placeholder": "Enter a badge…", - "birthday_panel.title": "Birthdays", - "birthdays_modal.empty": "None of your friends have birthday today.", - "boost_modal.combo": "You kan trykke {combo} for å hoppe over dette neste gang", - "boost_modal.title": "Repost?", - "bundle_column_error.body": "Noe gikk galt mens denne komponenten lastet.", + "backups.empty_message": "Ingen sikkerhetskopier funnet. {action}", + "backups.empty_message.action": "Opprette en nå?", + "backups.pending": "Avventer", + "badge_input.placeholder": "Skriv inn et merke…", + "birthday_panel.title": "Bursdager", + "birthdays_modal.empty": "Ingen av vennene dine har bursdag i dag.", + "boost_modal.combo": "Du kan trykke på {combo} for å hoppe over dette neste gang", + "boost_modal.title": "Repostere?", + "bundle_column_error.body": "Noe gikk galt under innlastingen av denne siden.", "bundle_column_error.retry": "Prøv igjen", - "bundle_column_error.title": "Network error", + "bundle_column_error.title": "Nettverksfeil", "bundle_modal_error.close": "Lukk", - "bundle_modal_error.message": "Noe gikk galt da denne komponenten lastet.", + "bundle_modal_error.message": "Noe gikk galt mens denne komponenten lastet.", "bundle_modal_error.retry": "Prøv igjen", "card.back.label": "Tilbake", + "chat.actions.send": "Send", + "chat.failed_to_send": "Meldingen kunne ikke sendes.", + "chat.input.placeholder": "Skriv en melding", + "chat.new_message.title": "Ny Melding", + "chat.page_settings.accepting_messages.label": "Tillatt andre brukere å starte ny samtale med deg", + "chat.page_settings.play_sounds.label": "Spill av lyd når du mottar en melding", + "chat.page_settings.preferences": "Innstillinger", + "chat.page_settings.privacy": "Personvern", + "chat.page_settings.submit": "Lagre", + "chat.page_settings.title": "Meldingsinnstillinger", + "chat.retry": "Prøv igjen?", + "chat.welcome.accepting_messages.label": "La brukere starte ny samtale med deg", + "chat.welcome.notice": "Du kan endre disse innstillingene senere.", + "chat.welcome.submit": "Lagre & Gå videre", + "chat.welcome.subtitle": "Utveksle direktemeldinger med andre brukere.", + "chat.welcome.title": "Velkommen til {br} Samtaler!", + "chat_composer.unblock": "Opphev blokkering", + "chat_list_item.blocked_you": "Denne brukeren har blokkert deg", + "chat_list_item.blocking": "Du har blokkert denne brukeren", + "chat_message_list.blocked": "Du har blokkert denne brukeren", + "chat_message_list.blockedBy": "Du er blokkert av", + "chat_message_list.network_failure.action": "Prøv igjen", + "chat_message_list.network_failure.subtitle": "Det har oppstått en nettverksfeil.", + "chat_message_list.network_failure.title": "Ups!", + "chat_message_list_intro.actions.accept": "Aksepter", + "chat_message_list_intro.actions.leave_chat": "Forlat samtalen", + "chat_message_list_intro.actions.message_lifespan": "Meldinger som er eldre enn {day, plural, one {# day} other {# days}} er slettet.", + "chat_message_list_intro.actions.report": "Rapporter", + "chat_message_list_intro.intro": "vil starte en samtale med deg", + "chat_message_list_intro.leave_chat.confirm": "Forlat Samtale", + "chat_message_list_intro.leave_chat.heading": "Forlat Samtale", + "chat_message_list_intro.leave_chat.message": "Er du sikker på at du vil forlate denne samtalen? Meldingene blir slettet og denne samtalen blir fjernet fra innboksen.", + "chat_search.blankslate.body": "Søk etter noen å starte en samtale med.", + "chat_search.blankslate.title": "Start en samtale", + "chat_search.empty_results_blankslate.action": "Send melding til noen", + "chat_search.empty_results_blankslate.body": "Prøv og søk etter et annet navn.", + "chat_search.empty_results_blankslate.title": "Ingen treff funnet", + "chat_search.placeholder": "Skriv inn et navn", + "chat_search.title": "Meldinger", + "chat_settings.auto_delete.14days": "14 dager", + "chat_settings.auto_delete.2minutes": "2 minutter", + "chat_settings.auto_delete.30days": "30 dager", + "chat_settings.auto_delete.7days": "7 dager", + "chat_settings.auto_delete.90days": "90 dager", + "chat_settings.auto_delete.days": "{day, plural, one {# day} andre {# days}}", + "chat_settings.auto_delete.hint": "Sendte meldinger blir slettet automatisk etter den tiden du har valgt", + "chat_settings.auto_delete.label": "Meldinger som er slettet automatisk", + "chat_settings.block.confirm": "Blokker", + "chat_settings.block.heading": "Blokker @{acct}", + "chat_settings.block.message": "Blokkering vil forhindre at denne profilen ikke får sendt deg melding eller se innholdet ditt. Du kan oppheve blokkeringen senere.", + "chat_settings.leave.confirm": "Forlat Samtale", + "chat_settings.leave.heading": "Forlat Samtale", + "chat_settings.leave.message": "Er du sikker på at du vil forlate denne samtalen? Meldinger vil bli slettet for deg, og denne samtalen vil bli fjernet fra innboksen din.", + "chat_settings.options.block_user": "Blokker @{acct}", + "chat_settings.options.leave_chat": "Forlat Samtale", + "chat_settings.options.report_user": "Rapporter @{acct}", + "chat_settings.options.unblock_user": "Opphev blokkering @{acct}", + "chat_settings.title": "Samtale Detaljer", + "chat_settings.unblock.confirm": "Opphev blokkering", + "chat_settings.unblock.heading": "Opphev blokkering", + "chat_settings.unblock.message": "Oppheving av blokkering vil tillate denne profilen å sende deg direktemeldinger og se innholdet ditt.", + "chat_window.auto_delete_label": "Meldinger blir automatisk slettet etter {day, plural, one {# day} other {# days}}", + "chat_window.auto_delete_tooltip": "Samtalemeldinger er satt til automatisk sletting etter {day, plural, one {# day} other {# days}} ved sending.", + "chats.actions.copy": "Kopier", "chats.actions.delete": "Delete message", - "chats.actions.more": "More", + "chats.actions.deleteForMe": "Slett for meg", + "chats.actions.more": "Mer", "chats.actions.report": "Report user", - "chats.dividers.today": "Today", + "chats.dividers.today": "Idag", + "chats.main.blankslate.new_chat": "Start en samtale med noen", + "chats.main.blankslate.subtitle": "Søk etter noen å starte en samtale med", + "chats.main.blankslate.title": "Ingen meldinger enda", + "chats.main.blankslate_with_chats.subtitle": "Velg fra en av de åpne samtalene dine, eller opprett en ny melding.", + "chats.main.blankslate_with_chats.title": "Velg en samtale", "chats.search_placeholder": "Start a chat with…", - "column.admin.awaiting_approval": "Awaiting Approval", + "column.admin.announcements": "Kunngjøring", + "column.admin.awaiting_approval": "Avventer godkjenning", + "column.admin.create_announcement": "Opprett kunngjøring", "column.admin.dashboard": "Dashboard", - "column.admin.moderation_log": "Moderation Log", + "column.admin.edit_announcement": "Rediger kunngjøring", + "column.admin.moderation_log": "Moderering Logg", "column.admin.reports": "Reports", - "column.admin.reports.menu.moderation_log": "Moderation Log", + "column.admin.reports.menu.moderation_log": "Moderering Logg", "column.admin.users": "Brukere", "column.aliases": "Account aliases", "column.aliases.create_error": "Kunne ikke opprette alias", @@ -211,43 +307,50 @@ "column.community": "Lokal tidslinje", "column.crypto_donate": "Doner kryptovaluta", "column.developers": "Utviklere", - "column.developers.service_worker": "Service Worker", - "column.direct": "Direct messages", + "column.developers.service_worker": "Servicearbeider", + "column.direct": "Direktemeldinger", "column.directory": "Utforsk profiler", "column.domain_blocks": "Skjulte domener", "column.edit_profile": "Rediger profil", + "column.event_map": "Sted for arrangementet", + "column.event_participants": "Deltakere på arrangementet", + "column.events": "Hendelse", "column.export_data": "Export data", - "column.familiar_followers": "People you know following {name}", + "column.familiar_followers": "Folk du kjenner følger {name}", "column.favourited_statuses": "Likte innlegg", - "column.favourites": "Likes", - "column.federation_restrictions": "Federation Restrictions", - "column.filters": "Muted words", - "column.filters.add_new": "Add New Filter", - "column.filters.conversations": "Conversations", - "column.filters.create_error": "Error adding filter", - "column.filters.delete": "Delete", - "column.filters.delete_error": "Error deleting filter", - "column.filters.drop_header": "Drop instead of hide", - "column.filters.drop_hint": "Filtered posts will disappear irreversibly, even if filter is later removed", - "column.filters.expires": "Expire after", - "column.filters.expires_hint": "Expiration dates are not currently supported", - "column.filters.home_timeline": "Home timeline", - "column.filters.keyword": "Keyword or phrase", - "column.filters.notifications": "Notifications", - "column.filters.public_timeline": "Public timeline", - "column.filters.subheading_add_new": "Add New Filter", - "column.filters.subheading_filters": "Current Filters", - "column.filters.whole_word_header": "Whole word", - "column.filters.whole_word_hint": "When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word", + "column.favourites": "Liker", + "column.federation_restrictions": "Føderasjonsrestriksjoner", + "column.filters": "Dempede ord", + "column.filters.add_new": "Legg til nytt filter", + "column.filters.conversations": "Samtaler", + "column.filters.create_error": "Feil ved tilføying av filter", + "column.filters.delete": "Slett", + "column.filters.delete_error": "Feil ved sletting av filter", + "column.filters.drop_header": "Slipp i stedet for å gjemme", + "column.filters.drop_hint": "Filtrerte innlegg forsvinner irreversibelt, selv om filteret senere fjernes", + "column.filters.expires": "Utløper etter", + "column.filters.expires_hint": "Utløpsdatoer støttes for øyeblikket ikke", + "column.filters.home_timeline": "Hjemmetidslinje", + "column.filters.keyword": "Nøkkelord eller frase", + "column.filters.notifications": "Varslinger", + "column.filters.public_timeline": "Offentlig tidslinje", + "column.filters.subheading_add_new": "Legg til nytt filter", + "column.filters.subheading_filters": "Nåværende filtre", + "column.filters.whole_word_header": "Hele ord", + "column.filters.whole_word_hint": "Når søkeordet eller frasen bare er alfanumerisk, vil det bare bli brukt hvis det samsvarer med hele ordet", "column.follow_requests": "Følgeforespørsler", - "column.followers": "Followers", - "column.following": "Following", + "column.followers": "Følgere", + "column.following": "Følger", + "column.group_blocked_members": "Blokkerte medlemmer", + "column.group_pending_requests": "Ventende forespørsler", + "column.groups": "Grupper", "column.home": "Hjem", "column.import_data": "Import data", "column.info": "Server information", "column.lists": "Lister", - "column.mentions": "Mentions", - "column.mfa": "Multi-Factor Authentication", + "column.manage_group": "Administrere gruppe", + "column.mentions": "Nevner", + "column.mfa": "Multifaktorautentisering", "column.mfa_cancel": "Avbryt", "column.mfa_confirm_button": "Bekreft", "column.mfa_disable_button": "Skru av", @@ -258,115 +361,171 @@ "column.pins": "Festede innlegg", "column.preferences": "Innstillinger", "column.public": "Felles tidslinje", + "column.quotes": "Legg inn sitater", "column.reactions": "Reaksjoner", - "column.reblogs": "Reposts", - "column.scheduled_statuses": "Scheduled Posts", - "column.search": "Search", - "column.settings_store": "Settings store", - "column.soapbox_config": "Soapbox config", - "column.test": "Test timeline", - "column_forbidden.body": "You do not have permission to access this page.", - "column_forbidden.title": "Forbidden", - "common.cancel": "Cancel", - "common.error": "Something isn't right. Try reloading the page.", - "compare_history_modal.header": "Edit history", - "compose.character_counter.title": "Used {chars} out of {maxChars} characters", - "compose.edit_success": "Your post was edited", - "compose.invalid_schedule": "You must schedule a post at least 5 minutes out.", - "compose.submit_success": "Your post was sent", + "column.reblogs": "Reposter", + "column.scheduled_statuses": "Planlagte innlegg", + "column.search": "Søk", + "column.settings_store": "Innstillingsbutikk", + "column.soapbox_config": "Soapbox konfigurasjon", + "column.test": "Test tidslinje", + "column_forbidden.body": "Du har ikke tilgang til denne siden.", + "column_forbidden.title": "Forbudt", + "common.cancel": "Avbryt", + "common.error": "Det er noe som ikke stemmer. Prøv å laste siden på nytt.", + "compare_history_modal.header": "Rediger historikk", + "compose.character_counter.title": "Brukt {chars} av {maxChars} {maxChars, plural, one {character} andre{characters}}", + "compose.edit_success": "Innlegget ditt ble redigert", + "compose.invalid_schedule": "Du må planlegge et innlegg minst 5 minutter ut.", + "compose.submit_success": "Ditt innlegg ble sendt!", + "compose_event.create": "Skape", + "compose_event.edit_success": "Arrangementet ditt ble redigert", + "compose_event.fields.approval_required": "Jeg vil godkjenne deltager forespørsler manuelt", + "compose_event.fields.banner_label": "Banner for arrangement", + "compose_event.fields.description_hint": "Markdown syntaks er støttet", + "compose_event.fields.description_label": "Arrangementbeskrivelse", + "compose_event.fields.description_placeholder": "Beskrivelse", + "compose_event.fields.end_time_label": "Sluttdato for arrangementet", + "compose_event.fields.end_time_placeholder": "Arrangementet avsluttes den…", + "compose_event.fields.has_end_time": "Arrangementet har sluttdato", + "compose_event.fields.location_label": "Sted for arrangementet", + "compose_event.fields.name_label": "Arrangementsnavn", + "compose_event.fields.name_placeholder": "Navn", + "compose_event.fields.start_time_label": "Arrangementets startdato", + "compose_event.fields.start_time_placeholder": "Arrangementet begynner den…", + "compose_event.participation_requests.authorize": "Autoriser", + "compose_event.participation_requests.authorize_success": "Bruker akseptert", + "compose_event.participation_requests.reject": "Avvis", + "compose_event.participation_requests.reject_success": "Bruker avvist", + "compose_event.reset_location": "Tilbakestill sted", + "compose_event.submit_success": "Arrangementet ditt ble opprettet", + "compose_event.tabs.edit": "Rediger detaljer", + "compose_event.tabs.pending": "Behandle forespørsler", + "compose_event.update": "Oppdater", + "compose_event.upload_banner": "Last opp banner for arrangement", "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.event_placeholder": "Innlegg til dette arrangementet", "compose_form.hashtag_warning": "Denne tuten blir ikke listet under noen emneknagger da den er ulistet. Kun offentlige tuter kan søktes etter med emneknagg.", "compose_form.lock_disclaimer": "Din konto er ikke {locked}. Hvem som helst kan følge deg og se dine private poster.", "compose_form.lock_disclaimer.lock": "låst", - "compose_form.markdown.marked": "Post markdown enabled", - "compose_form.markdown.unmarked": "Post markdown disabled", + "compose_form.markdown.marked": "Innlegg markdown aktivert", + "compose_form.markdown.unmarked": "Innlegg markdown deaktivert", "compose_form.message": "Message", "compose_form.placeholder": "Hva har du på hjertet?", "compose_form.poll.add_option": "Add a choice", - "compose_form.poll.duration": "Poll duration", - "compose_form.poll.multiselect": "Multi-Select", - "compose_form.poll.multiselect_detail": "Allow users to select multiple answers", + "compose_form.poll.duration": "Avstemningens varighet", + "compose_form.poll.multiselect": "Flervalg", + "compose_form.poll.multiselect_detail": "Tillat brukere å velge flere svar", "compose_form.poll.option_placeholder": "Choice {number}", - "compose_form.poll.remove": "Remove poll", + "compose_form.poll.remove": "Fjern avstemning", "compose_form.poll.remove_option": "Delete", "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices", "compose_form.poll.switch_to_single": "Change poll to allow for a single choice", - "compose_form.poll_placeholder": "Add a poll topic...", + "compose_form.poll_placeholder": "Legg til et avstemningsemne...", "compose_form.publish": "Tut", "compose_form.publish_loud": "{publish}!", - "compose_form.save_changes": "Save changes", - "compose_form.schedule": "Schedule", - "compose_form.scheduled_statuses.click_here": "Click here", - "compose_form.scheduled_statuses.message": "You have scheduled posts. {click_here} to see them.", - "compose_form.spoiler.marked": "Text is hidden behind warning", - "compose_form.spoiler.unmarked": "Text is not hidden", + "compose_form.save_changes": "Lagre endringer", + "compose_form.schedule": "Planlegg", + "compose_form.scheduled_statuses.click_here": "Klikk her", + "compose_form.scheduled_statuses.message": "Du har planlagte innlegg. {click_here} for å se dem.", + "compose_form.spoiler.marked": "Teksten er skjult bak advarsel", + "compose_form.spoiler.unmarked": "Teksten er ikke skjult", "compose_form.spoiler_placeholder": "Innholdsadvarsel", - "compose_form.spoiler_remove": "Remove sensitive", - "compose_form.spoiler_title": "Sensitive content", + "compose_form.spoiler_remove": "Fjern følsomhet", + "compose_form.spoiler_title": "Følsomt innhold", "confirmation_modal.cancel": "Avbryt", - "confirmations.admin.deactivate_user.confirm": "Deactivate @{name}", - "confirmations.admin.deactivate_user.heading": "Deactivate @{acct}", - "confirmations.admin.deactivate_user.message": "You are about to deactivate @{acct}. Deactivating a user is a reversible action.", + "confirmations.admin.deactivate_user.confirm": "Deaktiver @{name}", + "confirmations.admin.deactivate_user.heading": "Deaktiver @{acct}", + "confirmations.admin.deactivate_user.message": "Du er i ferd med å deaktivere @{acct}. Deaktivering av en bruker er en reversibel handling.", + "confirmations.admin.delete_announcement.confirm": "Slett", + "confirmations.admin.delete_announcement.heading": "Slett arrangement", + "confirmations.admin.delete_announcement.message": "Er du sikker på at du vil slette dette arrangementet?", "confirmations.admin.delete_local_user.checkbox": "Jeg forstår at jeg er i ferd med å slette en lokal bruker.", "confirmations.admin.delete_status.confirm": "Delete post", "confirmations.admin.delete_status.heading": "Delete post", - "confirmations.admin.delete_status.message": "You are about to delete a post by @{acct}. This action cannot be undone.", + "confirmations.admin.delete_status.message": "Du er i ferd med å slette et innlegg av @{acct}. Denne handlingen kan ikke angres.", "confirmations.admin.delete_user.confirm": "Delete @{name}", "confirmations.admin.delete_user.heading": "Delete @{acct}", - "confirmations.admin.delete_user.message": "You are about to delete @{acct}. THIS IS A DESTRUCTIVE ACTION THAT CANNOT BE UNDONE.", - "confirmations.admin.mark_status_not_sensitive.confirm": "Mark post not sensitive", - "confirmations.admin.mark_status_not_sensitive.heading": "Mark post not sensitive.", - "confirmations.admin.mark_status_not_sensitive.message": "You are about to mark a post by @{acct} not sensitive.", - "confirmations.admin.mark_status_sensitive.confirm": "Mark post sensitive", - "confirmations.admin.mark_status_sensitive.heading": "Mark post sensitive", - "confirmations.admin.mark_status_sensitive.message": "You are about to mark a post by @{acct} sensitive.", - "confirmations.admin.reject_user.confirm": "Reject @{name}", - "confirmations.admin.reject_user.heading": "Reject @{acct}", - "confirmations.admin.reject_user.message": "You are about to reject @{acct} registration request. This action cannot be undone.", + "confirmations.admin.delete_user.message": "Du er i ferd med å slette @{acct}. DETTE ER EN DESTRUKTIV HANDLING SOM IKKE KAN ANGRES.", + "confirmations.admin.mark_status_not_sensitive.confirm": "Merk innlegget som ikke sensitivt", + "confirmations.admin.mark_status_not_sensitive.heading": "Merk innlegget som ikke sensitivt", + "confirmations.admin.mark_status_not_sensitive.message": "Du er i ferd med å merke et innlegg av @{acct} som ikke sensitivt.", + "confirmations.admin.mark_status_sensitive.confirm": "Merk innlegget som sensitivt", + "confirmations.admin.mark_status_sensitive.heading": "Merk innlegget som sensitivt", + "confirmations.admin.mark_status_sensitive.message": "Du er i ferd med å merke et innlegg av @{acct} som sensitivt.", + "confirmations.admin.reject_user.confirm": "Avvis @{navn}", + "confirmations.admin.reject_user.heading": "Avvis @{acct}", + "confirmations.admin.reject_user.message": "Du er i ferd med å avvise @{acct} registreringsforespørsel. Denne handlingen kan ikke angres.", "confirmations.block.block_and_report": "Block & Report", "confirmations.block.confirm": "Blokkèr", "confirmations.block.heading": "Block @{name}", "confirmations.block.message": "Er du sikker på at du vil blokkere {name}?", - "confirmations.cancel_editing.confirm": "Cancel editing", - "confirmations.cancel_editing.heading": "Cancel post editing", - "confirmations.cancel_editing.message": "Are you sure you want to cancel editing this post? All changes will be lost.", + "confirmations.block_from_group.confirm": "Blokker", + "confirmations.block_from_group.heading": "Blokker gruppemedlemmer", + "confirmations.block_from_group.message": "Er du sikker på at du vil blokkere @{name} fra å samhandle med denne gruppen?", + "confirmations.cancel.confirm": "Avbryt", + "confirmations.cancel.heading": "Avbryt innlegg", + "confirmations.cancel.message": "Er du sikker på at du vil avbryte opprettelsen av dette innlegget?", + "confirmations.cancel_editing.confirm": "Avbryt redigering", + "confirmations.cancel_editing.heading": "Avbryt redigering av innlegg", + "confirmations.cancel_editing.message": "Er du sikker på at du vil avbryte redigering av dette innlegget? Alle endringer vil gå tapt.", + "confirmations.cancel_event_editing.heading": "Avbryt redigering av arrangement", + "confirmations.cancel_event_editing.message": "Er du sikker på at du vil avbryte redigeringen av dette arrangementet? Alle endringer vil gå tapt.", "confirmations.delete.confirm": "Slett", "confirmations.delete.heading": "Delete post", "confirmations.delete.message": "Er du sikker på at du vil slette denne statusen?", + "confirmations.delete_event.confirm": "Slett", + "confirmations.delete_event.heading": "Slett arrangement", + "confirmations.delete_event.message": "Er du sikker på at du vil slette dette arrangementet?", + "confirmations.delete_from_group.heading": "Slett fra gruppe", + "confirmations.delete_from_group.message": "Er du sikker på at vil slette @{name}'s innlegg?", + "confirmations.delete_group.confirm": "Slett", + "confirmations.delete_group.heading": "Slett gruppe", + "confirmations.delete_group.message": "Er du sikker på at du vil slette denne gruppa? Dette er en permanent handling som ikke kan angres.", "confirmations.delete_list.confirm": "Delete", "confirmations.delete_list.heading": "Delete list", "confirmations.delete_list.message": "Er du sikker på at du vil slette denne listen permanent?", "confirmations.domain_block.confirm": "Skjul alt fra domenet", "confirmations.domain_block.heading": "Block {domain}", "confirmations.domain_block.message": "Er du sikker på at du vil skjule hele domenet {domain}? I de fleste tilfeller er det bedre med målrettet blokkering eller demping.", + "confirmations.kick_from_group.confirm": "Spark ut", + "confirmations.kick_from_group.heading": "Spark ut gruppemedlem", + "confirmations.kick_from_group.message": "Er du sikker på at du vil sparke ut @{name} fra denne gruppa?", + "confirmations.leave_event.confirm": "Forlat arrangement", + "confirmations.leave_event.message": "Hvis du vil bli med på arrangementet igjen, vil forespørselen bli vurdert manuelt på nytt. Er du sikker på at du vil fortsette?", + "confirmations.leave_group.confirm": "Gå ut", + "confirmations.leave_group.heading": "Gå ut fra gruppe", + "confirmations.leave_group.message": "Du er i ferd med å forlate gruppa. Vil du fortsette?", "confirmations.mute.confirm": "Demp", - "confirmations.mute.heading": "Mute @{name}", + "confirmations.mute.heading": "Demp @{name}", "confirmations.mute.message": "Er du sikker på at du vil dempe {name}?", - "confirmations.redraft.confirm": "Delete & redraft", - "confirmations.redraft.heading": "Delete & redraft", + "confirmations.promote_in_group.confirm": "Fremhev", + "confirmations.promote_in_group.message": "Er du sikker på at du vil forfremme @{name}? Du vil ikke kunne degradere dem.", + "confirmations.redraft.confirm": "Slett & omformuler", + "confirmations.redraft.heading": "Slett & omformuler", "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, reposts and favourites to it.", - "confirmations.register.needs_approval": "Your account will be manually approved by an admin. Please be patient while we review your details.", - "confirmations.register.needs_approval.header": "Approval needed", - "confirmations.register.needs_confirmation": "Please check your inbox at {email} for confirmation instructions. You will need to verify your email address to continue.", - "confirmations.register.needs_confirmation.header": "Confirmation needed", - "confirmations.remove_from_followers.confirm": "Remove", - "confirmations.remove_from_followers.message": "Are you sure you want to remove {name} from your followers?", - "confirmations.reply.confirm": "Reply", - "confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?", - "confirmations.revoke_session.confirm": "Revoke", - "confirmations.revoke_session.heading": "Revoke current session", - "confirmations.revoke_session.message": "You are about to revoke your current session. You will be signed out.", - "confirmations.scheduled_status_delete.confirm": "Cancel", - "confirmations.scheduled_status_delete.heading": "Cancel scheduled post", - "confirmations.scheduled_status_delete.message": "Are you sure you want to cancel this scheduled post?", + "confirmations.register.needs_approval": "Kontoen din vil bli godkjent manuelt av en administrator. Vær tålmodig mens vi går gjennom opplysningene dine.", + "confirmations.register.needs_approval.header": "Godkjenning nødvendig", + "confirmations.register.needs_confirmation": "Sjekk innboksen din på {email} for bekreftelsesinstruksjoner. Du må bekrefte e-postadressen din for å fortsette.", + "confirmations.register.needs_confirmation.header": "Bekreftelse nødvendig", + "confirmations.remove_from_followers.confirm": "Fjerne", + "confirmations.remove_from_followers.message": "Er du sikker på at du vil fjerne {navn} fra dine følgere?", + "confirmations.reply.confirm": "Svar", + "confirmations.reply.message": "Hvis du svarer nå, overskrives meldingen du er i ferd med å skrive. Er du sikker på at du vil fortsette?", + "confirmations.revoke_session.confirm": "Tilbakekall", + "confirmations.revoke_session.heading": "Tilbakekall gjeldende økt", + "confirmations.revoke_session.message": "Du er i ferd med å tilbakekalle den nåværende økten. Du vil bli logget ut.", + "confirmations.scheduled_status_delete.confirm": "Avbryt", + "confirmations.scheduled_status_delete.heading": "Avbryt planlagt innlegg", + "confirmations.scheduled_status_delete.message": "Er du sikker på at du vil avbryte dette planlagte innlegget?", "confirmations.unfollow.confirm": "Slutt å følge", - "crypto_donate.explanation_box.message": "{siteTitle} accepts cryptocurrency donations. You may send a donation to any of the addresses below. Thank you for your support!", - "crypto_donate.explanation_box.title": "Sending cryptocurrency donations", - "crypto_donate_panel.actions.view": "Click to see {count} {count, plural, one {wallet} other {wallets}}", - "crypto_donate_panel.heading": "Donate Cryptocurrency", - "crypto_donate_panel.intro.message": "{siteTitle} accepts cryptocurrency donations to fund our service. Thank you for your support!", + "crypto_donate.explanation_box.message": "{siteTitle} godtar donasjoner av kryptovaluta. Du kan sende en donasjon til hvilken som helst av adressene nedenfor. Takk for støtten!", + "crypto_donate.explanation_box.title": "Sende donasjoner av kryptovaluta", + "crypto_donate_panel.actions.view": "Trykk for å se {count} {count, plural, one {wallet} other {wallets}}", + "crypto_donate_panel.heading": "Doner Kryptovaluta", + "crypto_donate_panel.intro.message": "{siteTitle} tar imot donasjoner i kryptovaluta for å finansiere tjenesten vår. Takk for din støtte!", "datepicker.day": "Day", - "datepicker.hint": "Scheduled to post at…", + "datepicker.hint": "Planlagt å legge ut på…", "datepicker.month": "Måned", "datepicker.next_month": "Neste måned", "datepicker.next_year": "Neste år", @@ -376,88 +535,89 @@ "developers.challenge.answer_label": "Svar", "developers.challenge.answer_placeholder": "Ditt svar", "developers.challenge.fail": "Feil svar", - "developers.challenge.message": "What is the result of calling {function}?", + "developers.challenge.message": "Hva er resultatet av å kalle {function}?", "developers.challenge.submit": "Bli utvikler", "developers.challenge.success": "Du er nå en utvikler", - "developers.leave": "Du har forlatt utviklerlaget", + "developers.leave": "Du har forlatt utviklere", "developers.navigation.app_create_label": "Opprett et program", "developers.navigation.intentional_error_label": "Trigger an error", - "developers.navigation.leave_developers_label": "Forlat utviklerlaget", + "developers.navigation.leave_developers_label": "Forlat utviklere", "developers.navigation.network_error_label": "Network error", - "developers.navigation.service_worker_label": "Service Worker", - "developers.navigation.settings_store_label": "Settings store", - "developers.navigation.test_timeline_label": "Test timeline", + "developers.navigation.service_worker_label": "Servicearbeider", + "developers.navigation.settings_store_label": "Innstillingsbutikk", + "developers.navigation.show_toast": "Utløs Toast", + "developers.navigation.test_timeline_label": "Test tidslinje", "developers.settings_store.advanced": "Avanserte innstillinger", - "developers.settings_store.hint": "It is possible to directly edit your user settings here. BE CAREFUL! Editing this section can break your account, and you will only be able to recover through the API.", + "developers.settings_store.hint": "Det er mulig å redigere brukerinnstillingene dine direkte her. VÆR FORSIKTIG! Redigering av denne delen kan ødelegge kontoen din, og du vil bare kunne gjenopprette den via API-en.", "direct.search_placeholder": "Send a message to…", - "directory.federated": "From known fediverse", - "directory.local": "From {domain} only", - "directory.new_arrivals": "New arrivals", - "directory.recently_active": "Recently active", - "edit_email.header": "Change Email", + "directory.federated": "Fra kjente fediverse", + "directory.local": "Kun fra {domene}", + "directory.new_arrivals": "Nyankomne", + "directory.recently_active": "Nylig aktiv", + "edit_email.header": "Endre E-post", "edit_email.placeholder": "me@example.com", - "edit_federation.followers_only": "Hide posts except to followers", - "edit_federation.force_nsfw": "Force attachments to be marked sensitive", - "edit_federation.media_removal": "Strip media", - "edit_federation.reject": "Reject all activities", - "edit_federation.save": "Save", - "edit_federation.success": "{host} federation was updated", - "edit_federation.unlisted": "Force posts unlisted", - "edit_password.header": "Change Password", - "edit_profile.error": "Profile update failed", - "edit_profile.fields.accepts_email_list_label": "Subscribe to newsletter", + "edit_federation.followers_only": "Skjul innlegg unntatt for følgere", + "edit_federation.force_nsfw": "Tving vedlegg til å bli merket som sensitive", + "edit_federation.media_removal": "Fjern medier", + "edit_federation.reject": "Avvis alle aktiviteter", + "edit_federation.save": "Lagre", + "edit_federation.success": "{host} føderasjonen ble oppdatert", + "edit_federation.unlisted": "Tving innlegg som ikke er oppført", + "edit_password.header": "Endre passord", + "edit_profile.error": "Profiloppdatering mislyktes", + "edit_profile.fields.accepts_email_list_label": "Meld deg på nyhetsbrev", "edit_profile.fields.avatar_label": "Avatar", "edit_profile.fields.bio_label": "Bio", - "edit_profile.fields.bio_placeholder": "Tell us about yourself.", - "edit_profile.fields.birthday_label": "Birthday", - "edit_profile.fields.birthday_placeholder": "Your birthday", - "edit_profile.fields.bot_label": "This is a bot account", - "edit_profile.fields.discoverable_label": "Allow account discovery", - "edit_profile.fields.display_name_label": "Display name", + "edit_profile.fields.bio_placeholder": "Fortell oss om deg selv.", + "edit_profile.fields.birthday_label": "Fødselsdag", + "edit_profile.fields.birthday_placeholder": "Fødselsdagen din", + "edit_profile.fields.bot_label": "Dette er en data konto", + "edit_profile.fields.discoverable_label": "Tillat kontooppdagelse", + "edit_profile.fields.display_name_label": "Visningsnavn", "edit_profile.fields.display_name_placeholder": "Name", "edit_profile.fields.header_label": "Header", - "edit_profile.fields.hide_network_label": "Hide network", - "edit_profile.fields.location_label": "Location", - "edit_profile.fields.location_placeholder": "Location", + "edit_profile.fields.hide_network_label": "Skjul nettverk", + "edit_profile.fields.location_label": "Plassering", + "edit_profile.fields.location_placeholder": "Plassering", "edit_profile.fields.locked_label": "Lock account", "edit_profile.fields.meta_fields.content_placeholder": "Content", "edit_profile.fields.meta_fields.label_placeholder": "Label", - "edit_profile.fields.meta_fields_label": "Profile fields", - "edit_profile.fields.stranger_notifications_label": "Block notifications from strangers", + "edit_profile.fields.meta_fields_label": "Profil felt", + "edit_profile.fields.stranger_notifications_label": "Blokker varsler fra fremmede", "edit_profile.fields.website_label": "Website", - "edit_profile.fields.website_placeholder": "Display a Link", - "edit_profile.header": "Edit Profile", - "edit_profile.hints.accepts_email_list": "Opt-in to news and marketing updates.", - "edit_profile.hints.avatar": "PNG, GIF or JPG. Will be downscaled to {size}", - "edit_profile.hints.bot": "This account mainly performs automated actions and might not be monitored", - "edit_profile.hints.discoverable": "Display account in profile directory and allow indexing by external services", - "edit_profile.hints.header": "PNG, GIF or JPG. Will be downscaled to {size}", - "edit_profile.hints.hide_network": "Who you follow and who follows you will not be shown on your profile", - "edit_profile.hints.locked": "Requires you to manually approve followers", - "edit_profile.hints.meta_fields": "You can have up to {count, plural, one {# custom field} other {# custom fields}} displayed on your profile.", - "edit_profile.hints.stranger_notifications": "Only show notifications from people you follow", - "edit_profile.save": "Save", + "edit_profile.fields.website_placeholder": "Vis en lenke", + "edit_profile.header": "Rediger profil", + "edit_profile.hints.accepts_email_list": "Meld deg på nyheter og markedsføringsoppdateringer.", + "edit_profile.hints.avatar": "PNG, GIF eller JPG. Vil bli nedskalert til {size}.", + "edit_profile.hints.bot": "Denne kontoen utfører hovedsakelig automatiserte handlinger og er kanskje ikke overvåket", + "edit_profile.hints.discoverable": "Vis konto i profilkatalog og tillat indeksering av eksterne tjenester", + "edit_profile.hints.header": "PNG, GIF eller JPG. Vil bli nedskalert til {size}", + "edit_profile.hints.hide_network": "Hvem du følger og hvem som følger deg vil ikke bli vist på profilen din", + "edit_profile.hints.locked": "Krever at du manuelt godkjenner følgere", + "edit_profile.hints.meta_fields": "Du kan ha opptil {count, plural, one {# custom field} other {# custom fields}} vist på profilen din.", + "edit_profile.hints.stranger_notifications": "Vis bare varsler fra personer du følger", + "edit_profile.save": "Lagre", "edit_profile.success": "Profile saved!", - "email_confirmation.success": "Your email has been confirmed!", - "email_passthru.confirmed.body": "Close this tab and continue the registration process on the {bold} from which you sent this email confirmation.", - "email_passthru.confirmed.heading": "Email Confirmed!", - "email_passthru.fail.expired": "Your email token has expired", - "email_passthru.fail.generic": "Unable to confirm your email", - "email_passthru.fail.invalid_token": "Your token is invalid", - "email_passthru.fail.not_found": "Your email token is invalid.", - "email_passthru.generic_fail.body": "Please request a new email confirmation.", - "email_passthru.generic_fail.heading": "Something Went Wrong", - "email_passthru.success": "Your email has been verified!", - "email_passthru.token_expired.body": "Your email token has expired. Please request a new email confirmation from the {bold} from which you sent this email confirmation.", - "email_passthru.token_expired.heading": "Token Expired", - "email_passthru.token_not_found.body": "Your email token was not found. Please request a new email confirmation from the {bold} from which you sent this email confirmation.", - "email_passthru.token_not_found.heading": "Invalid Token", + "email_confirmation.success": "Din e-post er bekreftet!", + "email_passthru.confirmed.body": "Lukk denne fanen og fortsett registreringsprosessen på {bold} som du sendte denne e-postbekreftelsen fra.", + "email_passthru.confirmed.heading": "E-post bekreftet!", + "email_passthru.fail.expired": "E-posttokenet ditt er utløpt", + "email_passthru.fail.generic": "Kan ikke bekrefte e-posten din", + "email_passthru.fail.invalid_token": "Tokenet ditt er ugyldig", + "email_passthru.fail.not_found": "E-posttokenet ditt er ugyldig.", + "email_passthru.generic_fail.body": "Vennligst be om en ny e-postbekreftelse.", + "email_passthru.generic_fail.heading": "Noe gikk galt", + "email_passthru.success": "E-posten din er bekreftet!", + "email_passthru.token_expired.body": "E-posttokenet ditt er utløpt. Be om en ny e-postbekreftelse fra {bold} som du sendte denne e-postbekreftelsen fra.", + "email_passthru.token_expired.heading": "Token Utløpt", + "email_passthru.token_not_found.body": "E-posttokenet ditt ble ikke funnet. Be om en ny e-postbekreftelse fra {bold} som du sendte denne e-postbekreftelsen fra.", + "email_passthru.token_not_found.heading": "Ugyldig Token", "email_verification.email.label": "E-mail address", - "email_verification.fail": "Failed to request email verification.", - "email_verification.header": "Enter your email address", - "email_verification.success": "Verification email sent successfully.", - "email_verification.taken": "is taken", - "email_verifilcation.exists": "This email has already been taken.", + "email_verification.fail": "Kunne ikke be om e-postbekreftelse.", + "email_verification.header": "Skriv inn e-postadressen din", + "email_verification.success": "Bekreftelses e-post sendt.", + "email_verification.taken": "er tatt", + "email_verifilcation.exists": "Denne e-posten er allerede tatt.", "embed.instructions": "Kopier koden under for å bygge inn denne statusen på hjemmesiden din.", "emoji_button.activity": "Aktivitet", "emoji_button.custom": "Tilpasset", @@ -465,127 +625,200 @@ "emoji_button.food": "Mat og drikke", "emoji_button.label": "Sett inn emoji", "emoji_button.nature": "Natur", - "emoji_button.not_found": "Ingen emojojoer!! (╯°□°)╯︵ ┻━┻", + "emoji_button.not_found": "Ingen emojier funnet.", "emoji_button.objects": "Objekter", "emoji_button.people": "Mennesker", "emoji_button.recent": "Hyppig brukt", - "emoji_button.search": "Søk...", + "emoji_button.search": "Søk…", "emoji_button.search_results": "Søkeresultat", "emoji_button.symbols": "Symboler", "emoji_button.travel": "Reise & steder", - "empty_column.account_blocked": "You are blocked by @{accountUsername}.", - "empty_column.account_favourited_statuses": "This user doesn't have any liked posts yet.", - "empty_column.account_timeline": "No posts here!", - "empty_column.account_unavailable": "Profile unavailable", - "empty_column.aliases": "You haven't created any account alias yet.", - "empty_column.aliases.suggestions": "There are no account suggestions available for the provided term.", - "empty_column.blocks": "You haven't blocked any users yet.", - "empty_column.bookmarks": "You don't have any bookmarks yet. When you add one, it will show up here.", + "empty_column.account_blocked": "Du er blokkert av @{accountUsername}.", + "empty_column.account_favourited_statuses": "Denne brukeren har ingen likte innlegg ennå.", + "empty_column.account_timeline": "Ingen innlegg her!", + "empty_column.account_unavailable": "Profilen er utilgjengelig", + "empty_column.admin.announcements": "Det er ingen kunngjøringer ennå.", + "empty_column.aliases": "Du har ikke opprettet noe kontoalias ennå.", + "empty_column.aliases.suggestions": "Det er ingen kontoforslag tilgjengelig for den angitte perioden.", + "empty_column.blocks": "Du har ikke blokkert noen brukere ennå.", + "empty_column.bookmarks": "Du har ingen bokmerker ennå. Når du legger til en, vises den her.", "empty_column.community": "Den lokale tidslinjen er tom. Skriv noe offentlig for å få snøballen til å rulle!", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", - "empty_column.domain_blocks": "There are no hidden domains yet.", + "empty_column.direct": "Du har ingen direktemeldinger ennå. Når du sender eller mottar en, vises den her.", + "empty_column.domain_blocks": "Det er ingen skjulte domener ennå.", + "empty_column.event_participant_requests": "Det er ingen ventende forespørsler om deltakelse på arrangementer.", + "empty_column.event_participants": "Ingen har blitt med på dette arrangementet ennå. Når noen gjør det, vil de dukke opp her.", "empty_column.favourited_statuses": "You don't have any favorite posts yet. When you favorite one, it will show up here.", "empty_column.favourites": "No one has favorited this post yet. When someone does, they will show up here.", - "empty_column.filters": "You haven't created any muted words yet.", - "empty_column.follow_recommendations": "Looks like no suggestions could be generated for you. You can try using search to look for people you might know or explore trending hashtags.", - "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.", + "empty_column.filters": "Du har ikke opprettet noen dempede ord ennå.", + "empty_column.follow_recommendations": "Det ser ut til at ingen forslag kunne genereres for deg. Du kan prøve å bruke søk for å se etter folk du kanskje kjenner, eller utforske populære emneknagger.", + "empty_column.follow_requests": "Du har ingen følgeforespørsler ennå. Når du mottar en, vil den dukke opp her.", + "empty_column.group": "Det er ingen innlegg i denne gruppen ennå.", + "empty_column.group_blocks": "Gruppa har ikke blokkert noen brukere ennå.", + "empty_column.group_membership_requests": "Det er ingen pågående forespørsler om medlemskap i denne gruppa.", "empty_column.hashtag": "Det er ingenting i denne hashtagen ennå.", "empty_column.home": "Du har ikke fulgt noen ennå. Besøk {public} eller bruk søk for å komme i gang og møte andre brukere.", "empty_column.home.local_tab": "the {site_title} tab", - "empty_column.home.subtitle": "{siteTitle} gets more interesting once you follow other users.", - "empty_column.home.title": "You're not following anyone yet", + "empty_column.home.subtitle": "{siteTitle} blir mer interessant når du følger andre brukere.", + "empty_column.home.title": "Du følger ikke noen ennå", "empty_column.list": "Det er ingenting i denne listen ennå. Når medlemmene av denne listen legger ut nye statuser vil de dukke opp her.", - "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.", - "empty_column.mutes": "You haven't muted any users yet.", + "empty_column.lists": "Du har ingen lister ennå. Når du oppretter en, vil den vises her.", + "empty_column.mutes": "Du har ikke ignorert noen brukere ennå.", "empty_column.notifications": "Du har ingen varsler ennå. Kommuniser med andre for å begynne samtalen.", - "empty_column.notifications_filtered": "You don't have any notifications of this type yet.", + "empty_column.notifications_filtered": "Du har ingen varsler av denne typen ennå.", "empty_column.public": "Det er ingenting her! Skriv noe offentlig, eller følg brukere manuelt fra andre instanser for å fylle den opp", - "empty_column.remote": "There is nothing here! Manually follow users from {instance} to fill it up.", - "empty_column.scheduled_statuses": "You don't have any scheduled statuses yet. When you add one, it will show up here.", - "empty_column.search.accounts": "There are no people results for \"{term}\"", - "empty_column.search.hashtags": "There are no hashtags results for \"{term}\"", - "empty_column.search.statuses": "There are no posts results for \"{term}\"", - "empty_column.test": "The test timeline is empty.", + "empty_column.quotes": "Dette innlegget har ikke blitt sitert ennå.", + "empty_column.remote": "Det er ingenting her! Følg brukere manuelt fra {instance} for å fylle den opp.", + "empty_column.scheduled_statuses": "Du har ingen planlagte statuser ennå. Når du legger til en, vil den vises her.", + "empty_column.search.accounts": "Det er ingen personresultater for \"{term}\"", + "empty_column.search.groups": "Det er ingen grupperesultater for \"{term}\"", + "empty_column.search.hashtags": "Det er ingen emneknagg-resultater for \"{term}\"", + "empty_column.search.statuses": "Det er ingen innleggsresultater for \"{term}\"", + "empty_column.test": "Testtidslinjen er tom.", + "event.banner": "Banner for arrangement", + "event.copy": "Kopier link til arrangement", + "event.date": "Dato", + "event.description": "Beskrivelse", + "event.discussion.empty": "Ingen har kommentert dette arrangementet ennå. Når noen gjør det, vil de vises her.", + "event.export_ics": "Eksporter til din kalender", + "event.external": "Vis arrangement på {domain}", + "event.join_state.accept": "Kommer", + "event.join_state.empty": "Delta", + "event.join_state.pending": "Avventer", + "event.join_state.rejected": "Kommer", + "event.location": "Sted", + "event.manage": "Behandle", + "event.organized_by": "Organisert av {name}", + "event.participants": "{count} {rawCount, plural, one {person} other {people}} kommer", + "event.quote": "Arrangement sitat", + "event.reblog": "Reposte arrangement", + "event.show_on_map": "Vis på kart", + "event.unreblog": "Opphev repostering av arrangement", + "event.website": "Eksterne linker", + "event_map.navigate": "Naviger", + "events.create_event": "Opprett arrangement", + "events.joined_events": "Meldte seg på arrangementer", + "events.joined_events.empty": "Du har ikke blitt med på noe arrangement ennå.", + "events.recent_events": "Nylige arrangementer", + "events.recent_events.empty": "Det er ingen offentlige arrangementer ennå.", "export_data.actions.export": "Export", - "export_data.actions.export_blocks": "Export blocks", - "export_data.actions.export_follows": "Export follows", - "export_data.actions.export_mutes": "Export mutes", - "export_data.blocks_label": "Blocks", - "export_data.follows_label": "Follows", - "export_data.hints.blocks": "Get a CSV file containing a list of blocked accounts", - "export_data.hints.follows": "Get a CSV file containing a list of followed accounts", - "export_data.hints.mutes": "Get a CSV file containing a list of muted accounts", - "export_data.mutes_label": "Mutes", - "export_data.success.blocks": "Blocks exported successfully", - "export_data.success.followers": "Followers exported successfully", - "export_data.success.mutes": "Mutes exported successfully", - "federation_restriction.federated_timeline_removal": "Fediverse timeline removal", - "federation_restriction.followers_only": "Hidden except to followers", - "federation_restriction.full_media_removal": "Full media removal", - "federation_restriction.media_nsfw": "Attachments marked NSFW", - "federation_restriction.partial_media_removal": "Partial media removal", - "federation_restrictions.empty_message": "{siteTitle} has not restricted any instances.", - "federation_restrictions.explanation_box.message": "Normally servers on the Fediverse can communicate freely. {siteTitle} has imposed restrictions on the following servers.", - "federation_restrictions.explanation_box.title": "Instance-specific policies", - "federation_restrictions.not_disclosed_message": "{siteTitle} does not disclose federation restrictions through the API.", - "fediverse_tab.explanation_box.dismiss": "Don't show again", - "fediverse_tab.explanation_box.explanation": "{site_title} is part of the Fediverse, a social network made up of thousands of independent social media sites (aka \"servers\"). The posts you see here are from 3rd-party servers. You have the freedom to engage with them, or to block any server you don't like. Pay attention to the full username after the second @ symbol to know which server a post is from. To see only {site_title} posts, visit {local}.", - "fediverse_tab.explanation_box.title": "What is the Fediverse?", - "feed_suggestions.heading": "Suggested Profiles", - "feed_suggestions.view_all": "View all", - "filters.added": "Filter added.", - "filters.context_header": "Filter contexts", - "filters.context_hint": "One or multiple contexts where the filter should apply", - "filters.filters_list_context_label": "Filter contexts:", - "filters.filters_list_drop": "Drop", - "filters.filters_list_hide": "Hide", - "filters.filters_list_phrase_label": "Keyword or phrase:", - "filters.filters_list_whole-word": "Whole word", - "filters.removed": "Filter deleted.", - "followRecommendations.heading": "Suggested Profiles", + "export_data.actions.export_blocks": "Eksporter blokkerte", + "export_data.actions.export_follows": "Eksport følgere", + "export_data.actions.export_mutes": "Eksporter ignorerte", + "export_data.blocks_label": "Blokkerte", + "export_data.follows_label": "Følgere", + "export_data.hints.blocks": "Få en CSV-fil med en liste over blokkerte kontoer", + "export_data.hints.follows": "Få en CSV-fil som inneholder en liste over fulgte kontoer", + "export_data.hints.mutes": "Få en CSV-fil som inneholder en liste over ignorerte kontoer", + "export_data.mutes_label": "Ignorerte", + "export_data.success.blocks": "Blokkerte eksportert vellykket", + "export_data.success.followers": "Følgere eksportert vellykket", + "export_data.success.mutes": "Ignorerte eksportert vellykket", + "federation_restriction.federated_timeline_removal": "Fjerning av Fødiverset tidslinje", + "federation_restriction.followers_only": "Skjult unntatt for følgere", + "federation_restriction.full_media_removal": "Fjern alle medier", + "federation_restriction.media_nsfw": "Vedlegg merket NSFW", + "federation_restriction.partial_media_removal": "Delvis fjerning av medier", + "federation_restrictions.empty_message": "{siteTitle} har ikke begrenset noen forekomster.", + "federation_restrictions.explanation_box.message": "Normalt kan servere på Fødiverset kommunisere fritt. {siteTitle} har innført restriksjoner på følgende servere.", + "federation_restrictions.explanation_box.title": "Forekomstspesifikke retningslinjer", + "federation_restrictions.not_disclosed_message": "{siteTitle} avslører ikke føderasjonsbegrensninger gjennom API-et.", + "fediverse_tab.explanation_box.dismiss": "Ikke vis igjen", + "fediverse_tab.explanation_box.explanation": "{site_title} er en del av Fødiverset, et sosialt nettverk som består av tusenvis av uavhengige sosiale medier (også kalt 'servere'). Innleggene du ser her er fra tredjepartsservere. Du har friheten til å engasjere deg med dem, eller til å blokkere hvilken som helst server du ikke liker. Vær oppmerksom på det fullstendige brukernavnet etter det andre @-symbolet for å vite hvilken server et innlegg er fra. For å se bare {site_title}-innlegg, gå til {local}.", + "fediverse_tab.explanation_box.title": "Hva er Fødiverset?", + "feed_suggestions.heading": "Foreslåtte Profiler", + "feed_suggestions.view_all": "Vis alle", + "filters.added": "Filter lagt til.", + "filters.context_header": "Filtrer kontekster", + "filters.context_hint": "En eller flere kontekster der filteret skal brukes", + "filters.filters_list_context_label": "Filtrer kontekster:", + "filters.filters_list_drop": "Fjern", + "filters.filters_list_hide": "Skjul", + "filters.filters_list_phrase_label": "Nøkkelord eller setning:", + "filters.filters_list_whole-word": "Hele ord", + "filters.removed": "Filter slettet.", + "followRecommendations.heading": "Foreslåtte Profiler", "follow_request.authorize": "Autorisér", "follow_request.reject": "Avvis", - "gdpr.accept": "Accept", - "gdpr.learn_more": "Learn more", - "gdpr.message": "{siteTitle} uses session cookies, which are essential to the website's functioning.", - "gdpr.title": "{siteTitle} uses cookies", + "gdpr.accept": "Godta", + "gdpr.learn_more": "Lær mer", + "gdpr.message": "{siteTitle} bruker øktinformasjonskapsler, som er avgjørende for at nettstedet skal fungere.", + "gdpr.title": "{siteTitle} bruker informasjonskapsler", "getting_started.open_source_notice": "{code_name} er fri programvare. Du kan bidra eller rapportere problemer på GitLab på {code_link} (v{code_version}).", - "hashtag.column_header.tag_mode.all": "and {additional}", + "group.admin_subheading": "Gruppeadministratorer", + "group.cancel_request": "Avbryt forespørsel", + "group.group_mod_authorize": "Aksepter", + "group.group_mod_authorize.success": "Aksepter @{name} til gruppe", + "group.group_mod_block": "Blokker @{name} fra gruppe", + "group.group_mod_block.success": "Blokkert @{navn} fra gruppe", + "group.group_mod_demote": "Degradere @{navn}", + "group.group_mod_demote.success": "Nedgradert @{name} til gruppebruker", + "group.group_mod_kick": "Spark ut @{name} fra gruppa", + "group.group_mod_kick.success": "Sparket ut @{name} fra gruppa", + "group.group_mod_promote_admin": "Forfremme @{name} til gruppeadministrator", + "group.group_mod_promote_admin.success": "Forfremmet @{name} til gruppeadministrator", + "group.group_mod_promote_mod": "Forfrem @{name} til gruppemoderator", + "group.group_mod_promote_mod.success": "Forfremmet @{name} til gruppemoderator", + "group.group_mod_reject": "Avvis", + "group.group_mod_reject.success": "Avvist @{navn} fra gruppen", + "group.group_mod_unblock": "Opphev Blokkering", + "group.group_mod_unblock.success": "Opphevet blokkeringen av @{navn} fra gruppa", + "group.header.alt": "Gruppeoverskrift", + "group.join.request_success": "Har bedt om å bli med i gruppa", + "group.join.success": "Ble med i gruppa", + "group.leave": "Forlat gruppa", + "group.leave.success": "Forlot gruppa", + "group.manage": "Behandle gruppe", + "group.moderator_subheading": "Gruppemoderatorer", + "group.privacy.locked": "Privat", + "group.privacy.public": "Offentlig", + "group.role.admin": "Administrator", + "group.role.moderator": "Moderator", + "group.tabs.all": "Alle", + "group.tabs.members": "Medlemmer", + "group.user_subheading": "Brukere", + "groups.empty.subtitle": "Begynn å oppdage grupper du kan bli med i, eller opprett din egen.", + "groups.empty.title": "Ingen grupper ennå", + "hashtag.column_header.tag_mode.all": "og {additional}", "hashtag.column_header.tag_mode.any": "or {additional}", - "hashtag.column_header.tag_mode.none": "without {additional}", + "hashtag.column_header.tag_mode.none": "uten {additional}", "header.home.label": "Home", - "header.login.forgot_password": "Forgot password?", + "header.login.forgot_password": "Glemt passord?", "header.login.label": "Log in", "header.login.password.label": "Password", - "header.login.username.placeholder": "Email or username", + "header.login.username.placeholder": "E-post eller brukernavn", "header.menu.title": "Open menu", - "header.register.label": "Register", + "header.register.label": "Registrer", "home.column_settings.show_reblogs": "Vis fremhevinger", "home.column_settings.show_replies": "Vis svar", "icon_button.icons": "Icons", - "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", + "icon_button.label": "Velg ikon", "import_data.actions.import": "Import", - "import_data.actions.import_blocks": "Import blocks", - "import_data.actions.import_follows": "Import follows", - "import_data.actions.import_mutes": "Import mutes", - "import_data.blocks_label": "Blocks", - "import_data.follows_label": "Follows", - "import_data.hints.blocks": "CSV file containing a list of blocked accounts", - "import_data.hints.follows": "CSV file containing a list of followed accounts", - "import_data.hints.mutes": "CSV file containing a list of muted accounts", - "import_data.mutes_label": "Mutes", - "import_data.success.blocks": "Blocks imported successfully", - "import_data.success.followers": "Followers imported successfully", - "import_data.success.mutes": "Mutes imported successfully", - "input.copy": "Copy", - "input.password.hide_password": "Hide password", - "input.password.show_password": "Show password", + "import_data.actions.import_blocks": "Importer blokkerte", + "import_data.actions.import_follows": "Importer følgere", + "import_data.actions.import_mutes": "Importer ignorerte", + "import_data.blocks_label": "Blokkerte", + "import_data.follows_label": "Følgere", + "import_data.hints.blocks": "CSV-fil som inneholder en liste over blokkerte kontoer", + "import_data.hints.follows": "CSV-fil som inneholder en liste over fulgte kontoer", + "import_data.hints.mutes": "CSV-fil som inneholder en liste over ignorerte kontoer", + "import_data.mutes_label": "Ignorerte", + "import_data.success.blocks": "Blokkerte ble importert", + "import_data.success.followers": "Følgere ble importert", + "import_data.success.mutes": "Ignorerte ble importert", + "input.copy": "Kopier", + "input.password.hide_password": "Skjul passord", + "input.password.show_password": "Vis passord", "intervals.full.days": "{number, plural, one {# day} other {# days}}", "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}", "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}", + "join_event.hint": "Du kan fortelle arrangøren hvorfor du ønsker å delta på dette arrangementet:", + "join_event.join": "Be om å bli med", + "join_event.placeholder": "Melding til arrangøren", + "join_event.request_success": "Bedt om å bli med på arrangementet", + "join_event.success": "Ble med på arrangementet", + "join_event.title": "Bli med på arrangement", "keyboard_shortcuts.back": "for å navigere tilbake", - "keyboard_shortcuts.blocked": "to open blocked users list", + "keyboard_shortcuts.blocked": "for å åpne listen over blokkerte brukere", "keyboard_shortcuts.boost": "å fremheve", "keyboard_shortcuts.compose": "å fokusere komponeringsfeltet", "keyboard_shortcuts.down": "for å flytte ned i listen", @@ -593,236 +826,275 @@ "keyboard_shortcuts.favourite": "for å favorittmarkere", "keyboard_shortcuts.favourites": "to open favorites list", "keyboard_shortcuts.heading": "Keyboard Shortcuts", - "keyboard_shortcuts.home": "to open home timeline", + "keyboard_shortcuts.home": "for å åpne hjemmetidslinjen", "keyboard_shortcuts.hotkey": "Lyntast", "keyboard_shortcuts.legend": "å vise denne forklaringen", "keyboard_shortcuts.mention": "å nevne forfatter", - "keyboard_shortcuts.muted": "to open muted users list", - "keyboard_shortcuts.my_profile": "to open your profile", - "keyboard_shortcuts.notifications": "to open notifications column", + "keyboard_shortcuts.muted": "for å åpne listen over ignorerte brukere", + "keyboard_shortcuts.my_profile": "for å åpne profilen din", + "keyboard_shortcuts.notifications": "for å åpne varslingskolonnen", "keyboard_shortcuts.open_media": "to open media", - "keyboard_shortcuts.pinned": "to open pinned posts list", - "keyboard_shortcuts.profile": "to open author's profile", + "keyboard_shortcuts.pinned": "for å åpne listen over festede innlegg", + "keyboard_shortcuts.profile": "for å åpne forfatterens profil", "keyboard_shortcuts.react": "to react", "keyboard_shortcuts.reply": "for å svare", - "keyboard_shortcuts.requests": "to open follow requests list", + "keyboard_shortcuts.requests": "for å åpne listen over følgeforespørsler", "keyboard_shortcuts.search": "å fokusere søk", - "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", - "keyboard_shortcuts.toggle_sensitivity": "to show/hide media", + "keyboard_shortcuts.toggle_hidden": "for å vise/skjule tekst bak CW", + "keyboard_shortcuts.toggle_sensitivity": "for å vise/skjule medier", "keyboard_shortcuts.toot": "å starte en helt ny tut", "keyboard_shortcuts.unfocus": "å ufokusere komponerings-/søkefeltet", "keyboard_shortcuts.up": "å flytte opp i listen", "landing_page_modal.download": "Download", - "landing_page_modal.helpCenter": "Help Center", + "landing_page_modal.helpCenter": "Hjelpesenter", "lightbox.close": "Lukk", "lightbox.next": "Neste", "lightbox.previous": "Forrige", "lightbox.view_context": "View context", - "list.click_to_add": "Click here to add people", - "list_adder.header_title": "Add or Remove from Lists", + "list.click_to_add": "Klikk her for å legge til personer", + "list_adder.header_title": "Legg til eller fjern fra lister", "lists.account.add": "Legg til i listen", "lists.account.remove": "Fjern fra listen", "lists.delete": "Delete list", "lists.edit": "Rediger listen", - "lists.edit.submit": "Change title", + "lists.edit.submit": "Endre tittel", "lists.new.create": "Ligg til liste", "lists.new.create_title": "Add list", - "lists.new.save_title": "Save Title", + "lists.new.save_title": "Lagre tittel", "lists.new.title_placeholder": "Ny listetittel", "lists.search": "Søk blant personer du følger", "lists.subheading": "Dine lister", - "loading_indicator.label": "Laster...", - "login.fields.instance_label": "Instance", + "loading_indicator.label": "Laster…", + "location_search.placeholder": "Finn en adresse", + "login.fields.instance_label": "Server", "login.fields.instance_placeholder": "example.com", - "login.fields.otp_code_hint": "Enter the two-factor code generated by your phone app or use one of your recovery codes", - "login.fields.otp_code_label": "Two-factor code:", + "login.fields.otp_code_hint": "Skriv inn tofaktorkoden som genereres av telefonappen din, eller bruk en av gjenopprettingskodene dine", + "login.fields.otp_code_label": "To-faktor kode:", "login.fields.password_placeholder": "Password", - "login.fields.username_label": "Email or username", + "login.fields.username_label": "E-post eller brukernavn", "login.log_in": "Log in", - "login.otp_log_in": "OTP Login", - "login.otp_log_in.fail": "Invalid code, please try again.", - "login.reset_password_hint": "Trouble logging in?", + "login.otp_log_in": "OTP-pålogging", + "login.otp_log_in.fail": "Ugyldig kode, vennligst prøv igjen.", + "login.reset_password_hint": "Problemer med å logge inn?", "login.sign_in": "Sign in", - "login_external.errors.instance_fail": "The instance returned an error.", - "login_external.errors.network_fail": "Connection failed. Is a browser extension blocking it?", + "login_external.errors.instance_fail": "Serveren returnerte en feil.", + "login_external.errors.network_fail": "Tilkoblingen mislyktes. Blokkerer en nettleserutvidelse den?", "login_form.header": "Sign In", - "media_panel.empty_message": "No media found.", + "manage_group.blocked_members": "Blokkerte medlemmer", + "manage_group.create": "Opprette", + "manage_group.delete_group": "Slett gruppe", + "manage_group.edit_group": "Rediger gruppe", + "manage_group.edit_success": "Gruppen ble redigert", + "manage_group.fields.description_label": "Beskrivelse", + "manage_group.fields.description_placeholder": "Beskrivelse", + "manage_group.fields.name_label": "Gruppenavn (påkrevd)", + "manage_group.fields.name_placeholder": "Navn på gruppe", + "manage_group.get_started": "La oss komme i gang!", + "manage_group.next": "Neste", + "manage_group.pending_requests": "Ventende forespørsler", + "manage_group.privacy.hint": "Disse innstillingene kan ikke endres senere.", + "manage_group.privacy.label": "Personverninnstillinger", + "manage_group.privacy.private.hint": "Kan oppdages. Brukere kan bli med etter at forespørselen er godkjent.", + "manage_group.privacy.private.label": "Privat (Eierens godkjenning kreves)", + "manage_group.privacy.public.hint": "Kan oppdages. Alle kan bli med.", + "manage_group.privacy.public.label": "Offentlig", + "manage_group.submit_success": "Gruppa ble opprettet", + "manage_group.tagline": "Grupper setter deg i kontakt med andre basert på felles interesser.", + "manage_group.update": "Oppdater", + "media_panel.empty_message": "Ingen medier funnet.", "media_panel.title": "Media", - "mfa.confirm.success_message": "MFA confirmed", - "mfa.disable.success_message": "MFA disabled", - "mfa.disabled": "Disabled", - "mfa.enabled": "Enabled", - "mfa.mfa_disable_enter_password": "Enter your current password to disable two-factor auth:", - "mfa.mfa_setup.code_hint": "Enter the code from your two-factor app.", + "mfa.confirm.success_message": "MFA bekreftet", + "mfa.disable.success_message": "MFA deaktivert", + "mfa.disabled": "Deaktivert", + "mfa.enabled": "Aktivert", + "mfa.mfa_disable_enter_password": "Skriv inn ditt nåværende passord for å deaktivere tofaktorautentisering.", + "mfa.mfa_setup.code_hint": "Skriv inn koden fra tofaktorappen din.", "mfa.mfa_setup.code_placeholder": "Code", - "mfa.mfa_setup.password_hint": "Enter your current password to confirm your identity.", + "mfa.mfa_setup.password_hint": "Skriv inn ditt nåværende passord for å bekrefte identiteten din.", "mfa.mfa_setup.password_placeholder": "Password", - "mfa.mfa_setup_scan_description": "Using your two-factor app, scan this QR code or enter text key:", - "mfa.mfa_setup_scan_title": "Scan", - "mfa.mfa_setup_verify_title": "Verify", - "mfa.otp_enabled_description": "You have enabled two-factor authentication via OTP.", - "mfa.otp_enabled_title": "OTP Enabled", - "mfa.setup_recoverycodes": "Recovery codes", - "mfa.setup_warning": "Write these codes down or save them somewhere secure - otherwise you won't see them again. If you lose access to your 2FA app and recovery codes you'll be locked out of your account.", - "migration.fields.acct.label": "Handle of the new account", - "migration.fields.acct.placeholder": "username@domain", - "migration.fields.confirm_password.label": "Current password", - "migration.hint": "This will move your followers to the new account. No other data will be moved. To perform migration, you need to {link} on your new account first.", - "migration.hint.cooldown_period": "If you migrate your account, you will not be able to migrate your account for {cooldownPeriod, plural, one {one day} other {the next # days}}.", - "migration.hint.link": "create an account alias", - "migration.move_account.fail": "Account migration failed.", - "migration.move_account.fail.cooldown_period": "You have moved your account too recently. Please try again later.", - "migration.move_account.success": "Account successfully moved.", - "migration.submit": "Move followers", - "missing_description_modal.cancel": "Cancel", + "mfa.mfa_setup_scan_description": "Bruk tofaktorappen din til å skanne denne QR-koden eller skriv inn tekstnøkkelen.", + "mfa.mfa_setup_scan_title": "Skann", + "mfa.mfa_setup_verify_title": "Bekrefte", + "mfa.otp_enabled_description": "Du har aktivert tofaktorautentisering via OTP.", + "mfa.otp_enabled_title": "OTP Aktivert", + "mfa.setup_recoverycodes": "Gjenopprettingskoder", + "mfa.setup_warning": "Skriv ned disse kodene eller lagre dem et sikkert sted - ellers vil du ikke se dem igjen. Hvis du mister tilgangen til 2FA-appen og gjenopprettingskodene dine, blir du låst ute av kontoen din.", + "migration.fields.acct.label": "Håndtering av den nye kontoen", + "migration.fields.acct.placeholder": "brukernavn@domene", + "migration.fields.confirm_password.label": "Gjeldende passord", + "migration.hint": "Dette vil flytte følgerne dine til den nye kontoen. Ingen andre data vil bli flyttet. Hvis du vil utføre overføringen, må du {link} på den nye kontoen først.", + "migration.hint.cooldown_period": "Hvis du overfører kontoen din, vil du ikke kunne overføre kontoen din for {cooldownPeriod, flertall, en {en dag} annen {de neste # dagene}}.", + "migration.hint.link": "opprette et kontoalias", + "migration.move_account.fail": "Kontooverføringen mislyktes.", + "migration.move_account.fail.cooldown_period": "Du har flyttet kontoen din for nylig. Prøv igjen senere.", + "migration.move_account.success": "Kontoen er flyttet.", + "migration.submit": "Flytt følgere", + "missing_description_modal.cancel": "Avbryt", "missing_description_modal.continue": "Post", - "missing_description_modal.description": "Continue anyway?", - "missing_description_modal.text": "You have not entered a description for all attachments. Continue anyway?", + "missing_description_modal.description": "Fortsette likevel?", + "missing_description_modal.text": "Du har ikke lagt inn en beskrivelse for alle vedlegg. Fortsette likevel?", "missing_indicator.label": "Ikke funnet", "missing_indicator.sublabel": "Denne ressursen ble ikke funnet", + "modals.policy.submit": "Godta & Fortsett", + "modals.policy.updateTitle": "Du har fått den nyeste versjonen av {siteTitle}! Ta deg tid til å se gjennom de spennende nye tingene vi har jobbet med.", "moderation_overlay.contact": "Contact", - "moderation_overlay.hide": "Hide content", - "moderation_overlay.show": "Show Content", - "moderation_overlay.subtitle": "This Post has been sent to Moderation for review and is only visible to you. If you believe this is an error please contact Support.", - "moderation_overlay.title": "Content Under Review", - "mute_modal.auto_expire": "Automatically expire mute?", - "mute_modal.duration": "Duration", + "moderation_overlay.hide": "Skjul innhold", + "moderation_overlay.show": "Vis Innhold", + "moderation_overlay.subtitle": "Dette innlegget er sendt til Moderering for gjennomgang og er bare synlig for deg. Hvis du mener at dette er en feil, vennligst kontakt kundeservice.", + "moderation_overlay.title": "Innhold under gjennomgang", + "mute_modal.auto_expire": "Utløper ignorerte automatisk?", + "mute_modal.duration": "Varighet", "mute_modal.hide_notifications": "Skjul varslinger fra denne brukeren?", "navbar.login.action": "Log in", - "navbar.login.forgot_password": "Forgot password?", + "navbar.login.forgot_password": "Glemt passord?", "navbar.login.password.label": "Password", - "navbar.login.username.placeholder": "Email or username", - "navigation.chats": "Chats", - "navigation.compose": "Compose", + "navbar.login.username.placeholder": "E-post eller brukernavn", + "navigation.chats": "Samtaler", + "navigation.compose": "Skriv", "navigation.dashboard": "Dashboard", - "navigation.developers": "Developers", + "navigation.developers": "Utviklere", "navigation.direct_messages": "Messages", "navigation.home": "Home", "navigation.notifications": "Notifications", - "navigation.search": "Search", + "navigation.search": "Søk", "navigation_bar.account_aliases": "Account aliases", - "navigation_bar.account_migration": "Move account", + "navigation_bar.account_migration": "Flytt konto", "navigation_bar.blocks": "Blokkerte brukere", "navigation_bar.compose": "Compose new post", - "navigation_bar.compose_direct": "Direct message", - "navigation_bar.compose_edit": "Edit post", - "navigation_bar.compose_quote": "Quote post", - "navigation_bar.compose_reply": "Reply to post", + "navigation_bar.compose_direct": "Direktemeldinger", + "navigation_bar.compose_edit": "Rediger innlegg", + "navigation_bar.compose_event": "Administrere arrangement", + "navigation_bar.compose_quote": "Siter innlegg", + "navigation_bar.compose_reply": "Svare på innlegget", + "navigation_bar.create_event": "Opprett nytt arrangement", + "navigation_bar.create_group": "Opprett gruppe", "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.edit_group": "Rediger Gruppe", "navigation_bar.favourites": "Favoritter", "navigation_bar.filters": "Muted words", "navigation_bar.follow_requests": "Følgeforespørsler", "navigation_bar.import_data": "Import data", - "navigation_bar.in_reply_to": "In reply to", - "navigation_bar.invites": "Invites", + "navigation_bar.in_reply_to": "Som svar på", + "navigation_bar.invites": "Inviterer", "navigation_bar.logout": "Logg ut", "navigation_bar.mutes": "Dempede brukere", "navigation_bar.preferences": "Preferanser", - "navigation_bar.profile_directory": "Profile directory", - "navigation_bar.soapbox_config": "Soapbox config", + "navigation_bar.profile_directory": "Profilkatalog", + "navigation_bar.soapbox_config": "Soapbox konfigurasjon", + "new_event_panel.action": "Opprett arrangement", + "new_event_panel.subtitle": "Finner du ikke det du leter etter? Planlegg ditt eget arrangement.", + "new_event_panel.title": "Opprett nytt arrangement", + "new_group_panel.action": "Opprett gruppe", + "new_group_panel.subtitle": "Finner du ikke det du leter etter? Start din egen private eller offentlige gruppe.", + "new_group_panel.title": "Opprett ny gruppe", "notification.favourite": "{name} likte din status", "notification.follow": "{name} fulgte deg", - "notification.follow_request": "{name} has requested to follow you", + "notification.follow_request": "{name} har bedt om å følge deg", "notification.mention": "{name} nevnte deg", - "notification.mentioned": "{name} mentioned you", - "notification.move": "{name} moved to {targetName}", + "notification.mentioned": "{name} nevnte deg", + "notification.move": "{name} flyttet til {targetName}", "notification.name": "{link}{others}", "notification.others": " + {count} {count, plural, one {other} other {others}}", - "notification.pleroma:chat_mention": "{name} sent you a message", - "notification.pleroma:emoji_reaction": "{name} reacted to your post", - "notification.poll": "A poll you have voted in has ended", + "notification.pleroma:chat_mention": "{name} sendte deg en melding", + "notification.pleroma:emoji_reaction": "{name} reagerte på innlegget ditt", + "notification.pleroma:event_reminder": "Et arrangement du deltar på starter snart", + "notification.pleroma:participation_accepted": "Du ble akseptert til å bli med på arrangementet", + "notification.pleroma:participation_request": "{name} ønsker å bli med på arrangementet ditt", + "notification.poll": "En avstemning du har stemt i er avsluttet", "notification.reblog": "{name} fremhevde din status", - "notification.status": "{name} just posted", - "notification.update": "{name} edited a post you interacted with", - "notification.user_approved": "Welcome to {instance}!", - "notifications.filter.all": "All", - "notifications.filter.boosts": "Reposts", - "notifications.filter.emoji_reacts": "Emoji reacts", + "notification.status": "{name} har nettopp lagt ut", + "notification.update": "{name} redigerte et innlegg du samhandlet med", + "notification.user_approved": "Velkommen til {instance}!", + "notifications.filter.all": "Alle", + "notifications.filter.boosts": "Reposter", + "notifications.filter.emoji_reacts": "Emoji reagerer", "notifications.filter.favourites": "Favorites", - "notifications.filter.follows": "Follows", - "notifications.filter.mentions": "Mentions", - "notifications.filter.polls": "Poll results", - "notifications.filter.statuses": "Updates from people you follow", - "notifications.group": "{count} notifications", - "notifications.queue_label": "Click to see {count} new {count, plural, one {notification} other {notifications}}", + "notifications.filter.follows": "Følger", + "notifications.filter.mentions": "Nevner", + "notifications.filter.polls": "Avstemningsresultater", + "notifications.filter.statuses": "Oppdateringer fra personer du følger", + "notifications.group": "{count, plural, one {# notification} other {# notifications}}", + "notifications.queue_label": "Trykk for å se {count} new {count, plural, one {notification} other {notifications}}", "oauth_consumer.tooltip": "Sign in with {provider}", - "oauth_consumers.title": "Other ways to sign in", - "onboarding.avatar.subtitle": "Just have fun with it.", - "onboarding.avatar.title": "Choose a profile picture", - "onboarding.bio.hint": "Max 500 characters", - "onboarding.bio.placeholder": "Tell the world a little about yourself…", - "onboarding.display_name.label": "Display name", - "onboarding.display_name.placeholder": "Eg. John Smith", - "onboarding.display_name.subtitle": "You can always edit this later.", - "onboarding.display_name.title": "Choose a display name", - "onboarding.done": "Done", - "onboarding.error": "An unexpected error occurred. Please try again or skip this step.", - "onboarding.fediverse.its_you": "This is you! Other people can follow you from other servers by using your full @-handle.", - "onboarding.fediverse.message": "The Fediverse is a social network made up of thousands of diverse and independently-run social media sites (aka \"servers\"). You can follow users — and like, repost, and reply to posts — from most other Fediverse servers, because they can communicate with {siteTitle}.", + "oauth_consumers.title": "Andre måter å logge på", + "onboarding.avatar.subtitle": "Bare ha det gøy med det.", + "onboarding.avatar.title": "Velg et profilbilde", + "onboarding.bio.hint": "Maks 500 tegn", + "onboarding.bio.placeholder": "Fortell verden litt om deg selv …", + "onboarding.display_name.label": "Visningsnavn", + "onboarding.display_name.placeholder": "F.eks. John Smith", + "onboarding.display_name.subtitle": "Du kan alltid redigere dette senere.", + "onboarding.display_name.title": "Velg et visningsnavn", + "onboarding.done": "Ferdig", + "onboarding.error": "Det oppstod en uventet feil. Prøv på nytt, eller hopp over dette trinnet.", + "onboarding.fediverse.its_you": "Dette er deg! Andre personer kan følge deg fra andre servere ved å bruke hele @-navnet ditt.", + "onboarding.fediverse.message": "Fødiverset er et sosialt nettverk som består av tusenvis av forskjellige og uavhengig drevne sosiale medier (aka 'servere'). Du kan følge brukere – og like, reposte og svare på innlegg – fra de fleste andre Fødivers-servere, fordi de kan kommunisere med {siteTitle}.", "onboarding.fediverse.next": "Next", - "onboarding.fediverse.other_instances": "When browsing your timeline, pay attention to the full username after the second @ symbol to know which server a post is from.", - "onboarding.fediverse.title": "{siteTitle} is just one part of the Fediverse", - "onboarding.fediverse.trailer": "Because it is distributed and anyone can run their own server, the Fediverse is resilient and open. If you choose to join another server or set up your own, you can interact with the same people and continue on the same social graph.", - "onboarding.finished.message": "We are very excited to welcome you to our community! Tap the button below to get started.", - "onboarding.finished.title": "Onboarding complete", - "onboarding.header.subtitle": "This will be shown at the top of your profile.", - "onboarding.header.title": "Pick a cover image", + "onboarding.fediverse.other_instances": "Når du surfer på tidslinjen din, vær oppmerksom på hele brukernavnet etter det andre @ -symbolet for å vite hvilken server et innlegg er fra.", + "onboarding.fediverse.title": "{siteTitle} er bare en del av Fødiverset", + "onboarding.fediverse.trailer": "Fordi den distribueres og alle kan kjøre sin egen server, er Fødiverset motstandsdyktig og åpen. Hvis du velger å bli med på en annen server eller sette opp din egen, kan du samhandle med de samme personene og fortsette på den samme sosiale grafen.", + "onboarding.finished.message": "Vi er veldig glade for å ønske deg velkommen til fellesskapet vårt! Trykk på knappen nedenfor for å komme i gang.", + "onboarding.finished.title": "Ferdig", + "onboarding.header.subtitle": "Dette vil vises øverst på profilen din.", + "onboarding.header.title": "Velg et forsidebilde", "onboarding.next": "Next", - "onboarding.note.subtitle": "You can always edit this later.", - "onboarding.note.title": "Write a short bio", - "onboarding.saving": "Saving…", - "onboarding.skip": "Skip for now", - "onboarding.suggestions.subtitle": "Here are a few of the most popular accounts you might like.", - "onboarding.suggestions.title": "Suggested accounts", - "onboarding.view_feed": "View Feed", - "password_reset.confirmation": "Check your email for confirmation.", - "password_reset.fields.username_placeholder": "Email or username", + "onboarding.note.subtitle": "Du kan alltid redigere dette senere.", + "onboarding.note.title": "Skriv en kort biografi", + "onboarding.saving": "Lagrer…", + "onboarding.skip": "Hopp over for nå", + "onboarding.suggestions.subtitle": "Her er noen av de mest populære kontoene du kanskje vil like.", + "onboarding.suggestions.title": "Foreslåtte kontoer", + "onboarding.view_feed": "Se Tidslinje", + "password_reset.confirmation": "Sjekk e-posten din for bekreftelse.", + "password_reset.fields.username_placeholder": "E-post eller brukernavn", "password_reset.header": "Reset Password", "password_reset.reset": "Reset password", - "patron.donate": "Donate", - "patron.title": "Funding Goal", - "pinned_accounts.title": "{name}’s choices", - "pinned_statuses.none": "No pins to show.", - "poll.choose_multiple": "Choose as many as you'd like.", - "poll.closed": "Closed", - "poll.non_anonymous": "Public poll", - "poll.non_anonymous.label": "Other instances may display the options you voted for", - "poll.refresh": "Refresh", + "patron.donate": "Donere", + "patron.title": "Mål for finansiering", + "pinned_accounts.title": "{name}’s valg", + "pinned_statuses.none": "Ingen festede innlegg å vise", + "poll.choose_multiple": "Velg så mange du vil.", + "poll.closed": "Lukket", + "poll.non_anonymous": "Offentlig avstemning", + "poll.non_anonymous.label": "Andre forekomster kan vise alternativene du stemte på", + "poll.refresh": "Oppdater", "poll.total_people": "{count, plural, one {# person} other {# people}}", "poll.total_votes": "{count, plural, one {# vote} other {# votes}}", "poll.vote": "Vote", - "poll.voted": "You voted for this answer", + "poll.voted": "Du stemte for dette svaret", "poll.votes": "{votes, plural, one {# vote} other {# votes}}", - "poll_button.add_poll": "Add a poll", - "poll_button.remove_poll": "Remove poll", - "preferences.fields.auto_play_gif_label": "Auto-play animated GIFs", - "preferences.fields.auto_play_video_label": "Auto-play videos", - "preferences.fields.autoload_more_label": "Automatically load more items when scrolled to the bottom of the page", - "preferences.fields.autoload_timelines_label": "Automatically load new posts when scrolled to the top of the page", - "preferences.fields.boost_modal_label": "Show confirmation dialog before reposting", + "poll_button.add_poll": "Legg til en meningsmåling", + "poll_button.remove_poll": "Fjern avstemning", + "preferences.fields.auto_play_gif_label": "Spill av animerte GIF-er automatisk", + "preferences.fields.auto_play_video_label": "Spill av videoer automatisk", + "preferences.fields.autoload_more_label": "Last automatisk inn flere elementer når du ruller til bunnen av siden", + "preferences.fields.autoload_timelines_label": "Last automatisk inn nye innlegg når de rulles til toppen av sidenge", + "preferences.fields.boost_modal_label": "Vis bekreftelsesdialog før du reposter", "preferences.fields.content_type_label": "Default post format", - "preferences.fields.delete_modal_label": "Show confirmation dialog before deleting a post", - "preferences.fields.demetricator_label": "Use Demetricator", + "preferences.fields.delete_modal_label": "Vis bekreftelsesdialogboks før du sletter et innlegg", + "preferences.fields.demetricator_label": "Bruk Innholdsvarsel", + "preferences.fields.demo_hint": "Bruk standard Soapbox-logo og fargevalg. Nyttig for å ta skjermbilder.", + "preferences.fields.demo_label": "Demomodus", "preferences.fields.display_media.default": "Hide media marked as sensitive", "preferences.fields.display_media.hide_all": "Always hide media", "preferences.fields.display_media.show_all": "Always show media", - "preferences.fields.expand_spoilers_label": "Always expand posts marked with content warnings", + "preferences.fields.expand_spoilers_label": "Utvid alltid innlegg merket med innholdsvarsler", "preferences.fields.language_label": "Language", "preferences.fields.media_display_label": "Media display", - "preferences.fields.missing_description_modal_label": "Show confirmation dialog before sending a post without media descriptions", + "preferences.fields.missing_description_modal_label": "Vis bekreftelsesdialog før du sender et innlegg uten mediebeskrivelser", "preferences.fields.privacy_label": "Default post privacy", - "preferences.fields.reduce_motion_label": "Reduce motion in animations", - "preferences.fields.system_font_label": "Use system's default font", + "preferences.fields.reduce_motion_label": "Reduser bevegelse i animasjoner", + "preferences.fields.system_font_label": "Bruk systemets standard skrifttype", "preferences.fields.theme": "Theme", - "preferences.fields.underline_links_label": "Always underline links in posts", - "preferences.fields.unfollow_modal_label": "Show confirmation dialog before unfollowing someone", - "preferences.hints.demetricator": "Decrease social media anxiety by hiding all numbers from the site.", - "preferences.notifications.advanced": "Show all notification categories", + "preferences.fields.underline_links_label": "Understrek alltid lenker i innlegg", + "preferences.fields.unfollow_modal_label": "Vis bekreftelsesdialogboks før du slutter å følge noen", + "preferences.hints.demetricator": "Reduser angst på sosiale medier ved å skjule alle tall fra nettstedet.", + "preferences.notifications.advanced": "Vis alle varslingskategorier", "preferences.options.content_type_markdown": "Markdown", - "preferences.options.content_type_plaintext": "Plain text", - "preferences.options.privacy_followers_only": "Followers-only", + "preferences.options.content_type_plaintext": "Ren tekst", + "preferences.options.privacy_followers_only": "Kun følgere", "preferences.options.privacy_public": "Public", - "preferences.options.privacy_unlisted": "Unlisted", + "preferences.options.privacy_unlisted": "Uoppført", "privacy.change": "Justér synlighet", "privacy.direct.long": "Post kun til nevnte brukere", "privacy.direct.short": "Direkte", @@ -832,230 +1104,253 @@ "privacy.public.short": "Offentlig", "privacy.unlisted.long": "Ikke vis i offentlige tidslinjer", "privacy.unlisted.short": "Uoppført", - "profile_dropdown.add_account": "Add an existing account", - "profile_dropdown.logout": "Log out @{acct}", - "profile_dropdown.switch_account": "Switch accounts", + "profile_dropdown.add_account": "Legg til en eksisterende konto", + "profile_dropdown.logout": "Logg ut @{acct}", + "profile_dropdown.switch_account": "Bytt konto", "profile_dropdown.theme": "Theme", - "profile_fields_panel.title": "Profile fields", - "reactions.all": "All", + "profile_fields_panel.title": "Profilfelt", + "reactions.all": "Alle", "regeneration_indicator.label": "Laster…", "regeneration_indicator.sublabel": "Dine startside forberedes!", - "register_invite.lead": "Complete the form below to create an account.", - "register_invite.title": "You've been invited to join {siteTitle}!", - "registration.acceptance": "By registering, you agree to the {terms} and {privacy}.", - "registration.agreement": "I agree to the {tos}.", - "registration.captcha.hint": "Click the image to get a new captcha", - "registration.captcha.placeholder": "Enter the pictured text", - "registration.closed_message": "{instance} is not accepting new members", - "registration.closed_title": "Registrations Closed", - "registration.confirmation_modal.close": "Close", - "registration.fields.confirm_placeholder": "Password (again)", + "register_invite.lead": "Fyll ut skjemaet nedenfor for å opprette en konto.", + "register_invite.title": "Du har blitt invitert til å bli med {siteTitle}!", + "registration.acceptance": "Ved å registrere deg godtar du {vilkårene} og {privacy}.", + "registration.agreement": "Jeg godtar {tos}.", + "registration.captcha.hint": "Klikk på bildet for å få en ny captcha", + "registration.captcha.placeholder": "Skriv inn teksten på bildet", + "registration.closed_message": "{instance} godtar ikke nye medlemmer", + "registration.closed_title": "Registreringer Stengt", + "registration.confirmation_modal.close": "Lukke", + "registration.fields.confirm_placeholder": "Passord (igjen)", "registration.fields.email_placeholder": "E-Mail address", "registration.fields.password_placeholder": "Password", - "registration.fields.username_hint": "Only letters, numbers, and underscores are allowed.", - "registration.fields.username_placeholder": "Username", - "registration.header": "Register your account", - "registration.newsletter": "Subscribe to newsletter.", - "registration.password_mismatch": "Passwords don't match.", - "registration.privacy": "Privacy Policy", - "registration.reason": "Why do you want to join?", - "registration.reason_hint": "This will help us review your application", + "registration.fields.username_hint": "Bare bokstaver, tall og understrekingstegn er tillatt.", + "registration.fields.username_placeholder": "Brukernavn", + "registration.header": "Registrer kontoen din", + "registration.newsletter": "Meld deg på nyhetsbrev.", + "registration.password_mismatch": "Passord samsvarer ikke.", + "registration.privacy": "Retningslinjer for personvern", + "registration.reason": "Hvorfor vil du være med?", + "registration.reason_hint": "Dette vil hjelpe oss med å gjennomgå søknaden din", "registration.sign_up": "Sign up", - "registration.tos": "Terms of Service", - "registration.username_unavailable": "Username is already taken.", - "registration.validation.capital_letter": "1 capital letter", - "registration.validation.lowercase_letter": "1 lowercase letter", - "registration.validation.minimum_characters": "8 characters", - "registrations.create_account": "Create an account", - "registrations.error": "Failed to register your account.", - "registrations.get_started": "Let's get started!", + "registration.tos": "Vilkår for bruk", + "registration.username_unavailable": "Brukernavn er allerede tatt.", + "registration.validation.capital_letter": "1 stor bokstav", + "registration.validation.lowercase_letter": "1 liten bokstav", + "registration.validation.minimum_characters": "8 tegn", + "registrations.create_account": "Opprett en konto", + "registrations.error": "Kunne ikke registrere kontoen din.", + "registrations.get_started": "La oss komme i gang!", "registrations.password.label": "Password", - "registrations.success": "Welcome to {siteTitle}!", - "registrations.tagline": "Social Media Without Discrimination", - "registrations.unprocessable_entity": "This username has already been taken.", - "registrations.username.hint": "May only contain A-Z, 0-9, and underscores", - "registrations.username.label": "Your username", + "registrations.success": "Velkommen til {siteTitle}!", + "registrations.tagline": "Sosiale medier uten diskriminering", + "registrations.unprocessable_entity": "Dette brukernavnet er allerede tatt.", + "registrations.username.hint": "Kan bare inneholde A-Z, 0-9 og understreker", + "registrations.username.label": "Brukernavnet ditt", "relative_time.days": "{number}d", "relative_time.hours": "{number}h", "relative_time.just_now": "nå", "relative_time.minutes": "{number}m", "relative_time.seconds": "{number}s", - "remote_instance.edit_federation": "Edit federation", - "remote_instance.federation_panel.heading": "Federation Restrictions", - "remote_instance.federation_panel.no_restrictions_message": "{siteTitle} has placed no restrictions on {host}.", - "remote_instance.federation_panel.restricted_message": "{siteTitle} blocks all activities from {host}.", - "remote_instance.federation_panel.some_restrictions_message": "{siteTitle} has placed some restrictions on {host}.", + "remote_instance.edit_federation": "Rediger føderasjon", + "remote_instance.federation_panel.heading": "Føderasjonsrestriksjoner", + "remote_instance.federation_panel.no_restrictions_message": "{siteTitle} har ikke lagt noen begrensninger på {host}.", + "remote_instance.federation_panel.restricted_message": "{siteTitle} blokkerer alle aktiviteter fra {host}.", + "remote_instance.federation_panel.some_restrictions_message": "{siteTitle} har lagt noen begrensninger på {host}.", "remote_instance.pin_host": "Pin {host}", - "remote_instance.unpin_host": "Unpin {host}", - "remote_interaction.account_placeholder": "Enter your username@domain you want to act from", + "remote_instance.unpin_host": "Løsne {host}", + "remote_interaction.account_placeholder": "Skriv inn brukernavn@domene du vil handle fra", "remote_interaction.divider": "or", - "remote_interaction.favourite": "Proceed to like", - "remote_interaction.favourite_title": "Like a post remotely", - "remote_interaction.follow": "Proceed to follow", - "remote_interaction.follow_title": "Follow {user} remotely", - "remote_interaction.poll_vote": "Proceed to vote", - "remote_interaction.poll_vote_title": "Vote in a poll remotely", - "remote_interaction.reblog": "Proceed to repost", - "remote_interaction.reblog_title": "Reblog a post remotely", - "remote_interaction.reply": "Proceed to reply", - "remote_interaction.reply_title": "Reply to a post remotely", - "remote_interaction.user_not_found_error": "Couldn't find given user", - "remote_timeline.filter_message": "You are viewing the timeline of {instance}.", + "remote_interaction.event_join": "Fortsett for å bli med", + "remote_interaction.event_join_title": "Bli med på et arrangement eksternt", + "remote_interaction.favourite": "Fortsett å like", + "remote_interaction.favourite_title": "Like et innlegg eksternt", + "remote_interaction.follow": "Fortsett å følge", + "remote_interaction.follow_title": "Følg {user} eksternt", + "remote_interaction.poll_vote": "Fortsett å stemme", + "remote_interaction.poll_vote_title": "Stemme i en avstemning eksternt", + "remote_interaction.reblog": "Fortsett å reposte", + "remote_interaction.reblog_title": "Reblogge et innlegg eksternt", + "remote_interaction.reply": "Fortsett å svare", + "remote_interaction.reply_title": "Svar på et innlegg eksternt", + "remote_interaction.user_not_found_error": "Finner ikke gitt bruker", + "remote_timeline.filter_message": "Du viser tidslinjen til {instance}.", "reply_indicator.cancel": "Avbryt", - "reply_mentions.account.add": "Add to mentions", - "reply_mentions.account.remove": "Remove from mentions", - "reply_mentions.more": "{count} more", - "reply_mentions.reply": "Replying to {accounts}", - "reply_mentions.reply.hoverable": "Replying to {accounts}", - "reply_mentions.reply_empty": "Replying to post", + "reply_mentions.account.add": "Legg til i omtaler", + "reply_mentions.account.remove": "Fjern fra omtaler", + "reply_mentions.more": "{count} mer", + "reply_mentions.reply": "Svare på {accounts}", + "reply_mentions.reply.hoverable": "Svare på {accounts}", + "reply_mentions.reply_empty": "Svare på innlegg", "report.block": "Block {target}", - "report.block_hint": "Do you also want to block this account?", - "report.confirmation.content": "If we find that this account is violating the {link} we will take further action on the matter.", - "report.confirmation.title": "Thanks for submitting your report.", - "report.done": "Done", - "report.forward": "Forward to {target}", - "report.forward_hint": "The account is from another server. Send a copy of the report there as well?", + "report.block_hint": "Vil du også blokkere denne kontoen?", + "report.chatMessage.context": "Når du rapporterer en brukers melding, sendes de fem meldingene før og fem meldinger etter den valgte til modereringsteamet vårt for konteks.", + "report.chatMessage.title": "Rapporter melding", + "report.confirmation.content": "Hvis vi finner ut at denne kontoen bryter med {link}, vil vi iverksette ytterligere tiltak i saken.", + "report.confirmation.title": "Takk for at du sendte inn rapporten.", + "report.done": "Ferdig", + "report.forward": "Videresend til {target}", + "report.forward_hint": "Kontoen er fra en annen server. Sende en kopi av rapporten dit også?", "report.next": "Next", - "report.otherActions.addAdditional": "Would you like to add additional statuses to this report?", - "report.otherActions.addMore": "Add more", - "report.otherActions.furtherActions": "Further actions:", - "report.otherActions.hideAdditional": "Hide additional statuses", - "report.otherActions.otherStatuses": "Include other statuses?", + "report.otherActions.addAdditional": "Ønsker du å legge til flere statuser i denne rapporten?", + "report.otherActions.addMore": "Legg til mer", + "report.otherActions.furtherActions": "Ytterligere tiltak:", + "report.otherActions.hideAdditional": "Skjul flere statuser", + "report.otherActions.otherStatuses": "Inkludere andre statuser?", "report.placeholder": "Tilleggskommentarer", - "report.previous": "Previous", - "report.reason.blankslate": "You have removed all statuses from being selected.", - "report.reason.title": "Reason for reporting", + "report.previous": "Forrige", + "report.reason.blankslate": "Du har fjernet alle statuser fra å bli valgt.", + "report.reason.title": "Årsak til rapportering", "report.submit": "Send inn", "report.target": "Rapporterer", - "reset_password.fail": "Expired token, please try again.", - "reset_password.header": "Set New Password", + "reset_password.fail": "Utløpt token, prøv igjen.", + "reset_password.header": "Angi nytt passord", "reset_password.password.label": "Password", - "reset_password.password.placeholder": "Placeholder", - "save": "Save", - "schedule.post_time": "Post Date/Time", - "schedule.remove": "Remove schedule", - "schedule_button.add_schedule": "Schedule post for later", - "schedule_button.remove_schedule": "Post immediately", - "scheduled_status.cancel": "Cancel", - "search.action": "Search for “{query}”", + "reset_password.password.placeholder": "Plassholder", + "save": "Lagre", + "schedule.post_time": "Innlegg Dato/tid", + "schedule.remove": "Fjern tidsplan", + "schedule_button.add_schedule": "Planlegg innlegg til senere", + "schedule_button.remove_schedule": "Publiser umiddelbart", + "scheduled_status.cancel": "Avbryt", + "search.action": "Søk etter “{query}”", "search.placeholder": "Søk", "search_results.accounts": "People", - "search_results.filter_message": "You are searching for posts from @{acct}.", - "search_results.hashtags": "Hashtags", + "search_results.filter_message": "Du søker etter innlegg fra @{acct}.", + "search_results.groups": "Grupper", + "search_results.hashtags": "Emneknagger", "search_results.statuses": "Posts", - "security.codes.fail": "Failed to fetch backup codes", - "security.confirm.fail": "Incorrect code or password. Try again.", - "security.delete_account.fail": "Account deletion failed.", - "security.delete_account.success": "Account successfully deleted.", - "security.disable.fail": "Incorrect password. Try again.", + "security.codes.fail": "Kunne ikke hente sikkerhetskoder", + "security.confirm.fail": "Feil kode eller passord. Prøv igjen.", + "security.delete_account.fail": "Sletting av konto mislyktes.", + "security.delete_account.success": "Kontoen er slettet.", + "security.disable.fail": "Feil passord. Prøv igjen.", "security.fields.email.label": "Email address", - "security.fields.new_password.label": "New password", - "security.fields.old_password.label": "Current password", + "security.fields.new_password.label": "Nytt passord", + "security.fields.old_password.label": "Gjeldende passord", "security.fields.password.label": "Password", - "security.fields.password_confirmation.label": "New password (again)", + "security.fields.password_confirmation.label": "Nytt passord (igjen)", "security.headers.delete": "Delete Account", - "security.headers.tokens": "Sessions", - "security.qr.fail": "Failed to fetch setup key", - "security.submit": "Save changes", + "security.headers.tokens": "Økter", + "security.qr.fail": "Kunne ikke hente konfigurasjonsnøkkelen", + "security.submit": "Lagre endringer", "security.submit.delete": "Delete Account", - "security.text.delete": "To delete your account, enter your password then click Delete Account. This is a permanent action that cannot be undone. Your account will be destroyed from this server, and a deletion request will be sent to other servers. It's not guaranteed that all servers will purge your account.", - "security.text.delete.local": "To delete your account, enter your password then click Delete Account. This is a permanent action that cannot be undone.", - "security.tokens.revoke": "Revoke", - "security.update_email.fail": "Update email failed.", - "security.update_email.success": "Email successfully updated.", - "security.update_password.fail": "Update password failed.", - "security.update_password.success": "Password successfully updated.", - "settings.account_migration": "Move Account", - "settings.change_email": "Change Email", - "settings.change_password": "Change Password", - "settings.configure_mfa": "Configure MFA", + "security.text.delete": "Hvis du vil slette kontoen, skriver du inn passordet og klikker på Slett konto. Dette er en permanent handling som ikke kan angres. Kontoen din vil bli ødelagt fra denne serveren, og en forespørsel om sletting vil bli sendt til andre servere. Det er ikke garantert at alle servere vil rense kontoen din.", + "security.text.delete.local": "Hvis du vil slette kontoen, skriver du inn passordet og klikker på Slett konto. Dette er en permanent handling som ikke kan angres.", + "security.tokens.revoke": "Oppheve", + "security.update_email.fail": "Oppdatering av e-post mislyktes.", + "security.update_email.success": "E-posten er oppdatert.", + "security.update_password.fail": "Oppdatering av passord mislyktes.", + "security.update_password.success": "Passordet ble oppdatert.", + "settings.account_migration": "Flytte konto", + "settings.change_email": "Endre e-postadresse", + "settings.change_password": "Endre passord", + "settings.configure_mfa": "Konfigurere MFA", "settings.delete_account": "Delete Account", - "settings.edit_profile": "Edit Profile", + "settings.edit_profile": "Rediger profil", + "settings.messages.label": "Tillatt andre brukere å starte ny samtale med deg", "settings.other": "Other options", - "settings.preferences": "Preferences", - "settings.profile": "Profile", - "settings.save.success": "Your preferences have been saved!", - "settings.security": "Security", - "settings.sessions": "Active sessions", - "settings.settings": "Settings", - "shared.tos": "Terms of Service", - "signup_panel.subtitle": "Sign up now to discuss what's happening.", - "signup_panel.title": "New to {site_title}?", - "site_preview.preview": "Preview", - "sms_verification.expired": "Your SMS token has expired.", - "sms_verification.fail": "Failed to send SMS message to your phone number.", - "sms_verification.header": "Enter your phone number", - "sms_verification.invalid": "Please enter a valid phone number.", - "sms_verification.modal.enter_code": "We sent you a 6-digit code via SMS. Enter it below.", - "sms_verification.modal.resend_code": "Resend verification code?", - "sms_verification.modal.verify_code": "Verify code", - "sms_verification.modal.verify_help_text": "Verify your phone number to start using {instance}.", - "sms_verification.modal.verify_number": "Verify phone number", - "sms_verification.modal.verify_sms": "Verify SMS", - "sms_verification.modal.verify_title": "Verify your phone number", - "sms_verification.phone.label": "Phone number", - "sms_verification.sent.actions.resend": "Resend verification code?", - "sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.", - "sms_verification.sent.header": "Verification code", - "sms_verification.success": "A verification code has been sent to your phone number.", - "soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.", - "soapbox_config.authenticated_profile_label": "Profiles require authentication", - "soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer", + "settings.preferences": "Preferanser", + "settings.profile": "Profil", + "settings.save.success": "Dine preferanser er lagret!", + "settings.security": "Sikkerhet", + "settings.sessions": "Aktive økter", + "settings.settings": "Innstillinger", + "shared.tos": "Vilkår for bruk", + "signup_panel.subtitle": "Registrer deg nå for å diskutere hva som skjer.", + "signup_panel.title": "Ny på {site_title}?", + "site_preview.preview": "Forhåndsvisning", + "sms_verification.expired": "SMS-tokenet ditt er utløpt.", + "sms_verification.fail": "Kan ikke sende SMS-melding til telefonnummeret ditt.", + "sms_verification.header": "Skriv inn telefonnummeret ditt", + "sms_verification.invalid": "Angi et gyldig telefonnummer.", + "sms_verification.modal.enter_code": "Vi sendte deg en 6-sifret kode via SMS. Skriv det inn nedenfor.", + "sms_verification.modal.resend_code": "Sende bekreftelseskode på nytt?", + "sms_verification.modal.verify_code": "Bekreft kode", + "sms_verification.modal.verify_help_text": "Bekreft telefonnummeret ditt for å begynne å bruke {instance}.", + "sms_verification.modal.verify_number": "Bekreft telefonnummer", + "sms_verification.modal.verify_sms": "Bekreft SMS", + "sms_verification.modal.verify_title": "Bekreft telefonnummeret ditt", + "sms_verification.phone.label": "Telefonnummer", + "sms_verification.sent.actions.resend": "Sende bekreftelseskode på nytt?", + "sms_verification.sent.body": "Vi sendte deg en 6-sifret kode via SMS. Skriv det inn nedenfor.", + "sms_verification.sent.header": "Bekreftelseskode", + "sms_verification.success": "En bekreftelseskode er sendt til telefonnummeret ditt.", + "soapbox_config.authenticated_profile_hint": "Brukere må være logget inn for å vise svar og medier på brukerprofiler.", + "soapbox_config.authenticated_profile_label": "Profiler krever autentisering", + "soapbox_config.copyright_footer.meta_fields.label_placeholder": "Bunntekst for opphavsrett", "soapbox_config.crypto_address.meta_fields.address_placeholder": "Address", - "soapbox_config.crypto_address.meta_fields.note_placeholder": "Note (optional)", + "soapbox_config.crypto_address.meta_fields.note_placeholder": "Merk (valgfritt)", "soapbox_config.crypto_address.meta_fields.ticker_placeholder": "Ticker", - "soapbox_config.crypto_donate_panel_limit.meta_fields.limit_placeholder": "Number of items to display in the crypto homepage widget", - "soapbox_config.cta_label": "Display call to action panels if not authenticated", + "soapbox_config.crypto_donate_panel_limit.meta_fields.limit_placeholder": "Antall elementer som skal vises i widgeten for krypto-hjemmesiden", + "soapbox_config.cta_label": "Vis handlingspaneler hvis de ikke er godkjent", "soapbox_config.custom_css.meta_fields.url_placeholder": "URL", - "soapbox_config.display_fqn_label": "Display domain (eg @user@domain) for local accounts.", - "soapbox_config.feed_injection_hint": "Inject the feed with additional content, such as suggested profiles.", - "soapbox_config.feed_injection_label": "Feed injection", - "soapbox_config.fields.crypto_addresses_label": "Cryptocurrency addresses", - "soapbox_config.fields.home_footer_fields_label": "Home footer items", + "soapbox_config.display_fqn_label": "Vis domene (f.eks. @bruker@domene) for lokale kontoer.", + "soapbox_config.feed_injection_hint": "Injiser tidslinjen med tilleggsinnhold, for eksempel foreslåtte profiler.", + "soapbox_config.feed_injection_label": "Tidslinje injisering", + "soapbox_config.fields.crypto_addresses_label": "Kryptovaluta adresser", + "soapbox_config.fields.edit_theme_label": "Rediger tema", + "soapbox_config.fields.home_footer_fields_label": "Bunntekst på hjemmesiden", "soapbox_config.fields.logo_label": "Logo", - "soapbox_config.fields.promo_panel_fields_label": "Promo panel items", + "soapbox_config.fields.promo_panel_fields_label": "Elementer i kampanjepanelet", "soapbox_config.fields.theme_label": "Default theme", - "soapbox_config.greentext_label": "Enable greentext support", - "soapbox_config.headings.advanced": "Advanced", - "soapbox_config.headings.cryptocurrency": "Cryptocurrency", + "soapbox_config.greentext_label": "Aktiver støtte for grønn tekst", + "soapbox_config.headings.advanced": "Avansert", + "soapbox_config.headings.cryptocurrency": "Kryptovaluta", + "soapbox_config.headings.events": "Arrangementer", "soapbox_config.headings.navigation": "Navigation", "soapbox_config.headings.options": "Options", "soapbox_config.headings.theme": "Theme", - "soapbox_config.hints.crypto_addresses": "Add cryptocurrency addresses so users of your site can donate to you. Order matters, and you must use lowercase ticker values.", - "soapbox_config.hints.home_footer_fields": "You can have custom defined links displayed on the footer of your static pages", - "soapbox_config.hints.logo": "SVG. At most 2 MB. Will be displayed to 50px height, maintaining aspect ratio", - "soapbox_config.hints.promo_panel_fields": "You can have custom defined links displayed on the right panel of the timelines page.", + "soapbox_config.hints.crypto_addresses": "Legg til kryptovaluta-adresser slik at brukere av nettstedet ditt kan donere til deg. Bestillingen er viktig, og du må bruke tickerverdier med små bokstaver.", + "soapbox_config.hints.home_footer_fields": "Du kan ha egendefinerte lenker vist i bunnteksten på de statiske sidene dine", + "soapbox_config.hints.logo": "SVG. Maksimalt 2 MB. Vil bli vist til 50 piksler i høyden, med opprettholdelse av størrelsesforholdet.", + "soapbox_config.hints.promo_panel_fields": "Du kan ha egendefinerte lenker som vises på høyre panel på tidslinjesiden.", "soapbox_config.hints.promo_panel_icons.link": "Soapbox Icons List", "soapbox_config.home_footer.meta_fields.label_placeholder": "Label", "soapbox_config.home_footer.meta_fields.url_placeholder": "URL", + "soapbox_config.media_preview_hint": "Noen 'backends' gir en optimalisert versjon av media for visning i tidslinjer. Imidlertid kan disse forhåndsvisningsbildene være for små uten ytterligere konfigurasjon.", + "soapbox_config.media_preview_label": "Foretrekker forhåndsvisning av medier for miniatyrbilder", "soapbox_config.promo_panel.meta_fields.icon_placeholder": "Icon", "soapbox_config.promo_panel.meta_fields.label_placeholder": "Label", "soapbox_config.promo_panel.meta_fields.url_placeholder": "URL", - "soapbox_config.raw_json_hint": "Edit the settings data directly. Changes made directly to the JSON file will override the form fields above. Click Save to apply your changes.", - "soapbox_config.raw_json_label": "Advanced: Edit raw JSON data", - "soapbox_config.save": "Save", - "soapbox_config.saved": "Soapbox config saved!", - "soapbox_config.verified_can_edit_name_label": "Allow verified users to edit their own display name.", - "sponsored.info.message": "{siteTitle} displays ads to help fund our service.", - "sponsored.info.title": "Why am I seeing this ad?", - "sponsored.subtitle": "Sponsored post", + "soapbox_config.raw_json_hint": "Rediger innstillingsdataene direkte. Endringer som gjøres direkte i JSON-filen, overstyrer skjemafeltene ovenfor. Klikk Lagre for å ta i bruk endringene.", + "soapbox_config.raw_json_invalid": "er ugyldig", + "soapbox_config.raw_json_label": "Avansert: Rediger rå JSON-data", + "soapbox_config.redirect_root_no_login_hint": "Bane for å omdirigere hjemmesiden når en bruker ikke er logget inn.", + "soapbox_config.redirect_root_no_login_label": "Omdiriger hjemmesiden", + "soapbox_config.save": "Lagre", + "soapbox_config.saved": "Soapbox-konfigurasjonen er lagret!", + "soapbox_config.tile_server_attribution_label": "Attribusjon for kartfliser", + "soapbox_config.tile_server_label": "Server for kartfliser", + "soapbox_config.verified_can_edit_name_label": "Tillat bekreftede brukere å redigere sitt eget visningsnavn.", + "sponsored.info.message": "{siteTitle} viser annonser for å bidra til å finansiere tjenesten vår.", + "sponsored.info.title": "Hvorfor ser jeg denne annonsen?", + "sponsored.subtitle": "Sponset innlegg", "status.admin_account": "Open moderation interface for @{name}", - "status.admin_status": "Open this post in the moderation interface", - "status.bookmark": "Bookmark", - "status.bookmarked": "Bookmark added.", - "status.cancel_reblog_private": "Un-repost", + "status.admin_status": "Åpne dette innlegget i modereringsgrensesnittet", + "status.approval.pending": "Venter på godkjenning", + "status.approval.rejected": "Avvist", + "status.bookmark": "Bokmerke", + "status.bookmarked": "Bokmerke lagt til.", + "status.cancel_reblog_private": "Fjern repostering", "status.cannot_reblog": "Denne posten kan ikke fremheves", "status.chat": "Chat with @{name}", - "status.copy": "Copy link to post", + "status.copy": "Kopier link til innlegget", "status.delete": "Slett", - "status.detailed_status": "Detailed conversation view", - "status.direct": "Direct message @{name}", - "status.edit": "Edit", + "status.detailed_status": "Detaljert samtalevisning", + "status.direct": "Direktemelding @{name}", + "status.disabled_replies.group_membership": "Bare gruppemedlemmer kan svare", + "status.edit": "Rediger", "status.embed": "Bygge inn", "status.external": "View post on {domain}", "status.favourite": "Lik", - "status.filtered": "Filtered", + "status.filtered": "Filtrert", + "status.group": "Publisert i {group}", + "status.group_mod_block": "Blokker @{name} fra gruppe", + "status.group_mod_delete": "Slett innlegg fra gruppe", + "status.group_mod_kick": "Spark ut @{navn} fra gruppe", "status.interactions.favourites": "{count, plural, one {Like} other {Likes}}", + "status.interactions.quotes": "{count, plural, one {Quote} other {Quotes}}", "status.interactions.reblogs": "{count, plural, one {Repost} other {Reposts}}", "status.load_more": "Last mer", "status.mention": "Nevn @{name}", @@ -1063,92 +1358,101 @@ "status.mute_conversation": "Demp samtale", "status.open": "Utvid denne statusen", "status.pin": "Fest på profilen", - "status.pinned": "Pinned post", - "status.quote": "Quote post", - "status.reactions.cry": "Sad", - "status.reactions.empty": "No one has reacted to this post yet. When someone does, they will show up here.", - "status.reactions.heart": "Love", + "status.pinned": "Festet innlegg", + "status.quote": "Siter innlegg", + "status.reactions.cry": "Trist", + "status.reactions.empty": "Ingen har reagert på dette innlegget ennå. Når noen gjør det, vil det dukke opp her.", + "status.reactions.heart": "Kjærlighet", "status.reactions.laughing": "Haha", - "status.reactions.like": "Like", - "status.reactions.open_mouth": "Wow", - "status.reactions.weary": "Weary", - "status.read_more": "Read more", + "status.reactions.like": "Liker", + "status.reactions.open_mouth": "Jøss", + "status.reactions.weary": "Sliten", + "status.read_more": "Les mer", "status.reblog": "Fremhev", - "status.reblog_private": "Repost to original audience", + "status.reblog_private": "Reposte til det opprinnelige publikummet", "status.reblogged_by": "Fremhevd av {name}", - "status.reblogs.empty": "No one has reposted this post yet. When someone does, they will show up here.", - "status.redraft": "Delete & re-draft", - "status.remove_account_from_group": "Remove account from group", - "status.remove_post_from_group": "Remove post from group", + "status.reblogs.empty": "Ingen har repostet dette innlegget ennå. Når noen gjør det, vil det dukke opp her.", + "status.redraft": "Slett & omformuler", + "status.remove_account_from_group": "Fjern konto fra gruppe", + "status.remove_post_from_group": "Fjern innlegg fra gruppe", "status.reply": "Svar", "status.replyAll": "Svar til samtale", "status.report": "Rapporter @{name}", "status.sensitive_warning": "Følsomt innhold", - "status.sensitive_warning.subtitle": "This content may not be suitable for all audiences.", + "status.sensitive_warning.subtitle": "Dette innholdet er kanskje ikke egnet for alle målgrupper.", "status.share": "Del", - "status.show_less_all": "Show less for all", - "status.show_more_all": "Show more for all", - "status.show_original": "Show original", + "status.show_less_all": "Vis mindre for alle", + "status.show_more_all": "Vis mer for alle", + "status.show_original": "Vis originalen", "status.title": "Post", - "status.title_direct": "Direct message", - "status.translate": "Translate", - "status.translated_from_with": "Translated from {lang} using {provider}", - "status.unbookmark": "Remove bookmark", - "status.unbookmarked": "Bookmark removed.", + "status.title_direct": "Direktemelding", + "status.translate": "Oversette", + "status.translated_from_with": "Oversatt fra {lang} ved hjelp av {provider}", + "status.unbookmark": "Fjern bokmerke", + "status.unbookmarked": "Bokmerke fjernet.", "status.unmute_conversation": "Ikke demp samtale", "status.unpin": "Angre festing på profilen", - "status_list.queue_label": "Click to see {count} new {count, plural, one {post} other {posts}}", - "statuses.quote_tombstone": "Post is unavailable.", - "statuses.tombstone": "One or more posts are unavailable.", + "status_list.queue_label": "Trykk for å se {count} new {count, plural, one {post} other {posts}}", + "statuses.quote_tombstone": "Innlegget er utilgjengelig.", + "statuses.tombstone": "Ett eller flere innlegg er utilgjengelige.", "streamfield.add": "Add", - "streamfield.remove": "Remove", - "suggestions.dismiss": "Dismiss suggestion", + "streamfield.remove": "Fjerne", + "suggestions.dismiss": "Avvis forslag", "sw.restart": "Restart", "sw.state.active": "Active", - "sw.state.loading": "Loading…", - "sw.state.unavailable": "Unavailable", - "sw.state.unknown": "Unknown", - "sw.state.waiting": "Waiting", + "sw.state.loading": "Laster…", + "sw.state.unavailable": "Utilgjengelig", + "sw.state.unknown": "Ukjent", + "sw.state.waiting": "Venter", "sw.status": "Status", "sw.url": "Script URL", - "tabs_bar.all": "All", + "tabs_bar.all": "Alle", "tabs_bar.dashboard": "Dashboard", "tabs_bar.fediverse": "Fediverse", + "tabs_bar.groups": "Grupper", "tabs_bar.home": "Hjem", "tabs_bar.local": "Local", - "tabs_bar.more": "More", + "tabs_bar.more": "Mer", "tabs_bar.notifications": "Varslinger", - "tabs_bar.profile": "Profile", - "tabs_bar.search": "Search", - "tabs_bar.settings": "Settings", - "theme_toggle.dark": "Dark", - "theme_toggle.light": "Light", + "tabs_bar.profile": "Profil", + "tabs_bar.search": "Søk", + "tabs_bar.settings": "Innstillinger", + "theme_editor.Reset": "Tilbakestill", + "theme_editor.export": "Eksporter tema", + "theme_editor.import": "Importer tema", + "theme_editor.import_success": "Temaet ble importert!", + "theme_editor.restore": "Gjenopprett standardtema", + "theme_editor.save": "Lagre tema", + "theme_editor.saved": "Tema oppdatert!", + "theme_toggle.dark": "Mørk", + "theme_toggle.light": "Lyst", "theme_toggle.system": "System", "thread_login.login": "Log in", - "thread_login.message": "Join {siteTitle} to get the full story and details.", + "thread_login.message": "Bli med i {siteTitle} for å få hele historien og detaljene.", "thread_login.signup": "Sign up", - "thread_login.title": "Continue the conversation", - "time_remaining.days": "{number, plural, one {# day} other {# days}} left", - "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left", - "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left", - "time_remaining.moments": "Moments remaining", - "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left", - "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking", - "trends.title": "Trends", - "trendsPanel.viewAll": "View all", - "unauthorized_modal.text": "You need to be logged in to do that.", + "thread_login.title": "Fortsett samtalen", + "time_remaining.days": "{number, plural, one {# day} other {# days}} igjen", + "time_remaining.hours": "{number, plural, one {# hour} other {# hours}} igjen", + "time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} igjen", + "time_remaining.moments": "Øyeblikk gjenstår", + "time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} igjen", + "toast.view": "Vis", + "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} prater", + "trends.title": "Trender", + "trendsPanel.viewAll": "Vis alle", + "unauthorized_modal.text": "Du må være logget inn for å gjøre det.", "unauthorized_modal.title": "Sign up for {site_title}", "upload_area.title": "Dra og slipp for å laste opp", "upload_button.label": "Legg til media", - "upload_error.image_size_limit": "Image exceeds the current file size limit ({limit})", - "upload_error.limit": "File upload limit exceeded.", - "upload_error.poll": "File upload not allowed with polls.", - "upload_error.video_duration_limit": "Video exceeds the current duration limit ({limit} seconds)", - "upload_error.video_size_limit": "Video exceeds the current file size limit ({limit})", + "upload_error.image_size_limit": "Bildet overskrider gjeldende filstørrelsesgrense ({limit})", + "upload_error.limit": "Filopplastingsgrensen er overskredet.", + "upload_error.poll": "Filopplasting er ikke tillatt med avstemninger.", + "upload_error.video_duration_limit": "Video overskrider gjeldende varighetsgrense ({limit}, plural, one {# second} other {# seconds}})", + "upload_error.video_size_limit": "Videoen overskrider gjeldende filstørrelsesgrense ({limit}", "upload_form.description": "Beskriv for synshemmede", - "upload_form.preview": "Preview", + "upload_form.preview": "Forhåndsvisning", "upload_form.undo": "Angre", - "upload_progress.label": "Laster opp...", + "upload_progress.label": "Laster opp…", "video.close": "Lukk video", "video.download": "Download file", "video.exit_fullscreen": "Lukk fullskjerm", @@ -1159,7 +1463,7 @@ "video.pause": "Pause", "video.play": "Spill av", "video.unmute": "Skru på lyd", - "waitlist.actions.verify_number": "Verify phone number", - "waitlist.body": "Welcome back to {title}! You were previously placed on our waitlist. Please verify your phone number to receive immediate access to your account!", + "waitlist.actions.verify_number": "Bekreft telefonnummer", + "waitlist.body": "Velkommen tilbake til {title}! Du ble tidligere satt på ventelisten vår. Bekreft telefonnummeret ditt for å få umiddelbar tilgang til kontoen din!", "who_to_follow.title": "Who To Follow" } diff --git a/app/soapbox/locales/oc.json b/app/soapbox/locales/oc.json index d5e5dca78..23b528b05 100644 --- a/app/soapbox/locales/oc.json +++ b/app/soapbox/locales/oc.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Mostrar las responsas", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/pl.json b/app/soapbox/locales/pl.json index 2b32f88dc..43c3c9400 100644 --- a/app/soapbox/locales/pl.json +++ b/app/soapbox/locales/pl.json @@ -18,7 +18,7 @@ "account.endorse.success": "@{acct} jest teraz polecany(-a) na Twoim profilu", "account.familiar_followers": "Obserwowany(-a) przez {accounts}", "account.familiar_followers.empty": "Nie znasz nikogo obserwującego {name}.", - "account.familiar_followers.more": "{count} {count, plural, one {innego użytkownika, którego obserwujesz} other {innych użytkowników, których obserwujesz}}", + "account.familiar_followers.more": "{count, plural, one {# innego użytkownika, którego obserwujesz} other {# innych użytkowników, których obserwujesz}}", "account.follow": "Obserwuj", "account.followers": "Obserwujący", "account.followers.empty": "Nikt jeszcze nie obserwuje tego użytkownika.", @@ -85,6 +85,12 @@ "account_search.placeholder": "Szukaj konta", "actualStatus.edited": "Edytowano {date}", "actualStatuses.quote_tombstone": "Wpis jest niedostępny", + "admin.announcements.action": "Utwórz ogłoszenie", + "admin.announcements.all_day": "Cały dzień", + "admin.announcements.delete": "Usuń", + "admin.announcements.edit": "Edytuj", + "admin.announcements.ends_at": "Kończy się:", + "admin.announcements.starts_at": "Rozpoczyna się:", "admin.awaiting_approval.approved_message": "Przyjęto {acct}!", "admin.awaiting_approval.empty_message": "Nikt nie oczekuje przyjęcia. Gdy zarejestruje się nowy użytkownik, możesz zatwierdzić go tutaj.", "admin.awaiting_approval.rejected_message": "Odrzucono {acct}!", @@ -103,7 +109,17 @@ "admin.dashcounters.user_count_label": "użytkownicy łącznie", "admin.dashwidgets.email_list_header": "Lista adresów e-mail", "admin.dashwidgets.software_header": "Oprogramowanie", - "admin.latest_accounts_panel.more": "Naciśnij, aby zobaczyć {count} {count, plural, one {konto} few {konta} many {kont}}", + "admin.edit_announcement.created": "Utworzono ogłoszenie", + "admin.edit_announcement.deleted": "Usunięto ogłoszenie", + "admin.edit_announcement.fields.content_label": "Treść", + "admin.edit_announcement.fields.content_placeholder": "Treść ogłoszenia", + "admin.edit_announcement.fields.end_time_label": "Data zakończenia", + "admin.edit_announcement.fields.end_time_placeholder": "Ogłoszenie kończy się:", + "admin.edit_announcement.fields.start_time_label": "Data rozpoczęcia", + "admin.edit_announcement.fields.start_time_placeholder": "Ogłoszenie rozpoczyna się:", + "admin.edit_announcement.save": "Zapisz", + "admin.edit_announcement.updated": "Edytowano ogłoszenie", + "admin.latest_accounts_panel.more": "Naciśnij, aby zobaczyć {count, plural, one {# konto} few {# konta} many {# kont}}", "admin.latest_accounts_panel.title": "Najnowsze konta", "admin.moderation_log.empty_message": "Nie wykonałeś(-aś) jeszcze żadnych działań moderacyjnych. Kiedy jakieś zostaną wykonane, ich historia pojawi się tutaj.", "admin.reports.actions.close": "Zamknij", @@ -140,7 +156,7 @@ "admin_nav.awaiting_approval": "Oczekujące zgłoszenia", "admin_nav.dashboard": "Panel administracyjny", "admin_nav.reports": "Zgłoszenia", - "age_verification.body": "{siteTitle} wymaga, by użytkownicy mieli przynajmniej {ageMinimum} lat aby korzystać z platformy. Osoby poniżej {ageMinimum} roku życia nie mogą korzystać z tej platformy.", + "age_verification.body": "{siteTitle} wymaga, by użytkownicy mieli przynajmniej {ageMinimum, plural, one {# rok} few {# lata} many {# lat} other {# lat}} aby korzystać z platformy. Osoby poniżej {ageMinimum, plural, other {# roku}} życia nie mogą korzystać z tej platformy.", "age_verification.fail": "Musisz mieć przynajmniej {ageMinimum, plural, one {# rok} few {# lata} many {# lat} other {# lat}}.", "age_verification.header": "Wprowadź datę urodzenia", "alert.unexpected.body": "Przepraszamy za niedogodności. Jeżeli problem nie ustanie, skontaktuj się z naszym wsparciem technicznym. Możesz też spróbować {clearCookies} (zostaniesz wylogowany(-a)).", @@ -170,6 +186,7 @@ "app_create.scopes_placeholder": "np. „read write follow”", "app_create.submit": "Utwórz aplikację", "app_create.website_label": "Strona", + "auth.awaiting_approval": "Twoje konto czeka na zatwierdzenie", "auth.invalid_credentials": "Nieprawidłowa nazwa użytkownika lub hasło", "auth.logged_out": "Wylogowano.", "auth_layout.register": "Utwórz konto", @@ -191,13 +208,65 @@ "card.back.label": "Wstecz", "chat.actions.send": "Wyślij", "chat.failed_to_send": "Nie udało się wysłać wiadomości.", + "chat.input.placeholder": "Wprowadź wiadomość", + "chat.new_message.title": "Nowa wiadomość", + "chat.page_settings.accepting_messages.label": "Pozwól innym użytkownikom zacząć rozmowę z Tobą", + "chat.page_settings.play_sounds.label": "Odtwarzaj dźwięk gdy otrzymasz wiadomość", + "chat.page_settings.preferences": "Preferencje", + "chat.page_settings.privacy": "Prywatność", + "chat.page_settings.submit": "Zapisz", + "chat.page_settings.title": "Ustawienia wiadomości", + "chat.retry": "Spróbować powonie?", + "chat.welcome.accepting_messages.label": "Pozwól użytkownikom zacząć rozmowę z Tobą", + "chat.welcome.notice": "Możesz zmienić te ustawienia później.", + "chat.welcome.submit": "Zapisz i kontynuuj", + "chat.welcome.subtitle": "Wymieniaj się wiadomościami bezpośrednimi z innymi.", + "chat_composer.unblock": "Odblokuj", + "chat_list_item.blocked_you": "Ten użytkownik zablokował Cię", + "chat_list_item.blocking": "Zablokowałeś(-aś) tego użytkownika", + "chat_message_list.blocked": "Zablokowałeś(-aś) tego użytkownika", + "chat_message_list.blockedBy": "Jesteś zablokowany(-a)", + "chat_message_list.network_failure.action": "Spróbuj ponownie", + "chat_message_list.network_failure.subtitle": "Natrafiliśmy na błąd sieci.", + "chat_message_list.network_failure.title": "O nie!", + "chat_message_list_intro.actions.accept": "Akceptuj", + "chat_message_list_intro.actions.leave_chat": "Opuść czat", + "chat_message_list_intro.actions.report": "Zgłoś", + "chat_message_list_intro.intro": "chce rozpocząć rozmowę z Tobą", + "chat_message_list_intro.leave_chat.confirm": "Opuść czat", + "chat_message_list_intro.leave_chat.heading": "Opuść czat", + "chat_search.blankslate.title": "Rozpocznij rozmowę", + "chat_search.placeholder": "Wprowadź nazwę", + "chat_search.title": "Wiadomości", + "chat_settings.auto_delete.14days": "14 fni", + "chat_settings.auto_delete.2minutes": "2 minuty", + "chat_settings.auto_delete.30days": "30 dni", + "chat_settings.auto_delete.7days": "7 dni", + "chat_settings.auto_delete.90days": "90 dni", + "chat_settings.auto_delete.days": "{day, plural, one {# dzień} few {# dni} other {# dni}}", + "chat_settings.block.confirm": "Zablokuj", + "chat_settings.block.heading": "Zablokuj @{acct}", + "chat_settings.leave.confirm": "Opuść czat", + "chat_settings.leave.heading": "Opuść czat", + "chat_settings.options.block_user": "Zablokuj @{acct}", + "chat_settings.options.leave_chat": "Opuść czat", + "chat_settings.options.report_user": "Zgłoś @{acct}", + "chat_settings.options.unblock_user": "Odblokuj @{acct}", + "chat_settings.title": "Szczegóły czatu", + "chat_settings.unblock.confirm": "Odblokuj", + "chat_settings.unblock.heading": "Odblokuj @{acct}", + "chats.actions.copy": "Kopiuj", "chats.actions.delete": "Usuń wiadomość", "chats.actions.more": "Więcej", "chats.actions.report": "Zgłoś użytkownika", "chats.dividers.today": "Dzisiaj", + "chats.main.blankslate_with_chats.title": "Wybierz czat", "chats.search_placeholder": "Rozpocznij rozmowę z…", + "column.admin.announcements": "Ogłoszenia", "column.admin.awaiting_approval": "Oczekujące na przyjęcie", + "column.admin.create_announcement": "Utwórz ogłoszenie", "column.admin.dashboard": "Panel administracyjny", + "column.admin.edit_announcement": "Edytuj ogłoszenie", "column.admin.moderation_log": "Dziennik moderacyjny", "column.admin.reports": "Zgłoszenia", "column.admin.reports.menu.moderation_log": "Dziennik moderacji", @@ -222,6 +291,9 @@ "column.directory": "Przeglądaj profile", "column.domain_blocks": "Ukryte domeny", "column.edit_profile": "Edytuj profil", + "column.event_map": "Lokalizacja wydarzenia", + "column.event_participants": "Uczestnicy wydarzenia", + "column.events": "Wydarzenia", "column.export_data": "Eksportuj dane", "column.familiar_followers": "Obserwujący {name} których znasz", "column.favourited_statuses": "Polubione wpisy", @@ -248,11 +320,14 @@ "column.follow_requests": "Prośby o obserwację", "column.followers": "Obserwujący", "column.following": "Obserwowani", + "column.group_blocked_members": "Zablokowani członkowie", + "column.group_pending_requests": "Oczekujące zgłoszenia", "column.groups": "Grupy", "column.home": "Strona główna", "column.import_data": "Importuj dane", "column.info": "Informacje o serwerze", "column.lists": "Listy", + "column.manage_group": "Zarządzaj grupą", "column.mentions": "W odpowiedzi do", "column.mfa": "Uwierzytelnianie wieloetapowe", "column.mfa_cancel": "Anuluj", @@ -265,6 +340,7 @@ "column.pins": "Przypięte wpisy", "column.preferences": "Preferencje", "column.public": "Globalna oś czasu", + "column.quotes": "Cytatu wpisu", "column.reactions": "Reakcje", "column.reblogs": "Podbicia", "column.scheduled_statuses": "Zaplanowane wpisy", @@ -307,6 +383,7 @@ "compose_event.update": "Aktualizuj", "compose_event.upload_banner": "Wyślij obraz", "compose_form.direct_message_warning": "Ten wpis będzie widoczny tylko dla wszystkich wspomnianych użytkowników.", + "compose_form.event_placeholder": "Napisz w tym wydarzeniu", "compose_form.hashtag_warning": "Ten wpis nie będzie widoczny pod podanymi hashtagami, ponieważ jest oznaczony jako niewidoczny. Tylko publiczne wpisy mogą zostać znalezione z użyciem hashtagów.", "compose_form.lock_disclaimer": "Twoje konto nie jest {locked}. Każdy, kto Cię obserwuje, może wyświetlać Twoje wpisy przeznaczone tylko dla obserwujących.", "compose_form.lock_disclaimer.lock": "zablokowane", @@ -339,6 +416,9 @@ "confirmations.admin.deactivate_user.confirm": "Dezaktywuj @{name}", "confirmations.admin.deactivate_user.heading": "Dezaktywuj @{acct}", "confirmations.admin.deactivate_user.message": "Zamierzasz zdezaktywować @{acct}. Dezaktywacja konta może zostać cofnięta.", + "confirmations.admin.delete_announcement.confirm": "Usuń", + "confirmations.admin.delete_announcement.heading": "Usuń ogłoszenie", + "confirmations.admin.delete_announcement.message": "Czy na pewno chcesz usunąć to ogłoszenie?", "confirmations.admin.delete_local_user.checkbox": "Wiem, że właśnie usuwam lokalnego użytkownika.", "confirmations.admin.delete_status.confirm": "Usuń wpis", "confirmations.admin.delete_status.heading": "Usuń wpis", @@ -359,18 +439,36 @@ "confirmations.block.confirm": "Zablokuj", "confirmations.block.heading": "Zablokuj @{name}", "confirmations.block.message": "Czy na pewno chcesz zablokować {name}?", + "confirmations.block_from_group.confirm": "Zablokuj", + "confirmations.block_from_group.heading": "Zablokuj członka grupy", + "confirmations.cancel.confirm": "Odrzuć", + "confirmations.cancel.heading": "Odrzuć wpis", + "confirmations.cancel.message": "Czy na pewno chcesz anulować pisanie tego wpisu?", "confirmations.cancel_editing.confirm": "Anuluj edycję", "confirmations.cancel_editing.heading": "Anuluj edycję wpisu", "confirmations.cancel_editing.message": "Czy na pewno chcesz anulować edytowanie wpisu? Niezapisane zmiany zostaną utracone.", + "confirmations.cancel_event_editing.heading": "Anuluj edycję wydarzenia", + "confirmations.cancel_event_editing.message": "Czy na pewno chcesz anulować edycję tego wydarzenia? Wszystkie zmiany zostaną utracone.", "confirmations.delete.confirm": "Usuń", "confirmations.delete.heading": "Usuń wpis", "confirmations.delete.message": "Czy na pewno chcesz usunąć ten wpis?", + "confirmations.delete_event.confirm": "Usuń", + "confirmations.delete_event.heading": "Usuń wydarzenie", + "confirmations.delete_event.message": "Czy na pewno chcesz usunąć to wydarzenie?", + "confirmations.delete_from_group.heading": "Usuń z grupy", + "confirmations.delete_from_group.message": "Czy na pewno chcesz usunąć wpis @{name}?", + "confirmations.delete_group.confirm": "Usuń", + "confirmations.delete_group.heading": "Usuń grupę", + "confirmations.delete_group.message": "Czy na pewno chcesz usunąć tę grupę? To działanie jest nieodwracalne.", "confirmations.delete_list.confirm": "Usuń", "confirmations.delete_list.heading": "Usuń listę", "confirmations.delete_list.message": "Czy na pewno chcesz bezpowrotnie usunąć tą listę?", "confirmations.domain_block.confirm": "Ukryj wszysyko z domeny", "confirmations.domain_block.heading": "Zablokuj {domain}", "confirmations.domain_block.message": "Czy na pewno chcesz zablokować całą domenę {domain}? Zwykle lepszym rozwiązaniem jest blokada lub wyciszenie kilku użytkowników.", + "confirmations.kick_from_group.confirm": "Wyrzuć", + "confirmations.kick_from_group.heading": "Wyrzuć członka grupy", + "confirmations.kick_from_group.message": "Czy na pewno chcesz wyrzucić @{name} z tej grupy?", "confirmations.leave_event.confirm": "Opuść wydarzenie", "confirmations.leave_event.message": "Jeśli będziesz chciał(a) dołączyć do wydarzenia jeszcze raz, prośba będzie musiała zostać ponownie zatwierdzona. Czy chcesz kontynuować?", "confirmations.leave_group.confirm": "Opuść", @@ -379,6 +477,8 @@ "confirmations.mute.confirm": "Wycisz", "confirmations.mute.heading": "Wycisz @{name}", "confirmations.mute.message": "Czy na pewno chcesz wyciszyć {name}?", + "confirmations.promote_in_group.confirm": "Promuj", + "confirmations.promote_in_group.message": "Czy na pewno chcesz promować @{name}? Nie będziesz mógł(-ła) obniżyć ich rangi.", "confirmations.redraft.confirm": "Usuń i przeredaguj", "confirmations.redraft.heading": "Usuń i przeredaguj", "confirmations.redraft.message": "Czy na pewno chcesz usunąć i przeredagować ten wpis? Polubienia i podbicia zostaną utracone, a odpowiedzi do oryginalnego wpisu zostaną osierocone.", @@ -423,6 +523,7 @@ "developers.navigation.network_error_label": "Błąd sieci", "developers.navigation.service_worker_label": "Service Worker", "developers.navigation.settings_store_label": "Settings store", + "developers.navigation.show_toast": "Wywołaj powiadomienie", "developers.navigation.test_timeline_label": "Testowa oś czasu", "developers.settings_store.advanced": "Zaawansowane ustawienia", "developers.settings_store.hint": "Możesz tu bezpośrednio edytować swoje ustawienia. UWAŻAJ! Edytowanie tej sekcji może uszkodzić Twoje konto, co może zostać naprawione tylko przez API.", @@ -462,7 +563,7 @@ "edit_profile.fields.meta_fields_label": "Pola profilu", "edit_profile.fields.stranger_notifications_label": "Blokuj powiadomienia od nieznajomych", "edit_profile.fields.website_label": "Strona internetowa", - "edit_profile.fields.website_placeholder": "Wyświetl link", + "edit_profile.fields.website_placeholder": "Wyświetl odnośnik", "edit_profile.header": "Edytuj profil", "edit_profile.hints.accepts_email_list": "Otrzymuj wiadomości i nowości marketingowe.", "edit_profile.hints.avatar": "PNG, GIF lub JPG. Zostanie zmniejszony do {size}", @@ -514,6 +615,7 @@ "empty_column.account_favourited_statuses": "Ten użytkownik nie polubił jeszcze żadnego wpisu.", "empty_column.account_timeline": "Brak wpisów tutaj!", "empty_column.account_unavailable": "Profil niedostępny", + "empty_column.admin.announcements": "Brak ogłoszeń.", "empty_column.aliases": "Nie utworzyłeś(-aś) jeszcze żadnego aliasu konta.", "empty_column.aliases.suggestions": "Brak propozycji kont dla podanej frazy.", "empty_column.blocks": "Nie zablokowałeś(-aś) jeszcze żadnego użytkownika.", @@ -521,12 +623,14 @@ "empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby zagaić!", "empty_column.direct": "Nie masz żadnych wiadomości bezpośrednich. Kiedy dostaniesz lub wyślesz jakąś, pojawi się ona tutaj.", "empty_column.domain_blocks": "Brak ukrytych domen.", + "empty_column.event_participant_requests": "Brak oczekujących zgłoszeń udziału w wydarzenie.", "empty_column.favourited_statuses": "Nie polubiłeś(-aś) żadnego wpisu. Kiedy to zrobisz, pojawi się on tutaj.", "empty_column.favourites": "Nikt nie dodał tego wpisu do ulubionych. Gdy ktoś to zrobi, pojawi się tutaj.", "empty_column.filters": "Nie wyciszyłeś(-aś) jeszcze żadnego słowa.", "empty_column.follow_recommendations": "Wygląda na to, że nie można wygenerować dla Ciebie sugestii kont do obserwacji. Możesz spróbować użyć wyszukiwania aby odnaleźć ciekawe profile, lub przejrzeć trendujące hashtagi.", "empty_column.follow_requests": "Nie masz żadnych próśb o możliwość obserwacji. Kiedy ktoś utworzy ją, pojawi się tutaj.", "empty_column.group": "Nie ma wpisów w tej grupie.", + "empty_column.group_blocks": "Ta grupa nie zablokowała jeszcze nikogo.", "empty_column.hashtag": "Nie ma wpisów oznaczonych tym hashtagiem. Możesz napisać pierwszy(-a)!", "empty_column.home": "Możesz też odwiedzić {public}, aby znaleźć innych użytkowników.", "empty_column.home.local_tab": "zakładkę {site_title}", @@ -545,6 +649,29 @@ "empty_column.search.hashtags": "Brak wyników wyszukiwania hashtagów dla „{term}”", "empty_column.search.statuses": "Brak wyników wyszukiwania wpisów dla „{term}”", "empty_column.test": "Testowa oś czasu jest pusta.", + "event.banner": "Baner wydarzenia", + "event.copy": "Kopiuj odnośnik do wydarzenia", + "event.date": "Data", + "event.description": "Opis", + "event.export_ics": "Eksportuj do kalendarza", + "event.external": "Wyświetl ogłoszenie na {domain}", + "event.join_state.accept": "Biorę udział", + "event.join_state.empty": "Weź udział", + "event.join_state.pending": "Oczekujące", + "event.join_state.rejected": "Biorę udział", + "event.location": "Lokalizacja", + "event.manage": "Zarządzaj", + "event.organized_by": "Organizowane przez {name}", + "event.quote": "Cytuj wydarzenie", + "event.reblog": "Udostępnij wydarzenie", + "event.show_on_map": "Pokaż na mapie", + "event.unreblog": "Cofnij udostępnienie", + "event.website": "Zewnętrzne odnośniki", + "event_map.navigate": "Nawiguj", + "events.create_event": "Utwórz wydarzenie", + "events.joined_events": "Dołączone wydarzenia", + "events.joined_events.empty": "Jeszcze nie dołączyłeś(-aś) do zadnego wydarzenia.", + "events.recent_events": "Najnowsze wydarzenia", "export_data.actions.export": "Eksportuj dane", "export_data.actions.export_blocks": "Eksportuj blokady", "export_data.actions.export_follows": "Eksportuj obserwacje", @@ -590,14 +717,28 @@ "gdpr.title": "{siteTitle} korzysta z ciasteczek", "getting_started.open_source_notice": "{code_name} jest oprogramowaniem o otwartym źródle. Możesz pomóc w rozwoju lub zgłaszać błędy na GitLabie tutaj: {code_link} (v{code_version}).", "group.admin_subheading": "Administratorzy grupy", + "group.cancel_request": "Anuluj zgłoszenie", + "group.group_mod_authorize": "Akceptuj", + "group.group_mod_authorize.success": "Przyjmij @{name} do grupy", + "group.group_mod_block": "Zablokuj @{name} z grupy", + "group.group_mod_block.success": "Zablokowano @{name} z grupy", + "group.group_mod_kick": "Wyrzuć @{name} z grupy", + "group.group_mod_kick.success": "Wyrzucono @{name} z grupy", + "group.group_mod_promote_admin": "Promuj @{name} na administratora grupy", + "group.group_mod_promote_admin.success": "Promowano @{name} na administratora grupy", + "group.group_mod_promote_mod": "Promuj @{name} na moderatora grupy", + "group.group_mod_promote_mod.success": "Promowano @{name} na moderatora grupy", + "group.group_mod_reject": "Odrzuć", + "group.group_mod_unblock": "Odblokuj", "group.header.alt": "Nagłówek grupy", - "group.join": "Dołącz do grupy", + "group.join.public": "Dołącz do grupy", "group.leave": "Opuść grupę", + "group.leave.success": "Opuść grupę", "group.manage": "Edytuj grupę", "group.moderator_subheading": "Moderatorzy grupy", "group.privacy.locked": "Prywatna", "group.privacy.public": "Publiczna", - "group.request_join": "Poproś o dołączenie do grupy", + "group.join.private": "Poproś o dołączenie do grupy", "group.role.admin": "Administrator", "group.role.moderator": "Moderator", "group.tabs.all": "Wszystko", @@ -619,7 +760,6 @@ "home.column_settings.show_replies": "Pokazuj odpowiedzi", "icon_button.icons": "Ikony", "icon_button.label": "Wybierz ikonę", - "icon_button.not_found": "Brak ikon!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Importuj", "import_data.actions.import_blocks": "Importuj blokady", "import_data.actions.import_follows": "Importuj obserwacje", @@ -1199,6 +1339,7 @@ "tabs_bar.profile": "Profil", "tabs_bar.search": "Szukaj", "tabs_bar.settings": "Ustawienia", + "theme_editor.saved": "Zaktualizowano motyw!", "theme_toggle.dark": "Ciemny", "theme_toggle.light": "Jasny", "theme_toggle.system": "Systemowy", @@ -1211,6 +1352,7 @@ "time_remaining.minutes": "{number, plural, one {Pozostała # minuta} few {Pozostały # minuty} many {Pozostało # minut} other {Pozostało # minut}}", "time_remaining.moments": "Pozostała chwila", "time_remaining.seconds": "{number, plural, one {Pozostała # sekunda} few {Pozostały # sekundy} many {Pozostało # sekund} other {Pozostało # sekund}}", + "toast.view": "Wyświetl", "trends.count_by_accounts": "{count} {rawCount, plural, one {osoba rozmawia} few {osoby rozmawiają} other {osób rozmawia}} o tym", "trends.title": "Trendy", "trendsPanel.viewAll": "Pokaż wszystkie", @@ -1221,7 +1363,7 @@ "upload_error.image_size_limit": "Obraz przekracza limit rozmiaru plików ({limit})", "upload_error.limit": "Przekroczono limit plików do wysłania.", "upload_error.poll": "Dołączanie plików nie dozwolone z głosowaniami.", - "upload_error.video_duration_limit": "Film przekracza limit długości ({limit} sekund)", + "upload_error.video_duration_limit": "Film przekracza limit długości ({limit, plural, one {# sekunda} few {# sekundy} other {# sekund}})", "upload_error.video_size_limit": "Film przekracza limit rozmiaru plików ({limit})", "upload_form.description": "Wprowadź opis dla niewidomych i niedowidzących", "upload_form.preview": "Podgląd", diff --git a/app/soapbox/locales/pt-BR.json b/app/soapbox/locales/pt-BR.json index 35e53ba4d..b43fd8103 100644 --- a/app/soapbox/locales/pt-BR.json +++ b/app/soapbox/locales/pt-BR.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Mostrar as respostas", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/pt.json b/app/soapbox/locales/pt.json index 1398c721f..3116b1e78 100644 --- a/app/soapbox/locales/pt.json +++ b/app/soapbox/locales/pt.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Mostrar respostas", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Importar", "import_data.actions.import_blocks": "Importar bloqueados", "import_data.actions.import_follows": "Importar contas a seguir", diff --git a/app/soapbox/locales/ro.json b/app/soapbox/locales/ro.json index bfc85555c..a5ff411d6 100644 --- a/app/soapbox/locales/ro.json +++ b/app/soapbox/locales/ro.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Arată răspunsurile", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/ru.json b/app/soapbox/locales/ru.json index 9fae3ad01..85b4244ec 100644 --- a/app/soapbox/locales/ru.json +++ b/app/soapbox/locales/ru.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Показывать ответы", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/sk.json b/app/soapbox/locales/sk.json index 131f45e39..4fb280236 100644 --- a/app/soapbox/locales/sk.json +++ b/app/soapbox/locales/sk.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Ukázať odpovede", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/sl.json b/app/soapbox/locales/sl.json index c4c2e135f..9f27c21a1 100644 --- a/app/soapbox/locales/sl.json +++ b/app/soapbox/locales/sl.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Pokaži odgovore", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/sr-Latn.json b/app/soapbox/locales/sr-Latn.json index ff6f05f00..439290e96 100644 --- a/app/soapbox/locales/sr-Latn.json +++ b/app/soapbox/locales/sr-Latn.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Prikaži odgovore", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/sr.json b/app/soapbox/locales/sr.json index e49ea247f..20d45a3eb 100644 --- a/app/soapbox/locales/sr.json +++ b/app/soapbox/locales/sr.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Прикажи одговоре", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/sv.json b/app/soapbox/locales/sv.json index 71101450e..e39b3b306 100644 --- a/app/soapbox/locales/sv.json +++ b/app/soapbox/locales/sv.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Visa svar", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/ta.json b/app/soapbox/locales/ta.json index dc569abe7..301f96849 100644 --- a/app/soapbox/locales/ta.json +++ b/app/soapbox/locales/ta.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "பதில்களைக் காண்பி", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/te.json b/app/soapbox/locales/te.json index db80c674b..0eef2b7f3 100644 --- a/app/soapbox/locales/te.json +++ b/app/soapbox/locales/te.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "ప్రత్యుత్తరాలను చూపించు", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/th.json b/app/soapbox/locales/th.json index d90ee1668..94a43faf6 100644 --- a/app/soapbox/locales/th.json +++ b/app/soapbox/locales/th.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "แสดงการตอบกลับ", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/tr.json b/app/soapbox/locales/tr.json index f354382ba..75e359d8a 100644 --- a/app/soapbox/locales/tr.json +++ b/app/soapbox/locales/tr.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Cevapları göster", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Import", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/uk.json b/app/soapbox/locales/uk.json index bc3a52352..790675be6 100644 --- a/app/soapbox/locales/uk.json +++ b/app/soapbox/locales/uk.json @@ -564,7 +564,6 @@ "home.column_settings.show_replies": "Показувати відповіді", "icon_button.icons": "Icons", "icon_button.label": "Select icon", - "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "Імпорт", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", diff --git a/app/soapbox/locales/zh-CN.json b/app/soapbox/locales/zh-CN.json index 0fa22a73c..e1eab5e31 100644 --- a/app/soapbox/locales/zh-CN.json +++ b/app/soapbox/locales/zh-CN.json @@ -10,7 +10,7 @@ "account.block_domain": "隐藏来自 {domain} 的内容", "account.blocked": "已屏蔽", "account.chat": "与 @{name} 聊天", - "account.deactivated": "已禁用", + "account.deactivated": "已停用", "account.direct": "发送私信给 @{name}", "account.domain_blocked": "站点已隐藏", "account.edit_profile": "修改个人资料", @@ -69,7 +69,7 @@ "account_moderation_modal.admin_fe": "在 AdminFE 中打开", "account_moderation_modal.fields.account_role": "人员级别", "account_moderation_modal.fields.badges": "自定义徽章", - "account_moderation_modal.fields.deactivate": "禁用帐号", + "account_moderation_modal.fields.deactivate": "停用帐号", "account_moderation_modal.fields.delete": "删除帐号", "account_moderation_modal.fields.suggested": "推荐关注的人", "account_moderation_modal.fields.verified": "已认证账户", @@ -85,12 +85,18 @@ "account_search.placeholder": "搜索帐号", "actualStatus.edited": "编辑于 {date}", "actualStatuses.quote_tombstone": "帖文不可用。", + "admin.announcements.action": "创建公告", + "admin.announcements.all_day": "全天", + "admin.announcements.delete": "删除", + "admin.announcements.edit": "编辑", + "admin.announcements.ends_at": "结束于:", + "admin.announcements.starts_at": "开始于:", "admin.awaiting_approval.approved_message": "{acct} 的注册申请已通过!", "admin.awaiting_approval.empty_message": "没有未处理的注册申请,如果有新的申请,它就会显示在这里。", "admin.awaiting_approval.rejected_message": "{acct} 的注册请求已拒绝。", "admin.dashboard.registration_mode.approval_hint": "用户可以注册,但帐户只有在管理员批准后才会被激活。", "admin.dashboard.registration_mode.approval_label": "需要批准", - "admin.dashboard.registration_mode.closed_hint": "用户不可以注册。但你仍可以邀请别人。", + "admin.dashboard.registration_mode.closed_hint": "用户不可以注册。但您仍可以邀请别人。", "admin.dashboard.registration_mode.closed_label": "私密", "admin.dashboard.registration_mode.open_hint": "任何人都可以加入。", "admin.dashboard.registration_mode.open_label": "公开", @@ -103,6 +109,18 @@ "admin.dashcounters.user_count_label": "总用户数", "admin.dashwidgets.email_list_header": "邮件列表", "admin.dashwidgets.software_header": "软件版本", + "admin.edit_announcement.created": "公告已创建", + "admin.edit_announcement.deleted": "公告已删除", + "admin.edit_announcement.fields.all_day_hint": "勾选后,将仅显示时间范围内的日期", + "admin.edit_announcement.fields.all_day_label": "全天事件", + "admin.edit_announcement.fields.content_label": "内容", + "admin.edit_announcement.fields.content_placeholder": "公告内容", + "admin.edit_announcement.fields.end_time_label": "结束日期", + "admin.edit_announcement.fields.end_time_placeholder": "公告结束于:", + "admin.edit_announcement.fields.start_time_label": "开始日期", + "admin.edit_announcement.fields.start_time_placeholder": "公告开始于:", + "admin.edit_announcement.save": "保存", + "admin.edit_announcement.updated": "公告已编辑", "admin.latest_accounts_panel.more": "点击查看 {count} 个帐号", "admin.latest_accounts_panel.title": "最近帐号", "admin.moderation_log.empty_message": "您尚未执行过管理操作,若执行过,则历史记录便会显示在这里。", @@ -110,19 +128,19 @@ "admin.reports.actions.view_status": "查看帖文", "admin.reports.empty_message": "没有未处理的举报,如果有新的举报,它就会显示在这里。", "admin.reports.report_closed_message": "对 @{name} 的举报已关闭", - "admin.reports.report_title": "对 @{name} 的举报", + "admin.reports.report_title": "对 {acct} 的举报", "admin.software.backend": "后端", "admin.software.frontend": "前端", "admin.statuses.actions.delete_status": "删除帖文", "admin.statuses.actions.mark_status_not_sensitive": "不再标记为敏感", "admin.statuses.actions.mark_status_sensitive": "标记为敏感", "admin.statuses.status_deleted_message": "@{acct} 的帖文已被删除", - "admin.statuses.status_marked_message_not_sensitive": "@{acct} 的帖文已不再被标记敏感", - "admin.statuses.status_marked_message_sensitive": "@{acct} 的帖文已被标记敏感", + "admin.statuses.status_marked_message_not_sensitive": "@{acct} 的帖文已不再被标记为敏感", + "admin.statuses.status_marked_message_sensitive": "@{acct} 的帖文已被标记为敏感", "admin.theme.title": "主题", "admin.user_index.empty": "未找到用户。", "admin.user_index.search_input_placeholder": "搜索哪位用户?", - "admin.users.actions.deactivate_user": "禁用帐号 @{name}", + "admin.users.actions.deactivate_user": "停用 @{name}", "admin.users.actions.delete_user": "删除帐号 @{name}", "admin.users.actions.demote_to_moderator_message": "@{acct} 已被降级为站务", "admin.users.actions.demote_to_user_message": "@{acct} 已被降级为普通用户", @@ -131,7 +149,7 @@ "admin.users.badges_saved_message": "自定义徽章已更新。", "admin.users.remove_donor_message": "@{acct} 已从捐赠者列表中移除", "admin.users.set_donor_message": "@{acct} 已被设置为捐赠者", - "admin.users.user_deactivated_message": "@{acct} 已被禁用", + "admin.users.user_deactivated_message": "@{acct} 已停用", "admin.users.user_deleted_message": "@{acct} 已被删除", "admin.users.user_suggested_message": "@{acct} 已被推荐", "admin.users.user_unsuggested_message": "@{acct} 已被取消推荐", @@ -208,7 +226,7 @@ "chat.welcome.title": "欢迎来到 {br} 聊天!", "chat_composer.unblock": "解除屏蔽", "chat_list_item.blocked_you": "此用户已将您屏蔽", - "chat_list_item.blocking": "您已屏蔽该用户", + "chat_list_item.blocking": "您已屏蔽此用户", "chat_message_list.blocked": "您屏蔽了此用户", "chat_message_list.blockedBy": "您被此用户屏蔽", "chat_message_list.network_failure.action": "重试", @@ -265,8 +283,11 @@ "chats.main.blankslate_with_chats.subtitle": "从打开的聊天中选择一项或创建新消息。", "chats.main.blankslate_with_chats.title": "选择聊天", "chats.search_placeholder": "开始聊天……", + "column.admin.announcements": "公告", "column.admin.awaiting_approval": "等待批准", + "column.admin.create_announcement": "创建公告", "column.admin.dashboard": "仪表板", + "column.admin.edit_announcement": "编辑公告", "column.admin.moderation_log": "管理记录", "column.admin.reports": "举报", "column.admin.reports.menu.moderation_log": "管理记录", @@ -359,14 +380,14 @@ "compose.submit_success": "帖文已发送!", "compose_event.create": "创建", "compose_event.edit_success": "您的活动被编辑了", - "compose_event.fields.approval_required": "我想手动批准参与请求", + "compose_event.fields.approval_required": "我想手动批准参与申请", "compose_event.fields.banner_label": "活动横幅", "compose_event.fields.description_hint": "支持 Markdown 语法", "compose_event.fields.description_label": "活动说明", "compose_event.fields.description_placeholder": "说明", "compose_event.fields.end_time_label": "活动结束日期", "compose_event.fields.end_time_placeholder": "活动结束于…", - "compose_event.fields.has_end_time": "该活动有结束日期", + "compose_event.fields.has_end_time": "此活动有结束日期", "compose_event.fields.location_label": "活动地点", "compose_event.fields.name_label": "活动名称", "compose_event.fields.name_placeholder": "名称", @@ -379,7 +400,7 @@ "compose_event.reset_location": "重设位置", "compose_event.submit_success": "您的活动已创建", "compose_event.tabs.edit": "编辑详细信息", - "compose_event.tabs.pending": "管理请求", + "compose_event.tabs.pending": "管理申请", "compose_event.update": "更新", "compose_event.upload_banner": "上传活动横幅", "compose_form.direct_message_warning": "此帖文仅对所有被提及的用户可见。", @@ -410,28 +431,31 @@ "compose_form.spoiler.marked": "正文已被折叠在警告信息之后", "compose_form.spoiler.unmarked": "正文未被折叠", "compose_form.spoiler_placeholder": "折叠部分的警告消息", - "compose_form.spoiler_remove": "移除敏感内容", + "compose_form.spoiler_remove": "移除敏感标记", "compose_form.spoiler_title": "敏感内容", "confirmation_modal.cancel": "取消", - "confirmations.admin.deactivate_user.confirm": "禁用帐号 @{name}", - "confirmations.admin.deactivate_user.heading": "禁用帐号 @{acct}", - "confirmations.admin.deactivate_user.message": "您确定要禁用帐号 @{acct} 吗?该操作无法撤回。", - "confirmations.admin.delete_local_user.checkbox": "我确定我要删除一个本地帐号。", + "confirmations.admin.deactivate_user.confirm": "停用帐号 @{name}", + "confirmations.admin.deactivate_user.heading": "停用帐号 @{acct}", + "confirmations.admin.deactivate_user.message": "您即将停用 @{acct}。停用用户是一个可撤销的操作。", + "confirmations.admin.delete_announcement.confirm": "删除", + "confirmations.admin.delete_announcement.heading": "删除公告", + "confirmations.admin.delete_announcement.message": "您确定要删除公告吗?", + "confirmations.admin.delete_local_user.checkbox": "我确定我即将要删除一个本站用户。", "confirmations.admin.delete_status.confirm": "删除帖文", "confirmations.admin.delete_status.heading": "删除帖文", - "confirmations.admin.delete_status.message": "您确定要删除 @{acct} 的帖文吗?该操作无法撤回。", + "confirmations.admin.delete_status.message": "您即将删除 @{acct} 的帖文。此操作无法撤销。", "confirmations.admin.delete_user.confirm": "删除帐号 @{name}", "confirmations.admin.delete_user.heading": "删除帐号 @{acct}", - "confirmations.admin.delete_user.message": "您确定要删除本站帐号 @{acct} 吗?该操作无法撤回。", - "confirmations.admin.mark_status_not_sensitive.confirm": "敏感为不敏感", - "confirmations.admin.mark_status_not_sensitive.heading": "标记贴文为非敏感。", - "confirmations.admin.mark_status_not_sensitive.message": "您将标记帐号 @{acct} 的帖文为非敏感。", + "confirmations.admin.delete_user.message": "您即将删除帐号 @{acct}。此操作极其危险,无法撤销。", + "confirmations.admin.mark_status_not_sensitive.confirm": "标记为非敏感", + "confirmations.admin.mark_status_not_sensitive.heading": "标记帖文为非敏感。", + "confirmations.admin.mark_status_not_sensitive.message": "您即将标记帐号 @{acct} 的帖文为非敏感。", "confirmations.admin.mark_status_sensitive.confirm": "标记为敏感帖文", "confirmations.admin.mark_status_sensitive.heading": "标记为敏感帖文", - "confirmations.admin.mark_status_sensitive.message": "您将标记帐号 @{acct} 的帖文为敏感。", + "confirmations.admin.mark_status_sensitive.message": "您即将标记帐号 @{acct} 的帖文为敏感。", "confirmations.admin.reject_user.confirm": "拒绝 @{name}", "confirmations.admin.reject_user.heading": "拒绝 @{acct}", - "confirmations.admin.reject_user.message": "您将拒绝 @{acct} 的注册请求。该操作无法撤回。", + "confirmations.admin.reject_user.message": "您即将拒绝 @{acct} 的注册请求。此操作无法撤销。", "confirmations.block.block_and_report": "屏蔽与举报", "confirmations.block.confirm": "屏蔽", "confirmations.block.heading": "屏蔽 @{name}", @@ -440,11 +464,11 @@ "confirmations.block_from_group.heading": "屏蔽群组成员", "confirmations.block_from_group.message": "您确定要屏蔽 @{name} 与此群组互动吗?", "confirmations.cancel.confirm": "丢弃", - "confirmations.cancel.heading": "丢弃贴文", - "confirmations.cancel.message": "您确定要取消创建此贴文吗?", + "confirmations.cancel.heading": "丢弃帖文", + "confirmations.cancel.message": "您确定要取消创建此帖文吗?", "confirmations.cancel_editing.confirm": "取消编辑", - "confirmations.cancel_editing.heading": "取消编辑贴文", - "confirmations.cancel_editing.message": "您确定您要取消编辑此贴文吗?所有的修改都会丢失。", + "confirmations.cancel_editing.heading": "取消编辑帖文", + "confirmations.cancel_editing.message": "您确定您要取消编辑此帖文吗?所有的修改都会丢失。", "confirmations.cancel_event_editing.heading": "取消活动编辑", "confirmations.cancel_event_editing.message": "您确定您要取消编辑此活动吗?所有的修改都会丢失。", "confirmations.delete.confirm": "删除", @@ -468,10 +492,10 @@ "confirmations.kick_from_group.heading": "踢出群组成员", "confirmations.kick_from_group.message": "您确定要将 @{name} 踢出此群组吗?", "confirmations.leave_event.confirm": "离开活动", - "confirmations.leave_event.message": "如果您想重新加入该活动,该请求将被再次人工审核。您确定要继续吗?", + "confirmations.leave_event.message": "如果您想重新加入此活动,申请将被再次人工审核。您确定要继续吗?", "confirmations.leave_group.confirm": "离开", "confirmations.leave_group.heading": "离开群组", - "confirmations.leave_group.message": "您即将离开此小组。你想继续吗?", + "confirmations.leave_group.message": "您即将离开此小组。您想继续吗?", "confirmations.mute.confirm": "静音", "confirmations.mute.heading": "静音 @{name}", "confirmations.mute.message": "您确定要静音 {name} 吗?", @@ -490,7 +514,7 @@ "confirmations.reply.message": "回复此消息将会覆盖当前正在编辑的信息。确定继续吗?", "confirmations.revoke_session.confirm": "撤销", "confirmations.revoke_session.heading": "撤销当前会话", - "confirmations.revoke_session.message": "您将撤销当前的会话。您将会登出。", + "confirmations.revoke_session.message": "您即将撤销当前的会话。您将会登出。", "confirmations.scheduled_status_delete.confirm": "取消", "confirmations.scheduled_status_delete.heading": "取消帖文定时发布", "confirmations.scheduled_status_delete.message": "您确定要取消此帖文的定时发布吗?", @@ -584,7 +608,7 @@ "email_passthru.generic_fail.body": "请重新请求确认邮件。", "email_passthru.generic_fail.heading": "出错了", "email_passthru.success": "您的电子邮件已通过验证!", - "email_passthru.token_expired.body": "您的电子邮件令牌已经过期。请从您发送此电子邮件确认的 {bold} 处申请新的电子邮件确认。", + "email_passthru.token_expired.body": "您的电子邮件令牌已经过期。请从您发送此电子邮件确认的 {bold} 处请求新的电子邮件确认。", "email_passthru.token_expired.heading": "令牌已经过期", "email_passthru.token_not_found.body": "您的电子邮件令牌没有找到。请从您发送此电子邮件确认的 {bold} 处申请新的电子邮件确认。", "email_passthru.token_not_found.heading": "非法令牌", @@ -596,6 +620,7 @@ "email_verifilcation.exists": "此电子邮件已被占用。", "embed.instructions": "要在您的站点上嵌入此帖文,请复制以下代码。", "emoji_button.activity": "活动", + "emoji_button.add_custom": "添加自定义表情符号", "emoji_button.custom": "自定义", "emoji_button.flags": "旗帜", "emoji_button.food": "食物和饮料", @@ -603,16 +628,26 @@ "emoji_button.nature": "自然", "emoji_button.not_found": "未找到表情符号。", "emoji_button.objects": "物体", + "emoji_button.oh_no": "哦不!", "emoji_button.people": "人物", + "emoji_button.pick": "选择一个表情符号…", "emoji_button.recent": "常用", "emoji_button.search": "搜索…", "emoji_button.search_results": "搜索结果", + "emoji_button.skins_1": "默认", + "emoji_button.skins_2": "浅色", + "emoji_button.skins_3": "中-浅色", + "emoji_button.skins_4": "中等", + "emoji_button.skins_5": "中-深色", + "emoji_button.skins_6": "深色", + "emoji_button.skins_choose": "选择默认肤色", "emoji_button.symbols": "符号", "emoji_button.travel": "旅行和地点", "empty_column.account_blocked": "您被 @{accountUsername} 屏蔽了。", - "empty_column.account_favourited_statuses": "此用户尚未点赞任何贴文。", + "empty_column.account_favourited_statuses": "此用户尚未点赞任何帖文。", "empty_column.account_timeline": "这里没有帖文!", "empty_column.account_unavailable": "个人资料不可用", + "empty_column.admin.announcements": "目前还没有公告。", "empty_column.aliases": "您尚未创建别名。", "empty_column.aliases.suggestions": "没有相匹配的帐号建议。", "empty_column.blocks": "您目前没有屏蔽任何用户。", @@ -620,7 +655,7 @@ "empty_column.community": "本站时间轴暂时没有内容,快写点什么让它动起来吧!", "empty_column.direct": "您还没有使用过私信。当您发出或者收到私信时,它会在这里显示。", "empty_column.domain_blocks": "目前没有被隐藏的站点。", - "empty_column.event_participant_requests": "目前没有待处理的活动参与请求。", + "empty_column.event_participant_requests": "目前没有待处理的活动参与申请。", "empty_column.event_participants": "没有人加入过此活动。若有,则会显示在这里。", "empty_column.favourited_statuses": "您还没有点赞过任何帖文。点赞过的帖文会显示在这里。", "empty_column.favourites": "没有人点赞过此帖文。若有,则会显示在这里。", @@ -641,8 +676,8 @@ "empty_column.notifications": "您还没有收到过任何通知,快和其他用户互动吧。", "empty_column.notifications_filtered": "您还没有任何此类通知。", "empty_column.public": "这里什么都没有!写一些公开的帖文,或者关注其他服务器的用户后,这里就会有帖文出现了", - "empty_column.quotes": "此贴文尚未被引用。", - "empty_column.remote": "这里什么都没有!关注本站或者其他站点的用户后,这里就会有用户出现了。", + "empty_column.quotes": "此帖文尚未被引用。", + "empty_column.remote": "这里什么都没有!请手动关注来自 {instance} 的用户以填充它。", "empty_column.scheduled_statuses": "暂无定时帖文。当您发布定时帖文后,它们会显示在这里。", "empty_column.search.accounts": "无帐号匹配 \"{term}\"", "empty_column.search.groups": "无群组匹配 \"{term}\"", @@ -726,7 +761,7 @@ "group.group_mod_block": "从群组中屏蔽 @{name}", "group.group_mod_block.success": "已从群组中屏蔽 @{name}", "group.group_mod_demote": "降级 @{name}", - "group.group_mod_demote.success": "@{acct} 已被降级为群组用户", + "group.group_mod_demote.success": "@{name} 已被降级为群组用户", "group.group_mod_kick": "从群组中踢出 @{name}", "group.group_mod_kick.success": "已从群组中踢出 @{name}", "group.group_mod_promote_admin": "升级 @{name} 至群组管理员", @@ -738,8 +773,9 @@ "group.group_mod_unblock": "解除屏蔽", "group.group_mod_unblock.success": "已从群组中解除屏蔽 @{name}", "group.header.alt": "群组标题", - "group.join": "加入群组", - "group.join.request_success": "已请求加入群组", + "group.join.private": "申请加入群组", + "group.join.public": "加入群组", + "group.join.request_success": "已申请加入群组", "group.join.success": "已加入群组", "group.leave": "离开群组", "group.leave.success": "离开了群组", @@ -747,12 +783,20 @@ "group.moderator_subheading": "群组监察员", "group.privacy.locked": "私有", "group.privacy.public": "公开", - "group.request_join": "请求加入群组", "group.role.admin": "管理员", "group.role.moderator": "监察员", "group.tabs.all": "全部", "group.tabs.members": "成员", "group.user_subheading": "用户", + "groups.discover.search.no_results.subtitle": "请尝试搜索其它群组。", + "groups.discover.search.no_results.title": "未找到匹配项", + "groups.discover.search.placeholder": "搜索", + "groups.discover.search.recent_searches.blankslate.subtitle": "搜索群组名、主题或关键词", + "groups.discover.search.recent_searches.blankslate.title": "没有近期的搜索", + "groups.discover.search.recent_searches.clear_all": "清除全部", + "groups.discover.search.recent_searches.title": "近期搜索", + "groups.discover.search.results.groups": "群组", + "groups.discover.search.results.member_count": "成员", "groups.empty.subtitle": "开始发现可加入的群组或创建您自己的群组。", "groups.empty.title": "尚无群组", "hashtag.column_header.tag_mode.all": "以及{additional}", @@ -769,7 +813,6 @@ "home.column_settings.show_replies": "显示回复", "icon_button.icons": "图标", "icon_button.label": "选择图标", - "icon_button.not_found": "没有图标! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "导入", "import_data.actions.import_blocks": "导入屏蔽列表", "import_data.actions.import_follows": "导入关注列表", @@ -790,9 +833,9 @@ "intervals.full.hours": "{number} 小时", "intervals.full.minutes": "{number} 分钟", "join_event.hint": "您可以告诉组织者您为什么想参加此活动:", - "join_event.join": "请求参加", + "join_event.join": "申请参加", "join_event.placeholder": "发送给组织者的信息", - "join_event.request_success": "已请求参加活动", + "join_event.request_success": "已申请参加活动", "join_event.success": "已参加活动", "join_event.title": "参加活动", "keyboard_shortcuts.back": "返回上一页", @@ -890,8 +933,8 @@ "mfa.mfa_setup.code_placeholder": "代码", "mfa.mfa_setup.password_hint": "输入您的当前密码来确认身份。", "mfa.mfa_setup.password_placeholder": "密码", - "mfa.mfa_setup_scan_description": "请使用 Google 身份验证器或其他的TOTP双重认证手机应用扫描此处的二维码。启用双重认证后,在登录时,您需要输入该应用生成的代码。", - "mfa.mfa_setup_scan_title": "如果您无法扫描二维码,请手动输入下列文本。", + "mfa.mfa_setup_scan_description": "使用您的双因素应用程序,扫描此二维码或输入文本密钥。", + "mfa.mfa_setup_scan_title": "扫描", "mfa.mfa_setup_verify_title": "启用", "mfa.otp_enabled_description": "您已经启用双重认证。", "mfa.otp_enabled_title": "双重认证已启用", @@ -963,8 +1006,8 @@ "new_event_panel.subtitle": "找不到您要查找的内容?安排您自己的活动。", "new_event_panel.title": "创建新活动", "new_group_panel.action": "创建群组", - "new_group_panel.subtitle": "找不到你要找的东西?开始你自己的私有或公共群组。", - "new_group_panel.title": "创建新群组", + "new_group_panel.subtitle": "找不到您要找的东西?开始您自己的私有或公共群组。", + "new_group_panel.title": "创建群组", "notification.favourite": "{name} 赞了您的帖文", "notification.follow": "{name} 开始关注您", "notification.follow_request": "{name} 请求关注您", @@ -976,7 +1019,7 @@ "notification.pleroma:chat_mention": "{name} 给您发送了信息", "notification.pleroma:emoji_reaction": "{name} 表情回应了您的帖文", "notification.pleroma:event_reminder": "您参加的一项活动即将开始", - "notification.pleroma:participation_accepted": "您已被接受加入该活动", + "notification.pleroma:participation_accepted": "您已被接受加入此活动", "notification.pleroma:participation_request": "{name} 想要参加您的活动", "notification.poll": "一项您参加的投票已经结束", "notification.reblog": "{name} 转载了您的帖文", @@ -1053,7 +1096,7 @@ "preferences.fields.demetricator_label": "使用 Demetricator", "preferences.fields.demo_hint": "使用默认的Soapbox标志和颜色方案。有助于截图。", "preferences.fields.demo_label": "演示模式", - "preferences.fields.display_media.default": "隐藏被标记为敏感内容的媒体", + "preferences.fields.display_media.default": "隐藏标记为敏感的帖文", "preferences.fields.display_media.hide_all": "总是隐藏所有媒体", "preferences.fields.display_media.show_all": "总是显示所有媒体", "preferences.fields.expand_spoilers_label": "始终展开标有内容警告的帖文", @@ -1151,7 +1194,7 @@ "remote_interaction.reblog_title": "远程转发帖文", "remote_interaction.reply": "前去回复", "remote_interaction.reply_title": "远程回复帖文", - "remote_interaction.user_not_found_error": "未找到该用户", + "remote_interaction.user_not_found_error": "未找到指定用户", "remote_timeline.filter_message": "您在查看 {instance} 的时间轴。", "reply_indicator.cancel": "取消", "reply_mentions.account.add": "添加到提及列表", @@ -1339,7 +1382,7 @@ "status.pinned": "置顶帖文", "status.quote": "引用帖文", "status.reactions.cry": "伤心", - "status.reactions.empty": "没有人表情回应过此贴文。若有,则会显示在这里。", + "status.reactions.empty": "没有人表情回应过此帖文。若有,则会显示在这里。", "status.reactions.heart": "爱心", "status.reactions.laughing": "哈哈", "status.reactions.like": "喜欢", diff --git a/app/soapbox/locales/zh-HK.json b/app/soapbox/locales/zh-HK.json index d25f7e8d0..032f50225 100644 --- a/app/soapbox/locales/zh-HK.json +++ b/app/soapbox/locales/zh-HK.json @@ -514,7 +514,6 @@ "home.column_settings.show_replies": "顯示回覆", "icon_button.icons": "圖示", "icon_button.label": "選取圖示", - "icon_button.not_found": "沒有圖示!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "匯入", "import_data.actions.import_blocks": "匯入封鎖名單", "import_data.actions.import_follows": "匯入追蹤名單", diff --git a/app/soapbox/locales/zh-TW.json b/app/soapbox/locales/zh-TW.json index 22971c5b8..4bd466253 100644 --- a/app/soapbox/locales/zh-TW.json +++ b/app/soapbox/locales/zh-TW.json @@ -514,7 +514,6 @@ "home.column_settings.show_replies": "顯示回覆", "icon_button.icons": "圖示", "icon_button.label": "選取圖示", - "icon_button.not_found": "沒有圖示!! (╯°□°)╯︵ ┻━┻", "import_data.actions.import": "匯入", "import_data.actions.import_blocks": "匯入封鎖名單", "import_data.actions.import_follows": "匯入追蹤名單", diff --git a/app/soapbox/normalizers/account.ts b/app/soapbox/normalizers/account.ts index fb6a04255..21c15dc68 100644 --- a/app/soapbox/normalizers/account.ts +++ b/app/soapbox/normalizers/account.ts @@ -11,7 +11,7 @@ import { fromJS, } from 'immutable'; -import emojify from 'soapbox/features/emoji/emoji'; +import emojify from 'soapbox/features/emoji'; import { normalizeEmoji } from 'soapbox/normalizers/emoji'; import { unescapeHTML } from 'soapbox/utils/html'; import { mergeDefined, makeEmojiMap } from 'soapbox/utils/normalizers'; diff --git a/app/soapbox/normalizers/announcement.ts b/app/soapbox/normalizers/announcement.ts index 4d665c15f..97eed98be 100644 --- a/app/soapbox/normalizers/announcement.ts +++ b/app/soapbox/normalizers/announcement.ts @@ -10,7 +10,7 @@ import { fromJS, } from 'immutable'; -import emojify from 'soapbox/features/emoji/emoji'; +import emojify from 'soapbox/features/emoji'; import { normalizeEmoji } from 'soapbox/normalizers/emoji'; import { makeEmojiMap } from 'soapbox/utils/normalizers'; diff --git a/app/soapbox/normalizers/group.ts b/app/soapbox/normalizers/group.ts index 397ec5eee..e4bf1df6b 100644 --- a/app/soapbox/normalizers/group.ts +++ b/app/soapbox/normalizers/group.ts @@ -10,7 +10,7 @@ import { fromJS, } from 'immutable'; -import emojify from 'soapbox/features/emoji/emoji'; +import emojify from 'soapbox/features/emoji'; import { normalizeEmoji } from 'soapbox/normalizers/emoji'; import { unescapeHTML } from 'soapbox/utils/html'; import { makeEmojiMap } from 'soapbox/utils/normalizers'; @@ -29,6 +29,7 @@ export const GroupRecord = ImmutableRecord({ id: '', locked: false, membership_required: false, + members_count: undefined as number | undefined, note: '', statuses_visibility: 'public', uri: '', @@ -127,6 +128,11 @@ const normalizeFqn = (group: ImmutableMap) => { return group.set('fqn', fqn); }; +const normalizeLocked = (group: ImmutableMap) => { + const locked = group.get('locked') || group.get('group_visibility') === 'members_only'; + return group.set('locked', locked); +}; + /** Rewrite `

    ` to empty string. */ const fixNote = (group: ImmutableMap) => { @@ -144,6 +150,7 @@ export const normalizeGroup = (group: Record) => { normalizeAvatar(group); normalizeHeader(group); normalizeFqn(group); + normalizeLocked(group); fixDisplayName(group); fixNote(group); addInternalFields(group); diff --git a/app/soapbox/normalizers/poll.ts b/app/soapbox/normalizers/poll.ts index efae796c3..726278a57 100644 --- a/app/soapbox/normalizers/poll.ts +++ b/app/soapbox/normalizers/poll.ts @@ -11,7 +11,7 @@ import { fromJS, } from 'immutable'; -import emojify from 'soapbox/features/emoji/emoji'; +import emojify from 'soapbox/features/emoji'; import { normalizeEmoji } from 'soapbox/normalizers/emoji'; import { makeEmojiMap } from 'soapbox/utils/normalizers'; diff --git a/app/soapbox/normalizers/status-edit.ts b/app/soapbox/normalizers/status-edit.ts index 7bf38adc1..6f5d8d53a 100644 --- a/app/soapbox/normalizers/status-edit.ts +++ b/app/soapbox/normalizers/status-edit.ts @@ -9,7 +9,7 @@ import { fromJS, } from 'immutable'; -import emojify from 'soapbox/features/emoji/emoji'; +import emojify from 'soapbox/features/emoji'; import { normalizeAttachment } from 'soapbox/normalizers/attachment'; import { normalizeEmoji } from 'soapbox/normalizers/emoji'; import { normalizePoll } from 'soapbox/normalizers/poll'; diff --git a/app/soapbox/pages/group-page.tsx b/app/soapbox/pages/group-page.tsx index d48caafd0..92518a55d 100644 --- a/app/soapbox/pages/group-page.tsx +++ b/app/soapbox/pages/group-page.tsx @@ -1,10 +1,8 @@ -import React, { useCallback, useEffect } from 'react'; +import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { useRouteMatch } from 'react-router-dom'; -import { fetchGroup } from 'soapbox/actions/groups'; -import MissingIndicator from 'soapbox/components/missing-indicator'; -import { Column, Layout } from 'soapbox/components/ui'; +import { Column, Icon, Layout, Stack, Text } from 'soapbox/components/ui'; import GroupHeader from 'soapbox/features/group/components/group-header'; import LinkFooter from 'soapbox/features/ui/components/link-footer'; import BundleContainer from 'soapbox/features/ui/containers/bundle-container'; @@ -13,8 +11,8 @@ import { GroupMediaPanel, SignUpPanel, } from 'soapbox/features/ui/util/async-components'; -import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; -import { makeGetGroup } from 'soapbox/selectors'; +import { useOwnAccount } from 'soapbox/hooks'; +import { useGroup } from 'soapbox/queries/groups'; import { Tabs } from '../components/ui'; @@ -34,23 +32,20 @@ interface IGroupPage { const GroupPage: React.FC = ({ params, children }) => { const intl = useIntl(); const match = useRouteMatch(); - const dispatch = useAppDispatch(); + const me = useOwnAccount(); const id = params?.id || ''; - const getGroup = useCallback(makeGetGroup(), []); - const group = useAppSelector(state => getGroup(state, id)); - const me = useAppSelector(state => state.me); + const { group } = useGroup(id); - useEffect(() => { - dispatch(fetchGroup(id)); - }, [id]); + const isNonMember = !group?.relationship || !group.relationship.member; + const isPrivate = group?.locked; - if ((group as any) === false) { - return ( - - ); - } + // if ((group as any) === false) { + // return ( + // + // ); + // } const items = [ { @@ -76,7 +71,18 @@ const GroupPage: React.FC = ({ params, children }) => { activeItem={match.path} /> - {children} + {(isNonMember && isPrivate) ? ( + +
    + +
    + + + Content is only visible to group members + +
    + + ) : children} {!me && ( diff --git a/app/soapbox/queries/groups.ts b/app/soapbox/queries/groups.ts new file mode 100644 index 000000000..5fb8ed2dc --- /dev/null +++ b/app/soapbox/queries/groups.ts @@ -0,0 +1,132 @@ +import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; + +import { fetchGroupRelationships } from 'soapbox/actions/groups'; +import { importFetchedGroups } from 'soapbox/actions/importer'; +import { getNextLink } from 'soapbox/api'; +import { useApi, useAppDispatch, useFeatures, useOwnAccount } from 'soapbox/hooks'; +import { normalizeGroup } from 'soapbox/normalizers'; +import { Group } from 'soapbox/types/entities'; +import { flattenPages, PaginatedResult } from 'soapbox/utils/queries'; + +const GroupKeys = { + group: (id: string) => ['groups', 'group', id] as const, + myGroups: (userId: string) => ['groups', userId] as const, + popularGroups: ['groups', 'popular'] as const, + suggestedGroups: ['groups', 'suggested'] as const, +}; + +const useGroups = () => { + const api = useApi(); + const account = useOwnAccount(); + const dispatch = useAppDispatch(); + const features = useFeatures(); + + const getGroups = async (pageParam?: any): Promise> => { + const endpoint = '/api/v1/groups'; + const nextPageLink = pageParam?.link; + const uri = nextPageLink || endpoint; + const response = await api.get(uri); + const { data } = response; + + const link = getNextLink(response); + const hasMore = !!link; + const result = data.map(normalizeGroup); + + // Note: Temporary while part of Groups is using Redux + dispatch(importFetchedGroups(result)); + dispatch(fetchGroupRelationships(result.map((item) => item.id))); + + return { + result, + hasMore, + link, + }; + }; + + const queryInfo = useInfiniteQuery( + GroupKeys.myGroups(account?.id as string), + ({ pageParam }: any) => getGroups(pageParam), + { + enabled: !!account && features.groups, + keepPreviousData: true, + getNextPageParam: (config) => { + if (config?.hasMore) { + return { nextLink: config?.link }; + } + + return undefined; + }, + }); + + const data = flattenPages(queryInfo.data); + + return { + ...queryInfo, + groups: data || [], + }; +}; + +const usePopularGroups = () => { + const api = useApi(); + const features = useFeatures(); + + const getQuery = async () => { + const { data } = await api.get('/api/v1/groups/search?q=group'); // '/api/v1/truth/trends/groups' + const result = data.map(normalizeGroup); + + return result; + }; + + const queryInfo = useQuery(GroupKeys.popularGroups, getQuery, { + enabled: features.groupsDiscovery, + placeholderData: [], + }); + + return { + groups: queryInfo.data || [], + ...queryInfo, + }; +}; + +const useSuggestedGroups = () => { + const api = useApi(); + const features = useFeatures(); + + const getQuery = async () => { + const { data } = await api.get('/api/mock/groups'); // /api/v1/truth/suggestions/groups + const result = data.map(normalizeGroup); + + return result; + }; + + const queryInfo = useQuery(GroupKeys.suggestedGroups, getQuery, { + enabled: features.groupsDiscovery, + placeholderData: [], + }); + + return { + groups: queryInfo.data || [], + ...queryInfo, + }; +}; + +const useGroup = (id: string) => { + const api = useApi(); + const features = useFeatures(); + + const getGroup = async () => { + const { data } = await api.get(`/api/v1/groups/${id}`); + return normalizeGroup(data); + }; + + const queryInfo = useQuery(GroupKeys.group(id), getGroup, { + enabled: features.groups && !!id, + }); + + return { + ...queryInfo, + group: queryInfo.data, + }; +}; + +export { useGroups, useGroup, usePopularGroups, useSuggestedGroups }; diff --git a/app/soapbox/queries/groups/search.ts b/app/soapbox/queries/groups/search.ts new file mode 100644 index 000000000..3da166cc2 --- /dev/null +++ b/app/soapbox/queries/groups/search.ts @@ -0,0 +1,67 @@ +import { useInfiniteQuery } from '@tanstack/react-query'; + +import { getNextLink } from 'soapbox/api'; +import { useApi, useFeatures } from 'soapbox/hooks'; +import { normalizeGroup } from 'soapbox/normalizers'; +import { Group } from 'soapbox/types/entities'; +import { flattenPages, PaginatedResult } from 'soapbox/utils/queries'; + +const GroupSearchKeys = { + search: (query?: string) => query ? ['groups', 'search', query] : ['groups', 'search'] as const, +}; + +type PageParam = { + link: string +} + +const useGroupSearch = (search?: string) => { + const api = useApi(); + const features = useFeatures(); + + const getSearchResults = async (pageParam: PageParam): Promise> => { + const nextPageLink = pageParam?.link; + const uri = nextPageLink || '/api/v1/groups/search'; + const response = await api.get(uri, { + params: search ? { + q: search, + } : undefined, + }); + const { data } = response; + + const link = getNextLink(response); + const hasMore = !!link; + const result = data.map(normalizeGroup); + + return { + result, + hasMore, + link, + }; + }; + + const queryInfo = useInfiniteQuery( + GroupSearchKeys.search(search), + ({ pageParam }) => getSearchResults(pageParam), + { + keepPreviousData: true, + enabled: features.groups && !!search, + getNextPageParam: (config) => { + if (config.hasMore) { + return { link: config.link }; + } + + return undefined; + }, + }); + + const data = flattenPages(queryInfo.data); + + return { + ...queryInfo, + groups: data || [], + }; +}; + +export { + useGroupSearch, +}; diff --git a/app/soapbox/reducers/__tests__/statuses.test.ts b/app/soapbox/reducers/__tests__/statuses.test.ts index 1db00ec64..06d82c871 100644 --- a/app/soapbox/reducers/__tests__/statuses.test.ts +++ b/app/soapbox/reducers/__tests__/statuses.test.ts @@ -118,7 +118,7 @@ describe('statuses reducer', () => { const status = require('soapbox/__fixtures__/status-custom-emoji.json'); const action = { type: STATUS_IMPORT, status }; - const expected = 'Hello :ablobcathyper: :ageblobcat: 😂 world 😋 test :blobcatphoto:'; + const expected = 'Hello :ablobcathyper: :ageblobcat: 😂 world 😋 test :blobcatphoto:'; const result = reducer(undefined, action).getIn(['AGm7uC9DaAIGUa4KYK', 'contentHtml']); expect(result).toBe(expected); diff --git a/app/soapbox/reducers/compose.ts b/app/soapbox/reducers/compose.ts index 46969edc3..39ed40eeb 100644 --- a/app/soapbox/reducers/compose.ts +++ b/app/soapbox/reducers/compose.ts @@ -1,6 +1,7 @@ import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, Record as ImmutableRecord, fromJS } from 'immutable'; import { v4 as uuid } from 'uuid'; +import { isNativeEmoji } from 'soapbox/features/emoji'; import { tagHistory } from 'soapbox/settings'; import { PLEROMA } from 'soapbox/utils/features'; import { hasIntegerMediaIds } from 'soapbox/utils/status'; @@ -59,7 +60,7 @@ import { normalizeAttachment } from '../normalizers/attachment'; import { unescapeHTML } from '../utils/html'; import type { AnyAction } from 'redux'; -import type { Emoji } from 'soapbox/components/autosuggest-emoji'; +import type { Emoji } from 'soapbox/features/emoji'; import type { Account as AccountEntity, APIEntity, @@ -194,7 +195,8 @@ const updateSuggestionTags = (compose: Compose, token: string, currentTrends: Im const insertEmoji = (compose: Compose, position: number, emojiData: Emoji, needsSpace: boolean) => { const oldText = compose.text; - const emoji = needsSpace ? ' ' + emojiData.native : emojiData.native; + const emojiText = isNativeEmoji(emojiData) ? emojiData.native : emojiData.colons; + const emoji = needsSpace ? ' ' + emojiText : emojiText; return compose.merge({ text: `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`, diff --git a/app/soapbox/reducers/custom-emojis.ts b/app/soapbox/reducers/custom-emojis.ts index 06b6bf79d..e63348580 100644 --- a/app/soapbox/reducers/custom-emojis.ts +++ b/app/soapbox/reducers/custom-emojis.ts @@ -1,15 +1,15 @@ import { List as ImmutableList, Map as ImmutableMap, fromJS } from 'immutable'; -import { emojis as emojiData } from 'soapbox/features/emoji/emoji-mart-data-light'; -import { addCustomToPool } from 'soapbox/features/emoji/emoji-mart-search-light'; +import { buildCustomEmojis } from 'soapbox/features/emoji'; +import emojiData from 'soapbox/features/emoji/data'; +import { addCustomToPool } from 'soapbox/features/emoji/search'; import { CUSTOM_EMOJIS_FETCH_SUCCESS } from '../actions/custom-emojis'; -import { buildCustomEmojis } from '../features/emoji/emoji'; import type { AnyAction } from 'redux'; import type { APIEntity } from 'soapbox/types/entities'; -const initialState = ImmutableList(); +const initialState = ImmutableList>(); // Populate custom emojis for composer autosuggest const autosuggestPopulate = (emojis: ImmutableList>) => { @@ -22,7 +22,7 @@ const importEmojis = (customEmojis: APIEntity[]) => { // Otherwise it breaks EmojiMart. // https://gitlab.com/soapbox-pub/soapbox/-/issues/610 const shortcode = emoji.get('shortcode', '').toLowerCase(); - return !emojiData[shortcode]; + return !emojiData.emojis[shortcode]; }); autosuggestPopulate(emojis); diff --git a/app/soapbox/reducers/settings.ts b/app/soapbox/reducers/settings.ts index 70a28a478..f124b38a5 100644 --- a/app/soapbox/reducers/settings.ts +++ b/app/soapbox/reducers/settings.ts @@ -13,7 +13,7 @@ import { FE_NAME, } from '../actions/settings'; -import type { Emoji } from 'soapbox/components/autosuggest-emoji'; +import type { Emoji } from 'soapbox/features/emoji'; import type { APIEntity } from 'soapbox/types/entities'; type State = ImmutableMap; diff --git a/app/soapbox/reducers/statuses.ts b/app/soapbox/reducers/statuses.ts index ed7ee1c2b..58d2a8b57 100644 --- a/app/soapbox/reducers/statuses.ts +++ b/app/soapbox/reducers/statuses.ts @@ -1,7 +1,7 @@ import escapeTextContentForBrowser from 'escape-html'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; -import emojify from 'soapbox/features/emoji/emoji'; +import emojify from 'soapbox/features/emoji'; import { normalizeStatus } from 'soapbox/normalizers'; import { simulateEmojiReact, simulateUnEmojiReact } from 'soapbox/utils/emoji-reacts'; import { stripCompatibilityFeatures, unescapeHTML } from 'soapbox/utils/html'; diff --git a/app/soapbox/settings.ts b/app/soapbox/settings.ts index 70efc0e8e..e0ee05852 100644 --- a/app/soapbox/settings.ts +++ b/app/soapbox/settings.ts @@ -53,3 +53,6 @@ export const pushNotificationsSetting = new Settings('soapbox_push_notification_ /** Remember hashtag usage. */ export const tagHistory = new Settings('soapbox_tag_history'); + +/** Remember group usage. */ +export const groupSearchHistory = new Settings('soapbox_group_search_history'); diff --git a/app/soapbox/utils/features.ts b/app/soapbox/utils/features.ts index 3b1c14b77..f4c029c9a 100644 --- a/app/soapbox/utils/features.ts +++ b/app/soapbox/utils/features.ts @@ -284,7 +284,7 @@ const getInstanceFeatures = (instance: Instance) => { * Whether chat messages can accept a `media_id` attachment. * @see POST /api/v1/pleroma/chats/:id/messages */ - chatsMedia: v.software !== TRUTHSOCIAL, + chatsMedia: v.software !== TRUTHSOCIAL || v.build === UNRELEASED, /** * Whether chat messages have read receipts. @@ -497,7 +497,12 @@ const getInstanceFeatures = (instance: Instance) => { * @see POST /api/v1/admin/groups/:group_id/unsuspend * @see DELETE /api/v1/admin/groups/:group_id */ - groups: false, + groups: v.build === UNRELEASED, + + /** + * Can see trending/suggested Groups. + */ + groupsDiscovery: v.software === TRUTHSOCIAL, /** * Can hide follows/followers lists and counts. diff --git a/app/soapbox/utils/groups.ts b/app/soapbox/utils/groups.ts new file mode 100644 index 000000000..28264e090 --- /dev/null +++ b/app/soapbox/utils/groups.ts @@ -0,0 +1,35 @@ +import { groupSearchHistory } from 'soapbox/settings'; + +const RECENT_SEARCHES_KEY = 'soapbox:recent-group-searches'; + +const clearRecentGroupSearches = (currentUserId: string) => groupSearchHistory.remove(currentUserId); + +const saveGroupSearch = (currentUserId: string, search: string) => { + let currentSearches: string[] = []; + + if (groupSearchHistory.get(currentUserId)) { + currentSearches = groupSearchHistory.get(currentUserId); + } + + if (currentSearches.indexOf(search) === -1) { + currentSearches.unshift(search); + if (currentSearches.length > 10) { + currentSearches.pop(); + } + + groupSearchHistory.set(currentUserId, currentSearches); + + return currentSearches; + } else { + // The search term has already been searched. Move it to the beginning + // of the cached list. + const indexOfSearch = currentSearches.indexOf(search); + const nextCurrentSearches = [...currentSearches]; + nextCurrentSearches.splice(0, 0, ...nextCurrentSearches.splice(indexOfSearch, 1)); + localStorage.setItem(RECENT_SEARCHES_KEY, JSON.stringify(nextCurrentSearches)); + + return nextCurrentSearches; + } +}; + +export { clearRecentGroupSearches, saveGroupSearch }; diff --git a/app/styles/emoji-picker.scss b/app/styles/emoji-picker.scss index 5a47b12e9..2efb2e388 100644 --- a/app/styles/emoji-picker.scss +++ b/app/styles/emoji-picker.scss @@ -1,296 +1,10 @@ -.emoji-mart, -.emoji-mart * { - box-sizing: border-box; - line-height: 1.15; +em-emoji-picker { + --rgb-background: 255 255 255; + --rgb-accent: var(--color-primary-600); + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } -.emoji-mart { - @apply text-base inline-block text-gray-900 dark:text-gray-100 rounded bg-white dark:bg-primary-900 shadow-lg; -} - -.emoji-mart .emoji-mart-emoji { - @apply p-1.5 align-middle; -} - -.emoji-mart-bar { - @apply border-0 border-solid border-gray-200 dark:border-gray-800; -} - -.emoji-mart-bar:first-child { - border-bottom-width: 1px; - border-top-left-radius: 5px; - border-top-right-radius: 5px; -} - -.emoji-mart-bar:last-child { - border-top-width: 1px; - border-bottom-left-radius: 5px; - border-bottom-right-radius: 5px; -} - -.emoji-mart-anchors { - @apply flex flex-row justify-between px-1.5; -} - -.emoji-mart-anchor { - @apply relative block flex-auto text-gray-700 dark:text-gray-600 text-center overflow-hidden transition-colors py-3 px-1; -} - -.emoji-mart-anchor:focus { outline: 0; } - -.emoji-mart-anchor:hover, -.emoji-mart-anchor:focus, -.emoji-mart-anchor-selected { - @apply text-gray-600 dark:text-gray-300; -} - -.emoji-mart-anchor-selected .emoji-mart-anchor-bar { - @apply bottom-0; -} - -.emoji-mart-anchor-bar { - @apply absolute -bottom-0.5 left-0 w-11/12 h-0.5 bg-primary-600; -} - -.emoji-mart-anchors i { - @apply inline-block w-full; - max-width: 22px; -} - -.emoji-mart-anchors svg, -.emoji-mart-anchors img { - fill: currentcolor; - height: 18px; - width: 18px; -} - -.emoji-mart-scroll { - overflow-y: scroll; - overflow-x: hidden; - height: 270px; - padding: 0 6px 6px; - will-change: transform; /* avoids "repaints on scroll" in mobile Chrome */ -} - -.emoji-mart-search { - @apply relative mt-1.5 p-2.5 pr-12 bg-white dark:bg-primary-900; -} - -.emoji-mart-search input { - @apply text-sm pr-9 block w-full border-gray-300 dark:bg-transparent dark:border-gray-800 rounded-full focus:ring-primary-500 focus:border-primary-500; - - &::-moz-focus-inner { - border: 0; - } - - &::-webkit-search-cancel-button { - @apply hidden; - } - - &::-moz-focus-inner, - &:focus, - &:active { - outline: 0 !important; - } -} - -.emoji-mart-search input, -.emoji-mart-search input::-webkit-search-decoration, -.emoji-mart-search input::-webkit-search-cancel-button, -.emoji-mart-search input::-webkit-search-results-button, -.emoji-mart-search input::-webkit-search-results-decoration { - /* remove webkit/blink styles for - * via https://stackoverflow.com/a/9422689 */ - appearance: none; -} - -.emoji-mart-search-icon { - @apply absolute z-10 border-0; - top: 20px; - right: 56px; - padding: 2px 5px 1px; -} - -.emoji-mart-search-icon svg { - @apply fill-gray-700 dark:fill-gray-600; -} - -.emoji-mart-search-icon:hover svg { - @apply stroke-gray-800; -} - -.emoji-mart-category .emoji-mart-emoji span { - @apply relative text-center; - z-index: 1; -} - -.emoji-mart-category .emoji-mart-emoji:hover::before { - @apply bg-gray-50 dark:bg-primary-800; - z-index: 0; - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border-radius: 100%; -} - -.emoji-mart-category-label { - z-index: 2; - position: relative; - position: sticky; - top: 0; -} - -.emoji-mart-category-label span { - @apply bg-white dark:bg-primary-900; - display: block; - width: 100%; - font-weight: 500; - padding: 5px 6px; -} - -.emoji-mart-category-list { - margin: 0; - padding: 0; -} - -.emoji-mart-category-list li { - list-style: none; - margin: 0; - padding: 0; - display: inline-block; -} - -.emoji-mart-emoji { - position: relative; - display: inline-block; - font-size: 0; - margin: 0; - padding: 0; - border: none; - background: none; - box-shadow: none; -} - -.emoji-mart-emoji-native { - font-family: 'Segoe UI Emoji', 'Segoe UI Symbol', 'Segoe UI', 'Apple Color Emoji', 'Twemoji Mozilla', 'Noto Color Emoji', 'Android Emoji', sans-serif; -} - -.emoji-mart-no-results { - @apply text-sm text-center text-gray-600 dark:text-gray-300; - padding-top: 70px; -} - -.emoji-mart-no-results-img { - display: block; - margin-left: auto; - margin-right: auto; - width: 50%; -} - -.emoji-mart-no-results .emoji-mart-category-label { - display: none; -} - -.emoji-mart-no-results .emoji-mart-no-results-label { - margin-top: 0.2em; -} - -.emoji-mart-no-results .emoji-mart-emoji:hover::before { - content: none; -} - -.emoji-mart-preview { - @apply hidden; -} - -/* For screenreaders only, via https://stackoverflow.com/a/19758620 */ -.emoji-mart-sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - border: 0; -} - -.emoji-picker-dropdown__menu { - @apply rounded-lg absolute mt-1.5; - transform: translateX(calc(-1 * env(safe-area-inset-right))); /* iOS PWA */ - z-index: 20000; - - .emoji-mart-scroll { - transition: opacity 200ms ease; - } - - &.selecting .emoji-mart-scroll { - opacity: 0.5; - } -} - -.emoji-picker-dropdown__modifiers { - position: absolute; - top: 65px; - right: 14px; - cursor: pointer; -} - -.emoji-picker-dropdown__modifiers__menu { - @apply absolute bg-white dark:bg-primary-900 rounded-3xl shadow overflow-hidden; - z-index: 4; - top: -4px; - left: -8px; - - button { - @apply block cursor-pointer border-0 px-2 py-1 bg-transparent; - - &:hover, - &:focus, - &:active { - @apply bg-gray-300 dark:bg-primary-600; - } - } - - .emoji-mart-emoji { - height: 22px; - } -} - -.font-icon-picker { - .emoji-mart-search { - // Search doesn't work. Hide it for now. - display: none; - padding: 10px !important; - } - - .emoji-mart-category-label > span { - padding: 9px 6px 5px; - } - - .emoji-mart-scroll { - border-radius: 4px; - } - - .emoji-mart-search-icon { - right: 18px; - } - - .emoji-mart-bar { - display: none; - } - - .fa { - font-size: 18px; - width: 22px; - height: 22px; - text-align: center; - } - - .fa-hack { - margin: 0 auto; - } +.dark em-emoji-picker { + --rgb-background: var(--color-primary-900); } diff --git a/app/styles/navigation.scss b/app/styles/navigation.scss index 67973e575..7860133fc 100644 --- a/app/styles/navigation.scss +++ b/app/styles/navigation.scss @@ -11,6 +11,7 @@ &__link { @apply px-2 py-2.5 space-y-1 flex flex-col flex-1 items-center text-gray-600 text-lg; + // padding: 8px 10px; // display: flex; // flex-direction: column; diff --git a/jest.config.cjs b/jest.config.cjs index 62139dedb..358bf9ecf 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -22,7 +22,6 @@ module.exports = { 'app/soapbox/**/*.mjs', 'app/soapbox/**/*.ts', 'app/soapbox/**/*.tsx', - '!app/soapbox/features/emoji/emoji-compressed.js', '!app/soapbox/service-worker/entry.ts', '!app/soapbox/jest/test-setup.ts', '!app/soapbox/jest/test-helpers.ts', diff --git a/package.json b/package.json index bd688b4fb..68afa1128 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,8 @@ "@babel/preset-react": "^7.18.6", "@babel/preset-typescript": "^7.18.6", "@babel/runtime": "^7.20.13", - "@floating-ui/react": "^0.19.1", + "@emoji-mart/data": "^1.1.2", + "@floating-ui/react": "^0.20.0", "@fontsource/inter": "^4.5.1", "@fontsource/roboto-mono": "^4.5.8", "@gamestdio/websocket": "^0.3.2", @@ -79,6 +80,7 @@ "@tanstack/react-query": "^4.0.10", "@testing-library/react": "^13.0.0", "@types/escape-html": "^1.0.1", + "@types/flexsearch": "^0.7.3", "@types/http-link-header": "^1.0.3", "@types/jest": "^29.0.0", "@types/leaflet": "^1.8.0", @@ -111,7 +113,6 @@ "bootstrap-icons": "^1.5.0", "bowser": "^2.11.0", "browserslist": "^4.16.6", - "cheerio": "^1.0.0-rc.10", "clsx": "^1.2.1", "copy-webpack-plugin": "^11.0.0", "core-js": "^3.27.2", @@ -120,11 +121,13 @@ "cssnano": "^5.1.10", "detect-passive-events": "^2.0.0", "dotenv": "^16.0.0", - "emoji-datasource": "5.0.1", - "emoji-mart": "npm:emoji-mart-lazyload", + "emoji-datasource": "14.0.0", + "emoji-mart": "^5.5.2", "escape-html": "^1.0.3", "exif-js": "^2.3.0", + "flexsearch": "^0.7.31", "fork-ts-checker-webpack-plugin": "^7.2.11", + "graphemesplit": "^2.4.4", "html-webpack-harddisk-plugin": "^2.0.0", "html-webpack-plugin": "^5.5.0", "http-link-header": "^1.0.2", @@ -214,6 +217,7 @@ "@storybook/manager-webpack5": "^6.5.16", "@storybook/react": "^6.5.16", "@storybook/testing-library": "^0.0.13", + "@tailwindcss/aspect-ratio": "^0.4.2", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^14.0.3", diff --git a/tailwind.config.cjs b/tailwind.config.cjs index d5c9d5f06..10270a77f 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -100,5 +100,6 @@ module.exports = { require('@tailwindcss/forms'), require('@tailwindcss/line-clamp'), require('@tailwindcss/typography'), + require('@tailwindcss/aspect-ratio'), ], }; diff --git a/yarn.lock b/yarn.lock index 3227303dc..ee3c2a45a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1698,6 +1698,11 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== +"@emoji-mart/data@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@emoji-mart/data/-/data-1.1.2.tgz#777c976f8f143df47cbb23a7077c9ca9fe5fc513" + integrity sha512-1HP8BxD2azjqWJvxIaWAMyTySeZY0Osr83ukYjltPVkNXeJvTz7yDrPLBtnrD5uqJ3tg4CcLuuBW09wahqL/fg== + "@eslint/eslintrc@^1.4.1": version "1.4.1" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz#af58772019a2d271b7e2d4c23ff4ddcba3ccfb3e" @@ -1713,31 +1718,31 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@floating-ui/core@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.2.0.tgz#ae7ae7923d41f3d84cb2fd88740a89436610bbec" - integrity sha512-GHUXPEhMEmTpnpIfesFA2KAoMJPb1SPQw964tToQwt+BbGXdhqTCWT1rOb0VURGylsxsYxiGMnseJ3IlclVpVA== - -"@floating-ui/dom@^1.1.1": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.2.0.tgz#a60212069cc58961c478037c30eba4b191c75316" - integrity sha512-QXzg57o1cjLz3cGETzKXjI3kx1xyS49DW9l7kV2jw2c8Yftd434t2hllX0sVGn2Q8MtcW/4pNm8bfE1/4n6mng== - dependencies: - "@floating-ui/core" "^1.2.0" - -"@floating-ui/react-dom@^1.2.2": +"@floating-ui/core@^1.2.2": version "1.2.2" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-1.2.2.tgz#ed256992fd44fcfcddc96da68b4b92f123d61871" - integrity sha512-DbmFBLwFrZhtXgCI2ra7wXYT8L2BN4/4AMQKyu05qzsVji51tXOfF36VE2gpMB6nhJGHa85PdEg75FB4+vnLFQ== - dependencies: - "@floating-ui/dom" "^1.1.1" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.2.2.tgz#66f62cf1b7de2ed23a09c101808536e68caffaec" + integrity sha512-FaO9KVLFnxknZaGWGmNtjD2CVFuc0u4yeGEofoyXO2wgRA7fLtkngT6UB0vtWQWuhH3iMTZZ/Y89CMeyGfn8pA== -"@floating-ui/react@^0.19.1": - version "0.19.1" - resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.19.1.tgz#bcaeaf3856dfeea388816f7e66750cab26208376" - integrity sha512-h7hr53rLp+VVvWvbu0dOBvGsLeeZwn1DTLIllIaLYjGWw20YhAgEqegHU+nc7BJ30ttxq4Sq6hqARm0ne6chXQ== +"@floating-ui/dom@^1.2.1": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.2.3.tgz#8dc6fbf799fbb5c29f705b54bdd51f3ab0ee03a2" + integrity sha512-lK9cZUrHSJLMVAdCvDqs6Ug8gr0wmqksYiaoj/bxj2gweRQkSuhg2/V6Jswz2KiQ0RAULbqw1oQDJIMpQ5GfGA== dependencies: - "@floating-ui/react-dom" "^1.2.2" + "@floating-ui/core" "^1.2.2" + +"@floating-ui/react-dom@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-1.3.0.tgz#4d35d416eb19811c2b0e9271100a6aa18c1579b3" + integrity sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g== + dependencies: + "@floating-ui/dom" "^1.2.1" + +"@floating-ui/react@^0.20.0": + version "0.20.1" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.20.1.tgz#20f23cb5615f5beefa6e2ac4d7630a56ccdf7131" + integrity sha512-JHTHJ+/YsIxNFH8uJDFa5OyI6dSUZcle6wAFe0zRTjgWD+rkACfBBoJtx2itTtn7C4a7xAz4jgxdEQcMel194g== + dependencies: + "@floating-ui/react-dom" "^1.3.0" aria-hidden "^1.1.3" tabbable "^6.0.1" @@ -3976,6 +3981,11 @@ resolved "https://registry.yarnpkg.com/@tabler/icons/-/icons-2.4.0.tgz#34b1b0d818dc00926b956c3424bff48b89a5b439" integrity sha512-JZY9Kk3UsQoqp7Rw/BuWw1PrkRwv5h0psjJBbj+Cn9UVyhdzr5vztg2mywXBAJ+jFBUL/pjnVcIvOzKFw4CXng== +"@tailwindcss/aspect-ratio@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@tailwindcss/aspect-ratio/-/aspect-ratio-0.4.2.tgz#9ffd52fee8e3c8b20623ff0dcb29e5c21fb0a9ba" + integrity sha512-8QPrypskfBa7QIMuKHg2TA7BqES6vhBrDLOv8Unb6FcFyd3TjKbc6lcmb9UPQHxfl24sXoJ41ux/H7qQQvfaSQ== + "@tailwindcss/forms@^0.5.3": version "0.5.3" resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.3.tgz#e4d7989686cbcaf416c53f1523df5225332a86e7" @@ -4273,6 +4283,11 @@ resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.29.tgz#a48795ecadf957f6c0d10e0c34af86c098fa5bee" integrity sha512-BsPXH/irW0ht0Ji6iw/jJaK8Lj3FJemon2gvEqHKpCdDCeemHa+rI3WBGq5z7cDMZgoLjY40oninGxqk+8NzNQ== +"@types/flexsearch@^0.7.3": + version "0.7.3" + resolved "https://registry.yarnpkg.com/@types/flexsearch/-/flexsearch-0.7.3.tgz#ee79b1618035c82284278e05652e91116765b634" + integrity sha512-HXwADeHEP4exXkCIwy2n1+i0f1ilP1ETQOH5KDOugjkTFZPntWo0Gr8stZOaebkxsdx+k0X/K6obU/+it07ocg== + "@types/fs-extra@^9.0.1": version "9.0.13" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45" @@ -6722,30 +6737,6 @@ character-reference-invalid@^1.0.0: resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== -cheerio-select@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-1.5.0.tgz#faf3daeb31b17c5e1a9dabcee288aaf8aafa5823" - integrity sha512-qocaHPv5ypefh6YNxvnbABM07KMxExbtbfuJoIie3iZXX1ERwYmJcIiRrr9H05ucQP1k28dav8rpdDgjQd8drg== - dependencies: - css-select "^4.1.3" - css-what "^5.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" - domutils "^2.7.0" - -cheerio@^1.0.0-rc.10: - version "1.0.0-rc.10" - resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.10.tgz#2ba3dcdfcc26e7956fc1f440e61d51c643379f3e" - integrity sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw== - dependencies: - cheerio-select "^1.5.0" - dom-serializer "^1.3.2" - domhandler "^4.2.0" - htmlparser2 "^6.1.0" - parse5 "^6.0.1" - parse5-htmlparser2-tree-adapter "^6.0.1" - tslib "^2.2.0" - "chokidar@>=3.0.0 <4.0.0": version "3.5.2" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" @@ -7455,7 +7446,7 @@ css-tree@^1.1.2, css-tree@^1.1.3: mdn-data "2.0.14" source-map "^0.6.1" -css-what@^5.0.0, css-what@^5.0.1: +css-what@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.0.1.tgz#3efa820131f4669a8ac2408f9c32e7c7de9f4cad" integrity sha512-FYDTSHb/7KXsWICVsxdmiExPjCfRC4qRFBdVwv7Ax9hMnvMmEjP9RfxTEZ3qPZGmADDn2vAKSo9UcN1jKVYscg== @@ -7978,7 +7969,7 @@ dom-helpers@^3.2.1, dom-helpers@^3.4.0: dependencies: "@babel/runtime" "^7.1.2" -dom-serializer@^1.0.1, dom-serializer@^1.3.2: +dom-serializer@^1.0.1: version "1.3.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== @@ -8023,7 +8014,7 @@ domhandler@^4.0.0, domhandler@^4.2.0: dependencies: domelementtype "^2.2.0" -domutils@^2.5.2, domutils@^2.6.0, domutils@^2.7.0: +domutils@^2.5.2, domutils@^2.6.0: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== @@ -8135,19 +8126,15 @@ emittery@^0.13.1: resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== -emoji-datasource@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/emoji-datasource/-/emoji-datasource-5.0.1.tgz#31eaaff7caa6640929327b4f4ff66f2bf313df0a" - integrity sha512-RXokuCv4o8RFLiigN1skAdZwJuJWqtBvcK3GVKpvAL/7BeH95enmKsli7cG8YZ85RTjyEe3+GAdpJJOV43KLKQ== +emoji-datasource@14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/emoji-datasource/-/emoji-datasource-14.0.0.tgz#99529a62f3a86546fc670c09b672ddc9f24f3d44" + integrity sha512-SoOv0lSa+9/2X9ulSRDhu2u1zAOaOv5vtMY3OYUDcQCoReEh0/3eQAMuBM9LyD7Hy3G4K7mDPDqVeHUWvy7cow== -"emoji-mart@npm:emoji-mart-lazyload": - version "3.0.1-j" - resolved "https://registry.yarnpkg.com/emoji-mart-lazyload/-/emoji-mart-lazyload-3.0.1-j.tgz#87a90d30b79d9145ece078d53e3e683c1a10ce9c" - integrity sha512-0wKF7MR0/iAeCIoiBLY+JjXCugycTgYRC2SL0y9/bjNSQlbeMdzILmPQJAufU/mgLFDUitOvjxLDhOZ9yxZ48g== - dependencies: - "@babel/runtime" "^7.0.0" - intersection-observer "^0.12.0" - prop-types "^15.6.0" +emoji-mart@^5.5.2: + version "5.5.2" + resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-5.5.2.tgz#3ddbaf053139cf4aa217650078bc1c50ca8381af" + integrity sha512-Sqc/nso4cjxhOwWJsp9xkVm8OF5c+mJLZJFoFfzRuKO+yWiN7K8c96xmtughYb0d/fZ8UC6cLIQ/p4BR6Pv3/A== emoji-regex@^8.0.0: version "8.0.0" @@ -9189,6 +9176,11 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.2.tgz#64bfed5cb68fe3ca78b3eb214ad97b63bedce561" integrity sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA== +flexsearch@^0.7.31: + version "0.7.31" + resolved "https://registry.yarnpkg.com/flexsearch/-/flexsearch-0.7.31.tgz#065d4110b95083110b9b6c762a71a77cc52e4702" + integrity sha512-XGozTsMPYkm+6b5QL3Z9wQcJjNYxp0CYn3U1gO7dwD6PAqU1SVWZxI9CCg3z+ml3YfqdPnrBehaBrnH2AGKbNA== + flush-write-stream@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" @@ -9754,6 +9746,14 @@ grapheme-splitter@^1.0.4: resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== +graphemesplit@^2.4.4: + version "2.4.4" + resolved "https://registry.yarnpkg.com/graphemesplit/-/graphemesplit-2.4.4.tgz#6d325c61e928efdaec2189f54a9b87babf89b75a" + integrity sha512-lKrpp1mk1NH26USxC/Asw4OHbhSQf5XfrWZ+CDv/dFVvd1j17kFgMotdJvOesmHkbFX9P9sBfpH8VogxOWLg8w== + dependencies: + js-base64 "^3.6.0" + unicode-trie "^2.0.0" + gzip-size@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" @@ -10455,11 +10455,6 @@ interpret@^3.1.1: resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== -intersection-observer@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.12.0.tgz#6c84628f67ce8698e5f9ccf857d97718745837aa" - integrity sha512-2Vkz8z46Dv401zTWudDGwO7KiGHNDkMv417T5ItcNYfmvHR/1qCTVBO9vwH8zZmQ0WkA/1ARwpysR9bsnop4NQ== - intersection-observer@^0.12.2: version "0.12.2" resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.12.2.tgz#4a45349cc0cd91916682b1f44c28d7ec737dc375" @@ -11655,6 +11650,11 @@ jest@^29.0.0: import-local "^3.0.2" jest-cli "^29.3.1" +js-base64@^3.6.0: + version "3.7.5" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.5.tgz#21e24cf6b886f76d6f5f165bfcd69cc55b9e3fca" + integrity sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA== + js-sdsl@^4.1.4: version "4.2.0" resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.2.0.tgz#278e98b7bea589b8baaf048c20aeb19eb7ad09d0" @@ -13471,6 +13471,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +pako@^0.2.5: + version "0.2.9" + resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" + integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA== + pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" @@ -13571,14 +13576,7 @@ parse-passwd@^1.0.0: resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" integrity sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q== -parse5-htmlparser2-tree-adapter@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" - integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== - dependencies: - parse5 "^6.0.1" - -parse5@^6.0.0, parse5@^6.0.1: +parse5@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== @@ -16886,6 +16884,11 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" +tiny-inflate@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" + integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== + tiny-invariant@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" @@ -17097,7 +17100,7 @@ tslib@^2.0.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== -tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.1: +tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== @@ -17291,6 +17294,14 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8" integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== +unicode-trie@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-2.0.0.tgz#8fd8845696e2e14a8b67d78fa9e0dd2cad62fec8" + integrity sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ== + dependencies: + pako "^0.2.5" + tiny-inflate "^1.0.0" + unified@9.2.0: version "9.2.0" resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.0.tgz#67a62c627c40589edebbf60f53edfd4d822027f8"