diff --git a/src/components/ChannelPreview/utils.tsx b/src/components/ChannelPreview/utils.tsx index 0d318b50bd..0c12182202 100644 --- a/src/components/ChannelPreview/utils.tsx +++ b/src/components/ChannelPreview/utils.tsx @@ -1,10 +1,11 @@ +import type { ReactNode } from 'react'; import React from 'react'; import ReactMarkdown from 'react-markdown'; -import type { ReactNode } from 'react'; -import type { Channel, PollVote, TranslationLanguages, UserResponse } from 'stream-chat'; +import type { Channel, PollVote, UserResponse } from 'stream-chat'; -import type { TranslationContextValue } from '../../context/TranslationContext'; import type { ChatContextValue } from '../../context'; +import { getTranslatedMessageText } from '../../context/MessageTranslationViewContext'; +import type { TranslationContextValue } from '../../context/TranslationContext'; import type { PluggableList } from 'unified'; import { htmlToTextPlugin, imageToLink, plusPlusToEmphasis } from '../Message'; import remarkGfm from 'remark-gfm'; @@ -45,7 +46,7 @@ export const getLatestMessagePreview = ( channel.state.latestMessages[channel.state.latestMessages.length - 1]; const previewTextToRender = - latestMessage?.i18n?.[`${userLanguage}_text` as `${TranslationLanguages}_text`] || + getTranslatedMessageText({ language: userLanguage, message: latestMessage }) || latestMessage?.text; const poll = latestMessage?.poll; diff --git a/src/components/Icons/icons.tsx b/src/components/Icons/icons.tsx index 754976cdb4..ae6902f971 100644 --- a/src/components/Icons/icons.tsx +++ b/src/components/Icons/icons.tsx @@ -907,6 +907,17 @@ export const IconThunder = createIcon( , ); +export const IconTranslate = createIcon( + 'IconTranslate', + , +); + export const IconTrashBin = createIcon( 'IconTrashBin', , diff --git a/src/components/Message/FixedHeightMessage.tsx b/src/components/Message/FixedHeightMessage.tsx index 181b196e43..e12ea9e68c 100644 --- a/src/components/Message/FixedHeightMessage.tsx +++ b/src/components/Message/FixedHeightMessage.tsx @@ -14,8 +14,9 @@ import { useMessageContext } from '../../context/MessageContext'; import { useTranslationContext } from '../../context/TranslationContext'; import { renderText } from './renderText'; -import type { LocalMessage, TranslationLanguages } from 'stream-chat'; +import type { LocalMessage } from 'stream-chat'; import { ModalGallery } from '../Attachment'; +import { getTranslatedMessageText } from '../../context/MessageTranslationViewContext'; const selectColor = (number: number, dark: boolean) => { const hue = number * 137.508; // use golden angle approximation @@ -58,8 +59,7 @@ const UnMemoizedFixedHeightMessage = (props: FixedHeightMessageProps) => { const role = useUserRole(message); const messageTextToRender = - message?.i18n?.[`${userLanguage}_text` as `${TranslationLanguages}_text`] || - message?.text; + getTranslatedMessageText({ language: userLanguage, message }) || message?.text; const renderedText = useMemo( () => renderText(messageTextToRender, message.mentioned_users), diff --git a/src/components/Message/Message.tsx b/src/components/Message/Message.tsx index 826f4d0265..515d1c0309 100644 --- a/src/components/Message/Message.tsx +++ b/src/components/Message/Message.tsx @@ -24,6 +24,7 @@ import { useChannelStateContext, useChatContext, useComponentContext, + useMessageTranslationViewContext, } from '../../context'; import { MessageSimple as DefaultMessage } from './MessageSimple'; @@ -74,6 +75,14 @@ const MessageWithContext = (props: MessageWithContextProps) => { const { client, isMessageAIGenerated } = useChatContext('Message'); const { channelConfig, read } = useChannelStateContext('Message'); const { Message: contextMessage } = useComponentContext('Message'); + const { getTranslationView, setTranslationView: setTranslationViewInContext } = + useMessageTranslationViewContext(); + + const translationView = getTranslationView(message.id, !!message.i18n); + const setTranslationView = useCallback( + (view: 'original' | 'translated') => setTranslationViewInContext(message.id, view), + [message.id, setTranslationViewInContext], + ); const actionsEnabled = message.type === 'regular' && message.status === 'received'; const MessageUIComponent = propMessage ?? contextMessage ?? DefaultMessage; @@ -161,6 +170,8 @@ const MessageWithContext = (props: MessageWithContextProps) => { messageIsUnread, onUserClick, onUserHover, + setTranslationView, + translationView, }; return ( diff --git a/src/components/Message/MessageSimple.tsx b/src/components/Message/MessageSimple.tsx index ee8dc063ac..39bbbc4d0c 100644 --- a/src/components/Message/MessageSimple.tsx +++ b/src/components/Message/MessageSimple.tsx @@ -15,6 +15,7 @@ import { isDateSeparatorMessage } from '../MessageList'; import { MessageAlsoSentInChannelIndicator as DefaultMessageAlsoSentInChannelIndicator } from './MessageAlsoSentInChannelIndicator'; import { MessageIsThreadReplyInChannelButtonIndicator as DefaultMessageIsThreadReplyInChannelButtonIndicator } from './MessageIsThreadReplyInChannelButtonIndicator'; import { ReminderNotification as DefaultReminderNotification } from './ReminderNotification'; +import { MessageTranslationIndicator as DefaultMessageTranslationIndicator } from './MessageTranslationIndicator'; import { useMessageReminder } from './hooks'; import { areMessageUIPropsEqual, @@ -89,6 +90,7 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => { MessageRepliesCountButton = DefaultMessageRepliesCountButton, MessageStatus = DefaultMessageStatus, MessageTimestamp = DefaultMessageTimestamp, + MessageTranslationIndicator = DefaultMessageTranslationIndicator, PinIndicator = DefaultPinIndicator, QuotedMessage = DefaultQuotedMessage, ReactionsList = DefaultReactionList, @@ -193,6 +195,7 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => { {message.pinned && } {threadList && message.show_in_channel && } {!!reminder && } + {message.user && ( { onMentionsClickMessage, onMentionsHoverMessage, renderText: contextRenderText, + translationView = 'translated', unsafeHTML, } = useMessageContext('MessageText'); @@ -41,8 +43,9 @@ const UnMemoizedMessageTextComponent = (props: MessageTextProps) => { const hasAttachment = messageHasAttachments(message); const messageTextToRender = - message.i18n?.[`${userLanguage}_text` as `${TranslationLanguages}_text`] || - message.text; + translationView === 'original' + ? message.text + : getTranslatedMessageText({ language: userLanguage, message }) || message.text; const messageText = useMemo( () => renderText(messageTextToRender, message.mentioned_users), diff --git a/src/components/Message/MessageTranslationIndicator.tsx b/src/components/Message/MessageTranslationIndicator.tsx new file mode 100644 index 0000000000..1a32cf8a2e --- /dev/null +++ b/src/components/Message/MessageTranslationIndicator.tsx @@ -0,0 +1,83 @@ +import type { LocalMessage } from 'stream-chat'; +import React, { useCallback, useMemo } from 'react'; +import { IconTranslate } from '../Icons'; +import { + getTranslatedMessageText, + useMessageContext, + useTranslationContext, +} from '../../context'; +import { Button } from '../Button'; + +export type TranslationIndicatorProps = { + message?: LocalMessage; +}; + +export const MessageTranslationIndicator = ({ + message: propMessage, +}: TranslationIndicatorProps) => { + const { t, userLanguage } = useTranslationContext(); + const { + message: contextMessage, + setTranslationView, + translationView, + } = useMessageContext('MessageTranslationIndicator'); + const message = propMessage ?? contextMessage; + + const translatedTextForUser = useMemo( + () => getTranslatedMessageText({ language: userLanguage, message }), + [userLanguage, message], + ); + + const hasTranslationForUserLanguage = useMemo( + () => + translatedTextForUser != null && + message?.text !== undefined && + translatedTextForUser !== message.text, + [translatedTextForUser, message?.text], + ); + + const viewingOriginal = useMemo( + () => + translationView === 'original' || + (translationView === undefined && !hasTranslationForUserLanguage), + [translationView, hasTranslationForUserLanguage], + ); + + const handleToggle = useCallback(() => { + setTranslationView?.(viewingOriginal ? 'translated' : 'original'); + }, [setTranslationView, viewingOriginal]); + + const sourceLanguageName = useMemo(() => { + const sourceLanguageCode = message?.i18n?.language; + if (!sourceLanguageCode) return ''; + const languageKey = 'language/' + sourceLanguageCode; + const translatedName = t(languageKey); + return translatedName && translatedName !== languageKey + ? translatedName + : sourceLanguageCode; + }, [message?.i18n?.language, t]); + + if (!message?.i18n || !setTranslationView) return null; + if (!hasTranslationForUserLanguage) return null; + + return ( +
+ + + {viewingOriginal + ? t('Original') + : sourceLanguageName + ? t('Translated from {{ language }}', { language: sourceLanguageName }) + : t('Translated')} + + · + +
+ ); +}; diff --git a/src/components/Message/index.ts b/src/components/Message/index.ts index 17f5ac4f76..4a958eb6f1 100644 --- a/src/components/Message/index.ts +++ b/src/components/Message/index.ts @@ -13,6 +13,7 @@ export * from './MessageSimple'; export * from './MessageStatus'; export * from './MessageText'; export * from './MessageTimestamp'; +export * from './MessageTranslationIndicator'; export * from './QuotedMessage'; export * from './ReminderNotification'; export * from './renderText'; diff --git a/src/components/Message/styling/Message.scss b/src/components/Message/styling/Message.scss index eefadf492b..9998481fcc 100644 --- a/src/components/Message/styling/Message.scss +++ b/src/components/Message/styling/Message.scss @@ -230,6 +230,7 @@ 'pin-indicator' 'also-sent-in-channel' 'message-reminder' + 'translation-indicator' 'message' 'translation-notice' 'custom-metadata' @@ -243,6 +244,7 @@ '. pin-indicator' '. also-sent-in-channel' '. message-reminder' + '. translation-indicator' 'avatar message' 'avatar translation-notice' 'avatar custom-metadata' @@ -256,6 +258,7 @@ 'pin-indicator .' 'also-sent-in-channel .' 'message-reminder .' + 'translation-indicator .' 'message avatar' 'translation-notice avatar' 'custom-metadata avatar' diff --git a/src/components/Message/styling/MessageTranslationIndicator.scss b/src/components/Message/styling/MessageTranslationIndicator.scss new file mode 100644 index 0000000000..41d271b485 --- /dev/null +++ b/src/components/Message/styling/MessageTranslationIndicator.scss @@ -0,0 +1,33 @@ +@use '../../../styling/utils'; + +.str-chat { + --str-chat__message-translation-indicator-color: var( + --text-primary-text, + var(--text-primary) + ); + --str-chat__message-translation-indicator-background-color: transparent; +} + +/* Translation indicator: below reminder in message grid. */ +.str-chat__message-translation-indicator { + display: flex; + align-items: center; + grid-area: translation-indicator; + padding-block: var(--spacing-xxs); + gap: var(--spacing-xxs); + margin: 0; + color: var(--str-chat__message-translation-indicator-color); + @include utils.component-layer-overrides('message-translation-indicator'); + + &, .str-chat__message-translation-indicator__translation-toggle { + font: var(--str-chat__metadata-default-text); + } + + .str-chat__message-translation-indicator__sign { + font: var(--str-chat__metadata-emphasis-text); + } + + svg path { + stroke-width: 1.5px; + } +} diff --git a/src/components/Message/styling/index.scss b/src/components/Message/styling/index.scss index 9fb0d49393..dcf4ae3d8f 100644 --- a/src/components/Message/styling/index.scss +++ b/src/components/Message/styling/index.scss @@ -4,6 +4,7 @@ @use 'MessageIsThreadReplyInChannelButtonIndicator'; @use 'MessageStatus'; @use 'MessageSystem'; +@use 'MessageTranslationIndicator'; @use 'QuotedMessage'; @use 'ReminderNotification'; @use 'UnreadMessageNotification'; diff --git a/src/components/MessageList/MessageList.tsx b/src/components/MessageList/MessageList.tsx index 121d07391c..750eb0b1d3 100644 --- a/src/components/MessageList/MessageList.tsx +++ b/src/components/MessageList/MessageList.tsx @@ -20,6 +20,7 @@ import { DialogManagerProvider } from '../../context'; import { useChatContext } from '../../context/ChatContext'; import { useComponentContext } from '../../context/ComponentContext'; import { MessageListContextProvider } from '../../context/MessageListContext'; +import { MessageTranslationViewProvider } from '../../context/MessageTranslationViewContext'; import { EmptyStateIndicator as DefaultEmptyStateIndicator } from '../EmptyStateIndicator'; import type { InfiniteScrollProps } from '../InfiniteScrollPaginator/InfiniteScroll'; import { InfiniteScroll } from '../InfiniteScrollPaginator/InfiniteScroll'; @@ -237,60 +238,62 @@ const MessageListWithContext = (props: MessageListWithContextProps) => { scrollToBottom, }} > - - - {!threadList && showUnreadMessagesNotification && ( - - )} -
- {showEmptyStateIndicator ? ( - - ) : ( - - {props.loadingMore && } -
- } - loadNextPage={loadMoreNewer} - loadPreviousPage={loadMore} - threshold={loadMoreScrollThreshold} - {...restInternalInfiniteScrollProps} - > - - {elements} - - - -
- + + + + {!threadList && showUnreadMessagesNotification && ( + )} -
-
-
- +
+ {showEmptyStateIndicator ? ( + + ) : ( + + {props.loadingMore && } +
+ } + loadNextPage={loadMoreNewer} + loadPreviousPage={loadMore} + threshold={loadMoreScrollThreshold} + {...restInternalInfiniteScrollProps} + > + + {elements} + + + +
+ + )} +
+ + + + ); }; diff --git a/src/components/MessageList/VirtualizedMessageList.tsx b/src/components/MessageList/VirtualizedMessageList.tsx index e85041363d..8c63b16adc 100644 --- a/src/components/MessageList/VirtualizedMessageList.tsx +++ b/src/components/MessageList/VirtualizedMessageList.tsx @@ -56,6 +56,7 @@ import type { ChatContextValue } from '../../context/ChatContext'; import { useChatContext } from '../../context/ChatContext'; import type { ComponentContextValue } from '../../context/ComponentContext'; import { useComponentContext } from '../../context/ComponentContext'; +import { MessageTranslationViewProvider } from '../../context/MessageTranslationViewContext'; import { VirtualizedMessageListContextProvider } from '../../context/VirtualizedMessageListContext'; import type { @@ -465,98 +466,102 @@ const VirtualizedMessageListWithContext = ( return ( - - - {!threadList && showUnreadMessagesNotification && ( - - )} -
- - atBottomStateChange={atBottomStateChange} - atBottomThreshold={100} - atTopStateChange={atTopStateChange} - atTopThreshold={100} - className='str-chat__message-list-scroll' - components={{ - EmptyPlaceholder, - Header, - Item, - ...virtuosoComponentsFromProps, - }} - computeItemKey={computeItemKey} - context={{ - additionalMessageInputProps, - closeReactionSelectorOnClick, - customClasses, - customMessageRenderer, - DateSeparator, - firstUnreadMessageId: channelUnreadUiState?.first_unread_message_id, - formatDate, - head, - lastOwnMessage, - lastReadDate: channelUnreadUiState?.last_read, - lastReadMessageId: channelUnreadUiState?.last_read_message_id, - lastReceivedMessageId, - loadingMore, - Message: MessageUIComponent, - messageActions, - messageGroupStyles, - MessageSystem, - numItemsPrepended, - openThread, - ownMessagesDeliveredToOthers, - ownMessagesReadByOthers, - processedMessages, - reactionDetailsSort, - renderText, - returnAllReadData, - shouldGroupByUser, - showAvatar, - sortReactionDetails, - sortReactions, - threadList, - unreadMessageCount: channelUnreadUiState?.unread_messages, - UnreadMessagesSeparator, - virtuosoRef: virtuoso, - }} - firstItemIndex={calculateFirstItemIndex(numItemsPrepended)} - followOutput={followOutput} - increaseViewportBy={{ bottom: 200, top: 0 }} - initialTopMostItemIndex={calculateInitialTopMostItemIndex( - processedMessages, - highlightedMessageId, - )} - itemContent={messageRenderer} - itemSize={fractionalItemSize} - itemsRendered={handleItemsRendered} - key={messageSetKey} - overscan={overscan} - ref={virtuoso} - style={{ overflowX: 'hidden' }} - totalCount={processedMessages.length} - {...overridingVirtuosoProps} - {...(scrollSeekPlaceHolder ? { scrollSeek: scrollSeekPlaceHolder } : {})} - {...(defaultItemHeight ? { defaultItemHeight } : {})} - /> -
-
- {TypingIndicator && } -
- - {giphyPreviewMessage && } + + + + {!threadList && showUnreadMessagesNotification && ( + + )} +
+ + atBottomStateChange={atBottomStateChange} + atBottomThreshold={100} + atTopStateChange={atTopStateChange} + atTopThreshold={100} + className='str-chat__message-list-scroll' + components={{ + EmptyPlaceholder, + Header, + Item, + ...virtuosoComponentsFromProps, + }} + computeItemKey={computeItemKey} + context={{ + additionalMessageInputProps, + closeReactionSelectorOnClick, + customClasses, + customMessageRenderer, + DateSeparator, + firstUnreadMessageId: channelUnreadUiState?.first_unread_message_id, + formatDate, + head, + lastOwnMessage, + lastReadDate: channelUnreadUiState?.last_read, + lastReadMessageId: channelUnreadUiState?.last_read_message_id, + lastReceivedMessageId, + loadingMore, + Message: MessageUIComponent, + messageActions, + messageGroupStyles, + MessageSystem, + numItemsPrepended, + openThread, + ownMessagesDeliveredToOthers, + ownMessagesReadByOthers, + processedMessages, + reactionDetailsSort, + renderText, + returnAllReadData, + shouldGroupByUser, + showAvatar, + sortReactionDetails, + sortReactions, + threadList, + unreadMessageCount: channelUnreadUiState?.unread_messages, + UnreadMessagesSeparator, + virtuosoRef: virtuoso, + }} + firstItemIndex={calculateFirstItemIndex(numItemsPrepended)} + followOutput={followOutput} + increaseViewportBy={{ bottom: 200, top: 0 }} + initialTopMostItemIndex={calculateInitialTopMostItemIndex( + processedMessages, + highlightedMessageId, + )} + itemContent={messageRenderer} + itemSize={fractionalItemSize} + itemsRendered={handleItemsRendered} + key={messageSetKey} + overscan={overscan} + ref={virtuoso} + style={{ overflowX: 'hidden' }} + totalCount={processedMessages.length} + {...overridingVirtuosoProps} + {...(scrollSeekPlaceHolder ? { scrollSeek: scrollSeekPlaceHolder } : {})} + {...(defaultItemHeight ? { defaultItemHeight } : {})} + /> +
+
+ {TypingIndicator && } +
+ + {giphyPreviewMessage && } +
); }; diff --git a/src/context/ComponentContext.tsx b/src/context/ComponentContext.tsx index a8f2aaf235..91532ba115 100644 --- a/src/context/ComponentContext.tsx +++ b/src/context/ComponentContext.tsx @@ -48,6 +48,7 @@ import type { ThreadListItemProps, ThreadListItemUIProps, TimestampProps, + TranslationIndicatorProps, TypingIndicatorProps, UnreadMessagesNotificationProps, UnreadMessagesSeparatorProps, @@ -186,6 +187,8 @@ export type ComponentContextValue = { RecordingPermissionDeniedNotification?: React.ComponentType; /** Custom UI component to display the message reminder information in the Message UI, defaults to and accepts same props as: [ReminderNotification](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/ReminderNotification.tsx) */ ReminderNotification?: React.ComponentType; + /** Custom UI component to display the message translation indicator when message has i18n, defaults to and accepts same props as: [MessageTranslationIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/TranslationIndicator.tsx) */ + MessageTranslationIndicator?: React.ComponentType; /** Custom component to display the search UI, defaults to and accepts same props as: [Search](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Search/Search.tsx) */ Search?: React.ComponentType; /** Custom component to display the UI where the searched string is entered, defaults to and accepts same props as: [SearchBar](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Search/SearchBar/SearchBar.tsx) */ diff --git a/src/context/MessageContext.tsx b/src/context/MessageContext.tsx index 36766699b3..edd68f0793 100644 --- a/src/context/MessageContext.tsx +++ b/src/context/MessageContext.tsx @@ -132,6 +132,16 @@ export type MessageContextValue = { threadList?: boolean; /** render HTML instead of markdown. Posting HTML is only allowed server-side */ unsafeHTML?: boolean; + /** + * User-specific view for translated messages: which text to show. + * - `'original'`: show `message.text` (source language). + * - `'translated'`: show the translation for the **current user language** (from + * `useTranslationContext().userLanguage`), i.e. `message.i18n[userLanguage + '_text']` + * or fallback to `message.text` when missing. Resolved via `getTranslatedMessageText`. + */ + translationView?: 'original' | 'translated'; + /** Set whether this message shows original or translated text (user-specific, does not change message data). */ + setTranslationView?: (view: 'original' | 'translated') => void; }; export const MessageContext = React.createContext( diff --git a/src/context/MessageTranslationViewContext.tsx b/src/context/MessageTranslationViewContext.tsx new file mode 100644 index 0000000000..caa17122ab --- /dev/null +++ b/src/context/MessageTranslationViewContext.tsx @@ -0,0 +1,114 @@ +/** + * Message translation view context: user-specific state for whether each message + * shows original text or a translation. + * + * ## Spec + * + * - **State**: Per message list (channel vs thread), we store a map + * `messageId → 'original' | 'translated'`. Default for messages with `message.i18n` + * is `'translated'`; otherwise `'original'`. + * + * - **Provider placement**: The provider is tied to the **message list**, not the channel. + * It is rendered inside `MessageList` and `VirtualizedMessageList`. That way the + * main channel list and the thread list each have their own translation view state + * (e.g. Thread.tsx gets correct behavior without sharing channel state). + * + * - **Multiple translations**: `message.i18n` can contain multiple languages, e.g.: + * `{ en_text: "Good morning", fr_text: "Bonjour", it_text: "Buongiorno", language: "en" }`. + * Which translation is shown is determined by the app’s **user language** + * (`useTranslationContext().userLanguage`). We use `message.i18n[userLanguage + '_text']`; + * if missing, we fall back to `message.text`. Only one translation is shown at a time. + * + * - **Source language**: When present, `message.i18n.language` is the original/source + * language of `message.text`. It can be used for the indicator label, e.g. + * "Translated from English · View original". + * + * - **Invariants**: The original message content, layout, grouping, and order stay + * unchanged. Removing or toggling translation only changes the annotation and which + * text is displayed. "View original" / "View translation" switch the displayed + * text and update the annotation (e.g. "Original · View translation" when showing + * original). + * + * - **Translation indicator visibility**: The translation indicator (e.g. "Translated · + * View original") is **not** shown when the currently viewed text already corresponds + * to `userLanguage` — for example when viewing original and the original text is the + * user-language version (i.e. `getTranslatedMessageText({ language: userLanguage, message })` + * equals `message.text`). In that case there is no meaningful original/translated choice, + * so the indicator is hidden. + */ + +import React, { createContext, useCallback, useContext, useState } from 'react'; +import type { LocalMessage, TranslationLanguages } from 'stream-chat'; + +/** + * Returns the translated message text for a given language from `message.i18n`, or + * undefined if not present. Used to resolve which of the multiple translations to show. + */ +export const getTranslatedMessageText = ({ + language, + message, +}: { + language: string; + message?: LocalMessage; +}): string | undefined => + message?.i18n?.[`${language}_text` as `${TranslationLanguages}_text`]; + +/** + * Which message text to show. + * - `'original'`: `message.text` (source language). + * - `'translated'`: translation for the **current user language** (TranslationContext’s + * `userLanguage`), i.e. `getTranslatedMessageText({ language: userLanguage, message })` + * or fallback to `message.text`. + */ +export type TranslationView = 'original' | 'translated'; + +export type MessageTranslationViewContextValue = { + getTranslationView: (messageId: string, hasI18n: boolean) => TranslationView; + setTranslationView: (messageId: string, view: TranslationView) => void; +}; + +const defaultContextValue: MessageTranslationViewContextValue = { + getTranslationView: (_messageId, hasI18n) => (hasI18n ? 'translated' : 'original'), + // eslint-disable-next-line @typescript-eslint/no-empty-function + setTranslationView: () => {}, +}; + +export const MessageTranslationViewContext = + createContext(defaultContextValue); + +export const MessageTranslationViewProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const [viewByMessageId, setViewByMessageId] = useState>( + {}, + ); + + const setTranslationView = useCallback((messageId: string, view: TranslationView) => { + setViewByMessageId((prev) => ({ ...prev, [messageId]: view })); + }, []); + + const getTranslationView = useCallback( + (messageId: string, hasI18n: boolean): TranslationView => + viewByMessageId[messageId] ?? (hasI18n ? 'translated' : 'original'), + [viewByMessageId], + ); + + const stableValue = React.useMemo( + () => ({ getTranslationView, setTranslationView }), + [getTranslationView, setTranslationView], + ); + + return ( + + {children} + + ); +}; + +export const useMessageTranslationViewContext = + (): MessageTranslationViewContextValue => { + const context = useContext(MessageTranslationViewContext); + return context ?? defaultContextValue; + }; diff --git a/src/context/index.ts b/src/context/index.ts index d42a0569a6..2277cdbab9 100644 --- a/src/context/index.ts +++ b/src/context/index.ts @@ -8,6 +8,7 @@ export * from './MessageContext'; export * from './MessageBounceContext'; export * from './MessageInputContext'; export * from './MessageListContext'; +export * from './MessageTranslationViewContext'; export * from './PollContext'; export * from './TranslationContext'; export * from './TypingContext'; diff --git a/src/i18n/de.json b/src/i18n/de.json index cbfc29c813..d75440e2b0 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -164,6 +164,63 @@ "giphy-command-description": "Poste ein zufälliges Gif in den Kanal", "Hide who voted": "Verbergen, wer abgestimmt hat", "Instant commands": "Instant commands", + "language/af": "Afrikaans", + "language/am": "Amharisch", + "language/ar": "Arabisch", + "language/az": "Aserbaidschanisch", + "language/bg": "Bulgarisch", + "language/bn": "Bengalisch", + "language/bs": "Bosnisch", + "language/cs": "Tschechisch", + "language/da": "Dänisch", + "language/de": "Deutsch", + "language/el": "Griechisch", + "language/en": "Englisch", + "language/es": "Spanisch", + "language/es-MX": "Spanisch (Mexiko)", + "language/et": "Estnisch", + "language/fa": "Persisch", + "language/fa-AF": "Dari", + "language/fi": "Finnisch", + "language/fr": "Französisch", + "language/fr-CA": "Französisch (Kanada)", + "language/ha": "Hausa", + "language/he": "Hebräisch", + "language/hi": "Hindi", + "language/hr": "Kroatisch", + "language/ht": "Haitianisches Kreolisch", + "language/hu": "Ungarisch", + "language/id": "Indonesisch", + "language/it": "Italienisch", + "language/ja": "Japanisch", + "language/ka": "Georgisch", + "language/ko": "Koreanisch", + "language/lt": "Litauisch", + "language/lv": "Lettisch", + "language/ms": "Malaiisch", + "language/nl": "Niederländisch", + "language/no": "Norwegisch", + "language/pl": "Polnisch", + "language/ps": "Paschtu", + "language/pt": "Portugiesisch", + "language/ro": "Rumänisch", + "language/ru": "Russisch", + "language/sk": "Slowakisch", + "language/sl": "Slowenisch", + "language/so": "Somali", + "language/sq": "Albanisch", + "language/sr": "Serbisch", + "language/sv": "Schwedisch", + "language/sw": "Swahili", + "language/ta": "Tamil", + "language/th": "Thailändisch", + "language/tl": "Tagalog", + "language/tr": "Türkisch", + "language/uk": "Ukrainisch", + "language/ur": "Urdu", + "language/vi": "Vietnamesisch", + "language/zh": "Chinesisch (Vereinfacht)", + "language/zh-TW": "Chinesisch (Traditionell)", "Latest Messages": "Neueste Nachrichten", "Let others add options": "Andere Optionen hinzufügen lassen", "Limit votes per person": "Stimmen pro Person begrenzen", @@ -206,6 +263,7 @@ "Option already exists": "Option existiert bereits", "Option is empty": "Option ist leer", "Options": "Optionen", + "Original": "Original", "People matching": "Passende Personen", "Photo": "Foto", "Pin": "Anheften", @@ -282,15 +340,17 @@ "Thread reply": "Thread-Antwort", "Thread Reply": "Thread-Antwort", "Threads": "Diskussionen", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Heute]\", \"nextDay\": \"[Morgen]\", \"lastDay\": \"[Gestern]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Letzte] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", + "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [um] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "Um mit der Aufnahme zu beginnen, erlauben Sie den Zugriff auf die Kamera in Ihrem Browser", "To start recording, allow the microphone access in your browser": "Um mit der Aufnahme zu beginnen, erlauben Sie den Zugriff auf das Mikrofon in Ihrem Browser", + "Translated": "Übersetzt", + "Translated from {{ language }}": "Übersetzung aus {{ language }}", "translationBuilderTopic/notification": "{{value, notification}}", "Type a number from 2 to 10": "Geben Sie eine Zahl von 2 bis 10 ein", "Type your message": "Nachricht eingeben", @@ -317,7 +377,9 @@ "Video": "Video", "View {{count}} comments_one": "{{count}} Kommentar anzeigen", "View {{count}} comments_other": "{{count}} Kommentare anzeigen", + "View original": "Original anzeigen", "View results": "Ergebnisse anzeigen", + "View translation": "Übersetzung anzeigen", "Voice message": "Sprachnachricht", "Voice message {{ duration }}": "Sprachnachricht {{ duration }}", "Vote ended": "Abstimmung beendet", diff --git a/src/i18n/en.json b/src/i18n/en.json index c42d3899a5..03a2f2f4af 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -164,6 +164,63 @@ "giphy-command-description": "Post a random gif to the channel", "Hide who voted": "Hide who voted", "Instant commands": "Instant commands", + "language/af": "Afrikaans", + "language/am": "Amharic", + "language/ar": "Arabic", + "language/az": "Azerbaijani", + "language/bg": "Bulgarian", + "language/bn": "Bengali", + "language/bs": "Bosnian", + "language/cs": "Czech", + "language/da": "Danish", + "language/de": "German", + "language/el": "Greek", + "language/en": "English", + "language/es": "Spanish", + "language/es-MX": "Spanish (Mexico)", + "language/et": "Estonian", + "language/fa": "Persian", + "language/fa-AF": "Dari", + "language/fi": "Finnish", + "language/fr": "French", + "language/fr-CA": "French (Canada)", + "language/ha": "Hausa", + "language/he": "Hebrew", + "language/hi": "Hindi", + "language/hr": "Croatian", + "language/ht": "Haitian Creole", + "language/hu": "Hungarian", + "language/id": "Indonesian", + "language/it": "Italian", + "language/ja": "Japanese", + "language/ka": "Georgian", + "language/ko": "Korean", + "language/lt": "Lithuanian", + "language/lv": "Latvian", + "language/ms": "Malay", + "language/nl": "Dutch", + "language/no": "Norwegian", + "language/pl": "Polish", + "language/ps": "Pashto", + "language/pt": "Portuguese", + "language/ro": "Romanian", + "language/ru": "Russian", + "language/sk": "Slovak", + "language/sl": "Slovenian", + "language/so": "Somali", + "language/sq": "Albanian", + "language/sr": "Serbian", + "language/sv": "Swedish", + "language/sw": "Swahili", + "language/ta": "Tamil", + "language/th": "Thai", + "language/tl": "Tagalog", + "language/tr": "Turkish", + "language/uk": "Ukrainian", + "language/ur": "Urdu", + "language/vi": "Vietnamese", + "language/zh": "Chinese (Simplified)", + "language/zh-TW": "Chinese (Traditional)", "Latest Messages": "Latest Messages", "Let others add options": "Let others add options", "Limit votes per person": "Limit votes per person", @@ -206,6 +263,7 @@ "Option already exists": "Option already exists", "Option is empty": "Option is empty", "Options": "Options", + "Original": "Original", "People matching": "People matching", "Photo": "Photo", "Pin": "Pin", @@ -291,6 +349,8 @@ "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "To start recording, allow the camera access in your browser", "To start recording, allow the microphone access in your browser": "To start recording, allow the microphone access in your browser", + "Translated": "Translated", + "Translated from {{ language }}": "Translated from {{ language }}", "translationBuilderTopic/notification": "{{value, notification}}", "Type a number from 2 to 10": "Type a number from 2 to 10", "Type your message": "Type your message", @@ -317,7 +377,9 @@ "Video": "Video", "View {{count}} comments_one": "View {{count}} comment", "View {{count}} comments_other": "View {{count}} comments", + "View original": "View original", "View results": "View results", + "View translation": "View translation", "Voice message": "Voice message", "Voice message {{ duration }}": "Voice message {{ duration }}", "Vote ended": "Vote ended", diff --git a/src/i18n/es.json b/src/i18n/es.json index f80166494f..602e4c5929 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -169,6 +169,63 @@ "giphy-command-description": "Publicar un gif aleatorio en el canal", "Hide who voted": "Ocultar quién votó", "Instant commands": "Comandos instantáneos", + "language/af": "Afrikáans", + "language/am": "Amárico", + "language/ar": "Árabe", + "language/az": "Azerbaiyano", + "language/bg": "Búlgaro", + "language/bn": "Bengalí", + "language/bs": "Bosnio", + "language/cs": "Checo", + "language/da": "Danés", + "language/de": "Alemán", + "language/el": "Griego", + "language/en": "Inglés", + "language/es": "Español", + "language/es-MX": "Español (México)", + "language/et": "Estonio", + "language/fa": "Persa", + "language/fa-AF": "Dari", + "language/fi": "Finlandés", + "language/fr": "Francés", + "language/fr-CA": "Francés (Canadá)", + "language/ha": "Hausa", + "language/he": "Hebreo", + "language/hi": "Hindi", + "language/hr": "Croata", + "language/ht": "Criollo haitiano", + "language/hu": "Húngaro", + "language/id": "Indonesio", + "language/it": "Italiano", + "language/ja": "Japonés", + "language/ka": "Georgiano", + "language/ko": "Coreano", + "language/lt": "Lituano", + "language/lv": "Letón", + "language/ms": "Malayo", + "language/nl": "Neerlandés", + "language/no": "Noruego", + "language/pl": "Polaco", + "language/ps": "Pastún", + "language/pt": "Portugués", + "language/ro": "Rumano", + "language/ru": "Ruso", + "language/sk": "Eslovaco", + "language/sl": "Esloveno", + "language/so": "Somalí", + "language/sq": "Albanés", + "language/sr": "Serbio", + "language/sv": "Sueco", + "language/sw": "Suajili", + "language/ta": "Tamil", + "language/th": "Tailandés", + "language/tl": "Tagalo", + "language/tr": "Turco", + "language/uk": "Ucraniano", + "language/ur": "Urdu", + "language/vi": "Vietnamita", + "language/zh": "Chino (simplificado)", + "language/zh-TW": "Chino (tradicional)", "Latest Messages": "Últimos mensajes", "Let others add options": "Permitir que otros añadan opciones", "Limit votes per person": "Limitar votos por persona", @@ -211,6 +268,7 @@ "Option already exists": "La opción ya existe", "Option is empty": "La opción está vacía", "Options": "Opciones", + "Original": "Original", "People matching": "Personas que coinciden", "Photo": "Foto", "Pin": "Fijar", @@ -291,15 +349,17 @@ "Thread reply": "Respuesta en hilo", "Thread Reply": "Respuesta en hilo", "Threads": "Hilos", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Hoy]\", \"nextDay\": \"[Mañana]\", \"lastDay\": \"[Ayer]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Último] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", + "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [a las] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "Para comenzar a grabar, permita el acceso a la cámara en su navegador", "To start recording, allow the microphone access in your browser": "Para comenzar a grabar, permita el acceso al micrófono en su navegador", + "Translated": "Traducido", + "Translated from {{ language }}": "Traducido de {{ language }}", "translationBuilderTopic/notification": "{{value, notification}}", "Type a number from 2 to 10": "Escribe un número del 2 al 10", "Type your message": "Escribe tu mensaje", @@ -328,7 +388,9 @@ "View {{count}} comments_one": "Ver {{count}} comentario", "View {{count}} comments_many": "Ver {{count}} comentarios", "View {{count}} comments_other": "Ver {{count}} comentarios", + "View original": "Ver original", "View results": "Ver resultados", + "View translation": "Ver traducción", "Voice message": "Mensaje de voz", "Voice message {{ duration }}": "Mensaje de voz {{ duration }}", "Vote ended": "Votación finalizada", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index e788a2f35b..1920175c23 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -169,6 +169,63 @@ "giphy-command-description": "Poster un GIF aléatoire dans le canal", "Hide who voted": "Masquer qui a voté", "Instant commands": "Commandes instantanées", + "language/af": "Afrikaans", + "language/am": "Amharique", + "language/ar": "Arabe", + "language/az": "Azéri", + "language/bg": "Bulgare", + "language/bn": "Bengali", + "language/bs": "Bosnien", + "language/cs": "Tchèque", + "language/da": "Danois", + "language/de": "Allemand", + "language/el": "Grec", + "language/en": "Anglais", + "language/es": "Espagnol", + "language/es-MX": "Espagnol (Mexique)", + "language/et": "Estonien", + "language/fa": "Persan", + "language/fa-AF": "Dari", + "language/fi": "Finnois", + "language/fr": "Français", + "language/fr-CA": "Français (Canada)", + "language/ha": "Haoussa", + "language/he": "Hébreu", + "language/hi": "Hindi", + "language/hr": "Croate", + "language/ht": "Créole haïtien", + "language/hu": "Hongrois", + "language/id": "Indonésien", + "language/it": "Italien", + "language/ja": "Japonais", + "language/ka": "Géorgien", + "language/ko": "Coréen", + "language/lt": "Lituanien", + "language/lv": "Letton", + "language/ms": "Malais", + "language/nl": "Néerlandais", + "language/no": "Norvégien", + "language/pl": "Polonais", + "language/ps": "Pachto", + "language/pt": "Portugais", + "language/ro": "Roumain", + "language/ru": "Russe", + "language/sk": "Slovaque", + "language/sl": "Slovène", + "language/so": "Somali", + "language/sq": "Albanais", + "language/sr": "Serbe", + "language/sv": "Suédois", + "language/sw": "Swahili", + "language/ta": "Tamoul", + "language/th": "Thaï", + "language/tl": "Tagalog", + "language/tr": "Turc", + "language/uk": "Ukrainien", + "language/ur": "Ourdou", + "language/vi": "Vietnamien", + "language/zh": "Chinois (simplifié)", + "language/zh-TW": "Chinois (traditionnel)", "Latest Messages": "Derniers messages", "Let others add options": "Permettre à d'autres d'ajouter des options", "Limit votes per person": "Limiter les votes par personne", @@ -211,6 +268,7 @@ "Option already exists": "L'option existe déjà", "Option is empty": "L'option est vide", "Options": "Options", + "Original": "Original", "People matching": "Correspondance de personnes", "Photo": "Photo", "Pin": "Épingler", @@ -291,15 +349,17 @@ "Thread reply": "Réponse dans le fil", "Thread Reply": "Réponse dans le fil", "Threads": "Fils", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Aujourd'hui]\", \"nextDay\": \"[Demain]\", \"lastDay\": \"[Hier]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Dernier] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", + "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [à] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "Pour commencer l'enregistrement, autorisez l'accès à la caméra dans votre navigateur", "To start recording, allow the microphone access in your browser": "Pour commencer l'enregistrement, autorisez l'accès au microphone dans votre navigateur", + "Translated": "Traduit", + "Translated from {{ language }}": "Traduit du {{ language }}", "translationBuilderTopic/notification": "{{value, notification}}", "Type a number from 2 to 10": "Tapez un nombre de 2 à 10", "Type your message": "Tapez votre message", @@ -328,7 +388,9 @@ "View {{count}} comments_one": "Voir {{count}} commentaire", "View {{count}} comments_many": "Voir {{count}} commentaires", "View {{count}} comments_other": "Voir {{count}} commentaires", + "View original": "Voir l'original", "View results": "Voir les résultats", + "View translation": "Voir la traduction", "Voice message": "Message vocal", "Voice message {{ duration }}": "Message vocal {{ duration }}", "Vote ended": "Vote terminé", diff --git a/src/i18n/hi.json b/src/i18n/hi.json index 9d6aca93fa..f6f6db5a92 100644 --- a/src/i18n/hi.json +++ b/src/i18n/hi.json @@ -165,6 +165,63 @@ "giphy-command-description": "चैनल पर एक क्रॉफिल जीआइएफ पोस्ट करें", "Hide who voted": "किसने वोट दिया छिपाएं", "Instant commands": "तत्काल कमांड", + "language/af": "अफ्रीकी", + "language/am": "अम्हारिक", + "language/ar": "अरबी", + "language/az": "अज़रबैजानी", + "language/bg": "बुल्गारियाई", + "language/bn": "बंगाली", + "language/bs": "बोस्नियाई", + "language/cs": "चेक", + "language/da": "डेनिश", + "language/de": "जर्मन", + "language/el": "यूनानी", + "language/en": "अंग्रेज़ी", + "language/es": "स्पेनिश", + "language/es-MX": "स्पेनिश (मेक्सिको)", + "language/et": "एस्तोनियाई", + "language/fa": "फ़ारसी", + "language/fa-AF": "दरी", + "language/fi": "फ़िनिश", + "language/fr": "फ़्रेंच", + "language/fr-CA": "फ़्रेंच (कनाडा)", + "language/ha": "हौसा", + "language/he": "हिब्रू", + "language/hi": "हिंदी", + "language/hr": "क्रोएशियाई", + "language/ht": "हैतियाई क्रियोल", + "language/hu": "हंगेरी", + "language/id": "इंडोनेशियाई", + "language/it": "इतालवी", + "language/ja": "जापानी", + "language/ka": "जॉर्जियाई", + "language/ko": "कोरियाई", + "language/lt": "लिथुआनियाई", + "language/lv": "लातवियाई", + "language/ms": "मलय", + "language/nl": "डच", + "language/no": "नॉर्वेजियाई", + "language/pl": "पोलिश", + "language/ps": "पश्तो", + "language/pt": "पुर्तगाली", + "language/ro": "रोमानियाई", + "language/ru": "रूसी", + "language/sk": "स्लोवाक", + "language/sl": "स्लोवेनियाई", + "language/so": "सोमाली", + "language/sq": "अल्बानियाई", + "language/sr": "सर्बियाई", + "language/sv": "स्वीडिश", + "language/sw": "स्वाहिली", + "language/ta": "तमिल", + "language/th": "थाई", + "language/tl": "तागालोग", + "language/tr": "तुर्की", + "language/uk": "यूक्रेनियाई", + "language/ur": "उर्दू", + "language/vi": "वियतनामी", + "language/zh": "चीनी (सरलीकृत)", + "language/zh-TW": "चीनी (पारंपरिक)", "Latest Messages": "नवीनतम संदेश", "Let others add options": "दूसरों को विकल्प जोड़ने दें", "Limit votes per person": "प्रति व्यक्ति वोट सीमित करें", @@ -207,6 +264,7 @@ "Option already exists": "विकल्प पहले से मौजूद है", "Option is empty": "विकल्प खाली है", "Options": "विकल्प", + "Original": "मूल", "People matching": "मेल खाते लोग", "Photo": "फ़ोटो", "Pin": "पिन", @@ -283,15 +341,17 @@ "Thread reply": "थ्रेड में उत्तर", "Thread Reply": "थ्रेड में उत्तर", "Threads": "थ्रेड्स", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[आज]\", \"nextDay\": \"[कल]\", \"lastDay\": \"[बीता कल]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[पिछला] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", + "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [को] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "रिकॉर्डिंग शुरू करने के लिए, अपने ब्राउज़र में कैमरा तक पहुँच दें", "To start recording, allow the microphone access in your browser": "रिकॉर्डिंग शुरू करने के लिए, अपने ब्राउज़र में माइक्रोफ़ोन तक पहुँच दें", + "Translated": "अनुवादित", + "Translated from {{ language }}": "{{ language }} से अनुवादित", "translationBuilderTopic/notification": "{{value, notification}}", "Type a number from 2 to 10": "2 से 10 तक का एक नंबर टाइप करें", "Type your message": "अपना मैसेज लिखे", @@ -318,7 +378,9 @@ "Video": "वीडियो", "View {{count}} comments_one": "देखें {{count}} टिप्पणी", "View {{count}} comments_other": "देखें {{count}} टिप्पणियाँ", + "View original": "मूल देखें", "View results": "परिणाम देखें", + "View translation": "अनुवाद देखें", "Voice message": "आवाज संदेश", "Voice message {{ duration }}": "वॉइस संदेश {{ duration }}", "Vote ended": "मतदान समाप्त", diff --git a/src/i18n/it.json b/src/i18n/it.json index e4ccba6c41..d9ee0d188e 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -169,6 +169,63 @@ "giphy-command-description": "Pubblica un gif casuale sul canale", "Hide who voted": "Nascondi chi ha votato", "Instant commands": "Comandi istantanei", + "language/af": "Afrikaans", + "language/am": "Amarico", + "language/ar": "Arabo", + "language/az": "Azero", + "language/bg": "Bulgaro", + "language/bn": "Bengalese", + "language/bs": "Bosniaco", + "language/cs": "Ceco", + "language/da": "Danese", + "language/de": "Tedesco", + "language/el": "Greco", + "language/en": "Inglese", + "language/es": "Spagnolo", + "language/es-MX": "Spagnolo (Messico)", + "language/et": "Estone", + "language/fa": "Persiano", + "language/fa-AF": "Dari", + "language/fi": "Finlandese", + "language/fr": "Francese", + "language/fr-CA": "Francese (Canada)", + "language/ha": "Hausa", + "language/he": "Ebraico", + "language/hi": "Hindi", + "language/hr": "Croato", + "language/ht": "Creolo haitiano", + "language/hu": "Ungherese", + "language/id": "Indonesiano", + "language/it": "Italiano", + "language/ja": "Giapponese", + "language/ka": "Georgiano", + "language/ko": "Coreano", + "language/lt": "Lituano", + "language/lv": "Lettone", + "language/ms": "Malese", + "language/nl": "Olandese", + "language/no": "Norvegese", + "language/pl": "Polacco", + "language/ps": "Pashto", + "language/pt": "Portoghese", + "language/ro": "Rumeno", + "language/ru": "Russo", + "language/sk": "Slovacco", + "language/sl": "Sloveno", + "language/so": "Somalo", + "language/sq": "Albanese", + "language/sr": "Serbo", + "language/sv": "Svedese", + "language/sw": "Swahili", + "language/ta": "Tamil", + "language/th": "Thai", + "language/tl": "Tagalog", + "language/tr": "Turco", + "language/uk": "Ucraino", + "language/ur": "Urdu", + "language/vi": "Vietnamita", + "language/zh": "Cinese (semplificato)", + "language/zh-TW": "Cinese (tradizionale)", "Latest Messages": "Ultimi messaggi", "Let others add options": "Lascia che altri aggiungano opzioni", "Limit votes per person": "Limita i voti per persona", @@ -211,6 +268,7 @@ "Option already exists": "L'opzione esiste già", "Option is empty": "L'opzione è vuota", "Options": "Opzioni", + "Original": "Originale", "People matching": "Persone che corrispondono", "Photo": "Foto", "Pin": "Appunta", @@ -291,15 +349,17 @@ "Thread reply": "Risposta nella discussione", "Thread Reply": "Risposta nella discussione", "Threads": "Thread", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Oggi]\", \"nextDay\": \"[Domani]\", \"lastDay\": \"[Ieri]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Scorsa] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", + "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [alle] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "Per iniziare a registrare, consenti l'accesso alla fotocamera nel tuo browser", "To start recording, allow the microphone access in your browser": "Per iniziare a registrare, consenti l'accesso al microfono nel tuo browser", + "Translated": "Tradotto", + "Translated from {{ language }}": "Tradotto da {{ language }}", "translationBuilderTopic/notification": "{{value, notification}}", "Type a number from 2 to 10": "Digita un numero da 2 a 10", "Type your message": "Scrivi il tuo messaggio", @@ -328,7 +388,9 @@ "View {{count}} comments_one": "Visualizza {{count}} commento", "View {{count}} comments_many": "Visualizza {{count}} commenti", "View {{count}} comments_other": "Visualizza {{count}} commenti", + "View original": "Visualizza originale", "View results": "Vedi risultati", + "View translation": "Visualizza traduzione", "Voice message": "Messaggio vocale", "Voice message {{ duration }}": "Messaggio vocale {{ duration }}", "Vote ended": "Voto terminato", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index 173beae863..70fec68c83 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -164,6 +164,63 @@ "giphy-command-description": "チャンネルにランダムなGIFを投稿する", "Hide who voted": "誰が投票したかを非表示にする", "Instant commands": "インスタントコマンド", + "language/af": "アフリカース語", + "language/am": "アムハラ語", + "language/ar": "アラビア語", + "language/az": "アゼルバイジャン語", + "language/bg": "ブルガリア語", + "language/bn": "ベンガル語", + "language/bs": "ボスニア語", + "language/cs": "チェコ語", + "language/da": "デンマーク語", + "language/de": "ドイツ語", + "language/el": "ギリシャ語", + "language/en": "英語", + "language/es": "スペイン語", + "language/es-MX": "スペイン語(メキシコ)", + "language/et": "エストニア語", + "language/fa": "ペルシア語", + "language/fa-AF": "ダリー語", + "language/fi": "フィンランド語", + "language/fr": "フランス語", + "language/fr-CA": "フランス語(カナダ)", + "language/ha": "ハウサ語", + "language/he": "ヘブライ語", + "language/hi": "ヒンディー語", + "language/hr": "クロアチア語", + "language/ht": "ハイチ・クレオール語", + "language/hu": "ハンガリー語", + "language/id": "インドネシア語", + "language/it": "イタリア語", + "language/ja": "日本語", + "language/ka": "グルジア語", + "language/ko": "韓国語", + "language/lt": "リトアニア語", + "language/lv": "ラトビア語", + "language/ms": "マレー語", + "language/nl": "オランダ語", + "language/no": "ノルウェー語", + "language/pl": "ポーランド語", + "language/ps": "パシュトー語", + "language/pt": "ポルトガル語", + "language/ro": "ルーマニア語", + "language/ru": "ロシア語", + "language/sk": "スロバキア語", + "language/sl": "スロベニア語", + "language/so": "ソマリ語", + "language/sq": "アルバニア語", + "language/sr": "セルビア語", + "language/sv": "スウェーデン語", + "language/sw": "スワヒリ語", + "language/ta": "タミル語", + "language/th": "タイ語", + "language/tl": "タガログ語", + "language/tr": "トルコ語", + "language/uk": "ウクライナ語", + "language/ur": "ウルドゥー語", + "language/vi": "ベトナム語", + "language/zh": "中国語(簡体字)", + "language/zh-TW": "中国語(繁体字)", "Latest Messages": "最新のメッセージ", "Let others add options": "他の人が選択肢を追加できるようにする", "Limit votes per person": "1人あたりの投票数を制限する", @@ -206,6 +263,7 @@ "Option already exists": "オプションは既に存在します", "Option is empty": "オプションが空です", "Options": "オプション", + "Original": "原文", "People matching": "一致する人", "Photo": "写真", "Pin": "ピン", @@ -282,15 +340,17 @@ "Thread reply": "スレッドの返信", "Thread Reply": "スレッドの返信", "Threads": "スレッド", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[今日]\", \"nextDay\": \"[明日]\", \"lastDay\": \"[昨日]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[先週の] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", + "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "録音を開始するには、ブラウザーでカメラへのアクセスを許可してください", "To start recording, allow the microphone access in your browser": "録音を開始するには、ブラウザーでマイクロフォンへのアクセスを許可してください", + "Translated": "翻訳済み", + "Translated from {{ language }}": "{{ language }}から翻訳", "translationBuilderTopic/notification": "{{value, notification}}", "Type a number from 2 to 10": "2から10までの数字を入力してください", "Type your message": "メッセージを入力してください", @@ -317,7 +377,9 @@ "Video": "動画", "View {{count}} comments_one": "{{count}} コメントを表示", "View {{count}} comments_other": "{{count}} コメントを表示", + "View original": "原文を表示", "View results": "結果を表示", + "View translation": "翻訳を表示", "Voice message": "ボイスメッセージ", "Voice message {{ duration }}": "ボイスメッセージ {{ duration }}", "Vote ended": "投票が終了しました", diff --git a/src/i18n/ko.json b/src/i18n/ko.json index 924bcb29dc..d76d2dbe48 100644 --- a/src/i18n/ko.json +++ b/src/i18n/ko.json @@ -164,6 +164,63 @@ "giphy-command-description": "채널에 무작위 GIF 게시", "Hide who voted": "누가 투표했는지 숨기기", "Instant commands": "즉시 명령어", + "language/af": "아프리칸스어", + "language/am": "암하라어", + "language/ar": "아랍어", + "language/az": "아제르바이잔어", + "language/bg": "불가리아어", + "language/bn": "벵골어", + "language/bs": "보스니아어", + "language/cs": "체코어", + "language/da": "덴마크어", + "language/de": "독일어", + "language/el": "그리스어", + "language/en": "영어", + "language/es": "스페인어", + "language/es-MX": "스페인어(멕시코)", + "language/et": "에스토니아어", + "language/fa": "페르시아어", + "language/fa-AF": "다리어", + "language/fi": "핀란드어", + "language/fr": "프랑스어", + "language/fr-CA": "프랑스어(캐나다)", + "language/ha": "하우사어", + "language/he": "히브리어", + "language/hi": "힌디어", + "language/hr": "크로아티아어", + "language/ht": "아이티 크리올어", + "language/hu": "헝가리어", + "language/id": "인도네시아어", + "language/it": "이탈리아어", + "language/ja": "일본어", + "language/ka": "조지아어", + "language/ko": "한국어", + "language/lt": "리투아니아어", + "language/lv": "라트비아어", + "language/ms": "말레이어", + "language/nl": "네덜란드어", + "language/no": "노르웨이어", + "language/pl": "폴란드어", + "language/ps": "파슈토어", + "language/pt": "포르투갈어", + "language/ro": "루마니아어", + "language/ru": "러시아어", + "language/sk": "슬로바키아어", + "language/sl": "슬로베니아어", + "language/so": "소말리아어", + "language/sq": "알바니아어", + "language/sr": "세르비아어", + "language/sv": "스웨덴어", + "language/sw": "스와힐리어", + "language/ta": "타밀어", + "language/th": "태국어", + "language/tl": "타갈로그어", + "language/tr": "터키어", + "language/uk": "우크라이나어", + "language/ur": "우르두어", + "language/vi": "베트남어", + "language/zh": "중국어(간체)", + "language/zh-TW": "중국어(번체)", "Latest Messages": "최신 메시지", "Let others add options": "다른 사람이 선택지를 추가할 수 있도록 허용", "Limit votes per person": "1인당 투표 수 제한", @@ -206,6 +263,7 @@ "Option already exists": "옵션이 이미 존재합니다", "Option is empty": "옵션이 비어 있습니다", "Options": "옵션", + "Original": "원문", "People matching": "일치하는 사람", "Photo": "사진", "Pin": "핀", @@ -282,15 +340,17 @@ "Thread reply": "스레드 답장", "Thread Reply": "스레드 답장", "Threads": "스레드", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[오늘]\", \"nextDay\": \"[내일]\", \"lastDay\": \"[어제]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[지난] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", + "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "브라우저에서 카메라 액세스를 허용하여 녹음을 시작합니다", "To start recording, allow the microphone access in your browser": "브라우저에서 마이크로폰 액세스를 허용하여 녹음을 시작합니다", + "Translated": "번역됨", + "Translated from {{ language }}": "{{ language }}(으)로 번역됨", "translationBuilderTopic/notification": "{{value, notification}}", "Type a number from 2 to 10": "2에서 10 사이의 숫자를 입력하세요", "Type your message": "메시지 입력", @@ -317,7 +377,9 @@ "Video": "동영상", "View {{count}} comments_one": "{{count}}개의 댓글 보기", "View {{count}} comments_other": "{{count}}개의 댓글 보기", + "View original": "원문 보기", "View results": "결과 보기", + "View translation": "번역 보기", "Voice message": "음성 메시지", "Voice message {{ duration }}": "음성 메시지 {{ duration }}", "Vote ended": "투표 종료", diff --git a/src/i18n/nl.json b/src/i18n/nl.json index 7ffc87889d..441d93dcc5 100644 --- a/src/i18n/nl.json +++ b/src/i18n/nl.json @@ -164,6 +164,63 @@ "giphy-command-description": "Plaats een willekeurige gif in het kanaal", "Hide who voted": "Verberg wie heeft gestemd", "Instant commands": "Snelle opdrachten", + "language/af": "Afrikaans", + "language/am": "Amhaars", + "language/ar": "Arabisch", + "language/az": "Azerbeidzjaans", + "language/bg": "Bulgaars", + "language/bn": "Bengaals", + "language/bs": "Bosnisch", + "language/cs": "Tsjechisch", + "language/da": "Deens", + "language/de": "Duits", + "language/el": "Grieks", + "language/en": "Engels", + "language/es": "Spaans", + "language/es-MX": "Spaans (Mexico)", + "language/et": "Estlands", + "language/fa": "Perzisch", + "language/fa-AF": "Dari", + "language/fi": "Fins", + "language/fr": "Frans", + "language/fr-CA": "Frans (Canada)", + "language/ha": "Hausa", + "language/he": "Hebreeuws", + "language/hi": "Hindi", + "language/hr": "Kroatisch", + "language/ht": "Haïtiaans Creools", + "language/hu": "Hongaars", + "language/id": "Indonesisch", + "language/it": "Italiaans", + "language/ja": "Japans", + "language/ka": "Georgisch", + "language/ko": "Koreaans", + "language/lt": "Litouws", + "language/lv": "Letlands", + "language/ms": "Maleis", + "language/nl": "Nederlands", + "language/no": "Noors", + "language/pl": "Pools", + "language/ps": "Pasjtoe", + "language/pt": "Portugees", + "language/ro": "Roemeens", + "language/ru": "Russisch", + "language/sk": "Slowaaks", + "language/sl": "Sloveens", + "language/so": "Somali", + "language/sq": "Albanees", + "language/sr": "Servisch", + "language/sv": "Zweeds", + "language/sw": "Swahili", + "language/ta": "Tamil", + "language/th": "Thai", + "language/tl": "Tagalog", + "language/tr": "Turks", + "language/uk": "Oekraïens", + "language/ur": "Urdu", + "language/vi": "Vietnamees", + "language/zh": "Chinees (vereenvoudigd)", + "language/zh-TW": "Chinees (traditioneel)", "Latest Messages": "Laatste berichten", "Let others add options": "Laat anderen opties toevoegen", "Limit votes per person": "Stemmen per persoon beperken", @@ -206,6 +263,7 @@ "Option already exists": "Optie bestaat al", "Option is empty": "Optie is leeg", "Options": "Opties", + "Original": "Origineel", "People matching": "Mensen die matchen", "Photo": "Foto", "Pin": "Vastmaken", @@ -284,15 +342,17 @@ "Thread reply": "Draadje antwoord", "Thread Reply": "Draadje antwoord", "Threads": "Discussies", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Vandaag]\", \"nextDay\": \"[Morgen]\", \"lastDay\": \"[Gisteren]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Laatste] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", + "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [om] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "Om te beginnen met opnemen, sta toegang tot de camera toe in uw browser", "To start recording, allow the microphone access in your browser": "Om te beginnen met opnemen, sta toegang tot de microfoon toe in uw browser", + "Translated": "Vertaald", + "Translated from {{ language }}": "Vertaald uit {{ language }}", "translationBuilderTopic/notification": "{{value, notification}}", "Type a number from 2 to 10": "Typ een getal van 2 tot 10", "Type your message": "Type je bericht", @@ -319,7 +379,9 @@ "Video": "Video", "View {{count}} comments_one": "Bekijk {{count}} opmerkingen", "View {{count}} comments_other": "Bekijk {{count}} opmerkingen", + "View original": "Origineel bekijken", "View results": "Bekijk resultaten", + "View translation": "Vertaling bekijken", "Voice message": "Spraakbericht", "Voice message {{ duration }}": "Spraakbericht {{ duration }}", "Vote ended": "Stemmen beëindigd", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index 90fc36ab12..15b2ac0b54 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -169,6 +169,63 @@ "giphy-command-description": "Postar um gif aleatório no canal", "Hide who voted": "Ocultar quem votou", "Instant commands": "Comandos instantâneos", + "language/af": "Africâner", + "language/am": "Amárico", + "language/ar": "Árabe", + "language/az": "Azerbaijano", + "language/bg": "Búlgaro", + "language/bn": "Bengali", + "language/bs": "Bósnio", + "language/cs": "Tcheco", + "language/da": "Dinamarquês", + "language/de": "Alemão", + "language/el": "Grego", + "language/en": "Inglês", + "language/es": "Espanhol", + "language/es-MX": "Espanhol (México)", + "language/et": "Estoniano", + "language/fa": "Persa", + "language/fa-AF": "Dari", + "language/fi": "Finlandês", + "language/fr": "Francês", + "language/fr-CA": "Francês (Canadá)", + "language/ha": "Hauçá", + "language/he": "Hebraico", + "language/hi": "Hindi", + "language/hr": "Croata", + "language/ht": "Crioulo haitiano", + "language/hu": "Húngaro", + "language/id": "Indonésio", + "language/it": "Italiano", + "language/ja": "Japonês", + "language/ka": "Georgiano", + "language/ko": "Coreano", + "language/lt": "Lituano", + "language/lv": "Letão", + "language/ms": "Malaio", + "language/nl": "Holandês", + "language/no": "Norueguês", + "language/pl": "Polonês", + "language/ps": "Pashto", + "language/pt": "Português", + "language/ro": "Romeno", + "language/ru": "Russo", + "language/sk": "Eslovaco", + "language/sl": "Esloveno", + "language/so": "Somali", + "language/sq": "Albanês", + "language/sr": "Sérvio", + "language/sv": "Sueco", + "language/sw": "Suaíli", + "language/ta": "Tâmil", + "language/th": "Tailandês", + "language/tl": "Tagalo", + "language/tr": "Turco", + "language/uk": "Ucraniano", + "language/ur": "Urdu", + "language/vi": "Vietnamita", + "language/zh": "Chinês (simplificado)", + "language/zh-TW": "Chinês (tradicional)", "Latest Messages": "Mensagens mais recentes", "Let others add options": "Permitir que outros adicionem opções", "Limit votes per person": "Limitar votos por pessoa", @@ -211,6 +268,7 @@ "Option already exists": "Opção já existe", "Option is empty": "A opção está vazia", "Options": "Opções", + "Original": "Original", "People matching": "Pessoas correspondentes", "Photo": "Foto", "Pin": "Fixar", @@ -291,15 +349,17 @@ "Thread reply": "Resposta no fio", "Thread Reply": "Resposta no fio", "Threads": "Tópicos", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Hoje]\", \"nextDay\": \"[Amanhã]\", \"lastDay\": \"[Ontem]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Último] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", + "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [às] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "Para começar a gravar, permita o acesso à câmera no seu navegador", "To start recording, allow the microphone access in your browser": "Para começar a gravar, permita o acesso ao microfone no seu navegador", + "Translated": "Traduzido", + "Translated from {{ language }}": "Traduzido de {{ language }}", "translationBuilderTopic/notification": "{{value, notification}}", "Type a number from 2 to 10": "Digite um número de 2 a 10", "Type your message": "Digite sua mensagem", @@ -328,7 +388,9 @@ "View {{count}} comments_one": "Ver {{count}} comentário", "View {{count}} comments_many": "Ver {{count}} comentários", "View {{count}} comments_other": "Ver {{count}} comentários", + "View original": "Ver original", "View results": "Ver resultados", + "View translation": "Ver tradução", "Voice message": "Mensagem de voz", "Voice message {{ duration }}": "Mensagem de voz {{ duration }}", "Vote ended": "Votação encerrada", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 0bf2a89b93..dc5bf0319e 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -174,6 +174,63 @@ "giphy-command-description": "Опубликовать случайную GIF-анимацию в канале", "Hide who voted": "Скрыть, кто голосовал", "Instant commands": "Мгновенные команды", + "language/af": "Африкаанс", + "language/am": "Амхарский", + "language/ar": "Арабский", + "language/az": "Азербайджанский", + "language/bg": "Болгарский", + "language/bn": "Бенгальский", + "language/bs": "Боснийский", + "language/cs": "Чешский", + "language/da": "Датский", + "language/de": "Немецкий", + "language/el": "Греческий", + "language/en": "Английский", + "language/es": "Испанский", + "language/es-MX": "Испанский (Мексика)", + "language/et": "Эстонский", + "language/fa": "Персидский", + "language/fa-AF": "Дари", + "language/fi": "Финский", + "language/fr": "Французский", + "language/fr-CA": "Французский (Канада)", + "language/ha": "Хауса", + "language/he": "Иврит", + "language/hi": "Хинди", + "language/hr": "Хорватский", + "language/ht": "Гаитянский креольский", + "language/hu": "Венгерский", + "language/id": "Индонезийский", + "language/it": "Итальянский", + "language/ja": "Японский", + "language/ka": "Грузинский", + "language/ko": "Корейский", + "language/lt": "Литовский", + "language/lv": "Латышский", + "language/ms": "Малайский", + "language/nl": "Нидерландский", + "language/no": "Норвежский", + "language/pl": "Польский", + "language/ps": "Пушту", + "language/pt": "Португальский", + "language/ro": "Румынский", + "language/ru": "Русский", + "language/sk": "Словацкий", + "language/sl": "Словенский", + "language/so": "Сомали", + "language/sq": "Албанский", + "language/sr": "Сербский", + "language/sv": "Шведский", + "language/sw": "Суахили", + "language/ta": "Тамильский", + "language/th": "Тайский", + "language/tl": "Тагальский", + "language/tr": "Турецкий", + "language/uk": "Украинский", + "language/ur": "Урду", + "language/vi": "Вьетнамский", + "language/zh": "Китайский (упрощённый)", + "language/zh-TW": "Китайский (традиционный)", "Latest Messages": "Последние сообщения", "Let others add options": "Разрешить другим добавлять варианты", "Limit votes per person": "Ограничить голоса на человека", @@ -216,6 +273,7 @@ "Option already exists": "Вариант уже существует", "Option is empty": "Вариант пуст", "Options": "Варианты", + "Original": "Оригинал", "People matching": "Совпадающие люди", "Photo": "Фото", "Pin": "Закрепить", @@ -300,15 +358,17 @@ "Thread reply": "Ответ в ветке", "Thread Reply": "Ответ в ветке", "Threads": "Треды", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Сегодня]\", \"nextDay\": \"[Завтра]\", \"lastDay\": \"[Вчера]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[В прошлый] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", + "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [в] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "Для начала записи разрешите доступ к камере в вашем браузере", "To start recording, allow the microphone access in your browser": "Для начала записи разрешите доступ к микрофону в вашем браузере", + "Translated": "Переведено", + "Translated from {{ language }}": "Переведено с {{ language }}", "translationBuilderTopic/notification": "{{value, notification}}", "Type a number from 2 to 10": "Введите число от 2 до 10", "Type your message": "Ваше сообщение", @@ -339,7 +399,9 @@ "View {{count}} comments_few": "Просмотреть {{count}} комментариев", "View {{count}} comments_many": "Просмотреть {{count}} комментариев", "View {{count}} comments_other": "Просмотреть {{count}} комментариев", + "View original": "Показать оригинал", "View results": "Посмотреть результаты", + "View translation": "Показать перевод", "Voice message": "Голосовое сообщение", "Voice message {{ duration }}": "Голосовое сообщение {{ duration }}", "Vote ended": "Голосование завершено", diff --git a/src/i18n/tr.json b/src/i18n/tr.json index 046d5a7078..004100c34e 100644 --- a/src/i18n/tr.json +++ b/src/i18n/tr.json @@ -164,6 +164,63 @@ "giphy-command-description": "Rastgele bir gif'i kanala gönder", "Hide who voted": "Kimin oy verdiğini gizle", "Instant commands": "Anlık komutlar", + "language/af": "Afrikanca", + "language/am": "Amharca", + "language/ar": "Arapça", + "language/az": "Azerice", + "language/bg": "Bulgarca", + "language/bn": "Bengalce", + "language/bs": "Boşnakça", + "language/cs": "Çekçe", + "language/da": "Danca", + "language/de": "Almanca", + "language/el": "Yunanca", + "language/en": "İngilizce", + "language/es": "İspanyolca", + "language/es-MX": "İspanyolca (Meksika)", + "language/et": "Estonyaca", + "language/fa": "Farsça", + "language/fa-AF": "Darice", + "language/fi": "Fince", + "language/fr": "Fransızca", + "language/fr-CA": "Fransızca (Kanada)", + "language/ha": "Hausa", + "language/he": "İbranice", + "language/hi": "Hintçe", + "language/hr": "Hırvatça", + "language/ht": "Haiti Kreolcesi", + "language/hu": "Macarca", + "language/id": "Endonezce", + "language/it": "İtalyanca", + "language/ja": "Japonca", + "language/ka": "Gürcüce", + "language/ko": "Korece", + "language/lt": "Litvanca", + "language/lv": "Letonca", + "language/ms": "Malayca", + "language/nl": "Felemenkçe", + "language/no": "Norveççe", + "language/pl": "Lehçe", + "language/ps": "Peştuca", + "language/pt": "Portekizce", + "language/ro": "Romence", + "language/ru": "Rusça", + "language/sk": "Slovakça", + "language/sl": "Slovence", + "language/so": "Somalice", + "language/sq": "Arnavutça", + "language/sr": "Sırpça", + "language/sv": "İsveççe", + "language/sw": "Svahilice", + "language/ta": "Tamilce", + "language/th": "Tayca", + "language/tl": "Tagalogca", + "language/tr": "Türkçe", + "language/uk": "Ukraynaca", + "language/ur": "Urduca", + "language/vi": "Vietnamca", + "language/zh": "Çince (basitleştirilmiş)", + "language/zh-TW": "Çince (geleneksel)", "Latest Messages": "Son Mesajlar", "Let others add options": "Başkalarının seçenek eklemesine izin ver", "Limit votes per person": "Kişi başına oy sınırı", @@ -206,6 +263,7 @@ "Option already exists": "Seçenek zaten mevcut", "Option is empty": "Seçenek boş", "Options": "Seçenekler", + "Original": "Orijinal", "People matching": "Eşleşen kişiler", "Photo": "Fotoğraf", "Pin": "Sabitle", @@ -282,15 +340,17 @@ "Thread reply": "Konu yanıtı", "Thread Reply": "Konu yanıtı", "Threads": "İleti dizileri", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Bugün]\", \"nextDay\": \"[Yarın]\", \"lastDay\": \"[Dün]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Geçen] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", + "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [saat] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "Kayıt yapmaya başlamak için tarayıcınızda kameraya erişime izin verin", "To start recording, allow the microphone access in your browser": "Kayıt yapmaya başlamak için tarayıcınızda mikrofona erişime izin verin", + "Translated": "Çevrildi", + "Translated from {{ language }}": "{{ language }} dilinden çevrildi", "translationBuilderTopic/notification": "{{value, notification}}", "Type a number from 2 to 10": "2 ile 10 arasında bir sayı yazın", "Type your message": "Mesajınızı yazın", @@ -317,7 +377,9 @@ "Video": "Video", "View {{count}} comments_one": "{{count}} yorumu görüntüle", "View {{count}} comments_other": "{{count}} yorumu görüntüle", + "View original": "Orijinali görüntüle", "View results": "Sonuçları görüntüle", + "View translation": "Çeviriyi görüntüle", "Voice message": "Sesli mesaj", "Voice message {{ duration }}": "Sesli mesaj {{ duration }}", "Vote ended": "Oylama sona erdi",